/* * (c) LSIIT, UMR CNRS/UdS * Authors: F. Larue. * * See licence.txt for additional information. */ #include "GradientWidget.h" #include GradientWidget::GradientWidget( QWidget *parent ) : QWidget(parent) { setMinimumSize( 100, 20 ); m_BelowCursor = -1; m_SelectedStop = -1; setMouseTracking( true ); setFocusPolicy( Qt::ClickFocus ); } GradientWidget::GradientWidget( const QColor& start, const QColor& end, QWidget *parent ) : GradientWidget(parent) { addStop( start, 0.0f ); addStop( end , 1.0f ); } GradientWidget::GradientWidget( const QList& colors, const QList& values, QWidget *parent ) : GradientWidget(parent) { setStops( colors, values ); } void GradientWidget::resizeEvent( QResizeEvent *evt ) { for( auto &s : m_Stops ) s.updateX( width()-1 ); } void GradientWidget::paintEvent( QPaintEvent *evt ) { if( !isValid() ) return; const int w = width(); const int h = height(); QColor color = this->palette().dark().color(); QPainter painter; painter.begin( this ); // Display gradients between every stop pair. painter.setPen( Qt::NoPen ); if( m_Stops.front().value() != 0.0f ) { painter.setBrush( QBrush(m_Stops.front().color()) ); painter.drawRect( 0, 3, m_Stops.front().x(), h-6 ); } if( m_Stops.back().value() != 1.0f ) { painter.setBrush( QBrush(m_Stops.back().color()) ); painter.drawRect( m_Stops.back().x(), 3, w-m_Stops.back().x(), h-6 ); } for( int i=0; i= 0 ) { QString text = m_KeyboardInputValue.isEmpty()? QString::number(m_Stops[m_SelectedStop].value()) : m_KeyboardInputValue; QFontMetrics fm( font() ); #if QT_VERSION < QT_VERSION_CHECK(5,11,0) int textW = fm.width( text ); #else int textW = fm.horizontalAdvance( text ); #endif int textX = m_Stops[m_SelectedStop].x() + 4; int textY = ((h+fm.height()) >> 1) - fm.descent(); if( textX+textW >= w ) textX -= textW + 7; painter.setOpacity( 0.5f ); painter.setPen( QPen(QColor(255,255,255),1.0f) ); painter.drawText( textX , textY+1, text ); painter.drawText( textX+1, textY , text ); painter.setOpacity( 0.25f ); painter.drawText( textX , textY-1, text ); painter.drawText( textX-1, textY , text ); painter.setOpacity( 1.0f ); painter.setPen( QPen(color,1.0f) ); painter.drawText( textX, textY, text ); } } painter.end(); } int GradientWidget::getInterval( float value ) const { int beg = 0, end = m_Stops.size(); while( end != beg ) { int p = beg + (end-beg)/2; if( value < m_Stops[p].value() ) end = p; else beg = p+1; } return end; } QColor GradientWidget::getColor( int interval, float value ) const { assert( isValid() ); if( interval == 0 ) return m_Stops.front().color(); else if( interval == m_Stops.size() ) return m_Stops.back().color(); else { const Stop &s0 = m_Stops[interval-1]; const Stop &s1 = m_Stops[interval ]; float alpha = (value - s0.value()) / (s1.value() - s0.value()); const QColor &c0 = s0.color(); const QColor &c1 = s1.color(); float r = c0.redF () + alpha*(c1.redF () - c0.redF ()); float g = c0.greenF() + alpha*(c1.greenF() - c0.greenF()); float b = c0.blueF () + alpha*(c1.blueF () - c0.blueF ()); return QColor::fromRgbF( r, g, b ); } } float GradientWidget::clampValueToStopInterval( int n, float value ) const { value = std::max( 0.0f, std::min(value,1.0f) ); if( n > 0 && value < m_Stops[n-1].value() ) value = m_Stops[n-1].value(); else if( n < m_Stops.size()-1 && value > m_Stops[n+1].value() ) value = m_Stops[n+1].value(); return value; } void GradientWidget::addStop( const QColor& color, float value ) { Stop stop( color, value ); stop.updateX( width()-1 ); m_Stops.insert( getInterval(value), stop ); emit gradientModified(); update(); } void GradientWidget::setStops( const QList& colors, const QList& values ) { assert( colors.size() == values.size() ); assert( !colors.empty() ); bool changed = false; QVector stopsBackup = m_Stops; m_Stops.clear(); auto c = colors.begin(); auto v = values.begin(); int i = 0; while( c != colors.end() ) { if( i < stopsBackup.size() ) changed |= (stopsBackup[i].color() != *c || stopsBackup[i].value() != *v); else changed = true; ++ i; Stop stop( *c, *v ); stop.updateX( width()-1 ); m_Stops.insert( getInterval(*v), stop ); ++ c; ++ v; } if( changed ) { emit gradientModified(); update(); } } QString GradientWidget::getShaderFunctionBodyGLSL( const QVector< QPair >& uniqueStops, int beg, int end, const QString& indent ) const { QString res; if( end != beg ) { int p = beg + (end-beg)/2; res += indent + "if( value <= " + QString::number( (double) m_Stops[ uniqueStops[p].first ].value() ) + " )\n"; res += getShaderFunctionBodyGLSL( uniqueStops, beg, p, indent+" " ); res += indent + "else\n"; res += getShaderFunctionBodyGLSL( uniqueStops, p+1, end, indent+" " ); } else { if( beg == 0 ) { QColor c = m_Stops.front().color(); res = indent + "return vec3(" + QString::number(c.redF()) + "," + QString::number(c.greenF()) + "," + QString::number(c.blueF()) + ");\n"; } else if( beg == uniqueStops.size() ) { QColor c = m_Stops.back().color(); res = indent + "return vec3(" + QString::number(c.redF()) + "," + QString::number(c.greenF()) + "," + QString::number(c.blueF()) + ");\n"; } else { const Stop &s0 = m_Stops[ uniqueStops[beg-1].second ]; const Stop &s1 = m_Stops[ uniqueStops[beg ].first ]; const QColor &c0 = s0.color(); const QColor &c1 = s1.color(); res += indent + "return mix( "; res += "vec3(" + QString::number(c0.redF()) + "," + QString::number(c0.greenF()) + "," + QString::number(c0.blueF()) + "), "; res += "vec3(" + QString::number(c1.redF()) + "," + QString::number(c1.greenF()) + "," + QString::number(c1.blueF()) + "), "; res += "(value-" + QString::number((double) s0.value()) + ")*" + QString::number(1.0 / (s1.value() - s0.value())); res += " );\n"; } } return res; } QString GradientWidget::getShaderFunctionGLSL( const QString& funcName ) const { QVector< QPair > uniqueStops; for( int left=0; left(left,right) ); left = right; } QString func = "vec3 " + funcName + "( float value )\n" "{\n" + getShaderFunctionBodyGLSL( uniqueStops, 0, uniqueStops.size(), " " ) + "}\n"; return func; } void GradientWidget::mouseDoubleClickEvent( QMouseEvent *evt ) { if( m_SelectedStop >= 0 ) { Stop &stop = m_Stops[m_SelectedStop]; QColor color = QColorDialog::getColor( stop.color(), this, "Select gradient stop color" ); if( color.isValid() && color != stop.color() ) { stop.setColor( color ); emit gradientModified(); update(); } } else { float value = float(evt->x()) / (width()-1); int interval = getInterval( value ); Stop stop( getColor(interval,value), value ); stop.updateX( width()-1 ); m_Stops.insert( interval, stop ); emit gradientModified(); update(); } } void GradientWidget::mousePressEvent( QMouseEvent *evt ) { m_SelectedStop = m_BelowCursor; m_SelectedStopRight = m_BelowCursorRight; m_PreviousMouseX = evt->x(); update(); } void GradientWidget::mouseMoveEvent( QMouseEvent *evt ) { if( (evt->buttons() & Qt::LeftButton) && m_SelectedStop >= 0 ) { if( m_SelectedStopRight != m_SelectedStop ) { if( evt->x()-m_PreviousMouseX > 0 ) m_SelectedStop = m_SelectedStopRight; else m_SelectedStopRight = m_SelectedStop; } float value = clampValueToStopInterval( m_SelectedStop, float(evt->x()) / (width()-1) ); if( value != m_Stops[m_SelectedStop].value() ) { m_Stops[m_SelectedStop].setValue( value ); m_Stops[m_SelectedStop].updateX( width()-1 ); emit gradientModified(); update(); } } else { int belowCursor = -1; for( int i=0; ix() >= m_Stops[i].x()-2 && evt->x() <= m_Stops[i].x()+2 ) { belowCursor = i; break; } if( belowCursor != m_BelowCursor ) { m_BelowCursor = belowCursor; if( belowCursor >= 0 ) { m_BelowCursorRight = belowCursor; while( m_BelowCursorRightkey() ) { case Qt::Key_0: case Qt::Key_1: case Qt::Key_2: case Qt::Key_3: case Qt::Key_4: case Qt::Key_5: case Qt::Key_6: case Qt::Key_7: case Qt::Key_8: case Qt::Key_9: case Qt::Key_Period: { bool ok; QString tmp = m_KeyboardInputValue + (char) evt->key(); if( tmp == "." ) tmp = "0."; float value = tmp.toFloat( &ok ); if( ok ) { if( value >= 2.0f ) { m_KeyboardInputValue = "0." + tmp; update(); } else if( value <= 1.0f ) { m_KeyboardInputValue = tmp; update(); } } break; } case Qt::Key_Backspace: { m_KeyboardInputValue = m_KeyboardInputValue.left( m_KeyboardInputValue.size()-1 ); update(); break; } case Qt::Key_Delete: { if( m_Stops.size() > 1 ) { m_Stops.remove( m_SelectedStop ); m_SelectedStop = -1; m_KeyboardInputValue = QString(); emit gradientModified(); update(); } break; } case Qt::Key_Enter: case Qt::Key_Return: { if( m_KeyboardInputValue.isEmpty() ) break; float value = m_KeyboardInputValue.toFloat(); m_KeyboardInputValue = QString(); if( m_SelectedStop != m_SelectedStopRight ) { if( value > m_Stops[m_SelectedStop].value() ) m_SelectedStop = m_SelectedStopRight; else m_SelectedStopRight = m_SelectedStop; } value = clampValueToStopInterval( m_SelectedStop, value ); if( value != m_Stops[m_SelectedStop].value() ) { m_Stops[m_SelectedStop].setValue( value ); m_Stops[m_SelectedStop].updateX( width()-1 ); emit gradientModified(); } update(); break; } default: QWidget::keyPressEvent( evt ); } } void GradientWidget::leaveEvent( QEvent *evt ) { if( m_SelectedStop >= 0 ) { m_SelectedStop = -1; update(); } } void GradientWidget::getStops( QVector &values, QVector &colors ) const { values.clear(); colors.clear(); if( m_Stops.isEmpty() ) return; values.reserve( m_Stops.size() ); colors.reserve( m_Stops.size() ); for( auto &s : m_Stops ) { values.push_back( s.value() ); colors.push_back( s.color() ); } } QString GradientWidget::ValueString() const { QString str; for( auto &s : m_Stops ) { QTextStream stream( &str ); if( &s != &m_Stops[0] ) stream << ", "; stream << (double) s.value() << ": "; stream << "#"; stream.setFieldWidth(2); stream.setPadChar('0'); stream << QString::number( s.color().red() , 16 ).toUpper(); stream << QString::number( s.color().green(), 16 ).toUpper(); stream << QString::number( s.color().blue() , 16 ).toUpper(); stream << QString::number( s.color().alpha(), 16 ).toUpper(); } return str; } bool GradientWidget::SetValueString( const QString &valueString ) { QStringList stopStrings = valueString.split( ',' ); QList values; QList colors; QRegExp rgbaExp( "#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})" ); for( auto &stop : stopStrings ) { QStringList valueAndColor = stop.remove( ' ' ).split( ':' ); if( valueAndColor.size() != 2 ) return false; bool ok; float value = valueAndColor.front().toFloat( &ok ); if( !ok ) return false; if( rgbaExp.indexIn(valueAndColor.back()) < 0 ) return false; int r = rgbaExp.cap(1).toInt( NULL, 16 ); int g = rgbaExp.cap(2).toInt( NULL, 16 ); int b = rgbaExp.cap(3).toInt( NULL, 16 ); int a = rgbaExp.cap(4).toInt( NULL, 16 ); values.push_back( value ); colors.push_back( QColor(r,g,b,a) ); } setStops( colors, values ); return true; }