diff --git a/ui/zenoedit/dialog/zeditparamlayoutdlg.cpp b/ui/zenoedit/dialog/zeditparamlayoutdlg.cpp index 0f63a54df3..1555a4eae1 100644 --- a/ui/zenoedit/dialog/zeditparamlayoutdlg.cpp +++ b/ui/zenoedit/dialog/zeditparamlayoutdlg.cpp @@ -36,6 +36,7 @@ static CONTROL_ITEM_INFO controlList[] = { {"Integer Vector 2", CONTROL_VEC2_INT, "vec2i", ":/icons/parameter_control_integerVector2.svg"}, {"Color", CONTROL_COLOR, "color", ":/icons/parameter_control_color.svg"}, {"Pure Color", CONTROL_PURE_COLOR, "color", ":/icons/parameter_control_color.svg"}, + {"Color Vec3f", CONTROL_COLOR_VEC3F, "color", ":/icons/parameter_control_color.svg"}, {"Curve", CONTROL_CURVE, "curve", ":/icons/parameter_control_curve.svg"}, {"SpinBox", CONTROL_HSPINBOX, "int", ":/icons/parameter_control_spinbox.svg"}, {"DoubleSpinBox", CONTROL_HDOUBLESPINBOX, "float", ":/icons/parameter_control_spinbox.svg"}, diff --git a/ui/zenoedit/nodesys/zenonode.cpp b/ui/zenoedit/nodesys/zenonode.cpp index c9036c31e0..bc685808aa 100644 --- a/ui/zenoedit/nodesys/zenonode.cpp +++ b/ui/zenoedit/nodesys/zenonode.cpp @@ -921,6 +921,7 @@ ZGraphicsLayout* ZenoNode::addParam(const QModelIndex& viewparamIdx, ZenoSubGrap case CONTROL_WRITEPATH: case CONTROL_MULTILINE_STRING: case CONTROL_PURE_COLOR: + case CONTROL_COLOR_VEC3F: case CONTROL_CURVE: case CONTROL_HSLIDER: case CONTROL_HSPINBOX: diff --git a/ui/zenoedit/panel/zenoproppanel.cpp b/ui/zenoedit/panel/zenoproppanel.cpp index ecb064b5e8..781d63194d 100644 --- a/ui/zenoedit/panel/zenoproppanel.cpp +++ b/ui/zenoedit/panel/zenoproppanel.cpp @@ -636,8 +636,18 @@ void ZenoPropPanel::onViewParamDataChanged(const QModelIndex& topLeft, const QMo } else if (QPushButton *pBtn = qobject_cast(ctrl.pControl)) { - if (value.canConvert()) + // purecolor + if (value.canConvert()) { pBtn->setStyleSheet(QString("background-color:%1; border:0;").arg(value.value().name())); + } + // colorvec3f + else if (value.canConvert()) { + UI_VECTYPE vec = value.value(); + if (vec.size() == 3) { + auto color = QColor::fromRgbF(vec[0], vec[1], vec[2]); + pBtn->setStyleSheet(QString("background-color:%1; border:0;").arg(color.name())); + } + } } //... } diff --git a/ui/zenoedit/res/stylesheet/qwidget.qss b/ui/zenoedit/res/stylesheet/qwidget.qss index 8b1f6e2eb8..1151c31d18 100644 --- a/ui/zenoedit/res/stylesheet/qwidget.qss +++ b/ui/zenoedit/res/stylesheet/qwidget.qss @@ -8,7 +8,7 @@ QDialog background: rgb(45,50,57); } -QDialog QPushButton +QDialog .QPushButton { background: #596270; color:#C3D2DF; @@ -17,19 +17,19 @@ QDialog QPushButton font-weight: 600; } -QDialog QPushButton:hover +QDialog .QPushButton:hover { background: #6A93BC; color:#CFDBE5; } -QDialog QPushButton:default +QDialog .QPushButton:default { background: #4578AC; color:#C3D2DF; } -QDialog QPushButton:disabled +QDialog .QPushButton:disabled { background: #8A919A; color:#9BAAB4; @@ -68,6 +68,11 @@ QDialog QLineEdit:disabled { color: #8f9ba8; } +QDialog QGroupBox { + font-size: 8pt; + font-weight: 500; + color: #A3B1C0; +} QDialog QCheckBox{ font-size: 9pt; @@ -98,6 +103,10 @@ QDialog QSpinBox::down-button border: none; } +QDialog QSpinBox:disabled { + background-color: #2F2F2F; +} + QDialog QTableWidget { background-color: #191D21; @@ -208,4 +217,4 @@ QWidget[cssClass = "welcomepage_link"] border-radius: 8px; border:1px solid #24282E; padding-left: 20px; -} \ No newline at end of file +} diff --git a/ui/zenoedit/res/stylesheet/spinbox.qss b/ui/zenoedit/res/stylesheet/spinbox.qss index 06d26840b1..f46440bbf6 100644 --- a/ui/zenoedit/res/stylesheet/spinbox.qss +++ b/ui/zenoedit/res/stylesheet/spinbox.qss @@ -72,7 +72,7 @@ QSpinBox[cssClass = "control"]::up-button:hover { image: url(:/icons/rightArrow-on.svg); } - + QDoubleSpinBox { background: #191D21; @@ -117,4 +117,37 @@ QDoubleSpinBox::up-button QDoubleSpinBox::up-button:hover { image: url(:/icons/rightArrow-on.svg); +} + +/* ^^^ the upper style is too large... */ +QDoubleSpinBox#MixedSpinBox +{ + height: 20px; + font-size: 9pt; + font-weight: 400; + border: 0; + color: #FFFFFF; + background-color: #191D21; +} + +QDoubleSpinBox#MixedSpinBox:focus +{ + border: 2px solid #4B9EF4; +} + +QDoubleSpinBox#MixedSpinBox:disabled +{ + background-color: #2F2F2F; +} + +QDoubleSpinBox#MixedSpinBox::up-button +{ + height: 0px; + width: 0px; +} + +QDoubleSpinBox#MixedSpinBox::down-button +{ + height: 0px; + width: 0px; } \ No newline at end of file diff --git a/ui/zenomodel/include/modeldata.h b/ui/zenomodel/include/modeldata.h index c42d6f9cf4..ae8a4923a9 100644 --- a/ui/zenomodel/include/modeldata.h +++ b/ui/zenomodel/include/modeldata.h @@ -17,6 +17,7 @@ enum PARAM_CONTROL { CONTROL_MULTILINE_STRING, CONTROL_COLOR, CONTROL_PURE_COLOR, + CONTROL_COLOR_VEC3F, CONTROL_CURVE, CONTROL_HSLIDER, CONTROL_HSPINBOX, diff --git a/ui/zenomodel/src/jsonhelper.cpp b/ui/zenomodel/src/jsonhelper.cpp index 1a8ea18a81..73dd2325a7 100644 --- a/ui/zenomodel/src/jsonhelper.cpp +++ b/ui/zenomodel/src/jsonhelper.cpp @@ -168,6 +168,11 @@ namespace JsonHelper int dim = -1; bool bFloat = false; UiHelper::parseVecType(type, dim, bFloat); + // ^^^ use regexp, but not include colorvec3f + // vvv so specify here + if (type == "colorvec3f") { + bFloat = true; + } for (int i = 0; i < vec.size(); i++) { if (!bFloat) diff --git a/ui/zenomodel/src/uihelper.cpp b/ui/zenomodel/src/uihelper.cpp index 0c465ca964..252fdf2225 100644 --- a/ui/zenomodel/src/uihelper.cpp +++ b/ui/zenomodel/src/uihelper.cpp @@ -159,6 +159,7 @@ bool UiHelper::validateVariant(const QVariant& var, const QString& type) { return (varType == QMetaType::User); } + case CONTROL_COLOR_VEC3F: case CONTROL_VEC2_FLOAT: case CONTROL_VEC2_INT: case CONTROL_VEC3_FLOAT: @@ -252,6 +253,7 @@ QVariant UiHelper::parseStringByType(const QString &defaultValue, const QString case CONTROL_COLOR: case CONTROL_ENUM: return defaultValue; + case CONTROL_COLOR_VEC3F: case CONTROL_VEC2_FLOAT: case CONTROL_VEC2_INT: case CONTROL_VEC3_FLOAT: @@ -387,7 +389,8 @@ QString UiHelper::getControlDesc(PARAM_CONTROL ctrl) case CONTROL_VEC3_INT: return "Integer Vector 3"; case CONTROL_VEC2_INT: return "Integer Vector 2"; case CONTROL_COLOR: return "Color"; - case CONTROL_PURE_COLOR: return "Pure Color"; + case CONTROL_PURE_COLOR: return "Pure Color"; + case CONTROL_COLOR_VEC3F: return "Color Vec3f"; case CONTROL_CURVE: return "Curve"; case CONTROL_HSPINBOX: return "SpinBox"; case CONTROL_HDOUBLESPINBOX: return "DoubleSpinBox"; @@ -466,6 +469,10 @@ PARAM_CONTROL UiHelper::getControlByDesc(const QString& descName) { return CONTROL_PURE_COLOR; } + else if (descName == "Color Vec3f") + { + return CONTROL_COLOR_VEC3F; + } else if (descName == "Curve") { return CONTROL_CURVE; @@ -554,7 +561,7 @@ QStringList UiHelper::getControlLists(const QString& type, bool isNodeUI) else if (type == "readpath") { ctrls = { CONTROL_READPATH }; } else if (type == "multiline_string") { ctrls = { CONTROL_STRING, CONTROL_MULTILINE_STRING }; } else if (type == "color") { //color is more general than heatmap. - ctrls = {CONTROL_COLOR, CONTROL_PURE_COLOR}; + ctrls = {CONTROL_COLOR, CONTROL_PURE_COLOR, CONTROL_COLOR_VEC3F}; } else if (type == "curve") { ctrls = { CONTROL_CURVE }; } else if (type.startsWith("enum ")) { @@ -609,6 +616,10 @@ PARAM_CONTROL UiHelper::getControlByType(const QString &type) return CONTROL_MULTILINE_STRING; } else if (type == "color") { //color is more general than heatmap. return CONTROL_COLOR; + } else if (type == "purecolor") { + return CONTROL_PURE_COLOR; + } else if (type == "colorvec3f") { //colorvec3f is for coloreditor, color is heatmap? ^^^^ + return CONTROL_COLOR_VEC3F; } else if (type == "curve") { return CONTROL_CURVE; } else if (type.startsWith("enum ")) { @@ -655,6 +666,8 @@ QString UiHelper::getTypeByControl(PARAM_CONTROL ctrl) case CONTROL_WRITEPATH: return "string"; case CONTROL_READPATH: return "string"; case CONTROL_COLOR: return "color"; //todo: is vec3? + case CONTROL_PURE_COLOR: return "purecolor"; + case CONTROL_COLOR_VEC3F: return "colorvec3f"; // ^^^ color vec is here case CONTROL_CURVE: return "curve"; case CONTROL_ENUM: return "string"; case CONTROL_HSLIDER: diff --git a/ui/zenoui/ColorEditor/ColorEditor.cpp b/ui/zenoui/ColorEditor/ColorEditor.cpp new file mode 100644 index 0000000000..ac69f1b18e --- /dev/null +++ b/ui/zenoui/ColorEditor/ColorEditor.cpp @@ -0,0 +1,1777 @@ +#include "ColorEditor.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "zenoui/style/zenostyle.h" + +//------------------------------------------- color correction ----------------------------------------------- +void ColorCorrection::correct(QColor& color) +{ + double r = color.redF(); + double g = color.greenF(); + double b = color.blueF(); + color.setRedF(std::pow(r, 1 / gamma)); + color.setGreenF(std::pow(g, 1 / gamma)); + color.setBlueF(std::pow(b, 1 / gamma)); +} + +void ColorCorrection::correct(QImage& image) +{ + for (int x = 0; x < image.width(); ++x) { + for (int y = 0; y < image.height(); ++y) { + QColor color = image.pixelColor(x, y); + correct(color); + image.setPixelColor(x, y, color); + } + } +} + +//--------------------------------------------------------- color wheel ------------------------------------------------ +class ColorWheel::Private +{ +public: + static constexpr int selectorRadius = 4; + static constexpr int comboSelectorRadius = 3; + int radius = 0; + QColor selectedColor = QColor(Qt::white); + QImage colorBuffer; + colorcombo::ICombination* colorCombination = nullptr; + ColorCorrection* colorCorrection = nullptr; + + void renderWheel(const QRect& rect) + { + auto center = rect.center(); + auto size = rect.size(); + + radius = std::min(rect.width(), rect.height()) / 2 - selectorRadius; + + // init buffer + colorBuffer = QImage(size, QImage::Format_ARGB32); + colorBuffer.fill(Qt::transparent); + + // create gradient + QConicalGradient hsvGradient(center, 0); + for (int deg = 0; deg < 360; deg += 60) { + hsvGradient.setColorAt(deg / 360.0, QColor::fromHsvF(deg / 360.0, 1.0, selectedColor.valueF())); + } + hsvGradient.setColorAt(1.0, QColor::fromHsvF(0.0, 1.0, selectedColor.valueF())); + + QRadialGradient valueGradient(center, radius); + valueGradient.setColorAt(0.0, QColor::fromHsvF(0.0, 0.0, selectedColor.valueF())); + valueGradient.setColorAt(1.0, Qt::transparent); + + QPainter painter(&colorBuffer); + painter.setRenderHint(QPainter::Antialiasing, true); + // draw color wheel + painter.setPen(Qt::transparent); + painter.setBrush(hsvGradient); + painter.drawEllipse(center, radius, radius); + painter.setBrush(valueGradient); + painter.drawEllipse(center, radius, radius); + + // color correction + if (colorCorrection) { + colorCorrection->correct(colorBuffer); + } + } +}; + +ColorWheel::ColorWheel(QWidget* parent) + : QWidget(parent) + , p(new Private) +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); +} + +void ColorWheel::setColorCombination(colorcombo::ICombination* combination) +{ + p->colorCombination = combination; + repaint(); +} + +void ColorWheel::setSelectedColor(const QColor& color) +{ + if (!isEnabled()) return; + + if (color.value() != p->selectedColor.value()) { + p->selectedColor = color; + p->renderWheel(this->rect()); + } + else { + p->selectedColor = color; + } + update(); +} + +void ColorWheel::setColorCorrection(ColorCorrection* colorCorrection) +{ + p->colorCorrection = colorCorrection; + p->renderWheel(this->rect()); + update(); +} + +QColor ColorWheel::getSelectedColor() const +{ + return p->selectedColor; +} + +QColor ColorWheel::getColor(int x, int y) const +{ + if (p->radius <= 0) return QColor(); + + auto line = QLineF(this->rect().center(), QPointF(x, y)); + auto h = line.angle() / 360.0; + auto s = std::min(1.0, line.length() / p->radius); + auto v = p->selectedColor.valueF(); + return QColor::fromHsvF(h, s, v); +} + +void ColorWheel::paintEvent(QPaintEvent* e) +{ + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing, true); + // draw wheel + painter.drawImage(0, 0, p->colorBuffer); + // draw selected color circle + painter.setPen(Qt::black); + painter.setBrush(Qt::white); + drawSelector(&painter, p->selectedColor, p->selectorRadius); + // draw color combination circle + if (p->colorCombination) { + auto colors = p->colorCombination->genColors(p->selectedColor); + for (const auto& color : colors) { + drawSelector(&painter, color, p->comboSelectorRadius); + } + // add selected color, so the user can switch between this + colors.push_back(p->selectedColor); + emit combinationColorChanged(colors); + } +} + +void ColorWheel::mousePressEvent(QMouseEvent* e) +{ + processMouseEvent(e); +} + +void ColorWheel::mouseMoveEvent(QMouseEvent* e) +{ + processMouseEvent(e); +} + +void ColorWheel::resizeEvent(QResizeEvent* e) +{ + p->renderWheel(this->rect()); +} + +void ColorWheel::processMouseEvent(QMouseEvent* e) +{ + if (e->buttons() & Qt::LeftButton) { + p->selectedColor = getColor(e->x(), e->y()); + emit colorSelected(p->selectedColor); + update(); + } +} + +void ColorWheel::drawSelector(QPainter* painter, const QColor& color, int radius) +{ + auto line = QLineF::fromPolar(color.hsvSaturationF() * p->radius, color.hsvHueF() * 360.0); + line.translate(this->rect().center()); + painter->drawEllipse(line.p2(), radius, radius); +} + +//-------------------------------------------------- color combination -------------------------------------------- +namespace colorcombo +{ +ICombination::ICombination(QObject* parent) + : QObject(parent) + , m_min(0) + , m_max(1) + , m_value(0) + , m_decimals(0) + , m_rangeEnabled(false) +{ +} + +ICombination::ICombination(double min, double max, double value, int decimals, bool rangeEnabled, QObject* parent) + : QObject(parent) + , m_min(min) + , m_max(max) + , m_value(value) + , m_decimals(decimals) + , m_rangeEnabled(rangeEnabled) +{ +} + +QString ICombination::name() +{ + return tr("None"); +} + +QVector ICombination::genColors(const QColor& color) +{ + return {}; +} + +void ICombination::setRange(double min, double max) +{ + m_min = min; + m_max = max; +} + +void ICombination::setValue(double value) +{ + m_value = value; +} + +void ICombination::setDecimals(int decimals) +{ + m_decimals = decimals; +} + +double ICombination::min() const +{ + return m_min; +} + +double ICombination::max() const +{ + return m_max; +} + +double ICombination::getValue() const +{ + return m_value; +} + +bool ICombination::rangeEnabled() const +{ + return m_rangeEnabled; +} + +int ICombination::decimals() const +{ + return m_decimals; +} + +Complementary::Complementary(QObject* parent) + : ICombination(parent) +{ +} + +QString Complementary::name() +{ + return tr("Complementary"); +} + +QVector Complementary::genColors(const QColor& color) +{ + return {QColor::fromHsv((color.hsvHue() + 180) % 360, color.hsvSaturation(), color.value())}; +} + +Monochromatic::Monochromatic(QObject* parent) + : ICombination(0, 1, 0.5, 4, true, parent) +{ +} + +QString Monochromatic::name() +{ + return tr("Monochromatic"); +} + +QVector Monochromatic::genColors(const QColor& color) +{ + double rate = getValue() / (max() - min()); + return {QColor::fromHsvF(color.hsvHueF(), color.hsvSaturationF(), color.valueF() * rate)}; +} + +Analogous::Analogous(QObject* parent) + : ICombination(0, 180, 30, 0, true, parent) +{ +} + +QString Analogous::name() +{ + return tr("Analogous"); +} + +QVector Analogous::genColors(const QColor& color) +{ + int add = getValue(); + return {QColor::fromHsv((color.hsvHue() + add) % 360, color.hsvSaturation(), color.value()), + QColor::fromHsv((color.hsvHue() - add + 360) % 360, color.hsvSaturation(), color.value())}; +} + +Triadic::Triadic(QObject* parent) + : ICombination(0, 180, 120, 0, true, parent) +{ +} + +QString Triadic::name() +{ + return tr("Triadic"); +} + +QVector Triadic::genColors(const QColor& color) +{ + int add = getValue(); + return {QColor::fromHsv((color.hsvHue() + add) % 360, color.hsvSaturation(), color.value()), + QColor::fromHsv((color.hsvHue() - add + 360) % 360, color.hsvSaturation(), color.value())}; +} + +Tetradic::Tetradic(QObject* parent) + : ICombination(-90, 90, 90, 0, true, parent) +{ +} + +QString Tetradic::name() +{ + return tr("Tetradic"); +} + +QVector Tetradic::genColors(const QColor& color) +{ + /* + * A--------B + * | | + * D--------C + * + * A : H, S, V + * B : H - 90 + factor * 180, S, V + * C : H + 180, S, V + * D : H + 90 + factor * 180, S, V + */ + int add = getValue(); + return {QColor::fromHsv((color.hsvHue() + add + 360) % 360, color.hsvSaturation(), color.value()), + QColor::fromHsv((color.hsvHue() + 180) % 360, color.hsvSaturation(), color.value()), + QColor::fromHsv((color.hsvHue() + add + 180 + 360) % 360, color.hsvSaturation(), color.value())}; +} +} // namespace colorcombo + +//--------------------------------------------------- color slider ------------------------------------------- +MixedSpinBox::MixedSpinBox(QWidget* parent) + : QDoubleSpinBox(parent) +{ + setObjectName("MixedSpinBox"); + setDecimals(4); + setSingleStep(0.0001); + setFocusPolicy(Qt::ClickFocus); +} + +QString MixedSpinBox::textFromValue(double value) const +{ + QString shortestNum = QLocale().toString(value, 'f', QLocale::FloatingPointShortest); + QString decimalNum = QLocale().toString(value, 'f', decimals()); + return shortestNum.size() <= decimalNum.size() ? shortestNum : decimalNum; +} + +void MixedSpinBox::keyPressEvent(QKeyEvent* e) +{ + if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { + clearFocus(); + } + else { + QDoubleSpinBox::keyPressEvent(e); + } +} + +class JumpableSlider::Private +{ +public: + double minValue = 0.0; + double maxValue = 1.0; + double singleStep = 0.0001; +}; + +JumpableSlider::JumpableSlider(QWidget* parent) + : JumpableSlider(Qt::Horizontal, parent) +{ +} + +JumpableSlider::JumpableSlider(Qt::Orientation orientation, QWidget* parent) + : QSlider(orientation, parent) + , p(new Private) +{ + connect(this, &QSlider::valueChanged, this, [this](int value) { emit valueChanged(value * p->singleStep); }); +} + +void JumpableSlider::setValue(double value) +{ + // need round + // 0.179999 * 1000 need be 180 + // int(0.179999 * 1000) = 179 + QSlider::setValue(std::round(value / p->singleStep)); +} + +void JumpableSlider::setMinimum(double value) +{ + QSlider::setMinimum(value / p->singleStep); + p->minValue = value; +} + +void JumpableSlider::setMaximum(double value) +{ + if (value < p->minValue) { + return; + } + QSlider::setMaximum(value / p->singleStep); + p->maxValue = value; +} + +void JumpableSlider::setRange(double minValue, double maxValue) +{ + setMinimum(minValue); + setMaximum(maxValue); +} + +void JumpableSlider::setSingleStep(double value) +{ + if (value == 0.0) { + return; + } + p->singleStep = value; + setMinimum(p->minValue); + setMaximum(p->maxValue); + setValue(this->value()); +} + +double JumpableSlider::value() const +{ + return QSlider::value() * p->singleStep; +} + +double JumpableSlider::minimum() const +{ + return p->minValue; +} + +double JumpableSlider::maximum() const +{ + return p->maxValue; +} + +double JumpableSlider::singleStep() const +{ + return p->singleStep; +} + +void JumpableSlider::mousePressEvent(QMouseEvent* e) +{ + if (e->button() == Qt::LeftButton) { + e->accept(); + setSliderDown(true); + handleMouseEvent(e); + } + else { + QSlider::mousePressEvent(e); + } +} + +void JumpableSlider::mouseMoveEvent(QMouseEvent* e) +{ + if (e->buttons() & Qt::LeftButton) { + e->accept(); + handleMouseEvent(e); + } + else { + QSlider::mouseMoveEvent(e); + } +} + +void JumpableSlider::mouseReleaseEvent(QMouseEvent* e) +{ + QSlider::mouseReleaseEvent(e); +} + +void JumpableSlider::handleMouseEvent(QMouseEvent* e) +{ + double newVal; + double maxValue = maximum(); + double minValue = minimum(); + if (orientation() == Qt::Horizontal) { + newVal = minValue + ((maxValue - minValue) * e->x()) / width(); + } + else { + newVal = minValue + ((maxValue - minValue) * (height() - e->y())) / height(); + } + setValue(!invertedAppearance() ? newVal : maxValue - newVal); +} + +class GradientSlider::Private +{ +public: + ColorCorrection* colorCorrection = nullptr; + QLinearGradient gradient; + QImage colorBuffer; + + Private() { gradient.setCoordinateMode(QGradient::StretchToDeviceMode); } + + void render(const QRect& rect, Qt::Orientation orientation, bool invertedAppearance) + { + QSize size = rect.size(); + colorBuffer = QImage(size, QImage::Format_ARGB32); + // update gradient final stop + double dir = invertedAppearance ? -1 : 1; + if (orientation == Qt::Horizontal) { + gradient.setFinalStop(dir, 0); + } + else { + gradient.setFinalStop(0, -dir); + } + + QPainter painter(&colorBuffer); + // draw gradient + painter.setBrush(gradient); + painter.drawRect(0, 0, colorBuffer.width(), colorBuffer.height()); + // color correction + if (colorCorrection) { + colorCorrection->correct(colorBuffer); + } + } +}; + +GradientSlider::GradientSlider(QWidget* parent) + : JumpableSlider(Qt::Horizontal, parent) + , p(new Private) +{ +} + +void GradientSlider::setGradient(const QColor& startColor, const QColor& stopColor) +{ + setGradient({{0, startColor}, {1, stopColor}}); +} + +void GradientSlider::setGradient(const QGradientStops& colors) +{ + if (colors.size() <= 1) { + qWarning() << "ColorSlider::setGradient: colors size should >= 2"; + return; + } + + p->gradient.setStops(colors); + p->render(this->rect(), orientation(), invertedAppearance()); + update(); +} + +void GradientSlider::setColorCorrection(ColorCorrection* colorCorrection) +{ + p->colorCorrection = colorCorrection; + p->render(this->rect(), orientation(), invertedAppearance()); + update(); +} + +QGradientStops GradientSlider::gradientColor() const +{ + return p->gradient.stops(); +} + +void GradientSlider::paintEvent(QPaintEvent* e) +{ + QPainter painter(this); + // draw groove + painter.drawImage(0, 0, p->colorBuffer); + + QPointF p1, p2; + if (orientation() == Qt::Horizontal) { + double pos = (value() - minimum()) / (maximum() - minimum()) * width(); + p1 = QPointF(pos, 0); + p2 = QPointF(pos, height()); + } + else { + double pos = height() - (value() - minimum()) / (maximum() - minimum()) * height(); + p1 = QPointF(0, pos); + p2 = QPointF(height(), pos); + } + // draw handle + painter.setPen(QPen(QColor("#5C5C5C"), 6)); + painter.drawLine(p1, p2); +} + +void GradientSlider::resizeEvent(QResizeEvent* e) +{ + p->render(this->rect(), orientation(), invertedAppearance()); +} + +class ColorSpinHSlider::Private +{ +public: + MixedSpinBox* spinbox; + GradientSlider* slider; + + Private(const QString& name, QWidget* parent) + { + QLabel* text = new QLabel(name, parent); + text->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + spinbox = new MixedSpinBox(parent); + spinbox->setButtonSymbols(QAbstractSpinBox::NoButtons); + slider = new GradientSlider(parent); + slider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + auto layout = new QHBoxLayout(parent); + layout->setAlignment(Qt::AlignLeft); + layout->setMargin(0); + layout->addWidget(text, 1); + layout->addWidget(spinbox, 2); + layout->addWidget(slider, 7); + } +}; +ColorSpinHSlider::ColorSpinHSlider(const QString& name, QWidget* parent) + : QWidget(parent) + , p(new Private(name, this)) +{ + connect(p->slider, &GradientSlider::valueChanged, this, &ColorSpinHSlider::valueChanged); + connect(p->slider, &GradientSlider::valueChanged, p->spinbox, &MixedSpinBox::setValue); + connect(p->spinbox, &MixedSpinBox::editingFinished, this, [this]() { p->slider->setValue(p->spinbox->value()); }); +} + +void ColorSpinHSlider::setGradient(const QColor& startColor, const QColor& stopColor) +{ + p->slider->setGradient(startColor, stopColor); +} + +void ColorSpinHSlider::setGradient(const QGradientStops& colors) +{ + p->slider->setGradient(colors); +} + +void ColorSpinHSlider::setColorCorrection(ColorCorrection* colorCorrection) +{ + p->slider->setColorCorrection(colorCorrection); +} + +void ColorSpinHSlider::setValue(double value) +{ + p->spinbox->setValue(value); + p->slider->setValue(value); +} + +void ColorSpinHSlider::setRange(double min, double max) +{ + p->slider->setRange(min, max); + p->spinbox->setRange(min, max); +} + +QGradientStops ColorSpinHSlider::gradientColor() const +{ + return p->slider->gradientColor(); +} + +double ColorSpinHSlider::value() const +{ + return p->slider->value(); +} + +//--------------------------------------------- color button ------------------------------------------------------- +class ColorButton::Private +{ +public: + QPoint pressPos; + QColor color; + ColorCorrection* colorCorrection = nullptr; + int bolderTopWidth = 0; + int bolderBottomWidth = 0; + int bolderLeftWidth = 0; + int bolderRightWidth = 0; + + void updateStyle(QPushButton* btn) + { + QColor showColor = color; + if (colorCorrection) { + colorCorrection->correct(showColor); + } + + int minWidth = ZenoStyle::dpiScaled(20); + int minHeight = ZenoStyle::dpiScaled(20); + auto style = QString("QPushButton{min-width:%1px;min-height:%2px;background-color:%3;" + "border-top:%4px solid;border-bottom:%5px solid;" + "border-left:%6px solid;border-right:%7px solid;}" + "QPushButton:pressed{border: 1px solid #ffd700;}") + .arg(minWidth) + .arg(minHeight) + .arg(showColor.name()) + .arg(bolderTopWidth) + .arg(bolderBottomWidth) + .arg(bolderLeftWidth) + .arg(bolderRightWidth); + btn->setStyleSheet(style); + } +}; + +ColorButton::ColorButton(QWidget* parent) + : QPushButton(parent) + , p(new Private) +{ + setAcceptDrops(true); + connect(this, &QPushButton::clicked, this, [this]() { emit colorClicked(p->color); }); +} + +void ColorButton::setColor(const QColor& color) +{ + p->color = color; + p->updateStyle(this); +} + +void ColorButton::setColorCorrection(ColorCorrection* colorCorrection) +{ + p->colorCorrection = colorCorrection; + p->updateStyle(this); +} + +void ColorButton::setBolderWidth(int top, int bottom, int left, int right) +{ + p->bolderTopWidth = top; + p->bolderBottomWidth = bottom; + p->bolderLeftWidth = left; + p->bolderRightWidth = right; + p->updateStyle(this); +} + +QColor ColorButton::color() const +{ + return p->color; +} + +void ColorButton::mousePressEvent(QMouseEvent* e) +{ + p->pressPos = e->pos(); + QPushButton::mousePressEvent(e); +} + +void ColorButton::mouseMoveEvent(QMouseEvent* e) +{ + if (e->buttons() & Qt::LeftButton) { + if ((p->pressPos - e->pos()).manhattanLength() > QApplication::startDragDistance()) { + QMimeData* mime = new QMimeData; + mime->setColorData(p->color); + QPixmap pix(width(), height()); + pix.fill(p->color); + QDrag* drg = new QDrag(this); + drg->setMimeData(mime); + drg->setPixmap(pix); + drg->exec(Qt::CopyAction); + // need let pushbutton release + QMouseEvent event(QEvent::MouseButtonRelease, e->pos(), Qt::LeftButton, Qt::LeftButton, 0); + QApplication::sendEvent(this, &event); + } + } +} + +void ColorButton::dragEnterEvent(QDragEnterEvent* e) +{ + if (qvariant_cast(e->mimeData()->colorData()).isValid()) + e->accept(); + else + e->ignore(); +} + +void ColorButton::dragLeaveEvent(QDragLeaveEvent*) +{ + if (hasFocus()) parentWidget()->setFocus(); +} + +void ColorButton::dropEvent(QDropEvent* e) +{ + auto color = qvariant_cast(e->mimeData()->colorData()); + if (color.isValid()) { + setColor(color); + emit colorDroped(color); + e->accept(); + } + else { + e->ignore(); + } +} + +//--------------------------------------------- color palette ------------------------------------------------------ +class ColorPalette::Private +{ +public: + int columnCount = 0; + QGridLayout* layout = nullptr; + ColorCorrection* colorCorrection = nullptr; + QVector colors; + + Private(int column, QScrollArea* parent) + { + columnCount = column; + + auto scrollWidget = new QWidget(parent); + layout = new QGridLayout(scrollWidget); + layout->setAlignment(Qt::AlignTop); + layout->setSpacing(0); + layout->setMargin(0); + + parent->setWidget(scrollWidget); + } + + std::pair getLayoutIndex(int index) { return {index / columnCount, index % columnCount}; } + + void updateLayout(int begin, int end) + { + for (int i = begin; i < end; ++i) { + int row = i / columnCount; + int col = i % columnCount; + auto btn = qobject_cast(layout->itemAtPosition(row, col)->widget()); + btn->setColor(colors[i]); + } + } + + void updateBolder(int begin, int end) + { + int size = colors.size(); + for (int i = begin; i < end; ++i) { + int row = i / columnCount; + int col = i % columnCount; + auto btn = qobject_cast(layout->itemAtPosition(row, col)->widget()); + int bolderLeftWidth = col == 0 ? 1 : 0; + int bolderTopWidth = row == 0 ? 1 : 0; + btn->setBolderWidth(bolderTopWidth, 1, bolderLeftWidth, 1); + } + } +}; + +ColorPalette::ColorPalette(int column, QWidget* parent) + : QScrollArea(parent) + , p(new Private(column, this)) +{ + setWidgetResizable(true); + setAcceptDrops(true); +} + +void ColorPalette::addColor(const QColor& color) +{ + int index = p->colors.size(); + p->colors.push_back(color); + + auto btn = new ColorButton(this); + btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + btn->setToolTip(tr("Ctrl + click to remove color")); + btn->setColorCorrection(p->colorCorrection); + connect(btn, &ColorButton::colorClicked, this, [this, index](const QColor& color) { + if (QApplication::keyboardModifiers() == Qt::ControlModifier) { + auto layoutIndex = p->getLayoutIndex(index); + removeColor(layoutIndex.first, layoutIndex.second); + } + else { + emit colorClicked(color); + } + }); + connect(btn, &ColorButton::colorDroped, this, [this, index](const QColor& color) { + // update color at index + p->colors[index] = color; + }); + + auto layoutIndex = p->getLayoutIndex(index); + p->layout->addWidget(btn, layoutIndex.first, layoutIndex.second); + + p->updateLayout(index, p->colors.size()); + p->updateBolder(index, p->colors.size()); +} + +void ColorPalette::setColor(const QColor& color, int row, int column) +{ + int index = row * p->columnCount + column; + p->colors[index] = color; + p->updateLayout(index, index + 1); +} + +void ColorPalette::removeColor(int row, int column) +{ + int size = p->colors.size(); + auto item = p->layout->takeAt(size - 1); + if (item->widget()) { + delete item->widget(); + } + delete item; + + int index = row * p->columnCount + column; + p->colors.remove(index); + p->updateLayout(index, p->colors.size()); + p->updateBolder(index, p->colors.size()); +} + +void ColorPalette::setColorCorrection(ColorCorrection* colorCorrection) +{ + p->colorCorrection = colorCorrection; + for (int i = 0; i < p->layout->count(); ++i) { + auto btn = qobject_cast(p->layout->itemAt(i)->widget()); + btn->setColorCorrection(colorCorrection); + } +} + +QColor ColorPalette::colorAt(int row, int column) const +{ + if (column >= p->columnCount) { + return QColor(); + } + int index = row * p->columnCount + column; + if (index >= p->colors.size()) { + return QColor(); + } + return p->colors[index]; +} + +QVector ColorPalette::colors() const +{ + return p->colors; +} + +void ColorPalette::dragEnterEvent(QDragEnterEvent* e) +{ + if (qvariant_cast(e->mimeData()->colorData()).isValid()) + e->accept(); + else + e->ignore(); +} + +void ColorPalette::dropEvent(QDropEvent* e) +{ + auto color = qvariant_cast(e->mimeData()->colorData()); + if (color.isValid()) { + addColor(color); + e->accept(); + } + else { + e->ignore(); + } +} + +//--------------------------------------------- color preview ------------------------------------------------------- +class ColorPreview::Private +{ +public: + ColorButton* pbtnCurrent; + ColorButton* pbtnPrevious; + + Private(const QColor& color, QWidget* parent) + : pbtnCurrent(new ColorButton(parent)) + , pbtnPrevious(new ColorButton(parent)) + { + // pbtnCurrent->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + // pbtnPrevious->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + + pbtnCurrent->setBolderWidth(1, 1, 0, 1); + pbtnPrevious->setBolderWidth(1, 1, 1, 1); + + pbtnCurrent->setColor(color); + pbtnPrevious->setColor(color); + + auto layout = new QHBoxLayout(parent); + layout->setSpacing(0); + layout->setMargin(0); + layout->addWidget(pbtnPrevious); + layout->addWidget(pbtnCurrent); + } + + void setCurrent(const QColor& color) { pbtnCurrent->setColor(color); } +}; + +ColorPreview::ColorPreview(const QColor& color, QWidget* parent) + : QWidget(parent) + , p(new Private(color, this)) +{ + // only emit when current color changed + connect(p->pbtnCurrent, &ColorButton::colorDroped, this, &ColorPreview::currentColorChanged); +} + +void ColorPreview::setCurrentColor(const QColor& color) +{ + p->setCurrent(color); +} + +void ColorPreview::setColorCorrection(ColorCorrection* colorCorrection) +{ + p->pbtnCurrent->setColorCorrection(colorCorrection); + p->pbtnPrevious->setColorCorrection(colorCorrection); +} + +QColor ColorPreview::currentColor() const +{ + return p->pbtnCurrent->color(); +} + +QColor ColorPreview::previousColor() const +{ + return p->pbtnPrevious->color(); +} + +//------------------------------------------- color combo widget --------------------------- +class ColorComboWidget::Private +{ +public: + std::queue combs; + QHBoxLayout* hlayout = nullptr; + QPushButton* switchBtn = nullptr; + JumpableSlider* factorSlider = nullptr; + MixedSpinBox* factorSpinbox = nullptr; + ColorCorrection* colorCorrection = nullptr; + + Private(QWidget* parent) + { + factorSpinbox = new MixedSpinBox(parent); + factorSlider = new JumpableSlider(Qt::Horizontal, parent); + switchBtn = new QPushButton(tr("switch"), parent); + switchBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + factorSpinbox->setButtonSymbols(QAbstractSpinBox::NoButtons); + + auto layout = new QGridLayout(parent); + layout->setMargin(0); + hlayout = new QHBoxLayout(); + hlayout->setMargin(0); + hlayout->setSpacing(0); + layout->addLayout(hlayout, 0, 0, 1, 9); + layout->addWidget(switchBtn, 0, 9, 1, 1); + layout->addWidget(factorSpinbox, 1, 0, 1, 4); + layout->addWidget(factorSlider, 1, 4, 1, 6); + } +}; + +ColorComboWidget::ColorComboWidget(QWidget* parent) + : QWidget(parent) + , p(new Private(this)) +{ + // dummy + addCombination(new colorcombo::ICombination(this)); + switchCombination(); + + connect(p->switchBtn, &QPushButton::clicked, this, &ColorComboWidget::switchCombination); + connect(p->factorSpinbox, &MixedSpinBox::editingFinished, this, [this]() { + double value = p->factorSpinbox->value(); + p->factorSlider->setValue(value); + }); + connect(p->factorSlider, &JumpableSlider::valueChanged, this, [this](double value) { + p->factorSpinbox->setValue(value); + auto comb = p->combs.front(); + comb->setValue(value); + emit combinationChanged(comb); + }); +} + +void ColorComboWidget::addCombination(colorcombo::ICombination* combo) +{ + p->combs.push(combo); +} + +void ColorComboWidget::clearCombination() +{ + while (!p->combs.empty()) { + p->combs.pop(); + } + // dummy + addCombination(new colorcombo::ICombination(this)); + switchCombination(); +} + +colorcombo::ICombination* ColorComboWidget::currentCombination() const +{ + return p->combs.front(); +} + +void ColorComboWidget::setColors(const QVector& colors) +{ + for (int i = 0; i < colors.size(); ++i) { + auto btn = qobject_cast(p->hlayout->itemAt(i)->widget()); + btn->setColor(colors[i]); + } +} + +void ColorComboWidget::setColorCorrection(ColorCorrection* colorCorrection) +{ + p->colorCorrection = colorCorrection; + for (int i = 0; i < p->hlayout->count(); ++i) { + auto btn = qobject_cast(p->hlayout->itemAt(i)->widget()); + btn->setColorCorrection(colorCorrection); + } +} + +void ColorComboWidget::switchCombination() +{ + if (p->combs.empty()) return; + + auto front = p->combs.front(); + p->combs.pop(); + p->combs.push(front); + + auto currentComb = p->combs.front(); + + // clear + QLayoutItem* item; + while (item = p->hlayout->takeAt(0)) { + if (item->widget()) { + delete item->widget(); + } + delete item; + } + // add + auto colors = currentComb->genColors(Qt::white); + int size = colors.size() + 1; + for (int i = 0; i < size; ++i) { + auto btn = new ColorButton(this); + int bolderRightWidth = i < size - 1 ? 0 : 1; + btn->setBolderWidth(1, 1, 1, bolderRightWidth); + btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + btn->setAcceptDrops(false); // color can't be changed by drop + btn->setColorCorrection(p->colorCorrection); + connect(btn, &ColorButton::colorClicked, this, &ColorComboWidget::colorClicked); + p->hlayout->addWidget(btn); + } + + p->factorSlider->blockSignals(true); + p->factorSpinbox->blockSignals(true); + p->factorSpinbox->setDecimals(currentComb->decimals()); // need set decimals first + p->factorSlider->setRange(currentComb->min(), currentComb->max()); + p->factorSpinbox->setRange(currentComb->min(), currentComb->max()); + p->factorSlider->setValue(currentComb->getValue()); + p->factorSpinbox->setValue(currentComb->getValue()); + p->factorSlider->setEnabled(currentComb->rangeEnabled()); + p->factorSpinbox->setEnabled(currentComb->rangeEnabled()); + p->factorSlider->blockSignals(false); + p->factorSpinbox->blockSignals(false); + + emit combinationChanged(currentComb); +} + +//------------------------------------------ color lineedit -------------------------------- + +ColorLineEdit::ColorLineEdit(QWidget* parent) + : QLineEdit(parent) +{ + connect(this, &ColorLineEdit::editingFinished, this, [this]() { + setText(text().toUpper()); + emit currentColorChanged(QColor(text())); + }); +} +void ColorLineEdit::setColor(const QColor& color) +{ + setText(color.name().toUpper()); +} + +void ColorLineEdit::keyPressEvent(QKeyEvent* e) +{ + if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { + clearFocus(); + } + QLineEdit::keyPressEvent(e); +} + +//------------------------------------------ color picker ---------------------------------- +class ColorPicker::Private +{ +public: + int rectLength = 20; + int scaleSize = 10; + QPoint cursorPos; + QImage fullScreenImg; + + void grabFullScreen() + { + const QDesktopWidget* desktop = QApplication::desktop(); + const QPixmap pixmap = QApplication::primaryScreen()->grabWindow(desktop->winId(), desktop->pos().x(), desktop->pos().y(), + desktop->width(), desktop->height()); + fullScreenImg = pixmap.toImage(); + } + + QRect getScreenRect() const + { + const QDesktopWidget* desktop = QApplication::desktop(); + return QRect(desktop->pos(), desktop->size()); + } + + QColor getColorAt(QPoint p) const + { + // p need in local coordinate + // e.g. if use QCursor::pos(), it's global pos, need mapFromGlobal(QCursor::pos()) + return fullScreenImg.pixelColor(p); + } + + QImage getScaledImage(QPoint p) const + { + int rectHalfLength = rectLength / 2; + QImage img = fullScreenImg.copy(p.x() - rectHalfLength, p.y() - rectHalfLength, rectLength, rectLength); + return img.scaled(scaleSize * rectLength, scaleSize * rectLength); + } + + QScreen* getScreenAt(QPoint p) const + { +#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) + QScreen* screen = QApplication::screenAt(p); +#else + int screenNum = QApplication::desktop()->screenNumber(p); + QScreen* screen = QApplication::screens().at(screenNum); +#endif + return screen; + } +}; + +ColorPicker::ColorPicker(QWidget* parent) + : QWidget(parent) + , p(new Private) +{ + setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); + setMouseTracking(true); + setCursor(Qt::CrossCursor); +} + +QColor ColorPicker::grabScreenColor(QPoint p) const +{ + // not use now, just make screenshot and get color from it + const QDesktopWidget* desktop = QApplication::desktop(); + const QPixmap pixmap = QApplication::primaryScreen()->grabWindow(desktop->winId(), p.x(), p.y(), 1, 1); + QImage i = pixmap.toImage(); + return i.pixel(0, 0); +} + +void ColorPicker::startColorPicking() +{ + p->grabFullScreen(); + showFullScreen(); // show fullscreen only covers one screen + QRect fullRect = p->getScreenRect(); + setGeometry(fullRect); // force reszie + setFocus(); + p->cursorPos = this->mapFromGlobal(QCursor::pos()); +} + +void ColorPicker::releaseColorPicking() +{ + hide(); +} + +void ColorPicker::paintEvent(QPaintEvent* e) +{ + QPainter painter(this); + // background + painter.drawImage(0, 0, p->fullScreenImg); + + // get screen info + QPoint globalPos = this->mapToGlobal(p->cursorPos); + auto screen = p->getScreenAt(globalPos); + auto rect = screen->geometry(); + QPoint bottomRight = this->mapFromGlobal(rect.bottomRight()); + + // scaled img + auto img = p->getScaledImage(p->cursorPos); + auto currentColor = p->getColorAt(p->cursorPos); + // calculate img pos + int dx = 20, dy = 20; + int x, y; + if (bottomRight.x() - p->cursorPos.x() < img.width() + dx) { + x = p->cursorPos.x() - img.width() - dx; + } + else { + x = p->cursorPos.x() + dx; + } + if (bottomRight.y() - p->cursorPos.y() < img.height() + dy) { + y = p->cursorPos.y() - img.height() + dy; + } + else { + y = p->cursorPos.y() + dy; + } + + painter.translate(x, y); + painter.drawImage(0, 0, img); + + int rectWidth = 10; + int halfRectWidth = rectWidth / 2; + int halfH = img.height() / 2; + int halfW = img.width() / 2; + // cross + painter.setPen(QPen(QColor("#aadafa7f"), rectWidth)); + painter.drawLine(halfW, halfRectWidth, halfW, halfH - rectWidth); + painter.drawLine(halfW, rectWidth + halfH, halfW, img.height() - halfRectWidth); + painter.drawLine(halfRectWidth, halfH, halfW - rectWidth, halfH); + painter.drawLine(rectWidth + halfW, halfH, img.width() - halfRectWidth, halfH); + // bolder + painter.setPen(QPen(qGray(currentColor.rgb()) > 127 ? Qt::black : Qt::white, 1)); + painter.drawRect(0, 0, img.width(), img.height()); + painter.drawRect(halfW - halfRectWidth, halfH - halfRectWidth, rectWidth, rectWidth); +} + +void ColorPicker::mouseMoveEvent(QMouseEvent* e) +{ + p->cursorPos = e->pos(); + update(); +} + +void ColorPicker::mouseReleaseEvent(QMouseEvent* e) +{ + if (e->button() == Qt::LeftButton) { + emit colorSelected(p->getColorAt(this->mapFromGlobal(QCursor::pos()))); + releaseColorPicking(); + } + else if (e->button() == Qt::RightButton) { + releaseColorPicking(); + } +} + +void ColorPicker::keyPressEvent(QKeyEvent* e) +{ + switch (e->key()) { + case Qt::Key_Escape: + releaseColorPicking(); + break; + case Qt::Key_Return: + case Qt::Key_Enter: + emit colorSelected(p->getColorAt(this->mapFromGlobal(QCursor::pos()))); + releaseColorPicking(); + break; + case Qt::Key_Up: + QCursor::setPos(p->cursorPos.x(), p->cursorPos.y() - 1); + break; + case Qt::Key_Down: + QCursor::setPos(p->cursorPos.x(), p->cursorPos.y() + 1); + break; + case Qt::Key_Left: + QCursor::setPos(p->cursorPos.x() - 1, p->cursorPos.y()); + break; + case Qt::Key_Right: + QCursor::setPos(p->cursorPos.x() + 1, p->cursorPos.y()); + break; + default: + break; + } +} + +void ColorPicker::focusOutEvent(QFocusEvent* e) +{ + releaseColorPicking(); +} + +//------------------------------------------------------- color data -------------------------------------------- +struct ColorEditorData +{ + static constexpr int rowCount = 4; + static constexpr int colCount = 12; + QColor standardColor[rowCount * colCount]; + + ColorEditorData() + { + // standard + int i = 0; + for (int s = 0; s < rowCount; ++s) { + for (int h = 0; h < colCount; ++h) { + standardColor[i++] = QColor::fromHsvF(1.0 * h / colCount, 1.0 - 1.0 * s / rowCount, 1.0); + } + } + } + + QVector readSettings() + { + const QSettings settings(QSettings::UserScope, QStringLiteral("__ColorEditor_4x12")); + int count = settings.value(QLatin1String("customCount")).toInt(); + QVector customColor(count); + // if zero, init with standard + if (count == 0) { + for (const auto& color : standardColor) { + customColor.append(color); + } + } + // otherwise, init with settings + else { + for (int i = 0; i < count; ++i) { + const QVariant v = settings.value(QLatin1String("customColors/") + QString::number(i)); + if (v.isValid()) { + customColor[i] = v.toUInt(); + } + } + } + + return customColor; + } + void writeSettings(const QVector& colors) + { + QSettings settings(QSettings::UserScope, QStringLiteral("__ColorEditor_4x12")); + int count = colors.size(); + settings.setValue(QLatin1String("customCount"), count); + for (int i = 0; i < count; ++i) { + settings.setValue(QLatin1String("customColors/") + QString::number(i), colors[i].rgb()); + } + } +}; + +//------------------------------------------ color editor ---------------------------------- +class ColorEditor::Private +{ +public: + ColorWheel* wheel; + QCheckBox* showInSRGB; + ColorLineEdit* colorText; + ColorPreview* preview; + ColorPicker* picker; + QPushButton* pickerBtn; + ColorComboWidget* combo; + QGroupBox* previewGroup; + QGroupBox* comboGroup; + ColorPalette* palette; + ColorSpinHSlider* rSlider; + ColorSpinHSlider* gSlider; + ColorSpinHSlider* bSlider; + ColorSpinHSlider* hSlider; + ColorSpinHSlider* sSlider; + ColorSpinHSlider* vSlider; + + QColor currentColor; + QColor selectedColor; + ColorEditorData colorData; + std::unique_ptr colorCorrection; + + Private(const QColor& color, QDialog* parent) + { + colorCorrection = std::unique_ptr(new ColorCorrection); + selectedColor = color; + // left + picker = new ColorPicker(parent); + pickerBtn = new QPushButton(tr("pick"), parent); + wheel = new ColorWheel(parent); + showInSRGB = new QCheckBox(tr("show in srgb"), parent); + colorText = new ColorLineEdit(parent); + preview = new ColorPreview(color, parent); + combo = new ColorComboWidget(parent); + previewGroup = new QGroupBox(tr("Previous/Current Colors"), parent); + comboGroup = new QGroupBox(tr("Color Combination"), parent); + + colorText->setMaximumWidth(ZenoStyle::dpiScaled(60)); + colorText->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + pickerBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + auto previewWidget = new QWidget(parent); + auto previewLayout = new QHBoxLayout(previewWidget); + previewLayout->setMargin(0); + previewLayout->addWidget(preview); + previewLayout->addWidget(pickerBtn); + + auto previewGroupLayout = new QHBoxLayout(previewGroup); + previewGroupLayout->addWidget(previewWidget); + + auto comboGroupLayout = new QHBoxLayout(comboGroup); + comboGroupLayout->addWidget(combo); + + auto leftWidget = new QWidget(parent); + auto leftLayout = new QGridLayout(leftWidget); + leftLayout->setContentsMargins(0, 0, 5, 0); + leftLayout->setSpacing(0); + leftLayout->addWidget(wheel, 0, 0, 1, 10); + leftLayout->addWidget(showInSRGB, 1, 0, 1, 5, Qt::AlignLeft); + leftLayout->addWidget(colorText, 1, 5, 1, 5, Qt::AlignRight); + leftLayout->addWidget(previewGroup, 2, 0, 1, 10); + leftLayout->addWidget(comboGroup, 3, 0, 1, 10); + + // right + palette = new ColorPalette(colorData.colCount, parent); + rSlider = new ColorSpinHSlider("R", parent); + gSlider = new ColorSpinHSlider("G", parent); + bSlider = new ColorSpinHSlider("B", parent); + hSlider = new ColorSpinHSlider("H", parent); + sSlider = new ColorSpinHSlider("S", parent); + vSlider = new ColorSpinHSlider("V", parent); + + auto colorSlider = new QWidget(parent); + auto colorSliderLayout = new QVBoxLayout(colorSlider); + colorSliderLayout->setContentsMargins(5, 0, 0, 0); + colorSliderLayout->setSpacing(2); + colorSliderLayout->addWidget(rSlider); + colorSliderLayout->addWidget(gSlider); + colorSliderLayout->addWidget(bSlider); + colorSliderLayout->addSpacing(5); + colorSliderLayout->addWidget(hSlider); + colorSliderLayout->addWidget(sSlider); + colorSliderLayout->addWidget(vSlider); + + rSlider->setRange(0, 1); + gSlider->setRange(0, 1); + bSlider->setRange(0, 1); + hSlider->setRange(0, 1); + sSlider->setRange(0, 1); + vSlider->setRange(0, 1); + + setGradientR(color); + setGradientG(color); + setGradientB(color); + setGradientH(color); + setGradientS(color); + setGradientV(color); + + auto rightSplitter = new QSplitter(Qt::Vertical, parent); + rightSplitter->addWidget(palette); + rightSplitter->addWidget(colorSlider); + rightSplitter->setCollapsible(0, false); + rightSplitter->setCollapsible(1, false); + auto equalH = std::max(palette->minimumSizeHint().height(), colorSlider->minimumSizeHint().height()); + rightSplitter->setSizes({equalH * 2, equalH * 1}); // setStretchFactor not always work well + + auto mainSplitter = new QSplitter(parent); + mainSplitter->addWidget(leftWidget); + mainSplitter->addWidget(rightSplitter); + mainSplitter->setStretchFactor(0, 3); + mainSplitter->setStretchFactor(1, 7); + mainSplitter->setCollapsible(0, false); + mainSplitter->setCollapsible(1, false); + // buttons + auto buttons = new QDialogButtonBox(parent); + auto okBtn = buttons->addButton(QDialogButtonBox::Ok); + auto cancleBtn = buttons->addButton(QDialogButtonBox::Cancel); + connect(okBtn, &QPushButton::clicked, parent, [this, parent]() { + selectedColor = currentColor; + parent->accept(); + }); + connect(cancleBtn, &QPushButton::clicked, parent, &QDialog::reject); + + auto layout = new QVBoxLayout(parent); + layout->setMargin(5); + layout->addWidget(mainSplitter); + layout->addWidget(buttons); + } + + void blockColorSignals(bool block) + { + wheel->blockSignals(block); + colorText->blockSignals(block); + preview->blockSignals(block); + combo->blockSignals(block); + palette->blockSignals(block); + rSlider->blockSignals(block); + gSlider->blockSignals(block); + bSlider->blockSignals(block); + hSlider->blockSignals(block); + sSlider->blockSignals(block); + vSlider->blockSignals(block); + } + + void setGradient(const QColor& color) + { + bool rChanged = color.red() != currentColor.red(); + bool gChanged = color.green() != currentColor.green(); + bool bChanged = color.blue() != currentColor.blue(); + bool hChanged = color.hsvHue() != currentColor.hsvHue(); + bool sChanged = color.hsvSaturation() != currentColor.hsvSaturation(); + bool vChanged = color.value() != currentColor.value(); + + if (gChanged || bChanged) { + setGradientR(color); + } + if (rChanged || bChanged) { + setGradientG(color); + } + if (rChanged || gChanged) { + setGradientB(color); + } + if (sChanged || vChanged) { + setGradientH(color); + } + if (hChanged || vChanged) { + setGradientS(color); + } + if (hChanged || sChanged) { + setGradientV(color); + } + } + + void setGradientR(const QColor& color) + { + rSlider->setGradient(QColor(0, color.green(), color.blue()), QColor(255, color.green(), color.blue())); + } + void setGradientG(const QColor& color) + { + gSlider->setGradient(QColor(color.red(), 0, color.blue()), QColor(color.red(), 255, color.blue())); + } + void setGradientB(const QColor& color) + { + bSlider->setGradient(QColor(color.red(), color.green(), 0), QColor(color.red(), color.green(), 255)); + } + void setGradientH(const QColor& color) + { + // hSlider is unique + static QGradientStops hColors(7); + for (int i = 0; i < hColors.size(); ++i) { + float f = 1.0 * i / (hColors.size() - 1); + hColors[i] = {f, QColor::fromHsvF(f, color.hsvSaturationF(), color.valueF())}; + } + hSlider->setGradient(hColors); + } + void setGradientS(const QColor& color) + { + sSlider->setGradient(QColor::fromHsvF(color.hsvHueF(), 0, color.valueF()), QColor::fromHsvF(color.hsvHueF(), 1, color.valueF())); + } + void setGradientV(const QColor& color) + { + vSlider->setGradient(QColor::fromHsvF(color.hsvHueF(), color.hsvSaturationF(), 0), + QColor::fromHsvF(color.hsvHueF(), color.hsvSaturationF(), 1)); + } +}; + +ColorEditor::ColorEditor(QWidget* parent) + : ColorEditor(Qt::white, parent) +{ +} + +ColorEditor::ColorEditor(const QColor& initial, QWidget* parent) + : QDialog(parent) + , p(new Private(initial, this)) +{ + setWindowFlag(Qt::WindowContextHelpButtonHint, false); + setWindowTitle(tr("ColorEditor")); + setMinimumSize(ZenoStyle::dpiScaled(500), ZenoStyle::dpiScaled(350)); + initSlots(); + // init combinations + p->combo->addCombination(new colorcombo::Complementary(this)); + p->combo->addCombination(new colorcombo::Analogous(this)); + p->combo->addCombination(new colorcombo::Monochromatic(this)); + p->combo->addCombination(new colorcombo::Triadic(this)); + p->combo->addCombination(new colorcombo::Tetradic(this)); + // init colors for palette + auto paletteColors = p->colorData.readSettings(); + for (const auto& color : paletteColors) { + p->palette->addColor(color); + } + // current combination + p->wheel->setColorCombination(p->combo->currentCombination()); + // current color + setCurrentColor(initial); + // show in srgb + p->showInSRGB->setChecked(true); +} + +void ColorEditor::setCurrentColor(const QColor& color) +{ + p->blockColorSignals(true); + { + p->wheel->setSelectedColor(color); + p->colorText->setColor(color); + p->preview->setCurrentColor(color); + p->setGradient(color); + p->rSlider->setValue(color.redF()); + p->gSlider->setValue(color.greenF()); + p->bSlider->setValue(color.blueF()); + p->hSlider->setValue(color.hsvHueF()); + p->sSlider->setValue(color.hsvSaturationF()); + p->vSlider->setValue(color.valueF()); + } + p->blockColorSignals(false); + + p->currentColor = color; +} + +QColor ColorEditor::currentColor() const +{ + return p->currentColor; +} + +QColor ColorEditor::selectedColor() const +{ + return p->selectedColor; +} + +void ColorEditor::setColorCombinations(const QVector combinations) +{ + p->combo->clearCombination(); + for (const auto& combination : combinations) { + p->combo->addCombination(combination); + } +} + +void ColorEditor::closeEvent(QCloseEvent* e) +{ + // save colors on close + p->colorData.writeSettings(p->palette->colors()); + QDialog::closeEvent(e); +} + +void ColorEditor::keyPressEvent(QKeyEvent* e) +{ + if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { + return; + } + QDialog::keyPressEvent(e); +} + +void ColorEditor::initSlots() +{ + // color correction + connect(p->showInSRGB, &QCheckBox::toggled, this, [this](bool checked) { + auto colorCorrection = checked ? p->colorCorrection.get() : nullptr; + p->wheel->setColorCorrection(colorCorrection); + p->palette->setColorCorrection(colorCorrection); + p->preview->setColorCorrection(colorCorrection); + p->combo->setColorCorrection(colorCorrection); + p->rSlider->setColorCorrection(colorCorrection); + p->gSlider->setColorCorrection(colorCorrection); + p->bSlider->setColorCorrection(colorCorrection); + p->hSlider->setColorCorrection(colorCorrection); + p->sSlider->setColorCorrection(colorCorrection); + p->vSlider->setColorCorrection(colorCorrection); + }); + // picker + connect(p->pickerBtn, &QPushButton::clicked, p->picker, &ColorPicker::startColorPicking); + connect(p->picker, &ColorPicker::colorSelected, this, &ColorEditor::setCurrentColor); + // color combination + connect(p->wheel, &ColorWheel::combinationColorChanged, p->combo, &ColorComboWidget::setColors); + connect(p->combo, &ColorComboWidget::combinationChanged, this, [this](colorcombo::ICombination* combination) { + p->wheel->setColorCombination(combination); + p->comboGroup->setTitle(combination->name()); + }); + // color wheel/text/preview/combo + connect(p->wheel, &ColorWheel::colorSelected, this, &ColorEditor::setCurrentColor); + connect(p->colorText, &ColorLineEdit::currentColorChanged, this, &ColorEditor::setCurrentColor); + connect(p->preview, &ColorPreview::currentColorChanged, this, &ColorEditor::setCurrentColor); + connect(p->palette, &ColorPalette::colorClicked, this, &ColorEditor::setCurrentColor); + connect(p->combo, &ColorComboWidget::colorClicked, this, [this](const QColor& color) { + // don't change wheel color + p->wheel->setEnabled(false); + setCurrentColor(color); + p->wheel->setEnabled(true); + }); + // color slider + connect(p->rSlider, &ColorSpinHSlider::valueChanged, this, [this](double value) { + auto color = QColor::fromRgbF(value, p->currentColor.greenF(), p->currentColor.blueF()); + setCurrentColor(color); + }); + connect(p->gSlider, &ColorSpinHSlider::valueChanged, this, [this](double value) { + auto color = QColor::fromRgbF(p->currentColor.redF(), value, p->currentColor.blueF()); + setCurrentColor(color); + }); + connect(p->bSlider, &ColorSpinHSlider::valueChanged, this, [this](double value) { + auto color = QColor::fromRgbF(p->currentColor.redF(), p->currentColor.greenF(), value); + setCurrentColor(color); + }); + connect(p->hSlider, &ColorSpinHSlider::valueChanged, this, [this](double value) { + auto color = QColor::fromHsvF(value, p->currentColor.hsvSaturationF(), p->currentColor.valueF()); + setCurrentColor(color); + }); + connect(p->sSlider, &ColorSpinHSlider::valueChanged, this, [this](double value) { + auto color = QColor::fromHsvF(p->currentColor.hsvHueF(), value, p->currentColor.valueF()); + setCurrentColor(color); + }); + connect(p->vSlider, &ColorSpinHSlider::valueChanged, this, [this](double value) { + auto color = QColor::fromHsvF(p->currentColor.hsvHueF(), p->currentColor.hsvSaturationF(), value); + setCurrentColor(color); + }); +} + +QColor ColorEditor::getColor(const QColor& initial, QWidget* parent, const QString& title) +{ + ColorEditor dlg(initial, parent); + if (!title.isEmpty()) dlg.setWindowTitle(title); + dlg.exec(); + return dlg.selectedColor(); +} diff --git a/ui/zenoui/ColorEditor/ColorEditor.h b/ui/zenoui/ColorEditor/ColorEditor.h new file mode 100644 index 0000000000..add4606900 --- /dev/null +++ b/ui/zenoui/ColorEditor/ColorEditor.h @@ -0,0 +1,368 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include + +//------------------------------------------- color correction ----------------------------------------------- +struct ColorCorrection +{ + float gamma = 2.2f; + void correct(QColor& color); + void correct(QImage& image); +}; + +//------------------------------------------- color combination ---------------------------------------------- +namespace colorcombo +{ +class ICombination : public QObject +{ + Q_OBJECT +public: + explicit ICombination(QObject* parent = nullptr); + explicit ICombination(double min, double max, double value, int decimals, bool rangeEnabled, QObject* parent = nullptr); + virtual ~ICombination() = default; + virtual QString name(); + virtual QVector genColors(const QColor& color); + void setRange(double min, double max); + void setValue(double value); + void setDecimals(int decimals); + double min() const; + double max() const; + double getValue() const; + bool rangeEnabled() const; + int decimals() const; + +private: + double m_min; + double m_max; + double m_value; + int m_decimals; + bool m_rangeEnabled; +}; + +class Complementary : public ICombination +{ +public: + explicit Complementary(QObject* parent = nullptr); + virtual QString name() override; + virtual QVector genColors(const QColor& color) override; +}; + +class Monochromatic : public ICombination +{ +public: + explicit Monochromatic(QObject* parent = nullptr); + virtual QString name() override; + virtual QVector genColors(const QColor& color) override; +}; + +class Analogous : public ICombination +{ +public: + explicit Analogous(QObject* parent = nullptr); + virtual QString name() override; + virtual QVector genColors(const QColor& color) override; +}; + +class Triadic : public ICombination +{ +public: + explicit Triadic(QObject* parent = nullptr); + virtual QString name() override; + virtual QVector genColors(const QColor& color) override; +}; + +class Tetradic : public ICombination +{ +public: + explicit Tetradic(QObject* parent = nullptr); + virtual QString name() override; + virtual QVector genColors(const QColor& color) override; +}; +} // namespace colorcombo + +//-------------------------------------------------- color wheel -------------------------------------------------- +class ColorWheel : public QWidget +{ + Q_OBJECT +public: + explicit ColorWheel(QWidget* parent = nullptr); + + void setColorCombination(colorcombo::ICombination* combination); + void setSelectedColor(const QColor& color); + void setColorCorrection(ColorCorrection* colorCorrection); + QColor getSelectedColor() const; + QColor getColor(int x, int y) const; + +signals: + void colorSelected(const QColor& color); + void combinationColorChanged(const QVector& colors); + +protected: + void paintEvent(QPaintEvent* e) override; + void mousePressEvent(QMouseEvent* e) override; + void mouseMoveEvent(QMouseEvent* e) override; + void resizeEvent(QResizeEvent* e) override; + +private: + void processMouseEvent(QMouseEvent* e); + void drawSelector(QPainter* painter, const QColor& color, int radius); + + class Private; + std::unique_ptr p; +}; + +//---------------------------------------------- color slider ------------------------------------------------------- +class MixedSpinBox : public QDoubleSpinBox +{ +public: + explicit MixedSpinBox(QWidget* parent = nullptr); + virtual QString textFromValue(double value) const override; + +protected: + void keyPressEvent(QKeyEvent* e) override; +}; + +class JumpableSlider : public QSlider +{ + Q_OBJECT +public: + explicit JumpableSlider(QWidget* parent); + explicit JumpableSlider(Qt::Orientation orientation, QWidget* parent = nullptr); + void setValue(double value); + void setMinimum(double value); + void setMaximum(double value); + void setRange(double minValue, double maxValue); + void setSingleStep(double value); + + double value() const; + double minimum() const; + double maximum() const; + double singleStep() const; + +signals: + void valueChanged(double value); + +protected: + void mousePressEvent(QMouseEvent* e) override; + void mouseMoveEvent(QMouseEvent* e) override; + void mouseReleaseEvent(QMouseEvent* e) override; + +private: + void handleMouseEvent(QMouseEvent* e); + + class Private; + std::unique_ptr p; +}; + +class GradientSlider : public JumpableSlider +{ + Q_OBJECT +public: + explicit GradientSlider(QWidget* parent = nullptr); + void setGradient(const QColor& startColor, const QColor& stopColor); + void setGradient(const QGradientStops& colors); + void setColorCorrection(ColorCorrection* colorCorrection); + QGradientStops gradientColor() const; + +protected: + void paintEvent(QPaintEvent* e) override; + void resizeEvent(QResizeEvent* e) override; + +private: + class Private; + std::unique_ptr p; +}; + +class ColorSpinHSlider : public QWidget +{ + Q_OBJECT +public: + explicit ColorSpinHSlider(const QString& name, QWidget* parent = nullptr); + void setGradient(const QColor& startColor, const QColor& stopColor); + void setGradient(const QGradientStops& colors); + void setColorCorrection(ColorCorrection* colorCorrection); + void setValue(double value); + void setRange(double min, double max); + QGradientStops gradientColor() const; + double value() const; + +signals: + void valueChanged(double value); + +private: + class Private; + std::unique_ptr p; +}; + +//--------------------------------------------- color button ------------------------------------------------------- +class ColorButton : public QPushButton +{ + Q_OBJECT +public: + explicit ColorButton(QWidget* parent = nullptr); + void setColor(const QColor& color); + void setColorCorrection(ColorCorrection* colorCorrection); + void setBolderWidth(int top, int bottom, int left, int right); + QColor color() const; + +signals: + void colorClicked(const QColor& color); + void colorDroped(const QColor& color); + +protected: + void mousePressEvent(QMouseEvent* e) override; + void mouseMoveEvent(QMouseEvent* e) override; + void dragEnterEvent(QDragEnterEvent* e) override; + void dragLeaveEvent(QDragLeaveEvent*) override; + void dropEvent(QDropEvent* e) override; + +private: + class Private; + std::unique_ptr p; +}; + +//--------------------------------------------- color palette ------------------------------------------------------ +class ColorPalette : public QScrollArea +{ + Q_OBJECT +public: + explicit ColorPalette(int column, QWidget* parent = nullptr); + void addColor(const QColor& color); + void setColor(const QColor& color, int row, int column); + void removeColor(int row, int column); + void setColorCorrection(ColorCorrection* colorCorrection); + QColor colorAt(int row, int column) const; + QVector colors() const; + +signals: + void colorClicked(const QColor& color); + +protected: + void dragEnterEvent(QDragEnterEvent* e) override; + void dropEvent(QDropEvent* e) override; + +private: + class Private; + std::unique_ptr p; +}; + +//--------------------------------------------- color preview ------------------------------------------------------- +class ColorPreview : public QWidget +{ + Q_OBJECT +public: + explicit ColorPreview(const QColor& color, QWidget* parent = nullptr); + void setCurrentColor(const QColor& color); + void setColorCorrection(ColorCorrection* colorCorrection); + QColor currentColor() const; + QColor previousColor() const; + +signals: + void currentColorChanged(const QColor& color); + +private: + class Private; + std::unique_ptr p; +}; + +//------------------------------------------- color combo widget --------------------------- +class ColorComboWidget : public QWidget +{ + Q_OBJECT +public: + explicit ColorComboWidget(QWidget* parent = nullptr); + + void addCombination(colorcombo::ICombination* combo); + void clearCombination(); + void switchCombination(); + void setColors(const QVector& colors); + void setColorCorrection(ColorCorrection* colorCorrection); + colorcombo::ICombination* currentCombination() const; + +signals: + void colorClicked(const QColor& color); + void combinationChanged(colorcombo::ICombination* combo); + +private: + class Private; + std::unique_ptr p; +}; + +//------------------------------------------ color lineedit -------------------------------- +class ColorLineEdit : public QLineEdit +{ + Q_OBJECT +public: + explicit ColorLineEdit(QWidget* parent = nullptr); + void setColor(const QColor& color); + +signals: + void currentColorChanged(const QColor& color); + +protected: + void keyPressEvent(QKeyEvent* e) override; +}; + +//------------------------------------------ color picker ---------------------------------- +class ColorPicker : public QWidget +{ + Q_OBJECT +public: + explicit ColorPicker(QWidget* parent = nullptr); + + QColor grabScreenColor(QPoint p) const; + void startColorPicking(); + void releaseColorPicking(); + +signals: + void colorSelected(const QColor& color); + +protected: + void paintEvent(QPaintEvent* e) override; + void mouseMoveEvent(QMouseEvent* e) override; + void mouseReleaseEvent(QMouseEvent* e) override; + void keyPressEvent(QKeyEvent* e) override; + void focusOutEvent(QFocusEvent* e) override; + +private: + class Private; + std::unique_ptr p; +}; + +//------------------------------------------ color editor ---------------------------------- +class ColorEditor : public QDialog +{ + Q_OBJECT +public: + explicit ColorEditor(QWidget* parent = nullptr); + explicit ColorEditor(const QColor& initial, QWidget* parent = nullptr); + + static QColor getColor(const QColor& initial, QWidget* parent = nullptr, const QString& title = ""); + + void setCurrentColor(const QColor& color); + QColor currentColor() const; + QColor selectedColor() const; + + void setColorCombinations(const QVector combinations); + +signals: + void currentColorChanged(const QColor& color); + +protected: + void closeEvent(QCloseEvent* e) override; + void keyPressEvent(QKeyEvent* e) override; + +private: + void initSlots(); + + class Private; + std::unique_ptr p; +}; \ No newline at end of file diff --git a/ui/zenoui/comctrl/gv/zenogvhelper.cpp b/ui/zenoui/comctrl/gv/zenogvhelper.cpp index 0ca640d2ad..b65f62bc9c 100644 --- a/ui/zenoui/comctrl/gv/zenogvhelper.cpp +++ b/ui/zenoui/comctrl/gv/zenogvhelper.cpp @@ -90,9 +90,18 @@ void ZenoGvHelper::setValue(QGraphicsItem* item, PARAM_CONTROL ctrl, const QVari else if (ZenoParamPushButton* pBtn = qobject_cast(pItem)) { //nothing need to be done. + // purecolor if (value.canConvert()) { pBtn->setProperty("color", value.value().name()); } + // colorvec3f + else if (value.canConvert()) { + UI_VECTYPE vec = value.value(); + if (vec.size() == 3) { + auto color = QColor::fromRgbF(vec[0], vec[1], vec[2]); + pBtn->setProperty("color", color.name()); + } + } } else if (ZenoVecEditItem* pBtn = qobject_cast(pItem)) { diff --git a/ui/zenoui/comctrl/gv/zitemfactory.cpp b/ui/zenoui/comctrl/gv/zitemfactory.cpp index ace70e4e9c..bbc9a483b9 100644 --- a/ui/zenoui/comctrl/gv/zitemfactory.cpp +++ b/ui/zenoui/comctrl/gv/zitemfactory.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include "zveceditoritem.h" #include "style/zenostyle.h" @@ -200,19 +201,37 @@ namespace zenoui pItemWidget = pEditBtn; break; } - case CONTROL_PURE_COLOR: { + case CONTROL_PURE_COLOR: + case CONTROL_COLOR_VEC3F: + { + QColor currentColor; + if (ctrl == CONTROL_PURE_COLOR) { + currentColor = value.value(); + } + else if (ctrl == CONTROL_COLOR_VEC3F) { + auto colorVec = value.value(); + currentColor = QColor::fromRgbF(colorVec[0], colorVec[1], colorVec[2]); + } + ZenoParamPushButton *pEditBtn = new ZenoParamPushButton("", -1, QSizePolicy::Expanding); pEditBtn->setData(GVKEY_SIZEHINT, ZenoStyle::dpiScaledSize(QSizeF(100, zenoui::g_ctrlHeight))); pEditBtn->setData(GVKEY_SIZEPOLICY, QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); pEditBtn->setData(GVKEY_TYPE, type); - pEditBtn->setProperty("color", value.value().name()); + pEditBtn->setProperty("color", currentColor.name()); QObject::connect(pEditBtn, &ZenoParamPushButton::clicked, [=]() { - QColor color = QColorDialog::getColor(QColor(pEditBtn->property("color").toString())); + QColor color = ColorEditor::getColor(QColor(pEditBtn->property("color").toString())); if (color.isValid()) { pEditBtn->setProperty("color", color.name()); - cbSet.cbEditFinished(QVariant::fromValue(color)); + if (ctrl == CONTROL_PURE_COLOR) { + cbSet.cbEditFinished(QVariant::fromValue(color)); + } + else if (ctrl == CONTROL_COLOR_VEC3F) { + UI_VECTYPE colorVec(3); + color.getRgbF(&colorVec[0], &colorVec[1], &colorVec[2]); + cbSet.cbEditFinished(QVariant::fromValue(colorVec)); + } } }); pItemWidget = pEditBtn; diff --git a/ui/zenoui/comctrl/zwidgetfactory.cpp b/ui/zenoui/comctrl/zwidgetfactory.cpp index ac022b5b6d..3d1a0ccc62 100644 --- a/ui/zenoui/comctrl/zwidgetfactory.cpp +++ b/ui/zenoui/comctrl/zwidgetfactory.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -108,16 +109,33 @@ namespace zenoui }); return pBtn; } - case CONTROL_PURE_COLOR: { + case CONTROL_PURE_COLOR: + case CONTROL_COLOR_VEC3F: + { + QColor currentColor; + if (ctrl == CONTROL_PURE_COLOR) { + currentColor = value.value(); + } + else if (ctrl == CONTROL_COLOR_VEC3F) { + auto colorVec = value.value(); + currentColor = QColor::fromRgbF(colorVec[0], colorVec[1], colorVec[2]); + } QPushButton *pBtn = new QPushButton; pBtn->setFixedSize(ZenoStyle::dpiScaled(100), ZenoStyle::dpiScaled(30)); - pBtn->setStyleSheet(QString("background-color:%1; border:0;").arg(value.value().name())); + pBtn->setStyleSheet(QString("background-color:%1; border:0;").arg(currentColor.name())); QObject::connect(pBtn, &QPushButton::clicked, [=]() { - QColor color = QColorDialog::getColor(pBtn->palette().window().color()); + QColor color = ColorEditor::getColor(pBtn->palette().window().color()); if (color.isValid()) { pBtn->setStyleSheet(QString("background-color:%1; border:0;").arg(color.name())); - cbSet.cbEditFinished(QVariant::fromValue(color)); + if (ctrl == CONTROL_PURE_COLOR) { + cbSet.cbEditFinished(QVariant::fromValue(color)); + } + else if (ctrl == CONTROL_COLOR_VEC3F) { + UI_VECTYPE colorVec(3); + color.getRgbF(&colorVec[0], &colorVec[1], &colorVec[2]); + cbSet.cbEditFinished(QVariant::fromValue(colorVec)); + } } }); return pBtn; diff --git a/zeno/src/nodes/color/MakeColor.cpp b/zeno/src/nodes/color/MakeColor.cpp new file mode 100644 index 0000000000..63c03a3399 --- /dev/null +++ b/zeno/src/nodes/color/MakeColor.cpp @@ -0,0 +1,25 @@ +#include +#include + +namespace zeno { + +struct MakeColor : zeno::INode { + virtual void apply() override { + auto color = get_input2("color"); + set_output2("color", std::move(color)); + } +}; + +ZENO_DEFNODE(MakeColor)({ + { + {"colorvec3f", "color", "1, 1, 1"}, + }, + { + {"vec3f", "color"}, + }, + { + }, + {"color"}, +}); + +} // namespace zeno