kwin/effects/desktopgrid/desktopgrid.cpp

1460 lines
56 KiB
C++
Raw Normal View History

2020-08-02 22:22:19 +00:00
/*
KWin - the KDE window manager
This file is part of the KDE project.
2020-08-02 22:22:19 +00:00
SPDX-FileCopyrightText: 2007 Lubos Lunak <l.lunak@kde.org>
SPDX-FileCopyrightText: 2008 Lucas Murray <lmurray@undefinedfire.com>
SPDX-FileCopyrightText: 2009 Martin Gräßlin <mgraesslin@kde.org>
2020-08-02 22:22:19 +00:00
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "desktopgrid.h"
// KConfigSkeleton
#include "desktopgridconfig.h"
#include "../presentwindows/presentwindows_proxy.h"
#include "../effect_builtins.h"
#include <QAction>
#include <QApplication>
#include <KGlobalAccel>
#include <KLocalizedString>
#include <netwm_def.h>
#include <QEvent>
#include <QMouseEvent>
#include <QTimer>
#include <QVector2D>
#include <QMatrix4x4>
#include <QQuickItem>
#include <QQmlContext>
#include <KWaylandServer/surface_interface.h>
#include <cmath>
namespace KWin
{
// WARNING, TODO: This effect relies on the desktop layout being EWMH-compliant.
DesktopGridEffect::DesktopGridEffect()
2011-01-30 14:34:42 +00:00
: activated(false)
, timeline()
2011-01-30 14:34:42 +00:00
, keyboardGrab(false)
, wasWindowMove(false)
, wasWindowCopy(false)
2011-01-30 14:34:42 +00:00
, wasDesktopMove(false)
, isValidMove(false)
, windowMove(nullptr)
, windowMoveDiff()
, windowMoveElevateTimer(new QTimer(this))
Provide expected presentation time to effects Effects are given the interval between two consecutive frames. The main flaw of this approach is that if the Compositor transitions from the idle state to "active" state, i.e. when there is something to repaint, effects may see a very large interval between the last painted frame and the current. In order to address this issue, the Scene invalidates the timer that is used to measure time between consecutive frames before the Compositor is about to become idle. While this works perfectly fine with Xinerama-style rendering, with per screen rendering, determining whether the compositor is about to idle is rather a tedious task mostly because a single output can't be used for the test. Furthermore, since the Compositor schedules pointless repaints just to ensure that it's idle, it might take several attempts to figure out whether the scene timer must be invalidated if you use (true) per screen rendering. Ideally, all effects should use a timeline helper that is aware of the underlying render loop and its timings. However, this option is off the table because it will involve a lot of work to implement it. Alternative and much simpler option is to pass the expected presentation time to effects rather than time between consecutive frames. This means that effects are responsible for determining how much animation timelines have to be advanced. Typically, an effect would have to store the presentation timestamp provided in either prePaint{Screen,Window} and use it in the subsequent prePaint{Screen,Window} call to estimate the amount of time passed between the next and the last frames. Unfortunately, this is an API incompatible change. However, it shouldn't take a lot of work to port third-party binary effects, which don't use the AnimationEffect class, to the new API. On the bright side, we no longer need to be concerned about the Compositor getting idle. We do still try to determine whether the Compositor is about to idle, primarily, because the OpenGL render backend swaps buffers on present, but that will change with the ongoing compositing timing rework.
2020-11-20 15:44:04 +00:00
, lastPresentTime(std::chrono::milliseconds::zero())
, gridSize()
2011-01-30 14:34:42 +00:00
, orientation(Qt::Horizontal)
, activeCell(1, 1)
, scale()
, unscaledBorder()
, scaledSize()
, scaledOffset()
, m_proxy(nullptr)
, m_activateAction(new QAction(this))
2011-01-30 14:34:42 +00:00
{
initConfig<DesktopGridConfig>();
// Load shortcuts
QAction* a = m_activateAction;
a->setObjectName(QStringLiteral("ShowDesktopGrid"));
2011-01-30 14:34:42 +00:00
a->setText(i18n("Show Desktop Grid"));
KGlobalAccel::self()->setDefaultShortcut(a, QList<QKeySequence>() << Qt::CTRL + Qt::Key_F8);
KGlobalAccel::self()->setShortcut(a, QList<QKeySequence>() << Qt::CTRL + Qt::Key_F8);
shortcut = KGlobalAccel::self()->shortcut(a);
effects->registerGlobalShortcut(Qt::CTRL + Qt::Key_F8, a);
Add support for global touchpad swipe gestures Summary: This change adds global touchpad swipe gestures to the GlobalShortcutsManager and hooks up the swipe gestures as defined at the Plasma Affenfels sprint: * swipe up: Desktop Grid * swipe down: Present Windows * swipe left: previous virtual desktop * swipe right: next virtual desktop The main work is handled by two new classes: SwipeGesture and GestureRecognizer. This is implemented in a way that it can be extended to also recognize touch screen gestures and pinch gestures. The SwipeGesture defines what is required for the gesture to trigger. Currently this includes the minimum and maximum number of fingers participating in the gesture and the direction. The gesture gets registered in the GestureRecognizer. The events for the gesture are fed into the GestureRecognizer. It evaluates which gestures could trigger and tracks them for every update of the gesture. In the process of the gesture tracking the GestureRecognizer emits signals on the Gesture: * started: when the Gesture gets considered for a sequence * cancelled: the Gesture no longer matches the sequence * triggered: the sequence ended and the Gesture still matches The remaining changes are related to hook up the existing shortcut framework with the new touchpad gestures. The GlobalShortcutManager gained support for it, InputRedirection and EffectsHandler offer methods to register a QAction. VirtualDesktopManager, PresentWindows and DesktopGrid are adjusted to support the gesture. Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel Tags: #plasma_on_wayland Differential Revision: https://phabricator.kde.org/D5097
2017-03-18 10:00:30 +00:00
effects->registerTouchpadSwipeShortcut(SwipeDirection::Up, a);
connect(a, &QAction::triggered, this, &DesktopGridEffect::toggle);
connect(KGlobalAccel::self(), &KGlobalAccel::globalShortcutChanged, this, &DesktopGridEffect::globalShortcutChanged);
connect(effects, &EffectsHandler::windowAdded, this, &DesktopGridEffect::slotWindowAdded);
connect(effects, &EffectsHandler::windowClosed, this, &DesktopGridEffect::slotWindowClosed);
connect(effects, &EffectsHandler::windowDeleted, this, &DesktopGridEffect::slotWindowDeleted);
connect(effects, &EffectsHandler::numberDesktopsChanged, this, &DesktopGridEffect::slotNumberDesktopsChanged);
connect(effects, &EffectsHandler::windowFrameGeometryChanged, this, &DesktopGridEffect::slotWindowFrameGeometryChanged);
connect(effects, &EffectsHandler::numberScreensChanged, this, &DesktopGridEffect::setup);
connect(effects, &EffectsHandler::screenAboutToLock, this, [this]() {
setActive(false);
windowMoveElevateTimer->stop();
if (keyboardGrab) {
effects->ungrabKeyboard();
keyboardGrab = false;
}
});
windowMoveElevateTimer->setInterval(QApplication::startDragTime());
windowMoveElevateTimer->setSingleShot(true);
connect(windowMoveElevateTimer, &QTimer::timeout, this, [this]() {
effects->setElevatedWindow(windowMove, true);
wasWindowMove = true;
});
// Load all other configuration details
2011-01-30 14:34:42 +00:00
reconfigure(ReconfigureAll);
}
DesktopGridEffect::~DesktopGridEffect()
2011-01-30 14:34:42 +00:00
{
}
2011-01-30 14:34:42 +00:00
void DesktopGridEffect::reconfigure(ReconfigureFlags)
{
DesktopGridConfig::self()->read();
2011-01-30 14:34:42 +00:00
foreach (ElectricBorder border, borderActivate) {
effects->unreserveElectricBorder(border, this);
2011-01-30 14:34:42 +00:00
}
borderActivate.clear();
foreach (int i, DesktopGridConfig::borderActivate()) {
2011-01-30 14:34:42 +00:00
borderActivate.append(ElectricBorder(i));
effects->reserveElectricBorder(ElectricBorder(i), this);
2011-01-30 14:34:42 +00:00
}
// TODO: rename zoomDuration to duration
zoomDuration = animationTime(DesktopGridConfig::zoomDuration() != 0 ? DesktopGridConfig::zoomDuration() : 300);
timeline.setEasingCurve(QEasingCurve::InOutSine);
2011-01-30 14:34:42 +00:00
timeline.setDuration(zoomDuration);
border = DesktopGridConfig::borderWidth();
desktopNameAlignment = Qt::Alignment(DesktopGridConfig::desktopNameAlignment());
layoutMode = DesktopGridConfig::layoutMode();
customLayoutRows = DesktopGridConfig::customLayoutRows();
clickBehavior = DesktopGridConfig::clickBehavior();
// deactivate and activate all touch border
const QVector<ElectricBorder> relevantBorders{ElectricLeft, ElectricTop, ElectricRight, ElectricBottom};
for (auto e : relevantBorders) {
effects->unregisterTouchBorder(e, m_activateAction);
}
const auto touchBorders = DesktopGridConfig::touchBorderActivate();
for (int i : touchBorders) {
if (!relevantBorders.contains(ElectricBorder(i))) {
continue;
}
effects->registerTouchBorder(ElectricBorder(i), m_activateAction);
}
2011-01-30 14:34:42 +00:00
}
//-----------------------------------------------------------------------------
// Screen painting
Provide expected presentation time to effects Effects are given the interval between two consecutive frames. The main flaw of this approach is that if the Compositor transitions from the idle state to "active" state, i.e. when there is something to repaint, effects may see a very large interval between the last painted frame and the current. In order to address this issue, the Scene invalidates the timer that is used to measure time between consecutive frames before the Compositor is about to become idle. While this works perfectly fine with Xinerama-style rendering, with per screen rendering, determining whether the compositor is about to idle is rather a tedious task mostly because a single output can't be used for the test. Furthermore, since the Compositor schedules pointless repaints just to ensure that it's idle, it might take several attempts to figure out whether the scene timer must be invalidated if you use (true) per screen rendering. Ideally, all effects should use a timeline helper that is aware of the underlying render loop and its timings. However, this option is off the table because it will involve a lot of work to implement it. Alternative and much simpler option is to pass the expected presentation time to effects rather than time between consecutive frames. This means that effects are responsible for determining how much animation timelines have to be advanced. Typically, an effect would have to store the presentation timestamp provided in either prePaint{Screen,Window} and use it in the subsequent prePaint{Screen,Window} call to estimate the amount of time passed between the next and the last frames. Unfortunately, this is an API incompatible change. However, it shouldn't take a lot of work to port third-party binary effects, which don't use the AnimationEffect class, to the new API. On the bright side, we no longer need to be concerned about the Compositor getting idle. We do still try to determine whether the Compositor is about to idle, primarily, because the OpenGL render backend swaps buffers on present, but that will change with the ongoing compositing timing rework.
2020-11-20 15:44:04 +00:00
void DesktopGridEffect::prePaintScreen(ScreenPrePaintData& data, std::chrono::milliseconds presentTime)
2011-01-30 14:34:42 +00:00
{
Provide expected presentation time to effects Effects are given the interval between two consecutive frames. The main flaw of this approach is that if the Compositor transitions from the idle state to "active" state, i.e. when there is something to repaint, effects may see a very large interval between the last painted frame and the current. In order to address this issue, the Scene invalidates the timer that is used to measure time between consecutive frames before the Compositor is about to become idle. While this works perfectly fine with Xinerama-style rendering, with per screen rendering, determining whether the compositor is about to idle is rather a tedious task mostly because a single output can't be used for the test. Furthermore, since the Compositor schedules pointless repaints just to ensure that it's idle, it might take several attempts to figure out whether the scene timer must be invalidated if you use (true) per screen rendering. Ideally, all effects should use a timeline helper that is aware of the underlying render loop and its timings. However, this option is off the table because it will involve a lot of work to implement it. Alternative and much simpler option is to pass the expected presentation time to effects rather than time between consecutive frames. This means that effects are responsible for determining how much animation timelines have to be advanced. Typically, an effect would have to store the presentation timestamp provided in either prePaint{Screen,Window} and use it in the subsequent prePaint{Screen,Window} call to estimate the amount of time passed between the next and the last frames. Unfortunately, this is an API incompatible change. However, it shouldn't take a lot of work to port third-party binary effects, which don't use the AnimationEffect class, to the new API. On the bright side, we no longer need to be concerned about the Compositor getting idle. We do still try to determine whether the Compositor is about to idle, primarily, because the OpenGL render backend swaps buffers on present, but that will change with the ongoing compositing timing rework.
2020-11-20 15:44:04 +00:00
// The animation code assumes that the time diff cannot be 0, let's work around it.
int time;
if (lastPresentTime.count()) {
time = std::max(1, int((presentTime - lastPresentTime).count()));
} else {
time = 1;
}
lastPresentTime = presentTime;
if (timeline.currentValue() != 0 || activated || (isUsingPresentWindows() && isMotionManagerMovingWindows())) {
2011-01-30 14:34:42 +00:00
if (activated)
timeline.setCurrentTime(timeline.currentTime() + time);
else
timeline.setCurrentTime(timeline.currentTime() - time);
2011-01-30 14:34:42 +00:00
for (int i = 0; i < effects->numberOfDesktops(); i++) {
if (i == highlightedDesktop - 1)
hoverTimeline[i]->setCurrentTime(hoverTimeline[i]->currentTime() + time);
else
hoverTimeline[i]->setCurrentTime(hoverTimeline[i]->currentTime() - time);
2011-01-30 14:34:42 +00:00
}
if (isUsingPresentWindows()) {
QList<WindowMotionManager>::iterator i;
2011-01-30 14:34:42 +00:00
for (i = m_managers.begin(); i != m_managers.end(); ++i)
(*i).calculate(time);
}
// PAINT_SCREEN_BACKGROUND_FIRST is needed because screen will be actually painted more than once,
// so with normal screen painting second screen paint would erase parts of the first paint
if (timeline.currentValue() != 0 || (isUsingPresentWindows() && isMotionManagerMovingWindows()))
data.mask |= PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_BACKGROUND_FIRST;
if (!activated && timeline.currentValue() == 0 && !(isUsingPresentWindows() && isMotionManagerMovingWindows()))
finish();
}
for (auto const &w : effects->stackingOrder()) {
w->setData(WindowForceBlurRole, QVariant(true));
}
Provide expected presentation time to effects Effects are given the interval between two consecutive frames. The main flaw of this approach is that if the Compositor transitions from the idle state to "active" state, i.e. when there is something to repaint, effects may see a very large interval between the last painted frame and the current. In order to address this issue, the Scene invalidates the timer that is used to measure time between consecutive frames before the Compositor is about to become idle. While this works perfectly fine with Xinerama-style rendering, with per screen rendering, determining whether the compositor is about to idle is rather a tedious task mostly because a single output can't be used for the test. Furthermore, since the Compositor schedules pointless repaints just to ensure that it's idle, it might take several attempts to figure out whether the scene timer must be invalidated if you use (true) per screen rendering. Ideally, all effects should use a timeline helper that is aware of the underlying render loop and its timings. However, this option is off the table because it will involve a lot of work to implement it. Alternative and much simpler option is to pass the expected presentation time to effects rather than time between consecutive frames. This means that effects are responsible for determining how much animation timelines have to be advanced. Typically, an effect would have to store the presentation timestamp provided in either prePaint{Screen,Window} and use it in the subsequent prePaint{Screen,Window} call to estimate the amount of time passed between the next and the last frames. Unfortunately, this is an API incompatible change. However, it shouldn't take a lot of work to port third-party binary effects, which don't use the AnimationEffect class, to the new API. On the bright side, we no longer need to be concerned about the Compositor getting idle. We do still try to determine whether the Compositor is about to idle, primarily, because the OpenGL render backend swaps buffers on present, but that will change with the ongoing compositing timing rework.
2020-11-20 15:44:04 +00:00
effects->prePaintScreen(data, presentTime);
2011-01-30 14:34:42 +00:00
}
void DesktopGridEffect::paintScreen(int mask, const QRegion &region, ScreenPaintData& data)
2011-01-30 14:34:42 +00:00
{
if (timeline.currentValue() == 0 && !isUsingPresentWindows()) {
2011-01-30 14:34:42 +00:00
effects->paintScreen(mask, region, data);
return;
2011-01-30 14:34:42 +00:00
}
for (int desktop = 1; desktop <= effects->numberOfDesktops(); desktop++) {
ScreenPaintData d = data;
paintingDesktop = desktop;
2011-01-30 14:34:42 +00:00
effects->paintScreen(mask, region, d);
}
// paint the add desktop button
for (EffectQuickScene *view : m_desktopButtons) {
view->rootItem()->setOpacity(timeline.currentValue());
effects->renderEffectQuickView(view);
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
if (isUsingPresentWindows() && windowMove && wasWindowMove) {
// the moving window has to be painted on top of all desktops
QPoint diff = cursorPos() - m_windowMoveStartPoint;
2011-01-30 14:34:42 +00:00
QRect geo = m_windowMoveGeometry.translated(diff);
WindowPaintData d(windowMove, data.projectionMatrix());
d *= QVector2D((qreal)geo.width() / (qreal)windowMove->width(), (qreal)geo.height() / (qreal)windowMove->height());
2013-03-15 14:51:36 +00:00
d += QPoint(geo.left() - windowMove->x(), geo.top() - windowMove->y());
2011-01-30 14:34:42 +00:00
effects->drawWindow(windowMove, PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_LANCZOS, infiniteRegion(), d);
}
2011-01-30 14:34:42 +00:00
if (desktopNameAlignment) {
for (int screen = 0; screen < effects->numScreens(); screen++) {
QRect screenGeom = effects->clientArea(ScreenArea, screen, 0);
int desktop = 1;
2011-01-30 14:34:42 +00:00
foreach (EffectFrame * frame, desktopNames) {
QPointF posTL(scalePos(screenGeom.topLeft(), desktop, screen));
QPointF posBR(scalePos(screenGeom.bottomRight(), desktop, screen));
QRect textArea(posTL.x(), posTL.y(), posBR.x() - posTL.x(), posBR.y() - posTL.y());
textArea.adjust(textArea.width() / 10, textArea.height() / 10,
-textArea.width() / 10, -textArea.height() / 10);
int x, y;
2011-01-30 14:34:42 +00:00
if (desktopNameAlignment & Qt::AlignLeft)
x = textArea.x();
2011-01-30 14:34:42 +00:00
else if (desktopNameAlignment & Qt::AlignRight)
x = textArea.right();
else
x = textArea.center().x();
2011-01-30 14:34:42 +00:00
if (desktopNameAlignment & Qt::AlignTop)
y = textArea.y();
2011-01-30 14:34:42 +00:00
else if (desktopNameAlignment & Qt::AlignBottom)
y = textArea.bottom();
else
y = textArea.center().y();
2011-01-30 14:34:42 +00:00
frame->setPosition(QPoint(x, y));
frame->render(region, timeline.currentValue(), 0.7);
++desktop;
}
}
}
2011-01-30 14:34:42 +00:00
}
void DesktopGridEffect::postPaintScreen()
2011-01-30 14:34:42 +00:00
{
Provide expected presentation time to effects Effects are given the interval between two consecutive frames. The main flaw of this approach is that if the Compositor transitions from the idle state to "active" state, i.e. when there is something to repaint, effects may see a very large interval between the last painted frame and the current. In order to address this issue, the Scene invalidates the timer that is used to measure time between consecutive frames before the Compositor is about to become idle. While this works perfectly fine with Xinerama-style rendering, with per screen rendering, determining whether the compositor is about to idle is rather a tedious task mostly because a single output can't be used for the test. Furthermore, since the Compositor schedules pointless repaints just to ensure that it's idle, it might take several attempts to figure out whether the scene timer must be invalidated if you use (true) per screen rendering. Ideally, all effects should use a timeline helper that is aware of the underlying render loop and its timings. However, this option is off the table because it will involve a lot of work to implement it. Alternative and much simpler option is to pass the expected presentation time to effects rather than time between consecutive frames. This means that effects are responsible for determining how much animation timelines have to be advanced. Typically, an effect would have to store the presentation timestamp provided in either prePaint{Screen,Window} and use it in the subsequent prePaint{Screen,Window} call to estimate the amount of time passed between the next and the last frames. Unfortunately, this is an API incompatible change. However, it shouldn't take a lot of work to port third-party binary effects, which don't use the AnimationEffect class, to the new API. On the bright side, we no longer need to be concerned about the Compositor getting idle. We do still try to determine whether the Compositor is about to idle, primarily, because the OpenGL render backend swaps buffers on present, but that will change with the ongoing compositing timing rework.
2020-11-20 15:44:04 +00:00
bool resetLastPresentTime = true;
if (activated ? timeline.currentValue() != 1 : timeline.currentValue() != 0) {
effects->addRepaintFull(); // Repaint during zoom
Provide expected presentation time to effects Effects are given the interval between two consecutive frames. The main flaw of this approach is that if the Compositor transitions from the idle state to "active" state, i.e. when there is something to repaint, effects may see a very large interval between the last painted frame and the current. In order to address this issue, the Scene invalidates the timer that is used to measure time between consecutive frames before the Compositor is about to become idle. While this works perfectly fine with Xinerama-style rendering, with per screen rendering, determining whether the compositor is about to idle is rather a tedious task mostly because a single output can't be used for the test. Furthermore, since the Compositor schedules pointless repaints just to ensure that it's idle, it might take several attempts to figure out whether the scene timer must be invalidated if you use (true) per screen rendering. Ideally, all effects should use a timeline helper that is aware of the underlying render loop and its timings. However, this option is off the table because it will involve a lot of work to implement it. Alternative and much simpler option is to pass the expected presentation time to effects rather than time between consecutive frames. This means that effects are responsible for determining how much animation timelines have to be advanced. Typically, an effect would have to store the presentation timestamp provided in either prePaint{Screen,Window} and use it in the subsequent prePaint{Screen,Window} call to estimate the amount of time passed between the next and the last frames. Unfortunately, this is an API incompatible change. However, it shouldn't take a lot of work to port third-party binary effects, which don't use the AnimationEffect class, to the new API. On the bright side, we no longer need to be concerned about the Compositor getting idle. We do still try to determine whether the Compositor is about to idle, primarily, because the OpenGL render backend swaps buffers on present, but that will change with the ongoing compositing timing rework.
2020-11-20 15:44:04 +00:00
resetLastPresentTime = false;
}
if (isUsingPresentWindows() && isMotionManagerMovingWindows()) {
effects->addRepaintFull();
Provide expected presentation time to effects Effects are given the interval between two consecutive frames. The main flaw of this approach is that if the Compositor transitions from the idle state to "active" state, i.e. when there is something to repaint, effects may see a very large interval between the last painted frame and the current. In order to address this issue, the Scene invalidates the timer that is used to measure time between consecutive frames before the Compositor is about to become idle. While this works perfectly fine with Xinerama-style rendering, with per screen rendering, determining whether the compositor is about to idle is rather a tedious task mostly because a single output can't be used for the test. Furthermore, since the Compositor schedules pointless repaints just to ensure that it's idle, it might take several attempts to figure out whether the scene timer must be invalidated if you use (true) per screen rendering. Ideally, all effects should use a timeline helper that is aware of the underlying render loop and its timings. However, this option is off the table because it will involve a lot of work to implement it. Alternative and much simpler option is to pass the expected presentation time to effects rather than time between consecutive frames. This means that effects are responsible for determining how much animation timelines have to be advanced. Typically, an effect would have to store the presentation timestamp provided in either prePaint{Screen,Window} and use it in the subsequent prePaint{Screen,Window} call to estimate the amount of time passed between the next and the last frames. Unfortunately, this is an API incompatible change. However, it shouldn't take a lot of work to port third-party binary effects, which don't use the AnimationEffect class, to the new API. On the bright side, we no longer need to be concerned about the Compositor getting idle. We do still try to determine whether the Compositor is about to idle, primarily, because the OpenGL render backend swaps buffers on present, but that will change with the ongoing compositing timing rework.
2020-11-20 15:44:04 +00:00
resetLastPresentTime = false;
}
2011-01-30 14:34:42 +00:00
if (activated) {
for (int i = 0; i < effects->numberOfDesktops(); i++) {
if (hoverTimeline[i]->currentValue() != 0.0 && hoverTimeline[i]->currentValue() != 1.0) {
2011-01-30 14:34:42 +00:00
// Repaint during soft highlighting
effects->addRepaintFull();
Provide expected presentation time to effects Effects are given the interval between two consecutive frames. The main flaw of this approach is that if the Compositor transitions from the idle state to "active" state, i.e. when there is something to repaint, effects may see a very large interval between the last painted frame and the current. In order to address this issue, the Scene invalidates the timer that is used to measure time between consecutive frames before the Compositor is about to become idle. While this works perfectly fine with Xinerama-style rendering, with per screen rendering, determining whether the compositor is about to idle is rather a tedious task mostly because a single output can't be used for the test. Furthermore, since the Compositor schedules pointless repaints just to ensure that it's idle, it might take several attempts to figure out whether the scene timer must be invalidated if you use (true) per screen rendering. Ideally, all effects should use a timeline helper that is aware of the underlying render loop and its timings. However, this option is off the table because it will involve a lot of work to implement it. Alternative and much simpler option is to pass the expected presentation time to effects rather than time between consecutive frames. This means that effects are responsible for determining how much animation timelines have to be advanced. Typically, an effect would have to store the presentation timestamp provided in either prePaint{Screen,Window} and use it in the subsequent prePaint{Screen,Window} call to estimate the amount of time passed between the next and the last frames. Unfortunately, this is an API incompatible change. However, it shouldn't take a lot of work to port third-party binary effects, which don't use the AnimationEffect class, to the new API. On the bright side, we no longer need to be concerned about the Compositor getting idle. We do still try to determine whether the Compositor is about to idle, primarily, because the OpenGL render backend swaps buffers on present, but that will change with the ongoing compositing timing rework.
2020-11-20 15:44:04 +00:00
resetLastPresentTime = false;
break;
}
}
}
Provide expected presentation time to effects Effects are given the interval between two consecutive frames. The main flaw of this approach is that if the Compositor transitions from the idle state to "active" state, i.e. when there is something to repaint, effects may see a very large interval between the last painted frame and the current. In order to address this issue, the Scene invalidates the timer that is used to measure time between consecutive frames before the Compositor is about to become idle. While this works perfectly fine with Xinerama-style rendering, with per screen rendering, determining whether the compositor is about to idle is rather a tedious task mostly because a single output can't be used for the test. Furthermore, since the Compositor schedules pointless repaints just to ensure that it's idle, it might take several attempts to figure out whether the scene timer must be invalidated if you use (true) per screen rendering. Ideally, all effects should use a timeline helper that is aware of the underlying render loop and its timings. However, this option is off the table because it will involve a lot of work to implement it. Alternative and much simpler option is to pass the expected presentation time to effects rather than time between consecutive frames. This means that effects are responsible for determining how much animation timelines have to be advanced. Typically, an effect would have to store the presentation timestamp provided in either prePaint{Screen,Window} and use it in the subsequent prePaint{Screen,Window} call to estimate the amount of time passed between the next and the last frames. Unfortunately, this is an API incompatible change. However, it shouldn't take a lot of work to port third-party binary effects, which don't use the AnimationEffect class, to the new API. On the bright side, we no longer need to be concerned about the Compositor getting idle. We do still try to determine whether the Compositor is about to idle, primarily, because the OpenGL render backend swaps buffers on present, but that will change with the ongoing compositing timing rework.
2020-11-20 15:44:04 +00:00
if (resetLastPresentTime) {
lastPresentTime = std::chrono::milliseconds::zero();
}
for (auto &w : effects->stackingOrder()) {
w->setData(WindowForceBlurRole, QVariant());
}
2011-01-30 14:34:42 +00:00
effects->postPaintScreen();
}
//-----------------------------------------------------------------------------
// Window painting
Provide expected presentation time to effects Effects are given the interval between two consecutive frames. The main flaw of this approach is that if the Compositor transitions from the idle state to "active" state, i.e. when there is something to repaint, effects may see a very large interval between the last painted frame and the current. In order to address this issue, the Scene invalidates the timer that is used to measure time between consecutive frames before the Compositor is about to become idle. While this works perfectly fine with Xinerama-style rendering, with per screen rendering, determining whether the compositor is about to idle is rather a tedious task mostly because a single output can't be used for the test. Furthermore, since the Compositor schedules pointless repaints just to ensure that it's idle, it might take several attempts to figure out whether the scene timer must be invalidated if you use (true) per screen rendering. Ideally, all effects should use a timeline helper that is aware of the underlying render loop and its timings. However, this option is off the table because it will involve a lot of work to implement it. Alternative and much simpler option is to pass the expected presentation time to effects rather than time between consecutive frames. This means that effects are responsible for determining how much animation timelines have to be advanced. Typically, an effect would have to store the presentation timestamp provided in either prePaint{Screen,Window} and use it in the subsequent prePaint{Screen,Window} call to estimate the amount of time passed between the next and the last frames. Unfortunately, this is an API incompatible change. However, it shouldn't take a lot of work to port third-party binary effects, which don't use the AnimationEffect class, to the new API. On the bright side, we no longer need to be concerned about the Compositor getting idle. We do still try to determine whether the Compositor is about to idle, primarily, because the OpenGL render backend swaps buffers on present, but that will change with the ongoing compositing timing rework.
2020-11-20 15:44:04 +00:00
void DesktopGridEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, std::chrono::milliseconds presentTime)
2011-01-30 14:34:42 +00:00
{
if (timeline.currentValue() != 0 || (isUsingPresentWindows() && isMotionManagerMovingWindows())) {
2011-01-30 14:34:42 +00:00
if (w->isOnDesktop(paintingDesktop)) {
w->enablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP);
if (w->isMinimized() && isUsingPresentWindows())
w->enablePainting(EffectWindow::PAINT_DISABLED_BY_MINIMIZE);
data.mask |= PAINT_WINDOW_TRANSFORMED;
// Split windows at screen edges
2011-01-30 14:34:42 +00:00
for (int screen = 0; screen < effects->numScreens(); screen++) {
QRect screenGeom = effects->clientArea(ScreenArea, screen, 0);
if (w->x() < screenGeom.x())
data.quads = data.quads.splitAtX(screenGeom.x() - w->x());
if (w->x() + w->width() > screenGeom.x() + screenGeom.width())
data.quads = data.quads.splitAtX(screenGeom.x() + screenGeom.width() - w->x());
if (w->y() < screenGeom.y())
data.quads = data.quads.splitAtY(screenGeom.y() - w->y());
if (w->y() + w->height() > screenGeom.y() + screenGeom.height())
data.quads = data.quads.splitAtY(screenGeom.y() + screenGeom.height() - w->y());
}
2011-01-30 14:34:42 +00:00
if (windowMove && wasWindowMove && windowMove->findModal() == w)
w->disablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP);
} else
w->disablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP);
}
Provide expected presentation time to effects Effects are given the interval between two consecutive frames. The main flaw of this approach is that if the Compositor transitions from the idle state to "active" state, i.e. when there is something to repaint, effects may see a very large interval between the last painted frame and the current. In order to address this issue, the Scene invalidates the timer that is used to measure time between consecutive frames before the Compositor is about to become idle. While this works perfectly fine with Xinerama-style rendering, with per screen rendering, determining whether the compositor is about to idle is rather a tedious task mostly because a single output can't be used for the test. Furthermore, since the Compositor schedules pointless repaints just to ensure that it's idle, it might take several attempts to figure out whether the scene timer must be invalidated if you use (true) per screen rendering. Ideally, all effects should use a timeline helper that is aware of the underlying render loop and its timings. However, this option is off the table because it will involve a lot of work to implement it. Alternative and much simpler option is to pass the expected presentation time to effects rather than time between consecutive frames. This means that effects are responsible for determining how much animation timelines have to be advanced. Typically, an effect would have to store the presentation timestamp provided in either prePaint{Screen,Window} and use it in the subsequent prePaint{Screen,Window} call to estimate the amount of time passed between the next and the last frames. Unfortunately, this is an API incompatible change. However, it shouldn't take a lot of work to port third-party binary effects, which don't use the AnimationEffect class, to the new API. On the bright side, we no longer need to be concerned about the Compositor getting idle. We do still try to determine whether the Compositor is about to idle, primarily, because the OpenGL render backend swaps buffers on present, but that will change with the ongoing compositing timing rework.
2020-11-20 15:44:04 +00:00
effects->prePaintWindow(w, data, presentTime);
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
void DesktopGridEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data)
{
if (timeline.currentValue() != 0 || (isUsingPresentWindows() && isMotionManagerMovingWindows())) {
if (isUsingPresentWindows() && w == windowMove && wasWindowMove &&
((!wasWindowCopy && sourceDesktop == paintingDesktop) ||
(sourceDesktop != highlightedDesktop && highlightedDesktop == paintingDesktop))) {
return; // will be painted on top of all other windows
2011-01-30 14:34:42 +00:00
}
qreal xScale = data.xScale();
qreal yScale = data.yScale();
data.multiplyBrightness(1.0 - (0.3 * (1.0 - hoverTimeline[paintingDesktop - 1]->currentValue())));
2011-01-30 14:34:42 +00:00
for (int screen = 0; screen < effects->numScreens(); screen++) {
QRect screenGeom = effects->clientArea(ScreenArea, screen, 0);
QRectF transformedGeo = w->geometry();
// Display all quads on the same screen on the same pass
WindowQuadList screenQuads;
bool quadsAdded = false;
2011-01-30 14:34:42 +00:00
if (isUsingPresentWindows()) {
WindowMotionManager& manager = m_managers[(paintingDesktop-1)*(effects->numScreens())+screen ];
if (manager.isManaging(w)) {
foreach (const WindowQuad & quad, data.quads)
screenQuads.append(quad);
2011-01-30 14:34:42 +00:00
transformedGeo = manager.transformedGeometry(w);
quadsAdded = true;
if (!manager.areWindowsMoving() && timeline.currentValue() == 1.0)
mask |= PAINT_WINDOW_LANCZOS;
2011-01-30 14:34:42 +00:00
} else if (w->screen() != screen)
quadsAdded = true; // we don't want parts of overlapping windows on the other screen
if (w->isDesktop())
quadsAdded = false;
2011-01-30 14:34:42 +00:00
}
if (!quadsAdded) {
foreach (const WindowQuad & quad, data.quads) {
QRect quadRect(
w->x() + quad.left(), w->y() + quad.top(),
quad.right() - quad.left(), quad.bottom() - quad.top()
2011-01-30 14:34:42 +00:00
);
if (quadRect.intersects(screenGeom))
screenQuads.append(quad);
}
2011-01-30 14:34:42 +00:00
}
if (screenQuads.isEmpty())
continue; // Nothing is being displayed, don't bother
WindowPaintData d = data;
d.quads = screenQuads;
2011-01-30 14:34:42 +00:00
QPointF newPos = scalePos(transformedGeo.topLeft().toPoint(), paintingDesktop, screen);
double progress = timeline.currentValue();
d.setXScale(interpolate(1, xScale * scale[screen] * (float)transformedGeo.width() / (float)w->geometry().width(), progress));
d.setYScale(interpolate(1, yScale * scale[screen] * (float)transformedGeo.height() / (float)w->geometry().height(), progress));
d += QPoint(qRound(newPos.x() - w->x()), qRound(newPos.y() - w->y()));
2011-01-30 14:34:42 +00:00
if (isUsingPresentWindows() && (w->isDock() || w->isSkipSwitcher())) {
// fade out panels if present windows is used
d.multiplyOpacity((1.0 - timeline.currentValue()));
2011-01-30 14:34:42 +00:00
}
if (isUsingPresentWindows() && w->isMinimized()) {
d.multiplyOpacity(timeline.currentValue());
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
if (effects->compositingType() == XRenderCompositing) {
// More exact clipping as XRender displays the entire window instead of just the quad
QPointF screenPosF = scalePos(screenGeom.topLeft(), paintingDesktop).toPoint();
QPoint screenPos(
2011-01-30 14:34:42 +00:00
qRound(screenPosF.x()),
qRound(screenPosF.y())
);
QSize screenSize(
2011-01-30 14:34:42 +00:00
qRound(interpolate(screenGeom.width(), scaledSize[screen].width(), progress)),
qRound(interpolate(screenGeom.height(), scaledSize[screen].height(), progress))
);
PaintClipper pc(effects->clientArea(ScreenArea, screen, 0) & QRect(screenPos, screenSize));
effects->paintWindow(w, mask, region, d);
} else {
if (w->isDesktop() && timeline.currentValue() == 1.0) {
// desktop windows are not in a motion manager and can always be rendered with
// lanczos sampling except for animations
mask |= PAINT_WINDOW_LANCZOS;
}
effects->paintWindow(w, mask, effects->clientArea(ScreenArea, screen, 0), d);
2011-01-30 14:34:42 +00:00
}
}
2011-01-30 14:34:42 +00:00
} else
effects->paintWindow(w, mask, region, data);
}
//-----------------------------------------------------------------------------
// User interaction
void DesktopGridEffect::slotWindowAdded(EffectWindow* w)
2011-01-30 14:34:42 +00:00
{
if (!activated)
return;
2011-01-30 14:34:42 +00:00
if (isUsingPresentWindows()) {
if (!isRelevantWithPresentWindows(w))
return; // don't add
foreach (const int i, desktopList(w)) {
WindowMotionManager& manager = m_managers[ i*effects->numScreens()+w->screen()];
2011-01-30 14:34:42 +00:00
manager.manage(w);
m_proxy->calculateWindowTransformations(manager.managedWindows(), w->screen(), manager);
}
}
2011-01-30 14:34:42 +00:00
effects->addRepaintFull();
}
void DesktopGridEffect::slotWindowClosed(EffectWindow* w)
2011-01-30 14:34:42 +00:00
{
if (!activated && timeline.currentValue() == 0)
return;
2011-01-30 14:34:42 +00:00
if (w == windowMove) {
effects->setElevatedWindow(windowMove, false);
windowMove = nullptr;
2011-01-30 14:34:42 +00:00
}
if (isUsingPresentWindows()) {
foreach (const int i, desktopList(w)) {
WindowMotionManager& manager = m_managers[i*effects->numScreens()+w->screen()];
manager.unmanage(w);
m_proxy->calculateWindowTransformations(manager.managedWindows(), w->screen(), manager);
}
2011-01-30 14:34:42 +00:00
}
effects->addRepaintFull();
}
void DesktopGridEffect::slotWindowDeleted(EffectWindow* w)
2011-01-30 14:34:42 +00:00
{
if (w == windowMove)
windowMove = nullptr;
if (isUsingPresentWindows()) {
for (QList<WindowMotionManager>::iterator it = m_managers.begin(),
end = m_managers.end(); it != end; ++it) {
it->unmanage(w);
}
}
2011-01-30 14:34:42 +00:00
}
void DesktopGridEffect::slotWindowFrameGeometryChanged(EffectWindow* w, const QRect& old)
2011-01-30 14:34:42 +00:00
{
Q_UNUSED(old)
if (!activated)
return;
2011-01-30 14:34:42 +00:00
if (w == windowMove && wasWindowMove)
return;
2011-01-30 14:34:42 +00:00
if (isUsingPresentWindows()) {
foreach (const int i, desktopList(w)) {
WindowMotionManager& manager = m_managers[i*effects->numScreens()+w->screen()];
m_proxy->calculateWindowTransformations(manager.managedWindows(), w->screen(), manager);
}
}
2011-01-30 14:34:42 +00:00
}
void DesktopGridEffect::windowInputMouseEvent(QEvent* e)
2011-01-30 14:34:42 +00:00
{
if ((e->type() != QEvent::MouseMove
&& e->type() != QEvent::MouseButtonPress
&& e->type() != QEvent::MouseButtonRelease)
|| timeline.currentValue() != 1) // Block user input during animations
return;
2011-01-30 14:34:42 +00:00
QMouseEvent* me = static_cast< QMouseEvent* >(e);
if (!(wasWindowMove || wasDesktopMove)) {
for (EffectQuickScene *view : m_desktopButtons) {
view->forwardMouseEvent(me);
if (e->isAccepted()) {
return;
}
}
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
if (e->type() == QEvent::MouseMove) {
int d = posToDesktop(me->pos());
if (windowMove != nullptr &&
(me->pos() - dragStartPos).manhattanLength() > QApplication::startDragDistance()) {
2011-01-30 14:34:42 +00:00
// Handle window moving
if (windowMoveElevateTimer->isActive()) { // Window started moving, but is not elevated yet!
windowMoveElevateTimer->stop();
effects->setElevatedWindow(windowMove, true);
}
2011-01-30 14:34:42 +00:00
if (!wasWindowMove) { // Activate on move
if (isUsingPresentWindows()) {
foreach (const int i, desktopList(windowMove)) {
2011-01-30 14:34:42 +00:00
WindowMotionManager& manager = m_managers[(i)*(effects->numScreens()) + windowMove->screen()];
if ((i + 1) == sourceDesktop) {
2011-01-30 14:34:42 +00:00
const QRectF transformedGeo = manager.transformedGeometry(windowMove);
const QPointF pos = scalePos(transformedGeo.topLeft().toPoint(), sourceDesktop, windowMove->screen());
2011-01-30 14:34:42 +00:00
const QSize size(scale[windowMove->screen()] *(float)transformedGeo.width(),
scale[windowMove->screen()] *(float)transformedGeo.height());
m_windowMoveGeometry = QRect(pos.toPoint(), size);
m_windowMoveStartPoint = me->pos();
}
2011-01-30 14:34:42 +00:00
manager.unmanage(windowMove);
if (EffectWindow* modal = windowMove->findModal()) {
if (manager.isManaging(modal))
manager.unmanage(modal);
}
m_proxy->calculateWindowTransformations(manager.managedWindows(), windowMove->screen(), manager);
}
wasWindowMove = true;
}
2011-01-30 14:34:42 +00:00
}
if (windowMove->isMovable() && !isUsingPresentWindows()) {
wasWindowMove = true;
2011-01-30 14:34:42 +00:00
int screen = effects->screenNumber(me->pos());
effects->moveWindow(windowMove, unscalePos(me->pos(), nullptr) + windowMoveDiff, true, 1.0 / scale[screen]);
}
if (wasWindowMove) {
if (effects->waylandDisplay() && (me->modifiers() & Qt::ControlModifier)) {
wasWindowCopy = true;
effects->defineCursor(Qt::DragCopyCursor);
} else {
wasWindowCopy = false;
effects->defineCursor(Qt::ClosedHandCursor);
}
if (d != highlightedDesktop) {
auto desktops = windowMove->desktops();
if (!desktops.contains(d)) {
desktops.append(d);
}
if (highlightedDesktop != sourceDesktop || !wasWindowCopy) {
desktops.removeOne(highlightedDesktop);
}
effects->windowToDesktops(windowMove, desktops);
const int screen = effects->screenNumber(me->pos());
if (screen != windowMove->screen())
effects->windowToScreen(windowMove, screen);
}
effects->addRepaintFull();
}
2011-01-30 14:34:42 +00:00
} else if ((me->buttons() & Qt::LeftButton) && !wasDesktopMove &&
(me->pos() - dragStartPos).manhattanLength() > QApplication::startDragDistance()) {
2011-01-30 14:34:42 +00:00
wasDesktopMove = true;
effects->defineCursor(Qt::ClosedHandCursor);
2011-01-30 14:34:42 +00:00
}
if (d != highlightedDesktop) { // Highlight desktop
if ((me->buttons() & Qt::LeftButton) && isValidMove && !wasWindowMove && d <= effects->numberOfDesktops()) {
EffectWindowList windows = effects->stackingOrder();
EffectWindowList stack[3];
for (EffectWindowList::const_iterator it = windows.constBegin(),
end = windows.constEnd(); it != end; ++it) {
EffectWindow *w = const_cast<EffectWindow*>(*it); // we're not really touching it here but below
2011-01-30 14:34:42 +00:00
if (w->isOnAllDesktops())
continue;
if (w->isOnDesktop(highlightedDesktop))
stack[0] << w;
if (w->isOnDesktop(d))
stack[1] << w;
if (w->isOnDesktop(m_originalMovingDesktop))
stack[2] << w;
}
const int desks[4] = {highlightedDesktop, d, m_originalMovingDesktop, highlightedDesktop};
for (int i = 0; i < 3; ++i ) {
if (desks[i] == desks[i+1])
continue;
foreach (EffectWindow *w, stack[i]) {
auto desktops = w->desktops();
desktops.removeOne(desks[i]);
desktops.append(desks[i+1]);
effects->windowToDesktops(w, desktops);
2011-01-30 14:34:42 +00:00
if (isUsingPresentWindows()) {
m_managers[(desks[i]-1)*(effects->numScreens()) + w->screen()].unmanage(w);
m_managers[(desks[i+1]-1)*(effects->numScreens()) + w->screen()].manage(w);
}
}
2011-01-30 14:34:42 +00:00
}
if (isUsingPresentWindows()) {
for (int i = 0; i < effects->numScreens(); i++) {
for (int j = 0; j < 3; ++j) {
WindowMotionManager& manager = m_managers[(desks[j]-1)*(effects->numScreens()) + i ];
m_proxy->calculateWindowTransformations(manager.managedWindows(), i, manager);
}
}
effects->addRepaintFull();
}
}
2011-01-30 14:34:42 +00:00
setHighlightedDesktop(d);
}
2011-01-30 14:34:42 +00:00
}
if (e->type() == QEvent::MouseButtonPress) {
if (me->buttons() == Qt::LeftButton) {
isValidMove = true;
dragStartPos = me->pos();
sourceDesktop = posToDesktop(me->pos());
bool isDesktop = (me->modifiers() & Qt::ShiftModifier);
EffectWindow* w = isDesktop ? nullptr : windowAt(me->pos());
if (w != nullptr)
isDesktop = w->isDesktop();
if (isDesktop)
m_originalMovingDesktop = posToDesktop(me->pos());
else
m_originalMovingDesktop = 0;
if (w != nullptr && !w->isDesktop() && (w->isMovable() || w->isMovableAcrossScreens() || isUsingPresentWindows())) {
2011-01-30 14:34:42 +00:00
// Prepare it for moving
windowMoveDiff = w->pos() - unscalePos(me->pos(), nullptr);
windowMove = w;
windowMoveElevateTimer->start();
}
} else if ((me->buttons() == Qt::MiddleButton || me->buttons() == Qt::RightButton) && windowMove == nullptr) {
2011-01-30 14:34:42 +00:00
EffectWindow* w = windowAt(me->pos());
if (w && w->isDesktop()) {
w = nullptr;
}
if (w != nullptr) {
const int desktop = posToDesktop(me->pos());
2011-01-30 14:34:42 +00:00
if (w->isOnAllDesktops()) {
effects->windowToDesktop(w, desktop);
} else {
effects->windowToDesktop(w, NET::OnAllDesktops);
}
const bool isOnAllDesktops = w->isOnAllDesktops();
if (isUsingPresentWindows()) {
for (int i = 0; i < effects->numberOfDesktops(); i++) {
if (i != desktop - 1) {
WindowMotionManager& manager = m_managers[ i*effects->numScreens() + w->screen()];
if (isOnAllDesktops)
2011-01-30 14:34:42 +00:00
manager.manage(w);
else
manager.unmanage(w);
m_proxy->calculateWindowTransformations(manager.managedWindows(), w->screen(), manager);
}
}
}
2011-01-30 14:34:42 +00:00
effects->addRepaintFull();
}
}
2011-01-30 14:34:42 +00:00
}
if (e->type() == QEvent::MouseButtonRelease && me->button() == Qt::LeftButton) {
isValidMove = false;
if (windowMove) {
if (windowMoveElevateTimer->isActive()) {
// no need to elevate window, it was just a click
windowMoveElevateTimer->stop();
}
if (clickBehavior == SwitchDesktopAndActivateWindow || wasWindowMove) {
// activate window if relevant config is set or window was moved
effects->activateWindow(windowMove);
}
}
if (wasWindowMove || wasDesktopMove) { // reset pointer
effects->defineCursor(Qt::PointingHandCursor);
} else { // click -> exit
const int desk = posToDesktop(me->pos());
if (desk > effects->numberOfDesktops())
return; // don't quit when missing desktop
setCurrentDesktop(desk);
2011-01-30 14:34:42 +00:00
setActive(false);
}
if (windowMove) {
if (wasWindowMove && isUsingPresentWindows()) {
const int targetDesktop = posToDesktop(cursorPos());
foreach (const int i, desktopList(windowMove)) {
WindowMotionManager& manager = m_managers[(i)*(effects->numScreens()) + windowMove->screen()];
manager.manage(windowMove);
if (EffectWindow* modal = windowMove->findModal())
manager.manage(modal);
if (i + 1 == targetDesktop) {
// for the desktop the window is dropped on, we use the current geometry
manager.setTransformedGeometry(windowMove, moveGeometryToDesktop(targetDesktop));
}
m_proxy->calculateWindowTransformations(manager.managedWindows(), windowMove->screen(), manager);
}
effects->addRepaintFull();
}
2011-01-30 14:34:42 +00:00
effects->setElevatedWindow(windowMove, false);
windowMove = nullptr;
}
wasWindowMove = false;
wasWindowCopy = false;
wasDesktopMove = false;
}
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
void DesktopGridEffect::grabbedKeyboardEvent(QKeyEvent* e)
{
if (timeline.currentValue() != 1) // Block user input during animations
return;
if (windowMove != nullptr)
return;
2011-01-30 14:34:42 +00:00
if (e->type() == QEvent::KeyPress) {
// check for global shortcuts
// HACK: keyboard grab disables the global shortcuts so we have to check for global shortcut (bug 156155)
2011-01-30 14:34:42 +00:00
if (shortcut.contains(e->key() + e->modifiers())) {
toggle();
return;
2011-01-30 14:34:42 +00:00
}
int desktop = -1;
// switch by F<number> or just <number>
2011-01-30 14:34:42 +00:00
if (e->key() >= Qt::Key_F1 && e->key() <= Qt::Key_F35)
desktop = e->key() - Qt::Key_F1 + 1;
2011-01-30 14:34:42 +00:00
else if (e->key() >= Qt::Key_0 && e->key() <= Qt::Key_9)
desktop = e->key() == Qt::Key_0 ? 10 : e->key() - Qt::Key_0;
2011-01-30 14:34:42 +00:00
if (desktop != -1) {
if (desktop <= effects->numberOfDesktops()) {
setHighlightedDesktop(desktop);
setCurrentDesktop(desktop);
setActive(false);
}
2011-01-30 14:34:42 +00:00
return;
}
switch(e->key()) {
// Wrap only on autorepeat
case Qt::Key_Left:
setHighlightedDesktop(desktopToLeft(highlightedDesktop, !e->isAutoRepeat()));
break;
case Qt::Key_Right:
setHighlightedDesktop(desktopToRight(highlightedDesktop, !e->isAutoRepeat()));
break;
case Qt::Key_Up:
setHighlightedDesktop(desktopUp(highlightedDesktop, !e->isAutoRepeat()));
break;
case Qt::Key_Down:
setHighlightedDesktop(desktopDown(highlightedDesktop, !e->isAutoRepeat()));
break;
case Qt::Key_Escape:
setActive(false);
return;
case Qt::Key_Enter:
case Qt::Key_Return:
case Qt::Key_Space:
setCurrentDesktop(highlightedDesktop);
setActive(false);
return;
case Qt::Key_Plus:
slotAddDesktop();
break;
case Qt::Key_Minus:
slotRemoveDesktop();
break;
default:
break;
}
}
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
bool DesktopGridEffect::borderActivated(ElectricBorder border)
{
if (!borderActivate.contains(border))
return false;
2011-01-30 14:34:42 +00:00
if (effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this)
return true;
toggle();
return true;
2011-01-30 14:34:42 +00:00
}
//-----------------------------------------------------------------------------
// Helper functions
// Transform a point to its position on the scaled grid
2011-01-30 14:34:42 +00:00
QPointF DesktopGridEffect::scalePos(const QPoint& pos, int desktop, int screen) const
{
if (screen == -1)
screen = effects->screenNumber(pos);
QRect screenGeom = effects->clientArea(ScreenArea, screen, 0);
QPoint desktopCell;
2011-01-30 14:34:42 +00:00
if (orientation == Qt::Horizontal) {
desktopCell.setX((desktop - 1) % gridSize.width() + 1);
desktopCell.setY((desktop - 1) / gridSize.width() + 1);
} else {
desktopCell.setX((desktop - 1) / gridSize.height() + 1);
desktopCell.setY((desktop - 1) % gridSize.height() + 1);
}
double progress = timeline.currentValue();
QPointF point(
interpolate(
(
2011-01-30 14:34:42 +00:00
(screenGeom.width() + unscaledBorder[screen]) *(desktopCell.x() - 1)
- (screenGeom.width() + unscaledBorder[screen]) *(activeCell.x() - 1)
) + pos.x(),
(
2011-01-30 14:34:42 +00:00
(scaledSize[screen].width() + border) *(desktopCell.x() - 1)
+ scaledOffset[screen].x()
2011-01-30 14:34:42 +00:00
+ (pos.x() - screenGeom.x()) * scale[screen]
),
2011-01-30 14:34:42 +00:00
progress),
interpolate(
(
2011-01-30 14:34:42 +00:00
(screenGeom.height() + unscaledBorder[screen]) *(desktopCell.y() - 1)
- (screenGeom.height() + unscaledBorder[screen]) *(activeCell.y() - 1)
) + pos.y(),
(
2011-01-30 14:34:42 +00:00
(scaledSize[screen].height() + border) *(desktopCell.y() - 1)
+ scaledOffset[screen].y()
2011-01-30 14:34:42 +00:00
+ (pos.y() - screenGeom.y()) * scale[screen]
),
2011-01-30 14:34:42 +00:00
progress)
);
return point;
2011-01-30 14:34:42 +00:00
}
// Detransform a point to its position on the full grid
// TODO: Doesn't correctly interpolate (Final position is correct though), don't forget to copy to posToDesktop()
2011-01-30 14:34:42 +00:00
QPoint DesktopGridEffect::unscalePos(const QPoint& pos, int* desktop) const
{
int screen = effects->screenNumber(pos);
QRect screenGeom = effects->clientArea(ScreenArea, screen, 0);
//double progress = timeline.currentValue();
double scaledX = /*interpolate(
( pos.x() - screenGeom.x() + unscaledBorder[screen] / 2.0 ) / ( screenGeom.width() + unscaledBorder[screen] ) + activeCell.x() - 1,*/
2011-01-30 14:34:42 +00:00
(pos.x() - scaledOffset[screen].x() + double(border) / 2.0) / (scaledSize[screen].width() + border)/*,
progress )*/;
double scaledY = /*interpolate(
( pos.y() - screenGeom.y() + unscaledBorder[screen] / 2.0 ) / ( screenGeom.height() + unscaledBorder[screen] ) + activeCell.y() - 1,*/
2011-01-30 14:34:42 +00:00
(pos.y() - scaledOffset[screen].y() + double(border) / 2.0) / (scaledSize[screen].height() + border)/*,
progress )*/;
2011-01-30 14:34:42 +00:00
int gx = qBound(0, int(scaledX), gridSize.width() - 1); // Zero-based
int gy = qBound(0, int(scaledY), gridSize.height() - 1);
scaledX -= gx;
scaledY -= gy;
if (desktop != nullptr) {
2011-01-30 14:34:42 +00:00
if (orientation == Qt::Horizontal)
*desktop = gy * gridSize.width() + gx + 1;
else
*desktop = gx * gridSize.height() + gy + 1;
2011-01-30 14:34:42 +00:00
}
return QPoint(
2011-01-30 14:34:42 +00:00
qBound(
screenGeom.x(),
qRound(
scaledX * (screenGeom.width() + unscaledBorder[screen])
- unscaledBorder[screen] / 2.0
+ screenGeom.x()
),
screenGeom.right()
),
qBound(
screenGeom.y(),
qRound(
scaledY * (screenGeom.height() + unscaledBorder[screen])
- unscaledBorder[screen] / 2.0
+ screenGeom.y()
),
screenGeom.bottom()
)
);
}
int DesktopGridEffect::posToDesktop(const QPoint& pos) const
{
// Copied from unscalePos()
int screen = effects->screenNumber(pos);
double scaledX = (pos.x() - scaledOffset[screen].x() + double(border) / 2.0) / (scaledSize[screen].width() + border);
double scaledY = (pos.y() - scaledOffset[screen].y() + double(border) / 2.0) / (scaledSize[screen].height() + border);
2011-01-30 14:34:42 +00:00
int gx = qBound(0, int(scaledX), gridSize.width() - 1); // Zero-based
int gy = qBound(0, int(scaledY), gridSize.height() - 1);
if (orientation == Qt::Horizontal)
return gy * gridSize.width() + gx + 1;
return gx * gridSize.height() + gy + 1;
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
EffectWindow* DesktopGridEffect::windowAt(QPoint pos) const
{
// Get stacking order top first
EffectWindowList windows = effects->stackingOrder();
EffectWindowList::Iterator begin = windows.begin();
EffectWindowList::Iterator end = windows.end();
--end;
2011-01-30 14:34:42 +00:00
while (begin < end)
qSwap(*begin++, *end--);
int desktop;
2011-01-30 14:34:42 +00:00
pos = unscalePos(pos, &desktop);
if (desktop > effects->numberOfDesktops())
return nullptr;
2011-01-30 14:34:42 +00:00
if (isUsingPresentWindows()) {
const int screen = effects->screenNumber(pos);
EffectWindow *w =
2011-01-30 14:34:42 +00:00
m_managers.at((desktop - 1) * (effects->numScreens()) + screen).windowAtPoint(pos, false);
if (w)
return w;
2011-01-30 14:34:42 +00:00
foreach (EffectWindow * w, windows) {
if (w->isOnDesktop(desktop) && w->isDesktop() && w->geometry().contains(pos))
return w;
}
2011-01-30 14:34:42 +00:00
} else {
foreach (EffectWindow * w, windows) {
if (w->isOnDesktop(desktop) && w->isOnCurrentActivity() && !w->isMinimized() && w->geometry().contains(pos))
return w;
}
}
return nullptr;
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
void DesktopGridEffect::setCurrentDesktop(int desktop)
{
if (orientation == Qt::Horizontal) {
activeCell.setX((desktop - 1) % gridSize.width() + 1);
activeCell.setY((desktop - 1) / gridSize.width() + 1);
} else {
activeCell.setX((desktop - 1) / gridSize.height() + 1);
activeCell.setY((desktop - 1) % gridSize.height() + 1);
}
2011-01-30 14:34:42 +00:00
if (effects->currentDesktop() != desktop)
effects->setCurrentDesktop(desktop);
}
2011-01-30 14:34:42 +00:00
void DesktopGridEffect::setHighlightedDesktop(int d)
{
if (d == highlightedDesktop || d <= 0 || d > effects->numberOfDesktops())
return;
if (highlightedDesktop > 0 && highlightedDesktop <= hoverTimeline.count())
hoverTimeline[highlightedDesktop-1]->setCurrentTime(qMin(hoverTimeline[highlightedDesktop-1]->currentTime(),
hoverTimeline[highlightedDesktop-1]->duration()));
highlightedDesktop = d;
if (highlightedDesktop <= hoverTimeline.count())
hoverTimeline[highlightedDesktop-1]->setCurrentTime(qMax(hoverTimeline[highlightedDesktop-1]->currentTime(), 0));
effects->addRepaintFull();
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
int DesktopGridEffect::desktopToRight(int desktop, bool wrap) const
{
// Copied from Workspace::desktopToRight()
int dt = desktop - 1;
2011-01-30 14:34:42 +00:00
if (orientation == Qt::Vertical) {
dt += gridSize.height();
2011-01-30 14:34:42 +00:00
if (dt >= effects->numberOfDesktops()) {
if (wrap)
dt -= effects->numberOfDesktops();
else
return desktop;
}
2011-01-30 14:34:42 +00:00
} else {
int d = (dt % gridSize.width()) + 1;
if (d >= gridSize.width()) {
if (wrap)
d -= gridSize.width();
else
return desktop;
}
2011-01-30 14:34:42 +00:00
dt = dt - (dt % gridSize.width()) + d;
}
2011-01-30 14:34:42 +00:00
return dt + 1;
}
2011-01-30 14:34:42 +00:00
int DesktopGridEffect::desktopToLeft(int desktop, bool wrap) const
{
// Copied from Workspace::desktopToLeft()
int dt = desktop - 1;
2011-01-30 14:34:42 +00:00
if (orientation == Qt::Vertical) {
dt -= gridSize.height();
2011-01-30 14:34:42 +00:00
if (dt < 0) {
if (wrap)
dt += effects->numberOfDesktops();
else
return desktop;
}
2011-01-30 14:34:42 +00:00
} else {
int d = (dt % gridSize.width()) - 1;
if (d < 0) {
if (wrap)
d += gridSize.width();
else
return desktop;
}
2011-01-30 14:34:42 +00:00
dt = dt - (dt % gridSize.width()) + d;
}
2011-01-30 14:34:42 +00:00
return dt + 1;
}
2011-01-30 14:34:42 +00:00
int DesktopGridEffect::desktopUp(int desktop, bool wrap) const
{
// Copied from Workspace::desktopUp()
int dt = desktop - 1;
2011-01-30 14:34:42 +00:00
if (orientation == Qt::Horizontal) {
dt -= gridSize.width();
2011-01-30 14:34:42 +00:00
if (dt < 0) {
if (wrap)
dt += effects->numberOfDesktops();
else
return desktop;
}
2011-01-30 14:34:42 +00:00
} else {
int d = (dt % gridSize.height()) - 1;
if (d < 0) {
if (wrap)
d += gridSize.height();
else
return desktop;
}
2011-01-30 14:34:42 +00:00
dt = dt - (dt % gridSize.height()) + d;
}
2011-01-30 14:34:42 +00:00
return dt + 1;
}
2011-01-30 14:34:42 +00:00
int DesktopGridEffect::desktopDown(int desktop, bool wrap) const
{
// Copied from Workspace::desktopDown()
int dt = desktop - 1;
2011-01-30 14:34:42 +00:00
if (orientation == Qt::Horizontal) {
dt += gridSize.width();
2011-01-30 14:34:42 +00:00
if (dt >= effects->numberOfDesktops()) {
if (wrap)
dt -= effects->numberOfDesktops();
else
return desktop;
}
2011-01-30 14:34:42 +00:00
} else {
int d = (dt % gridSize.height()) + 1;
if (d >= gridSize.height()) {
if (wrap)
d -= gridSize.height();
else
return desktop;
}
2011-01-30 14:34:42 +00:00
dt = dt - (dt % gridSize.height()) + d;
}
2011-01-30 14:34:42 +00:00
return dt + 1;
}
//-----------------------------------------------------------------------------
// Activation
void DesktopGridEffect::toggle()
2011-01-30 14:34:42 +00:00
{
setActive(!activated);
}
2011-01-30 14:34:42 +00:00
void DesktopGridEffect::setActive(bool active)
{
if (effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this)
return; // Only one fullscreen effect at a time thanks
2011-01-30 14:34:42 +00:00
if (active && isMotionManagerMovingWindows())
return; // Still moving windows from last usage - don't activate
2011-01-30 14:34:42 +00:00
if (activated == active)
return; // Already in that state
activated = active;
if (activated) {
effects->setShowingDesktop(false);
if (timeline.currentValue() == 0)
setup();
} else {
2011-01-30 14:34:42 +00:00
if (isUsingPresentWindows()) {
QList<WindowMotionManager>::iterator it;
2011-01-30 14:34:42 +00:00
for (it = m_managers.begin(); it != m_managers.end(); ++it) {
foreach (EffectWindow * w, (*it).managedWindows()) {
(*it).moveWindow(w, w->geometry());
}
}
2011-01-30 14:34:42 +00:00
}
QTimer::singleShot(zoomDuration + 1, this,
[this] {
if (activated)
return;
for (EffectQuickScene *view : m_desktopButtons) {
view->hide();
}
}
);
2011-01-30 14:34:42 +00:00
setHighlightedDesktop(effects->currentDesktop()); // Ensure selected desktop is highlighted
}
2011-01-30 14:34:42 +00:00
effects->addRepaintFull();
}
void DesktopGridEffect::setup()
2011-01-30 14:34:42 +00:00
{
if (!isActive())
return;
if (!keyboardGrab) {
keyboardGrab = effects->grabKeyboard(this);
effects->startMouseInterception(this, Qt::PointingHandCursor);
effects->setActiveFullScreenEffect(this);
}
2011-01-30 14:34:42 +00:00
setHighlightedDesktop(effects->currentDesktop());
// Soft highlighting
qDeleteAll(hoverTimeline);
hoverTimeline.clear();
2011-01-30 14:34:42 +00:00
for (int i = 0; i < effects->numberOfDesktops(); i++) {
QTimeLine *newTimeline = new QTimeLine(zoomDuration, this);
newTimeline->setEasingCurve(QEasingCurve::InOutSine);
2011-01-30 14:34:42 +00:00
hoverTimeline.append(newTimeline);
}
hoverTimeline[effects->currentDesktop() - 1]->setCurrentTime(hoverTimeline[effects->currentDesktop() - 1]->duration());
// Create desktop name textures if enabled
2011-01-30 14:34:42 +00:00
if (desktopNameAlignment) {
QFont font;
2011-01-30 14:34:42 +00:00
font.setBold(true);
font.setPointSize(12);
for (int i = 0; i < effects->numberOfDesktops(); i++) {
EffectFrame* frame = effects->effectFrame(EffectFrameUnstyled, false);
frame->setFont(font);
frame->setText(effects->desktopName(i + 1));
frame->setAlignment(desktopNameAlignment);
desktopNames.append(frame);
}
2011-01-30 14:34:42 +00:00
}
setupGrid();
2011-01-30 14:34:42 +00:00
setCurrentDesktop(effects->currentDesktop());
// setup the motion managers
if (clickBehavior == SwitchDesktopAndActivateWindow)
m_proxy = static_cast<PresentWindowsEffectProxy*>(effects->getProxy(BuiltInEffects::nameForEffect(BuiltInEffect::PresentWindows)));
2011-01-30 14:34:42 +00:00
if (isUsingPresentWindows()) {
m_proxy->reCreateGrids(); // revalidation on multiscreen, bug #351724
2011-01-30 14:34:42 +00:00
for (int i = 1; i <= effects->numberOfDesktops(); i++) {
for (int j = 0; j < effects->numScreens(); j++) {
WindowMotionManager manager;
2011-01-30 14:34:42 +00:00
foreach (EffectWindow * w, effects->stackingOrder()) {
if (w->isOnDesktop(i) && w->screen() == j &&isRelevantWithPresentWindows(w)) {
2011-01-30 14:34:42 +00:00
manager.manage(w);
}
2011-01-30 14:34:42 +00:00
}
m_proxy->calculateWindowTransformations(manager.managedWindows(), j, manager);
m_managers.append(manager);
}
}
2011-01-30 14:34:42 +00:00
}
auto it = m_desktopButtons.begin();
const int n = DesktopGridConfig::showAddRemove() ? effects->numScreens() : 0;
for (int i = 0; i < n; ++i) {
EffectQuickScene *view;
QSize size;
if (it == m_desktopButtons.end()) {
view = new EffectQuickScene(this);
connect(view, &EffectQuickView::repaintNeeded, this, []() {
effects->addRepaintFull();
});
view->rootContext()->setContextProperty("effects", effects);
view->setSource(QUrl(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kwin/effects/desktopgrid/main.qml"))));
QQuickItem *rootItem = view->rootItem();
if (!rootItem) {
delete view;
continue;
}
m_desktopButtons.append(view);
it = m_desktopButtons.end(); // changed through insert!
size = QSize(rootItem->implicitWidth(), rootItem->implicitHeight());
} else {
view = *it;
++it;
size = view->size();
}
2011-01-30 14:34:42 +00:00
const QRect screenRect = effects->clientArea(FullScreenArea, i, 1);
view->show(); // pseudo show must happen before geometry changes
const QPoint position(screenRect.right() - border/3 - size.width(),
screenRect.bottom() - border/3 - size.height());
view->setGeometry(QRect(position, size));
}
while (it != m_desktopButtons.end()) {
(*it)->deleteLater();
it = m_desktopButtons.erase(it);
}
2011-01-30 14:34:42 +00:00
}
void DesktopGridEffect::setupGrid()
2011-01-30 14:34:42 +00:00
{
// We need these variables for every paint so lets cache them
int x, y;
int numDesktops = effects->numberOfDesktops();
2011-01-30 14:34:42 +00:00
switch(layoutMode) {
default:
case LayoutPager:
orientation = Qt::Horizontal;
gridSize = effects->desktopGridSize();
// sanity check: pager may report incorrect size in case of one desktop
if (numDesktops == 1) {
gridSize = QSize(1, 1);
}
2011-01-30 14:34:42 +00:00
break;
case LayoutAutomatic:
y = sqrt(float(numDesktops)) + 0.5;
x = float(numDesktops) / float(y) + 0.5;
if (x * y < numDesktops)
x++;
orientation = Qt::Horizontal;
gridSize.setWidth(x);
gridSize.setHeight(y);
break;
case LayoutCustom:
orientation = Qt::Horizontal;
gridSize.setWidth(ceil(effects->numberOfDesktops() / double(customLayoutRows)));
gridSize.setHeight(customLayoutRows);
break;
}
scale.clear();
unscaledBorder.clear();
scaledSize.clear();
scaledOffset.clear();
2011-01-30 14:34:42 +00:00
for (int i = 0; i < effects->numScreens(); i++) {
QRect geom = effects->clientArea(ScreenArea, i, 0);
double sScale;
2011-01-30 14:34:42 +00:00
if (gridSize.width() > gridSize.height())
sScale = (geom.width() - border * (gridSize.width() + 1)) / double(geom.width() * gridSize.width());
else
2011-01-30 14:34:42 +00:00
sScale = (geom.height() - border * (gridSize.height() + 1)) / double(geom.height() * gridSize.height());
double sBorder = border / sScale;
QSizeF size(
2011-01-30 14:34:42 +00:00
double(geom.width()) * sScale,
double(geom.height()) * sScale
);
QPointF offset(
2011-01-30 14:34:42 +00:00
geom.x() + (geom.width() - size.width() * gridSize.width() - border *(gridSize.width() - 1)) / 2.0,
geom.y() + (geom.height() - size.height() * gridSize.height() - border *(gridSize.height() - 1)) / 2.0
);
scale.append(sScale);
unscaledBorder.append(sBorder);
scaledSize.append(size);
scaledOffset.append(offset);
}
2011-01-30 14:34:42 +00:00
}
void DesktopGridEffect::finish()
2011-01-30 14:34:42 +00:00
{
if (desktopNameAlignment) {
qDeleteAll(desktopNames);
desktopNames.clear();
2011-01-30 14:34:42 +00:00
}
windowMoveElevateTimer->stop();
2011-01-30 14:34:42 +00:00
if (keyboardGrab)
effects->ungrabKeyboard();
keyboardGrab = false;
Provide expected presentation time to effects Effects are given the interval between two consecutive frames. The main flaw of this approach is that if the Compositor transitions from the idle state to "active" state, i.e. when there is something to repaint, effects may see a very large interval between the last painted frame and the current. In order to address this issue, the Scene invalidates the timer that is used to measure time between consecutive frames before the Compositor is about to become idle. While this works perfectly fine with Xinerama-style rendering, with per screen rendering, determining whether the compositor is about to idle is rather a tedious task mostly because a single output can't be used for the test. Furthermore, since the Compositor schedules pointless repaints just to ensure that it's idle, it might take several attempts to figure out whether the scene timer must be invalidated if you use (true) per screen rendering. Ideally, all effects should use a timeline helper that is aware of the underlying render loop and its timings. However, this option is off the table because it will involve a lot of work to implement it. Alternative and much simpler option is to pass the expected presentation time to effects rather than time between consecutive frames. This means that effects are responsible for determining how much animation timelines have to be advanced. Typically, an effect would have to store the presentation timestamp provided in either prePaint{Screen,Window} and use it in the subsequent prePaint{Screen,Window} call to estimate the amount of time passed between the next and the last frames. Unfortunately, this is an API incompatible change. However, it shouldn't take a lot of work to port third-party binary effects, which don't use the AnimationEffect class, to the new API. On the bright side, we no longer need to be concerned about the Compositor getting idle. We do still try to determine whether the Compositor is about to idle, primarily, because the OpenGL render backend swaps buffers on present, but that will change with the ongoing compositing timing rework.
2020-11-20 15:44:04 +00:00
lastPresentTime = std::chrono::milliseconds::zero();
effects->stopMouseInterception(this);
effects->setActiveFullScreenEffect(nullptr);
2011-01-30 14:34:42 +00:00
if (isUsingPresentWindows()) {
while (!m_managers.isEmpty()) {
m_managers.first().unmanageAll();
m_managers.removeFirst();
}
m_proxy = nullptr;
2011-01-30 14:34:42 +00:00
}
}
void DesktopGridEffect::globalShortcutChanged(QAction *action, const QKeySequence& seq)
2011-01-30 14:34:42 +00:00
{
if (action->objectName() != QStringLiteral("ShowDesktopGrid")) {
return;
}
shortcut.clear();
shortcut.append(seq);
2011-01-30 14:34:42 +00:00
}
bool DesktopGridEffect::isMotionManagerMovingWindows() const
2011-01-30 14:34:42 +00:00
{
if (isUsingPresentWindows()) {
QList<WindowMotionManager>::const_iterator it;
2011-01-30 14:34:42 +00:00
for (it = m_managers.begin(); it != m_managers.end(); ++it) {
if ((*it).areWindowsMoving())
return true;
}
}
2011-01-30 14:34:42 +00:00
return false;
}
bool DesktopGridEffect::isUsingPresentWindows() const
2011-01-30 14:34:42 +00:00
{
return (m_proxy != nullptr);
2011-01-30 14:34:42 +00:00
}
// transforms the geometry of the moved window to a geometry on the desktop
// internal method only used when a window is dropped onto a desktop
2011-01-30 14:34:42 +00:00
QRectF DesktopGridEffect::moveGeometryToDesktop(int desktop) const
{
QPointF point = unscalePos(m_windowMoveGeometry.topLeft() + cursorPos() - m_windowMoveStartPoint);
const double scaleFactor = scale[ windowMove->screen()];
if (posToDesktop(m_windowMoveGeometry.topLeft() + cursorPos() - m_windowMoveStartPoint) != desktop) {
// topLeft is not on the desktop - check other corners
// if all corners are not on the desktop the window is bigger than the desktop - no matter what it will look strange
2011-01-30 14:34:42 +00:00
if (posToDesktop(m_windowMoveGeometry.topRight() + cursorPos() - m_windowMoveStartPoint) == desktop) {
point = unscalePos(m_windowMoveGeometry.topRight() + cursorPos() - m_windowMoveStartPoint) -
QPointF(m_windowMoveGeometry.width(), 0) / scaleFactor;
} else if (posToDesktop(m_windowMoveGeometry.bottomLeft() + cursorPos() - m_windowMoveStartPoint) == desktop) {
point = unscalePos(m_windowMoveGeometry.bottomLeft() + cursorPos() - m_windowMoveStartPoint) -
QPointF(0, m_windowMoveGeometry.height()) / scaleFactor;
} else if (posToDesktop(m_windowMoveGeometry.bottomRight() + cursorPos() - m_windowMoveStartPoint) == desktop) {
point = unscalePos(m_windowMoveGeometry.bottomRight() + cursorPos() - m_windowMoveStartPoint) -
QPointF(m_windowMoveGeometry.width(), m_windowMoveGeometry.height()) / scaleFactor;
}
}
2011-01-30 14:34:42 +00:00
return QRectF(point, m_windowMoveGeometry.size() / scaleFactor);
}
void DesktopGridEffect::slotAddDesktop()
2011-01-30 14:34:42 +00:00
{
effects->setNumberOfDesktops(effects->numberOfDesktops() + 1);
}
void DesktopGridEffect::slotRemoveDesktop()
2011-01-30 14:34:42 +00:00
{
effects->setNumberOfDesktops(effects->numberOfDesktops() - 1);
}
void DesktopGridEffect::slotNumberDesktopsChanged(uint old)
2011-01-30 14:34:42 +00:00
{
if (!activated)
return;
2013-01-08 10:04:07 +00:00
const uint desktop = effects->numberOfDesktops();
2011-01-30 14:34:42 +00:00
if (old < desktop)
desktopsAdded(old);
else
desktopsRemoved(old);
}
2011-01-30 14:34:42 +00:00
void DesktopGridEffect::desktopsAdded(int old)
{
const int desktop = effects->numberOfDesktops();
2011-01-30 14:34:42 +00:00
for (int i = old; i <= effects->numberOfDesktops(); i++) {
// add a timeline for the new desktop
QTimeLine *newTimeline = new QTimeLine(zoomDuration, this);
newTimeline->setEasingCurve(QEasingCurve::InOutSine);
2011-01-30 14:34:42 +00:00
hoverTimeline.append(newTimeline);
}
// Create desktop name textures if enabled
2011-01-30 14:34:42 +00:00
if (desktopNameAlignment) {
QFont font;
2011-01-30 14:34:42 +00:00
font.setBold(true);
font.setPointSize(12);
for (int i = old; i < desktop; i++) {
EffectFrame* frame = effects->effectFrame(EffectFrameUnstyled, false);
frame->setFont(font);
frame->setText(effects->desktopName(i + 1));
frame->setAlignment(desktopNameAlignment);
desktopNames.append(frame);
}
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
if (isUsingPresentWindows()) {
for (int i = old+1; i <= effects->numberOfDesktops(); ++i) {
for (int j = 0; j < effects->numScreens(); ++j) {
WindowMotionManager manager;
2011-01-30 14:34:42 +00:00
foreach (EffectWindow * w, effects->stackingOrder()) {
if (w->isOnDesktop(i) && w->screen() == j &&isRelevantWithPresentWindows(w)) {
2011-01-30 14:34:42 +00:00
manager.manage(w);
}
2011-01-30 14:34:42 +00:00
}
m_proxy->calculateWindowTransformations(manager.managedWindows(), j, manager);
m_managers.append(manager);
}
}
2011-01-30 14:34:42 +00:00
}
setupGrid();
// and repaint
effects->addRepaintFull();
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
void DesktopGridEffect::desktopsRemoved(int old)
{
const int desktop = effects->numberOfDesktops();
2011-01-30 14:34:42 +00:00
for (int i = desktop; i < old; i++) {
delete hoverTimeline.takeLast();
2011-01-30 14:34:42 +00:00
if (desktopNameAlignment) {
delete desktopNames.last();
desktopNames.removeLast();
2011-01-30 14:34:42 +00:00
}
if (isUsingPresentWindows()) {
for (int j = 0; j < effects->numScreens(); ++j) {
WindowMotionManager& manager = m_managers.last();
manager.unmanageAll();
m_managers.removeLast();
}
}
2011-01-30 14:34:42 +00:00
}
// add removed windows to the last desktop
2011-01-30 14:34:42 +00:00
if (isUsingPresentWindows()) {
for (int j = 0; j < effects->numScreens(); ++j) {
WindowMotionManager& manager = m_managers[(desktop-1)*(effects->numScreens())+j ];
foreach (EffectWindow * w, effects->stackingOrder()) {
if (manager.isManaging(w))
continue;
if (w->isOnDesktop(desktop) && w->screen() == j && isRelevantWithPresentWindows(w)) {
2011-01-30 14:34:42 +00:00
manager.manage(w);
}
}
2011-01-30 14:34:42 +00:00
m_proxy->calculateWindowTransformations(manager.managedWindows(), j, manager);
}
2011-01-30 14:34:42 +00:00
}
setupGrid();
// and repaint
effects->addRepaintFull();
2011-01-30 14:34:42 +00:00
}
//TODO: kill this function? or at least keep a consistent numeration with desktops starting from 1
QVector<int> DesktopGridEffect::desktopList(const EffectWindow *w) const
{
if (w->isOnAllDesktops()) {
static QVector<int> allDesktops;
if (allDesktops.count() != effects->numberOfDesktops()) {
allDesktops.resize(effects->numberOfDesktops());
for (int i = 0; i < effects->numberOfDesktops(); ++i)
allDesktops[i] = i;
}
return allDesktops;
}
QVector<int> desks;
desks.resize(w->desktops().count());
int i = 0;
for (const int desk : w->desktops()) {
desks[i++] = desk-1;
}
return desks;
}
bool DesktopGridEffect::isActive() const
{
return (timeline.currentValue() != 0 || activated || (isUsingPresentWindows() && isMotionManagerMovingWindows())) && !effects->isScreenLocked();
}
bool DesktopGridEffect::isRelevantWithPresentWindows(EffectWindow *w) const
{
if (w->isSpecialWindow() || w->isUtility()) {
return false;
}
if (w->isSkipSwitcher()) {
return false;
}
if (w->isDeleted()) {
return false;
}
if (!w->acceptsFocus()) {
return false;
}
if (!w->isOnCurrentActivity()) {
return false;
}
return true;
}
} // namespace