diff --git a/designsystems/fluent2/QskFluent2Skin.cpp b/designsystems/fluent2/QskFluent2Skin.cpp index 10f73b74..143be37c 100644 --- a/designsystems/fluent2/QskFluent2Skin.cpp +++ b/designsystems/fluent2/QskFluent2Skin.cpp @@ -1008,7 +1008,7 @@ void Editor::setupPushButtonColors( const auto text = Q::Text | section | variation; const auto icon = Q::Icon | section | variation; - for ( const auto state : { QskAspect::NoState, Q::Hovered, Q::Pressed, Q::Disabled } ) + for ( const auto state : { QskAspect::NoState, Q::Hovered, Q::Pressed, Q::Checked, Q::Disabled } ) { QRgb panelColor, borderColor1, borderColor2, textColor; int graphicRole; @@ -1023,7 +1023,7 @@ void Editor::setupPushButtonColors( textColor = pal.fillColor.textOnAccent.primary; graphicRole = W::GraphicRoleFillColorTextOnAccentPrimary; } - else if ( state == Q::Pressed ) + else if ( state == Q::Pressed || state == Q::Checked ) { panelColor = pal.fillColor.accent.tertiary; borderColor1 = borderColor2 = pal.strokeColor.control.onAccentDefault; @@ -1056,7 +1056,7 @@ void Editor::setupPushButtonColors( textColor = pal.fillColor.text.primary; graphicRole = W::GraphicRoleFillColorTextPrimary; } - else if ( state == Q::Pressed ) + else if ( state == Q::Pressed || state == Q::Checked ) { panelColor = pal.fillColor.control.tertiary; borderColor1 = borderColor2 = pal.strokeColor.control.defaultColor; @@ -1450,10 +1450,16 @@ void Editor::setupSliderMetrics() setShadowMetrics( Q::Handle, { shadowSpread, 0.0 } ); - setBoxBorderMetrics( Q::Handle, 5 ); - setBoxBorderMetrics( Q::Handle | Q::Hovered, 4 ); - setBoxBorderMetrics( Q::Handle | Q::Pressed, 6 ); - setBoxBorderMetrics( Q::Handle | Q::Disabled, 6 ); + setBoxBorderMetrics( Q::Handle, 5_px ); + setBoxBorderMetrics( Q::Handle | Q::Hovered, 4_px ); + setBoxBorderMetrics( Q::Handle | Q::Pressed, 6_px ); + setBoxBorderMetrics( Q::Handle | Q::Disabled, 6_px ); + + setFlag( Q::Tick | A::Option, Qsk::Maybe ); + setStrutSize( Q::Tick | A::Horizontal, 1_px, -1 ); + setStrutSize( Q::Tick | A::Vertical, -1, 1_px ); + + setAnimation( Q::Handle | A::Metric | A::Position, 100 ); } void Editor::setupSliderColors( @@ -1484,30 +1490,35 @@ void Editor::setupSliderColors( for ( auto state : { A::NoState, Q::Hovered, Q::Pressed, Q::Disabled } ) { - QRgb grooveColor, handleColor; + QRgb grooveColor, fillColor, handleColor; if ( state == A::NoState || state == Q::Hovered ) { grooveColor = pal.fillColor.controlStrong.defaultColor; + fillColor = pal.fillColor.accent.defaultColor; handleColor = pal.fillColor.accent.defaultColor; } else if ( state == Q::Pressed ) { grooveColor = pal.fillColor.controlStrong.defaultColor; handleColor = pal.fillColor.accent.tertiary; + fillColor = pal.fillColor.accent.defaultColor; } else if ( state == Q::Disabled ) { grooveColor = pal.fillColor.controlStrong.disabled; + fillColor = pal.fillColor.accent.disabled; handleColor = grooveColor; } grooveColor = rgbSolid( grooveColor, pal.background.solid.base ); setGradient( Q::Groove | section | state, grooveColor ); - setGradient( Q::Fill | section | state, grooveColor ); + setGradient( Q::Fill | section | state, fillColor ); setGradient( Q::Handle | section | state, handleColor ); } + + setGradient( Q::Tick, pal.fillColor.controlSolid.defaultColor ); } void Editor::setupSpinBoxMetrics() diff --git a/designsystems/fusion/QskFusionSkin.cpp b/designsystems/fusion/QskFusionSkin.cpp index bad14860..f3a50d2b 100644 --- a/designsystems/fusion/QskFusionSkin.cpp +++ b/designsystems/fusion/QskFusionSkin.cpp @@ -46,6 +46,8 @@ #include #include +#include + #include #include #include @@ -773,9 +775,10 @@ void Editor::setupSlider() { using A = QskAspect; using Q = QskSlider; + using SK = QskSliderSkinlet; using P = QPalette; - const qreal extent = 30_px; + const qreal extent = 16_px; // Panel @@ -784,6 +787,7 @@ void Editor::setupSlider() setBoxBorderMetrics( Q::Panel, 0 ); setGradient( Q::Panel, QskGradient() ); + // space for the handle setPadding( Q::Panel | A::Horizontal, QskMargins( 0.5 * extent, 0 ) ); setPadding( Q::Panel | A::Vertical, QskMargins( 0, 0.5 * extent ) ); @@ -823,6 +827,39 @@ void Editor::setupSlider() } } + { + /* + Tick + + QSlider optionally allows to display ticks left/right or top/bottom + of the groove. However this not supported by the skinlets yet. + ( Qt/Quick slider does not support showing ticks at all ) + + For the moment we make something up. TODO ... + */ + + setFlag( Q::Tick | A::Option, Qsk::Maybe ); + setStrutSize( Q::Tick, 2_px, 2_px ); + + const QskStateCombination combination( + QskStateCombination::CombinationNoState, Q::Focused | Q::Pressed ); + + const auto rgb = m_pal.active( P::Text ); + + setColor( Q::Tick, + QskRgb::interpolated( rgb, m_pal.groove, 0.2 ), combination ); + + setColor( Q::Tick | Q::Disabled, + QskRgb::interpolated( rgb, m_pal.groove, 0.5 ) ); + + setColor( Q::Tick | SK::Filled, + m_pal.active( P::HighlightedText ), combination ); + setColor( Q::Tick | SK::Filled | Q::Disabled, + m_pal.disabled( P::HighlightedText ) ); + } + + // Handle + setBoxShape( Q::Handle, 2 ); setBoxBorderMetrics( Q::Handle, 1 ); setBoxBorderColors( Q::Handle, m_pal.outline ); @@ -832,7 +869,7 @@ void Editor::setupSlider() Combination( { Q::Hovered, Q::Pressed } ) ); #endif - setStrutSize( Q::Handle, 16_px, 16_px ); + setStrutSize( Q::Handle, extent, extent ); for ( auto state : { A::NoState, Q::Pressed } ) { @@ -845,9 +882,7 @@ void Editor::setupSlider() QskRgb::lighter( rgb, 102 ) ); } - // move the handle smoothly, when using keys setAnimation( Q::Handle | A::Metric | A::Position, 2 * qskDuration ); - setAnimation( Q::Handle | A::Metric | A::Position | Q::Pressed, 0 ); } void Editor::setupSpinBox() diff --git a/designsystems/material3/CMakeLists.txt b/designsystems/material3/CMakeLists.txt index 051e8ed9..644837fc 100644 --- a/designsystems/material3/CMakeLists.txt +++ b/designsystems/material3/CMakeLists.txt @@ -3,11 +3,28 @@ # SPDX-License-Identifier: BSD-3-Clause ############################################################################ -set(SOURCES - QskMaterial3Global.h QskMaterial3Skin.h QskMaterial3Skin.cpp - QskMaterial3SkinFactory.h QskMaterial3SkinFactory.cpp +list(APPEND HEADERS + QskMaterial3Global.h QskMaterial3Skin.h QskMaterial3SkinFactory.h ) + +list(APPEND PRIVATE_HEADERS + QskMaterial3ProgressBarSkinlet.h + QskMaterial3SliderSkinlet.h +) + +list(APPEND SOURCES + QskMaterial3Skin.cpp + QskMaterial3SkinFactory.cpp + QskMaterial3ProgressBarSkinlet.cpp + QskMaterial3SliderSkinlet.cpp +) + qt_add_resources(SOURCES QskMaterial3Icons.qrc) -qsk_add_plugin(material3skin skins QskMaterial3SkinFactory ${SOURCES}) -set_target_properties(material3skin PROPERTIES DEFINE_SYMBOL QSK_MATERIAL3_MAKEDLL ) +qsk_add_plugin(material3skin skins QskMaterial3SkinFactory + ${SOURCES} ${HEADERS} ${PRIVATE_HEADERS} +) + +set_target_properties(material3skin PROPERTIES + DEFINE_SYMBOL QSK_MATERIAL3_MAKEDLL +) diff --git a/designsystems/material3/QskMaterial3ProgressBarSkinlet.cpp b/designsystems/material3/QskMaterial3ProgressBarSkinlet.cpp new file mode 100644 index 00000000..81622505 --- /dev/null +++ b/designsystems/material3/QskMaterial3ProgressBarSkinlet.cpp @@ -0,0 +1,96 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskMaterial3ProgressBarSkinlet.h" +#include +#include +#include +#include +#include +#include +#include + +using Q = QskProgressBar; + +QskMaterial3ProgressBarSkinlet::QskMaterial3ProgressBarSkinlet( QskSkin* skin ) + : Inherited( skin ) +{ + appendNodeRoles( { StopIndicatorRole } ); +} + +QSGNode* QskMaterial3ProgressBarSkinlet::updateSubNode( + const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const +{ + auto progressBar = static_cast< const QskProgressBar* >( skinnable ); + + switch( nodeRole ) + { + case GrooveRole: + { + auto clippedNode = QskSGNode::findChildNode( node, GrooveRole ); + clippedNode = Inherited::updateSubNode( skinnable, nodeRole, clippedNode ); + + if ( clippedNode == nullptr ) + return nullptr; + + auto clipNode = updateGrooveClipNode( progressBar, node ); + QskSGNode::setNodeRole( clippedNode, nodeRole ); + QskSGNode::setParentNode( clippedNode, clipNode ); + + return clipNode; + } + case StopIndicatorRole: + { + if ( !( progressBar->isIndeterminate() || progressBar->hasOrigin() ) ) + return updateStopIndicatorNode( progressBar, node ); + + return nullptr; + } + } + + return Inherited::updateSubNode( skinnable, nodeRole, node ); +} + +QSGNode* QskMaterial3ProgressBarSkinlet::updateStopIndicatorNode( + const QskProgressBar* progressBar, QSGNode* node ) const +{ + auto rect = progressBar->subControlRect( Q::Groove ); + if ( rect.isEmpty() ) + return nullptr; + + if( progressBar->orientation() == Qt::Horizontal ) + rect.setLeft( rect.right() - rect.height() ); + else + rect.setBottom( rect.top() + rect.width() ); + + const auto color = progressBar->gradientHint( Q::Fill ).endColor(); + const auto shape = progressBar->boxShapeHint( Q::Fill ); + + return updateBoxNode( progressBar, node, rect, shape, + QskBoxBorderMetrics(), QskBoxBorderColors(), color ); +} + +QSGNode* QskMaterial3ProgressBarSkinlet::updateGrooveClipNode( + const QskProgressBar* progressBar, QSGNode* node ) const +{ + auto rect = progressBar->subControlRect( Q::Fill ); + if ( rect.isEmpty() ) + return nullptr; + + QskMargins margins; + if ( progressBar->orientation() == Qt::Horizontal ) + margins.setMargins( rect.height(), 0.0 ); + else + margins.setMargins( 0.0, rect.width() ); + + rect = rect.marginsAdded( margins ); + + auto clipNode = QskSGNode::ensureNode< QskClipNode >( node ); + clipNode->setRegion( progressBar->subControlRect( Q::Groove ), rect ); + + return clipNode; +} + +#include "moc_QskMaterial3ProgressBarSkinlet.cpp" diff --git a/designsystems/material3/QskMaterial3ProgressBarSkinlet.h b/designsystems/material3/QskMaterial3ProgressBarSkinlet.h new file mode 100644 index 00000000..6ccd7d7c --- /dev/null +++ b/designsystems/material3/QskMaterial3ProgressBarSkinlet.h @@ -0,0 +1,37 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_MATERIAL3_PROGRESSBAR_SKINLET_H +#define QSK_MATERIAL3_PROGRESSBAR_SKINLET_H + +#include + +class QskProgressBar; + +class QskMaterial3ProgressBarSkinlet : QskProgressBarSkinlet +{ + Q_GADGET + + using Inherited = QskProgressBarSkinlet; + + public: + enum NodeRole + { + StopIndicatorRole = Inherited::RoleCount, + RoleCount + }; + + Q_INVOKABLE QskMaterial3ProgressBarSkinlet( QskSkin* = nullptr ); + + protected: + QSGNode* updateSubNode( const QskSkinnable*, + quint8 nodeRole, QSGNode* ) const override; + + private: + QSGNode* updateStopIndicatorNode( const QskProgressBar*, QSGNode* ) const; + QSGNode* updateGrooveClipNode( const QskProgressBar*, QSGNode* ) const; +}; + +#endif diff --git a/designsystems/material3/QskMaterial3Skin.cpp b/designsystems/material3/QskMaterial3Skin.cpp index 117d52cd..056be67b 100644 --- a/designsystems/material3/QskMaterial3Skin.cpp +++ b/designsystems/material3/QskMaterial3Skin.cpp @@ -9,6 +9,8 @@ */ #include "QskMaterial3Skin.h" +#include "QskMaterial3ProgressBarSkinlet.h" +#include "QskMaterial3SliderSkinlet.h" #include @@ -486,20 +488,13 @@ void Editor::setupProgressBar() using A = QskAspect; using Q = QskProgressBar; - auto size = 4_dp; - for ( auto subControl : { Q::Groove, Q::Fill } ) { - setMetric( subControl | A::Size, size ); - setPadding( subControl, 0 ); - - setBoxShape( subControl, 0 ); - setBoxBorderMetrics( subControl, 0 ); + setBoxShape( subControl, { 100, Qt::RelativeSize } ); + setMetric( subControl | A::Size, 4_dp ); } - setMetric( Q::Groove | A::Size, size ); setGradient( Q::Groove, m_pal.surfaceContainerHighest ); - setGradient( Q::Groove | Q::Disabled, m_pal.onSurface12 ); setGradient( Q::Fill, m_pal.primary ); @@ -510,9 +505,15 @@ void Editor::setupProgressRing() { using Q = QskProgressRing; + setArcMetrics( Q::Groove, 90, -360, 4_dp ); + setGradient( Q::Groove, m_pal.surfaceContainerHighest ); + setGradient( Q::Groove | Q::Disabled, m_pal.onSurface12 ); + + setSpacing( Q::Fill, 10 ); setStrutSize( Q::Fill, { 48_dp, 48_dp } ); - setGradient( Q::Fill, m_pal.primary ); setArcMetrics( Q::Fill, 90, -360, 4_dp ); + setGradient( Q::Fill, m_pal.primary ); + setGradient( Q::Fill | Q::Disabled, m_pal.onSurface38 ); } void Editor::setupRadioBox() @@ -725,6 +726,7 @@ void Editor::setupPushButton() setBoxShape( Q::Splash, 40_dp ); setAnimation( Q::Splash | QskAspect::Color, qskDuration ); + const auto checkedOpacity = m_pal.focusOpacity + m_pal.pressedOpacity; // elevated buttons: @@ -751,8 +753,11 @@ void Editor::setupPushButton() setGradient( Q::Panel | M3::Elevated | Q::Focused, elevatedPressedColor ); setShadowMetrics( Q::Panel | M3::Elevated | Q::Focused, m_pal.elevation1 ); - setGradient( Q::Panel | M3::Elevated | Q::Pressed, elevatedPressedColor ); - setShadowMetrics( Q::Panel | M3::Elevated | Q::Pressed, m_pal.elevation1 ); + for( const auto state: { Q::Pressed, Q::Checked } ) + { + setGradient( Q::Panel | M3::Elevated | state, elevatedPressedColor ); + setShadowMetrics( Q::Panel | M3::Elevated | state, m_pal.elevation1 ); + } // normal buttons (i.e. Filled): @@ -769,6 +774,8 @@ void Editor::setupPushButton() setGradient( Q::Panel | Q::Focused, focusColor ); setGradient( Q::Panel | Q::Pressed, focusColor ); + setGradient( Q::Panel | Q::Checked, + flattenedColor( m_pal.onPrimary, m_pal.primary, checkedOpacity ) ); setGradient( Q::Splash, stateLayerColor( m_pal.onPrimary, m_pal.hoverOpacity ) ); @@ -806,6 +813,10 @@ void Editor::setupPushButton() setGradient( Q::Panel | M3::Tonal | Q::Pressed, tonalPressedColor ); setShadowMetrics( Q::Panel | M3::Tonal | Q::Pressed, m_pal.elevation0 ); + const auto tonalCheckedColor = flattenedColor( m_pal.onSecondaryContainer, + m_pal.secondaryContainer, checkedOpacity ); + setGradient( Q::Panel | M3::Tonal | Q::Checked, tonalCheckedColor ); + // outlined buttons: @@ -831,6 +842,7 @@ void Editor::setupPushButton() setGradient( Q::Panel | M3::Outlined | Q::Pressed, m_pal.primary12 ); + setGradient( Q::Panel | M3::Outlined | Q::Checked, m_pal.primary12 ); /* text buttons: @@ -857,6 +869,8 @@ void Editor::setupPushButton() setGradient( Q::Panel | M3::Text | Q::Focused, m_pal.primary12 ); setGradient( Q::Panel | M3::Text | Q::Pressed, m_pal.primary12 ); + + setGradient( Q::Panel | M3::Text | Q::Checked, m_pal.primary12 ); } void Editor::setupDialogButtonBox() @@ -892,41 +906,60 @@ void Editor::setupSlider() { using A = QskAspect; using Q = QskSlider; + using SK = QskSliderSkinlet; - const QSizeF sliderSize( 48_dp, 44_dp ); - setStrutSize( Q::Panel | A::Horizontal, sliderSize ); - setStrutSize( Q::Panel | A::Vertical, sliderSize.transposed() ); + const auto extentGroove = 16_dp; + const auto extentPanel = 44_dp; - setBoxShape( Q::Groove | A::Horizontal, { 0, 100, 0, 100, Qt::RelativeSize } ); - setBoxShape( Q::Groove | A::Vertical, { 100, 100, 0, 0, Qt::RelativeSize } ); - setMetric( Q::Groove | A::Size, 16_dp ); - setMargin( Q::Groove | A::Horizontal, { 6_dp, 0, 0, 0 } ); - setMargin( Q::Groove | A::Vertical, {0, 0, 0, 6_dp } ); + setStrutSize( Q::Panel | A::Horizontal, 3 * extentGroove, extentPanel ); + setStrutSize( Q::Panel | A::Vertical, extentPanel, 3 * extentGroove ); + + setMetric( Q::Groove | A::Size, extentGroove ); + setMetric( Q::Fill | A::Size, extentGroove ); setGradient( Q::Groove, m_pal.primaryContainer ); setGradient( Q::Groove | Q::Disabled, m_pal.onSurface12 ); - setBoxShape( Q::Fill | A::Horizontal, { 100, 0, 100, 0, Qt::RelativeSize } ); - setBoxShape( Q::Fill | A::Vertical, { 0, 0, 100, 100, Qt::RelativeSize } ); - setMetric( Q::Fill | A::Size, 16_dp ); - setMargin( Q::Fill | A::Horizontal, { 0, 0, 6_dp, 0 } ); - setMargin( Q::Fill | A::Vertical, {0, 6_dp, 0, 0 } ); - setGradient( Q::Fill, m_pal.primary ); setGradient( Q::Fill | Q::Disabled, m_pal.onSurface38 ); setBoxShape( Q::Handle, 100, Qt::RelativeSize ); - setBoxBorderMetrics( Q::Handle, 0 ); + setBoxShape( Q::Groove, 100, Qt::RelativeSize ); + setBoxShape( Q::Fill, 100, Qt::RelativeSize ); - const QSizeF handleSize( 4_dp, 44_dp ); - const QSizeF handleSizeFocusedPressed( 2_dp, 44_dp ); - setStrutSize( Q::Handle | A::Horizontal, handleSize ); - setStrutSize( Q::Handle | A::Horizontal, handleSizeFocusedPressed, - { QskStateCombination::Combination, Q::Focused | Q::Pressed } ); + setStrutSize( Q::Tick, { 4_dp, 4_dp } ); + setBoxShape( Q::Tick, 100, Qt::RelativeSize ); - setStrutSize( Q::Handle | A::Vertical, handleSize.transposed() ); - setStrutSize( Q::Handle | A::Vertical, handleSizeFocusedPressed.transposed(), - { QskStateCombination::Combination, Q::Focused | Q::Pressed } ); + setGradient( Q::Tick, m_pal.primary ); + setGradient( Q::Tick | Q::Disabled, m_pal.onSurface ); + + setGradient( Q::Tick | SK::Filled, m_pal.secondaryContainer, + { QskStateCombination::CombinationNoState, Q::Focused | Q::Pressed } ); + setGradient( Q::Tick | SK::Filled | Q::Disabled, m_pal.inverseOnSurface ); + + setFlag( Q::Fill | A::Option, Qsk::Maybe ); + setFlag( Q::Tick | A::Option, Qsk::Maybe ); + + for ( const auto variation : { A::Horizontal, A::Vertical } ) + { + QSizeF handleSize( extentGroove, extentPanel ); + QskMargins margin1{ 6_dp, 0_dp }; + QskMargins margin2{ 7_dp, 0_dp }; + + if ( variation == A::Vertical ) + { + handleSize = handleSize.transposed(); + margin1 = margin1.rotated(); + margin2 = margin2.rotated(); + } + + const auto aspect = Q::Handle | variation; + + setStrutSize( aspect, handleSize ); + setMargin( aspect, margin1 ); + setMargin( aspect, margin2, + { QskStateCombination::Combination, Q::Focused | Q::Pressed } ); + } setGradient( Q::Handle, m_pal.primary ); setGradient( Q::Handle | Q::Pressed, m_pal.primary ); @@ -934,23 +967,6 @@ void Editor::setupSlider() const auto disabledColor = flattenedColor( m_pal.onSurface, m_pal.background, 0.38 ); setGradient( Q::Handle | Q::Disabled, disabledColor ); - for( auto indicator : { Q::GrooveStopIndicators, Q::FillStopIndicators } ) - { - setStrutSize( indicator, { 4_dp, 4_dp } ); - setBoxShape( indicator, 100, Qt::RelativeSize ); - } - - const auto p = 6_dp; - setPadding( Q::GrooveStopIndicators | A::Horizontal, { p, 0, p, 0 } ); - setPadding( Q::GrooveStopIndicators | A::Vertical, { 0, p, 0, p } ); - setPadding( Q::FillStopIndicators | A::Horizontal, { p, 0, p, 0 } ); - setPadding( Q::FillStopIndicators | A::Vertical, { 0, p, 0, p } ); - - setGradient( Q::GrooveStopIndicators, m_pal.primary ); - setGradient( Q::GrooveStopIndicators | Q::Disabled, m_pal.onSurface ); - setGradient( Q::FillStopIndicators, m_pal.secondaryContainer ); - setGradient( Q::FillStopIndicators | Q::Disabled, m_pal.inverseOnSurface ); - for( const auto state : { Q::Focused, Q::Pressed } ) { setStrutSize( Q::LabelContainer | state, 48_dp, 44_dp, @@ -966,9 +982,7 @@ void Editor::setupSlider() setColor( Q::LabelText, m_pal.inverseOnSurface ); setAlignment( Q::LabelText, Qt::AlignCenter ); - // move the handle smoothly when using keys setAnimation( Q::Handle | A::Metric | A::Position, 2 * qskDuration ); - setAnimation( Q::Handle | A::Metric | A::Position | Q::Pressed, 0 ); } void Editor::setupSpinBox() @@ -1596,6 +1610,8 @@ qreal QskMaterial3Theme::stateOpacity( int state ) const QskMaterial3Skin::QskMaterial3Skin( QObject* parent ) : Inherited( parent ) { + declareSkinlet< QskProgressBar, QskMaterial3ProgressBarSkinlet >(); + declareSkinlet< QskSlider, QskMaterial3SliderSkinlet >(); } QskMaterial3Skin::~QskMaterial3Skin() diff --git a/designsystems/material3/QskMaterial3SliderSkinlet.cpp b/designsystems/material3/QskMaterial3SliderSkinlet.cpp new file mode 100644 index 00000000..afb4e3bc --- /dev/null +++ b/designsystems/material3/QskMaterial3SliderSkinlet.cpp @@ -0,0 +1,124 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskMaterial3SliderSkinlet.h" + +#include +#include +#include +#include + +using Q = QskSlider; + +static inline bool qskHasOrigin( const QskSlider* slider ) +{ + return !qskFuzzyCompare( slider->origin(), slider->minimum() ); +} + +QskMaterial3SliderSkinlet::QskMaterial3SliderSkinlet( QskSkin* skin ) + : Inherited( skin ) +{ +} + +QRectF QskMaterial3SliderSkinlet::subControlRect( const QskSkinnable* skinnable, + const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const +{ + if ( subControl == Q::Scale ) + { + auto r = Inherited::subControlRect( skinnable, contentsRect, Q::Scale ); + + const auto handleSize = skinnable->strutSizeHint( Q::Handle ); + + const auto slider = static_cast< const QskSlider* >( skinnable ); + if( slider->orientation() == Qt::Horizontal ) + { + const auto m = 0.5 * handleSize.width(); + r.adjust( m, 0.0, -m, 0.0 ); + } + { + const auto m = 0.5 * handleSize.height(); + r.adjust( 0.0, m, 0.0, -m ); + } + + return r; + } + + return Inherited::subControlRect( skinnable, contentsRect, subControl ); +} + +QSGNode* QskMaterial3SliderSkinlet::updateSubNode( + const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const +{ + switch( nodeRole ) + { + case GrooveRole: + case FillRole: + { + auto clippedNode = QskSGNode::findChildNode( node, nodeRole ); + clippedNode = Inherited::updateSubNode( skinnable, nodeRole, clippedNode ); + + if ( clippedNode ) + { + const auto slider = static_cast< const QskSlider* >( skinnable ); + + auto clipNode = QskSGNode::ensureNode< QskClipNode >( node ); + + clipNode->setRegion( slider->subControlRect( Q::Panel ), + slider->subControlRect( Q::Handle ) ); + + QskSGNode::setNodeRole( clippedNode, nodeRole ); + QskSGNode::setParentNode( clippedNode, clipNode ); + + return clipNode; + } + + return nullptr; + } + } + + return Inherited::updateSubNode( skinnable, nodeRole, node ); +} + +QVector< qreal > QskMaterial3SliderSkinlet::graduation( const QskSlider* slider ) const +{ + QVector< qreal > graduation; + + if ( hasGraduation( slider ) ) + { + const auto g = Inherited::graduation( slider ); + + // adding the boundaries + + graduation.reserve( g.count() + 2 ); + graduation += slider->minimum(); + graduation += g; + graduation += slider->maximum(); + } + else + { + const auto policy = slider->flagHint< Qsk::Policy >( + Q::Tick | QskAspect::Option, Qsk::Maybe ); + + if ( policy != Qsk::Never ) + { + if ( qskHasOrigin( slider ) ) + { + graduation.reserve( 3 ); + graduation += slider->minimum(); + graduation += slider->origin(); + graduation += slider->maximum(); + } + else + { + graduation.reserve( 1 ); + graduation += slider->maximum(); + } + } + } + + return graduation; +} + +#include "moc_QskMaterial3SliderSkinlet.cpp" diff --git a/designsystems/material3/QskMaterial3SliderSkinlet.h b/designsystems/material3/QskMaterial3SliderSkinlet.h new file mode 100644 index 00000000..6ad9d02d --- /dev/null +++ b/designsystems/material3/QskMaterial3SliderSkinlet.h @@ -0,0 +1,30 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_MATERIAL3_SLIDER_SKINLET_H +#define QSK_MATERIAL3_SLIDER_SKINLET_H + +#include + +class QskMaterial3SliderSkinlet : QskSliderSkinlet +{ + Q_GADGET + + using Inherited = QskSliderSkinlet; + + public: + Q_INVOKABLE QskMaterial3SliderSkinlet( QskSkin* = nullptr ); + + QRectF subControlRect( const QskSkinnable*, + const QRectF& rect, QskAspect::Subcontrol ) const override; + + protected: + QSGNode* updateSubNode( const QskSkinnable*, + quint8 nodeRole, QSGNode* ) const override; + + QVector< qreal > graduation( const QskSlider* ) const override; +}; + +#endif diff --git a/doc/Doxyfile b/doc/Doxyfile index 7ccbbb04..0be33ef5 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -1,4 +1,4 @@ -# Doxyfile 1.9.3 +# Doxyfile 1.9.8 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -12,6 +12,16 @@ # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] #--------------------------------------------------------------------------- # Project related configuration options @@ -38,13 +48,13 @@ PROJECT_NAME = QSkinny # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 0.0.1 +PROJECT_NUMBER = 0.8.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = +PROJECT_BRIEF = C++/Qt UI toolkit # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 @@ -58,18 +68,30 @@ PROJECT_LOGO = # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = +OUTPUT_DIRECTORY = api -# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes -# performance problems for the file system. +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. # The default value is: NO. CREATE_SUBDIRS = NO +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode @@ -81,14 +103,14 @@ ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English @@ -336,6 +358,17 @@ MARKDOWN_SUPPORT = YES TOC_INCLUDE_HEADINGS = 5 +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or @@ -447,7 +480,7 @@ TYPEDEF_HIDES_STRUCT = NO LOOKUP_CACHE_SIZE = 0 -# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use # during processing. When set to 0 doxygen will based this on the number of # cores available in the system. You can set it explicitly to a value larger # than 0 to get more control over the balance between CPU load and processing @@ -460,6 +493,14 @@ LOOKUP_CACHE_SIZE = 0 NUM_PROC_THREADS = 1 +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -541,7 +582,8 @@ HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO, these classes will be included in the various overviews. This option -# has no effect if EXTRACT_ALL is enabled. +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO @@ -572,14 +614,15 @@ INTERNAL_DOCS = NO # filesystem is case sensitive (i.e. it supports files in the same directory # whose names only differ in casing), the option must be set to YES to properly # deal with such files in case they appear in the input. For filesystems that -# are not case sensitive the option should be be set to NO to properly deal with +# are not case sensitive the option should be set to NO to properly deal with # output files written for symbols that only differ in casing, such as for two # classes, one named CLASS and the other named Class, and to also support # references to files without having to specify the exact matching casing. On # Windows (including Cygwin) and MacOS, users should typically set this option # to NO, whereas on Linux or other Unix flavors it should typically be set to # YES. -# The default value is: system dependent. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. CASE_SENSE_NAMES = YES @@ -831,11 +874,26 @@ WARN_IF_INCOMPLETE_DOC = YES WARN_NO_PARAMDOC = NO +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about +# undocumented enumeration values. If set to NO, doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but # at the end of the doxygen process doxygen will return with a non-zero status. -# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. # The default value is: NO. WARN_AS_ERROR = NO @@ -846,10 +904,21 @@ WARN_AS_ERROR = NO # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). In case the file specified cannot be opened for writing the @@ -878,10 +947,21 @@ INPUT = . \ # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: # https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING # The default value is: UTF-8. INPUT_ENCODING = UTF-8 +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. @@ -893,12 +973,12 @@ INPUT_ENCODING = UTF-8 # Note the list of default checked file patterns might differ from the list of # default file extension mappings. # -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, -# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, -# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C -# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, -# *.vhdl, *.ucf, *.qsf and *.ice. +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, +# *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, *.php, +# *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be +# provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = @@ -931,20 +1011,22 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = *Private.* moc*.cpp *.moc +EXCLUDE_PATTERNS = *Private.* \ + moc*.cpp \ + *.moc # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # ANamespace::AClass, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = QskPainterCommand::PixmapData \ QskPainterCommand::ImageData \ - QskPainterCommand::StateData + QskPainterCommand::StateData \ + QHash \ + QList \ + std # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include @@ -987,6 +1069,11 @@ IMAGE_PATH = images # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. @@ -1028,6 +1115,15 @@ FILTER_SOURCE_PATTERNS = USE_MDFILE_AS_MAINPAGE = +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- @@ -1114,6 +1210,46 @@ USE_HTAGS = NO VERBATIM_HEADERS = YES +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS +# tag is set to YES then doxygen will add the directory of each input to the +# include path. +# The default value is: YES. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_ADD_INC_PATHS = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- @@ -1125,10 +1261,11 @@ VERBATIM_HEADERS = YES ALPHABETICAL_INDEX = YES -# In case all classes in a project start with a common prefix, all classes will -# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag -# can be used to specify a prefix (or a list of prefixes) that should be ignored -# while generating the index headers. +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = Qsk \ @@ -1208,10 +1345,15 @@ HTML_STYLESHEET = # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_STYLESHEET = +HTML_EXTRA_STYLESHEET = ./customdoxygen.css # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note @@ -1223,6 +1365,19 @@ HTML_EXTRA_STYLESHEET = HTML_EXTRA_FILES = +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generate light mode output, DARK always +# generate dark mode output, AUTO_LIGHT automatically set the mode according to +# the user preference, use light mode if no preference is set (the default), +# AUTO_DARK automatically set the mode according to the user preference, use +# dark mode if no preference is set and TOGGLE allow to user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a color-wheel, see @@ -1232,7 +1387,7 @@ HTML_EXTRA_FILES = # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_COLORSTYLE_HUE = 30 +HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use gray-scales only. A @@ -1251,16 +1406,7 @@ HTML_COLORSTYLE_SAT = 100 # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_COLORSTYLE_GAMMA = 130 - -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = NO +HTML_COLORSTYLE_GAMMA = 80 # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that @@ -1281,6 +1427,13 @@ HTML_DYNAMIC_MENUS = YES HTML_DYNAMIC_SECTIONS = NO +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = YES + # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to @@ -1411,6 +1564,16 @@ BINARY_TOC = NO TOC_EXPAND = NO +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help @@ -1586,17 +1749,6 @@ HTML_FORMULA_FORMAT = svg FORMULA_FONTSIZE = 10 -# Use the FORMULA_TRANSPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_TRANSPARENT = YES - # The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands # to create new LaTeX commands to be used in formulas as building blocks. See # the section "Including formulas" for details. @@ -1910,9 +2062,16 @@ PDF_HYPERLINKS = YES USE_PDFLATEX = YES -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode -# command to the generated LaTeX files. This will instruct LaTeX to keep running -# if errors occur, instead of asking the user for help. +# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error. +# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch +# mode nothing is printed on the terminal, errors are scrolled as if is +# hit at every error; missing files that TeX tries to input or request from +# keyboard input (\read on a not open input stream) cause the job to abort, +# NON_STOP In nonstop mode the diagnostic message will appear on the terminal, +# but there is no possibility of user interaction just like in batch mode, +# SCROLL In scroll mode, TeX will stop only for missing files to input or if +# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at +# each error, asking for user intervention. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -1933,14 +2092,6 @@ LATEX_HIDE_INDICES = NO LATEX_BIB_STYLE = plain -# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated -# page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_TIMESTAMP = NO - # The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) # path from which the emoji images will be read. If a relative path is entered, # it will be relative to the LATEX_OUTPUT directory. If left blank the @@ -2106,13 +2257,39 @@ DOCBOOK_OUTPUT = docbook #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an -# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures # the structure of the code including all documentation. Note that this feature # is still experimental and incomplete at the moment. # The default value is: NO. GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# Configuration options related to Sqlite3 output +#--------------------------------------------------------------------------- + +# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3 +# database with symbols found by doxygen stored in tables. +# The default value is: NO. + +GENERATE_SQLITE3 = NO + +# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be +# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put +# in front of it. +# The default directory is: sqlite3. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_OUTPUT = sqlite3 + +# The SQLITE3_OVERWRITE_DB tag is set to YES, the existing doxygen_sqlite3.db +# database file will be recreated with each doxygen run. If set to NO, doxygen +# will warn if an a database file is already found and not modify it. +# The default value is: YES. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_RECREATE_DB = YES + #--------------------------------------------------------------------------- # Configuration options related to the Perl module output #--------------------------------------------------------------------------- @@ -2187,7 +2364,8 @@ SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by the -# preprocessor. +# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of +# RECURSIVE has no effect here. # This tag requires that the tag SEARCH_INCLUDES is set to YES. INCLUDE_PATH = @@ -2255,15 +2433,15 @@ TAGFILES = GENERATE_TAGFILE = -# If the ALLEXTERNALS tag is set to YES, all external class will be listed in -# the class index. If set to NO, only the inherited external classes will be -# listed. +# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces +# will be listed in the class and namespace index. If set to NO, only the +# inherited external classes will be listed. # The default value is: NO. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will be +# in the topic index. If set to NO, only the current project's groups will be # listed. # The default value is: YES. @@ -2277,16 +2455,9 @@ EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES #--------------------------------------------------------------------------- -# Configuration options related to the dot tool +# Configuration options related to diagram generator tools #--------------------------------------------------------------------------- -# You can include diagrams made with dia in doxygen documentation. Doxygen will -# then run dia to produce the diagram and insert it in the documentation. The -# DIA_PATH tag allows you to specify the directory where the dia binary resides. -# If left empty dia is assumed to be found in the default search path. - -DIA_PATH = - # If set to YES the inheritance and collaboration graphs will hide inheritance # and usage relations if the target is undocumented or is not a class. # The default value is: YES. @@ -2295,10 +2466,10 @@ HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz (see: -# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent # Bell Labs. The other options in this section have no effect if this option is # set to NO -# The default value is: NO. +# The default value is: YES. HAVE_DOT = YES @@ -2312,37 +2483,51 @@ HAVE_DOT = YES DOT_NUM_THREADS = 0 -# When you want a differently looking font in the dot files that doxygen -# generates you can specify the font name using DOT_FONTNAME. You need to make -# sure dot is able to find the font, which can be done by putting it in a -# standard location or by setting the DOTFONTPATH environment variable or by -# setting DOT_FONTPATH to the directory containing the font. -# The default value is: Helvetica. +# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of +# subgraphs. When you want a differently looking font in the dot files that +# doxygen generates you can specify fontname, fontcolor and fontsize attributes. +# For details please see Node, +# Edge and Graph Attributes specification You need to make sure dot is able +# to find the font, which can be done by putting it in a standard location or by +# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. Default graphviz fontsize is 14. +# The default value is: fontname=Helvetica,fontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTNAME = Helvetica +DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" -# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of -# dot graphs. -# Minimum value: 4, maximum value: 24, default value: 10. +# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can +# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. Complete documentation about +# arrows shapes. +# The default value is: labelfontname=Helvetica,labelfontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTSIZE = 10 +DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10" -# By default doxygen will tell dot to use the default font as specified with -# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set -# the path where dot can find it using this tag. +# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes +# around nodes set 'shape=plain' or 'shape=plaintext' Shapes specification +# The default value is: shape=box,height=0.2,width=0.4. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" + +# You can set the path where dot can find font specified with fontname in +# DOT_COMMON_ATTR and others dot attributes. # This tag requires that the tag HAVE_DOT is set to YES. DOT_FONTPATH = -# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a -# graph for each documented class showing the direct and indirect inheritance -# relations. In case HAVE_DOT is set as well dot will be used to draw the graph, -# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set -# to TEXT the direct and indirect inheritance relations will be shown as texts / -# links. -# Possible values are: NO, YES, TEXT and GRAPH. +# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will +# generate a graph for each documented class showing the direct and indirect +# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and +# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case +# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the +# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used. +# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance +# relations will be shown as texts / links. +# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN. # The default value is: YES. CLASS_GRAPH = YES @@ -2350,14 +2535,21 @@ CLASS_GRAPH = YES # If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a # graph for each documented class showing the direct and indirect implementation # dependencies (inheritance, containment, and class references variables) of the -# class with other documented classes. +# class with other documented classes. Explicit enabling a collaboration graph, +# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the +# command \collaborationgraph. Disabling a collaboration graph can be +# accomplished by means of the command \hidecollaborationgraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. COLLABORATION_GRAPH = NO # If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for -# groups, showing the direct groups dependencies. +# groups, showing the direct groups dependencies. Explicit enabling a group +# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means +# of the command \groupgraph. Disabling a directory graph can be accomplished by +# means of the command \hidegroupgraph. See also the chapter Grouping in the +# manual. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2417,7 +2609,9 @@ TEMPLATE_RELATIONS = NO # If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to # YES then doxygen will generate a graph for each documented file showing the # direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO, +# can be accomplished by means of the command \includegraph. Disabling an +# include graph can be accomplished by means of the command \hideincludegraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2426,7 +2620,10 @@ INCLUDE_GRAPH = YES # If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are # set to YES then doxygen will generate a graph for each documented file showing # the direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set +# to NO, can be accomplished by means of the command \includedbygraph. Disabling +# an included by graph can be accomplished by means of the command +# \hideincludedbygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2466,7 +2663,10 @@ GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the # dependencies a directory has on other directories in a graphical way. The # dependency relations are determined by the #include relations between the -# files in the directories. +# files in the directories. Explicit enabling a directory graph, when +# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command +# \directorygraph. Disabling a directory graph can be accomplished by means of +# the command \hidedirectorygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2482,12 +2682,13 @@ DIR_GRAPH_MAX_DEPTH = 1 # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. For an explanation of the image formats see the section # output formats in the documentation of the dot tool (Graphviz (see: -# http://www.graphviz.org/)). +# https://www.graphviz.org/)). # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order # to make the SVG files visible in IE 9+ (other browsers do not have this # requirement). -# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, -# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and +# Possible values are: png, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, +# gif, gif:cairo, gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, +# png:cairo, png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and # png:gdiplus:gdiplus. # The default value is: png. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2519,11 +2720,12 @@ DOT_PATH = DOTFILE_DIRS = -# The MSCFILE_DIRS tag can be used to specify one or more directories that -# contain msc files that are included in the documentation (see the \mscfile -# command). +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. -MSCFILE_DIRS = +DIA_PATH = # The DIAFILE_DIRS tag can be used to specify one or more directories that # contain dia files that are included in the documentation (see the \diafile @@ -2573,18 +2775,6 @@ DOT_GRAPH_MAX_NODES = 20 MAX_DOT_GRAPH_DEPTH = 0 -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not seem -# to support this out of the box. -# -# Warning: Depending on the platform used, enabling this option may lead to -# badly anti-aliased labels on the edges of a graph (i.e. they become hard to -# read). -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_TRANSPARENT = YES - # Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) support @@ -2612,3 +2802,19 @@ GENERATE_LEGEND = NO # The default value is: YES. DOT_CLEANUP = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will +# use a built-in version of mscgen tool to produce the charts. Alternatively, +# the MSCGEN_TOOL tag can also specify the name an external tool. For instance, +# specifying prog as the value, doxygen will call the tool as prog -T +# -o . The external tool should support +# output file formats "png", "eps", "svg", and "ismap". + +MSCGEN_TOOL = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = diff --git a/doc/customdoxygen.css b/doc/customdoxygen.css new file mode 100644 index 00000000..749cfb17 --- /dev/null +++ b/doc/customdoxygen.css @@ -0,0 +1,182 @@ +/* Skia overrides for doxygen CSS. */ + +html { + --blue: rgb(0,114,178); + --green: rgb(0,158,115); + --red: rgb(213,94,0); + --orange: rgb(230,159,0); + --purple: rgb(204,121,167); + --brown: rgb(177,89,40); + --gray: rgb(79,79,79); + --light-blue: rgb(128,185,217); + --light-green: rgb(128,207,185); + --light-red: rgb(234,175,128); + --light-orange: rgb(243,207,128); + --light-purple: rgb(230,188,211); + --light-brown: rgb(216,172,148); + --light-gray: rgb(168,168,168); + + --dark-blue: rgb(0,65,101); + --dark-red: rgb(156,44,8); + + --white: rgb(254,254,254); + --dark-white: rgb(240,240,240); + --black: rgb(10,10,10); +} + +#titlearea { + /* background matches Skia logo. */ + background: rgb(248,248,248); + color: var(--blue); +} + +#main-nav .sm { + background-image: none; +} + +h2.groupheader { + border-bottom: var(--gray); + color: var(--dark-blue); +} + +div.qindex, div.navtab{ + background-color: var(--light-gray); + border: 1px solid var(--light-blue); +} + +a { + color: var(--blue); +} + +.contents a:visited { + color: var(--blue); +} + +a.qindexHL { + background-color: var(--light-gray); + color: var(--white); + border: 1px double var(--gray); +} + +.contents a.qindexHL:visited { + color: var(--white); +} + +a.code, a.code:visited, a.line, a.line:visited { + color: var(--blue); +} + +a.codeRef, a.codeRef:visited, a.lineRef, a.lineRef:visited { + color: var(--blue); +} + +pre.fragment { + border: 1px solid var(--orange); + background-color: var(--dark-white); +} + +div.fragment { + background-color: var(--dark-white); + border: 1px solid var(--orange); +} + +span.lineno { + border-right: 2px solid var(--green); + background-color: var(-light-gray); +} +span.lineno a { + background-color: var(--light-gray); +} + +span.lineno a:hover { + background-color: var(--light-gray); + color: var(--blue); +} + +div.ah, span.ah { + background-color: var(--black); + color: var(--white); + border: solid thin var(--gray); + box-shadow: 2px 2px 3px var(light-gray); + background-image: none; +} + +td.indexkey { + background-color: var(--light-gray); + border: 1px solid var(--orange); +} + +td.indexvalue { + background-color: var(--light-gray); + border: 1px solid var(--orange); +} + +tr.memlist { + background-color: var(--light-gray); +} + +span.keyword { + color: var(--green); +} + +span.keywordtype { + color: var(--brown); +} + +span.keywordflow { + color: var(--brown); +} + +span.comment { + color: var(--brown); +} + +span.charliteral { + color: var(--green); +} + +span.vhdldigit { + color: var(--purple); +} + +span.vhdlchar { + color: var(--black); +} + +blockquote { + background-color: var(--light-gray); + border-left: 2px solid var(--gray); +} + +.memtitle { + background-image: none; +} + +.memdoc, dl.reflist dd { + background-image: none; +} + +.paramname { + color: var(--dark-red); +} + +.tabsearch { + background-image: none; +} + +.navpath ul { + background-image: none; +} + +.navpath li { + background-image: none; +} + +.navpath li.navelem a:hover { + color: var(--blue) +} + +.navpath li.footer { + background-image:none; +} + diff --git a/doc/generate-website.md b/doc/generate-website.md index 121afe27..b9c5c4ce 100644 --- a/doc/generate-website.md +++ b/doc/generate-website.md @@ -4,66 +4,65 @@ You will need: -1. The `documentation-xml-website` branch of the Edelhirsch QSkinny repository - at https://github.com/edelhirsch/qskinny/tree/documentation-xml-website . -1. A recent version of doxygen; The currently used version is 1.9.1 built from - github sources. The `doxygen` binary needs to be in the $PATH. -1. A recent version of doxybook2 with some custom patches. The script - `generate-website.sh` should download and build the right version, however - the script might need some adaptation of paths. - For reference, the required version can be found at - https://github.com/edelhirsch/doxybook2/tree/jekyll . +1. A recent version of doxygen; The currently used version is 1.9.8. + The `doxygen` binary needs to be in the $PATH. +1. The `m4` command, which can be installed with `sudo apt install m4`. 1. A recent version of Jekyll (see https://jekyllrb.com/), which will generate - the static html pages. This and other required packages can be installed via + the static html pages. The currently used jekyll version is 4.2.2. + Also a recent version of bundler is required; this can be installed with: ``` - gem install jekyll:3.9.0 + gem install jekyll:4.2.2 gem install bundler:2.1.4 ``` - There might be some packages missing from the list above; in this case the - Gemfile in the qskinny-website repository might help. -1. Checkout the current website repository via +1. Checkout the repo to generate the website via + `git clone git@github.com:peter-ha/qskinny-website.git` +1. Checkout the live website repository via `git clone git@github.com:qskinny/qskinny.github.io.git` + ## Generating the website -Generating the static HTML sites is done with the `generate-website.sh` script -in the `qskinny/doc` directory. The script has some hardcoded paths and probably -needs some adaptation to run correctly. +### Generating the API documentation with doxygen +``` +cd ~/dev/qskinny/doc +export PATH=.:$PATH +doxygen +``` +This will generate the documentation into the `api` folder. -It will do the following: +### Testing and building the website locally -1. Generate HTML from doxygen. This step is needed because for some reason when - generating XML from doxygen there are no images with dependency graphs. - *Note*: This step is only executed if the `html` folder doesn't exist, - because otherwise it would take too long. -1. Generate XML from doxygen. The generated XML is used with doxybook2 in the - next step. - *Note*: This step is only executed if the `xml` folder doesn't exist, - because otherwise it would take too long. -1. Generate markdown from XML with doxybook2. This markdown will be used by - Jekyll to either server the website content locally or generate static - HTML from it, see below. +First copy the generated files from above to the website repo: -### Generating the website locally +``` +cp -r api ~/dev/qskinny-website/docs/ +``` -When the command line switch `-local` is used with the `generate-website.sh` -script, it will generate the content to a local folder `doxybook-out`. This is -meant to be able to copy selected files to the website directory at -`~/dev/qskinny-website`. -Otherwise, the script will copy the content to the website repository for -uploading (again, paths are hardcoded as of now). So when generating content -for the first time, just run the script without any switches, which should -generate the website to `~/dev/qskinny-website`. +Then test the website locally: -### Testing the website locally +``` +cd ~/dev/qskinny-website +bundle exec jekyll serve --livereload +``` -After having generated the website as described above, go to -`~/dev/qskinny-website` and run `jekyll serve --livereload`. This should start -a browser at http://127.0.0.1:4000/, which will display the website. +Then direct your browser to `http://127.0.0.1:4000/`. -### Generating the website publicly +If all looks good, generate the static HTML website: -When the command line switch `-publish` is used, the script will automatically -generate a new version of the homepage and publish it at -https://qskinny.github.io . This wil only work with the proper user rights of -course. +``` +bundle exec jekyll build +``` + +### Publishing the website + +Just copy over the generated HTML files to the public website repo and push a +new version of the homepage: + +``` +cp -r _site/* ~/dev/qskinny.github.io/ +cd ~/dev/qskinny.github.io/ +git commit -a -m "new version" # you might want to add new files +gith push +``` + +That's it, the new website is now published at https://qskinny.github.io/ . diff --git a/examples/gallery/inputs/InputPage.cpp b/examples/gallery/inputs/InputPage.cpp index e733edce..40bf4349 100644 --- a/examples/gallery/inputs/InputPage.cpp +++ b/examples/gallery/inputs/InputPage.cpp @@ -17,7 +17,7 @@ namespace public: enum Style { - Continous, + Continuous, Discrete, Centered }; @@ -33,15 +33,15 @@ namespace { case Discrete: { - setSnap( true ); + setSnapping( true ); setStepSize( 5 ); setPageSteps( 4 ); break; } - case Continous: + case Continuous: { - setSnap( false ); + setSnapping( false ); setStepSize( 1 ); setPageSteps( 10 ); @@ -98,14 +98,20 @@ InputPage::InputPage( QQuickItem* parent ) { Slider* continous; Slider* discrete; + Slider* centered; } sliders[2]; for ( int i = 0; i < 2; i++ ) { const auto orientation = static_cast< Qt::Orientation >( i + 1 ); - sliders[i].continous = new Slider( orientation, Slider::Continous ); + sliders[i].continous = new Slider( orientation, Slider::Continuous ); sliders[i].discrete = new Slider( orientation, Slider::Discrete ); + + auto slider = new Slider( orientation, Slider::Continuous ); + slider->setOrigin( slider->minimum() + + 0.5 * ( slider->maximum() - slider->minimum() ) ); + sliders[i].centered = slider; } auto spinBox = new QskSpinBox( 0.0, 100.0, 1.0 ); @@ -120,6 +126,7 @@ InputPage::InputPage( QQuickItem* parent ) vBox->addItem( sliders[0].continous ); vBox->addItem( sliders[0].discrete ); + vBox->addItem( sliders[0].centered ); vBox->addItem( inputBox ); vBox->addItem( spinBox ); @@ -127,6 +134,7 @@ InputPage::InputPage( QQuickItem* parent ) mainBox->setSpacing( 30 ); mainBox->addItem( sliders[1].continous ); mainBox->addItem( sliders[1].discrete ); + mainBox->addItem( sliders[1].centered ); mainBox->addItem( vBox ); auto inputs = findChildren< QskBoundedValueInput* >(); diff --git a/examples/iotdashboard/LightDisplay.cpp b/examples/iotdashboard/LightDisplay.cpp index 8ef43ea3..3e48c461 100644 --- a/examples/iotdashboard/LightDisplay.cpp +++ b/examples/iotdashboard/LightDisplay.cpp @@ -18,7 +18,7 @@ QSK_SUBCONTROL( LightDisplay, Tickmarks ) QSK_SUBCONTROL( LightDisplay, ValueText ) QSK_SUBCONTROL( LightDisplay, LeftLabel ) QSK_SUBCONTROL( LightDisplay, RightLabel ) -QSK_SUBCONTROL( LightDisplay, Knob ) +QSK_SUBCONTROL( LightDisplay, Handle ) QSK_STATE( LightDisplay, Pressed, ( QskAspect::FirstUserState << 1 ) ) @@ -44,6 +44,8 @@ LightDisplay::LightDisplay( QQuickItem* parent ) setAlignmentHint( ValueText, Qt::AlignRight ); setBoundaries( 0, 100 ); + setStepSize( 1.0 ); + setPageSteps( 10 ); } bool LightDisplay::isPressed() const @@ -53,16 +55,14 @@ bool LightDisplay::isPressed() const void LightDisplay::mousePressEvent( QMouseEvent* event ) { - QRectF handleRect = subControlRect( LightDisplay::Knob ); - + const auto handleRect = subControlRect( LightDisplay::Handle ); if ( handleRect.contains( event->pos() ) ) { setSkinStateFlag( Pressed ); + return; } - else - { - QskBoundedValueInput::mousePressEvent( event ); - } + + Inherited::mousePressEvent( event ); } void LightDisplay::mouseMoveEvent( QMouseEvent* event ) @@ -73,19 +73,17 @@ void LightDisplay::mouseMoveEvent( QMouseEvent* event ) const auto mousePos = qskMousePosition( event ); const auto rect = subControlRect( ColdAndWarmArc ); - bool arcContainsMousePos = arcContainsPoint( rect, mousePos ); - - if( !arcContainsMousePos ) + if( !arcContainsPoint( rect, mousePos ) ) { setSkinStateFlag( Pressed, false ); return; } const auto metrics = arcMetricsHint( ColdAndWarmArc ); - qreal angle = angleFromPoint( rect, mousePos ); const int tolerance = 20; + auto angle = angleFromPoint( rect, mousePos ); if( !angleInRange( metrics, angle ) ) { // we're slightly outside the range, but don't want to give up @@ -101,7 +99,7 @@ void LightDisplay::mouseMoveEvent( QMouseEvent* event ) } } - qreal ratio = ( metrics.spanAngle() - angle ) / metrics.spanAngle(); + const auto ratio = ( metrics.spanAngle() - angle ) / metrics.spanAngle(); setValueAsRatio( ratio ); } @@ -110,14 +108,29 @@ void LightDisplay::mouseReleaseEvent( QMouseEvent* /*event*/ ) setSkinStateFlag( Pressed, false ); } +void LightDisplay::keyPressEvent( QKeyEvent* event ) +{ + switch( event->key() ) + { + case Qt::Key_Left: + increment( -stepSize() ); + return; + + case Qt::Key_Right: + increment( stepSize() ); + return; + } + + Inherited::keyPressEvent( event ); +} + qreal LightDisplay::angleFromPoint( const QRectF& rect, const QPointF& point ) const { - QPointF circlePos( point.x() - rect.center().x(), + const QPointF circlePos( point.x() - rect.center().x(), rect.center().y() - point.y() ); - const qreal atan = qAtan2( circlePos.y(), circlePos.x() ); - const qreal angle = qRadiansToDegrees( atan ); - return angle; + const qreal angle = qAtan2( circlePos.y(), circlePos.x() ); + return qRadiansToDegrees( angle ); } bool LightDisplay::arcContainsPoint( const QRectF& rect, const QPointF& point ) const @@ -148,9 +161,7 @@ bool LightDisplay::arcContainsPoint( const QRectF& rect, const QPointF& point ) const bool pointOnArc = ( polarRadius + tolerance ) > radiusMin && ( polarRadius - tolerance ) < radiusMax; - bool ret = angleWithinRange && pointOnArc; - - return ret; + return angleWithinRange && pointOnArc; } #include "moc_LightDisplay.cpp" diff --git a/examples/iotdashboard/LightDisplay.h b/examples/iotdashboard/LightDisplay.h index 8c7728a5..928426ff 100644 --- a/examples/iotdashboard/LightDisplay.h +++ b/examples/iotdashboard/LightDisplay.h @@ -12,9 +12,12 @@ class LightDisplay : public QskBoundedValueInput { Q_OBJECT + using Inherited = QskBoundedValueInput; + public: - QSK_SUBCONTROLS( Panel, Groove, ColdAndWarmArc, Tickmarks, ValueText, - LeftLabel, RightLabel, Knob ) // ### rename knob to handle? + QSK_SUBCONTROLS( Panel, Groove, ColdAndWarmArc, Tickmarks, + ValueText, LeftLabel, RightLabel, Handle ) + QSK_STATES( Pressed ) LightDisplay( QQuickItem* parent = nullptr ); @@ -22,9 +25,11 @@ class LightDisplay : public QskBoundedValueInput bool isPressed() const; protected: - void mousePressEvent( QMouseEvent* e ) override; - void mouseMoveEvent( QMouseEvent* e ) override; - void mouseReleaseEvent( QMouseEvent* e ) override; + void mousePressEvent( QMouseEvent* ) override; + void mouseMoveEvent( QMouseEvent* ) override; + void mouseReleaseEvent( QMouseEvent* ) override; + + void keyPressEvent( QKeyEvent* ) override; private: qreal angleFromPoint( const QRectF&, const QPointF& ) const; diff --git a/examples/iotdashboard/LightDisplaySkinlet.cpp b/examples/iotdashboard/LightDisplaySkinlet.cpp index d045d367..88d527d0 100644 --- a/examples/iotdashboard/LightDisplaySkinlet.cpp +++ b/examples/iotdashboard/LightDisplaySkinlet.cpp @@ -105,12 +105,12 @@ QRectF LightDisplaySkinlet::subControlRect( const QskSkinnable* skinnable, return rect; } - else if( subControl == LightDisplay::Knob ) + else if( subControl == LightDisplay::Handle ) { const auto arcRect = subControlRect( skinnable, contentsRect, LightDisplay::ColdAndWarmArc ); const auto arcMetrics = display->arcMetricsHint( LightDisplay::ColdAndWarmArc ); - const auto knobSize = display->strutSizeHint( LightDisplay::Knob ); + const auto knobSize = display->strutSizeHint( LightDisplay::Handle ); const qreal radius = ( arcRect.width() - arcMetrics.thickness() ) / 2; const qreal angle = display->valueAsRatio() * 180; @@ -183,7 +183,7 @@ QSGNode* LightDisplaySkinlet::updateSubNode( } case KnobRole: { - return updateBoxNode( skinnable, node, LightDisplay::Knob ); + return updateBoxNode( skinnable, node, LightDisplay::Handle ); } } diff --git a/examples/iotdashboard/Skin.cpp b/examples/iotdashboard/Skin.cpp index 09b7b3c3..52396a1e 100644 --- a/examples/iotdashboard/Skin.cpp +++ b/examples/iotdashboard/Skin.cpp @@ -241,9 +241,9 @@ void Skin::initHints() ed.setFontRole( LightDisplay::ValueText, QskFontRole::Headline ); ed.setColor( LightDisplay::ValueText, 0xff929cb2 ); - ed.setStrutSize( LightDisplay::Knob, { 20, 20 } ); - ed.setBoxBorderMetrics( LightDisplay::Knob, 1 ); - ed.setBoxShape( LightDisplay::Knob, 100, Qt::RelativeSize ); + ed.setStrutSize( LightDisplay::Handle, { 20, 20 } ); + ed.setBoxBorderMetrics( LightDisplay::Handle, 1 ); + ed.setBoxShape( LightDisplay::Handle, 100, Qt::RelativeSize ); // palette dependent skin hints: ed.setGradient( MenuBar::Panel, palette.menuBar ); @@ -265,9 +265,9 @@ void Skin::initHints() ed.setShadowColor( UsageDiagramBox::Panel, palette.shadow ); ed.setGradient( LightDisplay::Panel, palette.box ); - ed.setGradient( LightDisplay::Knob, palette.box ); + ed.setGradient( LightDisplay::Handle, palette.box ); ed.setGradient( LightDisplay::ColdAndWarmArc, palette.lightDisplayColdAndWarmArc ); - ed.setBoxBorderColors( LightDisplay::Knob, palette.lightDisplayKnobBorder ); + ed.setBoxBorderColors( LightDisplay::Handle, palette.lightDisplayKnobBorder ); ed.setShadowMetrics( LightDisplay::Groove, { 0, 20 } ); ed.setShadowColor( LightDisplay::Groove, palette.shadow ); ed.setGradient( LightDisplay::Groove, palette.box ); diff --git a/playground/dials/Dial.cpp b/playground/dials/Dial.cpp index 7c842183..b4c0bd35 100644 --- a/playground/dials/Dial.cpp +++ b/playground/dials/Dial.cpp @@ -16,6 +16,7 @@ QSK_SUBCONTROL( Dial, Needle ) Dial::Dial( QQuickItem* parent ) : QskBoundedValueInput( parent ) { + setReadOnly( true ); } QVector< QString > Dial::tickLabels() const diff --git a/playground/plots/QskPlotView.cpp b/playground/plots/QskPlotView.cpp index 18fc60e0..8c346f37 100644 --- a/playground/plots/QskPlotView.cpp +++ b/playground/plots/QskPlotView.cpp @@ -11,7 +11,7 @@ #include #include -#include +#include #include #include @@ -315,7 +315,7 @@ void QskPlotView::updateNode( QSGNode* node ) if ( m_data->needsClipping() ) { if ( itemsNode == nullptr || itemsNode->type() != QSGNode::ClipNodeType ) - itemsNode = new QskBoxClipNode(); + itemsNode = new QskClipNode(); } else { diff --git a/playground/shadows/Slider.cpp b/playground/shadows/Slider.cpp index b6f4b8fc..96b47907 100644 --- a/playground/shadows/Slider.cpp +++ b/playground/shadows/Slider.cpp @@ -20,7 +20,7 @@ Slider::Slider( const QString& text, qreal min, qreal max, m_slider = new QskSlider( this ); m_slider->setBoundaries( min, max ); m_slider->setStepSize( step ); - m_slider->setSnap( true ); + m_slider->setSnapping( true ); m_slider->setValue( value ); m_valueLabel = new QskTextLabel( this ); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0c8fa825..cd594404 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -105,13 +105,13 @@ list(APPEND HEADERS nodes/QskArcRenderNode.h nodes/QskBasicLinesNode.h nodes/QskBoxNode.h - nodes/QskBoxClipNode.h nodes/QskBoxRectangleNode.h nodes/QskBoxRenderer.h nodes/QskBoxMetrics.h nodes/QskBoxBasicStroker.h nodes/QskBoxGradientStroker.h nodes/QskBoxShadowNode.h + nodes/QskClipNode.h nodes/QskColorRamp.h nodes/QskFillNode.h nodes/QskGraduationNode.h @@ -145,13 +145,13 @@ list(APPEND SOURCES nodes/QskArcRenderNode.cpp nodes/QskBasicLinesNode.cpp nodes/QskBoxNode.cpp - nodes/QskBoxClipNode.cpp nodes/QskBoxRectangleNode.cpp nodes/QskBoxRenderer.cpp nodes/QskBoxMetrics.cpp nodes/QskBoxBasicStroker.cpp nodes/QskBoxGradientStroker.cpp nodes/QskBoxShadowNode.cpp + nodes/QskClipNode.cpp nodes/QskColorRamp.cpp nodes/QskFillNode.cpp nodes/QskGraduationNode.cpp diff --git a/src/common/QskNamespace.h b/src/common/QskNamespace.h index 0129e81f..92cc96f6 100644 --- a/src/common/QskNamespace.h +++ b/src/common/QskNamespace.h @@ -13,6 +13,14 @@ namespace Qsk { Q_NAMESPACE_EXPORT( QSK_EXPORT ) + enum Policy + { + Maybe, + Always, + Never + }; + Q_ENUM_NS( Policy ) + enum Direction { LeftToRight, diff --git a/src/controls/QskBoundedControl.h b/src/controls/QskBoundedControl.h index ec89efc6..d9558c23 100644 --- a/src/controls/QskBoundedControl.h +++ b/src/controls/QskBoundedControl.h @@ -53,9 +53,9 @@ class QSK_EXPORT QskBoundedControl : public QskControl void componentComplete() override; + private: void adjustBoundaries( bool increasing ); - private: qreal m_minimum; qreal m_maximum; }; diff --git a/src/controls/QskBoundedInput.cpp b/src/controls/QskBoundedInput.cpp index b70d0f9e..396337af 100644 --- a/src/controls/QskBoundedInput.cpp +++ b/src/controls/QskBoundedInput.cpp @@ -18,7 +18,7 @@ class QskBoundedInput::PrivateData qreal stepSize = 0.1; uint pageSteps = 1; - bool snap = false; + bool snapping = false; }; QskBoundedInput::QskBoundedInput( QQuickItem* parent ) @@ -53,7 +53,7 @@ void QskBoundedInput::setStepSize( qreal stepSize ) if ( isComponentComplete() ) { - if ( m_data->snap && stepSize ) + if ( m_data->snapping && stepSize ) alignInput(); } } @@ -102,21 +102,21 @@ void QskBoundedInput::pageDown() increment( -pageSize() ); } -void QskBoundedInput::setSnap( bool snap ) +void QskBoundedInput::setSnapping( bool on ) { - if ( m_data->snap == snap ) + if ( m_data->snapping == on ) return; - m_data->snap = snap; - Q_EMIT snapChanged( snap ); + m_data->snapping = on; + Q_EMIT snappingChanged( on ); - if ( isComponentComplete() && snap ) + if ( isComponentComplete() && m_data->snapping ) alignInput(); } -bool QskBoundedInput::snap() const +bool QskBoundedInput::isSnapping() const { - return m_data->snap; + return m_data->snapping; } void QskBoundedInput::componentComplete() @@ -134,38 +134,6 @@ void QskBoundedInput::alignInput() { } -qreal QskBoundedInput::alignedValue( qreal value ) const -{ - value = boundedValue( value ); - - if ( value > minimum() && value < maximum() ) - { - if ( m_data->snap && m_data->stepSize ) - { - value = qRound( value / m_data->stepSize ) * m_data->stepSize; - value = boundedValue( value ); - } - } - - return value; -} - -QskIntervalF QskBoundedInput::alignedInterval( const QskIntervalF& interval ) const -{ - if ( m_data->snap ) - { - if ( const auto step = m_data->stepSize ) - { - const qreal lower = std::floor( interval.lowerBound() / step ) * step; - const qreal upper = std::ceil( interval.upperBound() / step ) * step; - - return QskIntervalF( lower, upper ); - } - } - - return interval; -} - void QskBoundedInput::setReadOnly( bool readOnly ) { if ( readOnly == isReadOnly() ) @@ -204,15 +172,6 @@ qreal QskBoundedInput::incrementForKey( const QKeyEvent* event ) const case Qt::Key_PageDown: return -pageSize(); - - default: - { - if ( qskIsStandardKeyInput( event, QKeySequence::MoveToNextChar ) ) - return m_data->stepSize; - - if ( qskIsStandardKeyInput( event, QKeySequence::MoveToPreviousChar ) ) - return -m_data->stepSize; - } } return 0.0; diff --git a/src/controls/QskBoundedInput.h b/src/controls/QskBoundedInput.h index 92a688af..51a6fd84 100644 --- a/src/controls/QskBoundedInput.h +++ b/src/controls/QskBoundedInput.h @@ -17,7 +17,7 @@ class QSK_EXPORT QskBoundedInput : public QskBoundedControl Q_PROPERTY( qreal stepSize READ stepSize WRITE setStepSize NOTIFY stepSizeChanged ) Q_PROPERTY( uint pageSteps READ pageSteps WRITE setPageSteps NOTIFY pageStepsChanged ) - Q_PROPERTY( bool snap READ snap WRITE setSnap NOTIFY snapChanged ) + Q_PROPERTY( bool snapping READ isSnapping WRITE setSnapping NOTIFY snappingChanged ) Q_PROPERTY( bool readOnly READ isReadOnly WRITE setReadOnly NOTIFY readOnlyChanged ) using Inherited = QskBoundedControl; @@ -30,10 +30,10 @@ class QSK_EXPORT QskBoundedInput : public QskBoundedControl qreal stepSize() const; qreal pageSize() const; // pageSteps() * stepSize() - uint pageSteps() const; + uint pageSteps() const; - void setSnap( bool ); - bool snap() const; + void setSnapping( bool ); + bool isSnapping() const; void setReadOnly( bool ); bool isReadOnly() const; @@ -52,7 +52,7 @@ class QSK_EXPORT QskBoundedInput : public QskBoundedControl Q_SIGNALS: void stepSizeChanged( qreal ); void pageStepsChanged( qreal ); - void snapChanged( bool ); + void snappingChanged( bool ); void readOnlyChanged( bool ); @@ -66,9 +66,6 @@ class QSK_EXPORT QskBoundedInput : public QskBoundedControl void componentComplete() override; virtual void alignInput(); - qreal alignedValue( qreal ) const; - QskIntervalF alignedInterval( const QskIntervalF& ) const; - qreal incrementForKey( const QKeyEvent* ) const; private: diff --git a/src/controls/QskBoundedRangeInput.cpp b/src/controls/QskBoundedRangeInput.cpp index 5c2ebe54..e7442259 100644 --- a/src/controls/QskBoundedRangeInput.cpp +++ b/src/controls/QskBoundedRangeInput.cpp @@ -88,7 +88,9 @@ void QskBoundedRangeInput::setRange( const QskIntervalF& range ) if ( isComponentComplete() ) { - newRange = alignedInterval( newRange ); + if ( isSnapping() && stepSize() ) + newRange = newRange.fuzzyAligned( stepSize() ); + newRange = fixupRange( newRange ); } @@ -128,7 +130,11 @@ QskIntervalF QskBoundedRangeInput::range() const void QskBoundedRangeInput::alignInput() { - setRangeInternal( alignedInterval( m_range ) ); + auto newRange = m_range; + if ( isSnapping() && stepSize() ) + newRange = newRange.fuzzyAligned( stepSize() ); + + setRangeInternal( newRange ); } QskIntervalF QskBoundedRangeInput::fixupRange( const QskIntervalF& range ) const diff --git a/src/controls/QskBoundedValueInput.cpp b/src/controls/QskBoundedValueInput.cpp index 098f7ff4..4deace08 100644 --- a/src/controls/QskBoundedValueInput.cpp +++ b/src/controls/QskBoundedValueInput.cpp @@ -9,6 +9,24 @@ #include #include +static qreal qskAlignedValue( const QskBoundedValueInput* input, qreal value ) +{ + value = input->boundedValue( value ); + + if ( value > input->minimum() && value < input->maximum() ) + { + if ( input->isSnapping() && input->stepSize() ) + { + const auto step = input->stepSize(); + + value = qRound( value / step ) * step; + value = input->boundedValue( value ); + } + } + + return value; +} + class QskBoundedValueInput::PrivateData { public: @@ -43,9 +61,25 @@ int QskBoundedValueInput::decimals() const return m_data->decimals; } +void QskBoundedValueInput::keyPressEvent( QKeyEvent* event ) +{ + switch( event->key() ) + { + case Qt::Key_Home: + setValue( minimum() ); + break; + + case Qt::Key_End: + setValue( maximum() ); + break; + } + + Inherited::keyPressEvent( event ); +} + void QskBoundedValueInput::alignInput() { - auto value = alignedValue( m_data->value ); + auto value = qskAlignedValue( this, m_data->value ); value = fixupValue( value ); setValueInternal( value ); @@ -71,7 +105,7 @@ void QskBoundedValueInput::setValue( qreal value ) { if ( isComponentComplete() ) { - value = alignedValue( value ); + value = qskAlignedValue( this, value ); value = fixupValue( value ); } diff --git a/src/controls/QskBoundedValueInput.h b/src/controls/QskBoundedValueInput.h index bd63b7e5..6f30bb74 100644 --- a/src/controls/QskBoundedValueInput.h +++ b/src/controls/QskBoundedValueInput.h @@ -51,6 +51,8 @@ class QSK_EXPORT QskBoundedValueInput : public QskBoundedInput void decimalsChanged( int ); protected: + void keyPressEvent( QKeyEvent* ) override; + virtual qreal fixupValue( qreal ) const; void alignInput() override; diff --git a/src/controls/QskPageIndicator.cpp b/src/controls/QskPageIndicator.cpp index fcf94251..e02760c0 100644 --- a/src/controls/QskPageIndicator.cpp +++ b/src/controls/QskPageIndicator.cpp @@ -59,7 +59,7 @@ class QskPageIndicator::PrivateData int pressedIndex = -1; int count; - Qt::Orientation orientation : 2; + Qt::Orientation orientation; }; QskPageIndicator::QskPageIndicator( int count, QQuickItem* parent ) diff --git a/src/controls/QskProgressBarSkinlet.cpp b/src/controls/QskProgressBarSkinlet.cpp index 225fb44c..f3b99a37 100644 --- a/src/controls/QskProgressBarSkinlet.cpp +++ b/src/controls/QskProgressBarSkinlet.cpp @@ -13,44 +13,76 @@ using Q = QskProgressBar; -namespace +static QskIntervalF qskFillInterval( const QskProgressIndicator* indicator ) { - QskIntervalF qskFillInterval( const QskProgressIndicator* indicator ) + qreal pos1, pos2; + + if ( indicator->isIndeterminate() ) { - qreal pos1, pos2; + const auto pos = indicator->positionHint( Q::Fill ); - if ( indicator->isIndeterminate() ) - { - const auto pos = indicator->positionHint( QskProgressIndicator::Fill ); + static const QEasingCurve curve( QEasingCurve::InOutCubic ); - static const QEasingCurve curve( QEasingCurve::InOutCubic ); + const qreal off = 0.15; - const qreal off = 0.15; - - pos1 = curve.valueForProgress( qMax( pos - off, 0.0 ) ); - pos2 = curve.valueForProgress( qMin( pos + off, 1.0 ) ); - } - else - { - 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() ) - { - pos1 = 1.0 - pos1; - pos2 = 1.0 - pos2; - } - } - - if ( pos1 > pos2 ) - std::swap( pos1, pos2 ); - - return QskIntervalF( pos1, pos2 ); + pos1 = curve.valueForProgress( qMax( pos - off, 0.0 ) ); + pos2 = curve.valueForProgress( qMin( pos + off, 1.0 ) ); } + else + { + 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() ) + { + pos1 = 1.0 - pos1; + pos2 = 1.0 - pos2; + } + } + + if ( pos1 > pos2 ) + std::swap( pos1, pos2 ); + + return QskIntervalF( pos1, pos2 ); +} + +static QskGradient qskFillGradient( const QskProgressBar* progressBar ) +{ + auto gradient = progressBar->gradientHint( Q::Fill ); + + if ( gradient.isVisible() && !gradient.isMonochrome() + && ( gradient.type() == QskGradient::Stops ) ) + { + /* + When having stops only we use a linear gradient, + where the colors are increasing in direction of the + progress value. We interprete the gradient as a + definition for the 100% situation and have to adjust + the stops for smaller bars. + + For this situation it would be more convenient to + adjust the start/stop positions, but the box renderer is + not supporting this yet. TODO ... + */ + + const auto intv = qskFillInterval( progressBar ); + + const auto stops = qskExtractedGradientStops( + gradient.stops(), intv.lowerBound(), intv.upperBound() ); + + gradient.setStops( stops ); + + gradient.setLinearDirection( progressBar->orientation() ); + + if ( progressBar->orientation() == Qt::Vertical || progressBar->layoutMirroring() ) + gradient.reverse(); + } + + return gradient; } QskProgressBarSkinlet::QskProgressBarSkinlet( QskSkin* skin ) @@ -69,28 +101,10 @@ QRectF QskProgressBarSkinlet::subControlRect( const auto bar = static_cast< const Q* >( skinnable ); if( subControl == Q::Groove ) - { - const auto grooveSize = bar->metric( Q::Groove | QskAspect::Size ); - - auto rect = contentsRect; - if ( bar->orientation() == Qt::Horizontal ) - { - rect.setY( rect.y() + 0.5 * ( rect.height() - grooveSize ) ); - rect.setHeight( grooveSize ); - } - else - { - rect.setX( rect.x() + 0.5 * ( rect.width() - grooveSize ) ); - rect.setWidth( grooveSize ); - } - - return rect; - } + return grooveRect( bar, contentsRect ); if( subControl == Q::Fill ) - { - return barRect( bar ); - } + return fillRect( bar ); return Inherited::subControlRect( skinnable, contentsRect, subControl ); } @@ -104,76 +118,61 @@ QSGNode* QskProgressBarSkinlet::updateGrooveNode( QSGNode* QskProgressBarSkinlet::updateFillNode( const QskProgressIndicator* indicator, QSGNode* node ) const { - const auto bar = static_cast< const Q* >( indicator ); - - const auto subControl = Q::Fill; - - const auto rect = indicator->subControlRect( subControl ); + const auto rect = indicator->subControlRect( Q::Fill ); if ( rect.isEmpty() ) return nullptr; - auto gradient = indicator->gradientHint( subControl ); - if ( !gradient.isVisible() ) - return nullptr; - - if ( ( gradient.type() == QskGradient::Stops ) && !gradient.isMonochrome() ) - { - /* - When having stops only we use a linear gradient, - where the colors are increasing in direction of the - progress value. We interprete the gradient as a - definition for the 100% situation and have to adjust - the stops for smaller bars. - - For this situation it would be more convenient to - adjust the start/stop positions, but the box renderer is - not supporting this yet. TODO ... - */ - - const auto intv = qskFillInterval( bar ); - - const auto stops = qskExtractedGradientStops( gradient.stops(), - intv.lowerBound(), intv.upperBound() ); - - gradient.setStops( stops ); - - gradient.setLinearDirection( static_cast< Qt::Orientation >( bar->orientation() ) ); - - if ( bar->orientation() == Qt::Vertical || bar->layoutMirroring() ) - gradient.reverse(); - } - - return updateBoxNode( indicator, node, rect, gradient, subControl ); + const auto progressBar = static_cast< const Q* >( indicator ); + return updateBoxNode( indicator, node, rect, + qskFillGradient( progressBar ), Q::Fill ); } -QRectF QskProgressBarSkinlet::barRect( const Q* bar ) const +QRectF QskProgressBarSkinlet::grooveRect( + const QskProgressBar* progressBar, const QRectF& contentsRect ) const { - const auto subControl = Q::Groove; + const auto size = progressBar->metric( Q::Groove | QskAspect::Size ); - const auto barSize = bar->metric( Q::Fill | QskAspect::Size ); - auto rect = bar->subControlRect( subControl ); - - if ( bar->orientation() == Qt::Horizontal ) + auto rect = contentsRect; + if ( progressBar->orientation() == Qt::Horizontal ) { - rect.setY( rect.y() + 0.5 * ( rect.height() - barSize ) ); - rect.setHeight( barSize ); + rect.setY( rect.y() + 0.5 * ( rect.height() - size ) ); + rect.setHeight( size ); } else { - rect.setX( rect.x() + 0.5 * ( rect.width() - barSize ) ); - rect.setWidth( barSize ); + rect.setX( rect.x() + 0.5 * ( rect.width() - size ) ); + rect.setWidth( size ); } - const auto borderMetrics = bar->boxBorderMetricsHint( subControl ); + return rect; +} - auto m = bar->paddingHint( subControl ); +QRectF QskProgressBarSkinlet::fillRect( const QskProgressBar* progressBar ) const +{ + const auto size = progressBar->metric( Q::Fill | QskAspect::Size ); + auto rect = progressBar->subControlRect( Q::Groove ); + + if ( progressBar->orientation() == Qt::Horizontal ) + { + rect.setY( rect.y() + 0.5 * ( rect.height() - size ) ); + rect.setHeight( size ); + } + else + { + rect.setX( rect.x() + 0.5 * ( rect.width() - size ) ); + rect.setWidth( size ); + } + + const auto borderMetrics = progressBar->boxBorderMetricsHint( Q::Groove ); + + auto m = progressBar->paddingHint( Q::Groove ); m += 0.5 * borderMetrics.toAbsolute( rect.size() ).widths(); rect = rect.marginsRemoved( m ); - const auto intv = qskFillInterval( bar ); + const auto intv = qskFillInterval( progressBar ); - if( bar->orientation() == Qt::Horizontal ) + if( progressBar->orientation() == Qt::Horizontal ) { const auto w = rect.width(); @@ -197,11 +196,12 @@ QSizeF QskProgressBarSkinlet::sizeHint( const QskSkinnable* skinnable, if ( which != Qt::PreferredSize ) return QSizeF(); - const auto bar = static_cast< const Q* >( skinnable ); + auto extent = skinnable->metric( Q::Groove | QskAspect::Size ); + extent = qMax( extent, skinnable->metric( Q::Fill | QskAspect::Size ) ); - const auto extent = bar->extent(); + const auto progressBar = static_cast< const QskProgressBar* >( skinnable ); - if ( bar->orientation() == Qt::Horizontal ) + if ( progressBar->orientation() == Qt::Horizontal ) return QSizeF( -1, extent ); else return QSizeF( extent, -1 ); diff --git a/src/controls/QskProgressBarSkinlet.h b/src/controls/QskProgressBarSkinlet.h index 581630bc..2ce543ae 100644 --- a/src/controls/QskProgressBarSkinlet.h +++ b/src/controls/QskProgressBarSkinlet.h @@ -31,7 +31,8 @@ class QSK_EXPORT QskProgressBarSkinlet : public QskProgressIndicatorSkinlet QSGNode* updateFillNode( const QskProgressIndicator*, QSGNode* ) const override; private: - QRectF barRect( const QskProgressBar* ) const; + QRectF fillRect( const QskProgressBar* ) const; + QRectF grooveRect( const QskProgressBar*, const QRectF& ) const; }; #endif diff --git a/src/controls/QskProgressIndicator.cpp b/src/controls/QskProgressIndicator.cpp index 7dd8d11e..8c7a507e 100644 --- a/src/controls/QskProgressIndicator.cpp +++ b/src/controls/QskProgressIndicator.cpp @@ -148,9 +148,7 @@ void QskProgressIndicator::resetExtent() qreal QskProgressIndicator::extent() const { - auto grooveSize = metric( Groove | QskAspect::Size ); - auto fillSize = metric( Fill | QskAspect::Size ); - return qMax( grooveSize, fillSize ); + return metric( Groove | QskAspect::Size ); } void QskProgressIndicator::setOrigin( qreal origin ) @@ -189,6 +187,11 @@ qreal QskProgressIndicator::origin() const return minimum(); } +bool QskProgressIndicator::hasOrigin() const +{ + return m_data->hasOrigin; +} + void QskProgressIndicator::setValue( qreal value ) { if ( isComponentComplete() ) diff --git a/src/controls/QskProgressIndicator.h b/src/controls/QskProgressIndicator.h index 4df628f7..9660796d 100644 --- a/src/controls/QskProgressIndicator.h +++ b/src/controls/QskProgressIndicator.h @@ -51,6 +51,7 @@ class QSK_EXPORT QskProgressIndicator : public QskBoundedControl void resetOrigin(); qreal origin() const; + bool hasOrigin() const; qreal value() const; qreal valueAsRatio() const; // [0.0, 1.0] @@ -73,7 +74,6 @@ class QSK_EXPORT QskProgressIndicator : public QskBoundedControl private: void setValueInternal( qreal value ); - void adjustBoundaries( bool increasing ); void adjustValue(); class PrivateData; diff --git a/src/controls/QskProgressRingSkinlet.cpp b/src/controls/QskProgressRingSkinlet.cpp index 167cb861..bb8f8d25 100644 --- a/src/controls/QskProgressRingSkinlet.cpp +++ b/src/controls/QskProgressRingSkinlet.cpp @@ -10,6 +10,36 @@ using Q = QskProgressRing; +static QskIntervalF qskFillInterval( const QskProgressIndicator* indicator ) +{ + qreal pos1, pos2; + + if ( indicator->isIndeterminate() ) + { + pos1 = indicator->positionHint( QskProgressIndicator::Fill ); + pos2 = 2 * pos1; + } + else + { + pos1 = indicator->valueAsRatio( indicator->origin() ); + pos2 = indicator->valueAsRatio( indicator->value() ); + } + + if ( pos1 > pos2 ) + std::swap( pos1, pos2 ); + + return QskIntervalF( pos1, pos2 ); +} + +static inline QPair< qreal, qreal > qskFillAngles( + const QskArcMetrics& metrics, const QskIntervalF& intv ) +{ + const auto startAngle = metrics.startAngle() + intv.lowerBound() * metrics.spanAngle(); + const auto endAngle = metrics.startAngle() + intv.upperBound() * metrics.spanAngle(); + + return { startAngle, endAngle }; +} + QskProgressRingSkinlet::QskProgressRingSkinlet( QskSkin* skin ) : Inherited( skin ) { @@ -32,6 +62,34 @@ QRectF QskProgressRingSkinlet::subControlRect( QSGNode* QskProgressRingSkinlet::updateGrooveNode( const QskProgressIndicator* indicator, QSGNode* node ) const { + const auto ring = static_cast< const Q* >( indicator ); + + const auto spacing = ring->spacingHint( Q::Fill ); // degrees + + if( spacing > 0.0 ) + { + const auto fillMetrics = ring->arcMetricsHint( Q::Fill ); + if ( fillMetrics.isClosed() ) + { + const auto fillAngles = qskFillAngles( fillMetrics, qskFillInterval( ring ) ); + + qreal startAngle, endAngle; + if ( fillAngles.second > fillAngles.first ) + { + startAngle = fillAngles.second + spacing; + endAngle = fillAngles.first + 360.0 - spacing; + } + else + { + startAngle = fillAngles.second - spacing; + endAngle = fillAngles.first - 360.0 + spacing; + } + + return updateArcNode( ring, node, + startAngle, endAngle - startAngle, Q::Groove ); + } + } + return updateArcNode( indicator, node, Q::Groove ); } @@ -54,7 +112,7 @@ QSGNode* QskProgressRingSkinlet::updateFillNode( if ( !gradient.isVisible() ) return nullptr; - const auto intv = fillInterval( ring ); + const auto intv = qskFillInterval( ring ); if ( ( gradient.type() == QskGradient::Stops ) && !gradient.isMonochrome() ) { @@ -67,10 +125,10 @@ QSGNode* QskProgressRingSkinlet::updateFillNode( gradient.reverse(); } - const auto startAngle = metrics.startAngle() + intv.lowerBound() * metrics.spanAngle(); - const auto spanAngle = intv.upperBound() * metrics.spanAngle(); + const auto angles = qskFillAngles( metrics, intv ); - return updateArcNode( ring, node, rect, gradient, startAngle, spanAngle, subControl ); + return updateArcNode( ring, node, rect, gradient, + angles.first, angles.second - angles.first, subControl ); } QSizeF QskProgressRingSkinlet::sizeHint( const QskSkinnable* skinnable, @@ -88,32 +146,11 @@ QSizeF QskProgressRingSkinlet::sizeHint( const QskSkinnable* skinnable, if ( constraint.width() >= 0.0 ) hint.setHeight( constraint.width() / aspectRatio ); - else + 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 c5dc815b..d7171deb 100644 --- a/src/controls/QskProgressRingSkinlet.h +++ b/src/controls/QskProgressRingSkinlet.h @@ -29,8 +29,6 @@ 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/QskSkinlet.cpp b/src/controls/QskSkinlet.cpp index 33a9661f..b7add39f 100644 --- a/src/controls/QskSkinlet.cpp +++ b/src/controls/QskSkinlet.cpp @@ -11,10 +11,10 @@ #include "QskBoxBorderColors.h" #include "QskBoxBorderMetrics.h" #include "QskBoxNode.h" -#include "QskBoxClipNode.h" #include "QskBoxRectangleNode.h" #include "QskBoxShapeMetrics.h" #include "QskBoxHints.h" +#include "QskClipNode.h" #include "QskColorFilter.h" #include "QskControl.h" #include "QskFunctions.h" @@ -641,15 +641,14 @@ QSGNode* QskSkinlet::updateBoxClipNode( const QskSkinnable* skinnable, QSGNode* QskSkinlet::updateBoxClipNode( const QskSkinnable* skinnable, QSGNode* node, const QRectF& rect, QskAspect::Subcontrol subControl ) { - auto clipNode = QskSGNode::ensureNode< QskBoxClipNode >( node ); + auto clipNode = QskSGNode::ensureNode< QskClipNode >( node ); const auto margins = skinnable->marginHint( subControl ); const auto clipRect = rect.marginsRemoved( margins ); if ( clipRect.isEmpty() ) { - clipNode->setIsRectangular( true ); - clipNode->setClipRect( clipRect ); + clipNode->setRect( clipRect ); } else { diff --git a/src/controls/QskSkinnable.cpp b/src/controls/QskSkinnable.cpp index f1c79795..844af312 100644 --- a/src/controls/QskSkinnable.cpp +++ b/src/controls/QskSkinnable.cpp @@ -437,6 +437,14 @@ qreal QskSkinnable::metric( const QskAspect aspect, QskSkinHintStatus* status ) return qskMetric< qreal >( this, aspect, status ); } +qreal QskSkinnable::metric( QskAspect aspect, qreal defaultValue ) const +{ + QskSkinHintStatus status; + + const auto value = qskMetric< qreal >( this, aspect, &status ); + return status.isValid() ? value : defaultValue; +} + bool QskSkinnable::setPositionHint( QskAspect aspect, qreal position ) { return qskSetMetric( this, aspect | QskAspect::Position, position ); diff --git a/src/controls/QskSkinnable.h b/src/controls/QskSkinnable.h index d7ec2d6b..08488ee0 100644 --- a/src/controls/QskSkinnable.h +++ b/src/controls/QskSkinnable.h @@ -175,6 +175,7 @@ class QSK_EXPORT QskSkinnable bool moveMetric( QskAspect, qreal ); bool resetMetric( QskAspect ); qreal metric( QskAspect, QskSkinHintStatus* = nullptr ) const; + qreal metric( QskAspect, qreal defaultValue ) const; bool setFlagHint( QskAspect, int flag ); template< typename T > T flagHint( QskAspect, T = T() ) const; diff --git a/src/controls/QskSlider.cpp b/src/controls/QskSlider.cpp index 00ad9017..1e38547f 100644 --- a/src/controls/QskSlider.cpp +++ b/src/controls/QskSlider.cpp @@ -6,35 +6,96 @@ #include "QskSlider.h" #include "QskAnimationHint.h" #include "QskAspect.h" -#include "QskIntervalF.h" #include "QskEvent.h" +#include "QskFunctions.h" QSK_SUBCONTROL( QskSlider, Panel ) QSK_SUBCONTROL( QskSlider, Groove ) QSK_SUBCONTROL( QskSlider, Fill ) QSK_SUBCONTROL( QskSlider, Scale ) +QSK_SUBCONTROL( QskSlider, Tick ) QSK_SUBCONTROL( QskSlider, Handle ) -QSK_SUBCONTROL( QskSlider, GrooveStopIndicators ) -QSK_SUBCONTROL( QskSlider, FillStopIndicators ) QSK_SUBCONTROL( QskSlider, LabelContainer ) QSK_SUBCONTROL( QskSlider, LabelText ) QSK_SYSTEM_STATE( QskSlider, Pressed, QskAspect::FirstSystemState << 2 ) +static QRectF qskHandleSelectionRect( const QskSlider* slider ) +{ + return slider->subControlRect( QskSlider::Handle ); +} + +static QRectF qskSliderSelectionRect( const QskSlider* slider ) +{ + const qreal margin = 10.0; + + const auto scaleRect = slider->subControlRect( QskSlider::Scale ); + const auto handleRect = qskHandleSelectionRect( slider ); + + auto r = slider->subControlRect( QskSlider::Panel ); + if ( slider->orientation() == Qt::Horizontal ) + { + r.setTop( qMin( r.top(), handleRect.top() ) ); + r.setBottom( qMax( r.bottom(), handleRect.bottom() ) ); + r.setLeft( scaleRect.left() - margin ); + r.setRight( scaleRect.right() + margin ); + } + else + { + r.setLeft( qMin( r.left(), handleRect.left() ) ); + r.setRight( qMax( r.right(), handleRect.right() ) ); + r.setTop( scaleRect.top() - margin ); + r.setBottom( scaleRect.bottom() + margin ); + } + + return r; +} + +static inline int qskKeyOffset( Qt::Orientation orientation, int key ) +{ + if ( orientation == Qt::Horizontal ) + { + if ( key == Qt::Key_Left ) + return -1; + + if ( key == Qt::Key_Right ) + return 1; + } + else + { + if ( key == Qt::Key_Down ) + return -1; + + if ( key == Qt::Key_Up ) + return 1; + } + + return 0; +} + class QskSlider::PrivateData { public: PrivateData( Qt::Orientation orientation ) : pressedValue( 0 ) + , hasOrigin( false ) + , inverted( false ) , tracking( true ) + , dragging( false ) , orientation( orientation ) { } QPointF pressedPos; qreal pressedValue; + + qreal origin = 0.0; + + bool hasOrigin : 1; + bool inverted : 1; bool tracking : 1; - Qt::Orientation orientation : 2; + bool dragging : 1; + uint orientation : 2; }; QskSlider::QskSlider( QQuickItem* parent ) @@ -62,11 +123,6 @@ QskSlider::~QskSlider() { } -bool QskSlider::isPressed() const -{ - return hasSkinState( Pressed ); -} - void QskSlider::setOrientation( Qt::Orientation orientation ) { if ( orientation != m_data->orientation ) @@ -79,13 +135,68 @@ void QskSlider::setOrientation( Qt::Orientation orientation ) resetImplicitSize(); update(); - Q_EMIT orientationChanged( m_data->orientation ); + Q_EMIT orientationChanged( this->orientation() ); } } Qt::Orientation QskSlider::orientation() const { - return m_data->orientation; + return static_cast< Qt::Orientation >( m_data->orientation ); +} + +void QskSlider::setInverted( bool on ) +{ + if ( on != m_data->inverted ) + { + m_data->inverted = on; + update(); + + Q_EMIT invertedChanged( on ); + } +} + +bool QskSlider::isInverted() const +{ + return m_data->inverted; +} + +void QskSlider::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 QskSlider::resetOrigin() +{ + if ( m_data->hasOrigin ) + { + m_data->hasOrigin = false; + + update(); + Q_EMIT originChanged( origin() ); + } +} + +qreal QskSlider::origin() const +{ + if ( m_data->hasOrigin ) + return boundedValue( m_data->origin ); + + return minimum(); +} + +bool QskSlider::hasOrigin() const +{ + return m_data->hasOrigin; } QskAspect::Variation QskSlider::effectiveVariation() const @@ -107,137 +218,139 @@ bool QskSlider::isTracking() const return m_data->tracking; } +void QskSlider::componentComplete() +{ + Inherited::componentComplete(); + if ( m_data->hasOrigin ) + m_data->origin = boundedValue( m_data->origin ); +} + void QskSlider::aboutToShow() { setPositionHint( Handle, valueAsRatio() ); Inherited::aboutToShow(); } -QSizeF QskSlider::handleSize() const -{ - return handleRect().size(); -} - -QRectF QskSlider::handleRect() const -{ - auto rect = subControlRect( Handle ); - -#if 1 // minimum handle strut size hardcoded here for now - const QSizeF strutSize( 60, 60 ); - const auto w = qMax( ( strutSize.width() - rect.width() ) / 2, 0.0 ); - const auto h = qMax( ( strutSize.height() - rect.height() ) / 2, 0.0 ); -#endif - - return rect.marginsAdded( { w, h, w, h } ); -} - void QskSlider::mousePressEvent( QMouseEvent* event ) { - if ( handleRect().contains( event->pos() ) ) + const auto pos = qskMousePosition( event ); + if ( !qskHandleSelectionRect( this ).contains( pos ) ) { - // Case 1: press started in the handle, start sliding + const auto r = qskSliderSelectionRect( this ); + if ( !r.contains( pos ) ) + { + Inherited::mousePressEvent( event ); + return; + } - m_data->pressedPos = event->pos(); - m_data->pressedValue = value(); - setSkinStateFlag( Pressed ); - Q_EMIT pressedChanged( true ); - } - else if ( pageSteps() == 0 ) - { - // Case 2: pageSize is not used, we're done here - } - else - { - // Case 3: pressed outside of the handle, page the scroller in - // the direction of the press requires an auto-repeat behavior - // until the slider reaches the destination, or it simply jumps - // there (configurable) + qreal ratio; + + const auto scaleRect = subControlRect( Scale ); + if ( m_data->orientation == Qt::Horizontal ) + ratio = ( pos.x() - scaleRect.left() ) / scaleRect.width(); + else + ratio = ( scaleRect.bottom() - pos.y() ) / scaleRect.height(); + + if ( m_data->inverted ) + ratio = 1.0 - ratio; + + setValue( valueFromRatio( ratio ) ); } + + setSkinStateFlag( Pressed ); + + m_data->pressedPos = pos; + m_data->pressedValue = value(); } void QskSlider::mouseMoveEvent( QMouseEvent* event ) { - if ( !isPressed() ) + if ( !hasSkinState( Pressed ) ) return; const auto mousePos = qskMousePosition( event ); const auto r = subControlRect( Scale ); + auto length = boundaryLength(); + if ( m_data->inverted ) + length = -length; + qreal newValue; if ( m_data->orientation == Qt::Horizontal ) { const auto distance = mousePos.x() - m_data->pressedPos.x(); - newValue = m_data->pressedValue + distance / r.width() * boundaryLength(); + newValue = m_data->pressedValue + distance / r.width() * length; } else { const auto distance = mousePos.y() - m_data->pressedPos.y(); - newValue = m_data->pressedValue - distance / r.height() * boundaryLength(); + newValue = m_data->pressedValue - distance / r.height() * length; } if ( m_data->tracking ) { + m_data->dragging = true; setValue( newValue ); + m_data->dragging = false; } else { + // moving the handle without changing the value moveHandleTo( newValue, QskAnimationHint() ); } } -void QskSlider::mouseReleaseEvent( QMouseEvent* event ) +void QskSlider::mouseReleaseEvent( QMouseEvent* ) { - if ( !isPressed() ) // Page event - { - const auto mousePos = qskMousePosition( event ); - - const auto szHandle = handleSize(); - const auto rect = contentsRect(); - - bool up; - if ( m_data->orientation == Qt::Horizontal ) - { - const qreal w = szHandle.width(); - - const qreal x = ( mousePos.x() - rect.x() - w * 0.5 ) / ( rect.width() - w ); - up = x > valueAsRatio(); - } - else - { - const qreal h = szHandle.height(); - - const qreal y = ( mousePos.y() - rect.y() - h * 0.5 ) / ( rect.height() - h ); - up = y < 1.0 - valueAsRatio(); - } - - if ( up ) - pageUp(); - else - pageDown(); - } - else - { - if ( !m_data->tracking ) - { - const auto pos = handlePosition(); - setValue( valueFromRatio( pos ) ); - } - } + if ( !m_data->tracking && ( m_data->pressedValue != value() ) ) + Q_EMIT valueChanged( value() ); setSkinStateFlag( Pressed, false ); - Q_EMIT pressedChanged( false ); } -qreal QskSlider::handlePosition() const +void QskSlider::keyPressEvent( QKeyEvent* event ) { - return positionHint( Handle ); + if ( auto offset = qskKeyOffset( orientation(), event->key() ) ) + { + if ( m_data->inverted ) + offset = -offset; + + increment( offset * stepSize() ); + return; + } + + if ( m_data->hasOrigin ) + { + switch( event->key() ) + { + case Qt::Key_Home: + { + setValue( origin() ); + return; + } + + case Qt::Key_End: + { + // we have 2 endpoints - better do nothing + return; + } + } + } + + Inherited::keyPressEvent( event ); } void QskSlider::moveHandle() { - const auto aspect = Handle | QskAspect::Metric | QskAspect::Position; - moveHandleTo( value(), animationHint( aspect | skinStates() ) ); + QskAnimationHint hint; + if ( !m_data->dragging ) + { + const auto aspect = Handle | QskAspect::Metric | QskAspect::Position; + hint = animationHint( aspect | skinStates() ); + } + + moveHandleTo( value(), hint ); } void QskSlider::moveHandleTo( qreal value, const QskAnimationHint& hint ) diff --git a/src/controls/QskSlider.h b/src/controls/QskSlider.h index 4452dc89..2681cb16 100644 --- a/src/controls/QskSlider.h +++ b/src/controls/QskSlider.h @@ -7,26 +7,29 @@ #define QSK_SLIDER_H #include "QskBoundedValueInput.h" +#include "QskNamespace.h" class QSK_EXPORT QskSlider : public QskBoundedValueInput { Q_OBJECT - Q_PROPERTY( bool isPressed READ isPressed NOTIFY pressedChanged ) - Q_PROPERTY( Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged ) + Q_PROPERTY( bool inverted READ isInverted + WRITE setInverted NOTIFY invertedChanged ) + Q_PROPERTY( bool tracking READ isTracking WRITE setTracking NOTIFY trackingChanged ) - Q_PROPERTY( qreal handlePosition READ handlePosition ) + Q_PROPERTY( qreal origin READ origin + WRITE setOrigin RESET resetOrigin NOTIFY originChanged ) using Inherited = QskBoundedValueInput; public: - QSK_SUBCONTROLS( Panel, Groove, Fill, Scale, Handle, - GrooveStopIndicators, FillStopIndicators, LabelContainer, LabelText ) + QSK_SUBCONTROLS( Panel, Groove, Fill, Scale, Tick, Handle, + LabelContainer, LabelText ) QSK_STATES( Pressed ) explicit QskSlider( QQuickItem* parent = nullptr ); @@ -34,11 +37,16 @@ class QSK_EXPORT QskSlider : public QskBoundedValueInput ~QskSlider() override; - bool isPressed() const; - void setOrientation( Qt::Orientation ); Qt::Orientation orientation() const; + void setInverted( bool ); + bool isInverted() const; + + void resetOrigin(); + qreal origin() const; + bool hasOrigin() const; + void setTracking( bool ); bool isTracking() const; @@ -46,20 +54,24 @@ class QSK_EXPORT QskSlider : public QskBoundedValueInput QskAspect::Variation effectiveVariation() const override; + public Q_SLOTS: + void setOrigin( qreal ); + Q_SIGNALS: - void pressedChanged( bool ); void orientationChanged( Qt::Orientation ); + void invertedChanged( bool ); void trackingChanged( bool ); + void originChanged( qreal ); protected: void mousePressEvent( QMouseEvent* ) override; void mouseMoveEvent( QMouseEvent* ) override; void mouseReleaseEvent( QMouseEvent* ) override; - QSizeF handleSize() const; - QRectF handleRect() const; + void keyPressEvent( QKeyEvent* ) override; void aboutToShow() override; + void componentComplete() override; private: void moveHandle(); diff --git a/src/controls/QskSliderSkinlet.cpp b/src/controls/QskSliderSkinlet.cpp index a4878256..8c948e60 100644 --- a/src/controls/QskSliderSkinlet.cpp +++ b/src/controls/QskSliderSkinlet.cpp @@ -5,58 +5,91 @@ #include "QskSliderSkinlet.h" #include "QskSlider.h" - -#include "QskAspect.h" -#include "QskBoxBorderMetrics.h" #include "QskFunctions.h" +#include "QskIntervalF.h" -#include -#include +#include +#include +#include +#include + +// the color of graduation ticks might different, when being on top of the filling +QSK_SYSTEM_STATE( QskSliderSkinlet, Filled, QskAspect::FirstUserState >> 1 ) using Q = QskSlider; -namespace +static inline qreal qskSubcontrolExtent( + const QskSkinnable* skinnable, QskAspect::Subcontrol subControl ) { - inline QRectF qskInnerPanelRect( - const QskSlider* slider, const QRectF& contentsRect ) + return skinnable->metric( subControl | QskAspect::Size, -1.0 ); +} + +static inline bool qskHasFilling( const QskSlider* slider ) +{ + const auto policy = slider->flagHint< Qsk::Policy >( + Q::Fill | QskAspect::Option, Qsk::Always ); + + switch( policy ) { - #if 1 - auto padding = slider->paddingHint( Q::Panel ); - padding += slider->boxBorderMetricsHint( Q::Panel ).widths(); + case Qsk::Never: + return false; - auto r = slider->subControlRect( contentsRect, Q::Panel ); - r = r.marginsRemoved( padding ); - #else - r = slider->subControlContentsRect( contentsRect, Q::Panel ); - #endif + case Qsk::Maybe: + return qskFuzzyCompare( slider->origin(), slider->minimum() ); - return r; - } - - QRectF qskInnerValueRect( const QskSlider* slider, const QRectF& contentsRect ) - { - // For M3 the stop indicators have some padding related to the groove (and fill), - // so we use the rect between first and last stop indicator as authoritative for - // indicators, handle etc. - const auto grooveIndicatorMargins = slider->paddingHint( Q::GrooveStopIndicators ); - const auto r = qskInnerPanelRect( slider, contentsRect ).marginsRemoved( grooveIndicatorMargins ); - return r; + default: + return true; } } +static QRectF qskInnerRect( const QskSlider* slider, + const QRectF& contentsRect, QskAspect::Subcontrol subControl ) +{ + auto r = slider->subControlContentsRect( contentsRect, Q::Panel ); + + const qreal extent = qskSubcontrolExtent( slider, subControl ); + + if ( extent >= 0.0 ) + { + if ( slider->orientation() == Qt::Horizontal ) + { + if ( extent < r.height() ) + { + r.setTop( r.center().y() - 0.5 * extent ); + r.setHeight( extent ); + } + } + else + { + if ( extent < r.width() ) + { + r.setLeft( r.center().x() - 0.5 * extent ); + r.setWidth( extent ); + } + } + } + + return r; +} + +static inline QPair< qreal, qreal > qskTickSpan( qreal min, qreal max, qreal length ) +{ + if ( length >= 0.0 ) + { + // using the center of [min,max] + min += 0.5 * ( max - min - length ); + max = min + length; + } + + return { min, max }; +} + + QskSliderSkinlet::QskSliderSkinlet( QskSkin* skin ) : Inherited( skin ) { - setNodeRoles( { - PanelRole, - GrooveRole, - FillRole, - FillStopIndicatorsRole, - GrooveStopIndicatorsRole, - HandleRole, - LabelContainerRole, - LabelTextRole, - } ); + setNodeRoles( { PanelRole, GrooveRole, FillRole, TicksRole, HandleRole, + LabelContainerRole, LabelTextRole } ); } QskSliderSkinlet::~QskSliderSkinlet() @@ -69,107 +102,29 @@ QRectF QskSliderSkinlet::subControlRect( const QskSkinnable* skinnable, const auto slider = static_cast< const QskSlider* >( skinnable ); if ( subControl == Q::Panel ) - { return panelRect( slider, contentsRect ); - } if ( subControl == Q::Groove ) - { - return grooveRect( slider, contentsRect ); - } + return qskInnerRect( slider, contentsRect, Q::Groove ); if ( subControl == Q::Fill ) - { return fillRect( slider, contentsRect ); - } - - if ( subControl == Q::Handle ) - { - return handleRect( slider, contentsRect ); - } if ( subControl == Q::Scale ) - { - return scaleRect( slider, contentsRect ); - } + return subControlRect( skinnable, contentsRect, Q::Groove ); + + if ( subControl == Q::Handle ) + return handleRect( slider, contentsRect ); if ( subControl == Q::LabelContainer ) - { return labelContainerRect( slider, contentsRect ); - } if ( subControl == Q::LabelText ) - { return labelContainerRect( slider, contentsRect ); - } return Inherited::subControlRect( skinnable, contentsRect, subControl ); } -int QskSliderSkinlet::sampleCount( const QskSkinnable* skinnable, - QskAspect::Subcontrol subControl ) const -{ - const auto slider = static_cast< const QskSlider* >( skinnable ); - - if( slider->snap() ) - { - const auto num = qCeil( slider->boundaryLength() / slider->stepSize() ) + 1; - return num; - } - else - { - return ( subControl == Q::GrooveStopIndicators ) ? 1 : 0; - } -} - -QRectF QskSliderSkinlet::sampleRect( - const QskSkinnable* skinnable, const QRectF& contentsRect, - QskAspect::Subcontrol subControl, int index ) const -{ - const auto slider = static_cast< const QskSlider* >( skinnable ); - - auto r = qskInnerValueRect( slider, contentsRect ); - - const auto size = slider->strutSizeHint( subControl ); - - const auto filledPoints = qFloor( ( slider->value() - slider->minimum() ) / slider->stepSize() ); - - if( slider->snap()) - { - if( slider->snap() - && ( ( index >= filledPoints && subControl == Q::FillStopIndicators ) - || ( index < filledPoints && subControl == Q::GrooveStopIndicators ) ) ) - { - return {}; - } - } - - const auto pos = slider->snap() ? slider->minimum() + index * slider->stepSize() : slider->maximum(); - - if( slider->orientation() == Qt::Horizontal ) - { - r.setTop( r.center().y() - size.height() / 2 ); - const auto x = r.left() + slider->valueAsRatio( pos ) * r.width() - size.width() / 2; - r.setLeft( x ); - } - else - { - r.setLeft( r.center().x() - size.width() / 2 ); - const auto y = r.bottom() - slider->valueAsRatio( pos ) * r.height() - size.height() / 2; - r.setTop( y ); - } - - r.setHeight( size.height() ); - r.setWidth( size.width() ); - - return r; -} - -QskAspect::States QskSliderSkinlet::sampleStates( const QskSkinnable*, QskAspect::Subcontrol, int ) const -{ - return {}; -} - QSGNode* QskSliderSkinlet::updateSubNode( const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const { @@ -178,56 +133,102 @@ QSGNode* QskSliderSkinlet::updateSubNode( switch ( nodeRole ) { case PanelRole: - { return updateBoxNode( slider, node, Q::Panel ); - } case GrooveRole: - { return updateBoxNode( slider, node, Q::Groove ); - } case FillRole: - { return updateBoxNode( slider, node, Q::Fill ); - } - - case GrooveStopIndicatorsRole: - { - return updateSeriesNode( slider, Q::GrooveStopIndicators, node ); - } - - case FillStopIndicatorsRole: - { - return updateSeriesNode( slider, Q::FillStopIndicators, node ); - } case HandleRole: - { return updateBoxNode( slider, node, Q::Handle ); - } + + case TicksRole: + return updateSeriesNode( slider, Q::Tick, node ); case LabelContainerRole: - { return updateBoxNode( slider, node, Q::LabelContainer ); - } case LabelTextRole: - { return updateTextNode( slider, node, slider->valueText(), Q::LabelText ); - } } return Inherited::updateSubNode( skinnable, nodeRole, node ); } +int QskSliderSkinlet::sampleCount( const QskSkinnable* skinnable, + QskAspect::Subcontrol subControl ) const +{ + if ( subControl == Q::Tick ) + { + const auto slider = static_cast< const QskSlider* >( skinnable ); + return graduation( slider ).count(); + } + + return Inherited::sampleCount( skinnable, subControl ); +} + +QVariant QskSliderSkinlet::sampleAt( const QskSkinnable* skinnable, + QskAspect::Subcontrol subControl, int index ) const +{ + if ( subControl == Q::Tick ) + { + const auto slider = static_cast< const QskSlider* >( skinnable ); + return graduation( slider ).value( index ); + } + + return Inherited::sampleAt( skinnable, subControl, index ); +} + +QskAspect::States QskSliderSkinlet::sampleStates( + const QskSkinnable* skinnable, QskAspect::Subcontrol subControl, int index ) const +{ + auto states = Inherited::sampleStates( skinnable, subControl, index ); + + const auto slider = static_cast< const QskSlider* >( skinnable ); + + if ( subControl == Q::Tick && qskHasFilling( slider ) ) + { + const auto tickValue = sampleAt( skinnable, subControl, index ); + if ( tickValue.canConvert< qreal >() ) + { + const auto intv = QskIntervalF::normalized( + slider->origin(), slider->value() ); + + if ( intv.contains( tickValue.value< qreal >() ) ) + states |= Filled; + } + } + + return states; +} + +QRectF QskSliderSkinlet::sampleRect( + const QskSkinnable* skinnable, const QRectF& contentsRect, + QskAspect::Subcontrol subControl, int index ) const +{ + if ( subControl == Q::Tick ) + { + const auto slider = static_cast< const QskSlider* >( skinnable ); + return tickRect( slider, contentsRect, index ); + } + + return Inherited::sampleRect( skinnable, contentsRect, subControl, index ); +} + QSGNode* QskSliderSkinlet::updateSampleNode( const QskSkinnable* skinnable, QskAspect::Subcontrol subControl, int index, QSGNode* node ) const { - const auto slider = static_cast< const QskSlider* >( skinnable ); - const auto rect = sampleRect( slider, slider->contentsRect(), subControl, index ); + if ( subControl == Q::Tick ) + { + const auto slider = static_cast< const QskSlider* >( skinnable ); + const auto rect = sampleRect( slider, slider->contentsRect(), subControl, index ); - return updateBoxNode( skinnable, node, rect, subControl ); + return updateBoxNode( skinnable, node, rect, subControl ); + } + + return Inherited::updateSampleNode( skinnable, subControl, index, node ); } QRectF QskSliderSkinlet::panelRect( @@ -235,104 +236,73 @@ QRectF QskSliderSkinlet::panelRect( { auto r = contentsRect; - const qreal size = slider->metric( Q::Panel | QskAspect::Size ); // 0: no hint - if ( size > 0 && size < r.height() ) + const qreal extent = qskSubcontrolExtent( slider, Q::Panel ); + if ( extent >= 0 && extent < r.height() ) { const auto alignment = slider->alignmentHint( Q::Panel ); if ( slider->orientation() == Qt::Horizontal ) - r = qskAlignedRectF( r, r.width(), size, alignment & Qt::AlignVertical_Mask ); + { + r = qskAlignedRectF( r, r.width(), + extent, alignment & Qt::AlignVertical_Mask ); + } else - r = qskAlignedRectF( r, size, r.height(), alignment & Qt::AlignHorizontal_Mask ); - } - - return r; -} - -QRectF QskSliderSkinlet::innerRect( const QskSlider* slider, - const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const -{ - auto r = qskInnerPanelRect( slider, contentsRect ); - - QskSkinHintStatus status; - - const qreal extent = slider->metric( subControl | QskAspect::Size, &status ); - - if ( slider->orientation() == Qt::Horizontal ) - { - if ( status.isValid() && ( extent < r.height() ) ) { - r.setTop( r.center().y() - 0.5 * extent ); - r.setHeight( extent ); - } - } - else - { - if ( status.isValid() && ( extent < r.width() ) ) - { - r.setLeft( r.center().x() - 0.5 * extent ); - r.setWidth( extent ); + r = qskAlignedRectF( r, extent, r.height(), + alignment & Qt::AlignHorizontal_Mask ); } } return r; } -QRectF QskSliderSkinlet::grooveRect( - const QskSlider* slider, const QRectF& contentsRect ) const -{ - const auto r = qskInnerPanelRect( slider, contentsRect ); - auto grooveRect = innerRect( slider, contentsRect, Q::Groove ); - const auto handleRect = slider->subControlRect( Q::Handle ); - - if ( slider->orientation() == Qt::Horizontal ) - { - grooveRect.setLeft( handleRect.right() ); - grooveRect.setRight( r.right() ); - } - else - { - grooveRect.setBottom( handleRect.top() ); - grooveRect.setTop( r.top() ); - } - - return grooveRect; -} - -QRectF QskSliderSkinlet::scaleRect( - const QskSlider* slider, const QRectF& rect ) const -{ - return innerRect( slider, rect, Q::Groove ); -} - QRectF QskSliderSkinlet::fillRect( const QskSlider* slider, const QRectF& contentsRect ) const { - const auto r = qskInnerPanelRect( slider, contentsRect ); - const auto handleRect = slider->subControlRect( Q::Handle ); + if ( !qskHasFilling( slider ) ) + return QRectF(); + + auto pos1 = slider->valueAsRatio( slider->origin() ); + auto pos2 = qBound( 0.0, slider->positionHint( Q::Handle ), 1.0 ); + + if ( slider->isInverted() ) + { + pos1 = 1.0 - pos1; + pos2 = 1.0 - pos2; + } + + if ( pos1 > pos2 ) + qSwap( pos1, pos2 ); + + auto r = qskInnerRect( slider, contentsRect, Q::Fill ); + + auto scaleRect = subControlRect( slider, contentsRect, Q::Scale ); - auto fillRect = innerRect( slider, contentsRect, Q::Fill ); if ( slider->orientation() == Qt::Horizontal ) { - fillRect.setLeft( r.left() ); - fillRect.setRight( handleRect.left() ); + if ( !qFuzzyIsNull( pos1 ) ) + r.setLeft( scaleRect.left() + pos1 * scaleRect.width() ); + + r.setRight( scaleRect.left() + pos2 * scaleRect.width() ); } else { - fillRect.setBottom( r.bottom() ); - fillRect.setTop( handleRect.bottom() ); + if ( !qFuzzyIsNull( pos1 ) ) + r.setBottom( scaleRect.bottom() - pos1 * scaleRect.height() ); + + r.setTop( scaleRect.bottom() - pos2 * scaleRect.height() ); } - return fillRect; + return r; } QRectF QskSliderSkinlet::handleRect( const QskSlider* slider, const QRectF& contentsRect ) const { auto handleSize = slider->strutSizeHint( Q::Handle ); - const auto pos = qBound( 0.0, slider->handlePosition(), 1.0 ); + const auto pos = qBound( 0.0, slider->positionHint( Q::Handle ), 1.0 ); - const auto r = qskInnerValueRect( slider, contentsRect ); + const auto r = subControlRect( slider, contentsRect, Q::Scale ); auto center = r.center(); if ( slider->orientation() == Qt::Horizontal ) @@ -343,7 +313,10 @@ QRectF QskSliderSkinlet::handleRect( if ( handleSize.width() < 0.0 ) handleSize.setWidth( handleSize.height() ); - center.setX( r.left() + pos * r.width() ); + if ( slider->isInverted() ) + center.setX( r.right() - pos * r.width() ); + else + center.setX( r.left() + pos * r.width() ); } else { @@ -353,17 +326,56 @@ QRectF QskSliderSkinlet::handleRect( if ( handleSize.height() < 0.0 ) handleSize.setHeight( handleSize.width() ); - center.setY( r.bottom() - pos * r.height() ); + if ( slider->isInverted() ) + center.setY( r.top() + pos * r.height() ); + else + center.setY( r.bottom() - pos * r.height() ); } QRectF handleRect( 0, 0, handleSize.width(), handleSize.height() ); handleRect.moveCenter( center ); - handleRect = handleRect.marginsRemoved( slider->marginHint( Q::Handle ) ); - return handleRect; } +QRectF QskSliderSkinlet::tickRect( const QskSlider* slider, + const QRectF& contentsRect, int index ) const +{ + const auto tickValue = sampleAt( slider, Q::Tick, index ); + if ( !tickValue.canConvert< qreal >() ) + return QRectF(); + + auto tickPos = slider->valueAsRatio( tickValue.value< qreal >() ); + if ( slider->isInverted() ) + tickPos = 1.0 - tickPos; + + const auto r = subControlRect( slider, contentsRect, Q::Scale ); + + const auto padding = slider->paddingHint( Q::Scale ); + const auto size = slider->strutSizeHint( Q::Tick ); + + if( slider->orientation() == Qt::Horizontal ) + { + const auto x = tickPos * r.width() - 0.5 * size.width(); + + const auto span = qskTickSpan( + padding.top(), r.height() - padding.bottom(), size.height() ); + + return QRectF( r.x() + x, r.y() + span.first, + size.width(), span.second - span.first ); + } + else + { + const auto y = tickPos * r.height() + 0.5 * size.height(); + + const auto span = qskTickSpan( + padding.left(), r.width() - padding.right(), size.width() ); + + return QRectF( r.x() + span.first, r.bottom() - y, + span.second - span.first, size.height() ); + } +} + QRectF QskSliderSkinlet::labelContainerRect( const QskSlider* slider, const QRectF& rect ) const { @@ -398,24 +410,73 @@ QRectF QskSliderSkinlet::labelContainerRect( return QRectF( x, y, size.width(), size.height() ); } - QSizeF QskSliderSkinlet::sizeHint( const QskSkinnable* skinnable, Qt::SizeHint which, const QSizeF& ) const { if ( which != Qt::PreferredSize ) return QSizeF(); - const auto panelHint = skinnable->strutSizeHint( Q::Panel ); - const auto grooveHint = skinnable->strutSizeHint( Q::Groove ); - const auto fillHint = skinnable->strutSizeHint( Q::Fill ); - const auto handleHint = skinnable->strutSizeHint( Q::Handle ); + auto extent = qskSubcontrolExtent( skinnable, Q::Panel ); + extent = qMax( extent, qskSubcontrolExtent( skinnable, Q::Groove ) ); + extent = qMax( extent, qskSubcontrolExtent( skinnable, Q::Fill ) ); - auto hint = panelHint; - hint = hint.expandedTo( grooveHint ); - hint = hint.expandedTo( fillHint ); - hint = hint.expandedTo( handleHint ); + const auto slider = static_cast< const QskSlider* >( skinnable ); + + auto hint = skinnable->strutSizeHint( Q::Handle ); + + if ( slider->orientation() == Qt::Horizontal ) + hint.setHeight( qMax( hint.height(), extent ) ); + else + hint.setWidth( qMax( hint.width(), extent ) ); return hint; } +bool QskSliderSkinlet::hasGraduation( const QskSlider* slider ) const +{ + if ( slider->stepSize() ) + { + const auto policy = slider->flagHint< Qsk::Policy >( + Q::Tick | QskAspect::Option, Qsk::Never ); + + switch( policy ) + { + case Qsk::Always: + return true; + + case Qsk::Maybe: + return slider->isSnapping(); + + case Qsk::Never: + return false; + } + } + + return false; +} + +QVector< qreal > QskSliderSkinlet::graduation( const QskSlider* slider ) const +{ + QVector< qreal > graduation; + + if ( hasGraduation( slider ) ) + { + const auto from = slider->minimum(); + const auto to = slider->maximum(); + + auto step = slider->stepSize(); + if ( from > to ) + step = -step; + + const auto n = qCeil( ( to - from ) / step ) - 1; + + graduation.reserve( n ); + + for ( int i = 1; i <= n; i++ ) + graduation += from + i * step; + } + + return graduation; +} + #include "moc_QskSliderSkinlet.cpp" diff --git a/src/controls/QskSliderSkinlet.h b/src/controls/QskSliderSkinlet.h index 34a7247b..2815bfa4 100644 --- a/src/controls/QskSliderSkinlet.h +++ b/src/controls/QskSliderSkinlet.h @@ -17,13 +17,14 @@ class QSK_EXPORT QskSliderSkinlet : public QskSkinlet using Inherited = QskSkinlet; public: + QSK_STATES( Filled ) + enum NodeRole { PanelRole, GrooveRole, FillRole, - GrooveStopIndicatorsRole, - FillStopIndicatorsRole, + TicksRole, HandleRole, LabelContainerRole, LabelTextRole, @@ -45,6 +46,9 @@ class QSK_EXPORT QskSliderSkinlet : public QskSkinlet QRectF sampleRect( const QskSkinnable*, const QRectF&, QskAspect::Subcontrol, int index ) const override; + QVariant sampleAt( const QskSkinnable*, + QskAspect::Subcontrol, int index ) const override; + QskAspect::States sampleStates( const QskSkinnable*, QskAspect::Subcontrol, int ) const override; @@ -55,15 +59,16 @@ class QSK_EXPORT QskSliderSkinlet : public QskSkinlet QSGNode* updateSampleNode( const QskSkinnable*, QskAspect::Subcontrol, int index, QSGNode* ) const override; + virtual QVector< qreal > graduation( const QskSlider* ) const; + bool hasGraduation( const QskSlider* ) const; + private: QRectF panelRect( const QskSlider*, const QRectF& ) const; - QRectF grooveRect( const QskSlider*, const QRectF& ) const; QRectF fillRect( const QskSlider*, const QRectF& ) const; QRectF handleRect( const QskSlider*, const QRectF& ) const; - QRectF scaleRect( const QskSlider*, const QRectF& ) const; - QRectF labelContainerRect( const QskSlider*, const QRectF& ) const; + QRectF tickRect( const QskSlider*, const QRectF&, int index ) const; - QRectF innerRect( const QskSlider*, const QRectF&, QskAspect::Subcontrol ) const; + QRectF labelContainerRect( const QskSlider*, const QRectF& ) const; }; #endif diff --git a/src/nodes/QskBoxBasicStroker.cpp b/src/nodes/QskBoxBasicStroker.cpp index 0221e3b9..2f9bc0e1 100644 --- a/src/nodes/QskBoxBasicStroker.cpp +++ b/src/nodes/QskBoxBasicStroker.cpp @@ -248,8 +248,7 @@ static inline void qskCreateFill( using namespace QskVertex; using namespace Qt; - const auto cn = m_metrics.corners; - const bool isHorizontal = m_metrics.preferredOrientation == Qt::Horizontal; + const bool isHorizontal = ( m_metrics.preferredOrientation == Qt::Horizontal ); if ( !m_metrics.isInsideRounded ) { @@ -258,7 +257,7 @@ static inline void qskCreateFill( } else if ( m_metrics.isOutsideSymmetric ) { - const int stepCount = cn[ 0 ].stepCount; + const int stepCount = m_metrics.corners[ 0 ].innerStepCount(); if ( isHorizontal ) { diff --git a/src/nodes/QskBoxClipNode.cpp b/src/nodes/QskBoxClipNode.cpp deleted file mode 100644 index 1006cb81..00000000 --- a/src/nodes/QskBoxClipNode.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/****************************************************************************** - * QSkinny - Copyright (C) The authors - * SPDX-License-Identifier: BSD-3-Clause - *****************************************************************************/ - -#include "QskBoxClipNode.h" -#include "QskBoxBorderMetrics.h" -#include "QskBoxRenderer.h" -#include "QskBoxShapeMetrics.h" -#include "QskFunctions.h" - -#include - -static inline QskHashValue qskMetricsHash( - const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& border ) -{ - QskHashValue hash = 13000; - - hash = shape.hash( hash ); - return border.hash( hash ); -} - -QskBoxClipNode::QskBoxClipNode() - : m_hash( 0 ) - , m_geometry( QSGGeometry::defaultAttributes_Point2D(), 0 ) -{ - setGeometry( &m_geometry ); -} - -QskBoxClipNode::~QskBoxClipNode() -{ -} - -void QskBoxClipNode::setBox( const QQuickWindow* window, const QRectF& rect, - const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& border ) -{ - const auto hash = qskMetricsHash( shape, border ); - if ( hash == m_hash && rect == m_rect ) - return; - - m_rect = rect; - m_hash = hash; - - bool isRectangular = false; - -#if 0 - /* - Depending on isRectangular the "renderer can use scissoring instead of stencil, - which is significantly faster." - - However the batch renderer ( qsgbatchrenderer.cpp ) is rounding the clip rectangle - to integers and the clip might become too small/large. - - So we always have to use stencil clipping - even if it might have a negative - impact on the performance. TODO ... - */ - - if ( shape.isRectangle() ) - isRectangular = true; -#endif - - if ( isRectangular ) - { - if ( m_geometry.vertexCount() > 0 ) - m_geometry.allocate( 0 ); - - setIsRectangular( true ); - } - else - { - setIsRectangular( false ); - - QskBoxRenderer renderer( window ); - renderer.setFillLines( rect, shape, border, m_geometry ); - } - - /* - Even in situations, where the clipping is not rectangular, it is - useful to know its bounding rectangle - */ - setClipRect( qskValidOrEmptyInnerRect( rect, border.widths() ) ); - - m_geometry.markVertexDataDirty(); - markDirty( QSGNode::DirtyGeometry ); -} diff --git a/src/nodes/QskBoxRenderer.cpp b/src/nodes/QskBoxRenderer.cpp index f5f039fb..fd8ab5e5 100644 --- a/src/nodes/QskBoxRenderer.cpp +++ b/src/nodes/QskBoxRenderer.cpp @@ -168,7 +168,8 @@ void QskBoxRenderer::setColoredBorderLines( const QRectF& rect, geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); geometry.markVertexDataDirty(); - const QskBoxBasicStroker stroker( QskBoxMetrics( rect, shape, border ), borderColors ); + const QskBoxMetrics metrics( rect, shape, border ); + const QskBoxBasicStroker stroker( metrics, borderColors ); if ( auto lines = qskAllocateColoredLines( geometry, stroker.borderCount() ) ) stroker.setBoxLines( lines, nullptr ); diff --git a/src/nodes/QskClipNode.cpp b/src/nodes/QskClipNode.cpp new file mode 100644 index 00000000..dfc5fe6f --- /dev/null +++ b/src/nodes/QskClipNode.cpp @@ -0,0 +1,151 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskClipNode.h" +#include "QskBoxBorderMetrics.h" +#include "QskBoxRenderer.h" +#include "QskBoxShapeMetrics.h" +#include "QskFunctions.h" +#include "QskVertex.h" + +#include + +static inline QskHashValue qskMetricsHash( + const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& border ) +{ + QskHashValue hash = 13000; + + hash = shape.hash( hash ); + return border.hash( hash ); +} + +static inline void qskSetBoundingRect( QSGClipNode* node, const QRectF& rect ) +{ + /* + Depending on isRectangular: the "scene graph renderer can use + scissoring instead of stencil, which is significantly faster." + + However the batch renderer ( qsgbatchrenderer.cpp ) is rounding + the clip rectangle to integers and the clip might become too small/large. + So we always have to use stencil clipping - even if it might have a negative + impact on the performance. + + When isRectangular is set to false the clipRect is not used from the + renderer and we use the memory for the storing the bounding rectangle. + */ + + node->setIsRectangular( false ); + node->setClipRect( rect ); +} + +static inline QskVertex::Line* qskAllocateLines( + QSGGeometry& geometry, int lineCount ) +{ + geometry.allocate( 2 * lineCount ); // 2 points per line + return reinterpret_cast< QskVertex::Line* >( geometry.vertexData() ); +} + +QskClipNode::QskClipNode() + : m_hash( 0 ) + , m_geometry( QSGGeometry::defaultAttributes_Point2D(), 0 ) +{ + setGeometry( &m_geometry ); +} + +QskClipNode::~QskClipNode() +{ +} + +void QskClipNode::setRect( const QRectF& rect ) +{ + setRegion( rect, QRectF() ); +} + +void QskClipNode::setRegion( const QRectF& rect, const QRectF& excludedRect ) +{ + if ( rect.isEmpty() ) + { + /* + what about rectangles having a width/height + of 0 ( f.e lines ) TODO ... + */ + reset(); + return; + } + + const auto innerRect = excludedRect.isEmpty() + ? QRectF() : excludedRect.intersected( rect ); + + const auto hash = qHashBits( &innerRect, sizeof( innerRect ), 1450 ); + if ( ( hash == m_hash ) && ( rect == Inherited::clipRect() ) ) + return; + + qskSetBoundingRect( this, rect ); + m_hash = hash; + + m_geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); + + if ( innerRect.isEmpty() ) + { + const auto l = qskAllocateLines( m_geometry, 2 ); + + l[0].setLine( rect.topLeft(), rect.topRight() ); + l[1].setLine( rect.bottomLeft(), rect.bottomRight() ); + } + else + { + const auto l = qskAllocateLines( m_geometry, 5 ); + + l[0].setLine( rect.topLeft(), innerRect.topLeft() ); + l[1].setLine( rect.topRight(), innerRect.topRight() ); + l[2].setLine( rect.bottomRight(), innerRect.bottomRight() ); + l[3].setLine( rect.bottomLeft(), innerRect.bottomLeft() ); + l[4] = l[0]; + } + + m_geometry.markVertexDataDirty(); + markDirty( QSGNode::DirtyGeometry ); +} + +void QskClipNode::setBox( const QQuickWindow* window, const QRectF& rect, + const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& border ) +{ + if ( rect.isEmpty() ) + { + reset(); + return; + } + + const auto hash = qskMetricsHash( shape, border ); + if ( hash == m_hash && rect == boundingRectangle() ) + return; + + qskSetBoundingRect( this, rect ); + m_hash = hash; + + QskBoxRenderer renderer( window ); + renderer.setFillLines( rect, shape, border, m_geometry ); + + m_geometry.markVertexDataDirty(); + markDirty( QSGNode::DirtyGeometry ); +} + +void QskClipNode::reset() +{ + Inherited::setIsRectangular( true ); + Inherited::setClipRect( QRectF() ); + + if ( m_geometry.vertexData() ) + { + m_geometry.allocate( 0 ); + m_geometry.markVertexDataDirty(); + } + + if ( m_hash != 0 ) + { + m_hash = 0; + markDirty( QSGNode::DirtyGeometry ); + } +} diff --git a/src/nodes/QskBoxClipNode.h b/src/nodes/QskClipNode.h similarity index 50% rename from src/nodes/QskBoxClipNode.h rename to src/nodes/QskClipNode.h index 1a637792..b8e49273 100644 --- a/src/nodes/QskBoxClipNode.h +++ b/src/nodes/QskClipNode.h @@ -3,8 +3,8 @@ * SPDX-License-Identifier: BSD-3-Clause *****************************************************************************/ -#ifndef QSK_BOX_CLIP_NODE_H -#define QSK_BOX_CLIP_NODE_H +#ifndef QSK_CLIP_NODE_H +#define QSK_CLIP_NODE_H #include "QskGlobal.h" #include @@ -14,20 +14,36 @@ class QskBoxBorderMetrics; class QQuickWindow; -class QSK_EXPORT QskBoxClipNode : public QSGClipNode +class QSK_EXPORT QskClipNode : public QSGClipNode { + using Inherited = QSGClipNode; + public: - QskBoxClipNode(); - ~QskBoxClipNode() override; + QskClipNode(); + ~QskClipNode() override; + + void setRect( const QRectF& ); + void setRegion( const QRectF&, const QRectF& excludedRect ); void setBox( const QQuickWindow*, const QRectF&, const QskBoxShapeMetrics&, const QskBoxBorderMetrics& ); - private: - QskHashValue m_hash; - QRectF m_rect; + QRectF boundingRectangle() const; + private: + void reset(); + + void setIsRectangular( bool ) = delete; + void setClipRect( const QRectF& ) = delete; + QRectF clipRect() const = delete; + + QskHashValue m_hash; QSGGeometry m_geometry; }; +inline QRectF QskClipNode::boundingRectangle() const +{ + return Inherited::clipRect(); +} + #endif diff --git a/src/nodes/QskSGNode.cpp b/src/nodes/QskSGNode.cpp index 265a4d0f..5855cf0b 100644 --- a/src/nodes/QskSGNode.cpp +++ b/src/nodes/QskSGNode.cpp @@ -88,13 +88,16 @@ void QskSGNode::setParentNode( QSGNode* node, QSGNode* parent ) QSGNode* QskSGNode::findChildNode( QSGNode* parent, quint8 role ) { - auto node = parent->firstChild(); - while ( node ) + if ( parent ) { - if ( nodeRole( node ) == role ) - return node; + auto node = parent->firstChild(); + while ( node ) + { + if ( nodeRole( node ) == role ) + return node; - node = node->nextSibling(); + node = node->nextSibling(); + } } return nullptr;