effects/highlightwindows: Re-implement as AnimationEffect

On Wayland, the highlight window effect is not guaranteed to work all
the time because it uses the opacity to determine if the animation has
completed.

This change rewrites the highlight window effect using AnimationEffect
API to make it work both on X11 and Wayland. The implementation is based
on how the translucency effect works.
This commit is contained in:
Vlad Zahorodnii 2021-01-27 13:36:45 +02:00
parent f8d9b3bc6c
commit 4dcb3c495c
2 changed files with 99 additions and 188 deletions

View file

@ -13,8 +13,8 @@ namespace KWin
{
HighlightWindowEffect::HighlightWindowEffect()
: m_finishing(false)
, m_fadeDuration(float(animationTime(150)))
: m_easingCurve(QEasingCurve::Linear)
, m_fadeDuration(animationTime(150))
, m_monitorWindow(nullptr)
{
m_atom = effects->announceSupportProperty("_KDE_WINDOW_HIGHLIGHT", this);
@ -33,100 +33,35 @@ HighlightWindowEffect::HighlightWindowEffect()
);
}
HighlightWindowEffect::~HighlightWindowEffect()
{
}
static bool isInitiallyHidden(EffectWindow* w)
{
// Is the window initially hidden until it is highlighted?
return w->isMinimized() || !w->isOnCurrentDesktop();
}
void HighlightWindowEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, std::chrono::milliseconds presentTime)
static bool isHighlightWindow(EffectWindow *window)
{
// Calculate window opacities
QHash<EffectWindow*, HightlightWindowData>::iterator it = m_animations.find(w);
if (!m_highlightedWindows.isEmpty()) {
// Initial fade out and changing highlight animation
if (it == m_animations.end())
it = m_animations.insert(w, HightlightWindowData());
int time = 1;
if (it->lastPresentTime.count()) {
time = std::max(1, int((presentTime - it->lastPresentTime).count()));
}
it->lastPresentTime = presentTime;
float oldOpacity = it->opacity;
if (m_highlightedWindows.contains(w))
it->opacity = qMin(1.0f, oldOpacity + time / m_fadeDuration);
else if (w->isNormalWindow() || w->isDialog()) // Only fade out windows
it->opacity = qMax(isInitiallyHidden(w) ? 0.0f : 0.15f, oldOpacity - time / m_fadeDuration);
if (it->opacity < 0.98f)
data.setTranslucent();
if (oldOpacity != it->opacity)
effects->addRepaint(w->expandedGeometry());
} else if (m_finishing && m_animations.contains(w)) {
// Final fading back in animation
if (it == m_animations.end())
it = m_animations.insert(w, HightlightWindowData());
int time = 1;
if (it->lastPresentTime.count()) {
time = std::max(1, int((presentTime - it->lastPresentTime).count()));
}
it->lastPresentTime = presentTime;
float oldOpacity = it->opacity;
if (isInitiallyHidden(w))
it->opacity = qMax(0.0f, oldOpacity - time / m_fadeDuration);
else
it->opacity = qMin(1.0f, oldOpacity + time / m_fadeDuration);
if (it->opacity < 0.98f)
data.setTranslucent();
if (oldOpacity != it->opacity)
effects->addRepaint(w->expandedGeometry());
if (it->opacity > 0.98f || it->opacity < 0.02f) {
m_animations.remove(w); // We default to 1.0
it = m_animations.end();
}
}
// Show tabbed windows and windows on other desktops if highlighted
if (it != m_animations.end() && it->opacity > 0.01) {
if (w->isMinimized())
w->enablePainting(EffectWindow::PAINT_DISABLED_BY_MINIMIZE);
if (!w->isOnCurrentDesktop())
w->enablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP);
}
effects->prePaintWindow(w, data, presentTime);
}
void HighlightWindowEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data)
{
auto it = m_animations.constFind(w);
if (it != m_animations.constEnd()) {
data.multiplyOpacity(it->opacity);
}
effects->paintWindow(w, mask, region, data);
return window->isNormalWindow() || window->isDialog();
}
void HighlightWindowEffect::slotWindowAdded(EffectWindow* w)
{
if (!m_highlightedWindows.isEmpty()) {
// The effect is activated thus we need to add it to the opacity hash
foreach (const WId id, m_highlightedIds) {
// On X11, the tabbox may ask us to highlight itself before the windowAdded signal
// is emitted because override-redirect windows are shown after synthetic 50ms delay.
if (m_highlightedWindows.contains(w)) {
return;
}
// This window was demanded to be highlighted before it appeared on the screen.
for (const WId &id : qAsConst(m_highlightedIds)) {
if (w == effects->findWindow(id)) {
m_animations[w].opacity = 1.0; // this window was demanded to be highlighted before it appeared
startHighlightAnimation(w, 0);
return;
}
}
m_animations[w].opacity = 0.15; // this window is not currently highlighted
if (isHighlightWindow(w)) {
startGhostAnimation(w, 0); // this window is not currently highlighted
}
}
slotPropertyNotify(w, m_atom, w); // Check initial value
}
@ -188,87 +123,42 @@ void HighlightWindowEffect::slotPropertyNotify(EffectWindow* w, long a, EffectWi
return;
}
prepareHighlighting();
if (w)
m_animations[w].opacity = 1.0; // Because it's not in stackingOrder() yet
/* TODO: Finish thumbnails of offscreen windows, not sure if it's worth it though
if ( !m_highlightedWindow->isOnCurrentDesktop() )
{ // Window is offscreen, determine thumbnail position
QRect screenArea = effects->clientArea( MaximizeArea ); // Workable area of the active screen
QRect outerArea = outerArea.adjusted( outerArea.width() / 10, outerArea.height() / 10,
-outerArea.width() / 10, -outerArea.height() / 10 ); // Add 10% margin around the edge
QRect innerArea = outerArea.adjusted( outerArea.width() / 40, outerArea.height() / 40,
-outerArea.width() / 40, -outerArea.height() / 40 ); // Outer edge of the thumbnail border (2.5%)
QRect thumbArea = outerArea.adjusted( 20, 20, -20, -20 ); // Outer edge of the thumbnail (20px)
// Determine the maximum size that we can make the thumbnail within the innerArea
double areaAspect = double( thumbArea.width() ) / double( thumbArea.height() );
double windowAspect = aspectRatio( m_highlightedWindow );
QRect thumbRect; // Position doesn't matter right now, but it will later
if ( windowAspect > areaAspect )
// Top/bottom will touch first
thumbRect = QRect( 0, 0, widthForHeight( thumbArea.height() ), thumbArea.height() );
else // Left/right will touch first
thumbRect = QRect( 0, 0, thumbArea.width(), heightForWidth( thumbArea.width() ));
if ( thumbRect.width() >= m_highlightedWindow->width() )
// Area is larger than the window, just use the window's size
thumbRect = m_highlightedWindow->geometry();
// Determine position of desktop relative to the current one
QPoint direction = effects->desktopGridCoords( m_highlightedWindow->desktop() ) -
effects->desktopGridCoords( effects->currentDesktop() );
// Draw a line from the center of the current desktop to the center of the target desktop.
QPointF desktopLine( 0, 0, direction.x() * screenArea.width(), direction.y() * screenArea.height() );
desktopLeft.translate( screenArea.width() / 2, screenArea.height() / 2 ); // Move to the screen center
// Take the point where the line crosses the outerArea, this will be the tip of our arrow
QPointF arrowTip;
QLineF testLine( // Top
outerArea.x(), outerArea.y(),
outerArea.x() + outerArea.width(), outerArea.y() );
if ( desktopLine.intersect( testLine, &arrowTip ) != QLineF::BoundedIntersection )
{
testLine = QLineF( // Right
outerArea.x() + outerArea.width(), outerArea.y(),
outerArea.x() + outerArea.width(), outerArea.y() + outerArea.height() );
if ( desktopLine.intersect( testLine, &arrowTip ) != QLineF::BoundedIntersection )
{
testLine = QLineF( // Bottom
outerArea.x() + outerArea.width(), outerArea.y() + outerArea.height(),
outerArea.x(), outerArea.y() + outerArea.height() );
if ( desktopLine.intersect( testLine, &arrowTip ) != QLineF::BoundedIntersection )
{
testLine = QLineF( // Left
outerArea.x(), outerArea.y() + outerArea.height(),
outerArea.x(), outerArea.y() );
desktopLine.intersect( testLine, &arrowTip ); // Should never fail
}
}
}
m_arrowTip = arrowTip.toPoint();
} */
}
void HighlightWindowEffect::prepareHighlighting()
{
// Create window data for every window. Just calling [w] creates it.
m_finishing = false;
foreach (EffectWindow * w, effects->stackingOrder()) {
if (!m_animations.contains(w)) // Just in case we are still finishing from last time
m_animations[w].opacity = isInitiallyHidden(w) ? 0.0 : 1.0;
if (!m_highlightedWindows.isEmpty())
m_highlightedWindows.at(0)->addRepaintFull();
const EffectWindowList windows = effects->stackingOrder();
for (EffectWindow *window : windows) {
if (!isHighlightWindow(window)) {
continue;
}
if (isHighlighted(window)) {
startHighlightAnimation(window);
} else {
startGhostAnimation(window);
}
}
}
void HighlightWindowEffect::finishHighlighting()
{
m_finishing = true;
const EffectWindowList windows = effects->stackingOrder();
for (EffectWindow *window : windows) {
if (isHighlightWindow(window)) {
startRevertAnimation(window);
}
}
// Sanity check, ideally, this should never happen.
if (!m_animations.isEmpty()) {
for (quint64 &animationId : m_animations) {
cancel(animationId);
}
m_animations.clear();
}
m_monitorWindow = nullptr;
m_highlightedWindows.clear();
if (!m_animations.isEmpty())
m_animations.constBegin().key()->addRepaintFull();
}
void HighlightWindowEffect::highlightWindows(const QVector<KWin::EffectWindow *> &windows)
@ -287,9 +177,51 @@ void HighlightWindowEffect::highlightWindows(const QVector<KWin::EffectWindow *>
prepareHighlighting();
}
bool HighlightWindowEffect::isActive() const
void HighlightWindowEffect::startGhostAnimation(EffectWindow *window, int duration)
{
return !(m_animations.isEmpty() || effects->isScreenLocked());
if (duration == -1) {
duration = m_fadeDuration;
}
quint64 &animationId = m_animations[window];
if (animationId) {
retarget(animationId, FPx2(m_ghostOpacity, m_ghostOpacity), m_fadeDuration);
} else {
const qreal startOpacity = isInitiallyHidden(window) ? 0 : 1;
animationId = set(window, Opacity, 0, m_fadeDuration, FPx2(m_ghostOpacity, m_ghostOpacity),
m_easingCurve, 0, FPx2(startOpacity, startOpacity), false, false);
}
}
void HighlightWindowEffect::startHighlightAnimation(EffectWindow *window, int duration)
{
if (duration == -1) {
duration = m_fadeDuration;
}
quint64 &animationId = m_animations[window];
if (animationId) {
retarget(animationId, FPx2(1.0, 1.0), m_fadeDuration);
} else {
const qreal startOpacity = isInitiallyHidden(window) ? 0 : 1;
animationId = set(window, Opacity, 0, m_fadeDuration, FPx2(1.0, 1.0),
m_easingCurve, 0, FPx2(startOpacity, startOpacity), false, false);
}
}
void HighlightWindowEffect::startRevertAnimation(EffectWindow *window)
{
const quint64 animationId = m_animations.take(window);
if (animationId) {
const qreal startOpacity = isHighlighted(window) ? 1 : m_ghostOpacity;
const qreal endOpacity = isInitiallyHidden(window) ? 0 : 1;
animate(window, Opacity, 0, m_fadeDuration, FPx2(endOpacity, endOpacity),
m_easingCurve, 0, FPx2(startOpacity, startOpacity), false, false);
cancel(animationId);
}
}
bool HighlightWindowEffect::isHighlighted(EffectWindow *window) const
{
return m_highlightedWindows.contains(window);
}
bool HighlightWindowEffect::provides(Feature feature)

View file

@ -10,28 +10,17 @@
#ifndef KWIN_HIGHLIGHTWINDOW_H
#define KWIN_HIGHLIGHTWINDOW_H
#include <kwineffects.h>
#include <kwinanimationeffect.h>
namespace KWin
{
struct HightlightWindowData
{
std::chrono::milliseconds lastPresentTime = std::chrono::milliseconds::zero();
float opacity = 0;
};
class HighlightWindowEffect
: public Effect
class HighlightWindowEffect : public AnimationEffect
{
Q_OBJECT
public:
HighlightWindowEffect();
~HighlightWindowEffect() override;
void prePaintWindow(EffectWindow* w, WindowPrePaintData& data, std::chrono::milliseconds presentTime) override;
void paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) override;
bool isActive() const override;
int requestedEffectChainPosition() const override {
return 70;
@ -47,34 +36,24 @@ public Q_SLOTS:
void slotPropertyNotify(KWin::EffectWindow* w, long atom, EffectWindow *addedWindow = nullptr);
private:
void startGhostAnimation(EffectWindow *window, int duration = -1);
void startHighlightAnimation(EffectWindow *window, int duration = -1);
void startRevertAnimation(EffectWindow *window);
bool isHighlighted(EffectWindow *window) const;
void prepareHighlighting();
void finishHighlighting();
void highlightWindows(const QVector<KWin::EffectWindow *> &windows);
bool m_finishing;
float m_fadeDuration;
QHash<EffectWindow *, HightlightWindowData> m_animations;
long m_atom;
QList<EffectWindow*> m_highlightedWindows;
EffectWindow* m_monitorWindow;
QList<EffectWindow *> m_highlightedWindows;
QHash<EffectWindow *, quint64> m_animations;
QEasingCurve m_easingCurve;
int m_fadeDuration;
EffectWindow *m_monitorWindow;
QList<WId> m_highlightedIds;
// Offscreen position cache
/*QRect m_thumbArea; // Thumbnail area
QPoint m_arrowTip; // Position of the arrow's tip
QPoint m_arrowA; // Arrow vertex position at the base (First)
QPoint m_arrowB; // Arrow vertex position at the base (Second)
// Helper functions
inline double aspectRatio( EffectWindow *w )
{ return w->width() / double( w->height() ); }
inline int widthForHeight( EffectWindow *w, int height )
{ return int(( height / double( w->height() )) * w->width() ); }
inline int heightForWidth( EffectWindow *w, int width )
{ return int(( width / double( w->width() )) * w->height() ); }*/
float m_ghostOpacity = 0.15;
};
} // namespace