/******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2009 Lucas Murray 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 "kwineffects.h" #include "kwinglutils.h" #include "kwinxrenderutils.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef KWIN_HAVE_XRENDER_COMPOSITING #include #include #endif namespace KWin { void WindowPrePaintData::setTranslucent() { mask |= Effect::PAINT_WINDOW_TRANSLUCENT; mask &= ~Effect::PAINT_WINDOW_OPAQUE; clip = QRegion(); // cannot clip, will be transparent } void WindowPrePaintData::setTransformed() { mask |= Effect::PAINT_WINDOW_TRANSFORMED; } WindowPaintData::WindowPaintData(EffectWindow* w) : opacity(w->opacity()) , contents_opacity(1.0) , decoration_opacity(1.0) , xScale(1) , yScale(1) , zScale(1) , xTranslate(0) , yTranslate(0) , zTranslate(0) , saturation(1) , brightness(1) , shader(NULL) , rotation(NULL) { quads = w->buildQuads(); } ScreenPaintData::ScreenPaintData() : xScale(1) , yScale(1) , zScale(1) , xTranslate(0) , yTranslate(0) , zTranslate(0) , rotation(NULL) { } RotationData::RotationData() : axis(ZAxis) , angle(0.0) , xRotationPoint(0.0) , yRotationPoint(0.0) , zRotationPoint(0.0) { } //**************************************** // Effect //**************************************** Effect::Effect() { } Effect::~Effect() { } void Effect::reconfigure(ReconfigureFlags) { } void* Effect::proxy() { return NULL; } void Effect::windowUserMovedResized(EffectWindow* , bool, bool) { } void Effect::windowMoveResizeGeometryUpdate(EffectWindow* , const QRect&) { } void Effect::windowOpacityChanged(EffectWindow*, double) { } void Effect::windowAdded(EffectWindow*) { } void Effect::windowClosed(EffectWindow*) { } void Effect::windowDeleted(EffectWindow*) { } void Effect::windowActivated(EffectWindow*) { } void Effect::windowMinimized(EffectWindow*) { } void Effect::windowUnminimized(EffectWindow*) { } void Effect::clientGroupItemSwitched(EffectWindow*, EffectWindow*) { } void Effect::clientGroupItemAdded(EffectWindow*, EffectWindow*) { } void Effect::clientGroupItemRemoved(EffectWindow*, EffectWindow*) { } void Effect::windowInputMouseEvent(Window, QEvent*) { } void Effect::grabbedKeyboardEvent(QKeyEvent*) { } void Effect::propertyNotify(EffectWindow* , long) { } void Effect::desktopChanged(int) { } void Effect::windowDamaged(EffectWindow*, const QRect&) { } void Effect::windowGeometryShapeChanged(EffectWindow*, const QRect&) { } void Effect::tabBoxAdded(int) { } void Effect::tabBoxClosed() { } void Effect::tabBoxUpdated() { } void Effect::tabBoxKeyEvent(QKeyEvent*) { } bool Effect::borderActivated(ElectricBorder) { return false; } void Effect::mouseChanged(const QPoint&, const QPoint&, Qt::MouseButtons, Qt::MouseButtons, Qt::KeyboardModifiers, Qt::KeyboardModifiers) { } void Effect::prePaintScreen(ScreenPrePaintData& data, int time) { effects->prePaintScreen(data, time); } void Effect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { effects->paintScreen(mask, region, data); } void Effect::postPaintScreen() { effects->postPaintScreen(); } void Effect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { effects->prePaintWindow(w, data, time); } void Effect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { effects->paintWindow(w, mask, region, data); } void Effect::postPaintWindow(EffectWindow* w) { effects->postPaintWindow(w); } void Effect::paintEffectFrame(KWin::EffectFrame* frame, QRegion region, double opacity, double frameOpacity) { effects->paintEffectFrame(frame, region, opacity, frameOpacity); } bool Effect::provides(Feature) { return false; } void Effect::drawWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { effects->drawWindow(w, mask, region, data); } void Effect::buildQuads(EffectWindow* w, WindowQuadList& quadList) { effects->buildQuads(w, quadList); } QRect Effect::transformWindowDamage(EffectWindow* w, const QRect& r) { return effects->transformWindowDamage(w, r); } void Effect::setPositionTransformations(WindowPaintData& data, QRect& region, EffectWindow* w, const QRect& r, Qt::AspectRatioMode aspect) { QSize size = w->size(); size.scale(r.size(), aspect); data.xScale = size.width() / double(w->width()); data.yScale = size.height() / double(w->height()); int width = int(w->width() * data.xScale); int height = int(w->height() * data.yScale); int x = r.x() + (r.width() - width) / 2; int y = r.y() + (r.height() - height) / 2; region = QRect(x, y, width, height); data.xTranslate = x - w->x(); data.yTranslate = y - w->y(); } int Effect::displayWidth() { return KWin::displayWidth(); } int Effect::displayHeight() { return KWin::displayHeight(); } QPoint Effect::cursorPos() { return effects->cursorPos(); } double Effect::animationTime(const KConfigGroup& cfg, const QString& key, int defaultTime) { int time = cfg.readEntry(key, 0); return time != 0 ? time : qMax(defaultTime * effects->animationTimeFactor(), 1.); } double Effect::animationTime(int defaultTime) { // at least 1ms, otherwise 0ms times can break some things return qMax(defaultTime * effects->animationTimeFactor(), 1.); } void Effect::numberDesktopsChanged(int) { } //**************************************** // EffectsHandler //**************************************** EffectsHandler::EffectsHandler(CompositingType type) : current_paint_screen(0) , current_paint_window(0) , current_draw_window(0) , current_build_quads(0) , current_transform(0) , compositing_type(type) { if (compositing_type == NoCompositing) return; KWin::effects = this; } EffectsHandler::~EffectsHandler() { // All effects should already be unloaded by Impl dtor assert(loaded_effects.count() == 0); } QRect EffectsHandler::transformWindowDamage(EffectWindow* w, const QRect& r) { if (current_transform < loaded_effects.size()) { QRect rr = loaded_effects[current_transform++].second->transformWindowDamage(w, r); --current_transform; return rr; } else return r; } Window EffectsHandler::createInputWindow(Effect* e, const QRect& r, const QCursor& cursor) { return createInputWindow(e, r.x(), r.y(), r.width(), r.height(), cursor); } Window EffectsHandler::createFullScreenInputWindow(Effect* e, const QCursor& cursor) { return createInputWindow(e, 0, 0, displayWidth(), displayHeight(), cursor); } CompositingType EffectsHandler::compositingType() const { return compositing_type; } bool EffectsHandler::saturationSupported() const { switch(compositing_type) { #ifdef KWIN_HAVE_OPENGL_COMPOSITING case OpenGLCompositing: return GLTexture::saturationSupported(); #endif case XRenderCompositing: return false; // never default: abort(); } } void EffectsHandler::sendReloadMessage(const QString& effectname) { QDBusMessage message = QDBusMessage::createMethodCall("org.kde.kwin", "/KWin", "org.kde.KWin", "reconfigureEffect"); message << QString("kwin4_effect_" + effectname); QDBusConnection::sessionBus().send(message); } KConfigGroup EffectsHandler::effectConfig(const QString& effectname) { KSharedConfig::Ptr kwinconfig = KSharedConfig::openConfig("kwinrc", KConfig::NoGlobals); return kwinconfig->group("Effect-" + effectname); } bool EffectsHandler::checkDriverBlacklist(const KConfigGroup& blacklist) { #ifdef KWIN_HAVE_OPENGL_COMPOSITING if (effects->compositingType() == OpenGLCompositing) { QString vendor = QString((const char*)glGetString(GL_VENDOR)); QString renderer = QString((const char*)glGetString(GL_RENDERER)); QString version = QString((const char*)glGetString(GL_VERSION)); foreach (const QString & key, blacklist.keyList()) { // the key is a word in the renderer string or vendor referrencing the vendor in case of mesa // e.g. "Intel" or "Ati" if (renderer.contains(key, Qt::CaseInsensitive) || vendor.contains(key, Qt::CaseInsensitive)) { // the value for current key contains a string list of driver versions which have to be blacklisted QStringList versions = blacklist.readEntry< QStringList >(key, QStringList()); foreach (const QString & entry, versions) { QStringList parts = entry.split(":-:"); if (parts.size() != 2) { continue; } if (renderer.contains(parts[0], Qt::CaseInsensitive) && version.contains(parts[1], Qt::CaseInsensitive)) { // the version matches the renderer string - this driver is blacklisted, return return true; } } } } } return false; #else return false; #endif } EffectsHandler* effects = 0; //**************************************** // EffectWindow //**************************************** EffectWindow::EffectWindow() { } EffectWindow::~EffectWindow() { } bool EffectWindow::isOnCurrentDesktop() const { return isOnDesktop(effects->currentDesktop()); } bool EffectWindow::isOnDesktop(int d) const { return desktop() == d || isOnAllDesktops(); } bool EffectWindow::hasDecoration() const { return contentsRect() != QRect(0, 0, width(), height()); } //**************************************** // EffectWindowGroup //**************************************** EffectWindowGroup::~EffectWindowGroup() { } //**************************************** // GlobalShortcutsEditor //**************************************** GlobalShortcutsEditor::GlobalShortcutsEditor(QWidget *parent) : KShortcutsEditor(parent, GlobalAction) { } /*************************************************************** WindowQuad ***************************************************************/ WindowQuad WindowQuad::makeSubQuad(double x1, double y1, double x2, double y2) const { assert(x1 < x2 && y1 < y2 && x1 >= left() && x2 <= right() && y1 >= top() && y2 <= bottom()); #ifndef NDEBUG if (isTransformed()) kFatal(1212) << "Splitting quads is allowed only in pre-paint calls!" ; #endif WindowQuad ret(*this); // vertices are clockwise starting from topleft ret.verts[ 0 ].px = x1; ret.verts[ 3 ].px = x1; ret.verts[ 1 ].px = x2; ret.verts[ 2 ].px = x2; ret.verts[ 0 ].py = y1; ret.verts[ 1 ].py = y1; ret.verts[ 2 ].py = y2; ret.verts[ 3 ].py = y2; // original x/y are supposed to be the same, no transforming is done here ret.verts[ 0 ].ox = x1; ret.verts[ 3 ].ox = x1; ret.verts[ 1 ].ox = x2; ret.verts[ 2 ].ox = x2; ret.verts[ 0 ].oy = y1; ret.verts[ 1 ].oy = y1; ret.verts[ 2 ].oy = y2; ret.verts[ 3 ].oy = y2; double my_tleft = verts[ 0 ].tx; double my_tright = verts[ 2 ].tx; double my_ttop = verts[ 0 ].ty; double my_tbottom = verts[ 2 ].ty; double tleft = (x1 - left()) / (right() - left()) * (my_tright - my_tleft) + my_tleft; double tright = (x2 - left()) / (right() - left()) * (my_tright - my_tleft) + my_tleft; double ttop = (y1 - top()) / (bottom() - top()) * (my_tbottom - my_ttop) + my_ttop; double tbottom = (y2 - top()) / (bottom() - top()) * (my_tbottom - my_ttop) + my_ttop; ret.verts[ 0 ].tx = tleft; ret.verts[ 3 ].tx = tleft; ret.verts[ 1 ].tx = tright; ret.verts[ 2 ].tx = tright; ret.verts[ 0 ].ty = ttop; ret.verts[ 1 ].ty = ttop; ret.verts[ 2 ].ty = tbottom; ret.verts[ 3 ].ty = tbottom; return ret; } bool WindowQuad::smoothNeeded() const { // smoothing is needed if the width or height of the quad does not match the original size double width = verts[ 1 ].ox - verts[ 0 ].ox; double height = verts[ 2 ].oy - verts[ 1 ].oy; return(verts[ 1 ].px - verts[ 0 ].px != width || verts[ 2 ].px - verts[ 3 ].px != width || verts[ 2 ].py - verts[ 1 ].py != height || verts[ 3 ].py - verts[ 0 ].py != height); } /*************************************************************** WindowQuadList ***************************************************************/ WindowQuadList WindowQuadList::splitAtX(double x) const { WindowQuadList ret; foreach (const WindowQuad & quad, *this) { #ifndef NDEBUG if (quad.isTransformed()) kFatal(1212) << "Splitting quads is allowed only in pre-paint calls!" ; #endif bool wholeleft = true; bool wholeright = true; for (int i = 0; i < 4; ++i) { if (quad[ i ].x() < x) wholeright = false; if (quad[ i ].x() > x) wholeleft = false; } if (wholeleft || wholeright) { // is whole in one split part ret.append(quad); continue; } if (quad.left() == quad.right()) { // quad has no size ret.append(quad); continue; } ret.append(quad.makeSubQuad(quad.left(), quad.top(), x, quad.bottom())); ret.append(quad.makeSubQuad(x, quad.top(), quad.right(), quad.bottom())); } return ret; } WindowQuadList WindowQuadList::splitAtY(double y) const { WindowQuadList ret; foreach (const WindowQuad & quad, *this) { #ifndef NDEBUG if (quad.isTransformed()) kFatal(1212) << "Splitting quads is allowed only in pre-paint calls!" ; #endif bool wholetop = true; bool wholebottom = true; for (int i = 0; i < 4; ++i) { if (quad[ i ].y() < y) wholebottom = false; if (quad[ i ].y() > y) wholetop = false; } if (wholetop || wholebottom) { // is whole in one split part ret.append(quad); continue; } if (quad.top() == quad.bottom()) { // quad has no size ret.append(quad); continue; } ret.append(quad.makeSubQuad(quad.left(), quad.top(), quad.right(), y)); ret.append(quad.makeSubQuad(quad.left(), y, quad.right(), quad.bottom())); } return ret; } WindowQuadList WindowQuadList::makeGrid(int maxquadsize) const { if (empty()) return *this; // find the bounding rectangle double left = first().left(); double right = first().right(); double top = first().top(); double bottom = first().bottom(); foreach (const WindowQuad & quad, *this) { #ifndef NDEBUG if (quad.isTransformed()) kFatal(1212) << "Splitting quads is allowed only in pre-paint calls!" ; #endif left = qMin(left, quad.left()); right = qMax(right, quad.right()); top = qMin(top, quad.top()); bottom = qMax(bottom, quad.bottom()); } WindowQuadList ret; for (double x = left; x < right; x += maxquadsize) { for (double y = top; y < bottom; y += maxquadsize) { foreach (const WindowQuad & quad, *this) { if (QRectF(QPointF(quad.left(), quad.top()), QPointF(quad.right(), quad.bottom())) .intersects(QRectF(x, y, maxquadsize, maxquadsize))) { ret.append(quad.makeSubQuad(qMax(x, quad.left()), qMax(y, quad.top()), qMin(quad.right(), x + maxquadsize), qMin(quad.bottom(), y + maxquadsize))); } } } } return ret; } WindowQuadList WindowQuadList::makeRegularGrid(int xSubdivisions, int ySubdivisions) const { if (empty()) return *this; // find the bounding rectangle double left = first().left(); double right = first().right(); double top = first().top(); double bottom = first().bottom(); foreach (const WindowQuad & quad, *this) { #ifndef NDEBUG if (quad.isTransformed()) kFatal(1212) << "Splitting quads is allowed only in pre-paint calls!" ; #endif left = qMin(left, quad.left()); right = qMax(right, quad.right()); top = qMin(top, quad.top()); bottom = qMax(bottom, quad.bottom()); } double xincrement = (right - left) / xSubdivisions; double yincrement = (bottom - top) / ySubdivisions; WindowQuadList ret; for (double y = top; y < bottom; y += yincrement) { for (double x = left; x < right; x += xincrement) { foreach (const WindowQuad & quad, *this) { if (QRectF(QPointF(quad.left(), quad.top()), QPointF(quad.right(), quad.bottom())) .intersects(QRectF(x, y, xincrement, yincrement))) { ret.append(quad.makeSubQuad(qMax(x, quad.left()), qMax(y, quad.top()), qMin(quad.right(), x + xincrement), qMin(quad.bottom(), y + yincrement))); } } } } return ret; } void WindowQuadList::makeArrays(float** vertices, float** texcoords) const { *vertices = new float[ count() * 6 * 2 ]; *texcoords = new float[ count() * 6 * 2 ]; float* vpos = *vertices; float* tpos = *texcoords; for (int i = 0; i < count(); ++i) { *vpos++ = at(i)[ 1 ].x(); *vpos++ = at(i)[ 1 ].y(); *vpos++ = at(i)[ 0 ].x(); *vpos++ = at(i)[ 0 ].y(); *vpos++ = at(i)[ 3 ].x(); *vpos++ = at(i)[ 3 ].y(); *vpos++ = at(i)[ 3 ].x(); *vpos++ = at(i)[ 3 ].y(); *vpos++ = at(i)[ 2 ].x(); *vpos++ = at(i)[ 2 ].y(); *vpos++ = at(i)[ 1 ].x(); *vpos++ = at(i)[ 1 ].y(); *tpos++ = at(i)[ 1 ].tx; *tpos++ = at(i)[ 1 ].ty; *tpos++ = at(i)[ 0 ].tx; *tpos++ = at(i)[ 0 ].ty; *tpos++ = at(i)[ 3 ].tx; *tpos++ = at(i)[ 3 ].ty; *tpos++ = at(i)[ 3 ].tx; *tpos++ = at(i)[ 3 ].ty; *tpos++ = at(i)[ 2 ].tx; *tpos++ = at(i)[ 2 ].ty; *tpos++ = at(i)[ 1 ].tx; *tpos++ = at(i)[ 1 ].ty; } } WindowQuadList WindowQuadList::select(WindowQuadType type) const { foreach (const WindowQuad & q, *this) { if (q.type() != type) { // something else than ones to select, make a copy and filter WindowQuadList ret; foreach (const WindowQuad & q, *this) { if (q.type() == type) ret.append(q); } return ret; } } return *this; // nothing to filter out } WindowQuadList WindowQuadList::filterOut(WindowQuadType type) const { foreach (const WindowQuad & q, *this) { if (q.type() == type) { // something to filter out, make a copy and filter WindowQuadList ret; foreach (const WindowQuad & q, *this) { if (q.type() != type) ret.append(q); } return ret; } } return *this; // nothing to filter out } bool WindowQuadList::smoothNeeded() const { foreach (const WindowQuad & q, *this) if (q.smoothNeeded()) return true; return false; } bool WindowQuadList::isTransformed() const { foreach (const WindowQuad & q, *this) if (q.isTransformed()) return true; return false; } /*************************************************************** PaintClipper ***************************************************************/ QStack< QRegion >* PaintClipper::areas = NULL; PaintClipper::PaintClipper(const QRegion& allowed_area) : area(allowed_area) { push(area); } PaintClipper::~PaintClipper() { pop(area); } void PaintClipper::push(const QRegion& allowed_area) { if (allowed_area == infiniteRegion()) // don't push these return; if (areas == NULL) areas = new QStack< QRegion >; areas->push(allowed_area); } void PaintClipper::pop(const QRegion& allowed_area) { if (allowed_area == infiniteRegion()) return; Q_ASSERT(areas != NULL); Q_ASSERT(areas->top() == allowed_area); areas->pop(); if (areas->isEmpty()) { delete areas; areas = NULL; } } bool PaintClipper::clip() { return areas != NULL; } QRegion PaintClipper::paintArea() { assert(areas != NULL); // can be called only with clip() == true QRegion ret = QRegion(0, 0, displayWidth(), displayHeight()); foreach (const QRegion & r, *areas) ret &= r; return ret; } struct PaintClipper::Iterator::Data { Data() : index(0) {} int index; #ifdef KWIN_HAVE_OPENGL_COMPOSITING QVector< QRect > rects; #endif }; PaintClipper::Iterator::Iterator() : data(new Data) { #ifdef KWIN_HAVE_OPENGL_COMPOSITING if (clip() && effects->compositingType() == OpenGLCompositing) { #ifndef KWIN_HAVE_OPENGLES glPushAttrib(GL_SCISSOR_BIT); #endif if (!effects->isRenderTargetBound()) glEnable(GL_SCISSOR_TEST); data->rects = paintArea().rects(); data->index = -1; next(); // move to the first one } #endif #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (clip() && effects->compositingType() == XRenderCompositing) { XserverRegion region = toXserverRegion(paintArea()); XFixesSetPictureClipRegion(display(), effects->xrenderBufferPicture(), 0, 0, region); XFixesDestroyRegion(display(), region); // it's ref-counted } #endif } PaintClipper::Iterator::~Iterator() { #ifdef KWIN_HAVE_OPENGL_COMPOSITING if (clip() && effects->compositingType() == OpenGLCompositing) { if (!effects->isRenderTargetBound()) glDisable(GL_SCISSOR_TEST); #ifndef KWIN_HAVE_OPENGLES glPopAttrib(); #endif } #endif #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (clip() && effects->compositingType() == XRenderCompositing) XFixesSetPictureClipRegion(display(), effects->xrenderBufferPicture(), 0, 0, None); #endif delete data; } bool PaintClipper::Iterator::isDone() { if (!clip()) return data->index == 1; // run once #ifdef KWIN_HAVE_OPENGL_COMPOSITING if (effects->compositingType() == OpenGLCompositing) return data->index >= data->rects.count(); // run once per each area #endif #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (effects->compositingType() == XRenderCompositing) return data->index == 1; // run once #endif abort(); } void PaintClipper::Iterator::next() { data->index++; #ifdef KWIN_HAVE_OPENGL_COMPOSITING if (clip() && effects->compositingType() == OpenGLCompositing && !effects->isRenderTargetBound() && data->index < data->rects.count()) { const QRect& r = data->rects[ data->index ]; // Scissor rect has to be given in OpenGL coords glScissor(r.x(), displayHeight() - r.y() - r.height(), r.width(), r.height()); } #endif } QRect PaintClipper::Iterator::boundingRect() const { if (!clip()) return infiniteRegion(); #ifdef KWIN_HAVE_OPENGL_COMPOSITING if (effects->compositingType() == OpenGLCompositing) return data->rects[ data->index ]; #endif #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (effects->compositingType() == XRenderCompositing) return paintArea().boundingRect(); #endif abort(); return infiniteRegion(); } /*************************************************************** TimeLine ***************************************************************/ TimeLine::TimeLine(const int duration) { m_Time = 0; m_Duration = duration; m_TimeLine = new QTimeLine(m_Duration ? m_Duration : 1); // (avoid QTimeLine warning) m_TimeLine->setFrameRange(0, m_Duration); setCurveShape(EaseInCurve); } TimeLine::TimeLine(const TimeLine &other) { m_Time = other.m_Time; m_Duration = other.m_Duration; m_TimeLine = new QTimeLine(m_Duration ? m_Duration : 1); m_TimeLine->setFrameRange(0, m_Duration); setCurveShape(other.m_CurveShape); if (m_Duration != 0) setProgress(m_Progress); } TimeLine::~TimeLine() { delete m_TimeLine; } int TimeLine::duration() const { return m_Duration; } void TimeLine::setDuration(const int msec) { m_Duration = msec; m_TimeLine->setDuration(m_Duration); m_TimeLine->setFrameRange(0, m_Duration); } double TimeLine::value() const { Q_ASSERT(m_Duration != 0); return valueForTime(m_Time); } double TimeLine::valueForTime(const int msec) const { Q_ASSERT(m_Duration != 0); // Catch non QTimeLine CurveShapes here, (but there are none right now) // else use QTimeLine ... return m_TimeLine->valueForTime(msec); } void TimeLine::addTime(const int msec) { Q_ASSERT(m_Duration != 0); m_Time = qMin(m_Duration, m_Time + msec); m_Progress = (double)m_Time / m_Duration; } void TimeLine::removeTime(const int msec) { Q_ASSERT(m_Duration != 0); m_Time = qMax(0, m_Time - msec); m_Progress = (double)m_Time / m_Duration; } void TimeLine::setProgress(const double progress) { Q_ASSERT(m_Duration != 0); m_Progress = progress; m_Time = qRound(m_Duration * progress); } double TimeLine::progress() const { Q_ASSERT(m_Duration != 0); return m_Progress; } int TimeLine::time() const { Q_ASSERT(m_Duration != 0); return m_Time; } void TimeLine::addProgress(const double progress) { Q_ASSERT(m_Duration != 0); m_Progress += progress; m_Time = (int)(m_Duration * m_Progress); } void TimeLine::setCurveShape(CurveShape curveShape) { switch(curveShape) { case EaseInCurve: m_TimeLine->setCurveShape(QTimeLine::EaseInCurve); break; case EaseOutCurve: m_TimeLine->setCurveShape(QTimeLine::EaseOutCurve); break; case EaseInOutCurve: m_TimeLine->setCurveShape(QTimeLine::EaseInOutCurve); break; case LinearCurve: m_TimeLine->setCurveShape(QTimeLine::LinearCurve); break; case SineCurve: m_TimeLine->setCurveShape(QTimeLine::SineCurve); break; } m_CurveShape = curveShape; } /*************************************************************** Motion1D ***************************************************************/ Motion1D::Motion1D(double initial, double strength, double smoothness) : Motion(initial, strength, smoothness) { } Motion1D::Motion1D(const Motion1D &other) : Motion(other) { } Motion1D::~Motion1D() { } /*************************************************************** Motion2D ***************************************************************/ Motion2D::Motion2D(QPointF initial, double strength, double smoothness) : Motion(initial, strength, smoothness) { } Motion2D::Motion2D(const Motion2D &other) : Motion(other) { } Motion2D::~Motion2D() { } /*************************************************************** WindowMotionManager ***************************************************************/ WindowMotionManager::WindowMotionManager(bool useGlobalAnimationModifier) : m_useGlobalAnimationModifier(useGlobalAnimationModifier) { // TODO: Allow developer to modify motion attributes } // TODO: What happens when the window moves by an external force? WindowMotionManager::~WindowMotionManager() { } void WindowMotionManager::manage(EffectWindow *w) { if (m_managedWindows.contains(w)) return; double strength = 0.08; double smoothness = 4.0; if (m_useGlobalAnimationModifier && effects->animationTimeFactor()) { // If the factor is == 0 then we just skip the calculation completely strength = 0.08 / effects->animationTimeFactor(); smoothness = effects->animationTimeFactor() * 4.0; } m_managedWindows[ w ] = WindowMotion(); m_managedWindows[ w ].translation.setStrength(strength); m_managedWindows[ w ].translation.setSmoothness(smoothness); m_managedWindows[ w ].scale.setStrength(strength * 1.33); m_managedWindows[ w ].scale.setSmoothness(smoothness / 2.0); m_managedWindows[ w ].translation.setValue(w->pos()); m_managedWindows[ w ].scale.setValue(QPointF(1.0, 1.0)); } void WindowMotionManager::unmanage(EffectWindow *w) { if (!m_managedWindows.contains(w)) return; QPointF diffT = m_managedWindows[ w ].translation.distance(); QPointF diffS = m_managedWindows[ w ].scale.distance(); m_movingWindowsSet.remove(w); m_managedWindows.remove(w); } void WindowMotionManager::unmanageAll() { m_managedWindows.clear(); m_movingWindowsSet.clear(); } void WindowMotionManager::calculate(int time) { if (!effects->animationTimeFactor()) { // Just skip it completely if the user wants no animation m_movingWindowsSet.clear(); QHash::iterator it = m_managedWindows.begin(); for (; it != m_managedWindows.end(); it++) { WindowMotion *motion = &it.value(); motion->translation.finish(); motion->scale.finish(); } } QHash::iterator it = m_managedWindows.begin(); for (; it != m_managedWindows.end(); it++) { WindowMotion *motion = &it.value(); bool stopped = false; // TODO: What happens when distance() == 0 but we are still moving fast? // TODO: Motion needs to be calculated from the window's center QPointF diffT = motion->translation.distance(); if (diffT != QPoint(0.0, 0.0)) { // Still moving motion->translation.calculate(time); diffT = motion->translation.distance(); if (qAbs(diffT.x()) < 0.5 && qAbs(motion->translation.velocity().x()) < 0.2 && qAbs(diffT.y()) < 0.5 && qAbs(motion->translation.velocity().y()) < 0.2) { // Hide tiny oscillations motion->translation.finish(); diffT = QPoint(0.0, 0.0); stopped = true; } } QPointF diffS = motion->scale.distance(); if (diffS != QPoint(0.0, 0.0)) { // Still scaling motion->scale.calculate(time); diffS = motion->scale.distance(); if (qAbs(diffS.x()) < 0.001 && qAbs(motion->scale.velocity().x()) < 0.05 && qAbs(diffS.y()) < 0.001 && qAbs(motion->scale.velocity().y()) < 0.05) { // Hide tiny oscillations motion->scale.finish(); diffS = QPoint(0.0, 0.0); stopped = true; } } // We just finished this window's motion if (stopped && diffT == QPoint(0.0, 0.0) && diffS == QPoint(0.0, 0.0)) m_movingWindowsSet.remove(it.key()); } } void WindowMotionManager::reset() { if (!m_managedWindows.count()) return; EffectWindowList windows = m_managedWindows.keys(); for (int i = 0; i < windows.size(); i++) { EffectWindow *w = windows.at(i); m_managedWindows[ w ].translation.setTarget(w->pos()); m_managedWindows[ w ].translation.finish(); m_managedWindows[ w ].scale.setTarget(QPointF(1.0, 1.0)); m_managedWindows[ w ].scale.finish(); } } void WindowMotionManager::reset(EffectWindow *w) { if (!m_managedWindows.contains(w)) return; m_managedWindows[ w ].translation.setTarget(w->pos()); m_managedWindows[ w ].translation.finish(); m_managedWindows[ w ].scale.setTarget(QPointF(1.0, 1.0)); m_managedWindows[ w ].scale.finish(); } void WindowMotionManager::apply(EffectWindow *w, WindowPaintData &data) { if (!m_managedWindows.contains(w)) return; // TODO: Take into account existing scale so that we can work with multiple managers (E.g. Present windows + grid) data.xTranslate += m_managedWindows[ w ].translation.value().x() - w->x(); data.yTranslate += m_managedWindows[ w ].translation.value().y() - w->y(); data.xScale *= m_managedWindows[ w ].scale.value().x(); data.yScale *= m_managedWindows[ w ].scale.value().y(); } void WindowMotionManager::moveWindow(EffectWindow *w, QPoint target, double scale, double yScale) { if (!m_managedWindows.contains(w)) abort(); // Notify the effect author that they did something wrong if (yScale == 0.0) yScale = scale; QPointF scalePoint(scale, yScale); if (m_managedWindows[ w ].translation.value() == target && m_managedWindows[ w ].scale.value() == scalePoint) return; // Window already at that position m_managedWindows[ w ].translation.setTarget(target); m_managedWindows[ w ].scale.setTarget(scalePoint); m_movingWindowsSet << w; } QRectF WindowMotionManager::transformedGeometry(EffectWindow *w) const { QRectF geometry(w->geometry()); // TODO: Take into account existing scale so that we can work with multiple managers (E.g. Present windows + grid) geometry.moveTo(m_managedWindows[ w ].translation.value()); geometry.setWidth(geometry.width() * m_managedWindows[ w ].scale.value().x()); geometry.setHeight(geometry.height() * m_managedWindows[ w ].scale.value().y()); return geometry; } void WindowMotionManager::setTransformedGeometry(EffectWindow *w, const QRectF &geometry) { m_managedWindows[ w ].translation.setValue(geometry.topLeft()); m_managedWindows[ w ].scale.setValue(QPointF(geometry.width() / qreal(w->width()), geometry.height() / qreal(w->height()))); } QRectF WindowMotionManager::targetGeometry(EffectWindow *w) const { QRectF geometry(w->geometry()); // TODO: Take into account existing scale so that we can work with multiple managers (E.g. Present windows + grid) geometry.moveTo(m_managedWindows[ w ].translation.target()); geometry.setWidth(geometry.width() * m_managedWindows[ w ].scale.target().x()); geometry.setHeight(geometry.height() * m_managedWindows[ w ].scale.target().y()); return geometry; } EffectWindow* WindowMotionManager::windowAtPoint(QPoint point, bool useStackingOrder) const { Q_UNUSED(useStackingOrder); // TODO: Stacking order uses EffectsHandler::stackingOrder() then filters by m_managedWindows QHash< EffectWindow*, WindowMotion >::ConstIterator it = m_managedWindows.constBegin(); while (it != m_managedWindows.constEnd()) { if (transformedGeometry(it.key()).contains(point)) return it.key(); ++it; } return NULL; } /*************************************************************** EffectFramePrivate ***************************************************************/ class EffectFramePrivate { public: EffectFramePrivate(); ~EffectFramePrivate(); bool crossFading; qreal crossFadeProgress; }; EffectFramePrivate::EffectFramePrivate() : crossFading(false) , crossFadeProgress(1.0) { } EffectFramePrivate::~EffectFramePrivate() { } /*************************************************************** EffectFrame ***************************************************************/ EffectFrame::EffectFrame() : d(new EffectFramePrivate) { } EffectFrame::~EffectFrame() { delete d; } qreal EffectFrame::crossFadeProgress() const { return d->crossFadeProgress; } void EffectFrame::setCrossFadeProgress(qreal progress) { d->crossFadeProgress = progress; } bool EffectFrame::isCrossFade() const { return d->crossFading; } void EffectFrame::enableCrossFade(bool enable) { d->crossFading = enable; } QColor EffectFrame::styledTextColor() { return Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor); } } // namespace