diff --git a/examples/gallery/CMakeLists.txt b/examples/gallery/CMakeLists.txt index e229a560c..19f2566c2 100644 --- a/examples/gallery/CMakeLists.txt +++ b/examples/gallery/CMakeLists.txt @@ -11,6 +11,35 @@ set(SOURCES selector/SelectorPage.h selector/SelectorPage.cpp dialog/DialogPage.h dialog/DialogPage.cpp listbox/ListBoxPage.h listbox/ListBoxPage.cpp + + colorpickers/QskAlphaSlider.h + colorpickers/QskAlphaSlider.cpp + colorpickers/QskAlphaSliderSkinlet.h + colorpickers/QskAlphaSliderSkinlet.cpp + colorpickers/ColorPickerPage.cpp + colorpickers/ColorPickerPage.h + colorpickers/QskColorRectangle.cpp + colorpickers/QskColorRectangle.h + colorpickers/QskColorRectangleSkinlet.cpp + colorpickers/QskColorRectangleSkinlet.h + colorpickers/QskColorTriangle.cpp + colorpickers/QskColorTriangle.h + colorpickers/QskColorTriangleNode.cpp + colorpickers/QskColorTriangleNode.h + colorpickers/QskColorTriangleSkinlet.cpp + colorpickers/QskColorTriangleSkinlet.h + colorpickers/QskColorWheel.cpp + colorpickers/QskColorWheel.h + colorpickers/QskColorWheelSkinlet.cpp + colorpickers/QskColorWheelSkinlet.h + colorpickers/QskColorCircle.h + colorpickers/QskColorCircle.cpp + colorpickers/QskColorCircleSkinlet.h + colorpickers/QskColorCircleSkinlet.cpp + colorpickers/QskCircleNode.h + colorpickers/QskCircleNode.cpp + + Page.h Page.cpp main.cpp ) diff --git a/examples/gallery/colorpickers/ColorPickerPage.cpp b/examples/gallery/colorpickers/ColorPickerPage.cpp new file mode 100644 index 000000000..09b8ba32f --- /dev/null +++ b/examples/gallery/colorpickers/ColorPickerPage.cpp @@ -0,0 +1,131 @@ +#include "ColorPickerPage.h" + +#include "QskColorCircle.h" +#include "QskColorCircleSkinlet.h" +#include "QskColorRectangle.h" +#include "QskColorRectangleSkinlet.h" +#include "QskColorTriangle.h" +#include "QskColorTriangleSkinlet.h" +#include "QskColorWheel.h" +#include "QskColorWheelSkinlet.h" + +#include "colorpickers/QskAlphaSlider.h" +#include "colorpickers/QskAlphaSliderSkinlet.h" +#include "colorpickers/QskColorCircle.h" +#include "colorpickers/QskColorCircleSkinlet.h" +#include "colorpickers/QskColorRectangle.h" +#include "colorpickers/QskColorTriangle.h" + + +#include +#include +#include +#include + +#include + +template< typename Skinnable, typename Skinlet, class... Types > +Skinnable* create( Types&&... args, QQuickItem* parent = nullptr ) +{ + auto* const control = new Skinnable( std::forward< Types >( args )..., parent ); + auto* const skinlet = new Skinlet; + control->setSkinlet( skinlet ); + skinlet->setOwnedBySkinnable( true ); + return control; +} + +ColorPickerPage::ColorPickerPage( QQuickItem* parent ) + : Page( parent ) +{ + auto* const layout = new QskGridBox( this ); + + auto* const colorWheel = create< QskColorWheel, QskColorWheelSkinlet >( layout ); + auto* const colorWheel2 = create< QskColorWheel, QskColorWheelSkinlet >( layout ); + auto* const colorWheelSlider = new QskSlider( layout ); + + auto* const colorTriangle = create< QskColorTriangle, QskColorTriangleSkinlet >( layout ); + auto* const colorTriangle2 = create< QskColorTriangle, QskColorTriangleSkinlet >( layout ); + auto* const colorTriangleSlider = new QskSlider( layout ); + + auto* const colorRectangle = create< QskColorRectangle, QskColorRectangleSkinlet >( layout ); + auto* const colorRectangle2 = create< QskColorRectangle, QskColorRectangleSkinlet >( layout ); + + auto* const colorCircle = create< QskColorCircle, QskColorCircleSkinlet >( layout ); + auto* const colorCircle2 = create< QskColorCircle, QskColorCircleSkinlet >( layout ); + + auto* const alphaSlider = create< QskAlphaSlider, QskAlphaSliderSkinlet >( layout ); + + colorWheel2->setGradientHint( QskColorWheel::Arc, + { { { 0.0, Qt::red }, { 0.3333, Qt::green }, { 0.6666, Qt::blue }, { 1.0, Qt::red } } } ); + colorTriangle2->setColors( Qt::red, Qt::green, Qt::blue ); + colorRectangle2->setCornerColor( Qt::TopLeftCorner, Qt::red ); + colorRectangle2->setCornerColor( Qt::TopRightCorner, Qt::green ); + colorRectangle2->setCornerColor( Qt::BottomLeftCorner, Qt::blue ); + colorRectangle2->setCornerColor( Qt::BottomRightCorner, Qt::yellow ); + + colorCircle2->setGradientHint( QskColorCircle::Circle, + { { { 0.0, Qt::red }, { 0.3333, Qt::green }, { 0.6666, Qt::blue }, { 1.0, Qt::red } } } ); + + layout->addItem( new QskTextLabel( "QskColorWheel" ), 0, 0 ); + layout->addItem( colorWheel, 1, 0 ); + layout->addItem( colorWheel2, 2, 0 ); + layout->addItem( colorWheelSlider, 3, 0 ); + layout->addItem( new QskTextLabel( "QskColorTriangle" ), 0, 1 ); + layout->addItem( colorTriangle, 1, 1 ); + layout->addItem( colorTriangle2, 2, 1 ); + layout->addItem( colorTriangleSlider, 3, 1 ); + layout->addItem( new QskTextLabel( "QskColorRectangle" ), 0, 2 ); + layout->addItem( colorRectangle, 1, 2 ); + layout->addItem( colorRectangle2, 2, 2 ); + layout->addItem( new QskTextLabel( "QskColorCircle" ), 0, 3 ); + layout->addItem( colorCircle, 1, 3 ); + layout->addItem( colorCircle2, 2, 3 ); + layout->addItem( new QskTextLabel( "QskAlphaSlider" ), 0, 4 ); + layout->addItem( alphaSlider, 1, 4 ); + + for ( int col = 0; col < layout->columnCount(); col++ ) + { + layout->setColumnStretchFactor( col, 1 ); + } + + connect( colorWheel, &QskColorWheel::selectedColorChanged, colorTriangle, + [ colorTriangle ]( + const QColor& color ) { colorTriangle->setColors( color, Qt::black, Qt::white ); } ); + + connect( colorCircle, &QskColorCircle::selectedColorChanged, colorTriangle, + [ colorTriangle ]( + const QColor& color ) { colorTriangle->setColors( color, Qt::black, Qt::white ); } ); + + connect( colorWheelSlider, &QskSlider::valueChanged, this, [ q = colorWheel ]( qreal v ) { + auto metrics = q->arcMetricsHint( QskColorWheel::Arc ); + metrics.setThickness( v * qMin( q->width(), q->height() ) / 2 ); + q->setArcMetricsHint( QskColorWheel::Arc, metrics ); + } ); + + connect( colorTriangleSlider, &QskSlider::valueChanged, this, [ = ]( qreal v ) { + colorTriangle->setPositionHint( QskColorTriangle::Triangle, 2 * M_PI * v ); + } ); + + connect( colorWheel, &QskColorWheel::selectedColorChanged, colorRectangle, + [ = ]( const QColor& color ) { + colorRectangle->setCornerColor( Qt::TopLeftCorner, Qt::white ); + colorRectangle->setCornerColor( Qt::TopRightCorner, color ); + colorRectangle->setCornerColor( Qt::BottomLeftCorner, Qt::black ); + colorRectangle->setCornerColor( Qt::BottomRightCorner, Qt::black ); + } ); + + connect( colorCircle, &QskColorCircle::selectedColorChanged, colorRectangle, + [ = ]( const QColor& color ) { + colorRectangle->setCornerColor( Qt::TopLeftCorner, Qt::white ); + colorRectangle->setCornerColor( Qt::TopRightCorner, color ); + colorRectangle->setCornerColor( Qt::BottomLeftCorner, Qt::black ); + colorRectangle->setCornerColor( Qt::BottomRightCorner, Qt::black ); + } ); + + connect( colorCircle, &QskColorCircle::selectedColorChanged, alphaSlider, + [ = ]( const QColor& color ) { + QskGradient gradient{color, Qt::transparent}; + gradient.setLinearDirection(alphaSlider->orientation()); + alphaSlider->setGradientHint(QskAlphaSlider::Groove, gradient); + } ); +} \ No newline at end of file diff --git a/examples/gallery/colorpickers/ColorPickerPage.h b/examples/gallery/colorpickers/ColorPickerPage.h new file mode 100644 index 000000000..848b8df5e --- /dev/null +++ b/examples/gallery/colorpickers/ColorPickerPage.h @@ -0,0 +1,14 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#pragma once + +#include "Page.h" + +class ColorPickerPage : public Page +{ + public: + explicit ColorPickerPage( QQuickItem* = nullptr ); +}; \ No newline at end of file diff --git a/examples/gallery/colorpickers/QskAlphaSlider.cpp b/examples/gallery/colorpickers/QskAlphaSlider.cpp new file mode 100644 index 000000000..bb87ea78a --- /dev/null +++ b/examples/gallery/colorpickers/QskAlphaSlider.cpp @@ -0,0 +1,13 @@ +#include "QskAlphaSlider.h" + +QSK_SUBCONTROL( QskAlphaSlider, Grid ) + +QskAlphaSlider::QskAlphaSlider( QQuickItem* const parent ) + : QskSlider( parent ) +{ + setGradientHint( Grid, { "#B2B2B2", "#EEEEEE" } ); + setGradientHint( Fill, {}); + setMetric( Groove | QskAspect::Size, 16 ); + setMetric( Fill | QskAspect::Size, 16 ); + setMetric( Grid | QskAspect::Size, 8 ); +} \ No newline at end of file diff --git a/examples/gallery/colorpickers/QskAlphaSlider.h b/examples/gallery/colorpickers/QskAlphaSlider.h new file mode 100644 index 000000000..f802ad8ec --- /dev/null +++ b/examples/gallery/colorpickers/QskAlphaSlider.h @@ -0,0 +1,17 @@ +#ifndef ALPHASLIDER_H_ +#define ALPHASLIDER_H_ + +#include "QskQuickItem.h" +#include + +class QskAlphaSlider : public QskSlider +{ + Q_OBJECT +public: + QSK_SUBCONTROLS(Grid) + QskAlphaSlider(QQuickItem* parent = nullptr); +}; + +#endif + + diff --git a/examples/gallery/colorpickers/QskAlphaSliderSkinlet.cpp b/examples/gallery/colorpickers/QskAlphaSliderSkinlet.cpp new file mode 100644 index 000000000..c6a6721ae --- /dev/null +++ b/examples/gallery/colorpickers/QskAlphaSliderSkinlet.cpp @@ -0,0 +1,183 @@ +#include "QskAlphaSliderSkinlet.h" +#include "QskAlphaSlider.h" + +#include "QskSlider.h" +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace +{ + class Material : public QSGMaterial + { + public: + QSGMaterialType* type() const override + { + static QSGMaterialType type; + return &type; + } + + QSGMaterialShader* createShader() const override; + + QColor m_colorA; + QColor m_colorB; + float m_gridSize; + }; + + class Shader : public QSGMaterialShader + { + public: + const char* vertexShader() const + { + return "attribute highp vec4 vertex; \n" + "uniform highp mat4 matrix; \n" + "void main() { \n" + " gl_Position = matrix * vertex; \n" + "}"; + } + + const char* fragmentShader() const + { + return "uniform lowp float opacity; \n" + "uniform lowp vec4 colorA; \n" + "uniform lowp vec4 colorB; \n" + "uniform lowp float size; \n" + " \n" + "vec4 color(in vec4 fragColor, in vec2 fragCoord, in float gridSize) \n" + "{ \n" + " vec2 pos = floor(fragCoord / gridSize); \n" + " float mask = mod(pos.x + mod(pos.y, 2.0), 2.0); \n" + " return mask * fragColor; \n" + "} \n" + " \n" + "void main() { \n" + " vec2 fragCoordA = gl_FragCoord.xy; \n" + " vec2 fragCoordB = gl_FragCoord.xy + vec2(size, 0.0); \n" + " gl_FragColor = color(colorA, fragCoordA, size) + \n" + " color(colorB, fragCoordB, size) * \n" + " opacity; \n" + "}"; + } + + char const* const* attributeNames() const + { + static char const* const names[] = { "vertex", 0 }; + return names; + } + + void initialize() + { + QSGMaterialShader::initialize(); + m_id.matrix = program()->uniformLocation( "matrix" ); + m_id.opacity = program()->uniformLocation( "opacity" ); + m_id.color_a = program()->uniformLocation( "colorA" ); + m_id.color_b = program()->uniformLocation( "colorB" ); + m_id.grid_size = program()->uniformLocation( "size" ); + } + + void updateState( + const RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial ) + { + Q_ASSERT( program()->isLinked() ); + + if ( state.isMatrixDirty() ) + { + program()->setUniformValue( m_id.matrix, state.combinedMatrix() ); + } + + if ( state.isOpacityDirty() ) + { + program()->setUniformValue( m_id.opacity, state.opacity() ); + } + + const auto* const material = static_cast< const Material* >( newMaterial ); + program()->setUniformValue( m_id.color_a, material->m_colorA ); + program()->setUniformValue( m_id.color_b, material->m_colorB ); + program()->setUniformValue( m_id.grid_size, material->m_gridSize ); + } + + private: + struct + { + int matrix = -1; + int opacity = -1; + int color_a = -1; + int color_b = -1; + int grid_size = -1; + } m_id; + }; + + QSGMaterialShader* Material::createShader() const + { + return new Shader; + } + + class GridNode : public QSGGeometryNode + { + public: + GridNode() + : m_geometry( QSGGeometry::defaultAttributes_Point2D(), 0 ) + { + setGeometry( &m_geometry ); + setMaterial( &m_material ); + setFlag( QSGGeometryNode::OwnsGeometry, false ); + setFlag( QSGGeometryNode::OwnsMaterial, false ); + } + + void update( const QRectF& rect, const QskBoxShapeMetrics& shape, + const QskBoxBorderMetrics& border, const QColor& color1, const QColor& color2, + const float gridSize ) + { + QskBoxRenderer::renderFillGeometry( rect, shape, border, m_geometry ); + m_material.m_colorA = color1; + m_material.m_colorB = color2; + m_material.m_gridSize = gridSize; + + // TODO update only if necessary + + m_geometry.markVertexDataDirty(); + markDirty( QSGNode::DirtyMaterial ); + markDirty( QSGNode::DirtyGeometry ); + } + + private: + QSGGeometry m_geometry; + Material m_material; + }; +} + +using Q = QskAlphaSlider; + +QskAlphaSliderSkinlet::QskAlphaSliderSkinlet( QskSkin* skin ) + : Inherited( skin ) +{ + setNodeRoles( { PanelRole, GridRole, GrooveRole, FillRole, HandleRole, RippleRole } ); +} + +QSGNode* QskAlphaSliderSkinlet::updateSubNode( + const QskSkinnable* skinnable, const quint8 nodeRole, QSGNode* const node ) const +{ + if ( nodeRole == GridRole ) + { + const auto* const q = static_cast< const Q* >( skinnable ); + const auto rect = q->subControlRect( Q::Groove ); + const auto shape = q->boxShapeHint( Q::Groove ).toAbsolute( rect.size() ); + const auto border = q->boxBorderMetricsHint( Q::Groove ); + const auto gradient = q->gradientHint( Q::Grid ); + const auto gridSize = q->metric( Q::Grid | QskAspect::Size ); + + auto* const grid = QskSGNode::ensureNode< GridNode >( node ); + grid->update( rect, shape, border, gradient.startColor(), gradient.endColor(), gridSize ); + return grid; + } + + return Inherited::updateSubNode( skinnable, nodeRole, node ); +} \ No newline at end of file diff --git a/examples/gallery/colorpickers/QskAlphaSliderSkinlet.h b/examples/gallery/colorpickers/QskAlphaSliderSkinlet.h new file mode 100644 index 000000000..99d5ae631 --- /dev/null +++ b/examples/gallery/colorpickers/QskAlphaSliderSkinlet.h @@ -0,0 +1,11 @@ +#include + +class QskAlphaSliderSkinlet : public QskSliderSkinlet +{ + using Inherited = QskSliderSkinlet; +public: + enum NodeRoles { GridRole = Inherited::RoleCount, RoleCount }; + Q_INVOKABLE QskAlphaSliderSkinlet(QskSkin* skin = nullptr); +protected: + QSGNode* updateSubNode( const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node) const override; +}; \ No newline at end of file diff --git a/examples/gallery/colorpickers/QskCircleNode.cpp b/examples/gallery/colorpickers/QskCircleNode.cpp new file mode 100644 index 000000000..e92eb0a15 --- /dev/null +++ b/examples/gallery/colorpickers/QskCircleNode.cpp @@ -0,0 +1,51 @@ +#include "QskCircleNode.h" +#include "colorpickers/QskCircleNode.h" + +#include +#include + +#include +#include +#include + +#include + +QskCircleNode::QskCircleNode() +{ + auto* const geometry = new QSGGeometry( QSGGeometry::defaultAttributes_ColoredPoint2D(), 0 ); + geometry->setDrawingMode( QSGGeometry::DrawTriangleFan ); + setGeometry( geometry ); + + auto* const material = new QSGVertexColorMaterial; + setMaterial( material ); +} + +void QskCircleNode::update( const QRectF& rect, const QskGradient& gradient, int vertexCount ) +{ + constexpr auto additional = 2; // +1 center, +1 for second 0 degree closing + vertexCount = qMax( 3, vertexCount ) + additional; + + if ( geometry()->vertexCount() != vertexCount ) + { + geometry()->allocate( vertexCount ); + } + + const auto step = 2 * M_PI / ( vertexCount - additional ); + const auto radius = qMin( rect.width(), rect.height() ) / 2; + + auto* const vertices = geometry()->vertexDataAsColoredPoint2D(); + vertices[ 0 ].set( rect.center().x(), rect.center().y(), 255, 255, 255, 255 ); + for ( int i = 1; i <= vertexCount; i++ ) + { + const auto angle = ( i - 1 ) * step; + const auto x = qFastCos( angle ) * radius + rect.center().x(); + const auto y = qFastSin( angle ) * radius + rect.center().y(); + const auto color = + gradient.extracted( angle / ( 2 * M_PI ), angle / ( 2 * M_PI ) ).startColor(); + vertices[ i ].set( x, y, color.red(), color.green(), color.blue(), color.alpha() ); + } + + geometry()->markVertexDataDirty(); + markDirty( QSGNode::DirtyGeometry ); + markDirty( QSGNode::DirtyMaterial ); +} \ No newline at end of file diff --git a/examples/gallery/colorpickers/QskCircleNode.h b/examples/gallery/colorpickers/QskCircleNode.h new file mode 100644 index 000000000..83f187004 --- /dev/null +++ b/examples/gallery/colorpickers/QskCircleNode.h @@ -0,0 +1,14 @@ +#ifndef QSK_CIRCLE_NODE_H_ +#define QSK_CIRCLE_NODE_H_ + +#include "QskGradient.h" +#include + +class QskCircleNode : public QSGGeometryNode +{ + public: + QskCircleNode(); + void update( const QRectF& rect, const QskGradient& gradient, int vertexCount = 360 ); +}; + +#endif \ No newline at end of file diff --git a/examples/gallery/colorpickers/QskColorCircle.cpp b/examples/gallery/colorpickers/QskColorCircle.cpp new file mode 100644 index 000000000..3e10b2783 --- /dev/null +++ b/examples/gallery/colorpickers/QskColorCircle.cpp @@ -0,0 +1,103 @@ +#include "QskColorCircle.h" +#include "QskAspect.h" +#include "QskControl.h" +#include "QskGradient.h" +#include "colorpickers/QskColorCircle.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace +{ + QColor qskColorAt( const QskGradient& gradient, qreal ratio ) + { + return gradient.extracted( ratio, ratio ).startColor(); + } + + QColor qskColorForAngle( const QskGradient& gradient, qreal angle ) + { + angle = ( angle < 0.0 ? angle + 2 * M_PI : angle ); + const auto ratio = qAbs( angle / ( 2 * M_PI ) ); + return qskColorAt( gradient, ratio ); + } +} + +QSK_SUBCONTROL( QskColorCircle, Circle ) +QSK_SUBCONTROL( QskColorCircle, Handle ) + +QskColorCircle::QskColorCircle( QQuickItem* parent ) + : QskControl( parent ) +{ + const QskGradient gradient( { + { 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 ) }, + } ); + + setGradientHint( Circle, gradient ); + + setBoxBorderColorsHint( Handle, Qt::white ); + setBoxBorderMetricsHint( Handle, 2 ); + setBoxShapeHint( Handle, { 100, 100, 100, 100, Qt::AbsoluteSize } ); + setStrutSizeHint( Handle, { 20, 20 } ); + + setAcceptedMouseButtons( Qt::LeftButton ); +} + +void QskColorCircle::mousePressEvent( QMouseEvent* event ) +{ +} + +void QskColorCircle::mouseReleaseEvent( QMouseEvent* event ) +{ +} + +void QskColorCircle::mouseMoveEvent( QMouseEvent* event ) +{ + const auto r = subControlRect( Circle ); + const auto ux = qskMapValueRange( event->pos().x(), r.left(), r.right(), -1, +1 ); + const auto uy = qskMapValueRange( event->pos().y(), r.top(), r.bottom(), -1, +1 ); + + const auto radius = QVector2D( ux, uy ).length(); + if ( radius > 1.0 ) + { + event->ignore(); + return; + } + + const auto rad = qAtan2( uy, ux ); + const auto ratio = rad / ( 2 * M_PI ); + const auto color = qskColorForAngle( gradientHint( Circle ), rad ); + const QskGradient gradient(Qt::white, color); + + const auto effectiveColor = qskColorAt(gradient, radius); + + setColor( Handle, effectiveColor ); + setSkinHint( Handle, QPointF{ ux, uy } ); + setSelectedColor( effectiveColor ); +} + +QColor QskColorCircle::selectedColor() const +{ + return m_selectedColor; +} + +void QskColorCircle::setSelectedColor( const QColor& color ) +{ + if ( m_selectedColor != color ) + { + m_selectedColor = color; + Q_EMIT selectedColorChanged( m_selectedColor ); + } +} \ No newline at end of file diff --git a/examples/gallery/colorpickers/QskColorCircle.h b/examples/gallery/colorpickers/QskColorCircle.h new file mode 100644 index 000000000..bf76c35b6 --- /dev/null +++ b/examples/gallery/colorpickers/QskColorCircle.h @@ -0,0 +1,30 @@ +#ifndef QSK_COLOR_CIRCLE_H_ +#define QSK_COLOR_CIRCLE_H_ + +#include "QskAspect.h" +#include +#include + +class QskColorCircle : public QskControl +{ + Q_OBJECT + public: + QSK_SUBCONTROLS( Circle, Handle ) + explicit QskColorCircle( QQuickItem* parent = nullptr ); + + QColor selectedColor() const; + + protected: + void mousePressEvent( QMouseEvent* event ) override; + void mouseReleaseEvent( QMouseEvent* event ) override; + void mouseMoveEvent( QMouseEvent* event ) override; + + Q_SIGNALS: + void selectedColorChanged( const QColor& color ); + + private: + void setSelectedColor( const QColor& color ); + QColor m_selectedColor; +}; + +#endif \ No newline at end of file diff --git a/examples/gallery/colorpickers/QskColorCircleSkinlet.cpp b/examples/gallery/colorpickers/QskColorCircleSkinlet.cpp new file mode 100644 index 000000000..761e05203 --- /dev/null +++ b/examples/gallery/colorpickers/QskColorCircleSkinlet.cpp @@ -0,0 +1,90 @@ +#include "QskColorCircleSkinlet.h" +#include "QskCircleNode.h" +#include "QskColorCircle.h" +#include "QskSkinlet.h" +#include "colorpickers/QskCircleNode.h" +#include "colorpickers/QskColorCircleSkinlet.h" + +#include +#include + +using Q = QskColorCircle; + +QskColorCircleSkinlet::QskColorCircleSkinlet( QskSkin* skin ) + : QskSkinlet( skin ) +{ + setNodeRoles( { CircleRole, HandleRole } ); +} + +QRectF QskColorCircleSkinlet::subControlRect( const QskSkinnable* skinnable, + const QRectF& contentsRect, QskAspect::Subcontrol subcontrol ) const +{ + const auto* const q = static_cast< const Q* >( skinnable ); + + if ( subcontrol == Q::Circle ) + { + const auto c = contentsRect.center(); + const auto r = qMin( contentsRect.width(), contentsRect.height() ) / 2; + return { QPointF{ c.x() - r, c.y() - r }, QPointF{ c.x() + r, c.y() + r } }; + } + + if ( subcontrol == Q::Handle ) + { + const auto c = contentsRect.center(); + const auto r = qMin( contentsRect.width(), contentsRect.height() ) / 2; + const auto p = q->effectiveSkinHint( Q::Handle ).toPointF(); + const auto s = q->strutSizeHint( Q::Handle ); + const auto w = s.width() / 2; + const auto h = s.height() / 2; + + if ( p == QPointF{ -1, -1 } ) + { + return {}; + } + + const auto x = c.x() + p.x() * r; + const auto y = c.y() + p.y() * r; + + return { + QPointF{ x - w, y - h }, + QPointF{ x + w, y + h }, + }; + } + + return QskSkinlet::subControlRect( skinnable, contentsRect, subcontrol ); +} + +QSGNode* QskColorCircleSkinlet::updateSubNode( + const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const +{ + const auto* const q = static_cast< const Q* >( skinnable ); + + if ( nodeRole == CircleRole ) + { + const auto gradient = q->gradientHint( Q::Circle ); + auto* const circle = QskSGNode::ensureNode< QskCircleNode >( node ); + circle->update( q->contentsRect(), gradient ); + return circle; + } + + if ( nodeRole == HandleRole ) + { + return updateBoxNode( skinnable, node, Q::Handle ); + } + + return QskSkinlet::updateSubNode( skinnable, nodeRole, node ); +} + +QSizeF QskColorCircleSkinlet::sizeHint( + const QskSkinnable* skinnable, Qt::SizeHint which, const QSizeF& ) const +{ + if ( which != Qt::PreferredSize ) + { + return {}; + } + + const auto* const q = static_cast< const Q* >( skinnable ); + const auto rect = q->contentsRect(); + const auto size = qMin( rect.width(), rect.height() ); + return { size, size }; +} \ No newline at end of file diff --git a/examples/gallery/colorpickers/QskColorCircleSkinlet.h b/examples/gallery/colorpickers/QskColorCircleSkinlet.h new file mode 100644 index 000000000..e6d592a05 --- /dev/null +++ b/examples/gallery/colorpickers/QskColorCircleSkinlet.h @@ -0,0 +1,29 @@ +#ifndef QSK_COLOR_CIRCLE_SKINLET_H_ +#define QSK_COLOR_CIRCLE_SKINLET_H_ + +#include + +class QskColorCircleSkinlet : public QskSkinlet +{ + Q_GADGET + public: + enum NodeRoles + { + CircleRole, + HandleRole + }; + + Q_INVOKABLE QskColorCircleSkinlet( QskSkin* skin = nullptr ); + + private: + QRectF subControlRect( const QskSkinnable* skinnable, const QRectF&, + QskAspect::Subcontrol subcontrol ) const override; + + QSGNode* updateSubNode( + const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const override; + + QSizeF sizeHint( + const QskSkinnable* skinnable, Qt::SizeHint which, const QSizeF& ) const override; +}; + +#endif \ No newline at end of file diff --git a/examples/gallery/colorpickers/QskColorRectangle.cpp b/examples/gallery/colorpickers/QskColorRectangle.cpp new file mode 100644 index 000000000..b079fd6f4 --- /dev/null +++ b/examples/gallery/colorpickers/QskColorRectangle.cpp @@ -0,0 +1,128 @@ +#include "QskColorRectangle.h" + +#include +#include +#include + +#include + +QSK_SUBCONTROL( QskColorRectangle, Panel ) +QSK_SUBCONTROL( QskColorRectangle, Handle ) + +namespace +{ + double qskMapValueRange( + double value, double srcMin, double srcMax, double dstMin, double dstMax ) + { + value = std::min( std::max( value, srcMin ), srcMax ); + double percentage = ( value - srcMin ) / ( srcMax - srcMin ); + double mappedValue = dstMin + percentage * ( dstMax - dstMin ); + return mappedValue; + } + + QColor qskInterpolatedColor( const QColor& topLeft, const QColor& topRight, + const QColor& bottomLeft, const QColor& bottomRight, const QPointF& position ) + { + const auto x = position.x(); + const auto y = position.y(); + + const QColor topInterpolated( int( ( 1.0 - x ) * topLeft.red() + x * topRight.red() ), + int( ( 1.0 - x ) * topLeft.green() + x * topRight.green() ), + int( ( 1.0 - x ) * topLeft.blue() + x * topRight.blue() ) ); + + const QColor bottomInterpolated( + int( ( 1.0 - x ) * bottomLeft.red() + x * bottomRight.red() ), + int( ( 1.0 - x ) * bottomLeft.green() + x * bottomRight.green() ), + int( ( 1.0 - x ) * bottomLeft.blue() + x * bottomRight.blue() ) ); + + return { int( ( 1.0 - y ) * topInterpolated.red() + y * bottomInterpolated.red() ), + int( ( 1.0 - y ) * topInterpolated.green() + y * bottomInterpolated.green() ), + int( ( 1.0 - y ) * topInterpolated.blue() + y * bottomInterpolated.blue() ) }; + } +} + +QskColorRectangle::QskColorRectangle( QQuickItem* parent ) + : QskControl( parent ) +{ + setAcceptedMouseButtons( Qt::LeftButton ); + + setBoxBorderColorsHint( Handle, Qt::white ); + setBoxBorderMetricsHint( Handle, 2 ); + setBoxShapeHint( Handle, { 100, 100, 100, 100, Qt::AbsoluteSize } ); + setStrutSizeHint( Handle, { 20, 20 } ); + setSkinHint( Handle, QPointF{ 50, 50 } ); +} + +QColor QskColorRectangle::selectedColor() const +{ + return m_selectedColor; +} + +void QskColorRectangle::setCornerColor( const Qt::Corner corner, const QColor& color ) +{ + switch ( corner ) + { + case Qt::TopLeftCorner: + case Qt::TopRightCorner: + case Qt::BottomLeftCorner: + case Qt::BottomRightCorner: + m_cornerColor[ corner ] = color; + update(); + break; + default: + break; + } +} + +QColor QskColorRectangle::cornerColor( const Qt::Corner corner ) const +{ + switch ( corner ) + { + case Qt::TopLeftCorner: + case Qt::TopRightCorner: + case Qt::BottomLeftCorner: + case Qt::BottomRightCorner: + return m_cornerColor[ corner ]; + default: + return {}; + } +} + +void QskColorRectangle::mousePressEvent( QMouseEvent* event ) +{ +} + +void QskColorRectangle::mouseReleaseEvent( QMouseEvent* event ) +{ +} + +void QskColorRectangle::mouseMoveEvent( QMouseEvent* event ) +{ + const auto r = subControlRect( Panel ); + if ( r.contains( event->pos() ) ) + { + const auto ux = qskMapValueRange( event->pos().x(), r.left(), r.right(), 0, 1 ); + const auto uy = qskMapValueRange( event->pos().y(), r.top(), r.bottom(), 0, 1 ); + const auto uv = QPointF{ ux, uy }; + + const auto color = qskInterpolatedColor( m_cornerColor[ Qt::TopLeftCorner ], + m_cornerColor[ Qt::TopRightCorner ], m_cornerColor[ Qt::BottomLeftCorner ], + m_cornerColor[ Qt::BottomRightCorner ], uv ); + + setColor( Handle, color ); + setSkinHint( Handle, uv ); + setSelectedColor( color ); + } +} + +void QskColorRectangle::setSelectedColor( const QColor& color ) +{ + if ( m_selectedColor != color ) + { + m_selectedColor = color; + update(); + Q_EMIT selectedColorChanged( m_selectedColor ); + } +} + +#include "moc_QskColorRectangle.cpp" \ No newline at end of file diff --git a/examples/gallery/colorpickers/QskColorRectangle.h b/examples/gallery/colorpickers/QskColorRectangle.h new file mode 100644 index 000000000..551299b53 --- /dev/null +++ b/examples/gallery/colorpickers/QskColorRectangle.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +class QskColorRectangle : public QskControl +{ + Q_OBJECT + Q_PROPERTY( QColor selectedColor READ selectedColor NOTIFY selectedColorChanged ) + + public: + QSK_SUBCONTROLS( Panel, Handle ) + explicit QskColorRectangle( QQuickItem* parent = nullptr ); + + QColor selectedColor() const; + + void setCornerColor( Qt::Corner corner, const QColor& color ); + QColor cornerColor( Qt::Corner corner ) const; + + protected: + void mousePressEvent( QMouseEvent* event ) override; + void mouseReleaseEvent( QMouseEvent* event ) override; + void mouseMoveEvent( QMouseEvent* event ) override; + + Q_SIGNALS: + void selectedColorChanged( const QColor& color ); + + private: + void setSelectedColor( const QColor& color ); + + QColor m_selectedColor; + QColor m_cornerColor[ 4 ]; +}; \ No newline at end of file diff --git a/examples/gallery/colorpickers/QskColorRectangleSkinlet.cpp b/examples/gallery/colorpickers/QskColorRectangleSkinlet.cpp new file mode 100644 index 000000000..88c4b2632 --- /dev/null +++ b/examples/gallery/colorpickers/QskColorRectangleSkinlet.cpp @@ -0,0 +1,102 @@ +#include "QskColorRectangleSkinlet.h" +#include "QskColorRectangle.h" + +#include +#include +#include + +#include +#include + +using Q = QskColorRectangle; + +namespace +{ + class QskVertexColoredRectangle : public QSGGeometryNode + { + public: + QskVertexColoredRectangle( const QColor& topLeft = {}, const QColor& topRight = {}, + const QColor& bottomLeft = {}, const QColor& bottomRight = {} ) + { + auto* const geometry = + new QSGGeometry( QSGGeometry::defaultAttributes_ColoredPoint2D(), 4 ); + geometry->setDrawingMode( QSGGeometry::DrawTriangleStrip ); + setGeometry( geometry ); + + auto* const material = new QSGVertexColorMaterial; + setMaterial( material ); + + update( {}, topLeft, topRight, bottomLeft, bottomRight ); + } + + void update( const QRectF& rect, const QColor& topLeft = {}, const QColor& topRight = {}, + const QColor& bottomLeft = {}, const QColor& bottomRight = {} ) + { + auto* const vertices = geometry()->vertexDataAsColoredPoint2D(); + vertices[ 0 ].set( rect.left(), rect.top(), topLeft.red(), topLeft.green(), + topLeft.blue(), topLeft.alpha() ); + vertices[ 1 ].set( rect.right(), rect.top(), topRight.red(), topRight.green(), + topRight.blue(), topRight.alpha() ); + vertices[ 2 ].set( rect.left(), rect.bottom(), bottomLeft.red(), bottomLeft.green(), + bottomLeft.blue(), bottomLeft.alpha() ); + vertices[ 3 ].set( rect.right(), rect.bottom(), bottomRight.red(), bottomRight.green(), + bottomRight.blue(), bottomRight.alpha() ); + + markDirty( QSGNode::DirtyGeometry ); + markDirty( QSGNode::DirtyMaterial ); + } + }; +} + +QskColorRectangleSkinlet::QskColorRectangleSkinlet( QskSkin* skin ) + : QskSkinlet( skin ) +{ + setNodeRoles( { PanelRole, HandleRole } ); +} + +QRectF QskColorRectangleSkinlet::subControlRect( const QskSkinnable* skinnable, + const QRectF& contentsRect, QskAspect::Subcontrol subcontrol ) const +{ + const auto* const q = static_cast< const Q* >( skinnable ); + if ( subcontrol == Q::Panel ) + { + return contentsRect; + } + if ( subcontrol == Q::Handle ) + { + const auto size = q->strutSizeHint( Q::Handle ); + const auto w = qMax( 0.0, size.width() ) / 2; + const auto h = qMax( 0.0, size.height() ) / 2; + + auto p = q->effectiveSkinHint( Q::Handle ).toPointF(); + p.rx() *= contentsRect.width(); + p.ry() *= contentsRect.height(); + p.rx() += contentsRect.left(); + p.ry() += contentsRect.top(); + + return { QPointF{ p.x() - w, p.y() - h }, QPointF{ p.x() + w, p.y() + h } }; + } + return QskSkinlet::subControlRect( skinnable, contentsRect, subcontrol ); +} + +QSGNode* QskColorRectangleSkinlet::updateSubNode( + const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const +{ + const auto* const q = static_cast< const Q* >( skinnable ); + + if ( nodeRole == PanelRole ) + { + auto* const rectangleNode = QskSGNode::ensureNode< QskVertexColoredRectangle >( node ); + rectangleNode->update( q->subControlRect( Q::Panel ), q->cornerColor( Qt::TopLeftCorner ), + q->cornerColor( Qt::TopRightCorner ), q->cornerColor( Qt::BottomLeftCorner ), + q->cornerColor( Qt::BottomRightCorner ) ); + return rectangleNode; + } + + if ( nodeRole == HandleRole ) + { + return updateBoxNode( skinnable, node, Q::Handle ); + } + + return QskSkinlet::updateSubNode( skinnable, nodeRole, node ); +} diff --git a/examples/gallery/colorpickers/QskColorRectangleSkinlet.h b/examples/gallery/colorpickers/QskColorRectangleSkinlet.h new file mode 100644 index 000000000..75ddee316 --- /dev/null +++ b/examples/gallery/colorpickers/QskColorRectangleSkinlet.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +class QskColorRectangleSkinlet : public QskSkinlet +{ + Q_GADGET + public: + enum NodeRoles + { + PanelRole, + HandleRole, + RoleCount + }; + Q_INVOKABLE QskColorRectangleSkinlet( QskSkin* skin = nullptr ); + + private: + QRectF subControlRect( const QskSkinnable* skinnable, const QRectF& contentsRect, + QskAspect::Subcontrol subcontrol ) const; + + QSGNode* updateSubNode( const QskSkinnable* skinnable, + quint8 nodeRole, QSGNode* node) const; +}; \ No newline at end of file diff --git a/examples/gallery/colorpickers/QskColorTriangle.cpp b/examples/gallery/colorpickers/QskColorTriangle.cpp new file mode 100644 index 000000000..cc599fd81 --- /dev/null +++ b/examples/gallery/colorpickers/QskColorTriangle.cpp @@ -0,0 +1,159 @@ +#include "QskColorTriangle.h" + +#include +#include +#include +#include + +#include +#include + +namespace +{ + QVector3D cartesian2barycentric( + const QPointF& p, const QPointF& t1, const QPointF& t2, const QPointF& t3 ) + { + const auto x = p.x(); + const auto y = p.y(); + + const auto x1 = t1.x(); + const auto y1 = t1.y(); + const auto x2 = t2.x(); + const auto y2 = t2.y(); + const auto x3 = t3.x(); + const auto y3 = t3.y(); + + const auto y2y3 = y2 - y3, x3x2 = x3 - x2, x1x3 = x1 - x3, y1y3 = y1 - y3, y3y1 = y3 - y1, + xx3 = x - x3, yy3 = y - y3; + + const auto d = y2y3 * x1x3 + x3x2 * y1y3, lambda1 = ( y2y3 * xx3 + x3x2 * yy3 ) / d, + lambda2 = ( y3y1 * xx3 + x1x3 * yy3 ) / d; + + return { ( float ) lambda1, ( float ) lambda2, ( float ) ( 1.0 - lambda1 - lambda2 ) }; + } + + QColor qskColorInTriangle( const QPointF& p, const QPointF& t1, const QPointF& t2, + const QPointF& t3, const QColor& c1, const QColor& c2, const QColor& c3 ) + { + const auto t = cartesian2barycentric( p, t1, t2, t3 ); + const auto r = + std::floor( ( c1.redF() * t[ 0 ] + c2.redF() * t[ 1 ] + c3.redF() * t[ 2 ] ) * 255.0 ); + const auto g = std::floor( + ( c1.greenF() * t[ 0 ] + c2.greenF() * t[ 1 ] + c3.greenF() * t[ 2 ] ) * 255.0 ); + const auto b = std::floor( + ( c1.blueF() * t[ 0 ] + c2.blueF() * t[ 1 ] + c3.blueF() * t[ 2 ] ) * 255.0 ); + + if ( t[ 0 ] < 0.0 || t[ 1 ] < 0.0 || t[ 2 ] < 0.0 ) + { + return {}; + } + + return { int( r ), int( g ), int( b ) }; + } +} + +QSK_SUBCONTROL( QskColorTriangle, Triangle ) +QSK_SUBCONTROL( QskColorTriangle, Handle ) + +QskColorTriangle::QskColorTriangle( QQuickItem* parent ) + : QskControl( parent ) +{ + setAcceptedMouseButtons( Qt::LeftButton ); + + setStrutSizeHint( Handle, { 20, 20 } ); + setColor( Handle, Qt::white ); + setBoxBorderColorsHint( Handle, { Qt::white } ); + setBoxBorderMetricsHint( Handle, { 2 } ); + setBoxShapeHint( Handle, { 100.0, 100.0, 100.0, 100.0, Qt::RelativeSize } ); + setSkinHint( Handle, QPointF{} ); +} + +QColor QskColorTriangle::selectedColor() const +{ + return m_selectedColor; +} + +void QskColorTriangle::setSelectedColor( const QColor& color ) +{ + if ( m_selectedColor != color ) + { + m_selectedColor = color; + setColor( Handle, color ); + Q_EMIT selectedColorChanged( m_selectedColor ); + } +} + +void QskColorTriangle::setColors( const QColor& c1, const QColor& c2, const QColor& c3 ) +{ + m_c1 = c1; + m_c2 = c2; + m_c3 = c3; + update(); +} + +QColor QskColorTriangle::color1() const +{ + return m_c1; +} + +QColor QskColorTriangle::color2() const +{ + return m_c2; +} + +QColor QskColorTriangle::color3() const +{ + return m_c3; +} + +void QskColorTriangle::mousePressEvent( QMouseEvent* event ) +{ +} + +void QskColorTriangle::mouseReleaseEvent( QMouseEvent* event ) +{ +} + +void QskColorTriangle::mouseMoveEvent( QMouseEvent* event ) +{ + const auto rect = subControlRect( Triangle ); + if ( rect.contains( event->pos() ) ) + { + static constexpr auto step = 2 * M_PI / 3; + const auto angle = positionHint( Triangle ); + const auto radius = qMin( rect.width(), rect.height() ) / 2; + QTransform transformation; + transformation.rotate( qRadiansToDegrees( angle ), Qt::ZAxis ); + + // TODO affine + + auto t1 = QPointF{ qFastCos( 0 * step ), qFastSin( 0 * step ) }; + auto t2 = QPointF{ qFastCos( 1 * step ), qFastSin( 1 * step ) }; + auto t3 = QPointF{ qFastCos( 2 * step ), qFastSin( 2 * step ) }; + + t1 = transformation.map( t1 ); + t2 = transformation.map( t2 ); + t3 = transformation.map( t3 ); + + t1 *= radius; + t2 *= radius; + t3 *= radius; + + t1 += rect.center(); + t2 += rect.center(); + t3 += rect.center(); + + const auto color = qskColorInTriangle( event->pos(), t1, t2, t3, m_c1, m_c2, m_c3 ); + if ( color.isValid() ) + { + setSelectedColor( color ); + + const auto x = qskMapValueRange( event->pos().x(), rect.left(), rect.right(), -1, +1 ); + const auto y = qskMapValueRange( event->pos().y(), rect.top(), rect.bottom(), +1, -1 ); + setSkinHint( Handle, QPointF{ x, y } ); + } + } + event->ignore(); +} + +#include "moc_QskColorTriangle.cpp" \ No newline at end of file diff --git a/examples/gallery/colorpickers/QskColorTriangle.h b/examples/gallery/colorpickers/QskColorTriangle.h new file mode 100644 index 000000000..2f3962b54 --- /dev/null +++ b/examples/gallery/colorpickers/QskColorTriangle.h @@ -0,0 +1,40 @@ +#ifndef QSK_COLOR_TRIANGLE_H_ +#define QSK_COLOR_TRIANGLE_H_ + +#include + +class QskColorTriangle : public QskControl +{ + Q_OBJECT + Q_PROPERTY( + QColor selectedColor READ selectedColor WRITE setSelectedColor NOTIFY selectedColorChanged ) + + public: + QSK_SUBCONTROLS( Triangle, Handle ) + explicit QskColorTriangle( QQuickItem* parent = nullptr ); + + QColor selectedColor() const; + + void setColors( const QColor& c1, const QColor& c2, const QColor& c3 ); + QColor color1() const; + QColor color2() const; + QColor color3() const; + + Q_SIGNALS: + void selectedColorChanged( const QColor& color ); + + protected: + void mousePressEvent( QMouseEvent* event ) override; + void mouseReleaseEvent( QMouseEvent* event ) override; + void mouseMoveEvent( QMouseEvent* event ) override; + + private: + void setSelectedColor( const QColor& color ); + + QColor m_selectedColor; + QColor m_c1 = Qt::transparent; + QColor m_c2 = Qt::black; + QColor m_c3 = Qt::white; +}; + +#endif \ No newline at end of file diff --git a/examples/gallery/colorpickers/QskColorTriangleNode.cpp b/examples/gallery/colorpickers/QskColorTriangleNode.cpp new file mode 100644 index 000000000..28a609d6e --- /dev/null +++ b/examples/gallery/colorpickers/QskColorTriangleNode.cpp @@ -0,0 +1,42 @@ +#include "QskColorTriangleNode.h" +#include + +namespace +{ + static constexpr auto vertexCount = 3; +} + +QskColorTriangleNode::QskColorTriangleNode() +{ + auto* const geometry = + new QSGGeometry( QSGGeometry::defaultAttributes_ColoredPoint2D(), vertexCount ); + geometry->setDrawingMode( QSGGeometry::DrawTriangles ); + setGeometry( geometry ); + + auto* const material = new QSGVertexColorMaterial; + setMaterial( material ); +} + +void QskColorTriangleNode::update( + const QRectF& rect, const QColor& c1, const QColor& c2, const QColor& c3, const qreal rad ) +{ + const QColor colors[]{ c1, c2, c3 }; + + const auto center = rect.center(); + const auto radius = qMin( rect.width(), rect.height() ) / 2; + + const auto step = 2 * M_PI / 3; + + auto* const vertices = geometry()->vertexDataAsColoredPoint2D(); + for ( int i = 0; i < vertexCount; ++i ) + { + const auto x = qFastCos( rad + i * step ) * radius + center.x(); + const auto y = qFastSin( rad + i * step ) * radius + center.y(); + const uchar c[ 4 ]{ ( uchar ) colors[ i ].red(), ( uchar ) colors[ i ].green(), + ( uchar ) colors[ i ].blue(), 255 }; + vertices[ i ].set( x, y, c[ 0 ], c[ 1 ], c[ 2 ], c[ 3 ] ); + } + geometry()->markVertexDataDirty(); + markDirty( QSGNode::DirtyGeometry ); + markDirty( QSGNode::DirtyMaterial ); +} \ No newline at end of file diff --git a/examples/gallery/colorpickers/QskColorTriangleNode.h b/examples/gallery/colorpickers/QskColorTriangleNode.h new file mode 100644 index 000000000..d8a04b453 --- /dev/null +++ b/examples/gallery/colorpickers/QskColorTriangleNode.h @@ -0,0 +1,19 @@ +#ifndef QSK_COLOR_TRIANGLE_NODE_H_ +#define QSK_COLOR_TRIANGLE_NODE_H_ + +#include +#include +#include + +class QskColorTriangleNode : public QSGGeometryNode +{ + using rad = qreal; + + public: + QskColorTriangleNode(); + + void update( + const QRectF& rect, const QColor& c1, const QColor& c2, const QColor& c3, rad angle ); +}; + +#endif \ No newline at end of file diff --git a/examples/gallery/colorpickers/QskColorTriangleSkinlet.cpp b/examples/gallery/colorpickers/QskColorTriangleSkinlet.cpp new file mode 100644 index 000000000..c34855868 --- /dev/null +++ b/examples/gallery/colorpickers/QskColorTriangleSkinlet.cpp @@ -0,0 +1,87 @@ +#include "QskColorTriangleSkinlet.h" +#include "QskColorTriangle.h" +#include "QskColorTriangleNode.h" +#include +#include + +using Q = QskColorTriangle; + +QskColorTriangleSkinlet::QskColorTriangleSkinlet( QskSkin* skin ) + : QskSkinlet( skin ) +{ + setNodeRoles( { TriangleRole, HandleRole } ); +} + +QRectF QskColorTriangleSkinlet::subControlRect( const QskSkinnable* skinnable, + const QRectF& contentsRect, QskAspect::Subcontrol subcontrol ) const +{ + const auto* const q = static_cast< const Q* >( skinnable ); + + const auto& r = contentsRect; + + if ( subcontrol == Q::Triangle ) + { + const auto size = ( qMin( r.width(), contentsRect.height() ) ) / 2; + auto rect = QRectF( 0, 0, size, size ); + + return { QPointF{ r.center().x() - size, r.center().y() - size }, + QPointF{ r.center().x() + size, r.center().y() + size } }; + } + + if ( subcontrol == Q::Handle ) + { + const auto rect = subControlRect( skinnable, contentsRect, Q::Triangle ); + const auto size = + qMin( q->strutSizeHint( Q::Handle ).width(), q->strutSizeHint( Q::Handle ).height() ) / 2; + const auto position = q->effectiveSkinHint( Q::Handle ).toPointF(); + const auto radius = qMin( rect.width(), rect.height() ) / 2; + + if ( position == QPoint{} ) + { + return {}; + } + + QTransform t; + t.rotate( qRadiansToDegrees( q->positionHint( Q::Triangle ) ), Qt::ZAxis ); + const auto c = t.map( QPointF{ position.x(), -1 * position.y() } ) * radius + rect.center(); + + return { QPointF{ c.x() - size, c.y() - size }, QPointF{ c.x() + size, c.y() + size } }; + } + return QskSkinlet::subControlRect( skinnable, contentsRect, subcontrol ); +} + +QSGNode* QskColorTriangleSkinlet::updateSubNode( + const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const +{ + const auto* const q = static_cast< const Q* >( skinnable ); + + if ( nodeRole == TriangleRole ) + { + const auto subcontrol = Q::Triangle; + const auto rect = subControlRect( skinnable, q->contentsRect(), subcontrol ); + auto* const triangleNode = QskSGNode::ensureNode< QskColorTriangleNode >( node ); + triangleNode->update( + rect, q->color1(), q->color2(), q->color3(), q->positionHint( Q::Triangle ) ); + return triangleNode; + } + if ( nodeRole == HandleRole ) + { + return updateBoxNode( skinnable, node, Q::Handle ); + } + + return QskSkinlet::updateSubNode( skinnable, nodeRole, node ); +} + +QSizeF QskColorTriangleSkinlet::sizeHint( + const QskSkinnable* skinnable, Qt::SizeHint which, const QSizeF& ) const +{ + if ( which != Qt::PreferredSize ) + { + return {}; + } + + const auto* const q = static_cast< const QskColorTriangle* >( skinnable ); + const auto rect = q->contentsRect(); + const auto size = qMin( rect.width(), rect.height() ); + return { size, size }; +} \ No newline at end of file diff --git a/examples/gallery/colorpickers/QskColorTriangleSkinlet.h b/examples/gallery/colorpickers/QskColorTriangleSkinlet.h new file mode 100644 index 000000000..fe7929c65 --- /dev/null +++ b/examples/gallery/colorpickers/QskColorTriangleSkinlet.h @@ -0,0 +1,29 @@ +#ifndef QSK_COLOR_TRIANGLE_SKINLET_H_ +#define QSK_COLOR_TRIANGLE_SKINLET_H_ + +#include + +class QskColorTriangleSkinlet : public QskSkinlet +{ + Q_GADGET + public: + enum NodeRoles + { + TriangleRole, + HandleRole + }; + + Q_INVOKABLE QskColorTriangleSkinlet( QskSkin* skin = nullptr ); + + private: + QRectF subControlRect( + const QskSkinnable* skinnable, const QRectF&, QskAspect::Subcontrol ) const override; + + QSGNode* updateSubNode( + const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* ) const override; + + QSizeF sizeHint( + const QskSkinnable* skinnable, Qt::SizeHint which, const QSizeF& ) const override; +}; + +#endif diff --git a/examples/gallery/colorpickers/QskColorWheel.cpp b/examples/gallery/colorpickers/QskColorWheel.cpp new file mode 100644 index 000000000..5e02cf9c9 --- /dev/null +++ b/examples/gallery/colorpickers/QskColorWheel.cpp @@ -0,0 +1,94 @@ +#include "QskColorWheel.h" +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + QColor qskColorAt( const QskGradient& gradient, qreal ratio ) + { + return gradient.extracted( ratio, ratio ).startColor(); + } + + QColor qskColorForAngle( const QskGradient& gradient, qreal angle ) + { + angle = ( angle < 0.0 ? angle + 2 * M_PI : angle ); + const auto ratio = qAbs( angle / ( 2 * M_PI ) ); + return qskColorAt( gradient, ratio ); + } +} + +QSK_SUBCONTROL( QskColorWheel, Arc ) +QSK_SUBCONTROL( QskColorWheel, Handle ) + +QskColorWheel::QskColorWheel( QQuickItem* parent ) + : QskControl( parent ) +{ + setAcceptedMouseButtons( Qt::LeftButton ); + + static const QskGradientStops stops = { + { 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( stops ); + setGradientHint( Arc, gradient ); + setArcMetricsHint( Arc, { 0, 360, 16 } ); + + setColor( Handle, Qt::white ); + setBoxBorderColorsHint( Handle, { Qt::white } ); + setBoxBorderMetricsHint( Handle, { 2 } ); + setBoxShapeHint( Handle, { 100.0, 100.0, 100.0, 100.0, Qt::RelativeSize } ); +} + +QColor QskColorWheel::selectedColor() const +{ + return m_selectedColor; +} + +void QskColorWheel::setSelectedColor( const QColor& color ) +{ + if ( m_selectedColor != color ) + { + m_selectedColor = color; + setColor( Handle, color ); + Q_EMIT selectedColorChanged( m_selectedColor ); + } +} + +void QskColorWheel::mousePressEvent( QMouseEvent* event ) +{ +} + +void QskColorWheel::mouseReleaseEvent( QMouseEvent* event ) +{ +} + +void QskColorWheel::mouseMoveEvent( QMouseEvent* event ) +{ + const auto r = contentsRect(); + const auto ux = qskMapValueRange( event->pos().x(), r.left(), r.right(), -1, +1 ); + const auto uy = qskMapValueRange( event->pos().y(), r.top(), r.bottom(), +1, -1 ); + + if ( QVector2D( ux, uy ).length() > 1.0 ) // TODO && Pressed + { + event->ignore(); + return; + } + + const auto rad = qAtan2( uy, ux ); + const auto color = qskColorForAngle( gradientHint( Arc ), rad ); + setPositionHint( Handle, -rad ); + setSelectedColor( color ); +} + +#include "moc_QskColorWheel.cpp" \ No newline at end of file diff --git a/examples/gallery/colorpickers/QskColorWheel.h b/examples/gallery/colorpickers/QskColorWheel.h new file mode 100644 index 000000000..18fa9fa2d --- /dev/null +++ b/examples/gallery/colorpickers/QskColorWheel.h @@ -0,0 +1,30 @@ +#ifndef QSK_COLOR_WHEEL_H_ +#define QSK_COLOR_WHEEL_H_ + +#include + +class QskColorWheel : public QskControl +{ + Q_OBJECT + Q_PROPERTY( + QColor selectedColor READ selectedColor WRITE setSelectedColor NOTIFY selectedColorChanged ) + public: + QSK_SUBCONTROLS( Arc, Handle ) + explicit QskColorWheel( QQuickItem* parent = nullptr ); + + QColor selectedColor() const; + void setSelectedColor( const QColor& color ); + + protected: + void mousePressEvent( QMouseEvent* event ) override; + void mouseReleaseEvent( QMouseEvent* event ) override; + void mouseMoveEvent( QMouseEvent* event ) override; + + Q_SIGNALS: + void selectedColorChanged( const QColor& color ); + + private: + QColor m_selectedColor; +}; + +#endif \ No newline at end of file diff --git a/examples/gallery/colorpickers/QskColorWheelSkinlet.cpp b/examples/gallery/colorpickers/QskColorWheelSkinlet.cpp new file mode 100644 index 000000000..d57148ad2 --- /dev/null +++ b/examples/gallery/colorpickers/QskColorWheelSkinlet.cpp @@ -0,0 +1,71 @@ +#include "QskColorWheelSkinlet.h" +#include "QskColorWheel.h" + +#include +#include + +using Q = QskColorWheel; + +QskColorWheelSkinlet::QskColorWheelSkinlet( QskSkin* skin ) + : QskSkinlet( skin ) +{ + setNodeRoles( { ArcRole, HandleRole } ); +} + +QRectF QskColorWheelSkinlet::subControlRect( const QskSkinnable* skinnable, + const QRectF& contentsRect, QskAspect::Subcontrol subcontrol ) const +{ + const auto* const q = static_cast< const Q* >( skinnable ); + + if ( subcontrol == Q::Arc ) + { + const auto size = qMin( contentsRect.width(), contentsRect.height() ); + return { contentsRect.center().x() - size / 2, contentsRect.center().y() - size / 2, size, + size }; + } + + if ( subcontrol == Q::Handle ) + { + const auto& r = contentsRect; + const auto thickness = skinnable->arcMetricsHint( Q::Arc ).thickness() / 2; + const auto angle = q->positionHint( Q::Handle ); + const auto radius = qMin( r.width(), r.height() ) / 2 - thickness; + const auto x = contentsRect.center().x() + radius * qFastCos( angle ); + const auto y = contentsRect.center().y() + radius * qFastSin( angle ); + return { QPointF{ x - thickness, y - thickness }, QPointF{ x + thickness, y + thickness } }; + } + + return QskSkinlet::subControlRect( skinnable, contentsRect, subcontrol ); +} + +QSGNode* QskColorWheelSkinlet::updateSubNode( + const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const +{ + const auto* const q = static_cast< const Q* >( skinnable ); + + if ( nodeRole == ArcRole ) + { + return updateArcNode( skinnable, node, Q::Arc ); + } + + if ( nodeRole == HandleRole ) + { + return updateBoxNode( skinnable, node, Q::Handle ); + } + + return QskSkinlet::updateSubNode( skinnable, nodeRole, node ); +} + +QSizeF QskColorWheelSkinlet::sizeHint( + const QskSkinnable* skinnable, Qt::SizeHint which, const QSizeF& ) const +{ + if ( which != Qt::PreferredSize ) + { + return {}; + } + + const auto* const q = static_cast< const Q* >( skinnable ); + const auto rect = q->contentsRect(); + const auto size = qMin( rect.width(), rect.height() ); + return { size, size }; +} \ No newline at end of file diff --git a/examples/gallery/colorpickers/QskColorWheelSkinlet.h b/examples/gallery/colorpickers/QskColorWheelSkinlet.h new file mode 100644 index 000000000..04f74dc1a --- /dev/null +++ b/examples/gallery/colorpickers/QskColorWheelSkinlet.h @@ -0,0 +1,30 @@ +#ifndef QSK_COLOR_WHEEL_SKINLET_H_ +#define QSK_COLOR_WHEEL_SKINLET_H_ + +#include +#include + +class QskColorWheelSkinlet : public QskSkinlet +{ + Q_GADGET + public: + enum NodeRoles + { + ArcRole, + HandleRole + }; + + Q_INVOKABLE QskColorWheelSkinlet( QskSkin* skin = nullptr ); + + private: + QRectF subControlRect( + const QskSkinnable* skinnable, const QRectF&, QskAspect::Subcontrol ) const override; + + QSGNode* updateSubNode( + const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* ) const override; + + QSizeF sizeHint( + const QskSkinnable* skinnable, Qt::SizeHint which, const QSizeF& ) const override; +}; + +#endif \ No newline at end of file diff --git a/examples/gallery/dialog/DialogPage.cpp b/examples/gallery/dialog/DialogPage.cpp index 0e9e79460..316cdc8d7 100644 --- a/examples/gallery/dialog/DialogPage.cpp +++ b/examples/gallery/dialog/DialogPage.cpp @@ -4,12 +4,26 @@ *****************************************************************************/ #include "DialogPage.h" - +#include "QskBox.h" +#include "QskBoxBorderColors.h" +#include "QskControl.h" +#include "QskGradient.h" +#include "QskGridBox.h" +#include "QskSizePolicy.h" +#include "QskSlider.h" +#include "QskTextLabel.h" + +#include +#include #include #include #include #include -#include +#include +#include +#include +#include +#include namespace { @@ -51,26 +65,27 @@ namespace auto selectButton = new Button( "Selection", this ); connect( selectButton, &Button::clicked, this, &ButtonBox::execSelection ); + auto colortButton = new Button( "Color", this ); + connect( colortButton, &Button::clicked, this, [ this ]() { execColor( window() ); } ); + setExtraSpacingAt( Qt::BottomEdge ); } private: void execMessage() { - qskDialog->message( "Message", "Request vector, over.", - QskStandardSymbol::NoSymbol, QskDialog::Close ); + qskDialog->message( + "Message", "Request vector, over.", QskStandardSymbol::NoSymbol, QskDialog::Close ); } void execInformation() { - qskDialog->information( "Information", - "We have clearance, Clarence." ); + qskDialog->information( "Information", "We have clearance, Clarence." ); } void execQuestion() { - qskDialog->question( "Question", - "Roger, Roger. Do we have a vector, Victor ?" ); + qskDialog->question( "Question", "Roger, Roger. Do we have a vector, Victor ?" ); } void execWarning() @@ -86,30 +101,148 @@ namespace void execSelection() { // of course we all love "The Teens" - const QStringList entries = - { - "Give Me More", - "Gimme Gimme Your Love", - "1-2-3-4 Red Light", - "New York", - "If You Walk Away", - "Eloise", - "On The Radio", - "We Are The Teens", - "Never Gonna Tell No Lie To You", - "Walking On Sunshine ", - "Get Out Of My Mind", - "Cover Girl ", - "Here I Stand", - "Gypsy Caravan", - "It´s Good To Have A Friend", - "We´ll Have A Party Tonite ´nite", - "Automatic World", - "Gimme Gimme Gimme Gimme Gimme Your Love" - }; + const QStringList entries = { "Give Me More", "Gimme Gimme Your Love", + "1-2-3-4 Red Light", "New York", "If You Walk Away", "Eloise", "On The Radio", + "We Are The Teens", "Never Gonna Tell No Lie To You", "Walking On Sunshine ", + "Get Out Of My Mind", "Cover Girl ", "Here I Stand", "Gypsy Caravan", + "It´s Good To Have A Friend", "We´ll Have A Party Tonite ´nite", "Automatic World", + "Gimme Gimme Gimme Gimme Gimme Your Love" }; qskDialog->select( "Here we go ...", "Hot Hot Hot", entries, 7 ); } + + void execColor( QQuickWindow* window ) + { + auto* const layout = new QskLinearBox( Qt::Vertical, window->contentItem() ); + layout->setPanel( true ); + auto* const tabBar = new QskTabView( layout ); + tabBar->addTab( "Sliders", [ window ]() { + auto* const layout = new QskGridBox( window->contentItem() ); + auto* const color = new QskSlider( Qt::Horizontal ); + auto* const brightness = new QskSlider( Qt::Horizontal ); + auto* const grayscale = new QskSlider( Qt::Horizontal ); + auto* const alpha = new QskSlider( Qt::Horizontal ); + auto* const preview = new QskBox; + auto* const text = new QskTextLabel( "#00000000" ); + preview->setPanel( true ); + alpha->setValue( 1.0 ); + layout->addItem( preview, 0, 0, 4, 1 ); + layout->addItem( color, 0, 1 ); + layout->addItem( grayscale, 1, 1 ); + layout->addItem( brightness, 2, 1 ); + layout->addItem( alpha, 3, 1 ); + layout->addItem( text, 4, 0 ); + + preview->setSizePolicy( Qt::Vertical, QskSizePolicy::ConstrainedMinimum ); + text->setAlignmentHint( QskTextLabel::Text, Qt::AlignHCenter | Qt::AlignVCenter ); + + static const QskGradientStops stopsRGB = { + { 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 ) }, + }; + + static const QskGradientStops stopsGrayscale = { + { 0.0000, Qt::black }, + { 1.0000, Qt::white }, + }; + + QskGradient gradient; + gradient.setLinearDirection( Qt::Horizontal ); + gradient.setStops( stopsRGB ); + + { + QskGradient gradient; + gradient.setLinearDirection( Qt::Horizontal ); + gradient.setStops( stopsGrayscale ); + grayscale->setGradientHint( QskSlider::Groove, gradient ); + } + + color->setGradientHint( QskSlider::Groove, gradient ); + + for ( QskSlider* slider : { color, brightness, alpha, grayscale } ) + { + slider->setGradientHint( QskSlider::Fill, {} ); + } + + auto colorFrom = []( QskSlider* slider ) { + return slider->gradientHint( QskSlider::Groove ) + .extracted( slider->valueAsRatio(), slider->valueAsRatio() ) + .startColor(); + }; + + auto updateColor = [ = ]( QskSlider* sender ) { + const auto ratio = sender->valueAsRatio(); + const auto rgb = sender->gradientHint( QskSlider::Groove ) + .extracted( ratio, ratio ) + .startColor(); + + { + auto c = colorFrom( color ).rgb(); + + QskGradient gradient; + gradient.setLinearDirection( Qt::Horizontal ); + gradient.setStops( { { 0.0, Qt::white }, { 0.5, c }, { 1.0, Qt::black } } ); + brightness->setGradientHint( QskSlider::Groove, gradient ); + } + + { + QskGradient gradient; + gradient.setLinearDirection( Qt::Horizontal ); + gradient.setStops( { { 0.0, Qt::transparent }, + { 1.0, { rgb.red(), rgb.green(), rgb.blue() } } } ); + alpha->setGradientHint( QskSlider::Groove, gradient ); + } + + { + auto color = preview->color( QskBox::Panel ); + color.setRed( rgb.red() ); + color.setGreen( rgb.green() ); + color.setBlue( rgb.blue() ); + preview->setColor( QskBox::Panel, color ); + preview->setBoxBorderColorsHint( QskBox::Panel, color.rgb() | 0xff000000 ); + + text->setText( color.name( QColor::HexArgb ) ); + } + }; + + auto updateAlpha = [ = ]( qreal alpha ) { + auto color = preview->color( QskBox::Panel ); + color.setAlphaF( alpha ); + preview->setColor( QskBox::Panel, color ); + text->setText( color.name( QColor::HexArgb ) ); + }; + + QObject::connect( color, &QskSlider::valueChanged, preview, + [ updateColor, sender = color ]() { updateColor( sender ); } ); + QObject::connect( grayscale, &QskSlider::valueChanged, preview, + [ updateColor, sender = grayscale ]() { updateColor( sender ); } ); + QObject::connect( brightness, &QskSlider::valueChanged, preview, + [ updateColor, sender = brightness ]() { updateColor( sender ); } ); + QObject::connect( alpha, &QskSlider::valueChanged, preview, updateAlpha ); + + preview->setFixedSize( 80, 80 ); + preview->setBoxShapeHint( QskBox::Panel, { 4 } ); + preview->setBoxBorderColorsHint( QskBox::Panel, { Qt::black } ); + preview->setBoxBorderMetricsHint( QskBox::Panel, { 2 } ); + preview->setMarginHint( QskBox::Panel, 2 ); + + layout->setColumnStretchFactor(0, 0); + layout->setColumnStretchFactor(1, 99); + + updateColor( color ); + updateAlpha( alpha->valueAsRatio() ); + return layout; + }() ); + layout->addSpacer( -1, 99 ); + auto* const button = new QskPushButton( "Close", layout ); + layout->setDefaultAlignment( Qt::AlignRight | Qt::AlignHCenter ); + QObject::connect( button, &QskPushButton::clicked, layout, &QskLinearBox::deleteLater ); + } }; } diff --git a/examples/gallery/main.cpp b/examples/gallery/main.cpp index 6861c7787..edae76a96 100644 --- a/examples/gallery/main.cpp +++ b/examples/gallery/main.cpp @@ -10,6 +10,7 @@ #include "selector/SelectorPage.h" #include "dialog/DialogPage.h" #include "listbox/ListBoxPage.h" +#include "colorpickers/ColorPickerPage.h" #include #include @@ -257,6 +258,7 @@ namespace tabView->addPage( "Selectors", new SelectorPage() ); tabView->addPage( "Dialogs", new DialogPage() ); tabView->addPage( "ListBox", new ListBoxPage() ); + tabView->addPage( "Color Pickers", new ColorPickerPage() ); connect( header, &Header::enabledToggled, tabView, &TabView::setPagesEnabled ); diff --git a/src/common/QskFunctions.h b/src/common/QskFunctions.h index 1ac3e3d92..50fd0d857 100644 --- a/src/common/QskFunctions.h +++ b/src/common/QskFunctions.h @@ -69,6 +69,15 @@ inline constexpr bool qskFuzzyCompare( qreal value1, qreal value2 ) return qFuzzyCompare( value1, value2 ); } +inline constexpr qreal qskMapValueRange( qreal value, qreal srcMin, qreal srcMax, qreal dstMin, qreal dstMax ) +{ + value = std::min( std::max( value, srcMin ), srcMax ); + double percentage = ( value - srcMin ) / ( srcMax - srcMin ); + double mappedValue = dstMin + percentage * ( dstMax - dstMin ); + return mappedValue; +} + + QSK_EXPORT qreal qskFuzzyFloor( qreal value, qreal stepSize ); QSK_EXPORT qreal qskFuzzyCeil( qreal value, qreal stepSize );