KWin
Loading...
Searching...
No Matches
slide.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2007 Lubos Lunak <l.lunak@kde.org>
6 SPDX-FileCopyrightText: 2008 Lucas Murray <lmurray@undefinedfire.com>
7 SPDX-FileCopyrightText: 2018 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
8
9 SPDX-License-Identifier: GPL-2.0-or-later
10*/
11
12// own
13#include "slide.h"
14#include "core/output.h"
16
17// KConfigSkeleton
18#include "slideconfig.h"
19
20#include <cmath>
21
22namespace KWin
23{
24
26{
27 SlideConfig::instance(effects->config());
29
31 this, &SlideEffect::desktopChanged);
33 this, &SlideEffect::desktopChanging);
34 connect(effects, QOverload<>::of(&EffectsHandler::desktopChangingCancelled),
35 this, &SlideEffect::desktopChangingCancelled);
37 this, &SlideEffect::windowAdded);
39 this, &SlideEffect::windowDeleted);
41 this, &SlideEffect::finishedSwitching);
43 this, &SlideEffect::finishedSwitching);
45 this, &SlideEffect::finishedSwitching);
47 this, &SlideEffect::finishedSwitching);
48
49 m_currentPosition = effects->desktopGridCoords(effects->currentDesktop());
50}
51
53{
54 finishedSwitching();
55}
56
61
62void SlideEffect::reconfigure(ReconfigureFlags)
63{
64 SlideConfig::self()->read();
65
66 const qreal springConstant = 300.0 / effects->animationTimeFactor();
67 const qreal dampingRatio = 1.1;
68
69 m_motionX = SpringMotion(springConstant, dampingRatio);
70 m_motionY = SpringMotion(springConstant, dampingRatio);
71
72 m_hGap = SlideConfig::horizontalGap();
73 m_vGap = SlideConfig::verticalGap();
74 m_slideBackground = SlideConfig::slideBackground();
75}
76
77inline QRegion buildClipRegion(const QPoint &pos, int w, int h)
78{
79 const QSize screenSize = effects->virtualScreenSize();
80 QRegion r = QRect(pos, screenSize);
82 r += (r & QRect(-w, 0, w, h)).translated(w, 0); // W
83 r += (r & QRect(w, 0, w, h)).translated(-w, 0); // E
84
85 r += (r & QRect(0, -h, w, h)).translated(0, h); // N
86 r += (r & QRect(0, h, w, h)).translated(0, -h); // S
87
88 r += (r & QRect(-w, -h, w, h)).translated(w, h); // NW
89 r += (r & QRect(w, -h, w, h)).translated(-w, h); // NE
90 r += (r & QRect(w, h, w, h)).translated(-w, -h); // SE
91 r += (r & QRect(-w, h, w, h)).translated(w, -h); // SW
92 }
93 return r;
94}
95
96void SlideEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
97{
98 std::chrono::milliseconds timeDelta = std::chrono::milliseconds::zero();
99 if (m_lastPresentTime.count()) {
100 timeDelta = presentTime - m_lastPresentTime;
101 }
102 m_lastPresentTime = presentTime;
103
104 if (m_state == State::ActiveAnimation) {
105 m_motionX.advance(timeDelta);
106 m_motionY.advance(timeDelta);
107 const QSize virtualSpaceSize = effects->virtualScreenSize();
108 m_currentPosition.setX(m_motionX.position() / virtualSpaceSize.width());
109 m_currentPosition.setY(m_motionY.position() / virtualSpaceSize.height());
110 }
111
112 const QList<VirtualDesktop *> desktops = effects->desktops();
113 const int w = effects->desktopGridWidth();
114 const int h = effects->desktopGridHeight();
115
116 // Clipping
117 m_paintCtx.visibleDesktops.clear();
118 m_paintCtx.visibleDesktops.reserve(4); // 4 - maximum number of visible desktops
119 bool includedX = false, includedY = false;
120 for (VirtualDesktop *desktop : desktops) {
121 const QPoint coords = effects->desktopGridCoords(desktop);
122 if (coords.x() % w == (int)(m_currentPosition.x()) % w) {
123 includedX = true;
124 } else if (coords.x() % w == ((int)(m_currentPosition.x()) + 1) % w) {
125 includedX = true;
126 }
127 if (coords.y() % h == (int)(m_currentPosition.y()) % h) {
128 includedY = true;
129 } else if (coords.y() % h == ((int)(m_currentPosition.y()) + 1) % h) {
130 includedY = true;
131 }
132
133 if (includedX && includedY) {
134 m_paintCtx.visibleDesktops << desktop;
135 }
136 }
137
139
140 effects->prePaintScreen(data, presentTime);
141}
142
143void SlideEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, Output *screen)
144{
145 m_paintCtx.wrap = effects->optionRollOverDesktops();
146 effects->paintScreen(renderTarget, viewport, mask, region, screen);
147}
148
149QPoint SlideEffect::getDrawCoords(QPointF pos, Output *screen)
150{
151 QPoint c = QPoint();
152 c.setX(pos.x() * (screen->geometry().width() + m_hGap));
153 c.setY(pos.y() * (screen->geometry().height() + m_vGap));
154 return c;
155}
156
161bool SlideEffect::isTranslated(const EffectWindow *w) const
162{
163 if (w->isOnAllDesktops()) {
164 if (w->isDesktop()) {
165 return m_slideBackground;
166 }
167 return false;
168 } else if (w == m_movingWindow) {
169 return false;
170 }
171 return true;
172}
173
177bool SlideEffect::willBePainted(const EffectWindow *w) const
178{
179 if (w->isOnAllDesktops()) {
180 return true;
181 }
182 if (w == m_movingWindow) {
183 return true;
184 }
185 for (VirtualDesktop *desktop : std::as_const(m_paintCtx.visibleDesktops)) {
186 if (w->isOnDesktop(desktop)) {
187 return true;
188 }
189 }
190 return false;
191}
192
193void SlideEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime)
194{
195 data.setTransformed();
196 effects->prePaintWindow(w, data, presentTime);
197}
198
199void SlideEffect::paintWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, QRegion region, WindowPaintData &data)
200{
201 if (!willBePainted(w)) {
202 return;
203 }
204
205 if (!isTranslated(w)) {
206 effects->paintWindow(renderTarget, viewport, w, mask, region, data);
207 return;
208 }
209
210 const int gridWidth = effects->desktopGridWidth();
211 const int gridHeight = effects->desktopGridHeight();
212
213 QPointF drawPosition = forcePositivePosition(m_currentPosition);
214 drawPosition = m_paintCtx.wrap ? constrainToDrawableRange(drawPosition) : drawPosition;
215
216 // If we're wrapping, draw the desktop in the second position.
217 const bool wrappingX = drawPosition.x() > gridWidth - 1;
218 const bool wrappingY = drawPosition.y() > gridHeight - 1;
219
220 const auto screens = effects->screens();
221
222 for (VirtualDesktop *desktop : std::as_const(m_paintCtx.visibleDesktops)) {
223 if (!w->isOnDesktop(desktop)) {
224 continue;
225 }
226 QPointF desktopTranslation = QPointF(effects->desktopGridCoords(desktop)) - drawPosition;
227 // Decide if that first desktop should be drawn at 0 or the higher position used for wrapping.
228 if (effects->desktopGridCoords(desktop).x() == 0 && wrappingX) {
229 desktopTranslation = QPointF(desktopTranslation.x() + gridWidth, desktopTranslation.y());
230 }
231 if (effects->desktopGridCoords(desktop).y() == 0 && wrappingY) {
232 desktopTranslation = QPointF(desktopTranslation.x(), desktopTranslation.y() + gridHeight);
233 }
234
235 for (Output *screen : screens) {
236 QPoint drawTranslation = getDrawCoords(desktopTranslation, screen);
237 data += drawTranslation;
238
239 const QRect screenArea = screen->geometry();
240 const QRect damage = screenArea.translated(drawTranslation).intersected(screenArea);
241
243 renderTarget, viewport, w, mask,
244 // Only paint the region that intersects the current screen and desktop.
245 region.intersected(damage),
246 data);
247
248 // Undo the translation for the next screen. I know, it hurts me too.
249 data += QPoint(-drawTranslation.x(), -drawTranslation.y());
250 }
251 }
252}
253
255{
256 if (m_state == State::ActiveAnimation && !m_motionX.isMoving() && !m_motionY.isMoving()) {
257 finishedSwitching();
258 }
259
262}
263
264/*
265 * Negative desktop positions aren't allowed.
266 */
267QPointF SlideEffect::forcePositivePosition(QPointF p) const
268{
269 if (p.x() < 0) {
270 p.setX(p.x() + std::ceil(-p.x() / effects->desktopGridWidth()) * effects->desktopGridWidth());
271 }
272 if (p.y() < 0) {
273 p.setY(p.y() + std::ceil(-p.y() / effects->desktopGridHeight()) * effects->desktopGridHeight());
274 }
275 return p;
276}
277
278bool SlideEffect::shouldElevate(const EffectWindow *w) const
279{
280 // Static docks(i.e. this effect doesn't slide docks) should be elevated
281 // so they can properly animate themselves when an user enters or leaves
282 // a virtual desktop with a window in fullscreen mode.
283 return w->isDock();
284}
285
286/*
287 * This function is called when the desktop changes.
288 * Called AFTER the gesture is released.
289 * Sets up animation to round off to the new current desktop.
290 */
291void SlideEffect::startAnimation(VirtualDesktop *old, VirtualDesktop *current, EffectWindow *movingWindow)
292{
293 if (m_state == State::Inactive) {
294 prepareSwitching();
295 }
296
297 m_state = State::ActiveAnimation;
298 m_movingWindow = movingWindow;
299
300 m_startPos = m_currentPosition;
301 m_endPos = effects->desktopGridCoords(current);
303 optimizePath();
304 }
305
306 const QSize virtualSpaceSize = effects->virtualScreenSize();
307 m_motionX.setAnchor(m_endPos.x() * virtualSpaceSize.width());
308 m_motionX.setPosition(m_startPos.x() * virtualSpaceSize.width());
309 m_motionY.setAnchor(m_endPos.y() * virtualSpaceSize.height());
310 m_motionY.setPosition(m_startPos.y() * virtualSpaceSize.height());
311
314}
315
316void SlideEffect::prepareSwitching()
317{
318 const auto windows = effects->stackingOrder();
319 m_windowData.reserve(windows.count());
320
321 for (EffectWindow *w : windows) {
322 m_windowData[w] = WindowData{
323 .visibilityRef = EffectWindowVisibleRef(w, EffectWindow::PAINT_DISABLED_BY_DESKTOP),
324 };
325
326 if (shouldElevate(w)) {
327 effects->setElevatedWindow(w, true);
328 m_elevatedWindows << w;
329 }
330 w->setData(WindowForceBackgroundContrastRole, QVariant(true));
331 w->setData(WindowForceBlurRole, QVariant(true));
332 }
333}
334
335void SlideEffect::finishedSwitching()
336{
337 if (m_state == State::Inactive) {
338 return;
339 }
340 const QList<EffectWindow *> windows = effects->stackingOrder();
341 for (EffectWindow *w : windows) {
342 w->setData(WindowForceBackgroundContrastRole, QVariant());
343 w->setData(WindowForceBlurRole, QVariant());
344 }
345
346 for (EffectWindow *w : std::as_const(m_elevatedWindows)) {
347 effects->setElevatedWindow(w, false);
348 }
349 m_elevatedWindows.clear();
350
351 m_windowData.clear();
352 m_movingWindow = nullptr;
353 m_state = State::Inactive;
354 m_lastPresentTime = std::chrono::milliseconds::zero();
356 m_currentPosition = effects->desktopGridCoords(effects->currentDesktop());
357}
358
359void SlideEffect::desktopChanged(VirtualDesktop *old, VirtualDesktop *current, EffectWindow *with)
360{
362 m_currentPosition = effects->desktopGridCoords(effects->currentDesktop());
363 return;
364 }
365
366 startAnimation(old, current, with);
367}
368
369void SlideEffect::desktopChanging(VirtualDesktop *old, QPointF desktopOffset, EffectWindow *with)
370{
372 return;
373 }
374
375 if (m_state == State::Inactive) {
376 prepareSwitching();
377 }
378
379 m_state = State::ActiveGesture;
380 m_movingWindow = with;
381
382 // Find desktop position based on animationDelta
383 QPoint gridPos = effects->desktopGridCoords(old);
384 m_currentPosition.setX(gridPos.x() + desktopOffset.x());
385 m_currentPosition.setY(gridPos.y() + desktopOffset.y());
386
388 m_currentPosition = forcePositivePosition(m_currentPosition);
389 } else {
390 m_currentPosition = moveInsideDesktopGrid(m_currentPosition);
391 }
392
395}
396
397void SlideEffect::desktopChangingCancelled()
398{
399 // If the fingers have been lifted and the current desktop didn't change, start animation
400 // to move back to the original virtual desktop.
401 if (effects->activeFullScreenEffect() == this) {
402 startAnimation(effects->currentDesktop(), effects->currentDesktop(), nullptr);
403 }
404}
405
406QPointF SlideEffect::moveInsideDesktopGrid(QPointF p)
407{
408 if (p.x() < 0) {
409 p.setX(0);
410 }
411 if (p.y() < 0) {
412 p.setY(0);
413 }
414 if (p.x() > effects->desktopGridWidth() - 1) {
415 p.setX(effects->desktopGridWidth() - 1);
416 }
417 if (p.y() > effects->desktopGridHeight() - 1) {
418 p.setY(effects->desktopGridHeight() - 1);
419 }
420 return p;
421}
422
423void SlideEffect::windowAdded(EffectWindow *w)
424{
425 if (m_state == State::Inactive) {
426 return;
427 }
428 if (shouldElevate(w)) {
429 effects->setElevatedWindow(w, true);
430 m_elevatedWindows << w;
431 }
432 w->setData(WindowForceBackgroundContrastRole, QVariant(true));
433 w->setData(WindowForceBlurRole, QVariant(true));
434
435 m_windowData[w] = WindowData{
436 .visibilityRef = EffectWindowVisibleRef(w, EffectWindow::PAINT_DISABLED_BY_DESKTOP),
437 };
438}
439
440void SlideEffect::windowDeleted(EffectWindow *w)
441{
442 if (m_state == State::Inactive) {
443 return;
444 }
445 if (w == m_movingWindow) {
446 m_movingWindow = nullptr;
447 }
448 m_elevatedWindows.removeAll(w);
449 m_windowData.remove(w);
450}
451
452/*
453 * Find the fastest path between two desktops.
454 * This function decides when it's better to wrap around the grid or not.
455 * Only call if wrapping is enabled.
456 */
457void SlideEffect::optimizePath()
458{
459 int w = effects->desktopGridWidth();
460 int h = effects->desktopGridHeight();
461
462 // Keep coordinates as low as possible
463 if (m_startPos.x() >= w && m_endPos.x() >= w) {
464 m_startPos.setX(fmod(m_startPos.x(), w));
465 m_endPos.setX(fmod(m_endPos.x(), w));
466 }
467 if (m_startPos.y() >= h && m_endPos.y() >= h) {
468 m_startPos.setY(fmod(m_startPos.y(), h));
469 m_endPos.setY(fmod(m_endPos.y(), h));
470 }
471
472 // Is there is a shorter possible route?
473 // If the x distance to be traveled is more than half the grid width, it's faster to wrap.
474 // To avoid negative coordinates, take the lower coordinate and raise.
475 if (std::abs((m_startPos.x() - m_endPos.x())) > w / 2.0) {
476 if (m_startPos.x() < m_endPos.x()) {
477 while (m_startPos.x() < m_endPos.x()) {
478 m_startPos.setX(m_startPos.x() + w);
479 }
480 } else {
481 while (m_endPos.x() < m_startPos.x()) {
482 m_endPos.setX(m_endPos.x() + w);
483 }
484 }
485 // Keep coordinates as low as possible
486 if (m_startPos.x() >= w && m_endPos.x() >= w) {
487 m_startPos.setX(fmod(m_startPos.x(), w));
488 m_endPos.setX(fmod(m_endPos.x(), w));
489 }
490 }
491
492 // Same for y
493 if (std::abs((m_endPos.y() - m_startPos.y())) > (double)h / (double)2) {
494 if (m_startPos.y() < m_endPos.y()) {
495 while (m_startPos.y() < m_endPos.y()) {
496 m_startPos.setY(m_startPos.y() + h);
497 }
498 } else {
499 while (m_endPos.y() < m_startPos.y()) {
500 m_endPos.setY(m_endPos.y() + h);
501 }
502 }
503 // Keep coordinates as low as possible
504 if (m_startPos.y() >= h && m_endPos.y() >= h) {
505 m_startPos.setY(fmod(m_startPos.y(), h));
506 m_endPos.setY(fmod(m_endPos.y(), h));
507 }
508 }
509}
510
511/*
512 * Takes the point and uses modulus to keep draw position within [0, desktopGridWidth]
513 * The render loop will draw the first desktop (0) after the last one (at position desktopGridWidth) for the wrap animation.
514 * This function finds the true fastest path, regardless of which direction the animation is already going;
515 * I was a little upset about this limitation until I realized that MacOS can't even wrap desktops :)
516 */
517QPointF SlideEffect::constrainToDrawableRange(QPointF p)
518{
519 p.setX(fmod(p.x(), effects->desktopGridWidth()));
520 p.setY(fmod(p.y(), effects->desktopGridHeight()));
521 return p;
522}
523
524} // namespace KWin
525
526#include "moc_slide.cpp"
Representation of a window used by/for Effect classes.
Q_SCRIPTABLE bool isOnDesktop(KWin::VirtualDesktop *desktop) const
void screenAdded(KWin::Output *screen)
void windowDeleted(KWin::EffectWindow *w)
bool animationsSupported() const
QPoint desktopGridCoords(VirtualDesktop *desktop) const
void paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, Output *screen)
void screenRemoved(KWin::Output *screen)
void desktopChanging(KWin::VirtualDesktop *currentDesktop, QPointF offset, KWin::EffectWindow *with)
QList< EffectWindow * > stackingOrder
Q_SCRIPTABLE void setElevatedWindow(KWin::EffectWindow *w, bool set)
void setActiveFullScreenEffect(Effect *e)
void desktopRemoved(KWin::VirtualDesktop *desktop)
void paintWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data)
KWin::VirtualDesktop * currentDesktop
void desktopAdded(KWin::VirtualDesktop *desktop)
void prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
QList< Output * > screens() const
void prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime)
void desktopChanged(KWin::VirtualDesktop *oldDesktop, KWin::VirtualDesktop *newDesktop, KWin::EffectWindow *with)
void desktopChangingCancelled()
QList< KWin::VirtualDesktop * > desktops
KSharedConfigPtr config() const
void windowAdded(KWin::EffectWindow *w)
Q_SCRIPTABLE void addRepaintFull()
Effect * activeFullScreenEffect() const
QRect geometry
Definition output.h:134
~SlideEffect() override
Definition slide.cpp:52
void reconfigure(ReconfigureFlags) override
Definition slide.cpp:62
void prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) override
Definition slide.cpp:96
void prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime) override
Definition slide.cpp:193
void postPaintScreen() override
Definition slide.cpp:254
void paintWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, QRegion region, WindowPaintData &data) override
Definition slide.cpp:199
static bool supported()
Definition slide.cpp:57
QList< VirtualDesktop * > visibleDesktops
Definition slide.h:123
void paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, Output *screen) override
Definition slide.cpp:143
void advance(std::chrono::milliseconds delta)
void setPosition(qreal position)
void setAnchor(qreal anchor)
bool isMoving() const
qreal position() const
@ PAINT_SCREEN_TRANSFORMED
Definition effect.h:562
@ PAINT_SCREEN_BACKGROUND_FIRST
Definition effect.h:571
@ ReconfigureAll
Definition effect.h:601
@ WindowForceBackgroundContrastRole
For fullscreen effects to enforce the background contrast,.
@ WindowForceBlurRole
For fullscreen effects to enforce blurring of windows,.
QRegion buildClipRegion(const QPoint &pos, int w, int h)
Definition slide.cpp:77
EffectsHandler * effects