/******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Martin Gräßlin Copyright (C) 2008 Torgny Johansson This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "snow.h" #include #include #include #include #include #include #include #include #include #ifdef KWIN_HAVE_OPENGL_COMPOSITING #include #endif namespace KWin { KWIN_EFFECT( snow, SnowEffect ) KWIN_EFFECT_SUPPORTED( snow, SnowEffect::supported() ) SnowEffect::SnowEffect() : texture( NULL ) , active( false) , snowBehindWindows( false ) , mShader( 0 ) , mInited( false ) , mUseShader( true ) , hasSnown( false ) { srandom( std::time( NULL ) ); nextFlakeMillis = 0; KActionCollection* actionCollection = new KActionCollection( this ); KAction* a = static_cast< KAction* >( actionCollection->addAction( "Snow" )); a->setText( i18n("Snow" )); a->setGlobalShortcut( KShortcut( Qt::CTRL + Qt::META + Qt::Key_F12 )); connect( a, SIGNAL( triggered( bool )), this, SLOT( toggle())); reconfigure( ReconfigureAll ); } SnowEffect::~SnowEffect() { delete texture; if( active ) delete mShader; } bool SnowEffect::supported() { return effects->compositingType() == OpenGLCompositing; } void SnowEffect::reconfigure( ReconfigureFlags ) { KConfigGroup conf = effects->effectConfig("Snow"); mNumberFlakes = conf.readEntry("Number", 200); mMinFlakeSize = conf.readEntry("MinFlakes", 10); mMaxFlakeSize = conf.readEntry("MaxFlakes", 50); mMaxVSpeed = conf.readEntry("MaxVSpeed", 2); mMaxHSpeed = conf.readEntry("MaxHSpeed", 1); snowBehindWindows = conf.readEntry("BehindWindows", true); } void SnowEffect::prePaintScreen( ScreenPrePaintData& data, int time ) { if ( active ) { // if number of active snowflakes is smaller than maximum number // create a random new snowflake nextFlakeMillis -= time; if ( ( nextFlakeMillis <= 0 ) && flakes.count() < mNumberFlakes) { int size = 0; while ( size < mMinFlakeSize ) size = random() % mMaxFlakeSize; SnowFlake flake = SnowFlake( random() % (displayWidth() - size), -size, size, size, mMaxVSpeed, mMaxHSpeed ); flakes.append( flake ); // calculation of next time of snowflake // depends on the time the flow needs to get to the bottom (screen size) // and the fps long next = ((500/(time+5))*(Effect::displayHeight()/flake.getVSpeed()))/mNumberFlakes; nextFlakeMillis = next; } data.mask |= PAINT_SCREEN_TRANSFORMED; hasSnown = false; } effects->prePaintScreen( data, time ); } void SnowEffect::paintScreen( int mask, QRegion region, ScreenPaintData& data ) { if( active && snowBehindWindows ) repaintRegion = QRegion( 0, 0, displayWidth(), displayHeight() ); effects->paintScreen( mask, region, data ); // paint normal screen if( active && !snowBehindWindows ) snowing( region ); } void SnowEffect::snowing( QRegion& region ) { if(! texture ) loadTexture(); if( texture ) { glActiveTexture(GL_TEXTURE0); texture->bind(); if( mUseShader && !mInited ) mUseShader = loadShader(); glPushAttrib( GL_CURRENT_BIT | GL_ENABLE_BIT ); glEnable( GL_BLEND ); glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); if( mUseShader ) { mShader->bind(); QRect rect = region.boundingRect(); mShader->setUniform( "left", rect.x() ); mShader->setUniform( "right", rect.x() + rect.width() ); mShader->setUniform( "top", rect.y() ); mShader->setUniform( "bottom", rect.y() + rect.height() ); } for (int i=0; isetUniform( "angle", flake.getRotationSpeed() ); mShader->setUniform( "x", flake.getX() ); mShader->setUniform( "hSpeed", flake.getHSpeed() ); mShader->setUniform( "vSpeed", flake.getVSpeed() ); mShader->setUniform( "frames", flake.getFrames() ); mShader->setUniform( "width", flake.getWidth() ); mShader->setUniform( "height", flake.getHeight() ); glCallList( list ); } else { if( !hasSnown ) { // only update during first paint in one frame flake.updateSpeedAndRotation(); } // no shader // save the matrix glPushMatrix(); // translate to the center of the flake glTranslatef(flake.getWidth()/2 + flake.getX(), flake.getHeight()/2 + flake.getY(), 0); // rotate around the Z-axis glRotatef(flake.getRotationAngle(), 0.0, 0.0, 1.0); // translate back to the starting point glTranslatef(-flake.getWidth()/2 - flake.getX(), -flake.getHeight()/2 - flake.getY(), 0); // paint the snowflake texture->render( region, flake.getRect()); // restore the matrix glPopMatrix(); } } if( mUseShader ) { mShader->unbind(); } glDisable( GL_BLEND ); texture->unbind(); glPopAttrib(); hasSnown = true; } } void SnowEffect::postPaintScreen() { if( active ) { if( snowBehindWindows ) effects->addRepaint( repaintRegion ); else effects->addRepaintFull(); } effects->postPaintScreen(); } void SnowEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) { if( active && snowBehindWindows && !w->isDesktop() && !w->isDock() && !w->hasAlpha() && data.opacity == 1.0 ) { repaintRegion -= QRegion( w->geometry() ); } effects->paintWindow( w, mask, region, data ); if( active && w->isDesktop() && snowBehindWindows ) { QRect rect = effects->clientArea( FullScreenArea, w->screen(), effects->currentDesktop()); QRegion snowRegion( rect.x() + data.xTranslate, rect.y() + data.yTranslate, (double)rect.width()*data.xScale, (double)rect.height()*data.yScale); if( mUseShader ) { // have to adjust the y values to fit OpenGL // in OpenGL y==0 is at bottom, in Qt at top int y = 0; if( effects->numScreens() > 1 ) { QRect fullArea = effects->clientArea( FullArea, 0, 1 ); if( fullArea.height() != rect.height() ) { if( rect.y() == 0 ) y = fullArea.height() - rect.height(); else y = fullArea.height() - rect.y() - rect.height(); } } snowRegion = QRegion( rect.x() + data.xTranslate, y + rect.height() - data.yTranslate - (double)rect.height()*data.yScale, (double)rect.width()*data.xScale, (double)rect.height()*data.yScale); } glPushMatrix(); glTranslatef( rect.x() + data.xTranslate, rect.y() + data.yTranslate, 0.0 ); glScalef( data.xScale, data.yScale, 1.0 ); snowing( snowRegion ); glPopMatrix(); } } void SnowEffect::toggle() { #ifdef KWIN_HAVE_OPENGL_COMPOSITING active = !active; if( active ) { list = glGenLists(1); } else { glDeleteLists( list, 1 ); flakes.clear(); if( mUseShader ) { delete mShader; mInited = false; mUseShader = true; } } effects->addRepaintFull(); #endif } bool SnowEffect::loadShader() { mInited = true; if( !(GLShader::fragmentShaderSupported() && (effects->compositingType() == OpenGLCompositing)) ) { kDebug(1212) << "Shaders not supported - waisting CPU cycles" << endl; return false; } QString fragmentshader = KGlobal::dirs()->findResource("data", "kwin/snow.frag"); QString vertexshader = KGlobal::dirs()->findResource("data", "kwin/snow.vert"); if(fragmentshader.isEmpty() || vertexshader.isEmpty()) { kDebug(1212) << "Couldn't locate shader files" << endl; return false; } mShader = new GLShader(vertexshader, fragmentshader); if(!mShader->isValid()) { kDebug(1212) << "The shader failed to load!" << endl; return false; } else { mShader->bind(); mShader->setUniform( "snowTexture", 0 ); mShader->unbind(); } kDebug(1212) << "using shader"; glNewList( list, GL_COMPILE ); glBegin( GL_QUADS ); glTexCoord2f( 0.0f, 0.0f ); glVertex2i( 0, 0 ); glTexCoord2f( 1.0f, 0.0f ); glVertex2i( 0, 0 ); glTexCoord2f( 1.0f, 1.0f ); glVertex2i( 0, 0 ); glTexCoord2f( 0.0f, 1.0f ); glVertex2i( 0, 0 ); glEnd(); glEndList(); return true; } void SnowEffect::loadTexture() { QString file = KGlobal::dirs()->findResource( "appdata", "snowflake.png" ); if( file.isEmpty()) return; texture = new GLTexture( file ); } // the snowflake class SnowFlake::SnowFlake(int x, int y, int width, int height, int maxVSpeed, int maxHSpeed) { int minVSpeed = maxVSpeed - 8; // 8 gives a nice difference in speed if(minVSpeed < 1) minVSpeed = 1; vSpeed = random()%maxVSpeed + minVSpeed; hSpeed = random()%(maxHSpeed+1); if(random()%2 < 1) hSpeed = -hSpeed; // to create negative hSpeeds at random rotationAngle = 0; rotationSpeed = random()%4 - 2; if(rotationSpeed == 0) rotationSpeed = 0.5; rect = QRect(x, y, width, height); frameCounter = 0; maxFrames = (displayHeight() + 2*height) / vSpeed; if( hSpeed > 0 ) maxFrames = qMin( maxFrames, (displayWidth() + width - x)/hSpeed ); else if( hSpeed < 0 ) maxFrames = qMin( maxFrames, (x + width)/(-hSpeed) ); } SnowFlake::~SnowFlake() { } int SnowFlake::getHSpeed() { return hSpeed; } QRect SnowFlake::getRect() { return rect; } void SnowFlake::updateSpeedAndRotation() { rotationAngle = rotationAngle+rotationSpeed; rect.translate(hSpeed, vSpeed); } float SnowFlake::getRotationAngle() { return rotationAngle; } float SnowFlake::getRotationSpeed() { return rotationSpeed; } int SnowFlake::getVSpeed() { return vSpeed; } int SnowFlake::getHeight() { return rect.height(); } int SnowFlake::getWidth() { return rect.width(); } int SnowFlake::getX() { return rect.x(); } int SnowFlake::getY() { return rect.y(); } void SnowFlake::setHeight(int height) { rect.setHeight(height); } void SnowFlake::setWidth(int width) { rect.setWidth(width); } void SnowFlake::setX(int x) { rect.setX(x); } void SnowFlake::setY(int y) { rect.setY(y); } int SnowFlake::addFrame() { return ( maxFrames - (++frameCounter) ); } int SnowFlake::getFrames() { return frameCounter; } } // namespace