magiclamp.cpp 16.1 KB
Newer Older
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
1 2 3
/*
    KWin - the KDE window manager
    This file is part of the KDE project.
4

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
5
    SPDX-FileCopyrightText: 2008 Martin Gräßlin <mgraesslin@kde.org>
6

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
7 8
    SPDX-License-Identifier: GPL-2.0-or-later
*/
9 10 11 12

// based on minimize animation by Rivo Laks <rivolaks@hot.ee>

#include "magiclamp.h"
13 14 15
// KConfigSkeleton
#include "magiclampconfig.h"

16 17 18 19
namespace KWin
{

MagicLampEffect::MagicLampEffect()
20
{
21
    initConfig<MagicLampConfig>();
22
    reconfigure(ReconfigureAll);
23 24 25
    connect(effects, &EffectsHandler::windowDeleted, this, &MagicLampEffect::slotWindowDeleted);
    connect(effects, &EffectsHandler::windowMinimized, this, &MagicLampEffect::slotWindowMinimized);
    connect(effects, &EffectsHandler::windowUnminimized, this, &MagicLampEffect::slotWindowUnminimized);
26
}
27

Martin Flöser's avatar
Martin Flöser committed
28
bool MagicLampEffect::supported()
29
{
30
    return effects->isOpenGLCompositing() && effects->animationsSupported();
31
}
Martin Flöser's avatar
Martin Flöser committed
32

33 34
void MagicLampEffect::reconfigure(ReconfigureFlags)
{
35
    MagicLampConfig::self()->read();
36 37 38 39 40 41 42

    // TODO: Rename animationDuration to duration so we can use
    // animationTime<MagicLampConfig>(250).
    const int d = MagicLampConfig::animationDuration() != 0
        ? MagicLampConfig::animationDuration()
        : 250;
    m_duration = std::chrono::milliseconds(static_cast<int>(animationTime(d)));
43
}
44

45 46
void MagicLampEffect::prePaintScreen(ScreenPrePaintData& data, int time)
{
47
    const std::chrono::milliseconds delta(time);
48

49 50 51 52
    auto animationIt = m_animations.begin();
    while (animationIt != m_animations.end()) {
        (*animationIt).update(delta);
        ++animationIt;
53 54
    }

55 56 57
    // We need to mark the screen windows as transformed. Otherwise the
    // whole screen won't be repainted, resulting in artefacts.
    data.mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS;
58 59

    effects->prePaintScreen(data, time);
60
}
61

62 63
void MagicLampEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time)
{
64 65
    // Schedule window for transformation if the animation is still in
    //  progress
66
    if (m_animations.contains(w)) {
67 68
        // We'll transform this window
        data.setTransformed();
69 70
        data.quads = data.quads.makeGrid(40);
        w->enablePainting(EffectWindow::PAINT_DISABLED_BY_MINIMIZE);
71 72
    }

73 74 75 76 77
    effects->prePaintWindow(w, data, time);
}

void MagicLampEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data)
{
78 79
    auto animationIt = m_animations.constFind(w);
    if (animationIt != m_animations.constEnd()) {
80
        // 0 = not minimized, 1 = fully minimized
81
        const qreal progress = (*animationIt).value();
82 83 84

        QRect geo = w->geometry();
        QRect icon = w->iconGeometry();
85
        IconPosition position = Top;
86
        // If there's no icon geometry, minimize to the center of the screen
87
        if (!icon.isValid()) {
88
            QRect extG = geo;
89
            QPoint pt = cursorPos();
90
            // focussing inside the window is no good, leads to ugly artefacts, find nearest border
91
            if (extG.contains(pt)) {
92
                const int d[2][2] = { {pt.x() - extG.x(), extG.right()  - pt.x()},
93 94
                    {pt.y() - extG.y(), extG.bottom() - pt.y()}
                };
95 96
                int di = d[1][0];
                position = Top;
97
                if (d[0][0] < di) {
98 99
                    di = d[0][0];
                    position = Left;
100 101
                }
                if (d[1][1] < di) {
102
                    di = d[1][1];
103
                    position = Bottom;
104
                }
105 106
                if (d[0][1] < di)
                    position = Right;
107 108 109 110 111 112 113
                switch(position) {
                case Top: pt.setY(extG.y()); break;
                case Left: pt.setX(extG.x()); break;
                case Bottom: pt.setY(extG.bottom()); break;
                case Right: pt.setX(extG.right()); break;
                }
            } else {
114 115 116
                if (pt.y() < geo.y())
                    position = Top;
                else if (pt.x() < geo.x())
117
                    position = Left;
118 119 120
                else if (pt.y() > geo.bottom())
                    position = Bottom;
                else if (pt.x() > geo.right())
121
                    position = Right;
122
            }
123 124
            icon = QRect(pt, QSize(0, 0));
        } else {
125
            // Assumption: there is a panel containing the icon position
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
126
            EffectWindow* panel = nullptr;
127 128
            foreach (EffectWindow * window, effects->stackingOrder()) {
                if (!window->isDock())
129 130 131
                    continue;
                // we have to use intersects as there seems to be a Plasma bug
                // the published icon geometry might be bigger than the panel
132
                if (window->geometry().intersects(icon)) {
133 134
                    panel = window;
                    break;
135
                }
136 137
            }
            if (panel) {
138 139
                // Assumption: width of horizonal panel is greater than its height and vice versa
                // The panel has to border one screen edge, so get it's screen area
140 141
                QRect panelScreen = effects->clientArea(ScreenArea, panel);
                if (panel->width() >= panel->height()) {
142
                    // horizontal panel
143
                    if (panel->y() <= panelScreen.height()/2)
144 145 146
                        position = Top;
                    else
                        position = Bottom;
147
                } else {
148
                    // vertical panel
149
                    if (panel->x() <= panelScreen.width()/2)
150 151 152
                        position = Left;
                    else
                        position = Right;
153
                }
154
            } else {
155
                // we did not find a panel, so it might be autohidden
156
                QRect iconScreen = effects->clientArea(ScreenArea, icon.topLeft(), effects->currentDesktop());
157
                // as the icon geometry could be overlap a screen edge we use an intersection
158
                QRect rect = iconScreen.intersected(icon);
159 160 161 162
                // here we need a different assumption: icon geometry borders one screen edge
                // this assumption might be wrong for e.g. task applet being the only applet in panel
                // in this case the icon borders two screen edges
                // there might be a wrong animation, but not distorted
163
                if (rect.x() == iconScreen.x()) {
164
                    position = Left;
165
                } else if (rect.x() + rect.width() == iconScreen.x() + iconScreen.width()) {
166
                    position = Right;
167
                } else if (rect.y() == iconScreen.y()) {
168
                    position = Top;
169
                } else {
170
                    position = Bottom;
171
                }
172
            }
173
        }
174

175 176 177 178 179 180 181 182 183 184 185 186 187 188
#define SANITIZE_PROGRESS   if (p_progress[0] < 0)\
                                p_progress[0] = -p_progress[0];\
                            if (p_progress[1] < 0)\
                                p_progress[1] = -p_progress[1]
#define SET_QUADS(_SET_A_, _A_, _DA_, _SET_B_, _B_, _O0_, _O1_, _O2_, _O3_) quad[0]._SET_A_((icon._A_() + icon._DA_()*(quad[0]._A_() / geo._DA_()) - (quad[0]._A_() + geo._A_()))*p_progress[_O0_] + quad[0]._A_());\
                                                                            quad[1]._SET_A_((icon._A_() + icon._DA_()*(quad[1]._A_() / geo._DA_()) - (quad[1]._A_() + geo._A_()))*p_progress[_O1_] + quad[1]._A_());\
                                                                            quad[2]._SET_A_((icon._A_() + icon._DA_()*(quad[2]._A_() / geo._DA_()) - (quad[2]._A_() + geo._A_()))*p_progress[_O2_] + quad[2]._A_());\
                                                                            quad[3]._SET_A_((icon._A_() + icon._DA_()*(quad[3]._A_() / geo._DA_()) - (quad[3]._A_() + geo._A_()))*p_progress[_O3_] + quad[3]._A_());\
                                                                            \
                                                                            quad[0]._SET_B_(quad[0]._B_() + offset[_O0_]);\
                                                                            quad[1]._SET_B_(quad[1]._B_() + offset[_O1_]);\
                                                                            quad[2]._SET_B_(quad[2]._B_() + offset[_O2_]);\
                                                                            quad[3]._SET_B_(quad[3]._B_() + offset[_O3_])

189
        WindowQuadList newQuads;
190
        newQuads.reserve(data.quads.count());
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
        float quadFactor;   // defines how fast a quad is vertically moved: y coordinates near to window top are slowed down
                            // it is used as quadFactor^3/windowHeight^3
                            // quadFactor is the y position of the quad but is changed towards becomming the window height
                            // by that the factor becomes 1 and has no influence any more
        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
        float p_progress[2] = {0,0};  // the factor which defines how far the x values have to be changed
                            // factor is the current moved y value diveded by the distance between icon and window
        WindowQuad lastQuad(WindowQuadError);
        lastQuad[0].setX(-1);
        lastQuad[0].setY(-1);
        lastQuad[1].setX(-1);
        lastQuad[1].setY(-1);
        lastQuad[2].setX(-1);
        lastQuad[2].setY(-1);

        if (position == Bottom) {
            float height_cube = float(geo.height()) * float(geo.height()) * float(geo.height());
            foreach (WindowQuad quad, data.quads) { // krazy:exclude=foreach

                if (quad[0].y() != lastQuad[0].y() || quad[2].y() != lastQuad[2].y()) {
211
                    quadFactor = quad[0].y() + (geo.height() - quad[0].y()) * progress;
212
                    offset[0] = (icon.y() + quad[0].y() - geo.y()) * progress * ((quadFactor * quadFactor * quadFactor) / height_cube);
213
                    quadFactor = quad[2].y() + (geo.height() - quad[2].y()) * progress;
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
                    offset[1] = (icon.y() + quad[2].y() - geo.y()) * progress * ((quadFactor * quadFactor * quadFactor) / height_cube);
                    p_progress[1] = qMin(offset[1] / (icon.y() + icon.height() - geo.y() - float(quad[2].y())), 1.0f);
                    p_progress[0] = qMin(offset[0] / (icon.y() + icon.height() - geo.y() - float(quad[0].y())), 1.0f);
                } else
                    lastQuad = quad;

                SANITIZE_PROGRESS;
                // x values are moved towards the center of the icon
                SET_QUADS(setX, x, width, setY, y, 0,0,1,1);

                newQuads.append(quad);
            }
        } else if (position == Top) {
            float height_cube = float(geo.height()) * float(geo.height()) * float(geo.height());
            foreach (WindowQuad quad, data.quads) { // krazy:exclude=foreach

                if (quad[0].y() != lastQuad[0].y() || quad[2].y() != lastQuad[2].y()) {
231
                    quadFactor = geo.height() - quad[0].y() + (quad[0].y()) * progress;
232
                    offset[0] = (geo.y() - icon.height() + geo.height() + quad[0].y() - icon.y()) * progress * ((quadFactor * quadFactor * quadFactor) / height_cube);
233
                    quadFactor = geo.height() - quad[2].y() + (quad[2].y()) * progress;
234 235 236 237 238 239 240 241
                    offset[1] = (geo.y() - icon.height() + geo.height() + quad[2].y() - icon.y()) * progress * ((quadFactor * quadFactor * quadFactor) / height_cube);
                    p_progress[0] = qMin(offset[0] / (geo.y() - icon.height() + geo.height() - icon.y() - float(geo.height() - quad[0].y())), 1.0f);
                    p_progress[1] = qMin(offset[1] / (geo.y() - icon.height() + geo.height() - icon.y() - float(geo.height() - quad[2].y())), 1.0f);
                } else
                    lastQuad = quad;

                offset[0] = -offset[0];
                offset[1] = -offset[1];
242

243
                SANITIZE_PROGRESS;
244
                // x values are moved towards the center of the icon
245 246 247 248 249 250 251 252 253
                SET_QUADS(setX, x, width, setY, y, 0,0,1,1);

                newQuads.append(quad);
            }
        } else if (position == Left) {
            float width_cube = float(geo.width()) * float(geo.width()) * float(geo.width());
            foreach (WindowQuad quad, data.quads) { // krazy:exclude=foreach

                if (quad[0].x() != lastQuad[0].x() || quad[1].x() != lastQuad[1].x()) {
254
                    quadFactor = geo.width() - quad[0].x() + (quad[0].x()) * progress;
255
                    offset[0] = (geo.x() - icon.width() + geo.width() + quad[0].x() - icon.x()) * progress * ((quadFactor * quadFactor * quadFactor) / width_cube);
256
                    quadFactor = geo.width() - quad[1].x() + (quad[1].x()) * progress;
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
                    offset[1] = (geo.x() - icon.width() + geo.width() + quad[1].x() - icon.x()) * progress * ((quadFactor * quadFactor * quadFactor) / width_cube);
                    p_progress[0] = qMin(offset[0] / (geo.x() - icon.width() + geo.width() - icon.x() - float(geo.width() - quad[0].x())), 1.0f);
                    p_progress[1] = qMin(offset[1] / (geo.x() - icon.width() + geo.width() - icon.x() - float(geo.width() - quad[1].x())), 1.0f);
                } else
                    lastQuad = quad;

                offset[0] = -offset[0];
                offset[1] = -offset[1];

                SANITIZE_PROGRESS;
                // y values are moved towards the center of the icon
                SET_QUADS(setY, y, height, setX, x, 0,1,1,0);

                newQuads.append(quad);
            }
        } else if (position == Right) {
            float width_cube = float(geo.width()) * float(geo.width()) * float(geo.width());
            foreach (WindowQuad quad, data.quads) { // krazy:exclude=foreach

                if (quad[0].x() != lastQuad[0].x() || quad[1].x() != lastQuad[1].x()) {
                    quadFactor = quad[0].x() + (geo.width() - quad[0].x()) * progress;
                    offset[0] = (icon.x() + quad[0].x() - geo.x()) * progress * ((quadFactor * quadFactor * quadFactor) / width_cube);
                    quadFactor = quad[1].x() + (geo.width() - quad[1].x()) * progress;
                    offset[1] = (icon.x() + quad[1].x() - geo.x()) * progress * ((quadFactor * quadFactor * quadFactor) / width_cube);
                    p_progress[0] = qMin(offset[0] / (icon.x() + icon.width() - geo.x() - float(quad[0].x())), 1.0f);
                    p_progress[1] = qMin(offset[1] / (icon.x() + icon.width() - geo.x() - float(quad[1].x())), 1.0f);
                } else
                    lastQuad = quad;

                SANITIZE_PROGRESS;
                // y values are moved towards the center of the icon
                SET_QUADS(setY, y, height, setX, x, 0,1,1,0);

                newQuads.append(quad);
291
            }
292
        }
293 294 295 296
        data.quads = newQuads;
    }

    // Call the next effect.
297 298
    effects->paintWindow(w, mask, region, data);
}
299 300

