diff --git a/Gui/ColorSelectorWidget.cpp b/Gui/ColorSelectorWidget.cpp new file mode 100644 index 0000000000..154b370773 --- /dev/null +++ b/Gui/ColorSelectorWidget.cpp @@ -0,0 +1,886 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * This file is part of Natron , + * (C) 2018-2021 The Natron developers + * (C) 2013-2018 INRIA and Alexandre Gauthier-Foichat + * + * Natron is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Natron is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Natron. If not, see + * ***** END LICENSE BLOCK ***** */ + +#include "ColorSelectorWidget.h" + +CLANG_DIAG_OFF(deprecated) +CLANG_DIAG_OFF(uninitialized) +#include +#include +#include +CLANG_DIAG_ON(deprecated) +CLANG_DIAG_ON(uninitialized) + +#include "Gui/Label.h" +#include "Engine/Lut.h" + +NATRON_NAMESPACE_ENTER + +ColorSelectorWidget::ColorSelectorWidget(QWidget *parent) + : QWidget(parent) + , _spinR(0) + , _spinG(0) + , _spinB(0) + , _spinH(0) + , _spinS(0) + , _spinV(0) + , _spinA(0) + , _slideR(0) + , _slideG(0) + , _slideB(0) + , _slideH(0) + , _slideS(0) + , _slideV(0) + , _slideA(0) + , _triangle(0) + , _hex(0) + , _button(0) + , _stack(0) +{ + QVBoxLayout *mainLayout = new QVBoxLayout(this); + + // colortriangle + _triangle = new QtColorTriangle(this); + // position the triangle properly before usage + _triangle->setColor( QColor::fromHsvF(0.0, 0.0, 0.0, 1.0) ); + _triangle->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + // sliders + _slideR = new ScaleSliderQWidget(0., + 1., + 0., + false, + ScaleSliderQWidget::eDataTypeDouble, + NULL, + eScaleTypeLinear, + this); + _slideG = new ScaleSliderQWidget(0., + 1., + 0., + false, + ScaleSliderQWidget::eDataTypeDouble, + NULL, + eScaleTypeLinear, + this); + _slideB = new ScaleSliderQWidget(0., + 1., + 0., + false, + ScaleSliderQWidget::eDataTypeDouble, + NULL, + eScaleTypeLinear, + this); + _slideH = new ScaleSliderQWidget(0., + 1., + 0., + false, + ScaleSliderQWidget::eDataTypeDouble, + NULL, + eScaleTypeLinear, + this); + _slideS = new ScaleSliderQWidget(0., + 1., + 0., + false, + ScaleSliderQWidget::eDataTypeDouble, + NULL, + eScaleTypeLinear, + this); + _slideV = new ScaleSliderQWidget(0., + 1., + 0., + false, + ScaleSliderQWidget::eDataTypeDouble, + NULL, + eScaleTypeLinear, + this); + _slideA = new ScaleSliderQWidget(0., + 1., + 0., + false, + ScaleSliderQWidget::eDataTypeDouble, + NULL, + eScaleTypeLinear, + this); + + _slideR->setMinimumAndMaximum(0., 1.); + _slideG->setMinimumAndMaximum(0., 1.); + _slideB->setMinimumAndMaximum(0., 1.); + _slideH->setMinimumAndMaximum(0., 1.); + _slideS->setMinimumAndMaximum(0., 1.); + _slideV->setMinimumAndMaximum(0., 1.); + _slideA->setMinimumAndMaximum(0., 1.); + + _slideR->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + _slideG->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + _slideB->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + _slideH->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + _slideS->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + _slideV->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + _slideA->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + // set line color to match channel (R/G/B/A) + _slideR->setUseLineColor(true, QColor(200, 70, 70) ); + _slideG->setUseLineColor(true, QColor(86, 166, 66) ); + _slideB->setUseLineColor(true, QColor(83, 121, 180) ); + _slideA->setUseLineColor(true, QColor(215, 215, 215) ); + + // override "knob" color on sliders + _slideR->setUseSliderColor(true, Qt::white); + _slideG->setUseSliderColor(true, Qt::white); + _slideB->setUseSliderColor(true, Qt::white); + _slideH->setUseSliderColor(true, Qt::white); + _slideS->setUseSliderColor(true, Qt::white); + _slideV->setUseSliderColor(true, Qt::white); + _slideA->setUseSliderColor(true, Qt::white); + + // spinboxes + _spinR = new SpinBox(this, SpinBox::eSpinBoxTypeDouble); + _spinG = new SpinBox(this, SpinBox::eSpinBoxTypeDouble); + _spinB = new SpinBox(this, SpinBox::eSpinBoxTypeDouble); + _spinH = new SpinBox(this, SpinBox::eSpinBoxTypeDouble); + _spinS = new SpinBox(this, SpinBox::eSpinBoxTypeDouble); + _spinV = new SpinBox(this, SpinBox::eSpinBoxTypeDouble); + _spinA = new SpinBox(this, SpinBox::eSpinBoxTypeDouble); + + _spinR->decimals(3); + _spinG->decimals(3); + _spinB->decimals(3); + _spinH->decimals(3); + _spinS->decimals(3); + _spinV->decimals(3); + _spinA->decimals(3); + + _spinR->setIncrement(0.01); + _spinG->setIncrement(0.01); + _spinB->setIncrement(0.01); + _spinH->setIncrement(0.01); + _spinS->setIncrement(0.01); + _spinV->setIncrement(0.01); + _spinA->setIncrement(0.01); + + _spinR->setMaximum(1.); + _spinR->setMinimum(0.); + _spinG->setMaximum(1.); + _spinG->setMinimum(0.); + _spinB->setMaximum(1.); + _spinB->setMinimum(0.); + _spinH->setMaximum(1.); + _spinH->setMinimum(0.); + _spinS->setMaximum(1.); + _spinS->setMinimum(0.); + _spinV->setMaximum(1.); + _spinV->setMinimum(0.); + _spinA->setMaximum(1.); + _spinA->setMinimum(0.); + + // set color to match channel (R/G/B/A) + _spinR->setUseLineColor(true, QColor(200, 70, 70) ); + _spinG->setUseLineColor(true, QColor(86, 166, 66) ); + _spinB->setUseLineColor(true, QColor(83, 121, 180) ); + _spinA->setUseLineColor(true, QColor(215, 215, 215) ); + + // hex + _hex = new LineEdit(this); + + // button + _button = new Button(QString::fromUtf8("RGB"), this); + _button->setCheckable(true); + + // set triangle size + setTriangleSize(); + + // set object names (for stylesheet) + _spinR->setObjectName( QString::fromUtf8("ColorSelectorRed") ); + _spinG->setObjectName( QString::fromUtf8("ColorSelectorGreen") ); + _spinB->setObjectName( QString::fromUtf8("ColorSelectorBlue") ); + _spinA->setObjectName( QString::fromUtf8("ColorSelectorAlpha") ); + _spinH->setObjectName( QString::fromUtf8("ColorSelectorHue") ); + _spinS->setObjectName( QString::fromUtf8("ColorSelectorSat") ); + _spinV->setObjectName( QString::fromUtf8("ColorSelectorVal") ); + _hex->setObjectName( QString::fromUtf8("ColorSelectorHex") ); + _button->setObjectName( QString::fromUtf8("ColorSelectorSwitch") ); + + // labels + Label *labelR = new Label(QString::fromUtf8("R"), this); + Label *labelG = new Label(QString::fromUtf8("G"), this); + Label *labelB = new Label(QString::fromUtf8("B"), this); + Label *labelA = new Label(QString::fromUtf8("A"), this); + Label *labelH = new Label(QString::fromUtf8("H"), this); + Label *labelS = new Label(QString::fromUtf8("S"), this); + Label *labelV = new Label(QString::fromUtf8("V"), this); + Label *labelHex = new Label(QString::fromUtf8("Hex"), this); + + labelR->setMinimumWidth(10); + labelG->setMinimumWidth(10); + labelB->setMinimumWidth(10); + labelA->setMinimumWidth(10); + labelH->setMinimumWidth(10); + labelS->setMinimumWidth(10); + labelV->setMinimumWidth(10); + + // tooltips + _spinR->setToolTip( QObject::tr("Red color value") ); + _spinG->setToolTip( QObject::tr("Green color value") ); + _spinB->setToolTip( QObject::tr("Blue color value") ); + _spinA->setToolTip( QObject::tr("Alpha value") ); + _spinH->setToolTip( QObject::tr("Hue color value") ); + _spinS->setToolTip( QObject::tr("Saturation value") ); + _spinV->setToolTip( QObject::tr("Brightness/Intensity value") ); + _hex->setToolTip( QObject::tr("A HTML hexadecimal color is specified with: #RRGGBB, where the RR (red), GG (green) and BB (blue) hexadecimal integers specify the components of the color.") ); + _button->setToolTip( QObject::tr("Switch to HSV") ); + + // layout + _stack = new QStackedWidget(this); + + QWidget *rWidget = new QWidget(this); + QHBoxLayout *rLayout = new QHBoxLayout(rWidget); + + QWidget *gWidget = new QWidget(this); + QHBoxLayout *gLayout = new QHBoxLayout(gWidget); + + QWidget *bWidget = new QWidget(this); + QHBoxLayout *bLayout = new QHBoxLayout(bWidget); + + QWidget *hWidget = new QWidget(this); + QHBoxLayout *hLayout = new QHBoxLayout(hWidget); + + QWidget *sWidget = new QWidget(this); + QHBoxLayout *sLayout = new QHBoxLayout(sWidget); + + QWidget *vWidget = new QWidget(this); + QHBoxLayout *vLayout = new QHBoxLayout(vWidget); + + QWidget *aWidget = new QWidget(this); + QHBoxLayout *aLayout = new QHBoxLayout(aWidget); + + QWidget *hexWidget = new QWidget(this); + QHBoxLayout *hexLayout = new QHBoxLayout(hexWidget); + + QWidget *rgbaWidget = new QWidget(this); + QVBoxLayout *rgbaLayout = new QVBoxLayout(rgbaWidget); + + QWidget *hsvWidget = new QWidget(this); + QVBoxLayout *hsvLayout = new QVBoxLayout(hsvWidget); + + QWidget *topWidget = new QWidget(this); + QHBoxLayout *topLayout = new QHBoxLayout(topWidget); + + QWidget *leftWidget = new QWidget(this); + QVBoxLayout *leftLayout = new QVBoxLayout(leftWidget); + + QWidget *rightWidget = new QWidget(this); + QVBoxLayout *rightLayout = new QVBoxLayout(rightWidget); + + QWidget *bottomWidget = new QWidget(this); + QHBoxLayout *bottomLayout = new QHBoxLayout(bottomWidget); + + _stack->setContentsMargins(0, 0, 0, 0); + hexWidget->setContentsMargins(0, 0, 0, 0); + topWidget->setContentsMargins(0, 0, 0, 0); + leftWidget->setContentsMargins(0, 0, 0, 0); + rightWidget->setContentsMargins(0, 0, 0, 0); + bottomWidget->setContentsMargins(0, 0, 10, 0); + + _stack->layout()->setContentsMargins(0, 0, 0, 0); + mainLayout->setContentsMargins(5, 5, 5, 5); + rLayout->setContentsMargins(0, 0, 0, 0); + gLayout->setContentsMargins(0, 0, 0, 0); + bLayout->setContentsMargins(0, 0, 0, 0); + hLayout->setContentsMargins(0, 0, 0, 0); + sLayout->setContentsMargins(0, 0, 0, 0); + vLayout->setContentsMargins(0, 0, 0, 0); + hexLayout->setContentsMargins(0, 0, 0, 0); + topLayout->setContentsMargins(0, 0, 0, 0); + leftLayout->setContentsMargins(0, 0, 0, 0); + rightLayout->setContentsMargins(0, 0, 0, 0); + bottomLayout->setContentsMargins(0, 0, 0, 0); + + QMargins aMargin = hsvLayout->contentsMargins(); + aMargin.setTop(0); + aLayout->setContentsMargins(aMargin); + + _stack->layout()->setSpacing(0); + mainLayout->setSpacing(0); + topLayout->setSpacing(0); + leftLayout->setSpacing(0); + rightLayout->setSpacing(0); + + rLayout->addWidget(labelR); + rLayout->addWidget(_spinR); + rLayout->addWidget(_slideR); + gLayout->addWidget(labelG); + gLayout->addWidget(_spinG); + gLayout->addWidget(_slideG); + bLayout->addWidget(labelB); + bLayout->addWidget(_spinB); + bLayout->addWidget(_slideB); + + hLayout->addWidget(labelH); + hLayout->addWidget(_spinH); + hLayout->addWidget(_slideH); + sLayout->addWidget(labelS); + sLayout->addWidget(_spinS); + sLayout->addWidget(_slideS); + vLayout->addWidget(labelV); + vLayout->addWidget(_spinV); + vLayout->addWidget(_slideV); + + aLayout->addWidget(labelA); + aLayout->addWidget(_spinA); + aLayout->addWidget(_slideA); + + hexLayout->addWidget(labelHex); + hexLayout->addWidget(_hex); + + rgbaLayout->addWidget(rWidget); + rgbaLayout->addWidget(gWidget); + rgbaLayout->addWidget(bWidget); + + hsvLayout->addWidget(hWidget); + hsvLayout->addWidget(sWidget); + hsvLayout->addWidget(vWidget); + + leftLayout->addWidget(_triangle); + + _stack->addWidget(rgbaWidget); + _stack->addWidget(hsvWidget); + + rightLayout->addWidget(_stack); + rightLayout->addWidget(aWidget); + + topLayout->addWidget(leftWidget); + topLayout->addWidget(rightWidget); + + bottomLayout->addWidget(_button); + bottomLayout->addStretch(); + bottomLayout->addWidget(hexWidget); + + mainLayout->addWidget(topWidget); + mainLayout->addWidget(bottomWidget); + + // connect the widgets + QObject::connect( _triangle, SIGNAL( colorChanged(QColor) ), + this, SLOT( handleTriangleColorChanged(QColor) ) ); + + QObject::connect( _spinR, SIGNAL( valueChanged(double) ), + this, SLOT( handleSpinRChanged(double) ) ); + QObject::connect( _spinG, SIGNAL( valueChanged(double) ), + this, SLOT( handleSpinGChanged(double) ) ); + QObject::connect( _spinB, SIGNAL( valueChanged(double) ), + this, SLOT( handleSpinBChanged(double) ) ); + QObject::connect( _spinH, SIGNAL( valueChanged(double) ), + this, SLOT( handleSpinHChanged(double) ) ); + QObject::connect( _spinS, SIGNAL( valueChanged(double) ), + this, SLOT( handleSpinSChanged(double) ) ); + QObject::connect( _spinV, SIGNAL( valueChanged(double) ), + this, SLOT( handleSpinVChanged(double) ) ); + QObject::connect( _spinA, SIGNAL( valueChanged(double) ), + this, SLOT( handleSpinAChanged(double) ) ); + + QObject::connect( _hex, SIGNAL( returnPressed() ), + this, SLOT( handleHexChanged() ) ); + + QObject::connect( _slideR, SIGNAL( positionChanged(double) ), + this, SLOT( handleSliderRMoved(double) ) ); + QObject::connect( _slideG, SIGNAL( positionChanged(double) ), + this, SLOT( handleSliderGMoved(double) ) ); + QObject::connect( _slideB, SIGNAL( positionChanged(double) ), + this, SLOT( handleSliderBMoved(double) ) ); + QObject::connect( _slideH, SIGNAL( positionChanged(double) ), + this, SLOT( handleSliderHMoved(double) ) ); + QObject::connect( _slideS, SIGNAL( positionChanged(double) ), + this, SLOT( handleSliderSMoved(double) ) ); + QObject::connect( _slideV, SIGNAL( positionChanged(double) ), + this, SLOT( handleSliderVMoved(double) ) ); + QObject::connect( _slideA, SIGNAL( positionChanged(double) ), + this, SLOT( handleSliderAMoved(double) ) ); + + QObject::connect( _button, SIGNAL( clicked(bool) ), + this, SLOT( handleButtonClicked(bool) ) ); +} + +void +ColorSelectorWidget::getColor(float *r, + float *g, + float *b, + float *a) +{ + *r = _spinR->value(); + *g = _spinG->value(); + *b = _spinB->value(); + *a = _spinA->value(); +} + +void +ColorSelectorWidget::setColor(float r, + float g, + float b, + float a) +{ + float h, s, v; + Color::rgb_to_hsv(r, g, b, &h, &s, &v); + + setRedChannel(r); + setGreenChannel(g); + setBlueChannel(b); + setHueChannel(h); + setSaturationChannel(s); + setValueChannel(v); + setAlphaChannel(a); + setTriangle(r, g, b, a); + setHex( _triangle->color() ); +} + +void +ColorSelectorWidget::setRedChannel(float value) +{ + _spinR->blockSignals(true); + _slideR->blockSignals(true); + + _spinR->setValue(value); + _slideR->seekScalePosition(value); + + _spinR->blockSignals(false); + _slideR->blockSignals(false); +} + +void +ColorSelectorWidget::setGreenChannel(float value) +{ + _spinG->blockSignals(true); + _slideG->blockSignals(true); + + _spinG->setValue(value); + _slideG->seekScalePosition(value); + + _spinG->blockSignals(false); + _slideG->blockSignals(false); +} + +void +ColorSelectorWidget::setBlueChannel(float value) +{ + _spinB->blockSignals(true); + _slideB->blockSignals(true); + + _spinB->setValue(value); + _slideB->seekScalePosition(value); + + _spinB->blockSignals(false); + _slideB->blockSignals(false); +} + +void +ColorSelectorWidget::setHueChannel(float value) +{ + _spinH->blockSignals(true); + _slideH->blockSignals(true); + + _spinH->setValue(value); + _slideH->seekScalePosition(value); + + setSliderHColor(); + + _spinH->blockSignals(false); + _slideH->blockSignals(false); +} + +void +ColorSelectorWidget::setSaturationChannel(float value) +{ + _spinS->blockSignals(true); + _slideS->blockSignals(true); + + _spinS->setValue(value); + _slideS->seekScalePosition(value); + + setSliderSColor(); + + _spinS->blockSignals(false); + _slideS->blockSignals(false); +} + +void +ColorSelectorWidget::setValueChannel(float value) +{ + _spinV->blockSignals(true); + _slideV->blockSignals(true); + + _spinV->setValue(value); + _slideV->seekScalePosition(value); + + setSliderVColor(); + + _spinV->blockSignals(false); + _slideV->blockSignals(false); +} + +void +ColorSelectorWidget::setAlphaChannel(float value) +{ + _spinA->blockSignals(true); + _slideA->blockSignals(true); + + _spinA->setValue(value); + _slideA->seekScalePosition(value); + + _spinA->blockSignals(false); + _slideA->blockSignals(false); +} + +void +ColorSelectorWidget::setTriangle(float r, + float g, + float b, + float a) +{ + QColor color = _triangle->color(); + color.setRgbF( Color::to_func_srgb(r), + Color::to_func_srgb(g), + Color::to_func_srgb(b) ); + color.setAlphaF(a); + + _triangle->blockSignals(true); + _triangle->setColor(color); + _triangle->blockSignals(false); +} + +void +ColorSelectorWidget::setHex(const QColor &color) +{ + if ( !color.isValid() ) { + return; + } + + _hex->blockSignals(true); + _hex->setText( color.name() ); + _hex->blockSignals(false); +} + +void +ColorSelectorWidget::announceColorChange() +{ + Q_EMIT colorChanged( _spinR->value(), + _spinG->value(), + _spinB->value(), + _spinA->value() ); +} + +void +ColorSelectorWidget::setTriangleSize() +{ + int triangleSize = 4; + int padding = 10; + + triangleSize = (_spinR->size().height() * triangleSize) + (triangleSize * padding); + + _triangle->setMinimumSize(triangleSize, triangleSize); + _triangle->setMaximumSize(triangleSize, triangleSize); +} + +void +ColorSelectorWidget::handleTriangleColorChanged(const QColor &color, + bool announce) +{ + setRedChannel( Color::from_func_srgb( color.redF() ) ); + setGreenChannel( Color::from_func_srgb( color.greenF() ) ); + setBlueChannel( Color::from_func_srgb( color.blueF() ) ); + setHueChannel( color.toHsv().hueF() ); + setSaturationChannel( color.toHsv().saturationF() ); + setValueChannel( color.toHsv().valueF() ); + setHex(color); + + if (announce) { + announceColorChange(); + } +} + +void ColorSelectorWidget::manageColorRGBChanged(bool announce) +{ + float r = _spinR->value(); + float g = _spinG->value(); + float b = _spinB->value(); + float a = _spinA->value(); + float h, s, v; + Color::rgb_to_hsv(r, g, b, &h, &s, &v); + + setHueChannel(h); + setSaturationChannel(s); + setValueChannel(v); + setTriangle(r, g, b, a); + setHex( _triangle->color() ); + + if (announce) { + announceColorChange(); + } +} + +void +ColorSelectorWidget::manageColorHSVChanged(bool announce) +{ + float h = _spinH->value(); + float s = _spinS->value(); + float v = _spinV->value(); + float a = _spinA->value(); + float r, g, b; + Color::hsv_to_rgb(h, s, v, &r, &g, &b); + + setRedChannel(r); + setGreenChannel(g); + setBlueChannel(b); + + QColor color = _triangle->color(); + color.setHsvF(h, s, v); + color.setAlphaF(a); + + _triangle->blockSignals(true); + _triangle->setColor(color); + _triangle->blockSignals(false); + + setHex(color); + + setSliderHColor(); + setSliderSColor(); + setSliderVColor(); + + if (announce) { + announceColorChange(); + } +} + +void +ColorSelectorWidget::manageColorAlphaChanged(bool announce) +{ + if (announce) { + announceColorChange(); + } +} + +void +ColorSelectorWidget::handleSpinRChanged(double value) +{ + _slideR->blockSignals(true); + _slideR->seekScalePosition(value); + _slideR->blockSignals(false); + + manageColorRGBChanged(); +} + +void +ColorSelectorWidget::handleSpinGChanged(double value) +{ + _slideG->blockSignals(true); + _slideG->seekScalePosition(value); + _slideG->blockSignals(false); + + manageColorRGBChanged(); +} + +void +ColorSelectorWidget::handleSpinBChanged(double value) +{ + _slideB->blockSignals(true); + _slideB->seekScalePosition(value); + _slideB->blockSignals(false); + + manageColorRGBChanged(); +} + +void +ColorSelectorWidget::handleSpinHChanged(double value) +{ + _slideH->blockSignals(true); + _slideH->seekScalePosition(value); + _slideH->blockSignals(false); + + manageColorHSVChanged(); +} + +void +ColorSelectorWidget::handleSpinSChanged(double value) +{ + _slideS->blockSignals(true); + _slideS->seekScalePosition(value); + _slideS->blockSignals(false); + + manageColorHSVChanged(); +} + +void +ColorSelectorWidget::handleSpinVChanged(double value) +{ + _slideV->blockSignals(true); + _slideV->seekScalePosition(value); + _slideV->blockSignals(false); + + manageColorHSVChanged(); +} + +void +ColorSelectorWidget::handleSpinAChanged(double value) +{ + _slideA->blockSignals(true); + _slideA->seekScalePosition(value); + _slideA->blockSignals(false); + + manageColorAlphaChanged(); +} + +void ColorSelectorWidget::handleHexChanged() +{ + QString value = _hex->text(); + if ( !value.startsWith( QString::fromUtf8("#") ) ) { + value.prepend( QString::fromUtf8("#") ); + } + + QColor color; + color.setNamedColor( _hex->text() ); + if ( !color.isValid() ) { + return; + } + + _triangle->setColor(color); +} + +void +ColorSelectorWidget::handleSliderRMoved(double value) +{ + _spinR->blockSignals(true); + _spinR->setValue(value); + _spinR->blockSignals(false); + + manageColorRGBChanged(); +} + +void +ColorSelectorWidget::handleSliderGMoved(double value) +{ + _spinG->blockSignals(true); + _spinG->setValue(value); + _spinG->blockSignals(false); + + manageColorRGBChanged(); +} + +void +ColorSelectorWidget::handleSliderBMoved(double value) +{ + _spinB->blockSignals(true); + _spinB->setValue(value); + _spinB->blockSignals(false); + + manageColorRGBChanged(); +} + +void +ColorSelectorWidget::handleSliderHMoved(double value) +{ + _spinH->blockSignals(true); + _spinH->setValue(value); + _spinH->blockSignals(false); + + manageColorHSVChanged(); +} + +void +ColorSelectorWidget::handleSliderSMoved(double value) +{ + _spinS->blockSignals(true); + _spinS->setValue(value); + _spinS->blockSignals(false); + + manageColorHSVChanged(); +} + +void +ColorSelectorWidget::handleSliderVMoved(double value) +{ + _spinV->blockSignals(true); + _spinV->setValue(value); + _spinV->blockSignals(false); + + manageColorHSVChanged(); +} + + +void +ColorSelectorWidget::handleSliderAMoved(double value) +{ + _spinA->blockSignals(true); + _spinA->setValue(value); + _spinA->blockSignals(false); + + manageColorAlphaChanged(); +} + +void +ColorSelectorWidget::setSliderHColor() +{ + QColor color; + color.setHsvF( _spinH->value(), + 1.0, + 1.0, + 1.0); + _spinH->setUseLineColor(true, color); + _slideH->setUseLineColor(true, color); +} + +void +ColorSelectorWidget::setSliderSColor() +{ + QColor color; + color.setHsvF( _spinH->value(), + _spinS->value(), + 1.0, + 1.0); + _spinS->setUseLineColor(true, color); + _slideS->setUseLineColor(true, color); +} + +void +ColorSelectorWidget::setSliderVColor() +{ + QColor color; + color.setHsvF( _spinH->value(), + _spinS->value(), + _spinV->value(), + 1.0); + _spinV->setUseLineColor(true, color); + _slideV->setUseLineColor(true, color); +} + +void +ColorSelectorWidget::handleButtonClicked(bool checked) +{ + if (checked) { + _button->setText( QString::fromUtf8("HSV") ); + _button->setToolTip( QObject::tr("Switch to RGB") ); + _stack->setCurrentIndex(1); + } else { + _button->setText( QString::fromUtf8("RGB") ); + _button->setToolTip( QObject::tr("Switch to HSV") ); + _stack->setCurrentIndex(0); + } +} + +NATRON_NAMESPACE_EXIT + +NATRON_NAMESPACE_USING +#include "moc_ColorSelectorWidget.cpp" diff --git a/Gui/ColorSelectorWidget.h b/Gui/ColorSelectorWidget.h new file mode 100644 index 0000000000..e973ce478b --- /dev/null +++ b/Gui/ColorSelectorWidget.h @@ -0,0 +1,156 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * This file is part of Natron , + * (C) 2018-2021 The Natron developers + * (C) 2013-2018 INRIA and Alexandre Gauthier-Foichat + * + * Natron is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Natron is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Natron. If not, see + * ***** END LICENSE BLOCK ***** */ + +#ifndef Gui_ColorSelectorWidget_h +#define Gui_ColorSelectorWidget_h + +#include "Global/Macros.h" + +CLANG_DIAG_OFF(deprecated) +CLANG_DIAG_OFF(uninitialized) +#include +#include +#include "Gui/QtColorTriangle.h" // from Qt Solutions +#include +#include +#include +CLANG_DIAG_ON(deprecated) +CLANG_DIAG_ON(uninitialized) + +#include "Gui/ScaleSliderQWidget.h" +#include "Gui/SpinBox.h" +#include "Gui/LineEdit.h" +#include "Gui/Button.h" + +NATRON_NAMESPACE_ENTER + +class ColorSelectorWidget : public QWidget +{ + Q_OBJECT + +public: + + explicit ColorSelectorWidget(QWidget *parent = NULL); + void getColor(float *r, float *g, float *b, float *a); + +Q_SIGNALS: + + void colorChanged(float r, float g, float b, float a); + void updateColor(); + +public Q_SLOTS: + + void setColor(float r, float g, float b, float a); + +private: + + SpinBox *_spinR; + SpinBox *_spinG; + SpinBox *_spinB; + SpinBox *_spinH; + SpinBox *_spinS; + SpinBox *_spinV; + SpinBox *_spinA; + + ScaleSliderQWidget *_slideR; + ScaleSliderQWidget *_slideG; + ScaleSliderQWidget *_slideB; + ScaleSliderQWidget *_slideH; + ScaleSliderQWidget *_slideS; + ScaleSliderQWidget *_slideV; + ScaleSliderQWidget *_slideA; + + QtColorTriangle *_triangle; + + LineEdit *_hex; + + Button *_button; + + QStackedWidget *_stack; + + void setRedChannel(float value); + void setGreenChannel(float value); + void setBlueChannel(float value); + void setHueChannel(float value); + void setSaturationChannel(float value); + void setValueChannel(float value); + void setAlphaChannel(float value); + void setTriangle(float r, float g, float b, float a); + void setHex(const QColor &color); + + void announceColorChange(); + + void setTriangleSize(); + +private Q_SLOTS: + + void handleTriangleColorChanged(const QColor &color, bool announce = true); + + void manageColorRGBChanged(bool announce = true); + void manageColorHSVChanged(bool announce = true); + void manageColorAlphaChanged(bool announce = true); + + void handleSpinRChanged(double value); + void handleSpinGChanged(double value); + void handleSpinBChanged(double value); + void handleSpinHChanged(double value); + void handleSpinSChanged(double value); + void handleSpinVChanged(double value); + void handleSpinAChanged(double value); + + void handleHexChanged(); + + void handleSliderRMoved(double value); + void handleSliderGMoved(double value); + void handleSliderBMoved(double value); + void handleSliderHMoved(double value); + void handleSliderSMoved(double value); + void handleSliderVMoved(double value); + void handleSliderAMoved(double value); + + void setSliderHColor(); + void setSliderSColor(); + void setSliderVColor(); + + void handleButtonClicked(bool checked); + + // workaround for QToolButton+QWidgetAction + // triggered signal(s) are never emitted!? + bool event(QEvent*e) override + { + if (e->type() == QEvent::Show) { + Q_EMIT updateColor(); + } + return QWidget::event(e); + } + + // https://bugreports.qt.io/browse/QTBUG-47406 + void mousePressEvent(QMouseEvent *e) override + { + e->accept(); + } + void mouseReleaseEvent(QMouseEvent *e) override + { + e->accept(); + } +}; + +NATRON_NAMESPACE_EXIT + +#endif // Gui_ColorSelectorWidget_h diff --git a/Gui/Gui.pro b/Gui/Gui.pro index 6f305f1e28..4ffe3a2177 100644 --- a/Gui/Gui.pro +++ b/Gui/Gui.pro @@ -80,6 +80,7 @@ SOURCES += \ ChannelsComboBox.cpp \ ClickableLabel.cpp \ ColoredFrame.cpp \ + ColorSelectorWidget.cpp \ ComboBox.cpp \ CurveEditor.cpp \ CurveEditorUndoRedo.cpp \ @@ -183,6 +184,7 @@ SOURCES += \ PropertiesBinWrapper.cpp \ PyGuiApp.cpp \ PythonPanels.cpp \ + QtColorTriangle.cpp \ QtEnumConvert.cpp \ RenderStatsDialog.cpp \ ResizableMessageBox.cpp \ @@ -235,6 +237,7 @@ HEADERS += \ ChannelsComboBox.h \ ClickableLabel.h \ ColoredFrame.h \ + ColorSelectorWidget.h \ ComboBox.h \ CurveEditor.h \ CurveEditorUndoRedo.h \ @@ -324,6 +327,7 @@ HEADERS += \ PyGuiApp.h \ Pyside_Gui_Python.h \ PythonPanels.h \ + QtColorTriangle.h \ QtEnumConvert.h \ RegisteredTabs.h \ RenderStatsDialog.h \ diff --git a/Gui/KnobGuiColor.cpp b/Gui/KnobGuiColor.cpp index 51feaaed71..38c6612b39 100644 --- a/Gui/KnobGuiColor.cpp +++ b/Gui/KnobGuiColor.cpp @@ -34,13 +34,13 @@ CLANG_DIAG_OFF(uninitialized) #include #include #include -#include #include #include #include #include #include #include +#include GCC_DIAG_UNUSED_PRIVATE_FIELD_OFF // /opt/local/include/QtGui/qmime.h:119:10: warning: private field 'type' is not used [-Wunused-private-field] #include @@ -207,7 +207,8 @@ KnobGuiColor::KnobGuiColor(KnobIPtr knob, : KnobGuiValue(knob, container) , _knob( boost::dynamic_pointer_cast(knob) ) , _colorLabel(0) - , _colorDialogButton(0) + , _colorSelector(0) + , _colorSelectorButton(0) , _lastColor() , _useSimplifiedUI(true) { @@ -302,15 +303,32 @@ KnobGuiColor::addExtraWidgets(QHBoxLayout* containerLayout) containerLayout->addSpacing( TO_DPIX(5) ); } - QPixmap buttonPix; - appPTR->getIcon(NATRON_PIXMAP_COLORWHEEL, NATRON_MEDIUM_BUTTON_ICON_SIZE, &buttonPix); - _colorDialogButton = new Button( QIcon(buttonPix), QString(), containerLayout->widget() ); - _colorDialogButton->setFixedSize(medSize); - _colorDialogButton->setIconSize(medIconSize); - _colorDialogButton->setToolTip( NATRON_NAMESPACE::convertFromPlainText(tr("Open the color dialog."), NATRON_NAMESPACE::WhiteSpaceNormal) ); - _colorDialogButton->setFocusPolicy(Qt::NoFocus); - QObject::connect( _colorDialogButton, SIGNAL(clicked()), this, SLOT(showColorDialog()) ); - containerLayout->addWidget(_colorDialogButton); + // add color selector popup + QPixmap colorSelectorPix; + appPTR->getIcon(NATRON_PIXMAP_COLORWHEEL, NATRON_MEDIUM_BUTTON_ICON_SIZE, &colorSelectorPix); + + _colorSelectorButton = new QToolButton( containerLayout->widget() ); + _colorSelectorButton->setObjectName( QString::fromUtf8("ColorSelectorButton") ); + _colorSelectorButton->setIcon( QIcon(colorSelectorPix) ); + _colorSelectorButton->setFixedSize(medSize); + _colorSelectorButton->setIconSize(medIconSize); + _colorSelectorButton->setPopupMode(QToolButton::InstantPopup); + _colorSelectorButton->setArrowType(Qt::NoArrow); + _colorSelectorButton->setAutoRaise(false); + _colorSelectorButton->setCheckable(false); + _colorSelectorButton->setToolTip( NATRON_NAMESPACE::convertFromPlainText(tr("Open Color Selector"), NATRON_NAMESPACE::WhiteSpaceNormal) ); + _colorSelectorButton->setFocusPolicy(Qt::NoFocus); + + _colorSelector = new ColorSelectorWidget( containerLayout->widget() ); + QObject::connect( _colorSelector, SIGNAL( colorChanged(float, float, float, float) ), + this, SLOT( onColorSelectorChanged(float, float, float, float) ) ); + QObject::connect( _colorSelector, SIGNAL( updateColor() ), + this, SLOT( updateColorSelector() ) ); + + QWidgetAction *colorPopupAction = new QWidgetAction( containerLayout->widget() ); + colorPopupAction->setDefaultWidget(_colorSelector); + _colorSelectorButton->addAction(colorPopupAction); + containerLayout->addWidget(_colorSelectorButton); if (_useSimplifiedUI) { KnobGuiValue::_hide(); @@ -324,6 +342,36 @@ KnobGuiColor::onMustShowAllDimension() onDimensionSwitchClicked(true); } +void +KnobGuiColor::onColorSelectorChanged(float r, + float g, + float b, + float a) +{ + KnobColorPtr knob = _knob.lock(); + int nDims = knob->getDimension(); + + assert(nDims == 1 || nDims == 3 || nDims == 4); + if (nDims != 1 && nDims != 3 && nDims != 4) { + throw std::logic_error("A color Knob can only have dimension 1, 3 or 4"); + } + + if (nDims == 1) { + knob->setValue(r, ViewSpec::all(), 0); + } else if (nDims == 3) { + knob->setValues(r, g, b, + ViewSpec::all(), + eValueChangedReasonNatronInternalEdited); + } else if (nDims == 4) { + knob->setValues(r, g, b, a, + ViewSpec::all(), + eValueChangedReasonNatronInternalEdited); + } + if ( getGui() ) { + getGui()->setDraftRenderEnabled(true); + } +} + void KnobGuiColor::updateLabel(double r, double g, @@ -373,7 +421,7 @@ KnobGuiColor::_hide() KnobGuiValue::_hide(); } _colorLabel->hide(); - _colorDialogButton->hide(); + _colorSelectorButton->hide(); } void @@ -383,7 +431,7 @@ KnobGuiColor::_show() KnobGuiValue::_show(); } _colorLabel->show(); - _colorDialogButton->show(); + _colorSelectorButton->show(); } void @@ -457,49 +505,13 @@ KnobGuiColor::onDimensionsExpanded() void KnobGuiColor::setEnabledExtraGui(bool enabled) { - _colorDialogButton->setEnabled(enabled); _colorLabel->setEnabledMode(enabled); + _colorSelectorButton->setEnabled(enabled); } void -KnobGuiColor::onDialogCurrentColorChanged(const QColor & color) +KnobGuiColor::updateColorSelector() { - KnobColorPtr knob = _knob.lock(); - bool isSimple = _useSimplifiedUI; - int nDims = knob->getDimension(); - - assert(nDims == 1 || nDims == 3 || nDims == 4); - if (nDims != 1 && nDims != 3 && nDims != 4) { - throw std::logic_error("A color Knob can only have dimension 1, 3 or 4"); - } - - if (nDims == 1) { - knob->setValue(color.redF(), ViewSpec::all(), 0); - } else if (nDims == 3) { - knob->setValues(isSimple ? color.redF() : Color::from_func_srgb( color.redF() ), - isSimple ? color.greenF() : Color::from_func_srgb( color.greenF() ), - isSimple ? color.blueF() : Color::from_func_srgb( color.blueF() ), - ViewSpec::all(), - eValueChangedReasonNatronInternalEdited); - } else if (nDims == 4) { - knob->setValues(isSimple ? color.redF() : Color::from_func_srgb( color.redF() ), - isSimple ? color.greenF() : Color::from_func_srgb( color.greenF() ), - isSimple ? color.blueF() : Color::from_func_srgb( color.blueF() ), - color.alphaF(), - ViewSpec::all(), - eValueChangedReasonNatronInternalEdited); - } - if ( getGui() ) { - getGui()->setDraftRenderEnabled(true); - } -} - -void -KnobGuiColor::showColorDialog() -{ - QColorDialog dialog( _colorLabel->parentWidget() ); - - dialog.setOption(QColorDialog::DontUseNativeDialog); KnobColorPtr knob = _knob.lock(); const int nDims = knob->getDimension(); double curR = knob->getValue(0); @@ -509,71 +521,22 @@ KnobGuiColor::showColorDialog() throw std::logic_error("A color Knob can only have dimension 1, 3 or 4"); } - _lastColor[0] = curR; double curG = curR; double curB = curR; double curA = 1.; if (nDims > 1) { curG = knob->getValue(1); - _lastColor[1] = curG; curB = knob->getValue(2); - _lastColor[2] = curB; } if (nDims > 3) { - dialog.setOption(QColorDialog::ShowAlphaChannel); curA = knob->getValue(3); - _lastColor[3] = curA; - } - - bool isSimple = _useSimplifiedUI; - QColor curColor; - curColor.setRgbF( Image::clamp(isSimple ? curR : Color::to_func_srgb(curR), 0., 1.), - Image::clamp(isSimple ? curG : Color::to_func_srgb(curG), 0., 1.), - Image::clamp(isSimple ? curB : Color::to_func_srgb(curB), 0., 1.), - Image::clamp(curA, 0., 1.) ); - dialog.setCurrentColor(curColor); - QObject::connect( &dialog, SIGNAL(currentColorChanged(QColor)), this, SLOT(onDialogCurrentColorChanged(QColor)) ); - if ( !dialog.exec() ) { - if (nDims == 3) { - knob->setValues(_lastColor[0], _lastColor[1], _lastColor[2], ViewSpec::all(), eValueChangedReasonNatronGuiEdited); - } else if (nDims == 4) { - knob->setValues(_lastColor[0], _lastColor[1], _lastColor[2], _lastColor[3], ViewSpec::all(), eValueChangedReasonNatronGuiEdited); - } else if (nDims == 1) { - knob->setValue(_lastColor[0], ViewSpec::all(), 0, eValueChangedReasonNatronGuiEdited, NULL); - } - } else { - QColor userColor = dialog.currentColor(); - std::vector color(4); - color[0] = isSimple ? userColor.redF() : Color::from_func_srgb( userColor.redF() ); - color[1] = isSimple ? userColor.greenF() : Color::from_func_srgb( userColor.greenF() ); - color[2] = isSimple ? userColor.blueF() : Color::from_func_srgb( userColor.blueF() ); - color[3] = userColor.alphaF(); - - for (int i = 0; i < 3; ++i) { - SpinBox* sb = 0; - getSpinBox(i, &sb); - assert(sb); - sb->setValue(color[i]); - } - - // Refresh the last value so that the undo command retrieves the value that was prior to opening the dialog - if (nDims == 3) { - knob->setValues(_lastColor[0], _lastColor[1], _lastColor[2], ViewSpec::all(), eValueChangedReasonUserEdited); - } else if (nDims == 4) { - knob->setValues(_lastColor[0], _lastColor[1], _lastColor[2], _lastColor[3], ViewSpec::all(), eValueChangedReasonUserEdited); - } else if (nDims == 1) { - knob->setValue(_lastColor[0], ViewSpec::all(), 0, eValueChangedReasonUserEdited); - } - - onSpinBoxValueChanged(); - } - if ( getGui() ) { - getGui()->setDraftRenderEnabled(false); - } - //knob->evaluateValueChange(0, knob->getCurrentTime(), ViewIdx(0), eValueChangedReasonNatronGuiEdited); -} // showColorDialog + _colorSelector->setColor( Image::clamp(curR, 0., 1.), + Image::clamp(curG, 0., 1.), + Image::clamp(curB, 0., 1.), + Image::clamp(curA, 0., 1.) ); +} bool KnobGuiColor::isAutoFoldDimensionsEnabled() const diff --git a/Gui/KnobGuiColor.h b/Gui/KnobGuiColor.h index fc9b7367ec..933424f8a4 100644 --- a/Gui/KnobGuiColor.h +++ b/Gui/KnobGuiColor.h @@ -36,6 +36,7 @@ CLANG_DIAG_OFF(uninitialized) #include #include #include +#include CLANG_DIAG_ON(deprecated) CLANG_DIAG_ON(uninitialized) @@ -48,6 +49,7 @@ CLANG_DIAG_ON(uninitialized) #include "Gui/CurveSelection.h" #include "Gui/KnobGuiValue.h" +#include "Gui/ColorSelectorWidget.h" #include "Gui/AnimatedCheckBox.h" #include "Gui/Label.h" #include "Gui/GuiFwd.h" @@ -129,7 +131,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON public Q_SLOTS: - void showColorDialog(); + void updateColorSelector(); void setPickingEnabled(bool enabled); @@ -137,8 +139,7 @@ public Q_SLOTS: void onMustShowAllDimension(); - void onDialogCurrentColorChanged(const QColor & color); - + void onColorSelectorChanged(float r, float g, float b, float a); Q_SIGNALS: @@ -185,7 +186,8 @@ public Q_SLOTS: KnobColorWPtr _knob; ColorPickerLabel *_colorLabel; - Button *_colorDialogButton; + ColorSelectorWidget *_colorSelector; + QToolButton *_colorSelectorButton; std::vector _lastColor; bool _useSimplifiedUI; }; diff --git a/Gui/QtColorTriangle.cpp b/Gui/QtColorTriangle.cpp new file mode 100644 index 0000000000..bee6f20a17 --- /dev/null +++ b/Gui/QtColorTriangle.cpp @@ -0,0 +1,1516 @@ +/**************************************************************************** +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Solutions Commercial License Agreement provided +** with the Software or, alternatively, in accordance with the terms +** contained in a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** Please note Third Party Software included with Qt Solutions may impose +** additional restrictions and it is the user's responsibility to ensure +** that they have met the licensing requirements of the GPL, LGPL, or Qt +** Solutions Commercial license and the relevant license of the Third +** Party Software they are using. +** +** If you are unsure which license is appropriate for your use, please +** contact Nokia at qt-info@nokia.com. +** +****************************************************************************/ + +/* + This widget is maintained in Natron under the GNU Lesser General Public 2.1 license. +*/ + +#include "QtColorTriangle.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/*! \class QtColorTriangle + + \brief The QtColorTriangle class provides a triangular color + selection widget. + + This widget uses the HSV color model, and is therefore useful for + selecting colors by eye. + + The triangle in the center of the widget is used for selecting + saturation and value, and the surrounding circle is used for + selecting hue. + + Use setColor() and color() to set and get the current color. + + \img colortriangle.png +*/ + +/*! \fn QtColorTriangle::colorChanged(const QColor &color) + + Whenever the color triangles color changes this signal is emitted + with the new \a color. +*/ + +const double PI = 3.14159265358979323846264338327950288419717; +const double TWOPI = 2.0*PI; + +/* + Used to store color values in the range 0..255 as doubles. +*/ +struct DoubleColor +{ + double r, g, b; + + DoubleColor() : r(0.0), g(0.0), b(0.0) {} + DoubleColor(double red, double green, double blue) : r(red), g(green), b(blue) {} + DoubleColor(const DoubleColor &c) : r(c.r), g(c.g), b(c.b) {} +}; + +/* + Used to store pairs of DoubleColor and DoublePoint in one structure. +*/ +struct Vertex { + DoubleColor color; + QPointF point; + + Vertex(const DoubleColor &c, const QPointF &p) : color(c), point(p) {} + Vertex(const QColor &c, const QPointF &p) + : color(DoubleColor((double) c.red(), (double) c.green(), + (double) c.blue())), point(p) {} +}; + +/*! \internal + +Swaps the Vertex at *a with the one at *b. + */ +static void swap(Vertex **a, Vertex **b) +{ + Vertex *tmp = *a; + *a = *b; + *b = tmp; +} + +/*! + Constructs a color triangle widget with the given \a parent. +*/ +QtColorTriangle::QtColorTriangle(QWidget *parent) + : QWidget(parent), bg(sizeHint(), QImage::Format_RGB32), selMode(Idle) +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + setFocusPolicy(Qt::StrongFocus); + + mustGenerateBackground = true; + + QColor tmp; + tmp.setHsv(76, 184, 206); + setColor(tmp); +} + +/*! + Destructs the color triangle. +*/ +QtColorTriangle::~QtColorTriangle() +{ +} + +/*! + \internal + + Generates the first background image. +*/ +void QtColorTriangle::polish() +{ + outerRadius = (contentsRect().width() - 1) / 2; + if ((contentsRect().height() - 1) / 2 < outerRadius) + outerRadius = (contentsRect().height() - 1) / 2; + + penWidth = (int) floor(outerRadius / 50.0); + ellipseSize = (int) floor(outerRadius / 12.5); + + double cx = (double) contentsRect().center().x(); + double cy = (double) contentsRect().center().y(); + + pa = QPointF(cx + (cos(a) * (outerRadius - (outerRadius / 5.0))), + cy - (sin(a) * (outerRadius - (outerRadius / 5.0)))); + pb = QPointF(cx + (cos(b) * (outerRadius - (outerRadius / 5.0))), + cy - (sin(b) * (outerRadius - (outerRadius / 5.0)))); + pc = QPointF(cx + (cos(c) * (outerRadius - (outerRadius / 5.0))), + cy - (sin(c) * (outerRadius - (outerRadius / 5.0)))); + pd = QPointF(cx + (cos(a) * (outerRadius - (outerRadius / 10.0))), + cy - (sin(a) * (outerRadius - (outerRadius / 10.0)))); + + // Find the current position of the selector + selectorPos = pointFromColor(curColor); + + update(); +} + +/*! \reimp + */ +QSize QtColorTriangle::sizeHint() const +{ + return QSize(100, 100); +} + +/*! + Forces the triangle widget to always be square. Returns the value + \a w. +*/ +int QtColorTriangle::heightForWidth(int w) const +{ + return w; +} + +/*! + \internal + + Generates a new static background image. This function is called + initially, and in resizeEvent. +*/ +void QtColorTriangle::genBackground() +{ + // Find the inner radius of the hue donut. + double innerRadius = outerRadius - outerRadius / 5; + + // Create an image of the same size as the contents rect. + bg = QImage(contentsRect().size(), QImage::Format_RGB32); + QPainter p(&bg); + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.fillRect(bg.rect(), palette().mid()); + + QConicalGradient gradient(bg.rect().center(), 90); + QColor color; + for (double i = 0; i <= 1.0; i += 0.1) { +#if QT_VERSION < 0x040100 + color.setHsv(int(i * 360.0), 255, 255); +#else + color.setHsv(int(360.0 - (i * 360.0)), 255, 255); +#endif + gradient.setColorAt(i, color); + } + + QRectF innerRadiusRect(bg.rect().center().x() - innerRadius, bg.rect().center().y() - innerRadius, + innerRadius * 2 + 1, innerRadius * 2 + 1); + QRectF outerRadiusRect(bg.rect().center().x() - outerRadius, bg.rect().center().y() - outerRadius, + outerRadius * 2 + 1, outerRadius * 2 + 1); + QPainterPath path; + path.addEllipse(innerRadiusRect); + path.addEllipse(outerRadiusRect); + + p.save(); + p.setClipPath(path); + p.fillRect(bg.rect(), gradient); + p.restore(); + + double penThickness = bg.width() / 400.0; + for (int f = 0; f <= 5760; f += 20) { + int value = int((0.5 + cos(((f - 1800) / 5760.0) * TWOPI) / 2) * 255.0); + + color.setHsv(int((f / 5760.0) * 360.0), 128 + (255 - value)/2, 255 - (255 - value)/4); + p.setPen(QPen(color, penThickness)); + p.drawArc(innerRadiusRect, 1440 - f, 20); + + color.setHsv(int((f / 5760.0) * 360.0), 128 + value/2, 255 - value/4); + p.setPen(QPen(color, penThickness)); + p.drawArc(outerRadiusRect, 2880 - 1440 - f, 20); + } + return; +} + +/*! + \internal + + Selects new hue or saturation/value values, depending on where the + mouse button was pressed initially. +*/ +void QtColorTriangle::mouseMoveEvent(QMouseEvent *e) +{ + if ((e->buttons() & Qt::LeftButton) == 0) + return; + + QPointF depos((double) e->pos().x(), (double) e->pos().y()); + bool newColor = false; + + if (selMode == SelectingHue) { + // If selecting hue, find the new angles for the points a,b,c + // of the triangle. The following update() will then redraw + // the triangle. + a = angleAt(depos, contentsRect()); + b = a + TWOPI / 3.0; + c = b + TWOPI / 3.0; + if (b > TWOPI) b -= TWOPI; + if (c > TWOPI) c -= TWOPI; + + double am = a - PI/2; + if (am < 0) am += TWOPI; + + curHue = 360 - (int) (((am) * 360.0) / TWOPI); + int h,s,v; + curColor.getHsv(&h, &s, &v); + + if (curHue != h) { + newColor = true; + curColor.setHsv(curHue, s, v); + } + + double cx = (double) contentsRect().center().x(); + double cy = (double) contentsRect().center().y(); + + pa = QPointF(cx + (cos(a) * (outerRadius - (outerRadius / 5.0))), + cy - (sin(a) * (outerRadius - (outerRadius / 5.0)))); + pb = QPointF(cx + (cos(b) * (outerRadius - (outerRadius / 5.0))), + cy - (sin(b) * (outerRadius - (outerRadius / 5.0)))); + pc = QPointF(cx + (cos(c) * (outerRadius - (outerRadius / 5.0))), + cy - (sin(c) * (outerRadius - (outerRadius / 5.0)))); + pd = QPointF(cx + (cos(a) * (outerRadius - (outerRadius / 10.0))), + cy - (sin(a) * (outerRadius - (outerRadius / 10.0)))); + + selectorPos = pointFromColor(curColor); + } else { + Vertex aa(Qt::black, pa); + Vertex bb(Qt::black, pb); + Vertex cc(Qt::black, pc); + + Vertex *p1 = &aa; + Vertex *p2 = &bb; + Vertex *p3 = &cc; + if (p1->point.y() > p2->point.y()) swap(&p1, &p2); + if (p1->point.y() > p3->point.y()) swap(&p1, &p3); + if (p2->point.y() > p3->point.y()) swap(&p2, &p3); + + selectorPos = movePointToTriangle(depos.x(), depos.y(), aa, bb, cc); + QColor col = colorFromPoint(selectorPos); + if (col != curColor) { + // Ensure that hue does not change when selecting + // saturation and value. + int h,s,v; + col.getHsv(&h, &s, &v); + curColor.setHsv(curHue, s, v); + newColor = true; + } + } + + if (newColor) + Q_EMIT colorChanged(curColor); + + update(); +} + +/*! + \internal + + When the left mouse button is pressed, this function determines + what part of the color triangle the cursor is, and from that it + initiates either selecting the hue (outside the triangle's area) + or the saturation/value (inside the triangle's area). +*/ +void QtColorTriangle::mousePressEvent(QMouseEvent *e) +{ + // Only respond to the left mouse button. + if (e->button() != Qt::LeftButton) + return; + + QPointF depos((double) e->pos().x(), (double) e->pos().y()); + double rad = radiusAt(depos, contentsRect()); + bool newColor = false; + + // As in mouseMoveEvent, either find the a,b,c angles or the + // radian position of the selector, then order an update. + if (rad > (outerRadius - (outerRadius / 5))) { + selMode = SelectingHue; + + a = angleAt(depos, contentsRect()); + b = a + TWOPI / 3.0; + c = b + TWOPI / 3.0; + if (b > TWOPI) b -= TWOPI; + if (c > TWOPI) c -= TWOPI; + + double am = a - PI/2; + if (am < 0) am += TWOPI; + + curHue = 360 - (int) ((am * 360.0) / TWOPI); + int h,s,v; + curColor.getHsv(&h, &s, &v); + + if (h != curHue) { + newColor = true; + curColor.setHsv(curHue, s, v); + } + + double cx = (double) contentsRect().center().x(); + double cy = (double) contentsRect().center().y(); + + pa = QPointF(cx + (cos(a) * (outerRadius - (outerRadius / 5.0))), + cy - (sin(a) * (outerRadius - (outerRadius / 5.0)))); + pb = QPointF(cx + (cos(b) * (outerRadius - (outerRadius / 5.0))), + cy - (sin(b) * (outerRadius - (outerRadius / 5.0)))); + pc = QPointF(cx + (cos(c) * (outerRadius - (outerRadius / 5.0))), + cy - (sin(c) * (outerRadius - (outerRadius / 5.0)))); + pd = QPointF(cx + (cos(a) * (outerRadius - (outerRadius / 10.0))), + cy - (sin(a) * (outerRadius - (outerRadius / 10.0)))); + + selectorPos = pointFromColor(curColor); + Q_EMIT colorChanged(curColor); + } else { + selMode = SelectingSatValue; + + Vertex aa(Qt::black, pa); + Vertex bb(Qt::black, pb); + Vertex cc(Qt::black, pc); + + Vertex *p1 = &aa; + Vertex *p2 = &bb; + Vertex *p3 = &cc; + if (p1->point.y() > p2->point.y()) swap(&p1, &p2); + if (p1->point.y() > p3->point.y()) swap(&p1, &p3); + if (p2->point.y() > p3->point.y()) swap(&p2, &p3); + + selectorPos = movePointToTriangle(depos.x(), depos.y(), aa, bb, cc); + QColor col = colorFromPoint(selectorPos); + if (col != curColor) { + curColor = col; + newColor = true; + } + } + + if (newColor) + Q_EMIT colorChanged(curColor); + + update(); +} + +/*! + \internal + + Stops selecting of colors with the mouse. +*/ +void QtColorTriangle::mouseReleaseEvent(QMouseEvent *e) +{ + if (e->button() == Qt::LeftButton) + selMode = Idle; +} + +/*! + \internal +*/ +void QtColorTriangle::keyPressEvent(QKeyEvent *e) +{ + switch (e->key()) { + case Qt::Key_Left: { + --curHue; + if (curHue < 0) curHue += 360; + int h,s,v; + curColor.getHsv(&h, &s, &v); + QColor tmp; + tmp.setHsv(curHue, s, v); + setColor(tmp); + } + break; + case Qt::Key_Right: { + ++curHue; + if (curHue > 359) curHue -= 360; + int h,s,v; + curColor.getHsv(&h, &s, &v); + QColor tmp; + tmp.setHsv(curHue, s, v); + setColor(tmp); + } + break; + case Qt::Key_Up: { + int h,s,v; + curColor.getHsv(&h, &s, &v); + QColor tmp; + if (e->modifiers() & Qt::ShiftModifier) { + if (s > 5) s -= 5; + else s = 0; + } else { + if (v > 5) v -= 5; + else v = 0; + } + tmp.setHsv(curHue, s, v); + setColor(tmp); + } + break; + case Qt::Key_Down: { + int h,s,v; + curColor.getHsv(&h, &s, &v); + QColor tmp; + if (e->modifiers() & Qt::ShiftModifier) { + if (s < 250) s += 5; + else s = 255; + } else { + if (v < 250) v += 5; + else v = 255; + } + tmp.setHsv(curHue, s, v); + setColor(tmp); + } + break; + }; +} + +/*! + \internal + + Regenerates the background image and sends an update. +*/ +void QtColorTriangle::resizeEvent(QResizeEvent *) +{ + outerRadius = (contentsRect().width() - 1) / 2; + if ((contentsRect().height() - 1) / 2 < outerRadius) + outerRadius = (contentsRect().height() - 1) / 2; + + penWidth = (int) floor(outerRadius / 50.0); + ellipseSize = (int) floor(outerRadius / 12.5); + + double cx = (double) contentsRect().center().x(); + double cy = (double) contentsRect().center().y(); + + pa = QPointF(cx + (cos(a) * (outerRadius - (outerRadius / 5.0))), + cy - (sin(a) * (outerRadius - (outerRadius / 5.0)))); + pb = QPointF(cx + (cos(b) * (outerRadius - (outerRadius / 5.0))), + cy - (sin(b) * (outerRadius - (outerRadius / 5.0)))); + pc = QPointF(cx + (cos(c) * (outerRadius - (outerRadius / 5.0))), + cy - (sin(c) * (outerRadius - (outerRadius / 5.0)))); + pd = QPointF(cx + (cos(a) * (outerRadius - (outerRadius / 10.0))), + cy - (sin(a) * (outerRadius - (outerRadius / 10.0)))); + + // Find the current position of the selector + selectorPos = pointFromColor(curColor); + + mustGenerateBackground = true; + update(); +} + +/*! \reimp + +First copies a background image of the hue donut and its + background color onto the frame, then draws the color triangle, + and finally the selectors. +*/ +void QtColorTriangle::paintEvent(QPaintEvent *e) +{ + QPainter p(this); + if (e->rect().intersects(contentsRect())) + p.setClipRegion(e->region().intersected(contentsRect())); + if (mustGenerateBackground) { + genBackground(); + mustGenerateBackground = false; + } + + // Blit the static generated background with the hue gradient onto + // the double buffer. + QImage buf = bg.copy(); + + // Draw the trigon + int h,s,v; + curColor.getHsv(&h, &s, &v); + + // Find the color with only the hue, and max value and saturation + QColor hueColor; + hueColor.setHsv(curHue, 255, 255); + + // Draw the triangle + drawTrigon(&buf, pa, pb, pc, hueColor); + + // Slow step: convert the image to a pixmap + QPixmap pix = QPixmap::fromImage(buf); + QPainter painter(&pix); + painter.setRenderHint(QPainter::Antialiasing); + + // Draw an outline of the triangle + QColor halfAlpha(0, 0, 0, 128); + painter.setPen(QPen(halfAlpha, 0)); + painter.drawLine(pa, pb); + painter.drawLine(pb, pc); + painter.drawLine(pc, pa); + + int ri, gi, bi; + hueColor.getRgb(&ri, &gi, &bi); + if ((ri * 30) + (gi * 59) + (bi * 11) > 12800) + painter.setPen(QPen(Qt::black, penWidth)); + else + painter.setPen(QPen(Qt::white, penWidth)); + painter.drawEllipse((int) (pd.x() - ellipseSize / 2.0), + (int) (pd.y() - ellipseSize / 2.0), + ellipseSize, ellipseSize); + + curColor.getRgb(&ri, &gi, &bi); + + // Find a color for painting the selector based on the brightness + // value of the color. + if ((ri * 30) + (gi * 59) + (bi * 11) > 12800) + painter.setPen(QPen(Qt::black, penWidth)); + else + painter.setPen(QPen(Qt::white, penWidth)); + + // Draw the selector ellipse. + painter.drawEllipse(QRectF(selectorPos.x() - ellipseSize / 2.0, + selectorPos.y() - ellipseSize / 2.0, + ellipseSize + 0.5, ellipseSize + 0.5)); + + // Blit + p.drawPixmap(contentsRect().topLeft(), pix); +} + +/*! \internal + +Draws a trigon (polygon with three corners \a pa, \a pb and \a pc + and three edges), using \a painter. + + Fills the trigon with a gradient, where the \a pa point has the + color \a color, \a pb is black and \a bc is white. Bilinear + gradient. +*/ +void QtColorTriangle::drawTrigon(QImage *buf, const QPointF &pa, + const QPointF &pb, const QPointF &pc, + const QColor &color) +{ + // Create three Vertex objects. A Vertex contains a double-point + // coordinate and a color. + // pa is the tip of the arrow + // pb is the black corner + // pc is the white corner + Vertex aa(color, pa); + Vertex bb(Qt::black, pb); + Vertex cc(Qt::white, pc); + + // Sort. Make p1 above p2, which is above p3 (using y coordinate). + // Bubble sorting is fastest here. + Vertex *p1 = &aa; + Vertex *p2 = &bb; + Vertex *p3 = &cc; + if (p1->point.y() > p2->point.y()) swap(&p1, &p2); + if (p1->point.y() > p3->point.y()) swap(&p1, &p3); + if (p2->point.y() > p3->point.y()) swap(&p2, &p3); + + // All the three y deltas are >= 0 + double p1p2ydist = p2->point.y() - p1->point.y(); + double p1p3ydist = p3->point.y() - p1->point.y(); + double p2p3ydist = p3->point.y() - p2->point.y(); + double p1p2xdist = p2->point.x() - p1->point.x(); + double p1p3xdist = p3->point.x() - p1->point.x(); + double p2p3xdist = p3->point.x() - p2->point.x(); + + // The first x delta decides wether we have a lefty or a righty + // trigon. + bool lefty = p1p2xdist < 0; + + // Left and right colors and X values. The key in this map is the + // y values. Our goal is to fill these structures with all the + // information needed to do a single pass top-to-bottom, + // left-to-right drawing of the trigon. + QVarLengthArray leftColors; + QVarLengthArray rightColors; + QVarLengthArray leftX; + QVarLengthArray rightX; + + leftColors.resize(int(floor(p3->point.y() + 1))); + rightColors.resize(int(floor(p3->point.y() + 1))); + leftX.resize(int(floor(p3->point.y() + 1))); + rightX.resize(int(floor(p3->point.y() + 1))); + + // Scan longy - find all left and right colors and X-values for + // the tallest edge (p1-p3). + DoubleColor source; + DoubleColor dest; + double r, g, b; + double rdelta, gdelta, bdelta; + double x; + double xdelta; + int y1, y2; + + // Initialize with known values + x = p1->point.x(); + source = p1->color; + dest = p3->color; + r = source.r; + g = source.g; + b = source.b; + y1 = (int) floor(p1->point.y()); + y2 = (int) floor(p3->point.y()); + + // Find slopes (notice that if the y dists are 0, we don't care + // about the slopes) + xdelta = p1p3ydist == 0.0 ? 0.0 : p1p3xdist / p1p3ydist; + rdelta = p1p3ydist == 0.0 ? 0.0 : (dest.r - r) / p1p3ydist; + gdelta = p1p3ydist == 0.0 ? 0.0 : (dest.g - g) / p1p3ydist; + bdelta = p1p3ydist == 0.0 ? 0.0 : (dest.b - b) / p1p3ydist; + + // Calculate gradients using linear approximation + int y; + for (y = y1; y < y2; ++y) { + if (lefty) { + rightColors[y] = DoubleColor(r, g, b); + rightX[y] = x; + } else { + leftColors[y] = DoubleColor(r, g, b); + leftX[y] = x; + } + + r += rdelta; + g += gdelta; + b += bdelta; + x += xdelta; + } + + // Scan top shorty - find all left and right colors and x-values + // for the topmost of the two not-tallest short edges. + x = p1->point.x(); + source = p1->color; + dest = p2->color; + r = source.r; + g = source.g; + b = source.b; + y1 = (int) floor(p1->point.y()); + y2 = (int) floor(p2->point.y()); + + // Find slopes (notice that if the y dists are 0, we don't care + // about the slopes) + xdelta = p1p2ydist == 0.0 ? 0.0 : p1p2xdist / p1p2ydist; + rdelta = p1p2ydist == 0.0 ? 0.0 : (dest.r - r) / p1p2ydist; + gdelta = p1p2ydist == 0.0 ? 0.0 : (dest.g - g) / p1p2ydist; + bdelta = p1p2ydist == 0.0 ? 0.0 : (dest.b - b) / p1p2ydist; + + // Calculate gradients using linear approximation + for (y = y1; y < y2; ++y) { + if (lefty) { + leftColors[y] = DoubleColor(r, g, b); + leftX[y] = x; + } else { + rightColors[y] = DoubleColor(r, g, b); + rightX[y] = x; + } + + r += rdelta; + g += gdelta; + b += bdelta; + x += xdelta; + } + + // Scan bottom shorty - find all left and right colors and + // x-values for the bottommost of the two not-tallest short edges. + x = p2->point.x(); + source = p2->color; + dest = p3->color; + r = source.r; + g = source.g; + b = source.b; + y1 = (int) floor(p2->point.y()); + y2 = (int) floor(p3->point.y()); + + // Find slopes (notice that if the y dists are 0, we don't care + // about the slopes) + xdelta = p2p3ydist == 0.0 ? 0.0 : p2p3xdist / p2p3ydist; + rdelta = p2p3ydist == 0.0 ? 0.0 : (dest.r - r) / p2p3ydist; + gdelta = p2p3ydist == 0.0 ? 0.0 : (dest.g - g) / p2p3ydist; + bdelta = p2p3ydist == 0.0 ? 0.0 : (dest.b - b) / p2p3ydist; + + // Calculate gradients using linear approximation + for (y = y1; y < y2; ++y) { + if (lefty) { + leftColors[y] = DoubleColor(r, g, b); + leftX[y] = x; + } else { + rightColors[y] = DoubleColor(r, g, b); + rightX[y] = x; + } + + r += rdelta; + g += gdelta; + b += bdelta; + x += xdelta; + } + + // Inner loop. For each y in the left map of x-values, draw one + // line from left to right. + const int p3yfloor = int(floor(p3->point.y())); + for (int y = int(floor(p1->point.y())); y < p3yfloor; ++y) { + double lx = leftX[y]; + double rx = rightX[y]; + + int lxi = (int) floor(lx); + int rxi = (int) floor(rx); + DoubleColor rc = rightColors[y]; + DoubleColor lc = leftColors[y]; + + // if the xdist is 0, don't draw anything. + double xdist = rx - lx; + if (xdist != 0.0) { + double r = lc.r; + double g = lc.g; + double b = lc.b; + double rdelta = (rc.r - r) / xdist; + double gdelta = (rc.g - g) / xdist; + double bdelta = (rc.b - b) / xdist; + + QRgb *scanline = reinterpret_cast(buf->scanLine(y)); + scanline += lxi; + + // Inner loop 2. Draws the line from left to right. + for (int i = lxi; i < rxi; ++i) { + *scanline++ = qRgb((int) r, (int) g, (int) b); + r += rdelta; + g += gdelta; + b += bdelta; + } + } + } +} + +/*! \internal + +Sets the color of the triangle to \a col. + */ +void QtColorTriangle::setColor(const QColor &col) +{ + if (col == curColor) + return; + + curColor = col; + + int h, s, v; + curColor.getHsv(&h, &s, &v); + + // Never use an invalid hue to display colors + if (h != -1) + curHue = h; + + a = (((360 - curHue) * TWOPI) / 360.0); + a += PI / 2.0; + if (a > TWOPI) a -= TWOPI; + + b = a + TWOPI/3; + c = b + TWOPI/3; + + if (b > TWOPI) b -= TWOPI; + if (c > TWOPI) c -= TWOPI; + + double cx = (double) contentsRect().center().x(); + double cy = (double) contentsRect().center().y(); + double innerRadius = outerRadius - (outerRadius / 5.0); + double pointerRadius = outerRadius - (outerRadius / 10.0); + + pa = QPointF(cx + (cos(a) * innerRadius), cy - (sin(a) * innerRadius)); + pb = QPointF(cx + (cos(b) * innerRadius), cy - (sin(b) * innerRadius)); + pc = QPointF(cx + (cos(c) * innerRadius), cy - (sin(c) * innerRadius)); + pd = QPointF(cx + (cos(a) * pointerRadius), cy - (sin(a) * pointerRadius)); + + selectorPos = pointFromColor(curColor); + update(); + + Q_EMIT colorChanged(curColor); +} + +/*! \internal + +Returns the current color of the triangle. + */ +QColor QtColorTriangle::color() const +{ + return curColor; +} + +/*! + \internal + + Returns the distance from \a pos to the center of \a rect. +*/ +double QtColorTriangle::radiusAt(const QPointF &pos, const QRect &rect) const +{ + double mousexdist = pos.x() - (double) rect.center().x(); + double mouseydist = pos.y() - (double) rect.center().y(); + return sqrt(mousexdist * mousexdist + mouseydist * mouseydist); +} + +/*! + \internal + + With origin set to the center of \a rect, this function returns + the angle in radians between the line that starts at (0,0) and + ends at (1,0) and the line that stars at (0,0) and ends at \a pos. +*/ +double QtColorTriangle::angleAt(const QPointF &pos, const QRect &rect) const +{ + double mousexdist = pos.x() - (double) rect.center().x(); + double mouseydist = pos.y() - (double) rect.center().y(); + double mouserad = sqrt(mousexdist * mousexdist + mouseydist * mouseydist); + if (mouserad == 0.0) + return 0.0; + + double angle = acos(mousexdist / mouserad); + if (mouseydist >= 0) + angle = TWOPI - angle; + + return angle; +} + +/*! \internal + +Returns a * a. + */ +inline double qsqr(double a) +{ + return a * a; +} + +/*! \internal + +Returns the length of the vector (x,y). + */ +inline double vlen(double x, double y) +{ + return sqrt(qsqr(x) + qsqr(y)); +} + +/*! \internal + +Returns the vector product of (x1,y1) and (x2,y2). + */ +inline double vprod(double x1, double y1, double x2, double y2) +{ + return x1 * x2 + y1 * y2; +} + +/*! \internal + +Returns true if the point cos(p),sin(p) is on the arc between + cos(a1),sin(a1) and cos(a2),sin(a2); otherwise returns false. +*/ +bool angleBetweenAngles(double p, double a1, double a2) +{ + if (a1 > a2) { + a2 += TWOPI; + if (p < PI) p += TWOPI; + } + + return p >= a1 && p < a2; +} + +/*! \internal + + A line from a to b is one of several lines in an equilateral + polygon, and they are drawn counter clockwise. This line therefore + has one side facing in and one facing out of the polygon. This + function determines wether (x,y) is on the inside or outside of + the given line, defined by the "from" coordinate (ax,ay) and the + "to" coordinate (bx,by). + + The point (px,py) is the intersection between the a-b line and the + perpendicular projection of (x,y) onto that line. + + Returns true if (x,y) is above the line; otherwise returns false. + + If ax and bx are equal and ay and by are equal (line is a point), + this function will return true if (x,y) is equal to this point. +*/ +static bool pointAbovePoint(double x, double y, double px, double py, + double ax, double ay, double bx, double by) +{ + bool result = false; + + if (floor(ax) > floor(bx)) { + if (floor(ay) < floor(by)) { + // line is draw upright-to-downleft + if (floor(x) < floor(px) || floor(y) < floor(py)) + result = true; + } else if (floor(ay) > floor(by)) { + // line is draw downright-to-upleft + if (floor(x) > floor(px) || floor(y) < floor(py)) + result = true; + } else { + // line is flat horizontal + if (y < ay) result = true; + } + } else if (floor(ax) < floor(bx)) { + if (floor(ay) < floor(by)) { + // line is draw upleft-to-downright + if (floor(x) < floor(px) || floor(y) > floor(py)) + result = true; + } else if (floor(ay) > floor(by)) { + // line is draw downleft-to-upright + if (floor(x) > floor(px) || floor(y) > floor(py)) + result = true; + } else { + // line is flat horizontal + if (y > ay) + result = true; + } + } else { + // line is vertical + if (floor(ay) < floor(by)) { + if (x < ax) result = true; + } else if (floor(ay) > floor(by)) { + if (x > ax) result = true; + } else { + if (!(x == ax && y == ay)) + result = true; + } + } + + return result; +} + +/*! \internal + +if (ax,ay) to (bx,by) describes a line, and (x,y) is a point on + that line, returns -1 if (x,y) is outside the (ax,ay) bounds, 1 if + it is outside the (bx,by) bounds and 0 if (x,y) is within (ax,ay) + and (bx,by). +*/ +static int pointInLine(double x, double y, double ax, double ay, + double bx, double by) +{ + if (ax > bx) { + if (ay < by) { + // line is draw upright-to-downleft + + // if (x,y) is in on or above the upper right point, + // return -1. + if (y <= ay && x >= ax) + return -1; + + // if (x,y) is in on or below the lower left point, + // return 1. + if (y >= by && x <= bx) + return 1; + } else { + // line is draw downright-to-upleft + + // If the line is flat, only use the x coordinate. + if (floor(ay) == floor(by)) { + // if (x is to the right of the rightmost point, + // return -1. otherwise if x is to the left of the + // leftmost point, return 1. + if (x >= ax) + return -1; + else if (x <= bx) + return 1; + } else { + // if (x,y) is on or below the lower right point, + // return -1. + if (y >= ay && x >= ax) + return -1; + + // if (x,y) is on or above the upper left point, + // return 1. + if (y <= by && x <= bx) + return 1; + } + } + } else { + if (ay < by) { + // line is draw upleft-to-downright + + // If (x,y) is on or above the upper left point, return + // -1. + if (y <= ay && x <= ax) + return -1; + + // If (x,y) is on or below the lower right point, return + // 1. + if (y >= by && x >= bx) + return 1; + } else { + // line is draw downleft-to-upright + + // If the line is flat, only use the x coordinate. + if (floor(ay) == floor(by)) { + if (x <= ax) + return -1; + else if (x >= bx) + return 1; + } else { + // If (x,y) is on or below the lower left point, return + // -1. + if (y >= ay && x <= ax) + return -1; + + // If (x,y) is on or above the upper right point, return + // 1. + if (y <= by && x >= bx) + return 1; + } + } + } + + // No tests proved that (x,y) was outside [(ax,ay),(bx,by)], so we + // assume it's inside the line's bounds. + return 0; +} + +/*! \internal + + \a a, \a b and \a c are corner points of an equilateral triangle. + (\a x,\a y) is an arbitrary point inside or outside this triangle. + + If (x,y) is inside the triangle, this function returns the double + point (x,y). + + Otherwise, the intersection of the perpendicular projection of + (x,y) onto the closest triangle edge is returned, unless this + intersection is outside the triangle's bounds, in which case the + corner closest to the intersection is returned instead. + + Yes, it's trigonometry. +*/ +QPointF QtColorTriangle::movePointToTriangle(double x, double y, const Vertex &a, + const Vertex &b, const Vertex &c) const +{ + // Let v1A be the vector from (x,y) to a. + // Let v2A be the vector from a to b. + // Find the angle alphaA between v1A and v2A. + double v1xA = x - a.point.x(); + double v1yA = y - a.point.y(); + double v2xA = b.point.x() - a.point.x(); + double v2yA = b.point.y() - a.point.y(); + double vpA = vprod(v1xA, v1yA, v2xA, v2yA); + double cosA = vpA / (vlen(v1xA, v1yA) * vlen(v2xA, v2yA)); + double alphaA = acos(cosA); + + // Let v1B be the vector from x to b. + // Let v2B be the vector from b to c. + double v1xB = x - b.point.x(); + double v1yB = y - b.point.y(); + double v2xB = c.point.x() - b.point.x(); + double v2yB = c.point.y() - b.point.y(); + double vpB = vprod(v1xB, v1yB, v2xB, v2yB); + double cosB = vpB / (vlen(v1xB, v1yB) * vlen(v2xB, v2yB)); + double alphaB = acos(cosB); + + // Let v1C be the vector from x to c. + // Let v2C be the vector from c back to a. + double v1xC = x - c.point.x(); + double v1yC = y - c.point.y(); + double v2xC = a.point.x() - c.point.x(); + double v2yC = a.point.y() - c.point.y(); + double vpC = vprod(v1xC, v1yC, v2xC, v2yC); + double cosC = vpC / (vlen(v1xC, v1yC) * vlen(v2xC, v2yC)); + double alphaC = acos(cosC); + + // Find the radian angles between the (1,0) vector and the points + // A, B, C and (x,y). Use this information to determine which of + // the edges we should project (x,y) onto. + double angleA = angleAt(a.point, contentsRect()); + double angleB = angleAt(b.point, contentsRect()); + double angleC = angleAt(c.point, contentsRect()); + double angleP = angleAt(QPointF(x, y), contentsRect()); + + // If (x,y) is in the a-b area, project onto the a-b vector. + if (angleBetweenAngles(angleP, angleA, angleB)) { + // Find the distance from (x,y) to a. Then use the slope of + // the a-b vector with this distance and the angle between a-b + // and a-(x,y) to determine the point of intersection of the + // perpendicular projection from (x,y) onto a-b. + double pdist = sqrt(qsqr(x - a.point.x()) + qsqr(y - a.point.y())); + + // the length of all edges is always > 0 + double p0x = a.point.x() + ((b.point.x() - a.point.x()) / vlen(v2xB, v2yB)) * cos(alphaA) * pdist; + double p0y = a.point.y() + ((b.point.y() - a.point.y()) / vlen(v2xB, v2yB)) * cos(alphaA) * pdist; + + // If (x,y) is above the a-b line, which basically means it's + // outside the triangle, then return its projection onto a-b. + if (pointAbovePoint(x, y, p0x, p0y, a.point.x(), a.point.y(), b.point.x(), b.point.y())) { + // If the projection is "outside" a, return a. If it is + // outside b, return b. Otherwise return the projection. + int n = pointInLine(p0x, p0y, a.point.x(), a.point.y(), b.point.x(), b.point.y()); + if (n < 0) + return a.point; + else if (n > 0) + return b.point; + + return QPointF(p0x, p0y); + } + } else if (angleBetweenAngles(angleP, angleB, angleC)) { + // If (x,y) is in the b-c area, project onto the b-c vector. + double pdist = sqrt(qsqr(x - b.point.x()) + qsqr(y - b.point.y())); + + // the length of all edges is always > 0 + double p0x = b.point.x() + ((c.point.x() - b.point.x()) / vlen(v2xC, v2yC)) * cos(alphaB) * pdist; + double p0y = b.point.y() + ((c.point.y() - b.point.y()) / vlen(v2xC, v2yC)) * cos(alphaB) * pdist; + + if (pointAbovePoint(x, y, p0x, p0y, b.point.x(), b.point.y(), c.point.x(), c.point.y())) { + int n = pointInLine(p0x, p0y, b.point.x(), b.point.y(), c.point.x(), c.point.y()); + if (n < 0) + return b.point; + else if (n > 0) + return c.point; + return QPointF(p0x, p0y); + } + } else if (angleBetweenAngles(angleP, angleC, angleA)) { + // If (x,y) is in the c-a area, project onto the c-a vector. + double pdist = sqrt(qsqr(x - c.point.x()) + qsqr(y - c.point.y())); + + // the length of all edges is always > 0 + double p0x = c.point.x() + ((a.point.x() - c.point.x()) / vlen(v2xA, v2yA)) * cos(alphaC) * pdist; + double p0y = c.point.y() + ((a.point.y() - c.point.y()) / vlen(v2xA, v2yA)) * cos(alphaC) * pdist; + + if (pointAbovePoint(x, y, p0x, p0y, c.point.x(), c.point.y(), a.point.x(), a.point.y())) { + int n = pointInLine(p0x, p0y, c.point.x(), c.point.y(), a.point.x(), a.point.y()); + if (n < 0) + return c.point; + else if (n > 0) + return a.point; + return QPointF(p0x, p0y); + } + } + + // (x,y) is inside the triangle (inside a-b, b-c and a-c). + return QPointF(x, y); +} + +/*! \internal + + Given the color \a col, this function determines the point in the + equilateral triangle defined with (pa, pb, pc) that displays this + color. The function assumes the color at pa has a hue equal to the + hue of \a col, and that pb is black and pc is white. + + In this certain type of triangle, we observe that saturation grows + from the black-color edge towards the black-white edge. The value + grows from the black corner towards the white-color edge. Using + the intersection of the saturation and value points on the three + edges, we are able to determine the point with the same saturation + and value as \a col. +*/ +QPointF QtColorTriangle::pointFromColor(const QColor &col) const +{ + // Simplifications for the corner cases. + if (col == Qt::black) + return pb; + else if (col == Qt::white) + return pc; + + // Find the x and y slopes + double ab_deltax = pb.x() - pa.x(); + double ab_deltay = pb.y() - pa.y(); + double bc_deltax = pc.x() - pb.x(); + double bc_deltay = pc.y() - pb.y(); + double ac_deltax = pc.x() - pa.x(); + double ac_deltay = pc.y() - pa.y(); + + // Extract the h,s,v values of col. + int hue,sat,val; + col.getHsv(&hue, &sat, &val); + + // Find the line that passes through the triangle where the value + // is equal to our color's value. + double p1 = pa.x() + (ab_deltax * (double) (255 - val)) / 255.0; + double q1 = pa.y() + (ab_deltay * (double) (255 - val)) / 255.0; + double p2 = pb.x() + (bc_deltax * (double) val) / 255.0; + double q2 = pb.y() + (bc_deltay * (double) val) / 255.0; + + // Find the line that passes through the triangle where the + // saturation is equal to our color's value. + double p3 = pa.x() + (ac_deltax * (double) (255 - sat)) / 255.0; + double q3 = pa.y() + (ac_deltay * (double) (255 - sat)) / 255.0; + double p4 = pb.x(); + double q4 = pb.y(); + + // Find the intersection between these lines. + double x = 0; + double y = 0; + if (p1 != p2) { + double a = (q2 - q1) / (p2 - p1); + double c = (q4 - q3) / (p4 - p3); + double b = q1 - a * p1; + double d = q3 - c * p3; + + x = (d - b) / (a - c); + y = a * x + b; + } + else { + x = p1; + y = q3 + (x - p3) * (q4 - q3) / (p4 - p3); + } + + return QPointF(x, y); +} + +/*! \internal + + Determines the color in the color triangle at the point \a p. Uses + linear interpolation to find the colors to the left and right of + \a p, then uses the same technique to find the color at \a p using + these two colors. +*/ +QColor QtColorTriangle::colorFromPoint(const QPointF &p) const +{ + // Find the outer radius of the hue gradient. + int outerRadius = (contentsRect().width() - 1) / 2; + if ((contentsRect().height() - 1) / 2 < outerRadius) + outerRadius = (contentsRect().height() - 1) / 2; + + // Find the center coordinates + double cx = (double) contentsRect().center().x(); + double cy = (double) contentsRect().center().y(); + + // Find the a, b and c from their angles, the center of the rect + // and the radius of the hue gradient donut. + QPointF pa(cx + (cos(a) * (outerRadius - (outerRadius / 5.0))), + cy - (sin(a) * (outerRadius - (outerRadius / 5.0)))); + QPointF pb(cx + (cos(b) * (outerRadius - (outerRadius / 5.0))), + cy - (sin(b) * (outerRadius - (outerRadius / 5.0)))); + QPointF pc(cx + (cos(c) * (outerRadius - (outerRadius / 5.0))), + cy - (sin(c) * (outerRadius - (outerRadius / 5.0)))); + + // Find the hue value from the angle of the 'a' point. + double angle = a - PI/2.0; + if (angle < 0) angle += TWOPI; + double hue = (360.0 * angle) / TWOPI; + + // Create the color of the 'a' corner point. We know that b is + // black and c is white. + QColor color; + color.setHsv(360 - (int) floor(hue), 255, 255); + + // See also drawTrigon(), which basically does exactly the same to + // determine all colors in the trigon. + Vertex aa(color, pa); + Vertex bb(Qt::black, pb); + Vertex cc(Qt::white, pc); + + // Make sure p1 is above p2, which is above p3. + Vertex *p1 = &aa; + Vertex *p2 = &bb; + Vertex *p3 = &cc; + if (p1->point.y() > p2->point.y()) swap(&p1, &p2); + if (p1->point.y() > p3->point.y()) swap(&p1, &p3); + if (p2->point.y() > p3->point.y()) swap(&p2, &p3); + + // Find the slopes of all edges in the trigon. All the three y + // deltas here are positive because of the above sorting. + double p1p2ydist = p2->point.y() - p1->point.y(); + double p1p3ydist = p3->point.y() - p1->point.y(); + double p2p3ydist = p3->point.y() - p2->point.y(); + double p1p2xdist = p2->point.x() - p1->point.x(); + double p1p3xdist = p3->point.x() - p1->point.x(); + double p2p3xdist = p3->point.x() - p2->point.x(); + + // The first x delta decides wether we have a lefty or a righty + // trigon. A lefty trigon has its tallest edge on the right hand + // side of the trigon. The righty trigon has it on its left side. + // This property determines wether the left or the right set of x + // coordinates will be continuous. + bool lefty = p1p2xdist < 0; + + // Find whether the selector's y is in the first or second shorty, + // counting from the top and downwards. This is used to find the + // color at the selector point. + bool firstshorty = (p.y() >= p1->point.y() && p.y() < p2->point.y()); + + // From the y value of the selector's position, find the left and + // right x values. + double leftx; + double rightx; + if (lefty) { + if (firstshorty) { + leftx = p1->point.x(); + if (floor(p1p2ydist) != 0.0) { + leftx += (p1p2xdist * (p.y() - p1->point.y())) / p1p2ydist; + } else { + leftx = qMin(p1->point.x(), p2->point.x()); + } + } else { + leftx = p2->point.x(); + if (floor(p2p3ydist) != 0.0) { + leftx += (p2p3xdist * (p.y() - p2->point.y())) / p2p3ydist; + } else { + leftx = qMin(p2->point.x(), p3->point.x()); + } + } + + rightx = p1->point.x(); + rightx += (p1p3xdist * (p.y() - p1->point.y())) / p1p3ydist; + } else { + leftx = p1->point.x(); + leftx += (p1p3xdist * (p.y() - p1->point.y())) / p1p3ydist; + + if (firstshorty) { + rightx = p1->point.x(); + if (floor(p1p2ydist) != 0.0) { + rightx += (p1p2xdist * (p.y() - p1->point.y())) / p1p2ydist; + } else { + rightx = qMax(p1->point.x(), p2->point.x()); + } + } else { + rightx = p2->point.x(); + if (floor(p2p3ydist) != 0.0) { + rightx += (p2p3xdist * (p.y() - p2->point.y())) / p2p3ydist; + } else { + rightx = qMax(p2->point.x(), p3->point.x()); + } + } + } + + // Find the r,g,b values of the points on the trigon's edges that + // are to the left and right of the selector. + double rshort = 0, gshort = 0, bshort = 0; + double rlong = 0, glong = 0, blong = 0; + if (firstshorty) { + if (floor(p1p2ydist) != 0.0) { + rshort = p2->color.r * (p.y() - p1->point.y()) / p1p2ydist; + gshort = p2->color.g * (p.y() - p1->point.y()) / p1p2ydist; + bshort = p2->color.b * (p.y() - p1->point.y()) / p1p2ydist; + rshort += p1->color.r * (p2->point.y() - p.y()) / p1p2ydist; + gshort += p1->color.g * (p2->point.y() - p.y()) / p1p2ydist; + bshort += p1->color.b * (p2->point.y() - p.y()) / p1p2ydist; + } else { + if (lefty) { + if (p1->point.x() <= p2->point.x()) { + rshort = p1->color.r; + gshort = p1->color.g; + bshort = p1->color.b; + } else { + rshort = p2->color.r; + gshort = p2->color.g; + bshort = p2->color.b; + } + } else { + if (p1->point.x() > p2->point.x()) { + rshort = p1->color.r; + gshort = p1->color.g; + bshort = p1->color.b; + } else { + rshort = p2->color.r; + gshort = p2->color.g; + bshort = p2->color.b; + } + } + } + } else { + if (floor(p2p3ydist) != 0.0) { + rshort = p3->color.r * (p.y() - p2->point.y()) / p2p3ydist; + gshort = p3->color.g * (p.y() - p2->point.y()) / p2p3ydist; + bshort = p3->color.b * (p.y() - p2->point.y()) / p2p3ydist; + rshort += p2->color.r * (p3->point.y() - p.y()) / p2p3ydist; + gshort += p2->color.g * (p3->point.y() - p.y()) / p2p3ydist; + bshort += p2->color.b * (p3->point.y() - p.y()) / p2p3ydist; + } else { + if (lefty) { + if (p2->point.x() <= p3->point.x()) { + rshort = p2->color.r; + gshort = p2->color.g; + bshort = p2->color.b; + } else { + rshort = p3->color.r; + gshort = p3->color.g; + bshort = p3->color.b; + } + } else { + if (p2->point.x() > p3->point.x()) { + rshort = p2->color.r; + gshort = p2->color.g; + bshort = p2->color.b; + } else { + rshort = p3->color.r; + gshort = p3->color.g; + bshort = p3->color.b; + } + } + } + } + + // p1p3ydist is never 0 + rlong = p3->color.r * (p.y() - p1->point.y()) / p1p3ydist; + glong = p3->color.g * (p.y() - p1->point.y()) / p1p3ydist; + blong = p3->color.b * (p.y() - p1->point.y()) / p1p3ydist; + rlong += p1->color.r * (p3->point.y() - p.y()) / p1p3ydist; + glong += p1->color.g * (p3->point.y() - p.y()) / p1p3ydist; + blong += p1->color.b * (p3->point.y() - p.y()) / p1p3ydist; + + // rshort,gshort,bshort is the color on one of the shortys. + // rlong,glong,blong is the color on the longy. So depending on + // wether we have a lefty trigon or not, we can determine which + // colors are on the left and right edge. + double rl, gl, bl, rr, gr, br; + if (lefty) { + rl = rshort; gl = gshort; bl = bshort; + rr = rlong; gr = glong; br = blong; + } else { + rl = rlong; gl = glong; bl = blong; + rr = rshort; gr = gshort; br = bshort; + } + + // Find the distance from the left x to the right x (xdist). Then + // find the distances from the selector to each of these (saxdist + // and saxdist2). These distances are used to find the color at + // the selector. + double xdist = rightx - leftx; + double saxdist = p.x() - leftx; + double saxdist2 = xdist - saxdist; + + // Now determine the r,g,b values of the selector using a linear + // approximation. + double r, g, b; + if (xdist != 0.0) { + r = (saxdist2 * rl / xdist) + (saxdist * rr / xdist); + g = (saxdist2 * gl / xdist) + (saxdist * gr / xdist); + b = (saxdist2 * bl / xdist) + (saxdist * br / xdist); + } else { + // In theory, the left and right color will be equal here. But + // because of the loss of precision, we get an error on both + // colors. The best approximation we can get is from adding + // the two errors, which in theory will eliminate the error + // but in practise will only minimize it. + r = (rl + rr) / 2; + g = (gl + gr) / 2; + b = (bl + br) / 2; + } + + // Now floor the color components and fit them into proper + // boundaries. This again is to compensate for the error caused by + // loss of precision. + int ri = (int) floor(r); + int gi = (int) floor(g); + int bi = (int) floor(b); + if (ri < 0) ri = 0; + else if (ri > 255) ri = 255; + if (gi < 0) gi = 0; + else if (gi > 255) gi = 255; + if (bi < 0) bi = 0; + else if (bi > 255) bi = 255; + + // Voila, we have the color at the point of the selector. + return QColor(ri, gi, bi); +} diff --git a/Gui/QtColorTriangle.h b/Gui/QtColorTriangle.h new file mode 100644 index 0000000000..5d420733f2 --- /dev/null +++ b/Gui/QtColorTriangle.h @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Solutions Commercial License Agreement provided +** with the Software or, alternatively, in accordance with the terms +** contained in a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** Please note Third Party Software included with Qt Solutions may impose +** additional restrictions and it is the user's responsibility to ensure +** that they have met the licensing requirements of the GPL, LGPL, or Qt +** Solutions Commercial license and the relevant license of the Third +** Party Software they are using. +** +** If you are unsure which license is appropriate for your use, please +** contact Nokia at qt-info@nokia.com. +** +****************************************************************************/ + +/* + This widget is maintained in Natron under the GNU Lesser General Public 2.1 license. +*/ + +#ifndef QTCOLORTRIANGLE_H +#define QTCOLORTRIANGLE_H +#include +#include + +class QPointF; +struct Vertex; + +class QtColorTriangle : public QWidget +{ + Q_OBJECT + +public: + QtColorTriangle(QWidget *parent = 0); + ~QtColorTriangle(); + + QSize sizeHint() const; + int heightForWidth(int w) const; + + void polish(); + QColor color() const; + +Q_SIGNALS: + void colorChanged(const QColor &col); + +public Q_SLOTS: + void setColor(const QColor &col); + +protected: + void paintEvent(QPaintEvent *); + void mouseMoveEvent(QMouseEvent *); + void mousePressEvent(QMouseEvent *); + void mouseReleaseEvent(QMouseEvent *); + void keyPressEvent(QKeyEvent *e); + void resizeEvent(QResizeEvent *); + void drawTrigon(QImage *p, const QPointF &a, const QPointF &b, + const QPointF &c, const QColor &color); + +private: + double radiusAt(const QPointF &pos, const QRect &rect) const; + double angleAt(const QPointF &pos, const QRect &rect) const; + QPointF movePointToTriangle(double x, double y, const Vertex &a, + const Vertex &b, const Vertex &c) const; + + QPointF pointFromColor(const QColor &col) const; + QColor colorFromPoint(const QPointF &p) const; + + void genBackground(); + + QImage bg; + double a, b, c; + QPointF pa, pb, pc, pd; + + QColor curColor; + int curHue; + + bool mustGenerateBackground; + int penWidth; + int ellipseSize; + + int outerRadius; + QPointF selectorPos; + + enum SelectionMode { + Idle, + SelectingHue, + SelectingSatValue + } selMode; +}; + +#endif diff --git a/Gui/Resources/Stylesheets/mainstyle.qss b/Gui/Resources/Stylesheets/mainstyle.qss index 145ea4338e..89d8b5ba77 100644 --- a/Gui/Resources/Stylesheets/mainstyle.qss +++ b/Gui/Resources/Stylesheets/mainstyle.qss @@ -289,6 +289,9 @@ QPushButton:focus { QPushButton:!enabled { color: %8; } +QPushButton:checked { + background-color: %4; +} QHeaderView:section { color: %5; @@ -734,3 +737,20 @@ OutputScriptTextEdit { selection-background-color: %1; } +/* + Color Selector Button/Widget +*/ + +QToolButton#ColorSelectorButton { + border: none; +} + +QToolButton#ColorSelectorButton:menu-indicator { + image: none; +} + +QToolButton#ColorSelectorButton:pressed { + padding-left: 0px; + top: 0px; + left: 0px; +} diff --git a/Gui/ScaleSliderQWidget.cpp b/Gui/ScaleSliderQWidget.cpp index 8482d2c524..470d7dfcbd 100644 --- a/Gui/ScaleSliderQWidget.cpp +++ b/Gui/ScaleSliderQWidget.cpp @@ -73,6 +73,7 @@ struct ScaleSliderQWidgetPrivate bool dragging; QFont font; QColor sliderColor; + QColor customSliderColor; bool initialized; bool mustInitializeSliderPosition; bool readOnly; @@ -82,6 +83,7 @@ struct ScaleSliderQWidgetPrivate ScaleSliderQWidget::DataTypeEnum dataType; bool altered; bool useLineColor; + bool useSliderColor; QColor lineColor; bool allowDraftModeSetting; @@ -112,6 +114,7 @@ struct ScaleSliderQWidgetPrivate , dataType(dataType) , altered(false) , useLineColor(false) + , useSliderColor(false) , lineColor(Qt::black) , allowDraftModeSetting(allowDraftModeSetting) { @@ -572,9 +575,13 @@ ScaleSliderQWidget::paintEvent(QPaintEvent* /*e*/) double positionValue = _imp->zoomCtx.toWidgetCoordinates(_imp->value, 0).x(); SettingsPtr settings = appPTR->getCurrentSettings(); - double sliderColorRGB[3]; - settings->getSliderColor(&sliderColorRGB[0], &sliderColorRGB[1], &sliderColorRGB[2]); - _imp->sliderColor.setRgbF(sliderColorRGB[0], sliderColorRGB[1], sliderColorRGB[2]); + if (!_imp->useSliderColor) { + double sliderColorRGB[3]; + settings->getSliderColor(&sliderColorRGB[0], &sliderColorRGB[1], &sliderColorRGB[2]); + _imp->sliderColor.setRgbF(sliderColorRGB[0], sliderColorRGB[1], sliderColorRGB[2]); + } else { + _imp->sliderColor = _imp->customSliderColor; + } double selectionColorRGB[3]; settings->getSelectionColor(&selectionColorRGB[0], &selectionColorRGB[1], &selectionColorRGB[2]); @@ -652,6 +659,15 @@ ScaleSliderQWidget::setUseLineColor(bool use, update(); } +void +ScaleSliderQWidget::setUseSliderColor(bool use, + const QColor &color) +{ + _imp->useSliderColor = use; + _imp->customSliderColor = color; + update(); +} + NATRON_NAMESPACE_EXIT NATRON_NAMESPACE_USING diff --git a/Gui/ScaleSliderQWidget.h b/Gui/ScaleSliderQWidget.h index c7e09767c4..1e010b7d63 100644 --- a/Gui/ScaleSliderQWidget.h +++ b/Gui/ScaleSliderQWidget.h @@ -96,6 +96,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON bool getAltered() const; void setUseLineColor(bool use, const QColor& color); + void setUseSliderColor(bool use, const QColor &color); Q_SIGNALS: void editingFinished(bool hasMovedOnce);