File indexing completed on 2024-11-10 04:56:59
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 2007 Philip Falkner <philip.falkner@gmail.com> 0006 SPDX-FileCopyrightText: 2009 Martin Gräßlin <mgraesslin@kde.org> 0007 SPDX-FileCopyrightText: 2010 Alexandre Pereira <pereira.alex@gmail.com> 0008 SPDX-FileCopyrightText: 2018 Vlad Zahorodnii <vlad.zahorodnii@kde.org> 0009 0010 SPDX-License-Identifier: GPL-2.0-or-later 0011 */ 0012 0013 // own 0014 #include "glide.h" 0015 0016 // KConfigSkeleton 0017 #include "glideconfig.h" 0018 0019 #include "core/rendertarget.h" 0020 #include "core/renderviewport.h" 0021 #include "effect/effecthandler.h" 0022 0023 // Qt 0024 #include <QMatrix4x4> 0025 #include <QSet> 0026 #include <qmath.h> 0027 0028 #include <cmath> 0029 0030 namespace KWin 0031 { 0032 0033 static const QSet<QString> s_blacklist{ 0034 QStringLiteral("ksmserver ksmserver"), 0035 QStringLiteral("ksmserver-logout-greeter ksmserver-logout-greeter"), 0036 QStringLiteral("ksplashqml ksplashqml"), 0037 // Spectacle needs to be blacklisted in order to stay out of its own screenshots. 0038 QStringLiteral("spectacle spectacle"), // x11 0039 QStringLiteral("spectacle org.kde.spectacle"), // wayland 0040 }; 0041 0042 static QMatrix4x4 createPerspectiveMatrix(const QRectF &rect, const qreal scale, const QMatrix4x4 &renderTargetTransformation) 0043 { 0044 QMatrix4x4 ret = renderTargetTransformation; 0045 0046 const float fovY = std::tan(qDegreesToRadians(60.0f) / 2); 0047 const float aspect = 1.0f; 0048 const float zNear = 0.1f; 0049 const float zFar = 100.0f; 0050 0051 const float yMax = zNear * fovY; 0052 const float yMin = -yMax; 0053 const float xMin = yMin * aspect; 0054 const float xMax = yMax * aspect; 0055 0056 ret.frustum(xMin, xMax, yMin, yMax, zNear, zFar); 0057 0058 const auto deviceRect = scaledRect(rect, scale); 0059 0060 const float scaleFactor = 1.1 * fovY / yMax; 0061 ret.translate(xMin * scaleFactor, yMax * scaleFactor, -1.1); 0062 ret.scale((xMax - xMin) * scaleFactor / deviceRect.width(), 0063 -(yMax - yMin) * scaleFactor / deviceRect.height(), 0064 0.001); 0065 ret.translate(-deviceRect.x(), -deviceRect.y()); 0066 0067 return ret; 0068 } 0069 0070 GlideEffect::GlideEffect() 0071 { 0072 GlideConfig::instance(effects->config()); 0073 reconfigure(ReconfigureAll); 0074 0075 connect(effects, &EffectsHandler::windowAdded, this, &GlideEffect::windowAdded); 0076 connect(effects, &EffectsHandler::windowClosed, this, &GlideEffect::windowClosed); 0077 connect(effects, &EffectsHandler::windowDataChanged, this, &GlideEffect::windowDataChanged); 0078 } 0079 0080 GlideEffect::~GlideEffect() = default; 0081 0082 void GlideEffect::reconfigure(ReconfigureFlags flags) 0083 { 0084 GlideConfig::self()->read(); 0085 m_duration = std::chrono::milliseconds(animationTime<GlideConfig>(160)); 0086 0087 m_inParams.edge = static_cast<RotationEdge>(GlideConfig::inRotationEdge()); 0088 m_inParams.angle.from = GlideConfig::inRotationAngle(); 0089 m_inParams.angle.to = 0.0; 0090 m_inParams.distance.from = GlideConfig::inDistance(); 0091 m_inParams.distance.to = 0.0; 0092 m_inParams.opacity.from = GlideConfig::inOpacity(); 0093 m_inParams.opacity.to = 1.0; 0094 0095 m_outParams.edge = static_cast<RotationEdge>(GlideConfig::outRotationEdge()); 0096 m_outParams.angle.from = 0.0; 0097 m_outParams.angle.to = GlideConfig::outRotationAngle(); 0098 m_outParams.distance.from = 0.0; 0099 m_outParams.distance.to = GlideConfig::outDistance(); 0100 m_outParams.opacity.from = 1.0; 0101 m_outParams.opacity.to = GlideConfig::outOpacity(); 0102 } 0103 0104 void GlideEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) 0105 { 0106 data.mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; 0107 0108 effects->prePaintScreen(data, presentTime); 0109 } 0110 0111 void GlideEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime) 0112 { 0113 auto animationIt = m_animations.find(w); 0114 if (animationIt != m_animations.end()) { 0115 (*animationIt).timeLine.advance(presentTime); 0116 data.setTransformed(); 0117 } 0118 0119 effects->prePaintWindow(w, data, presentTime); 0120 } 0121 0122 void GlideEffect::paintWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, QRegion region, WindowPaintData &data) 0123 { 0124 auto animationIt = m_animations.constFind(w); 0125 if (animationIt == m_animations.constEnd()) { 0126 effects->paintWindow(renderTarget, viewport, w, mask, region, data); 0127 return; 0128 } 0129 0130 // Perspective projection distorts objects near edges 0131 // of the viewport. This is critical because distortions 0132 // near edges of the viewport are not desired with this effect. 0133 // To fix this, the center of the window will be moved to the origin, 0134 // after applying perspective projection, the center is moved back 0135 // to its "original" projected position. Overall, this is how the window 0136 // will be transformed: 0137 // [move to the origin] -> [rotate] -> [translate] -> 0138 // -> [perspective projection] -> [reverse "move to the origin"] 0139 0140 const QMatrix4x4 oldProjMatrix = createPerspectiveMatrix(viewport.renderRect(), viewport.scale(), renderTarget.transform().toMatrix()); 0141 const auto frame = w->frameGeometry(); 0142 const QRectF windowGeo = scaledRect(frame, viewport.scale()); 0143 const QVector3D invOffset = oldProjMatrix.map(QVector3D(windowGeo.center())); 0144 QMatrix4x4 invOffsetMatrix; 0145 invOffsetMatrix.translate(invOffset.x(), invOffset.y()); 0146 0147 data.setProjectionMatrix(invOffsetMatrix * oldProjMatrix); 0148 0149 // Move the center of the window to the origin. 0150 const QPointF offset = viewport.renderRect().center() - w->frameGeometry().center(); 0151 data.translate(offset.x(), offset.y()); 0152 0153 const GlideParams params = w->isDeleted() ? m_outParams : m_inParams; 0154 const qreal t = (*animationIt).timeLine.value(); 0155 0156 switch (params.edge) { 0157 case RotationEdge::Top: 0158 data.setRotationAxis(Qt::XAxis); 0159 data.setRotationOrigin(QVector3D(0, 0, 0)); 0160 data.setRotationAngle(-interpolate(params.angle.from, params.angle.to, t)); 0161 break; 0162 0163 case RotationEdge::Right: 0164 data.setRotationAxis(Qt::YAxis); 0165 data.setRotationOrigin(QVector3D(w->width(), 0, 0)); 0166 data.setRotationAngle(-interpolate(params.angle.from, params.angle.to, t)); 0167 break; 0168 0169 case RotationEdge::Bottom: 0170 data.setRotationAxis(Qt::XAxis); 0171 data.setRotationOrigin(QVector3D(0, w->height(), 0)); 0172 data.setRotationAngle(interpolate(params.angle.from, params.angle.to, t)); 0173 break; 0174 0175 case RotationEdge::Left: 0176 data.setRotationAxis(Qt::YAxis); 0177 data.setRotationOrigin(QVector3D(0, 0, 0)); 0178 data.setRotationAngle(interpolate(params.angle.from, params.angle.to, t)); 0179 break; 0180 0181 default: 0182 // Fallback to Top. 0183 data.setRotationAxis(Qt::XAxis); 0184 data.setRotationOrigin(QVector3D(0, 0, 0)); 0185 data.setRotationAngle(-interpolate(params.angle.from, params.angle.to, t)); 0186 break; 0187 } 0188 0189 data.setZTranslation(-interpolate(params.distance.from, params.distance.to, t)); 0190 data.multiplyOpacity(interpolate(params.opacity.from, params.opacity.to, t)); 0191 0192 effects->paintWindow(renderTarget, viewport, w, mask, region, data); 0193 } 0194 0195 void GlideEffect::postPaintScreen() 0196 { 0197 auto animationIt = m_animations.begin(); 0198 while (animationIt != m_animations.end()) { 0199 if ((*animationIt).timeLine.done()) { 0200 animationIt = m_animations.erase(animationIt); 0201 } else { 0202 ++animationIt; 0203 } 0204 } 0205 0206 effects->addRepaintFull(); 0207 effects->postPaintScreen(); 0208 } 0209 0210 bool GlideEffect::isActive() const 0211 { 0212 return !m_animations.isEmpty(); 0213 } 0214 0215 bool GlideEffect::supported() 0216 { 0217 return effects->isOpenGLCompositing() 0218 && effects->animationsSupported(); 0219 } 0220 0221 void GlideEffect::windowAdded(EffectWindow *w) 0222 { 0223 if (effects->activeFullScreenEffect()) { 0224 return; 0225 } 0226 0227 if (!isGlideWindow(w)) { 0228 return; 0229 } 0230 0231 if (!w->isVisible()) { 0232 return; 0233 } 0234 0235 const void *addGrab = w->data(WindowAddedGrabRole).value<void *>(); 0236 if (addGrab && addGrab != this) { 0237 return; 0238 } 0239 0240 w->setData(WindowAddedGrabRole, QVariant::fromValue(static_cast<void *>(this))); 0241 0242 GlideAnimation &animation = m_animations[w]; 0243 animation.timeLine.reset(); 0244 animation.timeLine.setDirection(TimeLine::Forward); 0245 animation.timeLine.setDuration(m_duration); 0246 animation.timeLine.setEasingCurve(QEasingCurve::InCurve); 0247 0248 effects->addRepaintFull(); 0249 } 0250 0251 void GlideEffect::windowClosed(EffectWindow *w) 0252 { 0253 if (effects->activeFullScreenEffect()) { 0254 return; 0255 } 0256 0257 if (!isGlideWindow(w)) { 0258 return; 0259 } 0260 0261 if (!w->isVisible() || w->skipsCloseAnimation()) { 0262 return; 0263 } 0264 0265 const void *closeGrab = w->data(WindowClosedGrabRole).value<void *>(); 0266 if (closeGrab && closeGrab != this) { 0267 return; 0268 } 0269 0270 w->setData(WindowClosedGrabRole, QVariant::fromValue(static_cast<void *>(this))); 0271 0272 GlideAnimation &animation = m_animations[w]; 0273 animation.deletedRef = EffectWindowDeletedRef(w); 0274 animation.timeLine.reset(); 0275 animation.timeLine.setDirection(TimeLine::Forward); 0276 animation.timeLine.setDuration(m_duration); 0277 animation.timeLine.setEasingCurve(QEasingCurve::OutCurve); 0278 0279 effects->addRepaintFull(); 0280 } 0281 0282 void GlideEffect::windowDataChanged(EffectWindow *w, int role) 0283 { 0284 if (role != WindowAddedGrabRole && role != WindowClosedGrabRole) { 0285 return; 0286 } 0287 0288 if (w->data(role).value<void *>() == this) { 0289 return; 0290 } 0291 0292 auto animationIt = m_animations.find(w); 0293 if (animationIt != m_animations.end()) { 0294 m_animations.erase(animationIt); 0295 } 0296 } 0297 0298 bool GlideEffect::isGlideWindow(EffectWindow *w) const 0299 { 0300 // We don't want to animate most of plasmashell's windows, yet, some 0301 // of them we want to, for example, Task Manager Settings window. 0302 // The problem is that all those window share single window class. 0303 // So, the only way to decide whether a window should be animated is 0304 // to use a heuristic: if a window has decoration, then it's most 0305 // likely a dialog or a settings window so we have to animate it. 0306 if (w->windowClass() == QLatin1String("plasmashell plasmashell") 0307 || w->windowClass() == QLatin1String("plasmashell org.kde.plasmashell")) { 0308 return w->hasDecoration(); 0309 } 0310 0311 if (s_blacklist.contains(w->windowClass())) { 0312 return false; 0313 } 0314 0315 if (w->hasDecoration()) { 0316 return true; 0317 } 0318 0319 // Don't animate combobox popups, tooltips, popup menus, etc. 0320 if (w->isPopupWindow()) { 0321 return false; 0322 } 0323 0324 // Don't animate the outline and the screenlocker as it looks bad. 0325 if (w->isLockScreen() || w->isOutline()) { 0326 return false; 0327 } 0328 0329 // Override-redirect windows are usually used for user interface 0330 // concepts that are not expected to be animated by this effect. 0331 if (w->isX11Client() && !w->isManaged()) { 0332 return false; 0333 } 0334 0335 return w->isNormalWindow() 0336 || w->isDialog(); 0337 } 0338 0339 } // namespace KWin 0340 0341 #include "moc_glide.cpp"