diff --git a/effects/diminactive/diminactive.h b/effects/diminactive/diminactive.h --- a/effects/diminactive/diminactive.h +++ b/effects/diminactive/diminactive.h @@ -4,6 +4,7 @@ Copyright (C) 2007 Lubos Lunak Copyright (C) 2007 Christian Nitschkowski +Copyright (C) 2018 Vlad Zagorodniy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,66 +23,108 @@ #ifndef KWIN_DIMINACTIVE_H #define KWIN_DIMINACTIVE_H -// Include with base class for effects. +// kwineffects #include -#include - namespace KWin { -class DimInactiveEffect - : public Effect +class DimInactiveEffect : public Effect { Q_OBJECT - Q_PROPERTY(bool dimPanels READ isDimPanels) - Q_PROPERTY(bool dimDesktop READ isDimDesktop) - Q_PROPERTY(bool dimKeepAbove READ isDimKeepAbove) - Q_PROPERTY(bool dimByGroup READ isDimByGroup) - Q_PROPERTY(int dimStrength READ configuredDimStrength) + Q_PROPERTY(int dimStrength READ dimStrength) + Q_PROPERTY(bool dimPanels READ dimPanels) + Q_PROPERTY(bool dimDesktop READ dimDesktop) + Q_PROPERTY(bool dimKeepAbove READ dimKeepAbove) + Q_PROPERTY(bool dimByGroup READ dimByGroup) + public: DimInactiveEffect(); - virtual void reconfigure(ReconfigureFlags); - virtual void prePaintScreen(ScreenPrePaintData& data, int time); - virtual void paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data); - - int requestedEffectChainPosition() const override { - return 50; - } - - // for properties - bool isDimPanels() const { - return dim_panels; - } - bool isDimDesktop() const { - return dim_desktop; - } - bool isDimKeepAbove() const { - return dim_keepabove; - } - bool isDimByGroup() const { - return dim_by_group; - } - int configuredDimStrength() const { - return dim_strength; - } -public Q_SLOTS: - void slotWindowActivated(KWin::EffectWindow* c); - void slotWindowDeleted(KWin::EffectWindow *w); + ~DimInactiveEffect() override; + + void reconfigure(ReconfigureFlags flags) override; + + void prePaintScreen(ScreenPrePaintData &data, int time) override; + void paintWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data) override; + void postPaintScreen() override; + + int requestedEffectChainPosition() const override; + bool isActive() const override; + + int dimStrength() const; + bool dimPanels() const; + bool dimDesktop() const; + bool dimKeepAbove() const; + bool dimByGroup() const; + +private Q_SLOTS: + void windowActivated(EffectWindow *w); + void windowClosed(EffectWindow *w); + void windowDeleted(EffectWindow *w); + void activeFullScreenEffectChanged(); private: - bool dimWindow(const EffectWindow* w) const; - QTimeLine timeline; - EffectWindow* active; - EffectWindow* previousActive; - QTimeLine previousActiveTimeline; - int dim_strength; // reduce saturation and brightness by this percentage - bool dim_panels; // do/don't dim also all panels - bool dim_desktop; // do/don't dim the desktop - bool dim_keepabove; // do/don't dim keep-above windows - bool dim_by_group; // keep visible all windows from the active window's group or only the active window + void dimWindow(WindowPaintData &data, qreal strength); + bool canDimWindow(const EffectWindow *w) const; + void scheduleInTransition(EffectWindow *w); + void scheduleGroupInTransition(EffectWindow *w); + void scheduleOutTransition(EffectWindow *w); + void scheduleGroupOutTransition(EffectWindow *w); + void scheduleRepaint(EffectWindow *w); + +private: + qreal m_dimStrength; + bool m_dimPanels; + bool m_dimDesktop; + bool m_dimKeepAbove; + bool m_dimByGroup; + + EffectWindow *m_activeWindow; + const EffectWindowGroup *m_activeWindowGroup; + QHash m_transitions; + QHash m_forceDim; + + struct { + bool active = false; + TimeLine timeLine; + } m_fullScreenTransition; }; -} // namespace +inline int DimInactiveEffect::requestedEffectChainPosition() const +{ + return 50; +} + +inline bool DimInactiveEffect::isActive() const +{ + return true; +} + +inline int DimInactiveEffect::dimStrength() const +{ + return qRound(m_dimStrength * 100.0); +} + +inline bool DimInactiveEffect::dimPanels() const +{ + return m_dimPanels; +} + +inline bool DimInactiveEffect::dimDesktop() const +{ + return m_dimDesktop; +} + +inline bool DimInactiveEffect::dimKeepAbove() const +{ + return m_dimKeepAbove; +} + +inline bool DimInactiveEffect::dimByGroup() const +{ + return m_dimByGroup; +} + +} // namespace KWin #endif diff --git a/effects/diminactive/diminactive.cpp b/effects/diminactive/diminactive.cpp --- a/effects/diminactive/diminactive.cpp +++ b/effects/diminactive/diminactive.cpp @@ -4,6 +4,7 @@ Copyright (C) 2007 Lubos Lunak Copyright (C) 2007 Christian Nitschkowski +Copyright (C) 2018 Vlad Zagorodniy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,124 +20,371 @@ along with this program. If not, see . *********************************************************************/ +// own #include "diminactive.h" + // KConfigSkeleton #include "diminactiveconfig.h" -#include - namespace KWin { +/** + * Checks if two windows belong to the same window group + * + * One possible example of a window group is an app window and app + * preferences window(e.g. Dolphin window and Dolphin Preferences window). + * + * @param w1 The first window + * @param w2 The second window + * @returns @c true if both windows belong to the same window group, @c false otherwise + **/ +static inline bool belongToSameGroup(const EffectWindow *w1, const EffectWindow *w2) +{ + return w1 && w2 && w1->group() && w1->group() == w2->group(); +} + DimInactiveEffect::DimInactiveEffect() { + m_activeWindow = nullptr; + initConfig(); reconfigure(ReconfigureAll); - timeline.setDuration(animationTime(250)); - previousActiveTimeline.setDuration(animationTime(250)); - active = effects->activeWindow(); - previousActive = NULL; - connect(effects, SIGNAL(windowActivated(KWin::EffectWindow*)), this, SLOT(slotWindowActivated(KWin::EffectWindow*))); - connect(effects, SIGNAL(windowDeleted(KWin::EffectWindow*)), this, SLOT(slotWindowDeleted(KWin::EffectWindow*))); + + connect(effects, &EffectsHandler::windowActivated, + this, &DimInactiveEffect::windowActivated); + connect(effects, &EffectsHandler::windowClosed, + this, &DimInactiveEffect::windowClosed); + connect(effects, &EffectsHandler::windowDeleted, + this, &DimInactiveEffect::windowDeleted); + connect(effects, &EffectsHandler::activeFullScreenEffectChanged, + this, &DimInactiveEffect::activeFullScreenEffectChanged); } -void DimInactiveEffect::reconfigure(ReconfigureFlags) +DimInactiveEffect::~DimInactiveEffect() { +} + +void DimInactiveEffect::reconfigure(ReconfigureFlags flags) +{ + Q_UNUSED(flags) + DimInactiveConfig::self()->read(); - dim_panels = DimInactiveConfig::dimPanels(); - dim_desktop = DimInactiveConfig::dimDesktop(); - dim_keepabove = DimInactiveConfig::dimKeepAbove(); - dim_by_group = DimInactiveConfig::dimByGroup(); - dim_strength = DimInactiveConfig::strength(); + + // TODO: Use normalized strength param. + m_dimStrength = DimInactiveConfig::strength() / 100.0; + m_dimPanels = DimInactiveConfig::dimPanels(); + m_dimDesktop = DimInactiveConfig::dimDesktop(); + m_dimKeepAbove = DimInactiveConfig::dimKeepAbove(); + m_dimByGroup = DimInactiveConfig::dimByGroup(); + + EffectWindow *activeWindow = effects->activeWindow(); + m_activeWindow = (activeWindow && canDimWindow(activeWindow)) + ? activeWindow + : nullptr; + + m_activeWindowGroup = (m_dimByGroup && m_activeWindow) + ? m_activeWindow->group() + : nullptr; + + m_fullScreenTransition.timeLine.setDuration( + std::chrono::milliseconds(static_cast(animationTime(250)))); + effects->addRepaintFull(); } -void DimInactiveEffect::prePaintScreen(ScreenPrePaintData& data, int time) +void DimInactiveEffect::prePaintScreen(ScreenPrePaintData &data, int time) { - double oldValue = timeline.currentValue(); - if (effects->activeFullScreenEffect()) - timeline.setCurrentTime(timeline.currentTime() - time); - else - timeline.setCurrentTime(timeline.currentTime() + time); - if (oldValue != timeline.currentValue()) - effects->addRepaintFull(); - if (previousActive) { - // We are fading out the previous window - previousActive->addRepaintFull(); - previousActiveTimeline.setCurrentTime(previousActiveTimeline.currentTime() + time); + const std::chrono::milliseconds delta(time); + + if (m_fullScreenTransition.active) { + m_fullScreenTransition.timeLine.update(delta); } + + auto transitionIt = m_transitions.begin(); + while (transitionIt != m_transitions.end()) { + (*transitionIt).update(delta); + ++transitionIt; + } + effects->prePaintScreen(data, time); } -void DimInactiveEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) +void DimInactiveEffect::paintWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data) { - if (dimWindow(w) || w == previousActive) { - double previous = 1.0; - if (w == previousActive) - previous = previousActiveTimeline.currentValue(); - if (previousActiveTimeline.currentValue() == 1.0) - previousActive = NULL; - data.multiplyBrightness((1.0 - (dim_strength / 100.0) * timeline.currentValue() * previous)); - data.multiplySaturation((1.0 - (dim_strength / 100.0) * timeline.currentValue() * previous)); + auto transitionIt = m_transitions.constFind(w); + if (transitionIt != m_transitions.constEnd()) { + const qreal transitionProgress = (*transitionIt).value(); + dimWindow(data, m_dimStrength * transitionProgress); + effects->paintWindow(w, mask, region, data); + return; + } + + auto forceIt = m_forceDim.constFind(w); + if (forceIt != m_forceDim.constEnd()) { + const qreal forcedStrength = *forceIt; + dimWindow(data, forcedStrength); + effects->paintWindow(w, mask, region, data); + return; + } + + if (canDimWindow(w)) { + dimWindow(data, m_dimStrength); } + effects->paintWindow(w, mask, region, data); } -bool DimInactiveEffect::dimWindow(const EffectWindow* w) const -{ - if (effects->activeWindow() == w) - return false; // never dim active window - if (active && dim_by_group && active->group() == w->group()) - return false; // don't dim in active group if configured so - if (w->isDock() && !dim_panels) - return false; // don't dim panels if configured so - if (w->isDesktop() && !dim_desktop) - return false; // don't dim the desktop if configured so - if (w->keepAbove() && !dim_keepabove) - return false; // don't dim keep-above windows if configured so - if (!w->isNormalWindow() && !w->isDialog() && !w->isDock() && !w->isDesktop()) - return false; // don't dim more special window types - // don't dim unmanaged windows, grouping doesn't work for them and maybe dimming - // them doesn't make sense in general (they should be short-lived anyway) - if (!w->isManaged()) +void DimInactiveEffect::postPaintScreen() +{ + if (m_fullScreenTransition.active) { + if (m_fullScreenTransition.timeLine.done()) { + m_fullScreenTransition.active = false; + } + effects->addRepaintFull(); + } + + auto transitionIt = m_transitions.begin(); + while (transitionIt != m_transitions.end()) { + EffectWindow *w = transitionIt.key(); + if ((*transitionIt).done()) { + transitionIt = m_transitions.erase(transitionIt); + } else { + ++transitionIt; + } + w->addRepaintFull(); + } + + effects->postPaintScreen(); +} + +void DimInactiveEffect::dimWindow(WindowPaintData &data, qreal strength) +{ + qreal dimFactor; + if (m_fullScreenTransition.active) { + dimFactor = 1.0 - m_fullScreenTransition.timeLine.value(); + } else if (effects->activeFullScreenEffect()) { + dimFactor = 0.0; + } else { + dimFactor = 1.0; + } + + data.multiplyBrightness(1.0 - strength * dimFactor); + data.multiplySaturation(1.0 - strength * dimFactor); +} + +bool DimInactiveEffect::canDimWindow(const EffectWindow *w) const +{ + if (m_activeWindow == w) { + return false; + } + + if (m_dimByGroup && belongToSameGroup(m_activeWindow, w)) { return false; - return true; // dim the rest + } + + if (w->isDock() && !m_dimPanels) { + return false; + } + + if (w->isDesktop() && !m_dimDesktop) { + return false; + } + + if (w->keepAbove() && !m_dimKeepAbove) { + return false; + } + + if (!w->isManaged()) { + return false; + } + + return w->isNormalWindow() + || w->isDialog() + || w->isUtility() + || w->isDock() + || w->isDesktop(); } -void DimInactiveEffect::slotWindowDeleted(EffectWindow* w) +void DimInactiveEffect::scheduleInTransition(EffectWindow *w) { - if (w == previousActive) - previousActive = NULL; + TimeLine &timeLine = m_transitions[w]; + timeLine.setDuration( + std::chrono::milliseconds(static_cast(animationTime(160)))); + if (timeLine.done()) { + // If the Out animation is still active, then we're trucating + // duration of the timeline(from 250ms to 160ms). If the timeline + // is about to be finished with the old duration, then after + // changing duration it will be in the "done" state. Thus, we + // have to reset the timeline, otherwise it won't update progress. + timeLine.reset(); + } + timeLine.setDirection(TimeLine::Backward); + timeLine.setEasingCurve(QEasingCurve::InOutSine); } -void DimInactiveEffect::slotWindowActivated(EffectWindow* w) +void DimInactiveEffect::scheduleGroupInTransition(EffectWindow *w) { - if (active != NULL) { - previousActive = active; - previousActiveTimeline.setCurrentTime(0); - if (!dimWindow(previousActive)) - previousActive = NULL; + if (!m_dimByGroup) { + scheduleInTransition(w); + return; + } + + if (!w->group()) { + scheduleInTransition(w); + return; + } - if (dim_by_group) { - if ((w == NULL || w->group() != active->group()) && active->group() != NULL) { - // repaint windows that are no longer in the active group - foreach (EffectWindow * tmp, active->group()->members()) - tmp->addRepaintFull(); - } - } else - active->addRepaintFull(); + const auto members = w->group()->members(); + for (EffectWindow *member : members) { + scheduleInTransition(member); + } +} + +void DimInactiveEffect::scheduleOutTransition(EffectWindow *w) +{ + TimeLine &timeLine = m_transitions[w]; + timeLine.setDuration( + std::chrono::milliseconds(static_cast(animationTime(250)))); + if (timeLine.done()) { + timeLine.reset(); } - active = w; - if (active != NULL) { - if (dim_by_group) { - if (active->group() != NULL) { - // repaint newly active windows - foreach (EffectWindow * tmp, active->group()->members()) - tmp->addRepaintFull(); - } - } else - active->addRepaintFull(); + timeLine.setDirection(TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::InOutSine); +} + +void DimInactiveEffect::scheduleGroupOutTransition(EffectWindow *w) +{ + if (!m_dimByGroup) { + scheduleOutTransition(w); + return; + } + + if (!w->group()) { + scheduleOutTransition(w); + return; + } + + const auto members = w->group()->members(); + for (EffectWindow *member : members) { + scheduleOutTransition(member); + } +} + +void DimInactiveEffect::scheduleRepaint(EffectWindow *w) +{ + if (!m_dimByGroup) { + w->addRepaintFull(); + return; + } + + if (!w->group()) { + w->addRepaintFull(); + return; + } + + const auto members = w->group()->members(); + for (EffectWindow *member : members) { + member->addRepaintFull(); + } +} + +void DimInactiveEffect::windowActivated(EffectWindow *w) +{ + if (!w) { + return; + } + + if (m_activeWindow == w) { + return; } + + if (m_dimByGroup && belongToSameGroup(m_activeWindow, w)) { + m_activeWindow = w; + return; + } + + // WORKAROUND: Deleted windows do not belong to any of window groups. + // So, if one of windows in a window group is closed, the In transition + // will be false-triggered for the rest of the window group. In addition + // to the active window, keep track of active window group so we can + // tell whether "focus" moved from a closed window to some other window + // in a window group. + if (m_dimByGroup && w->group() && w->group() == m_activeWindowGroup) { + m_activeWindow = w; + return; + } + + EffectWindow *previousActiveWindow = m_activeWindow; + m_activeWindow = canDimWindow(w) ? w : nullptr; + + m_activeWindowGroup = (m_dimByGroup && m_activeWindow) + ? m_activeWindow->group() + : nullptr; + + if (previousActiveWindow) { + scheduleGroupOutTransition(previousActiveWindow); + scheduleRepaint(previousActiveWindow); + } + + if (m_activeWindow) { + scheduleGroupInTransition(m_activeWindow); + scheduleRepaint(m_activeWindow); + } +} + +void DimInactiveEffect::windowClosed(EffectWindow *w) +{ + // When a window is closed, we should force current dim strength that + // is applied to it to avoid flickering when some effect animates + // the disappearing of the window. If there is no such effect then + // it won't be dimmed. + qreal forcedStrength = 0.0; + bool shouldForceDim = false; + + auto transitionIt = m_transitions.find(w); + if (transitionIt != m_transitions.end()) { + forcedStrength = m_dimStrength * (*transitionIt).value(); + shouldForceDim = true; + m_transitions.erase(transitionIt); + } else if (m_activeWindow == w) { + forcedStrength = 0.0; + shouldForceDim = true; + } else if (m_dimByGroup && belongToSameGroup(m_activeWindow, w)) { + forcedStrength = 0.0; + shouldForceDim = true; + } else if (canDimWindow(w)) { + forcedStrength = m_dimStrength; + shouldForceDim = true; + } + + if (shouldForceDim) { + m_forceDim.insert(w, forcedStrength); + } + + if (m_activeWindow == w) { + m_activeWindow = nullptr; + } +} + +void DimInactiveEffect::windowDeleted(EffectWindow *w) +{ + m_forceDim.remove(w); +} + +void DimInactiveEffect::activeFullScreenEffectChanged() +{ + if (m_fullScreenTransition.timeLine.done()) { + m_fullScreenTransition.timeLine.reset(); + } + m_fullScreenTransition.timeLine.setDirection( + effects->activeFullScreenEffect() + ? TimeLine::Forward + : TimeLine::Backward + ); + m_fullScreenTransition.active = true; + + effects->addRepaintFull(); } -} // namespace +} // namespace KWin diff --git a/effects/diminactive/diminactive_config.h b/effects/diminactive/diminactive_config.h --- a/effects/diminactive/diminactive_config.h +++ b/effects/diminactive/diminactive_config.h @@ -3,6 +3,7 @@ This file is part of the KDE project. Copyright (C) 2007 Christian Nitschkowski +Copyright (C) 2018 Vlad Zagorodniy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,32 +22,27 @@ #ifndef KWIN_DIMINACTIVE_CONFIG_H #define KWIN_DIMINACTIVE_CONFIG_H -#include +#include #include "ui_diminactive_config.h" namespace KWin { -class DimInactiveEffectConfigForm : public QWidget, public Ui::DimInactiveEffectConfigForm -{ - Q_OBJECT -public: - explicit DimInactiveEffectConfigForm(QWidget* parent); -}; - class DimInactiveEffectConfig : public KCModule { Q_OBJECT + public: - explicit DimInactiveEffectConfig(QWidget* parent = 0, const QVariantList& args = QVariantList()); + explicit DimInactiveEffectConfig(QWidget *parent = nullptr, const QVariantList &args = QVariantList()); + ~DimInactiveEffectConfig() override; - virtual void save(); + void save() override; private: - DimInactiveEffectConfigForm* m_ui; + ::Ui::DimInactiveEffectConfig m_ui; }; -} // namespace +} // namespace KWin #endif diff --git a/effects/diminactive/diminactive_config.cpp b/effects/diminactive/diminactive_config.cpp --- a/effects/diminactive/diminactive_config.cpp +++ b/effects/diminactive/diminactive_config.cpp @@ -3,6 +3,7 @@ This file is part of the KDE project. Copyright (C) 2007 Christian Nitschkowski +Copyright (C) 2018 Vlad Zagorodniy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,56 +20,46 @@ *********************************************************************/ #include "diminactive_config.h" + // KConfigSkeleton #include "diminactiveconfig.h" #include #include -#include -#include #include #include -#include -#include - K_PLUGIN_FACTORY_WITH_JSON(DimInactiveEffectConfigFactory, "diminactive_config.json", registerPlugin();) namespace KWin { -DimInactiveEffectConfigForm::DimInactiveEffectConfigForm(QWidget* parent) : QWidget(parent) +DimInactiveEffectConfig::DimInactiveEffectConfig(QWidget *parent, const QVariantList &args) + : KCModule(KAboutData::pluginData(QStringLiteral("diminactive")), parent, args) { - setupUi(this); + m_ui.setupUi(this); + DimInactiveConfig::instance(KWIN_CONFIG); + addConfig(DimInactiveConfig::self(), this); + load(); } -DimInactiveEffectConfig::DimInactiveEffectConfig(QWidget* parent, const QVariantList& args) : - KCModule(KAboutData::pluginData(QStringLiteral("diminactive")), parent, args) +DimInactiveEffectConfig::~DimInactiveEffectConfig() { - m_ui = new DimInactiveEffectConfigForm(this); - - QVBoxLayout* layout = new QVBoxLayout(this); - - layout->addWidget(m_ui); - - DimInactiveConfig::instance(KWIN_CONFIG); - addConfig(DimInactiveConfig::self(), m_ui); - - load(); } void DimInactiveEffectConfig::save() { KCModule::save(); + OrgKdeKwinEffectsInterface interface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Effects"), QDBusConnection::sessionBus()); interface.reconfigureEffect(QStringLiteral("diminactive")); } -} // namespace +} // namespace KWin #include "diminactive_config.moc" diff --git a/effects/diminactive/diminactive_config.ui b/effects/diminactive/diminactive_config.ui --- a/effects/diminactive/diminactive_config.ui +++ b/effects/diminactive/diminactive_config.ui @@ -1,144 +1,76 @@ - KWin::DimInactiveEffectConfigForm - + DimInactiveEffectConfig + 0 0 - 298 - 161 + 400 + 160 - - - + + + - Apply effect to &panels + Strength: - - - - Apply effect to the desk&top + + + + + 0 + 0 + + + + 100 + + + 5 - - + + - Apply effect to &keep-above windows + Dim: - - + + - Apply effect to &groups + Docks and panels - - + + - &Strength: - - - kcfg_Strength + Desktop - - - - - 120 - 0 - - - - 1 - - - 100 - - - 25 - - - Qt::Horizontal + + + + Keep above windows - - - - 1 - - - 100 - - - 25 + + + + By window group - - - - Qt::Vertical - - - - 20 - 0 - - - - - - kcfg_sliderStrength - kcfg_Strength - kcfg_DimPanels - kcfg_DimDesktop - kcfg_DimByGroup - - - - kcfg_Strength - valueChanged(int) - kcfg_sliderStrength - setValue(int) - - - 288 - 29 - - - 195 - 26 - - - - - kcfg_sliderStrength - valueChanged(int) - kcfg_Strength - setValue(int) - - - 136 - 26 - - - 288 - 29 - - - - +