add color picker

This commit is contained in:
Peter Hartmann 2025-01-27 09:21:32 +01:00
parent 25b7088d69
commit e36aa93557
10 changed files with 555 additions and 0 deletions

View File

@ -104,6 +104,9 @@ namespace
auto directorySelectionButton = new Button( "Directory selection", this );
connect( directorySelectionButton, &Button::clicked, this, &ButtonBox::execDirectorySelection );
auto colorSelectionButton = new Button( "Color selection", this );
connect( colorSelectionButton, &Button::clicked, this, &ButtonBox::execColorSelection );
setExtraSpacingAt( Qt::BottomEdge );
}
@ -181,6 +184,15 @@ namespace
// not implemented for now (class is not public)
#else
( void ) qskDialog->selectDirectory( "select directory", QDir::currentPath() );
#endif
}
void execColorSelection()
{
#ifndef QSK_USE_EXEC
// not implemented for now (class is not public)
#else
( void ) qskDialog->selectColor( "select color" );
#endif
}
};

View File

@ -111,6 +111,12 @@ namespace
{
switch( dialogType )
{
case ColorDialog:
{
auto color = qskDialog->selectColor( "select color" );
qDebug() << "selected color" << QColor( color );
break;
}
case FileDialog:
{
auto file = qskDialog->selectFile( "select file", QDir::currentPath() );

View File

@ -452,7 +452,14 @@ list(APPEND HEADERS
dialogs/QskSelectionWindow.h
)
list(APPEND PRIVATE_HEADERS
dialogs/QskColorPicker.h
dialogs/QskColorPickerSkinlet.h
)
list(APPEND SOURCES
dialogs/QskColorPicker.cpp
dialogs/QskColorPickerSkinlet.cpp
dialogs/QskDialogButton.cpp
dialogs/QskDialogButtonBox.cpp
dialogs/QskDialog.cpp

View File

@ -109,6 +109,9 @@
#include "QskStatusIndicator.h"
#include "QskStatusIndicatorSkinlet.h"
#include "QskColorPicker.h"
#include "QskColorPickerSkinlet.h"
#include "QskInternalMacros.h"
#include <qhash.h>
@ -219,6 +222,7 @@ QskSkin::QskSkin( QObject* parent )
declareSkinlet< QskProgressBar, QskProgressBarSkinlet >();
declareSkinlet< QskProgressRing, QskProgressRingSkinlet >();
declareSkinlet< QskRadioBox, QskRadioBoxSkinlet >();
declareSkinlet< QskColorPicker, QskColorPickerSkinlet >();
const QFont font = QGuiApplication::font();
setupFontTable( font.family(), font.italic() );

View File

@ -0,0 +1,126 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#include "QskColorPicker.h"
#include "QskColorPickerSkinlet.h"
#include "QskEvent.h"
#include "QskFunctions.h"
QSK_SUBCONTROL( QskColorPicker, Panel )
QSK_SUBCONTROL( QskColorPicker, ColorPane )
QSK_SUBCONTROL( QskColorPicker, Selector )
class QskColorPicker::PrivateData
{
public:
qreal value = 255;
bool isPressed = false;
int colorChangedEventType;
};
QskColorPicker::QskColorPicker( QQuickItem* parent )
: Inherited( parent )
, m_data( new PrivateData )
{
m_data->colorChangedEventType = QEvent::registerEventType();
setAcceptedMouseButtons( Qt::LeftButton );
setBoundaries( 0, 255 );
}
QskColorPicker::~QskColorPicker() = default;
QColor QskColorPicker::selectedColor() const
{
auto* skinlet = static_cast< const QskColorPickerSkinlet* >( effectiveSkinlet() );
Q_ASSERT( skinlet );
return skinlet->selectedColor();
}
qreal QskColorPicker::value() const
{
return m_data->value;
}
qreal QskColorPicker::valueAsRatio() const
{
return valueAsRatio( m_data->value );
}
int QskColorPicker::colorChangedEventType() const
{
return m_data->colorChangedEventType;
}
void QskColorPicker::setValue( qreal v )
{
if( !qskFuzzyCompare( m_data->value, v ) )
{
m_data->value = v;
update();
Q_EMIT valueChanged( m_data->value );
}
}
void QskColorPicker::setValueAsRatio( qreal ratio )
{
ratio = qBound( 0.0, ratio, 1.0 );
setValue( minimum() + ratio * boundaryLength() );
}
bool QskColorPicker::event( QEvent* event )
{
if( event->type() == colorChangedEventType() )
{
event->setAccepted( true );
Q_EMIT selectedColorChanged();
return true;
}
return Inherited::event( event );
}
void QskColorPicker::mousePressEvent( QMouseEvent* event )
{
m_data->isPressed = true;
updatePosition( event );
}
void QskColorPicker::mouseMoveEvent( QMouseEvent* event )
{
if( !m_data->isPressed )
{
return;
}
updatePosition( event );
}
void QskColorPicker::mouseReleaseEvent( QMouseEvent* )
{
m_data->isPressed = false;
}
void QskColorPicker::updatePosition( QMouseEvent* event )
{
auto p = qskMousePosition( event );
const auto rect = subControlRect( ColorPane );
p.rx() = qBound( rect.x(), p.x(), rect.right() );
p.ry() = qBound( rect.y(), p.y(), rect.bottom() );
const auto oldX = positionHint( Selector | QskAspect::Horizontal );
const auto oldY = positionHint( Selector | QskAspect::Vertical );
if( !qskFuzzyCompare( p.x(), oldX ) || !qskFuzzyCompare( p.y(), oldY ) )
{
setPositionHint( Selector | QskAspect::Horizontal, p.x() );
setPositionHint( Selector | QskAspect::Vertical, p.y() );
update();
}
}
#include "moc_QskColorPicker.cpp"

View File

@ -0,0 +1,54 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#ifndef QSK_COLOR_PICKER_H
#define QSK_COLOR_PICKER_H
#include "QskBoundedControl.h"
#include <memory>
class QskColorPicker : public QskBoundedControl
{
Q_OBJECT
using Inherited = QskBoundedControl;
public:
QSK_SUBCONTROLS( Panel, ColorPane, Selector )
QskColorPicker( QQuickItem* parentItem = nullptr );
~QskColorPicker() override;
QColor selectedColor() const;
qreal value() const; // value as in hue / saturation / value
qreal valueAsRatio() const; // [0.0, 1.0]
using QskBoundedControl::valueAsRatio;
int colorChangedEventType() const;
public Q_SLOTS:
void setValue( qreal );
void setValueAsRatio( qreal );
Q_SIGNALS:
void valueChanged( qreal );
void selectedColorChanged() const;
protected:
bool event( QEvent* ) override;
void mousePressEvent( QMouseEvent* ) override;
void mouseMoveEvent( QMouseEvent* ) override;
void mouseReleaseEvent( QMouseEvent* ) override;
private:
void updatePosition( QMouseEvent* );
class PrivateData;
std::unique_ptr< PrivateData > m_data;
};
#endif

View File

@ -0,0 +1,181 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#include "QskColorPickerSkinlet.h"
#include "QskColorPicker.h"
#include "QskSGNode.h"
#include "QskPaintedNode.h"
#include <QPainter>
using Q = QskColorPicker;
namespace
{
class ColorPaneNode : public QskPaintedNode
{
public:
ColorPaneNode()
{
}
void paint( QPainter* p, const QSize& size, const void* nodeData ) override
{
if( m_image.size() != size )
{
m_image = QImage( size.width(), size.height(), QImage::Format_RGB32 );
}
QColor color;
float h, s;
const float v = *reinterpret_cast< const int* >( nodeData ) / 255.0;
for( int x = 0; x < m_image.width(); x++ )
{
h = ( float ) x / m_image.width();
for( int y = 0; y < m_image.height(); y++ )
{
s = 1.0 - ( float ) y / m_image.height();
color.setHsvF( h, s, v );
m_image.setPixel( x, y, color.rgb() );
}
}
p->drawImage( QPointF( 0, 0 ), m_image );
}
void updateNode( QQuickWindow* window, const QRectF& rect, int value )
{
update( window, rect, QSizeF(), &value );
}
QColor selectedColor( const QPointF& p ) const
{
return m_image.pixelColor( p.toPoint() );
}
protected:
QskHashValue hash( const void* nodeData ) const override
{
const auto* value = reinterpret_cast< const int* >( nodeData );
return *value;
}
private:
QImage m_image;
};
}
class QskColorPickerSkinlet::PrivateData
{
public:
QColor selectedColor;
ColorPaneNode* colorPaneNode = nullptr;
};
QskColorPickerSkinlet::QskColorPickerSkinlet( QskSkin* skin )
: Inherited( skin )
, m_data( new PrivateData )
{
setNodeRoles( { PanelRole, ColorPaneRole, SelectorRole } );
}
QskColorPickerSkinlet::~QskColorPickerSkinlet() = default;
QSGNode* QskColorPickerSkinlet::updateSubNode(
const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
{
const auto q = static_cast< const Q* >( skinnable );
switch ( nodeRole )
{
case PanelRole:
{
return updateBoxNode( skinnable, node, Q::Panel );
}
case ColorPaneRole:
{
return updateColorPaneNode( q, node );
}
case SelectorRole:
{
auto* n = updateBoxNode( skinnable, node, Q::Selector );
updateSelectedColor( q );
return n;
}
}
return Inherited::updateSubNode( skinnable, nodeRole, node );
}
QRectF QskColorPickerSkinlet::subControlRect(
const QskSkinnable* skinnable, const QRectF& contentsRect,
QskAspect::Subcontrol subControl ) const
{
const auto q = static_cast< const Q* >( skinnable );
if( subControl == Q::Panel || subControl == Q::ColorPane )
{
return contentsRect;
}
if( subControl == Q::Selector )
{
const auto size = q->strutSizeHint( Q::Selector );
const auto x = q->positionHint( Q::Selector | QskAspect::Horizontal );
const auto y = q->positionHint( Q::Selector | QskAspect::Vertical );
QRectF r( { x - size.width() / 2.0, y - size.height() / 2.0 }, size );
return r;
}
return Inherited::subControlRect( skinnable, contentsRect, subControl );
}
QColor QskColorPickerSkinlet::selectedColor() const
{
return m_data->selectedColor;
}
QSGNode* QskColorPickerSkinlet::updateColorPaneNode(
const QskColorPicker* q, QSGNode* node ) const
{
m_data->colorPaneNode = QskSGNode::ensureNode< ColorPaneNode >( node );
const auto rect = q->subControlRect( Q::ColorPane );
m_data->colorPaneNode->updateNode( q->window(), rect, q->value() );
updateSelectedColor( q );
return m_data->colorPaneNode;
}
QPointF QskColorPickerSkinlet::selectorPos( const Q* q ) const
{
const auto x = q->positionHint( Q::Selector | QskAspect::Horizontal );
const auto y = q->positionHint( Q::Selector | QskAspect::Vertical );
QPointF p( x, y );
const auto rect = q->subControlRect( Q::ColorPane );
p.rx() = qBound( rect.x(), p.x(), rect.right() );
p.ry() = qBound( rect.y(), p.y(), rect.bottom() );
return p;
}
void QskColorPickerSkinlet::updateSelectedColor( const Q* q ) const
{
const auto color = m_data->colorPaneNode->selectedColor( selectorPos( q ) );
m_data->selectedColor = color;
auto* e = new QEvent( static_cast< QEvent::Type >( q->colorChangedEventType() ) );
QCoreApplication::postEvent( const_cast< Q* >( q ), e );
}
#include "moc_QskColorPickerSkinlet.cpp"

View File

@ -0,0 +1,52 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#ifndef QSK_COLOR_PICKER_SKINLET_H
#define QSK_COLOR_PICKER_SKINLET_H
#include "QskSkinlet.h"
class QskColorPicker;
class QskColorPickerSkinlet : public QskSkinlet
{
Q_GADGET
using Inherited = QskSkinlet;
public:
enum NodeRole : quint8
{
PanelRole,
ColorPaneRole,
SelectorRole,
RoleCount
};
Q_INVOKABLE QskColorPickerSkinlet( QskSkin* = nullptr );
~QskColorPickerSkinlet() override;
QRectF subControlRect( const QskSkinnable*,
const QRectF&, QskAspect::Subcontrol ) const override;
QColor selectedColor() const;
protected:
QSGNode* updateSubNode( const QskSkinnable*,
quint8 nodeRole, QSGNode* ) const override;
QSGNode* updateColorPaneNode( const QskColorPicker*, QSGNode* ) const;
private:
QRectF cursorRect( const QskSkinnable*, const QRectF&, int index ) const;
QPointF selectorPos( const QskColorPicker* ) const;
void updateSelectedColor( const QskColorPicker* ) const;
class PrivateData;
std::unique_ptr< PrivateData > m_data;
};
#endif

View File

@ -14,11 +14,16 @@
#include "QskSelectionSubWindow.h"
#include "QskSelectionWindow.h"
#include "QskBoxBorderColors.h"
#include "QskBoxBorderMetrics.h"
#include "QskBoxShapeMetrics.h"
#include "QskColorPicker.h"
#include "QskEvent.h"
#include "QskFunctions.h"
#include "QskListView.h"
#include "QskPushButton.h"
#include "QskScrollArea.h"
#include "QskSlider.h"
#include "QskFocusIndicator.h"
@ -458,6 +463,72 @@ namespace
FileSystemView* m_fileView;
};
template< typename W >
class ColorSelectionWindow : public WindowOrSubWindow< W >
{
using Inherited = WindowOrSubWindow< W >;
public:
ColorSelectionWindow( QObject* parent, const QString& title,
QskDialog::Actions actions, QskDialog::Action defaultAction )
: WindowOrSubWindow< W >( parent, title, actions, defaultAction )
{
auto* outerBox = new QskLinearBox( Qt::Vertical );
outerBox->setMargins( 20 );
outerBox->setSpacing( 20 );
#if 1
outerBox->setFixedSize( 700, 500 );
#endif
auto* upperBox = new QskLinearBox( Qt::Horizontal, outerBox );
upperBox->setSpacing( 12 );
m_picker = new QskColorPicker( upperBox );
m_picker->setStrutSizeHint( QskColorPicker::Selector, { 18, 18 } );
m_picker->setBoxShapeHint( QskColorPicker::Selector, { 100, Qt::RelativeSize } );
m_picker->setBoxBorderMetricsHint( QskColorPicker::Selector, 2 );
m_picker->setBoxBorderColorsHint( QskColorPicker::Selector, Qt::black );
m_picker->setGradientHint( QskColorPicker::Selector, Qt::transparent );
auto* outputBox = new QskBox( upperBox );
outputBox->setPanel( true );
QObject::connect( m_picker, &QskColorPicker::selectedColorChanged,
this, [this, outputBox]()
{
const auto c = m_picker->selectedColor();
outputBox->setGradientHint( QskBox::Panel, c );
} );
upperBox->setStretchFactor( m_picker, 9 );
upperBox->setStretchFactor( outputBox, 1 );
auto* valueSlider = new QskSlider( outerBox );
valueSlider->setBoundaries( 0, 1 );
valueSlider->setValue( m_picker->value() );
QskGradient g( Qt::black, Qt::white );
g.setLinearDirection( Qt::Horizontal );
valueSlider->setGradientHint( QskSlider::Groove, g );
valueSlider->setGradientHint( QskSlider::Fill, Qt::transparent );
valueSlider->setGradientHint( QskSlider::Handle, Qt::black );
QObject::connect( valueSlider, &QskSlider::valueChanged,
m_picker, &QskColorPicker::setValueAsRatio );
Inherited::setContentItem( outerBox );
}
QColor selectedColor() const
{
return m_picker->selectedColor();
}
private:
QskColorPicker* m_picker;
};
}
static QQuickWindow* qskSomeQuickWindow()
@ -625,6 +696,17 @@ static QString qskSelectPath( FileSelectionWindow< W >& window )
return selectedFile;
}
template< typename W >
static QColor qskSelectColor( ColorSelectionWindow< W >& window )
{
QColor selectedColor = window.selectedColor();
if( window.exec() == QskDialog::Accepted )
selectedColor = window.selectedColor();
return selectedColor;
}
class QskDialog::PrivateData
{
public:
@ -801,6 +883,34 @@ QString QskDialog::selectDirectory(
return qskSelectPath< QskDialogWindow >( window );
}
QColor QskDialog::selectColor( const QString& title ) const
{
#if 1
// should be parameters
const auto actions = QskDialog::Ok | QskDialog::Cancel;
const auto defaultAction = QskDialog::Ok;
#endif
if ( m_data->policy == EmbeddedBox )
{
auto quickWindow = qobject_cast< QQuickWindow* >( m_data->transientParent );
if ( quickWindow == nullptr )
quickWindow = qskSomeQuickWindow();
if ( quickWindow )
{
ColorSelectionWindow< QskDialogSubWindow > window( quickWindow, title,
actions, defaultAction );
return qskSelectColor< QskDialogSubWindow >( window );
}
}
ColorSelectionWindow< QskDialogWindow > window( m_data->transientParent, title,
actions, defaultAction );
return qskSelectColor< QskDialogWindow >( window );
}
QskDialog::ActionRole QskDialog::actionRole( Action action )
{
using Q = QPlatformDialogHelper;

View File

@ -8,6 +8,7 @@
#include "QskGlobal.h"
#include <qrgb.h>
#include <qobject.h>
#include <memory>
@ -136,6 +137,8 @@ class QSK_EXPORT QskDialog : public QObject
Q_INVOKABLE QString selectDirectory( const QString& title,
const QString& directory ) const;
Q_INVOKABLE QColor selectColor( const QString& title ) const;
static ActionRole actionRole( Action action );
Q_SIGNALS: