File indexing completed on 2024-11-10 04:57:02
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 2008 Martin Gräßlin <mgraesslin@kde.org> 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 0010 // based on minimize animation by Rivo Laks <rivolaks@hot.ee> 0011 0012 #include "magiclamp.h" 0013 #include "effect/effecthandler.h" 0014 // KConfigSkeleton 0015 #include "magiclampconfig.h" 0016 0017 namespace KWin 0018 { 0019 0020 MagicLampEffect::MagicLampEffect() 0021 { 0022 MagicLampConfig::instance(effects->config()); 0023 reconfigure(ReconfigureAll); 0024 connect(effects, &EffectsHandler::windowAdded, this, &MagicLampEffect::slotWindowAdded); 0025 connect(effects, &EffectsHandler::windowDeleted, this, &MagicLampEffect::slotWindowDeleted); 0026 0027 const auto windows = effects->stackingOrder(); 0028 for (EffectWindow *window : windows) { 0029 slotWindowAdded(window); 0030 } 0031 0032 setVertexSnappingMode(RenderGeometry::VertexSnappingMode::None); 0033 } 0034 0035 bool MagicLampEffect::supported() 0036 { 0037 return OffscreenEffect::supported() && effects->animationsSupported(); 0038 } 0039 0040 void MagicLampEffect::reconfigure(ReconfigureFlags) 0041 { 0042 MagicLampConfig::self()->read(); 0043 0044 // TODO: Rename animationDuration to duration so we can use 0045 // animationTime<MagicLampConfig>(250). 0046 const int d = MagicLampConfig::animationDuration() != 0 0047 ? MagicLampConfig::animationDuration() 0048 : 250; 0049 m_duration = std::chrono::milliseconds(static_cast<int>(animationTime(d))); 0050 } 0051 0052 void MagicLampEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) 0053 { 0054 // We need to mark the screen windows as transformed. Otherwise the 0055 // whole screen won't be repainted, resulting in artefacts. 0056 data.mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; 0057 0058 effects->prePaintScreen(data, presentTime); 0059 } 0060 0061 void MagicLampEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime) 0062 { 0063 // Schedule window for transformation if the animation is still in 0064 // progress 0065 auto animationIt = m_animations.find(w); 0066 if (animationIt != m_animations.end()) { 0067 (*animationIt).timeLine.advance(presentTime); 0068 data.setTransformed(); 0069 } 0070 0071 effects->prePaintWindow(w, data, presentTime); 0072 } 0073 0074 void MagicLampEffect::apply(EffectWindow *w, int mask, WindowPaintData &data, WindowQuadList &quads) 0075 { 0076 auto animationIt = m_animations.constFind(w); 0077 if (animationIt != m_animations.constEnd()) { 0078 // 0 = not minimized, 1 = fully minimized 0079 const qreal progress = (*animationIt).timeLine.value(); 0080 0081 QRect geo = w->frameGeometry().toRect(); 0082 QRect icon = w->iconGeometry().toRect(); 0083 IconPosition position = Top; 0084 // If there's no icon geometry, minimize to the center of the screen 0085 if (!icon.isValid()) { 0086 QRect extG = geo; 0087 QPoint pt = cursorPos().toPoint(); 0088 // focussing inside the window is no good, leads to ugly artefacts, find nearest border 0089 if (extG.contains(pt)) { 0090 const int d[2][2] = {{pt.x() - extG.x(), extG.right() - pt.x()}, 0091 {pt.y() - extG.y(), extG.bottom() - pt.y()}}; 0092 int di = d[1][0]; 0093 position = Top; 0094 if (d[0][0] < di) { 0095 di = d[0][0]; 0096 position = Left; 0097 } 0098 if (d[1][1] < di) { 0099 di = d[1][1]; 0100 position = Bottom; 0101 } 0102 if (d[0][1] < di) { 0103 position = Right; 0104 } 0105 switch (position) { 0106 case Top: 0107 pt.setY(extG.y()); 0108 break; 0109 case Left: 0110 pt.setX(extG.x()); 0111 break; 0112 case Bottom: 0113 pt.setY(extG.bottom()); 0114 break; 0115 case Right: 0116 pt.setX(extG.right()); 0117 break; 0118 } 0119 } else { 0120 if (pt.y() < geo.y()) { 0121 position = Top; 0122 } else if (pt.x() < geo.x()) { 0123 position = Left; 0124 } else if (pt.y() > geo.bottom()) { 0125 position = Bottom; 0126 } else if (pt.x() > geo.right()) { 0127 position = Right; 0128 } 0129 } 0130 icon = QRect(pt, QSize(0, 0)); 0131 } else { 0132 // Assumption: there is a panel containing the icon position 0133 EffectWindow *panel = nullptr; 0134 const auto stackingOrder = effects->stackingOrder(); 0135 for (EffectWindow *window : stackingOrder) { 0136 if (!window->isDock()) { 0137 continue; 0138 } 0139 // we have to use intersects as there seems to be a Plasma bug 0140 // the published icon geometry might be bigger than the panel 0141 if (window->frameGeometry().intersects(icon)) { 0142 panel = window; 0143 break; 0144 } 0145 } 0146 if (panel) { 0147 // Assumption: width of horizonal panel is greater than its height and vice versa 0148 const QRectF windowScreen = effects->clientArea(ScreenArea, w); 0149 0150 if (panel->width() >= panel->height()) { 0151 // horizontal panel 0152 if (icon.center().y() <= windowScreen.center().y()) { 0153 position = Top; 0154 } else { 0155 position = Bottom; 0156 } 0157 } else { 0158 // vertical panel 0159 if (icon.center().x() <= windowScreen.center().x()) { 0160 position = Left; 0161 } else { 0162 position = Right; 0163 } 0164 } 0165 0166 // If the panel is hidden, move the icon offscreen so the animation looks correct. 0167 if (panel->isHidden()) { 0168 const QRectF panelScreen = effects->clientArea(ScreenArea, panel); 0169 switch (position) { 0170 case Bottom: 0171 icon.moveTop(panelScreen.y() + panelScreen.height()); 0172 break; 0173 case Top: 0174 icon.moveTop(panelScreen.y() - icon.height()); 0175 break; 0176 case Left: 0177 icon.moveLeft(panelScreen.x() - icon.width()); 0178 break; 0179 case Right: 0180 icon.moveLeft(panelScreen.x() + panelScreen.width()); 0181 break; 0182 } 0183 } 0184 } else { 0185 // we did not find a panel, so it might be autohidden 0186 QRectF iconScreen = effects->clientArea(ScreenArea, icon.topLeft(), effects->currentDesktop()); 0187 // as the icon geometry could be overlap a screen edge we use an intersection 0188 QRectF rect = iconScreen.intersected(icon); 0189 // here we need a different assumption: icon geometry borders one screen edge 0190 // this assumption might be wrong for e.g. task applet being the only applet in panel 0191 // in this case the icon borders two screen edges 0192 // there might be a wrong animation, but not distorted 0193 if (rect.x() == iconScreen.x()) { 0194 position = Left; 0195 } else if (rect.x() + rect.width() == iconScreen.x() + iconScreen.width()) { 0196 position = Right; 0197 } else if (rect.y() == iconScreen.y()) { 0198 position = Top; 0199 } else { 0200 position = Bottom; 0201 } 0202 } 0203 } 0204 0205 quads = quads.makeGrid(40); 0206 float quadFactor; // defines how fast a quad is vertically moved: y coordinates near to window top are slowed down 0207 // it is used as quadFactor^3/windowHeight^3 0208 // quadFactor is the y position of the quad but is changed towards becomming the window height 0209 // by that the factor becomes 1 and has no influence any more 0210 float offset[2] = {0, 0}; // how far has a quad to be moved? Distance between icon and window multiplied by the progress and by the quadFactor 0211 float p_progress[2] = {0, 0}; // the factor which defines how far the x values have to be changed 0212 // factor is the current moved y value diveded by the distance between icon and window 0213 WindowQuad lastQuad; 0214 lastQuad[0].setX(-1); 0215 lastQuad[0].setY(-1); 0216 lastQuad[1].setX(-1); 0217 lastQuad[1].setY(-1); 0218 lastQuad[2].setX(-1); 0219 lastQuad[2].setY(-1); 0220 0221 if (position == Bottom) { 0222 const float height_cube = float(geo.height()) * float(geo.height()) * float(geo.height()); 0223 const float maxY = icon.y() - geo.y(); 0224 0225 for (WindowQuad &quad : quads) { 0226 0227 if (quad[0].y() != lastQuad[0].y() || quad[2].y() != lastQuad[2].y()) { 0228 quadFactor = quad[0].y() + (geo.height() - quad[0].y()) * progress; 0229 offset[0] = (icon.y() + quad[0].y() - geo.y()) * progress * ((quadFactor * quadFactor * quadFactor) / height_cube); 0230 quadFactor = quad[2].y() + (geo.height() - quad[2].y()) * progress; 0231 offset[1] = (icon.y() + quad[2].y() - geo.y()) * progress * ((quadFactor * quadFactor * quadFactor) / height_cube); 0232 p_progress[1] = std::min(offset[1] / (icon.y() - geo.y() - float(quad[2].y())), 1.0f); 0233 p_progress[0] = std::min(offset[0] / (icon.y() - geo.y() - float(quad[0].y())), 1.0f); 0234 } else { 0235 lastQuad = quad; 0236 } 0237 0238 p_progress[0] = std::abs(p_progress[0]); 0239 p_progress[1] = std::abs(p_progress[1]); 0240 0241 // x values are moved towards the center of the icon 0242 quad[0].setX((icon.x() + icon.width() * (quad[0].x() / geo.width()) - (quad[0].x() + geo.x())) * p_progress[0] + quad[0].x()); 0243 quad[1].setX((icon.x() + icon.width() * (quad[1].x() / geo.width()) - (quad[1].x() + geo.x())) * p_progress[0] + quad[1].x()); 0244 quad[2].setX((icon.x() + icon.width() * (quad[2].x() / geo.width()) - (quad[2].x() + geo.x())) * p_progress[1] + quad[2].x()); 0245 quad[3].setX((icon.x() + icon.width() * (quad[3].x() / geo.width()) - (quad[3].x() + geo.x())) * p_progress[1] + quad[3].x()); 0246 0247 quad[0].setY(std::min(maxY, float(quad[0].y()) + offset[0])); 0248 quad[1].setY(std::min(maxY, float(quad[1].y()) + offset[0])); 0249 quad[2].setY(std::min(maxY, float(quad[2].y()) + offset[1])); 0250 quad[3].setY(std::min(maxY, float(quad[3].y()) + offset[1])); 0251 } 0252 } else if (position == Top) { 0253 const float height_cube = float(geo.height()) * float(geo.height()) * float(geo.height()); 0254 const float minY = icon.y() + icon.height() - geo.y(); 0255 0256 for (WindowQuad &quad : quads) { 0257 0258 if (quad[0].y() != lastQuad[0].y() || quad[2].y() != lastQuad[2].y()) { 0259 quadFactor = geo.height() - quad[0].y() + (quad[0].y()) * progress; 0260 offset[0] = (geo.y() - icon.height() + geo.height() + quad[0].y() - icon.y()) * progress * ((quadFactor * quadFactor * quadFactor) / height_cube); 0261 quadFactor = geo.height() - quad[2].y() + (quad[2].y()) * progress; 0262 offset[1] = (geo.y() - icon.height() + geo.height() + quad[2].y() - icon.y()) * progress * ((quadFactor * quadFactor * quadFactor) / height_cube); 0263 p_progress[0] = std::min(offset[0] / (geo.y() - icon.height() + geo.height() - icon.y() - float(geo.height() - quad[0].y())), 1.0f); 0264 p_progress[1] = std::min(offset[1] / (geo.y() - icon.height() + geo.height() - icon.y() - float(geo.height() - quad[2].y())), 1.0f); 0265 } else { 0266 lastQuad = quad; 0267 } 0268 0269 offset[0] = -offset[0]; 0270 offset[1] = -offset[1]; 0271 0272 p_progress[0] = std::abs(p_progress[0]); 0273 p_progress[1] = std::abs(p_progress[1]); 0274 0275 // x values are moved towards the center of the icon 0276 quad[0].setX((icon.x() + icon.width() * (quad[0].x() / geo.width()) - (quad[0].x() + geo.x())) * p_progress[0] + quad[0].x()); 0277 quad[1].setX((icon.x() + icon.width() * (quad[1].x() / geo.width()) - (quad[1].x() + geo.x())) * p_progress[0] + quad[1].x()); 0278 quad[2].setX((icon.x() + icon.width() * (quad[2].x() / geo.width()) - (quad[2].x() + geo.x())) * p_progress[1] + quad[2].x()); 0279 quad[3].setX((icon.x() + icon.width() * (quad[3].x() / geo.width()) - (quad[3].x() + geo.x())) * p_progress[1] + quad[3].x()); 0280 0281 quad[0].setY(std::max(minY, float(quad[0].y()) + offset[0])); 0282 quad[1].setY(std::max(minY, float(quad[1].y()) + offset[0])); 0283 quad[2].setY(std::max(minY, float(quad[2].y()) + offset[1])); 0284 quad[3].setY(std::max(minY, float(quad[3].y()) + offset[1])); 0285 } 0286 } else if (position == Left) { 0287 const float width_cube = float(geo.width()) * float(geo.width()) * float(geo.width()); 0288 const float minX = icon.x() + icon.width() - geo.x(); 0289 0290 for (WindowQuad &quad : quads) { 0291 0292 if (quad[0].x() != lastQuad[0].x() || quad[1].x() != lastQuad[1].x()) { 0293 quadFactor = geo.width() - quad[0].x() + (quad[0].x()) * progress; 0294 offset[0] = (geo.x() - icon.width() + geo.width() + quad[0].x() - icon.x()) * progress * ((quadFactor * quadFactor * quadFactor) / width_cube); 0295 quadFactor = geo.width() - quad[1].x() + (quad[1].x()) * progress; 0296 offset[1] = (geo.x() - icon.width() + geo.width() + quad[1].x() - icon.x()) * progress * ((quadFactor * quadFactor * quadFactor) / width_cube); 0297 p_progress[0] = std::min(offset[0] / (geo.x() - icon.width() + geo.width() - icon.x() - float(geo.width() - quad[0].x())), 1.0f); 0298 p_progress[1] = std::min(offset[1] / (geo.x() - icon.width() + geo.width() - icon.x() - float(geo.width() - quad[1].x())), 1.0f); 0299 } else { 0300 lastQuad = quad; 0301 } 0302 0303 offset[0] = -offset[0]; 0304 offset[1] = -offset[1]; 0305 0306 p_progress[0] = std::abs(p_progress[0]); 0307 p_progress[1] = std::abs(p_progress[1]); 0308 0309 // y values are moved towards the center of the icon 0310 quad[0].setY((icon.y() + icon.height() * (quad[0].y() / geo.height()) - (quad[0].y() + geo.y())) * p_progress[0] + quad[0].y()); 0311 quad[1].setY((icon.y() + icon.height() * (quad[1].y() / geo.height()) - (quad[1].y() + geo.y())) * p_progress[1] + quad[1].y()); 0312 quad[2].setY((icon.y() + icon.height() * (quad[2].y() / geo.height()) - (quad[2].y() + geo.y())) * p_progress[1] + quad[2].y()); 0313 quad[3].setY((icon.y() + icon.height() * (quad[3].y() / geo.height()) - (quad[3].y() + geo.y())) * p_progress[0] + quad[3].y()); 0314 0315 quad[0].setX(std::max(minX, float(quad[0].x()) + offset[0])); 0316 quad[1].setX(std::max(minX, float(quad[1].x()) + offset[1])); 0317 quad[2].setX(std::max(minX, float(quad[2].x()) + offset[1])); 0318 quad[3].setX(std::max(minX, float(quad[3].x()) + offset[0])); 0319 } 0320 } else if (position == Right) { 0321 const float width_cube = float(geo.width()) * float(geo.width()) * float(geo.width()); 0322 const float maxX = icon.x() - geo.x(); 0323 0324 for (WindowQuad &quad : quads) { 0325 0326 if (quad[0].x() != lastQuad[0].x() || quad[1].x() != lastQuad[1].x()) { 0327 quadFactor = quad[0].x() + (geo.width() - quad[0].x()) * progress; 0328 offset[0] = (icon.x() + quad[0].x() - geo.x()) * progress * ((quadFactor * quadFactor * quadFactor) / width_cube); 0329 quadFactor = quad[1].x() + (geo.width() - quad[1].x()) * progress; 0330 offset[1] = (icon.x() + quad[1].x() - geo.x()) * progress * ((quadFactor * quadFactor * quadFactor) / width_cube); 0331 p_progress[0] = std::min(offset[0] / (icon.x() - geo.x() - float(quad[0].x())), 1.0f); 0332 p_progress[1] = std::min(offset[1] / (icon.x() - geo.x() - float(quad[1].x())), 1.0f); 0333 } else { 0334 lastQuad = quad; 0335 } 0336 0337 p_progress[0] = std::abs(p_progress[0]); 0338 p_progress[1] = std::abs(p_progress[1]); 0339 0340 // y values are moved towards the center of the icon 0341 quad[0].setY((icon.y() + icon.height() * (quad[0].y() / geo.height()) - (quad[0].y() + geo.y())) * p_progress[0] + quad[0].y()); 0342 quad[1].setY((icon.y() + icon.height() * (quad[1].y() / geo.height()) - (quad[1].y() + geo.y())) * p_progress[1] + quad[1].y()); 0343 quad[2].setY((icon.y() + icon.height() * (quad[2].y() / geo.height()) - (quad[2].y() + geo.y())) * p_progress[1] + quad[2].y()); 0344 quad[3].setY((icon.y() + icon.height() * (quad[3].y() / geo.height()) - (quad[3].y() + geo.y())) * p_progress[0] + quad[3].y()); 0345 0346 quad[0].setX(std::min(maxX, float(quad[0].x()) + offset[0])); 0347 quad[1].setX(std::min(maxX, float(quad[1].x()) + offset[1])); 0348 quad[2].setX(std::min(maxX, float(quad[2].x()) + offset[1])); 0349 quad[3].setX(std::min(maxX, float(quad[3].x()) + offset[0])); 0350 } 0351 } 0352 } 0353 } 0354 0355 void MagicLampEffect::postPaintScreen() 0356 { 0357 auto animationIt = m_animations.begin(); 0358 while (animationIt != m_animations.end()) { 0359 if ((*animationIt).timeLine.done()) { 0360 unredirect(animationIt.key()); 0361 animationIt = m_animations.erase(animationIt); 0362 } else { 0363 ++animationIt; 0364 } 0365 } 0366 0367 effects->addRepaintFull(); 0368 0369 // Call the next effect. 0370 effects->postPaintScreen(); 0371 } 0372 0373 void MagicLampEffect::slotWindowAdded(EffectWindow *w) 0374 { 0375 connect(w, &EffectWindow::minimizedChanged, this, [this, w]() { 0376 if (w->isMinimized()) { 0377 slotWindowMinimized(w); 0378 } else { 0379 slotWindowUnminimized(w); 0380 } 0381 }); 0382 } 0383 0384 void MagicLampEffect::slotWindowDeleted(EffectWindow *w) 0385 { 0386 m_animations.remove(w); 0387 } 0388 0389 void MagicLampEffect::slotWindowMinimized(EffectWindow *w) 0390 { 0391 if (effects->activeFullScreenEffect()) { 0392 return; 0393 } 0394 0395 MagicLampAnimation &animation = m_animations[w]; 0396 0397 if (animation.timeLine.running()) { 0398 animation.timeLine.toggleDirection(); 0399 } else { 0400 animation.visibleRef = EffectWindowVisibleRef(w, EffectWindow::PAINT_DISABLED_BY_MINIMIZE); 0401 animation.timeLine.setDirection(TimeLine::Forward); 0402 animation.timeLine.setDuration(m_duration); 0403 animation.timeLine.setEasingCurve(QEasingCurve::Linear); 0404 } 0405 0406 redirect(w); 0407 effects->addRepaintFull(); 0408 } 0409 0410 void MagicLampEffect::slotWindowUnminimized(EffectWindow *w) 0411 { 0412 if (effects->activeFullScreenEffect()) { 0413 return; 0414 } 0415 0416 MagicLampAnimation &animation = m_animations[w]; 0417 0418 if (animation.timeLine.running()) { 0419 animation.timeLine.toggleDirection(); 0420 } else { 0421 animation.visibleRef = EffectWindowVisibleRef(w, EffectWindow::PAINT_DISABLED_BY_MINIMIZE); 0422 animation.timeLine.setDirection(TimeLine::Backward); 0423 animation.timeLine.setDuration(m_duration); 0424 animation.timeLine.setEasingCurve(QEasingCurve::Linear); 0425 } 0426 0427 redirect(w); 0428 effects->addRepaintFull(); 0429 } 0430 0431 bool MagicLampEffect::isActive() const 0432 { 0433 return !m_animations.isEmpty(); 0434 } 0435 0436 } // namespace 0437 0438 #include "moc_magiclamp.cpp"