diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 7d021744..126abdde 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -175,7 +175,7 @@ jobs: } - { name: "macOS Latest Clang Qt5", - os: macos-latest, + os: macos-13, artifact: "macos_clang.7z", build_type: "Release", cc: "clang", @@ -277,7 +277,8 @@ jobs: - name: Install dependencies on macos if: startsWith(matrix.config.os, 'macos') run: | - brew install p7zip cmake ninja + find /opt/homebrew/ -name EXTERNALLY-MANAGED|xargs rm + brew install cmake ninja ninja --version cmake --version @@ -322,7 +323,7 @@ jobs: - name: Install Qt5 if: endsWith(matrix.config.name, 'Qt5') - uses: jurplel/install-qt-action@v3 + uses: jurplel/install-qt-action@v4 with: version: "5.15.2" target: "desktop" @@ -336,7 +337,7 @@ jobs: - name: Install Qt6 if: endsWith(matrix.config.name, 'Qt6') - uses: jurplel/install-qt-action@v3 + uses: jurplel/install-qt-action@v4 with: version: "6.5.0" target: "desktop" diff --git a/LICENSES b/LICENSES index af85ab72..6c7da6d7 100644 --- a/LICENSES +++ b/LICENSES @@ -19,3 +19,13 @@ b) Cassowary constraint solving algorithm Code: https://github.com/nucleic/kiwi SPDX-License-Identifier: BSD 3-Clause "New" or "Revised" License Copyright (c) 2013, Nucleic Development Team + +c) Material3 Icons + + Code: https://github.com/marella/material-design-icons + SPDX-License-Identifier: Apache License 2.0 + +d) Fluent2 Icons + + Code: https://github.com/microsoft/fluentui-system-icons + SPDX-License-Identifier: MIT License diff --git a/designsystems/fluent2/CMakeLists.txt b/designsystems/fluent2/CMakeLists.txt index 2441647f..4fb73177 100644 --- a/designsystems/fluent2/CMakeLists.txt +++ b/designsystems/fluent2/CMakeLists.txt @@ -9,7 +9,7 @@ set(SOURCES QskFluent2Skin.h QskFluent2Skin.cpp QskFluent2SkinFactory.h QskFluent2SkinFactory.cpp ) -qt_add_resources(SOURCES icons.qrc) +qt_add_resources(SOURCES QskFluent2Icons.qrc) qsk_add_plugin(fluent2skin skins QskFluent2SkinFactory ${SOURCES}) set_target_properties(fluent2skin PROPERTIES DEFINE_SYMBOL QSK_FLUENT2_MAKEDLL ) diff --git a/designsystems/fluent2/QskFluent2Icons.qrc b/designsystems/fluent2/QskFluent2Icons.qrc new file mode 100644 index 00000000..7587fb4c --- /dev/null +++ b/designsystems/fluent2/QskFluent2Icons.qrc @@ -0,0 +1,7 @@ + + + icons/qvg/checkmark.qvg + icons/qvg/chevron_down.qvg + icons/qvg/chevron_up.qvg + + diff --git a/designsystems/fluent2/QskFluent2Skin.cpp b/designsystems/fluent2/QskFluent2Skin.cpp index d52c10c0..3ccb3bc2 100644 --- a/designsystems/fluent2/QskFluent2Skin.cpp +++ b/designsystems/fluent2/QskFluent2Skin.cpp @@ -103,6 +103,13 @@ #include #include +static void qskFluent2InitResources() +{ + Q_INIT_RESOURCE( QskFluent2Icons ); +} + +Q_CONSTRUCTOR_FUNCTION( qskFluent2InitResources ) + namespace Fluent2 { using F = QskFontRole; @@ -412,7 +419,6 @@ void Editor::setupCheckBoxMetrics() setStrutSize( Q::Box, { 20_px, 20_px } ); // 18 + 2*1 border setBoxShape( Q::Box, 4_px ); // adapt to us taking the border into account setBoxBorderMetrics( Q::Box, 1_px ); - setPadding( Q::Box, 5_px ); // "icon size" setFontRole( Q::Text, Fluent2::Body ); } @@ -541,9 +547,9 @@ void Editor::setupComboBoxMetrics() setAlignment( Q::Text, Qt::AlignLeft | Qt::AlignVCenter ); setFontRole( Q::Text, Fluent2::Body ); - setStrutSize( Q::StatusIndicator, 12_px, 12_px ); - setSymbol( Q::StatusIndicator, symbol( "spin-box-arrow-down" ) ); - setSymbol( Q::StatusIndicator | Q::PopupOpen, symbol( "spin-box-arrow-up" ) ); + setStrutSize( Q::StatusIndicator, 16_px, 16_px ); + setSymbol( Q::StatusIndicator, symbol( "chevron_down" ) ); + // setSymbol( Q::StatusIndicator | Q::PopupOpen, symbol( "chevron_up" ) ); // Using Focused (Pressed doesn't exist yet): setBoxBorderMetrics( Q::Panel | Q::Focused, { 1_px, 1_px, 1_px, 2_px } ); @@ -1497,28 +1503,38 @@ void Editor::setupSpinBoxMetrics() { using Q = QskSpinBox; + /* + The F2 NumberBox has 2 modes for the Up/Down panels ( a.k.a Spinner ): + + - compact ( -> QskSpinBox::UpDownControl ) + - inline ( -> QskSpinBox::ButtonsRight ) + + TODO: in compact mode the panels/indicators are blown up, when being hovered + */ setHint( Q::Panel | QskAspect::Style, Q::ButtonsRight ); + setStrutSize( Q::Panel, { -1, 32_px } ); setBoxBorderMetrics( Q::Panel, 1_px ); setBoxShape( Q::Panel, 3_px ); - setPadding( Q::Panel, { 11_px, 0, 11_px, 0 } ); - setAlignment( Q::Text, Qt::AlignLeft ); + setAlignment( Q::Text, Qt::AlignLeft | Qt::AlignVCenter ); setFontRole( Q::Text, Fluent2::Body ); - setPadding( Q::TextPanel, { 11_px, 5_px, 0, 0 } ); + setPadding( Q::TextPanel, { 11_px, 0, 11_px, 0 } ); - setStrutSize( Q::UpPanel, 32_px, 20_px ); - setPadding( Q::UpPanel, { 11_px, 7_px, 11_px, 7_px } ); + for ( auto panel : { Q::UpPanel, Q::DownPanel } ) + setStrutSize( panel, 32_px, 18_px ); - setStrutSize( Q::DownPanel, 34_px, 20_px ); - setPadding( Q::DownPanel, { 11_px, 7_px, 13_px, 7_px } ); + setSymbol( Q::UpIndicator, symbol( "chevron_up" ) ); + setSymbol( Q::DownIndicator, symbol( "chevron_down" ) ); - setSymbol( Q::UpIndicator, symbol( "spin-box-arrow-up" ) ); - setSymbol( Q::DownIndicator, symbol( "spin-box-arrow-down" ) ); + setPadding( Q::UpPanel, { 0, 1_px, 0, 0 } ); + setPadding( Q::DownPanel, { 0, 0, 0, 1_px } ); - // Focused (Pressed doesn't exist yet): - setBoxBorderMetrics( Q::Panel | Q::Focused, { 1_px, 1_px, 1_px, 2_px } ); +#if 0 + // QskSpinBox::Pressed is missing yet + setBoxBorderMetrics( Q::Panel | Q::Pressed, { 1_px, 1_px, 1_px, 2_px } ); +#endif } void Editor::setupSpinBoxColors( @@ -1581,8 +1597,17 @@ void Editor::setupSpinBoxColors( panelColor = rgbSolid( panelColor, pal.background.solid.base ); setGradient( panel, panelColor ); + setBoxBorderGradient( panel, borderColor1, borderColor2, panelColor ); + if ( state == Q::Focused ) + { + const auto colors = boxBorderColors( panel ); + + setBoxBorderColors( panel | Q::Decreasing, colors ); + setBoxBorderColors( panel | Q::Increasing, colors ); + } + setColor( text, textColor ); setGraphicRole( upIndicator, graphicRole ); diff --git a/designsystems/fluent2/icons.qrc b/designsystems/fluent2/icons.qrc deleted file mode 100644 index 4e322fd8..00000000 --- a/designsystems/fluent2/icons.qrc +++ /dev/null @@ -1,10 +0,0 @@ - - - icons/qvg/checkmark.qvg - icons/qvg/combo-box-arrow-closed.qvg - icons/qvg/combo-box-arrow-open.qvg - icons/qvg/segmented-button-check.qvg - icons/qvg/spin-box-arrow-down.qvg - icons/qvg/spin-box-arrow-up.qvg - - diff --git a/designsystems/fluent2/icons/README b/designsystems/fluent2/icons/README new file mode 100644 index 00000000..a45cea97 --- /dev/null +++ b/designsystems/fluent2/icons/README @@ -0,0 +1,10 @@ +SVGs have been taken from https://github.com/microsoft/fluentui-system-icons/tree/main/assets. + +Icons are available in different sizes. As SVGs can be scaled we only need +one version of them - chosing the '12'. + +As we are replacing the colors of the SVGs using graphic filters we set the +color in the SVGs manually to black ( instead of #212121 ). So they are in +line with icons coming from somewhere else. + +Names have been shortened ( ic_fluent_xyz_16_regular.svg -> xyz.svg ) diff --git a/designsystems/fluent2/icons/checkmark.svg b/designsystems/fluent2/icons/checkmark.svg index 70b15e35..f87de9aa 100644 --- a/designsystems/fluent2/icons/checkmark.svg +++ b/designsystems/fluent2/icons/checkmark.svg @@ -1,4 +1,3 @@ - - + + - diff --git a/designsystems/fluent2/icons/chevron_down.svg b/designsystems/fluent2/icons/chevron_down.svg new file mode 100644 index 00000000..8ac9ed11 --- /dev/null +++ b/designsystems/fluent2/icons/chevron_down.svg @@ -0,0 +1,3 @@ + + + diff --git a/designsystems/fluent2/icons/chevron_up.svg b/designsystems/fluent2/icons/chevron_up.svg new file mode 100644 index 00000000..28426e1d --- /dev/null +++ b/designsystems/fluent2/icons/chevron_up.svg @@ -0,0 +1,3 @@ + + + diff --git a/designsystems/fluent2/icons/combo-box-arrow-closed.svg b/designsystems/fluent2/icons/combo-box-arrow-closed.svg deleted file mode 100644 index c288b426..00000000 --- a/designsystems/fluent2/icons/combo-box-arrow-closed.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/designsystems/fluent2/icons/combo-box-arrow-open.svg b/designsystems/fluent2/icons/combo-box-arrow-open.svg deleted file mode 100644 index 4138c2cd..00000000 --- a/designsystems/fluent2/icons/combo-box-arrow-open.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/designsystems/fluent2/icons/icon2qvg.sh b/designsystems/fluent2/icons/icon2qvg.sh new file mode 100755 index 00000000..c562ca17 --- /dev/null +++ b/designsystems/fluent2/icons/icon2qvg.sh @@ -0,0 +1,15 @@ +#! /bin/sh + +if [ $# -eq 0 ]; then + echo "Usage $0 file ..." + exit 1 +fi + +for file in $* +do + base=`basename -s .svg $file` + echo "${base}.svg -> qvg/${base}.qvg" + svg2qvg ${base}.svg qvg/${base}.qvg +done + +exit $status diff --git a/designsystems/fluent2/icons/qvg/checkmark.qvg b/designsystems/fluent2/icons/qvg/checkmark.qvg index dc894a09..40ab0c75 100644 Binary files a/designsystems/fluent2/icons/qvg/checkmark.qvg and b/designsystems/fluent2/icons/qvg/checkmark.qvg differ diff --git a/designsystems/fluent2/icons/qvg/chevron_down.qvg b/designsystems/fluent2/icons/qvg/chevron_down.qvg new file mode 100644 index 00000000..a8d90d49 Binary files /dev/null and b/designsystems/fluent2/icons/qvg/chevron_down.qvg differ diff --git a/designsystems/fluent2/icons/qvg/chevron_up.qvg b/designsystems/fluent2/icons/qvg/chevron_up.qvg new file mode 100644 index 00000000..11f3abc4 Binary files /dev/null and b/designsystems/fluent2/icons/qvg/chevron_up.qvg differ diff --git a/designsystems/fluent2/icons/qvg/combo-box-arrow-closed.qvg b/designsystems/fluent2/icons/qvg/combo-box-arrow-closed.qvg deleted file mode 100644 index 2585b892..00000000 Binary files a/designsystems/fluent2/icons/qvg/combo-box-arrow-closed.qvg and /dev/null differ diff --git a/designsystems/fluent2/icons/qvg/combo-box-arrow-open.qvg b/designsystems/fluent2/icons/qvg/combo-box-arrow-open.qvg deleted file mode 100644 index 6ddcf6c0..00000000 Binary files a/designsystems/fluent2/icons/qvg/combo-box-arrow-open.qvg and /dev/null differ diff --git a/designsystems/fluent2/icons/qvg/segmented-button-check.qvg b/designsystems/fluent2/icons/qvg/segmented-button-check.qvg deleted file mode 100644 index fa0e1ce2..00000000 Binary files a/designsystems/fluent2/icons/qvg/segmented-button-check.qvg and /dev/null differ diff --git a/designsystems/fluent2/icons/qvg/spin-box-arrow-down.qvg b/designsystems/fluent2/icons/qvg/spin-box-arrow-down.qvg deleted file mode 100644 index 2825e1fb..00000000 Binary files a/designsystems/fluent2/icons/qvg/spin-box-arrow-down.qvg and /dev/null differ diff --git a/designsystems/fluent2/icons/qvg/spin-box-arrow-up.qvg b/designsystems/fluent2/icons/qvg/spin-box-arrow-up.qvg deleted file mode 100644 index 3ae75f32..00000000 Binary files a/designsystems/fluent2/icons/qvg/spin-box-arrow-up.qvg and /dev/null differ diff --git a/designsystems/fluent2/icons/segmented-button-check.svg b/designsystems/fluent2/icons/segmented-button-check.svg deleted file mode 100644 index b0b66ac9..00000000 --- a/designsystems/fluent2/icons/segmented-button-check.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/designsystems/fluent2/icons/spin-box-arrow-down.svg b/designsystems/fluent2/icons/spin-box-arrow-down.svg deleted file mode 100644 index a3ffcd48..00000000 --- a/designsystems/fluent2/icons/spin-box-arrow-down.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/designsystems/fluent2/icons/spin-box-arrow-up.svg b/designsystems/fluent2/icons/spin-box-arrow-up.svg deleted file mode 100644 index 34301711..00000000 --- a/designsystems/fluent2/icons/spin-box-arrow-up.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/designsystems/fusion/QskFusionSkin.cpp b/designsystems/fusion/QskFusionSkin.cpp index b2add0a2..d637fdfd 100644 --- a/designsystems/fusion/QskFusionSkin.cpp +++ b/designsystems/fusion/QskFusionSkin.cpp @@ -145,13 +145,21 @@ namespace if ( strcmp( name, "checkMark" ) == 0 ) { + graphic.setViewBox( QRectF( 0.0, 0.0, 14.0, 14.0 ) ); + QPainterPath path; - path.moveTo( 0.11, 0.47 ); - path.lineTo( 0.5, 1.0); - path.lineTo( 1.0, 0.0 ); + path.moveTo( 3.5, 7.0 ); + path.lineTo( 6.5, 14.0 ); + path.lineTo( 11.0, 1.0 ); + + QPen pen( Qt::black, 2.8 ); + pen.setCapStyle( Qt::FlatCap ); + pen.setJoinStyle( Qt::BevelJoin ); QPainter painter( &graphic ); - painter.setPen( QPen( Qt::black, 0.25 ) ); + painter.setRenderHint( QPainter::Antialiasing ); + + painter.setPen( pen ); painter.drawPath( path ); } @@ -209,19 +217,12 @@ void Editor::setupCheckBox() } } - setGraphicRole( Q::Indicator, QskFusionSkin::GraphicNormal ); + setGraphicRole( Q::Indicator, QskFusionSkin::GraphicIndicator ); setGraphicRole( Q::Indicator | Q::Disabled, QskFusionSkin::GraphicDisabled ); setGraphicRole( Q::Indicator | Q::Error, QskFusionSkin::GraphicError ); -#if 0 - // aligning/scaling of the symbols needs to be fixed in the skinlet TODO .. - - setPadding( Q::Box, 4_dp ); + setPadding( Q::Box, 3_dp ); const auto checkMark = symbol( "checkMark" ); -#else - setPadding( Q::Box, 2_dp ); - const auto checkMark = QskStandardSymbol::graphic( QskStandardSymbol::CheckMark ); -#endif for ( auto state : { QskAspect::NoState, Q::Disabled } ) { @@ -277,6 +278,7 @@ void Editor::setupComboBox() setGraphicRole( Q::Icon | Q::Disabled, QskFusionSkin::GraphicDisabled ); setStrutSize( Q::StatusIndicator, 10_dp, 10_dp ); + setGraphicRole( Q::StatusIndicator, QskFusionSkin::GraphicIndicator ); setGraphicRole( Q::StatusIndicator | Q::Disabled, QskFusionSkin::GraphicDisabled ); setAlignment( Q::StatusIndicator, Qt::AlignRight | Qt::AlignVCenter ); @@ -859,8 +861,6 @@ void Editor::setupSpinBox() setPadding( Q::TextPanel, 5_dp ); setBoxShape( Q::TextPanel, 2, 0, 2, 0 ); - - setColor( Q::Text, m_pal.active( P::Text ) ); setGradient( Q::TextPanel | Q::Disabled, m_pal.disabled( P::Base ) ); setBoxBorderMetrics( Q::TextPanel, 1_dp ); @@ -872,6 +872,9 @@ void Editor::setupSpinBox() Combination( { Q::Increasing, Q::Decreasing, Q::Hovered } ) ); #endif + setColor( Q::Text, m_pal.active( P::Text ) ); + setAlignment( Q::Text, Qt::AlignCenter ); + setBoxShape( Q::UpPanel, 0, 2_dp, 0, 0 ); setBoxBorderMetrics( Q::UpPanel, 0_dp, 1_dp, 1_dp, 0_dp ); @@ -1338,6 +1341,13 @@ void QskFusionSkin::initHints() setGraphicColor( GraphicError, palette.error ); setGraphicColor( GraphicHighlighted, palette.active( P::HighlightedText ) ); + { + auto rgb = palette.darker( P::Active, P::Text, 120 ); + rgb = QskRgb::toTransparent( rgb, 180 ); + + setGraphicColor( GraphicIndicator, rgb ); + } + Editor editor( palette, &hintTable() ); editor.setup(); } diff --git a/designsystems/fusion/QskFusionSkin.h b/designsystems/fusion/QskFusionSkin.h index c3feb9e7..3ec1f178 100644 --- a/designsystems/fusion/QskFusionSkin.h +++ b/designsystems/fusion/QskFusionSkin.h @@ -24,7 +24,8 @@ class QSK_FUSION_EXPORT QskFusionSkin : public QskSkin GraphicNormal, GraphicDisabled, GraphicHighlighted, - GraphicError + GraphicError, + GraphicIndicator }; protected: diff --git a/designsystems/material3/CMakeLists.txt b/designsystems/material3/CMakeLists.txt index cce742e0..051e8ed9 100644 --- a/designsystems/material3/CMakeLists.txt +++ b/designsystems/material3/CMakeLists.txt @@ -7,7 +7,7 @@ set(SOURCES QskMaterial3Global.h QskMaterial3Skin.h QskMaterial3Skin.cpp QskMaterial3SkinFactory.h QskMaterial3SkinFactory.cpp ) -qt_add_resources(SOURCES icons.qrc) +qt_add_resources(SOURCES QskMaterial3Icons.qrc) qsk_add_plugin(material3skin skins QskMaterial3SkinFactory ${SOURCES}) set_target_properties(material3skin PROPERTIES DEFINE_SYMBOL QSK_MATERIAL3_MAKEDLL ) diff --git a/designsystems/material3/QskMaterial3Icons.qrc b/designsystems/material3/QskMaterial3Icons.qrc new file mode 100644 index 00000000..4e55e3be --- /dev/null +++ b/designsystems/material3/QskMaterial3Icons.qrc @@ -0,0 +1,19 @@ + + + + + + icons/qvg/check_small.qvg + icons/qvg/combo-box-arrow-closed.qvg + icons/qvg/combo-box-arrow-open.qvg + icons/qvg/segmented-button-check.qvg + + + + icons/qvg/add.qvg + icons/qvg/arrow_drop_down.qvg + icons/qvg/arrow_drop_up.qvg + icons/qvg/check.qvg + icons/qvg/remove.qvg + + diff --git a/designsystems/material3/QskMaterial3Skin.cpp b/designsystems/material3/QskMaterial3Skin.cpp index b18e1bf9..b4664475 100644 --- a/designsystems/material3/QskMaterial3Skin.cpp +++ b/designsystems/material3/QskMaterial3Skin.cpp @@ -64,6 +64,13 @@ #include #include +static void qskMaterial3InitResources() +{ + Q_INIT_RESOURCE( QskMaterial3Icons ); +} + +Q_CONSTRUCTOR_FUNCTION( qskMaterial3InitResources ) + static const int qskDuration = 150; namespace @@ -105,6 +112,15 @@ namespace return qskDpToPixels( value ); } + class Combination : public QskStateCombination + { + public: + constexpr Combination( const QskAspect::States states ) + : QskStateCombination( CombinationNoState, states ) + { + } + }; + class Editor : private QskSkinHintTableEditor { Q_GADGET @@ -585,7 +601,7 @@ void Editor::setupSegmentedBar() { // Icon - setSymbol( Q::Icon, symbol( "segmented-button-check" ) ); + setSymbol( Q::Icon, symbol( "check" ) ); setStrutSize( Q::Icon, 18_dp, 18_dp ); setGraphicRole( Q::Icon, QskMaterial3Skin::GraphicRoleOnSurface ); @@ -881,13 +897,21 @@ void Editor::setupSpinBox() using Q = QskSpinBox; setHint( Q::Panel | QskAspect::Style, Q::ButtonsLeftAndRight ); + + setBoxShape( Q::Panel, 4_dp ); + setBoxBorderMetrics( Q::Panel, 1_dp ); + + setBoxBorderColors( Q::Panel, m_pal.outline ); + setBoxBorderColors( Q::Panel | Q::Focused, m_pal.primary, + Combination( { Q::Increasing, Q::Decreasing } ) ); + + setPadding( Q::Panel, 4_dp ); setSpacing( Q::Panel, 4_dp ); setStrutSize( Q::TextPanel, 80_dp, 40_dp ); setStrutSize( Q::UpPanel, 40_dp,40_dp ); setStrutSize( Q::DownPanel, 40_dp, 40_dp ); - setAlignment( Q::Panel, Qt::AlignHCenter ); setAlignment( Q::Text, Qt::AlignCenter ); for( const auto subControl : { Q::DownPanel, Q::UpPanel, Q::TextPanel } ) @@ -898,31 +922,31 @@ void Editor::setupSpinBox() for( const auto subControl : { Q::DownPanel, Q::UpPanel } ) { - setGradient( subControl, m_pal.primary ); - setGradient( subControl | Q::Disabled, m_pal.onSurface12 ); + setGradient( subControl | Q::Hovered, m_pal.primary8 ); setPadding( subControl, 10 ); } { - const auto focusColor = flattenedColor( m_pal.onPrimary, m_pal.primary, 0.12 ); - - setGradient( Q::DownPanel | Q::Decreasing, focusColor ); - setGradient( Q::UpPanel | Q::Increasing, focusColor ); + setGradient( Q::DownPanel | Q::Decreasing, m_pal.primary12 ); + setGradient( Q::UpPanel | Q::Increasing, m_pal.primary12 ); } - setSymbol( Q::UpIndicator, symbol( "combo-box-arrow-open" ) ); - setSymbol( Q::DownIndicator, symbol( "combo-box-arrow-closed" ) ); + setSymbol( Q::UpIndicator, symbol( "add" ) ); + setSymbol( Q::DownIndicator, symbol( "remove" ) ); for( const auto subControl : { Q::DownIndicator, Q::UpIndicator } ) { setAlignment( subControl, Qt::AlignCenter ); +#if 0 setGraphicRole( subControl, QskMaterial3Skin::GraphicRoleOnPrimary ); setGraphicRole( subControl | Q::Disabled, QskMaterial3Skin::GraphicRoleOnSurface38 ); +#endif } setColor( Q::Text, m_pal.onBackground ); setColor( Q::Text | Q::Disabled, m_pal.onSurface38 ); +#if 0 setPadding( Q::TextPanel, 5_dp ); setBoxShape( Q::TextPanel, 4_dp, 4_dp, 0, 0 ); setBoxBorderMetrics( Q::TextPanel, 0, 0, 0, 1_dp ); @@ -939,6 +963,7 @@ void Editor::setupSpinBox() setColor( Q::TextPanel | Q::Disabled, m_pal.onSurface38 ); setBoxBorderColors( Q::TextPanel | Q::Disabled, m_pal.onSurface38 ); +#endif } void Editor::setupSwitchButton() diff --git a/designsystems/material3/icons.qrc b/designsystems/material3/icons.qrc deleted file mode 100644 index ab149d96..00000000 --- a/designsystems/material3/icons.qrc +++ /dev/null @@ -1,8 +0,0 @@ - - - icons/qvg/check_small.qvg - icons/qvg/combo-box-arrow-closed.qvg - icons/qvg/combo-box-arrow-open.qvg - icons/qvg/segmented-button-check.qvg - - diff --git a/designsystems/material3/icons/LICENSE b/designsystems/material3/icons/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/designsystems/material3/icons/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/designsystems/material3/icons/add.svg b/designsystems/material3/icons/add.svg new file mode 100644 index 00000000..b0570646 --- /dev/null +++ b/designsystems/material3/icons/add.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/designsystems/material3/icons/arrow_drop_down.svg b/designsystems/material3/icons/arrow_drop_down.svg new file mode 100644 index 00000000..37d90c90 --- /dev/null +++ b/designsystems/material3/icons/arrow_drop_down.svg @@ -0,0 +1 @@ + diff --git a/designsystems/material3/icons/arrow_drop_up.svg b/designsystems/material3/icons/arrow_drop_up.svg new file mode 100644 index 00000000..24f0831a --- /dev/null +++ b/designsystems/material3/icons/arrow_drop_up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/designsystems/material3/icons/check.svg b/designsystems/material3/icons/check.svg new file mode 100644 index 00000000..3be223be --- /dev/null +++ b/designsystems/material3/icons/check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/designsystems/material3/icons/icon2qvg.sh b/designsystems/material3/icons/icon2qvg.sh new file mode 100755 index 00000000..c562ca17 --- /dev/null +++ b/designsystems/material3/icons/icon2qvg.sh @@ -0,0 +1,15 @@ +#! /bin/sh + +if [ $# -eq 0 ]; then + echo "Usage $0 file ..." + exit 1 +fi + +for file in $* +do + base=`basename -s .svg $file` + echo "${base}.svg -> qvg/${base}.qvg" + svg2qvg ${base}.svg qvg/${base}.qvg +done + +exit $status diff --git a/designsystems/material3/icons/qvg/add.qvg b/designsystems/material3/icons/qvg/add.qvg new file mode 100644 index 00000000..de7e62c6 Binary files /dev/null and b/designsystems/material3/icons/qvg/add.qvg differ diff --git a/designsystems/material3/icons/qvg/arrow_drop_down.qvg b/designsystems/material3/icons/qvg/arrow_drop_down.qvg new file mode 100644 index 00000000..496378d6 Binary files /dev/null and b/designsystems/material3/icons/qvg/arrow_drop_down.qvg differ diff --git a/designsystems/material3/icons/qvg/arrow_drop_up.qvg b/designsystems/material3/icons/qvg/arrow_drop_up.qvg new file mode 100644 index 00000000..09b1976e Binary files /dev/null and b/designsystems/material3/icons/qvg/arrow_drop_up.qvg differ diff --git a/designsystems/material3/icons/qvg/check.qvg b/designsystems/material3/icons/qvg/check.qvg new file mode 100644 index 00000000..8fb73515 Binary files /dev/null and b/designsystems/material3/icons/qvg/check.qvg differ diff --git a/designsystems/material3/icons/qvg/check_small.qvg b/designsystems/material3/icons/qvg/check_small.qvg index ea738b86..4b92ce5b 100644 Binary files a/designsystems/material3/icons/qvg/check_small.qvg and b/designsystems/material3/icons/qvg/check_small.qvg differ diff --git a/designsystems/material3/icons/qvg/combo-box-arrow-closed.qvg b/designsystems/material3/icons/qvg/combo-box-arrow-closed.qvg index 2585b892..83f33a45 100644 Binary files a/designsystems/material3/icons/qvg/combo-box-arrow-closed.qvg and b/designsystems/material3/icons/qvg/combo-box-arrow-closed.qvg differ diff --git a/designsystems/material3/icons/qvg/combo-box-arrow-open.qvg b/designsystems/material3/icons/qvg/combo-box-arrow-open.qvg index 6ddcf6c0..1e757789 100644 Binary files a/designsystems/material3/icons/qvg/combo-box-arrow-open.qvg and b/designsystems/material3/icons/qvg/combo-box-arrow-open.qvg differ diff --git a/designsystems/material3/icons/qvg/remove.qvg b/designsystems/material3/icons/qvg/remove.qvg new file mode 100644 index 00000000..6598d5b0 Binary files /dev/null and b/designsystems/material3/icons/qvg/remove.qvg differ diff --git a/designsystems/material3/icons/qvg/segmented-button-check.qvg b/designsystems/material3/icons/qvg/segmented-button-check.qvg index fa0e1ce2..1b0148e3 100644 Binary files a/designsystems/material3/icons/qvg/segmented-button-check.qvg and b/designsystems/material3/icons/qvg/segmented-button-check.qvg differ diff --git a/designsystems/material3/icons/remove.svg b/designsystems/material3/icons/remove.svg new file mode 100644 index 00000000..9fa856a5 --- /dev/null +++ b/designsystems/material3/icons/remove.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/tutorials/11-How-to-build-for-Wasm.asciidoc b/doc/tutorials/11-How-to-build-for-Wasm.asciidoc new file mode 100644 index 00000000..6c8d7f87 --- /dev/null +++ b/doc/tutorials/11-How-to-build-for-Wasm.asciidoc @@ -0,0 +1,51 @@ +--- +title: 10. Building QSkinny for WebAssembly (Wasm) +layout: docs +--- + +:doctitle: 10. Building QSkinny for WebAssembly (Wasm) +:notitle: + +== 10. Building QSkinny for WebAssembly (Wasm) + + +=== Build Qt for Wasm + +Build Qt for Wasm from source as described here: https://doc.qt.io/qt-6/wasm.html#building-qt-from-source ; The verified Qt version for QSkinny as of this writing was 6.6.0. It might also work to use a downloaded version of Qt for Wasm, but some additional libraries will need to be built. +After configuring Qt as described in the link above, for QSkinny you will need the qtbase, qtdeclarative, qtshadertools and qtsvg modules. +Assuming the Emscripten SDK in `~/dev/emscripten` and Qt in `~/dev/qt6`, Qt can be compiled the following way: + +[source] +.... +cd ~/dev/qt6 +source "~/dev/emsdk/emsdk_env.sh" +cmake --build . -t qtbase -t qtdeclarative -t qtshadertools -t qtsvg +.... + +This will install all required libs in `~/dev/qt6/qtbase/lib`. + + +=== Build QSkinny for Wasm + +With the Qt version from above QSkinny can be built for WAsm, assuming it has been checked out at `~/dev/qskinny`. Note the configuration option `BUILD_QSKDLL=OFF` for static + builds: + +[source] +.... +mkdir -p ~/dev/qskinny-wasm-build +source "~/dev/emsdk/emsdk_env.sh" +~/dev/qt6/qtbase/bin/qt-cmake -S ~/dev/qskinny -B ~/dev/qskinny-wasm-build -DBUILD_QSKDLL=OFF +.... + + +=== Run QSkinny for Wasm + +Qt creates the HTML wrappers automatically, so there is not much to do except letting Emscripten start the server and open our app in the browser: + +[source] +.... +/usr/bin/python3 ~/dev/emsdk/upstream/emscripten/emrun.py --browser firefox --port 30001 --no_emrun_detect ~/dev/qskinny-wasm-build/examples/bin/iotdashboard.html +.... + +.The IOT dashboard example in a browser +image::../images/iotdashboard-wasm.png[The IOT dashboard example in a browser] diff --git a/doc/tutorials/images/iotdashboard-wasm.png b/doc/tutorials/images/iotdashboard-wasm.png new file mode 100644 index 00000000..a7111e95 Binary files /dev/null and b/doc/tutorials/images/iotdashboard-wasm.png differ diff --git a/examples/gallery/dialog/DialogPage.cpp b/examples/gallery/dialog/DialogPage.cpp index 143baaff..62e8f8a1 100644 --- a/examples/gallery/dialog/DialogPage.cpp +++ b/examples/gallery/dialog/DialogPage.cpp @@ -9,6 +9,60 @@ #include #include +#if QT_CONFIG(thread) + /* + WebAssembly without asyncify support does not allow recursive + event loops. As we did not implement QskDialog::message and friends + with callbacks yet ( TODO ) we do have some dummy code here to avoid + crashes. + */ + #define QSK_USE_EXEC +#endif + +#ifndef QSK_USE_EXEC + +#include +#include +#include + +namespace +{ + void openMessageSubWindow( QQuickWindow* window, const QString& title, + const QString& text, QskDialog::Actions actions, QskDialog::Action defaultAction ) + { + auto subWindow = new QskMessageSubWindow( window->contentItem() ); + subWindow->setPopupFlag( QskPopup::DeleteOnClose ); + subWindow->setModal( true ); + + subWindow->setTitle( title ); + subWindow->setDialogActions( actions ); + subWindow->setDefaultDialogAction( defaultAction ); + + subWindow->setText( text ); + ( void ) subWindow->open(); + } + + void openSelectSubWindow( QQuickWindow* window, const QString& title, + QskDialog::Actions actions, QskDialog::Action defaultAction, + const QStringList& entries, int selectedRow ) + { + auto subWindow = new QskSelectionSubWindow( window->contentItem() ); + subWindow->setPopupFlag( QskPopup::DeleteOnClose ); + subWindow->setModal( true ); + + subWindow->setTitle( title ); + subWindow->setDialogActions( actions ); + subWindow->setDefaultDialogAction( defaultAction ); + + subWindow->setEntries( entries ); + subWindow->setSelectedRow( selectedRow ); + + ( void ) subWindow->open(); + } +} + +#endif + namespace { class Button : public QskPushButton @@ -48,17 +102,32 @@ namespace private: void execMessage() { - qskDialog->information( "Message", "Request vector, over." ); + const QString title( "Message" ); + const QString message( "Request vector, over." ); +#ifndef QSK_USE_EXEC + openMessageSubWindow( window(), title, message, + QskDialog::Ok, QskDialog::Ok ); +#else + qskDialog->information( title, message ); +#endif } void execQuestion() { - qskDialog->question( "Question", - "Roger, Roger. Do we have a vector, Victor ?" ); + const QString title( "Question" ); + const QString question( "Roger, Roger. Do we have a vector, Victor ?" ); +#ifndef QSK_USE_EXEC + openMessageSubWindow( window(), title, question, + QskDialog::Yes | QskDialog::No, QskDialog::Yes ); +#else + (void )qskDialog->question( title, question ); +#endif } void execSelection() { + const QString title( "The Teens" ); + const QStringList entries = { "Give Me More", @@ -81,7 +150,12 @@ namespace "Gimme Gimme Gimme Gimme Gimme Your Love" }; - qskDialog->select( "The Teens", entries, 7 ); +#ifndef QSK_USE_EXEC + openSelectSubWindow( window(), title, + QskDialog::Ok | QskDialog::Cancel, QskDialog::Ok, entries, 7 ); +#else + (void )qskDialog->select( title, entries, 7 ); +#endif } }; } diff --git a/examples/gallery/icons/icon2qvg.sh b/examples/gallery/icons/icon2qvg.sh new file mode 100755 index 00000000..c562ca17 --- /dev/null +++ b/examples/gallery/icons/icon2qvg.sh @@ -0,0 +1,15 @@ +#! /bin/sh + +if [ $# -eq 0 ]; then + echo "Usage $0 file ..." + exit 1 +fi + +for file in $* +do + base=`basename -s .svg $file` + echo "${base}.svg -> qvg/${base}.qvg" + svg2qvg ${base}.svg qvg/${base}.qvg +done + +exit $status diff --git a/examples/gallery/icons/qvg/airport_shuttle.qvg b/examples/gallery/icons/qvg/airport_shuttle.qvg index fe2cb660..fc2feaa8 100644 Binary files a/examples/gallery/icons/qvg/airport_shuttle.qvg 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 index c4d0cd47..7980008b 100644 Binary files a/examples/gallery/icons/qvg/flight.qvg 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 index 84289057..bea68757 100644 Binary files a/examples/gallery/icons/qvg/local_pizza.qvg 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 index ce9688d9..73e18e1a 100644 Binary files a/examples/gallery/icons/qvg/plus.qvg 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 index cbf98807..ff3876b8 100644 Binary files a/examples/gallery/icons/qvg/sports_soccer.qvg and b/examples/gallery/icons/qvg/sports_soccer.qvg differ diff --git a/examples/gallery/inputs/InputPage.cpp b/examples/gallery/inputs/InputPage.cpp index efc52e11..8f41c912 100644 --- a/examples/gallery/inputs/InputPage.cpp +++ b/examples/gallery/inputs/InputPage.cpp @@ -62,16 +62,10 @@ namespace { auto spinBox = new QskSpinBox( 0.0, 100.0, 1.0, this ); + spinBox->setSizePolicy( Qt::Horizontal, QskSizePolicy::Fixed ); spinBox->setPageSize( 5 ); spinBox->setValue( 35 ); } - - { - auto spinBox = new QskSpinBox( 10.0, 100.0, 1.0, this ); - spinBox->setPageSize( 10 ); - spinBox->setDecoration( QskSpinBox::NoDecoration ); - spinBox->setValue( 50 ); - } } }; } diff --git a/examples/gallery/progressbar/ProgressBarPage.cpp b/examples/gallery/progressbar/ProgressBarPage.cpp index fe0e7b07..3517eb91 100644 --- a/examples/gallery/progressbar/ProgressBarPage.cpp +++ b/examples/gallery/progressbar/ProgressBarPage.cpp @@ -159,6 +159,7 @@ void ProgressBarPage::populate() { auto* ring = new QskProgressRing( determinateRingsHBox ); ring->setSize( size ); + ring->setLayoutAlignmentHint( Qt::AlignCenter ); QQuickItem* parentItem; diff --git a/examples/iotdashboard/CMakeLists.txt b/examples/iotdashboard/CMakeLists.txt index 183c0112..afeffdb9 100644 --- a/examples/iotdashboard/CMakeLists.txt +++ b/examples/iotdashboard/CMakeLists.txt @@ -6,11 +6,8 @@ set(SOURCES Box.h Box.cpp BoxWithButtons.h BoxWithButtons.cpp - CircularProgressBar.h CircularProgressBar.cpp - CircularProgressBarSkinlet.h CircularProgressBarSkinlet.cpp Diagram.h Diagram.cpp DiagramSkinlet.h DiagramSkinlet.cpp - EnergyMeter.h EnergyMeter.cpp GraphicProvider.h GraphicProvider.cpp GridBox.h GridBox.cpp LightDisplaySkinlet.h LightDisplaySkinlet.cpp @@ -31,7 +28,7 @@ set(SOURCES UsageBox.h UsageBox.cpp UsageDiagram.h UsageDiagram.cpp StoragePage.h StoragePage.cpp - StorageMeter.h StorageMeter.cpp + ValueMeter.h ValueMeter.cpp StorageBar.h StorageBar.cpp StorageBarSkinlet.h StorageBarSkinlet.cpp nodes/DiagramDataNode.h nodes/DiagramDataNode.cpp diff --git a/examples/iotdashboard/CircularProgressBar.cpp b/examples/iotdashboard/CircularProgressBar.cpp deleted file mode 100644 index 479c5668..00000000 --- a/examples/iotdashboard/CircularProgressBar.cpp +++ /dev/null @@ -1,197 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2021 Edelhirsch Software GmbH - * SPDX-License-Identifier: BSD-3-Clause - *****************************************************************************/ - -#include "CircularProgressBar.h" - -#include -#include - -QSK_SUBCONTROL( CircularProgressBar, Groove ) -QSK_SUBCONTROL( CircularProgressBar, Bar ) - -namespace -{ - class PositionAnimator : public QskAnimator - { - public: - PositionAnimator( CircularProgressBar* progressBar ) - : m_progressBar( progressBar ) - { - setAutoRepeat( true ); - setDuration( 1300 ); - - setWindow( progressBar->window() ); - } - - void advance( qreal value ) override - { - const auto aspect = CircularProgressBar::Bar | QskAspect::Position; - - m_progressBar->setMetric( aspect, value ); - m_progressBar->update(); - } - - private: - CircularProgressBar* m_progressBar; - }; -} - -class CircularProgressBar::PrivateData -{ - public: - void updateIndeterminateAnimator( CircularProgressBar* progressBar ) - { - if ( !isIndeterminate ) - { - delete animator; - animator = nullptr; - - return; - } - - if ( progressBar->window() && progressBar->isVisible() ) - { - if ( animator == nullptr ) - animator = new PositionAnimator( progressBar ); - - animator->start(); - } - else - { - if ( animator ) - animator->stop(); - } - } - - PositionAnimator* animator = nullptr; - - qreal value = 0.0; - qreal origin = 0.0; - - bool hasOrigin = false; - bool isIndeterminate = false; -}; - -CircularProgressBar::~CircularProgressBar() = default; - -CircularProgressBar::CircularProgressBar( qreal min, qreal max, QQuickItem* parent ) - : QskBoundedControl( min, max, parent ) - , m_data( new PrivateData ) -{ - m_data->value = minimum(); - - initSizePolicy( QskSizePolicy::MinimumExpanding, QskSizePolicy::MinimumExpanding ); - - connect( this, &QskBoundedControl::boundariesChanged, - this, &CircularProgressBar::adjustValue ); -} - -CircularProgressBar::CircularProgressBar( QQuickItem* parent ) - : CircularProgressBar( 0.0, 100.0, parent ) -{ -} - -bool CircularProgressBar::isIndeterminate() const -{ - return m_data->isIndeterminate; -} - -void CircularProgressBar::setIndeterminate( bool on ) -{ - if ( on == m_data->isIndeterminate ) - return; - - m_data->isIndeterminate = on; - m_data->updateIndeterminateAnimator( this ); - - update(); - Q_EMIT indeterminateChanged( on ); -} - -void CircularProgressBar::resetOrigin() -{ - if ( m_data->hasOrigin ) - { - m_data->hasOrigin = false; - - update(); - Q_EMIT originChanged( origin() ); - } -} - -qreal CircularProgressBar::origin() const -{ - if ( m_data->hasOrigin ) - { - return boundedValue( m_data->origin ); - } - - return minimum(); -} - -qreal CircularProgressBar::value() const -{ - return m_data->value; -} - -qreal CircularProgressBar::valueAsRatio() const -{ - return QskBoundedControl::valueAsRatio( m_data->value ); -} - -void CircularProgressBar::setValue( qreal value ) -{ - if ( isComponentComplete() ) - value = boundedValue( value ); - - setValueInternal( value ); -} - -void CircularProgressBar::setValueAsRatio( qreal ratio ) -{ - ratio = qBound( 0.0, ratio, 1.0 ); - setValue( minimum() + ratio * boundaryLength() ); -} - -void CircularProgressBar::setOrigin( qreal origin ) -{ - if ( isComponentComplete() ) - origin = boundedValue( origin ); - - if( !m_data->hasOrigin || !qskFuzzyCompare( m_data->origin, origin ) ) - { - m_data->hasOrigin = true; - m_data->origin = origin; - - update(); - Q_EMIT originChanged( origin ); - } -} - -void CircularProgressBar::componentComplete() -{ - Inherited::componentComplete(); - adjustValue(); -} - -void CircularProgressBar::setValueInternal( qreal value ) -{ - if ( !qskFuzzyCompare( value, m_data->value ) ) - { - m_data->value = value; - - Q_EMIT valueChanged( value ); - - update(); - } -} - -void CircularProgressBar::adjustValue() -{ - if ( isComponentComplete() ) - setValueInternal( boundedValue( m_data->value ) ); -} - -#include "moc_CircularProgressBar.cpp" diff --git a/examples/iotdashboard/CircularProgressBar.h b/examples/iotdashboard/CircularProgressBar.h deleted file mode 100644 index 9cd84898..00000000 --- a/examples/iotdashboard/CircularProgressBar.h +++ /dev/null @@ -1,61 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2021 Edelhirsch Software GmbH - * SPDX-License-Identifier: BSD-3-Clause - *****************************************************************************/ - -#pragma once - -#include - -class CircularProgressBar : public QskBoundedControl -{ - Q_OBJECT - - Q_PROPERTY( bool indeterminate READ isIndeterminate - WRITE setIndeterminate NOTIFY indeterminateChanged ) - - Q_PROPERTY( qreal origin READ origin - WRITE setOrigin RESET resetOrigin NOTIFY originChanged ) - - Q_PROPERTY( qreal value READ value WRITE setValue NOTIFY valueChanged ) - Q_PROPERTY( qreal valueAsRatio READ valueAsRatio - WRITE setValueAsRatio NOTIFY valueChanged ) - - using Inherited = QskBoundedControl; - - public: - QSK_SUBCONTROLS( Groove, Bar ) - - CircularProgressBar( qreal min, qreal max, QQuickItem* parent = nullptr ); - CircularProgressBar( QQuickItem* parent = nullptr ); - ~CircularProgressBar() override; - - bool isIndeterminate() const; - void setIndeterminate( bool on = true ); - - void resetOrigin(); - qreal origin() const; - - qreal value() const; - qreal valueAsRatio() const; // [0.0, 1.0] - - public Q_SLOTS: - void setValue( qreal ); - void setValueAsRatio( qreal ); - void setOrigin( qreal ); - - Q_SIGNALS: - void indeterminateChanged( bool ); - void valueChanged( qreal ); - void originChanged( qreal ); - - protected: - void componentComplete() override; - - private: - void setValueInternal( qreal value ); - void adjustValue(); - - class PrivateData; - std::unique_ptr< PrivateData > m_data; -}; diff --git a/examples/iotdashboard/CircularProgressBarSkinlet.cpp b/examples/iotdashboard/CircularProgressBarSkinlet.cpp deleted file mode 100644 index 0bd5f418..00000000 --- a/examples/iotdashboard/CircularProgressBarSkinlet.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2021 Edelhirsch Software GmbH - * SPDX-License-Identifier: BSD-3-Clause - *****************************************************************************/ - -#include "CircularProgressBarSkinlet.h" -#include "CircularProgressBar.h" - -CircularProgressBarSkinlet::CircularProgressBarSkinlet( QskSkin* skin ) - : QskSkinlet( skin ) -{ - setNodeRoles( { GrooveRole, BarRole } ); -} - -CircularProgressBarSkinlet::~CircularProgressBarSkinlet() -{ -} - -QRectF CircularProgressBarSkinlet::subControlRect( - const QskSkinnable*, const QRectF& contentsRect, QskAspect::Subcontrol ) const -{ - return contentsRect; -} - -QSGNode* CircularProgressBarSkinlet::updateSubNode( - const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const -{ - switch( nodeRole ) - { - case GrooveRole: - { - return updateArcNode( skinnable, node, CircularProgressBar::Groove ); - } - case BarRole: - { - const auto bar = static_cast< const CircularProgressBar* >( skinnable ); - - const qreal startAngle = 90.0; - const qreal spanAngle = 360.0 * bar->valueAsRatio(); - - return updateArcNode( skinnable, node, startAngle, -spanAngle, - CircularProgressBar::Bar ); - } - } - - return Inherited::updateSubNode( skinnable, nodeRole, node ); -} - -#include "moc_CircularProgressBarSkinlet.cpp" diff --git a/examples/iotdashboard/CircularProgressBarSkinlet.h b/examples/iotdashboard/CircularProgressBarSkinlet.h deleted file mode 100644 index 9075fbb3..00000000 --- a/examples/iotdashboard/CircularProgressBarSkinlet.h +++ /dev/null @@ -1,36 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2021 Edelhirsch Software GmbH - * SPDX-License-Identifier: BSD-3-Clause - *****************************************************************************/ - -#pragma once - -#include - -class CircularProgressBar; - -class CircularProgressBarSkinlet : public QskSkinlet -{ - Q_GADGET - - using Inherited = QskSkinlet; - - public: - enum NodeRole - { - GrooveRole, - BarRole, - - RoleCount, - }; - - Q_INVOKABLE CircularProgressBarSkinlet( QskSkin* = nullptr ); - ~CircularProgressBarSkinlet() override; - - QRectF subControlRect( const QskSkinnable*, - const QRectF&, QskAspect::Subcontrol ) const override; - - protected: - QSGNode* updateSubNode( const QskSkinnable*, - quint8 nodeRole, QSGNode* ) const override; -}; diff --git a/examples/iotdashboard/DashboardPage.cpp b/examples/iotdashboard/DashboardPage.cpp index a91291bf..3c7f5028 100644 --- a/examples/iotdashboard/DashboardPage.cpp +++ b/examples/iotdashboard/DashboardPage.cpp @@ -14,17 +14,6 @@ #include "TopBar.h" #include "UsageBox.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#include - QSK_SUBCONTROL( DashboardPage, Panel ) namespace diff --git a/examples/iotdashboard/EnergyMeter.cpp b/examples/iotdashboard/EnergyMeter.cpp deleted file mode 100644 index f9d20537..00000000 --- a/examples/iotdashboard/EnergyMeter.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2021 Edelhirsch Software GmbH - * SPDX-License-Identifier: BSD-3-Clause - *****************************************************************************/ - -#include "EnergyMeter.h" -#include "CircularProgressBar.h" - -#include -#include - -namespace -{ - class ValueLabel : public QskTextLabel - { - public: - ValueLabel( QQuickItem* parent ) - : QskTextLabel( parent ) - { - initSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Fixed ); - setLayoutAlignmentHint( Qt::AlignCenter ); - setFontRole( QskFontRole::Caption ); - } - - void setValue( int value ) - { - setText( locale().toString( value ) + " " + locale().percent() ); - } - }; -} - -EnergyMeter::EnergyMeter( const QColor& textColor, - const QskGradient& gradient, int value, QQuickItem* parent ) - : QskControl( parent ) -{ - setAutoLayoutChildren( true ); - - auto valueBar = new CircularProgressBar( this ); - valueBar->setGradientHint( CircularProgressBar::Bar, gradient ); - valueBar->setValue( value ); - - auto valueLabel = new ValueLabel( this ); - valueLabel->setTextColor( textColor ); - valueLabel->setValue( value ); -} - -QSizeF EnergyMeter::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/EnergyMeter.h b/examples/iotdashboard/EnergyMeter.h deleted file mode 100644 index 7780dd9e..00000000 --- a/examples/iotdashboard/EnergyMeter.h +++ /dev/null @@ -1,18 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2021 Edelhirsch Software GmbH - * SPDX-License-Identifier: BSD-3-Clause - *****************************************************************************/ - -#pragma once - -#include - -class EnergyMeter : public QskControl -{ - public: - EnergyMeter( const QColor&, const QskGradient&, - int progress, QQuickItem* parent = nullptr ); - - protected: - QSizeF contentsSizeHint( Qt::SizeHint, const QSizeF& ) const override; -}; diff --git a/examples/iotdashboard/LightDisplay.cpp b/examples/iotdashboard/LightDisplay.cpp index 9f305b51..8ef43ea3 100644 --- a/examples/iotdashboard/LightDisplay.cpp +++ b/examples/iotdashboard/LightDisplay.cpp @@ -51,17 +51,6 @@ bool LightDisplay::isPressed() const return hasSkinState( Pressed ); } -void LightDisplay::setGradient( const QskGradient& gradient ) -{ - m_gradient = gradient; - update(); -} - -const QskGradient& LightDisplay::gradient() const -{ - return m_gradient; -} - void LightDisplay::mousePressEvent( QMouseEvent* event ) { QRectF handleRect = subControlRect( LightDisplay::Knob ); diff --git a/examples/iotdashboard/LightDisplay.h b/examples/iotdashboard/LightDisplay.h index bddd5f47..8c7728a5 100644 --- a/examples/iotdashboard/LightDisplay.h +++ b/examples/iotdashboard/LightDisplay.h @@ -6,7 +6,6 @@ #pragma once #include -#include #include class LightDisplay : public QskBoundedValueInput @@ -22,9 +21,6 @@ class LightDisplay : public QskBoundedValueInput bool isPressed() const; - void setGradient( const QskGradient& ); - const QskGradient& gradient() const; - protected: void mousePressEvent( QMouseEvent* e ) override; void mouseMoveEvent( QMouseEvent* e ) override; @@ -33,9 +29,4 @@ class LightDisplay : public QskBoundedValueInput private: qreal angleFromPoint( const QRectF&, const QPointF& ) const; bool arcContainsPoint( const QRectF&, const QPointF& ) const; - - QskShadowMetrics m_shadow; - QColor m_shadowColor = Qt::black; - - QskGradient m_gradient; }; diff --git a/examples/iotdashboard/Skin.cpp b/examples/iotdashboard/Skin.cpp index 5e7b56e3..09b7b3c3 100644 --- a/examples/iotdashboard/Skin.cpp +++ b/examples/iotdashboard/Skin.cpp @@ -7,8 +7,6 @@ #include "Box.h" #include "BoxWithButtons.h" -#include "CircularProgressBar.h" -#include "CircularProgressBarSkinlet.h" #include "DashboardPage.h" #include "Diagram.h" #include "DiagramSkinlet.h" @@ -21,7 +19,6 @@ #include "RoundedIcon.h" #include "StorageBar.h" #include "StorageBarSkinlet.h" -#include "StorageMeter.h" #include "StoragePage.h" #include "TopBar.h" #include "UsageBox.h" @@ -37,6 +34,7 @@ #include #include #include +#include #include #include @@ -56,7 +54,6 @@ Skin::Skin( QObject* parent ) { setObjectName( "iot" ); - declareSkinlet< CircularProgressBar, CircularProgressBarSkinlet >(); declareSkinlet< Diagram, DiagramSkinlet >(); declareSkinlet< LightDisplay, LightDisplaySkinlet >(); declareSkinlet< StorageBar, StorageBarSkinlet >(); @@ -87,9 +84,9 @@ void Skin::initHints() ed.setPadding( MainContentGridBox::Panel, { 19, 0, 27, 24 } ); - // menu bar: - { + // menu bar: + using Q = QskPushButton; using A = QskAspect; @@ -113,25 +110,27 @@ void Skin::initHints() ed.setAlignment( Q::Icon | A::Header, Qt::AlignCenter ); } - // top bar: - ed.setPadding( TopBar::Panel, { 25, 35, 25, 0 } ); + { + // top bar: - 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 ); + ed.setPadding( TopBar::Panel, { 25, 35, 25, 0 } ); - // 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 ); + 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 ); + + ed.setGradient( TopBarItem::Item1, 0xffff5c00, 0xffff3122 ); + ed.setGradient( TopBarItem::Item2, 0xff6776ff, 0xff6100ff ); + ed.setGradient( TopBarItem::Item3, 0xffffce50, 0xffff3122 ); + ed.setGradient( TopBarItem::Item4, 0xff6776ff, 0xff6100ff ); + } // the bar gradient is defined through the top bar items above - ed.setArcMetrics( CircularProgressBar::Groove, 90, -360, 8.53 ); + ed.setArcMetrics( QskProgressRing::Groove, 90, -360, 8.53 ); // the span angle will be set in the progress bar, we just give a dummy // value here: - ed.setArcMetrics( CircularProgressBar::Bar, 90, -180, 8.53 ); + ed.setArcMetrics( QskProgressRing::Fill, ed.arcMetrics( QskProgressRing::Groove ) ); ed.setFontRole( TimeTitleLabel::Text, { QskFontRole::Caption, QskFontRole::High } ); @@ -283,8 +282,8 @@ void Skin::initHints() ed.setColor( QskTextLabel::Text, palette.text ); ed.setColor( UsageDiagramBox::DayText, palette.text ); - ed.setMetric( CircularProgressBar::Groove | QskAspect::Border, 2 ); - ed.setColor( CircularProgressBar::Groove | QskAspect::Border, + ed.setMetric( QskProgressRing::Groove | QskAspect::Border, 2 ); + ed.setColor( QskProgressRing::Groove | QskAspect::Border, palette.circularProgressBarGroove ); // storage bar @@ -302,9 +301,13 @@ void Skin::initHints() // storage meter { - ed.setGradient( StorageMeter::Status, - { { { 0.00, "#00ff00" }, { 0.33, "#00ff00" }, { 0.33, "#ffaf00" }, { 0.66, "#ffaf00" }, - { 0.66, "#ff0000" }, { 1.00, "#ff0000" } } } ); + ed.setGradient( StoragePage::Status, + { { + { 0.00, "#00ff00" }, { 0.33, "#00ff00" }, + { 0.33, "#ffaf00" }, { 0.66, "#ffaf00" }, + { 0.66, "#ff0000" }, { 1.00, "#ff0000" } + } } + ); } } @@ -322,7 +325,7 @@ Skin::Palette Skin::palette( QskSkin::ColorScheme colorScheme ) const Qt::white, 0xff4a4a4a, 0xff555555, - { { { 0.0, 0xff991100 }, { 0.2, 0xff9a7a57 }, { 0.5, 0xff3726af }, { 1.0, Qt::black } } }, + { { { 0.0, 0xff991100 }, { 0.4, 0xff9a7a57 }, { 1.0, 0xff3726af } } }, 0x10ffffff, 0xff222222 }; @@ -339,8 +342,7 @@ Skin::Palette Skin::palette( QskSkin::ColorScheme colorScheme ) const Qt::black, 0xffe5e5e5, 0xffc4c4c4, - { { { 0.0, 0xffff3122 }, { 0.2, 0xfffeeeb7 }, { 0.3, 0xffa7b0ff }, { 0.5, 0xff6776ff }, - { 1.0, Qt::black } } }, + { { { 0.0, 0xffff3122 }, { 0.4, 0xfffeeeb7 }, { 0.6, 0xffa7b0ff }, { 1.0, 0xff6776ff } } }, 0x10000000, 0xffdddddd }; diff --git a/examples/iotdashboard/Skin.h b/examples/iotdashboard/Skin.h index 1b2cd7f1..1a06c446 100644 --- a/examples/iotdashboard/Skin.h +++ b/examples/iotdashboard/Skin.h @@ -34,5 +34,4 @@ class Skin : public QskSkin void initHints() override; Palette palette( ColorScheme ) const; - void initHints( const Palette& ); }; diff --git a/examples/iotdashboard/StorageMeter.cpp b/examples/iotdashboard/StorageMeter.cpp deleted file mode 100644 index af9d528b..00000000 --- a/examples/iotdashboard/StorageMeter.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2022 Edelhirsch Software GmbH - * SPDX-License-Identifier: BSD-3-Clause - *****************************************************************************/ - -#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( QskFontRole::Caption ); -} - -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 deleted file mode 100644 index 16a8f454..00000000 --- a/examples/iotdashboard/StorageMeter.h +++ /dev/null @@ -1,22 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2022 Edelhirsch Software GmbH - * SPDX-License-Identifier: BSD-3-Clause - *****************************************************************************/ - -#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 index f1ff5b3c..038a6e39 100644 --- a/examples/iotdashboard/StoragePage.cpp +++ b/examples/iotdashboard/StoragePage.cpp @@ -6,7 +6,7 @@ #include "StoragePage.h" #include "Box.h" #include "StorageBar.h" -#include "StorageMeter.h" +#include "ValueMeter.h" #include #include #include @@ -20,6 +20,60 @@ #include QSK_SUBCONTROL( StoragePage, Panel ) +QSK_SUBCONTROL( StoragePage, Status ) + +namespace +{ + 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; + }; + + class StorageMeter final : public ValueMeter + { + public: + StorageMeter( QQuickItem* parent = nullptr ) + : ValueMeter( parent ) + { + setFixedSize( 64, 64 ); + + connect( this, &ValueMeter::valueChanged, + this, &StorageMeter::setStatusColor ); + } + + private: + void setStatusColor( qreal value ) + { + const auto color = qskInterpolatedColorAt( + gradientHint( StoragePage::Status ).stops(), value / 100.0 ); + + setFillGradient( { color, color.lighter() } ); + setTextColor( color ); + } + }; +} struct StorageRowAnimator final : public QObject, public QskAnimator { @@ -85,8 +139,6 @@ void StoragePage::addRow( const QString& title, const QString& description, 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( QskFontRole::Headline ); @@ -136,5 +188,6 @@ void StoragePage::addRow( const QString& title, const QString& description, bar->setDocuments( media.documents * v ); bar->setOthers( media.others * v ); }; - connect( sync, &QskPushButton::clicked, animator, [ animator ]() { animator->start(); } ); + connect( sync, &QskPushButton::clicked, + animator, [ animator ]() { animator->start(); } ); } diff --git a/examples/iotdashboard/StoragePage.h b/examples/iotdashboard/StoragePage.h index efb09d41..7f1d0146 100644 --- a/examples/iotdashboard/StoragePage.h +++ b/examples/iotdashboard/StoragePage.h @@ -7,44 +7,15 @@ #include #include -#include - -class QQuickItem; class StoragePage final : public QskLinearBox { public: - QSK_SUBCONTROLS( Panel ) - explicit StoragePage( QQuickItem* parent = nullptr ); + QSK_SUBCONTROLS( Panel, Status ) + + 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 9deb9089..0667f21e 100644 --- a/examples/iotdashboard/TopBar.cpp +++ b/examples/iotdashboard/TopBar.cpp @@ -4,7 +4,7 @@ *****************************************************************************/ #include "TopBar.h" -#include "EnergyMeter.h" +#include "ValueMeter.h" #include #include @@ -41,6 +41,22 @@ namespace return TopBarItem::Item4; } } + + class EnergyMeter : public ValueMeter + { + public: + EnergyMeter( const QColor& textColor, + const QskGradient& gradient, int value, QQuickItem* parent ) + : ValueMeter( parent ) + { + setFillGradient( gradient ); + setValue( value ); + setTextColor( textColor ); + + setFixedSize( 57, 57 ); + } + }; + } TopBarItem::TopBarItem( @@ -62,9 +78,7 @@ TopBarItem::TopBarItem( const auto subcontrol = subcontrolForIndex( index ); const auto textColor = color( subcontrol | QskAspect::TextColor ); - auto pieChart = new EnergyMeter( - textColor, gradient, progress, pieChartAndDisplay ); - pieChart->setSizePolicy( Qt::Horizontal, QskSizePolicy::Constrained ); + (void) new EnergyMeter( textColor, gradient, progress, pieChartAndDisplay ); auto display = new QskLinearBox( Qt::Vertical, pieChartAndDisplay ); display->setSpacing( 0 ); diff --git a/examples/iotdashboard/ValueMeter.cpp b/examples/iotdashboard/ValueMeter.cpp new file mode 100644 index 00000000..43f34dd2 --- /dev/null +++ b/examples/iotdashboard/ValueMeter.cpp @@ -0,0 +1,42 @@ +/****************************************************************************** + * Copyright (C) 2022 Edelhirsch Software GmbH + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "ValueMeter.h" + +#include +#include + +ValueMeter::ValueMeter( QQuickItem* parent ) + : QskProgressRing( parent ) +{ + setAutoLayoutChildren( true ); + initSizePolicy( QskSizePolicy::MinimumExpanding, QskSizePolicy::Constrained ); + + m_label = new QskTextLabel( this ); + m_label->setSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Fixed ); + m_label->setLayoutAlignmentHint( Qt::AlignCenter ); + m_label->setFontRole( QskFontRole::Caption ); + + connect( this, &QskProgressRing::valueChanged, + this, &ValueMeter::updateMeter ); + + updateMeter( value() ); +} + +void ValueMeter::updateMeter( const qreal value ) +{ + m_label->setText( text( value ) ); +} + +QString ValueMeter::text( qreal value ) const +{ + value = static_cast< int >( value ); + return locale().toString( value ) + ' ' + locale().percent(); +} + +void ValueMeter::setTextColor( const QColor& color ) +{ + m_label->setTextColor( color ); +} diff --git a/examples/iotdashboard/ValueMeter.h b/examples/iotdashboard/ValueMeter.h new file mode 100644 index 00000000..52d31dd3 --- /dev/null +++ b/examples/iotdashboard/ValueMeter.h @@ -0,0 +1,25 @@ +/****************************************************************************** + * Copyright (C) 2022 Edelhirsch Software GmbH + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#pragma once + +#include + +class QskTextLabel; + +class ValueMeter : public QskProgressRing +{ + public: + ValueMeter( QQuickItem* parent = nullptr ); + + void setTextColor( const QColor& ); + + protected: + virtual QString text( qreal ) const; + + private: + void updateMeter( qreal value ); + QskTextLabel* m_label = nullptr; +}; diff --git a/examples/iotdashboard/images/icon2qvg.sh b/examples/iotdashboard/images/icon2qvg.sh new file mode 100755 index 00000000..c562ca17 --- /dev/null +++ b/examples/iotdashboard/images/icon2qvg.sh @@ -0,0 +1,15 @@ +#! /bin/sh + +if [ $# -eq 0 ]; then + echo "Usage $0 file ..." + exit 1 +fi + +for file in $* +do + base=`basename -s .svg $file` + echo "${base}.svg -> qvg/${base}.qvg" + svg2qvg ${base}.svg qvg/${base}.qvg +done + +exit $status diff --git a/examples/iotdashboard/images/qvg/ac.qvg b/examples/iotdashboard/images/qvg/ac.qvg index 56ba5116..dfb13034 100644 Binary files a/examples/iotdashboard/images/qvg/ac.qvg 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 index 85ae5e05..f1d86e56 100644 Binary files a/examples/iotdashboard/images/qvg/dashboard.qvg 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 index aefe5935..c3d323d6 100644 Binary files a/examples/iotdashboard/images/qvg/devices.qvg 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 index 71e37f4e..e4cc9906 100644 Binary files a/examples/iotdashboard/images/qvg/down.qvg 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 index f71f332c..c7f96afe 100644 Binary files a/examples/iotdashboard/images/qvg/humidity.qvg 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 index 34d9e6d5..e90a7c38 100644 Binary files a/examples/iotdashboard/images/qvg/indoor-temperature.qvg 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 index 42adabc6..4bf3df10 100644 Binary files a/examples/iotdashboard/images/qvg/lamps.qvg 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 index 9213e3c3..ede8a8c1 100644 Binary files a/examples/iotdashboard/images/qvg/logout.qvg 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 index 067c743a..7fc15101 100644 Binary files a/examples/iotdashboard/images/qvg/main-icon.qvg 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 index b6170fdc..4102ee76 100644 Binary files a/examples/iotdashboard/images/qvg/members.qvg 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 index 365c6090..600f2184 100644 Binary files a/examples/iotdashboard/images/qvg/music-system.qvg 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 index d0bb7001..6671862f 100644 Binary files a/examples/iotdashboard/images/qvg/rooms.qvg 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 index 5d413904..58db7763 100644 Binary files a/examples/iotdashboard/images/qvg/router.qvg 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 index c43fb548..1adc83f3 100644 Binary files a/examples/iotdashboard/images/qvg/statistics.qvg 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 index 1313ee76..4a1f4a9b 100644 Binary files a/examples/iotdashboard/images/qvg/storage.qvg 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 index 58c142f6..6c379e34 100644 Binary files a/examples/iotdashboard/images/qvg/up.qvg and b/examples/iotdashboard/images/qvg/up.qvg differ diff --git a/examples/iotdashboard/main.cpp b/examples/iotdashboard/main.cpp index 587f7ae5..f0b2c681 100644 --- a/examples/iotdashboard/main.cpp +++ b/examples/iotdashboard/main.cpp @@ -28,7 +28,6 @@ int main( int argc, char* argv[] ) QGuiApplication app( argc, argv ); - QskSetup::setUpdateFlag( QskItem::PreferRasterForTextures, true ); qskSkinManager->setSkin( new Skin() ); Qsk::addGraphicProvider( QString(), new GraphicProvider() ); diff --git a/examples/mycontrols/main.cpp b/examples/mycontrols/main.cpp index 5d961a88..ec9923c7 100644 --- a/examples/mycontrols/main.cpp +++ b/examples/mycontrols/main.cpp @@ -139,8 +139,6 @@ int main( int argc, char* argv[] ) qskSkinManager->registerFactory( QStringLiteral( "MySkinFactory" ), new MySkinFactory() ); - QskSetup::setUpdateFlag( QskItem::PreferRasterForTextures, true ); - Window window; window.resize( 480, 360 ); window.show(); diff --git a/examples/qvgviewer/qvg/Tux.qvg b/examples/qvgviewer/qvg/Tux.qvg index f94effbf..f37fe99b 100644 Binary files a/examples/qvgviewer/qvg/Tux.qvg and b/examples/qvgviewer/qvg/Tux.qvg differ diff --git a/playground/charts/CircularChartSkinlet.cpp b/playground/charts/CircularChartSkinlet.cpp index 736479a9..efdabda4 100644 --- a/playground/charts/CircularChartSkinlet.cpp +++ b/playground/charts/CircularChartSkinlet.cpp @@ -9,153 +9,10 @@ #include #include -#include +#include -#include #include -#define PAINTED_NODE 0 - -#if PAINTED_NODE - -/* - This is a fallback implementation for a user who is using an outdated - version of QSkinny where only shaders for linear gradients are available. - */ -#include -#include -#include -#include -#include - -namespace -{ - QConicalGradient qskQConicalGradient( - const QskGradientStops& stops, qreal startAngle, qreal spanAngle ) - { - QskGradientStops scaledStops; - scaledStops.reserve( stops.size() ); - - const auto ratio = qAbs( spanAngle ) / 360.0; - - if ( spanAngle > 0.0 ) - { - for ( auto it = stops.cbegin(); it != stops.cend(); ++it ) - scaledStops += { ratio * it->position(), it->color() }; - } - else - { - for ( auto it = stops.crbegin(); it != stops.crend(); ++it ) - scaledStops += { 1.0 - ratio * it->position(), it->color() }; - } - - QConicalGradient qGradient( QPointF(), startAngle ); - qGradient.setStops( qskToQGradientStops( scaledStops ) ); - - return qGradient; - } - - class PaintedArcNode : public QskPaintedNode - { - public: - void setArcData( const QRectF&, const QskArcMetrics&, - qreal, const QColor&, const QskGradient&, QQuickWindow* ); - - protected: - void paint( QPainter*, const QSize&, const void* nodeData ) override; - QskHashValue hash( const void* nodeData ) const override; - - private: - QskHashValue arcHash( const QRectF&, const QskArcMetrics&, - qreal, const QColor&, const QskGradient& ) const; - - QBrush fillBrush( const QskGradient&, const QRectF&, qreal, qreal ) const; - - struct ArcData - { - QPointF translation; - QPen pen; - QBrush brush; - QPainterPath path; - - QskHashValue hash; - }; - }; - - void PaintedArcNode::setArcData( - const QRectF& rect, const QskArcMetrics& metrics, - qreal borderWidth, const QColor& borderColor, - const QskGradient& gradient, QQuickWindow* window ) - { - const auto hash = arcHash( rect, metrics, borderWidth, borderColor, gradient ); - - const auto brush = fillBrush( gradient, rect, - metrics.startAngle(), metrics.spanAngle() ); - - QPen pen( borderColor, borderWidth ); - if ( borderWidth <= 0.0 ) - pen.setStyle( Qt::NoPen ); - - const auto path = metrics.painterPath( rect ); - const auto r = path.controlPointRect(); - - const ArcData arcData { r.topLeft(), pen, brush, path, hash }; - update( window, r, QSizeF(), &arcData ); - } - - void PaintedArcNode::paint( QPainter* painter, const QSize&, const void* nodeData ) - { - const auto arcData = reinterpret_cast< const ArcData* >( nodeData ); - - painter->setRenderHint( QPainter::Antialiasing, true ); - painter->translate( -arcData->translation ); - painter->setPen( arcData->pen ); - painter->setBrush( arcData->brush ); - painter->drawPath( arcData->path ); - } - - QskHashValue PaintedArcNode::hash( const void* nodeData ) const - { - const auto arcData = reinterpret_cast< const ArcData* >( nodeData ); - return arcData->hash; - } - - QBrush PaintedArcNode::fillBrush( const QskGradient& gradient, - const QRectF& rect, qreal startAngle, qreal spanAngle ) const - { - const auto qGradient = qskQConicalGradient( - gradient.stops(), startAngle, spanAngle ); - - const qreal sz = qMax( rect.width(), rect.height() ); - const qreal sx = rect.width() / sz; - const qreal sy = rect.height() / sz; - - QTransform t; - t.scale( sx, sy ); - t.translate( rect.center().x() / sx, rect.center().y() / sy ); - - QBrush brush( qGradient ); - brush.setTransform( t ); - - return brush; - } - - inline QskHashValue PaintedArcNode::arcHash( - const QRectF& rect, const QskArcMetrics& metrics, qreal borderWidth, - const QColor& borderColor, const QskGradient& gradient ) const - { - auto hash = metrics.hash( 6753 ); - hash = qHashBits( &rect, sizeof( rect ), hash ); - hash = qHash( borderWidth, hash ); - hash = qHash( borderColor.rgba(), hash ); - hash = gradient.hash( hash ); - - return hash; - } -} - -#endif // PAINTED_NODE - namespace { inline QskArcMetrics segmentMetrics( @@ -334,39 +191,16 @@ QSGNode* CircularChartSkinlet::updateSampleNode( const QskSkinnable* skinnable, return nullptr; } -QSGNode* CircularChartSkinlet::updateArcSegmentNode( - const QskSkinnable* skinnable, +QSGNode* CircularChartSkinlet::updateArcSegmentNode( const QskSkinnable*, QSGNode* node, qreal borderWidth, const QColor& borderColor, const QskGradient& gradient, const QskArcMetrics& metrics ) const { -#if PAINTED_NODE - auto arcNode = static_cast< PaintedArcNode* >( node ); + auto arcNode = static_cast< QskArcRenderNode* >( node ); if ( arcNode == nullptr ) - arcNode = new PaintedArcNode(); + arcNode = new QskArcRenderNode(); - const auto chart = static_cast< const CircularChart* >( skinnable ); - - arcNode->setArcData( m_data->closedArcRect, metrics, - borderWidth, borderColor, gradient, chart->window() ); -#else - Q_UNUSED( skinnable ) - - auto fillGradient = gradient; - - if ( fillGradient.type() == QskGradient::Stops ) - { - fillGradient.setStretchMode( QskGradient::StretchToSize ); - fillGradient.setConicDirection( 0.5, 0.5, - metrics.startAngle(), metrics.spanAngle() ); - } - - auto arcNode = static_cast< QskArcNode* >( node ); - if ( arcNode == nullptr ) - arcNode = new QskArcNode(); - - arcNode->setArcData( m_data->closedArcRect, metrics, - borderWidth, borderColor, fillGradient ); -#endif + arcNode->updateNode( m_data->closedArcRect, metrics, true, + borderWidth, borderColor, gradient ); return arcNode; } diff --git a/playground/shadows/ArcPage.cpp b/playground/shadows/ArcPage.cpp index 3ed5c5e4..b4d020e1 100644 --- a/playground/shadows/ArcPage.cpp +++ b/playground/shadows/ArcPage.cpp @@ -73,32 +73,36 @@ namespace } } }; + + class Arc : public ShadowedArc + { + public: + Arc() + { + setStartAngle( 45.0 ); + setSpanAngle( 270.0 ); + setThickness( 10.0 ); + + setFillGradient( Qt::darkRed ); + + setBorderWidth( 0 ); + setBorderColor( Qt::darkYellow ); + + setShadowColor( Qt::black ); + setSpreadRadius( 0.0 ); + setBlurRadius( 4.0 ); + setOffsetX( 2.0 ); + setOffsetY( 2.0 ); + } + }; } ArcPage::ArcPage( QQuickItem* parent ) : QskLinearBox( Qt::Vertical, parent ) { - auto arc = new ShadowedArc(); + auto arc = new Arc(); arc->setMargins( 40 ); // some extra space for testing the offsets - { - // initial settings - arc->setStartAngle( 45.0 ); - arc->setSpanAngle( 270.0 ); - arc->setThickness( 10.0 ); - - arc->setFillColor( Qt::darkRed ); - - arc->setBorderWidth( 0 ); - arc->setBorderColor( Qt::darkYellow ); - - arc->setShadowColor( Qt::black ); - arc->setSpreadRadius( 0.0 ); - arc->setBlurRadius( 4.0 ); - arc->setOffsetX( 2.0 ); - arc->setOffsetY( 2.0 ); - } - auto panel = new ControlPanel( arc ); panel->setSizePolicy( Qt::Vertical, QskSizePolicy::Fixed ); diff --git a/playground/shadows/ShadowedArc.cpp b/playground/shadows/ShadowedArc.cpp index bb209f3f..6f7ff077 100644 --- a/playground/shadows/ShadowedArc.cpp +++ b/playground/shadows/ShadowedArc.cpp @@ -101,7 +101,7 @@ ShadowedArc::ShadowedArc( QQuickItem* parent ) setArcMetrics( { 0.0, 360.0, 1.0, Qt::RelativeSize } ); - setFillColor( Qt::darkRed ); + setFillGradient( Qt::darkRed ); setBorderWidth( 0 ); setBorderColor( Qt::gray ); @@ -216,14 +216,14 @@ qreal ShadowedArc::blurRadius() const return shadowMetrics().blurRadius(); } -void ShadowedArc::setFillColor( const QColor& color ) +void ShadowedArc::setFillGradient( const QskGradient& gradient ) { - setColor( Arc, color ); + setGradientHint( Arc, gradient ); } -QColor ShadowedArc::fillColor() const +QskGradient ShadowedArc::fillGradient() const { - return color( Arc ); + return gradientHint( Arc ); } void ShadowedArc::setShadowColor( const QColor& color ) diff --git a/playground/shadows/ShadowedArc.h b/playground/shadows/ShadowedArc.h index c8f486d3..2f683e26 100644 --- a/playground/shadows/ShadowedArc.h +++ b/playground/shadows/ShadowedArc.h @@ -9,6 +9,7 @@ class QskShadowMetrics; class QskArcMetrics; +class QskGradient; class ShadowedArc : public QskControl { @@ -35,7 +36,7 @@ class ShadowedArc : public QskControl qreal blurRadius() const; QColor borderColor() const; - QColor fillColor() const; + QskGradient fillGradient() const; QColor shadowColor() const; public Q_SLOTS: @@ -52,7 +53,7 @@ class ShadowedArc : public QskControl void setBlurRadius( qreal ); void setBorderColor( const QColor& ); - void setFillColor( const QColor& ); + void setFillGradient( const QskGradient& ); void setShadowColor( const QColor& ); private: diff --git a/qmlexport/CMakeLists.txt b/qmlexport/CMakeLists.txt index a4660b24..e6d51add 100644 --- a/qmlexport/CMakeLists.txt +++ b/qmlexport/CMakeLists.txt @@ -3,16 +3,25 @@ # SPDX-License-Identifier: BSD-3-Clause ############################################################################ -set(HEADERS +list(APPEND HEADERS QskQmlGlobal.h QskShortcutQml.h QskLayoutQml.h - QskQml.h) + QskQmlModule.h + QskQmlRegister.h + QskQml.h +) -set(SOURCES +list(APPEND SOURCES QskShortcutQml.cpp QskLayoutQml.cpp - QskQml.cpp) + QskQml.cpp +) + +if (QT_VERSION_MAJOR GREATER_EQUAL 6) + list(APPEND HEADERS QskQmlClassInfo.h) + list(APPEND SOURCES QskQmlClassInfo.cpp) +endif() set(target qskqmlexport) diff --git a/qmlexport/QskQml.cpp b/qmlexport/QskQml.cpp index 54a00043..e1a251db 100644 --- a/qmlexport/QskQml.cpp +++ b/qmlexport/QskQml.cpp @@ -4,8 +4,8 @@ *****************************************************************************/ #include "QskQml.h" -#include "QskQml.hpp" +#include "QskQmlRegister.h" #include "QskLayoutQml.h" #include "QskShortcutQml.h" @@ -44,8 +44,6 @@ #include #include #include -#include -#include #include #include #include @@ -61,9 +59,9 @@ #include #if QT_VERSION < QT_VERSION_CHECK( 6, 2, 0 ) - QSK_QT_PRIVATE_BEGIN +QSK_QT_PRIVATE_BEGIN #include - QSK_QT_PRIVATE_END +QSK_QT_PRIVATE_END #endif #if QT_VERSION < QT_VERSION_CHECK( 6, 5, 0 ) @@ -191,9 +189,6 @@ namespace void QskQml::registerTypes() { - qmlRegisterUncreatableType< QskSkin >( QSK_MODULE_NAME, 1, 0, "Skin", QString() ); - qRegisterMetaType< QskSkin* >(); - registerObject< QskShortcutQml >( "Shortcut" ); registerObject< QskWindow >(); diff --git a/qmlexport/QskQml.hpp b/qmlexport/QskQml.hpp deleted file mode 100644 index dff4db9a..00000000 --- a/qmlexport/QskQml.hpp +++ /dev/null @@ -1,276 +0,0 @@ -/****************************************************************************** - * QSkinny - Copyright (C) The authors - * SPDX-License-Identifier: BSD-3-Clause - *****************************************************************************/ - -#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 ); - } - - inline 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/QskQmlClassInfo.cpp b/qmlexport/QskQmlClassInfo.cpp new file mode 100644 index 00000000..a51e4203 --- /dev/null +++ b/qmlexport/QskQmlClassInfo.cpp @@ -0,0 +1,86 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskQmlClassInfo.h" +#include "QskQmlModule.h" +#include + +using namespace QskQml; + +ClassInfo::ClassInfo( const char* qmlName, const QMetaObject* metaObject ) +{ + m_info.structVersion = QSK_STRUCT_VERSION; + + m_info.objectSize = 0; + m_info.create = nullptr; + m_info.userdata = nullptr; // unused + + /* + 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 ... + */ + m_info.createValueType = nullptr; + + m_info.uri = QskQmlModule::name; + m_info.version = QTypeRevision::fromVersion( + QskQmlModule::version[0], QskQmlModule::version[1] ); + + m_info.elementName = qmlName; + m_info.metaObject = metaObject; + + /* + We do not use attached properties as it always comes with + creating extra QObjects. + */ + m_info.attachedPropertiesFunction = nullptr; + m_info.attachedPropertiesMetaObject = nullptr; + + m_info.parserStatusCast = m_info.valueSourceCast = m_info.valueInterceptorCast = -1; + + /* + We do not use extensions as it always comes with + creating extra QObjects. + */ + m_info.extensionObjectCreate = nullptr; + m_info.extensionMetaObject = nullptr; + + m_info.customParser = nullptr; // QQmlCustomParser, unused + + m_info.revision = QTypeRevision::zero(); + m_info.finalizerCast = -1; + + m_info.creationMethod = QQmlPrivate::ValueTypeCreationMethod::Structured; +} + +QByteArray ClassInfo::normalizedListName( + const char* containerName, const QMetaObject& metaObject ) +{ + static QByteArray name; + name.reserve( 256 ); + + const int length1 = strlen( containerName ); + const int length2 = strlen( metaObject.className() ); + + name.resize( length1 + length2 + 3 ); + + auto p = name.data(); + + memcpy( p, containerName, size_t( length1 ) ); + p += length1; + + *p++ = '<'; + + memcpy( p, metaObject.className(), size_t( length2 ) ); + p += length2; + + *p++ = '>'; + *p++ = '\0'; + + return name; +} diff --git a/qmlexport/QskQmlClassInfo.h b/qmlexport/QskQmlClassInfo.h new file mode 100644 index 00000000..e3698c6d --- /dev/null +++ b/qmlexport/QskQmlClassInfo.h @@ -0,0 +1,84 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_QML_CLASSINFO_H +#define QSK_QML_CLASSINFO_H + +#include + +class QByteArray; + +#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 + +namespace QskQml +{ + class ClassInfo + { + public: + ClassInfo( const char* qmlName, const QMetaObject* ); + template< typename T > void setTypeInfo(); + + int registerType(); + + private: + static QByteArray normalizedListName( + const char* containerName, const QMetaObject& ); + + QQmlPrivate::RegisterType m_info; + }; + + template< typename T > + inline void ClassInfo::setTypeInfo() + { + using namespace QQmlPrivate; + + constexpr bool isObject = std::is_base_of_v< QObject, T >; + + if ( isObject ) + m_info.typeId = QMetaType::fromType< T* >(); + else + m_info.typeId = QMetaType::fromType< T >(); + + m_info.objectSize = sizeof( T ); + m_info.create = Constructors< T >::createInto; + + m_info.createValueType = ValueType< T, void >::create; + + const auto name = normalizedListName( + isObject ? "QQmlListProperty" : "QList", T::staticMetaObject ); + + /* + QMetaType::fromType< QList< T >() creates a lot of symbols + that end up in QskQml.o for all gadgets. So we export only + registered lists. Registration might be done in the qskinny library + itself - or in QskQml.cpp. + + As we do not design plain data being a QObject I'm not sure + if we need to have QQmlListProperty< T > at all ... + */ + m_info.listId = QMetaType::fromName( name.constData() ); + + m_info.parserStatusCast = StaticCastSelector< T, QQmlParserStatus >::cast(); + m_info.valueSourceCast = StaticCastSelector< T, QQmlPropertyValueSource >::cast(); + m_info.valueInterceptorCast = StaticCastSelector< T, QQmlPropertyValueInterceptor >::cast(); + +#if QSK_STRUCT_VERSION >= 1 + m_info.finalizerCast = StaticCastSelector< T, QQmlFinalizerHook >::cast(); +#endif + } + + inline int ClassInfo::registerType() + { + return qmlregister( QQmlPrivate::TypeRegistration, &m_info ); + } +} + +#endif diff --git a/qmlexport/QskQmlModule.h b/qmlexport/QskQmlModule.h new file mode 100644 index 00000000..e036ef8a --- /dev/null +++ b/qmlexport/QskQmlModule.h @@ -0,0 +1,17 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_QML_MODULE_H +#define QSK_QML_MODULE_H + +namespace QskQmlModule +{ + const char name[] = "Skinny"; + + // major, minor + const int version[] = { 1, 0 }; +} + +#endif diff --git a/qmlexport/QskQmlRegister.h b/qmlexport/QskQmlRegister.h new file mode 100644 index 00000000..134309b2 --- /dev/null +++ b/qmlexport/QskQmlRegister.h @@ -0,0 +1,131 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_QML_REGISTER_H +#define QSK_QML_REGISTER_H + +#include "QskQmlModule.h" + +#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) +#include "QskQmlClassInfo.h" +#endif + +#include + +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) + #define QSK_QML_REGISTER 0 +#else + #define QSK_QML_REGISTER 1 +#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; + } + + template< typename T > + inline int registerUncreatableType( const char* qmlName ) + { +#if QSK_QML_REGISTER + ClassInfo typeInfo( qmlName, &T::staticMetaObject ); + typeInfo.setTypeInfo< T >(); + + return typeInfo.registerType(); +#else + return qmlRegisterUncreatableType< T >( QskQmlModule::name, + QskQmlModule::version[0], QskQmlModule::version[1], qmlName, QString() ); +#endif + } + + inline int registerUncreatableMetaObject( + const QMetaObject& staticMetaObject, const char* qmlName ) + { +#if QSK_QML_REGISTER + ClassInfo typeInfo( qmlName, &staticMetaObject ); + return typeInfo.registerType(); +#else + return qmlRegisterUncreatableMetaObject( staticMetaObject, + QskQmlModule::name, QskQmlModule::version[0], QskQmlModule::version[1], + qmlName, QString() ); +#endif + } + + template< typename T > + inline int registerObject( const char* qmlName = nullptr ) + { + // the class name without the "Qsk" prefix + if ( qmlName == nullptr ) + qmlName = classNameQml( T::staticMetaObject ); + +#if QSK_QML_REGISTER + ClassInfo typeInfo( qmlName, &T::staticMetaObject ); + typeInfo.setTypeInfo< T >(); + + return typeInfo.registerType(); +#else + return qmlRegisterType< T >( QskQmlModule::name, + QskQmlModule::version[0], QskQmlModule::version[1], qmlName ); +#endif + } + + template< typename T > + inline int registerGadget() + { + auto className = classNameQml( T::staticMetaObject ); + +#if QSK_QML_REGISTER + /* + According to the QML naming rules uncreatables have to + start with a lowercase letter, 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] ); + + return registerUncreatableType< T >( name.constData() ); +#else + return registerUncreatableType< T >( className ); +#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( QskQmlModule::name, + QskQmlModule::version[0], QskQmlModule::version[1], name, singleton ); + } +} + +#ifdef QSK_QML_REGISTER + #undef QSK_QML_REGISTER +#endif + +#endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 02d6957b..64d031b2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -101,6 +101,8 @@ list(APPEND SOURCES list(APPEND HEADERS nodes/QskArcNode.h + nodes/QskArcRenderer.h + nodes/QskArcRenderNode.h nodes/QskArcShadowNode.h nodes/QskBasicLinesNode.h nodes/QskBoxNode.h @@ -141,6 +143,8 @@ list(APPEND PRIVATE_HEADERS list(APPEND SOURCES nodes/QskArcNode.cpp + nodes/QskArcRenderer.cpp + nodes/QskArcRenderNode.cpp nodes/QskArcShadowNode.cpp nodes/QskBasicLinesNode.cpp nodes/QskBoxNode.cpp diff --git a/src/common/QskArcMetrics.cpp b/src/common/QskArcMetrics.cpp index a7981827..220630f9 100644 --- a/src/common/QskArcMetrics.cpp +++ b/src/common/QskArcMetrics.cpp @@ -21,6 +21,83 @@ static void qskRegisterArcMetrics() Q_CONSTRUCTOR_FUNCTION( qskRegisterArcMetrics ) +static inline QPainterPath qskRadialPathPath( + const QRectF& rect, qreal startAngle, qreal spanAngle, qreal width ) +{ + const auto sz = qMin( rect.width(), rect.height() ); + + const auto tx = width * rect.width() / sz; + const auto ty = width * rect.height() / sz; + + const auto innerRect = rect.adjusted( tx, ty, -tx, -ty ); + + QPainterPath path; + + if ( innerRect.isEmpty() ) + { + if ( qAbs( spanAngle ) >= 360.0 ) + { + path.addEllipse( rect ); + } + else + { + // pie + path.arcMoveTo( rect, startAngle ); + path.arcTo( rect, startAngle, spanAngle ); + path.lineTo( rect.center() ); + path.closeSubpath(); + } + } + else + { + if ( qAbs( spanAngle ) >= 360.0 ) + { + path.addEllipse( rect ); + + QPainterPath innerPath; + innerPath.addEllipse( innerRect ); + path -= innerPath; + } + else + { + /* + We need the end point of the inner arc to add the line that connects + the inner/outer arcs. As QPainterPath does not offer such a method + we insert a dummy arcMoveTo and grab the calculated position. + */ + path.arcMoveTo( innerRect, startAngle + spanAngle ); + const auto pos = path.currentPosition(); + + path.arcMoveTo( rect, startAngle ); // replaces the dummy arcMoveTo above + path.arcTo( rect, startAngle, spanAngle ); + + path.lineTo( pos ); + path.arcTo( innerRect, startAngle + spanAngle, -spanAngle ); + + path.closeSubpath(); + } + } + + return path; +} + +static inline QPainterPath qskOrthogonalPath( + const QRectF& rect, qreal startAngle, qreal spanAngle, qreal width ) +{ + const auto t2 = 0.5 * width; + const auto r = rect.adjusted( t2, t2, -t2, -t2 ); + + QPainterPath arcPath; + arcPath.arcMoveTo( r, startAngle ); + arcPath.arcTo( r, startAngle, spanAngle ); + + QPainterPathStroker stroker; + stroker.setCapStyle( Qt::FlatCap ); + stroker.setWidth( width ); + + return stroker.createStroke( arcPath ); +} + static inline qreal qskInterpolated( qreal from, qreal to, qreal ratio ) { return from + ( to - from ) * ratio; @@ -48,7 +125,7 @@ void QskArcMetrics::setSpanAngle( qreal spanAngle ) noexcept void QskArcMetrics::setSizeMode( Qt::SizeMode sizeMode ) noexcept { - m_sizeMode = sizeMode; + m_relativeSize = ( sizeMode == Qt::RelativeSize ); } bool QskArcMetrics::isClosed() const @@ -79,7 +156,7 @@ bool QskArcMetrics::containsAngle( qreal angle ) const QskArcMetrics QskArcMetrics::interpolated( const QskArcMetrics& to, qreal ratio ) const noexcept { - if ( ( *this == to ) || ( m_sizeMode != to.m_sizeMode ) ) + if ( ( *this == to ) || ( m_relativeSize != to.m_relativeSize ) ) return to; const qreal thickness = qskInterpolated( m_thickness, to.m_thickness, ratio ); @@ -87,7 +164,7 @@ QskArcMetrics QskArcMetrics::interpolated( const qreal s1 = qskInterpolated( m_startAngle, to.m_startAngle, ratio ); const qreal s2 = qskInterpolated( endAngle(), to.endAngle(), ratio ); - return QskArcMetrics( s1, s2 - s1, thickness, m_sizeMode ); + return QskArcMetrics( s1, s2 - s1, thickness, sizeMode() ); } QVariant QskArcMetrics::interpolate( @@ -97,6 +174,11 @@ QVariant QskArcMetrics::interpolate( return QVariant::fromValue( from.interpolated( to, progress ) ); } +QskArcMetrics QskArcMetrics::toAbsolute( const QSizeF& size ) const noexcept +{ + return toAbsolute( 0.5 * size.width(), 0.5 * size.height() ); +} + QskArcMetrics QskArcMetrics::toAbsolute( qreal radiusX, qreal radiusY ) const noexcept { if ( radiusX < 0.0 ) @@ -110,73 +192,33 @@ QskArcMetrics QskArcMetrics::toAbsolute( qreal radiusX, qreal radiusY ) const no QskArcMetrics QskArcMetrics::toAbsolute( qreal radius ) const noexcept { - if ( m_sizeMode != Qt::RelativeSize ) + if ( !m_relativeSize ) return *this; const qreal t = qskEffectiveThickness( radius, m_thickness ); return QskArcMetrics( m_startAngle, m_spanAngle, t, Qt::AbsoluteSize ); } -QPainterPath QskArcMetrics::painterPath( const QRectF& ellipseRect ) const +QPainterPath QskArcMetrics::painterPath( const QRectF& rect, bool radial ) const { - const auto sz = qMin( ellipseRect.width(), ellipseRect.height() ); - - qreal t = m_thickness; - if ( m_sizeMode == Qt::RelativeSize ) - t = qskEffectiveThickness( 0.5 * sz, t ); - - if ( t <= 0.0 || qFuzzyIsNull( m_spanAngle ) ) - return QPainterPath(); - - const auto tx = t * ellipseRect.width() / sz; - const auto ty = t * ellipseRect.height() / sz; - - const auto innerRect = ellipseRect.adjusted( tx, ty, -tx, -ty ); - QPainterPath path; - if ( innerRect.isEmpty() ) + if ( !qFuzzyIsNull( m_spanAngle ) ) { - if ( qAbs( m_spanAngle ) >= 360.0 ) + qreal t = m_thickness; + + if ( m_relativeSize ) { - path.addEllipse( ellipseRect ); + const auto sz = qMin( rect.width(), rect.height() ); + t = qskEffectiveThickness( 0.5 * sz, t ); } - else + + if ( t > 0.0 ) { - // pie - path.arcMoveTo( ellipseRect, m_startAngle ); - path.arcTo( ellipseRect, m_startAngle, m_spanAngle ); - path.lineTo( ellipseRect.center() ); - path.closeSubpath(); - } - } - else - { - if ( qAbs( m_spanAngle ) >= 360.0 ) - { - path.addEllipse( ellipseRect ); - - QPainterPath innerPath; - innerPath.addEllipse( innerRect ); - path -= innerPath; - } - else - { - /* - We need the end point of the inner arc to add the line that connects - the inner/outer arcs. As QPainterPath does not offer such a method - we insert a dummy arcMoveTo and grab the calculated position. - */ - path.arcMoveTo( innerRect, m_startAngle + m_spanAngle ); - const auto pos = path.currentPosition(); - - path.arcMoveTo( ellipseRect, m_startAngle ); // replaces the dummy arcMoveTo above - path.arcTo( ellipseRect, m_startAngle, m_spanAngle ); - - path.lineTo( pos ); - path.arcTo( innerRect, m_startAngle + m_spanAngle, -m_spanAngle ); - - path.closeSubpath(); + if ( radial ) + path = qskRadialPathPath( rect, m_startAngle, m_spanAngle, t ); + else + path = qskOrthogonalPath( rect, m_startAngle, m_spanAngle, t ); } } @@ -212,8 +254,7 @@ QskHashValue QskArcMetrics::hash( QskHashValue seed ) const noexcept hash = qHash( m_startAngle, hash ); hash = qHash( m_spanAngle, hash ); - const int mode = m_sizeMode; - return qHashBits( &mode, sizeof( mode ), hash ); + return qHash( m_relativeSize, hash ); } #ifndef QT_NO_DEBUG_STREAM diff --git a/src/common/QskArcMetrics.h b/src/common/QskArcMetrics.h index fe80ed46..608e9d8e 100644 --- a/src/common/QskArcMetrics.h +++ b/src/common/QskArcMetrics.h @@ -29,8 +29,8 @@ class QSK_EXPORT QskArcMetrics constexpr QskArcMetrics( qreal thickness, Qt::SizeMode = Qt::AbsoluteSize ) noexcept; - constexpr QskArcMetrics( qreal startAngle, qreal spanAngle, - qreal thickness, Qt::SizeMode = Qt::AbsoluteSize ) noexcept; + constexpr QskArcMetrics( qreal startAngle, qreal spanAngle, qreal thickness, + Qt::SizeMode = Qt::AbsoluteSize ) noexcept; bool operator==( const QskArcMetrics& ) const noexcept; bool operator!=( const QskArcMetrics& ) const noexcept; @@ -58,10 +58,26 @@ class QSK_EXPORT QskArcMetrics QskArcMetrics interpolated( const QskArcMetrics&, qreal value ) const noexcept; + QskArcMetrics toAbsolute( const QSizeF& ) const noexcept; QskArcMetrics toAbsolute( qreal radiusX, qreal radiusY ) const noexcept; QskArcMetrics toAbsolute( qreal radius ) const noexcept; - QPainterPath painterPath( const QRectF& ellipseRect ) const; + /* + The arc is interpolated by pairs of points, where one point is on + the outer and the other on the inner side of the arc. The length between + these points depends on the thickness. + + When radial is set the inner point lies on the line between the outer point + and the center of the arc. This corresponds to the lines of a conic gradient. + + Otherwise the line between the inner and outer point is orthogonal to the + tangent at the point in the middle of the arc. This is how the width + of the pen is expanded by QPainter::drawArc. + + Note, that the radial flag is irrelevant for circular arcs as the tangent + is always orthogonal to any point on the circle. + */ + QPainterPath painterPath( const QRectF& ellipseRect, bool radial = false ) const; QRectF boundingRect( const QRectF& ellipseRect ) const; QSizeF boundingSize( const QSizeF& ellipseSize ) const; @@ -76,7 +92,8 @@ class QSK_EXPORT QskArcMetrics qreal m_spanAngle = 0.0; qreal m_thickness = 0.0; - Qt::SizeMode m_sizeMode = Qt::AbsoluteSize; + + bool m_relativeSize = false; }; inline constexpr QskArcMetrics::QskArcMetrics( @@ -85,23 +102,22 @@ inline constexpr QskArcMetrics::QskArcMetrics( { } -inline constexpr QskArcMetrics::QskArcMetrics( - qreal startAngle, qreal spanAngle, +inline constexpr QskArcMetrics::QskArcMetrics( qreal startAngle, qreal spanAngle, qreal thickness, Qt::SizeMode sizeMode ) noexcept : m_startAngle( startAngle ) , m_spanAngle( spanAngle ) , m_thickness( thickness ) - , m_sizeMode( sizeMode ) + , m_relativeSize( sizeMode == Qt::RelativeSize ) { } inline bool QskArcMetrics::operator==( const QskArcMetrics& other ) const noexcept { - return ( qskFuzzyCompare( m_thickness, other.m_thickness ) + return qskFuzzyCompare( m_thickness, other.m_thickness ) && qskFuzzyCompare( m_startAngle, other.m_startAngle ) && qskFuzzyCompare( m_spanAngle, other.m_spanAngle ) - && m_sizeMode == other.m_sizeMode ); + && ( m_relativeSize == other.m_relativeSize ); } inline bool QskArcMetrics::operator!=( @@ -142,7 +158,7 @@ inline constexpr qreal QskArcMetrics::angleAtRatio( qreal ratio ) const noexcept inline constexpr Qt::SizeMode QskArcMetrics::sizeMode() const noexcept { - return m_sizeMode; + return m_relativeSize ? Qt::RelativeSize : Qt::AbsoluteSize; } #ifndef QT_NO_DEBUG_STREAM diff --git a/src/common/QskGraduation.cpp b/src/common/QskGraduation.cpp index 3d20132d..1eab1f28 100644 --- a/src/common/QskGraduation.cpp +++ b/src/common/QskGraduation.cpp @@ -17,8 +17,6 @@ namespace Engine { // What about using qskFuzzyCompare and friends ??? - const double _eps = 1.0e-6; - inline int fuzzyCompare( double value1, double value2, double intervalSize ) { const double eps = std::abs( 1.0e-6 * intervalSize ); diff --git a/src/common/QskMetaInvokable.cpp b/src/common/QskMetaInvokable.cpp index 09a576f0..31a6c0df 100644 --- a/src/common/QskMetaInvokable.cpp +++ b/src/common/QskMetaInvokable.cpp @@ -26,15 +26,6 @@ static void qskRegisterMetaInvokable() Q_CONSTRUCTOR_FUNCTION( qskRegisterMetaInvokable ) -static inline void* qskMetaTypeCreate( int type, const void* copy ) -{ -#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) - return QMetaType( type ).create( copy ); -#else - return QMetaType::create( type, copy ); -#endif -} - namespace { using CallFunction = QObjectPrivate::StaticMetaCallFunction; diff --git a/src/controls/QskComboBox.cpp b/src/controls/QskComboBox.cpp index 47721c8a..0ca87675 100644 --- a/src/controls/QskComboBox.cpp +++ b/src/controls/QskComboBox.cpp @@ -237,7 +237,7 @@ void QskComboBox::openPopup() auto menu = new QskMenu(); menu->setWrapping( false ); - menu->setOrigin( mapToScene( cr.topLeft() ) ); + menu->setOrigin( mapToScene( cr.bottomLeft() ) ); menu->setFixedWidth( cr.width() ); menu->setOptions( m_data->options ); diff --git a/src/controls/QskProgressBarSkinlet.cpp b/src/controls/QskProgressBarSkinlet.cpp index 74fe9e69..225fb44c 100644 --- a/src/controls/QskProgressBarSkinlet.cpp +++ b/src/controls/QskProgressBarSkinlet.cpp @@ -15,13 +15,13 @@ using Q = QskProgressBar; namespace { - QskIntervalF qskFillInterval( const Q* bar ) + QskIntervalF qskFillInterval( const QskProgressIndicator* indicator ) { qreal pos1, pos2; - if ( bar->isIndeterminate() ) + if ( indicator->isIndeterminate() ) { - const auto pos = bar->positionHint( QskProgressIndicator::Fill ); + const auto pos = indicator->positionHint( QskProgressIndicator::Fill ); static const QEasingCurve curve( QEasingCurve::InOutCubic ); @@ -32,10 +32,11 @@ namespace } else { - pos1 = bar->valueAsRatio( bar->origin() ); - pos2 = bar->valueAsRatio( bar->value() ); + pos1 = indicator->valueAsRatio( indicator->origin() ); + pos2 = indicator->valueAsRatio( indicator->value() ); } + auto bar = static_cast< const QskProgressBar* >( indicator ); if( bar->orientation() == Qt::Horizontal ) { if ( bar->layoutMirroring() ) @@ -107,11 +108,11 @@ QSGNode* QskProgressBarSkinlet::updateFillNode( const auto subControl = Q::Fill; - const auto rect = bar->subControlRect( subControl ); + const auto rect = indicator->subControlRect( subControl ); if ( rect.isEmpty() ) return nullptr; - auto gradient = bar->gradientHint( subControl ); + auto gradient = indicator->gradientHint( subControl ); if ( !gradient.isVisible() ) return nullptr; diff --git a/src/controls/QskProgressRing.cpp b/src/controls/QskProgressRing.cpp index dcc1d8d6..af6c414b 100644 --- a/src/controls/QskProgressRing.cpp +++ b/src/controls/QskProgressRing.cpp @@ -4,7 +4,6 @@ *****************************************************************************/ #include "QskProgressRing.h" - #include "QskIntervalF.h" QSK_SUBCONTROL( QskProgressRing, Groove ) @@ -20,6 +19,8 @@ QskProgressRing::QskProgressRing( qreal min, qreal max, QQuickItem* parent ) : Inherited( min, max, parent ) , m_data( new PrivateData ) { + initSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Fixed ); + m_data->size = NormalSize; setSubcontrolProxy( Inherited::Groove, Groove ); diff --git a/src/controls/QskProgressRingSkinlet.cpp b/src/controls/QskProgressRingSkinlet.cpp index 86e90329..167cb861 100644 --- a/src/controls/QskProgressRingSkinlet.cpp +++ b/src/controls/QskProgressRingSkinlet.cpp @@ -10,31 +10,6 @@ using Q = QskProgressRing; -namespace -{ - QskIntervalF qskFillInterval( const Q* ring ) - { - qreal pos1, pos2; - - if ( ring->isIndeterminate() ) - { - const auto pos = ring->positionHint( QskProgressIndicator::Fill ); - - pos1 = pos2 = pos; - } - else - { - pos1 = ring->valueAsRatio( ring->origin() ); - pos2 = ring->valueAsRatio( ring->value() ); - } - - if ( pos1 > pos2 ) - std::swap( pos1, pos2 ); - - return QskIntervalF( pos1, pos2 ); - } -} - QskProgressRingSkinlet::QskProgressRingSkinlet( QskSkin* skin ) : Inherited( skin ) { @@ -48,27 +23,8 @@ QRectF QskProgressRingSkinlet::subControlRect( const QskSkinnable* skinnable, const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const { - const auto ring = static_cast< const Q* >( skinnable ); - if( subControl == Q::Groove || subControl == Q::Fill ) - { - auto rect = contentsRect; - const auto size = ring->strutSizeHint( Q::Fill ); - - if( ring->layoutMirroring() ) - { - rect.setLeft( rect.right() - size.width() ); - } - else - { - rect.setWidth( size.width() ); - } - - rect.setTop( rect.top() + 0.5 * ( rect.height() - size.height() ) ); - rect.setHeight( size.height() ); - - return rect; - } + return contentsRect; return Inherited::subControlRect( skinnable, contentsRect, subControl ); } @@ -90,36 +46,74 @@ QSGNode* QskProgressRingSkinlet::updateFillNode( if ( rect.isEmpty() ) return nullptr; + const auto metrics = ring->arcMetricsHint( subControl ); + if ( metrics.isNull() ) + return nullptr; + auto gradient = ring->gradientHint( subControl ); if ( !gradient.isVisible() ) return nullptr; + const auto intv = fillInterval( ring ); + if ( ( gradient.type() == QskGradient::Stops ) && !gradient.isMonochrome() ) { - const auto center = rect.center(); - const auto arcMetrics = ring->arcMetricsHint( Q::Fill ); - gradient.setConicDirection( center.x(), center.y(), arcMetrics.startAngle(), arcMetrics.spanAngle() ); + const auto stops = qskExtractedGradientStops( gradient.stops(), + intv.lowerBound(), intv.upperBound() ); + + gradient.setStops( stops ); + + if ( metrics.spanAngle() < 0.0 ) + gradient.reverse(); } - const auto interval = qskFillInterval( ring ); - const auto arcMetrics = ring->arcMetricsHint( subControl ); - - const auto startAngle = arcMetrics.startAngle() + interval.lowerBound() * arcMetrics.spanAngle(); - const auto spanAngle = interval.upperBound() * arcMetrics.spanAngle(); + const auto startAngle = metrics.startAngle() + intv.lowerBound() * metrics.spanAngle(); + const auto spanAngle = intv.upperBound() * metrics.spanAngle(); return updateArcNode( ring, node, rect, gradient, startAngle, spanAngle, subControl ); } QSizeF QskProgressRingSkinlet::sizeHint( const QskSkinnable* skinnable, - Qt::SizeHint which, const QSizeF& ) const + Qt::SizeHint which, const QSizeF& constraint ) const { if ( which != Qt::PreferredSize ) return QSizeF(); - const auto ring = static_cast< const Q* >( skinnable ); + auto hint = skinnable->strutSizeHint( Q::Fill ); + hint = hint.expandedTo( skinnable->strutSizeHint( Q::Groove ) ); - const auto r = ring->strutSizeHint( Q::Fill ); - return r; + if ( !constraint.isEmpty() ) + { + const qreal aspectRatio = hint.isEmpty() ? 1.0 : hint.width() / hint.height(); + + if ( constraint.width() >= 0.0 ) + hint.setHeight( constraint.width() / aspectRatio ); + else + hint.setWidth( constraint.height() * aspectRatio ); + } + + return hint; +} + +QskIntervalF QskProgressRingSkinlet::fillInterval( + const QskProgressIndicator* indicator ) const +{ + qreal pos1, pos2; + + if ( indicator->isIndeterminate() ) + { + pos1 = pos2 = indicator->positionHint( QskProgressIndicator::Fill ); + } + else + { + pos1 = indicator->valueAsRatio( indicator->origin() ); + pos2 = indicator->valueAsRatio( indicator->value() ); + } + + if ( pos1 > pos2 ) + std::swap( pos1, pos2 ); + + return QskIntervalF( pos1, pos2 ); } #include "moc_QskProgressRingSkinlet.cpp" diff --git a/src/controls/QskProgressRingSkinlet.h b/src/controls/QskProgressRingSkinlet.h index 767930b1..c5dc815b 100644 --- a/src/controls/QskProgressRingSkinlet.h +++ b/src/controls/QskProgressRingSkinlet.h @@ -8,7 +8,7 @@ #include "QskProgressIndicatorSkinlet.h" -class QskProgressRing; +class QskIntervalF; class QSK_EXPORT QskProgressRingSkinlet : public QskProgressIndicatorSkinlet { @@ -29,6 +29,8 @@ class QSK_EXPORT QskProgressRingSkinlet : public QskProgressIndicatorSkinlet protected: QSGNode* updateGrooveNode( const QskProgressIndicator*, QSGNode* ) const override; QSGNode* updateFillNode( const QskProgressIndicator*, QSGNode* ) const override; + + QskIntervalF fillInterval( const QskProgressIndicator* ) const; }; #endif diff --git a/src/controls/QskSetup.cpp b/src/controls/QskSetup.cpp index fae8d9e1..e0ffb30e 100644 --- a/src/controls/QskSetup.cpp +++ b/src/controls/QskSetup.cpp @@ -26,7 +26,7 @@ namespace { QskItem::UpdateFlags flags; - if ( hasEnvironment( "QSK_PREFER_RASTER" ) ) + if ( !hasEnvironment( "QSK_PREFER_FBO_PAINTING" ) ) flags |= QskItem::PreferRasterForTextures; if ( hasEnvironment( "QSK_FORCE_BACKGROUND" ) ) diff --git a/src/controls/QskSpinBox.cpp b/src/controls/QskSpinBox.cpp index 56ae6e7f..6195062f 100644 --- a/src/controls/QskSpinBox.cpp +++ b/src/controls/QskSpinBox.cpp @@ -41,12 +41,7 @@ namespace inline QskAspect aspectDecoration() { - return QskSpinBox::Panel | QskAspect::NoType | QskAspect::Style; - } - - inline QskAspect aspectTextAlignment() - { - return QskSpinBox::TextPanel | QskAspect::NoType | QskAspect::Alignment; + return QskSpinBox::Panel | QskAspect::Style; } } @@ -176,26 +171,6 @@ QskSpinBox::Decoration QskSpinBox::decoration() const return flagHint< QskSpinBox::Decoration >( aspectDecoration(), ButtonsLeftAndRight ); } -void QskSpinBox::setTextAlignment( Qt::Alignment alignment ) -{ - alignment &= Qt::AlignHorizontal_Mask; - - if ( setFlagHint( aspectTextAlignment(), alignment ) ) - Q_EMIT textAlignmentChanged( alignment ); -} - -void QskSpinBox::resetTextAlignment() -{ - if ( resetSkinHint( aspectTextAlignment() ) ) - Q_EMIT textAlignmentChanged( textAlignment() ); -} - -Qt::Alignment QskSpinBox::textAlignment() const -{ - return flagHint< Qt::Alignment >( - aspectTextAlignment(), Qt::AlignLeft ) & Qt::AlignHorizontal_Mask; -} - void QskSpinBox::setWrapping( bool on ) { if ( on != m_data->wrapping ) diff --git a/src/controls/QskSpinBox.h b/src/controls/QskSpinBox.h index 518537c1..38f62c35 100644 --- a/src/controls/QskSpinBox.h +++ b/src/controls/QskSpinBox.h @@ -22,9 +22,6 @@ class QSK_EXPORT QskSpinBox : public QskBoundedValueInput Q_PROPERTY( int decimals READ decimals WRITE setDecimals NOTIFY decimalsChanged ) - Q_PROPERTY( Qt::Alignment textAlignment READ textAlignment - WRITE setTextAlignment RESET textAlignment NOTIFY textAlignmentChanged ) - Q_PROPERTY( QString text READ text NOTIFY textChanged ) public: @@ -53,11 +50,6 @@ class QSK_EXPORT QskSpinBox : public QskBoundedValueInput void resetDecoration(); Decoration decoration() const; - // Qt::AlignLeft, Qt::AlignRight or Qt::AlignHCenter. - void setTextAlignment( Qt::Alignment ); - void resetTextAlignment(); - Qt::Alignment textAlignment() const; - void setWrapping( bool ); bool isWrapping() const; @@ -71,10 +63,7 @@ class QSK_EXPORT QskSpinBox : public QskBoundedValueInput Q_SIGNALS: void decorationChanged( Decoration ); - void textAlignmentChanged( Qt::Alignment ); - void wrappingChanged( bool ); - void decimalsChanged( int ); void textChanged(); diff --git a/src/controls/QskSpinBoxSkinlet.cpp b/src/controls/QskSpinBoxSkinlet.cpp index 8970f703..3af7272c 100644 --- a/src/controls/QskSpinBoxSkinlet.cpp +++ b/src/controls/QskSpinBoxSkinlet.cpp @@ -117,11 +117,7 @@ QSGNode* QskSpinBoxSkinlet::updateSubNode( case TextRole: { auto spinBox = static_cast< const QskSpinBox* >( skinnable ); - - const auto rect = subControlRect( spinBox, spinBox->contentsRect(), Q::Text ); - - return updateTextNode( spinBox, node, rect, - spinBox->textAlignment(), spinBox->text(), Q::Text ); + return updateTextNode( spinBox, node, spinBox->text(), Q::Text ); } } @@ -135,11 +131,11 @@ QRectF QskSpinBoxSkinlet::textPanelRect( auto spinBox = static_cast< const QskSpinBox* >( skinnable ); + auto r = spinBox->innerBox( Q::Panel, rect ); + const auto decoration = spinBox->decoration(); if ( decoration == Q::NoDecoration ) - return rect; - - auto r = rect; + return r; const auto spacing = spinBox->spacingHint( Q::Panel ); @@ -174,12 +170,14 @@ QRectF QskSpinBoxSkinlet::textPanelRect( } QRectF QskSpinBoxSkinlet::buttonRect( const QskSkinnable* skinnable, - const QRectF& rect, QskAspect::Subcontrol subControl ) const + const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const { using Q = QskSpinBox; const auto spinBox = static_cast< const QskSpinBox* >( skinnable ); + const auto rect = spinBox->innerBox( Q::Panel, contentsRect ); + if ( const auto decoration = spinBox->decoration() ) { qreal x, y, w, h; @@ -212,7 +210,7 @@ QRectF QskSpinBoxSkinlet::buttonRect( const QskSkinnable* skinnable, if( subControl == Q::UpPanel ) { - const auto downRect = buttonRect( skinnable, rect, Q::DownPanel ); + const auto downRect = subControlRect( skinnable, contentsRect, Q::DownPanel ); x = downRect.left() - w; } else @@ -220,7 +218,7 @@ QRectF QskSpinBoxSkinlet::buttonRect( const QskSkinnable* skinnable, x = rect.right() - w; } - y = 0.5 * ( rect.height() - h ); + y = rect.top() + 0.5 * ( rect.height() - h ); } else { @@ -235,7 +233,7 @@ QRectF QskSpinBoxSkinlet::buttonRect( const QskSkinnable* skinnable, w = h; x = ( subControl == Q::UpPanel ) ? rect.right() - w : rect.left(); - y = 0.5 * ( rect.height() - h ); + y = rect.top() + 0.5 * ( rect.height() - h ); } return QRectF( x, y, w, h ); @@ -308,7 +306,9 @@ QSizeF QskSpinBoxSkinlet::sizeHint( const QskSkinnable* skinnable, } } + hint = hint.grownBy( spinBox->paddingHint( Q::Panel ) ); hint = hint.expandedTo( spinBox->strutSizeHint( Q::Panel ) ); + return hint; } diff --git a/src/graphic/QskGraphic.cpp b/src/graphic/QskGraphic.cpp index 791e79ae..aed3f610 100644 --- a/src/graphic/QskGraphic.cpp +++ b/src/graphic/QskGraphic.cpp @@ -339,7 +339,7 @@ class QskGraphic::PrivateData : public QSharedData PrivateData( const PrivateData& other ) : QSharedData( other ) - , defaultSize( other.defaultSize ) + , viewBox( other.viewBox ) , commands( other.commands ) , pathInfos( other.pathInfos ) , boundingRect( other.boundingRect ) @@ -354,7 +354,18 @@ class QskGraphic::PrivateData : public QSharedData { return ( modificationId == other.modificationId ) && ( renderHints == other.renderHints ) && - ( defaultSize == other.defaultSize ); + ( viewBox == other.viewBox ); + } + + void resetCommands() + { + commands.clear(); + pathInfos.clear(); + + commandTypes = 0; + boundingRect = pointRect = { 0.0, 0.0, -1.0, -1.0 }; + + modificationId = 0; } inline void addCommand( const QskPainterCommand& command ) @@ -365,7 +376,7 @@ class QskGraphic::PrivateData : public QSharedData modificationId = nextId.fetchAndAddRelaxed( 1 ); } - QSizeF defaultSize; + QRectF viewBox = { 0.0, 0.0, -1.0, -1.0 }; QVector< QskPainterCommand > commands; QVector< QskGraphicPrivate::PathInfo > pathInfos; @@ -485,6 +496,16 @@ int QskGraphic::metric( PaintDeviceMetric deviceMetric ) const value = metric( PdmDevicePixelRatio ) * devicePixelRatioFScale(); break; } +#if QT_VERSION >= QT_VERSION_CHECK( 6, 8, 0 ) + case PdmDevicePixelRatioF_EncodedA: + case PdmDevicePixelRatioF_EncodedB: + { +#if 0 + value = QPaintDevice::encodeMetricF( metric, devicePixelRatio() ); +#endif + break; + } +#endif } return value; @@ -492,16 +513,9 @@ int QskGraphic::metric( PaintDeviceMetric deviceMetric ) const void QskGraphic::reset() { - m_data->commands.clear(); - m_data->pathInfos.clear(); + m_data->resetCommands(); - m_data->commandTypes = 0; - - m_data->boundingRect = QRectF( 0.0, 0.0, -1.0, -1.0 ); - m_data->pointRect = QRectF( 0.0, 0.0, -1.0, -1.0 ); - m_data->defaultSize = QSizeF(); - - m_data->modificationId = 0; + m_data->viewBox = { 0.0, 0.0, -1.0, -1.0 }; delete m_paintEngine; m_paintEngine = nullptr; @@ -580,18 +594,20 @@ QSize QskGraphic::sizeMetrics() const return QSize( qCeil( sz.width() ), qCeil( sz.height() ) ); } -void QskGraphic::setDefaultSize( const QSizeF& size ) +void QskGraphic::setViewBox( const QRectF& rect ) { - const double w = qMax( qreal( 0.0 ), size.width() ); - const double h = qMax( qreal( 0.0 ), size.height() ); + m_data->viewBox = rect; +} - m_data->defaultSize = QSizeF( w, h ); +QRectF QskGraphic::viewBox() const +{ + return m_data->viewBox; } QSizeF QskGraphic::defaultSize() const { - if ( !m_data->defaultSize.isEmpty() ) - return m_data->defaultSize; + if ( !m_data->viewBox.isEmpty() ) + return m_data->viewBox.size(); return boundingRect().size(); } @@ -670,30 +686,42 @@ void QskGraphic::render( QPainter* painter, const QRectF& rect, if ( isEmpty() || rect.isEmpty() ) return; + const bool scalePens = !( m_data->renderHints & RenderPensUnscaled ); + qreal sx = 1.0; qreal sy = 1.0; - if ( m_data->pointRect.width() > 0.0 ) - sx = rect.width() / m_data->pointRect.width(); + QRectF boundingBox = m_data->viewBox; - if ( m_data->pointRect.height() > 0.0 ) - sy = rect.height() / m_data->pointRect.height(); - - const bool scalePens = !( m_data->renderHints & RenderPensUnscaled ); - - for ( const auto& info : std::as_const( m_data->pathInfos ) ) + if ( !boundingBox.isEmpty() ) { - const qreal ssx = info.scaleFactorX( m_data->pointRect, - rect, m_data->boundingRect, scalePens ); + sx = rect.width() / boundingBox.width(); + sy = rect.height() / boundingBox.height(); + } + else + { + boundingBox = m_data->boundingRect; - if ( ssx > 0.0 ) - sx = qMin( sx, ssx ); + if ( m_data->pointRect.width() > 0.0 ) + sx = rect.width() / m_data->pointRect.width(); - const qreal ssy = info.scaleFactorY( m_data->pointRect, - rect, m_data->boundingRect, scalePens ); + if ( m_data->pointRect.height() > 0.0 ) + sy = rect.height() / m_data->pointRect.height(); - if ( ssy > 0.0 ) - sy = qMin( sy, ssy ); + for ( const auto& info : std::as_const( m_data->pathInfos ) ) + { + 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, m_data->boundingRect, scalePens ); + + if ( ssy > 0.0 ) + sy = qMin( sy, ssy ); + } } if ( aspectRatioMode == Qt::KeepAspectRatio ) @@ -705,15 +733,17 @@ void QskGraphic::render( QPainter* painter, const QRectF& rect, sx = sy = qMax( sx, sy ); } - const auto& br = m_data->boundingRect; - const auto rc = rect.center(); - QTransform tr; - tr.translate( - rc.x() - 0.5 * sx * br.width(), - rc.y() - 0.5 * sy * br.height() ); - tr.scale( sx, sy ); - tr.translate( -br.x(), -br.y() ); + + { + const auto rc = rect.center(); + + tr.translate( + rc.x() - 0.5 * sx * boundingBox.width(), + rc.y() - 0.5 * sy * boundingBox.height() ); + tr.scale( sx, sy ); + tr.translate( -boundingBox.x(), -boundingBox.y() ); + } const auto transform = painter->transform(); @@ -986,7 +1016,7 @@ const QVector< QskPainterCommand >& QskGraphic::commands() const void QskGraphic::setCommands( const QVector< QskPainterCommand >& commands ) { - reset(); + m_data->resetCommands(); const int numCommands = commands.size(); if ( numCommands <= 0 ) @@ -1019,9 +1049,7 @@ quint64 QskGraphic::modificationId() const QskHashValue QskGraphic::hash( QskHashValue seed ) const { auto hash = qHash( m_data->renderHints, seed ); - - hash = qHash( m_data->defaultSize.width(), hash ); - hash = qHash( m_data->defaultSize.height(), hash ); + hash = qHashBits( &m_data->viewBox, sizeof( QRectF ), hash ); return qHash( m_data->modificationId, hash ); } diff --git a/src/graphic/QskGraphic.h b/src/graphic/QskGraphic.h index bd678397..34565aa0 100644 --- a/src/graphic/QskGraphic.h +++ b/src/graphic/QskGraphic.h @@ -29,11 +29,14 @@ class QSK_EXPORT QskGraphic : public QPaintDevice Q_GADGET Q_PROPERTY( qreal aspectRatio READ aspectRatio ) + Q_PROPERTY( QRectF viewBox READ viewBox WRITE setViewBox ) Q_PROPERTY( QRectF boundingRect READ boundingRect ) Q_PROPERTY( QRectF controlPointRect READ controlPointRect ) Q_PROPERTY( QSizeF defaultSize READ defaultSize ) Q_PROPERTY( quint64 modificationId READ modificationId ) + using Inherited = QPaintDevice; + public: enum RenderHint { @@ -106,9 +109,11 @@ class QSK_EXPORT QskGraphic : public QPaintDevice const QVector< QskPainterCommand >& commands() const; void setCommands( const QVector< QskPainterCommand >& ); - void setDefaultSize( const QSizeF& ); QSizeF defaultSize() const; + void setViewBox( const QRectF& ); + QRectF viewBox() const; + qreal aspectRatio() const; qreal heightForWidth( qreal width ) const; diff --git a/src/graphic/QskGraphicIO.cpp b/src/graphic/QskGraphicIO.cpp index 77d21785..b654d6f9 100644 --- a/src/graphic/QskGraphicIO.cpp +++ b/src/graphic/QskGraphicIO.cpp @@ -19,10 +19,9 @@ static const char qskMagicNumber[] = "QSKG"; /* To avoid crashes ( fonts ), when svg2qvg was running with a different Qt version, than the one of the application we hardcode - the datastream format to Qt 5.6. Once everything is running with - Qt 5.15 we can increase the version. TODO ... + the datastream format. */ -static const int qskDataStreamVersion = QDataStream::Qt_5_6; +static const int qskDataStreamVersion = QDataStream::Qt_5_15; static inline void qskWritePathData( const QPainterPath& path, QDataStream& s ) @@ -242,6 +241,9 @@ QskGraphic QskGraphicIO::read( QIODevice* dev ) return QskGraphic(); } + QRectF viewBox; + stream >> viewBox; + quint32 numCommands; stream >> numCommands; @@ -281,6 +283,7 @@ QskGraphic QskGraphicIO::read( QIODevice* dev ) } QskGraphic graphic; + graphic.setViewBox( viewBox ); graphic.setCommands( commands ); return graphic; @@ -316,6 +319,8 @@ bool QskGraphicIO::write( const QskGraphic& graphic, QIODevice* dev ) stream.setByteOrder( QDataStream::BigEndian ); stream.writeRawData( qskMagicNumber, 4 ); + stream << graphic.viewBox(); + const auto numCommands = graphic.commands().size(); const QskPainterCommand* cmds = graphic.commands().constData(); diff --git a/src/nodes/QskArcNode.cpp b/src/nodes/QskArcNode.cpp index e3de5c0a..c2dc3f7a 100644 --- a/src/nodes/QskArcNode.cpp +++ b/src/nodes/QskArcNode.cpp @@ -6,14 +6,14 @@ #include "QskArcNode.h" #include "QskArcMetrics.h" #include "QskArcShadowNode.h" +#include "QskArcRenderNode.h" +#include "QskArcRenderer.h" #include "QskMargins.h" #include "QskGradient.h" #include "QskShapeNode.h" -#include "QskStrokeNode.h" #include "QskSGNode.h" #include "QskShadowMetrics.h" -#include #include namespace @@ -21,8 +21,9 @@ namespace enum NodeRole { ShadowRole, - FillRole, - BorderRole + + PathRole, + ArcRole }; } @@ -43,35 +44,18 @@ static inline QskGradient qskEffectiveGradient( return gradient; } -static inline QskArcMetrics qskEffectiveMetrics( - const QskArcMetrics& metrics, const QRectF& rect ) +template< typename Node > +inline Node* qskInsertOrRemoveNode( QSGNode* parentNode, quint8 role, bool isValid ) { - if ( metrics.sizeMode() == Qt::RelativeSize ) - { - const auto rx = 0.5 * rect.width(); - const auto ry = 0.5 * rect.height(); + using namespace QskSGNode; - return metrics.toAbsolute( rx, ry ); - } + Node* oldNode = static_cast< Node* >( findChildNode( parentNode, role ) ); + Node* newNode = isValid ? ensureNode< Node >( oldNode ) : nullptr; - return metrics; -} + static const QVector< quint8 > roles = { ShadowRole, PathRole, ArcRole }; + replaceChildNode( roles, role, parentNode, oldNode, newNode ); -static inline QRectF qskEffectiveRect( - const QRectF& rect, const qreal borderWidth ) -{ - if ( borderWidth <= 0.0 ) - return rect; - - return qskValidOrEmptyInnerRect( rect, QskMargins( 0.5 * borderWidth ) ); -} - -static void qskUpdateChildren( QSGNode* parentNode, quint8 role, QSGNode* node ) -{ - static const QVector< quint8 > roles = { ShadowRole, FillRole, BorderRole }; - - auto oldNode = QskSGNode::findChildNode( parentNode, role ); - QskSGNode::replaceChildNode( roles, role, parentNode, oldNode, node ); + return newNode; } QskArcNode::QskArcNode() @@ -83,56 +67,44 @@ QskArcNode::~QskArcNode() } void QskArcNode::setArcData( const QRectF& rect, - const QskArcMetrics& arcMetrics, const QskGradient& fillGradient ) + const QskArcMetrics& arcMetrics, const QskGradient& gradient ) { - setArcData( rect, arcMetrics, 0.0, QColor(), fillGradient, {}, {} ); + setArcData( rect, arcMetrics, 0.0, QColor(), gradient, {}, {} ); } void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics, - const qreal borderWidth, const QColor& borderColor, const QskGradient& fillGradient ) + const qreal borderWidth, const QColor& borderColor, const QskGradient& gradient ) { - setArcData( rect, arcMetrics, borderWidth, borderColor, fillGradient, {}, {} ); + setArcData( rect, arcMetrics, borderWidth, borderColor, gradient, {}, {} ); } void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics, - const qreal borderWidth, const QColor& borderColor, const QskGradient& fillGradient, + const qreal borderWidth, const QColor& borderColor, const QskGradient& gradient, const QColor& shadowColor, const QskShadowMetrics& shadowMetrics ) { - const auto metricsArc = qskEffectiveMetrics( arcMetrics, rect ); - const auto gradient = qskEffectiveGradient( fillGradient, metricsArc ); + const bool radial = false; - auto shadowNode = static_cast< QskArcShadowNode* >( - QskSGNode::findChildNode( this, ShadowRole ) ); + const auto metricsArc = arcMetrics.toAbsolute( rect.size() ); - auto fillNode = static_cast< QskShapeNode* >( - QskSGNode::findChildNode( this, FillRole ) ); - - auto borderNode = static_cast< QskStrokeNode* >( - QskSGNode::findChildNode( this, BorderRole ) ); - - const auto arcRect = qskEffectiveRect( rect, borderWidth ); - if ( arcRect.isEmpty() ) + if ( metricsArc.isNull() || rect.isEmpty() ) { - delete shadowNode; - delete fillNode; - delete borderNode; + delete QskSGNode::findChildNode( this, ShadowRole ); + delete QskSGNode::findChildNode( this, PathRole ); + delete QskSGNode::findChildNode( this, ArcRole ); + return; } - const auto isFillNodeVisible = gradient.isVisible() && !metricsArc.isNull(); - const auto isStrokeNodeVisible = borderWidth > 0.0 && borderColor.alpha() > 0; - const auto isShadowNodeVisible = shadowColor.alpha() > 0.0 && isFillNodeVisible; + const auto hasFilling = gradient.isVisible(); + const auto hasBorder = ( borderWidth > 0.0 ) + && borderColor.isValid() && ( borderColor.alpha() > 0 ); + const auto hasShadow = shadowColor.isValid() && ( shadowColor.alpha() > 0 ); - const auto path = metricsArc.painterPath( arcRect ); + auto shadowNode = qskInsertOrRemoveNode< QskArcShadowNode >( + this, ShadowRole, hasFilling && hasShadow ); - if ( isShadowNodeVisible ) + if ( shadowNode ) { - if ( shadowNode == nullptr ) - { - shadowNode = new QskArcShadowNode; - QskSGNode::setNodeRole( shadowNode, ShadowRole ); - } - /* The shader of the shadow node is for circular arcs and we have some unwanted scaling issues for the spread/blur values when having ellipsoid @@ -140,55 +112,30 @@ void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics and not only to its radius. TODO ... */ - const auto sm = shadowMetrics.toAbsolute( arcRect.size() ); - const auto shadowRect = sm.shadowRect( arcRect ); + const auto sm = shadowMetrics.toAbsolute( rect.size() ); + const auto shadowRect = sm.shadowRect( rect ); const auto spreadRadius = sm.spreadRadius() + 0.5 * metricsArc.thickness(); shadowNode->setShadowData( shadowRect, spreadRadius, sm.blurRadius(), metricsArc.startAngle(), metricsArc.spanAngle(), shadowColor ); } - else + + auto pathNode = qskInsertOrRemoveNode< QskShapeNode >( this, PathRole, + hasFilling && !QskArcRenderer::isGradientSupported( rect, metricsArc, gradient ) ); + + if ( pathNode ) { - delete shadowNode; - shadowNode = nullptr; + const auto path = metricsArc.painterPath( rect, radial ); + pathNode->updateNode( path, QTransform(), rect, + qskEffectiveGradient( gradient, metricsArc ) ); } - if ( isFillNodeVisible ) + auto arcNode = qskInsertOrRemoveNode< QskArcRenderNode >( + this, ArcRole, hasBorder || ( hasFilling && !pathNode ) ); + + if ( arcNode ) { - if ( fillNode == nullptr ) - { - fillNode = new QskShapeNode; - QskSGNode::setNodeRole( fillNode, FillRole ); - } - - fillNode->updateNode( path, QTransform(), arcRect, gradient ); + arcNode->updateNode( rect, metricsArc, radial, + borderWidth, borderColor, pathNode ? QskGradient() : gradient ); } - else - { - delete fillNode; - fillNode = nullptr; - } - - if ( isStrokeNodeVisible ) - { - if ( borderNode == nullptr ) - { - borderNode = new QskStrokeNode; - QskSGNode::setNodeRole( borderNode, BorderRole ); - } - - QPen pen( borderColor, borderWidth ); - pen.setCapStyle( Qt::FlatCap ); - - borderNode->updateNode( path, QTransform(), pen ); - } - else - { - delete borderNode; - borderNode = nullptr; - } - - qskUpdateChildren(this, ShadowRole, shadowNode); - qskUpdateChildren(this, FillRole, fillNode); - qskUpdateChildren(this, BorderRole, borderNode); } diff --git a/src/nodes/QskArcNode.h b/src/nodes/QskArcNode.h index c4fa330a..b641533e 100644 --- a/src/nodes/QskArcNode.h +++ b/src/nodes/QskArcNode.h @@ -6,18 +6,14 @@ #ifndef QSK_ARC_NODE_H #define QSK_ARC_NODE_H -#include "QskShapeNode.h" +#include "QskGlobal.h" +#include class QskArcMetrics; class QskGradient; class QskShadowMetrics; -/* - For the moment a QPainterPath/QskShapeNode. - But we can do better by creatig vertex lists manually - like what is done by the box renderer. TODO ... - */ -class QSK_EXPORT QskArcNode : public QskShapeNode +class QSK_EXPORT QskArcNode : public QSGNode { public: QskArcNode(); diff --git a/src/nodes/QskArcRenderNode.cpp b/src/nodes/QskArcRenderNode.cpp new file mode 100644 index 00000000..0d7ffddd --- /dev/null +++ b/src/nodes/QskArcRenderNode.cpp @@ -0,0 +1,121 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskArcRenderNode.h" +#include "QskGradient.h" +#include "QskArcRenderer.h" +#include "QskArcMetrics.h" +#include "QskGradient.h" +#include "QskSGNode.h" +#include "QskFillNodePrivate.h" + +class QskArcRenderNodePrivate final : public QskFillNodePrivate +{ + public: + inline void resetValues() { hash = 0; } + + QskHashValue hash = 0; +}; + +QskArcRenderNode::QskArcRenderNode() + : QskFillNode( *new QskArcRenderNodePrivate ) +{ +} + +QskArcRenderNode::~QskArcRenderNode() +{ +} + +void QskArcRenderNode::updateNode( const QRectF& rect, + const QskArcMetrics& metrics, const QskGradient& gradient ) +{ + updateNode( rect, metrics, false, 0.0, QColor(), gradient ); +} + +void QskArcRenderNode::updateNode( const QRectF& rect, + const QskArcMetrics& metrics, qreal borderWidth, const QColor& borderColor ) +{ + updateNode( rect, metrics, false, borderWidth, borderColor, QskGradient() ); +} + +void QskArcRenderNode::updateNode( + const QRectF& rect, const QskArcMetrics& arcMetrics, bool radial, + qreal borderWidth, const QColor& borderColor, const QskGradient& gradient ) +{ + Q_D( QskArcRenderNode ); + + const auto metrics = arcMetrics.toAbsolute( rect.size() ); + const auto borderMax = 0.5 * metrics.thickness(); + + const bool hasFill = gradient.isVisible() && ( borderWidth < borderMax ); + const bool hasBorder = ( borderWidth > 0.0 ) + && borderColor.isValid() && ( borderColor.alpha() > 0 ); + + if ( rect.isEmpty() || metrics.isNull() || !( hasFill || hasBorder ) ) + { + d->resetValues(); + QskSGNode::resetGeometry( this ); + + return; + } + + borderWidth = qMin( borderWidth, borderMax ); + + QskHashValue hash = 3496; + + hash = qHashBits( &rect, sizeof( QRectF ), hash ); + hash = qHash( borderWidth, hash ); + hash = qHashBits( &borderColor, sizeof( borderColor ), hash ); + hash = metrics.hash( hash ); + hash = gradient.hash( hash ); + hash = qHash( radial, hash ); + + if ( hash == d->hash ) + return; + + d->hash = hash; + + auto coloring = QskFillNode::Polychrome; + +#if 0 + if ( !( hasFill && hasBorder ) ) + { + if ( hasBorder || ( hasFill && gradient.isMonochrome() ) ) + coloring = QskFillNode::Monochrome; + } +#endif + + auto& geometry = *this->geometry(); + + if ( coloring == QskFillNode::Polychrome ) + { + setColoring( coloring ); + + QskArcRenderer::renderArc( rect, metrics, radial, + borderWidth, gradient, borderColor, geometry ); + } + else + { + if ( hasFill ) + { + setColoring( gradient.rgbStart() ); + + QskArcRenderer::renderArc( rect, metrics, radial, + borderWidth, gradient, borderColor, geometry ); + } + else + { + setColoring( borderColor ); + + QskArcRenderer::renderArc( rect, metrics, radial, + borderWidth, gradient, borderColor, geometry ); + } + } + + markDirty( QSGNode::DirtyGeometry ); + markDirty( QSGNode::DirtyMaterial ); + + geometry.markVertexDataDirty(); +} diff --git a/src/nodes/QskArcRenderNode.h b/src/nodes/QskArcRenderNode.h new file mode 100644 index 00000000..f0d248ab --- /dev/null +++ b/src/nodes/QskArcRenderNode.h @@ -0,0 +1,37 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_ARC_RENDER_NODE_H +#define QSK_ARC_RENDER_NODE_H + +#include "QskGlobal.h" +#include "QskFillNode.h" + +class QskGradient; +class QskArcMetrics; + +class QskArcRenderNodePrivate; + +class QSK_EXPORT QskArcRenderNode : public QskFillNode +{ + using Inherited = QskFillNode; + + public: + QskArcRenderNode(); + ~QskArcRenderNode() override; + + void updateNode( const QRectF&, const QskArcMetrics&, const QskGradient& ); + + void updateNode( const QRectF&, const QskArcMetrics&, + qreal borderWidth, const QColor& borderColor ); + + void updateNode( const QRectF&, const QskArcMetrics&, bool radial, + qreal borderWidth, const QColor& borderColor, const QskGradient& ); + + private: + Q_DECLARE_PRIVATE( QskArcRenderNode ) +}; + +#endif diff --git a/src/nodes/QskArcRenderer.cpp b/src/nodes/QskArcRenderer.cpp new file mode 100644 index 00000000..1a7963ce --- /dev/null +++ b/src/nodes/QskArcRenderer.cpp @@ -0,0 +1,554 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskArcRenderer.h" +#include "QskArcMetrics.h" +#include "QskGradient.h" +#include "QskVertex.h" +#include "QskBoxColorMap.h" +#include "QskRgbValue.h" + +#include +#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() ); +} + +namespace +{ + template< class Line > + class OrthogonalStroker + { + public: + OrthogonalStroker( const QRectF& rect, qreal thickness, qreal border ) + : m_thickness( thickness ) + , m_border( border ) + , m_rx( 0.5 * ( rect.width() - m_thickness ) ) + , m_ry( 0.5 * ( rect.height() - m_thickness ) ) + , m_offsetToBorder( 0.5 * m_thickness - border ) + , m_aspectRatio( m_rx / m_ry ) + , m_cx( rect.x() + 0.5 * rect.width() ) + , m_cy( rect.y() + 0.5 * rect.height() ) + { + } + + inline void setLinesAt( const qreal radians, + const QskVertex::Color fillColor, const QskVertex::Color borderColor, + Line* fill, Line* outerBorder, Line* innerBorder ) const + { + const auto cos = qFastCos( radians ); + const auto sin = qFastSin( radians ); + + const auto v = normalVector( cos, sin ); + + const QPointF p0( m_cx + m_rx * cos, m_cy - m_ry * sin ); + + const auto v1 = v * m_offsetToBorder; + + const auto p1 = p0 + v1; + const auto p2 = p0 - v1; + + if ( fill ) + fill->setLine( p1, p2, fillColor ); + + if ( outerBorder ) + { + const auto v2 = v * m_border; + + outerBorder->setLine( p1 + v2, p1, borderColor ); + innerBorder->setLine( p2 - v2, p2, borderColor ); + } + } + + inline void setClosingBorderLines( const Line& l, + Line* lines, qreal sign, const QskVertex::Color color ) const + { + const auto& pos = l.p1; + const auto& l0 = lines[0]; + + const auto dx = sign * l0.dy(); + const auto dy = sign * l0.dx(); + + lines[-3].setLine( pos.x, pos.y, pos.x, pos.y, color ); + lines[-2].setLine( pos.x + dx, pos.y - dy, pos.x, pos.y, color ); + lines[-1].setLine( l0.x1() + dx, l0.y1() - dy, l0.x1(), l0.y1(), color ); + } + + private: + inline QPointF normalVector( const qreal cos, const qreal sin ) const + { + /* + The inner/outer points are found by shifting orthogonally along the + ellipse tangent: + + m = w / h * tan( angle ) + y = m * x; + x² + y² = 1.0 + + => x = 1.0 / sqrt( 1.0 + m² ); + + Note: the angle of the orthogonal vector could + also be found ( first quadrant ) by: + + atan2( tan( angle ), h / w ); + + Note: we return the vector mirrored vertically, so that it + matches the coordinate system used by Qt. + */ + + if ( qFuzzyIsNull( cos ) ) + return { 0.0, ( sin < 0.0 ) ? 1.0 : -1.0 }; + + const qreal m = m_aspectRatio * ( sin / cos ); + const qreal t = 1.0 / qSqrt( 1.0 + m * m ); + + const auto dx = ( cos >= 0.0 ) ? t : -t; + return { dx, -m * dx }; + } + + const qreal m_thickness; + const qreal m_border; + + // radii t the middle of the arc + const qreal m_rx, m_ry; + + // distances between the middle and the beginning of the border + const qreal m_offsetToBorder; + + const qreal m_aspectRatio; // m_rx / m_ry + + // center + const qreal m_cx, m_cy; + }; + + template< class Line > + class RadialStroker + { + public: + RadialStroker( const QRectF& rect, qreal thickness, qreal border ) + : m_sx( qMax( rect.width() / rect.height(), 1.0 ) ) + , m_sy( qMax( rect.height() / rect.width(), 1.0 ) ) + , m_rx1( 0.5 * rect.width() ) + , m_ry1( 0.5 * rect.height() ) + , m_rx2( m_rx1 - m_sx * border ) + , m_ry2( m_ry1 - m_sy * border ) + , m_rx3( m_rx1 - m_sx * ( thickness - border ) ) + , m_ry3( m_ry1 - m_sy * ( thickness - border ) ) + , m_rx4( m_rx1 - m_sx * thickness ) + , m_ry4( m_ry1 - m_sy * thickness ) + , m_center( rect.x() + m_rx1, rect.y() + m_ry1 ) + { + } + + inline void setLinesAt( const qreal radians, + const QskVertex::Color fillColor, const QskVertex::Color borderColor, + Line* fill, Line* outer, Line* inner ) const + { + const QPointF v( qFastCos( radians ), -qFastSin( radians ) ); + + const auto x1 = m_center.x() + m_rx2 * v.x(); + const auto y1 = m_center.y() + m_ry2 * v.y(); + + const auto x2 = m_center.x() + m_rx3 * v.x(); + const auto y2 = m_center.y() + m_ry3 * v.y(); + + if ( fill ) + fill->setLine( x1, y1, x2, y2, fillColor ); + + if ( outer ) + { + const auto x3 = m_center.x() + m_rx1 * v.x(); + const auto y3 = m_center.y() + m_ry1 * v.y(); + + const auto x4 = m_center.x() + m_rx4 * v.x(); + const auto y4 = m_center.y() + m_ry4 * v.y(); + + outer->setLine( x3, y3, x1, y1, borderColor ); + inner->setLine( x4, y4, x2, y2, borderColor ); + } + } + + inline void setClosingBorderLines( const Line& l, + Line* lines, qreal sign, const QskVertex::Color color ) const + { + const auto& pos = l.p1; + + // Good enough until it is decided if we want to keep the radial mode. + const auto& l0 = lines[0]; + + const auto s = m_sx / m_sy; + const auto dx = sign * l0.dy() * s; + const auto dy = sign * l0.dx() / s; + + lines[-3].setLine( pos.x, pos.y, pos.x, pos.y, color ); + lines[-2].setLine( pos.x + dx, pos.y - dy, pos.x, pos.y, color ); + lines[-1].setLine( l0.x1() + dx, l0.y1() - dy, l0.x1(), l0.y1(), color ); + } + + private: + // stretch factors of the ellipse + const qreal m_sx, m_sy; + + // radii: out->in + const qreal m_rx1, m_ry1, m_rx2, m_ry2, m_rx3, m_ry3, m_rx4, m_ry4; + + // center point + const QPointF m_center; + }; + + template< class Line > + class CircularStroker + { + public: + CircularStroker( const QRectF& rect, qreal thickness, qreal border ) + : m_center( rect.center() ) + , m_radius( 0.5 * ( rect.width() - thickness ) ) + , m_distOut( 0.5 * thickness ) + , m_distIn( m_distOut - border ) + { + } + + inline void setLinesAt( const qreal radians, + const QskVertex::Color fillColor, const QskVertex::Color borderColor, + Line* fill, Line* outer, Line* inner ) const + { + const QPointF v( qFastCos( radians ), -qFastSin( radians ) ); + + const auto p0 = m_center + m_radius * v; + const auto dv1 = v * m_distIn; + + const auto p1 = p0 + dv1; + const auto p2 = p0 - dv1; + + if ( fill ) + fill->setLine( p1, p2, fillColor ); + + if ( outer ) + { + const auto dv2 = v * m_distOut; + + const auto p3 = p0 + dv2; + const auto p4 = p0 - dv2; + + outer->setLine( p3, p1, borderColor ); + inner->setLine( p4, p2, borderColor ); + } + } + + inline void setClosingBorderLines( const Line& l, + Line* lines, qreal sign, const QskVertex::Color color ) const + { + const auto& pos = l.p1; + const auto& l0 = lines[0]; + + const auto dx = sign * l0.dy(); + const auto dy = sign * l0.dx(); + + lines[-3].setLine( pos.x, pos.y, pos.x, pos.y, color ); + lines[-2].setLine( pos.x + dx, pos.y - dy, pos.x, pos.y, color ); + lines[-1].setLine( l0.x1() + dx, l0.y1() - dy, l0.x1(), l0.y1(), color ); + } + + private: + // center point + const QPointF m_center; + const qreal m_radius; // middle of the arc + + // distances from the middle to the inner/outer side of the border + const qreal m_distOut, m_distIn; + }; +} + +namespace +{ + class Renderer + { + public: + Renderer( const QRectF&, const QskArcMetrics&, + bool radial, const QskGradient&, const QskVertex::Color& ); + + int fillCount() const; + int borderCount() const; + + template< class Line > + void renderArc( const qreal thickness, const qreal border, Line*, Line* ) const; + + private: + int arcLineCount() const; + + template< class LineStroker, class Line > + void renderLines( const LineStroker&, Line*, Line* ) const; + + const QRectF& m_rect; + + const qreal m_radians1; + const qreal m_radians2; + + const bool m_radial; // for circular arcs radial/orthogonal does not differ + const bool m_closed; + + const QskGradient& m_gradient; + const QskVertex::Color m_borderColor; + }; + + Renderer::Renderer( const QRectF& rect, const QskArcMetrics& metrics, + bool radial, const QskGradient& gradient, const QskVertex::Color& borderColor ) + : m_rect( rect ) + , m_radians1( qDegreesToRadians( metrics.startAngle() ) ) + , m_radians2( qDegreesToRadians( metrics.endAngle() ) ) + , m_radial( radial ) + , m_closed( metrics.isClosed() ) + , m_gradient( gradient ) + , m_borderColor( borderColor ) + { + } + + int Renderer::arcLineCount() const + { + // not very sophisticated - TODO ... + + const auto radius = 0.5 * qMax( m_rect.width(), m_rect.height() ); + const auto radians = qAbs( m_radians2 - m_radians1 ); + + const auto count = qCeil( ( radius * radians ) / 3.0 ); + return qBound( 3, count, 160 ); + } + + int Renderer::fillCount() const + { + if ( !m_gradient.isVisible() ) + return 0; + + return arcLineCount() + m_gradient.stepCount() - 1; + } + + template< class Line > + void Renderer::renderArc( const qreal thickness, const qreal border, + Line* fillLines, Line* borderLines ) const + { + if ( qskFuzzyCompare( m_rect.width(), m_rect.height() ) ) + { + const CircularStroker< Line > stroker( m_rect, thickness, border ); + renderLines( stroker, fillLines, borderLines ); + } + else if ( m_radial ) + { + const RadialStroker< Line > stroker( m_rect, thickness, border ); + renderLines( stroker, fillLines, borderLines ); + } + else + { + const OrthogonalStroker< Line > stroker( m_rect, thickness, border ); + renderLines( stroker, fillLines, borderLines ); + } + } + + template< class LineStroker, class Line > + void Renderer::renderLines( const LineStroker& lineStroker, + Line* fillLines, Line* borderLines ) const + { + QskBoxRenderer::GradientIterator it; + + if ( fillLines ) + { + if ( m_gradient.stepCount() <= 1 ) + { + it.reset( m_gradient.rgbStart(), m_gradient.rgbEnd() ); + } + else + { + it.reset( m_gradient.stops() ); + it.advance(); // the first stop is always covered by the contour + } + } + + const auto count = arcLineCount(); + const auto radiansSpan = m_radians2 - m_radians1; + + const qreal stepMax = count - 1; + const auto stepSize = radiansSpan / stepMax; + + auto l = fillLines; + + auto outer = borderLines; + auto inner = borderLines; + + if ( borderLines ) + { + outer = borderLines; + if ( !m_closed ) + outer += 3; + + inner = outer + count; + if ( !m_closed ) + inner += 3; + } + + for ( int i = 0; i < count; i++ ) + { + const auto progress = i / stepMax; + + while( !it.isDone() && ( it.position() < progress ) ) + { + const auto radians = m_radians1 + it.position() * radiansSpan; + lineStroker.setLinesAt( radians, it.color(), m_borderColor, + l++, nullptr, nullptr ); + + it.advance(); + } + + const auto radians = m_radians1 + i * stepSize; + const auto color = it.colorAt( progress ); + + lineStroker.setLinesAt( radians, color, m_borderColor, + l ? l++ : nullptr, + outer ? outer + i : nullptr, + inner ? inner + count - 1 - i : nullptr + ); + } + + if ( borderLines && !m_closed ) + { + const auto sign = ( radiansSpan > 0.0 ) ? 1.0 : -1.0; + + lineStroker.setClosingBorderLines( inner[count - 1], outer, sign, m_borderColor ); + lineStroker.setClosingBorderLines( outer[count - 1], inner, sign, m_borderColor ); + } + } + + int Renderer::borderCount() const + { + if ( m_borderColor.a == 0 ) + return 0; + + auto count = 2 * arcLineCount(); + if ( !m_closed ) + count += 2 * 3; + + return count; + } +} + +bool QskArcRenderer::isGradientSupported( const QRectF& rect, + const QskArcMetrics& metrics, const QskGradient& gradient ) +{ + if ( rect.isEmpty() || metrics.isNull() ) + return true; + + if ( !gradient.isVisible() || gradient.isMonochrome() ) + return true; + + switch( gradient.type() ) + { + case QskGradient::Stops: + { + return true; + } + case QskGradient::Conic: + { + const auto direction = gradient.conicDirection(); + if ( direction.center() == rect.center() ) + { + const auto aspectRatio = rect.width() / rect.height(); + if ( qskFuzzyCompare( direction.aspectRatio(), aspectRatio ) ) + { + /* + we should be able to create a list of stops from + this gradient that works for the renderer. TODO ... + */ + } + } + + return false; + } + default: + { + return false; + } + } + + return false; +} + +void QskArcRenderer::renderArc( const QRectF& rect, const QskArcMetrics& metrics, + bool radial, const QskGradient& gradient, QSGGeometry& geometry ) +{ + renderArc( rect, metrics, radial, 0, gradient, QColor(), geometry ); +} + +void QskArcRenderer::renderArc( const QRectF& rect, const QskArcMetrics& metrics, + bool radial, qreal borderWidth, const QskGradient& gradient, + const QColor& borderColor, QSGGeometry& geometry ) +{ + geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); + + const Renderer renderer( rect, metrics, radial, gradient, + borderColor.isValid() ? borderColor : QColor( 0, 0, 0, 0 ) ); + + const auto borderCount = renderer.borderCount(); + const auto fillCount = renderer.fillCount(); + + auto lineCount = borderCount + fillCount; + if ( borderCount && fillCount ) + lineCount++; // connecting line + + const auto lines = qskAllocateColoredLines( geometry, lineCount ); + if ( lines ) + { + const auto fillLines = fillCount ? lines : nullptr; + const auto borderLines = borderCount ? lines + lineCount - borderCount : nullptr; + + renderer.renderArc( metrics.thickness(), borderWidth, fillLines, borderLines ); + + if ( fillCount && borderCount ) + { + const auto idx = fillCount; + + lines[idx].p1 = lines[idx - 1].p2; + lines[idx].p2 = lines[idx + 1].p1; + } + } +} + +void QskArcRenderer::renderBorderGeometry( const QRectF& rect, + const QskArcMetrics& metrics, bool radial, qreal borderWidth, QSGGeometry& geometry ) +{ + geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); + + const Renderer renderer( rect, metrics, radial, QskGradient(), 0 ); + + const auto lines = qskAllocateLines( geometry, renderer.borderCount() ); + if ( lines ) + { + QskVertex::Line* fill = nullptr; + renderer.renderArc( metrics.thickness(), borderWidth, fill, lines ); + } +} + +void QskArcRenderer::renderFillGeometry( const QRectF& rect, + const QskArcMetrics& metrics, bool radial, qreal borderWidth, QSGGeometry& geometry ) +{ + geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); + + const Renderer renderer( rect, metrics, radial, QskRgb::Black, 0 ); + + const auto lines = qskAllocateLines( geometry, renderer.fillCount() ); + if ( lines ) + { + QskVertex::Line* border = nullptr; + renderer.renderArc( metrics.thickness(), borderWidth, lines, border ); + } +} diff --git a/src/nodes/QskArcRenderer.h b/src/nodes/QskArcRenderer.h new file mode 100644 index 00000000..1b0ce528 --- /dev/null +++ b/src/nodes/QskArcRenderer.h @@ -0,0 +1,48 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_ARC_RENDERER_H +#define QSK_ARC_RENDERER_H + +#include "QskGlobal.h" + +class QskArcMetrics; +class QskGradient; + +class QSGGeometry; +class QRectF; +class QColor; + +namespace QskArcRenderer +{ + /* + Filling the geometry without any color information: + see QSGGeometry::defaultAttributes_Point2D() + + - clip nodes + - using shaders setting the colors + */ + + QSK_EXPORT void renderBorderGeometry( const QRectF&, + const QskArcMetrics&, bool radial, qreal borderWidth, QSGGeometry& ); + + QSK_EXPORT void renderFillGeometry( const QRectF&, + const QskArcMetrics&, bool radial, qreal borderWidth, QSGGeometry& ); + + /* + Filling the geometry with color information: + see QSGGeometry::defaultAttributes_ColoredPoint2D() + */ + QSK_EXPORT bool isGradientSupported( + const QRectF&, const QskArcMetrics&, const QskGradient& ); + + QSK_EXPORT void renderArc( const QRectF&, const QskArcMetrics&, bool radial, + qreal borderWidth, const QskGradient&, const QColor& borderColor, QSGGeometry& ); + + QSK_EXPORT void renderArc( const QRectF&, const QskArcMetrics&, bool radial, + const QskGradient&, QSGGeometry& ); +} + +#endif diff --git a/src/nodes/QskTextureRenderer.cpp b/src/nodes/QskTextureRenderer.cpp index cade0829..83796f7f 100644 --- a/src/nodes/QskTextureRenderer.cpp +++ b/src/nodes/QskTextureRenderer.cpp @@ -122,7 +122,7 @@ quint32 QskTextureRenderer::createPaintedTextureGL( Binding GL_ARRAY_BUFFER/GL_ELEMENT_ARRAY_BUFFER to 0 seems to be enough. However - as we do not know what is finally painted and what the - OpenGL paint engine is doing with better reinitialize everything. + OpenGL paint engine is doing we better reinitialize everything. Hope this has no side effects as the context will leave the function in a modified state. Otherwise we could try to change the buffers diff --git a/src/nodes/QskVertex.h b/src/nodes/QskVertex.h index 0d928a84..967d2382 100644 --- a/src/nodes/QskVertex.h +++ b/src/nodes/QskVertex.h @@ -26,7 +26,7 @@ namespace QskVertex Color( QRgb ) noexcept; Color( const QColor& ); - Color interpolatedTo( Color, double ratio ) const noexcept; + Color interpolatedTo( Color, qreal ratio ) const noexcept; constexpr bool operator==( const Color& ) const noexcept; constexpr bool operator!=( const Color& ) const noexcept; @@ -60,7 +60,7 @@ namespace QskVertex if ( a < 255 ) { - const double af = a / 255.0; + const auto af = a / 255.0; r *= af; g *= af; @@ -115,6 +115,11 @@ namespace QskVertex p2.set( x2, y2 ); } + inline void setLine( const QPointF& p1, const QPointF& p2 ) noexcept + { + setLine( p1.x(), p1.y(), p2.x(), p2.y() ); + } + inline void setHLine( float x1, float x2, float y ) noexcept { setLine( x1, y, x2, y ); @@ -125,14 +130,32 @@ namespace QskVertex setLine( x, y1, x, y2 ); } + inline void setLine( const QPointF& p1, const QPointF& p2, Color ) noexcept + { + /* The color parameter makes no sense, but is useful + when being used from templated code + */ + + setLine( p1.x(), p1.y(), p2.x(), p2.y() ); + } + 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 + when being used from templated code */ setLine( x1, y1, x2, y2 ); } + inline float x1() const noexcept { return p1.x; } + inline float y1() const noexcept { return p1.y; } + + inline float x2() const noexcept { return p2.x; } + inline float y2() const noexcept { return p2.y; } + + inline float dx() const noexcept { return p2.x - p1.x; } + inline float dy() const noexcept { return p2.y - p1.y; } + QSGGeometry::Point2D p1; QSGGeometry::Point2D p2; }; @@ -172,6 +195,15 @@ namespace QskVertex setLine( x, y1, color, x, y2, color ); } + inline float x1() const noexcept { return p1.x; } + inline float y1() const noexcept { return p1.y; } + + inline float x2() const noexcept { return p2.x; } + inline float y2() const noexcept { return p2.y; } + + inline float dx() const noexcept { return p2.x - p1.x; } + inline float dy() const noexcept { return p2.y - p1.y; } + QSGGeometry::ColoredPoint2D p1; QSGGeometry::ColoredPoint2D p2; }; @@ -214,15 +246,15 @@ namespace QskVertex m_stepIndex = 0; m_stepCount = stepCount; - const double angleStep = M_PI_2 / stepCount; + const auto 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 qreal cos() const { return m_cos; } + inline qreal sin() const { return m_inverted ? -m_sin : m_sin; } inline int step() const { return m_stepIndex; } inline int stepCount() const { return m_stepCount; } @@ -253,7 +285,7 @@ namespace QskVertex } else { - const double cos0 = m_cos; + const auto cos0 = m_cos; m_cos = m_cos * m_cosStep + m_sin * m_sinStep; m_sin = m_sin * m_cosStep - cos0 * m_sinStep; @@ -269,9 +301,9 @@ namespace QskVertex inline void operator++() { increment(); } - static int segmentHint( double radius ) + static int segmentHint( qreal radius ) { - const double arcLength = radius * M_PI_2; + const auto arcLength = radius * M_PI_2; return qBound( 3, qCeil( arcLength / 3.0 ), 18 ); // every 3 pixels } @@ -292,12 +324,12 @@ namespace QskVertex } private: - double m_cos; - double m_sin; + qreal m_cos; + qreal m_sin; int m_stepIndex; - double m_cosStep; - double m_sinStep; + qreal m_cosStep; + qreal m_sinStep; int m_stepCount; bool m_inverted; diff --git a/support/CMakeLists.txt b/support/CMakeLists.txt index 9666ea99..e2243357 100644 --- a/support/CMakeLists.txt +++ b/support/CMakeLists.txt @@ -11,7 +11,7 @@ set(SOURCES SkinnyShortcut.h SkinnyShortcut.cpp ) -qt_add_resources(SOURCES fonts.qrc) +qt_add_resources(SOURCES DejaVuSans.qrc Roboto.qrc SegoeUI.qrc) set(target qsktestsupport) diff --git a/support/DejaVuSans.qrc b/support/DejaVuSans.qrc new file mode 100644 index 00000000..324c6a33 --- /dev/null +++ b/support/DejaVuSans.qrc @@ -0,0 +1,7 @@ + + + + fonts/DejaVuSans/DejaVuSans.ttf + + + diff --git a/support/Roboto.qrc b/support/Roboto.qrc new file mode 100644 index 00000000..290c7469 --- /dev/null +++ b/support/Roboto.qrc @@ -0,0 +1,18 @@ + + + + fonts/Roboto/Roboto-BlackItalic.ttf + fonts/Roboto/Roboto-Black.ttf + fonts/Roboto/Roboto-BoldItalic.ttf + fonts/Roboto/Roboto-Bold.ttf + fonts/Roboto/Roboto-Italic.ttf + fonts/Roboto/Roboto-LightItalic.ttf + fonts/Roboto/Roboto-Light.ttf + fonts/Roboto/Roboto-MediumItalic.ttf + fonts/Roboto/Roboto-Medium.ttf + fonts/Roboto/Roboto-Regular.ttf + fonts/Roboto/Roboto-ThinItalic.ttf + fonts/Roboto/Roboto-Thin.ttf + + + diff --git a/support/SegoeUI.qrc b/support/SegoeUI.qrc new file mode 100644 index 00000000..644baf22 --- /dev/null +++ b/support/SegoeUI.qrc @@ -0,0 +1,18 @@ + + + + fonts/SegoeUI/segoeuib.ttf + fonts/SegoeUI/segoeuii.ttf + fonts/SegoeUI/segoeuil.ttf + fonts/SegoeUI/segoeuisl.ttf + fonts/SegoeUI/segoeui.ttf + fonts/SegoeUI/segoeuiz.ttf + fonts/SegoeUI/seguibli.ttf + fonts/SegoeUI/seguibl.ttf + fonts/SegoeUI/seguili.ttf + fonts/SegoeUI/seguisbi.ttf + fonts/SegoeUI/seguisb.ttf + fonts/SegoeUI/seguisli.ttf + + + diff --git a/support/fonts.qrc b/support/fonts.qrc deleted file mode 100644 index 1a42a3c2..00000000 --- a/support/fonts.qrc +++ /dev/null @@ -1,36 +0,0 @@ - - - - fonts/DejaVuSans/DejaVuSans.ttf - - fonts/Roboto/Roboto-BlackItalic.ttf - fonts/Roboto/Roboto-Black.ttf - fonts/Roboto/Roboto-BoldItalic.ttf - fonts/Roboto/Roboto-Bold.ttf - fonts/Roboto/Roboto-Italic.ttf - fonts/Roboto/Roboto-LightItalic.ttf - fonts/Roboto/Roboto-Light.ttf - fonts/Roboto/Roboto-MediumItalic.ttf - fonts/Roboto/Roboto-Medium.ttf - fonts/Roboto/Roboto-Regular.ttf - fonts/Roboto/Roboto-ThinItalic.ttf - fonts/Roboto/Roboto-Thin.ttf - - fonts/SegoeUI/segoeuib.ttf - fonts/SegoeUI/segoeuii.ttf - fonts/SegoeUI/segoeuil.ttf - fonts/SegoeUI/segoeuisl.ttf - fonts/SegoeUI/segoeui.ttf - fonts/SegoeUI/segoeuiz.ttf - fonts/SegoeUI/seguibli.ttf - fonts/SegoeUI/seguibl.ttf - fonts/SegoeUI/seguiemj.ttf - fonts/SegoeUI/seguihis.ttf - fonts/SegoeUI/seguili.ttf - fonts/SegoeUI/seguisbi.ttf - fonts/SegoeUI/seguisb.ttf - fonts/SegoeUI/seguisli.ttf - fonts/SegoeUI/seguisym.ttf - - - diff --git a/support/fonts/SegoeUI/README b/support/fonts/SegoeUI/README deleted file mode 100644 index e69de29b..00000000 diff --git a/support/fonts/SegoeUI/seguiemj.ttf b/support/fonts/SegoeUI/seguiemj.ttf deleted file mode 100755 index 77497bf7..00000000 Binary files a/support/fonts/SegoeUI/seguiemj.ttf and /dev/null differ diff --git a/support/fonts/SegoeUI/seguihis.ttf b/support/fonts/SegoeUI/seguihis.ttf deleted file mode 100755 index a10c573e..00000000 Binary files a/support/fonts/SegoeUI/seguihis.ttf and /dev/null differ diff --git a/support/fonts/SegoeUI/seguisym.ttf b/support/fonts/SegoeUI/seguisym.ttf deleted file mode 100755 index d987ce50..00000000 Binary files a/support/fonts/SegoeUI/seguisym.ttf and /dev/null differ diff --git a/tools/svg2qvg/main.cpp b/tools/svg2qvg/main.cpp index d5ffcf82..3988076a 100644 --- a/tools/svg2qvg/main.cpp +++ b/tools/svg2qvg/main.cpp @@ -25,6 +25,22 @@ static void usage( const char* appName ) qWarning() << "usage: " << appName << "svgfile qvgfile"; } +static QRectF viewBox( QSvgRenderer& renderer ) +{ + /* + QSvgRenderer::viewBoxF() returns a bounding box when no viewBox + has been defined. So we clear the viewBox and compare the result with + the initial value - assuming, that there was a viewBox when they differ. + */ + const auto viewBox = renderer.viewBoxF(); + + renderer.setViewBox( QRectF() ); + const bool hasViewBox = ( viewBox != renderer.viewBoxF() ); + renderer.setViewBox( viewBox ); + + return hasViewBox ? viewBox : QRectF( 0.0, 0.0, -1.0, -1.0 ); +} + int main( int argc, char* argv[] ) { if ( argc != 3 ) @@ -56,6 +72,7 @@ int main( int argc, char* argv[] ) return -2; QskGraphic graphic; + graphic.setViewBox( ::viewBox( renderer ) ); QPainter painter( &graphic ); renderer.render( &painter );