Commit dc2c26c6 authored by Frédéric Larue's avatar Frédéric Larue
Browse files

GradientWidget added to the Widgets project.

parent fb52c6b5
/*
* (c) LSIIT, UMR CNRS/UdS
* Authors: F. Larue.
*
* See licence.txt for additional information.
*/
#include "GradientWidget.h"
#include <cassert>
GradientWidget::GradientWidget( QWidget *parent ) : QWidget(parent)
{
setMinimumSize( 200, 30 );
m_BelowCursor = -1;
m_SelectedStop = -1;
setMouseTracking( true );
setFocusPolicy( Qt::ClickFocus );
}
GradientWidget::GradientWidget( const QColor& start, const QColor& end, QWidget *parent ) : GradientWidget(parent)
{
m_Stops.push_back( Stop(start, 0.0f) );
m_Stops.push_back( Stop(end , 1.0f) );
}
GradientWidget::GradientWidget( const QList<QColor>& colors, const QList<float>& 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().windowText().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<m_Stops.size()-1; ++i )
{
Stop &start = m_Stops[i];
Stop &end = m_Stops[i+1];
QLinearGradient g( start.x(), 0.0f, end.x(), 0.0f );
g.setColorAt( 0.0f, start.color() );
g.setColorAt( 1.0f, end .color() );
painter.setBrush( QBrush(g) );
painter.drawRect( start.x(), 3, end.x()-start.x()+1, h-6 );
}
// Display stop cursors.
for( int i=0; i<m_Stops.size(); ++i )
{
Stop &s = m_Stops[i];
// Vertical bar.
painter.setPen( QPen(QColor(255,255,255),1.0f) );
painter.setOpacity( 0.25f );
painter.drawLine( s.x() , 3, s.x() , h-4 );
painter.setOpacity( 0.22f );
painter.drawLine( s.x()-1, 3, s.x()-1, h-4 );
painter.drawLine( s.x()+1, 3, s.x()+1, h-4 );
painter.setOpacity( 1.0f );
if( i == m_SelectedStop )
painter.setPen( QPen(color,1.0f) );
else
painter.setPen( QPen(color,1.0f,Qt::DotLine) );
painter.drawLine( s.x(), 3, s.x(), h-4 );
painter.setPen( QPen(color,1.0f) );
// Top arrow head.
if( i == m_SelectedStop )
painter.drawLine( s.x()-3, 0, s.x()+3, 0 );
painter.drawLine( s.x()-2, 1, s.x()+2, 1 );
painter.drawLine( s.x()-1, 2, s.x()+1, 2 );
// Bottom arrow head.
if( i == m_SelectedStop )
painter.drawLine( s.x()-3, h-1, s.x()+3, h-1 );
painter.drawLine( s.x()-2, h-2, s.x()+2, h-2 );
painter.drawLine( s.x()-1, h-3, s.x()+1, h-3 );
}
if( m_SelectedStop >= 0 )
{
QString text = m_KeyboardInputValue.isEmpty()? QString::number(m_Stops[m_SelectedStop].value()) : m_KeyboardInputValue;
QFontMetrics fm( font() );
int textW = fm.width( text );
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-1, textY , text );
painter.drawText( textX , textY-1, text );
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 );
update();
}
void GradientWidget::setStops( const QList<QColor>& colors, const QList<float>& values )
{
assert( colors.size() == values.size() );
assert( !colors.empty() );
m_Stops.clear();
auto c = colors.begin();
auto v = values.begin();
while( c != colors.end() )
{
addStop( *c, *v );
++ c;
++ v;
}
}
QString GradientWidget::getShaderFunctionBodyGLSL( int beg, int end, const QString& indent )
{
QString res;
if( end != beg )
{
int p = beg + (end-beg)/2;
res += indent + "if( value <= " + QString::number( m_Stops[p].value() ) + " )\n";
res += getShaderFunctionBodyGLSL( beg, p, indent+" " );
res += indent + "else\n";
res += getShaderFunctionBodyGLSL( 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 == m_Stops.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[beg-1];
const Stop &s1 = m_Stops[beg ];
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(s0.value()) + ")*" + QString::number(1.0f / (s1.value() - s0.value()));
res += " );\n";
}
}
return res;
}
QString GradientWidget::getShaderFunctionGLSL( const QString& funcName )
{
QString func =
"vec3 " + funcName + "( float value )\n"
"{\n"
+ getShaderFunctionBodyGLSL( 0, m_Stops.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() )
{
stop.setColor( color );
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 );
update();
}
}
void GradientWidget::mousePressEvent( QMouseEvent *evt )
{
m_SelectedStop = m_BelowCursor;
update();
}
void GradientWidget::mouseMoveEvent( QMouseEvent *evt )
{
if( (evt->buttons() & Qt::LeftButton) && m_SelectedStop >= 0 )
{
float value = clampValueToStopInterval( m_SelectedStop, float(evt->x()) / (width()-1) );
m_Stops[m_SelectedStop].setValue( value );
m_Stops[m_SelectedStop].updateX( width()-1 );
update();
}
else
{
int belowCursor = -1;
for( int i=0; i<m_Stops.size(); ++i )
if( evt->x() >= m_Stops[i].x()-2 && evt->x() <= m_Stops[i].x()+2 )
{
belowCursor = i;
break;
}
if( belowCursor != m_BelowCursor )
{
m_BelowCursor = belowCursor;
setCursor( belowCursor<0? Qt::ArrowCursor : Qt::PointingHandCursor );
}
}
}
void GradientWidget::keyPressEvent( QKeyEvent *evt )
{
if( m_SelectedStop < 0 )
{
m_KeyboardInputValue = QString();
QWidget::keyPressEvent( evt );
return;
}
switch( evt->key() )
{
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.";
tmp.toFloat( &ok );
if( ok )
{
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();
update();
}
break;
}
case Qt::Key_Enter:
case Qt::Key_Return:
{
if( m_KeyboardInputValue.isEmpty() )
break;
float value = m_KeyboardInputValue.toFloat();
m_KeyboardInputValue = QString();
m_Stops[m_SelectedStop].setValue( clampValueToStopInterval( m_SelectedStop, value ) );
m_Stops[m_SelectedStop].updateX( width()-1 );
update();
break;
}
default: QWidget::keyPressEvent( evt );
}
}
void GradientWidget::leaveEvent( QEvent *evt )
{
if( m_SelectedStop >= 0 )
{
m_SelectedStop = -1;
update();
}
}
/*
* (c) LSIIT, UMR CNRS/UdS
* Authors: F. Larue.
*
* See licence.txt for additional information.
*/
#ifndef __GRADIENTWIDGET_H__
#define __GRADIENTWIDGET_H__
#include <QtWidgets>
class GradientWidget : public QWidget
{
Q_OBJECT
public:
class Stop
{
QColor m_Color;
float m_Value;
int m_X;
public:
inline Stop() {}
inline Stop( const QColor& color, float value ) : m_Color(color), m_Value(value) {}
inline const QColor& color() const { return m_Color; }
inline void setColor( const QColor& color ) { m_Color = color; }
inline float value() const { return m_Value; }
inline void setValue( float value ) { m_Value = value; }
inline int x() const { return m_X; }
inline void updateX( int range ) { m_X = m_Value * range; }
};
private:
QVector< Stop > m_Stops;
int m_SelectedStop;
int m_BelowCursor;
QString m_KeyboardInputValue;
int getInterval( float value ) const;
QColor getColor( int interval, float value ) const;
float clampValueToStopInterval( int n, float value ) const;
QString getShaderFunctionBodyGLSL( int beg, int end, const QString& indent );
protected:
void resizeEvent( QResizeEvent *evt );
void paintEvent( QPaintEvent *evt );
void mouseDoubleClickEvent( QMouseEvent *evt );
void mousePressEvent( QMouseEvent *evt );
void mouseMoveEvent( QMouseEvent *evt );
void keyPressEvent( QKeyEvent *evt );
void leaveEvent( QEvent *evt );
public:
GradientWidget( QWidget *parent = NULL );
GradientWidget( const QColor& start, const QColor& end, QWidget *parent = NULL );
GradientWidget( const QList<QColor>& colors, const QList<float>& values, QWidget *parent = NULL );
inline bool isValid() const { return !m_Stops.isEmpty(); }
void removeAllStops() { m_Stops.clear(); update(); }
void addStop( const QColor& color, float value );
void setStops( const QList<QColor>& colors, const QList<float>& values );
inline QColor getColor( float v ) const { return getColor( getInterval(v), v ); }
QString getShaderFunctionGLSL( const QString& funcName );
};
#endif // __GRADIENTWIDGET_H__
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment