diff --git a/playground/charts/ChartView.cpp b/playground/charts/ChartView.cpp index 72eed589..78e4efc7 100644 --- a/playground/charts/ChartView.cpp +++ b/playground/charts/ChartView.cpp @@ -17,11 +17,75 @@ #include #include #include +#include +#include +#include #include +QSK_SUBCONTROL(ArcControl, Arc) + namespace { + class LinearGradientSlider : public QskSlider + { + Q_OBJECT + Q_PROPERTY( + QColor selectedColor READ selectedColor NOTIFY selectedColorChanged ) + using Inherited = QskSlider; + + public: + explicit LinearGradientSlider( QQuickItem* parent = nullptr ); + explicit LinearGradientSlider( Qt::Orientation orientation, QQuickItem* parent = nullptr ); + QColor selectedColor() const; + + Q_SIGNALS: + void selectedColorChanged(); + }; + + LinearGradientSlider::LinearGradientSlider( QQuickItem* parent ) + : LinearGradientSlider( Qt::Horizontal, parent ) + { + } + + LinearGradientSlider::LinearGradientSlider( Qt::Orientation orientation, QQuickItem* parent ) + : Inherited( orientation, parent ) + { + static const QVector< QskGradientStop > gradientStops = { + { 0.0000, QColor::fromRgb( 255, 0, 0 ) }, + { 0.1667, QColor::fromRgb( 255, 255, 0 ) }, + { 0.3333, QColor::fromRgb( 0, 255, 0 ) }, + { 0.5000, QColor::fromRgb( 0, 255, 255 ) }, + { 0.6667, QColor::fromRgb( 0, 0, 255 ) }, + { 0.8333, QColor::fromRgb( 255, 0, 255 ) }, + { 1.0000, QColor::fromRgb( 255, 0, 0 ) }, + }; + + QskGradient gradient( gradientStops ); + gradient.setLinearDirection( orientation ); + setGradientHint(Groove, {gradientStops}); + + setColor( Inherited::Fill, Qt::transparent ); + setGradientHint( Inherited::Groove, gradient ); + setBoxBorderColorsHint( Inherited::Handle, Qt::white ); + setBoxBorderMetricsHint( Inherited::Handle, 2 ); + + connect( this, &QskSlider::valueChanged, this, [ this, gradient ]( qreal value ) { + value = this->orientation() == Qt::Horizontal ? value : 1.0 - value; + const auto selectedColor = gradient.extracted( value, value ).startColor(); + setColor( Inherited::Handle, selectedColor ); + setColor( Inherited::Ripple, selectedColor ); + } ); + + valueChanged(0.0); + } + + QColor LinearGradientSlider::selectedColor() const + { + const auto gradient = gradientHint(Groove); + return gradient.extracted( value(), value() ).startColor(); + } + class ChartBox : public QskControl { Q_OBJECT @@ -83,18 +147,21 @@ namespace } namespace -{ +{ class SliderBox : public QskLinearBox { Q_OBJECT public: - SliderBox( const QString& label, qreal min, qreal max, qreal value ) + SliderBox( + const QString& label, qreal min, qreal max, qreal value, + std::function< QskSlider*( QQuickItem* ) > allocator = + []( QQuickItem* parent = nullptr ) { return new QskSlider( parent ); } ) { auto textLabel = new QskTextLabel( label, this ); textLabel->setSizePolicy( Qt::Horizontal, QskSizePolicy::Fixed ); - auto slider = new QskSlider( this ); + auto slider = allocator( this ); slider->setBoundaries( min, max ); slider->setValue( value ); slider->setStepSize( 1.0 ); @@ -110,7 +177,7 @@ namespace } namespace -{ +{ class ControlPanel : public QskGridBox { Q_OBJECT @@ -126,8 +193,11 @@ namespace auto sliderStart = new SliderBox( "Angle", 0.0, 360.0, metrics.startAngle() ); auto sliderSpan = new SliderBox( "Span", -360.0, 360.0, metrics.spanAngle() ); auto sliderExtent = new SliderBox( "Extent", 10.0, 100.0, metrics.thickness() ); - auto sliderOffsetX = new SliderBox( "Offset X", 0.0, 100.0, 0 ); - auto sliderOffsetY = new SliderBox( "Offset Y", 0.0, 100.0, 0 ); + auto shadowExtent = new SliderBox( "Shadow Extent", 0.0, 100.0, 50 ); + auto sliderOffsetX = new SliderBox( "Offset X", -1.0, +1.0, 0 ); + auto sliderOffsetY = new SliderBox( "Offset Y", -1.0, +1.0, 0 ); + auto sliderFillColor = new SliderBox( "Fill Color", 0.0, 1.0, 0 , []( QQuickItem* parent = nullptr ) { return new LinearGradientSlider( parent ); }); + auto sliderShadowColor = new SliderBox( "Shadow Color", 0.0, 1.0, 0, []( QQuickItem* parent = nullptr ) { return new LinearGradientSlider( parent ); } ); connect( sliderStart, &SliderBox::valueChanged, this, &ControlPanel::startAngleChanged ); @@ -138,15 +208,47 @@ namespace connect( sliderExtent, &SliderBox::valueChanged, this, &ControlPanel::thicknessChanged ); + connect( sliderExtent, &SliderBox::valueChanged, + this, &ControlPanel::thicknessChanged ); + + connect( sliderOffsetX, &SliderBox::valueChanged, + this, &ControlPanel::offsetXChanged ); + + connect( sliderOffsetY, &SliderBox::valueChanged, + this, &ControlPanel::offsetYChanged ); + + connect( shadowExtent, &SliderBox::valueChanged, + this, &ControlPanel::shadowExtendChanged ); + + connect( sliderFillColor, &SliderBox::valueChanged, this, [=](){ + auto* const slider = sliderFillColor->findChild(); + Q_EMIT fillColorChanged(slider->selectedColor()); + } ); + + connect( sliderShadowColor, &SliderBox::valueChanged, this, [=](){ + auto* const slider = sliderShadowColor->findChild(); + Q_EMIT shadowColorChanged(slider->selectedColor()); + } ); + addItem( sliderStart, 0, 0 ); addItem( sliderExtent, 0, 1 ); + addItem( shadowExtent, 0, 2 ); addItem( sliderSpan, 1, 0, 1, 2 ); + addItem( sliderOffsetX, 2, 0, 1, 1 ); + addItem( sliderOffsetY, 2, 1, 1, 1 ); + addItem( sliderFillColor, 3, 0, 1, 1 ); + addItem( sliderShadowColor, 3, 1, 1, 1 ); } Q_SIGNALS: void thicknessChanged( qreal ); void startAngleChanged( qreal ); void spanAngleChanged( qreal ); + void offsetXChanged( qreal ); + void offsetYChanged( qreal ); + void fillColorChanged( QColor ); + void shadowColorChanged( QColor ); + void shadowExtendChanged( qreal ); }; class Legend : public QskGridBox @@ -200,7 +302,7 @@ namespace } -ChartView::ChartView( QskControl* chart, QQuickItem* parent ) +ChartView::ChartView( ArcControl* chart, QQuickItem* parent ) : QskMainView( parent ) { auto hBox = new QskLinearBox( Qt::Horizontal ); @@ -215,24 +317,50 @@ ChartView::ChartView( QskControl* chart, QQuickItem* parent ) auto controlPanel = new ControlPanel( chart->arcMetricsHint(QskControl::Background) ); controlPanel->setSizePolicy( Qt::Vertical, QskSizePolicy::Fixed ); - connect( controlPanel, &ControlPanel::thicknessChanged, chart, [ chart ](qreal v) { - auto m = chart->arcMetricsHint( QskControl::Background ); - m.setThickness(v); - chart->setArcMetricsHint( QskControl::Background, m ); + const auto subcontrol = ArcControl::Arc; + + connect( controlPanel, &ControlPanel::thicknessChanged, chart, [ = ]( qreal v ) { + auto m = chart->arcMetricsHint( subcontrol ); + m.setThickness( v ) ; + chart->setArcMetricsHint( subcontrol, m ); + } ); + + connect( controlPanel, &ControlPanel::startAngleChanged, chart, [ = ]( qreal v ) { + auto m = chart->arcMetricsHint( subcontrol ); + m.setStartAngle( v ); + chart->setArcMetricsHint( subcontrol, m ); } ); - connect( controlPanel, &ControlPanel::startAngleChanged, chart, [ chart ](qreal v) { - auto m = chart->arcMetricsHint( QskControl::Background ); - m.setStartAngle(v); - chart->setArcMetricsHint( QskControl::Background, m ); + connect( controlPanel, &ControlPanel::spanAngleChanged, chart, [ = ]( qreal v ) { + auto m = chart->arcMetricsHint( subcontrol ); + m.setSpanAngle( v ); + chart->setArcMetricsHint( subcontrol, m ); } ); - connect( controlPanel, &ControlPanel::spanAngleChanged, chart, [ chart ](qreal v) { - auto m = chart->arcMetricsHint( QskControl::Background ); - m.setSpanAngle(v); - chart->setArcMetricsHint( QskControl::Background, m ); + connect( controlPanel, &ControlPanel::offsetXChanged, chart, [ = ]( qreal v ) { + auto h = chart->shadowMetricsHint( subcontrol ); + h.setOffsetX( v ); + chart->setShadowMetricsHint( subcontrol, h ); } ); + connect( controlPanel, &ControlPanel::offsetYChanged, chart, [ = ]( qreal v ) { + auto h = chart->shadowMetricsHint( subcontrol ); + h.setOffsetY( v ); + chart->setShadowMetricsHint( subcontrol, h ); + } ); + + connect( controlPanel, &ControlPanel::shadowExtendChanged, chart, [ = ]( qreal v ) { + auto h = chart->shadowMetricsHint( subcontrol ); + h.setSpreadRadius( v ); + chart->setShadowMetricsHint( subcontrol, h ); + } ); + + connect( controlPanel, &ControlPanel::fillColorChanged, chart, + [ = ]( QColor c ) { chart->setColor( subcontrol, c ); } ); + + connect( controlPanel, &ControlPanel::shadowColorChanged, chart, + [ = ]( QColor c ) { chart->setShadowColorHint( subcontrol, c ); } ); + setHeader( controlPanel ); setBody( hBox ); } diff --git a/playground/charts/ChartView.h b/playground/charts/ChartView.h index a60a2b8c..ea5c789c 100644 --- a/playground/charts/ChartView.h +++ b/playground/charts/ChartView.h @@ -7,10 +7,17 @@ #include +class ArcControl : public QskControl +{ +public: + QSK_SUBCONTROLS(Arc) +}; + + class CircularChart; class ChartView : public QskMainView { public: - ChartView( QskControl*, QQuickItem* parent = nullptr ); + ChartView( ArcControl*, QQuickItem* parent = nullptr ); }; diff --git a/playground/charts/CircularChartSkinlet.cpp b/playground/charts/CircularChartSkinlet.cpp index b81c9f70..d13fb90d 100644 --- a/playground/charts/CircularChartSkinlet.cpp +++ b/playground/charts/CircularChartSkinlet.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -366,7 +367,7 @@ QSGNode* CircularChartSkinlet::updateArcSegmentNode( arcNode = new QskArcNode(); arcNode->setArcData( m_data->closedArcRect, metrics, - borderWidth, borderColor, fillGradient ); + borderWidth, borderColor, fillGradient, {}, {} ); #endif return arcNode; diff --git a/playground/charts/main.cpp b/playground/charts/main.cpp index aabd7a6a..5cd1fc54 100644 --- a/playground/charts/main.cpp +++ b/playground/charts/main.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -64,6 +65,8 @@ namespace }; } +using Q = ArcControl; + class ArcSkinlet : public QskSkinlet { public: @@ -71,6 +74,7 @@ class ArcSkinlet : public QskSkinlet { Arc }; + ArcSkinlet( QskSkin* skin = nullptr ) : QskSkinlet( skin ) { @@ -85,14 +89,20 @@ class ArcSkinlet : public QskSkinlet auto* const arcNode = ( node == nullptr ) ? new QskArcNode : static_cast< QskArcNode* >( node ); - const auto* const q = static_cast< const QskControl* >( skinnable ); + const auto* const q = static_cast< const Q* >( skinnable ); const auto rect = q->contentsRect(); - const auto metrics = skinnable->arcMetricsHint(QskControl::Background); - const auto path = metrics.painterPath(rect); - const auto borderColor = q->color(QskControl::Background | QskAspect::Border); - const auto borderWidth = q->metric(QskControl::Background | QskAspect::Border); - arcNode->setArcData( rect, metrics, borderWidth, borderColor, { Qt::red } ); + const auto metrics = skinnable->arcMetricsHint(Q::Arc); + + const auto fillGradient = q->gradientHint(Q::Arc); + + const auto borderColor = q->color(Q::Arc | QskAspect::Border); + const auto borderWidth = q->metric(Q::Arc | QskAspect::Border); + + const auto shadowColor = q->shadowColorHint(Q::Arc); + const auto shadowMetrics = q->shadowMetricsHint(Q::Arc); + + arcNode->setArcData( rect, metrics, borderWidth, borderColor, fillGradient, shadowColor, shadowMetrics); return arcNode; } return nullptr; @@ -109,7 +119,7 @@ int main( int argc, char* argv[] ) SkinnyShortcut::enable( SkinnyShortcut::AllShortcuts ); - auto* const control = new QskControl; + auto* const control = new ArcControl; auto* const skinlet = new ArcSkinlet; control->setSkinlet(skinlet); skinlet->setOwnedBySkinnable(true); @@ -117,9 +127,18 @@ int main( int argc, char* argv[] ) QskArcMetrics metrics; metrics.setSpanAngle(270); metrics.setThickness(10); - control->setArcMetricsHint(QskControl::Background, metrics); - control->setMetric(QskControl::Background | QskAspect::Border, 4); - control->setColor(QskControl::Background | QskAspect::Border, Qt::blue); + + QskShadowMetrics shadowMetrics; + shadowMetrics.setSpreadRadius(10); + shadowMetrics.setSizeMode(Qt::SizeMode::RelativeSize); + + control->setBackgroundColor(Qt::white); + control->setGradientHint(Q::Arc, {Qt::red}); + control->setArcMetricsHint(Q::Arc, metrics); + control->setMetric(Q::Arc | QskAspect::Border, 4); + control->setColor(Q::Arc | QskAspect::Border, Qt::blue); + control->setShadowColorHint(Q::Arc, Qt::blue); + control->setShadowMetricsHint(Q::Arc, shadowMetrics); QskWindow window; window.addItem( new ChartView( control ) ); diff --git a/src/controls/QskSkinlet.cpp b/src/controls/QskSkinlet.cpp index e6351519..c99884d3 100644 --- a/src/controls/QskSkinlet.cpp +++ b/src/controls/QskSkinlet.cpp @@ -231,7 +231,7 @@ static inline QSGNode* qskUpdateArcNode( return nullptr; auto arcNode = QskSGNode::ensureNode< QskArcNode >( node ); - arcNode->setArcData( rect, metrics, borderWidth, borderColor, gradient ); + arcNode->setArcData( rect, metrics, borderWidth, borderColor, gradient, {}, {} ); return arcNode; } diff --git a/src/nodes/QskArcNode.cpp b/src/nodes/QskArcNode.cpp index be4fd192..679d93ff 100644 --- a/src/nodes/QskArcNode.cpp +++ b/src/nodes/QskArcNode.cpp @@ -10,6 +10,7 @@ #include "QskShapeNode.h" #include "QskStrokeNode.h" #include "QskSGNode.h" +#include "QskShadowMetrics.h" #include #include @@ -25,6 +26,7 @@ namespace { QColor color = Qt::red; QRectF rect; + QPointF offset; qreal radius = 1.0; // [0.0,1.0] qreal thickness = 0.2; qreal startAngle = 0.0; //< degree [0.0,360.0] @@ -61,17 +63,18 @@ namespace const auto& startAngle = newState->startAngle; const auto& spanAngle = newState->spanAngle; const auto& extend = newState->extend; + const auto& offset = newState->offset; auto& p = *program(); - p.setUniformValue("color", color.redF(), color.greenF(), color.blueF(), 1.0f); - p.setUniformValue("rect", rect.x(), rect.y(), rect.width(), rect.height()); - p.setUniformValue("radius", (float) radius); - p.setUniformValue("thickness", (float) thickness); - p.setUniformValue("startAngle", (float) startAngle - 90.0f); - p.setUniformValue("spanAngle", (float) spanAngle); - p.setUniformValue("extend", (float) extend); + p.setUniformValue( "color", color.redF(), color.greenF(), color.blueF(), 1.0f ); + p.setUniformValue( "rect", rect.x(), rect.y(), rect.width(), rect.height() ); + p.setUniformValue( "radius", ( float ) radius ); + p.setUniformValue( "thickness", ( float ) thickness ); + p.setUniformValue( "startAngle", ( float ) startAngle - 90.0f ); + p.setUniformValue( "spanAngle", ( float ) spanAngle ); + p.setUniformValue( "extend", ( float ) extend ); + p.setUniformValue( "offset", ( float ) offset.x(), ( float ) offset.y() ); } - }; class QskArcShadowNode : public QSGGeometryNode @@ -85,10 +88,11 @@ namespace material()->setFlag(QSGMaterial::Blending); } - void update(const QRectF& rect, const QskArcMetrics& metrics, const QColor& color, const qreal extend) + void update(const QRectF& rect, const QskArcMetrics& metrics, const QColor& color, const QskShadowMetrics& shadowMetrics = {}, const qreal borderWidth = 0.0) { auto* const vertices = geometry()->vertexDataAsPoint2D(); - const auto r = rect.adjusted( 0, -4, +4, 0 ); + const auto b = borderWidth / 2; + const auto r = rect.adjusted( -b, -b, +b, +b ); vertices[0].set(r.left(), r.top()); vertices[1].set(r.left(), r.bottom()); vertices[2].set(r.right(), r.top()); @@ -100,11 +104,12 @@ namespace auto& state = *material->state(); state.color = color; state.rect = r; - state.radius = 1.0 - (metrics.thickness() + extend / 4) / size; + state.radius = 1.0 - (metrics.thickness() + borderWidth) / size; state.thickness = 2 * metrics.thickness() / size; state.startAngle = metrics.startAngle(); state.spanAngle = metrics.spanAngle(); - state.extend = extend; + state.extend = shadowMetrics.spreadRadius(); + state.offset = shadowMetrics.offset(); markDirty( QSGNode::DirtyMaterial ); } @@ -165,11 +170,11 @@ QskArcNode::~QskArcNode() void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics, const QskGradient& fillGradient ) { - setArcData( rect, arcMetrics, 0.0, QColor(), fillGradient ); + setArcData( rect, arcMetrics, 0.0, QColor(), fillGradient, {}, {} ); } void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics, - qreal borderWidth, const QColor& borderColor, const QskGradient& fillGradient ) + const qreal borderWidth, const QColor& borderColor, const QskGradient& fillGradient, const QColor& shadowColor, const QskShadowMetrics& shadowMetrics ) { enum NodeRole { @@ -201,18 +206,17 @@ void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics const auto path = metrics.painterPath( arcRect ); - if ( true /* TODO */ ) + if ( shadowColor.alpha() > 0.0 ) { if ( shadowNode == nullptr ) { shadowNode = new QskArcShadowNode; QskSGNode::setNodeRole( shadowNode, ShadowRole ); } - - const auto extend = 16.0; - shadowNode->update( arcRect, metrics, Qt::black, extend ); + + shadowNode->update( arcRect, metrics, shadowColor, shadowMetrics, borderWidth ); } - else + else { delete shadowNode; shadowNode = nullptr; @@ -261,7 +265,7 @@ void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics { removeAllChildNodes(); - for ( QSGNode* node : { ( QSGNode* ) shadowNode, ( QSGNode* ) fillNode, ( QSGNode* ) borderNode }) + for ( QSGNode* node : { ( QSGNode* ) borderNode, ( QSGNode* ) shadowNode, ( QSGNode* ) fillNode }) { if ( node != nullptr ) { diff --git a/src/nodes/QskArcNode.h b/src/nodes/QskArcNode.h index 1e3e44d1..b34ec70b 100644 --- a/src/nodes/QskArcNode.h +++ b/src/nodes/QskArcNode.h @@ -10,6 +10,7 @@ class QskArcMetrics; class QskGradient; +class QskShadowMetrics; /* For the moment a QPainterPath/QskShapeNode. @@ -24,7 +25,8 @@ class QSK_EXPORT QskArcNode : public QskShapeNode void setArcData( const QRectF&, const QskArcMetrics&, const QskGradient& ); void setArcData( const QRectF&, const QskArcMetrics&, - qreal borderWidth, const QColor& borderColor, const QskGradient& ); + qreal borderWidth, const QColor& borderColor, const QskGradient&, + const QColor& shadowColor, const QskShadowMetrics& shadowMetrics); }; #endif diff --git a/src/nodes/shaders/arcshadow.frag b/src/nodes/shaders/arcshadow.frag index 49b44592..cd5fe036 100644 --- a/src/nodes/shaders/arcshadow.frag +++ b/src/nodes/shaders/arcshadow.frag @@ -2,6 +2,7 @@ uniform lowp float qt_Opacity; uniform lowp vec4 color; uniform lowp vec4 rect; +uniform lowp vec2 offset; uniform lowp float radius; uniform lowp float thickness; uniform lowp float startAngle; @@ -35,10 +36,18 @@ void main() // rotation float ra = radians(startAngle + spanAngle / 2.0); - { + { + p = p + offset; + float sin_ra = sin(ra); float cos_ra = cos(ra); - p = mat2(cos_ra, -sin_ra, sin_ra, cos_ra) * p; + mat2 transform = mat2 + ( + cos_ra, -sin_ra, + sin_ra, cos_ra + ); + + p = transform * p; } // distance @@ -48,5 +57,5 @@ void main() // coloring float v = 1.0 - abs(d) * e; float a = d >= 0.0 && abs(d) < e ? v : 0.0; // alpha - gl_FragColor = vec4(color.rgb, a) * qt_Opacity; + gl_FragColor = vec4(color.rgb, 1.0) * a * qt_Opacity; } \ No newline at end of file