void MagicLampEffect::postPaintScreen()
301
{
302 303 304 305 306 307 308 309 310 311
    auto animationIt = m_animations.begin();
    while (animationIt != m_animations.end()) {
        if ((*animationIt).done()) {
            animationIt = m_animations.erase(animationIt);
        } else {
            ++animationIt;
        }
    }

    effects->addRepaintFull();
312 313 314

    // Call the next effect.
    effects->postPaintScreen();
315
}
316

317
void MagicLampEffect::slotWindowDeleted(EffectWindow* w)
318
{
319
    m_animations.remove(w);
320
}
Thomas Lübking's avatar
Thomas Lübking committed
321

322
void MagicLampEffect::slotWindowMinimized(EffectWindow* w)
323 324
{
    if (effects->activeFullScreenEffect())
325
        return;
326 327 328 329 330 331 332 333 334

    TimeLine &timeLine = m_animations[w];

    if (timeLine.running()) {
        timeLine.toggleDirection();
    } else {
        timeLine.setDirection(TimeLine::Forward);
        timeLine.setDuration(m_duration);
        timeLine.setEasingCurve(QEasingCurve::Linear);
335
    }
336 337

    effects->addRepaintFull();
338
}
339

340
void MagicLampEffect::slotWindowUnminimized(EffectWindow* w)
341 342
{
    if (effects->activeFullScreenEffect())
343
        return;
344 345 346 347 348 349 350 351 352

    TimeLine &timeLine = m_animations[w];

    if (timeLine.running()) {
        timeLine.toggleDirection();
    } else {
        timeLine.setDirection(TimeLine::Backward);
        timeLine.setDuration(m_duration);
        timeLine.setEasingCurve(QEasingCurve::Linear);
353
    }
354 355

    effects->addRepaintFull();
356
}
357

358 359
bool MagicLampEffect::isActive() const
{
360
    return !m_animations.isEmpty();
361 362
}

363
} // namespace