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"