diff --git a/.github/workflows/qmake.yml b/.github/workflows/qmake.yml index 8308be14..2033c644 100644 --- a/.github/workflows/qmake.yml +++ b/.github/workflows/qmake.yml @@ -127,28 +127,28 @@ jobs: build-linux-qt-current: - name: Linux Qt 6.3 (current) build + name: Linux Qt 6.4 (current) build runs-on: ubuntu-latest env: DISPLAY: ":1" steps: - uses: actions/checkout@v2 - name: Cache Qt - id: cache-qt-6-3 + id: cache-qt-6-4 uses: actions/cache@v1 # not v2! with: - path: ../Qt/6.3.0 - key: ${{ runner.os }}-QtCache-Qt6-3 + path: ../Qt/6.4.1 + key: ${{ runner.os }}-QtCache-Qt6-4 - name: Install Qt uses: jurplel/install-qt-action@v2 with: - version: '6.3.0' + version: '6.4.1' host: 'linux' target: 'desktop' install-deps: 'true' modules: 'qtwebengine' - cached: ${{ steps.cache-qt-6-3.outputs.cache-hit }} + cached: ${{ steps.cache-qt-6-4.outputs.cache-hit }} setup-python: 'true' tools: '' set-env: 'true' @@ -174,7 +174,7 @@ jobs: ./examples/bin/gallery & sleep 10 echo taking screenshot - import -pause 1 -window root screenshot-linux-qt6-3.jpg + import -pause 1 -window root screenshot-linux-qt6-4.jpg echo killing gallery killall gallery echo killing Xvfb @@ -182,8 +182,8 @@ jobs: - name: Upload smoke test artifacts uses: actions/upload-artifact@v2 with: - name: screenshot-linux-qt6-3.jpg - path: screenshot-linux-qt6-3.jpg + name: screenshot-linux-qt6-4.jpg + path: screenshot-linux-qt6-4.jpg build-windows-qt5-15: @@ -297,27 +297,27 @@ jobs: build-windows-qt-current: - name: Windows Qt 6.3 (current) build + name: Windows Qt 6.4 (current) build runs-on: windows-latest steps: - uses: actions/checkout@v2 - name: Cache Qt - id: cache-qt-6-3 + id: cache-qt-6-4 uses: actions/cache@v1 # not v2! with: - path: ../Qt/6.3.0 - key: ${{ runner.os }}-QtCache-Qt6-3 + path: ../Qt/6.4.1 + key: ${{ runner.os }}-QtCache-Qt6-4 - name: Install Qt uses: jurplel/install-qt-action@v2 with: - version: '6.3.0' + version: '6.4.1' host: 'windows' target: 'desktop' arch: 'win64_msvc2019_64' install-deps: 'true' modules: 'qtwebengine' - cached: ${{ steps.cache-qt-6-3.outputs.cache-hit }} + cached: ${{ steps.cache-qt-6-4.outputs.cache-hit }} setup-python: 'true' tools: '' set-env: 'true' @@ -346,7 +346,7 @@ jobs: Start-Sleep -s 10 - uses: OrbitalOwen/desktop-screenshot-action@0.1 with: - file-name: 'screenshot-windows-qt6-3.jpg' + file-name: 'screenshot-windows-qt6-4.jpg' - name: Cleanup smoke test run: taskkill /IM gallery.exe /T @@ -443,26 +443,26 @@ jobs: run: killall gallery build-mac-qt-current: - name: MacOS Qt 6.3 (current) build + name: MacOS Qt 6.4 (current) build runs-on: macos-latest steps: - uses: actions/checkout@v2 - name: Cache Qt - id: cache-qt-6-3 + id: cache-qt-6-4 uses: actions/cache@v1 # not v2! with: - path: ../Qt/6.3.0 - key: ${{ runner.os }}-QtCache-Qt6-3 + path: ../Qt/6.4.1 + key: ${{ runner.os }}-QtCache-Qt6-4 - name: Install Qt uses: jurplel/install-qt-action@v2 with: - version: '6.3.0' + version: '6.4.1' host: 'mac' target: 'desktop' install-deps: 'true' modules: 'qtwebengine' - cached: ${{ steps.cache-qt-6-3.outputs.cache-hit }} + cached: ${{ steps.cache-qt-6-4.outputs.cache-hit }} setup-python: 'true' tools: '' set-env: 'true' @@ -483,6 +483,6 @@ jobs: sleep 10 - uses: OrbitalOwen/desktop-screenshot-action@0.1 with: - file-name: 'screenshot-macos-qt6-3.jpg' + file-name: 'screenshot-macos-qt6-4.jpg' - name: Cleanup smoke test run: killall gallery diff --git a/README.md b/README.md index dcdc8065..89e64fb8 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ controls is limited to the needs of the driving projects. QSkinny is supposed to run on all platforms being supported by Qt/Quick. But so far only Linux is actively tested. -It might support all versions Qt >= 5.12, but you can rely on: +It might support all versions Qt >= 5.15, but you can rely on: - Qt 5.15 - current long term supported ( LTS ) version of Qt diff --git a/doc/classes/QskControl.dox b/doc/classes/QskControl.dox index 0a578f59..04c57754 100644 --- a/doc/classes/QskControl.dox +++ b/doc/classes/QskControl.dox @@ -56,18 +56,6 @@ \accessors locale(), setLocale(), resetLocale(), localeChanged() */ -/*! - \property bool QskControl::autoFillBackground - - This property holds whether the background is filled automatically - with the background gradient. - - \accessors autoFillBackground(), setAutoFillBackground() - - \sa background() - \saqt QWidget::autoFillBackground -*/ - /*! \property bool QskControl::autoLayoutChildren @@ -223,11 +211,8 @@ Sets the margins around the contents of the control - The value is stored in the local hint table for the aspect: - QskControl::Control | QskAspect::Metric | QskAspect::Margin - \param margin Margin for all sides - \aspect QskControl::Control | QskAspect::Metric | QskAspect::Margin + \aspect QskControl::Background | QskAspect::Metric | QskAspect::Padding \sa contentsRect(), margins(), QskSkinnable::setMarginHint() */ @@ -235,15 +220,14 @@ /*! \fn QskControl::setMargins( qreal, qreal, qreal, qreal ) - The value is stored in the local hint table for the aspect: - QskControl::Control | QskAspect::Metric | QskAspect::Margin + Sets the margins around the contents of the control \param left Left margin \param top Top margin \param right Right margin \param bottom Bottom margin - \aspect QskControl::Control | QskAspect::Metric | QskAspect::Margin + \aspect QskControl::Background | QskAspect::Metric | QskAspect::Padding \sa contentsRect(), margins(), QskSkinnable::setMarginHint() */ @@ -254,7 +238,7 @@ Sets the margins around the contents of the control \param margins Margins - \aspect QskControl::Control | QskAspect::Metric | QskAspect::Margin + \aspect QskControl::Background | QskAspect::Metric | QskAspect::Padding \sa contentsRect(), margins() \saqt QEvent::ContentsRectChange @@ -265,7 +249,7 @@ Reset the margins to the default value provided from the skin - \aspect QskControl::Control | QskAspect::Metric | QskAspect::Margin + \aspect QskControl::Background | QskAspect::Metric | QskAspect::Padding \sa contentsRect(), setMargins(), margins() \saqt QEvent::ContentsRectChange @@ -275,7 +259,7 @@ \fn QskControl::margins() const \return margins around the contents of the control - \aspect QskControl::Control | QskAspect::Metric | QskAspect::Margin + \aspect QskControl::Background | QskAspect::Metric | QskAspect::Padding \sa contentsRect(), setMargins() \saqt QEvent::ContentsRectChange @@ -284,11 +268,12 @@ /*! \fn QskControl::setBackgroundColor - A conveninece method that enables the \ref autoFillBackground property - and sets a solid color as background. Usually used for debugging - layout problems. + A conveninece method for setting a monochrome gradient as background. Often used + for debugging layout problems. - \sa setBackground(), setAutoFillBackground() + \aspect QskControl::Background | QskAspect::Color + + \sa setBackground() \sa QskQuickItem::DebugForceBackground */ @@ -296,10 +281,9 @@ \fn QskControl::setBackground Set the gradient that is used to fill the background, - when \ref autoFillBackground is enabled. - \aspect QskControl::Control | QskAspect::Color - \sa resetBackground(), background(), autoFillBackground() + \aspect QskControl::Background | QskAspect::Color + \sa resetBackground(), background(), setBackgroundColor */ /*! @@ -307,18 +291,17 @@ Reset the background gradient to the default colors from the skin - \aspect QskControl::Control | QskAspect::Color - \sa setBackground(), background(), autoFillBackground() + \aspect QskControl::Background | QskAspect::Color + \sa setBackground(), setBackgroundColor(), background() */ /*! \fn QskControl::background() const - \return Gradient that is used to fill the background, - when autoFillBackground is enabled. + \return Gradient that is used to fill the background - \aspect QskControl::Control | QskAspect::Color - \sa setBackground(), resetBackground(), autoFillBackground() + \aspect QskControl::Background | QskAspect::Color + \sa setBackground(), setBackgroundColor(), resetBackground() */ /*! @@ -329,6 +312,8 @@ contentsRect() is a rectangle being used for laying out scene graph nodes, while layoutRect() is used for child items. + \aspect QskControl::Background | QskAspect::Metric | QskAspect::Padding + \sa margins(), setMargins(), layoutRect() */ @@ -440,25 +425,11 @@ \sa QskSkinnable::subControlContentsRect */ -/*! - \fn QskControl::setAutoFillBackground - - Set or clear the \ref autoFillBackground property - \sa autoFillBackground() -*/ - -/*! - \fn QskControl::autoFillBackground() const - - \return Value of the \ref autoFillBackground property - \sa setAutoFillBackground() -*/ - /*! \fn QskControl::setAutoLayoutChildren Set or clear the autoLayoutChildren property - \sa autoFillBackground() + \sa autoLayoutChildren() */ /*! diff --git a/examples/boxes/Box.cpp b/examples/boxes/Box.cpp index dbad97ac..bac88653 100644 --- a/examples/boxes/Box.cpp +++ b/examples/boxes/Box.cpp @@ -9,6 +9,20 @@ #include #include #include +#include + +static inline void setStartStop( Box::FillType type, QskGradient& gradient ) +{ + qreal x1 = 0.0, y1 = 0.0, x2 = 0.0, y2 = 0.0; + + if ( type != Box::Horizontal ) + y2 = 1.0; + + if ( type != Box::Vertical ) + x2 = 1.0; + + gradient.setLinearDirection( x1, y1, x2, y2 ); +} Box::Box( QQuickItem* parentItem ) : QskBox( parentItem ) @@ -19,14 +33,14 @@ Box::Box( QQuickItem* parentItem ) setBoxShapeHint( QskBox::Panel, QskBoxShapeMetrics() ); setBoxBorderMetricsHint( QskBox::Panel, QskBoxBorderMetrics() ); setBoxBorderColorsHint( QskBox::Panel, QskBoxBorderColors() ); - setGradientHint( QskBox::Panel, QskGradient() ); + setPanelGradient( QskGradient() ); } -void Box::setBackground( FillType type, QGradient::Preset preset, bool inverted ) +void Box::setWebGradient( FillType type, QGradient::Preset preset, bool inverted ) { if ( type == Unfilled ) { - setGradient( QskGradient() ); + setPanelGradient( QskGradient() ); return; } @@ -34,54 +48,73 @@ void Box::setBackground( FillType type, QGradient::Preset preset, bool inverted if ( type == Solid ) { - const auto& stops = gradient.stops(); + const auto color = QskRgb::interpolated( + gradient.startColor(), gradient.endColor(), 0.5 ); - const auto color = QskGradientStop::interpolated( - stops.first(), stops.last(), 0.5 ); - - setGradient( QskGradient( color ) ); + gradient.setStops( color ); } - else - { - const auto orientation = - static_cast< QskGradient::Orientation >( type - 2 ); - gradient.setOrientation( orientation ); + setStartStop( type, gradient ); - if ( inverted ) - gradient.reverse(); + if ( inverted ) + gradient.reverse(); - setGradient( gradient ); - } + setPanelGradient( gradient ); } -void Box::setBackground( FillType type, const QRgb base, bool inverted ) +void Box::setTonalGradient( FillType type, const QRgb base, bool inverted ) { if ( type == Unfilled ) { - setGradient( QskGradient() ); + setPanelGradient( QskGradient() ); return; } - const QskHctColor htcColor( base ); + QskGradient gradient; + const QskHctColor htcColor( base ); if ( type == Solid ) { - setGradient( htcColor.toned( 50 ).rgb() ); + gradient.setStops( htcColor.toned( 50 ).rgb() ); } - else + else if ( type != Unfilled ) { - const auto dark = htcColor.toned( 40 ).rgb(); - const auto light = htcColor.toned( 70 ).rgb(); - - const auto orientation = - static_cast< QskGradient::Orientation >( type - 2 ); - - if ( inverted ) - setGradient( orientation, dark, light ); - else - setGradient( orientation, light, dark ); + gradient.setStops( htcColor.toned( 70 ).rgb(), + htcColor.toned( 40 ).rgb() ); } + + setStartStop( type, gradient ); + + if ( inverted ) + gradient.reverse(); + + setPanelGradient( gradient ); +} + +void Box::setTonalPalette( FillType type, const QRgb base ) +{ + if ( type == Unfilled || type == Solid ) + { + setTonalGradient( type, base ); + return; + } + + QskGradient gradient; + setStartStop( type, gradient ); + + { + const QskHctColor hctColor( base ); + + QVector< QRgb > colors; + colors.reserve( 10 ); + + for ( int i = 0; i < 10; i++ ) + colors += hctColor.toned( 90 - i * 7 ).rgb(); + + gradient.setStops( qskBuildGradientStops( colors, true ) ); + } + + setPanelGradient( gradient ); } void Box::setBorder( BorderType type, const QRgb base ) @@ -166,26 +199,30 @@ void Box::setBorderWidth( int width ) void Box::setGradient( QRgb rgb ) { - setGradient( QskGradient( QColor::fromRgba( rgb ) ) ); + setGradient( QColor::fromRgba( rgb ) ); } void Box::setGradient( Qt::GlobalColor color ) { - setGradient( QskGradient( color ) ); + setGradient( QColor( color ) ); } void Box::setGradient( const QColor& color ) { - setGradient( QskGradient( color ) ); + setPanelGradient( QskGradient( color ) ); } -void Box::setGradient( QskGradient::Orientation orientation, +void Box::setGradient( FillType fillType, const QColor& color1, const QColor& color2 ) { - setGradient( QskGradient( orientation, color1, color2 ) ); + QskGradientStops stops; + stops += QskGradientStop( 0.0, color1 ); + stops += QskGradientStop( 1.0, color2 ); + + setGradient( fillType, stops ); } -void Box::setGradient( QskGradient::Orientation orientation, +void Box::setGradient( FillType fillType, const QColor& color1, const QColor& color2, const QColor& color3 ) { QskGradientStops stops; @@ -193,24 +230,31 @@ void Box::setGradient( QskGradient::Orientation orientation, stops += QskGradientStop( 0.5, color2 ); stops += QskGradientStop( 1.0, color3 ); - setGradient( QskGradient( orientation, stops ) ); + setGradient( fillType, stops ); } -void Box::setGradient( const QskGradient& gradient ) +void Box::setGradient( FillType fillType, const QskGradientStops& stops ) +{ + QskGradient gradient; + + if ( fillType == Solid ) + { + const auto color = QskRgb::interpolated( + stops.first().rgb(), stops.last().rgb(), 0.5 ); + + gradient.setStops( color ); + } + else if ( fillType != Unfilled ) + { + gradient.setStops( stops ); + } + + setStartStop( fillType, gradient ); + + setPanelGradient( gradient ); +} + +void Box::setPanelGradient( const QskGradient& gradient ) { setGradientHint( QskBox::Panel, gradient ); } - -void Box::setGradient( - const QskGradient::Orientation orientation, const QRgb base ) -{ - const QskHctColor hctColor( base ); - - QVector< QRgb > rgb; - rgb.reserve( 10 ); - - for ( int i = 0; i < 10; i++ ) - rgb += hctColor.toned( 90 - i * 7 ).rgb(); - - setGradient( QskGradient( orientation, QskGradient::colorStops( rgb, true ) ) ); -} diff --git a/examples/boxes/Box.h b/examples/boxes/Box.h index de2c4205..d45d9d12 100644 --- a/examples/boxes/Box.h +++ b/examples/boxes/Box.h @@ -31,9 +31,6 @@ class Box : public QskBox Box( QQuickItem* parentItem = nullptr ); - void setBackground( FillType, QRgb, bool inverted = false ); - void setBackground( FillType, QGradient::Preset, bool inverted = false ); - void setBorder( BorderType type, QRgb ); void setShape( const QskBoxShapeMetrics& ); @@ -51,11 +48,15 @@ class Box : public QskBox void setGradient( Qt::GlobalColor ); void setGradient( const QColor& ); - void setGradient( QskGradient::Orientation, const QColor&, const QColor& ); + void setGradient( FillType, const QColor&, const QColor& ); + void setGradient( FillType, const QColor&, const QColor&, const QColor& ); + void setGradient( FillType, const QskGradientStops& ); - void setGradient( QskGradient::Orientation, - const QColor&, const QColor&, const QColor& ); + void setTonalGradient( FillType, QRgb, bool inverted = false ); + void setWebGradient( FillType, QGradient::Preset, bool inverted = false ); - void setGradient( const QskGradient& ); - void setGradient( const QskGradient::Orientation, QRgb ); + void setTonalPalette( FillType, QRgb ); + + private: + void setPanelGradient( const QskGradient& ); }; diff --git a/examples/boxes/main.cpp b/examples/boxes/main.cpp index 8641e48d..6adbb7c1 100644 --- a/examples/boxes/main.cpp +++ b/examples/boxes/main.cpp @@ -14,13 +14,14 @@ #include #include #include -#include #include #include #include +#define TONAL_GRADIENTS 1 + namespace { // Some leftover definitions from M(aterial)2. TODO ... @@ -71,11 +72,17 @@ static void addTestRectangle( QskLinearBox* parent ) box->setBorderWidth( 10, 20, 40, 20 ); QskBoxShapeMetrics shape( 50, Qt::RelativeSize ); + shape.setScalingMode( QskBoxShapeMetrics::Elliptic ); shape.setRadius( Qt::BottomRightCorner, 30 ); shape.setRadius( Qt::TopRightCorner, 70 ); box->setShape( shape ); - box->setGradient( QskGradient::Diagonal, QskRgb::DodgerBlue ); + +#if TONAL_GRADIENTS + box->setTonalPalette( Box::Diagonal, QskRgb::DodgerBlue ); +#else + box->setGradient( Box::Diagonal, QskRgb::DodgerBlue, QskRgb::DarkSlateBlue ); +#endif } static void addRectangles1( QskLinearBox* parent ) @@ -84,7 +91,11 @@ static void addRectangles1( QskLinearBox* parent ) Box::Horizontal, Box::Vertical, Box::Diagonal } ) { auto* rectangle = new MyRectangle( parent ); - rectangle->setBackground( type, QskRgb::Teal ); +#if TONAL_GRADIENTS + rectangle->setTonalGradient( type, QskRgb::Teal ); +#else + rectangle->setGradient( type, Qt::red, Qt::blue ); +#endif } } @@ -95,7 +106,11 @@ static void addRectangles2( QskLinearBox* parent ) { auto* rectangle = new MyRectangle( parent ); rectangle->setBorder( Box::Flat, QskRgb::SaddleBrown ); - rectangle->setBackground( type, QGradient::SunnyMorning ); +#if TONAL_GRADIENTS + rectangle->setWebGradient( type, QGradient::SunnyMorning ); +#else + rectangle->setGradient( type, Qt::red, Qt::blue ); +#endif } } @@ -113,19 +128,19 @@ static void addRectangles3( QskLinearBox* parent ) box = new MyRectangle( parent ); box->setBorder( Box::Sunken1, borderTheme ); - box->setGradient( QskGradient::Diagonal, Grey400, Grey500 ); + box->setGradient( Box::Diagonal, Grey400, Grey500 ); box = new MyRectangle( parent ); box->setBorder( Box::Raised2, borderTheme ); - box->setGradient( QskGradient::Vertical, Grey400, Grey500 ); + box->setGradient( Box::Vertical, Grey400, Grey500 ); box = new MyRectangle( parent ); box->setBorder( Box::Raised2, borderTheme ); - box->setBackground( Box::Vertical, QGradient::RiverCity, true ); + box->setWebGradient( Box::Vertical, QGradient::RiverCity, true ); box = new MyRectangle( parent ); box->setBorder( Box::Sunken2, borderTheme ); - box->setBackground( Box::Vertical, QGradient::RiverCity, false ); + box->setWebGradient( Box::Vertical, QGradient::RiverCity, false ); } static void addRectangles4( QskLinearBox* parent ) @@ -134,7 +149,11 @@ static void addRectangles4( QskLinearBox* parent ) Box::Horizontal, Box::Vertical, Box::Diagonal } ) { auto* box = new MyRoundedRectangle( parent ); - box->setBackground( type, QskRgb::OrangeRed ); +#if TONAL_GRADIENTS + box->setTonalGradient( type, QskRgb::OrangeRed ); +#else + box->setGradient( type, Qt::red, Qt::blue ); +#endif } } @@ -145,7 +164,11 @@ static void addRectangles5( QskLinearBox* parent ) { auto* box = new MyRoundedRectangle( parent ); box->setBorder( Box::Flat, QskRgb::RoyalBlue ); - box->setBackground( type, QskRgb::DeepPink ); +#if TONAL_GRADIENTS + box->setTonalGradient( type, QskRgb::DeepPink ); +#else + box->setGradient( type, Qt::red, Qt::blue ); +#endif } } @@ -163,19 +186,19 @@ static void addRectangles6( QskLinearBox* parent ) box = new MyRoundedRectangle( parent ); box->setBorder( Box::Sunken1, borderTheme ); - box->setGradient( QskGradient::Diagonal, Grey400, Grey500 ); + box->setGradient( Box::Diagonal, Grey400, Grey500 ); box = new MyRoundedRectangle( parent ); box->setBorder( Box::Raised2, borderTheme ); - box->setGradient( QskGradient::Vertical, Grey400, Grey500 ); + box->setGradient( Box::Vertical, Grey400, Grey500 ); box = new MyRoundedRectangle( parent ); box->setBorder( Box::Raised2, borderTheme ); - box->setGradient( QskGradient::Vertical, Lime300, Lime600 ); + box->setGradient( Box::Vertical, Lime300, Lime600 ); box = new MyRoundedRectangle( parent ); box->setBorder( Box::Sunken2, borderTheme ); - box->setGradient( QskGradient::Vertical, Lime600, Lime300 ); + box->setGradient( Box::Vertical, Lime600, Lime300 ); } static void addRectangles7( QskLinearBox* parent ) @@ -184,7 +207,7 @@ static void addRectangles7( QskLinearBox* parent ) Box::Horizontal, Box::Vertical, Box::Diagonal } ) { auto* box = new MyEllipse( parent ); - box->setBackground( type, QskRgb::SlateGrey ); + box->setTonalGradient( type, QskRgb::SlateGrey ); } } @@ -195,7 +218,7 @@ static void addRectangles8( QskLinearBox* parent ) { auto* box = new MyEllipse( parent ); box->setBorder( Box::Flat, QskRgb::RoyalBlue ); - box->setBackground( type, QskRgb::FireBrick ); + box->setTonalGradient( type, QskRgb::FireBrick ); } } @@ -213,53 +236,60 @@ static void addRectangles9( QskLinearBox* parent ) box = new MyEllipse( parent ); box->setBorder( Box::Sunken1, borderTheme ); - box->setGradient( QskGradient::Diagonal, Grey400, Grey500 ); + box->setGradient( Box::Diagonal, Grey400, Grey500 ); box = new MyEllipse( parent ); box->setBorder( Box::Raised2, borderTheme ); - box->setGradient( QskGradient::Vertical, Grey400, Grey500 ); + box->setGradient( Box::Vertical, Grey400, Grey500 ); box = new MyEllipse( parent ); box->setBorder( Box::Raised2, borderTheme ); - box->setGradient( QskGradient::Vertical, Lime200, Lime600 ); + box->setGradient( Box::Vertical, Lime200, Lime600 ); box = new MyEllipse( parent ); box->setBorder( Box::Sunken2, borderTheme ); - box->setGradient( QskGradient::Vertical, Lime600, Lime200 ); + box->setGradient( Box::Vertical, Lime600, Lime200 ); } static void addRectangles10( QskLinearBox* parent ) { - QColor borderTheme( "Indigo" ); + using namespace QskRgb; + + QColor borderTheme( Indigo ); // borderTheme.setAlpha( 100 ); + QskGradientStops stops; + stops += QskGradientStop( 0.0, DeepPink ); + stops += QskGradientStop( 0.5, DarkOrange ); + stops += QskGradientStop( 1.0, HotPink ); + Box* box; box = new Box( parent ); - box->setGradient( QskGradient::Horizontal, "DeepPink", "DarkOrange", "HotPink" ); + box->setGradient( Box::Horizontal, stops ); box = new Box( parent ); box->setBorderWidth( 10 ); box->setBorderGradient( borderTheme ); - box->setGradient( QskGradient::Diagonal, "DeepPink", "DarkOrange", "HotPink" ); + box->setGradient( Box::Diagonal, stops ); box = new Box( parent ); box->setShape( 100, Qt::RelativeSize ); box->setBorderWidth( 5 ); box->setBorderGradient( borderTheme ); - box->setGradient( QskGradient::Vertical, "DeepPink", "DarkOrange", "HotPink" ); + box->setGradient( Box::Vertical, stops ); box = new Box( parent ); box->setShape( 100, Qt::RelativeSize ); box->setBorderWidth( 5 ); box->setBorderGradient( borderTheme ); - box->setGradient( QskGradient::Diagonal, "DeepPink", "DarkOrange", "HotPink" ); + box->setGradient( Box::Diagonal, stops ); box = new Box( parent ); box->setShape( 100, Qt::RelativeSize ); box->setBorderWidth( 5, 20, 30, 5 ); box->setBorderGradient( borderTheme ); - box->setGradient( QskGradient::Vertical, "DeepPink", "DarkOrange", "HotPink" ); + box->setGradient( Box::Vertical, stops ); } static void addRectangles11( QskLinearBox* parent ) @@ -279,111 +309,110 @@ static void addRectangles11( QskLinearBox* parent ) bw[ i - 1 ] = 0; box->setBorderWidth( bw[ 0 ], bw[ 1 ], bw[ 2 ], bw[ 3 ] ); - box->setBackground( fillType[ i ], QskRgb::Sienna, i >= 3 ); + box->setTonalGradient( fillType[ i ], QskRgb::Sienna, i >= 3 ); } } static void addRectangles12( QskLinearBox* parent ) { - for ( auto orientation : { QskGradient::Vertical, - QskGradient::Horizontal, QskGradient::Diagonal } ) + for ( auto fillType : { Box::Vertical, Box::Horizontal, Box::Diagonal } ) { auto* box = new Box( parent ); box->setBorderWidth( 0 ); - box->setGradient( orientation, QskRgb::LightSlateGray ); + box->setTonalPalette( fillType, QskRgb::LightSlateGray ); } - for ( auto orientation : { QskGradient::Vertical, QskGradient::Diagonal } ) + for ( auto fillType : { Box::Vertical, Box::Diagonal } ) { auto* box = new Box( parent ); box->setBorder( Box::Flat, QskRgb::OrangeRed ); - box->setGradient( orientation, QskRgb::DodgerBlue ); + box->setTonalPalette( fillType, QskRgb::DodgerBlue ); } - for ( auto orientation : { QskGradient::Vertical, - QskGradient::Horizontal, QskGradient::Diagonal } ) + for ( auto fillType : { Box::Vertical, Box::Horizontal, Box::Diagonal } ) { auto* box = new Box( parent ); box->setBorderWidth( 0 ); box->setShape( 30, 40, Qt::RelativeSize ); - box->setGradient( orientation, QskRgb::LightSlateGray ); + box->setTonalPalette( fillType, QskRgb::LightSlateGray ); } - for ( auto orientation : { QskGradient::Vertical, QskGradient::Diagonal } ) + for ( auto fillType : { Box::Vertical, Box::Diagonal } ) { auto* box = new Box( parent ); box->setBorder( Box::Flat, QskRgb::OrangeRed ); box->setShape( 30, 40, Qt::RelativeSize ); - box->setGradient( orientation, QskRgb::DodgerBlue ); + box->setTonalPalette( fillType, QskRgb::DodgerBlue ); } - for ( auto orientation : { QskGradient::Vertical, - QskGradient::Horizontal, QskGradient::Diagonal } ) + for ( auto fillType : { Box::Vertical, Box::Horizontal, Box::Diagonal } ) { auto* box = new Box( parent ); box->setBorderWidth( 0 ); box->setShape( 100, 100, Qt::RelativeSize ); - box->setGradient( orientation, QskRgb::LightSlateGray ); + box->setTonalPalette( fillType, QskRgb::LightSlateGray ); } - for ( auto orientation : { QskGradient::Vertical, QskGradient::Diagonal } ) + for ( auto fillType : { Box::Vertical, Box::Diagonal } ) { auto* box = new Box( parent ); box->setBorder( Box::Flat, QskRgb::OrangeRed ); box->setShape( 100, 100, Qt::RelativeSize ); - box->setGradient( orientation, QskRgb::DodgerBlue ); + box->setTonalPalette( fillType, QskRgb::DodgerBlue ); } } static void addRectanglesRest( QskLinearBox* parent ) { + using namespace QskRgb; + Box* box; box = new Box( parent ); box->setBorderWidth( 20, 0, 40, 0 ); - box->setBorderGradient( { "DarkSeaGreen" } ); + box->setBorderGradient( DarkSeaGreen ); box = new Box( parent ); box->setShape( 40, Qt::RelativeSize ); box->setBorderWidth( 20, 10, 30, 15 ); - box->setBorderGradient( { "DarkOrange" } ); - box->setGradient( QskGradient::Vertical, "LightSteelBlue", "SteelBlue" ); + box->setBorderGradient( DarkOrange ); + box->setGradient( Box::Vertical, LightSteelBlue, SteelBlue ); box = new Box( parent ); box->setBorderWidth( 20, 0, 10, 20 ); - box->setBorderGradient( { "MediumSeaGreen" } ); - box->setGradient( "DodgerBlue" ); + box->setBorderGradient( MediumSeaGreen ); + box->setGradient( DodgerBlue ); box = new Box( parent ); box->setShape( 20, Qt::AbsoluteSize ); box->setBorderWidth( 2, 10, 40, 2 ); - box->setBorderGradient( { "Crimson" } ); + box->setBorderGradient( Crimson ); box->setGradient( QskRgb::WhiteSmoke ); box = new Box( parent ); box->setShape( 100, Qt::RelativeSize ); box->setBorderWidth( 5, 20, 5, 0 ); - box->setBorderGradient( { "CadetBlue" } ); - box->setGradient( QskGradient::Vertical, "Gainsboro", "Seashell", "LightGray" ); + box->setBorderGradient( { CadetBlue } ); + box->setGradient( Box::Vertical, Gainsboro, Seashell, LightGray ); } -static void addColoredBorderRectangles1( QskLinearBox* parent, bool rounded, Box::FillType fillType ) +static void addColoredBorderRectangles1( QskLinearBox* parent, + bool rounded, Box::FillType fillType ) { auto* box = new Box( parent ); box->setBorderWidth( 20 ); - QskGradient gradient1( Qt::Vertical, { { 0.0, Qt::blue }, - { 0.9, Qt::yellow }, - { 1.0, Qt::darkRed } } ); - QskGradient gradient2( Qt::Vertical, { { 0.0, Qt::black }, - { 0.3, Qt::white }, - { 0.7, Qt::white }, - { 1.0, Qt::black } } ); - QskGradient gradient3( Qt::green ); - QskGradient gradient4( Qt::Vertical, Qt::magenta, Qt::cyan ); - box->setBorderGradients( gradient1, gradient2, gradient3, gradient4 ); + + QskGradientStops stops[4]; + + stops[0] = { { 0.0, Qt::blue }, { 0.9, Qt::yellow }, { 1.0, Qt::darkRed } }; + stops[1] = { { 0.0, Qt::black }, { 0.3, Qt::white }, { 0.7, Qt::white }, { 1.0, Qt::black } }; + stops[2] = { { 0.0, Qt::green }, { 1.0, Qt::green } }; + stops[3] = { { 0.0, Qt::magenta }, { 1.0, Qt::cyan } }; + + box->setBorderGradients( stops[0], stops[1], stops[2], stops[3] ); if( fillType != Box::Unfilled ) - box->setBackground( fillType, QskRgb::CornflowerBlue ); + box->setTonalGradient( fillType, QskRgb::CornflowerBlue ); if( rounded ) box->setShape( 30, Qt::AbsoluteSize ); @@ -396,7 +425,7 @@ static void addColoredBorderRectangles2( QskLinearBox* parent, bool rounded, Box box->setBorderGradients( Qt::red, Qt::green, Qt::blue, Qt::yellow ); if( fillType != Box::Unfilled ) - box->setBackground( fillType, QskRgb::CornflowerBlue ); + box->setTonalGradient( fillType, QskRgb::CornflowerBlue ); if( rounded ) box->setShape( 30, Qt::AbsoluteSize ); @@ -406,26 +435,37 @@ static void addColoredBorderRectangles3( QskLinearBox* parent, bool rounded, Box { Box* box = new Box( parent ); box->setBorderWidth( 20 ); - QskGradient gradient1( Qt::Vertical, { { 0.0, Qt::yellow }, - { 0.2, Qt::gray }, - { 0.6, Qt::magenta }, - { 1.0, Qt::green } } ); - QskGradient gradient2( Qt::Vertical, { { 0.0, Qt::darkYellow }, - { 0.2, Qt::cyan }, - { 1.0, Qt::darkMagenta } } ); - QskGradient gradient3( Qt::Vertical, { { 0.0, Qt::red }, - { 0.25, Qt::green }, - { 0.5, Qt::blue }, - { 0.75, Qt::magenta }, - { 1.0, Qt::cyan } } ); - QskGradient gradient4( Qt::Vertical, { { 0.0, Qt::red }, - { 0.3, Qt::green }, - { 0.7, Qt::blue }, - { 1.0, Qt::cyan } } ); - box->setBorderGradients( gradient3, gradient3, gradient3, gradient3 ); + + QskGradientStops stops[4]; + + stops[0] = { + { 0.3, Qt::yellow }, + { 0.3, Qt::gray }, { 0.7, Qt::gray }, + { 0.7, Qt::green } + }; + + stops[1] = { + { 0.3, Qt::darkYellow }, + { 0.3, Qt::cyan }, { 0.7, Qt::cyan }, + { 0.7, Qt::darkMagenta } + }; + + stops[2] = { + { 0.3, Qt::red }, + { 0.3, Qt::blue }, { 0.7, Qt::blue }, + { 0.7, Qt::darkMagenta } + }; + + stops[3] = { + { 0.3, Qt::darkYellow }, + { 0.3, Qt::darkRed }, { 0.7, Qt::darkRed }, + { 0.7, Qt::darkBlue } + }; + + box->setBorderGradients( stops[0], stops[1], stops[2], stops[3] ); if( fillType != Box::Unfilled ) - box->setBackground( fillType, QskRgb::CornflowerBlue ); + box->setTonalGradient( fillType, QskRgb::CornflowerBlue ); if( rounded ) box->setShape( 30, Qt::AbsoluteSize ); @@ -435,11 +475,11 @@ static void addColoredBorderRectangles4( QskLinearBox* parent, bool rounded, Box { Box* box = new Box( parent ); box->setBorderWidth( 20 ); - QskGradient gradient( Qt::Vertical, Qt::magenta, Qt::cyan ); - box->setBorderGradients( gradient, gradient, gradient, gradient ); + + box->setBorderGradient( QskGradient( Qt::magenta, Qt::cyan ) ); if( fillType != Box::Unfilled ) - box->setBackground( fillType, QskRgb::CornflowerBlue ); + box->setTonalGradient( fillType, QskRgb::CornflowerBlue ); if( rounded ) box->setShape( 30, Qt::AbsoluteSize ); @@ -449,14 +489,14 @@ static void addColoredBorderRectangles5( QskLinearBox* parent, bool rounded, Box { Box* box = new Box( parent ); box->setBorderWidth( 20 ); - QskGradient gradient( Qt::Vertical, { { 0.0, Qt::black }, - { 0.3, Qt::white }, - { 0.7, Qt::white }, - { 1.0, Qt::black } } ); - box->setBorderGradients( gradient, gradient, gradient, gradient ); + + const QskGradientStops stops = { { 0.0, Qt::black }, { 0.3, Qt::white }, + { 0.7, Qt::white }, { 1.0, Qt::black } }; + + box->setBorderGradient( stops ); if( fillType != Box::Unfilled ) - box->setBackground( fillType, QskRgb::CornflowerBlue ); + box->setTonalGradient( fillType, QskRgb::CornflowerBlue ); if( rounded ) box->setShape( { 10, 20, 20, 40 } ); diff --git a/examples/buttons/TestButton.qml b/examples/buttons/TestButton.qml index 33e083cb..e2489044 100644 --- a/examples/buttons/TestButton.qml +++ b/examples/buttons/TestButton.qml @@ -6,8 +6,8 @@ Qsk.PushButton sizePolicy { // avoid the effect of long texts - horizontalPolicy: Qsk.SizePolicy.Ignored - verticalPolicy: Qsk.SizePolicy.Ignored + horizontal: Qsk.SizePolicy.Ignored + vertical: Qsk.SizePolicy.Ignored } minimumSize diff --git a/examples/desktop/main.cpp b/examples/desktop/main.cpp index bec6c8c6..fc50506f 100644 --- a/examples/desktop/main.cpp +++ b/examples/desktop/main.cpp @@ -7,13 +7,13 @@ #include #include -#include #include #include #include #include #include #include +#include #include #include @@ -57,9 +57,11 @@ int main( int argc, char* argv[] ) SkinnyShortcut::enable( SkinnyShortcut::AllShortcuts ); + QskGradient gradient( QskRgb::DarkSlateGray, QskRgb::LightSlateGray ); + gradient.setLinearDirection( 0.0, 0.0, 1.0, 1.0 ); + QskSubWindowArea* area = new QskSubWindowArea(); - area->setGradientHint( QskSubWindowArea::Panel, - QskGradient( QskGradient::Diagonal, "DarkSlateGray", "LightSlateGray" ) ); + area->setGradientHint( QskSubWindowArea::Panel, gradient ); QRectF windowRect( 0, 0, 250, 250 ); diff --git a/examples/frames/Frame.cpp b/examples/frames/Frame.cpp index e859070b..31ccd440 100644 --- a/examples/frames/Frame.cpp +++ b/examples/frames/Frame.cpp @@ -7,9 +7,8 @@ #include #include -#include +#include #include -#include #include static inline qreal effectiveRadius( const QRectF& rect, qreal percentage ) @@ -99,7 +98,7 @@ void Frame::updateNode( QSGNode* parentNode ) const quint8 nodeRole = 0; - auto node = static_cast< QskBoxNode* >( + auto node = static_cast< QskBoxRectangleNode* >( QskSGNode::findChildNode( parentNode, nodeRole ) ); const QRectF rect = contentsRect(); @@ -111,7 +110,7 @@ void Frame::updateNode( QSGNode* parentNode ) if ( node == nullptr ) { - node = new QskBoxNode; + node = new QskBoxRectangleNode; QskSGNode::setNodeRole( node, nodeRole ); } @@ -121,7 +120,7 @@ void Frame::updateNode( QSGNode* parentNode ) parentNode->appendChildNode( node ); } -void Frame::updateFrameNode( const QRectF& rect, QskBoxNode* node ) +void Frame::updateFrameNode( const QRectF& rect, QskBoxRectangleNode* node ) { const QColor dark = m_color.darker( 150 ); const QColor light = m_color.lighter( 150 ); @@ -151,7 +150,7 @@ void Frame::updateFrameNode( const QRectF& rect, QskBoxNode* node ) const QskBoxBorderColors borderColors( c1, c1, c2, c2 ); const qreal radius = effectiveRadius( rect, m_radius ); - node->setBoxData( rect, radius, m_frameWidth, borderColors, m_color ); + node->updateNode( rect, radius, m_frameWidth, borderColors, m_color ); } #include "moc_Frame.cpp" diff --git a/examples/frames/Frame.h b/examples/frames/Frame.h index 55c2f687..b8ed2f5f 100644 --- a/examples/frames/Frame.h +++ b/examples/frames/Frame.h @@ -7,7 +7,7 @@ #include "QskControl.h" -class QskBoxNode; +class QskBoxRectangleNode; class Frame : public QskControl { @@ -62,7 +62,7 @@ class Frame : public QskControl void updateNode( QSGNode* ) override; private: - void updateFrameNode( const QRectF&, QskBoxNode* ); + void updateFrameNode( const QRectF&, QskBoxRectangleNode* ); Style m_style; QColor m_color; diff --git a/examples/gallery/button/ButtonPage.cpp b/examples/gallery/button/ButtonPage.cpp index abb5727f..540d4f8d 100644 --- a/examples/gallery/button/ButtonPage.cpp +++ b/examples/gallery/button/ButtonPage.cpp @@ -30,7 +30,6 @@ namespace void populate() { const char* texts[] = { "Press Me", "Check Me" }; - const char* graphics[] = { "diamond/khaki", "ellipse/sandybrown" }; for ( int i = 0; i < 6; i++ ) { @@ -42,7 +41,7 @@ namespace if ( i > 1 ) { - auto src = QStringLiteral( "image://shapes/" ) + graphics[ index ]; + auto src = QStringLiteral( "plus" ); button->setGraphicSource( src ); } @@ -84,7 +83,7 @@ namespace { public: CheckButtonBox( QQuickItem* parent = nullptr ) - : QskLinearBox( Qt::Horizontal, parent ) + : QskLinearBox( Qt::Horizontal, 2, parent ) { setSpacing( 40 ); setExtraSpacingAt( Qt::LeftEdge | Qt::RightEdge | Qt::BottomEdge ); @@ -94,6 +93,9 @@ namespace auto button2 = new QskCheckBox( "Options 2", this ); button2->setLayoutMirroring( true ); + + auto button3 = new QskCheckBox( "Error", this ); + button3->setSkinStateFlag( QskCheckBox::Error ); } }; } diff --git a/examples/gallery/gallery.pro b/examples/gallery/gallery.pro index d282c339..cae45a37 100644 --- a/examples/gallery/gallery.pro +++ b/examples/gallery/gallery.pro @@ -48,3 +48,6 @@ HEADERS += \ SOURCES += \ Page.cpp \ main.cpp + +RESOURCES += \ + icons.qrc \ diff --git a/examples/gallery/icons.qrc b/examples/gallery/icons.qrc new file mode 100644 index 00000000..001e16e2 --- /dev/null +++ b/examples/gallery/icons.qrc @@ -0,0 +1,9 @@ + + + icons/qvg/airport_shuttle.qvg + icons/qvg/flight.qvg + icons/qvg/local_pizza.qvg + icons/qvg/plus.qvg + icons/qvg/sports_soccer.qvg + + diff --git a/examples/gallery/icons/airport_shuttle.svg b/examples/gallery/icons/airport_shuttle.svg new file mode 100644 index 00000000..77c8c91c --- /dev/null +++ b/examples/gallery/icons/airport_shuttle.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/gallery/icons/flight.svg b/examples/gallery/icons/flight.svg new file mode 100644 index 00000000..e58b8009 --- /dev/null +++ b/examples/gallery/icons/flight.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/gallery/icons/local_pizza.svg b/examples/gallery/icons/local_pizza.svg new file mode 100644 index 00000000..64f80fc5 --- /dev/null +++ b/examples/gallery/icons/local_pizza.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/gallery/icons/plus.svg b/examples/gallery/icons/plus.svg new file mode 100644 index 00000000..ac5859d3 --- /dev/null +++ b/examples/gallery/icons/plus.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/gallery/icons/qvg/airport_shuttle.qvg b/examples/gallery/icons/qvg/airport_shuttle.qvg new file mode 100644 index 00000000..b52ec830 Binary files /dev/null and b/examples/gallery/icons/qvg/airport_shuttle.qvg differ diff --git a/examples/gallery/icons/qvg/flight.qvg b/examples/gallery/icons/qvg/flight.qvg new file mode 100644 index 00000000..d31dd2de Binary files /dev/null and b/examples/gallery/icons/qvg/flight.qvg differ diff --git a/examples/gallery/icons/qvg/local_pizza.qvg b/examples/gallery/icons/qvg/local_pizza.qvg new file mode 100644 index 00000000..61967c8f Binary files /dev/null and b/examples/gallery/icons/qvg/local_pizza.qvg differ diff --git a/examples/gallery/icons/qvg/plus.qvg b/examples/gallery/icons/qvg/plus.qvg new file mode 100644 index 00000000..6d3d6d42 Binary files /dev/null and b/examples/gallery/icons/qvg/plus.qvg differ diff --git a/examples/gallery/icons/qvg/sports_soccer.qvg b/examples/gallery/icons/qvg/sports_soccer.qvg new file mode 100644 index 00000000..52557da2 Binary files /dev/null and b/examples/gallery/icons/qvg/sports_soccer.qvg differ diff --git a/examples/gallery/icons/sports_soccer.svg b/examples/gallery/icons/sports_soccer.svg new file mode 100644 index 00000000..5aabceb6 --- /dev/null +++ b/examples/gallery/icons/sports_soccer.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/gallery/progressbar/ProgressBarPage.cpp b/examples/gallery/progressbar/ProgressBarPage.cpp index e3e0146b..31917d0f 100644 --- a/examples/gallery/progressbar/ProgressBarPage.cpp +++ b/examples/gallery/progressbar/ProgressBarPage.cpp @@ -29,15 +29,13 @@ namespace { const QskHctColor hctColor( base ); - QVector< QRgb > rgb; - rgb += hctColor.toned( 75 ).rgb(); - rgb += hctColor.toned( 60 ).rgb(); - rgb += hctColor.toned( 45 ).rgb(); - rgb += hctColor.toned( 30 ).rgb(); + QVector< QRgb > colors; + colors += hctColor.toned( 75 ).rgb(); + colors += hctColor.toned( 60 ).rgb(); + colors += hctColor.toned( 45 ).rgb(); + colors += hctColor.toned( 30 ).rgb(); - const auto stops = QskGradient::colorStops( rgb, true ); - - setBarGradient( QskGradient( orientation(), stops ) ); + setBarGradient( qskBuildGradientStops( colors, true ) ); } }; } diff --git a/examples/gallery/selector/SelectorPage.cpp b/examples/gallery/selector/SelectorPage.cpp index 483949a8..81e90e85 100644 --- a/examples/gallery/selector/SelectorPage.cpp +++ b/examples/gallery/selector/SelectorPage.cpp @@ -19,31 +19,33 @@ namespace orientation = ( orientation == Qt::Horizontal ) ? Qt::Vertical : Qt::Horizontal; + const char* texts[] = + { + "airport", + "flight", + "pizza", + "soccer" + }; + { auto bar = new QskSegmentedBar( orientation, this ); - bar->addText( "Option 1" ); - bar->addText( "Option 2" ); - bar->addText( "Option 3" ); - bar->addText( "Option 4" ); + for ( const auto text: texts ) + bar->addOption( {}, text ); } { - const auto prefix = QStringLiteral( "image://shapes/" ); - const char* icons[] = { - "rectangle/crimson", - "triangleright/thistle", - "ellipse/khaki", - "ring/sandybrown", - "star/darkviolet", - "hexagon/darkslategray" + "airport_shuttle", + "flight", + "local_pizza", + "sports_soccer" }; auto bar = new QskSegmentedBar( orientation, this ); - for ( const auto icon : icons ) - bar->addGraphic( prefix + icon ); + for ( uint i = 0; i < sizeof( icons ) / sizeof( icons[ 0 ] ); ++i ) + bar->addOption( QUrl( QString( icons[ i ] ) ), texts[ i ] ); } setExtraSpacingAt( Qt::LeftEdge | Qt::BottomEdge ); diff --git a/examples/glabels/glabels.qml b/examples/glabels/glabels.qml index dacda2bc..096a4003 100644 --- a/examples/glabels/glabels.qml +++ b/examples/glabels/glabels.qml @@ -34,7 +34,7 @@ Qsk.Window { source: modelData - sourceSize.width: 100 // width according to aspect ratio + graphicStrutSize.width: 100 // height: according to aspect ratio fillMode: Qsk.GraphicLabel.PreserveAspectFit alignment: Qt.AlignCenter //mirror: true diff --git a/examples/iotdashboard/BoxWithButtons.cpp b/examples/iotdashboard/BoxWithButtons.cpp index 8b5fc5b7..cfed81ba 100644 --- a/examples/iotdashboard/BoxWithButtons.cpp +++ b/examples/iotdashboard/BoxWithButtons.cpp @@ -19,6 +19,8 @@ namespace { class UpAndDownBox : public QskLinearBox { + Q_OBJECT + public: UpAndDownBox( QQuickItem* parent ) : QskLinearBox( Qt::Vertical, parent ) @@ -26,15 +28,25 @@ namespace setSizePolicy( Qt::Horizontal, QskSizePolicy::Fixed ); setSpacing( 0 ); - new RoundButton( QskAspect::Top, this ); - new RoundButton( QskAspect::Bottom, this ); + auto* const topButton = new RoundButton( QskAspect::Top, this ); + connect( topButton, &QskPushButton::clicked, this, &UpAndDownBox::increase ); + + auto* const bottomButton = new RoundButton( QskAspect::Bottom, this ); + connect( bottomButton, &QskPushButton::clicked, this, &UpAndDownBox::decrease ); } + + Q_SIGNALS: + void increase(); + void decrease(); }; } -BoxWithButtons::BoxWithButtons( const QString& title, const QString& value, - bool isBright, QQuickItem* parent ) +BoxWithButtons::BoxWithButtons( const QString& title, const QString &prefix, + const int initialValue, const QString &suffix, + bool isBright, QQuickItem* parent ) : Box( QString(), parent ) + , m_prefix( prefix ) + , m_suffix( suffix ) { setSubcontrolProxy( QskBox::Panel, Panel ); @@ -44,7 +56,8 @@ BoxWithButtons::BoxWithButtons( const QString& title, const QString& value, layout->setSpacing( 20 ); auto iconLabel = new RoundedIcon( isBright, layout ); - iconLabel->setSource( title ); + iconLabel->setGraphicSource( title ); + iconLabel->setGraphicStrutSize( { 35.17, 35.17 } ); iconLabel->setFixedSize( 68, 68 ); auto titleAndValue = new QskLinearBox( Qt::Vertical, layout ); @@ -54,10 +67,30 @@ BoxWithButtons::BoxWithButtons( const QString& title, const QString& value, auto* titleLabel = new QskTextLabel( title, titleAndValue ); titleLabel->setFontRole( Skin::TitleFont ); - auto valueLabel = new QskTextLabel( value, titleAndValue ); - valueLabel->setSubcontrolProxy( QskTextLabel::Text, ValueText ); + m_valueLabel = new QskTextLabel( titleAndValue ); + m_valueLabel->setSubcontrolProxy( QskTextLabel::Text, ValueText ); + setValue( initialValue ); layout->addStretch( 1 ); - new UpAndDownBox( layout ); + auto* const upAndDownBox = new UpAndDownBox( layout ); + + connect( upAndDownBox, &UpAndDownBox::increase, this, [this]() + { + setValue( m_value + 1 ); + } ); + + connect( upAndDownBox, &UpAndDownBox::decrease, this, [this]() + { + setValue( m_value - 1 ); + } ); } + +void BoxWithButtons::setValue( const int value ) +{ + m_value = qBound( 0, value, 100 ); + const QString text = m_prefix + QString::number( m_value ) + m_suffix; + m_valueLabel->setText( text ); +} + +#include "BoxWithButtons.moc" diff --git a/examples/iotdashboard/BoxWithButtons.h b/examples/iotdashboard/BoxWithButtons.h index 03bba159..247be00f 100644 --- a/examples/iotdashboard/BoxWithButtons.h +++ b/examples/iotdashboard/BoxWithButtons.h @@ -7,11 +7,22 @@ #include "Box.h" +class QskTextLabel; + class BoxWithButtons : public Box { public: QSK_SUBCONTROLS( Panel, ValuePanel, ValueText ) - BoxWithButtons( const QString& title, const QString& value, - bool isBright, QQuickItem* parent = nullptr ); + BoxWithButtons( const QString& title, const QString& prefix, + const int initialValue, const QString& suffix, + bool isBright, QQuickItem* parent = nullptr ); + + private: + void setValue( const int value ); + + const QString m_prefix; + int m_value; + const QString m_suffix; + QskTextLabel* m_valueLabel; }; diff --git a/examples/iotdashboard/CircularProgressBar.cpp b/examples/iotdashboard/CircularProgressBar.cpp index 91578197..14a6583c 100644 --- a/examples/iotdashboard/CircularProgressBar.cpp +++ b/examples/iotdashboard/CircularProgressBar.cpp @@ -6,7 +6,6 @@ #include "CircularProgressBar.h" #include -#include #include QSK_SUBCONTROL( CircularProgressBar, Groove ) @@ -75,6 +74,8 @@ class CircularProgressBar::PrivateData bool isIndeterminate = false; }; +CircularProgressBar::~CircularProgressBar() = default; + CircularProgressBar::CircularProgressBar( qreal min, qreal max, QQuickItem* parent ) : QskBoundedControl( min, max, parent ) , m_data( new PrivateData ) diff --git a/examples/iotdashboard/CircularProgressBar.h b/examples/iotdashboard/CircularProgressBar.h index 24a0c4fc..efb71b9d 100644 --- a/examples/iotdashboard/CircularProgressBar.h +++ b/examples/iotdashboard/CircularProgressBar.h @@ -6,9 +6,6 @@ #pragma once #include -#include - -#include class CircularProgressBar : public QskBoundedControl { @@ -31,6 +28,7 @@ class CircularProgressBar : public QskBoundedControl CircularProgressBar( qreal min, qreal max, QQuickItem* parent = nullptr ); CircularProgressBar( QQuickItem* parent = nullptr ); + ~CircularProgressBar(); bool isIndeterminate() const; void setIndeterminate( bool on = true ); diff --git a/examples/iotdashboard/DashboardPage.cpp b/examples/iotdashboard/DashboardPage.cpp index 902863f0..baede5e5 100644 --- a/examples/iotdashboard/DashboardPage.cpp +++ b/examples/iotdashboard/DashboardPage.cpp @@ -11,7 +11,6 @@ #include "LightDisplay.h" #include "GridBox.h" #include "MyDevices.h" -#include "PieChart.h" #include "TopBar.h" #include "UsageBox.h" @@ -34,7 +33,7 @@ namespace { public: IndoorTemperature( QQuickItem* parent = nullptr ) - : BoxWithButtons( "Indoor Temperature", "+24", true, parent ) + : BoxWithButtons( "Indoor Temperature", "+", 24, {}, true, parent ) { } }; @@ -43,7 +42,7 @@ namespace { public: Humidity( QQuickItem* parent = nullptr ) - : BoxWithButtons( "Humidity", "30%", false, parent ) + : BoxWithButtons( "Humidity", {}, 30, "%", false, parent ) { } }; diff --git a/examples/iotdashboard/DevicesPage.cpp b/examples/iotdashboard/DevicesPage.cpp new file mode 100644 index 00000000..e525bdf4 --- /dev/null +++ b/examples/iotdashboard/DevicesPage.cpp @@ -0,0 +1,21 @@ +/****************************************************************************** + * Copyright (C) 2021 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "DevicesPage.h" + +#include +#include + +QSK_SUBCONTROL( DevicesPage, Panel ) + +DevicesPage::DevicesPage( QQuickItem* parent ) + : QskLinearBox( Qt::Vertical, parent ) +{ + auto* const textLabel = new QskTextLabel( "devices page", this ); + textLabel->setAlignmentHint( QskTextLabel::Text, Qt::AlignCenter ); + textLabel->setFontRole( QskSkin::HugeFont ); +} + +#include "moc_DevicesPage.cpp" diff --git a/examples/iotdashboard/DevicesPage.h b/examples/iotdashboard/DevicesPage.h new file mode 100644 index 00000000..2241997f --- /dev/null +++ b/examples/iotdashboard/DevicesPage.h @@ -0,0 +1,19 @@ +/****************************************************************************** + * Copyright (C) 2021 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#pragma once + +#include +#include + +class DevicesPage : public QskLinearBox +{ + Q_OBJECT + + public: + QSK_SUBCONTROLS( Panel ) + + DevicesPage( QQuickItem* parent ); +}; diff --git a/examples/iotdashboard/DiagramSkinlet.cpp b/examples/iotdashboard/DiagramSkinlet.cpp index 46193e19..82d5cffb 100644 --- a/examples/iotdashboard/DiagramSkinlet.cpp +++ b/examples/iotdashboard/DiagramSkinlet.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include namespace @@ -98,8 +98,6 @@ QSGNode* DiagramSkinlet::updateChartNode( const Diagram* diagram, QSGNode* node const QRectF rect = diagram->subControlRect( Q::Chart ); const qreal yMax = diagram->yMax(); - const QVector< Diagram::Type > types = { Diagram::Line, Diagram::Area, Diagram::Bar }; - for( int i = 0; i < diagram->dataPoints().size(); ++i ) { QSGNode* chartNode; @@ -122,10 +120,8 @@ QSGNode* DiagramSkinlet::updateChartNode( const Diagram* diagram, QSGNode* node int lineWidth = diagram->metric( lineSubcontrol | QskAspect::Size ); - for( int j = 0; j < types.size(); ++j ) + for( const auto type : { Diagram::Line, Diagram::Area, Diagram::Bar } ) { - const auto type = types.at( j ); - if( diagram->typesAt( i ) & type ) { QColor color; @@ -146,15 +142,15 @@ QSGNode* DiagramSkinlet::updateChartNode( const Diagram* diagram, QSGNode* node for( int k = 0; k < dataPoints.size(); ++k ) { - QskBoxNode* barNode; + QskBoxRectangleNode* barNode; if( barsNode->childCount() > k ) { - barNode = static_cast< QskBoxNode* >( barsNode->childAtIndex( k ) ); + barNode = static_cast< QskBoxRectangleNode* >( barsNode->childAtIndex( k ) ); } else { - barNode = new QskBoxNode; + barNode = new QskBoxRectangleNode; barsNode->appendChildNode( barNode ); } @@ -176,7 +172,7 @@ QSGNode* DiagramSkinlet::updateChartNode( const Diagram* diagram, QSGNode* node color = diagram->color( barSubcontrol ); const auto shape = diagram->boxShapeHint( barSubcontrol ); - barNode->setBoxData( barRect, shape, {}, {}, color ); + barNode->updateNode( barRect, shape, {}, {}, color ); } } else @@ -208,9 +204,8 @@ QSGNode* DiagramSkinlet::updateChartNode( const Diagram* diagram, QSGNode* node dataPointNode->update( rect, nodeType, color, dataPoints, yMax, false, lineWidth ); } + nodeIndex++; } - - nodeIndex++; } while( nodeIndex < chartNode->childCount() ) diff --git a/examples/iotdashboard/PieChartPainted.cpp b/examples/iotdashboard/EnergyMeter.cpp similarity index 63% rename from examples/iotdashboard/PieChartPainted.cpp rename to examples/iotdashboard/EnergyMeter.cpp index 4e1bfb13..2da14108 100644 --- a/examples/iotdashboard/PieChartPainted.cpp +++ b/examples/iotdashboard/EnergyMeter.cpp @@ -3,23 +3,18 @@ * This file may be used under the terms of the 3-clause BSD License *****************************************************************************/ -#include "PieChartPainted.h" +#include "EnergyMeter.h" #include "CircularProgressBar.h" -#include -#include -#include -#include -#include #include -#include +#include namespace { - class ProgressLabel : public QskTextLabel + class ValueLabel : public QskTextLabel { public: - ProgressLabel( QQuickItem* parent ) + ValueLabel( QQuickItem* parent ) : QskTextLabel( parent ) { initSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Fixed ); @@ -34,22 +29,22 @@ namespace }; } -PieChartPainted::PieChartPainted( const QColor& textColor, const QskGradient& gradient, - int progress, QQuickItem* parent ) +EnergyMeter::EnergyMeter( const QColor& textColor, + const QskGradient& gradient, int value, QQuickItem* parent ) : QskControl( parent ) { setAutoLayoutChildren( true ); - auto progressBar = new CircularProgressBar( this ); - progressBar->setGradientHint( CircularProgressBar::Bar, gradient ); - progressBar->setValue( progress ); + auto valueBar = new CircularProgressBar( this ); + valueBar->setGradientHint( CircularProgressBar::Bar, gradient ); + valueBar->setValue( value ); - auto progressLabel = new ProgressLabel( this ); - progressLabel->setTextColor( textColor ); - progressLabel->setValue( progress ); + auto valueLabel = new ValueLabel( this ); + valueLabel->setTextColor( textColor ); + valueLabel->setValue( value ); } -QSizeF PieChartPainted::contentsSizeHint( +QSizeF EnergyMeter::contentsSizeHint( Qt::SizeHint which, const QSizeF& constraint ) const { if ( which != Qt::PreferredSize ) diff --git a/examples/iotdashboard/PieChartPainted.h b/examples/iotdashboard/EnergyMeter.h similarity index 82% rename from examples/iotdashboard/PieChartPainted.h rename to examples/iotdashboard/EnergyMeter.h index a3bfa596..27e8e66d 100644 --- a/examples/iotdashboard/PieChartPainted.h +++ b/examples/iotdashboard/EnergyMeter.h @@ -7,10 +7,10 @@ #include -class PieChartPainted : public QskControl +class EnergyMeter : public QskControl { public: - PieChartPainted( const QColor&, const QskGradient&, + EnergyMeter( const QColor&, const QskGradient&, int progress, QQuickItem* parent = nullptr ); protected: diff --git a/examples/iotdashboard/GraphicProvider.cpp b/examples/iotdashboard/GraphicProvider.cpp index 45e777de..acdec602 100644 --- a/examples/iotdashboard/GraphicProvider.cpp +++ b/examples/iotdashboard/GraphicProvider.cpp @@ -6,10 +6,8 @@ #include "GraphicProvider.h" #include +#include -#include -#include -#include #include const inline QString pathName( const QString& baseName, const QString& suffix ) @@ -23,7 +21,7 @@ const inline QString pathName( const QString& baseName, const QString& suffix ) const QskGraphic* GraphicProvider::loadGraphic( const QString& id ) const { - static QString scope = QStringLiteral( ":/images/" ); + static QString scope = QStringLiteral( ":/images/qvg/" ); QString baseName = scope; baseName += id.toLower().replace( ' ', '-' ); @@ -31,30 +29,12 @@ const QskGraphic* GraphicProvider::loadGraphic( const QString& id ) const auto path = pathName( baseName, QString() ); if ( path.isEmpty() ) - path = pathName( baseName, ".png" ); - - if ( path.isEmpty() ) - path = pathName( baseName, ".svg" ); + path = pathName( baseName, ".qvg" ); QskGraphic graphic; if ( !path.isEmpty() ) - { - if ( path.endsWith( ".png" ) ) - { - graphic = QskGraphic::fromImage( QImage( path ) ); - } - else - { - QSvgRenderer renderer; - if ( renderer.load( path ) ) - { - QPainter painter( &graphic ); - renderer.render( &painter ); - painter.end(); - } - } - } + graphic = QskGraphicIO::read( path ); return graphic.isNull() ? nullptr : new QskGraphic( graphic ); } diff --git a/examples/iotdashboard/LightDisplay.cpp b/examples/iotdashboard/LightDisplay.cpp index 7c345a11..42dd8713 100644 --- a/examples/iotdashboard/LightDisplay.cpp +++ b/examples/iotdashboard/LightDisplay.cpp @@ -43,10 +43,6 @@ LightDisplay::LightDisplay( QQuickItem* parent ) setAlignmentHint( ValueText, Qt::AlignRight ); setBoundaries( 0, 100 ); - - // ### move to Skin: - setShadow( { 0, 20 } ); - setShadowColor( 0xe5e5e5 ); } bool LightDisplay::isPressed() const @@ -54,17 +50,6 @@ bool LightDisplay::isPressed() const return hasSkinState( Pressed ); } -void LightDisplay::setShadow( const QskShadowMetrics& shadow ) -{ - m_shadow = shadow; - update(); -} - -const QskShadowMetrics& LightDisplay::shadow() const -{ - return m_shadow; -} - void LightDisplay::setGradient( const QskGradient& gradient ) { m_gradient = gradient; @@ -76,17 +61,6 @@ const QskGradient& LightDisplay::gradient() const return m_gradient; } -void LightDisplay::setShadowColor( const QColor& color ) -{ - m_shadowColor = color; - update(); -} - -QColor LightDisplay::shadowColor() const -{ - return m_shadowColor; -} - void LightDisplay::mousePressEvent( QMouseEvent* event ) { QRectF handleRect = subControlRect( LightDisplay::Knob ); @@ -117,7 +91,7 @@ void LightDisplay::mouseMoveEvent( QMouseEvent* event ) return; } - const QskArcMetrics metrics = arcMetricsHint( ColdAndWarmArc ); + const auto metrics = arcMetricsHint( ColdAndWarmArc ); qreal angle = angleFromPoint( rect, mousePos ); const int tolerance = 20; @@ -161,7 +135,7 @@ bool LightDisplay::arcContainsPoint( const QRectF& rect, const QPointF& point ) // putting this in an own function just because it might be useful // at other places in the future - const QskArcMetrics metrics = arcMetricsHint( ColdAndWarmArc ); + const auto metrics = arcMetricsHint( ColdAndWarmArc ); const int tolerance = 20; // 1. check angle diff --git a/examples/iotdashboard/LightDisplay.h b/examples/iotdashboard/LightDisplay.h index 9aa7f0b6..9049a5b3 100644 --- a/examples/iotdashboard/LightDisplay.h +++ b/examples/iotdashboard/LightDisplay.h @@ -22,15 +22,9 @@ class LightDisplay : public QskBoundedValueInput bool isPressed() const; - void setShadow( const QskShadowMetrics& ); - const QskShadowMetrics& shadow() const; - void setGradient( const QskGradient& ); const QskGradient& gradient() const; - void setShadowColor( const QColor& ); - QColor shadowColor() const; - protected: void mousePressEvent( QMouseEvent* e ) override; void mouseMoveEvent( QMouseEvent* e ) override; diff --git a/examples/iotdashboard/LightDisplaySkinlet.cpp b/examples/iotdashboard/LightDisplaySkinlet.cpp index 2a4a7f3e..6c6308bc 100644 --- a/examples/iotdashboard/LightDisplaySkinlet.cpp +++ b/examples/iotdashboard/LightDisplaySkinlet.cpp @@ -37,8 +37,8 @@ QRectF LightDisplaySkinlet::subControlRect( const QskSkinnable* skinnable, if( subControl == LightDisplay::Groove || subControl == LightDisplay::Panel ) { - QSizeF textSize = textLabelsSize( display ); - QskArcMetrics arcMetrics = display->arcMetricsHint( LightDisplay::ColdAndWarmArc ); + const auto textSize = textLabelsSize( display ); + const auto arcMetrics = display->arcMetricsHint( LightDisplay::ColdAndWarmArc ); const qreal ticksWidth = display->arcMetricsHint( LightDisplay::Tickmarks ).width() + ticksSpacing; const qreal x = textSize.width() + arcMetrics.width() + ticksWidth; @@ -81,7 +81,7 @@ QRectF LightDisplaySkinlet::subControlRect( const QskSkinnable* skinnable, else if( subControl == LightDisplay::LeftLabel ) { const QRectF ticksRect = subControlRect( skinnable, contentsRect, LightDisplay::Tickmarks ); - QSizeF size = textLabelsSize( display ); + const auto size = textLabelsSize( display ); rect.setWidth( size.width() ); @@ -92,8 +92,8 @@ QRectF LightDisplaySkinlet::subControlRect( const QskSkinnable* skinnable, } else if( subControl == LightDisplay::RightLabel ) { - QRectF ticksRect = subControlRect( skinnable, contentsRect, LightDisplay::Tickmarks ); - QSizeF size = textLabelsSize( display ); + const auto ticksRect = subControlRect( skinnable, contentsRect, LightDisplay::Tickmarks ); + const auto size = textLabelsSize( display ); rect.setX( ticksRect.x() + ticksRect.width() ); @@ -104,9 +104,9 @@ QRectF LightDisplaySkinlet::subControlRect( const QskSkinnable* skinnable, } else if( subControl == LightDisplay::Knob ) { - QRectF arcRect = subControlRect( skinnable, contentsRect, LightDisplay::ColdAndWarmArc ); - QskArcMetrics arcMetrics = display->arcMetricsHint( LightDisplay::ColdAndWarmArc ); - QSizeF knobSize = display->strutSizeHint( LightDisplay::Knob ); + const auto arcRect = subControlRect( skinnable, contentsRect, LightDisplay::ColdAndWarmArc ); + const auto arcMetrics = display->arcMetricsHint( LightDisplay::ColdAndWarmArc ); + const auto knobSize = display->strutSizeHint( LightDisplay::Knob ); const qreal radius = ( arcRect.width() - arcMetrics.width() ) / 2; const qreal angle = display->valueAsRatio() * 180; @@ -137,18 +137,7 @@ QSGNode* LightDisplaySkinlet::updateSubNode( } case GrooveRole: { - const QRectF grooveRect = display->subControlRect( LightDisplay::Groove ); - if ( grooveRect.isEmpty() ) - return nullptr; - - const auto& shadowMetrics = display->shadow(); - const auto shadowRect = shadowMetrics.shadowRect( grooveRect ); - - auto shadowNode = QskSGNode::ensureNode< QskBoxShadowNode >( node ); - shadowNode->setShadowData( shadowRect, grooveRect.width() / 2, - shadowMetrics.blurRadius(), display->shadowColor() ); - - return shadowNode; + return updateBoxNode( skinnable, node, LightDisplay::Groove ); } case ColdAndWarmArcRole: { diff --git a/examples/iotdashboard/MainItem.cpp b/examples/iotdashboard/MainItem.cpp index 9d483994..225fa16c 100644 --- a/examples/iotdashboard/MainItem.cpp +++ b/examples/iotdashboard/MainItem.cpp @@ -1,8 +1,12 @@ #include "MainItem.h" #include "DashboardPage.h" +#include "DevicesPage.h" #include "MenuBar.h" +#include "MembersPage.h" #include "RoomsPage.h" +#include "StatisticsPage.h" +#include "StoragePage.h" #include #include @@ -12,13 +16,120 @@ #include #include #include +#include + +#include + +QPair< Cube::Position, Cube::Edge > Cube::s_neighbors[ Cube::NumPositions ][ Cube::NumEdges ] = +{ + // neighbors of Left side: + { + { Cube::BackPos, Cube::BottomEdge }, // going Left + { Cube::FrontPos, Cube::BottomEdge }, // going Right + { Cube::TopPos, Cube::LeftEdge }, // going Top + { Cube::BottomPos, Cube::RightEdge } // going Bottom + }, + + // Right: + { + { Cube::FrontPos, Cube::BottomEdge }, + { Cube::BackPos, Cube::BottomEdge }, + { Cube::TopPos, Cube::RightEdge }, + { Cube::BottomPos, Cube::LeftEdge } + }, + + // Top: + { + { Cube::LeftPos, Cube::RightEdge }, + { Cube::RightPos, Cube::LeftEdge }, + { Cube::BackPos, Cube::TopEdge }, + { Cube::FrontPos, Cube::BottomEdge } + }, + + // Bottom: + { + { Cube::LeftPos, Cube::LeftEdge }, + { Cube::RightPos, Cube::RightEdge }, + { Cube::FrontPos, Cube::BottomEdge }, + { Cube::BackPos, Cube::TopEdge } + }, + + // Front: + { + { Cube::LeftPos, Cube::BottomEdge }, + { Cube::RightPos, Cube::BottomEdge }, + { Cube::TopPos, Cube::BottomEdge }, + { Cube::BottomPos, Cube::BottomEdge } + }, + + // Back: + { + { Cube::RightPos, Cube::BottomEdge }, + { Cube::LeftPos, Cube::BottomEdge }, + { Cube::TopPos, Cube::TopEdge }, + { Cube::BottomPos, Cube::BottomEdge } + } +}; + +Cube::Edge Cube::s_edgeTransformations[ Cube::NumEdges ][ Cube::NumEdges ] = +{ + // current edge is LeftEdge: + { Cube::TopEdge, // Left + Cube::BottomEdge, // Right + Cube::RightEdge, // Top + Cube::LeftEdge }, // Bottom + + // Right: + { Cube::BottomEdge, + Cube::TopEdge, + Cube::LeftEdge, + Cube::RightEdge }, + + // Top: + { Cube::RightEdge, + Cube::LeftEdge, + Cube::BottomEdge, + Cube::TopEdge }, + + // Bottom: + { Cube::LeftEdge, + Cube::RightEdge, + Cube::TopEdge, + Cube::BottomEdge } +}; Cube::Cube( QQuickItem* parent ) : QskStackBox( false, parent ) + , m_destination( FrontPos ) + , m_currentEdge( BottomEdge ) + , m_isIntermediateHop( false ) { + // The code below covers the case where we need 2 cube movements to get + // to the desired position. + // We use transientIndexChanged here to be sure to start a new transition + // at the end; indexChanged doesn't work here. + + connect( this, &QskStackBox::transientIndexChanged, this, [ this ]( qreal position ) + { + const bool animationIsFinished = ( position == qFloor( position ) ); + + if( animationIsFinished && position != m_destination ) + { + QTimer::singleShot( 0, this, [this]() + { + m_isIntermediateHop = true; + switchToPosition( m_destination ); + } ); + } + } ); + + QTimer::singleShot( 0, this, [this]() + { + Q_EMIT cubeIndexChanged( m_destination ); + } ); } -void Cube::startAnimation( Qsk::Direction direction ) +void Cube::doSwitch( Qsk::Direction direction, Position position ) { using Animator = QskStackBoxAnimator4; @@ -27,12 +138,24 @@ void Cube::startAnimation( Qsk::Direction direction ) if ( animator == nullptr ) { animator = new Animator( this ); - animator->setEasingCurve( QEasingCurve::InOutQuad ); animator->setDuration( 1000 ); - setAnimator( animator ); } + if( position == m_destination && !m_isIntermediateHop ) // 1 hop + { + animator->setEasingCurve( QEasingCurve::InOutQuad ); + } + else if( !m_isIntermediateHop ) // 1st of 2 hops + { + animator->setEasingCurve( QEasingCurve::InQuad ); + } + else // 2nd of 2 hops + { + animator->setEasingCurve( QEasingCurve::OutQuad ); + m_isIntermediateHop = false; + } + const auto orientation = ( direction == Qsk::LeftToRight || direction == Qsk::RightToLeft ) ? Qt::Horizontal : Qt::Vertical; animator->setOrientation( orientation ); @@ -40,32 +163,108 @@ void Cube::startAnimation( Qsk::Direction direction ) const bool inverted = ( direction == Qsk::LeftToRight || direction == Qsk::TopToBottom ); animator->setInverted( inverted ); - int newIndex; + updateEdge( direction, position ); - switch( direction ) + setCurrentIndex( position ); + + if( position == m_destination ) { - case Qsk::LeftToRight: - case Qsk::TopToBottom: - newIndex = currentIndex() + 1; - break; - case Qsk::RightToLeft: - case Qsk::BottomToTop: - newIndex = currentIndex() - 1; - break; + Q_EMIT cubeIndexChanged( position ); + } +} + +void Cube::switchPosition( const Qsk::Direction direction ) +{ + m_destination = neighbor( currentPosition(), direction ); + + doSwitch( direction, m_destination ); +} + +void Cube::switchToPosition( const Position position ) +{ + if( currentPosition() == position ) + return; + + m_destination = position; + + const auto direction = this->direction( currentPosition(), position ); + const auto nextPosition = neighbor( currentPosition(), direction ); + + doSwitch( direction, nextPosition ); +} + +void Cube::keyPressEvent( QKeyEvent* event ) +{ + Qsk::Direction direction; + + switch( event->key() ) + { + case Qt::Key_Up: + direction = Qsk::TopToBottom; + break; + case Qt::Key_Down: + direction = Qsk::BottomToTop; + break; + case Qt::Key_Left: + direction = Qsk::LeftToRight; + break; + case Qt::Key_Right: + direction = Qsk::RightToLeft; + break; + default: + return; } - newIndex %= itemCount(); - if( newIndex < 0 ) - newIndex += itemCount(); + switchPosition( direction ); +} - setCurrentIndex( newIndex ); +Cube::Position Cube::currentPosition() const +{ + return static_cast< Position >( currentIndex() ); +} + +Cube::Position Cube::neighbor( const Position position, const Qsk::Direction direction ) const +{ + const auto index = s_edgeTransformations[ m_currentEdge ][ direction ]; + const auto n = s_neighbors[ position ][ index ].first; + return n; +} + +Qsk::Direction Cube::direction( const Position from, const Position to ) const +{ + // if direct neighbor: use that direction + // otherwise: we need 2 swipes, direction doesn't matter, so choose right to left + + const auto neighbors = s_neighbors[ from ]; + + for( int i = 0; i < NumEdges; ++i ) + { + if( neighbors[ i ].first == to ) + { + return static_cast< Qsk::Direction >( i ); + } + } + + return Qsk::RightToLeft; +} + +void Cube::updateEdge( Qsk::Direction direction, Position position ) +{ + m_currentEdge = s_neighbors[ currentPosition() ][ direction ].second; + + // When going back to Front, Left etc., switch back to + // the bottom edge, otherwise it gets to confusing: + if( position != TopPos && position != BottomPos ) + { + m_currentEdge = BottomEdge; + } } MainItem::MainItem( QQuickItem* parent ) : QskControl( parent ) - , m_cube( new Cube( this ) ) - , m_mainLayout( new QskLinearBox( Qt::Horizontal, m_cube ) ) - , m_otherLayout( new QskLinearBox( Qt::Horizontal, m_cube ) ) + , m_mainLayout( new QskLinearBox( Qt::Horizontal, this ) ) + , m_menuBar( new MenuBar( m_mainLayout ) ) + , m_cube( new Cube( m_mainLayout ) ) { setAutoLayoutChildren( true ); setAcceptedMouseButtons( Qt::LeftButton ); @@ -77,18 +276,32 @@ MainItem::MainItem( QQuickItem* parent ) m_mainLayout->setSpacing( 0 ); - m_otherLayout->setSpacing( 0 ); + connect( m_menuBar, &MenuBar::pageChangeRequested, this, [this]( int index ) + { + const auto position = static_cast< Cube::Position >( index ); + m_cube->switchToPosition( position ); + } ); - (void) new MenuBar( m_mainLayout ); - (void) new DashboardPage( m_mainLayout ); + connect( m_cube, &Cube::cubeIndexChanged, m_menuBar, &MenuBar::setActivePage ); - (void) new MenuBar( m_otherLayout ); - (void) new RoomsPage( m_otherLayout ); + auto* const dashboardPage = new DashboardPage( m_cube ); + auto* const roomsPage = new RoomsPage( m_cube ); + auto* const devicesPage = new DevicesPage( m_cube ); + auto* const statisticsPage = new StatisticsPage( m_cube ); + auto* const storagePage = new StoragePage( m_cube ); + auto* const membersPage = new MembersPage( m_cube ); - m_cube->addItem( m_mainLayout ); - m_cube->addItem( m_otherLayout ); + m_cube->insertItem( Cube::LeftPos, statisticsPage ); + m_cube->insertItem( Cube::RightPos, roomsPage ); + m_cube->insertItem( Cube::TopPos, storagePage ); + m_cube->insertItem( Cube::BottomPos, membersPage ); + m_cube->insertItem( Cube::FrontPos, dashboardPage ); + m_cube->insertItem( Cube::BackPos, devicesPage ); - m_cube->setCurrentItem( m_mainLayout ); + // the current item needs to be the one at the Front: + m_cube->setCurrentItem( dashboardPage ); + + installEventFilter( this ); } void MainItem::gestureEvent( QskGestureEvent* event ) @@ -96,7 +309,7 @@ void MainItem::gestureEvent( QskGestureEvent* event ) if( event->gesture()->state() == QskGesture::Finished && event->gesture()->type() == QskGesture::Pan ) { - auto* panGesture = static_cast< const QskPanGesture* >( event->gesture().get() ); + const auto* panGesture = static_cast< const QskPanGesture* >( event->gesture().get() ); const auto delta = panGesture->origin() - panGesture->position(); @@ -111,7 +324,20 @@ void MainItem::gestureEvent( QskGestureEvent* event ) direction = ( delta.y() < 0 ) ? Qsk::TopToBottom : Qsk::BottomToTop; } - m_cube->startAnimation( direction ); + m_cube->switchPosition( direction ); + } +} + +bool MainItem::eventFilter( QObject* object, QEvent* event ) +{ + if ( event->type() == QEvent::KeyPress ) + { + QCoreApplication::sendEvent( m_cube, event ); + return true; + } + else + { + return QObject::eventFilter( object, event ); } } @@ -136,3 +362,5 @@ bool MainItem::gestureFilter( QQuickItem* item, QEvent* event ) return recognizer.processEvent( item, event, false ); } + +#include "moc_MainItem.cpp" diff --git a/examples/iotdashboard/MainItem.h b/examples/iotdashboard/MainItem.h index 48928e92..0bde2792 100644 --- a/examples/iotdashboard/MainItem.h +++ b/examples/iotdashboard/MainItem.h @@ -6,14 +6,61 @@ #include +class MenuBar; class QskBox; class QskLinearBox; class Cube : public QskStackBox { + Q_OBJECT + public: + enum Edge { + LeftEdge = Qsk::LeftToRight, + RightEdge = Qsk::RightToLeft, + TopEdge = Qsk::TopToBottom, + BottomEdge = Qsk::BottomToTop, + NumEdges + }; + Q_ENUM( Edge ) + + enum Position { + LeftPos = LeftEdge, + RightPos = RightEdge, + TopPos = TopEdge, + BottomPos = BottomEdge, + FrontPos, + BackPos, + NumPositions + }; + Q_ENUM( Position ) + explicit Cube( QQuickItem* parent = nullptr ); - void startAnimation( Qsk::Direction direction ); + + public Q_SLOTS: + void switchPosition( const Qsk::Direction direction ); + void switchToPosition( const Cube::Position position ); + + Q_SIGNALS: + // might be different from indexChanged: + void cubeIndexChanged( const int index ); + + protected: + void keyPressEvent( QKeyEvent* event ) override; + + private: + Position currentPosition() const; + Position neighbor( const Position position, const Qsk::Direction direction ) const; + Qsk::Direction direction( const Position from, const Position to ) const; + void updateEdge( Qsk::Direction direction, Position position ); + void doSwitch( Qsk::Direction direction, Position position ); + + Position m_destination; + Edge m_currentEdge; + bool m_isIntermediateHop; + + static QPair< Position, Edge > s_neighbors[ NumPositions ][ NumEdges ]; + static Edge s_edgeTransformations[ NumEdges ][ NumEdges ]; }; class MainItem : public QskControl @@ -24,12 +71,13 @@ class MainItem : public QskControl MainItem( QQuickItem* parent = nullptr ); protected: + bool eventFilter(QObject *obj, QEvent *event) override final; bool gestureFilter( QQuickItem*, QEvent* ) override final; void gestureEvent( QskGestureEvent* ) override final; private: - Cube* m_cube; QskLinearBox* m_mainLayout; - QskLinearBox* m_otherLayout; + MenuBar* m_menuBar; + Cube* m_cube; QskPanGestureRecognizer m_panRecognizer; }; diff --git a/examples/iotdashboard/MembersPage.cpp b/examples/iotdashboard/MembersPage.cpp new file mode 100644 index 00000000..cab47243 --- /dev/null +++ b/examples/iotdashboard/MembersPage.cpp @@ -0,0 +1,21 @@ +/****************************************************************************** + * Copyright (C) 2021 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "MembersPage.h" + +#include +#include + +QSK_SUBCONTROL( MembersPage, Panel ) + +MembersPage::MembersPage( QQuickItem* parent ) + : QskLinearBox( Qt::Vertical, parent ) +{ + auto* const textLabel = new QskTextLabel( "members page", this ); + textLabel->setAlignmentHint( QskTextLabel::Text, Qt::AlignCenter ); + textLabel->setFontRole( QskSkin::HugeFont ); +} + +#include "moc_MembersPage.cpp" diff --git a/examples/iotdashboard/MembersPage.h b/examples/iotdashboard/MembersPage.h new file mode 100644 index 00000000..ea5cfd91 --- /dev/null +++ b/examples/iotdashboard/MembersPage.h @@ -0,0 +1,19 @@ +/****************************************************************************** + * Copyright (C) 2021 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#pragma once + +#include +#include + +class MembersPage : public QskLinearBox +{ + Q_OBJECT + + public: + QSK_SUBCONTROLS( Panel ) + + MembersPage( QQuickItem* parent ); +}; diff --git a/examples/iotdashboard/MenuBar.cpp b/examples/iotdashboard/MenuBar.cpp index ccbcd5d3..a26536ef 100644 --- a/examples/iotdashboard/MenuBar.cpp +++ b/examples/iotdashboard/MenuBar.cpp @@ -5,58 +5,85 @@ #include "MenuBar.h" +#include + QSK_SUBCONTROL( MenuBarTopLabel, Graphic ) -QSK_SUBCONTROL( MenuBarGraphicLabel, Graphic ) -QSK_SUBCONTROL( MenuBarLabel, Text ) -QSK_SUBCONTROL( MenuItem, Panel ) + +QSK_SUBCONTROL( MenuButton, Panel ) +QSK_SUBCONTROL( MenuButton, Text ) +QSK_SUBCONTROL( MenuButton, Graphic ) + QSK_SUBCONTROL( MenuBar, Panel ) -QSK_STATE( MenuItem, Active, ( QskAspect::FirstUserState << 1 ) ) - -MenuItem::MenuItem( const QString& name, QQuickItem* parent ) - : QskLinearBox( Qt::Horizontal, parent ) +MenuButton::MenuButton( const QString& name, QQuickItem* parent ) + : QskPushButton( name, parent ) { + setCheckable( true ); initSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Fixed ); - setSpacing( 6 ); - setAcceptHoverEvents( true ); + setSubcontrolProxy( QskPushButton::Panel, MenuButton::Panel ); + setSubcontrolProxy( QskPushButton::Text, MenuButton::Text ); + setSubcontrolProxy( QskPushButton::Graphic, MenuButton::Graphic ); - setPanel( true ); - setSubcontrolProxy( QskBox::Panel, MenuItem::Panel ); - - auto graphicLabel = new MenuBarGraphicLabel( name, this ); - graphicLabel->setSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Fixed ); - graphicLabel->setFixedWidth( metric( MenuBarGraphicLabel::Graphic | QskAspect::Size ) ); - - new MenuBarLabel( name, this ); + setGraphicSource( name ); } MenuBar::MenuBar( QQuickItem* parent ) : QskLinearBox( Qt::Vertical, parent ) + , m_currentIndex( Cube::FrontPos ) { setPanel( true ); setSubcontrolProxy( QskBox::Panel, MenuBar::Panel ); - initSizePolicy( QskSizePolicy::Minimum, QskSizePolicy::Preferred ); - setSpacing( 8 ); + initSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Preferred ); + setSpacing( 0 ); auto graphicLabel = new MenuBarTopLabel( "main-icon", this ); graphicLabel->setMargins( marginHint( MenuBarTopLabel::Graphic ) ); graphicLabel->setSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Fixed ); - m_entryStrings = { "Dashboard", "Rooms", "Devices", "Statistics", "Storage", "Members" }; - - for( const auto& entryString : qAsConst( m_entryStrings ) ) + // ### unify the information with the one from MainItem + const QVector< QPair< Cube::Position, QString > > entries = { - auto* entry = new MenuItem( entryString, this ); - m_entries.append( entry ); - } + { Cube::FrontPos, "Dashboard" }, + { Cube::RightPos, "Rooms" }, + { Cube::BackPos, "Devices" }, + { Cube::LeftPos, "Statistics" }, + { Cube::TopPos, "Storage" }, + { Cube::BottomPos, "Members" }, + }; - m_entries.at( m_activeEntry )->setSkinStateFlag( MenuItem::Active ); + for( const auto& entry : entries ) + { + auto* button = new MenuButton( entry.second, this ); + m_buttons[ entry.first ] = button; + + connect( button, &QskPushButton::pressed, this, [ this, entry ]() + { + for( auto* button : qAsConst( m_buttons ) ) + { + // the right button will be set to checked after this + button->setChecked( false ); + } + + Q_EMIT pageChangeRequested( entry.first ); + } ); + } addSpacer( 0, 1 ); // fill the space at the bottom - new MenuItem( "Logout", this ); + new MenuButton( "Logout", this ); +} + +void MenuBar::setActivePage( const int index ) +{ + m_buttons[ m_currentIndex ]->setChecked( false ); + m_currentIndex = index; + + QTimer::singleShot( 0, this, [this]() + { + m_buttons[ m_currentIndex ]->setChecked( true ); + } ); } #include "moc_MenuBar.cpp" diff --git a/examples/iotdashboard/MenuBar.h b/examples/iotdashboard/MenuBar.h index b141f260..a4cbfdf4 100644 --- a/examples/iotdashboard/MenuBar.h +++ b/examples/iotdashboard/MenuBar.h @@ -7,8 +7,11 @@ #include #include +#include #include +#include "MainItem.h" + class MenuBarTopLabel final : public QskGraphicLabel { Q_OBJECT @@ -23,43 +26,14 @@ class MenuBarTopLabel final : public QskGraphicLabel } }; -class MenuBarGraphicLabel final : public QskGraphicLabel +class MenuButton final : public QskPushButton { Q_OBJECT public: - QSK_SUBCONTROLS( Graphic ) + QSK_SUBCONTROLS( Panel, Text, Graphic ) - MenuBarGraphicLabel( const QString& icon, QQuickItem* parent = nullptr ) - : QskGraphicLabel( icon, parent ) - { - setSubcontrolProxy( QskGraphicLabel::Graphic, Graphic ); - } -}; - -class MenuBarLabel final : public QskTextLabel -{ - Q_OBJECT - - public: - QSK_SUBCONTROLS( Text ) - - MenuBarLabel( const QString& text, QQuickItem* parent = nullptr ) - : QskTextLabel( text, parent ) - { - setSubcontrolProxy( QskTextLabel::Text, Text ); - } -}; - -class MenuItem final : public QskLinearBox -{ - Q_OBJECT - - public: - QSK_SUBCONTROLS( Panel ) - QSK_STATES( Active ) - - MenuItem( const QString& name, QQuickItem* parent ); + MenuButton( const QString& name, QQuickItem* parent ); }; class MenuBar final : public QskLinearBox @@ -71,8 +45,13 @@ class MenuBar final : public QskLinearBox MenuBar( QQuickItem* parent ); + Q_SIGNALS: + void pageChangeRequested( const int index ); + + public Q_SLOTS: + void setActivePage( const int index ); + private: - QList< QString > m_entryStrings; - QList< MenuItem* > m_entries; - uint m_activeEntry = 0; + MenuButton* m_buttons[ Cube::NumPositions ]; + uint m_currentIndex; }; diff --git a/examples/iotdashboard/MyDevices.cpp b/examples/iotdashboard/MyDevices.cpp index d5fa4945..cf463014 100644 --- a/examples/iotdashboard/MyDevices.cpp +++ b/examples/iotdashboard/MyDevices.cpp @@ -4,12 +4,12 @@ *****************************************************************************/ #include "MyDevices.h" -#include "Skin.h" #include "RoundedIcon.h" #include #include #include +#include #include #include @@ -26,8 +26,10 @@ namespace auto icon = new RoundedIcon( isBright, this ); icon->setPale( true ); - icon->setSource( name ); + icon->setGraphicSource( name ); + icon->setGraphicStrutSize( { 36, 36 } ); icon->setFixedSize( 68, 68 ); + icon->setCheckable( true ); auto textLabel = new QskTextLabel( name, this ); textLabel->setFontRole( QskSkin::TinyFont ); diff --git a/examples/iotdashboard/PieChart.cpp b/examples/iotdashboard/PieChart.cpp deleted file mode 100644 index 4a8bf227..00000000 --- a/examples/iotdashboard/PieChart.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2021 Edelhirsch Software GmbH - * This file may be used under the terms of the 3-clause BSD License - *****************************************************************************/ - -#include "PieChart.h" - -QSK_SUBCONTROL( PieChart, Panel ) -QSK_SUBCONTROL( PieChart, Labels ) - -PieChart::PieChart( QQuickItem* parent ) - : QskControl( parent ) -{ -} - -QVector< float > PieChart::angles() const -{ - return m_angles; -} - -void PieChart::setAngles( const QVector< float >& angles ) -{ - m_angles = angles; -} - -QVector< QString > PieChart::labels() const -{ - return m_labels; -} - -void PieChart::setLabels( const QVector< QString >& labels ) -{ - m_labels = labels; -} - -#include "moc_PieChart.cpp" diff --git a/examples/iotdashboard/PieChart.h b/examples/iotdashboard/PieChart.h deleted file mode 100644 index 32901ad9..00000000 --- a/examples/iotdashboard/PieChart.h +++ /dev/null @@ -1,28 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2021 Edelhirsch Software GmbH - * This file may be used under the terms of the 3-clause BSD License - *****************************************************************************/ - -#pragma once - -#include - -class PieChart : public QskControl -{ - Q_OBJECT - - public: - QSK_SUBCONTROLS( Panel, Labels ) - - PieChart( QQuickItem* parent = nullptr ); - - QVector< float > angles() const; - void setAngles( const QVector< float >& angles ); - - QVector< QString > labels() const; - void setLabels( const QVector< QString >& labels ); - - private: - QVector< float > m_angles; - QVector< QString > m_labels; -}; diff --git a/examples/iotdashboard/PieChartSkinlet.cpp b/examples/iotdashboard/PieChartSkinlet.cpp deleted file mode 100644 index 4bc29c8c..00000000 --- a/examples/iotdashboard/PieChartSkinlet.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2021 Edelhirsch Software GmbH - * This file may be used under the terms of the 3-clause BSD License - *****************************************************************************/ - -#include "PieChartSkinlet.h" -#include "PieChart.h" - -#include -#include -#include -#include - -PieChartSkinlet::PieChartSkinlet( QskSkin* skin ) - : QskSkinlet( skin ) -{ - setNodeRoles( { PanelRole, LabelsRole } ); -} - -QRectF PieChartSkinlet::subControlRect( const QskSkinnable*, - const QRectF& contentsRect, QskAspect::Subcontrol ) const -{ - return contentsRect; -} - -QSGNode* PieChartSkinlet::updateSubNode( const QskSkinnable* skinnable, - quint8 nodeRole, QSGNode* node ) const -{ - const auto pieChart = static_cast< const PieChart* >( skinnable ); - - switch( nodeRole ) - { - case PanelRole: - return updatePanelNode( pieChart, node ); - - case LabelsRole: - return updateLabelsNode( pieChart, node ); - - default: - return nullptr; - } -} - -QSGNode* PieChartSkinlet::updatePanelNode( const PieChart* pieChart, QSGNode* node ) const -{ - auto boxNode = static_cast< QskBoxNode* >( node ); - - if( boxNode == nullptr ) - { - boxNode = new QskBoxNode; - } - - auto panelRect = subControlRect( pieChart, pieChart->contentsRect(), PieChart::Panel ); - - // ### when displaying a legend we might want to revise this - if( panelRect.width() > panelRect.height() ) - { - panelRect.setWidth( panelRect.height() ); - } - else if( panelRect.width() < panelRect.height() ) - { - panelRect.setHeight( panelRect.width() ); - } - - const qreal radius = panelRect.width() / 2; - - QskBoxShapeMetrics shapeMetrics( radius, radius, radius, radius ); - QskBoxBorderMetrics borderMetrics = pieChart->boxBorderMetricsHint( PieChart::Panel ); - QskBoxBorderColors borderColors = pieChart->boxBorderColorsHint( PieChart::Panel ); - QskGradient gradient = pieChart->gradientHint( PieChart::Panel ); - boxNode->setBoxData( panelRect, shapeMetrics, borderMetrics, borderColors, gradient ); - - return boxNode; -} - -QSGNode* PieChartSkinlet::updateLabelsNode( const PieChart* pieChart, QSGNode* ) const -{ - const int labelsCount = pieChart->labels().count(); - - if( labelsCount < 1 ) - { - return nullptr; - } - - return nullptr; -} - -#include "moc_PieChartSkinlet.cpp" diff --git a/examples/iotdashboard/PieChartSkinlet.h b/examples/iotdashboard/PieChartSkinlet.h deleted file mode 100644 index b06c0066..00000000 --- a/examples/iotdashboard/PieChartSkinlet.h +++ /dev/null @@ -1,35 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2021 Edelhirsch Software GmbH - * This file may be used under the terms of the 3-clause BSD License - *****************************************************************************/ - -#pragma once - -#include - -class PieChart; - -class PieChartSkinlet : public QskSkinlet -{ - Q_GADGET - - public: - enum NodeRole - { - PanelRole, - LabelsRole - }; - - Q_INVOKABLE PieChartSkinlet( QskSkin* skin = nullptr ); - - QRectF subControlRect( const QskSkinnable*, - const QRectF&, QskAspect::Subcontrol ) const override; - - protected: - virtual QSGNode* updateSubNode( const QskSkinnable*, - quint8 nodeRole, QSGNode* node ) const override; - - private: - QSGNode* updatePanelNode( const PieChart*, QSGNode* ) const; - QSGNode* updateLabelsNode( const PieChart*, QSGNode* ) const; -}; diff --git a/examples/iotdashboard/RoundButton.h b/examples/iotdashboard/RoundButton.h index 5064b055..6806575a 100644 --- a/examples/iotdashboard/RoundButton.h +++ b/examples/iotdashboard/RoundButton.h @@ -7,7 +7,7 @@ #include -class RoundButton : QskPushButton +class RoundButton : public QskPushButton { Q_OBJECT diff --git a/examples/iotdashboard/RoundedIcon.cpp b/examples/iotdashboard/RoundedIcon.cpp index 9f5cea12..5c9c531b 100644 --- a/examples/iotdashboard/RoundedIcon.cpp +++ b/examples/iotdashboard/RoundedIcon.cpp @@ -7,25 +7,24 @@ QSK_SUBCONTROL( RoundedIcon, Panel ) QSK_SUBCONTROL( RoundedIcon, PalePanel ) +QSK_SUBCONTROL( RoundedIcon, Graphic ) QSK_STATE( RoundedIcon, Bright, ( QskAspect::FirstUserState << 1 ) ) RoundedIcon::RoundedIcon( bool isBright, QQuickItem* parent ) - : QskGraphicLabel( parent ) + : QskPushButton( parent ) { - setAlignment( Qt::AlignCenter ); - setFillMode( QskGraphicLabel::Pad ); - if( isBright ) setSkinStateFlag( Bright ); - setPanel( true ); setPale( false ); + + setSubcontrolProxy( QskPushButton::Graphic, Graphic ); } void RoundedIcon::setPale( bool on ) { - setSubcontrolProxy( QskGraphicLabel::Panel, on ? PalePanel : Panel ); + setSubcontrolProxy( QskPushButton::Panel, on ? PalePanel : Panel ); } #include "moc_RoundedIcon.cpp" diff --git a/examples/iotdashboard/RoundedIcon.h b/examples/iotdashboard/RoundedIcon.h index b232d6d1..b448c42c 100644 --- a/examples/iotdashboard/RoundedIcon.h +++ b/examples/iotdashboard/RoundedIcon.h @@ -5,18 +5,21 @@ #pragma once -#include +#include -class QskGraphicLabel; - -class RoundedIcon : public QskGraphicLabel +class RoundedIcon : public QskPushButton { Q_OBJECT public: - QSK_SUBCONTROLS( Panel, PalePanel ) + QSK_SUBCONTROLS( Panel, PalePanel, Graphic ) QSK_STATES( Bright ) // to differentiate between orange and purple + enum { + NormalRole, + CheckedRole, + } GraphicRole; + RoundedIcon( bool isBright, QQuickItem* parent = nullptr ); void setPale( bool ); diff --git a/examples/iotdashboard/Skin.cpp b/examples/iotdashboard/Skin.cpp index b68de35e..66efb80a 100644 --- a/examples/iotdashboard/Skin.cpp +++ b/examples/iotdashboard/Skin.cpp @@ -9,26 +9,34 @@ #include "BoxWithButtons.h" #include "CircularProgressBar.h" #include "CircularProgressBarSkinlet.h" +#include "DashboardPage.h" #include "Diagram.h" #include "DiagramSkinlet.h" #include "GridBox.h" #include "LightDisplay.h" #include "LightDisplaySkinlet.h" -#include "DashboardPage.h" #include "MenuBar.h" -#include "RoundedIcon.h" +#include "RoomsPage.h" #include "RoundButton.h" +#include "RoundedIcon.h" +#include "StorageBar.h" +#include "StorageBarSkinlet.h" +#include "StorageMeter.h" +#include "StoragePage.h" #include "TopBar.h" #include "UsageBox.h" #include "UsageDiagram.h" #include -#include -#include #include +#include +#include +#include #include +#include #include #include +#include #include #include @@ -39,7 +47,7 @@ namespace { QFont font( "Proxima Nova" ); - if( semiBold ) + if ( semiBold ) { font.setWeight( QFont::Bold ); } @@ -47,7 +55,6 @@ namespace font.setPointSizeF( pointSize /*/ qskDpiScaled( 1.0 )*/ ); return font; } - } Skin::Skin( const Palette& palette, QObject* parent ) @@ -56,6 +63,7 @@ Skin::Skin( const Palette& palette, QObject* parent ) declareSkinlet< CircularProgressBar, CircularProgressBarSkinlet >(); declareSkinlet< Diagram, DiagramSkinlet >(); declareSkinlet< LightDisplay, LightDisplaySkinlet >(); + declareSkinlet< StorageBar, StorageBarSkinlet >(); initHints( palette ); } @@ -80,42 +88,43 @@ void Skin::initHints( const Palette& palette ) QskSkinHintTableEditor ed( &hintTable() ); - ed.setPadding( MainContentGridBox::Panel, {19, 0, 27, 24} ); - + ed.setPadding( MainContentGridBox::Panel, { 19, 0, 27, 24 } ); // menu bar: + ed.setMargin( MenuBarTopLabel::Graphic, { 50, 5, 50, 65 } ); + ed.setPadding( MenuBar::Panel, {0, 35, 0, 12} ); - ed.setStrutSize( MenuItem::Panel | QskAspect::Size, {140, 40} ); - ed.setPadding( MenuItem::Panel, {30, 0, 30, 0} ); + ed.setStrutSize( MenuButton::Panel | QskAspect::Size, {140, 40} ); QColor color( Qt::white ); color.setAlphaF( 0.09 ); - ed.setGradient( MenuItem::Panel | QskControl::Hovered, color ); + ed.setGradient( MenuButton::Panel | QskControl::Hovered, color ); color.setAlphaF( 0.14 ); - ed.setGradient( MenuItem::Panel | MenuItem::Active, color ); + ed.setGradient( MenuButton::Panel | MenuButton::Checked, color ); + ed.setSpacing( MenuButton::Panel, 10 ); - ed.setColor( MenuBarLabel::Text, Qt::white ); - ed.setFontRole( MenuBarLabel::Text, QskSkin::SmallFont ); + ed.setColor( MenuButton::Text, Qt::white ); + ed.setFontRole( MenuButton::Text, QskSkin::SmallFont ); + ed.setAlignment( MenuButton::Text, Qt::AlignLeft | Qt::AlignVCenter ); - ed.setMargin( MenuBarTopLabel::Graphic, { 50, 0, 50, 54 } ); - - ed.setMetric( MenuBarGraphicLabel::Graphic | QskAspect::Size, 14 ); - ed.setAlignment( MenuBarGraphicLabel::Graphic, Qt::AlignCenter ); + ed.setPadding( MenuButton::Graphic, { 30, 0, 0, 0 } ); + ed.setStrutSize( MenuButton::Graphic, { 14, -1 } ); + ed.setAlignment( MenuButton::Graphic, Qt::AlignCenter ); // top bar: - ed.setPadding( TopBar::Panel, {25, 35, 25, 0} ); + ed.setPadding( TopBar::Panel, { 25, 35, 25, 0 } ); - ed.setColor( TopBarItem::Item1 | QskAspect::TextColor, "#ff3122" ); - ed.setColor( TopBarItem::Item2 | QskAspect::TextColor, "#6776ff" ); - ed.setColor( TopBarItem::Item3 | QskAspect::TextColor, "#f99055" ); - ed.setColor( TopBarItem::Item4 | QskAspect::TextColor, "#6776ff" ); + ed.setColor( TopBarItem::Item1 | QskAspect::TextColor, 0xffff3122 ); + ed.setColor( TopBarItem::Item2 | QskAspect::TextColor, 0xff6776ff ); + ed.setColor( TopBarItem::Item3 | QskAspect::TextColor, 0xfff99055 ); + ed.setColor( TopBarItem::Item4 | QskAspect::TextColor, 0xff6776ff ); - // conical gradients are counterclockwise, so specify the 2nd color first: - ed.setGradient( TopBarItem::Item1, { QskGradient::Horizontal, "#FF3122", "#FF5C00" } ); - ed.setGradient( TopBarItem::Item2, { QskGradient::Horizontal, "#6100FF", "#6776FF" } ); - ed.setGradient( TopBarItem::Item3, { QskGradient::Horizontal, "#FF3122", "#FFCE50" } ); - ed.setGradient( TopBarItem::Item4, { QskGradient::Horizontal, "#6100FF", "#6776FF" } ); + // arcs are counterclockwise, so specify the 2nd color first: + ed.setGradient( TopBarItem::Item1, 0xffff3122, 0xffff5c00 ); + ed.setGradient( TopBarItem::Item2, 0xff6100ff, 0xff6776ff ); + ed.setGradient( TopBarItem::Item3, 0xffff3122, 0xffffce50 ); + ed.setGradient( TopBarItem::Item4, 0xff6100ff, 0xff6776ff ); // the bar gradient is defined through the top bar items above ed.setArcMetrics( CircularProgressBar::Groove, { 8.53, 90, -360 } ); @@ -126,14 +135,14 @@ void Skin::initHints( const Palette& palette ) ed.setFontRole( TimeTitleLabel::Text, Skin::TitleFont ); ed.setFontRole( TimeLabel::Text, QskSkin::HugeFont ); - ed.setColor( TimeLabel::Text, "#6776FF" ); + ed.setColor( TimeLabel::Text, 0xff6776ff ); // boxes: ed.setPadding( Box::Panel, 8 ); // content in boxes (indoor temperature, humidity etc.): ed.setFontRole( UsageBox::Separator, QskSkin::SmallFont ); - ed.setColor( UsageBox::Separator, "#dddddd" ); + ed.setColor( UsageBox::Separator, 0xffdddddd ); ed.setPadding( BoxWithButtons::Panel, 8 ); @@ -141,14 +150,41 @@ void Skin::initHints( const Palette& palette ) { ed.setBoxShape( subControl, 6 ); - QskGradient normal( QskGradient::Vertical, "#6776FF", "#6100FF" ); - QskGradient bright( QskGradient::Vertical, "#ff7d34", "#ff3122" ); + QskGradient normal( 0xff6776ff, 0xff6100ff ); + normal.setLinearDirection( Qt::Vertical ); - if ( subControl == RoundedIcon::PalePanel ) + QskGradient bright( 0xffff7d34, 0xffff3122 ); + bright.setLinearDirection( Qt::Vertical ); + + if ( subControl == RoundedIcon::PalePanel ) // My Devices section { const uint alpha = 38; normal.setAlpha( alpha ); bright.setAlpha( alpha ); + + auto pressedNormal = normal; + pressedNormal.setAlpha( 10 ); + auto pressedBright = bright; + pressedBright.setAlpha( 10 ); + + const int duration = 300; + + ed.setGradient( RoundedIcon::PalePanel | QskAbstractButton::Checked, pressedNormal ); + ed.setGradient( + RoundedIcon::PalePanel | RoundedIcon::Bright | QskAbstractButton::Checked, + pressedBright ); + ed.setAnimation( RoundedIcon::PalePanel | QskAspect::Color, duration ); + + ed.setGraphicRole( RoundedIcon::Graphic, RoundedIcon::NormalRole ); + ed.setGraphicRole( RoundedIcon::Graphic | QskAbstractButton::Checked, + RoundedIcon::CheckedRole, + { QskStateCombination::CombinationNoState, RoundedIcon::Bright } ); + ed.setAnimation( RoundedIcon::Graphic, duration ); + + QskColorFilter filter; + filter.addColorSubstitution( + 0xff606675, palette.deviceGraphic ); // color comes from the SVG + setGraphicFilter( RoundedIcon::CheckedRole, filter ); } ed.setGradient( subControl, normal ); @@ -156,7 +192,7 @@ void Skin::initHints( const Palette& palette ) } ed.setFontRole( BoxWithButtons::ValueText, QskSkin::HugeFont ); - ed.setColor( BoxWithButtons::ValueText, "#929cb2" ); + ed.setColor( BoxWithButtons::ValueText, 0xff929cb2 ); ed.setPadding( BoxWithButtons::ValuePanel, 0, 10, 0, 0 ); @@ -170,16 +206,16 @@ void Skin::initHints( const Palette& palette ) ed.setStrutSize( UsageDiagramLegend::Symbol, 8, 8 ); ed.setBoxShape( UsageDiagramLegend::Symbol, 100, Qt::RelativeSize ); // a circle - ed.setGradient( UsageDiagramLegend::Symbol | UsageDiagramLegend::Water, {"#6776ff"} ); - ed.setGradient( UsageDiagramLegend::Symbol | UsageDiagramLegend::Electricity, {"#ff3122"} ); - ed.setGradient( UsageDiagramLegend::Symbol | UsageDiagramLegend::Gas, {"#ff7d34"} ); + ed.setGradient( UsageDiagramLegend::Symbol | UsageDiagramLegend::Water, { 0xff6776ff } ); + ed.setGradient( UsageDiagramLegend::Symbol | UsageDiagramLegend::Electricity, { 0xffff3122 } ); + ed.setGradient( UsageDiagramLegend::Symbol | UsageDiagramLegend::Gas, { 0xffff7d34 } ); ed.setPadding( UsageDiagramBox::Panel, 0 ); // new diagram: - ed.setColor( Diagram::ChartArea1, "#666776ff" ); - ed.setColor( Diagram::ChartArea2, "#66ff3122" ); - ed.setColor( Diagram::ChartArea3, "#66ff7d34" ); + ed.setColor( Diagram::ChartArea1, 0x666776ff ); + ed.setColor( Diagram::ChartArea2, 0x66ff3122 ); + ed.setColor( Diagram::ChartArea3, 0x66ff7d34 ); ed.setColor( Diagram::ChartBar1, 0xff6776ff ); ed.setColor( Diagram::ChartBar2, 0xffff3122 ); @@ -197,30 +233,25 @@ void Skin::initHints( const Palette& palette ) ed.setBoxShape( LightDisplay::Panel, 100, Qt::RelativeSize ); ed.setArcMetrics( LightDisplay::ColdAndWarmArc, 8.785, 0, 180 ); - QskGradient coldGradient( Qt::Horizontal, { { 0.0, 0xffff3122 }, - { 0.2, 0xfffeeeb7 }, - { 0.3, 0xffa7b0ff }, - { 0.5, 0xff6776ff }, - { 1.0, Qt::black } } ); - ed.setGradient( LightDisplay::ColdAndWarmArc, coldGradient ); ed.setMetric( LightDisplay::Tickmarks, 1 ); ed.setArcMetrics( LightDisplay::Tickmarks, { 4.69, 0, 180 } ); - ed.setColor( LightDisplay::Tickmarks, 0x55929CB2 ); + ed.setColor( LightDisplay::Tickmarks, 0x55929cb2 ); ed.setFontRole( LightDisplay::ValueText, QskSkin::LargeFont ); - ed.setColor( LightDisplay::ValueText, "#929cb2" ); + ed.setColor( LightDisplay::ValueText, 0xff929cb2 ); ed.setStrutSize( LightDisplay::Knob, { 20, 20 } ); ed.setBoxBorderMetrics( LightDisplay::Knob, 1 ); - ed.setBoxBorderColors( LightDisplay::Knob, 0xffc4c4c4 ); ed.setBoxShape( LightDisplay::Knob, 100, Qt::RelativeSize ); // palette dependent skin hints: ed.setGradient( MenuBar::Panel, palette.menuBar ); ed.setGradient( DashboardPage::Panel, palette.mainContent ); + ed.setGradient( RoomsPage::Panel, palette.mainContent ); + ed.setGradient( StoragePage::Panel, palette.mainContent ); - ed.setColor( Box::Panel, palette.box.startColor() ); + ed.setColor( Box::Panel, palette.box ); QskShadowMetrics shadowMetrics( 0, 10 ); ed.setShadowMetrics( Box::Panel, shadowMetrics ); ed.setShadowColor( Box::Panel, palette.shadow ); @@ -235,9 +266,80 @@ void Skin::initHints( const Palette& palette ) ed.setGradient( LightDisplay::Panel, palette.box ); ed.setGradient( LightDisplay::Knob, palette.box ); + ed.setGradient( LightDisplay::ColdAndWarmArc, palette.lightDisplayColdAndWarmArc ); + ed.setBoxBorderColors( LightDisplay::Knob, palette.lightDisplayKnobBorder ); + ed.setShadowMetrics( LightDisplay::Groove, { 0, 20 } ); + ed.setShadowColor( LightDisplay::Groove, palette.shadow ); + ed.setGradient( LightDisplay::Groove, palette.box ); + ed.setBoxShape( LightDisplay::Groove, 100, Qt::RelativeSize ); + ed.setGradient( RoundButton::Panel, palette.roundButton ); + ed.setGradient( RoundButton::Panel | QskAbstractButton::Pressed, palette.roundButtonPressed, + { QskStateCombination::CombinationNoState, RoundButton::Top } ); + ed.setAnimation( RoundButton::Panel | QskAspect::Color, 100 ); + ed.setBoxBorderColors( UsageDiagramBox::DaysBox, palette.weekdayBox ); ed.setColor( QskTextLabel::Text, palette.text ); ed.setColor( UsageDiagramBox::DayText, palette.text ); - ed.setGradient( CircularProgressBar::Groove, palette.circularProgressBarGroove ); + + auto grooveGradient = palette.circularProgressBarGroove; + grooveGradient.setDirection( QskGradient::Linear ); + ed.setGradient( CircularProgressBar::Groove, grooveGradient ); + + // storage bar + { + const auto make_gradient = []( const QColor color ) -> QskGradient { + return { color.lighter(), color }; + }; + ed.setGradient( StorageBar::Pictures, make_gradient( "#FFBE0B" ) ); + ed.setGradient( StorageBar::Music, make_gradient( "#FB5607" ) ); + ed.setGradient( StorageBar::Videos, make_gradient( "#FF006E" ) ); + ed.setGradient( StorageBar::Documents, make_gradient( "#8338EC" ) ); + ed.setGradient( StorageBar::Others, make_gradient( "#3A86FF" ) ); + ed.setGradient( StorageBar::Free, make_gradient( "lightgray" ) ); + } + + // storage meter + { + ed.setGradient( StorageMeter::Status, + { { { 0.00, "#00ff00" }, { 0.33, "#00ff00" }, { 0.33, "#ffaf00" }, { 0.66, "#ffaf00" }, + { 0.66, "#ff0000" }, { 1.00, "#ff0000" } } } ); + } +} + +Skin::Palette DaytimeSkin::palette() const +{ + return { + 0xff6d7bfb, + 0xfffbfbfb, + Qt::white, + 0xfff7f7f7, + 0xffe5e5e5, + 0xfff4f4f4, + Qt::black, + 0xffe5e5e5, + 0xffc4c4c4, + { { { 0.0, 0xffff3122 }, { 0.2, 0xfffeeeb7 }, { 0.3, 0xffa7b0ff }, { 0.5, 0xff6776ff }, + { 1.0, Qt::black } } }, + { { { 0.0, 0xffc4c4c4 }, { 0.5, 0xfff8f8f8 }, { 1.0, 0xffc4c4c4 } } }, + 0xffdddddd, + }; +} + +Skin::Palette NighttimeSkin::palette() const +{ + return { + 0xff2937A7, + 0xff040404, + Qt::black, + 0xff0a0a0a, + 0xff1a1a1a, + 0xff0c0c0c, + Qt::white, + 0xff4a4a4a, + 0xff555555, + { { { 0.0, 0xff991100 }, { 0.2, 0xff9a7a57 }, { 0.5, 0xff3726af }, { 1.0, Qt::black } } }, + { { { 0.0, 0xff666666 }, { 0.5, 0xff222222 }, { 1.0, 0xff333333 } } }, + 0xff222222, + }; } diff --git a/examples/iotdashboard/Skin.h b/examples/iotdashboard/Skin.h index 1845fa96..74e8f69b 100644 --- a/examples/iotdashboard/Skin.h +++ b/examples/iotdashboard/Skin.h @@ -11,34 +11,20 @@ class Skin : public QskSkin { public: - class Palette + struct Palette { - public: - Palette( const QskGradient& menuBar, const QskGradient& mainContent, - const QskGradient& box, const QColor& lightDisplay, - const QskGradient& roundButton, const QColor& weekdayBox, - const QColor& text, const QColor& shadow, - const QskGradient& circularProgressBarGroove ) - : menuBar( menuBar ) - , mainContent( mainContent ) - , box( box ) - , lightDisplay( lightDisplay ) - , roundButton( roundButton ) - , weekdayBox( weekdayBox ) - , text( text ) - , shadow( shadow ) - , circularProgressBarGroove( circularProgressBarGroove ) - { - } - QskGradient menuBar; - QskGradient mainContent; - QskGradient box; - QColor lightDisplay; - QskGradient roundButton; + QColor menuBar; + QColor mainContent; + QColor box; + QColor roundButton; + QColor roundButtonPressed; QColor weekdayBox; QColor text; QColor shadow; + QColor lightDisplayKnobBorder; + QskGradient lightDisplayColdAndWarmArc; QskGradient circularProgressBarGroove; + QRgb deviceGraphic; }; Skin( const Palette& palette, QObject* parent = nullptr ); @@ -57,24 +43,22 @@ class DaytimeSkin : public Skin { public: DaytimeSkin( QObject* parent = nullptr ) - : Skin( - Skin::Palette( {"#6D7BFB"}, {"#fbfbfb"}, {"#ffffff"}, - "#ffffff", {"#f7f7f7"}, {"#f4f4f4"}, Qt::black, 0xffe5e5e5, - { QskGradient::Vertical, { { 0.0, 0xffc4c4c4 }, { 0.5, 0xfff8f8f8 }, { 1.0, 0xffc4c4c4 } } } ) - , parent ) + : Skin( palette(), parent ) { } + + private: + Palette palette() const; }; class NighttimeSkin : public Skin { public: NighttimeSkin( QObject* parent = nullptr ) - : Skin( - Skin::Palette( {"#2937A7"}, {"#040404"}, {"#000000"}, - "#000000", {"#0a0a0a"}, {"#0c0c0c"}, Qt::white, 0xff1a1a1a, - { QskGradient::Vertical, { { 0.0, 0xff666666 }, { 0.5, 0xff222222 }, { 1.0, 0xff333333 } } } ) - , parent ) + : Skin( palette(), parent ) { } + + private: + Palette palette() const; }; diff --git a/examples/iotdashboard/StatisticsPage.cpp b/examples/iotdashboard/StatisticsPage.cpp new file mode 100644 index 00000000..2153efef --- /dev/null +++ b/examples/iotdashboard/StatisticsPage.cpp @@ -0,0 +1,21 @@ +/****************************************************************************** + * Copyright (C) 2021 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "StatisticsPage.h" + +#include +#include + +QSK_SUBCONTROL( StatisticsPage, Panel ) + +StatisticsPage::StatisticsPage( QQuickItem* parent ) + : QskLinearBox( Qt::Vertical, parent ) +{ + auto* const textLabel = new QskTextLabel( "statistics page", this ); + textLabel->setAlignmentHint( QskTextLabel::Text, Qt::AlignCenter ); + textLabel->setFontRole( QskSkin::HugeFont ); +} + +#include "moc_StatisticsPage.cpp" diff --git a/examples/iotdashboard/StatisticsPage.h b/examples/iotdashboard/StatisticsPage.h new file mode 100644 index 00000000..8224e096 --- /dev/null +++ b/examples/iotdashboard/StatisticsPage.h @@ -0,0 +1,19 @@ +/****************************************************************************** + * Copyright (C) 2021 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#pragma once + +#include +#include + +class StatisticsPage : public QskLinearBox +{ + Q_OBJECT + + public: + QSK_SUBCONTROLS( Panel ) + + StatisticsPage( QQuickItem* parent ); +}; diff --git a/examples/iotdashboard/StorageBar.cpp b/examples/iotdashboard/StorageBar.cpp new file mode 100644 index 00000000..de29b94b --- /dev/null +++ b/examples/iotdashboard/StorageBar.cpp @@ -0,0 +1,120 @@ +/****************************************************************************** + * Copyright (C) 2022 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "StorageBar.h" +#include +#include +#include +#include + +QSK_SUBCONTROL( StorageBar, Pictures ) +QSK_SUBCONTROL( StorageBar, Music ) +QSK_SUBCONTROL( StorageBar, Videos ) +QSK_SUBCONTROL( StorageBar, Documents ) +QSK_SUBCONTROL( StorageBar, Others ) +QSK_SUBCONTROL( StorageBar, Free ) + +using S = StorageBar; + +StorageBar::StorageBar( QskQuickItem* const parent ) + : Inherited( parent ) +{ + static constexpr qreal size = 16.0; + static constexpr qreal radius = size / 2.0; + + setMinimumSize( -1, size ); + setMaximumSize( -1, size ); + + setBoxShapeHint( S::Pictures, { radius, 0.0, radius, 0.0 } ); + setBoxShapeHint( S::Free, { 0.0, radius, 0.0, radius } ); +} + +qreal StorageBar::pictures() const +{ + return m_pictures; +} + +void StorageBar::setPictures( qreal newPictures ) +{ + if ( qFuzzyCompare( m_pictures, newPictures ) ) + { + return; + } + + m_pictures = newPictures; + Q_EMIT picturesChanged( m_pictures ); + update(); +} + +qreal StorageBar::music() const +{ + return m_music; +} + +void StorageBar::setMusic( qreal newMusic ) +{ + if ( qFuzzyCompare( m_music, newMusic ) ) + { + return; + } + m_music = newMusic; + Q_EMIT musicChanged( m_music ); + update(); +} + +qreal StorageBar::videos() const +{ + return m_videos; +} + +void StorageBar::setVideos( qreal newVideos ) +{ + if ( qFuzzyCompare( m_videos, newVideos ) ) + { + return; + } + m_videos = newVideos; + Q_EMIT videosChanged( m_videos ); + update(); +} + +qreal StorageBar::documents() const +{ + return m_documents; +} + +void StorageBar::setDocuments( qreal newDocuments ) +{ + if ( qFuzzyCompare( m_documents, newDocuments ) ) + { + return; + } + m_documents = newDocuments; + Q_EMIT documentsChanged( m_documents ); + update(); +} + +qreal StorageBar::others() const +{ + return m_others; +} + +void StorageBar::setOthers( qreal newOthers ) +{ + if ( qFuzzyCompare( m_others, newOthers ) ) + { + return; + } + m_others = newOthers; + Q_EMIT othersChanged( m_others ); + update(); +} + +qreal StorageBar::free() const +{ + return 1.0 - m_pictures - m_music - m_videos - m_documents - m_others; +} + +#include "moc_StorageBar.cpp" diff --git a/examples/iotdashboard/StorageBar.h b/examples/iotdashboard/StorageBar.h new file mode 100644 index 00000000..6d6dddf0 --- /dev/null +++ b/examples/iotdashboard/StorageBar.h @@ -0,0 +1,51 @@ +/****************************************************************************** + * Copyright (C) 2022 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#pragma once + +#include + +class StorageBar final : public QskControl +{ + using Inherited = QskControl; + + Q_OBJECT + Q_PROPERTY( qreal pictures READ pictures WRITE setPictures NOTIFY picturesChanged ) + Q_PROPERTY( qreal music READ music WRITE setMusic NOTIFY musicChanged ) + Q_PROPERTY( qreal videos READ videos WRITE setVideos NOTIFY videosChanged ) + Q_PROPERTY( qreal documents READ documents WRITE setDocuments NOTIFY documentsChanged ) + Q_PROPERTY( qreal others READ others WRITE setOthers NOTIFY othersChanged ) + + public: + QSK_SUBCONTROLS( Pictures, Music, Videos, Documents, Others, Free ) + explicit StorageBar( QskQuickItem* parent = nullptr ); + + qreal pictures() const; + void setPictures( qreal newPictures ); + qreal music() const; + void setMusic( qreal newMusic ); + qreal videos() const; + void setVideos( qreal newVideos ); + qreal documents() const; + void setDocuments( qreal newDocuments ); + qreal others() const; + void setOthers( qreal newOthers ); + qreal free() const; + + Q_SIGNALS: + + void picturesChanged( qreal value ); + void musicChanged( qreal value ); + void videosChanged( qreal value ); + void documentsChanged( qreal value ); + void othersChanged( qreal value ); + + private: + qreal m_pictures{ 0.0 }; + qreal m_music{ 0.0 }; + qreal m_videos{ 0.0 }; + qreal m_documents{ 0.0 }; + qreal m_others{ 0.0 }; +}; diff --git a/examples/iotdashboard/StorageBarSkinlet.cpp b/examples/iotdashboard/StorageBarSkinlet.cpp new file mode 100644 index 00000000..1d65c516 --- /dev/null +++ b/examples/iotdashboard/StorageBarSkinlet.cpp @@ -0,0 +1,107 @@ +/****************************************************************************** + * Copyright (C) 2022 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "StorageBarSkinlet.h" +#include "StorageBar.h" + +using S = StorageBar; + +StorageBarSkinlet::StorageBarSkinlet( QskSkin* skin ) + : Inherited( skin ) +{ + setNodeRoles( { Pictures, Music, Videos, Documents, Others, Free } ); +} + +QRectF StorageBarSkinlet::subControlRect( const QskSkinnable* skinnable, const QRectF& contentsRect, + QskAspect::Subcontrol subControl ) const +{ + const auto* const bar = static_cast< const S* >( skinnable ); + + auto x = contentsRect.x(); + const auto y = contentsRect.y(); + const auto w = contentsRect.width(); + const auto h = contentsRect.height(); + + // segement widths + const auto p = w * bar->pictures(); + const auto m = w * bar->music(); + const auto v = w * bar->videos(); + const auto d = w * bar->documents(); + const auto o = w * bar->others(); + const auto f = w * bar->free(); + + if ( subControl == S::Pictures ) + { + return { x, y, p, h }; + } + x += p; + + if ( subControl == S::Music ) + { + return { x, y, m, h }; + } + x += m; + + if ( subControl == S::Videos ) + { + return { x, y, v, h }; + } + x += v; + + if ( subControl == S::Documents ) + { + return { x, y, d, h }; + } + x += d; + + if ( subControl == S::Others ) + { + return { x, y, o, h }; + } + x += o; + + if ( subControl == S::Free ) + { + return { x, y, f, h }; + } + + return Inherited::subControlRect( skinnable, contentsRect, subControl ); +} + +namespace +{ + inline QSGNode* updateSegmentBoxNode( + const S* const skinnable, const QskAspect::Subcontrol& subcontrol, QSGNode* const node ) + { + return QskSkinlet::updateBoxNode( skinnable, node, skinnable->subControlRect( subcontrol ), + skinnable->gradientHint( subcontrol ), subcontrol ); + } +} + +QSGNode* StorageBarSkinlet::updateSubNode( + const QskSkinnable* const skinnable, const quint8 nodeRole, QSGNode* const node ) const +{ + const auto* const bar = static_cast< const S* >( skinnable ); + + switch ( nodeRole ) + { + case Pictures: + return updateSegmentBoxNode( bar, S::Pictures, node ); + case Music: + return updateSegmentBoxNode( bar, S::Music, node ); + case Videos: + return updateSegmentBoxNode( bar, S::Videos, node ); + case Documents: + return updateSegmentBoxNode( bar, S::Documents, node ); + case Others: + return updateSegmentBoxNode( bar, S::Others, node ); + case Free: + return updateSegmentBoxNode( bar, S::Free, node ); + default: + return Inherited::updateSubNode( skinnable, nodeRole, node ); + } +} + +#include "moc_StorageBarSkinlet.cpp" diff --git a/examples/iotdashboard/StorageBarSkinlet.h b/examples/iotdashboard/StorageBarSkinlet.h new file mode 100644 index 00000000..1731d100 --- /dev/null +++ b/examples/iotdashboard/StorageBarSkinlet.h @@ -0,0 +1,32 @@ +/****************************************************************************** + * Copyright (C) 2022 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#pragma once + +#include + +class StorageBarSkinlet final : public QskSkinlet +{ + Q_GADGET + using Inherited = QskSkinlet; + + public: + enum NodeRole + { + Pictures, + Music, + Videos, + Documents, + Others, + Free + }; + Q_INVOKABLE StorageBarSkinlet( QskSkin* skin = nullptr ); + + private: + QRectF subControlRect( const QskSkinnable* skinnable, const QRectF& contentsRect, + QskAspect::Subcontrol subControl ) const override; + QSGNode* updateSubNode( + const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const override; +}; diff --git a/examples/iotdashboard/StorageMeter.cpp b/examples/iotdashboard/StorageMeter.cpp new file mode 100644 index 00000000..f1a59a30 --- /dev/null +++ b/examples/iotdashboard/StorageMeter.cpp @@ -0,0 +1,65 @@ +/****************************************************************************** + * Copyright (C) 2022 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "StorageMeter.h" +#include "CircularProgressBar.h" +#include +#include + +QSK_SUBCONTROL( StorageMeter, Status ) + +namespace +{ + inline QString make_text( const QLocale& locale, const qreal value ) + { + return locale.toString( static_cast< int >( value ) ) + " " + locale.percent(); + } +} + +StorageMeter::StorageMeter( QQuickItem* parent ) noexcept + : CircularProgressBar( parent ) + , label( new QskTextLabel( this ) ) +{ + setAutoLayoutChildren( true ); + setSizePolicy( QskSizePolicy::Preferred, QskSizePolicy::Constrained ); + + label->setText( make_text( locale(), value() ) ); + label->setSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Fixed ); + label->setLayoutAlignmentHint( Qt::AlignCenter ); + label->setFontRole( QskSkin::SmallFont ); +} + +void StorageMeter::setValue( const qreal value ) +{ + const auto gradient = gradientHint( StorageMeter::Status ); + const auto color = gradient.extracted( value / 100.0, value / 100.0 ).startColor(); + setGradientHint( StorageMeter::Bar, { color, color.lighter() } ); + CircularProgressBar::setValue( value ); + label->setTextColor( color ); + label->setText( make_text( locale(), value ) ); +} + +QSizeF StorageMeter::contentsSizeHint( Qt::SizeHint which, const QSizeF& constraint ) const +{ + if ( which != Qt::PreferredSize ) + return QSizeF(); + + qreal size; + + if ( constraint.width() > 0 ) + { + size = constraint.width(); + } + else if ( constraint.height() > 0 ) + { + size = constraint.height(); + } + else + { + size = 57; + } + + return QSizeF( size, size ); +} diff --git a/examples/iotdashboard/StorageMeter.h b/examples/iotdashboard/StorageMeter.h new file mode 100644 index 00000000..402f5c45 --- /dev/null +++ b/examples/iotdashboard/StorageMeter.h @@ -0,0 +1,22 @@ +/****************************************************************************** + * Copyright (C) 2022 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#pragma once + +#include "CircularProgressBar.h" +#include + +class StorageMeter final : public CircularProgressBar +{ + public: + QSK_SUBCONTROLS( Status ) + explicit StorageMeter( QQuickItem* parent = nullptr ) noexcept; + public Q_SLOTS: + void setValue( qreal value ); + + private: + QSizeF contentsSizeHint( Qt::SizeHint which, const QSizeF& constraint ) const override; + class QskTextLabel* label = nullptr; +}; diff --git a/examples/iotdashboard/StoragePage.cpp b/examples/iotdashboard/StoragePage.cpp new file mode 100644 index 00000000..f545d10f --- /dev/null +++ b/examples/iotdashboard/StoragePage.cpp @@ -0,0 +1,144 @@ +/****************************************************************************** + * Copyright (C) 2022 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "StoragePage.h" +#include "Box.h" +#include "CircularProgressBar.h" +#include "Diagram.h" +#include "EnergyMeter.h" +#include "StorageBar.h" +#include "StorageMeter.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QSK_SUBCONTROL( StoragePage, Panel ) + +struct StorageRowAnimator final : public QObject, public QskAnimator +{ + explicit StorageRowAnimator( QQuickWindow* const window, QObject* parent ) + : QObject( parent ) + { + setEasingCurve( QEasingCurve::InQuad ); + setWindow( window ); + setDuration( 400 ); + } + + void advance( qreal value ) override + { + callback( value ); + } + + std::function< void( qreal ) > callback = []( qreal ) {}; +}; + +StoragePage::StoragePage( QQuickItem* const parent ) + : QskLinearBox( Qt::Vertical, parent ) +{ + setPanel( true ); + setSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Expanding ); + setDefaultAlignment( Qt::AlignTop ); + setSpacing( 24 ); + setMargins( 30 ); + setSizePolicy( Qt::Horizontal, QskSizePolicy::Minimum ); + + setSubcontrolProxy( QskBox::Panel, StoragePage::Panel ); + + addRow( "Backup (B:)", "Used for daily backups", 0.1, 0.1, 0.1, 0.02, 0.01 ); + addRow( "Share (S:)", "Used for sharing files publicly", 0.05, 0.05, 0.2, 0.2, 0.01 ); + addRow( "Exchange (X:)", "Used for exchanging large files", 0.1, 0.1, 0.1, 0.1, 0.5 ); + + addSpacer( 1, 99 ); +} + +void StoragePage::addRow( const QString& title, const QString& description, + qreal pictures, qreal music, qreal videos, qreal documents, qreal others ) +{ + Storage storage; + + storage.title = title; + storage.description = description; + storage.distribution.pictures = pictures; + storage.distribution.music = music; + storage.distribution.videos = videos; + storage.distribution.documents = documents; + storage.distribution.others = others; + + auto* const box = new Box( "Network Storage", this ); + auto* const layout = new QskLinearBox( Qt::Horizontal, box ); + auto* const left = new QskLinearBox( Qt::Vertical, layout ); + left->setDefaultAlignment( Qt::AlignCenter ); + auto* const center = new QskLinearBox( Qt::Vertical, layout ); + left->setDefaultAlignment( Qt::AlignLeft ); + auto* const right = new QskLinearBox( Qt::Vertical, layout ); + layout->setStretchFactor( left, 1 ); + layout->setStretchFactor( center, 2 ); + layout->setStretchFactor( right, 5 ); + + const auto percent = 100.0 * ( 1.0 - storage.distribution.free() ); + auto* const meter = new StorageMeter( left ); + meter->setValue( percent ); + meter->setMinimumSize( 64, 64 ); + meter->setMaximumSize( 64, 64 ); + + auto* const maintitle = new QskTextLabel( storage.title, center ); + maintitle->setFontRole( QskSkin::LargeFont ); + auto* const subtitle = new QskTextLabel( storage.description, center ); + subtitle->setFontRole( QskSkin::MediumFont ); + + const auto& media = storage.distribution; + + auto* const bar = new StorageBar( right ); + bar->setPictures( media.pictures ); + bar->setMusic( media.music ); + bar->setVideos( media.videos ); + bar->setDocuments( media.documents ); + bar->setOthers( media.others ); + + auto* const legend = new QskLinearBox( Qt::Horizontal, right ); + legend->setSpacing( 12 ); + legend->addSpacer( 1, 999 ); + auto* const sync = new QskPushButton( "Update", legend ); + sync->setFontRoleHint( QskPushButton::Text, QskSkin::SmallFont ); + + using S = StorageBar; + for ( const auto& pair : QVector< QPair< QString, QskGradient > >{ + { QStringLiteral( "Picture" ), bar->gradientHint( S::Pictures ) }, + { QStringLiteral( "Music" ), bar->gradientHint( S::Music ) }, + { QStringLiteral( "Videos" ), bar->gradientHint( S::Videos ) }, + { QStringLiteral( "Documents" ), bar->gradientHint( S::Documents ) }, + { QStringLiteral( "Others" ), bar->gradientHint( S::Others ) }, + { QStringLiteral( "Free" ), bar->gradientHint( S::Free ) } } ) + { + constexpr int size = 8; + auto* const dot = new QskBox( legend ); + dot->setBoxShapeHint( QskBox::Panel, { size / 2 } ); + dot->setMinimumSize( size, size ); + dot->setMaximumSize( size, size ); + dot->setGradientHint( QskBox::Panel, pair.second ); + auto* const label = new QskTextLabel( pair.first, legend ); + label->setFontRole( QskSkin::SmallFont ); + } + + auto* const animator = new StorageRowAnimator( window(), sync ); + animator->callback = [ meter, bar, media ]( qreal v ) { + meter->setValue( 100 * ( 1.0 - media.free() ) * v ); + bar->setPictures( media.pictures * v ); + bar->setMusic( media.music * v ); + bar->setVideos( media.videos * v ); + bar->setDocuments( media.documents * v ); + bar->setOthers( media.others * v ); + }; + connect( sync, &QskPushButton::clicked, animator, [ animator ]() { animator->start(); } ); +} diff --git a/examples/iotdashboard/StoragePage.h b/examples/iotdashboard/StoragePage.h new file mode 100644 index 00000000..0d189260 --- /dev/null +++ b/examples/iotdashboard/StoragePage.h @@ -0,0 +1,50 @@ +/****************************************************************************** + * Copyright (C) 2022 Edelhirsch Software GmbH + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#pragma once + +#include +#include +#include + +class QQuickItem; + +class StoragePage final : public QskLinearBox +{ + public: + QSK_SUBCONTROLS( Panel ) + explicit StoragePage( QQuickItem* parent = nullptr ); + + private: + struct Storage + { + struct Media + { + qreal pictures = 0; + qreal music = 0; + qreal videos = 0; + qreal documents = 0; + qreal others = 0; + + inline constexpr bool operator==( const Media& rhs ) const noexcept + { + return pictures == rhs.pictures && music == rhs.music && videos == rhs.videos && + documents == rhs.documents && others == rhs.others; + } + + inline constexpr qreal free() const noexcept + { + return 1.0 - pictures - music - videos - documents - others; + } + }; + + QString title; + QString description; + Media distribution; + }; + + void addRow( const QString& title, const QString& description, + qreal pictures, qreal music, qreal videos, qreal documents, qreal others ); +}; diff --git a/examples/iotdashboard/TopBar.cpp b/examples/iotdashboard/TopBar.cpp index 50da3d05..b73f6856 100644 --- a/examples/iotdashboard/TopBar.cpp +++ b/examples/iotdashboard/TopBar.cpp @@ -4,7 +4,7 @@ *****************************************************************************/ #include "TopBar.h" -#include "PieChartPainted.h" +#include "EnergyMeter.h" #include #include @@ -62,7 +62,7 @@ TopBarItem::TopBarItem( const auto subcontrol = subcontrolForIndex( index ); const auto textColor = color( subcontrol | QskAspect::TextColor ); - auto pieChart = new PieChartPainted( + auto pieChart = new EnergyMeter( textColor, gradient, progress, pieChartAndDisplay ); pieChart->setSizePolicy( Qt::Horizontal, QskSizePolicy::Constrained ); diff --git a/examples/iotdashboard/images.qrc b/examples/iotdashboard/images.qrc index 38a9148a..8026b261 100644 --- a/examples/iotdashboard/images.qrc +++ b/examples/iotdashboard/images.qrc @@ -1,20 +1,20 @@ - - images/main-icon.svg - images/dashboard.svg - images/rooms.svg - images/devices.svg - images/statistics.svg - images/storage.svg - images/members.svg - images/logout.svg - images/indoor-temperature.svg - images/humidity.svg - images/up.svg - images/down.svg - images/lamps.svg - images/music-system.svg - images/ac.svg - images/router.svg + + images/qvg/main-icon.qvg + images/qvg/dashboard.qvg + images/qvg/rooms.qvg + images/qvg/devices.qvg + images/qvg/statistics.qvg + images/qvg/storage.qvg + images/qvg/members.qvg + images/qvg/logout.qvg + images/qvg/indoor-temperature.qvg + images/qvg/humidity.qvg + images/qvg/up.qvg + images/qvg/down.qvg + images/qvg/lamps.qvg + images/qvg/music-system.qvg + images/qvg/ac.qvg + images/qvg/router.qvg diff --git a/examples/iotdashboard/images/ac.png b/examples/iotdashboard/images/ac.png deleted file mode 100644 index 51a75aa9..00000000 Binary files a/examples/iotdashboard/images/ac.png and /dev/null differ diff --git a/examples/iotdashboard/images/dashboard.png b/examples/iotdashboard/images/dashboard.png deleted file mode 100644 index 126322f0..00000000 Binary files a/examples/iotdashboard/images/dashboard.png and /dev/null differ diff --git a/examples/iotdashboard/images/devices.png b/examples/iotdashboard/images/devices.png deleted file mode 100644 index a84d68bf..00000000 Binary files a/examples/iotdashboard/images/devices.png and /dev/null differ diff --git a/examples/iotdashboard/images/down.png b/examples/iotdashboard/images/down.png deleted file mode 100644 index 8c321ad1..00000000 Binary files a/examples/iotdashboard/images/down.png and /dev/null differ diff --git a/examples/iotdashboard/images/humidity.png b/examples/iotdashboard/images/humidity.png deleted file mode 100644 index d280cf4e..00000000 Binary files a/examples/iotdashboard/images/humidity.png and /dev/null differ diff --git a/examples/iotdashboard/images/indoor-temperature.png b/examples/iotdashboard/images/indoor-temperature.png deleted file mode 100644 index 1823aade..00000000 Binary files a/examples/iotdashboard/images/indoor-temperature.png and /dev/null differ diff --git a/examples/iotdashboard/images/lamps.png b/examples/iotdashboard/images/lamps.png deleted file mode 100644 index 91536c1b..00000000 Binary files a/examples/iotdashboard/images/lamps.png and /dev/null differ diff --git a/examples/iotdashboard/images/logout.png b/examples/iotdashboard/images/logout.png deleted file mode 100644 index 98d7a580..00000000 Binary files a/examples/iotdashboard/images/logout.png and /dev/null differ diff --git a/examples/iotdashboard/images/members.png b/examples/iotdashboard/images/members.png deleted file mode 100644 index f408bb70..00000000 Binary files a/examples/iotdashboard/images/members.png and /dev/null differ diff --git a/examples/iotdashboard/images/music-system.png b/examples/iotdashboard/images/music-system.png deleted file mode 100644 index 2df28477..00000000 Binary files a/examples/iotdashboard/images/music-system.png and /dev/null differ diff --git a/examples/iotdashboard/images/qvg/ac.qvg b/examples/iotdashboard/images/qvg/ac.qvg new file mode 100644 index 00000000..56ba5116 Binary files /dev/null and b/examples/iotdashboard/images/qvg/ac.qvg differ diff --git a/examples/iotdashboard/images/qvg/dashboard.qvg b/examples/iotdashboard/images/qvg/dashboard.qvg new file mode 100644 index 00000000..85ae5e05 Binary files /dev/null and b/examples/iotdashboard/images/qvg/dashboard.qvg differ diff --git a/examples/iotdashboard/images/qvg/devices.qvg b/examples/iotdashboard/images/qvg/devices.qvg new file mode 100644 index 00000000..aefe5935 Binary files /dev/null and b/examples/iotdashboard/images/qvg/devices.qvg differ diff --git a/examples/iotdashboard/images/qvg/down.qvg b/examples/iotdashboard/images/qvg/down.qvg new file mode 100644 index 00000000..71e37f4e Binary files /dev/null and b/examples/iotdashboard/images/qvg/down.qvg differ diff --git a/examples/iotdashboard/images/qvg/humidity.qvg b/examples/iotdashboard/images/qvg/humidity.qvg new file mode 100644 index 00000000..f71f332c Binary files /dev/null and b/examples/iotdashboard/images/qvg/humidity.qvg differ diff --git a/examples/iotdashboard/images/qvg/indoor-temperature.qvg b/examples/iotdashboard/images/qvg/indoor-temperature.qvg new file mode 100644 index 00000000..34d9e6d5 Binary files /dev/null and b/examples/iotdashboard/images/qvg/indoor-temperature.qvg differ diff --git a/examples/iotdashboard/images/qvg/lamps.qvg b/examples/iotdashboard/images/qvg/lamps.qvg new file mode 100644 index 00000000..42adabc6 Binary files /dev/null and b/examples/iotdashboard/images/qvg/lamps.qvg differ diff --git a/examples/iotdashboard/images/qvg/logout.qvg b/examples/iotdashboard/images/qvg/logout.qvg new file mode 100644 index 00000000..9213e3c3 Binary files /dev/null and b/examples/iotdashboard/images/qvg/logout.qvg differ diff --git a/examples/iotdashboard/images/qvg/main-icon.qvg b/examples/iotdashboard/images/qvg/main-icon.qvg new file mode 100644 index 00000000..067c743a Binary files /dev/null and b/examples/iotdashboard/images/qvg/main-icon.qvg differ diff --git a/examples/iotdashboard/images/qvg/members.qvg b/examples/iotdashboard/images/qvg/members.qvg new file mode 100644 index 00000000..b6170fdc Binary files /dev/null and b/examples/iotdashboard/images/qvg/members.qvg differ diff --git a/examples/iotdashboard/images/qvg/music-system.qvg b/examples/iotdashboard/images/qvg/music-system.qvg new file mode 100644 index 00000000..365c6090 Binary files /dev/null and b/examples/iotdashboard/images/qvg/music-system.qvg differ diff --git a/examples/iotdashboard/images/qvg/rooms.qvg b/examples/iotdashboard/images/qvg/rooms.qvg new file mode 100644 index 00000000..d0bb7001 Binary files /dev/null and b/examples/iotdashboard/images/qvg/rooms.qvg differ diff --git a/examples/iotdashboard/images/qvg/router.qvg b/examples/iotdashboard/images/qvg/router.qvg new file mode 100644 index 00000000..5d413904 Binary files /dev/null and b/examples/iotdashboard/images/qvg/router.qvg differ diff --git a/examples/iotdashboard/images/qvg/statistics.qvg b/examples/iotdashboard/images/qvg/statistics.qvg new file mode 100644 index 00000000..c43fb548 Binary files /dev/null and b/examples/iotdashboard/images/qvg/statistics.qvg differ diff --git a/examples/iotdashboard/images/qvg/storage.qvg b/examples/iotdashboard/images/qvg/storage.qvg new file mode 100644 index 00000000..1313ee76 Binary files /dev/null and b/examples/iotdashboard/images/qvg/storage.qvg differ diff --git a/examples/iotdashboard/images/qvg/up.qvg b/examples/iotdashboard/images/qvg/up.qvg new file mode 100644 index 00000000..58c142f6 Binary files /dev/null and b/examples/iotdashboard/images/qvg/up.qvg differ diff --git a/examples/iotdashboard/images/rooms.png b/examples/iotdashboard/images/rooms.png deleted file mode 100644 index d193e7a6..00000000 Binary files a/examples/iotdashboard/images/rooms.png and /dev/null differ diff --git a/examples/iotdashboard/images/router.png b/examples/iotdashboard/images/router.png deleted file mode 100644 index 86bd1ca8..00000000 Binary files a/examples/iotdashboard/images/router.png and /dev/null differ diff --git a/examples/iotdashboard/images/statistics.png b/examples/iotdashboard/images/statistics.png deleted file mode 100644 index 15829ec0..00000000 Binary files a/examples/iotdashboard/images/statistics.png and /dev/null differ diff --git a/examples/iotdashboard/images/storage.png b/examples/iotdashboard/images/storage.png deleted file mode 100644 index d9908c96..00000000 Binary files a/examples/iotdashboard/images/storage.png and /dev/null differ diff --git a/examples/iotdashboard/images/up.png b/examples/iotdashboard/images/up.png deleted file mode 100644 index aff671ba..00000000 Binary files a/examples/iotdashboard/images/up.png and /dev/null differ diff --git a/examples/iotdashboard/iotdashboard.pro b/examples/iotdashboard/iotdashboard.pro index 897b38f4..5c7dc598 100644 --- a/examples/iotdashboard/iotdashboard.pro +++ b/examples/iotdashboard/iotdashboard.pro @@ -1,34 +1,36 @@ CONFIG += qskexample -QT += svg -QT += quick_private # TODO: examples should not use private headers - SOURCES += \ Box.cpp \ BoxWithButtons.cpp \ CircularProgressBar.cpp \ CircularProgressBarSkinlet.cpp \ DashboardPage.cpp \ + DevicesPage.cpp \ Diagram.cpp \ DiagramSkinlet.cpp \ + EnergyMeter.cpp \ GraphicProvider.cpp \ GridBox.cpp \ LightDisplaySkinlet.cpp \ LightDisplay.cpp \ MainItem.cpp \ + MainWindow.cpp \ MenuBar.cpp \ + MembersPage.cpp \ MyDevices.cpp \ - PieChart.cpp \ - PieChartPainted.cpp \ - PieChartSkinlet.cpp \ RoomsPage.cpp \ RoundedIcon.cpp \ Skin.cpp \ + StatisticsPage.cpp \ TopBar.cpp \ RoundButton.cpp \ UsageBox.cpp \ UsageDiagram.cpp \ - MainWindow.cpp \ + StoragePage.cpp \ + StorageMeter.cpp \ + StorageBar.cpp \ + StorageBarSkinlet.cpp \ main.cpp \ SOURCES += \ @@ -43,25 +45,30 @@ HEADERS += \ CircularProgressBarSkinlet.h \ Diagram.h \ DiagramSkinlet.h \ + EnergyMeter.h \ GraphicProvider.h \ GridBox.h \ LightDisplaySkinlet.h \ LightDisplay.h \ DashboardPage.h \ + DevicesPage.h \ MainItem.h \ MainWindow.h \ + MembersPage.h \ MenuBar.h \ MyDevices.h \ - PieChart.h \ - PieChartPainted.h \ - PieChartSkinlet.h \ RoomsPage.h \ RoundedIcon.h \ Skin.h \ + StatisticsPage.h \ TopBar.h \ RoundButton.h \ UsageBox.h \ - UsageDiagram.h + UsageDiagram.h \ + StoragePage.h \ + StorageMeter.h \ + StorageBar.h \ + StorageBarSkinlet.h \ HEADERS += \ nodes/DiagramDataNode.h \ diff --git a/examples/layouts/layouts.qml b/examples/layouts/layouts.qml index a06c66ef..48a4dd2a 100644 --- a/examples/layouts/layouts.qml +++ b/examples/layouts/layouts.qml @@ -4,18 +4,18 @@ import Test 1.0 GridBox { - //margins: 10 // only possible with Qt <= 6.1 + //margins: 10 // only possible with Qt <= 6.1 || Qt >= 6.5 margins { left: 10; top: 10; right: 10; bottom: 10 } - autoFillBackground : true + background: + ({ + linear: { x1: 0, y1: 0, x2: 1, y2: 1 }, // diagonal - background - { stops: [ { position: 0.0, color: "Red" }, - { position: 1.0, color: "Yellow" }, + { position: 1.0, color: "Yellow" } ] - } + }) TestRectangle { diff --git a/examples/messageboxQml/messagebox.qml b/examples/messageboxQml/messagebox.qml index 5aa0cc45..6be1eccc 100644 --- a/examples/messageboxQml/messagebox.qml +++ b/examples/messageboxQml/messagebox.qml @@ -26,7 +26,7 @@ Qsk.Window focus: true - sizePolicy.verticalPolicy: Qsk.SizePolicy.MinimumExpanding + sizePolicy.vertical: Qsk.SizePolicy.MinimumExpanding onClicked: { @@ -42,7 +42,7 @@ Qsk.Window id: informationButton text: "Information" - sizePolicy.verticalPolicy: Qsk.SizePolicy.MinimumExpanding + sizePolicy.vertical: Qsk.SizePolicy.MinimumExpanding onClicked: { @@ -57,7 +57,7 @@ Qsk.Window id: questionButton text: "Question" - sizePolicy.verticalPolicy: Qsk.SizePolicy.MinimumExpanding + sizePolicy.vertical: Qsk.SizePolicy.MinimumExpanding onClicked: { @@ -78,7 +78,7 @@ Qsk.Window id: warningButton text: "Warning" - sizePolicy.verticalPolicy: Qsk.SizePolicy.MinimumExpanding + sizePolicy.vertical: Qsk.SizePolicy.MinimumExpanding onClicked: { @@ -93,7 +93,7 @@ Qsk.Window id: criticalButton text: "Critical" - sizePolicy.verticalPolicy: Qsk.SizePolicy.MinimumExpanding + sizePolicy.vertical: Qsk.SizePolicy.MinimumExpanding onClicked: { @@ -108,7 +108,7 @@ Qsk.Window id: selectionButton text: "Selection" - sizePolicy.verticalPolicy: Qsk.SizePolicy.MinimumExpanding + sizePolicy.vertical: Qsk.SizePolicy.MinimumExpanding onClicked: { diff --git a/examples/mycontrols/MySkin.cpp b/examples/mycontrols/MySkin.cpp index 21e21b6b..079178f0 100644 --- a/examples/mycontrols/MySkin.cpp +++ b/examples/mycontrols/MySkin.cpp @@ -191,8 +191,6 @@ namespace editor.setTable( &hintTable() ); editor.setAnimator( 200, QEasingCurve::Linear ); - editor.setGradient( QskAspect::Control, Qt::gray ); - editor.setupFocusIndicator( 2, 3, 6, DarkBlue ); editor.setupBox( 2, 8, DarkCyan, LightCyan ); @@ -215,8 +213,6 @@ namespace editor.setTable( &hintTable() ); editor.setAnimator( 100, QEasingCurve::InQuad ); - editor.setGradient( QskAspect::Control, Qt::gray ); - editor.setupFocusIndicator( 2, 6, 6, Crimson ); editor.setupBox( 4, 30, LightPink, MistyRose ); diff --git a/examples/qvgviewer/MainWindow.cpp b/examples/qvgviewer/MainWindow.cpp index 96c65dde..15843f2c 100644 --- a/examples/qvgviewer/MainWindow.cpp +++ b/examples/qvgviewer/MainWindow.cpp @@ -50,25 +50,25 @@ class GraphicLabel : public QskGraphicLabel QskGradient gradient; if ( on ) { - gradient.setColor( qRgb( 40, 40, 40 ) ); + gradient = QskGradient( qRgb( 40, 40, 40 ) ); setGraphicRole( Inverted ); } else { - gradient.setColor( QskRgb::Wheat ); + gradient = QskGradient( QskRgb::Wheat ); setGraphicRole( Normal ); } const int duration = 500; - const QskGradient oldGradient = background(); + const auto oldGradient = gradientHint( Panel ); setGradientHint( Panel, gradient ); // finally setup a smooth transition manually - startTransition( QskAspect::Control | QskAspect::Color, duration, + startTransition( Panel | QskAspect::Color, duration, QVariant::fromValue( oldGradient ), QVariant::fromValue( gradient ) ); - startTransition( QskGraphicLabel::Graphic | QskAspect::GraphicRole, + startTransition( Graphic | QskAspect::GraphicRole, duration, oldRole, graphicRole() ); } }; diff --git a/examples/qvgviewer/main.cpp b/examples/qvgviewer/main.cpp index 458709e1..d2d55c23 100644 --- a/examples/qvgviewer/main.cpp +++ b/examples/qvgviewer/main.cpp @@ -22,7 +22,7 @@ int main( int argc, char* argv[] ) SkinnyShortcut::enable( SkinnyShortcut::AllShortcuts ); - QskFocusIndicator* focusIndicator = new QskFocusIndicator(); + auto focusIndicator = new QskFocusIndicator(); focusIndicator->setObjectName( "FocusIndicator" ); MainWindow window; diff --git a/playground/dials/DialSkinlet.cpp b/playground/dials/DialSkinlet.cpp index ffee9a37..aa584f28 100644 --- a/playground/dials/DialSkinlet.cpp +++ b/playground/dials/DialSkinlet.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include #include @@ -17,6 +16,7 @@ #include #include #include +#include #include namespace diff --git a/playground/dials/SkinFactory.cpp b/playground/dials/SkinFactory.cpp index 22a3e8d8..dc7300e8 100644 --- a/playground/dials/SkinFactory.cpp +++ b/playground/dials/SkinFactory.cpp @@ -56,8 +56,10 @@ namespace ed.setBoxBorderMetrics( Q::Knob, 2 ); ed.setStrutSize( Q::Knob, 30, 30 ); ed.setBoxShape( Q::Knob, 100, Qt::RelativeSize ); - ed.setGradient( Q::Knob, - QskGradient( QskGradient::Diagonal, rgb2, rgb1 ) ); + + QskGradient gradient( rgb2, rgb1 ); + gradient.setLinearDirection( 0.0, 0.0, 1.0, 1.0 ); + ed.setGradient( Q::Knob, gradient ); ed.setMetric( Q::Needle | QskAspect::Size, 2 ); ed.setMetric( Q::Needle | QskAspect::Margin, 10 ); diff --git a/playground/gradients/GradientQuickShape.cpp b/playground/gradients/GradientQuickShape.cpp new file mode 100644 index 00000000..82ffacc9 --- /dev/null +++ b/playground/gradients/GradientQuickShape.cpp @@ -0,0 +1,195 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "GradientQuickShape.h" + +#include +#include + +QSK_QT_PRIVATE_BEGIN + +#ifndef signals + #define signals Q_SIGNALS +#endif + +#include +#include +#include + +QSK_QT_PRIVATE_END + +namespace +{ + class ShapePath : public QQuickShapePath + { + public: + ShapePath( QObject* parent = nullptr ) + : QQuickShapePath( parent ) + { + setStrokeWidth( 0 ); + } + + void setRect( const QRectF& rect ) + { + auto& path = QQuickShapePathPrivate::get( this )->_path; + + path.clear(); + path.addRect( rect ); + } + + void setGradient( const QRectF& rect, const QskGradient& gradient ) + { + auto d = QQuickShapePathPrivate::get( this ); + + delete d->sfp.fillGradient; + + d->sfp.fillGradient = createShapeGradient( rect, gradient ); + d->sfp.fillGradient->setParent( this ); + } + + private: + QQuickShapeGradient* createShapeGradient( + const QRectF& rect, const QskGradient& gradient ) const + { + QQuickShapeGradient* shapeGradient = nullptr; + + auto effectiveGradient = gradient.effectiveGradient(); + effectiveGradient.stretchTo( rect ); + + switch( static_cast< int >( gradient.type() ) ) + { + case QskGradient::Linear: + { + const auto dir = effectiveGradient.linearDirection(); + + auto g = new QQuickShapeLinearGradient(); + + g->setX1( dir.x1() ); + g->setY1( dir.y1() ); + g->setX2( dir.x2() ); + g->setY2( dir.y2() ); + + shapeGradient = g; + break; + } + + case QskGradient::Radial: + { + const auto dir = effectiveGradient.radialDirection(); + + auto g = new QQuickShapeRadialGradient(); + + g->setCenterX( dir.x() ); + g->setCenterY( dir.y() ); + g->setFocalX( dir.x() ); + g->setFocalY( dir.y() ); + + g->setCenterRadius( qMax( dir.radiusX(), dir.radiusY() ) ); + + shapeGradient = g; + break; + } + + case QskGradient::Conic: + { + const auto dir = effectiveGradient.conicDirection(); + + auto g = new QQuickShapeConicalGradient(); + + g->setCenterX( dir.x() ); + g->setCenterY( dir.y() ); + g->setAngle( dir.startAngle() ); // dir.spanAngle() is not supported + + shapeGradient = g; + break; + } + } + + shapeGradient->setSpread( + static_cast< QQuickShapeGradient::SpreadMode >( gradient.spreadMode() ) ); + + /* + QQuickGradient has been made in the early days of Qt5 for the QML + use case. Everything - even each stop - is a QObject. + */ + const auto qtStops = qskToQGradientStops( gradient.stops() ); + + for ( const auto& stop : qtStops ) + { + class MyGradient : public QObject + { + public: + QList< QQuickGradientStop* > m_stops; + }; + + auto s = new QQuickGradientStop( shapeGradient ); + s->setPosition( stop.first ); + s->setColor( stop.second ); + + reinterpret_cast< MyGradient* >( shapeGradient )->m_stops += s; + } + + return shapeGradient; + } + }; + + class ShapeItem : public QQuickShape + { + public: + ShapeItem() + { + auto d = QQuickShapePrivate::get( this ); + d->sp += new ShapePath( this ); + } + + QSGNode* updateShapeNode( QQuickWindow* window, const QRectF& rect, + const QskGradient& gradient, QSGNode* node ) + { + auto d = QQuickShapePrivate::get( this ); + + ShapePath path; + path.setRect( rect ); + path.setGradient( rect, gradient ); + + d->sp += &path; + d->spChanged = true; + + d->refWindow( window ); + updatePolish(); + node = QQuickShape::updatePaintNode( node, nullptr ); + d->derefWindow(); + + d->sp.clear(); + + return node; + } + + private: + QSGNode* updatePaintNode( QSGNode*, UpdatePaintNodeData* ) override + { + Q_ASSERT( false ); + return nullptr; + } + }; +} + +Q_GLOBAL_STATIC( ShapeItem, shapeItem ) + +QSGNode* GradientQuickShape::updateNode( QQuickWindow* window, + const QRectF& rect, const QskGradient& gradient, QSGNode* node ) +{ + /* + Unfortunately the different materials for the gradients are hidden + in private classes of the quickshape module, and can't be accessed + from application code. Hard to understand why such basic functionality + is not offered like QSGFlatColorMaterial and friends. Anyway - we have + QskGradientMaterial now ... + + But for the purpose of comparing our shaders with those from quickshape we + use a static QQuickShape to create/update scene graph node, that actually + belong to a different QQuickItem. + */ + return shapeItem->updateShapeNode( window, rect, gradient, node ); +} diff --git a/playground/gradients/GradientQuickShape.h b/playground/gradients/GradientQuickShape.h new file mode 100644 index 00000000..9a3f662a --- /dev/null +++ b/playground/gradients/GradientQuickShape.h @@ -0,0 +1,17 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#pragma once + +class QskGradient; +class QSGNode; +class QQuickWindow; +class QRectF; + +namespace GradientQuickShape +{ + QSGNode* updateNode( QQuickWindow*, const QRectF&, + const QskGradient&, QSGNode* ); +} diff --git a/playground/gradients/GradientView.cpp b/playground/gradients/GradientView.cpp new file mode 100644 index 00000000..9a32f25b --- /dev/null +++ b/playground/gradients/GradientView.cpp @@ -0,0 +1,198 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "GradientView.h" + +#ifdef SHAPE_GRADIENT + #include "GradientQuickShape.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace +{ + template< typename Node > + inline Node* gradientNode( QSGNode* node ) + { + if ( node == nullptr ) + node = new Node(); + + return static_cast< Node* >( node ); + } + + class PaintedNode final : public QskPaintedNode + { + public: + PaintedNode() + { + setRenderHint( QskPaintedNode::Raster ); + } + + void updateNode( QQuickWindow* window, + const QRectF& rect, const QskGradient& gradient ) + { + update( window, rect, QSizeF(), &gradient ); + } + + QskHashValue hash( const void* nodeData ) const override + { + const auto gradient = reinterpret_cast< const QskGradient* >( nodeData ); + return gradient->hash(); + } + + protected: + void paint( QPainter* painter, const QSize& size, const void* nodeData ) override + { + const auto gradient = reinterpret_cast< const QskGradient* >( nodeData ); + const QRect rect( 0, 0, size.width(), size.height() ); + + painter->fillRect( rect, gradient->stretchedTo( rect ).toQGradient() ); + } + }; + + class InfoLabel : public QskTextLabel + { + public: + InfoLabel( GradientView::NodeType nodeType, QQuickItem* parent ) + : QskTextLabel( parent ) + { + QString text; + + switch( nodeType ) + { + case GradientView::Painted: + text = "QskPaintedNode"; + break; + + case GradientView::BoxRectangle: + text = "QskBoxRectangleNode"; + break; + + case GradientView::BoxFill: + text = "QskBoxFillNode"; + break; + + #ifdef SHAPE_GRADIENT + case GradientView::Shape: + text = "QQuickShapeGenericNode"; + break; + #endif + + default: + break; + } + + if ( !text.isEmpty() ) + { + QColor c( Qt::white ); + c.setAlpha( 200 ); + setBackgroundColor( c ); + + setText( text ); + } + } + }; +} + +GradientView::GradientView( NodeType nodeType, QQuickItem* parent ) + : QQuickItem( parent ) + , m_nodeType( nodeType ) +{ + setFlag( QQuickItem::ItemHasContents, true ); + + auto label = new InfoLabel( nodeType, this ); + label->setPosition( 10, 10 ); + label->setSize( label->sizeHint() ); +} + +GradientView::NodeType GradientView::GradientView::nodeType() const +{ + return m_nodeType; +} + +void GradientView::setGradient( const QskGradient& gradient ) +{ + m_gradient = gradient; + update(); +} + +QskGradient GradientView::gradient() const +{ + return m_gradient; +} + +#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) +void GradientView::geometryChange( const QRectF&, const QRectF& ) +#else +void GradientView::geometryChanged( const QRectF&, const QRectF& ) +#endif +{ + update(); +} + +QSGNode* GradientView::updatePaintNode( + QSGNode* oldNode, QQuickItem::UpdatePaintNodeData* ) +{ + const QRectF rect( 0, 0, width(), height() ); + + switch( m_nodeType ) + { + case Painted: + { + auto node = gradientNode< PaintedNode >( oldNode ); + node->updateNode( window(), rect, m_gradient ); + + return node; + } + case BoxFill: + { + auto node = gradientNode< QskBoxFillNode >( oldNode ); + node->updateNode( rect, m_gradient ); + + return node; + } + case BoxRectangle: + { + QskBoxShapeMetrics shape; + shape.setRadius( 80 ); + + if ( !QskBox::isGradientSupported( shape, m_gradient ) ) + { + delete oldNode; + return nullptr; + } + + auto node = gradientNode< QskBoxRectangleNode >( oldNode ); + node->updateNode( rect, shape, m_gradient ); + + return node; + } +#ifdef SHAPE_GRADIENT + case Shape: + { + return GradientQuickShape::updateNode( + window(), rect, m_gradient, oldNode ); + } +#endif + default: + break; + } + + return nullptr; +} + +#include "moc_GradientView.cpp" diff --git a/playground/gradients/GradientView.h b/playground/gradients/GradientView.h new file mode 100644 index 00000000..3d8d4646 --- /dev/null +++ b/playground/gradients/GradientView.h @@ -0,0 +1,49 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#pragma once + +#include +#include + +class GradientView : public QQuickItem +{ + Q_OBJECT + + Q_PROPERTY( QskGradient gradient READ gradient WRITE setGradient ) + + public: + enum NodeType + { + Painted, + BoxFill, + BoxRectangle, +#ifdef SHAPE_GRADIENT + Shape, +#endif + + NumNodeTypes + }; + Q_ENUM( NodeType ) + + GradientView( NodeType, QQuickItem* parent = nullptr ); + + NodeType nodeType() const; + + void setGradient( const QskGradient& ); + QskGradient gradient() const; + + protected: + QSGNode* updatePaintNode( QSGNode*, QQuickItem::UpdatePaintNodeData* ) override; +#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) + void geometryChange( const QRectF&, const QRectF& ) override; +#else + void geometryChanged( const QRectF&, const QRectF& ) override; +#endif + + private: + const NodeType m_nodeType; + QskGradient m_gradient; +}; diff --git a/playground/gradients/gradients.pro b/playground/gradients/gradients.pro new file mode 100644 index 00000000..4bd9e8f8 --- /dev/null +++ b/playground/gradients/gradients.pro @@ -0,0 +1,18 @@ +CONFIG += qskexample + +HEADERS += \ + GradientView.h + +SOURCES += \ + GradientView.cpp \ + main.cpp + +qtHaveModule(quickshapes_private) { + + QT += quickshapes_private + + HEADERS += GradientQuickShape.h + SOURCES += GradientQuickShape.cpp + + DEFINES += SHAPE_GRADIENT +} diff --git a/playground/gradients/main.cpp b/playground/gradients/main.cpp new file mode 100644 index 00000000..5507ff74 --- /dev/null +++ b/playground/gradients/main.cpp @@ -0,0 +1,205 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "GradientView.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace +{ + class NumberInput : public QskLinearBox + { + Q_OBJECT + + public: + NumberInput( const QString& label, qreal value, QQuickItem* parent = nullptr ) + : QskLinearBox( Qt::Horizontal, parent ) + { + new QskTextLabel( label, this ); + + m_input = new QskTextInput( this ); + m_input->setValidator( new QDoubleValidator( -9.99, 9.99, 2, m_input ) ); + m_input->setText( QString::number( value ) ); + + const QFontMetricsF fm( m_input->font() ); + m_input->setFixedWidth( fm.horizontalAdvance( "-0.000" ) ); + + connect( m_input, &QskTextInput::editingChanged, + this, [ this ]( bool on ) { if ( !on ) Q_EMIT valueChanged(); } ); + + setSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Fixed ); + } + + qreal value() const + { + return m_input->text().toDouble(); + } + + Q_SIGNALS: + void valueChanged(); + + private: + QskTextInput* m_input; + }; + + class VectorBox : public QskLinearBox + { + Q_OBJECT + + public: + VectorBox( QQuickItem* parent = nullptr ) + : QskLinearBox( Qt::Horizontal, parent ) + { + setSizePolicy( Qt::Vertical, QskSizePolicy::Fixed ); + setExtraSpacingAt( Qt::BottomEdge | Qt::RightEdge ); + + setSpacing( 10 ); + + m_inputs[0] = new NumberInput( "X1", 0, this ); + m_inputs[1] = new NumberInput( "Y1", 0, this ); + m_inputs[2] = new NumberInput( "X2", 1, this ); + m_inputs[3] = new NumberInput( "Y2", 1, this ); + + for ( auto input : m_inputs ) + { + connect( input, &NumberInput::valueChanged, + this, [this] { Q_EMIT vectorChanged( vector() ); } ); + } + } + + QLineF vector() const + { + const auto x1 = m_inputs[0]->value(); + const auto y1 = m_inputs[1]->value(); + const auto x2 = m_inputs[2]->value(); + const auto y2 = m_inputs[3]->value(); + + return QLineF( x1, y1, x2, y2 ); + } + + + Q_SIGNALS: + void vectorChanged( const QLineF& ); + + private: + NumberInput* m_inputs[4]; + }; + + class GradientBox : public QskLinearBox + { + Q_OBJECT + + public: + GradientBox( QQuickItem* parent = nullptr ) + : QskLinearBox( Qt::Horizontal, 2, parent ) + { + setSpacing( 10 ); + + for ( int i = 0; i < GradientView::NumNodeTypes; i++ ) + { + const auto nodeType = static_cast< GradientView::NodeType >( i ); + m_views[i] = new GradientView( nodeType, this ); + } + + setVector( QLineF( 0, 0, 1, 0 ) ); + setColors( { Qt::green, Qt::red, Qt::yellow, Qt::cyan, Qt::darkCyan } ); + } + + public Q_SLOTS: + void setVector( const QLineF& vector ) + { + m_gradient.setLinearDirection( vector ); + updateViews(); + } + + void setStops( const QskGradientStops& stops ) + { + m_gradient.setStops( stops ); + updateViews(); + } + + private: + void setColors( const QVector< QColor >& colors ) + { + const auto step = 1.0 / colors.size(); + + QskGradientStops stops; + + for ( int i = 0; i < colors.size(); i++ ) + { + stops += { i * step, colors[i] }; + stops += { ( i + 1 ) * step, colors[i] }; + } + + setStops( stops ); + } + + void updateViews() + { + for ( auto view : m_views ) + { + if ( view ) + view->setGradient( m_gradient ); + } + } + + QskGradient m_gradient; + GradientView* m_views[ GradientView::NumNodeTypes ]; + }; + + class MainView : public QskLinearBox + { + public: + MainView( QQuickItem* parent = nullptr ) + : QskLinearBox( Qt::Vertical, parent ) + { + auto vectorBox = new VectorBox( this ); + vectorBox->setMargins( 10, 10, 10, 0 ); + + auto gradientBox = new GradientBox( this ); + gradientBox->setMargins( 10 ); + + gradientBox->setVector( vectorBox->vector() ); + + connect( vectorBox, &VectorBox::vectorChanged, + gradientBox, &GradientBox::setVector ); + } + }; +} + +int main( int argc, char** argv ) +{ + qputenv( "QT_IM_MODULE", QByteArray() ); // no virtual keyboard + + QGuiApplication app( argc, argv ); + + Skinny::init(); // we need a skin + SkinnyShortcut::enable( SkinnyShortcut::Quit | SkinnyShortcut::DebugBackground ); + qskSetup->setSkin( "squiek" ); + + QskWindow window; + window.setColor( QskRgb::Wheat ); + window.addItem( new MainView() ); + window.resize( 800, 600 ); + window.show(); + + return app.exec(); +} + +#include "main.moc" diff --git a/playground/playground.pro b/playground/playground.pro index fedb7191..c464205e 100644 --- a/playground/playground.pro +++ b/playground/playground.pro @@ -4,11 +4,12 @@ SUBDIRS += \ anchors \ dials \ dialogbuttons \ + gradients \ invoker \ inputpanel \ - images - -SUBDIRS += shadows + images \ + shadows \ + shapes qtHaveModule(webengine) { diff --git a/playground/shapes/GeometricShape.cpp b/playground/shapes/GeometricShape.cpp new file mode 100644 index 00000000..e04ab17f --- /dev/null +++ b/playground/shapes/GeometricShape.cpp @@ -0,0 +1,57 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "GeometricShape.h" +#include "Stroke.h" + +#include + +GeometricShape::GeometricShape( QQuickItem* parent ) + : ShapeItem( parent ) +{ +} + +GeometricShape::GeometricShape( Figure figure, QQuickItem* parent ) + : GeometricShape( parent ) +{ + setFigure( figure ); +} + +void GeometricShape::setFigure( Figure figure ) +{ + m_figure = figure; + + using namespace SkinnyShapeFactory; + setPath( shapePath( static_cast< Shape >( figure ), QSizeF( 50, 50 ) ) ); +} + +GeometricShape::Figure GeometricShape::figure() const +{ + return m_figure; +} + +void GeometricShape::setBorder( const QColor& color ) +{ + Stroke stroke( color ); +#if 0 + stroke.setCosmetic( true ); +#endif + + stroke.setWidth( stroke.isCosmetic() ? 8 : 2 ); + stroke.setJoinStyle( Stroke::MiterJoin ); + +#if 0 + stroke.setLineStyle( Stroke::DashLine ); + stroke.setColor( QskRgb::toTransparent( color, 100 ) ); +#endif + setStroke( stroke ); +} + +QColor GeometricShape::border() const +{ + return stroke().color(); +} + +#include "moc_GeometricShape.cpp" diff --git a/playground/shapes/GeometricShape.h b/playground/shapes/GeometricShape.h new file mode 100644 index 00000000..1d60cbeb --- /dev/null +++ b/playground/shapes/GeometricShape.h @@ -0,0 +1,45 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#pragma once + +#include "ShapeItem.h" + +class GeometricShape : public ShapeItem +{ + Q_OBJECT + + Q_PROPERTY( Figure figure READ figure WRITE setFigure ) + Q_PROPERTY( QColor border READ border WRITE setBorder ) + + public: + enum Figure + { + Rectangle, + Diamond, + TriangleDown, + TriangleUp, + TriangleLeft, + TriangleRight, + Ellipse, + Ring, + Star, + Hexagon, + Arc + }; + Q_ENUM( Figure ); + + GeometricShape( QQuickItem* parent = nullptr ); + GeometricShape( Figure figure, QQuickItem* parent = nullptr ); + + void setFigure( Figure ); + Figure figure() const; + + void setBorder( const QColor& ); + QColor border() const; + + private: + Figure m_figure = Rectangle; +}; diff --git a/playground/shapes/ShapeItem.cpp b/playground/shapes/ShapeItem.cpp new file mode 100644 index 00000000..32c58ec9 --- /dev/null +++ b/playground/shapes/ShapeItem.cpp @@ -0,0 +1,232 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "ShapeItem.h" +#include "Stroke.h" + +#include +#include +#include +#include + +#include +#include + +#define STROKE_ADJUSTMENT 0 + +static inline QTransform transformForRects( const QRectF& r1, const QRectF& r2 ) +{ + return QTransform::fromTranslate( -r1.x(), -r1.y() ) + * QTransform::fromScale( r2.width() / r1.width(), r2.height() / r1.height() ) + * QTransform::fromTranslate( r2.x(), r2.y() ); +} + +#if STROKE_ADJUSTMENT + +static inline qreal effectiveStrokeWidth( + const Stroke& stroke, const QRectF& r1, const QRectF& r2 ) +{ + qreal width = qMax( stroke.width(), 0.0 ); + + if ( !stroke.isCosmetic() ) + { + const qreal sx = r1.width() / r2.width(); + const qreal sy = r1.height() / r2.height(); + + width *= std::min( sx, sy ); + } + + return width; +} + +#endif + +class ShapeItem::PrivateData +{ + public: + inline PrivateData() + : stroke( Qt::black, -1.0 ) // no stroke + , gradient( Qt::white ) + { + } + + Stroke stroke; + QskGradient gradient; + QPainterPath path; +}; + +ShapeItem::ShapeItem( QQuickItem* parent ) + : QskControl( parent ) + , m_data( new PrivateData() ) +{ + setMargins( 20 ); + setSizePolicy( QskSizePolicy::Ignored, QskSizePolicy::Ignored ); +} + +ShapeItem::~ShapeItem() +{ +} + +void ShapeItem::setStroke( const QColor& color, qreal width ) +{ + setStroke( Stroke( color, width ) ); +} + +void ShapeItem::setStroke( const Stroke& stroke ) +{ + if ( stroke != m_data->stroke ) + { + m_data->stroke = stroke; + update(); + + Q_EMIT strokeChanged( m_data->stroke ); + } +} + +void ShapeItem::resetStroke() +{ + setStroke( Stroke( Qt::black, -1.0 ) ); +} + +Stroke ShapeItem::stroke() const +{ + return m_data->stroke; +} + +void ShapeItem::setGradient( const QskGradient& gradient ) +{ + if ( gradient != m_data->gradient ) + { + m_data->gradient = gradient; + update(); + + Q_EMIT gradientChanged( m_data->gradient ); + } +} + +void ShapeItem::resetGradient() +{ + setGradient( Qt::white ); +} + +QskGradient ShapeItem::gradient() const +{ + return m_data->gradient; +} + +void ShapeItem::setPath( const QPainterPath& path ) +{ + if ( path != m_data->path ) + { + m_data->path = path; + update(); + + Q_EMIT pathChanged( m_data->path ); + } +} + +void ShapeItem::resetPath() +{ + setPath( QPainterPath() ); +} + +QPainterPath ShapeItem::path() const +{ + return m_data->path; +} + +void ShapeItem::updateNode( QSGNode* parentNode ) +{ + enum NodeRole + { + FillRole, + BorderRole + }; + + const auto rect = contentsRect(); + const auto pathRect = m_data->path.controlPointRect(); + + auto fillNode = static_cast< QskShapeNode* >( + QskSGNode::findChildNode( parentNode, FillRole ) ); + + auto borderNode = static_cast< QskStrokeNode* >( + QskSGNode::findChildNode( parentNode, BorderRole ) ); + + if ( rect.isEmpty() || pathRect.isEmpty() ) + { + delete fillNode; + delete borderNode; + + return; + } + + if ( m_data->gradient.isVisible() ) + { + if ( fillNode == nullptr ) + { + fillNode = new QskShapeNode; + QskSGNode::setNodeRole( fillNode, FillRole ); + + parentNode->prependChildNode( fillNode ); + } + + auto fillRect = rect; + +#if STROKE_ADJUSTMENT + /* + when the stroke is not opaque ( transparent color or dashed ) we + would see, that the filling is not inside. But simply adjusting + by the half of the stroke width is only correct for rectangles. TODO ... + */ + const auto pw2 = 0.5 * ::effectiveStrokeWidth( m_data->stroke, rect, pathRect ); + fillRect.adjust( pw2, pw2, -pw2, -pw2 ); +#endif + + const auto transform = ::transformForRects( pathRect, fillRect ); + fillNode->updateNode( m_data->path, transform, fillRect, m_data->gradient ); + } + else + { + delete fillNode; + } + + if ( m_data->stroke.isVisible() ) + { + const auto pen = m_data->stroke.toPen(); +#if 0 + if ( pen.widthF() > 1.0 ) + { + if ( !( pen.isSolid() && pen.color().alpha() == 255 ) ) + { + /* + We might end up with overlapping parts + at corners with angles < 180° + + What about translating the stroke into + a path ( QPainterPathStroker ) and using + a QskShapeNode then. TODO ... + */ + } + } +#endif + + if ( borderNode == nullptr ) + { + borderNode = new QskStrokeNode; + QskSGNode::setNodeRole( borderNode, BorderRole ); + + parentNode->appendChildNode( borderNode ); + } + + const auto transform = ::transformForRects( pathRect, rect ); + borderNode->updateNode( m_data->path, transform, pen ); + } + else + { + delete borderNode; + } +} + +#include "moc_ShapeItem.cpp" diff --git a/playground/shapes/ShapeItem.h b/playground/shapes/ShapeItem.h new file mode 100644 index 00000000..704cce81 --- /dev/null +++ b/playground/shapes/ShapeItem.h @@ -0,0 +1,55 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#pragma once + +#include + +class Stroke; +class QskGradient; +class QPainterPath; + +class ShapeItem : public QskControl +{ + Q_OBJECT + + Q_PROPERTY( Stroke stroke READ stroke WRITE setStroke + RESET resetStroke NOTIFY strokeChanged ) + + Q_PROPERTY( QskGradient gradient READ gradient WRITE setGradient + RESET resetGradient NOTIFY gradientChanged ) + + Q_PROPERTY( QPainterPath path READ path WRITE setPath + RESET resetPath NOTIFY pathChanged ) + + public: + ShapeItem( QQuickItem* parent = nullptr ); + ~ShapeItem() override; + + Stroke stroke() const; + void setStroke( const Stroke& ); + void setStroke( const QColor&, qreal width = 1.0 ); + void resetStroke(); + + QskGradient gradient() const; + void setGradient( const QskGradient& ); + void resetGradient(); + + QPainterPath path() const; + void setPath( const QPainterPath& ); + void resetPath(); + + Q_SIGNALS: + void strokeChanged( const Stroke& ); + void gradientChanged( const QskGradient& ); + void pathChanged( const QPainterPath& ); + + protected: + void updateNode( QSGNode* ) override; + + private: + class PrivateData; + std::unique_ptr< PrivateData > m_data; +}; diff --git a/playground/shapes/Stroke.cpp b/playground/shapes/Stroke.cpp new file mode 100644 index 00000000..0336ee78 --- /dev/null +++ b/playground/shapes/Stroke.cpp @@ -0,0 +1,75 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "Stroke.h" +#include + +Stroke::Stroke( const QPen& pen ) noexcept + : m_width( pen.widthF() ) + , m_miterLimit( pen.miterLimit() ) + , m_color( pen.color() ) + , m_lineStyle( ( pen.style() == Qt::DashLine ) ? DashLine : SolidLine ) + , m_joinStyle( static_cast< JoinStyle >( pen.joinStyle() ) ) + , m_capStyle( static_cast< CapStyle >( pen.capStyle() ) ) + , m_cosmetic( pen.isCosmetic() ) +{ +} + +void Stroke::setColor( const QColor& color ) noexcept +{ + m_color = color; +} + +void Stroke::setWidth( qreal width ) noexcept +{ + m_width = width; +} + +void Stroke::setLineStyle( LineStyle style ) +{ + m_lineStyle = style; +} + +void Stroke::setCapStyle( CapStyle capStyle ) noexcept +{ + m_capStyle = capStyle; +} + +void Stroke::setJoinStyle( JoinStyle joinStyle ) noexcept +{ + m_joinStyle = joinStyle; +} + +void Stroke::setMiterLimit( int miterLimit ) noexcept +{ + m_miterLimit = miterLimit; +} + +void Stroke::setCosmetic( bool on ) noexcept +{ + m_cosmetic = on; +} + +bool Stroke::isVisible() const +{ + return ( m_width > 0.0 ) && m_color.isValid() && ( m_color.alpha() > 0 ); +} + +QPen Stroke::toPen() const +{ + QPen pen( + m_color, + m_width, + ( m_width > 0.0 ) ? static_cast< Qt::PenStyle >( m_lineStyle ) : Qt::NoPen, + static_cast< Qt::PenCapStyle >( m_capStyle ), + static_cast< Qt::PenJoinStyle >( m_joinStyle ) + ); + + pen.setCosmetic( m_cosmetic ); + + return pen; +} + +#include "moc_Stroke.cpp" diff --git a/playground/shapes/Stroke.h b/playground/shapes/Stroke.h new file mode 100644 index 00000000..45e76741 --- /dev/null +++ b/playground/shapes/Stroke.h @@ -0,0 +1,152 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#pragma once + +#include +#include + +class QPen; + +class Stroke +{ + Q_GADGET + + Q_PROPERTY( QColor color READ color WRITE setColor ) + + Q_PROPERTY( LineStyle lineStyle READ lineStyle WRITE setLineStyle ) + Q_PROPERTY( qreal width READ width WRITE setWidth ) + Q_PROPERTY( JoinStyle joinStyle READ joinStyle WRITE setJoinStyle ) + Q_PROPERTY( int miterLimit READ miterLimit WRITE setMiterLimit ) + Q_PROPERTY( CapStyle capStyle READ capStyle WRITE setCapStyle ) + + Q_PROPERTY( bool cosmetic READ isCosmetic WRITE setCosmetic ) + + public: + enum LineStyle + { + SolidLine = Qt::SolidLine, + DashLine = Qt::DashLine + }; + Q_ENUM( LineStyle ) + + enum JoinStyle + { + MiterJoin = Qt::MiterJoin, + BevelJoin = Qt::BevelJoin, + RoundJoin = Qt::RoundJoin + }; + Q_ENUM( JoinStyle ) + + enum CapStyle + { + FlatCap = Qt::FlatCap, + SquareCap = Qt::SquareCap, + RoundCap = Qt::RoundCap + }; + Q_ENUM( CapStyle ) + + Stroke() noexcept = default; + Stroke( const QColor&, qreal width = 1.0 ) noexcept; + Stroke( const QPen& ) noexcept; + + bool operator==( const Stroke& ) const noexcept; + bool operator!=( const Stroke& ) const noexcept; + + void setColor( const QColor& ) noexcept; + QColor color() const noexcept; + + void setWidth( qreal ) noexcept; + qreal width() const noexcept; + + void setLineStyle( LineStyle ); + LineStyle lineStyle() const noexcept; + + void setCapStyle( CapStyle ) noexcept; + CapStyle capStyle() const noexcept; + + void setJoinStyle( JoinStyle ) noexcept; + JoinStyle joinStyle() const noexcept; + + void setMiterLimit( int ) noexcept; + int miterLimit() const noexcept; + + void setCosmetic( bool ) noexcept; + bool isCosmetic() const noexcept; + + bool isVisible() const; + + QPen toPen() const; + + private: + qreal m_width = 1.0; + + int m_miterLimit = 2; + + QColor m_color = Qt::black; + + LineStyle m_lineStyle = SolidLine; + JoinStyle m_joinStyle = BevelJoin; + CapStyle m_capStyle = SquareCap; + + bool m_cosmetic = false; +}; + +inline Stroke::Stroke( const QColor& color, qreal width ) noexcept + : m_width( width ) + , m_color( color ) +{ +} + +inline QColor Stroke::color() const noexcept +{ + return m_color; +} + +inline qreal Stroke::width() const noexcept +{ + return m_width; +} + +inline Stroke::LineStyle Stroke::lineStyle() const noexcept +{ + return m_lineStyle; +} + +inline Stroke::CapStyle Stroke::capStyle() const noexcept +{ + return m_capStyle; +} + +inline Stroke::JoinStyle Stroke::joinStyle() const noexcept +{ + return m_joinStyle; +} + +inline int Stroke::miterLimit() const noexcept +{ + return m_miterLimit; +} + +inline bool Stroke::isCosmetic() const noexcept +{ + return m_cosmetic; +} + +inline bool Stroke::operator==( const Stroke& other ) const noexcept +{ + return ( m_width == other.m_width ) + && ( m_miterLimit == other.m_miterLimit ) + && ( m_color == other.m_color ) + && ( m_lineStyle == other.m_lineStyle ) + && ( m_joinStyle == other.m_joinStyle ) + && ( m_capStyle == other.m_capStyle ) + && ( m_cosmetic == other.m_cosmetic ); +} + +inline bool Stroke::operator!=( const Stroke& other ) const noexcept +{ + return !( *this == other ); +} diff --git a/playground/shapes/Window.cpp b/playground/shapes/Window.cpp new file mode 100644 index 00000000..0fe711a0 --- /dev/null +++ b/playground/shapes/Window.cpp @@ -0,0 +1,211 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "Window.h" +#include "GeometricShape.h" + +#include +#include +#include +#include + +namespace +{ + class Page : public QskLinearBox + { + public: + Page( QQuickItem* parent = nullptr ) + : QskLinearBox( Qt::Horizontal, 2, parent ) + { + } + }; + + class LinearGradientPage : public Page + { + public: + LinearGradientPage( QQuickItem* parent = nullptr ) + : Page( parent ) + { + { + auto shapeItem = new GeometricShape( GeometricShape::Hexagon, this ); + + shapeItem->setBorder( QskRgb::Indigo ); + + QskGradient gradient( QGradient::PhoenixStart ); + gradient.setLinearDirection( 0.0, 0.0, 0.2, 0.5 ); + gradient.setSpreadMode( QskGradient::ReflectSpread ); + + shapeItem->setGradient( gradient ); + } + + { + auto shapeItem = new GeometricShape( GeometricShape::Star, this ); + + shapeItem->setBorder( Qt::black ); + + const QVector< QskGradientStop > stops = + { { 0.5, QskRgb::RoyalBlue }, { 0.5, QskRgb::LemonChiffon } }; + + QskGradient gradient( stops ); + gradient.setLinearDirection( 0.0, 0.0, 0.05, 0.1 ); + gradient.setSpreadMode( QskGradient::RepeatSpread ); + + shapeItem->setGradient( gradient ); + } + { + auto shapeItem = new GeometricShape( GeometricShape::Rectangle, this ); + + shapeItem->setBorder( Qt::black ); + + const QVector< QskGradientStop > stops = + { { 0.5, QskRgb::MediumVioletRed }, { 0.5, QskRgb::Navy } }; + + QskGradient gradient( stops ); + gradient.setLinearDirection( 0.3, 0.7, 0.75, 0.3 ); + + shapeItem->setGradient( gradient ); + } + } + }; + + class RadialGradientPage : public Page + { + public: + RadialGradientPage( QQuickItem* parent = nullptr ) + : Page( parent ) + { + { + auto shapeItem = new GeometricShape( GeometricShape::Rectangle, this ); + + shapeItem->setBorder( QskRgb::Indigo ); + + QskGradient gradient( QskRgb::LightYellow, QskRgb::MidnightBlue ); + gradient.setRadialDirection( QskRadialDirection( 0.7, 0.3, 0.25, 0.0 ) ); + gradient.setSpreadMode( QskGradient::PadSpread ); + + shapeItem->setGradient( gradient ); + } + { + auto shapeItem = new GeometricShape( GeometricShape::Ellipse, this ); + + shapeItem->setBorder( Qt::black ); + + QVector< QskGradientStop > stops; + + stops += QskGradientStop( 0.0, Qt::green ); + stops += QskGradientStop( 0.2, Qt::green ); + stops += QskGradientStop( 0.2, Qt::red ); + stops += QskGradientStop( 0.4, Qt::red ); + stops += QskGradientStop( 0.4, Qt::yellow ); + stops += QskGradientStop( 0.6, Qt::yellow ); + stops += QskGradientStop( 0.6, Qt::cyan ); + stops += QskGradientStop( 0.8, Qt::cyan ); + stops += QskGradientStop( 0.8, Qt::darkCyan ); + stops += QskGradientStop( 1.0, Qt::darkCyan ); + + QskGradient gradient( stops ); + gradient.setDirection( QskGradient::Radial ); + gradient.setSpreadMode( QskGradient::PadSpread ); + + shapeItem->setGradient( gradient ); + } + { + auto shapeItem = new GeometricShape( GeometricShape::Rectangle, this ); + + shapeItem->setBorder( QskRgb::Indigo ); + + QskGradient gradient( QGradient::LilyMeadow ); + gradient.setRadialDirection( 0.5, 0.7, 0.25 ); + gradient.setSpreadMode( QskGradient::RepeatSpread ); + + shapeItem->setGradient( gradient ); + } + { + auto shapeItem = new GeometricShape( GeometricShape::Rectangle, this ); + + shapeItem->setBorder( QskRgb::Indigo ); + + QskGradient gradient( Qt::red, Qt::blue ); + gradient.setRadialDirection( 0.6, 0.4, 0.1 ); + gradient.setSpreadMode( QskGradient::ReflectSpread ); + + shapeItem->setGradient( gradient ); + } + } + }; + + class ConicGradientPage : public Page + { + public: + ConicGradientPage( QQuickItem* parent = nullptr ) + : Page( parent ) + { + { + auto shapeItem = new GeometricShape( GeometricShape::Ellipse, this ); + + shapeItem->setBorder( Qt::black ); + + QskGradient gradient( QGradient::JuicyPeach ); + gradient.setConicDirection( 0.5, 0.5, 30.0, 60.0 ); + gradient.setSpreadMode( QskGradient::ReflectSpread ); + + shapeItem->setGradient( gradient ); + } + { + auto shapeItem = new GeometricShape( GeometricShape::TriangleUp, this ); + + shapeItem->setBorder( Qt::black ); + + QskGradient gradient( QGradient::WinterNeva ); + gradient.setConicDirection( 0.5, 0.5, 30.0, 60.0 ); + gradient.setSpreadMode( QskGradient::RepeatSpread ); + + shapeItem->setGradient( gradient ); + } + { + auto shapeItem = new GeometricShape( GeometricShape::Arc, this ); + + shapeItem->setBorder( Qt::black ); + + QskGradient gradient( QGradient::SpikyNaga ); + gradient.setConicDirection( 0.5, 0.5, 300.0, -240.0 ); + + shapeItem->setGradient( gradient ); + } + { + auto shapeItem = new GeometricShape( GeometricShape::Diamond, this ); + + QskGradient gradient( QGradient::FabledSunset ); + gradient.setConicDirection( 0.5, 0.5, 45.0, 180.0 ); + gradient.setSpreadMode( QskGradient::ReflectSpread ); + + shapeItem->setGradient( gradient ); + } + } + }; + + class TabView : public QskTabView + { + public: + TabView( QQuickItem* parentItem = nullptr ) + : QskTabView( parentItem ) + { + setMargins( 10 ); + setAutoFitTabs( true ); + setTabBarEdge( Qt::TopEdge ); + + addTab( "Radial Gradients", new RadialGradientPage() ); + addTab( "Conic Gradients", new ConicGradientPage() ); + addTab( "Linear Gradients", new LinearGradientPage() ); + } + }; +} + +Window::Window() +{ + setColor( Qt::gray ); + addItem( new TabView() ); + resize( 800, 600 ); +} diff --git a/playground/shapes/Window.h b/playground/shapes/Window.h new file mode 100644 index 00000000..cff551e7 --- /dev/null +++ b/playground/shapes/Window.h @@ -0,0 +1,14 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#pragma once + +#include + +class Window : public QskWindow +{ + public: + Window(); +}; diff --git a/playground/shapes/main.cpp b/playground/shapes/main.cpp new file mode 100644 index 00000000..5d755e5d --- /dev/null +++ b/playground/shapes/main.cpp @@ -0,0 +1,78 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "Window.h" +#include "GeometricShape.h" +#include "Stroke.h" + +#include +#include + +#include +#include + +#include + +template< typename T > +static inline int registerType( const char* qmlName ) +{ + return qmlRegisterType< T >( "Shapes", 1, 0, qmlName ); +} + +template< typename T > +static inline int registerValueType( const char* qmlName ) +{ +#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) + QByteArray name = qmlName; + name.data()[0] = std::tolower( name.data()[0] ); + return registerType< T >( name.constData() ); +#else + return qmlRegisterUncreatableType< T >( "Shapes", 1, 0, qmlName, QString() ); +#endif +} + +static bool doQml( int argc, char* argv[] ) +{ + for ( int i = 1; i < argc; i++ ) + { + if ( strcmp( argv[i], "-qml" ) == 0 ) + return true; + } + + return false; +} + +int main( int argc, char* argv[] ) +{ +#ifdef ITEM_STATISTICS + QskObjectCounter counter( true ); +#endif + + QGuiApplication app( argc, argv ); + SkinnyShortcut::enable( SkinnyShortcut::AllShortcuts ); + + if ( doQml( argc, argv ) ) + { + qDebug() << "Running QML"; + + QskQml::registerTypes(); + + registerType< GeometricShape >( "Shape" ); + registerValueType< Stroke >( "Stroke" ); + + QQmlApplicationEngine engine( QUrl( "qrc:/qml/shapes.qml" ) ); + + return app.exec(); + } + else + { + qDebug() << "Running C++"; + + Window window; + window.show(); + + return app.exec(); + } +} diff --git a/playground/shapes/shapes.pro b/playground/shapes/shapes.pro new file mode 100644 index 00000000..4ceeb610 --- /dev/null +++ b/playground/shapes/shapes.pro @@ -0,0 +1,17 @@ +CONFIG += qskexample qskqmlexport + +RESOURCES += \ + shapes.qrc + +HEADERS += \ + Stroke.h \ + ShapeItem.h \ + GeometricShape.h \ + Window.h + +SOURCES += \ + Stroke.cpp \ + ShapeItem.cpp \ + GeometricShape.cpp \ + Window.cpp \ + main.cpp diff --git a/playground/shapes/shapes.qml b/playground/shapes/shapes.qml new file mode 100644 index 00000000..3f62e5e8 --- /dev/null +++ b/playground/shapes/shapes.qml @@ -0,0 +1,258 @@ +import QtQuick 2.0 +import Skinny 1.0 as Qsk +import Shapes 1.0 +import "qrc:/qml" + +Qsk.Window +{ + visible: true + width: 800 + height: 600 + + color: "Gray" + + Qsk.LinearBox + { + id: pageLinear + + orientation: Qt.Horizontal + dimension: 2 + + Shape + { + figure: Shape.Hexagon + border: "indigo" + + gradient: + ({ + linear: { x1: 0, y1: 0, x2: 0.2, y2: 0.5 }, + spreadMode: Qsk.Gradient.ReflectSpread, + + // PhonixStart + stops: [ + { position: 0.0, color: "#f83600" }, + { position: 1.0, color: "#f9d423" } + ] + }) + } + + Shape + { + figure: Shape.Star + border: "black" + + gradient: + ({ + linear: { x1: 0, y1: 0, x2: 0.05, y2: 0.1 }, + spreadMode: Qsk.Gradient.RepeatSpread, + + stops: [ + { position: 0.5, color: "RoyalBlue" }, + { position: 0.5, color: "LemonChiffon" } + ] + }) + } + + Shape + { + figure: Shape.Rectangle + border: "black" + + gradient: + ({ + linear: { x1: 0.3, y1: 0.7, x2: 0.75, y2: 0.3 }, + + stops: [ + { position: 0.5, color: "MediumVioletRed" }, + { position: 0.5, color: "Navy" } + ] + }) + } + } + + Qsk.LinearBox + { + id: pageConic + + orientation: Qt.Horizontal + dimension: 2 + + Shape + { + figure: Shape.Ellipse + border: "black" + + gradient: + ({ + conic: { x: 0.5, y: 0.5, startAngle: 30, spanAngle: 60 }, + spreadMode: Qsk.Gradient.ReflectSpread, + + // JuicyPeach + stops: [ + { position: 0.0, color: "#ffecd2" }, + { position: 1.0, color: "#fcb69f" } + ] + }) + } + + Shape + { + figure: Shape.TriangleUp + border: "black" + + gradient: + ({ + conic: { x: 0.5, y: 0.5, startAngle: 30, spanAngle: 60 }, + spreadMode: Qsk.Gradient.RepeatSpread, + + // WinterNeva + stops: [ + { position: 0.0, color: "#a1c4fd" }, + { position: 1.0, color: "#c2e9fb" } + ] + }) + } + + Shape + { + figure: Shape.Arc + border: "black" + + gradient: + ({ + conic: { x: 0.5, y: 0.5, startAngle: 300, spanAngle: -240 }, + + // SpikyNaga + stops: [ + { position: 0.00, color: "#505285" }, + { position: 0.12, color: "#585e92" }, + { position: 0.25, color: "#65689f" }, + { position: 0.37, color: "#7474b0" }, + { position: 0.50, color: "#7e7ebb" }, + { position: 0.62, color: "#8389c7" }, + { position: 0.75, color: "#9795d4" }, + { position: 0.87, color: "#a2a1dc" }, + { position: 1.00, color: "#b5aee4" } + ] + }) + } + + Shape + { + figure: Shape.Diamond + + gradient: + ({ + conic: { x: 0.5, y: 0.5, startAngle: 45, spanAngle: 180 }, + spreadMode: Qsk.Gradient.ReflectSpread, + + // FabledSunset + stops: [ + { position: 0.00, color: "#231557" }, + { position: 0.29, color: "#44107a" }, + { position: 0.67, color: "#ff1361" }, + { position: 1.00, color: "#fff800" } + ] + }) + } + } + + Qsk.LinearBox + { + id: pageRadial + + orientation: Qt.Horizontal + dimension: 2 + + Shape + { + figure: Shape.Rectangle + border: "indigo" + + gradient: + ({ + radial: { x: 0.7, y: 0.3, radiusX: 0.25, radiusY: 0.0 }, + + stops: [ + { position: 0.0, color: "LightYellow" }, + { position: 1.0, color: "MidnightBlue" } + ] + }) + } + + Shape + { + figure: Shape.Ellipse + border: "black" + + gradient: + ({ + radial: { x: 0.5, y: 0.5, radiusX: 0.5, radiusY: 0.5 }, + spreadMode: Qsk.Gradient.PadSpread, + + stops: [ + { position: 0.0, color: "lime" }, + { position: 0.2, color: "lime" }, + { position: 0.2, color: "red" }, + { position: 0.4, color: "red" }, + { position: 0.4, color: "yellow" }, + { position: 0.6, color: "yellow" }, + { position: 0.6, color: "cyan" }, + { position: 0.8, color: "cyan" }, + { position: 0.8, color: "darkcyan" }, + { position: 1.0, color: "darkcyan" } + ] + }) + } + + Shape + { + figure: Shape.Rectangle + border: "indigo" + + gradient: + ({ + radial: { x: 0.5, y: 0.7, radiusX: 0.25, radiusY: 0.25 }, + spreadMode: Qsk.Gradient.RepeatSpread, + + // LilyMeadow + stops: [ + { position: 0.00, color: "#65379b" }, + { position: 0.53, color: "#886aea" }, + { position: 1.00, color: "#6457C6" } + ] + }) + } + + Shape + { + figure: Shape.Rectangle + border: "indigo" + + gradient: + ({ + radial: { x: 0.6, y: 0.4, radiusX: 0.1, radiusY: 0.1 }, + spreadMode: Qsk.Gradient.ReflectSpread, + + stops: [ + { position: 0.0, color: "red" }, + { position: 1.0, color: "blue" } + ] + }) + } + } + + Qsk.TabView + { + margins { left: 10; top: 10; right: 10; bottom: 10 } + autoFitTabs: true + tabBarEdge: Qt.TopEdge + + Component.onCompleted: + { + addTab( "Radial Gradients", pageRadial ); + addTab( "Conic Gradients", pageConic ); + addTab( "Linear Gradients", pageLinear ); + } + } +} diff --git a/playground/shapes/shapes.qrc b/playground/shapes/shapes.qrc new file mode 100644 index 00000000..6be775dd --- /dev/null +++ b/playground/shapes/shapes.qrc @@ -0,0 +1,5 @@ + + + shapes.qml + + diff --git a/qmlexport/QskQml.cpp b/qmlexport/QskQml.cpp index 441bf47f..a4bf4c0d 100644 --- a/qmlexport/QskQml.cpp +++ b/qmlexport/QskQml.cpp @@ -4,6 +4,8 @@ *****************************************************************************/ #include "QskQml.h" +#include "QskQml.hpp" + #include "QskLayoutQml.h" #include "QskShortcutQml.h" #include "QskMainQml.h" @@ -18,11 +20,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -36,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -47,128 +52,182 @@ #include #include +#if QT_VERSION < QT_VERSION_CHECK( 6, 2, 0 ) + QSK_QT_PRIVATE_BEGIN + #include + QSK_QT_PRIVATE_END +#endif + +#if QT_VERSION < QT_VERSION_CHECK( 6, 5, 0 ) + +#include #include -#include - -QSK_QT_PRIVATE_BEGIN -#include -QSK_QT_PRIVATE_END - -#define QSK_MODULE_NAME "Skinny" - -#define QSK_REGISTER( className, typeName ) \ - qmlRegisterType< className >( QSK_MODULE_NAME, 1, 0, typeName ); - -#define QSK_REGISTER_GADGET( className, typeName ) \ - qRegisterMetaType< className >(); \ - qmlRegisterUncreatableType< className >( QSK_MODULE_NAME, 1, 0, typeName, QString() ) - -// Required for QFlags to be constructed from an enum value -#define QSK_REGISTER_FLAGS( Type ) \ - QMetaType::registerConverter< int, Type >([] ( int value ) { return Type( value ); }) - -#define QSK_REGISTER_SINGLETON( className, typeName, singleton ) \ - qmlRegisterSingletonType< className >( QSK_MODULE_NAME, 1, 0, typeName, \ - [] ( QQmlEngine*, QJSEngine* ) { return dynamic_cast< QObject* >( singleton ); } ) - -#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) - -#include namespace { - class WarningBlocker + /* + Since Qt 6.5 we have QML_STRUCTURED_VALUE and do not need to + write our own converter. + + However: we should also be able to implement a generic converter from the + metatype information: TODO ... + + For the moment we have these converters: + */ + QskGradientStop toGradientStop( const QJSValue& value ) { - public: - WarningBlocker() - { - m_oldFilter = QLoggingCategory::installFilter( &WarningBlocker::filter ); - } + return QskGradientStop( + value.property( QStringLiteral( "position" ) ).toNumber(), + value.property( QStringLiteral( "color" ) ).toVariant().value< QColor >() + ); + } - ~WarningBlocker() - { - QLoggingCategory::installFilter( m_oldFilter ); - } + QskLinearDirection toLinearDirection( const QJSValue& value ) + { + return QskLinearDirection( + value.property( QStringLiteral( "x1" ) ).toNumber(), + value.property( QStringLiteral( "y1" ) ).toNumber(), + value.property( QStringLiteral( "x2" ) ).toNumber(), + value.property( QStringLiteral( "y2" ) ).toNumber() ); + } - private: + QskConicDirection toConicDirection( const QJSValue& value ) + { + return QskConicDirection( + value.property( QStringLiteral( "x" ) ).toNumber(), + value.property( QStringLiteral( "y" ) ).toNumber(), + value.property( QStringLiteral( "startAngle" ) ).toNumber(), + value.property( QStringLiteral( "spanAngle" ) ).toNumber() ); + } - static void filter( QLoggingCategory* category ) + QskRadialDirection toRadialDirection( const QJSValue& value ) + { + return QskRadialDirection( + value.property( QStringLiteral( "x" ) ).toNumber(), + value.property( QStringLiteral( "y" ) ).toNumber(), + value.property( QStringLiteral( "radiusX" ) ).toNumber(), + value.property( QStringLiteral( "radiusY" ) ).toNumber() ); + } + + QskGradient toGradient( const QJSValue& value ) + { + QskGradient gradient; + + QJSValueIterator it( value ); + + while ( it.hasNext() ) { - if ( qstrcmp( category->categoryName(), "qt.qml.typeregistration" ) == 0 ) + it.next(); + + auto v = it.value(); + + if ( v.isObject() ) { - category->setEnabled( QtWarningMsg, false); - return; - } + if ( v.isArray() ) + { + if ( it.name() == QStringLiteral( "stops" ) ) + { + QskGradientStops stops; - m_oldFilter(category); + const int n = v.property( QStringLiteral( "length" ) ).toInt(); + for ( int i = 0; i < n; i++ ) + stops += toGradientStop( v.property( i ) ); + + gradient.setStops( stops ); + } + } + else + { + if ( it.name() == QStringLiteral( "linear" ) ) + { + gradient.setLinearDirection( toLinearDirection( v ) ); + } + else if ( it.name() == QStringLiteral( "conic" ) ) + { + gradient.setConicDirection( toConicDirection( v ) ); + } + else if ( it.name() == QStringLiteral( "radial" ) ) + { + gradient.setRadialDirection( toRadialDirection( v ) ); + } + } + } + else if ( v.isNumber() ) + { + if ( it.name() == QStringLiteral( "spreadMode" ) ) + { + const auto s = v.toNumber(); + if ( s >= QskGradient::PadSpread && s <= QskGradient::RepeatSpread ) + { + gradient.setSpreadMode( + static_cast< QskGradient::SpreadMode >( s ) ); + } + } + } } - static QLoggingCategory::CategoryFilter m_oldFilter; - }; + return gradient; + } - QLoggingCategory::CategoryFilter WarningBlocker::m_oldFilter; + void registerJSConverters() + { + QMetaType::registerConverter< QJSValue, QskGradient >( toGradient ); + QMetaType::registerConverter< QJSValue, QskLinearDirection >( toLinearDirection ); + QMetaType::registerConverter< QJSValue, QskConicDirection >( toConicDirection ); + QMetaType::registerConverter< QJSValue, QskRadialDirection >( toRadialDirection ); + QMetaType::registerConverter< QJSValue, QskGradientStop >( toGradientStop ); + } } #endif -static inline QskGradientStop qskToGradientStop( const QJSValue& value ) -{ - return QskGradientStop( - value.property( QStringLiteral( "position" ) ).toNumber(), - value.property( QStringLiteral( "color" ) ).toVariant().value< QColor >() - ); -} - void QskQml::registerTypes() { -#if 0 -#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) - qmlRegisterRevision< QQuickItem, 6 >( QSK_MODULE_NAME, 1, 0 ); -#endif -#endif - qmlRegisterUncreatableType< QskSetup >( QSK_MODULE_NAME, 1, 0, "Setup", QString() ); qmlRegisterUncreatableType< QskSkin >( QSK_MODULE_NAME, 1, 0, "Skin", QString() ); qRegisterMetaType< QskSkin* >(); - QSK_REGISTER( QskMain, "Main" ); - QSK_REGISTER( QskShortcutQml, "Shortcut" ); + registerObject< QskMain >(); + registerObject< QskShortcutQml >( "Shortcut" ); - QSK_REGISTER( QskWindow, "Window" ); + registerObject< QskWindow >(); - QSK_REGISTER( QskDialogWindow, "DialogWindow" ); - QSK_REGISTER( QskMessageWindow, "MessageWindow" ); - QSK_REGISTER( QskSelectionWindow, "SelectionWindow" ); + registerObject< QskDialogWindow >(); + registerObject< QskMessageWindow >(); + registerObject< QskSelectionWindow >(); - QSK_REGISTER( QskGridBoxQml, "GridBox" ); - QSK_REGISTER( QskLinearBoxQml, "LinearBox" ); + registerObject< QskGridBoxQml >( "GridBox" ); + registerObject< QskLinearBoxQml >( "LinearBox" ); - QSK_REGISTER( QskControl, "Control" ); - QSK_REGISTER( QskGraphicLabel, "GraphicLabel" ); - QSK_REGISTER( QskVirtualKeyboard, "VirtualKeyboard" ); - QSK_REGISTER( QskTextLabel, "TextLabel" ); - QSK_REGISTER( QskTabButton, "TabButton" ); - QSK_REGISTER( QskTabBar, "TabBar" ); - QSK_REGISTER( QskTabView, "TabView" ); - QSK_REGISTER( QskFocusIndicator, "FocusIndicator" ); - QSK_REGISTER( QskSeparator, "Separator" ); - QSK_REGISTER( QskProgressBar, "ProgressBar" ); - QSK_REGISTER( QskPushButton, "PushButton" ); - QSK_REGISTER( QskScrollView, "ScrollView" ); - QSK_REGISTER( QskScrollArea, "ScrollArea" ); - QSK_REGISTER( QskSlider, "Slider" ); - QSK_REGISTER( QskSimpleListBox, "SimpleListBox" ); - QSK_REGISTER( QskDialogButton, "DialogButton" ); - QSK_REGISTER( QskDialogButtonBox, "DialogButtonBox" ); - QSK_REGISTER( QskPopup, "Popup" ); - QSK_REGISTER( QskStatusIndicator, "StatusIndicator" ); - QSK_REGISTER( QskSubWindow, "SubWindow" ); - QSK_REGISTER( QskSubWindowArea, "SubWindowArea" ); - QSK_REGISTER( QskDialogSubWindow, "DialogSubWindow" ); + registerObject< QskControl >(); + registerObject< QskGraphicLabel >(); + registerObject< QskVirtualKeyboard >(); + registerObject< QskTextLabel >(); + registerObject< QskTabButton >(); + registerObject< QskTabBar >(); + registerObject< QskTabView >(); + registerObject< QskFocusIndicator >(); + registerObject< QskSeparator >(); + registerObject< QskProgressBar >(); + registerObject< QskPushButton >(); + registerObject< QskScrollView >(); + registerObject< QskScrollArea >(); + registerObject< QskSlider >(); + registerObject< QskSimpleListBox >(); + registerObject< QskDialogButton >(); + registerObject< QskDialogButtonBox >(); + registerObject< QskPopup >(); + registerObject< QskStatusIndicator >(); + registerObject< QskSubWindow >(); + registerObject< QskSubWindowArea >(); + registerObject< QskDialogSubWindow >(); - QSK_REGISTER_SINGLETON( QskDialog, "Dialog", QskDialog::instance() ); + registerSingleton< QskDialog >( QskDialog::instance() ); - qmlRegisterUncreatableType< QskSkin >( "Skinny.Skins", 1, 0, "Skin", QString() ); +#if 0 + qmlRegisterUncreatableType< QskSkin >( "Skinny.Skins", + QSK_VERSION_MAJOR, QSK_VERSION_MINOR, "Skin", QString() ); +#endif QSK_REGISTER_FLAGS( QskQuickItem::UpdateFlag ); QSK_REGISTER_FLAGS( QskQuickItem::UpdateFlags ); @@ -176,77 +235,39 @@ void QskQml::registerTypes() QSK_REGISTER_FLAGS( QskDialog::Actions ); - { -#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) - /* - The QML engine warns about registering uncreatables with names starting with - a capital letter. But as those classes usually appear only as scope for - local enums in QML, we do want to have capitals. f.e.: + registerGadget< QskBoxBorderMetrics >(); + registerGadget< QskBoxShapeMetrics >(); + registerGadget< QskShadowMetrics >(); + registerGadget< QskIntervalF >(); + registerGadget< QskLayoutMetrics >(); + registerGadget< QskMargins >(); - - "policy.horizonalPolicy : SizePolicy::Minimum". + registerGadget< QskGradient >(); + registerGadget< QskGradientStop >(); + registerGadget< QskLinearDirection >(); + registerGadget< QskConicDirection >(); + registerGadget< QskRadialDirection >(); - Maybe we need to introduce some dummy gadgets exposing the enums - in capital letters by using QML_FOREIGN_NAMESPACE, while the - original gadget is exposed in lower letters. TODO ... - */ - WarningBlocker warningBlocker; + registerGadget< QskAspect >(); + registerGadget< QskPlacementPolicy >(); + registerGadget< QskSizePolicy >(); + registerGadget< QskTextOptions >(); + + registerNamespace( QskStandardSymbol::staticMetaObject ); + +#if QT_VERSION < QT_VERSION_CHECK( 6, 5, 0 ) + registerJSConverters(); #endif - QSK_REGISTER_GADGET( QskBoxBorderMetrics, "BorderMetrics" ); - QSK_REGISTER_GADGET( QskBoxShapeMetrics, "Shape" ); - QSK_REGISTER_GADGET( QskShadowMetrics, "ShadowMetrics" ); - QSK_REGISTER_GADGET( QskGradient, "Gradient" ); - QSK_REGISTER_GADGET( QskGradientStop, "GradientStop" ); - QSK_REGISTER_GADGET( QskIntervalF, "IntervalF" ); - QSK_REGISTER_GADGET( QskLayoutMetrics, "LayoutMetrics" ); - QSK_REGISTER_GADGET( QskSizePolicy, "SizePolicy" ); - QSK_REGISTER_GADGET( QskTextOptions, "TextOptions" ); - QSK_REGISTER_GADGET( QskMargins, "Margins" ); - QSK_REGISTER_GADGET( QskAspect, "Aspect" ); - } - - // Support (lists of) GradientStop - QMetaType::registerConverter< QJSValue, QskGradientStop >( qskToGradientStop ); - - QMetaType::registerConverter< QJSValue, QskGradientStops >( - - []( const QJSValue& value ) - { - QskGradientStops stops; - if ( value.isArray() ) - { - QJSValueIterator it( value ); - - while ( it.next() && it.hasNext() ) - stops.append( qskToGradientStop( it.value() ) ); - } - return stops; - } - ); #if QT_VERSION < QT_VERSION_CHECK( 6, 2, 0 ) - // how to do this with >= 6.2 TODO ... + /* + Since Qt 6.5 invokable constructors are accessible from QML, something + what was possibe until Qt 6.2 with string converters. For Qt [6.2,6.4] + we do not have any solution. + */ + QQmlMetaType::registerCustomStringConverter( qMetaTypeId< QskMargins >(), []( const QString& s ) { return QVariant::fromValue( QskMargins( s.toDouble() ) ); } ); #endif - - // Support QskSizePolicy in QML user properties - QMetaType::registerConverter< QJSValue, QskSizePolicy >( - []( const QJSValue& value ) - { - return QskSizePolicy( - static_cast< QskSizePolicy::Policy >( value.property( 0 ).toInt() ), - static_cast< QskSizePolicy::Policy >( value.property( 1 ).toInt() ) ); - } - ); - -#if 1 - QMetaType::registerConverter< int, QskSizePolicy >( - []( int value ) - { - const auto policy = static_cast< QskSizePolicy::Policy >( value ); - return QskSizePolicy( policy, policy ); - } - ); -#endif } diff --git a/qmlexport/QskQml.hpp b/qmlexport/QskQml.hpp new file mode 100644 index 00000000..c120ee65 --- /dev/null +++ b/qmlexport/QskQml.hpp @@ -0,0 +1,276 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_QML_HPP +#define QSK_QML_HPP + +#include +#include + +#define QSK_MODULE_NAME "Skinny" +#define QSK_VERSION_MAJOR 1 +#define QSK_VERSION_MINOR 0 + +#if QT_VERSION < QT_VERSION_CHECK( 6, 3, 0 ) + #define QSK_STRUCT_VERSION 0 +#elif QT_VERSION < QT_VERSION_CHECK( 6, 5, 0 ) + #define QSK_STRUCT_VERSION 1 +#else + #define QSK_STRUCT_VERSION 2 +#endif + +// Required for QFlags to be constructed from an enum value +#define QSK_REGISTER_FLAGS( Type ) \ + QMetaType::registerConverter< int, Type >( []( int value ) { return Type( value ); } ) + +namespace QskQml +{ + inline const char* classNameQml( const QMetaObject& metaObject ) + { + // without the "Qsk" prefix + return metaObject.className() + 3; + } + + /* + ClassInfo corresponds to the most reecent QQmlPrivate::RegisterType + ( structVersion: 2 introduced with Qt 6.5 ) + */ + class ClassInfo + { + public: + + template< typename T > + void setTypeInfo() + { + using namespace QQmlPrivate; + +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) + const char* className = T::staticMetaObject.className(); \ + + const int nameLen = int(strlen(className) ); \ + const int listLen = int(strlen("QQmlListProperty<") ); \ + + QVarLengthArray< char,64 > listName(listLen + nameLen + 2); \ + memcpy(listName.data(), "QQmlListProperty<", size_t(listLen) ); \ + memcpy(listName.data() + listLen, className, size_t(nameLen) ); \ + listName[listLen + nameLen] = '>'; \ + listName[listLen + nameLen + 1] = '\0'; + + typeId = qMetaTypeId< T* >( ); + listId = qRegisterNormalizedMetaType< QQmlListProperty< T > >( listName.constData() ); +#else + if constexpr (std::is_base_of_v< QObject, T >) + { + typeId = QMetaType::fromType< T* >( ); + listId = QMetaType::fromType< QQmlListProperty< T > >( ); + } + else + { + typeId = QMetaType::fromType< T >( ); + listId = QMetaType::fromType< QList< T > >( ); + } + + createValueType = ValueType< T, void >::create; +#endif + + + parserStatusCast = StaticCastSelector< T,QQmlParserStatus >::cast(); + valueSourceCast = StaticCastSelector< T,QQmlPropertyValueSource >::cast(); + valueInterceptorCast = StaticCastSelector< T,QQmlPropertyValueInterceptor >::cast(); +#if QSK_STRUCT_VERSION >= 1 + finalizerCast = StaticCastSelector< T,QQmlFinalizerHook >::cast(); +#endif + } + + public: + const int structVersion = QSK_STRUCT_VERSION; + +#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) + QMetaType typeId; + QMetaType listId; +#else + int typeId = 0; + int listId = 0; +#endif + + int objectSize = 0; + +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) + void ( *create )( void* ) = nullptr; +#else + void ( *create )( void*, void* ) = nullptr; + void* const userdata = nullptr; // unused +#endif + + const QString noCreationReason; // unused + +#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) + /* + This one was introdued with Qt 6.x, but never worked + as expected. With Qt 6.5 it has been replaced by adding + the creationMethod that is triggering to look for + invokable constructors. + Let's check if it makes any sense to initialize it below + at all. TODO ... + */ + QVariant ( *createValueType )( const QJSValue& ) = nullptr; +#endif + + const char* const uri = QSK_MODULE_NAME; + +#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) + const QTypeRevision version = + QTypeRevision::fromVersion( QSK_VERSION_MAJOR, QSK_VERSION_MINOR ); +#else + const int versionMajor = QSK_VERSION_MAJOR; + const int versionMinor = QSK_VERSION_MINOR; +#endif + const char* elementName = nullptr; + const QMetaObject* metaObject = nullptr; + + /* + We do not use attached properties as it always comes with + creating extra QObjects. + */ + QObject* (* const attachedPropertiesFunction)( QObject* ) = nullptr; + const QMetaObject* const attachedPropertiesMetaObject = nullptr; + + int parserStatusCast = -1; + int valueSourceCast = -1; + int valueInterceptorCast = -1; + + /* + We do not use extensions as it always comes with + creating extra QObjects. + */ + QObject* (* const extensionObjectCreate )( QObject* ) = nullptr; + const QMetaObject* const extensionMetaObject = nullptr; + + void* const customParser = nullptr; // QQmlCustomParser, unused + +#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) + const QTypeRevision revision = QTypeRevision::zero(); +#else + const int revision = 0; +#endif + int finalizerCast = -1; + + const int creationMethod = 2; // ValueTypeCreationMethod::Structured + }; + + template< typename T > + inline int registerType( const char* qmlName ) + { + using namespace QQmlPrivate; + + ClassInfo type; + + type.setTypeInfo< T >(); + + type.objectSize = sizeof( T ); +#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) + type.create = Constructors< T >::createInto; +#else + type.create = createInto< T >; +#endif + + type.elementName = qmlName; + type.metaObject = &T::staticMetaObject; + + return qmlregister( TypeRegistration, &type ); + } + + template< typename T > + inline int registerUncreatableType( const char* qmlName ) + { + using namespace QQmlPrivate; + + ClassInfo type; + + type.setTypeInfo< T >(); + +#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) + type.objectSize = sizeof( T ); + type.create = Constructors< T >::createInto; +#endif + + type.elementName = qmlName; + type.metaObject = &T::staticMetaObject; + + return qmlregister( TypeRegistration, &type ); + } + + int registerUncreatableMetaObject( + const QMetaObject& staticMetaObject, const char* qmlName ) + { + using namespace QQmlPrivate; + + ClassInfo type; + + type.elementName = qmlName; + type.metaObject = &staticMetaObject; + + return qmlregister( TypeRegistration, &type ); + } + + template< typename T > + inline void registerObject( const char* qmlName = nullptr ) + { + // the class name without the "Qsk" prefix + if ( qmlName == nullptr ) + qmlName = classNameQml( T::staticMetaObject ); + + ( void ) registerType< T >( qmlName ); + } + + template< typename T > + inline void registerGadget() + { + auto className = classNameQml( T::staticMetaObject ); + +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) + registerUncreatableType< T >( className ); +#else + /* + According to the QML naming rules uncreatables have to + start with a lowercase letter ( since Qt6 ), while namespaces + and creatable items usually start with a upper letter. + This results in an odd naming scheme for the enums defined inside of gadgets. + + To work around this we register the gadget twice - starting with + upper or lower letter. + + Maybe it would make sense to only pass stripped metaObjects, where all + enums are removed from the first and everything else than the enums from + the second. TODO ... + */ + + if ( T::staticMetaObject.enumeratorCount() > 0 ) + { + registerUncreatableMetaObject( T::staticMetaObject, className ); + } + + QByteArray name = className; + name.data()[0] = std::tolower( name.data()[0] ); + registerUncreatableType< T >( name.constData() ); +#endif + } + + inline int registerNamespace( const QMetaObject& metaObject ) + { + return registerUncreatableMetaObject( metaObject, classNameQml( metaObject ) ); + } + + template< typename T > + inline int registerSingleton( QObject* singleton ) + { + const auto name = classNameQml( T::staticMetaObject ); + + return qmlRegisterSingletonInstance( QSK_MODULE_NAME, + QSK_VERSION_MAJOR, QSK_VERSION_MINOR, name, singleton ); + } +} + +#endif diff --git a/qmlexport/qmlexport.pro b/qmlexport/qmlexport.pro index 24857af7..12c44dd9 100644 --- a/qmlexport/qmlexport.pro +++ b/qmlexport/qmlexport.pro @@ -6,6 +6,9 @@ CONFIG += qskinny contains(QSK_CONFIG, QskDll): DEFINES += QSK_QML_MAKEDLL +HEADERS += \ + QskQml.hpp + HEADERS += \ QskQmlGlobal.h \ QskShortcutQml.h \ diff --git a/skins/material3/QskMaterial3Skin.cpp b/skins/material3/QskMaterial3Skin.cpp index f8b289a5..ffde2ef5 100644 --- a/skins/material3/QskMaterial3Skin.cpp +++ b/skins/material3/QskMaterial3Skin.cpp @@ -9,10 +9,13 @@ #include #include -#include +#include #include #include #include +#include +#include +#include #include #include #include @@ -24,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -126,7 +130,6 @@ namespace void setupBox(); void setupCheckBox(); void setupDialogButtonBox(); - void setupDialogButton(); void setupFocusIndicator(); void setupInputPanel(); void setupVirtualKeyboard(); @@ -151,10 +154,10 @@ namespace const QskMaterial3Theme& m_pal; }; - QFont createFont( int pixelSize, qreal tracking, QFont::Weight weight ) + QFont createFont( const QString& name, int lineHeight, int size, qreal tracking, QFont::Weight weight ) { - QFont font( "Roboto" ); - font.setPixelSize( pixelSize ); + QFont font( name, size ); + font.setPixelSize( lineHeight ); if( !qskFuzzyCompare( tracking, 0.0 ) ) { @@ -185,7 +188,6 @@ void Editor::setup() setupBox(); setupCheckBox(); setupDialogButtonBox(); - setupDialogButton(); setupFocusIndicator(); setupInputPanel(); setupVirtualKeyboard(); @@ -212,37 +214,83 @@ void Editor::setupControl() { using A = QskAspect; - setPadding( A::Control, 11_dp ); + setPadding( A::NoSubcontrol, 11_dp ); - setGradient( A::Control, m_pal.background ); - setColor( A::Control | A::StyleColor, m_pal.onBackground ); + setGradient( A::NoSubcontrol, m_pal.background ); + setColor( A::NoSubcontrol | A::StyleColor, m_pal.onBackground ); } void Editor::setupCheckBox() { + // skin hints are ordered according to + // https://m3.material.io/components/checkbox/specs + using Q = QskCheckBox; - setSpacing( Q::Panel, 10_dp ); + setSpacing( Q::Panel, 40_dp ); - setStrutSize( Q::Box, 24_dp, 24_dp ); - - setPadding( Q::Box, 6_dp ); + setStrutSize( Q::Box, 18_dp, 18_dp ); setBoxShape( Q::Box, 2_dp ); - setBoxBorderMetrics( Q::Box, 2_dp ); - setBoxBorderColors( Q::Box, m_pal.onBackground ); + + setBoxBorderColors( Q::Box, m_pal.onSurface ); +#if 1 + // hack: if border metrics == box shape, alpha value will be discarded + setBoxBorderMetrics( Q::Box, 1.99_dp ); +#endif + + setGradient( Q::Box, m_pal.background ); // not mentioned in the specs, but needed for animation + setGradient( Q::Box | Q::Checked, m_pal.primary ); setBoxBorderMetrics( Q::Box | Q::Checked, 0 ); - setGradient( Q::Box, m_pal.background ); - setGradient( Q::Box | Q::Checked, m_pal.primary ); - setGradient( Q::Box | Q::Disabled, m_pal.surfaceVariant12 ); - setGradient( Q::Box | Q::Checked | Q::Disabled, m_pal.onSurface12 ); + setPadding( Q::Box, 3_dp ); // "icon size" - setColor( Q::Indicator, m_pal.background ); - setColor( Q::Indicator | Q::Checked, m_pal.onPrimary ); - setColor( Q::Indicator | Q::Checked | Q::Disabled, m_pal.onSurface38 ); + setGraphicRole( Q::Indicator, QskMaterial3Skin::GraphicRoleOnPrimary ); - setColor( Q::Text, m_pal.onBackground ); - setTextOptions( Q::Text, Qt::ElideMiddle, QskTextOptions::NoWrap ); + setBoxBorderColors( Q::Box | Q::Error, m_pal.error ); + + setGradient( Q::Box | Q::Checked | Q::Error, m_pal.error ); + + setGraphicRole( Q::Indicator | Q::Error, QskMaterial3Skin::GraphicRoleOnError ); + + setStrutSize( Q::Ripple, { 40_dp, 40_dp } ); + setBoxShape( Q::Ripple, 100, Qt::RelativeSize ); + setGradient( Q::Ripple, Qt::transparent ); + + setColor( Q::Text, m_pal.onBackground ); // not mentioned in the specs + + // States + + // 2. Disabled + + setBoxBorderColors( Q::Box | Q::Disabled, m_pal.onSurface38 ); + setBoxShape( Q::Box | Q::Disabled, 2_dp ); + + setGradient( Q::Box | Q::Disabled | Q::Checked, m_pal.onSurface38 ); + setGradient( Q::Box | Q::Disabled | Q::Checked | Q::Error, m_pal.onSurface38 ); + + setGraphicRole( Q::Indicator | Q::Disabled | Q::Checked, QskMaterial3Skin::GraphicRoleSurface ); + + // 3. Hovered + + setGradient( Q::Ripple | Q::Hovered | Q::Checked, m_pal.primary8 ); + setGradient( Q::Ripple | Q::Hovered, m_pal.onSurface8 ); + setGradient( Q::Ripple | Q::Error | Q::Hovered, m_pal.error8 ); + setGradient( Q::Ripple | Q::Error | Q::Hovered | Q::Checked, m_pal.error8 ); + + // 4. Focused + + setGradient( Q::Ripple | Q::Focused | Q::Checked, m_pal.primary12 ); + setGradient( Q::Ripple | Q::Focused, m_pal.onSurface12 ); + setGradient( Q::Ripple | Q::Error | Q::Focused, m_pal.error12 ); + setGradient( Q::Ripple | Q::Error | Q::Focused | Q::Checked, m_pal.error12 ); + + // 5. Pressed + + setGradient( Q::Ripple | Q::Pressed, m_pal.primary12 ); + setGradient( Q::Ripple | Q::Pressed | Q::Checked, m_pal.primary12 ); + setGradient( Q::Ripple | Q::Hovered | Q::Pressed, m_pal.primary12 ); + setGradient( Q::Ripple | Q::Error | Q::Pressed, m_pal.error12 ); + setGradient( Q::Ripple | Q::Error | Q::Pressed | Q::Checked, m_pal.error12 ); } void Editor::setupBox() @@ -385,13 +433,15 @@ void Editor::setupSegmentedBar() using A = QskAspect; using Q = QskSegmentedBar; - const QSize strutSize( -1, 40_dp ); + const QSize panelStrutSize( -1, 48_dp ); + const QSize segmentStrutSize( 48_dp, 40_dp ); { - // Panel + // Container + setGradient( Q::Panel, Qt::transparent ); setPadding( Q::Panel, 0 ); - setSpacing( Q::Panel, 0 ); + setSpacing( Q::Panel, 8_dp ); setBoxShape( Q::Panel, 100, Qt::RelativeSize ); @@ -399,30 +449,32 @@ void Editor::setupSegmentedBar() setBoxBorderColors( Q::Panel, m_pal.outline ); setBoxBorderColors( Q::Panel | Q::Disabled, m_pal.onSurface12 ); - setStrutSize( Q::Panel | A::Horizontal, strutSize ); - setStrutSize( Q::Panel | A::Vertical, strutSize.transposed() ); + setStrutSize( Q::Panel | A::Horizontal, panelStrutSize ); + setStrutSize( Q::Panel | A::Vertical, panelStrutSize.transposed() ); } { // Segment + setStrutSize( Q::Segment | A::Horizontal, segmentStrutSize ); + setStrutSize( Q::Segment | A::Vertical, segmentStrutSize.transposed() ); setGradient( Q::Segment, Qt::transparent ); - setPadding( Q::Segment, 0 ); + setPadding( Q::Segment | A::Horizontal, { 12_dp, 0, 12_dp, 0 } ); + setPadding( Q::Segment | A::Vertical, { 0, 12_dp, 0, 12_dp } ); } { // Separator - setStrutSize( Q::Separator | A::Horizontal, 1_dp, strutSize.height() ); - setStrutSize( Q::Separator | A::Vertical, strutSize.height(), 1_dp ); + setStrutSize( Q::Separator | A::Horizontal, 1_dp, segmentStrutSize.height() ); + setStrutSize( Q::Separator | A::Vertical, segmentStrutSize.height(), 1_dp ); setPadding( Q::Separator, 0 ); setGradient( Q::Separator, m_pal.outline ); - setColor( Q::Separator | Q::Disabled, m_pal.onSurface38 ); + setColor( Q::Separator | Q::Disabled, m_pal.onSurface12 ); } { // Cursor - setMargin( Q::Cursor, 1_dp ); setBoxShape( Q::Cursor, 0 ); setBoxShape( Q::Cursor | Q::Minimum | A::Horizontal, @@ -441,8 +493,6 @@ void Editor::setupSegmentedBar() setGradient( Q::Cursor, m_pal.secondaryContainer ); setGradient( Q::Cursor | Q::Disabled, m_pal.onSurface12 ); - - setAnimation( Q::Cursor | A::Metric | A::Position, 100 ); } { @@ -461,8 +511,12 @@ void Editor::setupSegmentedBar() { // Graphic - setPadding( Q::Graphic, 10_dp ); - setMargin( Q::Graphic, 10_dp ); + setPadding( Q::Graphic, 0_dp ); + setStrutSize( Q::Graphic, { 18_dp, 18_dp } ); + + setGraphicRole( Q::Graphic, QskMaterial3Skin::GraphicRoleOnSurface ); + setGraphicRole( Q::Graphic | Q::Selected, QskMaterial3Skin::GraphicRoleOnSecondaryContainer ); + setGraphicRole( Q::Graphic | Q::Disabled, QskMaterial3Skin::GraphicRoleOnSurface38 ); } } @@ -510,22 +564,18 @@ void Editor::setupPushButton() using Q = QskPushButton; setFlagHint( Q::Panel | QskAspect::Direction, Qsk::LeftToRight ); - setStrutSize( Q::Panel, -1, 31_dp ); - setSpacing( Q::Panel, 4_dp ); - setPadding( Q::Panel, { 24_dp, 0, 20_dp, 0 } ); - + setStrutSize( Q::Panel, -1, 40_dp ); + setSpacing( Q::Panel, 8_dp ); + setPadding( Q::Panel, { 24_dp, 0, 24_dp, 0 } ); setBoxShape( Q::Panel, 100, Qt::RelativeSize ); - setAlignment( Q::Graphic, Qt::AlignCenter ); - setStrutSize( Q::Graphic, 24_dp, 24_dp ); - setPadding( Q::Graphic, 0 ); + setStrutSize( Q::Graphic, 18_dp, 18_dp ); + setPadding( Q::Graphic, { 0, 0, 8_dp, 0 } ); + setGraphicRole( Q::Graphic, QskMaterial3Skin::GraphicRoleOnPrimary ); setFontRole( Q::Text, QskMaterial3Skin::M3LabelLarge ); setPadding( Q::Text, 0 ); - setAlignment( Q::Text | A::Vertical, Qt::AlignCenter ); - setAlignment( Q::Text | A::Horizontal, Qt::AlignLeft | Qt::AlignVCenter ); - // normal buttons (i.e. Filled): setGradient( Q::Panel, m_pal.primary ); @@ -555,23 +605,6 @@ void Editor::setupPushButton() setAnimation( Q::Text | A::Color, qskDuration ); } -void Editor::setupDialogButton() -{ - using Q = QskDialogButton; - - setStrutSize( Q::Panel, 48_dp, -1 ); - setSpacing( Q::Panel, 8_dp ); - setPadding( Q::Panel, { 12_dp, 13_dp, 12_dp, 13_dp } ); - setBoxShape( Q::Panel, 100, Qt::RelativeSize ); - setGradient( Q::Panel, m_pal.secondaryContainer ); - - setGradient( Q::Panel | Q::Hovered, stateLayerColor( m_pal.primary, m_pal.hoverOpacity ) ); - setGradient( Q::Panel | Q::Pressed, stateLayerColor( m_pal.primary, m_pal.pressedOpacity ) ); - - setColor( Q::Text, m_pal.primary ); - setFontRole( Q::Text, QskMaterial3Skin::M3LabelLarge ); -} - void Editor::setupDialogButtonBox() { using Q = QskDialogButtonBox; @@ -925,8 +958,8 @@ QskMaterial3Theme::QskMaterial3Theme( Lightness lightness ) { } -QskMaterial3Theme::QskMaterial3Theme(Lightness lightness, - std::array palettes ) +QskMaterial3Theme::QskMaterial3Theme( Lightness lightness, + std::array< QskHctColor, NumPaletteTypes > palettes ) : m_palettes( palettes ) { if ( lightness == Light ) @@ -996,14 +1029,19 @@ QskMaterial3Theme::QskMaterial3Theme(Lightness lightness, shadow = m_palettes[ Neutral ].toned( 0 ).rgb(); } + primary8 = QskRgb::toTransparentF( primary, 0.08 ); primary12 = QskRgb::toTransparentF( primary, 0.12 ); + error8 = QskRgb::toTransparentF( error, 0.08 ); + error12 = QskRgb::toTransparentF( error, 0.12 ); + surface1 = flattenedColor( primary, background, 0.05 ); surface2 = flattenedColor( primary, background, 0.08 ); surface3 = flattenedColor( primary, background, 0.11 ); surface4 = flattenedColor( primary, background, 0.12 ); surface5 = flattenedColor( primary, background, 0.14 ); + onSurface8 = QskRgb::toTransparentF( onSurface, 0.08 ); onSurface12 = QskRgb::toTransparentF( onSurface, 0.12 ); onSurface38 = QskRgb::toTransparentF( onSurface, 0.38 ); @@ -1014,10 +1052,26 @@ QskMaterial3Theme::QskMaterial3Theme(Lightness lightness, elevationLight3 = QskShadowMetrics( -1, 11, { 0, 2 } ); } +QskMaterial3GraphicProvder::QskMaterial3GraphicProvder( QObject* parent ) + : Inherited( parent ) +{ +} + +const QskGraphic* QskMaterial3GraphicProvder::loadGraphic( const QString& id ) const +{ + const QString name = QString( ":/icons/qvg/%1.qvg" ).arg( id ); + const QskGraphic graphic = QskGraphicIO::read( name ); + + return graphic.isNull() ? nullptr : new QskGraphic( graphic ); +} + QskMaterial3Skin::QskMaterial3Skin( const QskMaterial3Theme& palette, QObject* parent ) : Inherited( parent ) { + addGraphicProvider( {}, new QskMaterial3GraphicProvder() ); + setupFonts(); + setupGraphicFilters( palette ); Editor editor( &hintTable(), palette ); editor.setup(); @@ -1027,14 +1081,64 @@ QskMaterial3Skin::~QskMaterial3Skin() { } +QskGraphic QskMaterial3Skin::symbol( int symbolType ) const +{ + switch ( symbolType ) + { + case QskStandardSymbol::CheckMark: + { + const auto* provider = graphicProvider( {} ); + return *( provider->requestGraphic( "check_small" ) ); + } + case QskStandardSymbol::CrossMark: + { + return {}; + } + case QskStandardSymbol::SegmentedBarCheckMark: + { + const auto* provider = graphicProvider( {} ); + return *( provider->requestGraphic( "segmented-button-check" ) ); + } + default: + return Inherited::symbol( symbolType ); + } +} + void QskMaterial3Skin::setupFonts() { Inherited::setupFonts( QStringLiteral( "Roboto" ) ); - setFont( M3BodyMedium, createFont( 14_dp, 0.25, QFont::Normal ) ); - setFont( M3BodyLarge, createFont( 16_dp, 0.5, QFont::Normal ) ); - setFont( M3HeadlineSmall, createFont( 28_dp, 0.0, QFont::Normal ) ); - setFont( M3LabelLarge, createFont( 14_dp, 0.1, QFont::Medium ) ); + setFont( M3BodyMedium, createFont( QStringLiteral( "Roboto Regular"), 20_dp, 14_dp, 0.25, QFont::Normal ) ); + setFont( M3BodyLarge, createFont( QStringLiteral( "Roboto Medium" ), 24_dp, 16_dp, 0.5, QFont::Normal ) ); + setFont( M3HeadlineSmall, createFont( QStringLiteral( "Roboto Regular" ), 32_dp, 28_dp, 0.0, QFont::Normal ) ); + setFont( M3LabelLarge, createFont( "Roboto Medium", 20_dp, 14_dp, 0.1, QFont::Medium ) ); +} + +void QskMaterial3Skin::setupGraphicFilters( const QskMaterial3Theme& palette ) +{ + QskColorFilter onPrimaryFilter; + onPrimaryFilter.addColorSubstitution( Qt::white, palette.onPrimary ); + setGraphicFilter( GraphicRoleOnPrimary, onPrimaryFilter ); + + QskColorFilter onSecondaryContainerFilter; + onSecondaryContainerFilter.addColorSubstitution( Qt::white, palette.onSecondaryContainer ); + setGraphicFilter( GraphicRoleOnSecondaryContainer, onSecondaryContainerFilter ); + + QskColorFilter onErrorFilter; + onErrorFilter.addColorSubstitution( Qt::white, palette.onError ); + setGraphicFilter( GraphicRoleOnError, onErrorFilter ); + + QskColorFilter onSurfaceFilter; + onSurfaceFilter.addColorSubstitution( Qt::white, palette.onSurface ); + setGraphicFilter( GraphicRoleOnSurface, onSurfaceFilter ); + + QskColorFilter onSurfaceFilter38; + onSurfaceFilter38.addColorSubstitution( Qt::white, palette.onSurface38 ); + setGraphicFilter( GraphicRoleOnSurface38, onSurfaceFilter38 ); + + QskColorFilter surfaceFilter; + surfaceFilter.addColorSubstitution( Qt::white, palette.surface ); + setGraphicFilter( GraphicRoleSurface, surfaceFilter ); } #include "moc_QskMaterial3Skin.cpp" diff --git a/skins/material3/QskMaterial3Skin.h b/skins/material3/QskMaterial3Skin.h index af04c787..1c35b50b 100644 --- a/skins/material3/QskMaterial3Skin.h +++ b/skins/material3/QskMaterial3Skin.h @@ -8,6 +8,7 @@ #include "QskMaterial3Global.h" +#include #include #include #include @@ -39,6 +40,7 @@ class QSK_MATERIAL3_EXPORT QskMaterial3Theme QskMaterial3Theme( Lightness, std::array< QskHctColor, NumPaletteTypes > ); QRgb primary; + QRgb primary8; QRgb primary12; QRgb onPrimary; QRgb primaryContainer; @@ -55,6 +57,8 @@ class QSK_MATERIAL3_EXPORT QskMaterial3Theme QRgb onTertiaryContainer; QRgb error; + QRgb error8; + QRgb error12; QRgb onError; QRgb errorContainer; QRgb onErrorContainer; @@ -69,6 +73,7 @@ class QSK_MATERIAL3_EXPORT QskMaterial3Theme QRgb surface5; QRgb onSurface; + QRgb onSurface8; QRgb onSurface12; QRgb onSurface38; @@ -92,6 +97,19 @@ class QSK_MATERIAL3_EXPORT QskMaterial3Theme std::array< QskHctColor, NumPaletteTypes > m_palettes; }; +class QSK_MATERIAL3_EXPORT QskMaterial3GraphicProvder : public QskGraphicProvider +{ + Q_OBJECT + + using Inherited = QskGraphicProvider; + + public: + QskMaterial3GraphicProvder( QObject* parent = nullptr ); + + protected: + virtual const QskGraphic* loadGraphic( const QString& id ) const override; +}; + class QSK_MATERIAL3_EXPORT QskMaterial3Skin : public QskSkin { Q_OBJECT @@ -102,6 +120,18 @@ class QSK_MATERIAL3_EXPORT QskMaterial3Skin : public QskSkin QskMaterial3Skin( const QskMaterial3Theme&, QObject* parent = nullptr ); ~QskMaterial3Skin() override; + virtual QskGraphic symbol( int symbolType ) const override; + + enum GraphicRole + { + GraphicRoleOnError, + GraphicRoleOnPrimary, + GraphicRoleOnSecondaryContainer, + GraphicRoleOnSurface, + GraphicRoleOnSurface38, + GraphicRoleSurface, + }; + enum FontRole { M3BodyMedium = QskSkin::HugeFont + 1, @@ -112,6 +142,7 @@ class QSK_MATERIAL3_EXPORT QskMaterial3Skin : public QskSkin private: void setupFonts(); + void setupGraphicFilters( const QskMaterial3Theme& palette ); }; #endif diff --git a/skins/material3/icons.qrc b/skins/material3/icons.qrc new file mode 100644 index 00000000..a2466aea --- /dev/null +++ b/skins/material3/icons.qrc @@ -0,0 +1,6 @@ + + + icons/qvg/check_small.qvg + icons/qvg/segmented-button-check.qvg + + diff --git a/skins/material3/icons/check_small.svg b/skins/material3/icons/check_small.svg new file mode 100644 index 00000000..3fce0e84 --- /dev/null +++ b/skins/material3/icons/check_small.svg @@ -0,0 +1,4 @@ + + + + diff --git a/skins/material3/icons/qvg/check_small.qvg b/skins/material3/icons/qvg/check_small.qvg new file mode 100644 index 00000000..19ae6901 Binary files /dev/null and b/skins/material3/icons/qvg/check_small.qvg differ diff --git a/skins/material3/icons/qvg/segmented-button-check.qvg b/skins/material3/icons/qvg/segmented-button-check.qvg new file mode 100644 index 00000000..b9c74eea Binary files /dev/null and b/skins/material3/icons/qvg/segmented-button-check.qvg differ diff --git a/skins/material3/icons/segmented-button-check.svg b/skins/material3/icons/segmented-button-check.svg new file mode 100644 index 00000000..5d4b949b --- /dev/null +++ b/skins/material3/icons/segmented-button-check.svg @@ -0,0 +1,4 @@ + + + + diff --git a/skins/material3/material3.pro b/skins/material3/material3.pro index 97317b2d..fafc9a08 100644 --- a/skins/material3/material3.pro +++ b/skins/material3/material3.pro @@ -16,8 +16,10 @@ SOURCES += \ QskMaterial3Skin.cpp \ QskMaterial3SkinFactory.cpp +RESOURCES += \ + icons.qrc \ + OTHER_FILES += metadata.json target.path = $${QSK_INSTALL_PLUGINS}/$${QSK_PLUGIN_SUBDIR} INSTALLS = target - diff --git a/skins/squiek/QskSquiekSkin.cpp b/skins/squiek/QskSquiekSkin.cpp index 6886ab27..b1516a98 100644 --- a/skins/squiek/QskSquiekSkin.cpp +++ b/skins/squiek/QskSquiekSkin.cpp @@ -9,7 +9,6 @@ #include #include -#include #include #include #include @@ -40,7 +39,6 @@ #include #include #include -#include #include #include #include @@ -135,7 +133,6 @@ namespace void setupBox(); void setupCheckBox(); - void setupDialogButton(); void setupDialogButtonBox(); void setupFocusIndicator(); void setupInputPanel(); @@ -178,12 +175,7 @@ namespace void Editor::setSeparator( QskAspect aspect ) { - QskGradient gradient( QskGradient::Vertical, m_pal.lighter110, m_pal.darker125 ); - - if ( aspect.placement() == QskAspect::Vertical ) - gradient.setOrientation( QskGradient::Horizontal ); - - setGradient( aspect, gradient ); + setGradient( aspect, m_pal.lighter110, m_pal.darker125 ); setBoxShape( aspect, 0 ); setBoxBorderMetrics( aspect, 0 ); } @@ -196,7 +188,7 @@ void Editor::setButton( QskAspect aspect, PanelStyle style, qreal border ) QskBoxBorderColors borderColors; QskGradient gradient; - gradient.setOrientation( QskGradient::Vertical ); + gradient.setLinearDirection( Qt::Vertical ); switch ( style ) { @@ -204,7 +196,7 @@ void Editor::setButton( QskAspect aspect, PanelStyle style, qreal border ) { borderColors.setGradientAt( Qt::TopEdge | Qt::LeftEdge, m_pal.lighter135 ); borderColors.setGradientAt( Qt::RightEdge | Qt::BottomEdge, m_pal.darker200 ); - gradient.setColors( m_pal.lighter125, m_pal.lighter110 ); + gradient.setStops( m_pal.lighter125, m_pal.lighter110 ); break; } @@ -212,14 +204,14 @@ void Editor::setButton( QskAspect aspect, PanelStyle style, qreal border ) { borderColors.setGradientAt( Qt::TopEdge | Qt::LeftEdge, m_pal.darker200 ); borderColors.setGradientAt( Qt::RightEdge | Qt::BottomEdge, m_pal.lighter135 ); - gradient.setColors( m_pal.lighter110, m_pal.lighter125 ); + gradient.setStops( m_pal.lighter110, m_pal.lighter125 ); break; } case Plain: { borderColors.setGradients( m_pal.darker125 ); - gradient.setColor( m_pal.lighter125 ); + gradient.setStops( m_pal.lighter125 ); break; } @@ -230,7 +222,7 @@ void Editor::setButton( QskAspect aspect, PanelStyle style, qreal border ) noColor.setAlpha( 0 ); borderColors.setGradients( noColor ); - gradient.setColor( noColor ); + gradient.setStops( noColor ); if ( style == NoPanel ) border = 0; @@ -257,7 +249,6 @@ void Editor::setup() setupBox(); setupCheckBox(); setupDialogButtonBox(); - setupDialogButton(); setupFocusIndicator(); setupInputPanel(); setupInputPredictionBar(); @@ -286,11 +277,11 @@ void Editor::setupControl() using A = QskAspect; using Q = QskControl; - setPadding( A::Control, 4 ); + setPadding( A::NoSubcontrol, 4 ); - setGradient( A::Control, m_pal.lighter135 ); - setColor( A::Control | A::StyleColor, m_pal.themeForeground ); - setColor( A::Control | A::StyleColor | Q::Disabled, m_pal.theme ); + setGradient( A::NoSubcontrol, m_pal.lighter135 ); + setColor( A::NoSubcontrol | A::StyleColor, m_pal.themeForeground ); + setColor( A::NoSubcontrol | A::StyleColor | Q::Disabled, m_pal.theme ); } void Editor::setupBox() @@ -355,11 +346,8 @@ void Editor::setupMenu() const bool isCascading = qskMaybeDesktopPlatform(); setFlagHint( Q::Panel | A::Style, isCascading ); -#if 0 - setPadding( Q::Separator, QMarginsF( 10, 0, 10, 0 ) ); -#endif setMetric( Q::Separator | A::Size, qskDpiScaled( 2 ) ); - setSeparator( Q::Separator | A::Horizontal ); + setSeparator( Q::Separator ); setPadding( Q::Segment, QskMargins( 2, 10, 2, 10 ) ); setSpacing( Q::Segment, 5 ); @@ -475,9 +463,7 @@ void Editor::setupSeparator() using Q = QskSeparator; setMetric( Q::Panel | A::Size, 4 ); - - setSeparator( Q::Panel | A::Horizontal ); - setSeparator( Q::Panel | A::Vertical ); + setSeparator( Q::Panel ); } void Editor::setupSegmentedBar() @@ -485,6 +471,8 @@ void Editor::setupSegmentedBar() using A = QskAspect; using Q = QskSegmentedBar; + const uint duration = 100; + { // Panel @@ -524,7 +512,7 @@ void Editor::setupSegmentedBar() setGradient( Q::Cursor | Q::Disabled, QColor( Qt::gray ).darker( 110 ) ); setBoxBorderColors( Q::Cursor | Q::Disabled, Qt::gray ); - setAnimation( Q::Cursor | A::Metric | A::Position, 100 ); + setAnimation( Q::Cursor | A::Metric | A::Position, duration ); } for( auto subControl : { Q::Panel, Q::Cursor } ) @@ -540,6 +528,8 @@ void Editor::setupSegmentedBar() for( auto state : { A::NoState, Q::Selected } ) setColor( Q::Text | state | Q::Disabled, m_pal.darker200 ); + + setAnimation( Q::Text | A::Color, duration ); } { @@ -618,31 +608,6 @@ void Editor::setupPushButton() setAlignment( Q::Graphic, Qt::AlignCenter ); } -void Editor::setupDialogButton() -{ - using A = QskAspect; - using Q = QskDialogButton; - - // panel - setStrutSize( Q::Panel, qskDpiScaled( 75.0 ), qskDpiScaled( 23.0 ) ); - - setPadding( Q::Panel, 10 ); - setMetric( Q::Panel | A::Spacing, 4 ); - - setButton( Q::Panel, Raised ); - setButton( Q::Panel | Q::Pressed, Sunken ); - - setAnimation( Q::Panel | A::Color, qskDuration ); - setAnimation( Q::Panel | A::Metric, qskDuration ); - - // text - setFlagHint( Q::Text | Q::Disabled | A::Style, Qsk::Sunken ); - setAlignment( Q::Text, Qt::AlignCenter ); - - setColor( Q::Text, m_pal.themeForeground ); - setColor( Q::Text | Q::Disabled, m_pal.darker200 ); -} - void Editor::setupDialogButtonBox() { using Q = QskDialogButtonBox; @@ -662,8 +627,7 @@ void Editor::setupTabButton() for ( auto placement : { A::Top, A::Bottom } ) { - setGradient( Q::Panel | placement, - QskGradient( Qt::Vertical, m_pal.lighter125, m_pal.lighter110 ) ); + setVGradient( Q::Panel | placement, m_pal.lighter125, m_pal.lighter110 ); for ( const auto state : { Q::Checked | A::NoState, Q::Checked | Q::Pressed } ) { @@ -904,6 +868,7 @@ void Editor::setupScrollView() using Q = QskScrollView; setMetric( Q::Panel | A::Spacing, 4 ); + setGradient( Q::Panel, QskGradient() ); setBoxBorderMetrics( Q::Viewport, 2 ); setBoxShape( Q::Viewport, 8 ); @@ -1036,7 +1001,7 @@ void Editor::setupSwitchButton() setBoxShape( Q::Handle, 100, Qt::RelativeSize ); setStrutSize( Q::Handle, handleSize, handleSize ); - setGradient( Q::Handle, QskGradient( Qt::Vertical, m_pal.lighter150, m_pal.lighter110 ) ); + setVGradient( Q::Handle, m_pal.lighter150, m_pal.lighter110 ); setGradient( Q::Handle | Q::Disabled, m_pal.lighter110 ); setBoxBorderMetrics( Q::Handle, 2 ); diff --git a/src/common/QskAspect.cpp b/src/common/QskAspect.cpp index d59ceeee..30533426 100644 --- a/src/common/QskAspect.cpp +++ b/src/common/QskAspect.cpp @@ -17,6 +17,17 @@ static_assert( sizeof( QskAspect ) == sizeof( quint64 ), "QskAspect::Aspect has to match quint64" ); +static void qskRegisterAspect() +{ + qRegisterMetaType< QskAspect >(); + +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) + QMetaType::registerEqualsComparator< QskAspect >(); +#endif +} + +Q_CONSTRUCTOR_FUNCTION( qskRegisterAspect ) + namespace { using namespace std; diff --git a/src/common/QskAspect.h b/src/common/QskAspect.h index f066d8eb..cdd8ce15 100644 --- a/src/common/QskAspect.h +++ b/src/common/QskAspect.h @@ -85,7 +85,7 @@ class QSK_EXPORT QskAspect enum Subcontrol : quint16 { - Control = 0, + NoSubcontrol = 0, LastSubcontrol = ( 1 << 12 ) - 1 }; @@ -150,7 +150,9 @@ class QSK_EXPORT QskAspect void setAnimator( bool on ) noexcept; constexpr Subcontrol subControl() const noexcept; - void setSubControl( Subcontrol ) noexcept; + void setSubcontrol( Subcontrol ) noexcept; + constexpr bool hasSubcontrol() const noexcept; + void clearSubcontrol() noexcept; constexpr Section section() const noexcept; void setSection( Section ) noexcept; @@ -241,7 +243,7 @@ constexpr inline QskAspect::State operator>>( QskAspect::State a, const int b ) } inline constexpr QskAspect::QskAspect() noexcept - : QskAspect( Control, Body, Flag, NoPlacement ) + : QskAspect( NoSubcontrol, Body, Flag, NoPlacement ) { } @@ -251,17 +253,17 @@ inline constexpr QskAspect::QskAspect( Subcontrol subControl ) noexcept } inline constexpr QskAspect::QskAspect( Section section ) noexcept - : QskAspect( Control, section, Flag, NoPlacement ) + : QskAspect( NoSubcontrol, section, Flag, NoPlacement ) { } inline constexpr QskAspect::QskAspect( Type type ) noexcept - : QskAspect( Control, Body, type, NoPlacement ) + : QskAspect( NoSubcontrol, Body, type, NoPlacement ) { } inline constexpr QskAspect::QskAspect( Placement placement ) noexcept - : QskAspect( Control, Body, Flag, placement ) + : QskAspect( NoSubcontrol, Body, Flag, placement ) { } @@ -402,11 +404,21 @@ inline constexpr QskAspect::Subcontrol QskAspect::subControl() const noexcept return static_cast< Subcontrol >( m_bits.subControl ); } -inline void QskAspect::setSubControl( Subcontrol subControl ) noexcept +inline void QskAspect::setSubcontrol( Subcontrol subControl ) noexcept { m_bits.subControl = subControl; } +inline constexpr bool QskAspect::hasSubcontrol() const noexcept +{ + return m_bits.subControl != 0; +} + +inline void QskAspect::clearSubcontrol() noexcept +{ + m_bits.subControl = 0; +} + inline constexpr QskAspect::Section QskAspect::section() const noexcept { return static_cast< Section >( m_bits.section ); diff --git a/src/common/QskBoxBorderColors.cpp b/src/common/QskBoxBorderColors.cpp index 1029d029..87d31046 100644 --- a/src/common/QskBoxBorderColors.cpp +++ b/src/common/QskBoxBorderColors.cpp @@ -152,19 +152,24 @@ bool QskBoxBorderColors::isVisible() const bool QskBoxBorderColors::isMonochrome() const { - if ( m_gradients[ 1 ] != m_gradients[ 0 ] ) - return false; - - if ( m_gradients[ 2 ] != m_gradients[ 1 ] ) - return false; - - if ( m_gradients[ 3 ] != m_gradients[ 2 ] ) - return false; - - return m_gradients[ 0 ].isMonochrome() + if ( m_gradients[ 0 ].isMonochrome() && m_gradients[ 1 ].isMonochrome() && m_gradients[ 2 ].isMonochrome() - && m_gradients[ 3 ].isMonochrome(); + && m_gradients[ 3 ].isMonochrome() ) + { + if ( m_gradients[ 1 ].rgbStart() != m_gradients[ 0 ].rgbStart() ) + return false; + + if ( m_gradients[ 2 ].rgbStart() != m_gradients[ 1 ].rgbStart() ) + return false; + + if ( m_gradients[ 3 ].rgbStart() != m_gradients[ 2 ].rgbStart() ) + return false; + + return true; + } + + return false; } bool QskBoxBorderColors::isValid() const @@ -188,8 +193,8 @@ QskBoxBorderColors QskBoxBorderColors::interpolated( the color and use always use the other color. TODO ... */ #endif - auto& gradient = colors.m_gradients[ i ]; - gradient = gradient.interpolated( to.m_gradients[ i ], ratio ); + colors.m_gradients[ i ] = + m_gradients[ i ].interpolated( to.m_gradients[ i ], ratio ); } return colors; @@ -233,7 +238,7 @@ QDebug operator<<( QDebug debug, const QskBoxBorderColors& colors ) if ( colors.isMonochrome() ) { const auto& gradient = colors.gradientAt( Qt::LeftEdge ); - QskRgb::debugColor( debug, gradient.startColor() ); + QskRgb::debugColor( debug, gradient.rgbStart() ); } else { @@ -242,7 +247,7 @@ QDebug operator<<( QDebug debug, const QskBoxBorderColors& colors ) const char prompts[] = { 'L', 'T', 'R', 'B' }; const Edge edges[] = { LeftEdge, TopEdge, RightEdge, BottomEdge }; - for ( int i = 0; i <= 4; i++ ) + for ( int i = 0; i < 4; i++ ) { if ( i != 0 ) debug << ", "; @@ -252,7 +257,7 @@ QDebug operator<<( QDebug debug, const QskBoxBorderColors& colors ) debug << prompts[ i ] << ": "; if ( gradient.isValid() && gradient.isMonochrome() ) - QskRgb::debugColor( debug, gradient.startColor() ); + QskRgb::debugColor( debug, gradient.rgbStart() ); else debug << gradient; } diff --git a/src/common/QskBoxBorderMetrics.cpp b/src/common/QskBoxBorderMetrics.cpp index 18178062..9d96c82b 100644 --- a/src/common/QskBoxBorderMetrics.cpp +++ b/src/common/QskBoxBorderMetrics.cpp @@ -7,6 +7,7 @@ #include #include +#include static void qskRegisterBoxBorderMetrics() { @@ -79,6 +80,15 @@ QskBoxBorderMetrics QskBoxBorderMetrics::toAbsolute( const QSizeF& size ) const return absoluted; } +QRectF QskBoxBorderMetrics::adjustedRect( const QRectF& rect ) const +{ + if ( m_sizeMode == Qt::AbsoluteSize ) + return rect.marginsRemoved( m_widths ); + + const auto m = toAbsolute( rect.size() ); + return rect.marginsRemoved( m.m_widths ); +} + QskBoxBorderMetrics QskBoxBorderMetrics::interpolated( const QskBoxBorderMetrics& to, qreal ratio ) const noexcept { diff --git a/src/common/QskBoxBorderMetrics.h b/src/common/QskBoxBorderMetrics.h index 189eb30c..8d6d277b 100644 --- a/src/common/QskBoxBorderMetrics.h +++ b/src/common/QskBoxBorderMetrics.h @@ -77,6 +77,8 @@ class QSK_EXPORT QskBoxBorderMetrics constexpr bool isEquidistant() const noexcept; + QRectF adjustedRect( const QRectF& )const ; + private: QskMargins m_widths; Qt::SizeMode m_sizeMode; diff --git a/src/common/QskBoxShapeMetrics.cpp b/src/common/QskBoxShapeMetrics.cpp index 17c21be1..f0726f0e 100644 --- a/src/common/QskBoxShapeMetrics.cpp +++ b/src/common/QskBoxShapeMetrics.cpp @@ -89,48 +89,47 @@ QskBoxShapeMetrics QskBoxShapeMetrics::toAbsolute( const QSizeF& size ) const no if ( m_sizeMode != Qt::RelativeSize ) return *this; - QskBoxShapeMetrics absoluted = *this; - if ( size.isEmpty() ) + return QskBoxShapeMetrics(); + + QskBoxShapeMetrics shape = *this; + shape.m_sizeMode = Qt::AbsoluteSize; + + for ( int i = 0; i < 4; i++ ) { - for ( int i = 0; i < 4; i++ ) - absoluted.m_radii[ i ] = QSizeF( 0.0, 0.0 ); - } - else - { - for ( int i = 0; i < 4; i++ ) + auto& radius = shape.m_radii[ i ]; + + if ( radius.isEmpty() ) { - auto& radius = absoluted.m_radii[ i ]; + radius.rheight() = radius.rwidth() = 0.0; + continue; + } - const qreal rx = qskAbsoluted( size.width(), radius.width() ); - const qreal ry = qskAbsoluted( size.height(), radius.height() ); + const qreal rx = qskAbsoluted( size.width(), radius.width() ); + const qreal ry = qskAbsoluted( size.height(), radius.height() ); - switch ( m_aspectRatioMode ) + if ( m_scalingMode == Circular ) + { + radius.rheight() = radius.rwidth() = std::min( rx, ry ); + } + else + { + const auto ratio = radius.height() / radius.width(); + + if ( ratio >= 1.0 ) { - case Qt::IgnoreAspectRatio: - { - radius.rwidth() = rx; - radius.rheight() = ry; - break; - } - case Qt::KeepAspectRatio: - { - radius.rwidth() = std::min( rx, ry ); - radius.rheight() = std::min( rx, ry ); - break; - } - case Qt::KeepAspectRatioByExpanding: - { - radius.rwidth() = std::max( rx, ry ); - radius.rheight() = std::max( rx, ry ); - break; - } + radius.rwidth() = ry / ratio; + radius.rheight() = ry; + } + else + { + radius.rwidth() = rx; + radius.rheight() = rx * ratio; } } } - absoluted.m_sizeMode = Qt::AbsoluteSize; - return absoluted; + return shape; } QskBoxShapeMetrics QskBoxShapeMetrics::interpolated( @@ -146,7 +145,7 @@ QskBoxShapeMetrics QskBoxShapeMetrics::interpolated( qskInterpolatedSize( m_radii[ 1 ], to.m_radii[ 1 ], ratio ), qskInterpolatedSize( m_radii[ 2 ], to.m_radii[ 2 ], ratio ), qskInterpolatedSize( m_radii[ 3 ], to.m_radii[ 3 ], ratio ), - to.m_sizeMode, to.m_aspectRatioMode ); + to.m_sizeMode, to.m_scalingMode ); } QVariant QskBoxShapeMetrics::interpolate( diff --git a/src/common/QskBoxShapeMetrics.h b/src/common/QskBoxShapeMetrics.h index 637400fd..c9b04c53 100644 --- a/src/common/QskBoxShapeMetrics.h +++ b/src/common/QskBoxShapeMetrics.h @@ -26,10 +26,17 @@ class QSK_EXPORT QskBoxShapeMetrics Q_PROPERTY( qreal radius READ radiusX WRITE setRadius ) Q_PROPERTY( Qt::SizeMode sizeMode READ sizeMode WRITE setSizeMode ) - Q_PROPERTY( Qt::AspectRatioMode aspectRatioMode - READ aspectRatioMode WRITE setAspectRatioMode ) + Q_PROPERTY( ScalingMode scalingMode READ scalingMode WRITE setScalingMode ) public: + enum ScalingMode + { + // How to scale, when translating to Qt::AbsoluteSize + Circular, + Elliptic + }; + Q_ENUM( ScalingMode ); + constexpr QskBoxShapeMetrics() noexcept; constexpr QskBoxShapeMetrics( qreal topLeft, qreal topRight, @@ -83,8 +90,8 @@ class QSK_EXPORT QskBoxShapeMetrics void setSizeMode( Qt::SizeMode ) noexcept; constexpr Qt::SizeMode sizeMode() const noexcept; - void setAspectRatioMode( Qt::AspectRatioMode ) noexcept; - constexpr Qt::AspectRatioMode aspectRatioMode() const noexcept; + void setScalingMode( ScalingMode ) noexcept; + constexpr ScalingMode scalingMode() const noexcept; QskBoxShapeMetrics interpolated( const QskBoxShapeMetrics&, qreal value ) const noexcept; @@ -105,22 +112,22 @@ class QSK_EXPORT QskBoxShapeMetrics inline constexpr QskBoxShapeMetrics( const QSizeF& topLeft, const QSizeF& topRight, const QSizeF& bottomLeft, const QSizeF& bottomRight, - Qt::SizeMode sizeMode, Qt::AspectRatioMode aspectRatioMode ) noexcept + Qt::SizeMode sizeMode, ScalingMode scalingMode ) noexcept : m_radii{ topLeft, topRight, bottomLeft, bottomRight } , m_sizeMode( sizeMode ) - , m_aspectRatioMode( aspectRatioMode ) + , m_scalingMode( scalingMode ) { } QSizeF m_radii[ 4 ]; Qt::SizeMode m_sizeMode : 2; - Qt::AspectRatioMode m_aspectRatioMode : 2; + ScalingMode m_scalingMode : 1; }; inline constexpr QskBoxShapeMetrics::QskBoxShapeMetrics() noexcept : m_radii{ { 0.0, 0.0 }, { 0.0, 0.0 }, { 0.0, 0.0 }, { 0.0, 0.0 } } , m_sizeMode( Qt::AbsoluteSize ) - , m_aspectRatioMode( Qt::KeepAspectRatio ) + , m_scalingMode( Circular ) { } @@ -135,7 +142,7 @@ inline constexpr QskBoxShapeMetrics::QskBoxShapeMetrics( : m_radii{ { radiusX, radiusY }, { radiusX, radiusY }, { radiusX, radiusY }, { radiusX, radiusY } } , m_sizeMode( sizeMode ) - , m_aspectRatioMode( Qt::KeepAspectRatio ) + , m_scalingMode( Circular ) { } @@ -144,7 +151,7 @@ inline constexpr QskBoxShapeMetrics::QskBoxShapeMetrics( qreal topLeft, qreal to : m_radii{ { topLeft, topLeft }, { topRight, topRight }, { bottomLeft, bottomLeft }, { bottomRight, bottomRight } } , m_sizeMode( sizeMode ) - , m_aspectRatioMode( Qt::KeepAspectRatio ) + , m_scalingMode( Circular ) { } @@ -152,7 +159,7 @@ inline constexpr bool QskBoxShapeMetrics::operator==( const QskBoxShapeMetrics& other ) const noexcept { return ( m_sizeMode == other.m_sizeMode ) - && ( m_aspectRatioMode == other.m_aspectRatioMode ) + && ( m_scalingMode == other.m_scalingMode ) && ( m_radii[ 0 ] == other.m_radii[ 0 ] ) && ( m_radii[ 1 ] == other.m_radii[ 1 ] ) && ( m_radii[ 2 ] == other.m_radii[ 2 ] ) @@ -250,15 +257,15 @@ inline constexpr Qt::SizeMode QskBoxShapeMetrics::sizeMode() const noexcept return m_sizeMode; } -inline void QskBoxShapeMetrics::setAspectRatioMode( - Qt::AspectRatioMode aspectRatioMode ) noexcept +inline void QskBoxShapeMetrics::setScalingMode( ScalingMode scalingMode ) noexcept { - m_aspectRatioMode = aspectRatioMode; + m_scalingMode = scalingMode; } -inline constexpr Qt::AspectRatioMode QskBoxShapeMetrics::aspectRatioMode() const noexcept +inline constexpr QskBoxShapeMetrics::ScalingMode + QskBoxShapeMetrics::scalingMode() const noexcept { - return m_aspectRatioMode; + return m_scalingMode; } inline constexpr bool QskBoxShapeMetrics::isRectellipse() const noexcept @@ -281,7 +288,7 @@ inline constexpr QskBoxShapeMetrics QskBoxShapeMetrics::transposed() const noexc return QskBoxShapeMetrics( m_radii[ 0 ].transposed(), m_radii[ 1 ].transposed(), m_radii[ 2 ].transposed(), m_radii[ 3 ].transposed(), - m_sizeMode, m_aspectRatioMode ); + m_sizeMode, m_scalingMode ); } #ifndef QT_NO_DEBUG_STREAM diff --git a/src/common/QskGradient.cpp b/src/common/QskGradient.cpp index b4294a39..6619f66c 100644 --- a/src/common/QskGradient.cpp +++ b/src/common/QskGradient.cpp @@ -5,11 +5,11 @@ #include "QskGradient.h" #include "QskRgbValue.h" +#include "QskGradientDirection.h" +#include "QskFunctions.h" -#include #include - -#include +#include static void qskRegisterGradient() { @@ -25,21 +25,13 @@ static void qskRegisterGradient() Q_CONSTRUCTOR_FUNCTION( qskRegisterGradient ) -static inline QskGradient::Orientation qskOrientation( Qt::Orientation o ) -{ - return ( o == Qt::Vertical ) - ? QskGradient::Vertical : QskGradient::Horizontal; -} - static inline bool qskIsGradientValid( const QskGradientStops& stops ) { - if ( stops.size() < 2 ) + if ( stops.isEmpty() ) return false; - if ( stops.first().position() != 0.0 || stops.last().position() != 1.0 ) - { + if ( stops.first().position() < 0.0 || stops.last().position() > 1.0 ) return false; - } if ( !stops.first().color().isValid() ) return false; @@ -56,235 +48,224 @@ static inline bool qskIsGradientValid( const QskGradientStops& stops ) return true; } -static inline bool qskIsMonochrome( const QskGradientStops& stops ) +static inline bool qskCanBeInterpolated( const QskGradient& from, const QskGradient& to ) { - for ( int i = 1; i < stops.size(); i++ ) - { - if ( stops[ i ].color() != stops[ 0 ].color() ) - return false; - } + if ( from.isMonochrome() || to.isMonochrome() ) + return true; - return true; + return from.type() == to.type(); } -static inline bool qskIsVisible( const QskGradientStops& stops ) +static inline QTransform qskTransformForRect( int, const QRectF& rect ) { - for ( const auto& stop : stops ) - { - const auto& c = stop.color(); - if ( c.isValid() && c.alpha() > 0 ) - return true; - } + const qreal x = rect.x(); + const qreal y = rect.y(); + const qreal w = rect.width(); + const qreal h = rect.height(); - return false; + return QTransform( w, 0, 0, h, x, y ); } - -static inline QColor qskInterpolated( - const QskGradientStop& s1, const QskGradientStop& s2, qreal pos ) -{ - if ( s1.color() == s2.color() ) - return s1.color(); - - const qreal ratio = ( pos - s1.position() ) / ( s2.position() - s1.position() ); - return QskRgb::interpolated( s1.color(), s2.color(), ratio ); -} - -static inline bool qskComparePositions( - const QskGradientStops& s1, const QskGradientStops& s2 ) -{ - if ( s1.count() != s2.count() ) - return false; - - // the first is always at 0.0, the last at 1.0 - for ( int i = 1; i < s1.count() - 1; i++ ) - { - if ( s1[ i ].position() != s2[ i ].position() ) - return false; - } - - return true; -} - -static inline QskGradientStops qskExpandedStops( - const QskGradientStops& s1, const QskGradientStops& s2 ) -{ - // expand s1 by stops matching to the positions from s2 - - if ( qskComparePositions( s1, s2 ) ) - return s1; - - QskGradientStops stops; - - stops += s1.first(); - - int i = 1, j = 1; - while ( ( i < s1.count() - 1 ) || ( j < s2.count() - 1 ) ) - { - if ( s1[ i ].position() < s2[ j ].position() ) - { - stops += s1[ i++ ]; - } - else - { - const qreal pos = s2[ j++ ].position(); - stops += QskGradientStop( pos, qskInterpolated( s1[ i - 1 ], s1[ i ], pos ) ); - } - } - - stops += s1.last(); - - return stops; -} - -static inline QskGradientStops qskExtractedStops( - const QskGradientStops& stops, qreal from, qreal to ) -{ - QskGradientStops extracted; - - if ( from == to ) - extracted.reserve( 2 ); - else - extracted.reserve( stops.size() ); - - int i = 0; - - if ( from == 0.0 ) - { - extracted += QskGradientStop( 0.0, stops[i++].color() ); - } - else - { - for ( i = 1; i < stops.count(); i++ ) - { - if ( stops[i].position() > from ) - break; - } - - const auto color = - QskGradientStop::interpolated( stops[i - 1], stops[i], from ); - - extracted += QskGradientStop( 0.0, color ); - } - - for ( ; i < stops.count(); i++ ) - { - const auto& s = stops[i]; - - if ( s.position() >= to ) - break; - - const auto pos = ( s.position() - from ) / ( to - from ); - extracted += QskGradientStop( pos, s.color() ); - } - - const auto color = QskGradientStop::interpolated( stops[i - 1], stops[i], to ); - extracted += QskGradientStop( 1.0, color ); - - return extracted; -} - -static inline QskGradientStops qskGradientStops( const QGradientStops& qtStops ) -{ - QskGradientStops stops; - stops.reserve( qtStops.count() ); - - for ( const auto& s : qtStops ) - stops += QskGradientStop( s.first, s.second ); - - return stops; -} - -static inline QskGradientStops qskColorStops( - const QRgb* rgb, int count, bool discrete ) -{ - QskGradientStops stops; - - if ( discrete ) - stops.reserve( 2 * count - 2 ); - else - stops.reserve( count ); - - stops += QskGradientStop( 0.0, rgb[0] ); - - if ( discrete ) - { - const auto step = 1.0 / count; - - for ( int i = 1; i < count; i++ ) - { - const qreal pos = i * step; - stops += QskGradientStop( pos, rgb[i - 1] ); - stops += QskGradientStop( pos, rgb[i] ); - } - } - else - { - const auto step = 1.0 / ( count - 1 ); - - for ( int i = 1; i < count - 1; i++ ) - stops += QskGradientStop( i * step, rgb[i] ); - } - - stops += QskGradientStop( 1.0, rgb[count - 1] ); - - return stops; -} - -QskGradient::QskGradient( Orientation orientation ) - : m_orientation( orientation ) - , m_isDirty( false ) - , m_isValid( false ) - , m_isMonchrome( true ) - , m_isVisible( false ) -{ -} - + QskGradient::QskGradient( const QColor& color ) - : QskGradient( Vertical ) + : QskGradient() { - setColor( color ); + setStops( color ); } -QskGradient::QskGradient( Qt::Orientation orientation, - const QColor& startColor, const QColor& stopColor ) - : QskGradient( qskOrientation( orientation ), startColor, stopColor ) +QskGradient::QskGradient( const QColor& color1, const QColor& color2 ) + : QskGradient() { + setStops( color1, color2 ); } -QskGradient::QskGradient( Orientation orientation, - const QColor& startColor, const QColor& stopColor ) - : QskGradient( orientation ) +QskGradient::QskGradient( QGradient::Preset preset ) + : QskGradient() { - setColors( startColor, stopColor ); + setStops( qskBuildGradientStops( QGradient( preset ).stops() ) ); } -QskGradient::QskGradient( Qt::Orientation orientation, const QskGradientStops& stops ) - : QskGradient( qskOrientation( orientation ), stops ) -{ -} - -QskGradient::QskGradient( Orientation orientation, const QskGradientStops& stops ) - : QskGradient( orientation ) +QskGradient::QskGradient( const QVector< QskGradientStop >& stops ) + : QskGradient() { setStops( stops ); } -QskGradient::QskGradient( Qt::Orientation orientation, QGradient::Preset preset ) - : QskGradient( qskOrientation( orientation ), preset ) +QskGradient::QskGradient( const QGradient& qGradient ) + : QskGradient() { + switch( qGradient.type() ) + { + case QGradient::LinearGradient: + { + m_type = Linear; + + const auto g = static_cast< const QLinearGradient* >( &qGradient ); + + m_values[0] = g->start().x(); + m_values[1] = g->start().y(); + m_values[2] = g->finalStop().x(); + m_values[3] = g->finalStop().y(); + + break; + } + case QGradient::RadialGradient: + { + m_type = Radial; + + const auto g = static_cast< const QRadialGradient* >( &qGradient ); + + if ( ( g->center() != g->focalPoint() ) || ( g->radius() != g->focalRadius() ) ) + qWarning() << "QskGradient: extended radial gradients are not supported."; + + m_values[0] = g->focalPoint().x(); + m_values[1] = g->focalPoint().y(); + m_values[3] = m_values[2] = g->focalRadius(); + + break; + } + case QGradient::ConicalGradient: + { + m_type = Conic; + + const auto g = static_cast< const QConicalGradient* >( &qGradient ); + + m_values[0] = g->center().x(); + m_values[1] = g->center().y(); + m_values[2] = g->angle(); + m_values[3] = 360.0; + + break; + } + default: + { + m_type = Stops; + break; + } + } + + m_spreadMode = static_cast< SpreadMode >( qGradient.spread() ); + + switch( qGradient.coordinateMode() ) + { + case QGradient::ObjectMode: + case QGradient::ObjectBoundingMode: + + m_stretchMode = StretchToSize; + break; + + case QGradient::LogicalMode: + + m_stretchMode = NoStretch; + break; + + case QGradient::StretchToDeviceMode: + { + qWarning() << "QskGradient: StretchToDeviceMode is not supportd."; + m_stretchMode = NoStretch; + } + } + + setStops( qskBuildGradientStops( qGradient.stops() ) ); } -QskGradient::QskGradient( Orientation orientation, QGradient::Preset preset ) - : QskGradient( orientation ) +QskGradient::QskGradient( const QskGradient& other ) noexcept + : m_stops( other.m_stops ) + , m_values{ other.m_values[0], other.m_values[1], + other.m_values[2], other.m_values[3], } + , m_type( other.m_type ) + , m_spreadMode( other.m_spreadMode ) + , m_stretchMode( other.m_stretchMode ) + , m_isDirty( other.m_isDirty ) + , m_isValid( other.m_isValid ) + , m_isMonchrome( other.m_isMonchrome ) + , m_isVisible( other.m_isVisible ) { - setStops( qskGradientStops( QGradient( preset ).stops() ) ); } QskGradient::~QskGradient() { } -bool QskGradient::isValid() const +QskGradient& QskGradient::operator=( const QskGradient& other ) noexcept +{ + m_type = other.m_type; + m_spreadMode = other.m_spreadMode; + m_stretchMode = other.m_stretchMode; + m_stops = other.m_stops; + + 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_isDirty = other.m_isDirty; + m_isValid = other.m_isValid; + m_isMonchrome = other.m_isMonchrome; + m_isVisible = other.m_isVisible; + + return *this; +} + +bool QskGradient::operator==( const QskGradient& other ) const noexcept +{ + return ( m_type == other.m_type ) + && ( m_spreadMode == other.m_spreadMode ) + && ( m_stretchMode == other.m_stretchMode ) + && ( 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_stops == other.m_stops ); +} + +void QskGradient::updateStatusBits() const +{ + // doing all bits in one loop ? + m_isValid = qskIsGradientValid( m_stops ); + + if ( m_isValid ) + { + m_isMonchrome = qskIsMonochrome( m_stops ); + m_isVisible = qskIsVisible( m_stops ); + } + else + { + m_isMonchrome = true; + m_isVisible = false; + } + + if ( m_isVisible ) + { + switch( m_type ) + { + case Linear: + { + m_isVisible = !( qskFuzzyCompare( m_values[0], m_values[2] ) + && qskFuzzyCompare( m_values[1], m_values[3] ) ); + break; + } + + case Radial: + { + m_isVisible = m_values[2] > 0.0 || m_values[3] > 0.0; // radius + break; + } + + case Conic: + { + m_isVisible = !qFuzzyIsNull( m_values[3] ); // spanAngle + break; + } + + default: + break; + } + } + + m_isDirty = false; +} + +bool QskGradient::isValid() const noexcept { if ( m_isDirty ) updateStatusBits(); @@ -292,16 +273,7 @@ bool QskGradient::isValid() const return m_isValid; } -void QskGradient::invalidate() -{ - if ( !m_stops.isEmpty() ) - { - m_stops.clear(); - m_isDirty = true; - } -} - -bool QskGradient::isMonochrome() const +bool QskGradient::isMonochrome() const noexcept { if ( m_isDirty ) updateStatusBits(); @@ -309,7 +281,7 @@ bool QskGradient::isMonochrome() const return m_isMonchrome; } -bool QskGradient::isVisible() const +bool QskGradient::isVisible() const noexcept { if ( m_isDirty ) updateStatusBits(); @@ -317,110 +289,79 @@ bool QskGradient::isVisible() const return m_isVisible; } -void QskGradient::setOrientation( Qt::Orientation orientation ) +void QskGradient::setStops( const QColor& color ) { - setOrientation( qskOrientation( orientation ) ); -} - -void QskGradient::setOrientation( Orientation orientation ) -{ - // does not change m_isDirty - m_orientation = orientation; -} - -void QskGradient::setColor( const QColor& color ) -{ - m_stops.clear(); - m_stops.reserve( 2 ); - - m_stops.append( QskGradientStop( 0.0, color ) ); - m_stops.append( QskGradientStop( 1.0, color ) ); - + m_stops = { { 0.0, color }, { 1.0, color } }; m_isDirty = true; } -void QskGradient::setColors( const QColor& startColor, const QColor& stopColor ) +void QskGradient::setStops( const QColor& color1, const QColor& color2 ) { - m_stops.clear(); - m_stops.reserve( 2 ); - - m_stops.append( QskGradientStop( 0.0, startColor ) ); - m_stops.append( QskGradientStop( 1.0, stopColor ) ); - + m_stops = { { 0.0, color1 }, { 1.0, color2 } }; m_isDirty = true; } +void QskGradient::setStops( QGradient::Preset preset ) +{ + const auto stops = qskBuildGradientStops( QGradient( preset ).stops() ); + setStops( stops ); +} + void QskGradient::setStops( const QskGradientStops& stops ) { - if ( !qskIsGradientValid( stops ) ) + if ( !stops.isEmpty() && !qskIsGradientValid( stops ) ) { qWarning( "Invalid gradient stops" ); - invalidate(); - return; + m_stops.clear(); } - - m_stops = stops; - m_isDirty = true; -} - -const QskGradientStops& QskGradient::stops() const -{ -#if 1 - /* - Returning a const& so that it is possible to write: - for ( const auto& stop : qAsConst( gradient.stops() ) ) - - Once we have changed QskGradientStop from QColor to QRgb - we should check if there is a better solution possible - */ -#endif - return m_stops; -} - -int QskGradient::stopCount() const -{ - return m_stops.count(); -} - -void QskGradient::setStopAt( int index, qreal stop ) -{ - if ( stop < 0.0 || stop > 1.0 ) + else { - qWarning( "Invalid gradient stop: %g, must be in the range [0,1]", stop ); - return; + m_stops = stops; } - if ( index >= m_stops.size() ) - m_stops.resize( index + 1 ); - - m_stops[ index ].setPosition( stop ); m_isDirty = true; } -qreal QskGradient::stopAt( int index ) const +int QskGradient::stepCount() const noexcept { - if ( index >= m_stops.size() ) + if ( !isValid() ) + return 0; + + int steps = m_stops.count() - 1; + + if ( m_stops.first().position() > 0.0 ) + steps++; + + if ( m_stops.last().position() < 1.0 ) + steps++; + + return steps; +} + +qreal QskGradient::stopAt( int index ) const noexcept +{ + if ( index < 0 || index >= m_stops.size() ) return -1.0; return m_stops[ index ].position(); } -void QskGradient::setColorAt( int index, const QColor& color ) +bool QskGradient::hasStopAt( qreal value ) const noexcept { - if ( !color.isValid() ) + // better use binary search TODO ... + for ( auto& stop : m_stops ) { - qWarning( "Invalid gradient color" ); - return; + if ( stop.position() == value ) + return true; + + if ( stop.position() > value ) + break; } - if ( index >= m_stops.size() ) - m_stops.resize( index + 1 ); - - m_stops[ index ].setColor( color ); - m_isDirty = true; + return false; } -QColor QskGradient::colorAt( int index ) const +QColor QskGradient::colorAt( int index ) const noexcept { if ( index >= m_stops.size() ) return QColor(); @@ -443,33 +384,82 @@ void QskGradient::setAlpha( int alpha ) m_isDirty = true; } -bool QskGradient::hasStopAt( qreal value ) const +void QskGradient::setSpreadMode( SpreadMode spreadMode ) { - // better use binary search TODO ... - for ( auto& stop : m_stops ) - { - if ( stop.position() == value ) - return true; - - if ( stop.position() > value ) - break; - } - - return false; + m_spreadMode = spreadMode; } -QskHashValue QskGradient::hash( QskHashValue seed ) const +void QskGradient::setStretchMode( StretchMode stretchMode ) { - if ( m_stops.isEmpty() ) - return seed; + m_stretchMode = stretchMode; +} - const auto o = orientation(); +void QskGradient::stretchTo( const QRectF& rect ) +{ + if ( m_stretchMode == NoStretch || m_type == Stops || rect.isEmpty() ) + return; // nothing to do - auto hash = qHashBits( &o, sizeof( o ), seed ); - for ( const auto& stop : m_stops ) - hash = stop.hash( hash ); + const auto transform = qskTransformForRect( m_stretchMode, rect ); - return hash; + switch( static_cast< int >( m_type ) ) + { + case Linear: + { + transform.map( m_values[0], m_values[1], &m_values[0], &m_values[1] ); + transform.map( m_values[2], m_values[3], &m_values[2], &m_values[3] ); + + break; + } + case Radial: + { + transform.map( m_values[0], m_values[1], &m_values[0], &m_values[1] ); + + qreal rx = qMax( m_values[2], 0.0 ); + qreal ry = qMax( m_values[3], 0.0 ); + + if ( rx == 0.0 || ry == 0.0 ) + { + /* + It would be more logical if the scaling happens according + the width, when rx is set ad v.v. But fitting the circle is + probably, what most use cases need - and how to specify + this. Maybe by introducing another stretchMode ... TODO + */ + const qreal r = qMin( rect.width(), rect.height() ) * qMax( rx, ry ); + m_values[2] = m_values[3] = r; + } + else + { + m_values[2] = rx * rect.width(); + m_values[3] = ry * rect.height(); + } + + break; + } + case Conic: + { + transform.map( m_values[0], m_values[1], &m_values[0], &m_values[1] ); + break; + } + } + + m_stretchMode = NoStretch; +} + +QskGradient QskGradient::stretchedTo( const QSizeF& size ) const +{ + return stretchedTo( QRectF( 0.0, 0.0, size.width(), size.height() ) ); +} + +QskGradient QskGradient::stretchedTo( const QRectF& rect ) const +{ + if ( m_stretchMode == NoStretch ) + return *this; + + QskGradient g = *this; + g.stretchTo( rect ); + + return g; } void QskGradient::reverse() @@ -492,158 +482,79 @@ QskGradient QskGradient::reversed() const QskGradient QskGradient::extracted( qreal from, qreal to ) const { - if ( from > to ) - return QskGradient( m_orientation ); + auto gradient = *this; - if ( isMonochrome() || ( from <= 0.0 && to >= 1.0 ) ) - return *this; + if ( !isValid() || ( from > to ) || ( from > 1.0 ) ) + { + gradient.clearStops(); + } + else if ( isMonochrome() ) + { + from = qMax( from, 0.0 ); + to = qMin( to, 1.0 ); - from = qMax( from, 0.0 ); - to = qMin( to, 1.0 ); + const auto color = m_stops.first().color(); - const auto stops = qskExtractedStops( m_stops, from, to ); - return QskGradient( orientation(), stops ); + gradient.setStops( { { from, color }, { to, color } } ); + } + else + { + gradient.setStops( qskExtractedGradientStops( m_stops, from, to ) ); + } + + return gradient; } -QskGradient QskGradient::interpolated( - const QskGradient& to, qreal value ) const +QskGradient QskGradient::interpolated( const QskGradient& to, qreal ratio ) const { - if ( !( isValid() && to.isValid() ) ) + if ( !isValid() && !to.isValid() ) + return to; + + QskGradient gradient; + + if ( qskCanBeInterpolated( *this, to ) ) { - if ( !isValid() && !to.isValid() ) - return to; + // We simply interpolate stops and values - qreal progress; - const QskGradient* gradient; + gradient = to; - if ( to.isValid() ) - { - progress = value; - gradient = &to; - } - else - { - progress = 1.0 - value; - gradient = this; - } + const auto stops = qskInterpolatedGradientStops( + m_stops, isMonochrome(), to.m_stops, to.isMonochrome(), ratio ); - /* - We interpolate as if the invalid gradient would be - a transparent version of the valid gradient - */ + gradient.setStops( stops ); - auto stops = gradient->m_stops; - for ( auto& stop : stops ) - { - auto c = stop.color(); - c.setAlpha( c.alpha() * progress ); - - stop.setColor( c ); - } - - return QskGradient( gradient->orientation(), stops ); - } - - if ( isMonochrome() && to.isMonochrome() ) - { - const auto c = QskRgb::interpolated( - m_stops[ 0 ].color(), to.m_stops[ 0 ].color(), value ); - - return QskGradient( to.orientation(), c, c ); - } - - if ( isMonochrome() ) - { - // we can ignore our stops - - const auto c = m_stops[ 0 ].color(); - - auto s2 = to.m_stops; - for ( int i = 0; i < s2.count(); i++ ) - { - const auto c2 = QskRgb::interpolated( c, s2[ i ].color(), value ); - s2[ i ].setColor( c2 ); - } - - return QskGradient( to.orientation(), s2 ); - } - - if ( to.isMonochrome() ) - { - // we can ignore the stops of to - - const auto c = to.m_stops[ 0 ].color(); - - auto s2 = m_stops; - for ( int i = 0; i < s2.count(); i++ ) - { - const auto c2 = QskRgb::interpolated( s2[ i ].color(), c, value ); - s2[ i ].setColor( c2 ); - } - - return QskGradient( orientation(), s2 ); - } - - if ( m_orientation == to.m_orientation ) - { - /* - we need to have the same number of stops - at the same positions - */ - - const auto s1 = qskExpandedStops( m_stops, to.m_stops ); - auto s2 = qskExpandedStops( to.m_stops, m_stops ); - - for ( int i = 0; i < s1.count(); i++ ) - { - const auto c2 = QskRgb::interpolated( - s1[ i ].color(), s2[ i ].color(), value ); - - s2[ i ].setColor( c2 ); - } - - return QskGradient( orientation(), s2 ); + for ( uint i = 0; i < sizeof( m_values ) / sizeof( m_values[0] ); i++ ) + gradient.m_values[i] = m_values[i] + ratio * ( to.m_values[i] - m_values[i] ); } else { /* The interpolation is devided into 2 steps. First we - interpolate into a monochrome gradient and then change - the orientation before we continue in direction of the - final gradient. + interpolate into a monochrome gradient and then + recolor the gradient towards the target gradient + This will always result in a smooth transition - even, when + interpolating between different gradient types */ - const auto c = m_stops[ 0 ].color(); + const auto c = QskRgb::interpolated( startColor(), to.startColor(), 0.5 ); - if ( value <= 0.5 ) + if ( ratio < 0.5 ) { - auto s2 = m_stops; + const auto r = 2.0 * ratio; - for ( int i = 0; i < s2.count(); i++ ) - { - const auto c2 = QskRgb::interpolated( - s2[ i ].color(), c, 2 * value ); - - s2[ i ].setColor( c2 ); - } - - return QskGradient( orientation(), s2 ); + gradient = *this; + gradient.setStops( qskInterpolatedGradientStops( m_stops, c, r ) ); } else { - auto s2 = to.m_stops; + const auto r = 2.0 * ( ratio - 0.5 ); - for ( int i = 0; i < s2.count(); i++ ) - { - const auto c2 = QskRgb::interpolated( - c, s2[ i ].color(), 2 * ( value - 0.5 ) ); - - s2[ i ].setColor( c2 ); - } - - return QskGradient( to.orientation(), s2 ); + gradient = to; + gradient.setStops( qskInterpolatedGradientStops( c, to.m_stops, r ) ); } } + + return gradient; } QVariant QskGradient::interpolate( @@ -652,45 +563,201 @@ QVariant QskGradient::interpolate( return QVariant::fromValue( from.interpolated( to, progress ) ); } -void QskGradient::updateStatusBits() const +void QskGradient::clearStops() { - // doing all bits in one loop ? - m_isValid = qskIsGradientValid( m_stops ); - - if ( m_isValid ) + if ( !m_stops.isEmpty() ) { - m_isMonchrome = qskIsMonochrome( m_stops ); - m_isVisible = qskIsVisible( m_stops ); + m_stops.clear(); + m_isDirty = true; } - else - { - m_isMonchrome = true; - m_isVisible = false; - } - - m_isDirty = false; } -QskGradientStops QskGradient::colorStops( - const QVector< QRgb >& rgb, bool discrete ) +QskHashValue QskGradient::hash( QskHashValue seed ) const { - const int count = rgb.count(); + auto hash = qHash( m_type, seed ); + hash = qHash( m_spreadMode, seed ); + hash = qHash( m_stretchMode, seed ); - if ( count == 0 ) - return QskGradientStops(); + if ( m_type != Stops ) + hash = qHashBits( m_values, sizeof( m_values ), hash ); - if ( count == 0 ) + for ( const auto& stop : m_stops ) + hash = stop.hash( hash ); + + return hash; +} + +void QskGradient::setLinearDirection( Qt::Orientation orientation ) +{ + setLinearDirection( QskLinearDirection( orientation ) ); +} + +void QskGradient::setLinearDirection( qreal x1, qreal y1, qreal x2, qreal y2 ) +{ + setLinearDirection( QskLinearDirection( x1, y1, x2, y2 ) ); +} + +void QskGradient::setLinearDirection( const QskLinearDirection& direction ) +{ + m_type = Linear; + + m_values[0] = direction.x1(); + m_values[1] = direction.y1(); + m_values[2] = direction.x2(); + m_values[3] = direction.y2(); +} + +QskLinearDirection QskGradient::linearDirection() const +{ + Q_ASSERT( m_type == Linear ); + + if ( m_type != Linear ) + return QskLinearDirection( 0.0, 0.0, 0.0, 0.0 ); + + return QskLinearDirection( m_values[0], m_values[1], m_values[2], m_values[3] ); +} + +void QskGradient::setRadialDirection( const qreal x, qreal y, qreal radius ) +{ + setRadialDirection( QskRadialDirection( x, y, radius ) ); +} + +void QskGradient::setRadialDirection( const qreal x, qreal y,qreal radiusX, qreal radiusY ) +{ + setRadialDirection( QskRadialDirection( x, y, radiusX, radiusY ) ); +} +void QskGradient::setRadialDirection( const QskRadialDirection& direction ) +{ + m_type = Radial; + + m_values[0] = direction.center().x(); + m_values[1] = direction.center().y(); + m_values[2] = direction.radiusX(); + m_values[3] = direction.radiusY(); +} + +QskRadialDirection QskGradient::radialDirection() const +{ + Q_ASSERT( m_type == Radial ); + + if ( m_type != Radial ) + return QskRadialDirection( 0.5, 0.5, 0.0 ); + + return QskRadialDirection( m_values[0], m_values[1], m_values[2], m_values[3] ); +} + +void QskGradient::setConicDirection( qreal x, qreal y ) +{ + setConicDirection( QskConicDirection( x, y ) ); +} + +void QskGradient::setConicDirection( qreal x, qreal y, + qreal startAngle, qreal spanAngle ) +{ + setConicDirection( QskConicDirection( x, y, startAngle, spanAngle ) ); +} + +void QskGradient::setConicDirection( const QskConicDirection& direction ) +{ + m_type = Conic; + + m_values[0] = direction.center().x(); + m_values[1] = direction.center().y(); + m_values[2] = direction.startAngle(); + m_values[3] = direction.spanAngle(); +} + +QskConicDirection QskGradient::conicDirection() const +{ + Q_ASSERT( m_type == Conic ); + + if ( m_type != Conic ) + return QskConicDirection( 0.5, 0.5, 0.0, 0.0 ); + + return QskConicDirection( m_values[0], m_values[1], m_values[2], m_values[3] ); +} + +void QskGradient::setDirection( Type type ) +{ + switch( type ) { - QskGradientStops stops; - stops.reserve( 2 ); + case Linear: + setLinearDirection( QskLinearDirection() ); + break; - stops += QskGradientStop( 0.0, rgb[0] ); - stops += QskGradientStop( 1.0, rgb[0] ); + case Radial: + setRadialDirection( QskRadialDirection() ); + break; - return stops; + case Conic: + setConicDirection( QskConicDirection() ); + break; + + case Stops: + resetDirection(); + break; + } +} + +void QskGradient::resetDirection() +{ + m_type = Stops; + m_values[0] = m_values[1] = m_values[2] = m_values[3] = 0.0; +} + +QskGradient QskGradient::effectiveGradient() const +{ + if ( ( m_type == QskGradient::Stops ) || isMonochrome() ) + { + // the shader for linear gradients is the fastest + + QskGradient g = *this; + g.setDirection( QskGradient::Linear ); + + return g; } - return qskColorStops( rgb.constData(), count, discrete ); + return *this; +} + +QGradient QskGradient::toQGradient() const +{ + QGradient g; + + switch( static_cast< int >( m_type ) ) + { + case Linear: + { + g = QLinearGradient( m_values[0], m_values[1], m_values[2], m_values[3] ); + break; + } + + case Radial: + { + g = QRadialGradient( m_values[0], m_values[1], m_values[2] ); + break; + } + + case Conic: + { + if ( m_values[3] != 360.0 ) + { + qWarning() << + "QskGradient: spanAngle got lost, when converting to QConicalGradient"; + } + + g = QConicalGradient( m_values[0], m_values[1], m_values[2] ); + break; + } + } + + g.setCoordinateMode( m_stretchMode == NoStretch + ? QGradient::LogicalMode : QGradient::ObjectMode ); + + g.setSpread( static_cast< QGradient::Spread >( m_spreadMode ) ); + g.setStops( qskToQGradientStops( m_stops ) ); + + return g; } #ifndef QT_NO_DEBUG_STREAM @@ -702,46 +769,95 @@ QDebug operator<<( QDebug debug, const QskGradient& gradient ) QDebugStateSaver saver( debug ); debug.nospace(); - debug << "Gradient"; + debug << "QskGradient("; - if ( !gradient.isValid() ) + switch ( gradient.type() ) { - debug << "()"; + case QskGradient::Linear: + { + debug << " L("; + + const auto dir = gradient.linearDirection(); + debug << dir.start().x() << "," << dir.start().y() + << "," << dir.stop().x() << "," << dir.stop().y() << ")"; + + break; + } + + case QskGradient::Radial: + { + debug << " R("; + + const auto dir = gradient.radialDirection(); + + debug << dir.center().x() << "," << dir.center().y() + << "," << dir.radiusX() << dir.radiusY() << ")"; + + break; + } + + case QskGradient::Conic: + { + debug << " C("; + + const auto dir = gradient.conicDirection(); + + debug << dir.center().x() << "," << dir.center().y() + << ",[" << dir.startAngle() << "," << dir.spanAngle() << "])"; + break; + } + + case QskGradient::Stops: + { + break; + } } - else - { - debug << "( "; + if ( !gradient.stops().isEmpty() ) + { if ( gradient.isMonochrome() ) { - QskRgb::debugColor( debug, gradient.startColor() ); + debug << ' '; + QskRgb::debugColor( debug, gradient.rgbStart() ); } else { - const char o[] = { 'H', 'V', 'D' }; - debug << o[ gradient.orientation() ] << ", "; + debug << " ( "; - if ( gradient.stops().count() == 2 ) + const auto& stops = gradient.stops(); + for ( int i = 0; i < stops.count(); i++ ) { - QskRgb::debugColor( debug, gradient.startColor() ); - debug << ", "; - QskRgb::debugColor( debug, gradient.endColor() ); - } - else - { - const auto& s = gradient.stops(); - for ( int i = 0; i < s.count(); i++ ) - { - if ( i != 0 ) - debug << ", "; + if ( i != 0 ) + debug << ", "; - debug << s[i]; - } + debug << stops[i]; } + + debug << " )"; } - debug << " )"; } + if ( gradient.stretchMode() == QskGradient::StretchToSize ) + { + debug << " SS"; + } + + switch( gradient.spreadMode() ) + { + case QskGradient::RepeatSpread: + debug << " RP"; + break; + + case QskGradient::ReflectSpread: + debug << " RF"; + break; + + case QskGradient::PadSpread: + break; + } + + debug << " )"; + return debug; } diff --git a/src/common/QskGradient.h b/src/common/QskGradient.h index 2a29926b..8c674212 100644 --- a/src/common/QskGradient.h +++ b/src/common/QskGradient.h @@ -6,115 +6,179 @@ #ifndef QSK_GRADIENT_H #define QSK_GRADIENT_H -#include "QskGlobal.h" #include "QskGradientStop.h" #include #include -#include + +class QskLinearDirection; +class QskRadialDirection; +class QskConicDirection; class QVariant; - -/* - Don't use QskGradientStops for definitions seen by moc - Otherwise exporting these interfaces to QML does not work. - */ -typedef QVector< QskGradientStop > QskGradientStops; +class QGradient; class QSK_EXPORT QskGradient { Q_GADGET - Q_PROPERTY( Orientation orientation READ orientation WRITE setOrientation ) - Q_PROPERTY( QVector< QskGradientStop > stops READ stops WRITE setStops ) + Q_PROPERTY( Type type READ type ) + + Q_PROPERTY( QskLinearDirection linear READ linearDirection WRITE setLinearDirection ) + Q_PROPERTY( QskConicDirection conic READ conicDirection WRITE setConicDirection ) + Q_PROPERTY( QskRadialDirection radial READ radialDirection WRITE setRadialDirection ) + + Q_PROPERTY( QskGradientStops stops READ stops WRITE setStops ) + + Q_PROPERTY( SpreadMode spreadMode READ spreadMode WRITE setSpreadMode ) + Q_PROPERTY( StretchMode stretchMode READ stretchMode WRITE setStretchMode ) Q_PROPERTY( bool valid READ isValid ) Q_PROPERTY( bool visible READ isVisible ) Q_PROPERTY( bool monochrome READ isMonochrome ) + Q_CLASSINFO( "DefaultProperty", "stops" ) + public: - // TODO: radial/canonical gradients + other diagonal linear gradients - enum Orientation + enum Type { - Horizontal, - Vertical, - Diagonal + Stops, + + Linear, + Radial, + Conic }; + Q_ENUM( Type ) - Q_ENUM( Orientation ) + enum SpreadMode + { + PadSpread, + ReflectSpread, + RepeatSpread + }; + Q_ENUM( SpreadMode ) + + enum StretchMode + { + NoStretch, + StretchToSize + }; + Q_ENUM( StretchMode ) + + QskGradient() noexcept; - QskGradient(); - QskGradient( Orientation ); QskGradient( Qt::GlobalColor ); QskGradient( QRgb ); QskGradient( const QColor& ); + QskGradient( const QColor&, const QColor& ); QskGradient( QGradient::Preset ); + QskGradient( const QskGradientStops& ); - QskGradient( Qt::Orientation, const QVector< QskGradientStop >& ); - QskGradient( Qt::Orientation, const QColor&, const QColor& ); - QskGradient( Qt::Orientation, QGradient::Preset ); + QskGradient( const QGradient& ); - QskGradient( Orientation, const QVector< QskGradientStop >& ); - QskGradient( Orientation, const QColor&, const QColor& ); - QskGradient( Orientation, QGradient::Preset ); + QskGradient( const QskGradient& ) noexcept; ~QskGradient(); - void setOrientation( Qt::Orientation ); - void setOrientation( Orientation ); - Orientation orientation() const; + QskGradient& operator=( const QskGradient& ) noexcept; - bool isValid() const; - Q_INVOKABLE void invalidate(); + bool operator==( const QskGradient& ) const noexcept; + bool operator!=( const QskGradient& ) const noexcept; - bool operator==( const QskGradient& ) const; - bool operator!=( const QskGradient& ) const; + QskGradient::Type type() const noexcept; - void setColor( const QColor& ); - void setColors( const QColor&, const QColor& ); + void setLinearDirection( const QskLinearDirection& ); + void setLinearDirection( qreal, qreal, qreal, qreal ); + void setLinearDirection( Qt::Orientation ); + QskLinearDirection linearDirection() const; - Q_INVOKABLE QColor startColor() const; - Q_INVOKABLE QColor endColor() const; + void setRadialDirection( const QskRadialDirection& ); + void setRadialDirection( const qreal x, qreal y, qreal radius ); + void setRadialDirection( const qreal x, qreal y, qreal radiusX, qreal radiusY ); + QskRadialDirection radialDirection() const; - Q_INVOKABLE void setStops( const QVector< QskGradientStop >& ); - Q_INVOKABLE const QVector< QskGradientStop >& stops() const; + void setConicDirection( qreal, qreal ); + void setConicDirection( qreal, qreal, qreal, qreal = 360.0 ); + void setConicDirection( const QskConicDirection& ); + QskConicDirection conicDirection() const; - Q_INVOKABLE bool hasStopAt( qreal value ) const; + void setDirection( Type ); + void resetDirection(); + + bool isValid() const noexcept; + bool isMonochrome() const noexcept; + bool isVisible() const noexcept; + + void setStops( const QskGradientStops& ); + const QskGradientStops& stops() const noexcept; + + void setStops( const QRgb ); + void setStops( Qt::GlobalColor ); + void setStops( const QColor& ); + + void setStops( const QColor&, const QColor& ); + void setStops( QGradient::Preset ); + + void clearStops(); + + Q_INVOKABLE bool hasStopAt( qreal value ) const noexcept; + + Q_INVOKABLE QColor startColor() const noexcept; + Q_INVOKABLE QColor endColor() const noexcept; + + QRgb rgbStart() const; + QRgb rgbEnd() const; void setAlpha( int alpha ); - bool isMonochrome() const; - bool isVisible() const; + void setSpreadMode( SpreadMode ); + SpreadMode spreadMode() const noexcept; + + void setStretchMode( StretchMode ); + StretchMode stretchMode() const noexcept; void reverse(); QskGradient reversed() const; - // all stops between [from, to] with positions streched into [0,1] - QskGradient extracted( qreal from, qreal start ) const; - QskGradient interpolated( const QskGradient&, qreal value ) const; + void stretchTo( const QRectF& ); + QskGradient stretchedTo( const QSizeF& ) const; + QskGradient stretchedTo( const QRectF& ) const; + + QskGradient effectiveGradient() const; + static QVariant interpolate( const QskGradient&, const QskGradient&, qreal progress ); - QskHashValue hash( QskHashValue seed ) const; + // all stops between [from, to] with positions streched into [0,1] + QskGradient extracted( qreal from, qreal start ) const; - Q_INVOKABLE qreal stopAt( int index ) const; - Q_INVOKABLE QColor colorAt( int index ) const; - Q_INVOKABLE int stopCount() const; + QskHashValue hash( QskHashValue seed = 0 ) const; - static QskGradientStops colorStops( - const QVector< QRgb >&, bool discrete = false ); + Q_INVOKABLE qreal stopAt( int index ) const noexcept; + Q_INVOKABLE QColor colorAt( int index ) const noexcept; + + int stepCount() const noexcept; + + QGradient toQGradient() const; private: - void setStopAt( int index, qreal stop ); - void setColorAt( int index, const QColor& color ); - void updateStatusBits() const; - QVector< QskGradientStop > m_stops; + private: + QskGradientStops m_stops; - int m_orientation : 4; + /* + Linear: x1, y1, x2, y2 + Radial: centerX, centerY, radiusX, radiusY + Conic: centerX, centerY, startAngle, spanAngle + */ + qreal m_values[4] = {}; + + unsigned int m_type : 3; + unsigned int m_spreadMode : 3; + unsigned int m_stretchMode : 3; mutable bool m_isDirty : 1; mutable bool m_isValid : 1; @@ -124,8 +188,14 @@ class QSK_EXPORT QskGradient Q_DECLARE_METATYPE( QskGradient ) -inline QskGradient::QskGradient() - : QskGradient( Vertical ) +inline QskGradient::QskGradient() noexcept + : m_type( Stops ) + , m_spreadMode( PadSpread ) + , m_stretchMode( StretchToSize ) + , m_isDirty( false ) + , m_isValid( false ) + , m_isMonchrome( true ) + , m_isVisible( false ) { } @@ -139,34 +209,68 @@ inline QskGradient::QskGradient( QRgb rgb ) { } -inline QskGradient::QskGradient( QGradient::Preset preset ) - : QskGradient( Vertical, preset ) +inline bool QskGradient::operator!=( const QskGradient& other ) const noexcept { + return !( *this == other ); } -inline QskGradient::Orientation QskGradient::orientation() const +inline QskGradient::Type QskGradient::type() const noexcept { - return static_cast< Orientation >( m_orientation ); + return static_cast< Type >( m_type ); } -inline QColor QskGradient::startColor() const +inline const QskGradientStops& QskGradient::stops() const noexcept { - return ( m_stops.size() >= 2 ) ? m_stops.first().color() : QColor(); +#if 1 + /* + Returning a const& so that it is possible to write: + for ( const auto& stop : qAsConst( gradient.stops() ) ) + + Once we have changed QskGradientStop from QColor to QRgb + we should check if there is a better solution possible + */ +#endif + return m_stops; } -inline QColor QskGradient::endColor() const +inline void QskGradient::setStops( QRgb rgb ) { - return ( m_stops.size() >= 2 ) ? m_stops.last().color() : QColor(); + setStops( QColor::fromRgba( rgb ) ); } -inline bool QskGradient::operator==( const QskGradient& other ) const +inline void QskGradient::setStops( Qt::GlobalColor color ) { - return ( m_orientation == other.m_orientation ) && ( m_stops == other.m_stops ); + setStops( QColor( color ) ); } -inline bool QskGradient::operator!=( const QskGradient& other ) const +inline QColor QskGradient::startColor() const noexcept { - return ( !( *this == other ) ); + return m_stops.isEmpty() ? QColor() : m_stops.first().color(); +} + +inline QColor QskGradient::endColor() const noexcept +{ + return m_stops.isEmpty() ? QColor() : m_stops.last().color(); +} + +inline QRgb QskGradient::rgbStart() const +{ + return m_stops.isEmpty() ? qRgba( 0, 0, 0, 255 ) : m_stops.first().rgb(); +} + +inline QRgb QskGradient::rgbEnd() const +{ + return m_stops.isEmpty() ? qRgba( 0, 0, 0, 255 ) : m_stops.last().rgb(); +} + +inline QskGradient::SpreadMode QskGradient::spreadMode() const noexcept +{ + return static_cast< SpreadMode >( m_spreadMode ); +} + +inline QskGradient::StretchMode QskGradient::stretchMode() const noexcept +{ + return static_cast< StretchMode >( m_stretchMode ); } #ifndef QT_NO_DEBUG_STREAM diff --git a/src/common/QskGradientDirection.cpp b/src/common/QskGradientDirection.cpp new file mode 100644 index 00000000..b387283a --- /dev/null +++ b/src/common/QskGradientDirection.cpp @@ -0,0 +1,276 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskGradientDirection.h" +#include + +static void qskRegisterGradientDirection() +{ + qRegisterMetaType< QskLinearDirection >(); + qRegisterMetaType< QskConicDirection >(); + qRegisterMetaType< QskRadialDirection >(); +} + +Q_CONSTRUCTOR_FUNCTION( qskRegisterGradientDirection ) + +// -- QskLinearDirection + +void QskLinearDirection::setOrientation( Qt::Orientation orientation ) noexcept +{ + m_x1 = m_y1 = 0.0; + setStart( 0.0, 0.0 ); + + if ( orientation == Qt::Vertical ) + { + m_x2 = 0.0; + m_y2 = 1.0; + } + else + { + m_x2 = 1.0; + m_y2 = 0.0; + } +} + +void QskLinearDirection::setVector( const QLineF& vector ) noexcept +{ + m_x1 = vector.x1(); + m_y1 = vector.y1(); + m_x2 = vector.x2(); + m_y2 = vector.y2(); + m_dot = -1.0; +} + +void QskLinearDirection::setVector( const QPointF& start, const QPointF& stop ) noexcept +{ + m_x1 = start.x(); + m_y1 = start.y(); + m_x2 = stop.x(); + m_y2 = stop.y(); + m_dot = -1.0; +} + +void QskLinearDirection::setVector( qreal x1, qreal y1, qreal x2, qreal y2 ) noexcept +{ + m_x1 = x1; + m_y1 = y1; + m_x2 = x2; + m_y2 = y2; + m_dot = -1.0; +} + +void QskLinearDirection::setStart( const QPointF& pos ) noexcept +{ + m_x1 = pos.x(); + m_y1 = pos.y(); + m_dot = -1.0; +} + +void QskLinearDirection::setStart( qreal x, qreal y ) noexcept +{ + m_x1 = x; + m_y1 = y; + m_dot = -1.0; +} + +void QskLinearDirection::setStop( const QPointF& pos ) noexcept +{ + m_x2 = pos.x(); + m_y2 = pos.y(); + m_dot = -1.0; +} + +void QskLinearDirection::setStop( qreal x, qreal y ) noexcept +{ + m_x2 = x; + m_y2 = y; + m_dot = -1.0; +} + +void QskLinearDirection::setX1( qreal x ) noexcept +{ + m_x1 = x; + m_dot = -1.0; +} + +void QskLinearDirection::setY1( qreal y ) noexcept +{ + m_y1 = y; + m_dot = -1.0; +} + +void QskLinearDirection::setX2( qreal x ) noexcept +{ + m_x2 = x; + m_dot = -1.0; +} + +void QskLinearDirection::setY2( qreal y ) noexcept +{ + m_y2 = y; + m_dot = -1.0; +} + +void QskLinearDirection::setInterval( Qt::Orientation orientation, qreal from, qreal to ) +{ + if ( orientation == Qt::Vertical ) + { + m_y1 = from; + m_y2 = to; + } + else + { + m_x1 = from; + m_x2 = to; + } + m_dot = -1.0; +} + +void QskLinearDirection::precalculate() const noexcept +{ + m_dx = m_x2 - m_x1; + m_dy = m_y2 - m_y1; + m_dot = m_dx * m_dx + m_dy * m_dy; +} + +static inline bool qskIntersectsTop( + qreal vx, qreal vy, qreal m, const QRectF& rect ) +{ + const auto cx = vx - ( vy - rect.top() ) / m; + return cx > rect.left() && cx < rect.right(); +} + +static inline bool qskIntersectsBottom( + qreal vx, qreal vy, qreal m, const QRectF& rect ) +{ + const auto cx = vx - ( vy - rect.bottom() ) / m; + return cx > rect.left() && cx < rect.right(); +} + +static inline bool qskIntersectsLeft( + qreal vx, qreal vy, qreal m, const QRectF& rect ) +{ + const auto cy = vy - ( vx - rect.left() ) * m; + return ( cy > rect.top() && cy < rect.bottom() ); +} + +static inline bool qskIntersectsRight( + qreal vx, qreal vy, qreal m, const QRectF& rect ) +{ + const auto cy = vy - ( vx - rect.right() ) * m; + return ( cy > rect.top() && cy < rect.bottom() ); +} + +bool QskLinearDirection::contains( const QRectF& rect ) const +{ + if ( m_y1 == m_y2 ) + { + return ( m_x1 <= rect.left() && m_x2 >= rect.right() ) + || ( m_x1 >= rect.right() && m_x2 <= rect.left() ); + } + + if ( m_x1 == m_x2 ) + { + return ( m_y1 <= rect.top() && m_y2 >= rect.bottom() ) + || ( m_y1 >= rect.bottom() && m_y2 <= rect.top() ); + } + + // are the normal vectors intersecting ? + + const auto m = ( m_x2 - m_x1 ) / ( m_y1 - m_y2 ); // slope of the normal vectors + + const bool intersecting = + qskIntersectsLeft( m_x1, m_y1, m, rect ) || + qskIntersectsRight( m_x1, m_y1, m, rect ) || + qskIntersectsTop( m_x1, m_y1, m, rect ) || + qskIntersectsBottom( m_x1, m_y1, m, rect ) || + qskIntersectsLeft( m_x2, m_y2, m, rect ) || + qskIntersectsRight( m_x2, m_y2, m, rect ) || + qskIntersectsTop( m_x2, m_y2, m, rect ) || + qskIntersectsBottom( m_x2, m_y2, m, rect ); + + return !intersecting; +} + +// -- QskConicDirection + +void QskConicDirection::setCenter( const QPointF& center ) noexcept +{ + m_x = center.x(); + m_y = center.y(); +} + +void QskConicDirection::setCenter( qreal x, qreal y ) noexcept +{ + m_x = x; + m_y = y; +} + +void QskConicDirection::setX( qreal x ) noexcept +{ + m_x = x; +} + +void QskConicDirection::setY( qreal y ) noexcept +{ + m_y = y; +} + +void QskConicDirection::setStartAngle( qreal degrees ) noexcept +{ + m_startAngle = degrees; +} + +void QskConicDirection::setSpanAngle( qreal degrees ) noexcept +{ + m_spanAngle = qBound( -360.0, degrees, 360.0 ); +} + +// -- QskRadialDirection + +void QskRadialDirection::setCenter( const QPointF& center ) noexcept +{ + m_x = center.x(); + m_y = center.y(); +} + +void QskRadialDirection::setCenter( qreal x, qreal y ) noexcept +{ + m_x = x; + m_y = y; +} + +void QskRadialDirection::setX( qreal x ) noexcept +{ + m_x = x; +} + +void QskRadialDirection::setY( qreal y ) noexcept +{ + m_y = y; +} + +void QskRadialDirection::setRadiusX( qreal radius ) noexcept +{ + m_radiusX = radius; +} + +void QskRadialDirection::setRadiusY( qreal radius ) noexcept +{ + m_radiusY = radius; +} + +void QskRadialDirection::setRadius( qreal radius ) noexcept +{ + m_radiusX = m_radiusY = radius; +} + +void QskRadialDirection::setRadius( qreal radiusX, qreal radiusY ) noexcept +{ + m_radiusX = radiusX; + m_radiusY = radiusY; +} + +#include "moc_QskGradientDirection.cpp" diff --git a/src/common/QskGradientDirection.h b/src/common/QskGradientDirection.h new file mode 100644 index 00000000..239890fe --- /dev/null +++ b/src/common/QskGradientDirection.h @@ -0,0 +1,397 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_GRDIENT_DIRECTION_H +#define QSK_GRDIENT_DIRECTION_H + +#include "QskGlobal.h" + +#include +#include +#include + +class QLineF; + +class QSK_EXPORT QskLinearDirection +{ + Q_GADGET + + Q_PROPERTY( qreal x1 READ x1 WRITE setX1 ) + Q_PROPERTY( qreal y1 READ y1 WRITE setY1 ) + Q_PROPERTY( qreal x2 READ x2 WRITE setX2 ) + Q_PROPERTY( qreal y2 READ y2 WRITE setY2 ) + + public: + constexpr QskLinearDirection() noexcept = default; + + constexpr QskLinearDirection( Qt::Orientation ) noexcept; + + constexpr QskLinearDirection( const QLineF& ) noexcept; + constexpr QskLinearDirection( const QPointF&, const QPointF& ) noexcept; + constexpr QskLinearDirection( qreal x1, qreal y1, qreal x2, qreal y2 ) noexcept; + + void setVector( const QLineF& ) noexcept; + void setVector( const QPointF&, const QPointF& ) noexcept; + void setVector( qreal x1, qreal y1, qreal x2, qreal y2 ) noexcept; + + constexpr QLineF vector() const noexcept; + + void setStart( const QPointF& ) noexcept; + void setStart( qreal x, qreal y ) noexcept; + + void setStop(const QPointF& ) noexcept; + void setStop( qreal x, qreal y ) noexcept; + + void setInterval( Qt::Orientation, qreal, qreal ); + + void setOrientation( Qt::Orientation ) noexcept; + constexpr bool isOriented( Qt::Orientation ) const noexcept; + + constexpr bool isHorizontal() const noexcept; + constexpr bool isVertical() const noexcept; + constexpr bool isTilted() const noexcept; + + constexpr QPointF start() const noexcept; + constexpr QPointF stop() const noexcept; + + constexpr qreal x1() const noexcept; + void setX1( qreal ) noexcept; + + constexpr qreal y1() const noexcept; + void setY1( qreal ) noexcept; + + constexpr qreal x2() const noexcept; + void setX2( qreal ) noexcept; + + constexpr qreal y2() const noexcept; + void setY2( qreal ) noexcept; + + qreal dx() const noexcept; + qreal dy() const noexcept; + + /* + In direction of the gradient vector, where 0.0 corresponds to + points on the perpendicular at the start and 1.0 to points on + the perpendicular of the end point. + + Also corresponds to the positions of the color stops and can be + used to calculate the color at a specific position. + */ + qreal valueAt( const QPointF& ) const; + qreal valueAt( qreal x, qreal y ) const; + + bool contains( const QRectF& ) const; + + private: + void precalculate() const noexcept; + + qreal m_x1 = 0.0; + qreal m_y1 = 0.0; + qreal m_x2 = 0.0; + qreal m_y2 = 1.0; + + mutable qreal m_dx = 0.0; + mutable qreal m_dy = 0.0; + mutable qreal m_dot = -1.0; +}; + +class QSK_EXPORT QskConicDirection +{ + Q_GADGET + + Q_PROPERTY( qreal x READ x WRITE setX ) + Q_PROPERTY( qreal y READ y WRITE setY ) + Q_PROPERTY( qreal startAngle READ startAngle WRITE setStartAngle ) + Q_PROPERTY( qreal spanAngle READ spanAngle WRITE setSpanAngle ) + + public: + // counter-clockwise + constexpr QskConicDirection() noexcept = default; + + constexpr QskConicDirection( qreal x, qreal y, qreal startAngle = 0.0 ) noexcept; + constexpr QskConicDirection( qreal x, qreal y, qreal startAngle, qreal spanAngle ) noexcept; + + constexpr QskConicDirection( const QPointF&, qreal startAngle = 0.0 ) noexcept; + constexpr QskConicDirection( const QPointF&, qreal startAngle, qreal spanAngle ) noexcept; + + constexpr QPointF center() const noexcept; + void setCenter(const QPointF& center) noexcept; + void setCenter(qreal x, qreal y) noexcept; + + void setX( qreal ) noexcept; + constexpr qreal x() const noexcept; + + void setY( qreal ) noexcept; + constexpr qreal y() const noexcept; + + constexpr qreal startAngle() const noexcept; + void setStartAngle( qreal ) noexcept; + + constexpr qreal spanAngle() const noexcept; + void setSpanAngle( qreal ) noexcept; + + private: + qreal m_x = 0.5; + qreal m_y = 0.5; + qreal m_startAngle = 0.0; + qreal m_spanAngle = 360.0; +}; + +class QSK_EXPORT QskRadialDirection +{ + Q_GADGET + + Q_PROPERTY( qreal x READ x WRITE setX ) + Q_PROPERTY( qreal y READ y WRITE setY ) + Q_PROPERTY( qreal radiusX READ radiusX WRITE setRadiusX ) + Q_PROPERTY( qreal radiusY READ radiusY WRITE setRadiusY ) + + public: + constexpr QskRadialDirection() noexcept = default; + + constexpr QskRadialDirection( const QPointF& center, qreal radius ) noexcept; + constexpr QskRadialDirection( qreal cx, qreal cy, qreal radius ) noexcept; + constexpr QskRadialDirection( qreal cx, qreal cy, qreal radiusX, qreal radiusY ) noexcept; + + constexpr QPointF center() const noexcept; + void setCenter(const QPointF& ) noexcept; + void setCenter(qreal x, qreal y) noexcept; + + void setX( qreal ) noexcept; + constexpr qreal x() const noexcept; + + void setY( qreal ) noexcept; + constexpr qreal y() const noexcept; + + constexpr qreal radiusX() const noexcept; + void setRadiusX( qreal ) noexcept; + + constexpr qreal radiusY() const noexcept; + void setRadiusY( qreal ) noexcept; + + void setRadius( qreal ) noexcept; + void setRadius( qreal, qreal ) noexcept; + + private: + qreal m_x = 0.5; + qreal m_y = 0.5; + qreal m_radiusX = 0.5; + qreal m_radiusY = 0.5; +}; + +inline constexpr QskLinearDirection::QskLinearDirection( + Qt::Orientation orientation ) noexcept + : m_x2( ( orientation == Qt::Vertical ) ? 0.0 : 1.0 ) + , m_y2( ( orientation == Qt::Vertical ) ? 1.0 : 0.0 ) +{ +} + +inline constexpr QskLinearDirection::QskLinearDirection( const QLineF& vector ) noexcept + : QskLinearDirection( vector.x1(), vector.y1(), vector.x2(), vector.y2() ) +{ +} + +inline constexpr QskLinearDirection::QskLinearDirection( + const QPointF& start, const QPointF& stop ) noexcept + : QskLinearDirection( start.x(), start.y(), stop.x(), stop.y() ) +{ +} + +inline constexpr QskLinearDirection::QskLinearDirection( + qreal x1, qreal y1, qreal x2, qreal y2 ) noexcept + : m_x1( x1 ) + , m_y1( y1 ) + , m_x2( x2 ) + , m_y2( y2 ) +{ +} + +inline constexpr qreal QskLinearDirection::x1() const noexcept +{ + return m_x1; +} + +inline constexpr qreal QskLinearDirection::y1() const noexcept +{ + return m_y1; +} + +inline constexpr qreal QskLinearDirection::x2() const noexcept +{ + return m_x2; +} + +inline constexpr qreal QskLinearDirection::y2() const noexcept +{ + return m_y2; +} + +inline qreal QskLinearDirection::dx() const noexcept +{ + if ( m_dot < 0.0 ) + precalculate(); + + return m_dx; +} + +inline qreal QskLinearDirection::dy() const noexcept +{ + if ( m_dot < 0.0 ) + precalculate(); + + return m_dy; +} + +inline constexpr QLineF QskLinearDirection::vector() const noexcept +{ + return QLineF( m_x1, m_y1, m_x2, m_y2 ); +} + +inline constexpr QPointF QskLinearDirection::start() const noexcept +{ + return QPointF( m_x1, m_y1 ); +} + +inline constexpr QPointF QskLinearDirection::stop() const noexcept +{ + return QPointF( m_x2, m_y2 ); +} + +inline constexpr bool QskLinearDirection::isOriented( + Qt::Orientation orientation ) const noexcept +{ + return ( orientation == Qt::Horizontal ) ? isHorizontal() : isVertical(); +} + +inline constexpr bool QskLinearDirection::isHorizontal() const noexcept +{ + return !isVertical() && ( m_y1 == m_y2 ); +} + +inline constexpr bool QskLinearDirection::isVertical() const noexcept +{ + return m_x1 == m_x2; +} + +inline constexpr bool QskLinearDirection::isTilted() const noexcept +{ + return ( m_x1 != m_x2 ) && ( m_y1 != m_y2 ); +} + +inline qreal QskLinearDirection::valueAt( const QPointF& pos ) const +{ + return valueAt( pos.x(), pos.y() ); +} + +inline qreal QskLinearDirection::valueAt( qreal x, qreal y ) const +{ + if ( m_dot < 0.0 ) + precalculate(); + + return ( ( x - m_x1 ) * m_dx + ( y - m_y1 ) * m_dy ) / m_dot; +} + +inline constexpr QskConicDirection::QskConicDirection( + qreal x, qreal y, qreal startAngle ) noexcept + : QskConicDirection( x, y, startAngle, 360.0 ) +{ +} + +inline constexpr QskConicDirection::QskConicDirection( + qreal x, qreal y, qreal startAngle, qreal spanAngle ) noexcept + : m_x( x ) + , m_y( y ) + , m_startAngle( startAngle ) + , m_spanAngle( spanAngle ) +{ +} + +inline constexpr QskConicDirection::QskConicDirection( + const QPointF& pos, qreal startAngle ) noexcept + : QskConicDirection( pos.x(), pos.y(), startAngle ) +{ +} + +inline constexpr QskConicDirection::QskConicDirection( + const QPointF& pos, qreal startAngle, qreal spanAngle ) noexcept + : QskConicDirection( pos.x(), pos.y(), startAngle, spanAngle ) +{ +} + +inline constexpr QPointF QskConicDirection::center() const noexcept +{ + return QPointF( m_x, m_y ); +} + +inline constexpr qreal QskConicDirection::startAngle() const noexcept +{ + return m_startAngle; +} + +inline constexpr qreal QskConicDirection::spanAngle() const noexcept +{ + return m_spanAngle; +} + +inline constexpr qreal QskConicDirection::x() const noexcept +{ + return m_x; +} + +inline constexpr qreal QskConicDirection::y() const noexcept +{ + return m_y; +} + +inline constexpr QskRadialDirection::QskRadialDirection( + qreal x, qreal y, qreal radius ) noexcept + : m_x( x ) + , m_y( y ) + , m_radiusX( radius ) + , m_radiusY( radius ) +{ +} + +inline constexpr QskRadialDirection::QskRadialDirection( + qreal x, qreal y, qreal radiusX, qreal radiusY ) noexcept + : m_x( x ) + , m_y( y ) + , m_radiusX( radiusX ) + , m_radiusY( radiusY ) +{ +} + +inline constexpr QskRadialDirection::QskRadialDirection( + const QPointF& center, qreal radius ) noexcept + : QskRadialDirection( center.x(), center.y(), radius ) +{ +} + +inline constexpr QPointF QskRadialDirection::center() const noexcept +{ + return QPointF( m_x, m_y ); +} + +inline constexpr qreal QskRadialDirection::x() const noexcept +{ + return m_x; +} + +inline constexpr qreal QskRadialDirection::y() const noexcept +{ + return m_y; +} + +inline constexpr qreal QskRadialDirection::radiusX() const noexcept +{ + return m_radiusX; +} + +inline constexpr qreal QskRadialDirection::radiusY() const noexcept +{ + return m_radiusY; +} + +#endif diff --git a/src/common/QskGradientStop.cpp b/src/common/QskGradientStop.cpp index 48dbdac2..4455d5c8 100644 --- a/src/common/QskGradientStop.cpp +++ b/src/common/QskGradientStop.cpp @@ -8,6 +8,7 @@ #include #include +#include #include @@ -27,19 +28,14 @@ void QskGradientStop::setPosition( qreal position ) noexcept m_position = position; } -void QskGradientStop::resetPosition() noexcept -{ - m_position = -1.0; -} - void QskGradientStop::setColor( const QColor& color ) noexcept { m_color = color; } -void QskGradientStop::resetColor() noexcept +void QskGradientStop::setRgb( QRgb rgb ) noexcept { - m_color = QColor(); + m_color = QColor::fromRgba( rgb ); } void QskGradientStop::setStop( qreal position, const QColor& color ) noexcept @@ -48,6 +44,12 @@ void QskGradientStop::setStop( qreal position, const QColor& color ) noexcept m_color = color; } +void QskGradientStop::setStop( qreal position, Qt::GlobalColor color ) noexcept +{ + m_position = position; + m_color = color; +} + QskHashValue QskGradientStop::hash( QskHashValue seed ) const noexcept { auto hash = qHashBits( &m_position, sizeof( m_position ), seed ); @@ -94,3 +96,327 @@ QDebug operator<<( QDebug debug, const QskGradientStop& stop ) #endif #include "moc_QskGradientStop.cpp" + +// some helper functions around QskGradientStops + +static inline QColor qskInterpolatedColor( + const QskGradientStops& stops, int index1, int index2, qreal position ) +{ + index1 = qBound( 0, index1, stops.count() - 1 ); + index2 = qBound( 0, index2, stops.count() - 1 ); + + return QskGradientStop::interpolated( stops[ index1 ], stops[ index2 ], position ); +} + +bool qskIsVisible( const QskGradientStops& stops ) noexcept +{ + for ( const auto& stop : stops ) + { + const auto& c = stop.color(); + if ( c.isValid() && c.alpha() > 0 ) + return true; + } + + return false; +} + +bool qskIsMonochrome( const QskGradientStops& stops ) noexcept +{ + for ( int i = 1; i < stops.size(); i++ ) + { + if ( stops[ i ].color() != stops[ 0 ].color() ) + return false; + } + + return true; +} + +QskGradientStops qskTransparentGradientStops( const QskGradientStops& stops, qreal ratio ) +{ + auto newStops = stops; + + for ( auto& stop : newStops ) + { + auto c = stop.color(); + c.setAlpha( c.alpha() * ratio ); + + stop.setColor( c ); + } + + return stops; +} + +QskGradientStops qskInterpolatedGradientStops( + const QskGradientStops& stops, const QColor& color, qreal ratio ) +{ + auto newStops = stops; + + for ( auto& stop : newStops ) + stop.setColor( QskRgb::interpolated( stop.color(), color, ratio ) ); + + return newStops; +} + +QskGradientStops qskInterpolatedGradientStops( + const QColor& color, const QskGradientStops& stops, qreal ratio ) +{ + auto newStops = stops; + + for ( auto& stop : newStops ) + stop.setColor( QskRgb::interpolated( color, stop.color(), ratio ) ); + + return newStops; +} + +static QskGradientStops qskInterpolatedStops( + const QskGradientStops& from, const QskGradientStops& to, qreal ratio ) +{ + /* + We have to insert interpolated stops for all positions + that can be found in s1 and s2. So in case there is no + stop at a specific position of the other stops we + have to calculate one temporarily before interpolating. + */ + QVector< QskGradientStop > stops; + + int i = 0, j = 0; + while ( ( i < from.count() ) || ( j < to.count() ) ) + { + qreal pos; + QColor c1, c2; + + if ( i == from.count() ) + { + c1 = from.last().color(); + c2 = to[j].color(); + pos = to[j++].position(); + } + else if ( j == to.count() ) + { + c1 = from[i].color(); + c2 = to.last().color(); + pos = from[i++].position(); + } + else + { + c1 = from[i].color(); + c2 = to[j].color(); + + const qreal dpos = from[i].position() - to[j].position(); + + if ( qFuzzyIsNull( dpos ) ) + { + pos = from[i++].position(); + j++; + } + else if ( dpos < 0.0 ) + { + pos = from[i++].position(); + + if ( j > 0 ) + { + const auto& stop = to[j - 1]; + c2 = QskRgb::interpolated( stop.color(), c2, pos - stop.position() ); + } + } + else + { + pos = to[j++].position(); + + if ( i > 0 ) + { + const auto& stop = from[i - 1]; + c1 = QskRgb::interpolated( stop.color(), c1, pos - stop.position() ); + } + } + } + + stops += QskGradientStop( pos, QskRgb::interpolated( c1, c2, ratio ) ); + } + + return stops; +} + +QskGradientStops qskInterpolatedGradientStops( + const QskGradientStops& from, bool fromIsMonochrome, + const QskGradientStops& to, bool toIsMonochrome, qreal ratio ) +{ + if ( from.isEmpty() && to.isEmpty() ) + return QskGradientStops(); + + if ( from.isEmpty() ) + return qskTransparentGradientStops( to, ratio ); + + if ( to.isEmpty() ) + return qskTransparentGradientStops( from, 1.0 - ratio ); + + if ( fromIsMonochrome && toIsMonochrome ) + { + const auto c = QskRgb::interpolated( + from[ 0 ].color(), to[ 0 ].color(), ratio ); + + return { { 0.0, c }, { 1.0, c } }; + } + + if ( fromIsMonochrome ) + { + return qskInterpolatedGradientStops( from[ 0 ].color(), to, ratio ); + } + + if ( toIsMonochrome ) + { + return qskInterpolatedGradientStops( from, to[ 0 ].color(), ratio ); + } + + return qskInterpolatedStops( from, to, ratio ); +} + +QskGradientStops qskExtractedGradientStops( + const QskGradientStops& stops, qreal from, qreal to ) +{ + if ( ( from > to ) || ( from > 1.0 ) || stops.isEmpty() ) + return QskGradientStops(); + + if ( ( from <= 0.0 ) && ( to >= 1.0 ) ) + return stops; + + from = qMax( from, 0.0 ); + to = qMin( to, 1.0 ); + + QVector< QskGradientStop > extracted; + extracted.reserve( stops.count() ); + + int i = 0; + + for ( ; i < stops.count(); i++ ) + { + if ( stops[i].position() > from ) + break; + } + + extracted += QskGradientStop( 0.0, + qskInterpolatedColor( stops, i - 1, i, from ) ); + + for ( ; i < stops.count(); i++ ) + { + if ( stops[i].position() >= to ) + break; + + const auto pos = ( stops[i].position() - from ) / ( to - from ); + extracted += QskGradientStop( pos, stops[i].color() ); + } + + extracted += QskGradientStop( 1.0, + qskInterpolatedColor( stops, i, i + 1, to ) ); + + return extracted; +} + +QskGradientStops qskRevertedGradientStops( const QskGradientStops& stops ) +{ + QVector< QskGradientStop > s; + s.reserve( stops.count() ); + + for ( auto it = stops.crbegin(); it != stops.crend(); ++it ) + s += QskGradientStop( 1.0 - it->position(), it->color() ); + + return s; +} + +QVector< QskGradientStop > qskBuildGradientStops( const QGradientStops& qtStops ) +{ + QVector< QskGradientStop > stops; + stops.reserve( qtStops.count() ); + + for ( const auto& s : qtStops ) + stops += QskGradientStop( s.first, s.second ); + + return stops; +} + +template< typename T > +static inline QVector< QskGradientStop > qskCreateStops( + const QVector< T > colors, bool discrete ) +{ + QVector< QskGradientStop > stops; + + const auto count = colors.count(); + if ( count == 0 ) + return stops; + + if ( count == 1 ) + { + stops.reserve( 2 ); + + stops += QskGradientStop( 0.0, colors[0] ); + stops += QskGradientStop( 1.0, colors[0] ); + + return stops; + } + + if ( discrete ) + { + const auto step = 1.0 / count; + stops.reserve( 2 * count - 2 ); + + stops += QskGradientStop( 0.0, colors[0] ); + + for ( int i = 1; i < count; i++ ) + { + const qreal pos = i * step; + stops += QskGradientStop( pos, colors[i - 1] ); + stops += QskGradientStop( pos, colors[i] ); + } + + stops += QskGradientStop( 1.0, colors[count - 1] ); + } + else + { + const auto step = 1.0 / ( count - 1 ); + stops.reserve( count ); + + stops += QskGradientStop( 0.0, colors[0] ); + + for ( int i = 1; i < count - 1; i++ ) + stops += QskGradientStop( i * step, colors[i] ); + + stops += QskGradientStop( 1.0, colors[count - 1] ); + } + + return stops; +} + +QskGradientStops qskBuildGradientStops( const QVector< QRgb >& colors, bool discrete ) +{ + return qskCreateStops( colors, discrete ); +} + +QskGradientStops qskBuildGradientStops( const QVector< QColor >& colors, bool discrete ) +{ + return qskCreateStops( colors, discrete ); +} + +QGradientStops qskToQGradientStops( const QskGradientStops& stops ) +{ + QGradientStops qStops; + qStops.reserve( stops.count() ); + + for ( const auto& stop : stops ) + { + QPair qStop = { stop.position(), stop.color() }; + + if ( !qStops.isEmpty() && qStops.last().first == qStop.first ) + { + /* + QGradient removes stops at the same position. So we have to insert + an invisible dummy offset + */ + qStop.first += 0.00001; + } + + qStops += qStop; + } + + return qStops; +} + diff --git a/src/common/QskGradientStop.h b/src/common/QskGradientStop.h index 3149eea4..b58a0d77 100644 --- a/src/common/QskGradientStop.h +++ b/src/common/QskGradientStop.h @@ -10,17 +10,21 @@ #include #include +#include + +typedef QPair< qreal, QColor > QGradientStop; class QSK_EXPORT QskGradientStop { Q_GADGET - Q_PROPERTY( qreal position READ position WRITE setPosition RESET resetPosition ) - Q_PROPERTY( QColor color READ color WRITE setColor RESET resetColor ) + Q_PROPERTY( qreal position READ position WRITE setPosition ) + Q_PROPERTY( QColor color READ color WRITE setColor ) public: - constexpr QskGradientStop() noexcept; + constexpr QskGradientStop() noexcept = default; constexpr QskGradientStop( qreal position, const QColor& ) noexcept; + constexpr QskGradientStop( const QGradientStop& ) noexcept; QskGradientStop( qreal position, Qt::GlobalColor ) noexcept; QskGradientStop( qreal position, QRgb ) noexcept; @@ -29,14 +33,17 @@ class QSK_EXPORT QskGradientStop constexpr bool operator!=( const QskGradientStop& ) const noexcept; void setStop( qreal position, const QColor& ) noexcept; + void setStop( qreal position, Qt::GlobalColor ) noexcept; + void setStop( qreal position, QRgb ) noexcept; constexpr qreal position() const noexcept; void setPosition( qreal position ) noexcept; - void resetPosition() noexcept; constexpr const QColor& color() const noexcept; void setColor( const QColor& ) noexcept; - void resetColor() noexcept; + + void setRgb( QRgb ) noexcept; + QRgb rgb() const noexcept; static QColor interpolated( const QskGradientStop&, const QskGradientStop&, qreal position ) noexcept; @@ -44,8 +51,8 @@ class QSK_EXPORT QskGradientStop QskHashValue hash( QskHashValue seed ) const noexcept; private: - qreal m_position; - QColor m_color; // using RGBA instead ? + qreal m_position = -1.0; + QColor m_color; // using QRgb instead ? }; #if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) @@ -54,11 +61,6 @@ class QSK_EXPORT QskGradientStop Q_DECLARE_METATYPE( QskGradientStop ) -inline constexpr QskGradientStop::QskGradientStop() noexcept - : m_position( -1.0 ) -{ -} - inline constexpr QskGradientStop::QskGradientStop( qreal position, const QColor& color ) noexcept : m_position( position ) @@ -78,6 +80,11 @@ inline QskGradientStop::QskGradientStop( { } +inline constexpr QskGradientStop::QskGradientStop( const QGradientStop& qtStop ) noexcept + : QskGradientStop( qtStop.first, qtStop.second ) +{ +} + inline constexpr qreal QskGradientStop::position() const noexcept { return m_position; @@ -88,6 +95,11 @@ inline constexpr const QColor& QskGradientStop::color() const noexcept return m_color; } +inline QRgb QskGradientStop::rgb() const noexcept +{ + return m_color.rgba(); +} + inline constexpr bool QskGradientStop::operator==( const QskGradientStop& other ) const noexcept { return ( m_position == other.m_position ) && ( m_color == other.m_color ); @@ -106,4 +118,36 @@ QSK_EXPORT QDebug operator<<( QDebug, const QskGradientStop& ); #endif +typedef QVector< QskGradientStop > QskGradientStops; + +QSK_EXPORT bool qskIsMonochrome( const QskGradientStops& ) noexcept; +QSK_EXPORT bool qskIsVisible( const QskGradientStops& ) noexcept; + +QSK_EXPORT QskGradientStops qskInterpolatedGradientStops( + const QskGradientStops&, bool, const QskGradientStops&, bool, + qreal ratio ); + +QSK_EXPORT QskGradientStops qskInterpolatedGradientStops( + const QskGradientStops&, const QColor&, qreal ratio ); + +QSK_EXPORT QskGradientStops qskInterpolatedGradientStops( + const QColor&, const QskGradientStops&, qreal ratio ); + +QSK_EXPORT QskGradientStops qskTransparentGradientStops( + const QskGradientStops&, qreal ratio ); + +QSK_EXPORT QskGradientStops qskExtractedGradientStops( + const QskGradientStops&, qreal from, qreal to ); + +QSK_EXPORT QskGradientStops qskBuildGradientStops( + const QVector< QRgb >&, bool discrete = false ); + +QSK_EXPORT QskGradientStops qskBuildGradientStops( + const QVector< QColor >&, bool discrete = false ); + +QSK_EXPORT QskGradientStops qskRevertedGradientStops( const QskGradientStops& ); + +QSK_EXPORT QskGradientStops qskBuildGradientStops( const QVector< QGradientStop >& ); +QSK_EXPORT QVector< QGradientStop > qskToQGradientStops( const QVector< QskGradientStop >& ); + #endif diff --git a/src/common/QskHctColor.h b/src/common/QskHctColor.h index 041c0e4e..7dafc247 100644 --- a/src/common/QskHctColor.h +++ b/src/common/QskHctColor.h @@ -12,7 +12,7 @@ /* For M(aterial)3 the new HTC color system has been created, that is based on H(ue), (C)hroma, (T)one: - + https://material.io/blog/science-of-color-design This system allows to create color palettes by varying the tone @@ -80,8 +80,10 @@ inline constexpr QskHctColor QskHctColor::toned( qreal tone ) const noexcept } #ifndef QT_NO_DEBUG_STREAM + class QDebug; QSK_EXPORT QDebug operator<<( QDebug, const QskHctColor& ); + #endif #endif diff --git a/src/common/QskMargins.cpp b/src/common/QskMargins.cpp index f1e65c3f..5aa5f98e 100644 --- a/src/common/QskMargins.cpp +++ b/src/common/QskMargins.cpp @@ -1,16 +1,22 @@ #include "QskMargins.h" #include -static void qskRegisterConverter() +static void qskRegisterMargins() { + qRegisterMetaType< QskMargins >(); + QMetaType::registerConverter< int, QskMargins >( []( int value ) { return QskMargins( value ); } ); QMetaType::registerConverter< qreal, QskMargins >( []( qreal value ) { return QskMargins( value ); } ); + +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) + QMetaType::registerEqualsComparator< QskMargins >(); +#endif } -Q_CONSTRUCTOR_FUNCTION( qskRegisterConverter ) +Q_CONSTRUCTOR_FUNCTION( qskRegisterMargins ) static inline qreal qskInterpolated( qreal from, qreal to, qreal ratio ) { diff --git a/src/common/QskMetaFunction.cpp b/src/common/QskMetaFunction.cpp index cef8eaba..7d817cb9 100644 --- a/src/common/QskMetaFunction.cpp +++ b/src/common/QskMetaFunction.cpp @@ -55,11 +55,7 @@ int QskMetaFunction::FunctionCall::typeInfo() const int QskMetaFunction::FunctionCall::refCount() const { auto that = const_cast< FunctionCall* >( this ); -#if QT_VERSION >= QT_VERSION_CHECK( 5, 14, 0 ) return reinterpret_cast< SlotObject* >( that )->ref.loadRelaxed(); -#else - return reinterpret_cast< SlotObject* >( that )->ref.load(); -#endif } QskMetaFunction::QskMetaFunction() diff --git a/src/common/QskNamespace.h b/src/common/QskNamespace.h index 3f274d12..b784c523 100644 --- a/src/common/QskNamespace.h +++ b/src/common/QskNamespace.h @@ -11,7 +11,7 @@ namespace Qsk { - QSK_EXPORT Q_NAMESPACE + Q_NAMESPACE_EXPORT( QSK_EXPORT ) enum Direction { diff --git a/src/common/QskPlacementPolicy.cpp b/src/common/QskPlacementPolicy.cpp index 49c7940e..3064d2b7 100644 --- a/src/common/QskPlacementPolicy.cpp +++ b/src/common/QskPlacementPolicy.cpp @@ -5,6 +5,17 @@ #include "QskPlacementPolicy.h" +static void qskRegisterPlacementPolicy() +{ + qRegisterMetaType< QskPlacementPolicy >(); + +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) + QMetaType::registerEqualsComparator< QskPlacementPolicy >(); +#endif +} + +Q_CONSTRUCTOR_FUNCTION( qskRegisterPlacementPolicy ) + #ifndef QT_NO_DEBUG_STREAM #include diff --git a/src/common/QskPlacementPolicy.h b/src/common/QskPlacementPolicy.h index 0bb4d1dc..6beb2afc 100644 --- a/src/common/QskPlacementPolicy.h +++ b/src/common/QskPlacementPolicy.h @@ -10,7 +10,7 @@ #include "QskNamespace.h" #include -class QskPlacementPolicy +class QSK_EXPORT QskPlacementPolicy { Q_GADGET diff --git a/src/common/QskRgbValue.cpp b/src/common/QskRgbValue.cpp index fb29be32..9a091b7a 100644 --- a/src/common/QskRgbValue.cpp +++ b/src/common/QskRgbValue.cpp @@ -5,6 +5,13 @@ #include "QskRgbValue.h" +#include +#include + +QSK_QT_PRIVATE_BEGIN +#include +QSK_QT_PRIVATE_END + namespace { inline int value( int from, int to, qreal ratio ) @@ -196,3 +203,96 @@ void QskRgb::debugColor( QDebug debug, QRgb rgb ) } #endif + +QImage QskRgb::colorTable( int size, const QskGradientStops& stops ) +{ + if ( size == 0 || stops.isEmpty() ) + return QImage(); + + QImage image( size, 1, QImage::Format_RGBA8888_Premultiplied ); + + if ( stops.size() == 1 ) + { + const auto rgb = ARGB2RGBA( qPremultiply( stops[0].rgb() ) ); + image.fill( rgb ); + + return image; + } + + auto values = reinterpret_cast< uint* >( image.bits() ); + + int index1, index2; + QRgb rgb1, rgb2; + + index1 = index2 = qRound( stops[0].position() * size ); + rgb1 = rgb2 = qPremultiply( stops[0].rgb() ); + + if ( index1 > 0 ) + { + const auto v = ARGB2RGBA( rgb1 ); + + for ( int i = 0; i < index1; i++ ) + values[i] = v; + } + + for ( int i = 1; i < stops.count(); i++ ) + { + const auto& stop = stops[i]; + + index2 = qRound( stop.position() * size ); + rgb2 = qPremultiply( stop.rgb() ); + + const auto n = index2 - index1; + + values[ index1 ] = ARGB2RGBA( rgb1 ); + for ( int j = 1; j < n; j++ ) + { + const auto rgb = QskRgb::interpolated( rgb1, rgb2, qreal( j ) / ( n - 1 ) ); + values[ index1 + j] = ARGB2RGBA( rgb ); + } + + index1 = index2; + rgb1 = rgb2; + } + + if ( index1 < size - 1 ) + { + const auto v = ARGB2RGBA( rgb1 ); + + for ( int i = index1; i < size ; i++ ) + values[i] = v; + } + + return image; +} + +QImage QskRgb::colorTable( const int size, + QRgb rgb1, QRgb rgb2, const QEasingCurve& curve ) +{ + if ( size == 0 ) + return QImage(); + + rgb1 = qPremultiply( rgb1 ); + rgb2 = qPremultiply( rgb2 ); + + QImage image( size, 1, QImage::Format_RGBA8888_Premultiplied ); + + if ( rgb1 == rgb2 ) + { + image.fill( ARGB2RGBA( rgb1 ) ); + return image; + } + + auto values = reinterpret_cast< uint* >( image.bits() ); + + for ( int i = 0; i < size; i++ ) + { + qreal progress = curve.valueForProgress( qreal( i ) / ( size - 1 ) ); + progress = qBound( 0.0, progress, 1.0 ); + + auto rgb = QskRgb::interpolated( rgb1, rgb2, progress ); + values[i] = ARGB2RGBA( rgb ); + } + + return image; +} diff --git a/src/common/QskRgbValue.h b/src/common/QskRgbValue.h index 39687898..2ff1b55e 100644 --- a/src/common/QskRgbValue.h +++ b/src/common/QskRgbValue.h @@ -7,8 +7,13 @@ #define QSK_RGB_VALUE_H #include "QskGlobal.h" +#include "QskGradientStop.h" + #include +class QEasingCurve; +class QImage; + namespace QskRgb { /* Web colors */ @@ -207,6 +212,17 @@ namespace QskRgb QSK_EXPORT QRgb darker( QRgb, int factor = 200 ) noexcept; } +namespace QskRgb +{ + /* + One dimensional array of colors ( height is always 1 ) of + Format_RGBA8888_Premultiplied + */ + + QSK_EXPORT QImage colorTable( int size, const QskGradientStops& ); + QSK_EXPORT QImage colorTable( int size, QRgb, QRgb, const QEasingCurve& ); +} + #ifndef QT_NO_DEBUG_STREAM class QDebug; diff --git a/src/common/QskSizePolicy.cpp b/src/common/QskSizePolicy.cpp index 4d4cbb92..fbae27f6 100644 --- a/src/common/QskSizePolicy.cpp +++ b/src/common/QskSizePolicy.cpp @@ -11,6 +11,17 @@ #include +static void qskRegisterSizePolicy() +{ + qRegisterMetaType< QskSizePolicy >(); + +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) + QMetaType::registerEqualsComparator< QskSizePolicy >(); +#endif +} + +Q_CONSTRUCTOR_FUNCTION( qskRegisterSizePolicy ) + QskSizePolicy::ConstraintType QskSizePolicy::constraintType() const noexcept { constexpr unsigned char mask = IgnoreFlag | ConstrainedFlag; diff --git a/src/common/QskSizePolicy.h b/src/common/QskSizePolicy.h index c348d589..a3322288 100644 --- a/src/common/QskSizePolicy.h +++ b/src/common/QskSizePolicy.h @@ -14,11 +14,8 @@ class QSK_EXPORT QskSizePolicy { Q_GADGET - Q_PROPERTY( Policy horizontalPolicy - READ horizontalPolicy WRITE setHorizontalPolicy ) - - Q_PROPERTY( Policy verticalPolicy - READ verticalPolicy WRITE setVerticalPolicy ) + Q_PROPERTY( Policy horizontal READ horizontalPolicy WRITE setHorizontalPolicy ) + Q_PROPERTY( Policy vertical READ verticalPolicy WRITE setVerticalPolicy ) public: enum Flag @@ -63,7 +60,7 @@ class QSK_EXPORT QskSizePolicy Q_ENUM( Policy ) Q_ENUM( ConstraintType ) - constexpr QskSizePolicy() noexcept; + constexpr QskSizePolicy() noexcept = default; constexpr QskSizePolicy( Policy horizontalPolicy, Policy verticalPolicy ) noexcept; constexpr bool operator==( const QskSizePolicy& ) const noexcept; @@ -88,16 +85,10 @@ class QSK_EXPORT QskSizePolicy void transpose() noexcept; private: - unsigned char m_horizontalPolicy; - unsigned char m_verticalPolicy; + unsigned char m_horizontalPolicy = Ignored; + unsigned char m_verticalPolicy = Ignored; }; -inline constexpr QskSizePolicy::QskSizePolicy() noexcept - : m_horizontalPolicy( Ignored ) - , m_verticalPolicy( Ignored ) -{ -} - inline constexpr QskSizePolicy::QskSizePolicy( Policy horizontalPolicy, Policy verticalPolicy ) noexcept : m_horizontalPolicy( horizontalPolicy ) diff --git a/src/common/QskTextOptions.cpp b/src/common/QskTextOptions.cpp index 98e992e4..712ec150 100644 --- a/src/common/QskTextOptions.cpp +++ b/src/common/QskTextOptions.cpp @@ -7,10 +7,14 @@ #include static void qskRegisterTextOptions() -{ +{ qRegisterMetaType< QskTextOptions >(); -} - + +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) + QMetaType::registerEqualsComparator< QskTextOptions >(); +#endif +} + Q_CONSTRUCTOR_FUNCTION( qskRegisterTextOptions ) int QskTextOptions::textFlags() const noexcept diff --git a/src/common/QskTextOptions.h b/src/common/QskTextOptions.h index b10b20cb..d95bf39a 100644 --- a/src/common/QskTextOptions.h +++ b/src/common/QskTextOptions.h @@ -31,6 +31,7 @@ class QSK_EXPORT QskTextOptions VerticalFit, Fit }; + Q_ENUM( FontSizeMode ) enum WrapMode { diff --git a/src/controls/QskCheckBox.cpp b/src/controls/QskCheckBox.cpp index 425772e1..286a2ea4 100644 --- a/src/controls/QskCheckBox.cpp +++ b/src/controls/QskCheckBox.cpp @@ -9,6 +9,9 @@ QSK_SUBCONTROL( QskCheckBox, Panel ) QSK_SUBCONTROL( QskCheckBox, Box ) QSK_SUBCONTROL( QskCheckBox, Indicator ) QSK_SUBCONTROL( QskCheckBox, Text ) +QSK_SUBCONTROL( QskCheckBox, Ripple ) + +QSK_SYSTEM_STATE( QskCheckBox, Error, QskAspect::FirstSystemState << 1 ) class QskCheckBox::PrivateData { diff --git a/src/controls/QskCheckBox.h b/src/controls/QskCheckBox.h index 0f67fd74..bc010749 100644 --- a/src/controls/QskCheckBox.h +++ b/src/controls/QskCheckBox.h @@ -17,7 +17,8 @@ class QSK_EXPORT QskCheckBox : public QskAbstractButton using Inherited = QskAbstractButton; public: - QSK_SUBCONTROLS( Panel, Box, Indicator, Text ) + QSK_SUBCONTROLS( Panel, Box, Indicator, Text, Ripple ) + QSK_STATES( Error ) QskCheckBox( QQuickItem* parent = nullptr ); QskCheckBox( const QString&, QQuickItem* parent = nullptr ); diff --git a/src/controls/QskCheckBoxSkinlet.cpp b/src/controls/QskCheckBoxSkinlet.cpp index 44ad66a5..d7fa07f4 100644 --- a/src/controls/QskCheckBoxSkinlet.cpp +++ b/src/controls/QskCheckBoxSkinlet.cpp @@ -15,7 +15,7 @@ QskCheckBoxSkinlet::QskCheckBoxSkinlet( QskSkin* skin ) : QskSkinlet( skin ) { - setNodeRoles( { BoxRole, IndicatorRole, TextRole } ); + setNodeRoles( { BoxRole, IndicatorRole, TextRole, RippleRole } ); } QskCheckBoxSkinlet::~QskCheckBoxSkinlet() @@ -47,6 +47,11 @@ QRectF QskCheckBoxSkinlet::subControlRect( const QskSkinnable* skinnable, return textRect( checkBox, contentsRect ); } + if ( subControl == QskCheckBox::Ripple ) + { + return rippleRect( checkBox, contentsRect ); + } + return contentsRect; } @@ -85,6 +90,21 @@ QRectF QskCheckBoxSkinlet::boxRect( return r; } +QRectF QskCheckBoxSkinlet::rippleRect( + const QskCheckBox* checkBox, const QRectF& rect ) const +{ + const auto rippleSize = checkBox->strutSizeHint( QskCheckBox::Ripple ); + const auto boxSize = checkBox->strutSizeHint( QskCheckBox::Box ); + + const auto w = ( rippleSize.width() - boxSize.width() ) / 2; + const auto h = ( rippleSize.height() - boxSize.height() ) / 2; + + auto r = boxRect( checkBox, rect ); + r = r.marginsAdded( { w, h, w, h } ); + + return r; +} + QSGNode* QskCheckBoxSkinlet::updateSubNode( const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const { @@ -103,6 +123,11 @@ QSGNode* QskCheckBoxSkinlet::updateSubNode( case TextRole: return updateTextNode( checkBox, node ); + + case RippleRole: + { + return updateBoxNode( checkBox, node, QskCheckBox::Ripple ); + } } return Inherited::updateSubNode( skinnable, nodeRole, node ); @@ -122,19 +147,6 @@ QSGNode* QskCheckBoxSkinlet::updateIndicatorNode( } auto graphic = checkBox->effectiveSkin()->symbol( symbol ); - -#if 1 - /* - Our default skins do not have the concept of colorRoles - implemented. Until then we do the recoloring manually here - */ - QskColorFilter filter; - filter.addColorSubstitution( Qt::black, - checkBox->color( QskCheckBox::Indicator ).rgba() ); - - graphic = QskGraphic::fromGraphic( graphic, filter ); -#endif - return updateGraphicNode( checkBox, node, graphic, QskCheckBox::Indicator ); } diff --git a/src/controls/QskCheckBoxSkinlet.h b/src/controls/QskCheckBoxSkinlet.h index 770d572b..19ddee11 100644 --- a/src/controls/QskCheckBoxSkinlet.h +++ b/src/controls/QskCheckBoxSkinlet.h @@ -23,6 +23,7 @@ class QSK_EXPORT QskCheckBoxSkinlet : public QskSkinlet BoxRole, IndicatorRole, TextRole, + RippleRole, RoleCount }; @@ -43,7 +44,8 @@ class QSK_EXPORT QskCheckBoxSkinlet : public QskSkinlet private: QRectF textRect( const QskCheckBox*, const QRectF& ) const; QRectF boxRect( const QskCheckBox*, const QRectF& ) const; - + QRectF rippleRect( const QskCheckBox*, const QRectF& ) const; + QSGNode* updateIndicatorNode( const QskCheckBox*, QSGNode* ) const; QSGNode* updateTextNode( const QskCheckBox*, QSGNode* ) const; }; diff --git a/src/controls/QskControl.cpp b/src/controls/QskControl.cpp index 7f76a2e3..f2be97bc 100644 --- a/src/controls/QskControl.cpp +++ b/src/controls/QskControl.cpp @@ -19,6 +19,8 @@ #include #include +QSK_SUBCONTROL( QskControl, Background ) + QSK_SYSTEM_STATE( QskControl, Disabled, QskAspect::FirstSystemState ) QSK_SYSTEM_STATE( QskControl, Hovered, QskAspect::LastSystemState >> 1 ) QSK_SYSTEM_STATE( QskControl, Focused, QskAspect::LastSystemState ) @@ -57,21 +59,6 @@ QskControl::~QskControl() #endif } -void QskControl::setAutoFillBackground( bool on ) -{ - Q_D( QskControl ); - if ( on != d->autoFillBackground ) - { - d->autoFillBackground = on; - update(); - } -} - -bool QskControl::autoFillBackground() const -{ - return d_func()->autoFillBackground; -} - void QskControl::setAutoLayoutChildren( bool on ) { Q_D( QskControl ); @@ -139,25 +126,24 @@ Qt::FocusPolicy QskControl::focusPolicy() const void QskControl::setBackgroundColor( const QColor& color ) { - setAutoFillBackground( true ); setBackground( QskGradient( color ) ); } void QskControl::setBackground( const QskGradient& gradient ) { - if ( setGradientHint( QskAspect::Control, gradient ) ) + if ( setGradientHint( QskControl::Background, gradient ) ) Q_EMIT backgroundChanged(); } void QskControl::resetBackground() { - if ( resetColor( QskAspect::Control ) ) + if ( resetColor( QskControl::Background ) ) Q_EMIT backgroundChanged(); } QskGradient QskControl::background() const { - return gradientHint( QskAspect::Control ); + return gradientHint( QskControl::Background ); } void QskControl::setMargins( qreal margin ) @@ -174,7 +160,7 @@ void QskControl::setMargins( const QMarginsF& margins ) { const auto m = QskMargins().expandedTo( margins ); - if ( setMarginHint( QskAspect::Control, m ) ) + if ( setPaddingHint( QskControl::Background, m ) ) { qskSendEventTo( this, QEvent::ContentsRectChange ); Q_EMIT marginsChanged( m ); @@ -183,7 +169,7 @@ void QskControl::setMargins( const QMarginsF& margins ) void QskControl::resetMargins() { - if ( resetMarginHint( QskAspect::Control ) ) + if ( resetPaddingHint( QskControl::Background ) ) { qskSendEventTo( this, QEvent::ContentsRectChange ); Q_EMIT marginsChanged( margins() ); @@ -192,7 +178,7 @@ void QskControl::resetMargins() QMarginsF QskControl::margins() const { - return marginHint( QskAspect::Control ); + return paddingHint( QskControl::Background ); } QRectF QskControl::contentsRect() const diff --git a/src/controls/QskControl.h b/src/controls/QskControl.h index 01387f59..49915072 100644 --- a/src/controls/QskControl.h +++ b/src/controls/QskControl.h @@ -29,9 +29,6 @@ class QSK_EXPORT QskControl : public QskQuickItem, public QskSkinnable Q_PROPERTY( QskAspect::Section section READ section WRITE setSection RESET resetSection NOTIFY sectionChanged ) - Q_PROPERTY( bool autoFillBackground READ autoFillBackground - WRITE setAutoFillBackground ) - Q_PROPERTY( bool autoLayoutChildren READ autoLayoutChildren WRITE setAutoLayoutChildren ) @@ -61,6 +58,7 @@ class QSK_EXPORT QskControl : public QskQuickItem, public QskSkinnable using Inherited = QskQuickItem; public: + QSK_SUBCONTROLS( Background ) QSK_STATES( Disabled, Hovered, Focused ) QskControl( QQuickItem* parent = nullptr ); @@ -72,12 +70,12 @@ class QSK_EXPORT QskControl : public QskQuickItem, public QskSkinnable void resetMargins(); QMarginsF margins() const; - void setBackgroundColor( const QColor& ); - void setBackground( const QskGradient& ); void resetBackground(); QskGradient background() const; + void setBackgroundColor( const QColor& ); + QRectF contentsRect() const; QRectF layoutRect() const; @@ -95,9 +93,6 @@ class QSK_EXPORT QskControl : public QskQuickItem, public QskSkinnable QRectF subControlContentsRect( QskAspect::Subcontrol ) const; QRectF subControlContentsRect( const QSizeF&, QskAspect::Subcontrol ) const; - void setAutoFillBackground( bool ); - bool autoFillBackground() const; - void setAutoLayoutChildren( bool ); bool autoLayoutChildren() const; diff --git a/src/controls/QskControlPrivate.cpp b/src/controls/QskControlPrivate.cpp index 96f9caf0..bda8777d 100644 --- a/src/controls/QskControlPrivate.cpp +++ b/src/controls/QskControlPrivate.cpp @@ -157,7 +157,6 @@ QskControlPrivate::QskControlPrivate() , layoutAlignmentHint( 0 ) , explicitLocale( false ) , explicitSection( false ) - , autoFillBackground( false ) , autoLayoutChildren( false ) , focusPolicy( Qt::NoFocus ) , isWheelEnabled( false ) @@ -422,7 +421,7 @@ void QskControlPrivate::setPlacementPolicy( else { this->visiblePlacementPolicy = - ( policy == QskPlacementPolicy::Reserve ) ? 1 : 0; + ( policy == QskPlacementPolicy::Reserve ) ? 1 : 0; if ( isTransparentForPositioner() ) { diff --git a/src/controls/QskControlPrivate.h b/src/controls/QskControlPrivate.h index d1854d8e..8c74a36a 100644 --- a/src/controls/QskControlPrivate.h +++ b/src/controls/QskControlPrivate.h @@ -59,7 +59,6 @@ class QskControlPrivate : public QskQuickItemPrivate bool explicitLocale : 1; bool explicitSection : 1; - bool autoFillBackground : 1; bool autoLayoutChildren : 1; uint focusPolicy : 4; diff --git a/src/controls/QskEvent.cpp b/src/controls/QskEvent.cpp index ba479d7a..cd84b5f4 100644 --- a/src/controls/QskEvent.cpp +++ b/src/controls/QskEvent.cpp @@ -208,9 +208,10 @@ QskGestureEvent* QskGestureEvent::clone() const // -- QskAnimatorEvent -QskAnimatorEvent::QskAnimatorEvent( QskAspect aspect, State state ) +QskAnimatorEvent::QskAnimatorEvent( QskAspect aspect, int index, State state ) : QskEvent( QskEvent::Animator ) , m_aspect( aspect ) + , m_index( index ) , m_state( state ) { } diff --git a/src/controls/QskEvent.h b/src/controls/QskEvent.h index ef91e5fc..4a0180c4 100644 --- a/src/controls/QskEvent.h +++ b/src/controls/QskEvent.h @@ -143,9 +143,10 @@ class QSK_EXPORT QskAnimatorEvent : public QskEvent Terminated }; - QskAnimatorEvent( QskAspect aspect, State state ); + QskAnimatorEvent( QskAspect aspect, int index, State state ); inline QskAspect aspect() const { return m_aspect; } + inline int index() const { return m_index; } inline State state() const { return m_state; } QskAnimatorEvent* clone() const override; @@ -155,6 +156,7 @@ class QSK_EXPORT QskAnimatorEvent : public QskEvent private: QskAspect m_aspect; + int m_index; State m_state; }; diff --git a/src/controls/QskGraphicLabelSkinlet.cpp b/src/controls/QskGraphicLabelSkinlet.cpp index e6a36707..5c9654e9 100644 --- a/src/controls/QskGraphicLabelSkinlet.cpp +++ b/src/controls/QskGraphicLabelSkinlet.cpp @@ -11,6 +11,8 @@ #include "QskFunctions.h" #include "QskGraphic.h" +#include + QskGraphicLabelSkinlet::QskGraphicLabelSkinlet( QskSkin* skin ) : Inherited( skin ) { @@ -96,7 +98,7 @@ QRect QskGraphicLabelSkinlet::graphicRect( } return qskAlignedRect( graphicRect, - ( int ) sz.width(), ( int ) sz.height(), label->alignment() ); + qCeil( sz.width() ), qCeil( sz.height() ), label->alignment() ); } QSGNode* QskGraphicLabelSkinlet::updateGraphicNode( diff --git a/src/controls/QskHintAnimator.cpp b/src/controls/QskHintAnimator.cpp index 95812bb1..48a9d249 100644 --- a/src/controls/QskHintAnimator.cpp +++ b/src/controls/QskHintAnimator.cpp @@ -12,7 +12,6 @@ #include #include -#include #include #define ALIGN_VALUES 0 @@ -93,7 +92,14 @@ static inline bool qskCheckReceiverThread( const QObject* receiver ) return ( thread == QThread::currentThread() ); } -QskHintAnimator::QskHintAnimator() +QskHintAnimator::QskHintAnimator() noexcept + : m_index( -1 ) +{ +} + +QskHintAnimator::QskHintAnimator( const QskAspect aspect, int index ) noexcept + : m_aspect( aspect ) + , m_index( index ) { } @@ -101,24 +107,29 @@ QskHintAnimator::~QskHintAnimator() { } -void QskHintAnimator::setAspect( QskAspect aspect ) +void QskHintAnimator::setAspect( const QskAspect aspect ) noexcept { m_aspect = aspect; } -void QskHintAnimator::setUpdateFlags( QskAnimationHint::UpdateFlags flags ) +void QskHintAnimator::setIndex( int index ) noexcept +{ + m_index = index; +} + +void QskHintAnimator::setUpdateFlags( QskAnimationHint::UpdateFlags flags ) noexcept { m_updateFlags = flags; } -void QskHintAnimator::setControl( QskControl* control ) +void QskHintAnimator::setControl( QskControl* control ) noexcept { m_control = control; } void QskHintAnimator::advance( qreal progress ) { - const QVariant oldValue = currentValue(); + const auto oldValue = currentValue(); Inherited::advance( progress ); @@ -154,8 +165,90 @@ void QskHintAnimator::advance( qreal progress ) } } +#ifndef QT_NO_DEBUG_STREAM + +#include + +QDebug operator<<( QDebug debug, const QskHintAnimator& animator ) +{ + QDebugStateSaver saver( debug ); + debug.nospace(); + + debug << "Animator" << "( "; + + debug << animator.aspect() << ", " << animator.endValue().typeName() << ", "; + + if ( animator.index() >= 0 ) + debug << animator.index() << ", "; + + if ( animator.isRunning() ) + debug << "R: " << animator.duration() << ", " << animator.elapsed(); + else + debug << "S" << animator.duration(); + + if ( auto control = animator.control() ) + debug << ", " << control->className() << ", " << (void*) control; + + debug << " )"; + + return debug; +} + +#endif + namespace { + class AnimatorMap : public std::vector< QskHintAnimator* > + { + public: + ~AnimatorMap() + { + qDeleteAll( *this ); + } + + inline const QskHintAnimator* find( const QskAspect aspect, int index ) const + { + const Key key { aspect, index }; + + auto it = std::lower_bound( cbegin(), cend(), key, lessThan ); + if ( it != cend() ) + { + if ( ( ( *it )->aspect() == aspect ) && ( ( *it )->index() == index ) ) + return *it; + } + + return nullptr; + } + + inline QskHintAnimator* findOrInsert( const QskAspect aspect, int index ) + { + const Key key { aspect, index }; + + auto it = std::lower_bound( begin(), end(), key, lessThan ); + if ( it == end() || ( *it )->aspect() != aspect || ( *it )->index() != index ) + { + it = insert( it, new QskHintAnimator( aspect, index ) ); + } + + return *it; + } + + private: + struct Key + { + QskAspect aspect; + int index; + }; + + static inline bool lessThan( const QskHintAnimator* animator, const Key& key ) + { + if ( animator->aspect() == key.aspect ) + return animator->index() < key.index; + + return animator->aspect() < key.aspect; + } + }; + class AnimatorGuard final : public QObject { Q_OBJECT @@ -204,13 +297,10 @@ namespace class QskHintAnimatorTable::PrivateData { public: - // we won't have many entries, so we prefer less memory over - // using a hash table - std::map< QskAspect, QskHintAnimator > map; + AnimatorMap animators; // a flat map }; QskHintAnimatorTable::QskHintAnimatorTable() - : m_data( nullptr ) { } @@ -218,64 +308,59 @@ QskHintAnimatorTable::~QskHintAnimatorTable() { if ( qskAnimatorGuard ) qskAnimatorGuard->unregisterTable( this ); + delete m_data; } void QskHintAnimatorTable::start( QskControl* control, - QskAspect aspect, QskAnimationHint animationHint, + const QskAspect aspect, int index, QskAnimationHint animationHint, const QVariant& from, const QVariant& to ) { if ( m_data == nullptr ) { m_data = new PrivateData(); + if ( qskAnimatorGuard ) qskAnimatorGuard->registerTable( this ); } - auto& animator = m_data->map[ aspect ]; + auto animator = m_data->animators.findOrInsert( aspect, index ); - animator.setAspect( aspect ); - animator.setStartValue( from ); - animator.setEndValue( to ); + animator->setStartValue( from ); + animator->setEndValue( to ); - animator.setDuration( animationHint.duration ); - animator.setEasingCurve( animationHint.type ); - animator.setUpdateFlags( animationHint.updateFlags ); + animator->setDuration( animationHint.duration ); + animator->setEasingCurve( animationHint.type ); + animator->setUpdateFlags( animationHint.updateFlags ); - animator.setControl( control ); - animator.setWindow( control->window() ); + animator->setControl( control ); + animator->setWindow( control->window() ); - animator.start(); + animator->start(); if ( qskCheckReceiverThread( control ) ) { - QskAnimatorEvent event( aspect, QskAnimatorEvent::Started ); + QskAnimatorEvent event( aspect, index, QskAnimatorEvent::Started ); QCoreApplication::sendEvent( control, &event ); } } -const QskHintAnimator* QskHintAnimatorTable::animator( QskAspect aspect ) const +const QskHintAnimator* QskHintAnimatorTable::animator( QskAspect aspect, int index ) const { - if ( m_data == nullptr ) - return nullptr; + if ( m_data ) + return m_data->animators.find( aspect, index ); - auto it = m_data->map.find( aspect ); - if ( it == m_data->map.end() ) - return nullptr; - - return &( it->second ); + return nullptr; } -QVariant QskHintAnimatorTable::currentValue( QskAspect aspect ) const +QVariant QskHintAnimatorTable::currentValue( QskAspect aspect, int index ) const { if ( m_data ) { - const auto it = m_data->map.find( aspect ); - if ( it != m_data->map.cend() ) + if ( auto animator = m_data->animators.find( aspect, index ) ) { - const auto& animator = it->second; - if ( animator.isRunning() ) - return animator.currentValue(); + if ( animator->isRunning() ) + return animator->currentValue(); } } @@ -287,21 +372,30 @@ bool QskHintAnimatorTable::cleanup() if ( m_data == nullptr ) return true; - for ( auto it = m_data->map.begin(); it != m_data->map.end(); ) - { - // remove all terminated animators - if ( !it->second.isRunning() ) - { - auto control = it->second.control(); - auto aspect = it->first; + auto& animators = m_data->animators; - it = m_data->map.erase( it ); + for ( auto it = animators.begin(); it != animators.end(); ) + { + auto animator = *it; + + // remove all terminated animators + if ( !animator->isRunning() ) + { + const auto control = animator->control(); + const auto aspect = animator->aspect(); + const auto index = animator->index(); + + delete animator; + + it = animators.erase( it ); if ( control ) { if ( qskCheckReceiverThread( control ) ) { - auto event = new QskAnimatorEvent( aspect, QskAnimatorEvent::Terminated ); + auto event = new QskAnimatorEvent( + aspect, index, QskAnimatorEvent::Terminated ); + QCoreApplication::postEvent( control, event ); } } @@ -312,7 +406,7 @@ bool QskHintAnimatorTable::cleanup() } } - if ( m_data->map.empty() ) + if ( animators.empty() ) { delete m_data; m_data = nullptr; diff --git a/src/controls/QskHintAnimator.h b/src/controls/QskHintAnimator.h index 8e67d727..81d5edd8 100644 --- a/src/controls/QskHintAnimator.h +++ b/src/controls/QskHintAnimator.h @@ -19,60 +19,91 @@ class QSK_EXPORT QskHintAnimator : public QskVariantAnimator using Inherited = QskVariantAnimator; public: - QskHintAnimator(); + QskHintAnimator() noexcept; + QskHintAnimator( QskAspect, int index ) noexcept; + ~QskHintAnimator() override; - void setAspect( QskAspect ); - QskAspect aspect() const; + void setAspect( QskAspect ) noexcept; + QskAspect aspect() const noexcept; - void setControl( QskControl* ); - QskControl* control() const; + void setIndex( int ) noexcept; + int index() const noexcept; - void setUpdateFlags( QskAnimationHint::UpdateFlags ); - QskAnimationHint::UpdateFlags updateFlags() const; + void setControl( QskControl* ) noexcept; + QskControl* control() const noexcept; + + void setUpdateFlags( QskAnimationHint::UpdateFlags ) noexcept; + QskAnimationHint::UpdateFlags updateFlags() const noexcept; void advance( qreal value ) override; + bool operator<( const QskHintAnimator& ) const noexcept; + private: QskAspect m_aspect; + int m_index; QskAnimationHint::UpdateFlags m_updateFlags; QPointer< QskControl > m_control; }; +#ifndef QT_NO_DEBUG_STREAM + +class QDebug; +QSK_EXPORT QDebug operator<<( QDebug, const QskHintAnimator& ); + +#endif + class QSK_EXPORT QskHintAnimatorTable { public: QskHintAnimatorTable(); ~QskHintAnimatorTable(); - void start( QskControl*, QskAspect, + void start( QskControl*, QskAspect, int index, QskAnimationHint, const QVariant& from, const QVariant& to ); - const QskHintAnimator* animator( QskAspect ) const; - QVariant currentValue( QskAspect ) const; + const QskHintAnimator* animator( QskAspect, int index = -1 ) const; + QVariant currentValue( QskAspect, int index = -1 ) const; bool cleanup(); + bool isEmpty() const; private: void reset(); class PrivateData; - PrivateData* m_data; + PrivateData* m_data = nullptr; }; -inline QskAspect QskHintAnimator::aspect() const +inline QskAspect QskHintAnimator::aspect() const noexcept { return m_aspect; } -inline QskAnimationHint::UpdateFlags QskHintAnimator::updateFlags() const +inline int QskHintAnimator::index() const noexcept +{ + return m_index; +} + +inline QskAnimationHint::UpdateFlags QskHintAnimator::updateFlags() const noexcept { return m_updateFlags; } -inline QskControl* QskHintAnimator::control() const +inline QskControl* QskHintAnimator::control() const noexcept { return m_control; } +inline bool QskHintAnimator::operator<( const QskHintAnimator& other ) const noexcept +{ + return m_aspect < other.m_aspect; +} + +inline bool QskHintAnimatorTable::isEmpty() const +{ + return m_data == nullptr; +} + #endif diff --git a/src/controls/QskMenuSkinlet.cpp b/src/controls/QskMenuSkinlet.cpp index fe25626a..2bda28d4 100644 --- a/src/controls/QskMenuSkinlet.cpp +++ b/src/controls/QskMenuSkinlet.cpp @@ -6,7 +6,6 @@ #include "QskMenuSkinlet.h" #include "QskMenu.h" -#include "QskBoxNode.h" #include "QskGraphic.h" #include "QskColorFilter.h" #include "QskTextOptions.h" @@ -531,7 +530,11 @@ QSGNode* QskMenuSkinlet::updateSampleNode( const QskSkinnable* skinnable, if ( subControl == Q::Separator ) { - return updateBoxNode( menu, node, rect, subControl ); + auto gradient = menu->gradientHint( subControl ); + if ( ( gradient.type() == QskGradient::Stops ) && !gradient.isMonochrome() ) + gradient.setLinearDirection( Qt::Vertical ); + + return updateBoxNode( menu, node, rect, gradient, subControl ); } return nullptr; diff --git a/src/controls/QskPageIndicatorSkinlet.cpp b/src/controls/QskPageIndicatorSkinlet.cpp index 6b57eda2..c6680408 100644 --- a/src/controls/QskPageIndicatorSkinlet.cpp +++ b/src/controls/QskPageIndicatorSkinlet.cpp @@ -6,7 +6,6 @@ #include "QskPageIndicatorSkinlet.h" #include "QskPageIndicator.h" -#include "QskBoxNode.h" #include "QskSGNode.h" #include "QskFunctions.h" diff --git a/src/controls/QskProgressBar.cpp b/src/controls/QskProgressBar.cpp index 11390281..3acc736c 100644 --- a/src/controls/QskProgressBar.cpp +++ b/src/controls/QskProgressBar.cpp @@ -6,7 +6,6 @@ #include "QskProgressBar.h" #include "QskIntervalF.h" -#include "QskGradient.h" #include "QskFunctions.h" #include "QskAnimator.h" #include "QskAspect.h" @@ -163,20 +162,12 @@ QskAspect::Placement QskProgressBar::effectivePlacement() const void QskProgressBar::setBarGradient( const QskGradient& gradient ) { - // An API where we set the stops only would be more accurate TODO ... - auto g = gradient; - - g.setOrientation( Qt::Horizontal ); - setGradientHint( Bar | QskAspect::Horizontal, g ); - - g.setOrientation( Qt::Vertical ); - setGradientHint( Bar | QskAspect::Vertical, g ); + setGradientHint( Bar, gradient ); } void QskProgressBar::resetBarGradient() { - resetColor( Bar | QskAspect::Vertical ); - resetColor( Bar | QskAspect::Horizontal ); + resetColor( Bar ); } QskGradient QskProgressBar::barGradient() const diff --git a/src/controls/QskProgressBarSkinlet.cpp b/src/controls/QskProgressBarSkinlet.cpp index 49f2ed46..cad4d197 100644 --- a/src/controls/QskProgressBarSkinlet.cpp +++ b/src/controls/QskProgressBarSkinlet.cpp @@ -7,6 +7,7 @@ #include "QskProgressBar.h" #include "QskIntervalF.h" #include "QskBoxBorderMetrics.h" +#include "QskGradientDirection.h" #include #include @@ -124,13 +125,27 @@ QSGNode* QskProgressBarSkinlet::updateBarNode( if ( !gradient.isVisible() ) return nullptr; - gradient.setOrientation( bar->orientation() ); - - if ( !gradient.isMonochrome() ) + if ( ( gradient.type() == QskGradient::Stops ) && !gradient.isMonochrome() ) { - const auto intv = qskBarInterval( bar ); - gradient = gradient.extracted( intv.lowerBound(), intv.upperBound() ); + /* + When having stops only we use a linear gradient, + where the colors are increasing in direction of the + progress value. We interprete the gradient as a + definition for the 100% situation and have to adjust + the stops for smaller bars. + For this situation it would be more convenient to + adjust the start/stop positions, but the box renderer is + not supporting this yet. TODO ... + */ + + const auto intv = qskBarInterval( bar ); + + const auto stops = qskExtractedGradientStops( gradient.stops(), + intv.lowerBound(), intv.upperBound() ); + + gradient.setStops( stops ); + gradient.setLinearDirection( bar->orientation() ); if ( bar->orientation() == Qt::Vertical || bar->layoutMirroring() ) gradient.reverse(); } diff --git a/src/controls/QskPushButtonSkinlet.cpp b/src/controls/QskPushButtonSkinlet.cpp index a7fcf71b..cac78a33 100644 --- a/src/controls/QskPushButtonSkinlet.cpp +++ b/src/controls/QskPushButtonSkinlet.cpp @@ -37,6 +37,9 @@ namespace setGraphicTextElements( button, QskPushButton::Text, button->text(), QskPushButton::Graphic, button->graphic().defaultSize() ); + + const auto alignment = button->alignmentHint( QskPushButton::Panel, Qt::AlignCenter ); + setFixedContent( QskPushButton::Text, Qt::Horizontal, alignment ); } }; } @@ -59,7 +62,7 @@ QRectF QskPushButtonSkinlet::subControlRect( const QskSkinnable* skinnable, if ( ( subControl == Q::Text ) || ( subControl == Q::Graphic ) ) { const auto r = button->subControlContentsRect( contentsRect, Q::Panel ); - + LayoutEngine layoutEngine( button ); layoutEngine.setGeometries( r ); @@ -192,9 +195,9 @@ QSizeF QskPushButtonSkinlet::sizeHint( const QskSkinnable* skinnable, LayoutEngine layoutEngine( button ); auto size = layoutEngine.sizeHint( which, QSizeF() ); - - size = size.expandedTo( button->strutSizeHint( Q::Panel ) ); size = button->outerBoxSize( Q::Panel, size ); + size = size.expandedTo( button->strutSizeHint( Q::Panel ) ); + size = size.grownBy( skinnable->marginHint( Q::Panel ) ); return size; } diff --git a/src/controls/QskSegmentedBar.cpp b/src/controls/QskSegmentedBar.cpp index d2a95ca0..c31fe12e 100644 --- a/src/controls/QskSegmentedBar.cpp +++ b/src/controls/QskSegmentedBar.cpp @@ -137,19 +137,13 @@ QskTextOptions QskSegmentedBar::textOptions() const return textOptionsHint( Text ); } -int QskSegmentedBar::addText( const QString& text ) +int QskSegmentedBar::addOption( const QUrl& graphicSource, const QString& text ) { - m_data->addOption( this, Option( QUrl(), text ) ); + m_data->addOption( this, Option( graphicSource, text ) ); return count() - 1; } -int QskSegmentedBar::addGraphic( const QUrl& graphicSource ) -{ - m_data->addOption( this, Option( graphicSource, QString() ) ); - return count() - 1; -} - -QVariant QskSegmentedBar::optionAt( int index ) const +QVariantList QskSegmentedBar::optionAt( int index ) const { const auto& options = m_data->options; @@ -158,14 +152,11 @@ QVariant QskSegmentedBar::optionAt( int index ) const const auto& option = options[ index ]; - QVariant value; + QVariantList list; + list += QVariant::fromValue( option.graphic ); + list += QVariant::fromValue( option.text ); - if ( option.graphicSource.isValid() ) - value = QVariant::fromValue( option.graphic ); - else - value = QVariant::fromValue( option.text ); - - return value; + return list; } QskAspect::Placement QskSegmentedBar::effectivePlacement() const @@ -371,6 +362,7 @@ void QskSegmentedBar::setSelectedIndex( int index ) if( index != m_data->selectedIndex ) { + const auto oldIndex = m_data->selectedIndex; m_data->selectedIndex = index; movePositionHint( Cursor, index ); @@ -380,6 +372,14 @@ void QskSegmentedBar::setSelectedIndex( int index ) setSkinStateFlag( Minimum, ( m_data->selectedIndex == 0 ) ); setSkinStateFlag( Maximum, ( m_data->selectedIndex == count() - 1 ) ); + + const auto states = skinStates(); + + if ( oldIndex >= 0 ) + startHintTransitions( states | Selected, states, oldIndex ); + + if ( index >= 0 ) + startHintTransitions( states, states | Selected, index ); } } diff --git a/src/controls/QskSegmentedBar.h b/src/controls/QskSegmentedBar.h index e2afa911..c726ba25 100644 --- a/src/controls/QskSegmentedBar.h +++ b/src/controls/QskSegmentedBar.h @@ -46,8 +46,7 @@ class QSK_EXPORT QskSegmentedBar : public QskControl void setTextOptions( const QskTextOptions& ); QskTextOptions textOptions() const; - int addText( const QString& ); - int addGraphic( const QUrl& ); + int addOption( const QUrl&, const QString& ); void clear(); @@ -56,7 +55,7 @@ class QSK_EXPORT QskSegmentedBar : public QskControl int count() const; - QVariant optionAt( int ) const; + QVariantList optionAt( int ) const; void setSegmentEnabled( int, bool ); bool isSegmentEnabled( int ) const; diff --git a/src/controls/QskSegmentedBarSkinlet.cpp b/src/controls/QskSegmentedBarSkinlet.cpp index 33590124..91dab9c1 100644 --- a/src/controls/QskSegmentedBarSkinlet.cpp +++ b/src/controls/QskSegmentedBarSkinlet.cpp @@ -8,18 +8,77 @@ #include "QskGraphic.h" #include "QskColorFilter.h" -#include "QskTextOptions.h" -#include "QskSGNode.h" #include "QskFunctions.h" +#include "QskSkin.h" +#include "QskStandardSymbol.h" +#include "QskSubcontrolLayoutEngine.h" #include #include +namespace +{ +#if 1 // unify with the implementation from QskMenu + template< class T > + static inline QVariant qskSampleAt( const QskSegmentedBar* bar, int index ) + { + const auto list = bar->optionAt( index ); + for ( const auto& value : list ) + { + if ( value.canConvert< T >() ) + return value; + } + + return QVariant(); + } + + template< class T > + static inline T qskValueAt( const QskSegmentedBar* bar, int index ) + { + const auto sample = qskSampleAt< T >( bar, index ); + return sample.template value< T >(); + } +#endif + + QskGraphic graphicAt( const QskSegmentedBar* bar, const int index ) + { + // note: It is a Material 3 peculiarity that the selected element + // always has the checkmark symbol. If we ever have another style + // implementing this control we should put this code into a + // subclass. + const auto graphic = ( bar->selectedIndex() == index ) + ? bar->effectiveSkin()->symbol( QskStandardSymbol::SegmentedBarCheckMark ) + : qskValueAt< QskGraphic >( bar, index ); + + return graphic; + } + + class LayoutEngine : public QskSubcontrolLayoutEngine + { + public: + LayoutEngine( const QskSegmentedBar* bar, int index ) + : QskSubcontrolLayoutEngine( bar->orientation() ) + { + setSpacing( bar->spacingHint( QskSegmentedBar::Panel ) ); + + setGraphicTextElements( bar, + QskSegmentedBar::Text, qskValueAt< QString >( bar, index ), + QskSegmentedBar::Graphic, graphicAt( bar, index ).defaultSize() ); + + if( bar->orientation() == Qt::Horizontal ) + { + const auto alignment = bar->alignmentHint( QskSegmentedBar::Panel, Qt::AlignCenter ); + setFixedContent( QskSegmentedBar::Text, Qt::Horizontal, alignment ); + } + } + }; +} + QskSegmentedBarSkinlet::QskSegmentedBarSkinlet( QskSkin* skin ) : Inherited( skin ) { - setNodeRoles( { PanelRole, SegmentRole, SeparatorRole, - CursorRole, TextRole, GraphicRole } ); + setNodeRoles( { CursorRole, PanelRole, SegmentRole, + SeparatorRole, TextRole, GraphicRole } ); } QskSegmentedBarSkinlet::~QskSegmentedBarSkinlet() = default; @@ -77,23 +136,22 @@ QRectF QskSegmentedBarSkinlet::segmentRect( { using Q = QskSegmentedBar; - const auto spacing = bar->spacingHint( Q::Panel ); const auto count = bar->count(); auto rect = subControlRect( bar, contentsRect, Q::Panel ); if( bar->orientation() == Qt::Horizontal ) { - const qreal w = ( rect.width() - ( count - 1 ) * spacing ) / count; + const qreal w = rect.width() / count; - rect.setLeft( index * ( w + spacing ) ); + rect.setLeft( index * w ); rect.setWidth( w ); } else { - const qreal h = ( rect.height() - ( count - 1 ) * spacing ) / count; + const qreal h = rect.height() / count; - rect.setTop( index * ( h + spacing ) ); + rect.setTop( index * h ); rect.setHeight( h ); } @@ -116,7 +174,7 @@ QRectF QskSegmentedBarSkinlet::separatorRect( if( bar->orientation() == Qt::Horizontal ) { - rect.setLeft( rect.right() ); + rect.setLeft( rect.right() ); // ### *0.5 or so? rect.setSize( { strutSize.width(), sh.height() } ); } else @@ -138,12 +196,12 @@ QSGNode* QskSegmentedBarSkinlet::updateSubNode( switch( nodeRole ) { - case PanelRole: - return updateBoxNode( skinnable, node, Q::Panel ); - case CursorRole: return updateBoxNode( skinnable, node, Q::Cursor ); + case PanelRole: + return updateBoxNode( skinnable, node, Q::Panel ); + case SegmentRole: return updateSeriesNode( skinnable, Q::Segment, node ); @@ -160,56 +218,37 @@ QSGNode* QskSegmentedBarSkinlet::updateSubNode( return nullptr; } -QSizeF QskSegmentedBarSkinlet::segmentSizeHint( const QskSegmentedBar* bar ) const +QSizeF QskSegmentedBarSkinlet::segmentSizeHint( const QskSegmentedBar* bar, Qt::SizeHint which ) const { - qreal widthMax = 0; - qreal graphicRatioMax = 0; + using Q = QskSegmentedBar; - const QFontMetricsF fm( bar->effectiveFont( QskSegmentedBar::Text ) ); + QSizeF sizeMax; for ( int i = 0; i < bar->count(); i++ ) { - const auto value = bar->optionAt( i ); + LayoutEngine layoutEngine( bar, i ); - if ( value.canConvert< QskGraphic >() ) + const auto graphic = bar->effectiveSkin()->symbol( QskStandardSymbol::SegmentedBarCheckMark ); + + // We want to know how big the element can grow when it is selected, + // i.e. when it has the checkmark symbol: + layoutEngine.setGraphicTextElements( bar, + QskSegmentedBar::Text, qskValueAt< QString >( bar, i ), + QskSegmentedBar::Graphic, graphic.defaultSize() ); + + const auto size = layoutEngine.sizeHint( which, QSizeF() ); + + if( size.width() > sizeMax.width() ) { - const auto graphic = value.value< QskGraphic >(); - - if ( !graphic.isNull() ) - { - const auto sz = graphic.defaultSize(); - - if( sz.isValid() ) - { - const qreal ratio = sz.width() / sz.height(); - - if( graphicRatioMax < ratio ) - graphicRatioMax = ratio; - } - } - } - else if ( value.canConvert< QString >() ) - { - const auto text = value.value< QString >(); - if ( !text.isEmpty() ) - { - const auto sz = fm.size( Qt::TextShowMnemonic, text ); - - if( sz.width() > widthMax ) - widthMax = sz.width(); - } + sizeMax = size; } } - if( graphicRatioMax > 0 ) - { - const qreal w = fm.height() * graphicRatioMax; + sizeMax = bar->outerBoxSize( Q::Segment, sizeMax ); + sizeMax = sizeMax.expandedTo( bar->strutSizeHint( Q::Segment ) ); + sizeMax = sizeMax.grownBy( bar->marginHint( Q::Segment ) ); - if( w > widthMax ) - widthMax = w; - } - - return bar->outerBoxSize( QskSegmentedBar::Segment, QSizeF( widthMax, fm.height() ) ); + return sizeMax; } QSizeF QskSegmentedBarSkinlet::sizeHint( const QskSkinnable* skinnable, @@ -232,7 +271,7 @@ QSizeF QskSegmentedBarSkinlet::sizeHint( const QskSkinnable* skinnable, const qreal spacing = skinnable->spacingHint( Q::Panel ); const auto bar = static_cast< const QskSegmentedBar* >( skinnable ); - const auto segmentSize = segmentSizeHint( bar ); + const auto segmentSize = segmentSizeHint( bar, which ); if( bar->orientation() == Qt::Horizontal ) { @@ -276,7 +315,10 @@ QRectF QskSegmentedBarSkinlet::sampleRect( const QskSkinnable* skinnable, if ( subControl == Q::Text || subControl == Q::Graphic ) { const auto rect = sampleRect( skinnable, contentsRect, Q::Segment, index ); - return rect; + + LayoutEngine layoutEngine( bar, index ); + layoutEngine.setGeometries( rect ); + return layoutEngine.subControlRect( subControl ); } return Inherited::sampleRect( skinnable, contentsRect, subControl, index ); @@ -325,11 +367,10 @@ QSGNode* QskSegmentedBarSkinlet::updateSampleNode( const QskSkinnable* skinnable if ( subControl == Q::Text ) { - const auto value = bar->optionAt( index ); - if ( value.canConvert< QString >() ) - { - const auto text = value.value< QString >(); + const auto text = qskValueAt< QString >( bar, index ); + if( !text.isEmpty() ) + { return QskSkinlet::updateTextNode( bar, node, rect, alignment, text, Q::Text ); } @@ -339,10 +380,10 @@ QSGNode* QskSegmentedBarSkinlet::updateSampleNode( const QskSkinnable* skinnable if ( subControl == Q::Graphic ) { - const auto value = bar->optionAt( index ); - if ( value.canConvert< QskGraphic >() ) + const auto graphic = graphicAt( bar, index ); + + if( !graphic.isEmpty() ) { - const auto graphic = value.value< QskGraphic >(); const auto filter = bar->effectiveGraphicFilter( subControl ); const auto padding = bar->paddingHint( Q::Graphic ); const auto graphicRect = rect.marginsRemoved( padding ); diff --git a/src/controls/QskSegmentedBarSkinlet.h b/src/controls/QskSegmentedBarSkinlet.h index 8bb8faa8..a61a2318 100644 --- a/src/controls/QskSegmentedBarSkinlet.h +++ b/src/controls/QskSegmentedBarSkinlet.h @@ -55,7 +55,7 @@ class QSK_EXPORT QskSegmentedBarSkinlet : public QskSkinlet QskAspect::Subcontrol, int index, QSGNode* ) const override; private: - QSizeF segmentSizeHint( const QskSegmentedBar* ) const; + QSizeF segmentSizeHint(const QskSegmentedBar*, Qt::SizeHint ) const; QRectF segmentRect( const QskSegmentedBar*, const QRectF&, int index ) const; QRectF separatorRect( const QskSegmentedBar*, const QRectF&, int index ) const; diff --git a/src/controls/QskSeparatorSkinlet.cpp b/src/controls/QskSeparatorSkinlet.cpp index 7df87569..a639bbb6 100644 --- a/src/controls/QskSeparatorSkinlet.cpp +++ b/src/controls/QskSeparatorSkinlet.cpp @@ -6,6 +6,7 @@ #include "QskSeparatorSkinlet.h" #include "QskSeparator.h" +#include "QskGradientDirection.h" #include "QskAspect.h" QskSeparatorSkinlet::QskSeparatorSkinlet( QskSkin* skin ) @@ -38,7 +39,21 @@ QSGNode* QskSeparatorSkinlet::updateSubNode( { case PanelRole: { - return updateBoxNode( separator, node, QskSeparator::Panel ); + using Q = QskSeparator; + + const auto rect = separator->subControlRect( Q::Panel ); + + auto gradient = separator->gradientHint( Q::Panel ); + if ( ( gradient.type() == QskGradient::Stops ) && !gradient.isMonochrome() ) + { + // gradient in opposite orientation + const auto orientation = ( separator->orientation() == Qt::Vertical ) + ? Qt::Horizontal : Qt::Vertical; + + gradient.setLinearDirection( orientation ); + } + + return updateBoxNode( separator, node, rect, gradient, Q::Panel ); } } diff --git a/src/controls/QskSkin.cpp b/src/controls/QskSkin.cpp index 77f59bb7..b97308e3 100644 --- a/src/controls/QskSkin.cpp +++ b/src/controls/QskSkin.cpp @@ -174,15 +174,21 @@ QskSkin::QskSkin( QObject* parent ) const QFont font = QGuiApplication::font(); setupFonts( font.family(), font.weight(), font.italic() ); + const auto noMargins = QVariant::fromValue( QskMargins( 0 ) ); + { // some defaults - const auto noMargins = QVariant::fromValue( QskMargins( 0 ) ); - const auto aspect = QskAspect::Control | QskAspect::Metric; + const auto aspect = QskAspect::NoSubcontrol | QskAspect::Metric; setSkinHint( aspect | QskAspect::Margin, noMargins ); setSkinHint( aspect | QskAspect::Padding, noMargins ); setSkinHint( aspect | QskAspect::Spacing, 0 ); } + + setSkinHint( QskControl::Background | QskAspect::Metric | QskAspect::Padding, noMargins ); + + setSkinHint( QskControl::Background | QskAspect::Color, + QVariant::fromValue( QskGradient() ) ); } QskSkin::~QskSkin() diff --git a/src/controls/QskSkinHintTableEditor.cpp b/src/controls/QskSkinHintTableEditor.cpp index b238a0e0..8e736d26 100644 --- a/src/controls/QskSkinHintTableEditor.cpp +++ b/src/controls/QskSkinHintTableEditor.cpp @@ -12,7 +12,6 @@ #include "QskBoxBorderMetrics.h" #include "QskBoxBorderColors.h" #include "QskShadowMetrics.h" -#include "QskGradient.h" namespace { @@ -225,7 +224,9 @@ void QskSkinHintTableEditor::setHGradient( QskAspect aspect, const QColor& color1, const QColor& color2, QskStateCombination combination ) { - const QskGradient gradient( QskGradient::Horizontal, color1, color2 ); + QskGradient gradient( color1, color2 ); + gradient.setLinearDirection( Qt::Horizontal ); + setGradient( aspect, gradient, combination ); } @@ -233,10 +234,20 @@ void QskSkinHintTableEditor::setVGradient( QskAspect aspect, const QColor& color1, const QColor& color2, QskStateCombination combination ) { - const QskGradient gradient( QskGradient::Vertical, color1, color2 ); + QskGradient gradient( color1, color2 ); + gradient.setLinearDirection( Qt::Vertical ); + setGradient( aspect, gradient, combination ); } +void QskSkinHintTableEditor::setGradient( + QskAspect aspect, const QColor& color1, const QColor& color2, + QskStateCombination combination ) +{ + const QskGradient gradient( color1, color2 ); + setColorHint( aspect, gradient , combination ); +} + void QskSkinHintTableEditor::setGradient( QskAspect aspect, const QskGradient& gradient, QskStateCombination combination ) diff --git a/src/controls/QskSkinHintTableEditor.h b/src/controls/QskSkinHintTableEditor.h index 91dac5b4..380da2f7 100644 --- a/src/controls/QskSkinHintTableEditor.h +++ b/src/controls/QskSkinHintTableEditor.h @@ -118,6 +118,9 @@ class QSK_EXPORT QskSkinHintTableEditor void setVGradient( QskAspect, const QColor&, const QColor&, QskStateCombination = QskStateCombination() ); + void setGradient( QskAspect, const QColor&, const QColor&, + QskStateCombination = QskStateCombination() ); + void setGradient( QskAspect, const QskGradient&, QskStateCombination = QskStateCombination() ); diff --git a/src/controls/QskSkinTransition.cpp b/src/controls/QskSkinTransition.cpp index 985ee28b..8ca0158b 100644 --- a/src/controls/QskSkinTransition.cpp +++ b/src/controls/QskSkinTransition.cpp @@ -416,15 +416,7 @@ inline bool WindowAnimator::isControlAffected( const QskControl* control, return false; } - if ( subControl == QskAspect::Control ) - { - if ( !control->autoFillBackground() ) - { - // no need to animate the background unless we show it - return false; - } - } - else + if ( subControl != QskAspect::NoSubcontrol ) { if ( !subControls.contains( subControl ) ) { diff --git a/src/controls/QskSkinlet.cpp b/src/controls/QskSkinlet.cpp index a7cf17b7..e672931d 100644 --- a/src/controls/QskSkinlet.cpp +++ b/src/controls/QskSkinlet.cpp @@ -10,9 +10,9 @@ #include "QskArcMetrics.h" #include "QskBoxBorderColors.h" #include "QskBoxBorderMetrics.h" -#include "QskBoxClipNode.h" #include "QskBoxNode.h" -#include "QskShadedBoxNode.h" +#include "QskBoxClipNode.h" +#include "QskBoxRectangleNode.h" #include "QskBoxShapeMetrics.h" #include "QskBoxHints.h" #include "QskColorFilter.h" @@ -21,6 +21,7 @@ #include "QskGradient.h" #include "QskGraphicNode.h" #include "QskGraphic.h" +#include "QskRectangleNode.h" #include "QskSGNode.h" #include "QskTextColors.h" #include "QskTextNode.h" @@ -175,7 +176,7 @@ static inline QskTextColors qskTextColors( return c; } -static inline QSGNode* qskUpdateShadedBoxNode( +static inline QSGNode* qskUpdateBoxNode( const QskSkinnable*, QSGNode* node, const QRectF& rect, const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& borderMetrics, const QskBoxBorderColors& borderColors, const QskGradient& gradient, @@ -190,14 +191,14 @@ static inline QSGNode* qskUpdateShadedBoxNode( if ( qskIsBoxVisible( absoluteMetrics, borderColors, gradient ) ) { - auto boxNode = static_cast< QskShadedBoxNode* >( node ); + auto boxNode = static_cast< QskBoxNode* >( node ); if ( boxNode == nullptr ) - boxNode = new QskShadedBoxNode(); + boxNode = new QskBoxNode(); const auto absoluteShape = shape.toAbsolute( size ); const auto absoluteShadowMetrics = shadowMetrics.toAbsolute( size ); - boxNode->setBoxData( rect, absoluteShape, absoluteMetrics, + boxNode->updateNode( rect, absoluteShape, absoluteMetrics, borderColors, gradient, absoluteShadowMetrics, shadowColor ); return boxNode; @@ -242,6 +243,8 @@ class QskSkinlet::PrivateData QskSkin* skin; QVector< quint8 > nodeRoles; + int animatorIndex = -1; + bool ownedBySkinnable : 1; }; @@ -269,6 +272,21 @@ bool QskSkinlet::isOwnedBySkinnable() const return m_data->ownedBySkinnable; } +void QskSkinlet::setAnimatorIndex( int index ) +{ + m_data->animatorIndex = index; +} + +void QskSkinlet::resetAnimatorIndex() +{ + m_data->animatorIndex = -1; +} + +int QskSkinlet::animatorIndex() const +{ + return m_data->animatorIndex; +} + void QskSkinlet::setNodeRoles( const QVector< quint8 >& nodeRoles ) { m_data->nodeRoles = nodeRoles; @@ -296,10 +314,7 @@ void QskSkinlet::updateNode( QskSkinnable* skinnable, QSGNode* parentNode ) cons // background oldNode = findChildNode( parentNode, BackgroundRole ); - - newNode = nullptr; - if ( control->autoFillBackground() ) - newNode = updateBackgroundNode( control, oldNode ); + newNode = updateBackgroundNode( control, oldNode ); replaceChildNode( BackgroundRole, parentNode, oldNode, newNode ); @@ -336,12 +351,10 @@ QSGNode* QskSkinlet::updateBackgroundNode( if ( !gradient.isValid() ) return nullptr; - auto boxNode = static_cast< QskBoxNode* >( node ); - if ( boxNode == nullptr ) - boxNode = new QskBoxNode(); + auto rectNode = QskSGNode::ensureNode< QskRectangleNode >( node ); + rectNode->updateNode( rect, gradient ); - boxNode->setBoxData( rect, gradient ); - return boxNode; + return rectNode; } QSGNode* QskSkinlet::updateDebugNode( @@ -418,7 +431,7 @@ QSGNode* QskSkinlet::updateBoxNode( const QskSkinnable* skinnable, const auto shadowMetrics = skinnable->shadowMetricsHint( subControl ); const auto shadowColor = skinnable->shadowColorHint( subControl ); - return qskUpdateShadedBoxNode( skinnable, node, + return qskUpdateBoxNode( skinnable, node, boxRect, shape, borderMetrics, borderColors, fillGradient, shadowMetrics, shadowColor ); } @@ -428,7 +441,7 @@ QSGNode* QskSkinlet::updateBoxNode( const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& borderMetrics, const QskBoxBorderColors& borderColors, const QskGradient& fillGradient ) { - return qskUpdateShadedBoxNode( skinnable, node, + return qskUpdateBoxNode( skinnable, node, rect, shape, borderMetrics, borderColors, fillGradient, QskShadowMetrics(), QColor() ); } @@ -436,7 +449,7 @@ QSGNode* QskSkinlet::updateBoxNode( QSGNode* QskSkinlet::updateBoxNode( const QskSkinnable* skinnable, QSGNode* node, const QRectF& rect, const QskBoxHints& hints ) { - return qskUpdateShadedBoxNode( skinnable, node, rect, + return qskUpdateBoxNode( skinnable, node, rect, hints.shape, hints.borderMetrics, hints.borderColors, hints.gradient, hints.shadowMetrics, hints.shadowColor ); } @@ -693,6 +706,25 @@ QSGNode* QskSkinlet::updateSeriesNode( const QskSkinnable* skinnable, QskSkinStateChanger stateChanger( skinnable ); stateChanger.setStates( newStates ); + class IndexChanger + { + public: + inline IndexChanger( const QskSkinlet* skinlet, int index ) + : m_skinlet( const_cast< QskSkinlet* >( skinlet ) ) + { + m_skinlet->setAnimatorIndex( index ); + } + + inline ~IndexChanger() + { + m_skinlet->resetAnimatorIndex(); + } + private: + QskSkinlet* m_skinlet; + }; + + IndexChanger indexChanger( this, i ); + newNode = updateSampleNode( skinnable, subControl, i, node ); } diff --git a/src/controls/QskSkinlet.h b/src/controls/QskSkinlet.h index 127af44e..79fa7554 100644 --- a/src/controls/QskSkinlet.h +++ b/src/controls/QskSkinlet.h @@ -71,6 +71,10 @@ class QSK_EXPORT QskSkinlet void setOwnedBySkinnable( bool on ); bool isOwnedBySkinnable() const; + void setAnimatorIndex( int ); + void resetAnimatorIndex(); + int animatorIndex() const; + // Helper functions for creating nodes static QSGNode* updateBoxNode( const QskSkinnable*, QSGNode*, diff --git a/src/controls/QskSkinnable.cpp b/src/controls/QskSkinnable.cpp index 958f0c53..81e99c60 100644 --- a/src/controls/QskSkinnable.cpp +++ b/src/controls/QskSkinnable.cpp @@ -211,7 +211,7 @@ static inline QskAspect qskSubstitutedAspect( } #endif - aspect.setSubControl( skinnable->effectiveSubcontrol( aspect.subControl() ) ); + aspect.setSubcontrol( skinnable->effectiveSubcontrol( aspect.subControl() ) ); return aspect; } @@ -297,10 +297,10 @@ const QskSkinlet* QskSkinnable::effectiveSkinlet() const void QskSkinnable::setSubcontrolProxy( QskAspect::Subcontrol subControl, QskAspect::Subcontrol proxy ) { - if ( subControl == QskAspect::Control ) + if ( subControl == QskAspect::NoSubcontrol ) return; // nonsense, we ignore this - if ( proxy == QskAspect::Control || subControl == proxy ) + if ( proxy == QskAspect::NoSubcontrol || subControl == proxy ) { resetSubcontrolProxy( subControl ); return; @@ -338,7 +338,7 @@ QskAspect::Subcontrol QskSkinnable::subcontrolProxy( QskAspect::Subcontrol subCo return it->second; } - return QskAspect::Control; + return QskAspect::NoSubcontrol; } QskSkinHintTable& QskSkinnable::hintTable() @@ -687,14 +687,14 @@ int QskSkinnable::fontRoleHint( return qskFlag( this, aspect | QskAspect::FontRole, status ); } -QFont QskSkinnable::effectiveFont( const QskAspect aspect ) const +QFont QskSkinnable::effectiveFont( const QskAspect::Subcontrol subControl ) const { - return effectiveSkin()->font( fontRoleHint( aspect ) ); + return effectiveSkin()->font( fontRoleHint( subControl ) ); } -qreal QskSkinnable::effectiveFontHeight( const QskAspect aspect ) const +qreal QskSkinnable::effectiveFontHeight( const QskAspect::Subcontrol subControl ) const { - const QFontMetricsF fm( effectiveFont( aspect ) ); + const QFontMetricsF fm( effectiveFont( subControl ) ); return fm.height(); } @@ -714,44 +714,53 @@ int QskSkinnable::graphicRoleHint( return qskFlag( this, aspect | QskAspect::GraphicRole, status ); } -QskColorFilter QskSkinnable::effectiveGraphicFilter( QskAspect aspect ) const +QskColorFilter QskSkinnable::effectiveGraphicFilter( + const QskAspect::Subcontrol subControl ) const { - aspect.setSubControl( effectiveSubcontrol( aspect.subControl() ) ); + /* + Usually we find the graphic role and return the related filter + from the skin. But as we can't interpolate between graphic roles + the corresponding animators interpolate the filters. + */ + + QskAspect aspect( effectiveSubcontrol( subControl ) | QskAspect::GraphicRole ); + aspect.setSection( section() ); aspect.setPlacement( effectivePlacement() ); - aspect = aspect | QskAspect::GraphicRole; QskSkinHintStatus status; const auto hint = storedHint( aspect | skinStates(), &status ); - if ( status.isValid() ) - { - // we need to know about how the aspect gets resolved - // before checking for animators + if ( !status.isValid() ) + return QskColorFilter(); - aspect.setSubControl( status.aspect.subControl() ); - } + aspect.setSubcontrol( status.aspect.subControl() ); + aspect.setSection( QskAspect::Body ); + aspect.setPlacement( QskAspect::NoPlacement ); - if ( !aspect.isAnimator() ) + const auto v = animatedHint( aspect, nullptr ); + + if ( v.canConvert< QskColorFilter >() ) + return v.value< QskColorFilter >(); + + if ( auto control = owningControl() ) { - auto v = animatedValue( aspect, nullptr ); + const auto graphicRole = hint.toInt(); + + const auto v = QskSkinTransition::animatedGraphicFilter( + control->window(), graphicRole ); + if ( v.canConvert< QskColorFilter >() ) - return v.value< QskColorFilter >(); - - if ( auto control = owningControl() ) { - v = QskSkinTransition::animatedGraphicFilter( - control->window(), hint.toInt() ); - - if ( v.canConvert< QskColorFilter >() ) - { - /* - As it is hard to find out which controls depend - on the animated graphic filters we reschedule - our updates here. - */ - control->update(); - return v.value< QskColorFilter >(); - } +#if 1 + /* + Design flaw: the animators for the skin transition do not + know about the controls, that are affected from the color + filter. As a workaround we schedule the update in the + getter: TODO ... + */ + control->update(); +#endif + return v.value< QskColorFilter >(); } } @@ -761,7 +770,7 @@ QskColorFilter QskSkinnable::effectiveGraphicFilter( QskAspect aspect ) const bool QskSkinnable::setAnimationHint( QskAspect aspect, QskAnimationHint hint ) { - aspect.setSubControl( effectiveSubcontrol( aspect.subControl() ) ); + aspect.setSubcontrol( effectiveSubcontrol( aspect.subControl() ) ); return m_data->hintTable.setAnimation( aspect, hint ); } @@ -856,7 +865,14 @@ bool QskSkinnable::resetSkinHint( QskAspect aspect ) QVariant QskSkinnable::effectiveSkinHint( QskAspect aspect, QskSkinHintStatus* status ) const { - aspect.setSubControl( effectiveSubcontrol( aspect.subControl() ) ); + aspect.setSubcontrol( effectiveSubcontrol( aspect.subControl() ) ); + + if ( !( aspect.isAnimator() || aspect.hasStates() ) ) + { + const auto v = animatedHint( aspect, status ); + if ( v.isValid() ) + return v; + } if ( aspect.section() == QskAspect::Body ) aspect.setSection( section() ); @@ -864,16 +880,20 @@ QVariant QskSkinnable::effectiveSkinHint( if ( aspect.placement() == QskAspect::NoPlacement ) aspect.setPlacement( effectivePlacement() ); - if ( aspect.isAnimator() ) - return storedHint( aspect, status ); - - const auto v = animatedValue( aspect, status ); - if ( v.isValid() ) - return v; - if ( !aspect.hasStates() ) aspect.setStates( skinStates() ); + if ( !aspect.isAnimator() && QskSkinTransition::isRunning() ) + { + /* + The skin has changed and the hints are interpolated + between the old and the new one over time + */ + const auto v = interpolatedHint( aspect, status ); + if ( v.isValid() ) + return v; + } + return storedHint( aspect, status ); } @@ -911,105 +931,76 @@ bool QskSkinnable::moveSkinHint( QskAspect aspect, const QVariant& value ) return moveSkinHint( aspect, effectiveSkinHint( aspect ), value ); } -QVariant QskSkinnable::animatedValue( +QVariant QskSkinnable::animatedHint( QskAspect aspect, QskSkinHintStatus* status ) const { QVariant v; - if ( !aspect.hasStates() ) + if ( !m_data->animators.isEmpty() ) { - /* - The local animators were invented to be stateless - and we never have an aspect with a state here. - But that might change ... - */ + const int index = effectiveSkinlet()->animatorIndex(); - auto a = aspect; + v = m_data->animators.currentValue( aspect, index ); + if ( !v.isValid() && index >= 0 ) + v = m_data->animators.currentValue( aspect, -1 ); + } - Q_FOREVER + if ( status && v.isValid() ) + { + status->source = QskSkinHintStatus::Animator; + status->aspect = aspect; + } + + return v; +} + +QVariant QskSkinnable::interpolatedHint( + QskAspect aspect, QskSkinHintStatus* status ) const +{ + if ( !QskSkinTransition::isRunning() || m_data->hintTable.hasHint( aspect ) ) + return QVariant(); + + const auto control = owningControl(); + if ( control == nullptr ) + return QVariant(); + + QVariant v; + + auto a = aspect; + + Q_FOREVER + { + v = QskSkinTransition::animatedHint( control->window(), aspect ); + + if ( !v.isValid() ) { - v = m_data->animators.currentValue( aspect ); - - if ( !v.isValid() ) + if ( const auto topState = aspect.topState() ) { - if ( aspect.placement() ) - { - // clear the placement bits and restart - aspect = a; - aspect.setPlacement( QskAspect::NoPlacement ); - - continue; - } - } - - if ( aspect.section() != QskAspect::Body ) - { - // try to resolve from QskAspect::Body - - a.setSection( QskAspect::Body ); - aspect = a; - + aspect.clearState( aspect.topState() ); continue; } - break; - } - } - - if ( !v.isValid() ) - { - if ( QskSkinTransition::isRunning() && - !m_data->hintTable.hasHint( aspect ) ) - { - /* - Next we check for values from the skin. Those - animators are usually from global skin/color changes - and are state aware - */ - - if ( const auto control = owningControl() ) + if ( aspect.placement() ) { - if ( !aspect.hasStates() ) - aspect.setStates( skinStates() ); + // clear the placement bits and restart + aspect = a; + aspect.setPlacement( QskAspect::NoPlacement ); - auto a = aspect; - - Q_FOREVER - { - v = QskSkinTransition::animatedHint( control->window(), aspect ); - - if ( !v.isValid() ) - { - if ( const auto topState = aspect.topState() ) - { - aspect.clearState( aspect.topState() ); - continue; - } - - if ( aspect.placement() ) - { - // clear the placement bits and restart - aspect = a; - aspect.setPlacement( QskAspect::NoPlacement ); - - continue; - } - } - - if ( aspect.section() != QskAspect::Body ) - { - // try to resolve from QskAspect::Body - - a.setSection( QskAspect::Body ); - aspect = a; - - continue; - } - - break; - } + continue; } } + + if ( a.section() != QskAspect::Body ) + { + // try to resolve with the default section + + a.setSection( QskAspect::Body ); + aspect = a; + + continue; + } + + break; } if ( status && v.isValid() ) @@ -1058,11 +1049,11 @@ const QVariant& QskSkinnable::storedHint( return *value; } - if ( aspect.subControl() != QskAspect::Control ) + if ( aspect.hasSubcontrol() ) { // trying to resolve something from the skin default settings - aspect.setSubControl( QskAspect::Control ); + aspect.clearSubcontrol(); aspect.clearStates(); if ( const auto value = skinTable.resolvedHint( aspect, &resolvedAspect ) ) @@ -1185,10 +1176,7 @@ QSizeF QskSkinnable::outerBoxSize( QskAspect aspect, const QSizeF& innerBoxSize ) const { const auto pd = qskEffectivePadding( this, aspect, innerBoxSize, false ); - - // since Qt 5.14 we would have QSizeF::grownBy ! - return QSizeF( innerBoxSize.width() + pd.width(), - innerBoxSize.height() + pd.height() ); + return innerBoxSize.grownBy( pd ); } QRectF QskSkinnable::outerBox( @@ -1228,11 +1216,17 @@ bool QskSkinnable::isTransitionAccepted( QskAspect aspect ) const void QskSkinnable::startTransition( QskAspect aspect, QskAnimationHint animationHint, const QVariant& from, const QVariant& to ) { - aspect.setSubControl( effectiveSubcontrol( aspect.subControl() ) ); - startHintTransition( aspect, animationHint, from, to ); + startTransition( aspect, -1, animationHint, from, to ); } -void QskSkinnable::startHintTransition( QskAspect aspect, +void QskSkinnable::startTransition( QskAspect aspect, int index, + QskAnimationHint animationHint, const QVariant& from, const QVariant& to ) +{ + aspect.setSubcontrol( effectiveSubcontrol( aspect.subControl() ) ); + startHintTransition( aspect, index, animationHint, from, to ); +} + +void QskSkinnable::startHintTransition( QskAspect aspect, int index, QskAnimationHint animationHint, const QVariant& from, const QVariant& to ) { if ( animationHint.duration <= 0 || ( from == to ) ) @@ -1256,19 +1250,25 @@ void QskSkinnable::startHintTransition( QskAspect aspect, v2.setValue( skin->graphicFilter( v2.toInt() ) ); } + /* + We do not need the extra bits that would slow down resolving + the effective aspect in animatedHint. + */ + aspect.clearStates(); + aspect.setSection( QskAspect::Body ); + aspect.setPlacement( QskAspect::NoPlacement ); aspect.setAnimator( false ); - aspect.setPlacement( effectivePlacement() ); #if DEBUG_ANIMATOR qDebug() << aspect << animationHint.duration; #endif - auto animator = m_data->animators.animator( aspect ); + auto animator = m_data->animators.animator( aspect, index ); if ( animator && animator->isRunning() ) v1 = animator->currentValue(); - m_data->animators.start( control, aspect, animationHint, v1, v2 ); + m_data->animators.start( control, aspect, index, animationHint, v1, v2 ); } void QskSkinnable::setSkinStateFlag( QskAspect::State stateFlag, bool on ) @@ -1303,79 +1303,111 @@ void QskSkinnable::setSkinStates( QskAspect::States newStates ) auto control = owningControl(); #if DEBUG_STATE - qDebug() << control->className() << ":" + const auto className = control ? control->className() : "QskSkinnable"; + + qDebug() << className << ":" << skinStateAsPrintable( m_data->skinState ) << "->" << skinStateAsPrintable( newState ); #endif - const auto skin = effectiveSkin(); - - if ( skin ) + if ( control && control->window() ) { - const auto mask = skin->hintTable().states() | m_data->hintTable.states(); - - if ( ( newStates & mask ) == ( m_data->skinStates & mask ) ) + if ( const auto skin = effectiveSkin() ) { - // the modified bits are not handled by the skin + const auto mask = m_data->hintTable.states() | skin->hintTable().states(); + if ( ( newStates & mask ) != ( m_data->skinStates & mask ) ) + { + /* + When there are no aspects for the changed state bits we know + that there won't be any animated transitions + */ - m_data->skinStates = newStates; - return; + startHintTransitions( m_data->skinStates, newStates ); + } } + + if ( control->flags() & QQuickItem::ItemHasContents ) + control->update(); } - if ( control->window() && isTransitionAccepted( QskAspect() ) ) + m_data->skinStates = newStates; +} + +bool QskSkinnable::startHintTransitions( + QskAspect::States oldStates, QskAspect::States newStates, int index ) +{ + if ( !isTransitionAccepted( QskAspect() ) ) { - const auto placement = effectivePlacement(); - const auto primitiveCount = QskAspect::primitiveCount(); + // the control does not like any animated transition at the moment + return false; + } - const auto subControls = control->subControls(); - for ( const auto subControl : subControls ) + bool started = false; // at least one transition has been started + + QskAspect aspect; + aspect.setPlacement( effectivePlacement() ); + aspect.setSection( section() ); + + const auto skin = effectiveSkin(); + const auto control = owningControl(); + + const auto primitiveCount = QskAspect::primitiveCount(); + + const auto subControls = control->subControls(); + for ( const auto subControl : subControls ) + { + aspect.setSubcontrol( subControl ); + + const auto& skinTable = skin->hintTable(); + + for ( uint i = 0; i < QskAspect::typeCount; i++ ) { - auto aspect = subControl | placement; + const auto type = static_cast< QskAspect::Type >( i ); - const auto& skinTable = skin->hintTable(); + const auto hint = effectiveAnimation( type, subControl, newStates ); - for ( uint i = 0; i < QskAspect::typeCount; i++ ) + if ( hint.duration > 0 ) { - const auto type = static_cast< QskAspect::Type >( i ); + /* + Starting an animator for all primitives, + that differ between the states + */ - const auto hint = effectiveAnimation( type, subControl, newStates ); - - if ( hint.duration > 0 ) + for ( uint i = 0; i < primitiveCount; i++ ) { - /* - Starting an animator for all primitives, - that differ between the states - */ + const auto primitive = static_cast< QskAspect::Primitive >( i ); + aspect.setPrimitive( type, primitive ); - for ( uint i = 0; i < primitiveCount; i++ ) + const auto a1 = aspect | oldStates; + const auto a2 = aspect | newStates; + + bool doTransition = true; + + if ( m_data->hintTable.states() == QskAspect::NoState ) { - const auto primitive = static_cast< QskAspect::Primitive >( i ); - aspect.setPrimitive( type, primitive ); + /* + In case we have no state aware aspects in the local + table we can avoid starting animators for aspects, + that are finally resolved from the same hint in + the skin table. + */ - const auto a1 = aspect | m_data->skinStates; - const auto a2 = aspect | newStates; + doTransition = !skinTable.isResolutionMatching( a1, a2 ); + } - bool doTransition = true; + if ( doTransition ) + { + startHintTransition( aspect, index, hint, + storedHint( a1 ), storedHint( a2 ) ); - if ( m_data->hintTable.states() == QskAspect::NoState ) - doTransition = !skinTable.isResolutionMatching( a1, a2 ); - - if ( doTransition ) - { - startHintTransition( aspect, hint, - storedHint( a1 ), storedHint( a2 ) ); - } + started = true; } } } } } - m_data->skinStates = newStates; - - if ( control->flags() & QQuickItem::ItemHasContents ) - control->update(); + return started; } QskSkin* QskSkinnable::effectiveSkin() const diff --git a/src/controls/QskSkinnable.h b/src/controls/QskSkinnable.h index 397e43de..e09bb84c 100644 --- a/src/controls/QskSkinnable.h +++ b/src/controls/QskSkinnable.h @@ -79,10 +79,9 @@ class QSK_EXPORT QskSkinnable void setSkinlet( const QskSkinlet* ); const QskSkinlet* skinlet() const; - QFont effectiveFont( QskAspect ) const; - qreal effectiveFontHeight( QskAspect ) const; - - QskColorFilter effectiveGraphicFilter( QskAspect ) const; + QFont effectiveFont( QskAspect::Subcontrol ) const; + qreal effectiveFontHeight( QskAspect::Subcontrol ) const; + QskColorFilter effectiveGraphicFilter( QskAspect::Subcontrol ) const; void setSubcontrolProxy( QskAspect::Subcontrol, QskAspect::Subcontrol proxy ); void resetSubcontrolProxy( QskAspect::Subcontrol ); @@ -125,6 +124,9 @@ class QSK_EXPORT QskSkinnable void startTransition( QskAspect, QskAnimationHint, const QVariant& from, const QVariant& to ); + void startTransition( QskAspect, int index, + QskAnimationHint, const QVariant& from, const QVariant& to ); + QskAspect::Subcontrol effectiveSubcontrol( QskAspect::Subcontrol ) const; QskControl* controlCast(); @@ -248,6 +250,8 @@ class QSK_EXPORT QskSkinnable const QskSkinHintTable& hintTable() const; + bool startHintTransitions( QskAspect::States, QskAspect::States, int index = -1 ); + protected: virtual void updateNode( QSGNode* ); virtual bool isTransitionAccepted( QskAspect ) const; @@ -259,10 +263,11 @@ class QSK_EXPORT QskSkinnable private: Q_DISABLE_COPY( QskSkinnable ) - void startHintTransition( QskAspect, + void startHintTransition( QskAspect, int index, QskAnimationHint, const QVariant& from, const QVariant& to ); - QVariant animatedValue( QskAspect, QskSkinHintStatus* ) const; + QVariant animatedHint( QskAspect, QskSkinHintStatus* ) const; + QVariant interpolatedHint( QskAspect, QskSkinHintStatus* ) const; const QVariant& storedHint( QskAspect, QskSkinHintStatus* = nullptr ) const; class PrivateData; diff --git a/src/controls/QskSliderSkinlet.cpp b/src/controls/QskSliderSkinlet.cpp index 6d25379c..ede1f99a 100644 --- a/src/controls/QskSliderSkinlet.cpp +++ b/src/controls/QskSliderSkinlet.cpp @@ -236,12 +236,15 @@ QRectF QskSliderSkinlet::handleRect( QRectF QskSliderSkinlet::rippleRect( const QskSlider* slider, const QRectF& rect ) const { + const auto rippleSize = slider->strutSizeHint( QskSlider::Ripple ); + const auto handleSize = slider->strutSizeHint( QskSlider::Handle ); + + const auto w = ( rippleSize.width() - handleSize.width() ) / 2; + const auto h = ( rippleSize.height() - handleSize.height() ) / 2; + auto r = handleRect( slider, rect ); - auto rippleSize = slider->strutSizeHint( QskSlider::Ripple ); - auto handleSize = slider->strutSizeHint( QskSlider::Handle ); - auto w = ( rippleSize.width() - handleSize.width() ) / 2, - h = ( rippleSize.height() - handleSize.height() ) / 2; r = r.marginsAdded( { w, h, w, h } ); + return r; } diff --git a/src/controls/QskSubWindowArea.h b/src/controls/QskSubWindowArea.h index 3cbc30d3..cacd94ea 100644 --- a/src/controls/QskSubWindowArea.h +++ b/src/controls/QskSubWindowArea.h @@ -8,7 +8,6 @@ #include "QskControl.h" -class QskGradient; class QskSubWindow; class QSK_EXPORT QskSubWindowArea : public QskControl diff --git a/src/controls/QskTabBar.cpp b/src/controls/QskTabBar.cpp index c469441d..b068e4f8 100644 --- a/src/controls/QskTabBar.cpp +++ b/src/controls/QskTabBar.cpp @@ -309,7 +309,7 @@ void QskTabBar::setEdge( Qt::Edge edge ) { const auto oldEdge = this->edge(); - setFlagHint( Panel | QskAspect::Style, edge ); + setFlagHint( Panel | QskAspect::Style, edge ); if ( edge == oldEdge ) return; diff --git a/src/controls/QskTabButton.cpp b/src/controls/QskTabButton.cpp index 0ffb7b8f..20b4167c 100644 --- a/src/controls/QskTabButton.cpp +++ b/src/controls/QskTabButton.cpp @@ -88,7 +88,7 @@ QskTextOptions QskTabButton::textOptions() const } void QskTabButton::resetTextOptions() -{ +{ if ( resetTextOptionsHint( Text ) ) Q_EMIT textOptionsChanged(); } diff --git a/src/controls/QskTabView.h b/src/controls/QskTabView.h index c9f2eb7f..133220d2 100644 --- a/src/controls/QskTabView.h +++ b/src/controls/QskTabView.h @@ -53,11 +53,11 @@ class QSK_EXPORT QskTabView : public QskControl int addTab( QskTabButton*, QQuickItem* ); int insertTab( int index, QskTabButton*, QQuickItem* ); - int addTab( const QString&, QQuickItem* ); - int insertTab( int index, const QString&, QQuickItem* ); + Q_INVOKABLE int addTab( const QString&, QQuickItem* ); + Q_INVOKABLE int insertTab( int index, const QString&, QQuickItem* ); - void removeTab( int index ); - void clear( bool autoDelete = false ); + Q_INVOKABLE void removeTab( int index ); + Q_INVOKABLE void clear( bool autoDelete = false ); QQuickItem* itemAt( int index ) const; QskTabButton* buttonAt( int index ) const; diff --git a/src/controls/QskTextLabel.cpp b/src/controls/QskTextLabel.cpp index b7d24237..dd8a897c 100644 --- a/src/controls/QskTextLabel.cpp +++ b/src/controls/QskTextLabel.cpp @@ -104,7 +104,7 @@ QskTextOptions QskTextLabel::textOptions() const } void QskTextLabel::resetTextOptions() -{ +{ if ( resetTextOptionsHint( Text ) ) Q_EMIT textOptionsChanged( textOptions() ); } diff --git a/src/controls/QskWindow.cpp b/src/controls/QskWindow.cpp index e63cd663..2bbc11f8 100644 --- a/src/controls/QskWindow.cpp +++ b/src/controls/QskWindow.cpp @@ -143,6 +143,7 @@ class QskWindowPrivate : public QQuickWindowPrivate , explicitLocale( false ) , deleteOnClose( false ) , autoLayoutChildren( true ) + , showedOnce( false ) { } @@ -163,6 +164,7 @@ class QskWindowPrivate : public QQuickWindowPrivate bool explicitLocale : 1; bool deleteOnClose : 1; bool autoLayoutChildren : 1; + bool showedOnce : 1; }; QskWindow::QskWindow( QWindow* parent ) @@ -303,15 +305,33 @@ bool QskWindow::event( QEvent* event ) { case QEvent::Show: { - if ( size().isEmpty() ) + if ( !d->showedOnce ) { - QSize sz = sizeConstraint(); - if ( !sz.isEmpty() ) - { - sz = sz.expandedTo( minimumSize() ); - sz = sz.boundedTo( maximumSize() ); + d->showedOnce = true; - resize( sz ); + /* + When a window has a platform window its size is taken + from the platform window - otherwise from d->geometry. + A top level window that has not been shown does not have + a platform window yet and therefore any size set before calling + QWindow::show() is stored there. + + Starting with Qt 6.5 an initial default size is set to the platform + window ( at least QXcbWindow ) before the Show event is sent + and we can't rely on QWindow::size() anymore. But even if d->geometry + is not used anymore we can initialize depending on it. + */ + + if ( d->geometry.size().isEmpty() ) + { + QSize sz = sizeConstraint(); + if ( !sz.isEmpty() ) + { + sz = sz.expandedTo( minimumSize() ); + sz = sz.boundedTo( maximumSize() ); + + resize( sz ); + } } } diff --git a/src/dialogs/QskDialogButton.cpp b/src/dialogs/QskDialogButton.cpp index c0294aae..f5a07e67 100644 --- a/src/dialogs/QskDialogButton.cpp +++ b/src/dialogs/QskDialogButton.cpp @@ -7,10 +7,6 @@ #include "QskDialogButtonBox.h" #include "QskSkin.h" -QSK_SUBCONTROL( QskDialogButton, Panel ) -QSK_SUBCONTROL( QskDialogButton, Text ) -QSK_SUBCONTROL( QskDialogButton, Graphic ) - QskDialogButton::QskDialogButton( QskDialog::Action action, QQuickItem* parent ) : QskPushButton( parent ) @@ -28,21 +24,6 @@ QskDialogButton::~QskDialogButton() { } -QskAspect::Subcontrol QskDialogButton::substitutedSubcontrol( - QskAspect::Subcontrol subControl ) const -{ - if ( subControl == QskPushButton::Panel ) - return QskDialogButton::Panel; - - if ( subControl == QskPushButton::Text ) - return QskDialogButton::Text; - - if ( subControl == QskPushButton::Graphic ) - return QskDialogButton::Graphic; - - return Inherited::substitutedSubcontrol( subControl ); -} - void QskDialogButton::setAction( QskDialog::Action action ) { if ( action != m_action ) diff --git a/src/dialogs/QskDialogButton.h b/src/dialogs/QskDialogButton.h index afb9e875..7f8eb803 100644 --- a/src/dialogs/QskDialogButton.h +++ b/src/dialogs/QskDialogButton.h @@ -19,7 +19,6 @@ class QSK_EXPORT QskDialogButton : public QskPushButton using Inherited = QskPushButton; public: - QSK_SUBCONTROLS( Panel, Text, Graphic ) QskDialogButton( QskDialog::Action, QQuickItem* parent = nullptr ); QskDialogButton( QQuickItem* parent = nullptr ); @@ -35,9 +34,6 @@ class QSK_EXPORT QskDialogButton : public QskPushButton protected: void changeEvent( QEvent* ) override; - QskAspect::Subcontrol substitutedSubcontrol( - QskAspect::Subcontrol ) const override; - private: void resetButton(); diff --git a/src/graphic/QskColorFilter.cpp b/src/graphic/QskColorFilter.cpp index 85c26fb2..e6094bd3 100644 --- a/src/graphic/QskColorFilter.cpp +++ b/src/graphic/QskColorFilter.cpp @@ -41,12 +41,11 @@ static inline QBrush qskSubstitutedBrush( { QBrush newBrush; - const QGradient* gradient = brush.gradient(); - if ( gradient ) + if ( const auto gradient = brush.gradient() ) { bool isModified = false; - QGradientStops stops = gradient->stops(); + auto stops = gradient->stops(); for ( auto& stop : stops ) { const QColor c = qskSubstitutedColor( substitions, stop.second ); @@ -61,7 +60,7 @@ static inline QBrush qskSubstitutedBrush( { newBrush = brush; - QGradient* newGradient = const_cast< QGradient* >( newBrush.gradient() ); + auto newGradient = const_cast< QGradient* >( newBrush.gradient() ); newGradient->setStops( stops ); } } @@ -167,7 +166,7 @@ QPen QskColorFilter::substituted( const QPen& pen ) const if ( m_substitutions.isEmpty() || pen.style() == Qt::NoPen ) return pen; - const QBrush newBrush = qskSubstitutedBrush( m_substitutions, pen.brush() ); + const auto newBrush = qskSubstitutedBrush( m_substitutions, pen.brush() ); if ( newBrush.style() == Qt::NoBrush ) return pen; @@ -181,7 +180,7 @@ QBrush QskColorFilter::substituted( const QBrush& brush ) const if ( m_substitutions.isEmpty() || brush.style() == Qt::NoBrush ) return brush; - const QBrush newBrush = qskSubstitutedBrush( m_substitutions, brush ); + const auto newBrush = qskSubstitutedBrush( m_substitutions, brush ); return ( newBrush.style() != Qt::NoBrush ) ? newBrush : brush; } diff --git a/src/graphic/QskGraphic.cpp b/src/graphic/QskGraphic.cpp index d2924e48..e7454038 100644 --- a/src/graphic/QskGraphic.cpp +++ b/src/graphic/QskGraphic.cpp @@ -257,18 +257,20 @@ namespace QskGraphicPrivate } inline double scaleFactorX( const QRectF& pathRect, - const QRectF& targetRect, bool scalePens ) const + const QRectF& targetRect, const QRectF& graphicBoundingRect, bool scalePens ) const { if ( pathRect.width() <= 0.0 ) return 0.0; const QPointF p0 = m_pointRect.center(); - const qreal l = qAbs( pathRect.left() - p0.x() ); - const qreal r = qAbs( pathRect.right() - p0.x() ); + const auto p = pathRect.united( m_boundingRect ); + + const qreal l = qAbs( p.left() - p0.x() ); + const qreal r = qAbs( p.right() - p0.x() ); const double w = 2.0 * qMin( l, r ) * - targetRect.width() / pathRect.width(); + targetRect.width() / graphicBoundingRect.width(); double sx; if ( scalePens && m_scalablePen ) @@ -288,18 +290,20 @@ namespace QskGraphicPrivate } inline double scaleFactorY( const QRectF& pathRect, - const QRectF& targetRect, bool scalePens ) const + const QRectF& targetRect, const QRectF& graphicBoundingRect, bool scalePens ) const { if ( pathRect.height() <= 0.0 ) return 0.0; const QPointF p0 = m_pointRect.center(); - const qreal t = qAbs( pathRect.top() - p0.y() ); - const qreal b = qAbs( pathRect.bottom() - p0.y() ); + const auto p = pathRect.united( m_boundingRect ); + + const qreal t = qAbs( p.top() - p0.y() ); + const qreal b = qAbs( p.bottom() - p0.y() ); const double h = 2.0 * qMin( t, b ) * - targetRect.height() / pathRect.height(); + targetRect.height() / graphicBoundingRect.height(); double sy; if ( scalePens && m_scalablePen ) @@ -680,14 +684,14 @@ void QskGraphic::render( QPainter* painter, const QRectF& rect, for ( const auto& info : qAsConst( m_data->pathInfos ) ) { - const qreal ssx = info.scaleFactorX( - m_data->pointRect, rect, scalePens ); + const qreal ssx = info.scaleFactorX( m_data->pointRect, + rect, m_data->boundingRect, scalePens ); if ( ssx > 0.0 ) sx = qMin( sx, ssx ); - const qreal ssy = info.scaleFactorY( - m_data->pointRect, rect, scalePens ); + const qreal ssy = info.scaleFactorY( m_data->pointRect, + rect, m_data->boundingRect, scalePens ); if ( ssy > 0.0 ) sy = qMin( sy, ssy ); @@ -702,15 +706,15 @@ void QskGraphic::render( QPainter* painter, const QRectF& rect, sx = sy = qMax( sx, sy ); } - const auto& pr = m_data->pointRect; + const auto& br = m_data->boundingRect; const auto rc = rect.center(); QTransform tr; tr.translate( - rc.x() - 0.5 * sx * pr.width(), - rc.y() - 0.5 * sy * pr.height() ); + rc.x() - 0.5 * sx * br.width(), + rc.y() - 0.5 * sy * br.height() ); tr.scale( sx, sy ); - tr.translate( -pr.x(), -pr.y() ); + tr.translate( -br.x(), -br.y() ); const auto transform = painter->transform(); @@ -1079,7 +1083,7 @@ QskGraphic QskGraphic::fromGraphic( QPainter painter( &recoloredGraphic ); graphic.render( &painter, colorFilter ); painter.end(); - + return recoloredGraphic; } diff --git a/src/graphic/QskStandardSymbol.cpp b/src/graphic/QskStandardSymbol.cpp index abddcae6..3c9fd6ea 100644 --- a/src/graphic/QskStandardSymbol.cpp +++ b/src/graphic/QskStandardSymbol.cpp @@ -260,6 +260,11 @@ QskGraphic QskStandardSymbol::graphic( Type symbolType ) qskCrossMarkGraphic( &painter ); break; } + case QskStandardSymbol::SegmentedBarCheckMark: + { + qskCheckMarkGraphic( &painter ); + break; + } case QskStandardSymbol::NoSymbol: case QskStandardSymbol::SymbolTypeCount: { diff --git a/src/graphic/QskStandardSymbol.h b/src/graphic/QskStandardSymbol.h index 385ec670..2b517319 100644 --- a/src/graphic/QskStandardSymbol.h +++ b/src/graphic/QskStandardSymbol.h @@ -13,7 +13,7 @@ class QskGraphic; namespace QskStandardSymbol { - QSK_EXPORT Q_NAMESPACE + Q_NAMESPACE_EXPORT( QSK_EXPORT ) enum Type { @@ -30,6 +30,8 @@ namespace QskStandardSymbol CheckMark, CrossMark, + SegmentedBarCheckMark, + SymbolTypeCount }; diff --git a/src/layouts/QskLayoutChain.cpp b/src/layouts/QskLayoutChain.cpp index 43a2f514..4b66122a 100644 --- a/src/layouts/QskLayoutChain.cpp +++ b/src/layouts/QskLayoutChain.cpp @@ -151,6 +151,8 @@ void QskLayoutChain::expandCells( if ( !maximum.isEmpty() && !cell.isValid ) cell.metrics.setMaximum( maximum[i].length ); + cell.metrics.normalize(); + if ( !cell.isValid ) { cell.isValid = true; diff --git a/src/layouts/QskLayoutMetrics.cpp b/src/layouts/QskLayoutMetrics.cpp index 086c29a3..7d45470d 100644 --- a/src/layouts/QskLayoutMetrics.cpp +++ b/src/layouts/QskLayoutMetrics.cpp @@ -8,6 +8,17 @@ #include #include +static void qskRegisterLayoutMetrics() +{ + qRegisterMetaType< QskLayoutMetrics >(); + +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) + QMetaType::registerEqualsComparator< QskLayoutMetrics >(); +#endif +} + +Q_CONSTRUCTOR_FUNCTION( qskRegisterLayoutMetrics ) + void QskLayoutMetrics::setMetric( int which, qreal metric ) noexcept { switch (which) diff --git a/src/layouts/QskStackBoxAnimator.cpp b/src/layouts/QskStackBoxAnimator.cpp index 80d75473..0eb43d79 100644 --- a/src/layouts/QskStackBoxAnimator.cpp +++ b/src/layouts/QskStackBoxAnimator.cpp @@ -67,14 +67,9 @@ namespace Q_OBJECT public: - RotationTransform( Qt::Axis axis, qreal dx1, qreal dy1, qreal dx2, - qreal dy2, qreal radians, QQuickItem* item ) + RotationTransform( Qt::Axis axis, qreal radians, QQuickItem* item ) : QQuickTransform( item ) , m_axis( axis ) - , m_dx1( dx1 ) - , m_dy1( dy1 ) - , m_dx2( dx2 ) - , m_dy2( dy2 ) , m_radians( radians ) { prependToItem( item ); @@ -89,34 +84,17 @@ namespace } } - void setPreTransform( qreal dx1, qreal dy1 ) - { - if( !qskFuzzyCompare( dx1, m_dx1 ) || !qskFuzzyCompare( dy1, m_dy1 ) ) - { - m_dx1 = dx1; - m_dy1 = dy1; - update(); - } - } - - void setPostTransform( qreal dx2, qreal dy2 ) - { - if( !qskFuzzyCompare( dx2, m_dx2 ) || !qskFuzzyCompare( dy2, m_dy2 ) ) - { - m_dx2 = dx2; - m_dy2 = dy2; - update(); - } - } - - void applyTo( QMatrix4x4* matrix ) const override + void applyTo( QMatrix4x4* matrix) const override { if ( const auto item = qobject_cast< QQuickItem* >( parent() ) ) { + const auto dx = 0.5 * item->width(); + const auto dy = 0.5 * item->height(); + QTransform transform; - transform.translate( m_dx1, m_dy1 ); + transform.translate( dx, dy ); transform.rotateRadians( m_radians, m_axis ); - transform.translate( m_dx2, m_dy2 ); + transform.translate( -dx, -dy ); *matrix *= transform; } @@ -124,19 +102,46 @@ namespace private: const Qt::Axis m_axis; - qreal m_dx1; - qreal m_dy1; - qreal m_dx2; - qreal m_dy2; qreal m_radians; }; - static RotationTransform* qskFindRotationTransform( const QQuickItem* item ) + class QuickTransform final : public QQuickTransform + { + Q_OBJECT + + public: + QuickTransform( QQuickItem* item ) + : QQuickTransform( item ) + { + prependToItem( item ); + } + + void setTransform( const QTransform& transform ) + { + if ( transform != m_transform ) + { + m_transform = transform; + update(); + } + } + + void applyTo( QMatrix4x4* matrix ) const override + { + if ( const auto item = qobject_cast< QQuickItem* >( parent() ) ) + *matrix *= m_transform; + } + + private: + QTransform m_transform; + }; + + template< typename Transform > + Transform* qskFindTransform( const QQuickItem* item ) { const auto& transforms = QQuickItemPrivate::get( item )->transforms; for ( const auto& t : transforms ) { - if ( auto transform = qobject_cast< RotationTransform* >( t ) ) + if ( auto transform = qobject_cast< Transform* >( t ) ) return transform; } @@ -402,19 +407,12 @@ void QskStackBoxAnimator2::setup() { const auto axis = ( m_orientation == Qt::Horizontal ) ? Qt::YAxis : Qt::XAxis; + if ( auto item = itemAt( 0 ) ) - { - const auto dx = 0.5 * item->width(); - const auto dy = 0.5 * item->height(); - ( void ) new RotationTransform( axis, dx, dy, -dx, -dy, 0.0, item ); - } + ( void ) new RotationTransform( axis, 0.0, item ); if ( auto item = itemAt( 1 ) ) - { - const auto dx = 0.5 * item->width(); - const auto dy = 0.5 * item->height(); - ( void ) new RotationTransform( axis, dx, dy, -dx, -dy, M_PI_2, item ); - } + ( void ) new RotationTransform( axis, M_PI_2, item ); } void QskStackBoxAnimator2::advanceIndex( qreal value ) @@ -428,7 +426,7 @@ void QskStackBoxAnimator2::advanceIndex( qreal value ) if ( !m_inverted ) radians = 2 * M_PI - radians; - auto rotation = qskFindRotationTransform( item ); + auto rotation = qskFindTransform< RotationTransform >( item ); rotation->setRadians( radians ); item->setVisible( true ); @@ -452,7 +450,7 @@ void QskStackBoxAnimator2::advanceIndex( qreal value ) if ( !m_inverted ) radians = 2 * M_PI - radians; - auto rotation = qskFindRotationTransform( item ); + auto rotation = qskFindTransform< RotationTransform >( item ); rotation->setRadians( radians ); item->setVisible( true ); @@ -466,7 +464,7 @@ void QskStackBoxAnimator2::done() { if ( auto item = itemAt( i ) ) { - delete qskFindRotationTransform( item ); + delete qskFindTransform< RotationTransform >( item ); item->setVisible( i == 1 ); } } @@ -552,127 +550,97 @@ bool QskStackBoxAnimator4::isInverted() const void QskStackBoxAnimator4::setup() { - const auto axis = ( m_orientation == Qt::Horizontal ) - ? Qt::YAxis : Qt::XAxis; - if ( auto item = itemAt( 0 ) ) { - ( void ) new RotationTransform( axis, 0.0, 0.0, 0.0, 0.0, 0.0, item ); + ( void ) new QuickTransform( item ); item->setVisible( true ); } if ( auto item = itemAt( 1 ) ) { - ( void ) new RotationTransform( axis, 0.0, 0.0, 0.0, 0.0, -M_PI_2, item ); + ( void ) new QuickTransform( item ); item->setVisible( true ); } } void QskStackBoxAnimator4::advanceIndex( qreal value ) { - if ( auto item = itemAt( 0 ) ) + auto item1 = itemAt( 1 ); + auto item2 = itemAt( 0 ); + + if ( isInverted() ) + std::swap( item1, item2 ); + + const qreal posEdge = isInverted() ? value : 1.0 - value; + + if ( item1 ) { - auto rotation = qskFindRotationTransform( item ); + const auto transform = transformation( item1, false, posEdge ); - qreal dx1, dy1, radians, dx2, dy2; + auto rotation = qskFindTransform< QuickTransform >( item1 ); + rotation->setTransform( transform ); + } - if( orientation() == Qt::Horizontal ) + if ( item2 ) + { + const auto transform = transformation( item2, true, posEdge ); + + auto rotation = qskFindTransform< QuickTransform >( item2 ); + rotation->setTransform( transform ); + } +} + +QTransform QskStackBoxAnimator4::transformation( + const QQuickItem* item, bool first, qreal posEdge ) const +{ + /* + first: left or top item + posEdge: position of the edge in the range of [0-1] + ( left->right, top->bottom ). + */ + + const qreal radians = M_PI_2 * ( 1.0 - posEdge ); + + QTransform transform; + + if( orientation() == Qt::Horizontal ) + { + const qreal dx = posEdge * ( item->x() + item->width() ); + const qreal dy = 0.5 * item->height(); + + if ( first ) { - const auto w = item->parentItem() ? item->parentItem()->width() : item->width(); - - if( isInverted() ) - { - dx1 = ( w - item->x() ) * value; - radians = -M_PI_2 * value; - dx2 = 0.0; - } - else - { - dx1 = w * ( 1 - value ) - item->x() * value; - radians = M_PI_2 * value; - dx2 = -w; - } - - dy1 = 0.5 * item->height(); - dy2 = -dy1; + transform.translate( -item->x() + dx, dy ); + transform.rotateRadians( radians, Qt::YAxis ); + transform.translate( -item->width(), -dy ); } else { - const auto h = item->parentItem() ? item->parentItem()->height() : item->height(); - - dx1 = 0.5 * item->width(); - dx2 = -dx1; - - if( isInverted() ) - { - dy1 = ( h - item->y() ) * value; - radians = -M_PI_2 * value; - dy2 = 0.0; - } - else - { - dy1 = h * ( 1 - value ) - item->y() * value; - radians = M_PI_2 * value; - dy2 = -h; - } - } - - rotation->setPreTransform( dx1, dy1 ); - rotation->setRadians( radians ); - rotation->setPostTransform( dx2, dy2 ); + transform.translate( dx, dy ); + transform.rotateRadians( radians - M_PI_2, Qt::YAxis ); + transform.translate( 0.0, -dy ); + } } - - if ( auto item = itemAt( 1 ) ) + else { - auto rotation = qskFindRotationTransform( item ); + const qreal dx = 0.5 * item->width(); + const qreal dy = posEdge * ( item->y() + item->height() ); - qreal dx1, dy1, radians, dx2, dy2; - - if( orientation() == Qt::Horizontal ) + if ( first ) { - const auto w = item->parentItem() ? item->parentItem()->width() : item->width(); - - if( isInverted() ) - { - dx1 = w * value - item->x() * ( 1 - value ); - radians = -M_PI_2 * ( value - 1 ); - dx2 = -w; - } - else - { - dx1 = ( item->width() + item->x() ) * ( 1 - value ); - radians = M_PI_2 * ( value - 1 ); - dx2 = 0.0; - } - - dy1 = 0.5 * item->height(); - dy2 = -dy1; + transform.translate( dx, -item->y() + dy ); + transform.rotateRadians( radians, Qt::XAxis ); + transform.translate( -dx, -item->height() ); } else { - const auto h = item->parentItem() ? item->parentItem()->height() : item->height(); - - dx1 = 0.5 * item->width(); - dx2 = -dx1; - - if( isInverted() ) - { - dy1 = h * value - item->y() * ( 1 - value ); - radians = -M_PI_2 * ( value - 1 ); - dy2 = -h; - } - else - { - dy1 = ( item->height() + item->y() ) * ( 1 - value ); - radians = M_PI_2 * ( value - 1 ); - dy2 = 0.0; - } + transform.translate( dx, dy ); + transform.rotateRadians( radians - M_PI_2, Qt::XAxis ); + transform.translate( -dx, 0.0 ); } - - rotation->setPreTransform( dx1, dy1 ); - rotation->setRadians( radians ); - rotation->setPostTransform( dx2, dy2 ); } + + return transform; } void QskStackBoxAnimator4::done() @@ -681,7 +649,7 @@ void QskStackBoxAnimator4::done() { if ( auto item = itemAt( i ) ) { - delete qskFindRotationTransform( item ); + delete qskFindTransform< QuickTransform >( item ); item->setVisible( i == 1 ); } } diff --git a/src/layouts/QskStackBoxAnimator.h b/src/layouts/QskStackBoxAnimator.h index ce3b0131..880f2abc 100644 --- a/src/layouts/QskStackBoxAnimator.h +++ b/src/layouts/QskStackBoxAnimator.h @@ -13,6 +13,7 @@ class QskStackBox; class QQuickItem; +class QTransform; class QSK_EXPORT QskStackBoxAnimator : public QObject, public QskAnimator { @@ -137,6 +138,8 @@ class QSK_EXPORT QskStackBoxAnimator4 : public QskStackBoxAnimator void done() override; private: + QTransform transformation( const QQuickItem*, bool increasing, qreal value ) const; + Qt::Orientation m_orientation : 2; bool m_inverted : 1; }; diff --git a/src/layouts/QskSubcontrolLayoutEngine.cpp b/src/layouts/QskSubcontrolLayoutEngine.cpp index 5c7f1b55..d7227df7 100644 --- a/src/layouts/QskSubcontrolLayoutEngine.cpp +++ b/src/layouts/QskSubcontrolLayoutEngine.cpp @@ -11,6 +11,7 @@ #include "QskMargins.h" #include "QskTextOptions.h" +#include #include #include #include @@ -334,9 +335,9 @@ void QskSubcontrolLayoutEngine::setGraphicTextElements( const QskSkinnable* skin */ GraphicElement* graphicElement = nullptr; - if ( !graphicSize.isEmpty() && ( graphicSubControl != QskAspect::Control ) ) + if ( !graphicSize.isEmpty() && ( graphicSubControl != QskAspect::NoSubcontrol ) ) { - graphicElement = dynamic_cast< GraphicElement * >( element( graphicSubControl ) ); + graphicElement = dynamic_cast< GraphicElement* >( element( graphicSubControl ) ); if ( graphicElement == nullptr ) { graphicElement = new GraphicElement( skinnable, graphicSubControl ); @@ -347,7 +348,7 @@ void QskSubcontrolLayoutEngine::setGraphicTextElements( const QskSkinnable* skin } TextElement* textElement = nullptr; - if ( !text.isEmpty() && ( textSubcontrol != QskAspect::Control ) ) + if ( !text.isEmpty() && ( textSubcontrol != QskAspect::NoSubcontrol ) ) { textElement = dynamic_cast< TextElement* >( element( textSubcontrol ) ); if ( textElement == nullptr ) @@ -389,7 +390,7 @@ void QskSubcontrolLayoutEngine::setGraphicTextElements( const QskSkinnable* skin } else { - graphicElement->setSizePolicy( SP::Fixed, SP::Fixed ); + graphicElement->setSizePolicy( SP::Preferred, SP::Fixed ); textElement->setSizePolicy( SP::Preferred, SP::Constrained ); } @@ -407,6 +408,54 @@ void QskSubcontrolLayoutEngine::setGraphicTextElements( const QskSkinnable* skin } } +void QskSubcontrolLayoutEngine::setFixedContent( QskAspect::Subcontrol subcontrol, Qt::Orientation orientation, Qt::Alignment alignment ) +{ + if( auto* e = element( subcontrol ) ) + { + e->setSizePolicy( QskSizePolicy::Fixed, e->sizePolicy().verticalPolicy() ); + } + + Qt::Edges extraSpacing; + + switch( orientation ) + { + case Qt::Horizontal: + extraSpacing |= ( extraSpacingAt() & ( Qt::TopEdge | Qt::BottomEdge ) ); + + if( alignment & Qt::AlignLeft ) + { + extraSpacing |= Qt::RightEdge; + } + else if( alignment & Qt::AlignRight ) + { + extraSpacing |= Qt::LeftEdge; + } + else if( alignment & Qt::AlignHCenter ) + { + extraSpacing |= Qt::LeftEdge | Qt::RightEdge; + } + break; + case Qt::Vertical: + extraSpacing |= ( extraSpacingAt() & ( Qt::LeftEdge | Qt::RightEdge ) ); + + if( alignment & Qt::AlignTop ) + { + extraSpacing |= Qt::BottomEdge; + } + else if( alignment & Qt::AlignBottom ) + { + extraSpacing |= Qt::TopEdge; + } + else if( alignment & Qt::AlignVCenter ) + { + extraSpacing |= Qt::TopEdge | Qt::BottomEdge; + } + break; + } + + setExtraSpacingAt( extraSpacing ); +} + void QskSubcontrolLayoutEngine::addElement( LayoutElement* element ) { m_data->elements += element; @@ -445,7 +494,7 @@ QskSizePolicy QskSubcontrolLayoutEngine::sizePolicyAt( int index ) const int QskSubcontrolLayoutEngine::count() const { - return m_data->elements.count(); + return m_data->elements.count(); } void QskSubcontrolLayoutEngine::layoutItems() diff --git a/src/layouts/QskSubcontrolLayoutEngine.h b/src/layouts/QskSubcontrolLayoutEngine.h index f965024e..c4fa4cb0 100644 --- a/src/layouts/QskSubcontrolLayoutEngine.h +++ b/src/layouts/QskSubcontrolLayoutEngine.h @@ -63,7 +63,6 @@ class QskSubcontrolLayoutEngine : public QskLayoutEngine2D virtual QSizeF implicitSize( const QSizeF& ) const = 0; int m_stretch = -1; - bool m_ignored = false; QskSizePolicy m_sizePolicy; @@ -128,6 +127,8 @@ class QskSubcontrolLayoutEngine : public QskLayoutEngine2D QskAspect::Subcontrol, const QString& text, QskAspect::Subcontrol, const QSizeF& graphicSize ); + void setFixedContent( QskAspect::Subcontrol, Qt::Orientation, Qt::Alignment ); + QRectF subControlRect( QskAspect::Subcontrol ) const; private: diff --git a/src/nodes/QskArcNode.cpp b/src/nodes/QskArcNode.cpp index be8feb07..a905ffcf 100644 --- a/src/nodes/QskArcNode.cpp +++ b/src/nodes/QskArcNode.cpp @@ -49,9 +49,7 @@ QskHashValue QskArcNode::hash( const void* nodeData ) const const auto arcData = reinterpret_cast< const ArcData* >( nodeData ); auto h = arcData->metrics.hash(); - - for( const auto& stop : qAsConst( arcData->gradient.stops() ) ) - h = stop.hash( h ); + return arcData->gradient.hash( h ); return h; } diff --git a/src/nodes/QskArcRenderer.cpp b/src/nodes/QskArcRenderer.cpp index 0f89d7d6..12e262a6 100644 --- a/src/nodes/QskArcRenderer.cpp +++ b/src/nodes/QskArcRenderer.cpp @@ -6,44 +6,45 @@ #include "QskArcRenderer.h" #include "QskArcMetrics.h" #include "QskGradient.h" +#include "QskGradientDirection.h" #include #include void QskArcRenderer::renderArc(const QRectF& rect, - const QskArcMetrics& metrics, const QskGradient& gradient, - QPainter* painter ) + const QskArcMetrics& metrics, const QskGradient& gradient, QPainter* painter ) { - painter->setRenderHint( QPainter::Antialiasing, true ); + bool isRadial = false; - QGradientStops stops; - stops.reserve( gradient.stops().count() ); - - for( const QskGradientStop& stop : qAsConst( gradient.stops() ) ) - stops += QGradientStop( stop.position(), stop.color() ); - - /* - horizontal is interpreted as in direction of the arc, - while vertical means from the inner to the outer border - */ + if ( gradient.type() == QskGradient::Linear ) + { + /* + Horizontal is interpreted as conic ( in direction of the arc ), + while Vertical means radial ( inner to outer border ) + */ + isRadial = gradient.linearDirection().isVertical(); + } QBrush brush; - if( gradient.orientation() == QskGradient::Vertical ) - { - QRadialGradient gradient( rect.center(), qMin( rect.width(), rect.height() ) ); - gradient.setStops( stops ); + const auto qStops = qskToQGradientStops( gradient.stops() ); - brush = gradient; + if( isRadial ) + { + QRadialGradient radial( rect.center(), qMin( rect.width(), rect.height() ) ); + radial.setStops( qStops ); + + brush = radial; } else { - QConicalGradient gradient( rect.center(), metrics.startAngle() ); - gradient.setStops( stops ); + QConicalGradient conical( rect.center(), metrics.startAngle() ); + conical.setStops( qStops ); - brush = gradient; + brush = conical; } + painter->setRenderHint( QPainter::Antialiasing, true ); painter->setPen( QPen( brush, metrics.width(), Qt::SolidLine, Qt::FlatCap ) ); const int startAngle = qRound( metrics.startAngle() * 16 ); diff --git a/src/nodes/QskBoxBasicStroker.cpp b/src/nodes/QskBoxBasicStroker.cpp new file mode 100644 index 00000000..31bf15ec --- /dev/null +++ b/src/nodes/QskBoxBasicStroker.cpp @@ -0,0 +1,844 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskBoxBasicStroker.h" +#include "QskBoxColorMap.h" + +namespace +{ + inline int gradientLineCount( const QskGradient& borderGradient ) + { + // only the intermediate gradient lines ! + return qMax( 0, borderGradient.stepCount() - 1 ); + } + + inline void setGradientLineAt( + Qt::Orientation orientation, qreal x1, qreal y1, qreal x2, qreal y2, + const QskGradientStop& stop, QskVertex::ColoredLine* line ) + { + if ( orientation == Qt::Horizontal ) + { + const auto pos = x1 + stop.position() * ( x2 - x1 ); + line->setLine( pos, y1, pos, y2, stop.rgb() ); + } + else + { + const auto pos = y1 + stop.position() * ( y2 - y1 ); + line->setLine( x1, pos, x2, pos, stop.rgb() ); + } + } + + inline int edgeToIndex( Qt::Edge edge ) + { return qCountTrailingZeroBits( (quint8) edge ); } + + class CornerIterator : public QskVertex::ArcIterator + { + public: + inline CornerIterator( const QskBoxMetrics& metrics ) + : m_corners( metrics.corners ) + { + } + + inline void resetSteps( int corner, bool inverted = false ) + { + reset( m_corners[ corner ].stepCount, inverted ); + } + + inline void setBorderLine( int corner, QskVertex::Line* line ) const + { + const auto& c = m_corners[ corner ]; + + line->setLine( c.xInner( cos() ), c.yInner( sin() ), + c.xOuter( cos() ), c.yOuter( sin() ) ); + } + + protected: + const QskBoxMetrics::Corner* m_corners; + }; + + class CornerIteratorColor : public CornerIterator + { + public: + inline CornerIteratorColor( const QskBoxMetrics& metrics, + const QskBoxBorderColors& colors ) + : CornerIterator( metrics ) + , m_colors{ + { colors.top().rgbStart(), colors.left().rgbEnd() }, + { colors.top().rgbEnd(), colors.right().rgbStart() }, + { colors.bottom().rgbEnd(), colors.left().rgbStart() }, + { colors.bottom().rgbStart(), colors.right().rgbEnd() } } + { + } + + inline void setBorderLine( int corner, QskVertex::ColoredLine* line ) const + { + const auto& c = m_corners[ corner ]; + + line->setLine( c.xInner( cos() ), c.yInner( sin() ), + c.xOuter( cos() ), c.yOuter( sin() ), color( corner ) ); + } + + private: + inline QskVertex::Color color( int corner ) const + { + const auto& cs = m_colors[ corner ]; + + if ( cs.first == cs.second ) + return cs.first; + + const auto ratio = step() / qreal( m_corners[ corner ].stepCount ); + return cs.first.interpolatedTo( cs.second, ratio ); + } + + const QPair< QskVertex::Color, QskVertex::Color > m_colors[4]; + }; + + class LineMap + { + public: + inline LineMap( const QskBoxMetrics& metrics ) + : m_corners( metrics.corners ) + { + } + + inline void setHLine( int corner1, int corner2, + qreal cos, qreal sin, QskVertex::Line* line ) const + { + const qreal y = m_corners[ corner1 ].yInner( sin ); + + const qreal x1 = m_corners[ corner1 ].xInner( cos ); + const qreal x2 = m_corners[ corner2 ].xInner( cos ); + + line->setLine( x1, y, x2, y ); + } + + inline void setVLine( int corner1, int corner2, + qreal cos, qreal sin, QskVertex::Line* line ) const + { + const qreal x = m_corners[ corner1 ].xInner( cos ); + + const qreal y1 = m_corners[ corner1 ].yInner( sin ); + const qreal y2 = m_corners[ corner2 ].yInner( sin ); + + line->setLine( x, y1, x, y2 ); + } + + inline void setLine( int corner1, int corner2, + qreal cos, qreal sin, QskVertex::Line* line ) const + { + const qreal x1 = m_corners[ corner1 ].xInner( cos ); + const qreal x2 = m_corners[ corner2 ].xInner( cos ); + + const qreal y1 = m_corners[ corner1 ].yInner( sin ); + const qreal y2 = m_corners[ corner2 ].yInner( sin ); + + line->setLine( x1, y1, x2, y2 ); + } + + const QskBoxMetrics::Corner* m_corners; + }; + + class FillMap + { + public: + inline FillMap( const QskBoxMetrics& metrics, const QskBox::ColorMap& colorMap ) + : m_colorMap( colorMap ) + , m_corners( metrics.corners ) + { + } + + inline void setHLine( int corner1, int corner2, + qreal cos, qreal sin, QskVertex::ColoredLine* line ) const + { + const qreal y = m_corners[ corner1 ].yInner( sin ); + + const qreal x1 = m_corners[ corner1 ].xInner( cos ); + const qreal x2 = m_corners[ corner2 ].xInner( cos ); + + m_colorMap.setLine( x1, y, x2, y, line ); + } + + inline void setVLine( int corner1, int corner2, + qreal cos, qreal sin, QskVertex::ColoredLine* line ) const + { + const qreal x = m_corners[ corner1 ].xInner( cos ); + + const qreal y1 = m_corners[ corner1 ].yInner( sin ); + const qreal y2 = m_corners[ corner2 ].yInner( sin ); + + m_colorMap.setLine( x, y1, x, y2, line ); + } + + inline void setLine( int corner1, int corner2, + qreal cos, qreal sin, QskVertex::ColoredLine* line ) const + { + const qreal x1 = m_corners[ corner1 ].xInner( cos ); + const qreal x2 = m_corners[ corner2 ].xInner( cos ); + + const qreal y1 = m_corners[ corner1 ].yInner( sin ); + const qreal y2 = m_corners[ corner2 ].yInner( sin ); + + m_colorMap.setLine( x1, y1, x2, y2, line ); + } + + const QskBox::ColorMap& m_colorMap; + const QskBoxMetrics::Corner* m_corners; + }; +} + +static inline QskVertex::ColoredLine* qskAddGradientLines( + const QLineF& l1, const QLineF& l2, const QskGradient& gradient, + QskVertex::ColoredLine* lines ) +{ + const auto stops = gradient.stops(); + + if ( stops.first().position() > 0.0 ) + ( lines++ )->setLine( l1, stops.first().rgb() ); + + for( const auto& stop : stops ) + { + const auto p1 = l1.p1() + stop.position() * ( l2.p1() - l1.p1() ); + const auto p2 = l1.p2() + stop.position() * ( l2.p2() - l1.p2() ); + + ( lines++ )->setLine( p1, p2, stop.rgb() ); + } + + if ( stops.last().position() < 1.0 ) + ( lines++ )->setLine( l2, stops.last().rgb() ); + + return lines; +} + +inline void qskSetRectBorderLines( const QRectF& in, const QRectF& out, + const QskBoxBorderColors& colors, QskVertex::ColoredLine* lines ) +{ + const QLineF cl[4] = + { + { in.right(), in.bottom(), out.right(), out.bottom() }, + { in.left(), in.bottom(), out.left(), out.bottom() }, + { in.left(), in.top(), out.left(), out.top() }, + { in.right(), in.top(), out.right(), out.top() } + }; + + if ( colors.isMonochrome() ) + { + const QskVertex::Color c = colors.left().rgbStart(); + + lines[0].setLine( cl[0], c ); + lines[1].setLine( cl[1], c ); + lines[2].setLine( cl[2], c ); + lines[3].setLine( cl[3], c ); + lines[4] = lines[ 0 ]; + } + else + { + lines = qskAddGradientLines( cl[0], cl[1], colors.bottom(), lines ); + lines = qskAddGradientLines( cl[1], cl[2], colors.left(), lines ); + lines = qskAddGradientLines( cl[2], cl[3], colors.top(), lines ); + lines = qskAddGradientLines( cl[3], cl[0], colors.right(), lines ); + } +} + +template< class Line, class FillMap > +static inline void qskCreateFill( + const QskBoxMetrics& m_metrics, const FillMap& map, Line* lines ) +{ + using namespace QskVertex; + using namespace Qt; + + const auto cn = m_metrics.corners; + const bool isHorizontal = m_metrics.preferredOrientation == Qt::Horizontal; + + if ( !m_metrics.isInsideRounded ) + { + map.setHLine( TopLeftCorner, TopRightCorner, 0.0, 1.0, lines ); + map.setHLine( BottomLeftCorner, BottomRightCorner, 0.0, 1.0, lines + 1 ); + } + else if ( m_metrics.isOutsideSymmetric ) + { + const int stepCount = cn[ 0 ].stepCount; + + if ( isHorizontal ) + { + Line* l1 = lines + stepCount; + Line* l2 = lines + stepCount + 1; + + for ( ArcIterator it( stepCount ); !it.isDone(); ++it ) + { + map.setVLine( TopLeftCorner, BottomLeftCorner, it.cos(), it.sin(), l1++ ); + map.setVLine( TopRightCorner, BottomRightCorner, it.cos(), it.sin(), l2-- ); + } + } + else + { + Line* l1 = lines; + Line* l2 = lines + 2 * stepCount + 1; + + for ( ArcIterator it( stepCount ); !it.isDone(); ++it ) + { + map.setHLine( TopLeftCorner, TopRightCorner, it.cos(), it.sin(), l1++ ); + map.setHLine( BottomLeftCorner, BottomRightCorner, it.cos(), it.sin(), l2-- ); + } + } + } + else if ( m_metrics.stepSymmetries ) + { + auto line = lines; + + if ( isHorizontal ) + { + int stepCount = qMax( cn[TopLeftCorner].stepCount, cn[BottomLeftCorner].stepCount ); + + for ( ArcIterator it( stepCount, true ); !it.isDone(); ++it ) + map.setVLine( TopLeftCorner, BottomLeftCorner, it.cos(), it.sin(), line++ ); + + stepCount = qMax( cn[TopRightCorner].stepCount, cn[BottomRightCorner].stepCount ); + + for ( ArcIterator it( stepCount, false ); !it.isDone(); ++it ) + map.setVLine( TopRightCorner, BottomRightCorner, it.cos(), it.sin(), line++ ); + } + else + { + int stepCount = qMax( cn[TopLeftCorner].stepCount, cn[TopRightCorner].stepCount ); + + for ( ArcIterator it( stepCount, false ); !it.isDone(); ++it ) + map.setHLine( TopLeftCorner, TopRightCorner, it.cos(), it.sin(), line++ ); + + stepCount = qMax( cn[BottomLeftCorner].stepCount, cn[BottomRightCorner].stepCount ); + + for ( ArcIterator it( stepCount, true ); !it.isDone(); ++it ) + map.setHLine( BottomLeftCorner, BottomRightCorner, it.cos(), it.sin(), line++ ); + } + } + else + { + /* + This fallback code creates the same points. The cases above are + simply micro oprimization reducing the loops or calculations + to get there. + */ + + auto line = lines; + + if ( isHorizontal ) + { + int stepCount = qMax( cn[TopLeftCorner].stepCount, cn[BottomLeftCorner].stepCount ); + + for ( ArcIterator it( stepCount, true ); !it.isDone(); ++it ) + map.setLine( TopLeftCorner, BottomLeftCorner, it.cos(), it.sin(), line++ ); + + stepCount = qMax( cn[TopRightCorner].stepCount, cn[BottomRightCorner].stepCount ); + + for ( ArcIterator it( stepCount, false ); !it.isDone(); ++it ) + map.setLine( TopRightCorner, BottomRightCorner, it.cos(), it.sin(), line++ ); + } + else + { + int stepCount = qMax( cn[TopLeftCorner].stepCount, cn[TopRightCorner].stepCount ); + + for ( ArcIterator it( stepCount, false ); !it.isDone(); ++it ) + map.setLine( TopLeftCorner, TopRightCorner, it.cos(), it.sin(), line++ ); + + stepCount = qMax( cn[BottomLeftCorner].stepCount, cn[BottomRightCorner].stepCount ); + + for ( ArcIterator it( stepCount, true ); !it.isDone(); ++it ) + map.setLine( BottomLeftCorner, BottomRightCorner, it.cos(), it.sin(), line++ ); + } + } +} + +QskBoxBasicStroker::GeometryLayout::GeometryLayout( + const QskBoxMetrics& metrics, const QskBoxBorderColors& colors ) +{ + const struct + { + Qt::Corner corner; + Qt::Edge edge; + } order[4] = + { + // counter clockwise + { Qt::BottomRightCorner, Qt::RightEdge }, + { Qt::TopRightCorner, Qt::TopEdge }, + { Qt::TopLeftCorner, Qt::LeftEdge }, + { Qt::BottomLeftCorner, Qt::BottomEdge } + }; + + /* + In case of horizontal filling the lines end at right edge, + while for vertical filling it is the bottom edge. + */ + const int index0 = ( metrics.preferredOrientation == Qt::Horizontal ) ? 1 : 0; + + int pos = index0; + + for ( int i = 0; i < 4; i++ ) + { + const int idx = ( index0 + i ) % 4; + + const auto corner = order[ idx ].corner; + const auto edge = order[ idx ].edge; + + this->cornerOffsets[ corner ] = pos; + pos += metrics.corners[ corner ].stepCount + 1; + + this->edgeOffsets[ edgeToIndex( edge ) ] = pos; + pos += gradientLineCount( colors.gradientAt( edge ) ); + } + + if ( index0 == 0 ) + { + this->closingOffsets[ 0 ] = 0; + this->closingOffsets[ 1 ] = pos; + this->lineCount = pos + 1; + } + else + { + pos--; + + this->closingOffsets[ 0 ] = pos; + this->closingOffsets[ 1 ] = 0; + this->lineCount = pos + 1; + } +} + +QskBoxBasicStroker::QskBoxBasicStroker( const QskBoxMetrics& metrics ) + : m_metrics( metrics ) + , m_geometryLayout( metrics, QskBoxBorderColors() ) + , m_isColored( false ) +{ +} + +QskBoxBasicStroker::QskBoxBasicStroker( const QskBoxMetrics& metrics, + const QskBoxBorderColors& borderColors ) + : QskBoxBasicStroker( metrics, borderColors, QskBox::ColorMap() ) +{ +} + +QskBoxBasicStroker::QskBoxBasicStroker( const QskBoxMetrics& metrics, + const QskBoxBorderColors& borderColors, const QskBox::ColorMap& colorMap ) + : m_metrics( metrics ) + , m_borderColors( borderColors ) + , m_colorMap( colorMap ) + , m_geometryLayout( metrics, m_borderColors ) + , m_isColored( true ) +{ +} + +void QskBoxBasicStroker::setBorderGradientLines( + QskVertex::ColoredLine* lines ) const +{ + const auto off = m_geometryLayout.edgeOffsets; + + setBorderGradientLines( Qt::TopEdge, lines + off[0] ); + setBorderGradientLines( Qt::LeftEdge, lines + off[1] ); + setBorderGradientLines( Qt::RightEdge, lines + off[2] ); + setBorderGradientLines( Qt::BottomEdge, lines + off[3] ); +} + +void QskBoxBasicStroker::setBorderGradientLines( + Qt::Edge edge, QskVertex::ColoredLine* lines ) const +{ + const auto& gradient = m_borderColors.gradientAt( edge ); + if( gradient.stepCount() <= 1 ) + { + // everything done as contour lines + return; + } + + const auto cn = m_metrics.corners; + + qreal x1, x2, y1, y2; + Qt::Orientation orientation; + + switch( edge ) + { + case Qt::LeftEdge: + { + orientation = Qt::Vertical; + + x1 = m_metrics.innerRect.left(); + x2 = m_metrics.outerRect.left(); + y1 = cn[ Qt::BottomLeftCorner ].yInner( 0.0 ); + y2 = cn[ Qt::TopLeftCorner ].yInner( 0.0 ); + + break; + } + case Qt::TopEdge: + { + orientation = Qt::Horizontal; + + x1 = cn[ Qt::TopLeftCorner ].xInner( 0.0 ); + x2 = cn[ Qt::TopRightCorner ].xInner( 0.0 ); + y1 = m_metrics.innerRect.top(); + y2 = m_metrics.outerRect.top(); + + break; + } + case Qt::BottomEdge: + { + orientation = Qt::Horizontal; + + x1 = cn[ Qt::BottomRightCorner ].xInner( 0.0 ); + x2 = cn[ Qt::BottomLeftCorner ].xInner( 0.0 ); + y1 = m_metrics.innerRect.bottom(); + y2 = m_metrics.outerRect.bottom(); + + break; + } + case Qt::RightEdge: + { + orientation = Qt::Vertical; + + x1 = m_metrics.innerRect.right(); + x2 = m_metrics.outerRect.right(); + y1 = cn[ Qt::TopRightCorner ].yInner( 0.0 ); + y2 = cn[ Qt::BottomRightCorner ].yInner( 0.0 ); + + break; + } + } + + auto line = lines; + const auto& stops = gradient.stops(); + + if ( stops.last().position() < 1.0 ) + setGradientLineAt( orientation, x1, y1, x2, y2, stops.last(), line++ ); + + for( int i = stops.count() - 2; i >= 1; i-- ) + setGradientLineAt( orientation, x1, y1, x2, y2, stops[i], line++ ); + + if ( stops.first().position() > 0.0 ) + setGradientLineAt( orientation, x1, y1, x2, y2, stops.first(), line++ ); +} + +void QskBoxBasicStroker::setBorderLines( QskVertex::Line* lines ) const +{ + Q_ASSERT( !m_isColored ); + + if ( !m_metrics.isOutsideRounded ) + { + const auto& out = m_metrics.outerRect; + const auto& in = m_metrics.innerRect; + + lines[0].setLine( in.right(), in.bottom(), out.right(), out.bottom() ); + lines[1].setLine( in.left(), in.bottom(), out.left(), out.bottom() ); + lines[2].setLine( in.left(), in.top(), out.left(), out.top() ); + lines[3].setLine( in.right(), in.top(), out.right(), out.top() ); + lines[4] = lines[ 0 ]; + + return; + } + + const auto& gl = m_geometryLayout; + const auto cn = m_metrics.corners; + + auto linesTL = lines + gl.cornerOffsets[ Qt::TopLeftCorner ]; + + auto linesTR = lines + gl.cornerOffsets[ Qt::TopRightCorner ] + + cn[ Qt::TopRightCorner ].stepCount; + + auto linesBL = lines + gl.cornerOffsets[ Qt::BottomLeftCorner ] + + cn[ Qt::BottomLeftCorner ].stepCount; + + auto linesBR = lines + gl.cornerOffsets[ Qt::BottomRightCorner ]; + + CornerIterator it( m_metrics ); + + if ( m_metrics.isOutsideSymmetric && m_metrics.isInsideRounded ) + { + for ( it.resetSteps( Qt::TopLeftCorner ); !it.isDone(); ++it ) + { + it.setBorderLine( Qt::TopLeftCorner, linesTL++ ); + it.setBorderLine( Qt::TopRightCorner, linesTR-- ); + it.setBorderLine( Qt::BottomLeftCorner, linesBL-- ); + it.setBorderLine( Qt::BottomRightCorner, linesBR++ ); + } + } + else + { + for ( it.resetSteps( Qt::TopLeftCorner ); !it.isDone(); ++it ) + it.setBorderLine( Qt::TopLeftCorner, linesTL++ ); + + for ( it.resetSteps( Qt::TopRightCorner ); !it.isDone(); ++it ) + it.setBorderLine( Qt::TopRightCorner, linesTR-- ); + + for ( it.resetSteps( Qt::BottomLeftCorner ); !it.isDone(); ++it ) + it.setBorderLine( Qt::BottomLeftCorner, linesBL-- ); + + for ( it.resetSteps( Qt::BottomRightCorner ); !it.isDone(); ++it ) + it.setBorderLine( Qt::BottomRightCorner, linesBR++); + } + + lines[ gl.closingOffsets[ 1 ] ] = lines[ gl.closingOffsets[ 0 ] ]; +} + +void QskBoxBasicStroker::setBorderLines( QskVertex::ColoredLine* lines ) const +{ + Q_ASSERT( m_isColored ); + Q_ASSERT( lines ); + + if ( !m_metrics.isOutsideRounded ) + { + qskSetRectBorderLines( m_metrics.innerRect, + m_metrics.outerRect, m_borderColors, lines ); + return; + } + + const auto& gl = m_geometryLayout; + const auto cn = m_metrics.corners; + + auto linesTL = lines + gl.cornerOffsets[ Qt::TopLeftCorner ]; + + auto linesTR = lines + gl.cornerOffsets[ Qt::TopRightCorner ] + + cn[ Qt::TopRightCorner ].stepCount; + + auto linesBL = lines + gl.cornerOffsets[ Qt::BottomLeftCorner ] + + cn[ Qt::BottomLeftCorner ].stepCount; + + auto linesBR = lines + gl.cornerOffsets[ Qt::BottomRightCorner ]; + + CornerIteratorColor it( m_metrics, m_borderColors ); + + if ( m_metrics.isOutsideSymmetric && m_metrics.isInsideRounded ) + { + for ( it.resetSteps( Qt::TopLeftCorner ); !it.isDone(); ++it ) + { + it.setBorderLine( Qt::TopLeftCorner, linesTL++ ); + it.setBorderLine( Qt::TopRightCorner, linesTR-- ); + it.setBorderLine( Qt::BottomLeftCorner, linesBL-- ); + it.setBorderLine( Qt::BottomRightCorner, linesBR++ ); + } + } + else + { + for ( it.resetSteps( Qt::TopLeftCorner ); !it.isDone(); ++it ) + it.setBorderLine( Qt::TopLeftCorner, linesTL++ ); + + for ( it.resetSteps( Qt::TopRightCorner ); !it.isDone(); ++it ) + it.setBorderLine( Qt::TopRightCorner, linesTR-- ); + + for ( it.resetSteps( Qt::BottomLeftCorner ); !it.isDone(); ++it ) + it.setBorderLine( Qt::BottomLeftCorner, linesBL-- ); + + for ( it.resetSteps( Qt::BottomRightCorner ); !it.isDone(); ++it ) + it.setBorderLine( Qt::BottomRightCorner, linesBR++ ); + } + + setBorderGradientLines( lines ); + lines[ gl.closingOffsets[ 1 ] ] = lines[ gl.closingOffsets[ 0 ] ]; +} + +void QskBoxBasicStroker::setFillLines( QskVertex::Line* lines ) const +{ + Q_ASSERT( !m_isColored ); + Q_ASSERT( lines ); + + if ( m_metrics.isInsideRounded ) + { + const LineMap map( m_metrics ); + qskCreateFill( m_metrics, map, lines ); + } + else // a rectangle + { + const auto& in = m_metrics.innerRect; + + lines[0].setLine( in.left(), in.top(), in.right(), in.top() ); + lines[1].setLine( in.left(), in.bottom(), in.right(), in.bottom() ); + } +} + +void QskBoxBasicStroker::setFillLines( QskVertex::ColoredLine* lines ) const +{ + Q_ASSERT( m_isColored ); + Q_ASSERT( lines ); + Q_ASSERT( !m_colorMap.isTransparent() ); + + if ( m_metrics.isInsideRounded ) + { + const FillMap map( m_metrics, m_colorMap ); + qskCreateFill( m_metrics, map, lines ); + } + else // a rectangle + { + const auto& in = m_metrics.innerRect; + + m_colorMap.setLine( in.left(), in.top(), in.right(), in.top(), lines + 0 ); + m_colorMap.setLine( in.left(), in.bottom(), in.right(), in.bottom(), lines + 1 ); + } +} + +void QskBoxBasicStroker::setBoxLines( QskVertex::ColoredLine* borderLines, + QskVertex::ColoredLine* fillLines ) const +{ + Q_ASSERT( m_isColored ); + Q_ASSERT( borderLines || fillLines ); + Q_ASSERT( fillLines == nullptr || !m_colorMap.isTransparent() ); + + if ( m_metrics.isOutsideSymmetric && m_metrics.isInsideRounded ) + { + if ( borderLines && fillLines ) + { + /* + Doing all in one allows a slightly faster implementation. + As this is the by far most common situation we do this + micro optimization. + */ + setBorderAndFillLines( borderLines, fillLines ); + return; + } + } + + if ( borderLines ) + setBorderLines( borderLines ); + + if ( fillLines ) + setFillLines( fillLines ); +} + +void QskBoxBasicStroker::setBorderAndFillLines( + QskVertex::ColoredLine* borderLines, QskVertex::ColoredLine* fillLines ) const +{ + using namespace Qt; + + const auto& gl = m_geometryLayout; + + const FillMap fillMap( m_metrics, m_colorMap ); + CornerIteratorColor it( m_metrics, m_borderColors ); + + /* + It would be possible to run over [0, 0.5 * M_PI_2] + and create 8 values ( instead of 4 ) in each step. TODO ... + */ + + const auto stepCount = m_metrics.corners[0].stepCount; + + auto linesTL = borderLines + gl.cornerOffsets[ TopLeftCorner ]; + auto linesTR = borderLines + gl.cornerOffsets[ TopRightCorner ] + stepCount; + auto linesBL = borderLines + gl.cornerOffsets[ BottomLeftCorner ] + stepCount; + auto linesBR = borderLines + gl.cornerOffsets[ BottomRightCorner ]; + + if ( m_metrics.preferredOrientation == Qt::Horizontal ) + { + auto l1 = fillLines + stepCount; + auto l2 = fillLines + stepCount + 1; + + for ( it.resetSteps( TopLeftCorner ); !it.isDone(); ++it ) + { + it.setBorderLine( TopLeftCorner, linesTL++ ); + it.setBorderLine( TopRightCorner, linesTR-- ); + it.setBorderLine( BottomLeftCorner, linesBL-- ); + it.setBorderLine( BottomRightCorner, linesBR++ ); + + fillMap.setVLine( TopLeftCorner, BottomLeftCorner, it.cos(), it.sin(), l1-- ); + fillMap.setVLine( TopRightCorner, BottomRightCorner, it.cos(), it.sin(), l2++ ); + } + } + else + { + auto l1 = fillLines; + auto l2 = fillLines + 2 * stepCount + 1; + + for ( it.resetSteps( TopLeftCorner ); !it.isDone(); ++it ) + { + it.setBorderLine( TopLeftCorner, linesTL++ ); + it.setBorderLine( TopRightCorner, linesTR-- ); + it.setBorderLine( BottomLeftCorner, linesBL-- ); + it.setBorderLine( BottomRightCorner, linesBR++ ); + + fillMap.setHLine( TopLeftCorner, TopRightCorner, it.cos(), it.sin(), l1++ ); + fillMap.setHLine( BottomLeftCorner, BottomRightCorner, it.cos(), it.sin(), l2-- ); + } + } + + if ( borderLines ) + { + setBorderGradientLines( borderLines ); + borderLines[ gl.closingOffsets[ 1 ] ] = borderLines[ gl.closingOffsets[ 0 ] ]; + } +} + +int QskBoxBasicStroker::borderCount() const +{ + if ( !m_metrics.hasBorder ) + return 0; + + if ( m_isColored && !m_borderColors.isVisible() ) + return 0; + + int n = 0; + + if ( m_metrics.isOutsideRounded ) + { + /* + 4: Number of lines is always one more than the number of steps. + 1: extra line at the end to close the border path + */ + n = m_metrics.outerStepCount() + 4 + 1; + + if ( m_isColored && !m_borderColors.isMonochrome() ) + { + n += gradientLineCount( m_borderColors.left() ); + n += gradientLineCount( m_borderColors.top() ); + n += gradientLineCount( m_borderColors.right() ); + n += gradientLineCount( m_borderColors.bottom() ); + } + } + else + { + /* + 4: One for each corner + 1: extra line at the end to close the border path + */ + n = 4 + 1; + + if ( m_isColored && !m_borderColors.isMonochrome() ) + { + const int gradientLines = -1 + + m_borderColors.left().stepCount() + + m_borderColors.top().stepCount() + + m_borderColors.right().stepCount() + + m_borderColors.bottom().stepCount(); + + n += qMax( gradientLines, 0 ); + } + } + + return n; +} + +int QskBoxBasicStroker::fillCount() const +{ + if ( m_metrics.innerRect.isEmpty() ) + return 0; + + if ( m_isColored && m_colorMap.isTransparent() ) + return 0; + + int n = 2; + + if ( m_metrics.isInsideRounded ) + { + const auto c = m_metrics.corners; + + if ( m_metrics.preferredOrientation == Qt::Horizontal ) + { + n += qMax( c[ Qt::TopLeftCorner ].innerStepCount(), + c[ Qt::BottomLeftCorner ].innerStepCount() ); + + n += qMax( c[ Qt::TopRightCorner ].innerStepCount(), + c[ Qt::BottomRightCorner ].innerStepCount() ); + } + else + { + n += qMax( c[ Qt::TopLeftCorner ].innerStepCount(), + c[ Qt::TopRightCorner ].innerStepCount() ); + + n += qMax( c[ Qt::BottomLeftCorner ].innerStepCount(), + c[ Qt::BottomRightCorner ].innerStepCount() ); + } + } + + return n; +} diff --git a/src/nodes/QskBoxBasicStroker.h b/src/nodes/QskBoxBasicStroker.h new file mode 100644 index 00000000..dadb909e --- /dev/null +++ b/src/nodes/QskBoxBasicStroker.h @@ -0,0 +1,87 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_BOX_BASIC_STROKER_H +#define QSK_BOX_BASIC_STROKER_H + +#include "QskBoxMetrics.h" +#include "QskBoxBorderColors.h" +#include "QskBoxColorMap.h" + +class QskBoxShapeMetrics; +class QskBoxBorderMetrics; + +namespace QskVertex +{ + class Line; + class ColoredLine; +} + +class QskBoxBasicStroker +{ + public: + QskBoxBasicStroker( const QskBoxMetrics& ); + QskBoxBasicStroker( const QskBoxMetrics&, const QskBoxBorderColors& ); + QskBoxBasicStroker( const QskBoxMetrics&, + const QskBoxBorderColors&, const QskBox::ColorMap& ); + + int fillCount() const; + int borderCount() const; + + /* + QskVertex::Line ( = QSGGeometry::Point2D ) + + Needed for: + + - monochrome coloring ( QSGSimpleMaterial ) + - clipping ( QSGClipNode ) + - shaders getting the color information from a color ramp + ( = QskGradientMatrial ) + */ + + void setBorderLines( QskVertex::Line* ) const; + void setFillLines( QskVertex::Line* ) const; + + /* + QskVertex::ColoredLine ( = QSGGeometry::ColoredPoint2D ) + + The color information is added to the geometry what allows + using the same shader regardless of the colors, what ends + up in better scene graph batching + */ + + void setBorderLines( QskVertex::ColoredLine* ) const; + void setFillLines( QskVertex::ColoredLine* ) const; + + void setBoxLines( QskVertex::ColoredLine*, QskVertex::ColoredLine* ) const; + + private: + + class GeometryLayout + { + public: + GeometryLayout( const QskBoxMetrics&, const QskBoxBorderColors& ); + + int cornerOffsets[ 4 ]; + int edgeOffsets[ 4 ]; + + int closingOffsets[2]; + int lineCount; + }; + + void setBorderGradientLines( QskVertex::ColoredLine* ) const; + void setBorderGradientLines( Qt::Edge, QskVertex::ColoredLine* ) const; + + void setBorderAndFillLines( QskVertex::ColoredLine*, QskVertex::ColoredLine* ) const; + + const QskBoxMetrics& m_metrics; + const QskBoxBorderColors m_borderColors; + const QskBox::ColorMap m_colorMap; + const GeometryLayout m_geometryLayout; + + const bool m_isColored; +}; + +#endif diff --git a/src/nodes/QskBoxClipNode.cpp b/src/nodes/QskBoxClipNode.cpp index 40b288e0..be4fc518 100644 --- a/src/nodes/QskBoxClipNode.cpp +++ b/src/nodes/QskBoxClipNode.cpp @@ -49,7 +49,7 @@ void QskBoxClipNode::setBox( const QRectF& rect, else { setIsRectangular( false ); - QskBoxRenderer().renderFill( rect, shape, border, m_geometry ); + QskBox::renderFillGeometry( rect, shape, border, m_geometry ); } /* diff --git a/src/nodes/QskBoxColorMap.h b/src/nodes/QskBoxColorMap.h new file mode 100644 index 00000000..a5440131 --- /dev/null +++ b/src/nodes/QskBoxColorMap.h @@ -0,0 +1,224 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_BOX_RENDERER_COLOR_MAP_H +#define QSK_BOX_RENDERER_COLOR_MAP_H + +#include +#include +#include + +namespace QskBox +{ + class ColorMap + { + public: + inline ColorMap() + : ColorMap( QskGradient() ) + { + } + + inline ColorMap( const QskGradient& gradient ) + : m_isTransparent( !gradient.isVisible() ) + , m_isMonochrome( gradient.isMonochrome() ) + , m_color1( gradient.rgbStart() ) + , m_color2( gradient.rgbEnd() ) + { + if ( !m_isMonochrome ) + { + const auto dir = gradient.linearDirection(); + + m_x = dir.x1(); + m_y = dir.y1(); + m_dx = dir.x2() - dir.x1(); + m_dy = dir.y2() - dir.y1(); + m_dot = m_dx * m_dx + m_dy * m_dy; + } + } + + inline void setLine( qreal x1, qreal y1, qreal x2, qreal y2, + QskVertex::ColoredLine* line ) const + { + if ( m_isMonochrome ) + { + line->setLine( x1, y1, x2, y2, m_color1 ); + } + else + { + const auto c1 = colorAt( x1, y1 ); + const auto c2 = colorAt( x2, y2 ); + + line->setLine( x1, y1, c1, x2, y2, c2 ); + } + } + + inline bool isMonochrome() const { return m_isMonochrome; } + inline bool isTransparent() const { return m_isTransparent; } + + static inline bool isGradientSupported( + const QskGradient& gradient, const QRectF& rect ) + { + if ( gradient.isMonochrome() ) + return true; + + switch( gradient.stepCount() ) + { + case 0: + return true; + + case 1: + { + Q_ASSERT( gradient.stretchMode() != QskGradient::StretchToSize ); + return gradient.linearDirection().contains( rect ); + } + + default: + return false; + } + } + + private: + inline QskVertex::Color colorAt( qreal x, qreal y ) const + { + return m_color1.interpolatedTo( m_color2, valueAt( x, y ) ); + } + + inline qreal valueAt( qreal x, qreal y ) const + { + const qreal dx = x - m_x; + const qreal dy = y - m_y; + + return ( dx * m_dx + dy * m_dy ) / m_dot; + } + + const bool m_isTransparent; + const bool m_isMonochrome; + + qreal m_x, m_y, m_dx, m_dy, m_dot; + + const QskVertex::Color m_color1; + const QskVertex::Color m_color2; + }; + + class GradientIterator + { + public: + GradientIterator() = default; + + inline GradientIterator( const QskVertex::Color color ) + : m_color1( color ) + , m_color2( color ) + { + } + + inline GradientIterator( + const QskVertex::Color& color1, const QskVertex::Color& color2 ) + : m_color1( color1 ) + , m_color2( color2 ) + { + } + + inline GradientIterator( const QskGradientStops& stops ) + : m_stops( stops ) + , m_color1( stops.first().rgb() ) + , m_color2( m_color1 ) + , m_pos1( stops.first().position() ) + , m_pos2( m_pos1 ) + , m_index( 0 ) + { + } + + inline void reset( const QskVertex::Color color ) + { + m_index = -1; + m_color1 = m_color2 = color; + } + + inline void reset( const QskVertex::Color& color1, + const QskVertex::Color& color2 ) + { + m_index = -1; + m_color1 = color1; + m_color2 = color2; + } + + inline void reset( const QskGradientStops& stops ) + { + m_stops = stops; + + m_index = 0; + m_color1 = m_color2 = stops.first().rgb(); + m_pos1 = m_pos2 = stops.first().position(); + } + + inline qreal position() const + { + return m_pos2; + } + + inline QskVertex::Color color() const + { + return m_color2; + } + + inline QskVertex::Color colorAt( qreal pos ) const + { + if ( m_color1 == m_color2 ) + return m_color1; + + if ( m_index < 0 ) + { + return m_color1.interpolatedTo( m_color2, pos ); + } + else + { + if ( m_pos2 == m_pos1 ) + return m_color1; + + const auto r = ( pos - m_pos1 ) / ( m_pos2 - m_pos1 ); + return m_color1.interpolatedTo( m_color2, r ); + } + } + + inline bool advance() + { + if ( m_index < 0 ) + return true; + + m_pos1 = m_pos2; + m_color1 = m_color2; + + if ( ++m_index < m_stops.size() ) + { + const auto& s = m_stops[ m_index ]; + + m_pos2 = s.position(); + m_color2 = s.rgb(); + } + + return !isDone(); + } + + inline bool isDone() const + { + if ( m_index < 0 ) + return true; + + return m_index >= m_stops.size(); + } + + private: + QskGradientStops m_stops; + + QskVertex::Color m_color1, m_color2; + + qreal m_pos1 = 0.0; + qreal m_pos2 = 1.0; + + int m_index = -1; + }; +} + +#endif diff --git a/src/nodes/QskBoxFillNode.cpp b/src/nodes/QskBoxFillNode.cpp new file mode 100644 index 00000000..82d28dd7 --- /dev/null +++ b/src/nodes/QskBoxFillNode.cpp @@ -0,0 +1,139 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskBoxFillNode.h" +#include "QskGradientMaterial.h" +#include "QskGradient.h" +#include "QskGradientDirection.h" +#include "QskBoxShapeMetrics.h" +#include "QskBoxBorderMetrics.h" +#include "QskBoxRenderer.h" +#include "QskSGNode.h" + +#include + +QSK_QT_PRIVATE_BEGIN +#include +QSK_QT_PRIVATE_END + +static inline QskHashValue qskMetricsHash( + const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& borderMetrics ) +{ + QskHashValue hash = 13000; + + hash = shape.hash( hash ); + return borderMetrics.hash( hash ); +} + +class QskBoxFillNodePrivate final : public QSGGeometryNodePrivate +{ + public: + QskBoxFillNodePrivate() + : geometry( QSGGeometry::defaultAttributes_Point2D(), 0 ) + { + } + + inline void resetValues() + { + rect = QRectF(); + gradientHash = 0; + metricsHash = 0; + } + + QRectF rect; + + QskHashValue gradientHash = 0; + QskHashValue metricsHash = 0; + + QSGGeometry geometry; + int gradientType = -1; +}; + +QskBoxFillNode::QskBoxFillNode() + : QSGGeometryNode( *new QskBoxFillNodePrivate ) +{ + Q_D( QskBoxFillNode ); + + setGeometry( &d->geometry ); + setMaterial( new QSGFlatColorMaterial() ); + setFlag( QSGNode::OwnsMaterial, true ); +} + +void QskBoxFillNode::updateNode( const QRectF& rect, const QskGradient& gradient ) +{ + updateNode( rect, QskBoxShapeMetrics(), QskBoxBorderMetrics(), gradient ); +} + +void QskBoxFillNode::updateNode( + const QRectF& rect, const QskBoxShapeMetrics& shapeMetrics, + const QskBoxBorderMetrics& borderMetrics, const QskGradient& gradient ) +{ + Q_D( QskBoxFillNode ); + + if ( rect.isEmpty() || !gradient.isVisible() ) + { + d->resetValues(); + QskSGNode::resetGeometry( this ); + + return; + } + + const auto metricsHash = qskMetricsHash( shapeMetrics, borderMetrics ); + const auto gradientHash = gradient.hash( 22879 ); + + const bool dirtyColors = gradientHash != d->gradientHash; + const bool dirtyMetrics = ( metricsHash != d->metricsHash ) || ( rect != d->rect ); + + d->metricsHash = metricsHash; + d->gradientHash = gradientHash; + d->rect = rect; + + if ( dirtyMetrics ) + { + QskBox::renderFillGeometry( rect, shapeMetrics, borderMetrics, d->geometry ); + markDirty( QSGNode::DirtyGeometry ); + } + + if ( gradient.isMonochrome() ) + { + if ( dirtyColors ) + { + if ( material() == nullptr || d->gradientType >= 0 ) + { + setMaterial( new QSGFlatColorMaterial() ); + d->gradientType = -1; + } + + const auto color = gradient.startColor().toRgb(); + + auto mat = static_cast< QSGFlatColorMaterial* >( material() ); + if ( mat->color() != color ) + { + mat->setColor( color ); + markDirty( QSGNode::DirtyMaterial ); + } + } + } + else + { + // dirtyMetrics: the shader also depends on the target rectangle ! + + if ( dirtyColors || dirtyMetrics ) + { + const auto effectiveGradient = gradient.effectiveGradient(); + const auto gradientType = effectiveGradient.type(); + + if ( ( material() == nullptr ) || ( gradientType != d->gradientType ) ) + { + setMaterial( QskGradientMaterial::createMaterial( gradientType ) ); + d->gradientType = gradientType; + } + + auto mat = static_cast< QskGradientMaterial* >( material() ); + if ( mat->updateGradient( rect, effectiveGradient ) ) + markDirty( QSGNode::DirtyMaterial ); + } + } +} diff --git a/src/nodes/QskBoxFillNode.h b/src/nodes/QskBoxFillNode.h new file mode 100644 index 00000000..437662c2 --- /dev/null +++ b/src/nodes/QskBoxFillNode.h @@ -0,0 +1,33 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_BOX_FILL_NODE_H +#define QSK_BOX_FILL_NODE_H + +#include "QskGlobal.h" +#include + +class QskGradient; +class QskBoxShapeMetrics; +class QskBoxBorderMetrics; + +class QskBoxFillNodePrivate; + +class QSK_EXPORT QskBoxFillNode : public QSGGeometryNode +{ + public: + QskBoxFillNode(); + + void updateNode( const QRectF&, + const QskBoxShapeMetrics&, const QskBoxBorderMetrics&, + const QskGradient& ); + + void updateNode( const QRectF&, const QskGradient& ); + + private: + Q_DECLARE_PRIVATE( QskBoxFillNode ) +}; + +#endif diff --git a/src/nodes/QskBoxGradientStroker.cpp b/src/nodes/QskBoxGradientStroker.cpp new file mode 100644 index 00000000..b743a315 --- /dev/null +++ b/src/nodes/QskBoxGradientStroker.cpp @@ -0,0 +1,850 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskBoxGradientStroker.h" +#include "QskBoxBasicStroker.h" +#include "QskVertex.h" +#include "QskBoxColorMap.h" +#include "QskBoxMetrics.h" + +namespace +{ + using namespace QskVertex; + + class Value + { + public: + qreal from, to; // opposite to the direction of the gradient + qreal pos; // in direction of the gradient + }; + + class FillerHV + { + public: + FillerHV( const QskBoxMetrics& metrics ) + : m_metrics( metrics ) + { + } + + int setLines( const QskGradient& gradient, ColoredLine* lines ) + { + using namespace Qt; + + const auto corners = m_metrics.corners; + const auto dir = gradient.linearDirection(); + + m_isVertical = dir.isVertical(); + m_t0 = m_isVertical ? dir.y1() : dir.x1(); + m_dt = m_isVertical ? dir.dy() : dir.dx(); + + ColoredLine* l = lines; + Value v1, v2; + ArcIterator arcIt; + + m_gradientIterator.reset( gradient.stops() ); + + m_c1 = &corners[ Qt::TopLeftCorner ]; + m_c2 = &corners[ m_isVertical ? Qt::TopRightCorner : Qt::BottomLeftCorner ]; + m_c3 = ( m_c1->stepCount > m_c2->stepCount ) ? m_c1 : m_c2; + + arcIt.reset( m_c3->stepCount, !m_isVertical ); + + v2 = contourValue( arcIt ); + + skipGradientLines( v2.pos ); + setContourLine( v2, l++ ); + + arcIt.increment(); + + do + { + v1 = v2; + v2 = contourValue( arcIt ); + + l = setGradientLines( v1, v2, l ); + setContourLine( v2, l++ ); + + arcIt.increment(); + } while( !arcIt.isDone() ); + + m_c1 = &corners[ m_isVertical ? Qt::BottomLeftCorner : Qt::TopRightCorner ]; + m_c2 = &corners[ Qt::BottomRightCorner ]; + m_c3 = ( m_c1->stepCount > m_c2->stepCount ) ? m_c1 : m_c2; + + arcIt.reset( m_c3->stepCount, m_isVertical ); + + if ( contourValue( arcIt ).pos <= v2.pos ) + arcIt.increment(); // ellipse: opening/closing parts are connected + + do + { + v1 = v2; + v2 = contourValue( arcIt ); + + l = setGradientLines( v1, v2, l ); + setContourLine( v2, l++ ); + + arcIt.increment(); + + } while( !arcIt.isDone() ); + + return l - lines; + } + + inline void skipGradientLines( qreal pos ) + { + while ( !m_gradientIterator.isDone() ) + { + if ( m_t0 + m_gradientIterator.position() * m_dt > pos ) + return; + + m_gradientIterator.advance(); + } + } + + inline ColoredLine* setGradientLines( + const Value& v1, const Value& v2, ColoredLine* lines ) + { + while ( !m_gradientIterator.isDone() ) + { + const auto pos = m_t0 + m_gradientIterator.position() * m_dt; + + if ( pos > v2.pos || qFuzzyIsNull( v2.pos - pos ) ) + return lines; + + const auto color = m_gradientIterator.color(); + + const qreal f = ( pos - v1.pos ) / ( v2.pos - v1.pos ); + + const qreal t1 = v1.from + f * ( v2.from - v1.from ); + const qreal t2 = v1.to + f * ( v2.to - v1.to ); + + setLine( t1, t2, pos, color, lines++ ); + + m_gradientIterator.advance(); + } + + return lines; + } + + inline void setContourLine( const Value& v, ColoredLine* line ) + { + const auto color = m_gradientIterator.colorAt( ( v.pos - m_t0 ) / m_dt ); + setLine( v.from, v.to, v.pos, color, line ); + } + + private: + inline Value contourValue( const ArcIterator& arcIt ) const + { + const auto cos = arcIt.cos(); + const auto sin = arcIt.sin(); + + if ( m_isVertical ) + return { m_c1->xInner( cos ), m_c2->xInner( cos ), m_c3->yInner( sin ) }; + else + return { m_c1->yInner( sin ), m_c2->yInner( sin ), m_c3->xInner( cos ) }; + } + + inline void setLine( qreal from, qreal to, qreal pos, + Color color, ColoredLine* line ) + { + if ( m_isVertical ) + line->setLine( from, pos, to, pos, color ); + else + line->setLine( pos, from, pos, to, color ); + } + + const QskBoxMetrics& m_metrics; + + bool m_isVertical; + qreal m_t0, m_dt; + + const QskBoxMetrics::Corner* m_c1, * m_c2, * m_c3; + QskBox::GradientIterator m_gradientIterator; + }; +} + +namespace +{ + using namespace QskVertex; + + class Point + { + public: + Point() = default; + inline Point( qreal x, qreal y, qreal v ): x( x ), y( y ), v( v ) {}; + + qreal x = 0; + qreal y = 0; + qreal v = 0; // value at (x,y) according the gradient vector in the range [0,1] + }; + + /* + Iterating over the interpolating ( ~tangent ) lines of the outline in order + of increasing gradient values. We have the options to run clockwise or + counter-clockwise + */ + class TangentLineIterator + { + public: + TangentLineIterator( const QskBoxMetrics& metrics, + const QskLinearDirection& dir, bool isClockwise ) + : m_metrics( metrics ) + , m_dir( dir ) + , m_isClockwise( isClockwise ) + { + using namespace Qt; + + if ( dir.dx() >= 0.0 ) + { + if ( dir.dy() >= 0.0 ) + { + m_corners[0] = TopLeftCorner; + m_corners[1] = m_isClockwise ? TopRightCorner : BottomLeftCorner; + m_corners[2] = BottomRightCorner; + } + else + { + m_corners[0] = BottomLeftCorner; + m_corners[1] = m_isClockwise ? BottomRightCorner : TopLeftCorner; + m_corners[2] = TopRightCorner; + } + } + else + { + if ( dir.dy() >= 0.0 ) + { + m_corners[0] = TopRightCorner; + m_corners[1] = m_isClockwise ? TopLeftCorner : BottomRightCorner; + m_corners[2] = BottomLeftCorner; + } + else + { + m_corners[0] = BottomRightCorner; + m_corners[1] = m_isClockwise ? BottomLeftCorner : TopRightCorner; + m_corners[2] = TopLeftCorner; + } + } + } + + inline QPointF pointAt( qreal value ) const + { + const auto dv = m_p2.v - m_p1.v; + if ( dv == 0.0 ) + return QPointF( m_p1.x, m_p1.y ); + + const qreal r = ( value - m_p1.v ) / dv; + return QPointF( m_p1.x + r * ( m_p2.x - m_p1.x ), + m_p1.y + r * ( m_p2.y - m_p1.y ) ); + } + + void setup( const QskVertex::ArcIterator& arcIterator ) + { + m_arcIterator = arcIterator; + + const auto& c = m_metrics.corners[ m_corners[0] ]; + + const qreal x = c.xInner( m_arcIterator.cos() ); + const qreal y = c.yInner( m_arcIterator.sin() ); + + m_p2 = m_p1 = { x, y, m_dir.valueAt( x, y ) }; + } + + inline bool isDone() const { return m_isDone; } + + inline qreal value() const { return m_p2.v; } + inline Qt::Corner corner() const { return m_corners[ m_cornerIndex ]; } + + inline Point p1() const { return m_p1; } + inline Point p2() const { return m_p2; } + + void incrementTo( qreal value ) + { + while ( !isDone() && ( value > m_p2.v ) ) + advance(); + } + + inline void advance() + { + if ( m_isDone ) + return; + + m_arcIterator.increment(); + + auto c = &m_metrics.corners[ m_corners[ m_cornerIndex ] ]; + + if( m_arcIterator.isDone() ) + { + if ( m_cornerIndex < 2 ) + { + c = &m_metrics.corners[ m_corners[ ++m_cornerIndex ] ]; + m_arcIterator.reset( c->innerStepCount(), !m_arcIterator.isInverted() ); + } + } + + m_p1 = m_p2; + + m_p2.x = c->xInner( m_arcIterator.cos() ); + m_p2.y = c->yInner( m_arcIterator.sin() ); + + m_p2.v = m_dir.valueAt( m_p2.x, m_p2.y ); + + if ( m_cornerIndex == 2 ) + { + if ( ( m_p2.v < m_p1.v ) || m_arcIterator.isDone() ) + { + // passing the opposite position from our starting point + m_p2 = m_p1; + m_isDone = true; + } + } + } + + private: + const QskBoxMetrics& m_metrics; + const QskLinearDirection m_dir; + + const bool m_isClockwise; + bool m_isDone = false; + + QskVertex::ArcIterator m_arcIterator; + + Point m_p1, m_p2; + + int m_cornerIndex = 0; + Qt::Corner m_corners[3]; + }; + + /* + OutlineIterator iterates a pair of tangent lines in direction of the gradient vector. + The second tangent line increases in oppsite direction so that + the value of the leading point of the first tangent line is in between the values + of the opposite tangent line. + + Contour and gradient lines can then be found from cutting the tangent lines + with the perpendicular of the gradient vector. + */ + class OutlineIterator + { + public: + OutlineIterator( const QskBoxMetrics& metrics, + const QskLinearDirection& dir, bool clockwise ) + : m_isVertical( dir.dx() == 0.0 ) + , m_it1( metrics, dir, clockwise ) + , m_it2( metrics, dir, !clockwise ) + { + const auto corner = m_it1.corner(); + const auto& c = metrics.corners[ m_it1.corner() ]; + + bool inverted = clockwise; + if ( corner == Qt::TopLeftCorner || corner == Qt::BottomRightCorner ) + inverted = clockwise; + else + inverted = !clockwise; + + QskVertex::ArcIterator arcIt; + arcIt.reset( c.innerStepCount(), inverted ); + + if ( c.innerStepCount() == 1 ) + { + m_it1.setup( arcIt ); + m_it2.setup( arcIt.reverted() ); + + // not rounded + m_it1.advance(); + m_it2.advance(); + } + else + { + arcIt.reset( c.innerStepCount(), clockwise ); + + qreal v1 = dir.valueAt( c.xInner( arcIt.cos() ), c.yInner( arcIt.sin() ) ); + + do + { + arcIt.increment(); + + const qreal v2 = dir.valueAt( + c.xInner( arcIt.cos() ), c.yInner( arcIt.sin() ) ); + + if ( v2 > v1 ) + break; + + v1 = v2; + + } while( !arcIt.isDone() ); + + arcIt.decrement(); + + m_it1.setup( arcIt ); + m_it2.setup( arcIt.reverted() ); + } + + m_it2.incrementTo( m_it1.value() ); + } + + inline void advance() + { + m_it1.advance(); + + /* + Increasing the opposite iterator until its value becomes larger + than the value of the leading iterator. Then the opposite + point for the next line can be found by interpolating + between p1/p2 of the opposite iterator. + */ + + if ( !m_it1.isDone() ) + m_it2.incrementTo( m_it1.value() ); + } + + inline bool isDone() const { return m_it1.isDone(); } + inline qreal value() const { return m_it1.value(); } + + inline void setLineAt( qreal value, Color color, ColoredLine* line ) + { + const auto p1 = m_it1.pointAt( value ); + const auto p2 = m_it2.pointAt( value ); + + setLine( p1.x(), p1.y(), p2.x(), p2.y(), color, line ); + } + + inline void setLine( Color color, ColoredLine* line ) + { + const auto p1 = m_it1.p2(); + const auto p2 = m_it2.pointAt( p1.v ); + + setLine( p1.x, p1.y, p2.x(), p2.y(), color, line ); + } + + private: + inline void setLine( qreal x1, qreal y1, qreal x2, qreal y2, + Color color, ColoredLine* line ) + { + if ( m_isVertical ) + { + if ( x1 < x2 ) + line->setLine( x1, y1, x2, y2, color ); + else + line->setLine( x2, y2, x1, y1, color ); + } + else + { + if ( y1 < y2 ) + line->setLine( x1, y1, x2, y2, color ); + else + line->setLine( x2, y2, x1, y1, color ); + } + } + + /* + The first iterator for running along the left or right + half of the ellipse. The other one is for finding the + corresponing point at the other side. + */ + + const bool m_isVertical; + TangentLineIterator m_it1, m_it2; + }; + + /* + ContourIterator runs a pair of OutlineIterators, one clockwise the other + counter clockwise in direction of the gradient vector, so that we + always pass all corners. + + The next contour line can always be found from the outline iterator with the + larger gradient value. Gradient lines can then be found from interpolating + between the tangent lines of the outline iterators. + */ + class ContourIterator + { + public: + ContourIterator( + const QskBoxMetrics& metrics, const QskLinearDirection& dir ) + : m_left( metrics, dir, false ) + , m_right( metrics, dir, true ) + { + m_right.advance(); + m_next = &m_left; + } + + inline void setGradientLine( qreal value, Color color, ColoredLine* line ) + { + m_next->setLineAt( value, color, line ); + } + + inline void setContourLine( Color color, ColoredLine* line ) + { + m_next->setLine( color, line ); + } + + inline qreal value() const { return m_next->value(); } + + inline bool advance() + { + if ( qFuzzyIsNull( m_left.value() - m_right.value() ) ) + { + m_left.advance(); + m_right.advance(); + } + else + { + m_next->advance(); + } + + if ( m_next->isDone() ) + return false; + + m_next = ( m_left.value() < m_right.value() ) ? &m_left : &m_right; + + return true; + } + + private: + OutlineIterator m_left, m_right; + OutlineIterator* m_next; + }; + + class FillerD + { + public: + FillerD( const QskBoxMetrics& metrics ) + : m_metrics( metrics ) + { + } + + int setLines( const QskGradient& gradient, ColoredLine* lines ) + { + ContourIterator it( m_metrics, gradient.linearDirection() ); + QskBox::GradientIterator gradientIt( gradient.stops() ); + + ColoredLine* l = lines; + + // skip leading gradient lines + while ( !gradientIt.isDone() ) + { + const auto pos = gradientIt.position(); + if ( pos > it.value() || qFuzzyIsNull( pos - it.value() ) ) + break; + + gradientIt.advance(); + } + + do + { + // gradient lines + while ( !gradientIt.isDone() ) + { + const auto pos = gradientIt.position(); + if ( pos > it.value() || qFuzzyIsNull( pos - it.value() ) ) + break; + + it.setGradientLine( pos, gradientIt.color(), l++ ); + + gradientIt.advance(); + } + + // contour line + const auto color = gradientIt.colorAt( it.value() ); + it.setContourLine( color, l++ ); + + } while ( it.advance() ); + + return l - lines; + } + + private: + const QskBoxMetrics& m_metrics; + }; + + class FillerRect + { + public: + FillerRect( const QskBoxMetrics& metrics ) + : m_metrics( metrics ) + { + } + + int setLines( const QskGradient& gradient, ColoredLine* lines ) const + { + const qreal x1 = m_metrics.innerRect.left(); + const qreal x2 = m_metrics.innerRect.right(); + const qreal y1 = m_metrics.innerRect.top(); + const qreal y2 = m_metrics.innerRect.bottom(); + + QskBox::GradientIterator it( gradient.stops() ); + ColoredLine* l = lines; + + const auto dir = gradient.linearDirection(); + + if ( dir.isTilted() ) + { + const qreal m = dir.dy() / dir.dx(); + const auto vec = dir.vector(); + + struct { qreal x, y, value; } c1, c2, c3, c4; + + { + // corners sorted in order their values + c1 = { x1, y1, dir.valueAt( x1, y1 ) }; + c2 = { x2, y1, dir.valueAt( x2, y1 ) }; + c3 = { x1, y2, dir.valueAt( x1, y2 ) }; + c4 = { x2, y2, dir.valueAt( x2, y2 ) }; + + if ( m < 0.0 ) + { + qSwap( c1, c3 ); + qSwap( c2, c4 ); + } + + if ( c1.value > c4.value ) + qSwap( c1, c4 ); + + if ( c2.value > c3.value ) + qSwap( c2, c3 ); + } + + // skipping all gradient lines before the first corner + while ( !it.isDone() && ( it.position() <= c1.value ) ) + it.advance(); + + setLine( c1.x, c1.y, c1.x, c1.y, it.colorAt( c1.value ), l++ ); + + while ( !it.isDone() && ( it.position() < c2.value ) ) + { + const auto p = vec.pointAt( it.position() ); + + const qreal y1 = p.y() + ( p.x() - c1.x ) / m; + const qreal x2 = p.x() + ( p.y() - c1.y ) * m; + + setLine( c1.x, y1, x2, c1.y, it.color(), l++ ); + it.advance(); + } + + if ( c1.x == c3.x ) // cutting left/right edges + { + const auto dy = ( c2.x - c3.x ) / m; + + setLine( c2.x, c2.y, c3.x, c2.y + dy, it.colorAt( c2.value ), l++ ); + + while ( !it.isDone() && ( it.position() < c3.value ) ) + { + const auto p = vec.pointAt( it.position() ); + + const qreal y1 = p.y() + ( p.x() - c2.x ) / m; + const qreal y2 = p.y() + ( p.x() - c3.x ) / m; + + setLine( c2.x, y1, c3.x, y2, it.color(), l++ ); + it.advance(); + } + + setLine( c2.x, c3.y - dy, c3.x, c3.y, it.colorAt( c3.value ), l++ ); + } + else // cutting top/bottom edges + { + const qreal dx = ( c2.y - c3.y ) * m; + + setLine( c2.x, c2.y, c2.x + dx, c3.y, it.colorAt( c2.value ), l++ ); + + while ( !it.isDone() && ( it.position() < c3.value ) ) + { + const auto p = vec.pointAt( it.position() ); + + const qreal x1 = p.x() + ( p.y() - c2.y ) * m; + const qreal x2 = p.x() + ( p.y() - c3.y ) * m; + + setLine( x1, c2.y, x2, c3.y, it.color(), l++ ); + it.advance(); + } + + setLine( c3.x - dx, c2.y, c3.x, c3.y, it.colorAt( c3.value ), l++ ); + } + + while ( !it.isDone() && ( it.position() < c4.value ) ) + { + const auto p = vec.pointAt( it.position() ); + + const qreal y1 = p.y() + ( p.x() - c4.x ) / m; + const qreal x2 = p.x() + ( p.y() - c4.y ) * m; + + setLine( c4.x, y1, x2, c4.y, it.color(), l++ ); + it.advance(); + } + + setLine( c4.x, c4.y, c4.x, c4.y, it.colorAt( c4.value ), l++ ); + } + else if ( dir.isVertical() ) + { + Q_ASSERT( dir.dy() > 0.0 ); // normalized in QskBoxRenderer + + const qreal min = ( y1 - dir.y1() ) / dir.dy(); + const qreal max = ( y2 - dir.y1() ) / dir.dy(); + + while ( !it.isDone() && ( it.position() <= min ) ) + it.advance(); + + setHLine( y1, it.colorAt( min ), l++ ); + + while ( !it.isDone() && ( it.position() < max ) ) + { + const auto y = dir.y1() + it.position() * dir.dy(); + setHLine( y, it.color(), l++ ); + + it.advance(); + } + + setHLine( y2, it.colorAt( max ), l++ ); + } + else // dir.isHorizontal + { + Q_ASSERT( dir.dx() > 0.0 ); // normalized in QskBoxRenderer + + const qreal min = ( x1 - dir.x1() ) / dir.dx(); + const qreal max = ( x2 - dir.x1() ) / dir.dx(); + + while ( !it.isDone() && ( it.position() <= min ) ) + it.advance(); + + setVLine( x1, it.colorAt( min ), l++ ); + + while ( !it.isDone() && ( it.position() < max ) ) + { + const auto x = dir.x1() + it.position() * dir.dx(); + setVLine( x, it.color(), l++ ); + + it.advance(); + } + + setVLine( x2, it.colorAt( max ), l++ ); + } + + return l - lines; + } + + private: + + inline void setHLine( qreal y, + QskVertex::Color color, QskVertex::ColoredLine* line ) const + { + const auto& r = m_metrics.innerRect; + line->setLine( r.left(), y, r.right(), y, color ); + } + + inline void setVLine( qreal x, + QskVertex::Color color, QskVertex::ColoredLine* line ) const + { + const auto& r = m_metrics.innerRect; + line->setLine( x, r.top(), x, r.bottom(), color ); + } + + inline void setLine( qreal x1, qreal y1, qreal x2, qreal y2, + Color color, ColoredLine* line ) const + { + if ( x1 <= x2 ) + line->setLine( x1, y1, x2, y2, color ); + else + line->setLine( x2, y2, x1, y1, color ); + } + + const QskBoxMetrics& m_metrics; + }; +} + +QskBoxGradientStroker::QskBoxGradientStroker( + const QskBoxMetrics& metrics, const QskGradient& gradient ) + : m_metrics( metrics ) + , m_gradient( gradient ) + , m_dir( m_gradient.linearDirection() ) +{ + +} + +int QskBoxGradientStroker::lineCount() const +{ + if ( m_metrics.innerRect.isEmpty() || !m_gradient.isVisible() ) + return 0; + + int n = m_gradient.stepCount() - 1; + + if ( m_metrics.isInsideRounded ) + { + if ( m_metrics.stepSymmetries && !m_dir.isTilted() ) + { + const QskBoxBasicStroker stroker( m_metrics, QskBoxBorderColors(), m_gradient ); + n += stroker.fillCount(); + } + else + { + n += m_metrics.innerStepCount() + 4; + } + } + else + { + n += 2; + + if ( m_dir.isTilted() ) + n += 2; // contour lines for the opposite corners + } + + /* + The gradient starts and/or ends inside of the rectangle + and we have to add pad with extra gradient lines. + */ + if ( !m_dir.contains( m_metrics.innerRect ) ) + n += 2; + + return n; +} + +void QskBoxGradientStroker::setLines( int lineCount, QskVertex::ColoredLine* lines ) +{ + int effectiveCount; + + if ( m_metrics.isInsideRounded ) + { + if ( m_metrics.stepSymmetries && !m_dir.isTilted() ) + { + FillerHV filler( m_metrics ); + effectiveCount = filler.setLines( m_gradient, lines ); + } + else + { + FillerD filler( m_metrics ); + effectiveCount = filler.setLines( m_gradient, lines ); + } + } + else // rectangle + { + FillerRect filler( m_metrics ); + effectiveCount = filler.setLines( m_gradient, lines ); + } + +#if 0 + qDebug() << "expected:" << lineCount << ", got:" << effectiveCount; +#endif + + if ( lineCount > 0 ) + { + if ( effectiveCount > lineCount ) + { + qFatal( "geometry: allocated memory exceeded: %d vs. %d", + effectiveCount, lineCount ); + } + + if ( effectiveCount < lineCount ) + { + /* + Gradient or contour lines might be at the same position + and we end up with less lines, than expected. + As it is hard to precalculate all corner cases we allow a defensive + allocaton policy and simply fill up the line buffer with duplicates + of the last line. + */ + + for ( int i = effectiveCount; i < lineCount; i++ ) + lines[i] = lines[i-1]; + } + } +} diff --git a/src/nodes/QskBoxGradientStroker.h b/src/nodes/QskBoxGradientStroker.h new file mode 100644 index 00000000..e9241f63 --- /dev/null +++ b/src/nodes/QskBoxGradientStroker.h @@ -0,0 +1,34 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_BOX_GRADIENT_STROKER_H +#define QSK_BOX_GRADIENT_STROKER_H + +#include "QskGradient.h" +#include "QskGradientDirection.h" + +class QskBoxShapeMetrics; +class QskBoxBorderMetrics; +class QskBoxMetrics; + +namespace QskVertex { class ColoredLine; } + +class QskBoxGradientStroker +{ + public: + QskBoxGradientStroker( const QskBoxMetrics&, const QskGradient& ); + + int lineCount() const; + void setLines( int lineCount, QskVertex::ColoredLine* ); + + private: + void setFillLines( int lineCount, QskVertex::ColoredLine* ); + + const QskBoxMetrics& m_metrics; + const QskGradient m_gradient; + const QskLinearDirection m_dir; +}; + +#endif diff --git a/src/nodes/QskBoxMetrics.cpp b/src/nodes/QskBoxMetrics.cpp new file mode 100644 index 00000000..b4dd7109 --- /dev/null +++ b/src/nodes/QskBoxMetrics.cpp @@ -0,0 +1,208 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskBoxMetrics.h" +#include "QskBoxShapeMetrics.h" +#include "QskBoxBorderMetrics.h" +#include "QskVertex.h" +#include "QskFunctions.h" + +QskBoxMetrics::QskBoxMetrics( const QRectF& rect, + const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& border ) + : outerRect( rect ) +{ + isOutsideRounded = !shape.isRectangle(); + + if ( !isOutsideRounded ) + { + isInsideRounded = false; + isOutsideSymmetric = true; + stepSymmetries = Qt::Vertical | Qt::Horizontal; + preferredOrientation = Qt::Vertical; + + const auto bw = border.widths(); + hasBorder = bw.width() > 0.0 || bw.height() > 0.0; + + innerRect = qskValidOrEmptyInnerRect( rect, bw ); + + return; + } + + isOutsideSymmetric = shape.isRectellipse(); + + { + const auto tl = shape.topLeft(); + const auto tr = shape.topRight(); + const auto bl = shape.bottomLeft(); + const auto br = shape.bottomRight(); + + if ( tl.isEmpty() || tr.isEmpty() || ( tl.height() == tr.height() ) ) + { + if ( bl.isEmpty() || br.isEmpty() || ( bl.height() == br.height() ) ) + stepSymmetries |= Qt::Vertical; + } + + if ( tl.isEmpty() || bl.isEmpty() || ( tl.width() == bl.width() ) ) + { + if ( tr.isEmpty() || br.isEmpty() || ( tr.width() == br.width() ) ) + stepSymmetries |= Qt::Horizontal; + } + } + + for ( int i = 0; i < 4; i++ ) + { + auto& c = corners[ i ]; + + const auto radius = shape.radius( static_cast< Qt::Corner >( i ) ); + + c.radiusX = qBound( 0.0, radius.width(), 0.5 * outerRect.width() ); + c.radiusY = qBound( 0.0, radius.height(), 0.5 * outerRect.height() ); + c.stepCount = QskVertex::ArcIterator::segmentHint( qMax( c.radiusX, c.radiusY ) ); + + switch ( i ) + { + case Qt::TopLeftCorner: + c.centerX = outerRect.left() + c.radiusX; + c.centerY = outerRect.top() + c.radiusY; + c.sx = -1.0; + c.sy = -1.0; + break; + + case Qt::TopRightCorner: + c.centerX = outerRect.right() - c.radiusX; + c.centerY = outerRect.top() + c.radiusY; + c.sx = +1.0; + c.sy = -1.0; + break; + + case Qt::BottomLeftCorner: + c.centerX = outerRect.left() + c.radiusX; + c.centerY = outerRect.bottom() - c.radiusY; + c.sx = -1.0; + c.sy = +1.0; + break; + + case Qt::BottomRightCorner: + c.centerX = outerRect.right() - c.radiusX; + c.centerY = outerRect.bottom() - c.radiusY; + c.sx = +1.0; + c.sy = +1.0; + break; + } + } + + { + const auto cleft = qMax( corners[ Qt::TopLeftCorner ].centerX, + corners[ Qt::BottomLeftCorner ].centerX ); + + const auto cright = qMin( corners[ Qt::TopRightCorner ].centerX, + corners[ Qt::BottomRightCorner ].centerX ); + + const auto ctop = qMax( corners[ Qt::TopLeftCorner ].centerY, + corners[ Qt::TopRightCorner ].centerY ); + + const auto cbottom = qMin( corners[ Qt::BottomLeftCorner ].centerY, + corners[ Qt::BottomRightCorner ].centerY ); + + // now the bounding rectangle of the fill area + + const auto bw = border.widths(); + hasBorder = bw.width() > 0.0 || bw.height() > 0.0; + + qreal l = outerRect.left() + bw.left(); + qreal t = outerRect.top() + bw.top(); + qreal r = outerRect.right() - bw.right(); + qreal b = outerRect.bottom() - bw.bottom(); + + l = qMin( l, cright ); + r = qMax( r, cleft ); + t = qMin( t, cbottom ); + b = qMax( b, ctop ); + + if ( l > r ) + l = r = r + 0.5 * ( l - r ); + + if ( t > b ) + t = b = b + 0.5 * ( t - b ); + + innerRect.setCoords( l, t, r, b ); + } + + const QskMargins margins( + innerRect.left() - outerRect.left(), + innerRect.top() - outerRect.top(), + outerRect.right() - innerRect.right(), + outerRect.bottom() - innerRect.bottom() ); + + isBorderRegular = margins.isEquidistant(); + + isInsideRounded = false; + + for ( int i = 0; i < 4; i++ ) + { + auto& c = corners[ i ]; + + if ( c.sx < 0.0 ) + c.radiusInnerX = c.radiusX - margins.left(); + else + c.radiusInnerX = c.radiusX - margins.right(); + + if ( c.sy < 0.0 ) + c.radiusInnerY = c.radiusY - margins.top(); + else + c.radiusInnerY = c.radiusY - margins.bottom(); + + if ( c.radiusInnerX > 0.0 && c.radiusInnerY > 0.0 ) + { + c.centerInnerX = c.centerX; + c.centerInnerY = c.centerY; + + isInsideRounded = true; + } + else + { + /* + not enough space for a rounded border -> the inner side + becomes rectangular + */ + c.radiusInnerX = c.radiusInnerY = 0.0; + c.centerInnerX = ( c.sx < 0.0 ) ? innerRect.left() : innerRect.right(); + c.centerInnerY = ( c.sy < 0.0 ) ? innerRect.top() : innerRect.bottom(); + } + } + + if ( stepSymmetries == Qt::Horizontal ) + { + preferredOrientation = Qt::Horizontal; + } + else if ( stepSymmetries == Qt::Vertical ) + { + preferredOrientation = Qt::Vertical; + } + else + { + const auto tl = corners[ Qt::TopLeftCorner ].innerStepCount(); + const auto tr = corners[ Qt::TopRightCorner ].innerStepCount(); + const auto bl = corners[ Qt::BottomLeftCorner ].innerStepCount(); + const auto br = corners[ Qt::BottomRightCorner ].innerStepCount(); + + if ( qMax( tl, tr ) + qMax( bl, br ) >= qMax( tl, bl ) + qMax( tr, br ) ) + preferredOrientation = Qt::Vertical; + else + preferredOrientation = Qt::Horizontal; + } +} + +int QskBoxMetrics::outerStepCount() const +{ + return corners[0].stepCount + corners[1].stepCount + + corners[2].stepCount + corners[3].stepCount; +} + +int QskBoxMetrics::innerStepCount() const +{ + return corners[0].innerStepCount() + corners[1].innerStepCount() + + corners[2].innerStepCount() + corners[3].innerStepCount(); +} diff --git a/src/nodes/QskBoxMetrics.h b/src/nodes/QskBoxMetrics.h new file mode 100644 index 00000000..abf7c824 --- /dev/null +++ b/src/nodes/QskBoxMetrics.h @@ -0,0 +1,92 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_BOX_METRICS_H +#define QSK_BOX_METRICS_H + +#include +#include + +class QskBoxShapeMetrics; +class QskBoxBorderMetrics; + +class QskBoxMetrics +{ + public: + QskBoxMetrics( const QRectF&, + const QskBoxShapeMetrics&, const QskBoxBorderMetrics& ); + + const QRectF outerRect; + QRectF innerRect; + + int outerStepCount() const; + int innerStepCount() const; + + struct Corner + { + inline qreal xInner( qreal cos ) const + { return centerInnerX + sx * cos * radiusInnerX; } + + inline qreal yInner( qreal sin ) const + { return centerInnerY + sy * sin * radiusInnerY; } + + inline qreal xOuter( qreal cos ) const + { return centerX + sx * ( cos * radiusX ); } + + inline qreal yOuter( qreal sin ) const + { return centerY + sy * ( sin * radiusY ); } + + inline int innerStepCount() const + { return ( centerInnerX == 0.0 ) ? 1 : stepCount; } + + qreal centerX, centerY; + qreal radiusX, radiusY; + + qreal centerInnerX, centerInnerY; + qreal radiusInnerX, radiusInnerY; + + qreal sx, sy; + + int stepCount = 0; + + } corners[ 4 ]; + + bool hasBorder = false; + + // the same border width on all sides + bool isBorderRegular = false; + + // true for plain rectangles + bool isOutsideRounded = false; + /* + outer radii are symmetric in both directions ( rectellipse ) + However the inner radii might differ when isBorderRegular is false + */ + bool isOutsideSymmetric = false; + + /* + When the border width exceed the inner radius the corner + becomes rectangular at the inside. When this happens to + all corners the inner part dgenerates to a rectangle and + can be filled with a simpler algo. + */ + bool isInsideRounded = false; + + /* + stepSymmetries indicates the directions, where we can + iterate with the the same steps along opposing corners. + This is possible, when both corners have the same radius + or one of them has a radius of 0. + */ + Qt::Orientations stepSymmetries; + + /* + In case stepSymmetries indicates both directions the direction + that needs less steps is preferred. + */ + Qt::Orientation preferredOrientation; +}; + +#endif diff --git a/src/nodes/QskBoxNode.cpp b/src/nodes/QskBoxNode.cpp index bc052a67..818f694b 100644 --- a/src/nodes/QskBoxNode.cpp +++ b/src/nodes/QskBoxNode.cpp @@ -4,224 +4,108 @@ *****************************************************************************/ #include "QskBoxNode.h" -#include "QskBoxBorderColors.h" -#include "QskBoxBorderMetrics.h" +#include "QskBoxFillNode.h" +#include "QskBoxShadowNode.h" +#include "QskBoxRectangleNode.h" #include "QskBoxRenderer.h" -#include "QskBoxShapeMetrics.h" +#include "QskSGNode.h" + #include "QskGradient.h" +#include "QskGradientDirection.h" +#include "QskShadowMetrics.h" +#include "QskBoxBorderMetrics.h" +#include "QskBoxBorderColors.h" -#include -#include -#include - -QSK_QT_PRIVATE_BEGIN -#include -QSK_QT_PRIVATE_END - -Q_GLOBAL_STATIC( QSGVertexColorMaterial, qskMaterialVertex ) - -static inline QskHashValue qskMetricsHash( - const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& borderMetrics ) +namespace { - QskHashValue hash = 13000; - - hash = shape.hash( hash ); - return borderMetrics.hash( hash ); -} - -static inline QskHashValue qskColorsHash( - const QskBoxBorderColors& borderColors, const QskGradient& fillGradient ) -{ - QskHashValue hash = 13000; - hash = borderColors.hash( hash ); - return fillGradient.hash( hash ); -} - -class QskBoxNodePrivate final : public QSGGeometryNodePrivate -{ - public: - QskBoxNodePrivate() - : geometry( QSGGeometry::defaultAttributes_ColoredPoint2D(), 0 ) + enum Role { + ShadowRole, + BoxRole, + FillRole + }; +} + +void qskUpdateChildren( QSGNode* parentNode, quint8 role, QSGNode* node ) +{ + static const QVector< quint8 > roles = { ShadowRole, BoxRole, FillRole }; + + auto oldNode = QskSGNode::findChildNode( parentNode, role ); + QskSGNode::replaceChildNode( roles, role, parentNode, oldNode, node ); +} + +template< typename Node > +inline Node* qskNode( QSGNode* parentNode, quint8 role ) +{ + using namespace QskSGNode; + + auto node = static_cast< Node* > ( findChildNode( parentNode, role ) ); + + if ( node == nullptr ) + { + node = new Node(); + setNodeRole( node, role ); } - QskHashValue metricsHash = 0; - QskHashValue colorsHash = 0; - QRectF rect; - - QSGGeometry geometry; -}; + return node; +} QskBoxNode::QskBoxNode() - : QSGGeometryNode( *new QskBoxNodePrivate ) { - Q_D( QskBoxNode ); - - setMaterial( qskMaterialVertex ); - setGeometry( &d->geometry ); } QskBoxNode::~QskBoxNode() { - if ( material() != qskMaterialVertex ) - delete material(); } -void QskBoxNode::setBoxData( const QRectF& rect, const QskGradient& fillGradient ) -{ - setBoxData( rect, QskBoxShapeMetrics(), QskBoxBorderMetrics(), - QskBoxBorderColors(), fillGradient ); -} - -void QskBoxNode::setBoxData( const QRectF& rect, +void QskBoxNode::updateNode( const QRectF& rect, const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& borderMetrics, - const QskBoxBorderColors& borderColors, const QskGradient& gradient ) + const QskBoxBorderColors& borderColors, const QskGradient& gradient, + const QskShadowMetrics& shadowMetrics, const QColor& shadowColor ) { - Q_D( QskBoxNode ); + using namespace QskSGNode; - QskGradient fillGradient = gradient; -#if 1 - // Renderer is buggy for monochrome gradients with stops. TODO ... - if ( fillGradient.stops().count() > 2 && fillGradient.isMonochrome() ) + QskBoxShadowNode* shadowNode = nullptr; + QskBoxRectangleNode* rectNode = nullptr; + QskBoxFillNode* fillNode = nullptr; + + if ( !shadowMetrics.isNull() + && shadowColor.isValid() && shadowColor.alpha() != 0 ) { - fillGradient.setColor( fillGradient.startColor() ); - } -#endif - -#if 1 - const auto metricsHash = qskMetricsHash( shape, borderMetrics ); - const auto colorsHash = qskColorsHash( borderColors, fillGradient ); - - if ( ( metricsHash == d->metricsHash ) && - ( colorsHash == d->colorsHash ) && ( rect == d->rect ) ) - { - return; + shadowNode = qskNode< QskBoxShadowNode >( this, ShadowRole ); + shadowNode->setShadowData( shadowMetrics.shadowRect( rect ), + shape, shadowMetrics.blurRadius(), shadowColor ); } - d->metricsHash = metricsHash; - d->colorsHash = colorsHash; - d->rect = rect; - - markDirty( QSGNode::DirtyMaterial ); - markDirty( QSGNode::DirtyGeometry ); -#endif - - if ( rect.isEmpty() ) - { - d->geometry.allocate( 0 ); - return; - } - - bool hasFill = fillGradient.isValid(); - - bool hasBorder = !borderMetrics.isNull(); - if ( hasBorder ) - { - /* - Wrong as the border width should have an - effect - even if not being visible. TODO ... - */ - - hasBorder = borderColors.isVisible(); - } - - if ( !hasBorder && !hasFill ) - { - d->geometry.allocate( 0 ); - return; - } - - const bool isFillMonochrome = hasFill ? fillGradient.isMonochrome() : true; - const bool isBorderMonochrome = hasBorder ? borderColors.isMonochrome() : true; - - if ( hasFill && hasBorder ) - { - if ( isFillMonochrome && isBorderMonochrome ) - { - if ( borderColors.left().startColor() == fillGradient.startColor() ) - { - // we can draw border and background in one - hasBorder = false; - } - } - } - -#if 0 /* - Always using the same material result in a better batching - but wastes some memory. when we have a solid color. - Maybe its worth to introduce a flag to control the behaviour, - but for the moment we go with performance. + QskBoxRectangleNode supports vertical/horizontal and many tilted + linear gradients. If our gradient doesn't fall into this category + we use a QskBoxFillNode. + + However the border is always done with a QskBoxRectangleNode */ - bool maybeFlat = true; - - if ( maybeFlat ) + if ( QskBox::isGradientSupported( shape, gradient ) ) { - if ( ( hasFill && hasBorder ) || - ( hasFill && !isFillMonochrome ) || - ( hasBorder && !isBorderMonochrome ) ) - { - maybeFlat = false; - } - } -#else - bool maybeFlat = false; -#endif - - QskBoxRenderer renderer; - - if ( !maybeFlat ) - { - setMonochrome( false ); - - renderer.renderBox( d->rect, shape, borderMetrics, - borderColors, fillGradient, *geometry() ); + rectNode = qskNode< QskBoxRectangleNode >( this, BoxRole ); + rectNode->updateNode( rect, shape, borderMetrics, borderColors, gradient ); } else { - // all is done with one color - setMonochrome( true ); - - auto* flatMaterial = static_cast< QSGFlatColorMaterial* >( material() ); - - if ( hasFill ) + if ( !borderMetrics.isNull() && borderColors.isVisible() ) { - flatMaterial->setColor( fillGradient.startColor() ); - renderer.renderFill( d->rect, shape, QskBoxBorderMetrics(), *geometry() ); + rectNode = qskNode< QskBoxRectangleNode >( this, BoxRole ); + rectNode->updateNode( rect, shape, borderMetrics, borderColors, QskGradient() ); } - else + + if ( gradient.isVisible() ) { - flatMaterial->setColor( borderColors.left().startColor().rgba() ); - renderer.renderBorder( d->rect, shape, borderMetrics, *geometry() ); + fillNode = qskNode< QskBoxFillNode >( this, FillRole ); + fillNode->updateNode( rect, shape, borderMetrics, gradient ); } } -} - -void QskBoxNode::setMonochrome( bool on ) -{ - const auto material = this->material(); - - if ( on == ( material != qskMaterialVertex ) ) - return; - - Q_D( QskBoxNode ); - - d->geometry.allocate( 0 ); - - if ( on ) - { - setMaterial( new QSGFlatColorMaterial() ); - - const QSGGeometry g( QSGGeometry::defaultAttributes_Point2D(), 0 ); - memcpy( ( void* ) &d->geometry, ( void* ) &g, sizeof( QSGGeometry ) ); - } - else - { - setMaterial( qskMaterialVertex ); - delete material; - - const QSGGeometry g( QSGGeometry::defaultAttributes_ColoredPoint2D(), 0 ); - memcpy( ( void* ) &d->geometry, ( void* ) &g, sizeof( QSGGeometry ) ); - } + + qskUpdateChildren( this, ShadowRole, shadowNode ); + qskUpdateChildren( this, BoxRole, rectNode ); + qskUpdateChildren( this, FillRole, fillNode ); } diff --git a/src/nodes/QskBoxNode.h b/src/nodes/QskBoxNode.h index 5aa9bab4..05e53910 100644 --- a/src/nodes/QskBoxNode.h +++ b/src/nodes/QskBoxNode.h @@ -9,30 +9,24 @@ #include "QskGlobal.h" #include +class QskShadowMetrics; class QskBoxShapeMetrics; class QskBoxBorderMetrics; class QskBoxBorderColors; class QskGradient; +class QskShadowMetrics; +class QColor; -class QskBoxNodePrivate; - -class QSK_EXPORT QskBoxNode : public QSGGeometryNode +class QSK_EXPORT QskBoxNode : public QSGNode { public: QskBoxNode(); ~QskBoxNode() override; - void setBoxData( const QRectF&, + void updateNode( const QRectF&, const QskBoxShapeMetrics&, const QskBoxBorderMetrics&, - const QskBoxBorderColors&, const QskGradient& ); - - void setBoxData( const QRectF& rect, const QskGradient& ); - - private: - void setMonochrome( bool on ); - - Q_DECLARE_PRIVATE( QskBoxNode ) - + const QskBoxBorderColors&, const QskGradient&, + const QskShadowMetrics&, const QColor& shadowColor ); }; #endif diff --git a/src/nodes/QskBoxRectangleNode.cpp b/src/nodes/QskBoxRectangleNode.cpp new file mode 100644 index 00000000..5ef2c19e --- /dev/null +++ b/src/nodes/QskBoxRectangleNode.cpp @@ -0,0 +1,248 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskBoxRectangleNode.h" +#include "QskBoxBorderColors.h" +#include "QskBoxBorderMetrics.h" +#include "QskBoxRenderer.h" +#include "QskBoxShapeMetrics.h" +#include "QskGradient.h" +#include "QskGradientDirection.h" + +#include +#include +#include + +QSK_QT_PRIVATE_BEGIN +#include +QSK_QT_PRIVATE_END + +Q_GLOBAL_STATIC( QSGVertexColorMaterial, qskMaterialColorVertex ) + +static inline QskHashValue qskMetricsHash( + const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& borderMetrics ) +{ + QskHashValue hash = 13000; + + hash = shape.hash( hash ); + return borderMetrics.hash( hash ); +} + +static inline QskHashValue qskColorsHash( + const QskBoxBorderColors& borderColors, const QskGradient& fillGradient ) +{ + QskHashValue hash = 13000; + hash = borderColors.hash( hash ); + return fillGradient.hash( hash ); +} + +#if 1 + +static inline QskGradient qskEffectiveGradient( const QskGradient& gradient ) +{ + auto g = gradient.effectiveGradient(); + + if ( g.type() != QskGradient::Linear ) + { + qWarning() << "QskBoxRectangleNode does not support radial/conic gradients"; + g.setDirection( QskGradient::Linear ); + } + + return g; +} + +#endif + +class QskBoxRectangleNodePrivate final : public QSGGeometryNodePrivate +{ + public: + QskBoxRectangleNodePrivate() + : geometry( QSGGeometry::defaultAttributes_ColoredPoint2D(), 0 ) + { + } + + QskHashValue metricsHash = 0; + QskHashValue colorsHash = 0; + QRectF rect; + + QSGGeometry geometry; +}; + +QskBoxRectangleNode::QskBoxRectangleNode() + : QSGGeometryNode( *new QskBoxRectangleNodePrivate ) +{ + Q_D( QskBoxRectangleNode ); + + setMaterial( qskMaterialColorVertex ); + setGeometry( &d->geometry ); +} + +QskBoxRectangleNode::~QskBoxRectangleNode() +{ + if ( material() != qskMaterialColorVertex ) + delete material(); +} + +void QskBoxRectangleNode::updateNode( + const QRectF& rect, const QskGradient& fillGradient ) +{ + updateNode( rect, QskBoxShapeMetrics(), QskBoxBorderMetrics(), + QskBoxBorderColors(), fillGradient ); +} + +void QskBoxRectangleNode::updateNode( const QRectF& rect, + const QskBoxShapeMetrics& shape, const QskGradient& fillGradient ) +{ + updateNode( rect, shape, QskBoxBorderMetrics(), + QskBoxBorderColors(), fillGradient ); +} + +void QskBoxRectangleNode::updateNode( const QRectF& rect, + const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& borderMetrics, + const QskBoxBorderColors& borderColors, const QskGradient& gradient ) +{ + Q_D( QskBoxRectangleNode ); + + /* + We supports certain linear gradients only. + Everything else has to be done using QskRectangleNode + */ + const auto fillGradient = qskEffectiveGradient( gradient ); + + const auto metricsHash = qskMetricsHash( shape, borderMetrics ); + const auto colorsHash = qskColorsHash( borderColors, fillGradient ); + + if ( ( metricsHash == d->metricsHash ) && + ( colorsHash == d->colorsHash ) && ( rect == d->rect ) ) + { + return; + } + + d->metricsHash = metricsHash; + d->colorsHash = colorsHash; + d->rect = rect; + + markDirty( QSGNode::DirtyMaterial ); + markDirty( QSGNode::DirtyGeometry ); + + if ( rect.isEmpty() ) + { + d->geometry.allocate( 0 ); + return; + } + + bool hasFill = fillGradient.isVisible(); + + bool hasBorder = !borderMetrics.isNull(); + if ( hasBorder ) + { + /* + Wrong as the border width should have an + effect - even if not being visible. TODO ... + */ + + hasBorder = borderColors.isVisible(); + } + + if ( !hasBorder && !hasFill ) + { + d->geometry.allocate( 0 ); + return; + } + + const bool isFillMonochrome = hasFill ? fillGradient.isMonochrome() : true; + const bool isBorderMonochrome = hasBorder ? borderColors.isMonochrome() : true; + + if ( hasFill && hasBorder ) + { + if ( isFillMonochrome && isBorderMonochrome ) + { + if ( borderColors.left().rgbStart() == fillGradient.rgbStart() ) + { + // we can draw border and background in one + hasBorder = false; + } + } + } + +#if 0 + /* + Always using the same material result in a better batching + but wastes some memory. when we have a solid color. + Maybe its worth to introduce a flag to control the behaviour, + but for the moment we go with performance. + */ + + bool maybeFlat = true; + + if ( maybeFlat ) + { + if ( ( hasFill && hasBorder ) || + ( hasFill && !isFillMonochrome ) || + ( hasBorder && !isBorderMonochrome ) ) + { + maybeFlat = false; + } + } +#else + bool maybeFlat = false; +#endif + + if ( !maybeFlat ) + { + setMonochrome( false ); + + QskBox::renderBox( d->rect, shape, borderMetrics, + borderColors, fillGradient, *geometry() ); + } + else + { + // all is done with one color + setMonochrome( true ); + + auto* flatMaterial = static_cast< QSGFlatColorMaterial* >( material() ); + + if ( hasFill ) + { + flatMaterial->setColor( fillGradient.rgbStart() ); + QskBox::renderFillGeometry( + d->rect, shape, QskBoxBorderMetrics(), *geometry() ); + } + else + { + flatMaterial->setColor( borderColors.left().rgbStart() ); + QskBox::renderBorderGeometry( + d->rect, shape, borderMetrics, *geometry() ); + } + } +} + +void QskBoxRectangleNode::setMonochrome( bool on ) +{ + const auto material = this->material(); + + if ( on == ( material != qskMaterialColorVertex ) ) + return; + + Q_D( QskBoxRectangleNode ); + + d->geometry.allocate( 0 ); + + if ( on ) + { + setMaterial( new QSGFlatColorMaterial() ); + + const QSGGeometry g( QSGGeometry::defaultAttributes_Point2D(), 0 ); + memcpy( ( void* ) &d->geometry, ( void* ) &g, sizeof( QSGGeometry ) ); + } + else + { + setMaterial( qskMaterialColorVertex ); + delete material; + + const QSGGeometry g( QSGGeometry::defaultAttributes_ColoredPoint2D(), 0 ); + memcpy( ( void* ) &d->geometry, ( void* ) &g, sizeof( QSGGeometry ) ); + } +} diff --git a/src/nodes/QskBoxRectangleNode.h b/src/nodes/QskBoxRectangleNode.h new file mode 100644 index 00000000..aff4d6ed --- /dev/null +++ b/src/nodes/QskBoxRectangleNode.h @@ -0,0 +1,40 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_BOX_RECTANGLE_NODE_H +#define QSK_BOX_RECTANGLE_NODE_H + +#include "QskGlobal.h" +#include + +class QskBoxShapeMetrics; +class QskBoxBorderMetrics; +class QskBoxBorderColors; +class QskGradient; + +class QskBoxRectangleNodePrivate; + +class QSK_EXPORT QskBoxRectangleNode : public QSGGeometryNode +{ + public: + QskBoxRectangleNode(); + ~QskBoxRectangleNode() override; + + void updateNode( const QRectF&, + const QskBoxShapeMetrics&, const QskBoxBorderMetrics&, + const QskBoxBorderColors&, const QskGradient& ); + + void updateNode( const QRectF& rect, const QskGradient& ); + + void updateNode( const QRectF& rect, + const QskBoxShapeMetrics&, const QskGradient& ); + + private: + void setMonochrome( bool on ); + + Q_DECLARE_PRIVATE( QskBoxRectangleNode ) +}; + +#endif diff --git a/src/nodes/QskBoxRenderer.cpp b/src/nodes/QskBoxRenderer.cpp new file mode 100644 index 00000000..ce5083aa --- /dev/null +++ b/src/nodes/QskBoxRenderer.cpp @@ -0,0 +1,216 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskBoxRenderer.h" +#include "QskBoxShapeMetrics.h" +#include "QskBoxBorderMetrics.h" +#include "QskBoxBorderColors.h" +#include "QskBoxMetrics.h" +#include "QskBoxBasicStroker.h" +#include "QskBoxGradientStroker.h" + +#include "QskGradient.h" +#include "QskGradientDirection.h" +#include "QskFunctions.h" + +#include + +static inline QskVertex::Line* qskAllocateLines( + QSGGeometry& geometry, int lineCount ) +{ + geometry.allocate( 2 * lineCount ); // 2 points per line + return reinterpret_cast< QskVertex::Line* >( geometry.vertexData() ); +} + +static inline QskVertex::ColoredLine* qskAllocateColoredLines( + QSGGeometry& geometry, int lineCount ) +{ + geometry.allocate( 2 * lineCount ); // 2 points per line + return reinterpret_cast< QskVertex::ColoredLine* >( geometry.vertexData() ); +} + +static inline QskGradient qskEffectiveGradient( + const QRectF& rect, const QskGradient& gradient ) +{ + const auto dir = gradient.linearDirection(); + + auto g = gradient; + + if ( !dir.isTilted() ) + { + /* + Dealing with inverted gradient vectors makes the code even + more unreadable. So we simply invert stops/vector instead. + */ + if ( ( dir.x1() > dir.x2() ) || ( dir.y1() > dir.y2() ) ) + { + g.setLinearDirection( dir.x2(), dir.y2(), dir.x1(), dir.y1() ); + + if ( !g.isMonochrome() ) + g.setStops( qskRevertedGradientStops( gradient.stops() ) ); + } + } + + if ( g.stretchMode() == QskGradient::StretchToSize ) + g.stretchTo( rect ); + + return g; +} + +static inline bool qskMaybeSpreading( const QskGradient& gradient ) +{ + if ( gradient.stretchMode() == QskGradient::StretchToSize ) + return !gradient.linearDirection().contains( QRectF( 0, 0, 1, 1 ) ); + + return true; +} + +bool QskBox::isGradientSupported( + const QskBoxShapeMetrics&, const QskGradient& gradient ) +{ + if ( !gradient.isVisible() || gradient.isMonochrome() ) + return true; + + switch( gradient.type() ) + { + case QskGradient::Stops: + { + // will be rendered as vertical linear gradient + return true; + } + case QskGradient::Linear: + { + if ( ( gradient.spreadMode() != QskGradient::PadSpread ) + && qskMaybeSpreading( gradient ) ) + { + // shouldn't be hard to implement the other spreadModes TODO ... + return false; + } + + return true; + } + + default: + { + /* + At least Conical gradients could be implemented easily TODO.. + + For the moment Radial/Conical gradients have to be done + with QskGradientMaterial + */ + return false; + } + } + + return false; +} + +void QskBox::renderBorderGeometry( + const QRectF& rect, const QskBoxShapeMetrics& shape, + const QskBoxBorderMetrics& border, QSGGeometry& geometry ) +{ + geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); + + const QskBoxMetrics metrics( rect, shape, border ); + const QskBoxBasicStroker stroker( metrics ); + + const auto lines = qskAllocateLines( geometry, stroker.borderCount() ); + if ( lines ) + stroker.setBorderLines( lines ); +} + +void QskBox::renderFillGeometry( + const QRectF& rect, const QskBoxShapeMetrics& shape, QSGGeometry& geometry ) +{ + renderFillGeometry( rect, shape, QskBoxBorderMetrics(), geometry ); +} + +void QskBox::renderFillGeometry( + const QRectF& rect, const QskBoxShapeMetrics& shape, + const QskBoxBorderMetrics& border, QSGGeometry& geometry ) +{ + geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); + + const QskBoxMetrics metrics( rect, shape, border ); + QskBoxBasicStroker stroker( metrics ); + + if ( auto lines = qskAllocateLines( geometry, stroker.fillCount() ) ) + stroker.setFillLines( lines ); +} + +void QskBox::renderBox( const QRectF& rect, + const QskBoxShapeMetrics& shape, const QskGradient& gradient, + QSGGeometry& geometry ) +{ + renderBox( rect, shape, QskBoxBorderMetrics(), + QskBoxBorderColors(), gradient, geometry ); +} + +void QskBox::renderBox( const QRectF& rect, + const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& border, + const QskBoxBorderColors& borderColors, const QskGradient& gradient, + QSGGeometry& geometry ) +{ + geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); + + const QskBoxMetrics metrics( rect, shape, border ); + const auto effectiveGradient = qskEffectiveGradient( metrics.innerRect, gradient ); + + if ( metrics.innerRect.isEmpty() || + QskBox::ColorMap::isGradientSupported( effectiveGradient, metrics.innerRect ) ) + { + /* + The gradient can be translated to a QskBox::ColorMap and we can do all + coloring by adding a color info to points of the contour lines. + The orientation of contour lines does not depend on the direction + of the gradient vector. + + This allows using simpler and faster algos. + */ + + const QskBoxBasicStroker stroker( metrics, borderColors, effectiveGradient ); + + const int fillCount = stroker.fillCount(); + const int borderCount = stroker.borderCount(); + + auto lines = qskAllocateColoredLines( geometry, borderCount + fillCount ); + + auto fillLines = fillCount ? lines : nullptr; + auto borderLines = borderCount ? lines + fillCount : nullptr; + + stroker.setBoxLines( borderLines, fillLines ); + } + else + { + /* + We need to create gradient and contour lines in the correct order + perpendicular to the gradient vector. + */ + const QskBoxBasicStroker borderStroker( metrics, borderColors ); + QskBoxGradientStroker fillStroker( metrics, effectiveGradient ); + + const int fillCount = fillStroker.lineCount(); + const int borderCount = borderStroker.borderCount(); + + const int extraLine = ( fillCount && borderCount && !metrics.isOutsideRounded ) ? 1 : 0; + + auto lines = qskAllocateColoredLines( + geometry, fillCount + borderCount + extraLine ); + + if ( fillCount ) + fillStroker.setLines( fillCount, lines ); + + if ( borderCount ) + borderStroker.setBorderLines( lines + fillCount + extraLine ); + + if ( extraLine ) + { + // dummy line to connect filling and border + const auto l = lines + fillCount; + l[0].p1 = l[-1].p2; + l[0].p2 = l[+1].p1; + } + } +} diff --git a/src/nodes/QskBoxRenderer.h b/src/nodes/QskBoxRenderer.h index 194f18a2..9cc00259 100644 --- a/src/nodes/QskBoxRenderer.h +++ b/src/nodes/QskBoxRenderer.h @@ -6,162 +6,47 @@ #ifndef QSK_BOX_RENDERER_H #define QSK_BOX_RENDERER_H -#include "QskBoxShapeMetrics.h" - -#include +#include "QskGlobal.h" class QskBoxBorderMetrics; class QskBoxBorderColors; +class QskBoxShapeMetrics; class QskGradient; class QSGGeometry; +class QRectF; -namespace QskVertex +namespace QskBox { - class ColoredLine; -} + /* + Filling the geometry without any color information: + see QSGGeometry::defaultAttributes_Point2D() -class QSK_EXPORT QskBoxRenderer -{ - public: - void renderBorder( const QRectF&, + - clip nodes + - using shaders setting the color information + */ + + QSK_EXPORT void renderBorderGeometry( const QRectF&, const QskBoxShapeMetrics&, const QskBoxBorderMetrics&, QSGGeometry& ); - void renderFill( const QRectF&, + QSK_EXPORT void renderFillGeometry( const QRectF&, const QskBoxShapeMetrics&, const QskBoxBorderMetrics&, QSGGeometry& ); - void renderBox( const QRectF&, + QSK_EXPORT void renderFillGeometry( const QRectF&, + const QskBoxShapeMetrics&, QSGGeometry& ); + + /* + Filling the geometry usually with color information: + see QSGGeometry::defaultAttributes_ColoredPoint2D() + */ + QSK_EXPORT bool isGradientSupported( const QskBoxShapeMetrics&, const QskGradient& ); + + QSK_EXPORT void renderBox( const QRectF&, const QskBoxShapeMetrics&, const QskBoxBorderMetrics&, const QskBoxBorderColors&, const QskGradient&, QSGGeometry& ); - class Quad - { - public: - inline constexpr Quad() noexcept - : left( 0.0 ) - , top( 0.0 ) - , right( 0.0 ) - , bottom( 0.0 ) - , width( 0.0 ) - , height( 0.0 ) - { - } - - inline constexpr Quad( const QRectF& rect ) noexcept - : left( rect.left() ) - , top( rect.top() ) - , right( rect.right() ) - , bottom( rect.bottom() ) - , width( rect.width() ) - , height( rect.height() ) - { - } - - inline constexpr bool operator==( const Quad& other ) const noexcept - { - return - ( left == other.left ) && - ( right == other.right ) && - ( top == other.top ) && - ( bottom == other.bottom ); - } - - inline constexpr bool operator!=( const Quad& other ) const noexcept - { - return !( *this == other ); - } - - inline constexpr bool isEmpty() const noexcept - { - return ( width <= 0 ) || ( height <= 0 ); - } - - qreal left, top, right, bottom, width, height; - }; - - class Metrics - { - public: - Metrics( const QRectF&, const QskBoxShapeMetrics&, const QskBoxBorderMetrics& ); - - Quad outerQuad; - Quad innerQuad; -#if 1 - Quad centerQuad; // to be removed -#endif - - struct Corner - { - bool isCropped; - qreal centerX, centerY; - qreal radiusX, radiusY; - qreal radiusInnerX, radiusInnerY; - - int stepCount; - - } corner[ 4 ]; - - bool isBorderRegular; - bool isRadiusRegular; - bool isTotallyCropped; - }; - - private: - void renderRectFill( const QRectF&, - const QskBoxShapeMetrics&, const QskBoxBorderMetrics&, QSGGeometry& ); - - void renderRectBorder( const QRectF&, - const QskBoxShapeMetrics&, const QskBoxBorderMetrics&, QSGGeometry& ); - - void renderRect( const QRectF&, - const QskBoxShapeMetrics&, const QskBoxBorderMetrics&, - const QskBoxBorderColors&, const QskGradient&, QSGGeometry& ); - - void renderRectellipseFill( const QRectF&, - const QskBoxShapeMetrics&, const QskBoxBorderMetrics&, QSGGeometry& ); - - void renderRectellipseBorder( const QRectF&, - const QskBoxShapeMetrics&, const QskBoxBorderMetrics&, QSGGeometry& ); - - void renderRectellipse( const QRectF&, - const QskBoxShapeMetrics&, const QskBoxBorderMetrics&, - const QskBoxBorderColors&, const QskGradient&, QSGGeometry& ); - - void renderDiagonalFill( const Metrics&, const QskGradient&, - int lineCount, QskVertex::ColoredLine* ); - - void renderRectFill( const Quad&, const QskGradient&, QskVertex::ColoredLine* ); -}; - -inline void QskBoxRenderer::renderBorder( - const QRectF& rect, const QskBoxShapeMetrics& shape, - const QskBoxBorderMetrics& border, QSGGeometry& geometry ) -{ - if ( shape.isRectangle() ) - renderRectBorder( rect, shape, border, geometry ); - else - renderRectellipseBorder( rect, shape, border, geometry ); -} - -inline void QskBoxRenderer::renderFill( - const QRectF& rect, const QskBoxShapeMetrics& shape, - const QskBoxBorderMetrics& border, QSGGeometry& geometry ) -{ - if ( shape.isRectangle() ) - renderRectFill( rect, shape, border, geometry ); - else - renderRectellipseFill( rect, shape, border, geometry ); -} - -inline void QskBoxRenderer::renderBox( const QRectF& rect, - const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& border, - const QskBoxBorderColors& borderColors, const QskGradient& gradient, - QSGGeometry& geometry ) -{ - if ( shape.isRectangle() ) - renderRect( rect, shape, border, borderColors, gradient, geometry ); - else - renderRectellipse( rect, shape, border, borderColors, gradient, geometry ); + QSK_EXPORT void renderBox( const QRectF&, + const QskBoxShapeMetrics&, const QskGradient&, QSGGeometry& ); } #endif diff --git a/src/nodes/QskBoxRendererColorMap.h b/src/nodes/QskBoxRendererColorMap.h deleted file mode 100644 index b6749b6b..00000000 --- a/src/nodes/QskBoxRendererColorMap.h +++ /dev/null @@ -1,265 +0,0 @@ -/****************************************************************************** - * QSkinny - Copyright (C) 2016 Uwe Rathmann - * This file may be used under the terms of the QSkinny License, Version 1.0 - *****************************************************************************/ - -#ifndef QSK_BOX_RENDERER_COLOR_MAP_H -#define QSK_BOX_RENDERER_COLOR_MAP_H - -#include -#include - -#include - -class QskBoxShapeMetrics; - -namespace QskVertex -{ - class ColorMapNone - { - public: - constexpr inline Color colorAt( qreal ) const - { - return Color(); - } - }; - - class ColorMapSolid - { - public: - constexpr inline ColorMapSolid( Color color ) - : m_color( color ) - { - } - - inline Color colorAt( qreal ) const - { - return m_color; - } - - private: - const Color m_color; - }; - - class ColorMapGradient - { - public: - inline ColorMapGradient( Color color1, Color color2 ) - : m_color1( color1 ) - , m_color2( color2 ) - { - } - - inline Color colorAt( qreal value ) const - { - return m_color1.interpolatedTo( m_color2, value ); - } - - private: - const Color m_color1; - const Color m_color2; - }; - - class ColorIterator - { - public: - static inline bool advance() - { - return false; - } - - inline qreal value() const - { - assert( false ); - return 0.0; - } - - inline Color color() const - { - assert( false ); - return Color(); - } - - static inline bool isDone() - { - return true; - } - }; - - class SolidColorIterator : public ColorIterator - { - public: - inline SolidColorIterator( const QColor& color ) - : m_color( color ) - { - } - - inline Color colorAt( qreal ) const - { - return m_color; - } - - private: - const Color m_color; - }; - - class TwoColorIterator01 : public ColorIterator - { - public: - inline TwoColorIterator01( const QColor& color1, const QColor& color2 ) - : m_color1( color1 ) - , m_color2( color2 ) - { - } - - inline Color colorAt( qreal value ) const - { - return m_color1.interpolatedTo( m_color2, value ); - } - - private: - const Color m_color1, m_color2; - }; - - class TwoColorIterator : public ColorIterator - { - public: - inline TwoColorIterator( qreal value1, qreal value2, - const QColor& color1, const QColor& color2 ) - : m_value1( value1 ) - , m_range( value2 - value1 ) - , m_color1( color1 ) - , m_color2( color2 ) - { - } - - inline Color colorAt( qreal value ) const - { - const qreal r = ( value - m_value1 ) / m_range; - return m_color1.interpolatedTo( m_color2, r ); - } - - private: - const qreal m_value1, m_range; - const Color m_color1, m_color2; - }; - - class GradientColorIterator : public ColorIterator - { - public: - inline GradientColorIterator( qreal value1, qreal value2, - const QskGradientStops& stops ) - : m_value1( value1 ) - , m_value2( value2 ) - , m_stops( stops ) - { - Q_ASSERT( stops.size() > 2 ); - - m_color1 = stops[ 0 ].color(); - m_color2 = stops[ 1 ].color(); - - m_valueStep1 = value1; - m_valueStep2 = valueAt( stops[ 1 ].position() ); - m_stepSize = m_valueStep2 - m_valueStep1; - - m_index = 1; - } - - inline qreal value() const - { - return m_valueStep2; - } - - inline Color color() const - { - return m_color2; - } - - inline Color colorAt( qreal value ) const - { - const qreal r = ( value - m_valueStep1 ) / m_stepSize; - return m_color1.interpolatedTo( m_color2, r ); - } - - inline bool advance() - { - const auto& stop = m_stops[ ++m_index ]; - - m_color1 = m_color2; - m_color2 = stop.color(); - - m_valueStep1 = m_valueStep2; - m_valueStep2 = valueAt( stop.position() ); - m_stepSize = m_valueStep2 - m_valueStep1; - - return !isDone(); - } - - inline bool isDone() const - { - return m_index >= m_stops.count() - 1; - } - - private: - inline qreal valueAt( qreal pos ) const - { - return m_value1 + pos * ( ( m_value2 - m_value1 ) ); - } - - const qreal m_value1, m_value2; - const QskGradientStops m_stops; - - int m_index; - qreal m_valueStep1, m_valueStep2, m_stepSize; - Color m_color1, m_color2; - }; - - template< class ContourIterator, class ColorIterator > - ColoredLine* fillOrdered( ContourIterator& contourIt, - ColorIterator& colorIt, ColoredLine* line ) - { - do - { - while ( !colorIt.isDone() && ( colorIt.value() < contourIt.value() ) ) - { - contourIt.setGradientLine( colorIt, line++ ); - colorIt.advance(); - } - - contourIt.setContourLine( colorIt, line++ ); - - } while ( contourIt.advance() ); - - return line; - } - - template< class ContourIterator > - ColoredLine* fillOrdered( ContourIterator& contourIt, - qreal value1, qreal value2, const QskGradient& gradient, ColoredLine* line ) - { - if ( gradient.stops().size() == 2 ) - { - if ( value2 == 1.0 && value1 == 0.0 ) - { - TwoColorIterator01 colorIt( gradient.startColor(), gradient.endColor() ); - line = fillOrdered( contourIt, colorIt, line ); - } - else - { - TwoColorIterator colorIt( value1, value2, - gradient.startColor(), gradient.endColor() ); - - line = fillOrdered( contourIt, colorIt, line ); - } - } - else - { - GradientColorIterator colorIt( value1, value2, gradient.stops() ); - line = fillOrdered( contourIt, colorIt, line ); - } - - return line; - } -} - -#endif diff --git a/src/nodes/QskBoxRendererDEllipse.cpp b/src/nodes/QskBoxRendererDEllipse.cpp deleted file mode 100644 index ade0f637..00000000 --- a/src/nodes/QskBoxRendererDEllipse.cpp +++ /dev/null @@ -1,628 +0,0 @@ -/****************************************************************************** - * QSkinny - Copyright (C) 2016 Uwe Rathmann - * This file may be used under the terms of the QSkinny License, Version 1.0 - *****************************************************************************/ - -#include "QskBoxRenderer.h" -#include "QskBoxRendererColorMap.h" -#include "QskGradient.h" -#include "QskVertex.h" - -#include - -namespace -{ - using namespace QskVertex; - - class ContourLine - { - public: - inline void setLine( qreal x1, qreal y1, qreal value1, - qreal x2, qreal y2, qreal value2 ) - { - p1.x = x1; - p1.y = y1; - p1.v = value1; - - p2.x = x2; - p2.y = y2; - p2.v = value2; - } - - inline QPointF pointAt( qreal value ) const - { - const qreal r = ( value - p1.v ) / ( p2.v - p1.v ); - return QPointF( p1.x + r * ( p2.x - p1.x ), p1.y + r * ( p2.y - p1.y ) ); - } - - struct - { - qreal x, y, v; - } p1, p2; - }; - - class ValueCurve - { - public: - ValueCurve( const QskBoxRenderer::Metrics& m ) - { - /* - The slopes of the value line and those for the fill lines. - */ - const qreal mv = -( m.innerQuad.width / m.innerQuad.height ); - const qreal md = m.innerQuad.height / m.innerQuad.width; - - /* - first we find the point, where the tangent line - with the slope of md touches the top left border - of the ellipse around the top left corner point - */ - - qreal xt, yt; - { - const auto& c = m.corner[ Qt::TopLeftCorner ]; - - const qreal k = c.radiusInnerY / c.radiusInnerX * md; - const qreal u = ::sqrt( 1.0 + k * k ); - - const qreal dx = c.radiusInnerX / u; - const qreal dy = ::sqrt( 1.0 - 1.0 / ( u * u ) ) * c.radiusInnerY; - - xt = c.centerX - dx; - yt = c.centerY - dy; - } - - /* - the cutting point between the tangent and the diagonal of the - fill rectangle is the origin of our line, where we iterate - the values along. - */ - - qreal left, top, right; - { - const auto& r = m.innerQuad; - - left = ( yt - r.top - mv * xt + md * r.left ) / ( md - mv ); - - const qreal dx = left - r.left; - - top = r.top + md * dx; - right = r.right - dx; - } - - /* - Finally we can precalculate the coefficients needed for - evaluating points in valueAt. - */ - - { - const qreal k = mv + 1.0 / mv; - const qreal r = top + left / mv; - const qreal w = right - left; - - m_coeff_0 = ( r / k - left ) / w; - m_coeff_y = -1.0 / ( k * w ); - m_coeff_x = -mv * m_coeff_y; - } - } - - inline qreal valueAt( qreal x, qreal y ) const - { - // The value, where the perpendicular runs through x,y - - return m_coeff_0 + x * m_coeff_x + y * m_coeff_y; - } - - private: - qreal m_coeff_0, m_coeff_x, m_coeff_y; - }; - - class ContourIterator - { - public: - ContourIterator() - : m_clockwise( true ) - , m_isLeading( true ) - , m_isDone( false ) - { - } - - void setup( const QskBoxRenderer::Metrics& metrics, - bool isLeading, bool clockwise, - qreal cos, qreal cosStep, qreal sin, qreal sinStep, - qreal x1, qreal y1, qreal v1, qreal x2, qreal y2, qreal v2 ) - { - m_cos = cos; - m_sin = sin; - - m_cosStep = cosStep; - m_sinStep = sinStep; - - m_stepInv1 = m_sinStep / m_cosStep; - m_stepInv2 = m_cosStep + m_sinStep * m_stepInv1; - - m_contourLine.setLine( x1, y1, v1, x2, y2, v2 ); - - m_clockwise = clockwise; - m_isLeading = isLeading; - m_isDone = false; - - m_corner = Qt::TopLeftCorner; - - if ( clockwise ) - { - if ( x2 >= metrics.corner[ Qt::TopRightCorner ].centerX ) - setCorner( Qt::TopRightCorner, metrics ); - } - else - { - if ( y2 >= metrics.corner[ Qt::BottomLeftCorner ].centerY ) - setCorner( Qt::BottomLeftCorner, metrics ); - } - } - - inline bool isClockwise() const { return m_clockwise; } - inline bool isDone() const { return m_isDone; } - inline qreal value() const { return m_contourLine.p2.v; } - - inline const ContourLine& contourLine() const { return m_contourLine; } - - inline void advance( const QskBoxRenderer::Metrics& metrics, const ValueCurve& curve ) - { - if ( m_isDone ) - return; - - using namespace Qt; - - const auto& corners = metrics.corner; - const auto& c = corners[ m_corner ]; - - m_contourLine.p1 = m_contourLine.p2; - - auto& p = m_contourLine.p2; - - if ( m_clockwise ) - { - switch ( m_corner ) - { - case TopLeftCorner: - { - if ( p.x >= c.centerX ) - { - p.x = corners[ TopRightCorner ].centerX; - p.y = metrics.innerQuad.top; - - setCorner( TopRightCorner, metrics ); - } - else - { - decrement(); - p.x = c.centerX - m_cos * c.radiusInnerX; - p.y = c.centerY - m_sin * c.radiusInnerY; - - if ( p.x >= corners[ TopRightCorner ].centerX ) - setCorner( TopRightCorner, metrics ); - } - - break; - } - case TopRightCorner: - { - if ( p.y >= c.centerY ) - { - p.x = metrics.innerQuad.right; - p.y = corners[ BottomRightCorner ].centerY; - - setCorner( BottomRightCorner, metrics ); - } - else - { - increment(); - p.x = c.centerX + m_cos * c.radiusInnerX; - p.y = c.centerY - m_sin * c.radiusInnerY; - - if ( p.y >= corners[ BottomRightCorner ].centerY ) - setCorner( BottomRightCorner, metrics ); - } - - break; - } - case BottomRightCorner: - { - // we are bottom/right - increment(); - - p.x = c.centerX + m_cos * c.radiusInnerX; - p.y = c.centerY + m_sin * c.radiusInnerY; - - break; - } - default: - { - Q_ASSERT( false ); - } - } - } - else - { - switch ( m_corner ) - { - case TopLeftCorner: - { - if ( p.y >= c.centerY ) - { - p.x = metrics.innerQuad.left; - p.y = corners[ BottomLeftCorner ].centerY; - - setCorner( BottomLeftCorner, metrics ); - } - else - { - increment(); - p.x = c.centerX - m_cos * c.radiusInnerX; - p.y = c.centerY - m_sin * c.radiusInnerY; - - if ( p.y >= corners[ BottomLeftCorner ].centerY ) - setCorner( BottomLeftCorner, metrics ); - } - - break; - } - case BottomLeftCorner: - { - if ( p.x >= c.centerX ) - { - p.x = corners[ BottomRightCorner ].centerX; - p.y = metrics.innerQuad.bottom; - - setCorner( BottomRightCorner, metrics ); - } - else - { - increment(); - p.x = c.centerX - m_cos * c.radiusInnerX; - p.y = c.centerY + m_sin * c.radiusInnerY; - - if ( p.x >= corners[ BottomRightCorner ].centerX ) - setCorner( BottomRightCorner, metrics ); - } - - break; - } - case BottomRightCorner: - { - increment(); - - p.x = c.centerX + m_cos * c.radiusInnerX; - p.y = c.centerY + m_sin * c.radiusInnerY; - - break; - } - default: - { - Q_ASSERT( false ); - } - } - } - - p.v = curve.valueAt( p.x, p.y ); - - if ( ( p.v > 0.5 ) && ( p.v < m_contourLine.p1.v ) ) - { - p = m_contourLine.p1; - m_isDone = true; - } - } - - private: - static constexpr qreal m_eps = 1e-4; - - inline void setCorner( - Qt::Corner corner, const QskBoxRenderer::Metrics& metrics ) - { - m_corner = corner; - const auto& c = metrics.corner[ corner ]; - - const double angleStep = M_PI_2 / c.stepCount; - - m_cosStep = qFastCos( angleStep ); - m_sinStep = qFastSin( angleStep ); - m_stepInv1 = m_sinStep / m_cosStep; - m_stepInv2 = m_cosStep + m_sinStep * m_stepInv1; - - bool horizontal; - if ( corner == Qt::TopRightCorner || corner == Qt::BottomLeftCorner ) - horizontal = !m_clockwise; - else - horizontal = m_clockwise; - - if ( horizontal ) - { - m_cos = 1.0; - m_sin = 0.0; - - m_sinStep = -m_sinStep; - } - else - { - m_cos = 0.0; - m_sin = 1.0; - } - } - - inline void increment() - { -#if 0 - /* - We are running into this assertions when closing the filling - at the bottom right corner for rectangles with small width/height - ratio. Seems to be no problem, but has to be understood. TODO ... - */ - - Q_ASSERT( m_sinStep < 0.0 || m_cos < 1.0 ); - Q_ASSERT( m_sinStep > 0.0 || m_cos > 0.0 ); -#endif - - const double cos0 = m_cos; - - m_cos = m_cos * m_cosStep + m_sin * m_sinStep; - m_sin = m_sin * m_cosStep - cos0 * m_sinStep; - - if ( m_sinStep < 0.0 ) - { - if ( m_cos < m_eps ) - m_cos = 0.0; - - if ( m_sin > 1.0 - m_eps ) - m_sin = 1.0; - } - else - { - if ( m_cos > 1.0 - m_eps ) - m_cos = 1.0; - - if ( m_sin < m_eps ) - m_sin = 0.0; - } - } - - inline void decrement() - { - m_cos = ( m_cos - m_stepInv1 * m_sin ) / m_stepInv2; - if ( std::abs( m_cos ) < m_eps ) - m_cos = 0.0; - - m_sin = m_sin / m_cosStep + m_stepInv1 * m_cos; - if ( std::abs( m_sin ) < m_eps ) - m_sin = 0.0; - } - - bool m_clockwise; - bool m_isLeading; - bool m_isDone; - - qreal m_cos, m_cosStep; - qreal m_sin, m_sinStep; - qreal m_stepInv1, m_stepInv2; - - ContourLine m_contourLine; - Qt::Corner m_corner; - }; - - class OutlineIterator - { - public: - OutlineIterator( const QskBoxRenderer::Metrics& metrics, - const ValueCurve& curve, bool clockwise ) - : m_metrics( metrics ) - , m_curve( curve ) - { - const auto& c = metrics.corner[ Qt::TopLeftCorner ]; - -#if 1 - // This does not need to be done twice !!! -#endif - const double angleStep = M_PI_2 / c.stepCount; - const qreal cosStep = qFastCos( angleStep ); - const qreal sinStep = qFastSin( angleStep ); - - /* - Initialize the iterators to start with the - minimal value, what is somewhere around the top left corner - when having a gradient going from top/left to bottom/right. - */ - qreal cos1 = 0.0; - qreal sin1 = 1.0; - - qreal x1 = c.centerX; - qreal y1 = metrics.innerQuad.top; - qreal v1 = m_curve.valueAt( x1, y1 ); - - for ( int step = 1;; step++ ) - { - const qreal cos2 = cos1 * cosStep + sin1 * sinStep; - const qreal sin2 = sin1 * cosStep - cos1 * sinStep; - - const qreal x2 = c.centerX - c.radiusInnerX * cos2; - const qreal y2 = c.centerY - c.radiusInnerY * sin2; - const qreal v2 = m_curve.valueAt( x2, y2 ); - - if ( v2 >= v1 || step >= c.stepCount ) - { - if ( clockwise ) - { - m_iterator[ 0 ].setup( metrics, true, true, - cos1, cosStep, sin1, sinStep, x2, y2, v2, x1, y1, v1 ); - - m_iterator[ 1 ].setup( metrics, false, false, - cos2, cosStep, sin2, sinStep, x1, y1, v1, x2, y2, v2 ); - } - else - { - m_iterator[ 0 ].setup( metrics, true, false, - cos2, cosStep, sin2, sinStep, x1, y1, v1, x2, y2, v2 ); - - m_iterator[ 1 ].setup( metrics, false, true, - cos1, cosStep, sin1, sinStep, x2, y2, v2, x1, y1, v1 ); - } - - while ( !m_iterator[ 1 ].isDone() && - ( m_iterator[ 0 ].value() > m_iterator[ 1 ].value() ) ) - { - m_iterator[ 1 ].advance( metrics, m_curve ); - } - - return; - } - - cos1 = cos2; - sin1 = sin2; - - x1 = x2; - y1 = y2; - v1 = v2; - } - } - - inline void advance() - { - m_iterator[ 0 ].advance( m_metrics, m_curve ); - - if ( !m_iterator[ 0 ].isDone() ) - { - /* - adjusting the counter vertex until its top value is above - the value of the border. Then our value is between - the values of the counter vertex and we find out counter - border point by cutting the counter vertex. - */ - - while ( !m_iterator[ 1 ].isDone() && - ( m_iterator[ 0 ].value() > m_iterator[ 1 ].value() ) ) - { - m_iterator[ 1 ].advance( m_metrics, m_curve ); - } - } - } - - inline bool isDone() const - { - return m_iterator[ 0 ].isDone(); - } - - inline qreal value() const - { - return m_iterator[ 0 ].value(); - } - - inline void setLineAt( qreal value, Color color, ColoredLine* line ) - { - const auto& contourLine1 = m_iterator[ 0 ].contourLine(); - const auto& contourLine2 = m_iterator[ 1 ].contourLine(); - - const QPointF pos = contourLine1.pointAt( value ); - const QPointF cPos = contourLine2.pointAt( value ); - - if ( m_iterator[ 0 ].isClockwise() ) - setLine( cPos.x(), cPos.y(), pos.x(), pos.y(), color, line ); - else - setLine( pos.x(), pos.y(), cPos.x(), cPos.y(), color, line ); - } - - inline void setLine( Color color, ColoredLine* line ) - { - const auto& contourLine1 = m_iterator[ 0 ].contourLine(); - const auto& contourLine2 = m_iterator[ 1 ].contourLine(); - - const QPointF cPos = contourLine2.pointAt( contourLine1.p2.v ); - - if ( m_iterator[ 0 ].isClockwise() ) - setLine( cPos.x(), cPos.y(), contourLine1.p2.x, contourLine1.p2.y, color, line ); - else - setLine( contourLine1.p2.x, contourLine1.p2.y, cPos.x(), cPos.y(), color, line ); - } - - private: - inline void setLine( qreal x1, qreal y1, qreal x2, qreal y2, - Color color, ColoredLine* line ) - { - line->setLine( x1, y1, x2, y2, color ); - } - - const QskBoxRenderer::Metrics& m_metrics; - const ValueCurve& m_curve; - - /* - The first iterator for running along the left or right - half of the ellipse. The other one is for finding the - corresponing point at the other side. */ - - ContourIterator m_iterator[ 2 ]; - }; - - class DRectellipseIterator - { - public: - DRectellipseIterator( - const QskBoxRenderer::Metrics& metrics, const ValueCurve& curve ) - : m_left( metrics, curve, false ) - , m_right( metrics, curve, true ) - { - m_next = ( m_left.value() < m_right.value() ) ? &m_left : &m_right; - } - - template< class ColorIterator > - inline void setGradientLine( const ColorIterator& it, ColoredLine* line ) - { - m_next->setLineAt( it.value(), it.color(), line ); - } - - template< class ColorIterator > - inline void setContourLine( const ColorIterator& it, ColoredLine* line ) - { - m_next->setLine( it.colorAt( m_next->value() ), line ); - } - - inline qreal value() const - { - return m_next->value(); - } - - inline bool advance() - { - m_next->advance(); - m_next = ( m_left.value() < m_right.value() ) ? &m_left : &m_right; - - return !m_next->isDone(); - } - - private: - OutlineIterator m_left, m_right; - OutlineIterator* m_next; - }; -} - -void QskBoxRenderer::renderDiagonalFill( const QskBoxRenderer::Metrics& metrics, - const QskGradient& gradient, int fillLineCount, QskVertex::ColoredLine* lines ) -{ - const ValueCurve curve( metrics ); - - DRectellipseIterator it( metrics, curve ); - auto line = QskVertex::fillOrdered( it, 0.0, 1.0, gradient, lines ); - - /* - There are a couple of reasons, why less points have been rendered - than expected: f.e the positions of the gradient lines are - calculated from a diagonal, where the start/endpoints - might be a little outside of the contour. - - As effects like this one are hard to precalculate we simply - insert dummy lines to match the allocated memory. - */ - - while ( line - lines < fillLineCount ) - { - line[ 0 ] = line[ -1 ]; - line++; - } -} diff --git a/src/nodes/QskBoxRendererEllipse.cpp b/src/nodes/QskBoxRendererEllipse.cpp deleted file mode 100644 index 808b640e..00000000 --- a/src/nodes/QskBoxRendererEllipse.cpp +++ /dev/null @@ -1,1511 +0,0 @@ -/****************************************************************************** - * QSkinny - Copyright (C) 2016 Uwe Rathmann - * This file may be used under the terms of the QSkinny License, Version 1.0 - *****************************************************************************/ - -#include "QskBoxRenderer.h" -#include "QskGradient.h" - -#include "QskBoxBorderColors.h" -#include "QskBoxBorderMetrics.h" -#include "QskBoxRendererColorMap.h" -#include "QskBoxShapeMetrics.h" - -#include -#include - -using namespace QskVertex; - -namespace -{ - enum - { - TopLeft = Qt::TopLeftCorner, - TopRight = Qt::TopRightCorner, - BottomLeft = Qt::BottomLeftCorner, - BottomRight = Qt::BottomRightCorner - }; - - class ArcIterator - { - public: - inline ArcIterator() = default; - - inline ArcIterator( int stepCount, bool inverted = false ) - { - reset( stepCount, inverted ); - } - - void reset( int stepCount, bool inverted ) - { - m_inverted = inverted; - - if ( inverted ) - { - m_cos = 1.0; - m_sin = 0.0; - } - else - { - m_cos = 0.0; - m_sin = 1.0; - } - - m_stepIndex = 0; - m_stepCount = stepCount; - - const double angleStep = M_PI_2 / stepCount; - m_cosStep = qFastCos( angleStep ); - m_sinStep = qFastSin( angleStep ); - } - - inline bool isInverted() const { return m_inverted; } - - inline double cos() const { return m_cos; } - inline double sin() const { return m_inverted ? -m_sin : m_sin; } - - inline int step() const { return m_stepIndex; } - inline int stepCount() const { return m_stepCount; } - inline bool isDone() const { return m_stepIndex > m_stepCount; } - - inline void increment() - { - const double cos0 = m_cos; - - m_cos = m_cos * m_cosStep + m_sin * m_sinStep; - m_sin = m_sin * m_cosStep - cos0 * m_sinStep; - - ++m_stepIndex; - } - - inline void operator++() { increment(); } - - static int segmentHint( double radius ) - { - const double arcLength = radius * M_PI_2; - return qBound( 3, qCeil( arcLength / 3.0 ), 18 ); // every 3 pixels - } - - private: - double m_cos; - double m_sin; - - int m_stepIndex; - double m_cosStep; - double m_sinStep; - - int m_stepCount; - bool m_inverted; - }; - - int additionalGradientStops( const QskGradient& gradient ) - { - return qMax( 0, gradient.stops().count() - 2 ); - } - - static inline QRgb qskRgbGradientStart( const QskGradient& gradient ) - { - return gradient.startColor().rgba(); - } - - static inline QRgb qskRgbGradientEnd( const QskGradient& gradient ) - { - return gradient.endColor().rgba(); - } - - static inline QRgb qskRgbBorder( const QskBoxBorderColors& borderColors ) - { - return qskRgbGradientStart( borderColors.left() ); - } -} - -namespace -{ - class BorderValuesUniform - { - public: - inline BorderValuesUniform( const QskBoxRenderer::Metrics& metrics ) - : m_corner( metrics.corner[ 0 ] ) - , m_dx1( m_corner.radiusInnerX ) - , m_dy1( m_corner.radiusInnerY ) - { - } - - inline void setAngle( qreal cos, qreal sin ) - { - if ( !m_corner.isCropped ) - { - m_dx1 = cos * m_corner.radiusInnerX; - m_dy1 = sin * m_corner.radiusInnerY; - } - - m_dx2 = cos * m_corner.radiusX; - m_dy2 = sin * m_corner.radiusY; - } - - inline qreal dx1( int ) const { return m_dx1; } - inline qreal dy1( int ) const { return m_dy1; } - inline qreal dx2( int ) const { return m_dx2; } - inline qreal dy2( int ) const { return m_dy2; } - - private: - const QskBoxRenderer::Metrics::Corner& m_corner; - qreal m_dx1, m_dy1, m_dx2, m_dy2; - }; - - class BorderValues - { - public: - inline BorderValues( const QskBoxRenderer::Metrics& metrics ) - : m_uniform( metrics.isRadiusRegular ) - { - for ( int i = 0; i < 4; i++ ) - { - const auto& c = metrics.corner[ i ]; - auto& v = m_inner[ i ]; - - if ( c.radiusInnerX >= 0.0 ) - { - v.x0 = 0.0; - v.rx = c.radiusInnerX; - } - else - { - v.x0 = c.radiusInnerX; - v.rx = 0.0; - } - - if ( c.radiusInnerY >= 0.0 ) - { - v.y0 = 0.0; - v.ry = c.radiusInnerY; - } - else - { - v.y0 = c.radiusInnerY; - v.ry = 0.0; - } - - m_outer[ i ].x0 = m_outer[ i ].y0 = 0.0; - m_outer[ i ].rx = c.radiusX; - m_outer[ i ].ry = c.radiusY; - } - } - - inline void setAngle( qreal cos, qreal sin ) - { - m_inner[ 0 ].setAngle( cos, sin ); - m_inner[ 1 ].setAngle( cos, sin ); - m_inner[ 2 ].setAngle( cos, sin ); - m_inner[ 3 ].setAngle( cos, sin ); - - m_outer[ 0 ].setAngle( cos, sin ); - if ( !m_uniform ) - { - m_outer[ 1 ].setAngle( cos, sin ); - m_outer[ 2 ].setAngle( cos, sin ); - m_outer[ 3 ].setAngle( cos, sin ); - } - } - - inline qreal dx1( int pos ) const { return m_inner[ pos].dx; } - - inline qreal dy1( int pos ) const { return m_inner[ pos ].dy; } - - inline qreal dx2( int pos ) const - { return m_uniform ? m_outer[ 0 ].dx : m_outer[ pos ].dx; } - - inline qreal dy2( int pos ) const - { return m_uniform ? m_outer[ 0 ].dy : m_outer[ pos ].dy; } - - private: - bool m_uniform; - class Values - { - public: - inline void setAngle( qreal cos, qreal sin ) - { - dx = x0 + cos * rx; - dy = y0 + sin * ry; - } - - qreal dx, dy; - qreal x0, y0, rx, ry; - }; - - Values m_inner[ 4 ]; - Values m_outer[ 4 ]; - }; - - class FillValues - { - public: - inline FillValues( const QskBoxRenderer::Metrics& metrics ) - { - for ( int i = 0; i < 4; i++ ) - { - const auto& c = metrics.corner[ i ]; - auto& v = m_inner[ i ]; - - if ( c.radiusInnerX >= 0.0 ) - { - v.x0 = 0.0; - v.rx = c.radiusInnerX; - } - else - { - v.x0 = c.radiusInnerX; - v.rx = 0.0; - } - - if ( c.radiusInnerY >= 0.0 ) - { - v.y0 = 0.0; - v.ry = c.radiusInnerY; - } - else - { - v.y0 = c.radiusInnerY; - v.ry = 0.0; - } - } - } - - inline void setAngle( qreal cos, qreal sin ) - { - m_inner[ 0 ].setAngle( cos, sin ); - m_inner[ 1 ].setAngle( cos, sin ); - m_inner[ 2 ].setAngle( cos, sin ); - m_inner[ 3 ].setAngle( cos, sin ); - } - - inline qreal dx( int pos ) const { return m_inner[ pos ].dx; } - inline qreal dy( int pos ) const { return m_inner[ pos ].dy; } - - private: - class Values - { - public: - inline void setAngle( qreal cos, qreal sin ) - { - dx = x0 + cos * rx; - dy = y0 + sin * ry; - } - - qreal dx, dy; - qreal x0, y0, rx, ry; - }; - - Values m_inner[ 4 ]; - }; -} - -namespace -{ - class VRectEllipseIterator - { - public: - VRectEllipseIterator( const QskBoxRenderer::Metrics& metrics ) - : m_metrics( metrics ) - , m_values( metrics ) - { - const auto& c = metrics.corner; - - m_v[ 0 ].left = c[ TopLeft ].centerX; - m_v[ 0 ].right = c[ TopRight ].centerX; - m_v[ 0 ].y = metrics.innerQuad.top; - - m_v[ 1 ] = m_v[ 0 ]; - - m_leadingCorner = ( c[ TopLeft ].stepCount >= c[ TopRight ].stepCount ) - ? TopLeft : TopRight; - - m_arcIterator.reset( c[ m_leadingCorner ].stepCount, false ); - } - - template< class ColorIterator > - inline void setGradientLine( const ColorIterator& it, ColoredLine* line ) - { - const qreal y = it.value(); - const qreal f = ( y - m_v[ 0 ].y ) / ( m_v[ 1 ].y - m_v[ 0 ].y ); - const qreal left = m_v[ 0 ].left + f * ( m_v[ 1 ].left - m_v[ 0 ].left ); - const qreal right = m_v[ 0 ].right + f * ( m_v[ 1 ].right - m_v[ 0 ].right ); - - line->setLine( left, y, right, y, it.color() ); - } - - template< class ColorIterator > - inline void setContourLine( const ColorIterator& it, ColoredLine* line ) - { - line->setLine( m_v[ 1 ].left, m_v[ 1 ].y, - m_v[ 1 ].right, m_v[ 1 ].y, it.colorAt( m_v[ 1 ].y ) ); - } - - inline qreal value() const - { - return m_v[ 1 ].y; - } - - inline bool advance() - { - const auto& centerQuad = m_metrics.centerQuad; - const auto& c = m_metrics.corner; - - if ( m_arcIterator.step() == m_arcIterator.stepCount() ) - { - if ( m_arcIterator.isInverted() ) - return false; - - m_leadingCorner = ( c[ BottomLeft ].stepCount >= c[ BottomRight ].stepCount ) - ? BottomLeft : BottomRight; - - m_arcIterator.reset( c[ m_leadingCorner ].stepCount, true ); - - if ( centerQuad.top < centerQuad.bottom ) - { - m_v[ 0 ] = m_v[ 1 ]; - - m_v[ 1 ].left = m_metrics.innerQuad.left; - m_v[ 1 ].right = m_metrics.innerQuad.right; - m_v[ 1 ].y = centerQuad.bottom; - - return true; - } - } - - m_arcIterator.increment(); - m_values.setAngle( m_arcIterator.cos(), m_arcIterator.sin() ); - - m_v[ 0 ] = m_v[ 1 ]; - - if ( m_arcIterator.isInverted() ) - { - m_v[ 1 ].left = c[ BottomLeft ].centerX - m_values.dx( BottomLeft ); - m_v[ 1 ].right = c[ BottomRight ].centerX + m_values.dx( BottomRight ); - m_v[ 1 ].y = c[ m_leadingCorner ].centerY + m_values.dy( m_leadingCorner ); - } - else - { - m_v[ 1 ].left = c[ TopLeft ].centerX - m_values.dx( TopLeft ); - m_v[ 1 ].right = c[ TopRight ].centerX + m_values.dx( TopRight ); - m_v[ 1 ].y = c[ m_leadingCorner ].centerY - m_values.dy( m_leadingCorner ); - } - - return true; - } - - private: - const QskBoxRenderer::Metrics& m_metrics; - ArcIterator m_arcIterator; - int m_leadingCorner; - FillValues m_values; - struct { qreal left, right, y; } m_v[ 2 ]; - }; - - class HRectEllipseIterator - { - public: - HRectEllipseIterator( const QskBoxRenderer::Metrics& metrics ) - : m_metrics( metrics ) - , m_values( metrics ) - { - const auto& c = metrics.corner; - - m_v[ 0 ].top = c[ TopLeft ].centerY; - m_v[ 0 ].bottom = c[ BottomLeft ].centerY; - m_v[ 0 ].x = metrics.innerQuad.left; - - m_v[ 1 ] = m_v[ 0 ]; - - m_leadingCorner = ( c[ TopLeft ].stepCount >= c[ BottomLeft ].stepCount ) - ? TopLeft : BottomLeft; - - m_arcIterator.reset( c[ m_leadingCorner ].stepCount, true ); - } - - template< class ColorIterator > - inline void setGradientLine( const ColorIterator& it, ColoredLine* line ) - { - const qreal x = it.value(); - const qreal f = ( x - m_v[ 0 ].x ) / ( m_v[ 1 ].x - m_v[ 0 ].x ); - const qreal top = m_v[ 0 ].top + f * ( m_v[ 1 ].top - m_v[ 0 ].top ); - const qreal bottom = m_v[ 0 ].bottom + f * ( m_v[ 1 ].bottom - m_v[ 0 ].bottom ); - - line->setLine( x, top, x, bottom, it.color() ); - } - - template< class ColorIterator > - inline void setContourLine( const ColorIterator& it, ColoredLine* line ) - { - line->setLine( m_v[ 1 ].x, m_v[ 1 ].top, - m_v[ 1 ].x, m_v[ 1 ].bottom, it.colorAt( m_v[ 1 ].x ) ); - } - - inline qreal value() const - { - return m_v[ 1 ].x; - } - - inline bool advance() - { - const auto& centerQuad = m_metrics.centerQuad; - const auto& c = m_metrics.corner; - - if ( m_arcIterator.step() == m_arcIterator.stepCount() ) - { - if ( !m_arcIterator.isInverted() ) - return false; - - m_leadingCorner = ( c[ TopRight ].stepCount >= c[ BottomRight ].stepCount ) - ? TopRight : BottomRight; - - m_arcIterator.reset( c[ m_leadingCorner ].stepCount, false ); - if ( centerQuad.left < centerQuad.right ) - { - m_v[ 0 ] = m_v[ 1 ]; - - m_v[ 1 ].top = m_metrics.innerQuad.top; - m_v[ 1 ].bottom = m_metrics.innerQuad.bottom; - m_v[ 1 ].x = centerQuad.right; - - return true; - } - } - - m_arcIterator.increment(); - m_values.setAngle( m_arcIterator.cos(), m_arcIterator.sin() ); - - m_v[ 0 ] = m_v[ 1 ]; - - if ( m_arcIterator.isInverted() ) - { - m_v[ 1 ].top = c[ TopLeft ].centerY - m_values.dy( TopLeft ); - m_v[ 1 ].bottom = c[ BottomLeft ].centerY + m_values.dy( BottomLeft ); - m_v[ 1 ].x = c[ m_leadingCorner ].centerX - m_values.dx( m_leadingCorner ); - } - else - { - m_v[ 1 ].top = c[ TopRight ].centerY - m_values.dy( TopRight ); - m_v[ 1 ].bottom = c[ BottomRight ].centerY + m_values.dy( BottomRight ); - m_v[ 1 ].x = c[ m_leadingCorner ].centerX + m_values.dx( m_leadingCorner ); - } - - return true; - } - - private: - const QskBoxRenderer::Metrics& m_metrics; - ArcIterator m_arcIterator; - int m_leadingCorner; - FillValues m_values; - struct { qreal top, bottom, x; } m_v[ 2 ]; - }; -} - -namespace -{ - class BorderMapNone - { - public: - static inline constexpr Color colorAt( int ) { return Color(); } - inline QskGradient gradient() const { return QskGradient(); } - }; - - class BorderMapSolid - { - public: - inline BorderMapSolid( QRgb rgb ) - : m_color( rgb ) - { - } - - inline Color colorAt( int ) const { return m_color; } - inline QskGradient gradient() const { return QskGradient(); } - - const Color m_color; - }; - - class BorderMapGradient - { - public: - inline BorderMapGradient( int stepCount, QRgb rgb1, QRgb rgb2, const QskGradient& gradient ) - : m_stepCount( stepCount ) - , m_color1( rgb1 ) - , m_color2( rgb2 ) - , m_gradient( gradient ) - { - } - - inline Color colorAt( int step ) const - { - return m_color1.interpolatedTo( m_color2, step / m_stepCount ); - } - - inline const QskGradient& gradient() const - { - return m_gradient; - } - - private: - const qreal m_stepCount; - const Color m_color1, m_color2; - const QskGradient m_gradient; - }; - - template< class Line, class BorderValues > - class Stroker - { - public: - inline Stroker( const QskBoxRenderer::Metrics& metrics ) - : m_metrics( metrics ) - { - } - - void addAdditionalLines( - float x11, float y11, float x12, float y12, // start line - float x21, float y21, float x22, float y22, // end line - const QskGradient& gradient, Line* lines ) - { - int additionalStopCount = additionalGradientStops( gradient ); - - auto s = gradient.stops(); - - for( int i = 1; i <= additionalStopCount; ++i ) - { - auto p = ( 1 - s.at( i ).position() ); - float xStart = x11 + p * ( x21 - x11 ), - yStart = y11 + p * ( y21 - y11 ), - xEnd = x12 + p * ( x22 - x12 ), - yEnd = y12 + p * ( y22 - y12 ); - - lines[ additionalStopCount - i + 1 ].setLine( xStart, yStart, - xEnd, yEnd, s.at( i ).color() ); - } - } - - template< class BorderMap, class FillMap > - inline void createLines( Qt::Orientation orientation, Line* borderLines, - const BorderMap& borderMapTL, const BorderMap& borderMapTR, - const BorderMap& borderMapBL, const BorderMap& borderMapBR, - Line* fillLines, FillMap& fillMap ) - { - const auto& c = m_metrics.corner; -#if 1 - // TODO ... - const int stepCount = c[ 0 ].stepCount; -#endif - - Line* linesBR, * linesTR, * linesTL, * linesBL; - linesBR = linesTR = linesTL = linesBL = nullptr; - - const int numCornerLines = stepCount + 1; - int numFillLines = fillLines ? 2 * numCornerLines : 0; - - if ( orientation == Qt::Vertical ) - { - if ( borderLines ) - { - linesBR = borderLines; - - linesTR = linesBR + numCornerLines - + additionalGradientStops( borderMapBR.gradient() ); - - linesTL = linesTR + numCornerLines - + additionalGradientStops( borderMapTR.gradient() ); - - linesBL = linesTL + numCornerLines - + additionalGradientStops( borderMapTL.gradient() ); - } - - if ( fillLines ) - { - if ( m_metrics.centerQuad.top >= m_metrics.centerQuad.bottom ) - numFillLines--; - } - } - else - { - if ( borderLines ) - { - linesTR = borderLines + 1; - - linesTL = linesTR + numCornerLines - + additionalGradientStops( borderMapTR.gradient() ); - - linesBL = linesTL + numCornerLines - + additionalGradientStops( borderMapTL.gradient() ); - - linesBR = linesBL + numCornerLines - + additionalGradientStops( borderMapBL.gradient() ); - } - - if ( fillLines ) - { - if ( m_metrics.centerQuad.left >= m_metrics.centerQuad.right ) - numFillLines--; - } - } - - BorderValues v( m_metrics ); - - /* - It would be possible to run over [0, 0.5 * M_PI_2] - and create 8 values ( instead of 4 ) in each step. TODO ... - */ - for ( ArcIterator it( stepCount, false ); !it.isDone(); ++it ) - { - v.setAngle( it.cos(), it.sin() ); - - if ( borderLines ) - { - const int j = it.step(); - const int k = numCornerLines - it.step() - 1; - - { - constexpr auto corner = TopLeft; - - linesTL[ j ].setLine( - c[ corner ].centerX - v.dx1( corner ), - c[ corner ].centerY - v.dy1( corner ), - c[ corner ].centerX - v.dx2( corner ), - c[ corner ].centerY - v.dy2( corner ), - borderMapTL.colorAt( j ) ); - } - - { - constexpr auto corner = TopRight; - - linesTR[ k ].setLine( - c[ corner ].centerX + v.dx1( corner ), - c[ corner ].centerY - v.dy1( corner ), - c[ corner ].centerX + v.dx2( corner ), - c[ corner ].centerY - v.dy2( corner ), - borderMapTR.colorAt( k ) ); - } - - { - constexpr auto corner = BottomLeft; - - linesBL[ k ].setLine( - c[ corner ].centerX - v.dx1( corner ), - c[ corner ].centerY + v.dy1( corner ), - c[ corner ].centerX - v.dx2( corner ), - c[ corner ].centerY + v.dy2( corner ), - borderMapBL.colorAt( k ) ); - } - - { - constexpr auto corner = BottomRight; - - linesBR[ j ].setLine( - c[ corner ].centerX + v.dx1( corner ), - c[ corner ].centerY + v.dy1( corner ), - c[ corner ].centerX + v.dx2( corner ), - c[ corner ].centerY + v.dy2( corner ), - borderMapBR.colorAt( j ) ); - } - - // at the beginning and end of the loop we can add - // additional lines for border gradients: - - if( j == 0 ) - { - if( additionalGradientStops( borderMapTR.gradient() ) > 0 ) - { - float x1TR = c[ TopRight ].centerX + v.dx1( TopRight ), - y1TR = c[ TopRight ].centerY - v.dy1( TopRight ), - x2TR = c[ TopRight ].centerX + v.dx2( TopRight ), - y2TR = c[ TopRight ].centerY - v.dy2( TopRight ), - - x1TL = c[ TopLeft ].centerX - v.dx1( TopLeft ), - y1TL = c[ TopLeft ].centerY - v.dy1( TopLeft ), - x2TL = c[ TopLeft ].centerX - v.dx2( TopLeft ), - y2TL = c[ TopLeft ].centerY - v.dy2( TopLeft ); - - addAdditionalLines( - x1TR, y1TR, x2TR, y2TR, - x1TL, y1TL, x2TL, y2TL, - borderMapTR.gradient(), linesTR + k ); - } - - if( additionalGradientStops( borderMapBL.gradient() ) > 0 ) - { - float x1BL = c[ BottomLeft ].centerX - v.dx1( BottomLeft ), - y1BL = c[ BottomLeft ].centerY + v.dy1( BottomLeft ), - x2BL = c[ BottomLeft ].centerX - v.dx2( BottomLeft ), - y2BL = c[ BottomLeft ].centerY + v.dy2( BottomLeft ), - - x1BR = c[ BottomRight ].centerX + v.dx1( BottomRight ), - y1BR = c[ BottomRight ].centerY + v.dy1( BottomRight ), - x2BR = c[ BottomRight ].centerX + v.dx2( BottomRight ), - y2BR = c[ BottomRight ].centerY + v.dy2( BottomRight ); - - addAdditionalLines( - x1BL, y1BL, x2BL, y2BL, - x1BR, y1BR, x2BR, y2BR, - borderMapBL.gradient(), linesBL + k ); - } - } - - if( j == numCornerLines - 1 ) - { - if( additionalGradientStops( borderMapTL.gradient() ) > 0 ) - { - float x1TL = c[ TopLeft ].centerX - v.dx1( TopLeft ), - y1TL = c[ TopLeft ].centerY - v.dy1( TopLeft ), - x2TL = c[ TopLeft ].centerX - v.dx2( TopLeft ), - y2TL = c[ TopLeft ].centerY - v.dy2( TopLeft ), - - x1BL = c[ BottomLeft ].centerX - v.dx1( BottomLeft ), - y1BL = c[ BottomLeft ].centerY + v.dy1( BottomLeft ), - x2BL = c[ BottomLeft ].centerX - v.dx2( BottomLeft ), - y2BL = c[ BottomLeft ].centerY + v.dy2( BottomLeft ); - - addAdditionalLines( - x1TL, y1TL, x2TL, y2TL, - x1BL, y1BL, x2BL, y2BL, - borderMapTL.gradient(), linesTL + j ); - } - - if( additionalGradientStops( borderMapBR.gradient() ) > 0 ) - { - float x1BR = c[ BottomRight ].centerX + v.dx1( BottomRight ), - y1BR = c[ BottomRight ].centerY + v.dy1( BottomRight ), - x2BR = c[ BottomRight ].centerX + v.dx2( BottomRight ), - y2BR = c[ BottomRight ].centerY + v.dy2( BottomRight ), - - x1TR = c[ TopRight ].centerX + v.dx1( TopRight ), - y1TR = c[ TopRight ].centerY - v.dy1( TopRight ), - x2TR = c[ TopRight ].centerX + v.dx2( TopRight ), - y2TR = c[ TopRight ].centerY - v.dy2( TopRight ); - - addAdditionalLines( - x1BR, y1BR, x2BR, y2BR, - x1TR, y1TR, x2TR, y2TR, - borderMapBR.gradient(), linesBR + j ); - } - } - } - - if ( fillLines ) - { - const auto& ri = m_metrics.innerQuad; - - if ( orientation == Qt::Vertical ) - { - const int j = it.step(); - const int k = numFillLines - it.step() - 1; - - const qreal x11 = c[ TopLeft ].centerX - v.dx1( TopLeft ); - const qreal x12 = c[ TopRight ].centerX + v.dx1( TopRight ); - const qreal y1 = c[ TopLeft ].centerY - v.dy1( TopLeft ); - const auto c1 = fillMap.colorAt( ( y1 - ri.top ) / ri.height ); - - const qreal x21 = c[ BottomLeft ].centerX - v.dx1( BottomLeft ); - const qreal x22 = c[ BottomRight ].centerX + v.dx1( BottomRight ); - const qreal y2 = c[ BottomLeft ].centerY + v.dy1( BottomLeft ); - const auto c2 = fillMap.colorAt( ( y2 - ri.top ) / ri.height ); - - fillLines[ j ].setLine( x11, y1, x12, y1, c1 ); - fillLines[ k ].setLine( x21, y2, x22, y2, c2 ); - } - else - { - const int j = stepCount - it.step(); - const int k = numFillLines - 1 - stepCount + it.step(); - - const qreal x1 = c[ TopLeft ].centerX - v.dx1( TopLeft ); - const qreal y11 = c[ TopLeft ].centerY - v.dy1( TopLeft ); - const qreal y12 = c[ BottomLeft ].centerY + v.dy1( BottomLeft ); - const auto c1 = fillMap.colorAt( ( x1 - ri.left ) / ri.width ); - - const qreal x2 = c[ TopRight ].centerX + v.dx1( TopRight ); - const qreal y21 = c[ TopRight ].centerY - v.dy1( TopRight ); - const qreal y22 = c[ BottomRight ].centerY + v.dy1( BottomRight ); - const auto c2 = fillMap.colorAt( ( x2 - ri.left ) / ri.width ); - - fillLines[ j ].setLine( x1, y11, x1, y12, c1 ); - fillLines[ k ].setLine( x2, y21, x2, y22, c2 ); - } - } - } - -#if 1 - if ( borderLines ) - { - const int additionalStops = - additionalGradientStops( borderMapBR.gradient() ) - + additionalGradientStops( borderMapTR.gradient() ) - + additionalGradientStops( borderMapTL.gradient() ) - + additionalGradientStops( borderMapBL.gradient() ); - - const int k = 4 * numCornerLines + additionalStops; - - if ( orientation == Qt::Vertical ) - borderLines[ k ] = borderLines[ 0 ]; - else - borderLines[ 0 ] = borderLines[ k ]; - } -#endif - } - - private: - const QskBoxRenderer::Metrics& m_metrics; - }; -} - -static inline Qt::Orientation qskQtOrientation( const QskGradient& gradient ) -{ - return ( gradient.orientation() == QskGradient::Vertical ) ? Qt::Vertical : Qt::Horizontal; -} - -static inline int qskFillLineCount( - const QskBoxRenderer::Metrics& metrics, const QskGradient& gradient ) -{ - const int stepCount = metrics.corner[ 0 ].stepCount; - - if ( !gradient.isVisible() ) - return 0; - - int lineCount = 0; - - switch ( gradient.orientation() ) - { - case QskGradient::Diagonal: - { - lineCount += 2 * ( stepCount + 1 ); - - if ( metrics.centerQuad.left >= metrics.centerQuad.right ) - lineCount--; - - if ( metrics.centerQuad.top >= metrics.centerQuad.bottom ) - lineCount--; - - /* - For diagonal lines the points at the opposite - side are no points interpolating the outline. - So we need to insert interpolating lines on both sides - */ - - lineCount *= 2; // a real ellipse could be done with lineCount lines: TODO ... - -#if 1 - /* - The termination of the fill algorithm is a bit random - and might result in having an additional line. - Until this effect is completely understood, we better - reserve memory for this to avoid crashes. - */ - - lineCount++; // happens in a corner case - needs to be understood: TODO -#endif - - break; - } - case QskGradient::Vertical: - { - lineCount += qMax( metrics.corner[ TopLeft ].stepCount, - metrics.corner[ TopRight ].stepCount ) + 1; - - lineCount += qMax( metrics.corner[ BottomLeft ].stepCount, - metrics.corner[ BottomRight ].stepCount ) + 1; - - if ( metrics.centerQuad.top >= metrics.centerQuad.bottom ) - lineCount--; - - break; - } - case QskGradient::Horizontal: - { - lineCount += qMax( metrics.corner[ TopLeft ].stepCount, - metrics.corner[ BottomLeft ].stepCount ) + 1; - - lineCount += qMax( metrics.corner[ TopRight ].stepCount, - metrics.corner[ BottomRight ].stepCount ) + 1; - - if ( metrics.centerQuad.left >= metrics.centerQuad.right ) - lineCount--; - - break; - } - } - - // adding vertexes for the stops - beside the first/last - - if ( !gradient.isMonochrome() ) - lineCount += gradient.stops().size() - 2; - - return lineCount; -} - -template< class Line, class BorderMap, class FillMap > -static inline void qskRenderLines( - const QskBoxRenderer::Metrics& metrics, - Qt::Orientation orientation, Line* borderLines, - const BorderMap& borderMapTL, const BorderMap& borderMapTR, - const BorderMap& borderMapBL, const BorderMap& borderMapBR, - Line* fillLines, const FillMap& fillMap ) -{ - if ( metrics.isBorderRegular && metrics.isRadiusRegular ) - { - // the same border width for all edges - - Stroker< Line, BorderValuesUniform > stroker( metrics ); - stroker.createLines( orientation, borderLines, - borderMapTL, borderMapTR, borderMapBL, borderMapBR, - fillLines, fillMap ); - } - else - { - Stroker< Line, BorderValues > stroker( metrics ); - stroker.createLines( orientation, borderLines, - borderMapTL, borderMapTR, borderMapBL, borderMapBR, - fillLines, fillMap ); - } -} - -template< class Line, class BorderMap, class FillMap > -static inline void qskRenderLines( - const QskBoxRenderer::Metrics& metrics, Qt::Orientation orientation, - Line* borderLines, const BorderMap& borderMap, Line* fillLines, - const FillMap& fillMap ) -{ - qskRenderLines( metrics, orientation, borderLines, - borderMap, borderMap, borderMap, borderMap, fillLines, fillMap ); -} - -template< class Line, class BorderMap > -static inline void qskRenderBorderLines( - const QskBoxRenderer::Metrics& metrics, - Qt::Orientation orientation, Line* lines, - const BorderMap& borderMapTL, const BorderMap& borderMapTR, - const BorderMap& borderMapBL, const BorderMap& borderMapBR ) -{ - qskRenderLines( metrics, orientation, lines, borderMapTL, borderMapTR, - borderMapBL, borderMapBR, static_cast< Line* >( nullptr ), ColorMapNone() ); -} - -template< class Line, class BorderMap > -static inline void qskRenderBorderLines( - const QskBoxRenderer::Metrics& metrics, - Qt::Orientation orientation, Line* lines, const BorderMap& borderMap ) -{ - qskRenderBorderLines( metrics, orientation, lines, - borderMap, borderMap, borderMap, borderMap ); -} - -template< class Line, class FillMap > -static inline void qskRenderFillLines( const QskBoxRenderer::Metrics& metrics, - Qt::Orientation orientation, Line* lines, const FillMap& fillMap ) -{ - qskRenderLines( metrics, orientation, - static_cast< Line* >( nullptr ), BorderMapNone(), lines, fillMap ); -} - -static inline void qskRenderBorder( const QskBoxRenderer::Metrics& metrics, - Qt::Orientation orientation, const QskBoxBorderColors& colors, ColoredLine* line ) -{ - const auto& c = colors; - - if ( colors.isMonochrome() ) - { - qskRenderBorderLines( metrics, orientation, line, BorderMapSolid( qskRgbBorder( c ) ) ); - } - else - { - const int stepCount = metrics.corner[ 0 ].stepCount; - - const auto& left = c.left(); - const auto& top = c.top(); - const auto& right = c.right(); - const auto& bottom = c.bottom(); - - qskRenderBorderLines( metrics, orientation, line, - BorderMapGradient( stepCount, qskRgbGradientStart( top ), qskRgbGradientEnd( left ), left ), - BorderMapGradient( stepCount, qskRgbGradientStart( right ), qskRgbGradientEnd( top ), top ), - BorderMapGradient( stepCount, qskRgbGradientStart( left ), qskRgbGradientEnd( bottom ), bottom ), - BorderMapGradient( stepCount, qskRgbGradientStart( bottom ), qskRgbGradientEnd( right ), right ) ); - } -} - -static inline void qskRenderFillRandom( - const QskBoxRenderer::Metrics& metrics, - const QskGradient& gradient, ColoredLine* line ) -{ - const auto orientation = qskQtOrientation( gradient ); - - if ( gradient.isMonochrome() ) - { - const ColorMapSolid map( gradient.startColor() ); - qskRenderFillLines( metrics, orientation, line, map ); - } - else - { - const ColorMapGradient map( gradient.startColor(), gradient.endColor() ); - qskRenderFillLines( metrics, orientation, line, map ); - } -} - -static inline void qskRenderBoxRandom( - const QskBoxRenderer::Metrics& metrics, const QskBoxBorderColors& borderColors, - const QskGradient& gradient, ColoredLine* fillLine, ColoredLine* borderLine ) -{ - const auto& bc = borderColors; - - if ( bc.isMonochrome() ) - { - const BorderMapSolid borderMap( qskRgbBorder( bc.left() ) ); - - if ( gradient.isMonochrome() ) - { - const ColorMapSolid fillMap( gradient.startColor() ); - qskRenderLines( metrics, Qt::Vertical, borderLine, borderMap, fillLine, fillMap ); - } - else - { - const auto orientation = qskQtOrientation( gradient ); - - const ColorMapGradient fillMap( gradient.startColor(), gradient.endColor() ); - qskRenderLines( metrics, orientation, borderLine, borderMap, fillLine, fillMap ); - } - } - else - { - const int n = metrics.corner[ 0 ].stepCount; - - const auto& left = bc.left(); - const auto& top = bc.top(); - const auto& right = bc.right(); - const auto& bottom = bc.bottom(); - - const BorderMapGradient tl( n, qskRgbGradientStart( top.startColor() ), qskRgbGradientEnd( left.endColor() ), left ); - const BorderMapGradient tr( n, qskRgbGradientStart( right ), qskRgbGradientEnd( top ), top ); - const BorderMapGradient bl( n, qskRgbGradientStart( left ), qskRgbGradientEnd( bottom ), bottom ); - const BorderMapGradient br( n, qskRgbGradientStart( bottom ), qskRgbGradientEnd( right ), right ); - - if ( gradient.isMonochrome() ) - { - const ColorMapSolid fillMap( gradient.startColor() ); - qskRenderLines( metrics, Qt::Vertical, borderLine, tl, tr, bl, br, fillLine, fillMap ); - } - else - { - const auto orientation = qskQtOrientation( gradient ); - - const ColorMapGradient fillMap( gradient.startColor(), gradient.endColor() ); - qskRenderLines( metrics, orientation, borderLine, tl, tr, bl, br, fillLine, fillMap ); - } - } -} - -static inline void qskRenderFillOrdered( - const QskBoxRenderer::Metrics& metrics, - const QskGradient& gradient, ColoredLine* lines ) -{ - const auto& r = metrics.innerQuad; - - /* - The algo for irregular radii at opposite corners is not yet - implemented TODO ... - */ - - if ( gradient.orientation() == QskGradient::Horizontal ) - { - HRectEllipseIterator it( metrics ); - QskVertex::fillOrdered( it, r.left, r.right, gradient, lines ); - } - else - { - VRectEllipseIterator it( metrics ); - QskVertex::fillOrdered( it, r.top, r.bottom, gradient, lines ); - } -} - -QskBoxRenderer::Metrics::Metrics( const QRectF& rect, - const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& border ) - : outerQuad( rect ) -{ - isRadiusRegular = shape.isRectellipse(); - - for ( int i = 0; i < 4; i++ ) - { - auto& c = corner[ i ]; - - const QSizeF radius = shape.radius( static_cast< Qt::Corner >( i ) ); - c.radiusX = qBound( 0.0, radius.width(), 0.5 * outerQuad.width ); - c.radiusY = qBound( 0.0, radius.height(), 0.5 * outerQuad.height ); - c.stepCount = ArcIterator::segmentHint( qMax( c.radiusX, c.radiusY ) ); - - switch ( i ) - { - case TopLeft: - c.centerX = outerQuad.left + c.radiusX; - c.centerY = outerQuad.top + c.radiusY; - break; - - case TopRight: - c.centerX = outerQuad.right - c.radiusX; - c.centerY = outerQuad.top + c.radiusY; - break; - - case BottomLeft: - c.centerX = outerQuad.left + c.radiusX; - c.centerY = outerQuad.bottom - c.radiusY; - break; - - case BottomRight: - c.centerX = outerQuad.right - c.radiusX; - c.centerY = outerQuad.bottom - c.radiusY; - break; - } - } - - centerQuad.left = qMax( corner[ TopLeft ].centerX, - corner[ BottomLeft ].centerX ); - - centerQuad.right = qMin( corner[ TopRight ].centerX, - corner[ BottomRight ].centerX ); - - centerQuad.top = qMax( corner[ TopLeft ].centerY, - corner[ TopRight ].centerY ); - - centerQuad.bottom = qMin( corner[ BottomLeft ].centerY, - corner[ BottomRight ].centerY ); - - centerQuad.width = centerQuad.right - centerQuad.left; - centerQuad.height = centerQuad.bottom - centerQuad.top; - - // now the bounding rectangle of the fill area - - const auto bw = border.widths(); - - innerQuad.left = outerQuad.left + bw.left(); - innerQuad.right = outerQuad.right - bw.right(); - innerQuad.top = outerQuad.top + bw.top(); - innerQuad.bottom = outerQuad.bottom - bw.bottom(); - - innerQuad.left = qMin( innerQuad.left, centerQuad.right ); - innerQuad.right = qMax( innerQuad.right, centerQuad.left ); - innerQuad.top = qMin( innerQuad.top, centerQuad.bottom ); - innerQuad.bottom = qMax( innerQuad.bottom, centerQuad.top ); - - if ( innerQuad.left > innerQuad.right ) - { - innerQuad.left = innerQuad.right = - innerQuad.right + 0.5 * ( innerQuad.left - innerQuad.right ); - } - - if ( innerQuad.top > innerQuad.bottom ) - { - innerQuad.top = innerQuad.bottom = - innerQuad.bottom + 0.5 * ( innerQuad.top - innerQuad.bottom ); - } - - innerQuad.width = innerQuad.right - innerQuad.left; - innerQuad.height = innerQuad.bottom - innerQuad.top; - - const qreal borderLeft = innerQuad.left - outerQuad.left; - const qreal borderTop = innerQuad.top - outerQuad.top; - const qreal borderRight = outerQuad.right - innerQuad.right; - const qreal borderBottom = outerQuad.bottom - innerQuad.bottom; - - for ( int i = 0; i < 4; i++ ) - { - auto& c = corner[ i ]; - - switch ( i ) - { - case TopLeft: - { - c.radiusInnerX = c.radiusX - borderLeft; - c.radiusInnerY = c.radiusY - borderTop; - - c.isCropped = ( c.centerX <= innerQuad.left ) || - ( c.centerY <= innerQuad.top ); - - break; - } - case TopRight: - { - c.radiusInnerX = c.radiusX - borderRight; - c.radiusInnerY = c.radiusY - borderTop; - - c.isCropped = ( c.centerX >= innerQuad.right ) || - ( c.centerY <= innerQuad.top ); - break; - } - case BottomLeft: - { - c.radiusInnerX = c.radiusX - borderLeft; - c.radiusInnerY = c.radiusY - borderBottom; - - c.isCropped = ( c.centerX <= innerQuad.left ) || - ( c.centerY >= innerQuad.bottom ); - break; - } - case BottomRight: - { - c.radiusInnerX = c.radiusX - borderRight; - c.radiusInnerY = c.radiusY - borderBottom; - - c.isCropped = ( c.centerX >= innerQuad.right ) || - ( c.centerY >= innerQuad.bottom ); - break; - } - } - } - - isTotallyCropped = - corner[ TopLeft ].isCropped && - corner[ TopRight ].isCropped && - corner[ BottomRight ].isCropped && - corner[ BottomLeft ].isCropped; - - // number of steps for iterating over the corners - - isBorderRegular = - ( borderLeft == borderTop ) && - ( borderTop == borderRight ) && - ( borderRight == borderBottom ); -} - -void QskBoxRenderer::renderRectellipseBorder( - const QRectF& rect, const QskBoxShapeMetrics& shape, - const QskBoxBorderMetrics& border, QSGGeometry& geometry ) -{ - const Metrics metrics( rect, shape, border ); - - if ( metrics.innerQuad == metrics.outerQuad ) - { - allocateLines< Line >( geometry, 0 ); - return; - } - - const int stepCount = metrics.corner[ 0 ].stepCount; - const int lineCount = 4 * ( stepCount + 1 ) + 1; - - const auto line = allocateLines< Line >( geometry, lineCount ); - qskRenderBorderLines( metrics, Qt::Vertical, line, BorderMapNone() ); -} - -void QskBoxRenderer::renderRectellipseFill( - const QRectF& rect, const QskBoxShapeMetrics& shape, - const QskBoxBorderMetrics& border, QSGGeometry& geometry ) -{ - const Metrics metrics( rect, shape, border ); - - if ( ( metrics.innerQuad.width <= 0 ) || ( metrics.innerQuad.height <= 0 ) ) - { - allocateLines< Line >( geometry, 0 ); - return; - } - - if ( metrics.isTotallyCropped ) - { - // degenerated to a rectangle - - const QRectF r( metrics.innerQuad.left, metrics.innerQuad.top, - metrics.innerQuad.width, metrics.innerQuad.height ); - - renderRectFill( r, QskBoxShapeMetrics(), - QskBoxBorderMetrics(), geometry ); - - return; - } - - const int stepCount = metrics.corner[ 0 ].stepCount; - - int lineCount = 2 * stepCount; - - if ( metrics.centerQuad.top >= metrics.centerQuad.bottom ) - { - // we need an extra line connecting the top/bottom corners - lineCount++; - } - else - { - // for some reason we have 2 more lines for true ellipses ?? - lineCount += 2; - } - - const auto line = allocateLines< Line >( geometry, lineCount ); - qskRenderFillLines( metrics, Qt::Vertical, line, ColorMapNone() ); -} - -void QskBoxRenderer::renderRectellipse( const QRectF& rect, - const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& border, - const QskBoxBorderColors& borderColors, const QskGradient& gradient, - QSGGeometry& geometry ) -{ - const Metrics metrics( rect, shape, border ); - - int fillLineCount = 0; - - if ( !metrics.innerQuad.isEmpty() && gradient.isVisible() ) - { - if ( metrics.isTotallyCropped ) - { - // degenerated to a rectangle - - fillLineCount = gradient.stops().count(); - -#if 1 - // code copied from QskBoxRendererRect.cpp TODO ... - - if ( gradient.orientation() == QskGradient::Diagonal ) - { - if ( metrics.centerQuad.width == metrics.centerQuad.height ) - { - if ( !gradient.hasStopAt( 0.5 ) ) - fillLineCount++; - } - else - { - // we might need extra lines for the corners - fillLineCount += 2; - } - } -#endif - } - else - { - fillLineCount = qskFillLineCount( metrics, gradient ); - } - } - - const int stepCount = metrics.corner[ 0 ].stepCount; - - int borderLineCount = 0; - - if ( borderColors.isVisible() && metrics.innerQuad != metrics.outerQuad ) - { - borderLineCount = 4 * ( stepCount + 1 ) + 1; - - const int additionalLines = - additionalGradientStops( borderColors.left() ) - + additionalGradientStops( borderColors.top() ) - + additionalGradientStops( borderColors.right() ) - + additionalGradientStops( borderColors.bottom() ); - - borderLineCount += additionalLines; - } - - int lineCount = borderLineCount + fillLineCount; - - bool extraLine = false; - if ( borderLineCount > 0 && fillLineCount > 0 ) - { - if ( !gradient.isMonochrome() && - ( gradient.orientation() == QskGradient::Diagonal ) ) - { - /* - The filling ends at 45° and we have no implementation - for creating the border from there. So we need to - insert an extra dummy line to connect fill and border - */ - extraLine = true; - lineCount++; - } - } - - auto line = allocateLines< ColoredLine >( geometry, lineCount ); - - bool fillRandom = true; - if ( fillLineCount > 0 ) - { - if ( metrics.isTotallyCropped ) - { - fillRandom = false; - } - else if ( !gradient.isMonochrome() ) - { - if ( gradient.stops().count() > 2 || - gradient.orientation() == QskGradient::Diagonal ) - { - fillRandom = false; - } - } - - if ( fillRandom ) - { - if ( !metrics.isRadiusRegular ) - { - /* - When we have a combination of corners with the same - or no radius we could use the faster random algo: TODO ... - */ - fillRandom = false; - } - } - } - - if ( ( fillLineCount > 0 ) && ( borderLineCount > 0 ) ) - { - if ( fillRandom ) - { - qskRenderBoxRandom( metrics, borderColors, - gradient, line, line + fillLineCount ); - } - else - { - if ( metrics.isTotallyCropped ) - { - renderRectFill( metrics.innerQuad, gradient, line ); - } - else if ( gradient.orientation() == QskGradient::Diagonal ) - { - renderDiagonalFill( metrics, gradient, fillLineCount, line ); - } - else - { - qskRenderFillOrdered( metrics, gradient, line ); - } - - auto borderLines = line + fillLineCount; - if ( extraLine ) - borderLines++; - - const auto orientation = qskQtOrientation( gradient ); - qskRenderBorder( metrics, orientation, borderColors, borderLines ); - - if ( extraLine ) - { - const auto l = line + fillLineCount; - l[ 0 ].p1 = l[ -1 ].p2; - l[ 0 ].p2 = l[ 1 ].p1; - } - } - } - else if ( fillLineCount > 0 ) - { - if ( fillRandom ) - { - qskRenderFillRandom( metrics, gradient, line ); - } - else - { - if ( metrics.isTotallyCropped ) - { - renderRectFill( metrics.innerQuad, gradient, line ); - } - else if ( gradient.orientation() == QskGradient::Diagonal ) - { - renderDiagonalFill( metrics, gradient, fillLineCount, line ); - } - else - { - qskRenderFillOrdered( metrics, gradient, line ); - } - } - } - else if ( borderLineCount > 0 ) - { -#if 1 - /* - In case of having an empty innerQuad and monochrome - border colors, we could treat it like filling without border. TODO ... - */ -#endif - qskRenderBorder( metrics, Qt::Vertical, borderColors, line ); - } -} diff --git a/src/nodes/QskBoxRendererRect.cpp b/src/nodes/QskBoxRendererRect.cpp deleted file mode 100644 index c7eca9b1..00000000 --- a/src/nodes/QskBoxRendererRect.cpp +++ /dev/null @@ -1,641 +0,0 @@ -/****************************************************************************** - * QSkinny - Copyright (C) 2016 Uwe Rathmann - * This file may be used under the terms of the QSkinny License, Version 1.0 - *****************************************************************************/ - -#include "QskBoxBorderColors.h" -#include "QskBoxBorderMetrics.h" -#include "QskBoxRenderer.h" -#include "QskBoxRendererColorMap.h" -#include "QskFunctions.h" -#include "QskGradient.h" -#include "QskVertex.h" - -using namespace QskVertex; - -namespace -{ - class VRectIterator - { - public: - inline VRectIterator( const QskBoxRenderer::Quad& rect ) - : m_rect( rect ) - , m_value( rect.top ) - { - } - - template< class ColorIterator > - inline void setGradientLine( const ColorIterator& it, ColoredLine* line ) - { - line->setHLine( m_rect.left, m_rect.right, it.value(), it.color() ); - } - - template< class ColorIterator > - inline void setContourLine( const ColorIterator& it, ColoredLine* line ) - { - line->setHLine( m_rect.left, m_rect.right, m_value, it.colorAt( m_value ) ); - } - - inline qreal value() const - { - return m_value; - } - - inline bool advance() - { - if ( m_value == m_rect.top ) - { - m_value = m_rect.bottom; - return true; - } - - return false; - } - - private: - const QskBoxRenderer::Quad& m_rect; - qreal m_value; - }; - - class HRectIterator - { - public: - inline HRectIterator( const QskBoxRenderer::Quad& rect ) - : m_rect( rect ) - , m_value( rect.left ) - { - } - - template< class ColorIterator > - inline void setGradientLine( const ColorIterator& it, ColoredLine* line ) - { - line->setVLine( it.value(), m_rect.top, m_rect.bottom, it.color() ); - } - - template< class ColorIterator > - inline void setContourLine( const ColorIterator& it, ColoredLine* line ) - { - line->setVLine( m_value, m_rect.top, m_rect.bottom, it.colorAt( m_value ) ); - } - - inline qreal value() const - { - return m_value; - } - - inline bool advance() - { - if ( m_value == m_rect.left ) - { - m_value = m_rect.right; - return true; - } - - return false; - } - - private: - const QskBoxRenderer::Quad& m_rect; - qreal m_value; - }; - - class DSquareIterator - { - public: - inline DSquareIterator( const QskBoxRenderer::Quad& rect ) - : m_rect( rect ) - , m_step( 0 ) - { - Q_ASSERT( rect.width == rect.height ); - } - - template< class ColorIterator > - inline void setGradientLine( const ColorIterator& it, ColoredLine* line ) - { - const auto v = it.value(); - - if ( v == 0.5 ) - { - // this line is also a contour line, so we can skip it - return; - } - else if ( v < 0.5 ) - { - const qreal dt = m_rect.width * 2 * v; - - line->setLine( m_rect.left, m_rect.top + dt, - m_rect.left + dt, m_rect.top, it.color() ); - } - else - { - const qreal dt = m_rect.width * 2 * ( v - 0.5 ); - line->setLine( m_rect.left + dt, m_rect.bottom, - m_rect.right, m_rect.top + dt, it.color() ); - } - } - - template< class ColorIterator > - inline void setContourLine( const ColorIterator& it, ColoredLine* line ) - { - const auto color = it.colorAt( value() ); - const auto& r = m_rect; - - switch ( m_step ) - { - case 0: - { - line->setLine( r.left, r.top, r.left, r.top, color ); - break; - } - case 1: - { - line->setLine( r.left, r.bottom, r.right, r.top, color ); - break; - } - case 2: - { - line->setLine( r.right, r.bottom, r.right, r.bottom, color ); - break; - } - } - } - - inline qreal value() const - { - return m_step * 0.5; - } - - inline bool advance() - { - return ++m_step <= 2; - } - - private: - const QskBoxRenderer::Quad& m_rect; - int m_step; - }; - - class DRectIterator - { - public: - inline DRectIterator( const QskBoxRenderer::Quad& rect ) - : m_rect( rect ) - , m_step( 0 ) - { - const qreal w = rect.width; - const qreal h = rect.height; - - Q_ASSERT( w != h ); - - const qreal w2 = w * w; - const qreal h2 = h * h; - - m_fx = ( w2 + h2 ) / w; - m_fy = ( w2 + h2 ) / h; - - m_lx = m_rect.top - h2 / w; - m_ly = m_rect.left - w2 / h; - - m_valueTR = w2 / ( w2 + h2 ); - m_valueBL = h2 / ( w2 + h2 ); - } - - template< class ColorIterator > - inline void setGradientLine( const ColorIterator& it, ColoredLine* line ) - { - const auto v = it.value(); - const auto color = it.color(); - const auto& r = m_rect; - - switch ( m_step ) - { - case 1: - { - const qreal dx = v * m_fx; - const qreal dy = v * m_fy; - - line->setLine( r.left, r.top + dy, r.left + dx, r.top, color ); - break; - } - case 2: - { - if ( r.width > r.height ) - { - const qreal dx = v * m_fx; - line->setLine( m_lx + dx, r.bottom, r.left + dx, r.top, color ); - } - else - { - const qreal dy = v * m_fy; - line->setLine( r.left, r.top + dy, r.right, m_ly + dy, color ); - } - break; - } - case 3: - { - const qreal dx = v * m_fx; - const qreal dy = v * m_fy; - - line->setLine( m_lx + dx, r.bottom, r.right, m_ly + dy, color ); - break; - } - } - } - - template< class ColorIterator > - inline void setContourLine( const ColorIterator& it, ColoredLine* line ) - { - const auto& r = m_rect; - - switch ( m_step ) - { - case 0: - { - line->setLine( r.left, r.top, - r.left, r.top, it.colorAt( 0.0 ) ); - - break; - } - case 1: - { - if ( r.width >= r.height ) - { - const qreal dx = m_valueBL * m_fx; - - line->setLine( r.left, r.bottom, - r.left + dx, r.top, it.colorAt( m_valueBL ) ); - } - else - { - const qreal dy = m_valueTR * m_fy; - - line->setLine( r.left, r.top + dy, - r.right, r.top, it.colorAt( m_valueTR ) ); - } - - break; - } - case 2: - { - if ( r.width >= r.height ) - { - const qreal dx = m_valueTR * m_fx; - - line->setLine( r.left + dx, r.bottom, - r.right, r.top, it.colorAt( m_valueTR ) ); - } - else - { - const qreal dy = m_valueBL * m_fy; - - line->setLine( r.left, r.bottom, - r.right, r.top + dy, it.colorAt( m_valueBL ) ); - } - - break; - } - case 3: - { - line->setLine( r.right, r.bottom, - r.right, r.bottom, it.colorAt( 1.0 ) ); - - break; - } - default: - { - Q_ASSERT( false ); - } - } - } - - inline qreal value() const - { - switch ( m_step ) - { - case 0: - return 0.0; - - case 1: - return std::min( m_valueBL, m_valueTR ); - - case 2: - return std::max( m_valueBL, m_valueTR ); - - default: - return 1.0; - } - } - - inline bool advance() - { - return ++m_step <= 3; - } - - private: - const QskBoxRenderer::Quad& m_rect; - - qreal m_fx, m_fy; - qreal m_lx, m_ly; - qreal m_valueTR, m_valueBL; - - int m_step; - }; -} - -static inline void qskCreateFillOrdered( const QskBoxRenderer::Quad& rect, - const QskGradient& gradient, ColoredLine* line ) -{ - switch ( gradient.orientation() ) - { - case QskGradient::Horizontal: - { - HRectIterator it( rect ); - line = QskVertex::fillOrdered( it, rect.left, rect.right, gradient, line ); - - break; - } - case QskGradient::Vertical: - { - VRectIterator it( rect ); - line = QskVertex::fillOrdered( it, rect.top, rect.bottom, gradient, line ); - - break; - } - case QskGradient::Diagonal: - { - if ( rect.width == rect.height ) - { - DSquareIterator it( rect ); - line = QskVertex::fillOrdered( it, 0.0, 1.0, gradient, line ); - } - else - { - DRectIterator it( rect ); - line = QskVertex::fillOrdered( it, 0.0, 1.0, gradient, line ); - } - - break; - } - } -} - -template< class ColorMap, class Line > -static inline void qskCreateFillRandom( - QskGradient::Orientation orientation, - const QskBoxRenderer::Quad& r, const ColorMap& map, Line* line ) -{ - if ( orientation == QskGradient::Vertical ) - { - ( line++ )->setLine( r.left, r.top, r.right, r.top, map.colorAt( 0.0 ) ); - ( line++ )->setLine( r.left, r.bottom, r.right, r.bottom, map.colorAt( 1.0 ) ); - } - else - { - ( line++ )->setLine( r.left, r.top, r.left, r.bottom, map.colorAt( 0.0 ) ); - ( line++ )->setLine( r.right, r.top, r.right, r.bottom, map.colorAt( 1.0 ) ); - } -} - -template< class Line > -static inline void qskCreateBorderMonochrome( - const QskBoxRenderer::Quad& out, const QskBoxRenderer::Quad& in, QRgb rgb, Line* line ) -{ - qskCreateBorderMonochrome( out, in, Color( rgb ), line ); -} - -template< class Line > -static inline void qskCreateBorderMonochrome( - const QskBoxRenderer::Quad& out, const QskBoxRenderer::Quad& in, Color color, Line* line ) -{ - auto l = line; - - ( l++ )->setLine( in.right, in.bottom, out.right, out.bottom, color ); - ( l++ )->setLine( in.left, in.bottom, out.left, out.bottom, color ); - ( l++ )->setLine( in.left, in.top, out.left, out.top, color ); - ( l++ )->setLine( in.right, in.top, out.right, out.top, color ); - - *l = line[ 0 ]; -} - -template< class Line > -static inline void qskCreateBorder( - const QskBoxRenderer::Quad& out, const QskBoxRenderer::Quad& in, - const QskBoxBorderColors& colors, Line* line ) -{ - const auto& gradientLeft = colors.left(); - const auto& gradientRight = colors.right(); - const auto& gradientTop = colors.top(); - const auto& gradientBottom = colors.bottom(); - - // qdebug - - const qreal dx1 = in.right - in.left; - const qreal dx2 = out.right - out.left; - const qreal dy1 = in.top - in.bottom; - const qreal dy2 = out.top - out.bottom; - - for( const auto& stop : qAsConst( gradientBottom.stops() ) ) - { - const Color c( stop.color() ); - const qreal x1 = in.right - stop.position() * dx1; - const qreal x2 = out.right - stop.position() * dx2; - const qreal y1 = in.bottom; - const qreal y2 = out.bottom; - - ( line++ )->setLine( x1, y1, x2, y2, c ); - } - - for( const auto& stop : qAsConst( gradientLeft.stops() ) ) - { - const Color c( stop.color() ); - const qreal x1 = in.left; - const qreal x2 = out.left; - const qreal y1 = in.bottom + stop.position() * dy1; - const qreal y2 = out.bottom + stop.position() * dy2; - - ( line++ )->setLine( x1, y1, x2, y2, c ); - } - - for( const auto& stop : qAsConst( gradientTop.stops() ) ) - { - const Color c( stop.color() ); - const qreal x1 = in.left + stop.position() * dx1; - const qreal x2 = out.left + stop.position() * dx2; - const qreal y1 = in.top; - const qreal y2 = out.top; - - ( line++ )->setLine( x1, y1, x2, y2, c ); - } - - for( const auto& stop : qAsConst( gradientRight.stops() ) ) - { - const Color c( stop.color() ); - const qreal x1 = in.right; - const qreal x2 = out.right; - // ( 1 - stop.position() ) because we want to make the gradients go - // around the border clock-wise: - const qreal y1 = in.bottom + ( 1 - stop.position() ) * dy1; - const qreal y2 = out.bottom + ( 1 - stop.position() ) * dy2; - - ( line++ )->setLine( x1, y1, x2, y2, c ); - } -} - -void QskBoxRenderer::renderRectBorder( - const QRectF& rect, const QskBoxShapeMetrics& shape, - const QskBoxBorderMetrics& border, QSGGeometry& geometry ) -{ - Q_UNUSED( shape ) - - const Quad out = rect; - const Quad in = qskValidOrEmptyInnerRect( rect, border.widths() ); - - if ( out == in ) - { - allocateLines< Line >( geometry, 0 ); - return; - } - - const auto line = allocateLines< Line >( geometry, 4 + 1 ); - qskCreateBorderMonochrome( out, in, Color(), line ); -} - -void QskBoxRenderer::renderRectFill( - const QRectF& rect, const QskBoxShapeMetrics& shape, - const QskBoxBorderMetrics& border, QSGGeometry& geometry ) -{ - Q_UNUSED( shape ) - - const Quad in = qskValidOrEmptyInnerRect( rect, border.widths() ); - - if ( in.isEmpty() ) - { - allocateLines< Line >( geometry, 0 ); - return; - } - - const auto line = allocateLines< Line >( geometry, 2 ); - - qskCreateFillRandom( QskGradient::Vertical, - in, ColorMapSolid( Color() ), line ); -} - -void QskBoxRenderer::renderRect( - const QRectF& rect, const QskBoxShapeMetrics& shape, - const QskBoxBorderMetrics& border, const QskBoxBorderColors& borderColors, - const QskGradient& gradient, QSGGeometry& geometry ) -{ - Q_UNUSED( shape ) - - const Quad out = rect; - const Quad in = qskValidOrEmptyInnerRect( rect, border.widths() ); - - int fillLineCount = 0; - if ( !in.isEmpty() ) - { - fillLineCount = gradient.stops().count(); - - if ( gradient.orientation() == QskGradient::Diagonal ) - { - if ( in.width == in.height ) - { - if ( !gradient.hasStopAt( 0.5 ) ) - fillLineCount++; - } - else - { - // we might need extra lines for the corners - fillLineCount += 2; - } - } - } - - int borderLineCount = 0; - if ( in != out ) - { - const auto& bc = borderColors; - - if ( bc.isVisible() ) - { - // We can build a rectangular border from the 4 diagonal - // lines at the corners, but need an additional line - // for closing the border. - - borderLineCount = 4 + 1; - - if ( !bc.isMonochrome() ) - { - // we might need extra lines to separate colors - // at the non closing corners - - // ### As an optimization we could check orientation and colors - // to test whether colors are the same - const int additionalLines = -1 - + bc.left().stops().count() - 1 - + bc.top().stops().count() - 1 - + bc.right().stops().count() - 1 - + bc.bottom().stops().count() - 1; - - borderLineCount += qMax( additionalLines, 0 ); - } - } - } - - auto line = allocateLines< ColoredLine >( geometry, borderLineCount + fillLineCount ); - - if ( fillLineCount > 0 ) - { - const auto& gd = gradient; - - if ( gd.isMonochrome() ) - { - const ColorMapSolid colorMap( gd.startColor() ); - qskCreateFillRandom( QskGradient::Vertical, in, colorMap, line ); - } - else - { - bool fillRandom = gd.stops().count() <= 2; - if ( fillRandom ) - { - /* - Not necessarily a requirement for being ordered, - but we didn't implement a random fill algo for - diagonal gradients yet. - */ - fillRandom = gd.orientation() != QskGradient::Diagonal; - } - - if ( fillRandom ) - { - const ColorMapGradient colorMap( gd.startColor(), gd.endColor() ); - qskCreateFillRandom( gd.orientation(), in, colorMap, line ); - } - else - { - qskCreateFillOrdered( in, gd, line ); - } - } - } - - if ( borderLineCount > 0 ) - { - const auto& bc = borderColors; - auto fillLines = line + fillLineCount; - - if ( bc.isMonochrome() ) - { - const auto rgb = bc.left().startColor().rgba(); - qskCreateBorderMonochrome( rect, in, rgb, fillLines ); - } - else - { - qskCreateBorder( rect, in, bc, fillLines ); - } - } -} - -void QskBoxRenderer::renderRectFill( const QskBoxRenderer::Quad& rect, - const QskGradient& gradient, QskVertex::ColoredLine* line ) -{ - qskCreateFillOrdered( rect, gradient, line ); -} diff --git a/src/nodes/QskBoxShadowNode.cpp b/src/nodes/QskBoxShadowNode.cpp index 44a1af50..17c3a5c5 100644 --- a/src/nodes/QskBoxShadowNode.cpp +++ b/src/nodes/QskBoxShadowNode.cpp @@ -230,12 +230,12 @@ int Material::compare( const QSGMaterial* other ) const { auto material = static_cast< const Material* >( other ); - if ( material->m_color != m_color - || material->m_aspect != m_aspect - || !qFuzzyCompare(material->m_blurExtent, m_blurExtent) - || !qFuzzyCompare(material->m_radius, m_radius) ) + if ( ( material->m_color == m_color ) + && ( material->m_aspect == m_aspect ) + && qFuzzyCompare(material->m_blurExtent, m_blurExtent) + && qFuzzyCompare(material->m_radius, m_radius) ) { - return 1; + return 0; } return QSGMaterial::compare( other ); diff --git a/src/nodes/QskBoxShadowNode.h b/src/nodes/QskBoxShadowNode.h index 56c8c0ca..82caefe4 100644 --- a/src/nodes/QskBoxShadowNode.h +++ b/src/nodes/QskBoxShadowNode.h @@ -7,7 +7,7 @@ #define QSK_BOX_SHADOW_NODE_H #include "QskGlobal.h" -#include +#include class QColor; class QskBoxShapeMetrics; diff --git a/src/nodes/QskColorRamp.cpp b/src/nodes/QskColorRamp.cpp new file mode 100644 index 00000000..9e6ba805 --- /dev/null +++ b/src/nodes/QskColorRamp.cpp @@ -0,0 +1,171 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskColorRamp.h" +#include "QskRgbValue.h" + +QSK_QT_PRIVATE_BEGIN +#include +#include +QSK_QT_PRIVATE_END + +#include + +namespace +{ + class Texture : public QSGPlainTexture + { + public: + Texture( const QskGradientStops& stops, QskGradient::SpreadMode spreadMode ) + { + /* + Qt creates tables of 1024 colors, while Chrome, Firefox, and Android + seem to use 256 colors only ( according to maybe outdated sources + from the internet ), + */ + + const int size = qBound( 256, 2 * stops.count(), 1024 ); + setImage( QskRgb::colorTable( size, stops ) ); + + const auto wrapMode = this->wrapMode( spreadMode ); + + setHorizontalWrapMode( wrapMode ); + setVerticalWrapMode( wrapMode ); + + setFiltering( QSGTexture::Linear ); + }; + + private: + static inline QSGTexture::WrapMode wrapMode( QskGradient::SpreadMode spreadMode ) + { + switch ( spreadMode ) + { + case QskGradient::RepeatSpread: + return QSGTexture::Repeat; + + case QskGradient::ReflectSpread: + return QSGTexture::MirroredRepeat; + + default: + return QSGTexture::ClampToEdge; + } + } + }; + + class HashKey + { + public: + inline bool operator==( const HashKey& other ) const + { + return rhi == other.rhi && spreadMode == other.spreadMode && stops == other.stops; + } + + const void* rhi; + const QskGradientStops stops; + const QskGradient::SpreadMode spreadMode; + }; + + inline size_t qHash( const HashKey& key, size_t seed = 0 ) + { + size_t values = seed + key.spreadMode; + + for ( const auto& stop : key.stops ) + values += stop.rgb(); + + return values; + } + + class Cache + { + public: + ~Cache() { qDeleteAll( m_hashTable ); } + + void cleanupRhi( const QRhi* ); + + Texture* texture( const void* rhi, + const QskGradientStops&, QskGradient::SpreadMode ); + + private: + QHash< HashKey, Texture* > m_hashTable; + QVector< const QRhi* > m_rhiTable; // no QSet: we usually have only one entry + }; + + static Cache* s_cache; +} + +static void qskCleanupCache() +{ + delete s_cache; + s_cache = nullptr; +} + +static void qskCleanupRhi( const QRhi* rhi ) +{ + if ( s_cache ) + s_cache->cleanupRhi( rhi ); +} + +Texture* Cache::texture( const void* rhi, + const QskGradientStops& stops, QskGradient::SpreadMode spreadMode ) +{ + const HashKey key { rhi, stops, spreadMode }; + + auto texture = m_hashTable[key]; + if ( texture == nullptr ) + { + texture = new Texture( stops, spreadMode ); + m_hashTable[ key ] = texture; + + if ( rhi != nullptr ) + { + auto myrhi = ( QRhi* )rhi; + + if ( !m_rhiTable.contains( myrhi ) ) + { + myrhi->addCleanupCallback( qskCleanupRhi ); + m_rhiTable += myrhi; + } + } + } + + return texture; +} + +void Cache::cleanupRhi( const QRhi* rhi ) +{ + for ( auto it = m_hashTable.begin(); it != m_hashTable.end(); ) + { + if ( it.key().rhi == rhi ) + { + delete it.value(); + it = m_hashTable.erase( it ); + } + else + { + ++it; + } + } + + m_rhiTable.removeAll( rhi ); +} + +QSGTexture* QskColorRamp::texture( const void* rhi, + const QskGradientStops& stops, QskGradient::SpreadMode spreadMode ) +{ + if ( s_cache == nullptr ) + { + s_cache = new Cache(); + + /* + For RHI we have QRhi::addCleanupCallback, but with + OpenGL we would have to fiddle around with QOpenGLSharedResource + But as the OpenGL path is only for Qt5 we do not want to spend + much energy on finetuning the resource management. + */ + qAddPostRoutine( qskCleanupCache ); + } + + return s_cache->texture( rhi, stops, spreadMode ); +} diff --git a/src/nodes/QskColorRamp.h b/src/nodes/QskColorRamp.h new file mode 100644 index 00000000..30a6cd8b --- /dev/null +++ b/src/nodes/QskColorRamp.h @@ -0,0 +1,20 @@ +/********************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_COLOR_RAMP_H +#define QSK_COLOR_RAMP_H + +#include "QskGlobal.h" +#include "QskGradient.h" + +class QSGTexture; + +namespace QskColorRamp +{ + QSGTexture* texture( const void* rhi, + const QskGradientStops&, QskGradient::SpreadMode ); +} + +#endif diff --git a/src/nodes/QskGradientMaterial.cpp b/src/nodes/QskGradientMaterial.cpp new file mode 100644 index 00000000..3f23e777 --- /dev/null +++ b/src/nodes/QskGradientMaterial.cpp @@ -0,0 +1,736 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskGradientMaterial.h" +#include "QskFunctions.h" +#include "QskRgbValue.h" +#include "QskGradientDirection.h" +#include "QskColorRamp.h" + +#include + +QSK_QT_PRIVATE_BEGIN +#include +#include +QSK_QT_PRIVATE_END + +#include + +// RHI shaders are supported by Qt 5.15 and Qt 6.x +#define SHADER_RHI + +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) + // Old type of shaders only with Qt 5.x + #define SHADER_GL +#endif + +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) + #include + using RhiShader = QSGMaterialRhiShader; +#else + using RhiShader = QSGMaterialShader; +#endif + +namespace +{ + class GradientMaterial : public QskGradientMaterial + { + public: + GradientMaterial( QskGradient::Type type ) + : QskGradientMaterial( type ) + { + setFlag( Blending | RequiresFullMatrix ); + +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) + setFlag( QSGMaterial::SupportsRhiShader, true ); +#endif + } + + int compare( const QSGMaterial* other ) const override + { + const auto mat = static_cast< const GradientMaterial* >( other ); + + if ( ( spreadMode() == mat->spreadMode() ) && ( stops() == mat->stops() ) ) + return 0; + + return QSGMaterial::compare( other ); + } + +#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) + // make Qt 5/6 APIs matchaing + + QSGMaterialShader* createShader( + QSGRendererInterface::RenderMode ) const override final + { + return createShader(); + } + + virtual QSGMaterialShader* createShader() const = 0; +#endif + + virtual bool setGradient( const QskGradient& ) = 0; + }; + +#ifdef SHADER_GL + + class GradientShaderGL : public QSGMaterialShader + { + public: + void setShaderFiles( const char* name ) + { + static const QString root( ":/qskinny/shaders/" ); + + setShaderSourceFile( QOpenGLShader::Vertex, root + name + ".vert" ); + setShaderSourceFile( QOpenGLShader::Fragment, root + name + ".frag" ); + } + + void initialize() override + { + m_opacityId = program()->uniformLocation( "opacity" ); + m_matrixId = program()->uniformLocation( "matrix" ); + } + + void updateState( const RenderState& state, + QSGMaterial* newMaterial, QSGMaterial* ) override final + { + auto p = program(); + auto material = static_cast< GradientMaterial* >( newMaterial ); + + if ( state.isOpacityDirty() ) + p->setUniformValue( m_opacityId, state.opacity() ); + + if ( state.isMatrixDirty() ) + p->setUniformValue(m_matrixId, state.combinedMatrix() ); + + updateUniformValues( material ); + + auto texture = QskColorRamp::texture( + nullptr, material->stops(), material->spreadMode() ); + texture->bind(); + } + + char const* const* attributeNames() const override final + { + static const char* const attr[] = { "vertexCoord", nullptr }; + return attr; + } + + virtual void updateUniformValues( const GradientMaterial* ) = 0; + + protected: + int m_opacityId = -1; + int m_matrixId = -1; + }; +#endif + +#ifdef SHADER_RHI + class GradientShaderRhi : public RhiShader + { + public: + void setShaderFiles( const char* name ) + { + static const QString root( ":/qskinny/shaders/" ); + + setShaderFileName( VertexStage, root + name + ".vert.qsb" ); + setShaderFileName( FragmentStage, root + name + ".frag.qsb" ); + } + + void updateSampledImage( RenderState& state, int binding, + QSGTexture* textures[], QSGMaterial* newMaterial, QSGMaterial* ) override final + { + if ( binding != 1 ) + return; + + auto material = static_cast< const GradientMaterial* >( newMaterial ); + + auto texture = QskColorRamp::texture( + state.rhi(), material->stops(), material->spreadMode() ); + +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) + texture->updateRhiTexture( state.rhi(), state.resourceUpdateBatch() ); +#else + texture->commitTextureOperations( state.rhi(), state.resourceUpdateBatch() ); +#endif + + textures[0] = texture; + } + }; +#endif +} + +namespace +{ + class LinearMaterial final : public GradientMaterial + { + public: + LinearMaterial() + : GradientMaterial( QskGradient::Linear ) + { + } + + bool setGradient( const QskGradient& gradient ) override + { + bool changed = false; + + if ( gradient.stops() != stops() ) + { + setStops( gradient.stops() ); + changed = true; + } + + /* + When having a gradient, that does not need spreading + we could set QskGradient::PadSpread to potentally reduce + the number of color ramps. TODO ... + */ + + if ( gradient.spreadMode() != spreadMode() ) + { + setSpreadMode( gradient.spreadMode() ); + changed = true; + } + + const auto dir = gradient.linearDirection(); + + const QVector4D vector( dir.x1(), dir.y1(), + dir.x2() - dir.x1(), dir.y2() - dir.y1() ); + + if ( m_gradientVector != vector ) + { + m_gradientVector = vector; + changed = true; + } + + return changed; + } + + QSGMaterialType* type() const override + { + static QSGMaterialType type; + return &type; + } + + int compare( const QSGMaterial* other ) const override + { + const auto mat = static_cast< const LinearMaterial* >( other ); + + if ( m_gradientVector != mat->m_gradientVector ) + return QSGMaterial::compare( other ); + else + return GradientMaterial::compare( other ); + } + + QSGMaterialShader* createShader() const override; + + /* + xy: position + zw: relative to position ( sign matters ) + */ + QVector4D m_gradientVector; + }; + +#ifdef SHADER_GL + class LinearShaderGL final : public GradientShaderGL + { + public: + LinearShaderGL() + { + setShaderFiles( "gradientlinear" ); + } + + void initialize() override + { + GradientShaderGL::initialize(); + m_vectorId = program()->uniformLocation( "vector" ); + } + + void updateUniformValues( const GradientMaterial* newMaterial ) override + { + auto material = static_cast< const LinearMaterial* >( newMaterial ); + program()->setUniformValue( m_vectorId, material->m_gradientVector ); + } + + private: + int m_vectorId = -1; + }; +#endif + +#ifdef SHADER_RHI + class LinearShaderRhi final : public GradientShaderRhi + { + public: + LinearShaderRhi() + { + setShaderFiles( "gradientlinear" ); + } + + bool updateUniformData( RenderState& state, + QSGMaterial* newMaterial, QSGMaterial* oldMaterial ) override + { + auto matNew = static_cast< LinearMaterial* >( newMaterial ); + auto matOld = static_cast< LinearMaterial* >( oldMaterial ); + + Q_ASSERT( state.uniformData()->size() >= 84 ); + + auto data = state.uniformData()->data(); + bool changed = false; + + if ( state.isMatrixDirty() ) + { + const auto matrix = state.combinedMatrix(); + memcpy( data + 0, matrix.constData(), 64 ); + + changed = true; + } + + if ( matOld == nullptr || matNew->m_gradientVector != matOld->m_gradientVector ) + { + memcpy( data + 64, &matNew->m_gradientVector, 16 ); + changed = true; + } + + if ( state.isOpacityDirty() ) + { + const float opacity = state.opacity(); + memcpy( data + 80, &opacity, 4 ); + + changed = true; + } + + return changed; + } + }; +#endif + + QSGMaterialShader* LinearMaterial::createShader() const + { +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) + if ( !( flags() & QSGMaterial::RhiShaderWanted ) ) + return new LinearShaderGL; +#endif + return new LinearShaderRhi; + } +} + +namespace +{ + class RadialMaterial final : public GradientMaterial + { + public: + RadialMaterial() + : GradientMaterial( QskGradient::Radial ) + { + } + + QSGMaterialType* type() const override + { + static QSGMaterialType type; + return &type; + } + + bool setGradient( const QskGradient& gradient ) override + { + bool changed = false; + + if ( gradient.stops() != stops() ) + { + setStops( gradient.stops() ); + changed = true; + } + + if ( gradient.spreadMode() != spreadMode() ) + { + setSpreadMode( gradient.spreadMode() ); + changed = true; + } + + const auto dir = gradient.radialDirection(); + + const QVector2D pos( dir.x(), dir.y() ); + const QVector2D radius( dir.radiusX(), dir.radiusY() ); + + if ( ( pos != m_center ) || ( m_radius != radius ) ) + { + m_center = pos; + m_radius = radius; + + changed = true; + } + + return changed; + } + + int compare( const QSGMaterial* other ) const override + { + const auto mat = static_cast< const RadialMaterial* >( other ); + + if ( ( m_center != mat->m_center ) || ( m_radius != mat->m_radius ) ) + { + return QSGMaterial::compare( other ); + } + else + { + return GradientMaterial::compare( other ); + } + } + + QSGMaterialShader* createShader() const override; + + QVector2D m_center; + QVector2D m_radius; + }; + +#ifdef SHADER_GL + class RadialShaderGL final : public GradientShaderGL + { + public: + RadialShaderGL() + { + setShaderFiles( "gradientradial" ); + } + + void initialize() override + { + GradientShaderGL::initialize(); + + auto p = program(); + + m_centerCoordId = p->uniformLocation( "centerCoord" ); + m_radiusId = p->uniformLocation( "radius" ); + } + + void updateUniformValues( const GradientMaterial* newMaterial ) override + { + auto material = static_cast< const RadialMaterial* >( newMaterial ); + + auto p = program(); + + p->setUniformValue( m_centerCoordId, material->m_center ); + p->setUniformValue( m_radiusId, material->m_radius ); + } + + private: + int m_centerCoordId = -1; + int m_radiusId = -1; + }; +#endif + +#ifdef SHADER_RHI + class RadialShaderRhi final : public GradientShaderRhi + { + public: + RadialShaderRhi() + { + setShaderFiles( "gradientradial" ); + } + + bool updateUniformData( RenderState& state, + QSGMaterial* newMaterial, QSGMaterial* oldMaterial) override + { + auto matNew = static_cast< RadialMaterial* >( newMaterial ); + auto matOld = static_cast< RadialMaterial* >( oldMaterial ); + + Q_ASSERT( state.uniformData()->size() >= 84 ); + + auto data = state.uniformData()->data(); + bool changed = false; + + if ( state.isMatrixDirty() ) + { + const auto matrix = state.combinedMatrix(); + memcpy( data + 0, matrix.constData(), 64 ); + + changed = true; + } + + if ( matOld == nullptr || matNew->m_center != matOld->m_center ) + { + memcpy( data + 64, &matNew->m_center, 8 ); + changed = true; + } + + if ( matOld == nullptr || matNew->m_radius != matOld->m_radius ) + { + memcpy( data + 72, &matNew->m_radius, 8 ); + changed = true; + } + + if ( state.isOpacityDirty() ) + { + const float opacity = state.opacity(); + memcpy( data + 80, &opacity, 4 ); + + changed = true; + } + + return changed; + } + }; +#endif + + QSGMaterialShader* RadialMaterial::createShader() const + { +#ifdef SHADER_GL + if ( !( flags() & QSGMaterial::RhiShaderWanted ) ) + return new RadialShaderGL; +#endif + + return new RadialShaderRhi; + } +} + +namespace +{ + class ConicMaterial final : public GradientMaterial + { + public: + ConicMaterial() + : GradientMaterial( QskGradient::Conic ) + { + } + + QSGMaterialType* type() const override + { + static QSGMaterialType type; + return &type; + } + + bool setGradient( const QskGradient& gradient ) override + { + bool changed = false; + + if ( gradient.stops() != stops() ) + { + setStops( gradient.stops() ); + changed = true; + } + + if ( gradient.spreadMode() != spreadMode() ) + { + setSpreadMode( gradient.spreadMode() ); + changed = true; + } + + const auto dir = gradient.conicDirection(); + + const QVector2D center( dir.x(), dir.y() ); + + if ( center != m_center ) + { + m_center = center; + changed = true; + } + + // Angles as ratio of a rotation + + float start = fmod( dir.startAngle(), 360.0 ) / 360.0; + if ( start < 0.0) + start += 1.0; + + float span; + + if ( dir.spanAngle() >= 360.0 ) + { + span = 1.0; + } + else if ( dir.spanAngle() <= -360.0 ) + { + span = -1.0; + } + else + { + span = fmod( dir.spanAngle(), 360.0 ) / 360.0; + } + + if ( ( start != m_start ) || ( span != m_span ) ) + { + m_start = start; + m_span = span; + + changed = true; + } + + return changed; + } + + int compare( const QSGMaterial* other ) const override + { + const auto mat = static_cast< const ConicMaterial* >( other ); + + if ( ( m_center != mat->m_center ) + || qskFuzzyCompare( m_start, mat->m_start ) + || qskFuzzyCompare( m_span, mat->m_span ) ) + { + return QSGMaterial::compare( other ); + } + + return GradientMaterial::compare( other ); + } + + QSGMaterialShader* createShader() const override; + + QVector2D m_center; + float m_start = 0.0; + float m_span = 1.0; + }; + +#ifdef SHADER_GL + class ConicShaderGL final : public GradientShaderGL + { + public: + ConicShaderGL() + { + setShaderFiles( "gradientconic" ); + } + + void initialize() override + { + GradientShaderGL::initialize(); + + m_centerCoordId = program()->uniformLocation( "centerCoord" ); + m_startId = program()->uniformLocation( "start" ); + m_spanId = program()->uniformLocation( "span" ); + } + + void updateUniformValues( const GradientMaterial* newMaterial ) override + { + auto material = static_cast< const ConicMaterial* >( newMaterial ); + + program()->setUniformValue( m_centerCoordId, material->m_center ); + program()->setUniformValue( m_startId, material->m_start ); + program()->setUniformValue( m_spanId, material->m_span ); + } + + private: + int m_centerCoordId = -1; + int m_startId = -1; + int m_spanId = -1; + }; +#endif + +#ifdef SHADER_RHI + class ConicShaderRhi final : public GradientShaderRhi + { + public: + ConicShaderRhi() + { + setShaderFiles( "gradientconic" ); + } + + bool updateUniformData( RenderState& state, + QSGMaterial* newMaterial, QSGMaterial* oldMaterial ) override + { + auto matNew = static_cast< ConicMaterial* >( newMaterial ); + auto matOld = static_cast< ConicMaterial* >( oldMaterial ); + + Q_ASSERT( state.uniformData()->size() >= 84 ); + + auto data = state.uniformData()->data(); + bool changed = false; + + if ( state.isMatrixDirty() ) + { + const auto matrix = state.combinedMatrix(); + memcpy( data + 0, matrix.constData(), 64 ); + + changed = true; + } + + if ( matOld == nullptr || matNew->m_center != matOld->m_center ) + { + memcpy( data + 64, &matNew->m_center, 8 ); + changed = true; + } + + if ( matOld == nullptr || matNew->m_start != matOld->m_start ) + { + memcpy( data + 72, &matNew->m_start, 4 ); + changed = true; + } + + if ( matOld == nullptr || matNew->m_span != matOld->m_span ) + { + memcpy( data + 76, &matNew->m_span, 4 ); + changed = true; + } + + if ( state.isOpacityDirty() ) + { + const float opacity = state.opacity(); + memcpy( data + 80, &opacity, 4 ); + + changed = true; + } + + return changed; + } + }; +#endif + + QSGMaterialShader* ConicMaterial::createShader() const + { +#ifdef SHADER_GL + if ( !( flags() & QSGMaterial::RhiShaderWanted ) ) + return new ConicShaderGL; +#endif + return new ConicShaderRhi; + } +} + +QskGradientMaterial::QskGradientMaterial( QskGradient::Type type ) + : m_gradientType( type ) +{ +} + +template< typename Material > +inline Material* qskEnsureMaterial( QskGradientMaterial* material ) +{ + if ( material == nullptr ) + material = new Material(); + + return static_cast< Material* >( material ); +} + +bool QskGradientMaterial::updateGradient( const QRectF& rect, const QskGradient& gradient ) +{ + Q_ASSERT( gradient.type() == m_gradientType ); + + if ( gradient.type() == m_gradientType ) + { + switch ( gradient.type() ) + { + case QskGradient::Linear: + case QskGradient::Radial: + case QskGradient::Conic: + { + auto material = static_cast< GradientMaterial* >( this ); + return material->setGradient( gradient.stretchedTo( rect ) ); + } + + default: + qWarning( "Invalid gradient type" ); + } + } + + return false; +} + +QskGradientMaterial* QskGradientMaterial::createMaterial( QskGradient::Type gradientType ) +{ + switch ( gradientType ) + { + case QskGradient::Linear: + return new LinearMaterial(); + + case QskGradient::Radial: + return new RadialMaterial(); + + case QskGradient::Conic: + return new ConicMaterial(); + + default: + return nullptr; + } +} diff --git a/src/nodes/QskGradientMaterial.h b/src/nodes/QskGradientMaterial.h new file mode 100644 index 00000000..fa6f9184 --- /dev/null +++ b/src/nodes/QskGradientMaterial.h @@ -0,0 +1,62 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_GRADIENT_MATERIAL +#define QSK_GRADIENT_MATERIAL + +#include "QskGlobal.h" +#include "QskGradient.h" +#include + +class QSK_EXPORT QskGradientMaterial : public QSGMaterial +{ + public: + static QskGradientMaterial* createMaterial( QskGradient::Type ); + + bool updateGradient( const QRectF&, const QskGradient& ); + QskGradient::Type gradientType() const; + + const QskGradientStops& stops() const; + QskGradient::SpreadMode spreadMode() const; + + protected: + QskGradientMaterial( QskGradient::Type ); + + void setStops( const QskGradientStops& ); + void setSpreadMode( QskGradient::SpreadMode ); + + private: + const QskGradient::Type m_gradientType; + + QskGradientStops m_stops; + QskGradient::SpreadMode m_spreadMode = QskGradient::PadSpread; +}; + +inline QskGradient::Type QskGradientMaterial::gradientType() const +{ + return m_gradientType; +} + +inline void QskGradientMaterial::setStops( const QskGradientStops& stops ) +{ + m_stops = stops; +} + +inline void QskGradientMaterial::setSpreadMode( QskGradient::SpreadMode spreadMode ) +{ + m_spreadMode = spreadMode; +} + +inline const QskGradientStops& QskGradientMaterial::stops() const +{ + return m_stops; +} + +inline QskGradient::SpreadMode QskGradientMaterial::spreadMode() const +{ + return m_spreadMode; +} + +#endif diff --git a/src/nodes/QskRectangleNode.cpp b/src/nodes/QskRectangleNode.cpp new file mode 100644 index 00000000..ca655544 --- /dev/null +++ b/src/nodes/QskRectangleNode.cpp @@ -0,0 +1,175 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskRectangleNode.h" +#include "QskGradient.h" +#include "QskSGNode.h" +#include "QskBoxRenderer.h" +#include "QskBoxShapeMetrics.h" +#include "QskGradientMaterial.h" + +#include +#include + +QSK_QT_PRIVATE_BEGIN +#include +QSK_QT_PRIVATE_END + +Q_GLOBAL_STATIC( QSGVertexColorMaterial, qskMaterialColorVertex ) + +class QskRectangleNodePrivate final : public QSGGeometryNodePrivate +{ + public: + QskRectangleNodePrivate() + : geometry( QSGGeometry::defaultAttributes_ColoredPoint2D(), 0 ) + { + } + + inline void resetValues() + { + rect = QRectF(); + shape = QskBoxShapeMetrics(); + gradientHash = 0; + metricsHash = 0; + } + + QSGGeometry geometry; + + QRectF rect; + QskBoxShapeMetrics shape; + + QskHashValue gradientHash = 0; + QskHashValue metricsHash = 0; + + int gradientType = -1; +}; + +QskRectangleNode::QskRectangleNode() + : QSGGeometryNode( *new QskRectangleNodePrivate ) +{ + Q_D( QskRectangleNode ); + + setFlag( OwnsMaterial, true ); + setGeometry( &d->geometry ); +} + +QskRectangleNode::~QskRectangleNode() +{ +} + +void QskRectangleNode::updateNode( + const QRectF& rect, const QskGradient& gradient ) +{ + updateNode( rect, QskBoxShapeMetrics(), gradient ); +} + +void QskRectangleNode::updateNode( + const QRectF& rect, const QskBoxShapeMetrics& shape, const QskGradient& gradient ) +{ + Q_D( QskRectangleNode ); + + if ( rect.isEmpty() || !gradient.isVisible() ) + { + d->resetValues(); + QskSGNode::resetGeometry( this ); + + return; + } + + const auto effectiveGradient = gradient.effectiveGradient(); + const auto effectiveShape = shape.toAbsolute( rect.size() ); + + const auto gradientHash = effectiveGradient.hash( 54228 ); + const auto metricsHash = effectiveShape.hash( 44564 ); + + const bool dirtyColors = gradientHash != d->gradientHash; + const bool dirtyMetrics = ( rect != d->rect ) || ( metricsHash != d->metricsHash ); + + if ( !( dirtyColors || dirtyMetrics ) ) + return; + + d->gradientHash = gradientHash; + d->metricsHash = metricsHash; + d->rect = rect; + d->shape = effectiveShape; + + if ( QskBox::isGradientSupported( effectiveShape, effectiveGradient ) ) + { + if ( material() != qskMaterialColorVertex ) + { + setMaterial( qskMaterialColorVertex ); + setFlag( OwnsMaterial, false ); + + d->gradientType = -1; + } + + if ( d->geometry.attributeCount() == 1 ) + { + const QSGGeometry g( QSGGeometry::defaultAttributes_ColoredPoint2D(), 0 ); + memcpy( ( void* ) &d->geometry, ( void* ) &g, sizeof( QSGGeometry ) ); + } + } + else + { + if ( material() == qskMaterialColorVertex ) + { + setMaterial( nullptr ); + setFlag( OwnsMaterial, true ); + + d->gradientType = -1; + } + + if ( d->geometry.attributeCount() != 1 ) + { + const QSGGeometry g( QSGGeometry::defaultAttributes_Point2D(), 0 ); + memcpy( ( void* ) &d->geometry, ( void* ) &g, sizeof( QSGGeometry ) ); + } + } + + if ( material() == qskMaterialColorVertex ) + { + /* + Colors are added to the vertices, while the material does + not depend on the gradient at all + */ + if ( dirtyMetrics || dirtyColors ) + { + QskBox::renderBox( rect, + effectiveShape, effectiveGradient, d->geometry ); + + markDirty( QSGNode::DirtyGeometry ); + } + } + else + { + /* + Colors are added by the shaders + + Monochrome gradients or QskGradient::Stops are supported by the + QskBoxRenderer. So we don't need to handle them here. + */ + if ( dirtyMetrics ) + { + QskBox::renderFillGeometry( rect, effectiveShape, d->geometry ); + markDirty( QSGNode::DirtyGeometry ); + } + + // dirtyMetrics: the shader also depends on the target rectangle ! + if ( dirtyColors || dirtyMetrics ) + { + const auto gradientType = effectiveGradient.type(); + + if ( gradientType != d->gradientType ) + { + setMaterial( QskGradientMaterial::createMaterial( gradientType ) ); + d->gradientType = gradientType; + } + + auto mat = static_cast< QskGradientMaterial* >( material() ); + if ( mat->updateGradient( rect, effectiveGradient ) ) + markDirty( QSGNode::DirtyMaterial ); + } + } +} diff --git a/src/nodes/QskRectangleNode.h b/src/nodes/QskRectangleNode.h new file mode 100644 index 00000000..176f2010 --- /dev/null +++ b/src/nodes/QskRectangleNode.h @@ -0,0 +1,34 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_RECTANGLE_NODE_H +#define QSK_RECTANGLE_NODE_H + +#include "QskGlobal.h" +#include + +class QskGradient; +class QskBoxShapeMetrics; +class QskRectangleNodePrivate; + +/* + QskRectangleNode is for rounded rectangles without a border. + Depending on the type of gradient it uses a different + material/geometry combination. + */ +class QSK_EXPORT QskRectangleNode : public QSGGeometryNode +{ + public: + QskRectangleNode(); + ~QskRectangleNode() override; + + void updateNode( const QRectF&, const QskGradient& ); + void updateNode( const QRectF&, const QskBoxShapeMetrics&, const QskGradient& ); + + private: + Q_DECLARE_PRIVATE( QskRectangleNode ) +}; + +#endif diff --git a/src/nodes/QskSGNode.cpp b/src/nodes/QskSGNode.cpp index ce9e9011..30b11785 100644 --- a/src/nodes/QskSGNode.cpp +++ b/src/nodes/QskSGNode.cpp @@ -166,3 +166,18 @@ void QskSGNode::replaceChildNode( delete oldNode; } } + +void QskSGNode::resetGeometry( QSGGeometryNode* node ) +{ + if ( node ) + { + if ( auto g = node->geometry() ) + { + if ( g->vertexCount() > 0 || g->indexCount() > 0 ) + { + g->allocate( 0, 0 ); + node->markDirty( QSGNode::DirtyGeometry ); + } + } + } +} diff --git a/src/nodes/QskSGNode.h b/src/nodes/QskSGNode.h index a0c02786..539dc404 100644 --- a/src/nodes/QskSGNode.h +++ b/src/nodes/QskSGNode.h @@ -84,6 +84,8 @@ namespace QskSGNode return static_cast< Node* >( node ); } + + void resetGeometry( QSGGeometryNode* ); } #endif diff --git a/src/nodes/QskShadedBoxNode.cpp b/src/nodes/QskShadedBoxNode.cpp deleted file mode 100644 index 1361cbfb..00000000 --- a/src/nodes/QskShadedBoxNode.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/****************************************************************************** - * QSkinny - Copyright (C) 2016 Uwe Rathmann - * This file may be used under the terms of the QSkinny License, Version 1.0 - *****************************************************************************/ - -#include "QskShadedBoxNode.h" -#include "QskBoxShadowNode.h" -#include "QskShadowMetrics.h" -#include - -QskShadedBoxNode::QskShadedBoxNode() -{ - m_boxNode.setFlag( QSGNode::OwnedByParent, false ); - appendChildNode( &m_boxNode ); -} - -QskShadedBoxNode::~QskShadedBoxNode() -{ -} - -void QskShadedBoxNode::setBoxData( const QRectF& rect, - const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& borderMetrics, - const QskBoxBorderColors& borderColors, const QskGradient& gradient, - const QskShadowMetrics& shadowMetrics, const QColor& shadowColor ) -{ - m_boxNode.setBoxData( rect, shape, borderMetrics, borderColors, gradient ); - - if ( shadowMetrics.isNull() - || !shadowColor.isValid() || shadowColor.alpha() == 0 ) - { - if ( m_shadowNode ) - { - removeChildNode( m_shadowNode ); - delete m_shadowNode; - m_shadowNode = nullptr; - } - } - else - { - if ( m_shadowNode == nullptr ) - { - m_shadowNode = new QskBoxShadowNode(); - insertChildNodeBefore( m_shadowNode, &m_boxNode ); - } - - m_shadowNode->setShadowData( shadowMetrics.shadowRect( rect ), - shape, shadowMetrics.blurRadius(), shadowColor ); - } -} diff --git a/src/nodes/QskShadedBoxNode.h b/src/nodes/QskShadedBoxNode.h deleted file mode 100644 index 3092b112..00000000 --- a/src/nodes/QskShadedBoxNode.h +++ /dev/null @@ -1,31 +0,0 @@ -/****************************************************************************** - * QSkinny - Copyright (C) 2016 Uwe Rathmann - * This file may be used under the terms of the QSkinny License, Version 1.0 - *****************************************************************************/ - -#ifndef QSK_SHADED_BOX_NODE_H -#define QSK_SHADED_BOX_NODE_H - -#include "QskGlobal.h" -#include "QskBoxNode.h" - -class QskBoxShadowNode; -class QskShadowMetrics; - -class QSK_EXPORT QskShadedBoxNode : public QSGNode -{ - public: - QskShadedBoxNode(); - ~QskShadedBoxNode() override; - - void setBoxData( const QRectF&, - const QskBoxShapeMetrics&, const QskBoxBorderMetrics&, - const QskBoxBorderColors&, const QskGradient&, - const QskShadowMetrics&, const QColor& shadowColor ); - - private: - QskBoxNode m_boxNode; - QskBoxShadowNode* m_shadowNode = nullptr; -}; - -#endif diff --git a/src/nodes/QskShapeNode.cpp b/src/nodes/QskShapeNode.cpp new file mode 100644 index 00000000..360dda8f --- /dev/null +++ b/src/nodes/QskShapeNode.cpp @@ -0,0 +1,155 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskShapeNode.h" +#include "QskGradientMaterial.h" +#include "QskGradient.h" +#include "QskGradientDirection.h" +#include "QskSGNode.h" + +#include + +QSK_QT_PRIVATE_BEGIN +#include +#include +#include +QSK_QT_PRIVATE_END + +static void qskUpdateGeometry( const QPainterPath& path, + const QTransform& transform, QSGGeometry& geometry ) +{ + const auto ts = qTriangulate( path, transform, 1, false ); + +#if 1 + geometry.allocate( ts.vertices.size(), ts.indices.size() ); + + auto vertexData = reinterpret_cast< float* >( geometry.vertexData() ); + const auto points = ts.vertices.constData(); + + for ( int i = 0; i < ts.vertices.count(); i++ ) + vertexData[i] = points[i]; + + memcpy( geometry.indexData(), ts.indices.data(), + ts.indices.size() * sizeof( quint16 ) ); +#else + /* + As we have to iterate over the vertex buffer to copy qreal to float + anyway we could reorder according to the index buffer and drop + the index buffer then ??? + + QTriangleSet: + + vertices: (x[i[n]], y[i[n]]), (x[j[n]], y[j[n]]), (x[k[n]], y[k[n]]), n = 0, 1, ... + QVector vertices; // [x[0], y[0], x[1], y[1], x[2], ...] + QVector indices; // [i[0], j[0], k[0], i[1], j[1], k[1], i[2], ...] + */ + const auto points = ts.vertices.constData(); + const auto indices = reinterpret_cast< const quint16* >( ts.indices.data() ); + + geometry.allocate( ts.indices.size() ); + + auto vertexData = geometry.vertexDataAsPoint2D(); + for ( int i = 0; i < ts.indices.size(); i++ ) + { + const int j = 2 * indices[i]; + vertexData[i].set( points[j], points[j + 1] ); + } +#endif +} + +class QskShapeNodePrivate final : public QSGGeometryNodePrivate +{ + public: + QskShapeNodePrivate() + : geometry( QSGGeometry::defaultAttributes_Point2D(), 0 ) + { + geometry.setDrawingMode( QSGGeometry::DrawTriangles ); + } + + QSGGeometry geometry; + int gradientType = -1; + + /* + Is there a better way to find out if the path has changed + beside storing a copy ( even, when internally with Copy On Write ) ? + */ + QPainterPath path; + QTransform transform; +}; + +QskShapeNode::QskShapeNode() + : QSGGeometryNode( *new QskShapeNodePrivate ) +{ + Q_D( QskShapeNode ); + + setGeometry( &d->geometry ); + setMaterial( new QSGFlatColorMaterial() ); + setFlag( QSGNode::OwnsMaterial, true ); +} + +void QskShapeNode::updateNode( const QPainterPath& path, + const QTransform& transform, const QRectF& rect, const QskGradient& gradient ) +{ + Q_D( QskShapeNode ); + + if ( path.isEmpty() || !gradient.isVisible() ) + { + d->path = QPainterPath(); + d->transform = QTransform(); + QskSGNode::resetGeometry( this ); + + return; + } + + if ( ( transform != d->transform ) || ( path != d->path ) ) + { + d->path = path; + d->transform = transform; + + qskUpdateGeometry( path, transform, d->geometry ); + markDirty( QSGNode::DirtyGeometry ); + } + + if ( gradient.isMonochrome() ) + { + if ( material() == nullptr || d->gradientType >= 0 ) + { + setMaterial( new QSGFlatColorMaterial() ); + d->gradientType = -1; + } + + const auto color = gradient.startColor().toRgb(); + + /* + We might want to use QSGVertexColorMaterial to improve the "batchability" + as this material does not depend on the specific colors. It could even be + batched with QskBoxNodes, that are usually using QSGVertexColorMaterial as well. + + However we would have to store the color information for each vertex. + For the moment we prefer less memory over better "batchability". + */ + auto mat = static_cast< QSGFlatColorMaterial* >( material() ); + if ( mat->color() != color ) + { + mat->setColor( color ); + markDirty( QSGNode::DirtyMaterial ); + } + } + else + { + const auto effectiveGradient = gradient.effectiveGradient(); + const auto gradientType = effectiveGradient.type(); + + if ( ( material() == nullptr ) || ( gradientType != d->gradientType ) ) + { + setMaterial( QskGradientMaterial::createMaterial( gradientType ) ); + d->gradientType = gradientType; + } + + auto mat = static_cast< QskGradientMaterial* >( material() ); + if ( mat->updateGradient( rect, effectiveGradient ) ) + markDirty( QSGNode::DirtyMaterial ); + } +} diff --git a/src/nodes/QskShapeNode.h b/src/nodes/QskShapeNode.h new file mode 100644 index 00000000..7f23e144 --- /dev/null +++ b/src/nodes/QskShapeNode.h @@ -0,0 +1,30 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_SHAPE_NODE_H +#define QSK_SHAPE_NODE_H + +#include "QskGlobal.h" +#include + +class QskGradient; +class QColor; +class QPainterPath; + +class QskShapeNodePrivate; + +class QSK_EXPORT QskShapeNode : public QSGGeometryNode +{ + public: + QskShapeNode(); + + void updateNode( const QPainterPath&, const QTransform&, + const QRectF&, const QskGradient& ); + + private: + Q_DECLARE_PRIVATE( QskShapeNode ) +}; + +#endif diff --git a/src/nodes/QskStrokeNode.cpp b/src/nodes/QskStrokeNode.cpp new file mode 100644 index 00000000..22dead29 --- /dev/null +++ b/src/nodes/QskStrokeNode.cpp @@ -0,0 +1,109 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskStrokeNode.h" +#include + +QSK_QT_PRIVATE_BEGIN +#include +#include +QSK_QT_PRIVATE_END + +class QskStrokeNodePrivate final : public QSGGeometryNodePrivate +{ + public: + QskStrokeNodePrivate() + : geometry( QSGGeometry::defaultAttributes_Point2D(), 0 ) + { + geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); + } + + QSGGeometry geometry; + QSGFlatColorMaterial material; +}; + +QskStrokeNode::QskStrokeNode() + : QSGGeometryNode( *new QskStrokeNodePrivate ) +{ + Q_D( QskStrokeNode ); + + setGeometry( &d->geometry ); + setMaterial( &d->material ); +} + +void QskStrokeNode::updateNode( + const QPainterPath& path, const QTransform& transform, const QPen& pen ) +{ + Q_D( QskStrokeNode ); + + if ( path.isEmpty() || ( pen.style() == Qt::NoPen ) || + !pen.color().isValid() || ( pen.color().alpha() == 0 ) ) + { + if ( d->geometry.vertexCount() > 0 ) + { + d->geometry.allocate( 0 ); + markDirty( QSGNode::DirtyGeometry ); + } + + return; + } + + if ( true ) // For the moment we always update the geometry. TODO ... + { + /* + Unfortunately QTriangulatingStroker does not offer on the fly + transformations - like with qTriangulate. TODO ... + */ + const auto scaledPath = transform.map( path ); + + auto effectivePen = pen; + + if ( !effectivePen.isCosmetic() ) + { + const auto scaleFactor = qMin( transform.m11(), transform.m22() ); + if ( scaleFactor != 1.0 ) + { + effectivePen.setWidth( effectivePen.widthF() * scaleFactor ); + effectivePen.setCosmetic( false ); + } + } + + QTriangulatingStroker stroker; + + if ( pen.style() == Qt::SolidLine ) + { + // clipRect, renderHint are ignored in QTriangulatingStroker::process + stroker.process( qtVectorPathForPath( scaledPath ), effectivePen, {}, {} ); + } + else + { + constexpr QRectF clipRect; // empty rect: no clipping + + QDashedStrokeProcessor dashStroker; + dashStroker.process( qtVectorPathForPath( scaledPath ), effectivePen, clipRect, {} ); + + const QVectorPath dashedVectorPath( dashStroker.points(), + dashStroker.elementCount(), dashStroker.elementTypes(), 0 ); + + stroker.process( dashedVectorPath, effectivePen, {}, {} ); + } + + // 2 vertices for each point + d->geometry.allocate( stroker.vertexCount() / 2 ); + + memcpy( d->geometry.vertexData(), stroker.vertices(), + stroker.vertexCount() * sizeof( float ) ); + + markDirty( QSGNode::DirtyGeometry ); + } + + const auto color = pen.color().toRgb(); + + if ( d->material.color() != color ) + { + d->material.setColor( color ); + markDirty( QSGNode::DirtyMaterial ); + } +} diff --git a/src/nodes/QskStrokeNode.h b/src/nodes/QskStrokeNode.h new file mode 100644 index 00000000..4e922aed --- /dev/null +++ b/src/nodes/QskStrokeNode.h @@ -0,0 +1,28 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_STROKE_NODE_H +#define QSK_STROKE_NODE_H + +#include "QskGlobal.h" +#include + +class QPen; +class QPainterPath; + +class QskStrokeNodePrivate; + +class QSK_EXPORT QskStrokeNode : public QSGGeometryNode +{ + public: + QskStrokeNode(); + + void updateNode( const QPainterPath&, const QTransform&, const QPen& ); + + private: + Q_DECLARE_PRIVATE( QskStrokeNode ) +}; + +#endif diff --git a/src/nodes/QskVertex.h b/src/nodes/QskVertex.h index 0700d0ae..96bd9bf7 100644 --- a/src/nodes/QskVertex.h +++ b/src/nodes/QskVertex.h @@ -10,10 +10,12 @@ #include #include +#include +#include namespace QskVertex { - class QSK_EXPORT Color + class Color { public: constexpr Color() noexcept; @@ -32,75 +34,6 @@ namespace QskVertex unsigned char r, g, b, a; }; - class QSK_EXPORT Line - { - public: - inline void setLine( float x1, float y1, float x2, float y2 ) noexcept - { - p1.set( x1, y1 ); - p2.set( x2, y2 ); - } - - inline void setHLine( float x1, float x2, float y ) noexcept - { - setLine( x1, y, x2, y ); - } - - inline void setVLine( float x, float y1, float y2 ) noexcept - { - setLine( x, y1, x, y2 ); - } - - inline void setLine( float x1, float y1, float x2, float y2, Color ) noexcept - { - /* The color parameter makes no sense, but is useful - when being using from templated code - */ - setLine( x1, y1, x2, y2 ); - } - - QSGGeometry::Point2D p1; - QSGGeometry::Point2D p2; - }; - - class QSK_EXPORT ColoredLine - { - public: - inline void setLine( float x1, float y1, Color c1, - float x2, float y2, Color c2 ) noexcept - { - p1.set( x1, y1, c1.r, c1.g, c1.b, c1.a ); - p2.set( x2, y2, c2.r, c2.g, c2.b, c2.a ); - } - - inline void setLine( float x1, float y1, float x2, float y2, Color color ) noexcept - { - setLine( x1, y1, color, x2, y2, color ); - } - - inline void setHLine( qreal x1, qreal x2, qreal y, Color color ) noexcept - { - setLine( x1, y, color, x2, y, color ); - } - - inline void setVLine( qreal x, qreal y1, qreal y2, Color color ) noexcept - { - setLine( x, y1, color, x, y2, color ); - } - - QSGGeometry::ColoredPoint2D p1; - QSGGeometry::ColoredPoint2D p2; - }; - - template< class Line > - static inline Line* allocateLines( QSGGeometry& geometry, int lineCount ) - { - geometry.allocate( 2 * lineCount ); // 2 points per line - return reinterpret_cast< Line* >( geometry.vertexData() ); - } - - void QSK_EXPORT debugGeometry( const QSGGeometry& ); - inline constexpr Color::Color() noexcept : r( 0 ) , g( 0 ) @@ -167,11 +100,219 @@ namespace QskVertex } } +namespace QskVertex +{ + class Line + { + public: + inline void setLine( float x1, float y1, float x2, float y2 ) noexcept + { + p1.set( x1, y1 ); + p2.set( x2, y2 ); + } + + inline void setHLine( float x1, float x2, float y ) noexcept + { + setLine( x1, y, x2, y ); + } + + inline void setVLine( float x, float y1, float y2 ) noexcept + { + setLine( x, y1, x, y2 ); + } + + inline void setLine( float x1, float y1, float x2, float y2, Color ) noexcept + { + /* The color parameter makes no sense, but is useful + when being using from templated code + */ + setLine( x1, y1, x2, y2 ); + } + + QSGGeometry::Point2D p1; + QSGGeometry::Point2D p2; + }; + + class ColoredLine + { + public: + inline void setLine( float x1, float y1, Color c1, + float x2, float y2, Color c2 ) noexcept + { + p1.set( x1, y1, c1.r, c1.g, c1.b, c1.a ); + p2.set( x2, y2, c2.r, c2.g, c2.b, c2.a ); + } + + inline void setLine( float x1, float y1, float x2, float y2, Color color ) noexcept + { + setLine( x1, y1, color, x2, y2, color ); + } + + inline void setLine( const QPointF& p1, const QPointF& p2, Color color ) noexcept + { + setLine( p1.x(), p1.y(), color, p2.x(), p2.y(), color ); + } + + inline void setLine( const QLineF& line, Color color ) noexcept + { + setLine( line.x1(), line.y1(), color, line.x2(), line.y2(), color ); + } + + inline void setHLine( qreal x1, qreal x2, qreal y, Color color ) noexcept + { + setLine( x1, y, color, x2, y, color ); + } + + inline void setVLine( qreal x, qreal y1, qreal y2, Color color ) noexcept + { + setLine( x, y1, color, x, y2, color ); + } + + QSGGeometry::ColoredPoint2D p1; + QSGGeometry::ColoredPoint2D p2; + }; + + template< class Line > + static inline Line* allocateLines( QSGGeometry& geometry, int lineCount ) + { + geometry.allocate( 2 * lineCount ); // 2 points per line + return reinterpret_cast< Line* >( geometry.vertexData() ); + } +} + +namespace QskVertex +{ + class ArcIterator + { + public: + inline ArcIterator() = default; + + inline ArcIterator( int stepCount, bool inverted = false ) + { + reset( stepCount, inverted ); + } + + void reset( int stepCount, bool inverted = false ) + { + m_inverted = inverted; + + if ( inverted ) + { + m_cos = 1.0; + m_sin = 0.0; + } + else + { + m_cos = 0.0; + m_sin = 1.0; + } + + m_stepIndex = 0; + m_stepCount = stepCount; + + const double angleStep = M_PI_2 / stepCount; + m_cosStep = qFastCos( angleStep ); + m_sinStep = qFastSin( angleStep ); + } + + inline bool isInverted() const { return m_inverted; } + + inline double cos() const { return m_cos; } + inline double sin() const { return m_inverted ? -m_sin : m_sin; } + + inline int step() const { return m_stepIndex; } + inline int stepCount() const { return m_stepCount; } + inline bool isDone() const { return m_stepIndex > m_stepCount; } + + inline void increment() + { + if ( ++m_stepIndex >= m_stepCount ) + { + if ( m_stepIndex == m_stepCount ) + { + /* + Doubles are not numerical stable and the small errors, + sum up when iterating in steps. To avoid having to deal with + fuzzy compares we manually fix cos/sin at the end. + */ + if ( m_inverted ) + { + m_cos = 0.0; + m_sin = -1.0; + } + else + { + m_cos = 1.0; + m_sin = 0.0; + } + } + } + else + { + const double cos0 = m_cos; + + m_cos = m_cos * m_cosStep + m_sin * m_sinStep; + m_sin = m_sin * m_cosStep - cos0 * m_sinStep; + } + } + + inline void decrement() + { + revert(); + increment(); + revert(); + } + + inline void operator++() { increment(); } + + static int segmentHint( double radius ) + { + const double arcLength = radius * M_PI_2; + return qBound( 3, qCeil( arcLength / 3.0 ), 18 ); // every 3 pixels + } + + inline void revert() + { + m_inverted = !m_inverted; + m_stepIndex = m_stepCount - m_stepIndex; + + m_sin = -m_sin; + } + + ArcIterator reverted() const + { + ArcIterator it = *this; + it.revert(); + + return it; + } + + private: + double m_cos; + double m_sin; + + int m_stepIndex; + double m_cosStep; + double m_sinStep; + + int m_stepCount; + bool m_inverted; + }; +} + +namespace QskVertex +{ + void debugGeometry( const QSGGeometry& ); +} + #ifndef QT_NO_DEBUG_STREAM -class QDebug; -QDebug operator<<( QDebug debug, QskVertex::Color ); -QDebug operator<<( QDebug debug, const QskVertex::ColoredLine& ); -QDebug operator<<( QDebug debug, const QskVertex::Line& ); + + class QDebug; + + QDebug operator<<( QDebug debug, QskVertex::Color ); + QDebug operator<<( QDebug debug, const QskVertex::ColoredLine& ); + QDebug operator<<( QDebug debug, const QskVertex::Line& ); + #endif #endif diff --git a/src/nodes/shaders.qrc b/src/nodes/shaders.qrc index dab52798..0301b991 100644 --- a/src/nodes/shaders.qrc +++ b/src/nodes/shaders.qrc @@ -1,9 +1,26 @@ + shaders/boxshadow.vert.qsb shaders/boxshadow.frag.qsb shaders/boxshadow.vert shaders/boxshadow.frag + + shaders/gradientconic.vert.qsb + shaders/gradientconic.frag.qsb + shaders/gradientconic.vert + shaders/gradientconic.frag + + shaders/gradientradial.vert.qsb + shaders/gradientradial.frag.qsb + shaders/gradientradial.vert + shaders/gradientradial.frag + + shaders/gradientlinear.vert.qsb + shaders/gradientlinear.frag.qsb + shaders/gradientlinear.vert + shaders/gradientlinear.frag + diff --git a/src/nodes/shaders/boxshadow-vulkan.frag b/src/nodes/shaders/boxshadow-vulkan.frag index 06a8df1a..a5d5451e 100644 --- a/src/nodes/shaders/boxshadow-vulkan.frag +++ b/src/nodes/shaders/boxshadow-vulkan.frag @@ -1,9 +1,9 @@ #version 440 -layout(location = 0) in vec2 coord; -layout(location = 0) out vec4 fragColor; +layout( location = 0 ) in vec2 coord; +layout( location = 0 ) out vec4 fragColor; -layout(std140, binding = 0) uniform buf +layout( std140, binding = 0 ) uniform buf { mat4 matrix; vec4 color; diff --git a/src/nodes/shaders/boxshadow-vulkan.vert b/src/nodes/shaders/boxshadow-vulkan.vert index 1d802d81..3a308060 100644 --- a/src/nodes/shaders/boxshadow-vulkan.vert +++ b/src/nodes/shaders/boxshadow-vulkan.vert @@ -1,11 +1,11 @@ #version 440 -layout(location = 0) in vec4 in_vertex; -layout(location = 1) in vec2 in_coord; +layout( location = 0 ) in vec4 in_vertex; +layout( location = 1 ) in vec2 in_coord; -layout(location = 0) out vec2 coord; +layout( location = 0 ) out vec2 coord; -layout(std140, binding = 0) uniform buf +layout( std140, binding = 0 ) uniform buf { mat4 matrix; vec4 color; diff --git a/src/nodes/shaders/boxshadow.frag.qsb b/src/nodes/shaders/boxshadow.frag.qsb index 088e7513..67bd10ec 100644 Binary files a/src/nodes/shaders/boxshadow.frag.qsb and b/src/nodes/shaders/boxshadow.frag.qsb differ diff --git a/src/nodes/shaders/boxshadow.vert.qsb b/src/nodes/shaders/boxshadow.vert.qsb index ae721f89..3f9a0c90 100644 Binary files a/src/nodes/shaders/boxshadow.vert.qsb and b/src/nodes/shaders/boxshadow.vert.qsb differ diff --git a/src/nodes/shaders/gradientconic-vulkan.frag b/src/nodes/shaders/gradientconic-vulkan.frag new file mode 100644 index 00000000..526e224d --- /dev/null +++ b/src/nodes/shaders/gradientconic-vulkan.frag @@ -0,0 +1,32 @@ +#version 440 + +layout( location = 0 ) in vec2 coord; +layout( location = 0 ) out vec4 fragColor; + +layout( std140, binding = 0 ) uniform buf +{ + mat4 matrix; + vec2 centerCoord; + float start; + float span; + float opacity; +} ubuf; + +layout( binding = 1 ) uniform sampler2D colorRamp; + +vec4 colorAt( highp float value ) +{ + return texture( colorRamp, vec2( value, 0.0 ) ); +} + +void main() +{ + /* + angles as ratio of a rotation: + start: [ 0.0, 1.0 [ + span: ] -1.0, 1.0 [ + */ + + float v = sign( ubuf.span ) * ( atan( -coord.y, coord.x ) / 6.2831853 - ubuf.start ); + fragColor = colorAt( ( v - floor( v ) ) / abs( ubuf.span ) ) * ubuf.opacity; +} diff --git a/src/nodes/shaders/gradientconic-vulkan.vert b/src/nodes/shaders/gradientconic-vulkan.vert new file mode 100644 index 00000000..c23fdebb --- /dev/null +++ b/src/nodes/shaders/gradientconic-vulkan.vert @@ -0,0 +1,21 @@ +#version 440 + +layout( location = 0 ) in vec4 vertexCoord; +layout( location = 0 ) out vec2 coord; + +layout( std140, binding = 0 ) uniform buf +{ + mat4 matrix; + vec2 centerCoord; + float start; + float span; + float opacity; +} ubuf; + +out gl_PerVertex { vec4 gl_Position; }; + +void main() +{ + coord = vertexCoord.xy - ubuf.centerCoord; + gl_Position = ubuf.matrix * vertexCoord; +} diff --git a/src/nodes/shaders/gradientconic.frag b/src/nodes/shaders/gradientconic.frag new file mode 100644 index 00000000..b131e165 --- /dev/null +++ b/src/nodes/shaders/gradientconic.frag @@ -0,0 +1,24 @@ +uniform sampler2D colorRamp; +uniform lowp float opacity; + +uniform highp float start; +uniform highp float span; + +varying highp vec2 coord; + +lowp vec4 colorAt( highp float value ) +{ + return texture2D( colorRamp, vec2( value, 0.0 ) ); +} + +void main() +{ + /* + angles as ratio of a rotation: + start: [ 0.0, 1.0 [ + span: ] -1.0, 1.0 [ + */ + + highp float v = sign( span ) * ( atan( -coord.y, coord.x ) / 6.2831853 - start ); + gl_FragColor = colorAt( ( v - floor( v ) ) / abs( span ) ) * opacity; +} diff --git a/src/nodes/shaders/gradientconic.frag.qsb b/src/nodes/shaders/gradientconic.frag.qsb new file mode 100644 index 00000000..3e75c1ac Binary files /dev/null and b/src/nodes/shaders/gradientconic.frag.qsb differ diff --git a/src/nodes/shaders/gradientconic.vert b/src/nodes/shaders/gradientconic.vert new file mode 100644 index 00000000..53b3dbf4 --- /dev/null +++ b/src/nodes/shaders/gradientconic.vert @@ -0,0 +1,12 @@ +attribute vec4 vertexCoord; + +uniform mat4 matrix; +uniform vec2 centerCoord; + +varying vec2 coord; + +void main() +{ + coord = vertexCoord.xy - centerCoord; + gl_Position = matrix * vertexCoord; +} diff --git a/src/nodes/shaders/gradientconic.vert.qsb b/src/nodes/shaders/gradientconic.vert.qsb new file mode 100644 index 00000000..abf4cd74 Binary files /dev/null and b/src/nodes/shaders/gradientconic.vert.qsb differ diff --git a/src/nodes/shaders/gradientlinear-vulkan.frag b/src/nodes/shaders/gradientlinear-vulkan.frag new file mode 100644 index 00000000..e9347c7a --- /dev/null +++ b/src/nodes/shaders/gradientlinear-vulkan.frag @@ -0,0 +1,23 @@ +#version 440 + +layout( location = 0 ) in float colorIndex; +layout( location = 0 ) out vec4 fragColor; + +layout( std140, binding = 0 ) uniform buf +{ + mat4 matrix; + vec4 vector; + float opacity; +} ubuf; + +layout( binding = 1 ) uniform sampler2D colorRamp; + +vec4 colorAt( float value ) +{ + return texture( colorRamp, vec2( value, 0.0 ) ); +} + +void main() +{ + fragColor = colorAt( colorIndex ) * ubuf.opacity; +} diff --git a/src/nodes/shaders/gradientlinear-vulkan.vert b/src/nodes/shaders/gradientlinear-vulkan.vert new file mode 100644 index 00000000..5e9f3ca9 --- /dev/null +++ b/src/nodes/shaders/gradientlinear-vulkan.vert @@ -0,0 +1,22 @@ +#version 440 + +layout( location = 0 ) in vec4 vertexCoord; +layout( location = 0 ) out float colorIndex; + +layout( std140, binding = 0 ) uniform buf +{ + mat4 matrix; + vec4 vector; + float opacity; +} ubuf; + +out gl_PerVertex { vec4 gl_Position; }; + +void main() +{ + vec2 pos = vertexCoord.xy - ubuf.vector.xy; + vec2 span = ubuf.vector.zw; + + colorIndex = dot( pos, span ) / dot( span, span ); + gl_Position = ubuf.matrix * vertexCoord; +} diff --git a/src/nodes/shaders/gradientlinear.frag b/src/nodes/shaders/gradientlinear.frag new file mode 100644 index 00000000..d1da259f --- /dev/null +++ b/src/nodes/shaders/gradientlinear.frag @@ -0,0 +1,14 @@ +uniform sampler2D colorRamp; +uniform highp float opacity; + +varying highp float colorIndex; + +lowp vec4 colorAt( float value ) +{ + return texture2D( colorRamp, vec2( value, 0.0 ) ); +} + +void main() +{ + gl_FragColor = colorAt( colorIndex ) * opacity; +} diff --git a/src/nodes/shaders/gradientlinear.frag.qsb b/src/nodes/shaders/gradientlinear.frag.qsb new file mode 100644 index 00000000..87c773bd Binary files /dev/null and b/src/nodes/shaders/gradientlinear.frag.qsb differ diff --git a/src/nodes/shaders/gradientlinear.vert b/src/nodes/shaders/gradientlinear.vert new file mode 100644 index 00000000..729b821b --- /dev/null +++ b/src/nodes/shaders/gradientlinear.vert @@ -0,0 +1,15 @@ +attribute vec4 vertexCoord; + +uniform mat4 matrix; +uniform vec4 vector; + +varying float colorIndex; + +void main() +{ + highp vec2 pos = vertexCoord.xy - vector.xy; + highp vec2 span = vector.zw; + + colorIndex = dot( pos, span ) / dot( span, span ); + gl_Position = matrix * vertexCoord; +} diff --git a/src/nodes/shaders/gradientlinear.vert.qsb b/src/nodes/shaders/gradientlinear.vert.qsb new file mode 100644 index 00000000..ef94ce40 Binary files /dev/null and b/src/nodes/shaders/gradientlinear.vert.qsb differ diff --git a/src/nodes/shaders/gradientradial-vulkan.frag b/src/nodes/shaders/gradientradial-vulkan.frag new file mode 100644 index 00000000..a250e8c7 --- /dev/null +++ b/src/nodes/shaders/gradientradial-vulkan.frag @@ -0,0 +1,24 @@ +#version 440 + +layout( location = 0 ) in vec2 coord; +layout( location = 0 ) out vec4 fragColor; + +layout( std140, binding = 0 ) uniform buf +{ + mat4 matrix; + vec2 centerCoord; + vec2 radius; + float opacity; +} ubuf; + +layout( binding = 1 ) uniform sampler2D colorRamp; + +vec4 colorAt( float value ) +{ + return texture( colorRamp, vec2( value, 0.0 ) ); +} + +void main() +{ + fragColor = colorAt( length( coord / ubuf.radius ) ) * ubuf.opacity; +} diff --git a/src/nodes/shaders/gradientradial-vulkan.vert b/src/nodes/shaders/gradientradial-vulkan.vert new file mode 100644 index 00000000..71c1b2e2 --- /dev/null +++ b/src/nodes/shaders/gradientradial-vulkan.vert @@ -0,0 +1,20 @@ +#version 440 + +layout( location = 0 ) in vec4 vertexCoord; +layout( location = 0 ) out vec2 coord; + +layout( std140, binding = 0 ) uniform buf +{ + mat4 matrix; + vec2 centerCoord; + vec2 radius; + float opacity; +} ubuf; + +out gl_PerVertex { vec4 gl_Position; }; + +void main() +{ + coord = vertexCoord.xy - ubuf.centerCoord; + gl_Position = ubuf.matrix * vertexCoord; +} diff --git a/src/nodes/shaders/gradientradial.frag b/src/nodes/shaders/gradientradial.frag new file mode 100644 index 00000000..3638a17d --- /dev/null +++ b/src/nodes/shaders/gradientradial.frag @@ -0,0 +1,16 @@ +uniform sampler2D colorRamp; +uniform lowp float opacity; + +uniform highp vec2 radius; + +varying highp vec2 coord; + +lowp vec4 colorAt( highp float value ) +{ + return texture2D( colorRamp, vec2( value, 0.0 ) ); +} + +void main() +{ + gl_FragColor = colorAt( length( coord / radius ) ) * opacity; +} diff --git a/src/nodes/shaders/gradientradial.frag.qsb b/src/nodes/shaders/gradientradial.frag.qsb new file mode 100644 index 00000000..c0b5aea2 Binary files /dev/null and b/src/nodes/shaders/gradientradial.frag.qsb differ diff --git a/src/nodes/shaders/gradientradial.vert b/src/nodes/shaders/gradientradial.vert new file mode 100644 index 00000000..53b3dbf4 --- /dev/null +++ b/src/nodes/shaders/gradientradial.vert @@ -0,0 +1,12 @@ +attribute vec4 vertexCoord; + +uniform mat4 matrix; +uniform vec2 centerCoord; + +varying vec2 coord; + +void main() +{ + coord = vertexCoord.xy - centerCoord; + gl_Position = matrix * vertexCoord; +} diff --git a/src/nodes/shaders/gradientradial.vert.qsb b/src/nodes/shaders/gradientradial.vert.qsb new file mode 100644 index 00000000..99fa7614 Binary files /dev/null and b/src/nodes/shaders/gradientradial.vert.qsb differ diff --git a/src/nodes/shaders/vulkan2qsb.sh b/src/nodes/shaders/vulkan2qsb.sh index 032a238d..84f7f389 100755 --- a/src/nodes/shaders/vulkan2qsb.sh +++ b/src/nodes/shaders/vulkan2qsb.sh @@ -1,4 +1,18 @@ #! /bin/sh -qsb --glsl 100es,120,150 --hlsl 50 --msl 12 -b -o boxshadow.vert.qsb boxshadow-vulkan.vert -qsb --glsl 100es,120,150 --hlsl 50 --msl 12 -b -o boxshadow.frag.qsb boxshadow-vulkan.frag +function qsbcompile { + qsbfile=`echo $1 | sed 's/-vulkan//'` + qsb --glsl 100es,120,150 --hlsl 50 --msl 12 -b -o ${qsbfile}.qsb $1 +} + +qsbcompile boxshadow-vulkan.vert +qsbcompile boxshadow-vulkan.frag + +qsbcompile gradientconic-vulkan.vert +qsbcompile gradientconic-vulkan.frag + +qsbcompile gradientradial-vulkan.vert +qsbcompile gradientradial-vulkan.frag + +qsbcompile gradientlinear-vulkan.vert +qsbcompile gradientlinear-vulkan.frag diff --git a/src/src.pro b/src/src.pro index 959b2c36..eed2d401 100644 --- a/src/src.pro +++ b/src/src.pro @@ -26,6 +26,7 @@ HEADERS += \ common/QskFunctions.h \ common/QskGlobal.h \ common/QskGradient.h \ + common/QskGradientDirection.h \ common/QskGradientStop.h \ common/QskHctColor.h \ common/QskIntervalF.h \ @@ -55,6 +56,7 @@ SOURCES += \ common/QskBoxHints.cpp \ common/QskFunctions.cpp \ common/QskGradient.cpp \ + common/QskGradientDirection.cpp \ common/QskGradientStop.cpp \ common/QskHctColor.cpp \ common/QskIntervalF.cpp \ @@ -101,16 +103,25 @@ HEADERS += \ nodes/QskArcRenderer.h \ nodes/QskBoxNode.h \ nodes/QskBoxClipNode.h \ + nodes/QskBoxFillNode.h \ + nodes/QskBoxRectangleNode.h \ nodes/QskBoxRenderer.h \ - nodes/QskBoxRendererColorMap.h \ + nodes/QskBoxMetrics.h \ + nodes/QskBoxBasicStroker.h \ + nodes/QskBoxGradientStroker.h \ + nodes/QskBoxColorMap.h \ nodes/QskBoxShadowNode.h \ + nodes/QskColorRamp.h \ nodes/QskGraphicNode.h \ nodes/QskPaintedNode.h \ nodes/QskPlainTextRenderer.h \ + nodes/QskRectangleNode.h \ nodes/QskRichTextRenderer.h \ nodes/QskScaleRenderer.h \ nodes/QskSGNode.h \ - nodes/QskShadedBoxNode.h \ + nodes/QskStrokeNode.h \ + nodes/QskShapeNode.h \ + nodes/QskGradientMaterial.h \ nodes/QskTextNode.h \ nodes/QskTextRenderer.h \ nodes/QskTextureRenderer.h \ @@ -122,17 +133,24 @@ SOURCES += \ nodes/QskArcRenderer.cpp \ nodes/QskBoxNode.cpp \ nodes/QskBoxClipNode.cpp \ - nodes/QskBoxRendererRect.cpp \ - nodes/QskBoxRendererEllipse.cpp \ - nodes/QskBoxRendererDEllipse.cpp \ + nodes/QskBoxFillNode.cpp \ + nodes/QskBoxRectangleNode.cpp \ + nodes/QskBoxRenderer.cpp \ + nodes/QskBoxMetrics.cpp \ + nodes/QskBoxBasicStroker.cpp \ + nodes/QskBoxGradientStroker.cpp \ nodes/QskBoxShadowNode.cpp \ + nodes/QskColorRamp.cpp \ nodes/QskGraphicNode.cpp \ nodes/QskPaintedNode.cpp \ nodes/QskPlainTextRenderer.cpp \ + nodes/QskRectangleNode.cpp \ nodes/QskRichTextRenderer.cpp \ nodes/QskScaleRenderer.cpp \ nodes/QskSGNode.cpp \ - nodes/QskShadedBoxNode.cpp \ + nodes/QskStrokeNode.cpp \ + nodes/QskShapeNode.cpp \ + nodes/QskGradientMaterial.cpp \ nodes/QskTextNode.cpp \ nodes/QskTextRenderer.cpp \ nodes/QskTextureRenderer.cpp \ diff --git a/support/SkinnyShapeFactory.cpp b/support/SkinnyShapeFactory.cpp index e892aeba..2ce61f4f 100644 --- a/support/SkinnyShapeFactory.cpp +++ b/support/SkinnyShapeFactory.cpp @@ -150,6 +150,23 @@ QPainterPath SkinnyShapeFactory::shapePath( Shape shape, const QSizeF& size ) path.addPolygon( hexagon ); break; } + case Arc: + { + path.arcMoveTo( rect, -60 ); + path.arcTo( rect, -60, 300 ); + + const double w = 0.25 * rect.width(); + const auto r = rect.adjusted( w, w, -w, -w ); + + QPainterPath innerPath; + + innerPath.arcMoveTo( r, 240 ); + innerPath.arcTo( r, 240, -300 ); + + path.connectPath( innerPath ); + + break; + } default: return QPainterPath(); @@ -158,3 +175,5 @@ QPainterPath SkinnyShapeFactory::shapePath( Shape shape, const QSizeF& size ) path.closeSubpath(); return path; } + +#include "moc_SkinnyShapeFactory.cpp" diff --git a/support/SkinnyShapeFactory.h b/support/SkinnyShapeFactory.h index 42794218..f6a36c0e 100644 --- a/support/SkinnyShapeFactory.h +++ b/support/SkinnyShapeFactory.h @@ -10,6 +10,8 @@ namespace SkinnyShapeFactory { + Q_NAMESPACE_EXPORT( SKINNY_EXPORT ) + // a couple of standard painter paths, that can // be used for testing @@ -25,6 +27,7 @@ namespace SkinnyShapeFactory Ring, Star, Hexagon, + Arc, ShapeCount };