Merge desktop grid and overview together with a new three-state design

Merges the desktop grid and overview effects together in a new three-state one;
you can switch between them with a certain shortcut or gesture, and you can also
still access either the desktop grid or overview directly.

Default shortcuts are also updated to be Meta+G for Grid, Meta+W for Overview,
Meta+Tab to switch between the three states and Meta+Shift+Tab to cycle in the
opposite direction.

BUG: 474044
BUG: 460661
BUG: 460774
BUG: 456572
BUG: 449601
BUG: 450262
BUG: 449801
BUG: 461510
BUG: 463886
BUG: 459754
BUG: 459749
BUG: 459748
BUG: 459467
FIXED-IN: 6.0
This commit is contained in:
Niccolò Venerandi 2023-09-25 11:14:37 +00:00
parent 1d6ac05a8c
commit 028dd552cf
29 changed files with 987 additions and 1872 deletions

View file

@ -51,6 +51,7 @@ option(KWIN_BUILD_TABBOX "Enable building of KWin Tabbox functionality" ON)
find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS
Concurrent
Core
Core5Compat
DBus
Quick
UiTools
@ -455,6 +456,7 @@ if (KF6DocTools_FOUND)
endif()
add_subdirectory(data)
add_subdirectory(kconf_update)
add_subdirectory(src)
if (BUILD_TESTING)

View file

@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: 2023 Niccolò Venerandi <niccolo.venerandi@kde.org>
# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
install(FILES kwin.upd
DESTINATION ${KDE_INSTALL_KCONFUPDATEDIR})
install(PROGRAMS kwin-6.0-overview-activities-shortcuts.py
DESTINATION ${KDE_INSTALL_KCONFUPDATEDIR})

View file

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2023 Niccolò Venerandi <niccolo.venerandi@kde.org>
# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import fileinput
for line in fileinput.input():
if line.startswith('next activity'):
print(line.replace('Meta+Tab', 'Meta+A'))
elif line.startswith('previous activity'):
print(line.replace('Meta+Shift+Tab', 'Meta+Shift+A'))
elif line.startswith('ShowDesktopGrid'):
pass
elif line.startswith('Overview'):
print('Overview=Meta+W,Meta+W,Toggle Overview')
print('Cycle Overview=Meta+Tab,Meta+Tab,Cycle through Overview and Grid View')
print('Cycle Overview Opposite=Meta+Shift+Tab,Meta+Shift+Tab,Cycle through Grid View and Overview')
print('Grid View=Meta+G,Meta+G,Toggle Grid View')
else:
print(line)

11
kconf_update/kwin.upd Normal file
View file

@ -0,0 +1,11 @@
# SPDX-FileCopyrightText: 2023 Niccolò Venerandi <niccolo.venerandi@kde.org>
# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
Version=5
# Changes the default Activities shortcut from Meta+Tab to Meta+A,
# so that the Overview can take its place
Id=change-activities-overview-shortcuts
File=kglobalshortcutsrc
Group=plasmashell,kwin
Script=kwin-6.0-overview-activities-shortcuts.py,python3

View file

@ -43,9 +43,9 @@ EffectTogglableState::EffectTogglableState(Effect *effect)
void EffectTogglableState::activate()
{
setStatus(Status::Active);
setInProgress(false);
setPartialActivationFactor(0.0);
setPartialActivationFactor(1.0);
setStatus(Status::Active);
}
void EffectTogglableState::setPartialActivationFactor(qreal factor)
@ -60,6 +60,13 @@ void EffectTogglableState::deactivate()
{
setInProgress(false);
setPartialActivationFactor(0.0);
setStatus(Status::Inactive);
}
void EffectTogglableState::stop()
{
setInProgress(false);
setStatus(Status::Stopped);
}
bool EffectTogglableState::inProgress() const
@ -103,7 +110,7 @@ void EffectTogglableState::partialDeactivate(qreal factor)
void EffectTogglableState::toggle()
{
if (m_status == Status::Inactive || m_partialActivationFactor > 0.5) {
if (m_status == Status::Inactive) {
activate();
Q_EMIT activated();
} else {
@ -114,6 +121,9 @@ void EffectTogglableState::toggle()
void EffectTogglableState::setProgress(qreal progress)
{
if (m_status == Status::Stopped) {
return;
}
if (!effects->hasActiveFullScreenEffect() || effects->activeFullScreenEffect() == parent()) {
switch (m_status) {
case Status::Inactive:
@ -128,6 +138,9 @@ void EffectTogglableState::setProgress(qreal progress)
void EffectTogglableState::setRegress(qreal regress)
{
if (m_status == Status::Stopped) {
return;
}
if (!effects->hasActiveFullScreenEffect() || effects->activeFullScreenEffect() == parent()) {
switch (m_status) {
case Status::Active:

View file

@ -29,7 +29,8 @@ public:
Inactive,
Activating,
Deactivating,
Active
Active,
Stopped
};
Q_ENUM(Status)
@ -61,6 +62,7 @@ public:
void activate();
void deactivate();
void toggle();
void stop();
void setStatus(Status status);
Status status() const
{

View file

@ -54,7 +54,6 @@ add_subdirectory(buttonrebinds)
add_subdirectory(colord-integration)
add_subdirectory(colorpicker)
add_subdirectory(desktopchangeosd)
add_subdirectory(desktopgrid)
add_subdirectory(dialogparent)
add_subdirectory(diminactive)
add_subdirectory(dimscreen)

View file

@ -1,49 +0,0 @@
# SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
#
# SPDX-License-Identifier: BSD-3-Clause
set(desktopgrid_SOURCES
main.cpp
desktopgrideffect.cpp
)
kconfig_add_kcfg_files(desktopgrid_SOURCES
desktopgridconfig.kcfgc
)
kwin_add_builtin_effect(desktopgrid ${desktopgrid_SOURCES})
target_link_libraries(desktopgrid PRIVATE
kwineffects
KF6::ConfigGui
KF6::GlobalAccel
KF6::I18n
Qt::Quick
)
#######################################
# Config
if (KWIN_BUILD_KCMS)
set(kwin_desktopgrid_config_SRCS desktopgrid_config.cpp)
ki18n_wrap_ui(kwin_desktopgrid_config_SRCS desktopgrid_config.ui)
kconfig_add_kcfg_files(kwin_desktopgrid_config_SRCS desktopgridconfig.kcfgc)
kwin_add_effect_config(kwin_desktopgrid_config ${kwin_desktopgrid_config_SRCS})
target_link_libraries(kwin_desktopgrid_config
KF6::KCMUtils
KF6::CoreAddons
KF6::GlobalAccel
KF6::I18n
KF6::XmlGui
Qt::Quick
kwineffects
KWinEffectsInterface
)
endif()
install(DIRECTORY qml DESTINATION ${KDE_INSTALL_DATADIR}/kwin/effects/desktopgrid)

View file

@ -1,127 +0,0 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2007 Rivo Laks <rivolaks@hot.ee>
SPDX-FileCopyrightText: 2008 Lucas Murray <lmurray@undefinedfire.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "desktopgrid_config.h"
#include <config-kwin.h>
// KConfigSkeleton
#include "desktopgridconfig.h"
#include <kwineffects_interface.h>
#include <QAction>
#include <KActionCollection>
#include <KGlobalAccel>
#include <KLocalizedString>
#include <KPluginFactory>
#include <kconfiggroup.h>
#include <QVBoxLayout>
K_PLUGIN_CLASS(KWin::DesktopGridEffectConfig)
namespace KWin
{
DesktopGridEffectConfigForm::DesktopGridEffectConfigForm(QWidget *parent)
: QWidget(parent)
{
setupUi(this);
}
DesktopGridEffectConfig::DesktopGridEffectConfig(QObject *parent, const KPluginMetaData &data)
: KCModule(parent, data)
, m_ui(widget())
{
QVBoxLayout *layout = new QVBoxLayout(widget());
layout->addWidget(&m_ui);
// Shortcut config. The shortcut belongs to the component "kwin"!
m_actionCollection = new KActionCollection(widget(), QStringLiteral("kwin"));
m_actionCollection->setComponentDisplayName(i18n("KWin"));
m_actionCollection->setConfigGroup(QStringLiteral("DesktopGrid"));
m_actionCollection->setConfigGlobal(true);
QAction *a = m_actionCollection->addAction(QStringLiteral("ShowDesktopGrid"));
a->setText(i18n("Show Desktop Grid"));
a->setProperty("isConfigurationAction", true);
KGlobalAccel::self()->setDefaultShortcut(a, QList<QKeySequence>() << (Qt::CTRL | Qt::Key_F8));
KGlobalAccel::self()->setShortcut(a, QList<QKeySequence>() << (Qt::CTRL | Qt::Key_F8));
m_ui.shortcutEditor->addCollection(m_actionCollection);
m_ui.desktopNameAlignmentCombo->addItem(i18nc("Desktop name alignment:", "Disabled"), QVariant(Qt::Alignment()));
m_ui.desktopNameAlignmentCombo->addItem(i18n("Top"), QVariant(Qt::AlignHCenter | Qt::AlignTop));
m_ui.desktopNameAlignmentCombo->addItem(i18n("Top-Right"), QVariant(Qt::AlignRight | Qt::AlignTop));
m_ui.desktopNameAlignmentCombo->addItem(i18n("Right"), QVariant(Qt::AlignRight | Qt::AlignVCenter));
m_ui.desktopNameAlignmentCombo->addItem(i18n("Bottom-Right"), QVariant(Qt::AlignRight | Qt::AlignBottom));
m_ui.desktopNameAlignmentCombo->addItem(i18n("Bottom"), QVariant(Qt::AlignHCenter | Qt::AlignBottom));
m_ui.desktopNameAlignmentCombo->addItem(i18n("Bottom-Left"), QVariant(Qt::AlignLeft | Qt::AlignBottom));
m_ui.desktopNameAlignmentCombo->addItem(i18n("Left"), QVariant(Qt::AlignLeft | Qt::AlignVCenter));
m_ui.desktopNameAlignmentCombo->addItem(i18n("Top-Left"), QVariant(Qt::AlignLeft | Qt::AlignTop));
m_ui.desktopNameAlignmentCombo->addItem(i18n("Center"), QVariant(Qt::AlignCenter));
DesktopGridConfig::instance(KWIN_CONFIG);
addConfig(DesktopGridConfig::self(), &m_ui);
connect(m_ui.kcfg_DesktopLayoutMode, qOverload<int>(&QComboBox::currentIndexChanged), this, &DesktopGridEffectConfig::desktopLayoutSelectionChanged);
connect(m_ui.desktopNameAlignmentCombo, qOverload<int>(&QComboBox::currentIndexChanged), this, &KCModule::markAsChanged);
connect(m_ui.shortcutEditor, &KShortcutsEditor::keyChange, this, &KCModule::markAsChanged);
}
DesktopGridEffectConfig::~DesktopGridEffectConfig()
{
// If save() is called undo() has no effect
m_ui.shortcutEditor->undo();
}
void DesktopGridEffectConfig::save()
{
m_ui.shortcutEditor->save();
DesktopGridConfig::setDesktopNameAlignment(m_ui.desktopNameAlignmentCombo->itemData(m_ui.desktopNameAlignmentCombo->currentIndex()).toInt());
KCModule::save();
DesktopGridConfig::self()->save();
OrgKdeKwinEffectsInterface interface(QStringLiteral("org.kde.KWin"),
QStringLiteral("/Effects"),
QDBusConnection::sessionBus());
interface.reconfigureEffect(QStringLiteral("desktopgrid"));
}
void DesktopGridEffectConfig::load()
{
KCModule::load();
m_ui.desktopNameAlignmentCombo->setCurrentIndex(m_ui.desktopNameAlignmentCombo->findData(QVariant(DesktopGridConfig::desktopNameAlignment())));
desktopLayoutSelectionChanged();
}
void DesktopGridEffectConfig::desktopLayoutSelectionChanged()
{
if (m_ui.kcfg_DesktopLayoutMode->currentIndex() == int(DesktopGridEffect::DesktopLayoutMode::LayoutCustom)) {
m_ui.layoutRowsLabel->setEnabled(true);
m_ui.kcfg_CustomLayoutRows->setEnabled(true);
} else {
m_ui.layoutRowsLabel->setEnabled(false);
m_ui.kcfg_CustomLayoutRows->setEnabled(false);
}
}
void DesktopGridEffectConfig::defaults()
{
KCModule::defaults();
m_ui.desktopNameAlignmentCombo->setCurrentIndex(0);
}
} // namespace
#include "desktopgrid_config.moc"
#include "moc_desktopgrid_config.cpp"

View file

@ -1,48 +0,0 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2007 Rivo Laks <rivolaks@hot.ee>
SPDX-FileCopyrightText: 2008 Lucas Murray <lmurray@undefinedfire.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <kcmodule.h>
#include "desktopgrideffect.h"
#include "ui_desktopgrid_config.h"
namespace KWin
{
class DesktopGridEffectConfigForm : public QWidget, public Ui::DesktopGridEffectConfigForm
{
Q_OBJECT
public:
explicit DesktopGridEffectConfigForm(QWidget *parent);
};
class DesktopGridEffectConfig : public KCModule
{
Q_OBJECT
public:
explicit DesktopGridEffectConfig(QObject *parent, const KPluginMetaData &data);
~DesktopGridEffectConfig() override;
public Q_SLOTS:
void save() override;
void load() override;
void defaults() override;
private Q_SLOTS:
void desktopLayoutSelectionChanged();
private:
DesktopGridEffectConfigForm m_ui;
KActionCollection *m_actionCollection;
};
} // namespace

View file

@ -1,206 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>KWin::DesktopGridEffectConfigForm</class>
<widget class="QWidget" name="KWin::DesktopGridEffectConfigForm">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>574</width>
<height>312</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Appearance</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="1">
<widget class="QComboBox" name="kcfg_DesktopLayoutMode">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>Pager</string>
</property>
</item>
<item>
<property name="text">
<string>Automatic</string>
</property>
</item>
<item>
<property name="text">
<string>Custom</string>
</property>
</item>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="layoutRowsLabel">
<property name="text">
<string>N&amp;umber of rows:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>kcfg_CustomLayoutRows</cstring>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="kcfg_CustomLayoutRows">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>20</number>
</property>
<property name="value">
<number>2</number>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="desktopNameAlignmentCombo">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Desktop &amp;name alignment:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>desktopNameAlignmentCombo</cstring>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="kcfg_ShowAddRemove">
<property name="text">
<string>Show buttons to alter count of virtual desktops</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>&amp;Grid layout mode:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>kcfg_DesktopLayoutMode</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="kcfg_LayoutMode">
<item>
<property name="text">
<string>Closest</string>
</property>
</item>
<item>
<property name="text">
<string>Natural</string>
</property>
</item>
<item>
<property name="text">
<string>None</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Windows layout:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>kcfg_LayoutMode</cstring>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Activation</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0" colspan="2">
<widget class="KShortcutsEditor" name="shortcutEditor">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KShortcutsEditor</class>
<extends>QWidget</extends>
<header>kshortcutseditor.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>desktopNameAlignmentCombo</tabstop>
<tabstop>kcfg_DesktopLayoutMode</tabstop>
<tabstop>kcfg_LayoutMode</tabstop>
<tabstop>kcfg_CustomLayoutRows</tabstop>
<tabstop>kcfg_ShowAddRemove</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View file

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
-->
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
<kcfgfile arg="true"/>
<group name="Effect-desktopgrid">
<entry name="BorderActivate" type="IntList" />
<entry name="TouchBorderActivate" type="IntList" />
<entry name="DesktopNameAlignment" type="Int">
<default>0</default>
</entry>
<entry name="LayoutMode" type="Int">
<default>1</default>
</entry>
<entry name="DesktopLayoutMode" type="Int">
<default code="true">0</default>
</entry>
<entry name="CustomLayoutRows" type="Int">
<default>2</default>
</entry>
<entry name="ShowAddRemove" type="Bool">
<default>true</default>
</entry>
</group>
</kcfg>

View file

@ -1,9 +0,0 @@
# SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
#
# SPDX-License-Identifier: CC0-1.0
File=desktopgridconfig.kcfg
ClassName=DesktopGridConfig
NameSpace=KWin
Singleton=true
Mutators=true

View file

@ -1,250 +0,0 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "desktopgrideffect.h"
#include "desktopgridconfig.h"
#include <QAction>
#include <QQuickItem>
#include <QTimer>
#include <cmath>
#include <KGlobalAccel>
#include <KLocalizedString>
namespace KWin
{
DesktopGridEffect::DesktopGridEffect()
: m_shutdownTimer(new QTimer(this))
, m_state(new EffectTogglableState(this))
, m_border(new EffectTogglableTouchBorder(m_state))
{
qmlRegisterUncreatableType<DesktopGridEffect>("org.kde.kwin.private.desktopgrid", 1, 0, "DesktopGridEffect", QStringLiteral("Cannot create elements of type DesktopGridEffect"));
m_shutdownTimer->setSingleShot(true);
connect(m_shutdownTimer, &QTimer::timeout, this, &DesktopGridEffect::realDeactivate);
connect(effects, &EffectsHandler::screenAboutToLock, this, &DesktopGridEffect::realDeactivate);
connect(effects, &EffectsHandler::desktopGridWidthChanged, this, &DesktopGridEffect::gridColumnsChanged);
connect(effects, &EffectsHandler::desktopGridHeightChanged, this, &DesktopGridEffect::gridRowsChanged);
connect(m_state, &EffectTogglableState::activated, this, &DesktopGridEffect::activate);
connect(m_state, &EffectTogglableState::deactivated, this, &DesktopGridEffect::deactivate);
connect(m_state, &EffectTogglableState::inProgressChanged, this, &DesktopGridEffect::gestureInProgressChanged);
connect(m_state, &EffectTogglableState::partialActivationFactorChanged, this, &DesktopGridEffect::partialActivationFactorChanged);
connect(m_state, &EffectTogglableState::statusChanged, this, [this](EffectTogglableState::Status status) {
setRunning(status != EffectTogglableState::Status::Inactive);
});
auto toggleAction = m_state->toggleAction();
toggleAction->setObjectName(QStringLiteral("ShowDesktopGrid"));
toggleAction->setText(i18n("Show Desktop Grid"));
KGlobalAccel::self()->setDefaultShortcut(toggleAction, QList<QKeySequence>() << (Qt::META | Qt::Key_F8));
KGlobalAccel::self()->setShortcut(toggleAction, QList<QKeySequence>() << (Qt::META | Qt::Key_F8));
m_toggleShortcut = KGlobalAccel::self()->shortcut(toggleAction);
auto gesture = new EffectTogglableGesture(m_state);
gesture->addTouchpadSwipeGesture(SwipeDirection::Up, 4);
initConfig<DesktopGridConfig>();
reconfigure(ReconfigureAll);
setSource(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kwin/effects/desktopgrid/qml/main.qml"))));
}
DesktopGridEffect::~DesktopGridEffect()
{
}
void DesktopGridEffect::reconfigure(ReconfigureFlags)
{
DesktopGridConfig::self()->read();
setLayout(DesktopGridConfig::layoutMode());
setAnimationDuration(animationTime(300));
for (const ElectricBorder &border : std::as_const(m_borderActivate)) {
effects->unreserveElectricBorder(border, this);
}
m_borderActivate.clear();
const QList<int> activateBorders = DesktopGridConfig::borderActivate();
for (const int &border : activateBorders) {
m_borderActivate.append(ElectricBorder(border));
effects->reserveElectricBorder(ElectricBorder(border), this);
}
m_border->setBorders(DesktopGridConfig::touchBorderActivate());
Q_EMIT showAddRemoveChanged();
Q_EMIT desktopNameAlignmentChanged();
Q_EMIT desktopLayoutModeChanged();
Q_EMIT customLayoutRowsChanged();
}
int DesktopGridEffect::requestedEffectChainPosition() const
{
return 70;
}
bool DesktopGridEffect::borderActivated(ElectricBorder border)
{
if (m_borderActivate.contains(border)) {
m_state->toggle();
return true;
}
return false;
}
void DesktopGridEffect::grabbedKeyboardEvent(QKeyEvent *keyEvent)
{
if (m_toggleShortcut.contains(keyEvent->key() | keyEvent->modifiers())) {
if (keyEvent->type() == QEvent::KeyPress) {
m_state->toggle();
}
return;
}
QuickSceneEffect::grabbedKeyboardEvent(keyEvent);
}
Qt::AlignmentFlag DesktopGridEffect::desktopNameAlignment() const
{
return Qt::AlignmentFlag(DesktopGridConfig::desktopNameAlignment());
}
DesktopGridEffect::DesktopLayoutMode DesktopGridEffect::desktopLayoutMode() const
{
return DesktopGridEffect::DesktopLayoutMode(DesktopGridConfig::desktopLayoutMode());
}
int DesktopGridEffect::customLayoutRows() const
{
return DesktopGridConfig::customLayoutRows();
}
void DesktopGridEffect::addDesktop() const
{
effects->setNumberOfDesktops(effects->numberOfDesktops() + 1);
}
void DesktopGridEffect::removeDesktop() const
{
effects->setNumberOfDesktops(effects->numberOfDesktops() - 1);
}
void DesktopGridEffect::swapDesktops(int from, int to)
{
QList<EffectWindow *> fromList;
QList<EffectWindow *> toList;
for (auto *w : effects->stackingOrder()) {
if (!w->isNormalWindow() || !w->isOnCurrentActivity() ) {
continue;
}
if (w->isOnDesktop(from)) {
fromList << w;
} else if (w->isOnDesktop(to)) {
toList << w;
}
}
for (auto *w : fromList) {
effects->windowToDesktop(w, to);
}
for (auto *w : toList) {
effects->windowToDesktop(w, from);
}
}
int DesktopGridEffect::gridRows() const
{
switch (desktopLayoutMode()) {
case DesktopLayoutMode::LayoutAutomatic:
return ceil(sqrt(effects->numberOfDesktops()));
case DesktopLayoutMode::LayoutCustom:
return std::clamp(customLayoutRows(), 1, effects->numberOfDesktops());
case DesktopLayoutMode::LayoutPager:
default:
return effects->desktopGridSize().height();
}
}
int DesktopGridEffect::gridColumns() const
{
switch (desktopLayoutMode()) {
case DesktopLayoutMode::LayoutAutomatic:
return ceil(sqrt(effects->numberOfDesktops()));
case DesktopLayoutMode::LayoutCustom:
return std::max(1.0, ceil(qreal(effects->numberOfDesktops()) / customLayoutRows()));
case DesktopLayoutMode::LayoutPager:
default:
return effects->desktopGridSize().width();
}
}
int DesktopGridEffect::animationDuration() const
{
return m_animationDuration;
}
void DesktopGridEffect::setAnimationDuration(int duration)
{
if (m_animationDuration != duration) {
m_animationDuration = duration;
Q_EMIT animationDurationChanged();
}
}
bool DesktopGridEffect::showAddRemove() const
{
return DesktopGridConfig::showAddRemove();
}
int DesktopGridEffect::layout() const
{
return m_layout;
}
void DesktopGridEffect::setLayout(int layout)
{
if (m_layout != layout) {
m_layout = layout;
Q_EMIT layoutChanged();
}
}
bool DesktopGridEffect::gestureInProgress() const
{
return m_state->inProgress();
}
void DesktopGridEffect::activate()
{
if (effects->isScreenLocked()) {
return;
}
m_state->activate();
}
void DesktopGridEffect::deactivate()
{
const auto screens = effects->screens();
for (const auto screen : screens) {
if (QuickSceneView *view = viewForScreen(screen)) {
QMetaObject::invokeMethod(view->rootItem(), "stop");
}
}
m_shutdownTimer->start(animationDuration());
m_state->deactivate();
}
void DesktopGridEffect::realDeactivate()
{
m_state->setStatus(EffectTogglableState::Status::Inactive);
}
} // namespace KWin
#include "moc_desktopgrideffect.cpp"

View file

@ -1,99 +0,0 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "libkwineffects/effecttogglablestate.h"
#include "libkwineffects/kwinquickeffect.h"
namespace KWin
{
class DesktopGridEffect : public QuickSceneEffect
{
Q_OBJECT
Q_PROPERTY(int gridRows READ gridRows NOTIFY gridRowsChanged)
Q_PROPERTY(int gridColumns READ gridColumns NOTIFY gridColumnsChanged)
Q_PROPERTY(int animationDuration READ animationDuration NOTIFY animationDurationChanged)
Q_PROPERTY(int layout READ layout NOTIFY layoutChanged)
Q_PROPERTY(qreal partialActivationFactor READ partialActivationFactor NOTIFY partialActivationFactorChanged)
Q_PROPERTY(bool gestureInProgress READ gestureInProgress NOTIFY gestureInProgressChanged)
Q_PROPERTY(bool showAddRemove READ showAddRemove NOTIFY showAddRemoveChanged)
Q_PROPERTY(Qt::AlignmentFlag desktopNameAlignment READ desktopNameAlignment NOTIFY desktopNameAlignmentChanged)
Q_PROPERTY(DesktopLayoutMode desktopLayoutMode READ desktopLayoutMode NOTIFY desktopLayoutModeChanged)
Q_PROPERTY(int customLayoutRows READ customLayoutRows NOTIFY customLayoutRowsChanged)
public:
enum class DesktopLayoutMode {
LayoutPager,
LayoutAutomatic,
LayoutCustom
};
Q_ENUM(DesktopLayoutMode)
DesktopGridEffect();
~DesktopGridEffect() override;
int layout() const;
void setLayout(int layout);
int animationDuration() const;
void setAnimationDuration(int duration);
bool showAddRemove() const;
qreal partialActivationFactor() const
{
return m_state->partialActivationFactor();
}
bool gestureInProgress() const;
int gridRows() const;
int gridColumns() const;
int requestedEffectChainPosition() const override;
bool borderActivated(ElectricBorder border) override;
void reconfigure(ReconfigureFlags flags) override;
void grabbedKeyboardEvent(QKeyEvent *keyEvent) override;
Qt::AlignmentFlag desktopNameAlignment() const;
DesktopLayoutMode desktopLayoutMode() const;
int customLayoutRows() const;
Q_INVOKABLE void addDesktop() const;
Q_INVOKABLE void removeDesktop() const;
Q_INVOKABLE void swapDesktops(int from, int to);
public Q_SLOTS:
void activate();
void deactivate();
Q_SIGNALS:
void gridRowsChanged();
void gridColumnsChanged();
void animationDurationChanged();
void layoutChanged();
void partialActivationFactorChanged();
void gestureInProgressChanged();
void showAddRemoveChanged();
void desktopNameAlignmentChanged();
void desktopLayoutModeChanged();
void customLayoutRowsChanged();
private:
void realDeactivate();
QTimer *m_shutdownTimer;
QList<QKeySequence> m_toggleShortcut;
QList<ElectricBorder> m_borderActivate;
EffectTogglableState *const m_state;
EffectTogglableTouchBorder *const m_border;
int m_animationDuration = 400;
int m_layout = 1;
};
} // namespace KWin

View file

@ -1,18 +0,0 @@
/*
SPDX-FileCopyrightText: 2022 Marco Martin <mart@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "desktopgrideffect.h"
namespace KWin
{
KWIN_EFFECT_FACTORY_SUPPORTED(DesktopGridEffect,
"metadata.json.stripped",
return DesktopGridEffect::supported();)
} // namespace KWin
#include "main.moc"

View file

@ -1,92 +0,0 @@
{
"KPlugin": {
"Category": "Window Management",
"Description": "Zoom out so all desktops are displayed side-by-side in a grid",
"Description[ar]": "بعّد لتُعرَض كلّ أسطح المكتب واحدة بجانب الأخرى في شبكة",
"Description[az]": "Bütün İş Masaların bir ekranda yan yana göstərmək",
"Description[be]": "Маштаб памяншаецца, і ўсе працоўныя сталы паказваюцца побач адзін з адным у сетцы",
"Description[bg]": "Намаляване на мащаба, докато всички работни плотове се представят като плочки един до друг",
"Description[ca@valencia]": "Reduïx tots els escriptoris per a visualitzar-los un al costat de l'altre en una quadrícula",
"Description[ca]": "Redueix tots els escriptoris per a visualitzar-los un al costat de l'altre en una quadrícula",
"Description[cs]": "Oddálí plochy a zobrazí je v mřížce",
"Description[de]": "Verkleinert die Arbeitsflächen, sodass sie in einem Raster nebeneinander zu sehen sind",
"Description[en_GB]": "Zoom out so all desktops are displayed side-by-side in a grid",
"Description[eo]": "Malzomi por ke ĉiuj labortabloj estu montrataj flank-al-flanke en krado",
"Description[es]": "Reduce la ampliación para que todos los escritorios se muestren uno al lado del otro en una cuadrícula",
"Description[et]": "Vähendamine, et kõik töölauad oleksid üksteise kõrval võrgustikus näha",
"Description[eu]": "Zooma urrundu mahaigain guztiak sareta batean bata bestearen ondoan ager daitezen",
"Description[fi]": "Loitontaa työpöytiä niin, että ne näytetään rinnakkain ruudukkona",
"Description[fr]": "Faire un zoom arrière de manière à afficher tous les bureaux côte à côte dans une grille",
"Description[hu]": "Kinagyítja az asztalt oly módon, hogy a virtuális asztalok rácsban elrendezve jelennek meg",
"Description[ia]": "Face zoom retro assi que omne scriptorios es monstrate flanco a flanco in un grillia",
"Description[id]": "Zoom keluar sehingga semua desktop ditampilkan sisi demi sisi di dalam kisi",
"Description[it]": "Arretra per mostrare tutti i desktop virtuali affiancati in una griglia",
"Description[ja]": "ズームアウトしてすべてのデスクトップを並べて表示します",
"Description[ka]": "დაპატარავება ისე, რომ ყველა სამუშაო მაგიდა გვერდიგვერდ, ბადეში იქნება ნაჩვენები",
"Description[ko]": "모든 바탕 화면을 한 화면에 볼 수 있도록 격자형으로 축소합니다",
"Description[nl]": "Verkleint alle bureaubladen zodat ze zij-aan-zij in een raster getoond kunnen worden",
"Description[nn]": "Forminsk skjermflata slik at alle skriveborda vert viste side om side",
"Description[pl]": "Pomniejsza do chwili, aż wszystkie pulpity będą widoczne obok siebie na siatce",
"Description[pt]": "Reduz o ecrã de forma a mostrar todos os ecrãs lado-a-lado numa grelha",
"Description[pt_BR]": "Reduz para que todas as áreas de trabalho sejam mostradas lado a lado em uma grade",
"Description[ro]": "Îndepărtează astfel încât toate birourile sunt afișate alăturat într-o grilă",
"Description[ru]": "Просмотр всех рабочих столов на одном экране",
"Description[sk]": "Oddiali všetky plochy a zobrazí ich vedľa seba v mriežke",
"Description[sl]": "Oddalji pogled tako, da so vsa namizja prikazana drugo ob drugem v mreži",
"Description[sv]": "Zooma ut så att alla skrivbord visas sida vid sida i ett rutnät",
"Description[ta]": "அனைத்து பணிமேடைகளை வரிசைகளிலும் நெடுவரிசைகளிலும் காட்டும்",
"Description[tr]": "Uzaklaştır, böylece tüm masaüstleri bir ızgarada yan yana görüntülenirler",
"Description[uk]": "Зменшення стільниць так, щоб всі стільниці було показано поруч у форматі таблиці",
"Description[vi]": "Thu nhỏ để tất cả các bàn làm việc được hiển thị cạnh nhau trong một lưới",
"Description[x-test]": "xxZoom out so all desktops are displayed side-by-side in a gridxx",
"Description[zh_CN]": "所有虚拟桌面缩小后显示在一套网格中",
"Description[zh_TW]": "縮小顯示讓所有桌面都同時顯示在格線內",
"EnabledByDefault": true,
"License": "GPL",
"Name": "Desktop Grid",
"Name[ar]": "شبكة سطح المكتب",
"Name[az]": "İş masası toru",
"Name[be]": "Сетка для працоўных сталоў",
"Name[bg]": "Плочки",
"Name[ca@valencia]": "Quadrícula de l'escriptori",
"Name[ca]": "Quadrícula de l'escriptori",
"Name[cs]": "Mřížka plochy",
"Name[de]": "Arbeitsflächen-Umschalter (Raster)",
"Name[en_GB]": "Desktop Grid",
"Name[eo]": "Labortabla Krado",
"Name[es]": "Rejilla del escritorio",
"Name[et]": "Töölauavõrgustik",
"Name[eu]": "Mahaigain sareta",
"Name[fi]": "Työpöydän ruudukko",
"Name[fr]": "Grille de bureaux",
"Name[hu]": "Asztalrács",
"Name[ia]": "Grillia de scriptorio",
"Name[id]": "Kisi Desktop",
"Name[it]": "Griglia dei Desktop",
"Name[ja]": "デスクトップグリッド",
"Name[ka]": "სამუშაო მაგიდის ბადე",
"Name[ko]": "바탕 화면 격자",
"Name[nl]": "Bureaubladraster",
"Name[nn]": "Skrivebordsoversikt",
"Name[pl]": "Siatka pulpitu",
"Name[pt]": "Grelha de Ecrãs",
"Name[pt_BR]": "Grade de áreas de trabalho",
"Name[ro]": "Grilă de birou",
"Name[ru]": "Все рабочие столы",
"Name[sk]": "Plochy v mriežke",
"Name[sl]": "Mreža namizja",
"Name[sv]": "Skrivbordsrutnät",
"Name[ta]": "பணிமேடை கிரிட்",
"Name[tr]": "Masaüstü Izgarası",
"Name[uk]": "Таблиця стільниць",
"Name[vi]": "Lưới bàn làm việc",
"Name[x-test]": "xxDesktop Gridxx",
"Name[zh_CN]": "虚拟桌面平铺网格",
"Name[zh_TW]": "桌面格線"
},
"X-KDE-ConfigModule": "kwin_desktopgrid_config",
"X-KWin-Border-Activate": true,
"org.kde.kwin.effect": {
"video": "https://files.kde.org/plasma/kwin/effect-videos/desktop_grid.mp4"
}
}

View file

@ -1,306 +0,0 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-FileCopyrightText: 2022 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2022 ivan tkachenko <me@ratijas.tk>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import org.kde.kwin as KWinComponents
import org.kde.kwin.private.effects
import org.kde.kirigami 2.20 as Kirigami
import org.kde.plasma.components 3.0 as PC3
import org.kde.kwin.private.desktopgrid
FocusScope {
id: desktopView
required property QtObject windowModel
required property QtObject desktop
required property var dndManagerStore
readonly property bool dragActive: heap.dragActive || dragHandler.active || xAnim.running || yAnim.running
property real panelOpacity: 1
focus: true
function selectLastItem(direction) {
heap.selectLastItem(direction);
}
DropArea {
anchors.fill: parent
onEntered: {
drag.accepted = true;
}
onDropped: drop => {
drop.accepted = true;
if (drag.source instanceof DesktopView) {
// dragging a desktop as a whole
if (drag.source === desktopView) {
drop.action = Qt.IgnoreAction;
return;
}
effect.swapDesktops(drag.source.desktop.x11DesktopNumber, desktop.x11DesktopNumber);
} else {
// dragging a KWin::Window
if (drag.source.desktops.length === 0 || drag.source.desktops.indexOf(desktopView.desktop) !== -1) {
drop.action = Qt.IgnoreAction;
return;
}
drag.source.desktops = [desktopView.desktop];
}
}
}
Connections {
target: effect
function onItemDroppedOutOfScreen(globalPos, item, screen) {
if (screen !== targetScreen) {
return;
}
const pos = screen.mapFromGlobal(globalPos);
if (!desktopView.contains(desktopView.mapFromItem(null, pos.x, pos.y))) {
return;
}
item.client.desktops = [desktopView.desktop];
}
}
Repeater {
model: KWinComponents.WindowFilterModel {
activity: KWinComponents.Workspace.currentActivity
desktop: desktopView.desktop
screenName: targetScreen.name
windowModel: desktopView.windowModel
windowType: KWinComponents.WindowFilterModel.Dock | KWinComponents.WindowFilterModel.Desktop
}
KWinComponents.WindowThumbnail {
wId: model.window.internalId
x: model.window.x - targetScreen.geometry.x
y: model.window.y - targetScreen.geometry.y
z: model.window.stackingOrder
width: model.window.width
height: model.window.height
opacity: model.window.dock ? desktopView.panelOpacity : 1
}
}
DragHandler {
id: dragHandler
target: heap
grabPermissions: PointerHandler.ApprovesTakeOverByHandlersOfSameType
onActiveChanged: {
if (!active) {
heap.Drag.drop();
Qt.callLater(heap.resetPosition)
}
}
}
WindowHeap {
id: heap
function resetPosition() {
x = 0;
y = 0;
}
Drag.active: dragHandler.active
Drag.proposedAction: Qt.MoveAction
Drag.supportedActions: Qt.MoveAction
Drag.source: desktopView
Drag.hotSpot: Qt.point(width * 0.5, height * 0.5)
width: parent.width
height: parent.height
focus: true
z: 9999
animationDuration: container.effect.animationDuration
absolutePositioning: false
animationEnabled: container.animationEnabled
organized: container.organized
layout.mode: effect.layout
dndManagerStore: desktopView.dndManagerStore
model: KWinComponents.WindowFilterModel {
activity: KWinComponents.Workspace.currentActivity
desktop: desktopView.desktop
screenName: targetScreen.name
windowModel: desktopView.windowModel
windowType: ~KWinComponents.WindowFilterModel.Dock &
~KWinComponents.WindowFilterModel.Desktop &
~KWinComponents.WindowFilterModel.Notification &
~KWinComponents.WindowFilterModel.CriticalNotification
}
delegate: WindowHeapDelegate {
windowHeap: heap
closeButtonVisible: false
windowTitleVisible: false
TapHandler {
acceptedPointerTypes: PointerDevice.GenericPointer | PointerDevice.Pen
acceptedButtons: Qt.MiddleButton | Qt.RightButton
onTapped: (eventPoint, button) => {
if (button === Qt.MiddleButton) {
window.closeWindow();
} else if (button === Qt.RightButton) {
if (window.desktops.length > 0) {
window.desktops = [];
} else {
window.desktops = [desktopView.desktop];
}
}
}
}
}
onActivated: effect.deactivate(effect.animationDuration);
Behavior on x {
enabled: !dragHandler.active
NumberAnimation {
id: xAnim
duration: container.effect.animationDuration
easing.type: Easing.OutCubic
}
}
Behavior on y {
enabled: !dragHandler.active
NumberAnimation {
id: yAnim
duration: container.effect.animationDuration
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: heap
acceptedButtons: Qt.NoButton
cursorShape: dragHandler.active ? Qt.ClosedHandCursor : Qt.ArrowCursor
}
PC3.Control {
id: desktopLabel
anchors.margins: Kirigami.Units.gridUnit
z: 9999
visible: effect.desktopNameAlignment !== 0
states: [
State {
when: container.effect.desktopNameAlignment === (Qt.AlignTop | Qt.AlignLeft)
AnchorChanges {
target: desktopLabel
anchors.left: desktopView.left
anchors.top: desktopView.top
}
PropertyChanges {
target: desktopLabel
transformOrigin: Item.TopLeft
}
},
State {
when: container.effect.desktopNameAlignment === (Qt.AlignTop | Qt.AlignHCenter)
AnchorChanges {
target: desktopLabel
anchors.top: desktopView.top
anchors.horizontalCenter: desktopView.horizontalCenter
}
PropertyChanges {
target: desktopLabel
transformOrigin: Item.Top
}
},
State {
when: container.effect.desktopNameAlignment === (Qt.AlignTop | Qt.AlignRight)
AnchorChanges {
target: desktopLabel
anchors.top: desktopView.top
anchors.right: desktopView.right
}
PropertyChanges {
target: desktopLabel
transformOrigin: Item.TopRight
}
},
State {
when: container.effect.desktopNameAlignment === (Qt.AlignVCenter | Qt.AlignLeft)
AnchorChanges {
target: desktopLabel
anchors.left: desktopView.left
anchors.verticalCenter: desktopView.verticalCenter
}
PropertyChanges {
target: desktopLabel
transformOrigin: Item.Left
}
},
State {
when: container.effect.desktopNameAlignment === Qt.AlignCenter
AnchorChanges {
target: desktopLabel
anchors.horizontalCenter: desktopView.horizontalCenter
anchors.verticalCenter: desktopView.verticalCenter
}
PropertyChanges {
target: desktopLabel
transformOrigin: Item.Center
}
},
State {
when: container.effect.desktopNameAlignment === (Qt.AlignVCenter | Qt.AlignRight)
AnchorChanges {
target: desktopLabel
anchors.verticalCenter: desktopView.verticalCenter
anchors.right: desktopView.right
}
PropertyChanges {
target: desktopLabel
transformOrigin: Item.Right
}
},
State {
when: container.effect.desktopNameAlignment === (Qt.AlignBottom | Qt.AlignLeft)
AnchorChanges {
target: desktopLabel
anchors.left: desktopView.left
anchors.bottom: desktopView.bottom
}
PropertyChanges {
target: desktopLabel
transformOrigin: Item.BottomLeft
}
},
State {
when: container.effect.desktopNameAlignment === (Qt.AlignBottom | Qt.AlignHCenter)
AnchorChanges {
target: desktopLabel
anchors.bottom: desktopView.bottom
anchors.horizontalCenter: desktopView.horizontalCenter
}
PropertyChanges {
target: desktopLabel
transformOrigin: Item.Bottom
}
},
State {
when: container.effect.desktopNameAlignment === (Qt.AlignBottom | Qt.AlignRight)
AnchorChanges {
target: desktopLabel
anchors.bottom: desktopView.bottom
anchors.right: desktopView.right
}
PropertyChanges {
target: desktopLabel
transformOrigin: Item.BottomRight
}
}
]
scale: 1 / desktopView.parent.scale
leftPadding: Kirigami.Units.smallSpacing
rightPadding: Kirigami.Units.smallSpacing
contentItem: PC3.Label {
text: desktopView.desktop.name
textFormat: Text.PlainText
}
background: Rectangle {
color: Kirigami.Theme.backgroundColor
radius: height
opacity: 0.6
}
}
}

View file

@ -1,328 +0,0 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-FileCopyrightText: 2022 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2022 ivan tkachenko <me@ratijas.tk>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import Qt5Compat.GraphicalEffects
import QtQuick.Layouts
import QtQuick.Window
import org.kde.kwin as KWinComponents
import org.kde.kwin.private.effects
import org.kde.kirigami 2.20 as Kirigami
import org.kde.plasma.components 3.0 as PC3
Rectangle {
id: container
readonly property QtObject effect: KWinComponents.SceneView.effect
readonly property QtObject targetScreen: KWinComponents.SceneView.screen
property bool animationEnabled: false
property bool organized: false
/** Shared Drag&Drop store to keep track of DND state across desktops. */
property var dndManagerStore: ({})
color: "black"
function start() {
animationEnabled = true;
organized = true;
}
function stop() {
organized = false;
}
function switchTo(desktop) {
KWinComponents.Workspace.currentDesktop = desktop;
effect.deactivate();
}
function selectNext(direction) {
let currentIndex = 0
for (let i = 0; i < gridRepeater.count; i++) {
if (gridRepeater.itemAt(i).focus) {
currentIndex = i;
break;
}
}
let x = currentIndex % grid.columns;
let y = Math.floor(currentIndex / grid.columns);
// the direction we move in is the opposite of the window to select
// i.e pressing left should select the rightmost window on the desktop
// to the left
let invertedDirection;
switch(direction) {
case WindowHeap.Direction.Up:
y--;
invertedDirection = WindowHeap.Direction.Down;
break;
case WindowHeap.Direction.Down:
y++
invertedDirection = WindowHeap.Direction.Up;
break;
case WindowHeap.Direction.Left:
x--;
invertedDirection = WindowHeap.Direction.Right;
break;
case WindowHeap.Direction.Right:
x++;
invertedDirection = WindowHeap.Direction.Left;
break;
}
if (x < 0 || x >= grid.columns) {
return false;
}
if (y < 0 || y >= grid.rows) {
return false;
}
let newIndex = y * grid.columns + x;
gridRepeater.itemAt(newIndex).focus = true;
gridRepeater.itemAt(newIndex).selectLastItem(invertedDirection);
return true;
}
Keys.onPressed: {
if (event.key === Qt.Key_Escape) {
effect.deactivate();
} else if (event.key === Qt.Key_Plus || event.key === Qt.Key_Equal) {
addButton.clicked();
} else if (event.key === Qt.Key_Minus) {
removeButton.clicked();
} else if (event.key >= Qt.Key_F1 && event.key <= Qt.Key_F12) {
const desktopId = event.key - Qt.Key_F1;
if (desktopId < gridRepeater.count) {
switchTo(gridRepeater.itemAt(desktopId).desktop);
}
} else if (event.key >= Qt.Key_0 && event.key <= Qt.Key_9) {
const desktopId = event.key === Qt.Key_0 ? 9 : (event.key - Qt.Key_1);
if (desktopId < gridRepeater.count) {
switchTo(gridRepeater.itemAt(desktopId).desktop);
}
} else if (event.key === Qt.Key_Up) {
event.accepted = selectNext(WindowHeap.Direction.Up);
if (!event.accepted) {
let view = effect.getView(Qt.TopEdge)
if (view) {
effect.activateView(view)
}
}
} else if (event.key === Qt.Key_Down) {
event.accepted = selectNext(WindowHeap.Direction.Down);
if (!event.accepted) {
let view = effect.getView(Qt.BottomEdge)
if (view) {
effect.activateView(view)
}
}
} else if (event.key === Qt.Key_Left) {
event.accepted = selectNext(WindowHeap.Direction.Left);
if (!event.accepted) {
let view = effect.getView(Qt.LeftEdge)
if (view) {
effect.activateView(view)
}
}
} else if (event.key === Qt.Key_Right) {
event.accepted = selectNext(WindowHeap.Direction.Right);
if (!event.accepted) {
let view = effect.getView(Qt.RightEdge)
if (view) {
effect.activateView(view)
}
}
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Space) {
for (let i = 0; i < gridRepeater.count; i++) {
if (gridRepeater.itemAt(i).focus) {
switchTo(gridRepeater.itemAt(i).desktop)
break;
}
}
}
}
Keys.priority: Keys.AfterItem
KWinComponents.VirtualDesktopModel {
id: desktopModel
}
KWinComponents.WindowModel {
id: stackModel
}
// A grid, not a gridlayout as a gridlayout positions its elements too late
Grid {
id: grid
property Item currentItem
readonly property real targetScale: Math.min(parent.width / width, parent.height / height)
property real panelOpacity: 1
Behavior on x {
enabled: !container.effect.gestureInProgress
NumberAnimation {
duration: container.effect.animationDuration
easing.type: Easing.OutCubic
}
}
Behavior on y {
enabled: !container.effect.gestureInProgress
NumberAnimation {
duration: container.effect.animationDuration
easing.type: Easing.OutCubic
}
}
Behavior on scale {
enabled: !container.effect.gestureInProgress
NumberAnimation {
duration: container.effect.animationDuration
easing.type: Easing.OutCubic
}
}
Behavior on panelOpacity {
enabled: !container.effect.gestureInProgress
NumberAnimation {
duration: container.effect.animationDuration
easing.type: Easing.OutCubic
}
}
width: (parent.width + columnSpacing) * columns - columnSpacing
height: (parent.height + rowSpacing) * rows - rowSpacing
rowSpacing: Kirigami.Units.gridUnit
columnSpacing: Kirigami.Units.gridUnit
rows: container.effect.gridRows
columns: container.effect.gridColumns
transformOrigin: Item.TopLeft
states: [
State {
when: container.effect.gestureInProgress
PropertyChanges {
target: grid
x: Math.max(0, container.width / 2 - (grid.width * grid.targetScale) / 2) * container.effect.partialActivationFactor - grid.currentItem.x * (1 - container.effect.partialActivationFactor)
y: Math.max(0, container.height / 2 - (grid.height * grid.targetScale) / 2) * container.effect.partialActivationFactor - grid.currentItem.y * (1 - container.effect.partialActivationFactor)
scale: 1 - (1 - grid.targetScale) * container.effect.partialActivationFactor
panelOpacity: 1 - container.effect.partialActivationFactor
}
PropertyChanges {
target: buttonsLayout
opacity: container.effect.partialActivationFactor
}
},
State {
when: container.organized
PropertyChanges {
target: grid
x: Math.max(0, container.width / 2 - (grid.width * grid.targetScale) / 2)
y: Math.max(0, container.height / 2 - (grid.height * grid.targetScale) / 2)
scale: grid.targetScale
panelOpacity: 0
}
PropertyChanges {
target: buttonsLayout
opacity: 1
}
},
State {
when: !container.organized
PropertyChanges {
target: grid
x: -grid.currentItem.x
y: -grid.currentItem.y
scale: 1
panelOpacity: 1
}
PropertyChanges {
target: buttonsLayout
opacity: 0
}
}
]
Repeater {
id: gridRepeater
model: desktopModel
DesktopView {
id: thumbnail
panelOpacity: grid.panelOpacity
readonly property bool current: KWinComponents.Workspace.currentDesktop === desktop
z: dragActive ? 1 : 0
onCurrentChanged: {
if (current) {
grid.currentItem = thumbnail;
}
}
Component.onCompleted: {
if (current) {
grid.currentItem = thumbnail;
}
}
width: container.width
height: container.height
windowModel: stackModel
dndManagerStore: container.dndManagerStore
Rectangle {
anchors.fill: parent
color: "transparent"
border {
color: Kirigami.Theme.highlightColor
width: 1 / grid.scale
}
visible: parent.activeFocus
}
TapHandler {
acceptedButtons: Qt.LeftButton
onTapped: {
KWinComponents.Workspace.currentDesktop = thumbnail.desktop;
container.effect.deactivate();
}
}
}
}
}
RowLayout {
id: buttonsLayout
anchors {
right: parent.right
bottom: parent.bottom
margins: Kirigami.Units.smallSpacing
}
spacing: Kirigami.Units.smallSpacing
visible: container.effect.showAddRemove
PC3.Button {
id: addButton
icon.name: "list-add"
onClicked: container.effect.addDesktop()
}
PC3.Button {
id: removeButton
icon.name: "list-remove"
onClicked: container.effect.removeDesktop()
}
Behavior on opacity {
enabled: !container.effect.gestureInProgress
NumberAnimation {
duration: container.effect.animationDuration
easing.type: Easing.OutCubic
}
}
}
TapHandler {
acceptedButtons: Qt.LeftButton
onTapped: {
container.effect.deactivate();
}
}
Component.onCompleted: start()
}

View file

@ -36,12 +36,33 @@ OverviewEffectConfig::OverviewEffectConfig(QObject *parent, const KPluginMetaDat
actionCollection->setConfigGroup(QStringLiteral("Overview"));
actionCollection->setConfigGlobal(true);
const QKeySequence defaultToggleShortcut = Qt::META | Qt::Key_W;
QAction *toggleAction = actionCollection->addAction(QStringLiteral("Overview"));
toggleAction->setText(i18n("Toggle Overview"));
toggleAction->setProperty("isConfigurationAction", true);
KGlobalAccel::self()->setDefaultShortcut(toggleAction, {defaultToggleShortcut});
KGlobalAccel::self()->setShortcut(toggleAction, {defaultToggleShortcut});
const QKeySequence defaultCycleShortcut = Qt::META | Qt::Key_Tab;
QAction *cycleAction = actionCollection->addAction(QStringLiteral("Cycle Overview"));
cycleAction->setText(i18nc("@action Overview and Grid View are the name of KWin effects", "Cycle through Overview and Grid View"));
cycleAction->setProperty("isConfigurationAction", true);
KGlobalAccel::self()->setDefaultShortcut(cycleAction, {defaultCycleShortcut});
KGlobalAccel::self()->setShortcut(cycleAction, {defaultCycleShortcut});
const QKeySequence defaultUncycleShortcut = Qt::META | Qt::SHIFT | Qt::Key_Tab;
QAction *reverseCycleAction = actionCollection->addAction(QStringLiteral("Cycle Overview Opposite"));
reverseCycleAction->setText(i18nc("@action Grid View and Overview are the name of KWin effects", "Cycle through Grid View and Overview"));
reverseCycleAction->setProperty("isConfigurationAction", true);
KGlobalAccel::self()->setDefaultShortcut(reverseCycleAction, {defaultUncycleShortcut});
KGlobalAccel::self()->setShortcut(reverseCycleAction, {defaultUncycleShortcut});
const QKeySequence defaultOverviewShortcut = Qt::META | Qt::Key_W;
QAction *overviewAction = actionCollection->addAction(QStringLiteral("Overview"));
overviewAction->setText(i18nc("@action Overview is the name of a KWin effect", "Toggle Overview"));
overviewAction->setProperty("isConfigurationAction", true);
KGlobalAccel::self()->setDefaultShortcut(overviewAction, {defaultOverviewShortcut});
KGlobalAccel::self()->setShortcut(overviewAction, {defaultOverviewShortcut});
const QKeySequence defaultGridShortcut = Qt::META | Qt::Key_G;
QAction *gridAction = actionCollection->addAction(QStringLiteral("Grid View"));
gridAction->setText(i18nc("@action Grid View is the name of a KWin effect", "Toggle Grid View"));
gridAction->setProperty("isConfigurationAction", true);
KGlobalAccel::self()->setDefaultShortcut(gridAction, {defaultGridShortcut});
KGlobalAccel::self()->setShortcut(gridAction, {defaultGridShortcut});
ui.shortcutsEditor->addCollection(actionCollection);
connect(ui.shortcutsEditor, &KShortcutsEditor::keyChange, this, &KCModule::markAsChanged);

View file

@ -51,7 +51,21 @@
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<item row="2" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Organize windows in the Grid View:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="kcfg_OrganizedGrid">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="KShortcutsEditor" name="shortcutsEditor">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">

View file

@ -16,6 +16,9 @@
<entry name="IgnoreMinimized" type="bool">
<default>false</default>
</entry>
<entry name="OrganizedGrid" type="bool">
<default>true</default>
</entry>
<entry name="BorderActivate" type="IntList" />
<entry name="TouchBorderActivate" type="IntList" />

View file

@ -19,35 +19,137 @@ namespace KWin
{
OverviewEffect::OverviewEffect()
: m_state(new EffectTogglableState(this))
, m_border(new EffectTogglableTouchBorder(m_state))
// manages the transition between inactive -> overview
: m_overviewState(new EffectTogglableState(this))
// manages the transition between overview -> grid
, m_transitionState(new EffectTogglableState(this))
// manages the transition betwee inactive -> overview
, m_gridState(new EffectTogglableState(this))
, m_border(new EffectTogglableTouchBorder(m_overviewState))
, m_shutdownTimer(new QTimer(this))
{
auto gesture = new EffectTogglableGesture(m_state);
gesture->addTouchpadPinchGesture(PinchDirection::Contracting, 4);
auto gesture = new EffectTogglableGesture(m_overviewState);
gesture->addTouchpadSwipeGesture(SwipeDirection::Up, 4);
gesture->addTouchscreenSwipeGesture(SwipeDirection::Up, 3);
connect(m_state, &EffectTogglableState::activated, this, &OverviewEffect::activate);
connect(m_state, &EffectTogglableState::deactivated, this, &OverviewEffect::deactivate);
connect(m_state, &EffectTogglableState::inProgressChanged, this, &OverviewEffect::gestureInProgressChanged);
connect(m_state, &EffectTogglableState::partialActivationFactorChanged, this, &OverviewEffect::partialActivationFactorChanged);
connect(m_state, &EffectTogglableState::statusChanged, this, [this](EffectTogglableState::Status status) {
auto transitionGesture = new EffectTogglableGesture(m_transitionState);
transitionGesture->addTouchpadSwipeGesture(SwipeDirection::Up, 4);
transitionGesture->addTouchscreenSwipeGesture(SwipeDirection::Up, 3);
m_transitionState->stop();
auto gridGesture = new EffectTogglableGesture(m_gridState);
gridGesture->addTouchpadSwipeGesture(SwipeDirection::Down, 4);
gridGesture->addTouchscreenSwipeGesture(SwipeDirection::Down, 3);
connect(m_overviewState, &EffectTogglableState::inProgressChanged, this, &OverviewEffect::overviewGestureInProgressChanged);
connect(m_overviewState, &EffectTogglableState::partialActivationFactorChanged, this, &OverviewEffect::overviewPartialActivationFactorChanged);
connect(m_overviewState, &EffectTogglableState::statusChanged, this, [this](EffectTogglableState::Status status) {
if (status == EffectTogglableState::Status::Activating || status == EffectTogglableState::Status::Active) {
m_searchText = QString();
setRunning(true);
m_gridState->stop();
}
setRunning(status != EffectTogglableState::Status::Inactive);
if (status == EffectTogglableState::Status::Active) {
m_transitionState->deactivate();
}
if (status == EffectTogglableState::Status::Inactive || status == EffectTogglableState::Status::Deactivating) {
m_transitionState->stop();
}
if (status == EffectTogglableState::Status::Inactive) {
m_gridState->deactivate();
deactivate();
}
});
connect(m_transitionState, &EffectTogglableState::statusChanged, this, [this](EffectTogglableState::Status status) {
if (status == EffectTogglableState::Status::Activating || status == EffectTogglableState::Status::Active) {
m_overviewState->stop();
}
if (status == EffectTogglableState::Status::Inactive) {
m_overviewState->activate();
}
if (status == EffectTogglableState::Status::Active) {
m_gridState->activate();
}
if (status == EffectTogglableState::Status::Inactive || status == EffectTogglableState::Status::Deactivating) {
m_gridState->stop();
}
});
connect(m_gridState, &EffectTogglableState::statusChanged, this, [this](EffectTogglableState::Status status) {
if (status == EffectTogglableState::Status::Activating || status == EffectTogglableState::Status::Active) {
m_searchText = QString();
setRunning(true);
m_overviewState->stop();
}
if (status == EffectTogglableState::Status::Inactive) {
m_overviewState->deactivate();
deactivate();
}
if (status == EffectTogglableState::Status::Active) {
m_transitionState->activate();
}
if (status == EffectTogglableState::Status::Inactive || status == EffectTogglableState::Status::Deactivating) {
m_transitionState->stop();
}
});
connect(m_transitionState, &EffectTogglableState::inProgressChanged, this, &OverviewEffect::transitionGestureInProgressChanged);
connect(m_transitionState, &EffectTogglableState::partialActivationFactorChanged, this, &OverviewEffect::transitionPartialActivationFactorChanged);
connect(m_gridState, &EffectTogglableState::inProgressChanged, this, &OverviewEffect::gridGestureInProgressChanged);
connect(m_gridState, &EffectTogglableState::partialActivationFactorChanged, this, &OverviewEffect::gridPartialActivationFactorChanged);
connect(effects, &EffectsHandler::desktopChanging, this, [this](uint old, QPointF desktopOffset, EffectWindow *with) {
m_desktopOffset = desktopOffset;
Q_EMIT desktopOffsetChanged();
});
connect(effects, &EffectsHandler::desktopChanged, this, [this](int old, int current, EffectWindow *with) {
m_desktopOffset = QPointF(0, 0);
Q_EMIT desktopOffsetChanged();
});
connect(effects, &EffectsHandler::desktopChangingCancelled, this, [this]() {
m_desktopOffset = QPointF(0, 0);
Q_EMIT desktopOffsetChanged();
});
m_shutdownTimer->setSingleShot(true);
connect(m_shutdownTimer, &QTimer::timeout, this, &OverviewEffect::realDeactivate);
const QKeySequence defaultToggleShortcut = Qt::META | Qt::Key_W;
auto toggleAction = m_state->toggleAction();
toggleAction->setObjectName(QStringLiteral("Overview"));
toggleAction->setText(i18n("Toggle Overview"));
KGlobalAccel::self()->setDefaultShortcut(toggleAction, {defaultToggleShortcut});
KGlobalAccel::self()->setShortcut(toggleAction, {defaultToggleShortcut});
m_toggleShortcut = KGlobalAccel::self()->shortcut(toggleAction);
const QKeySequence defaultCycleShortcut = Qt::META | Qt::Key_Tab;
auto cycleAction = new QAction(this);
connect(cycleAction, &QAction::triggered, this, &OverviewEffect::cycle);
cycleAction->setObjectName(QStringLiteral("Cycle Overview"));
cycleAction->setText(i18nc("@action Grid View and Overview are the name of KWin effects", "Cycle through Overview and Grid View"));
KGlobalAccel::self()->setDefaultShortcut(cycleAction, {defaultCycleShortcut});
KGlobalAccel::self()->setShortcut(cycleAction, {defaultCycleShortcut});
m_cycleShortcut = KGlobalAccel::self()->shortcut(cycleAction);
const QKeySequence defaultUncycleShortcut = Qt::META | Qt::SHIFT | Qt::Key_Tab;
auto reverseCycleAction = new QAction(this);
connect(reverseCycleAction, &QAction::triggered, this, &OverviewEffect::reverseCycle);
reverseCycleAction->setObjectName(QStringLiteral("Cycle Overview Opposite"));
reverseCycleAction->setText(i18nc("@action Grid View and Overview are the name of KWin effects", "Cycle through Grid View and Overview"));
KGlobalAccel::self()->setDefaultShortcut(reverseCycleAction, {defaultUncycleShortcut});
KGlobalAccel::self()->setShortcut(reverseCycleAction, {defaultUncycleShortcut});
m_reverseCycleShortcut = KGlobalAccel::self()->shortcut(reverseCycleAction);
const QKeySequence defaultOverviewShortcut = Qt::META | Qt::Key_W;
auto overviewAction = m_overviewState->toggleAction();
overviewAction->setObjectName(QStringLiteral("Overview"));
overviewAction->setText(i18nc("@action Overview is the name of a Kwin effect", "Toggle Overview"));
KGlobalAccel::self()->setDefaultShortcut(overviewAction, {defaultOverviewShortcut});
KGlobalAccel::self()->setShortcut(overviewAction, {defaultOverviewShortcut});
m_overviewShortcut = KGlobalAccel::self()->shortcut(overviewAction);
const QKeySequence defaultGridShortcut = Qt::META | Qt::Key_G;
auto gridAction = m_gridState->toggleAction();
gridAction->setObjectName(QStringLiteral("Grid View"));
gridAction->setText(i18nc("@action Grid view is the name of a Kwin effect", "Toggle Grid View"));
KGlobalAccel::self()->setDefaultShortcut(gridAction, {defaultGridShortcut});
KGlobalAccel::self()->setShortcut(gridAction, {defaultGridShortcut});
m_overviewShortcut = KGlobalAccel::self()->shortcut(gridAction);
connect(effects, &EffectsHandler::screenAboutToLock, this, &OverviewEffect::realDeactivate);
@ -95,6 +197,41 @@ void OverviewEffect::setAnimationDuration(int duration)
}
}
qreal OverviewEffect::overviewPartialActivationFactor() const
{
return m_overviewState->partialActivationFactor();
}
bool OverviewEffect::overviewGestureInProgress() const
{
return m_overviewState->inProgress();
}
qreal OverviewEffect::transitionPartialActivationFactor() const
{
return m_transitionState->partialActivationFactor();
}
bool OverviewEffect::transitionGestureInProgress() const
{
return m_transitionState->inProgress();
}
qreal OverviewEffect::gridPartialActivationFactor() const
{
return m_gridState->partialActivationFactor();
}
bool OverviewEffect::gridGestureInProgress() const
{
return m_gridState->inProgress();
}
QPointF OverviewEffect::desktopOffset() const
{
return m_desktopOffset;
}
int OverviewEffect::layout() const
{
return m_layout;
@ -105,6 +242,11 @@ bool OverviewEffect::ignoreMinimized() const
return OverviewConfig::ignoreMinimized();
}
bool OverviewEffect::organizedGrid() const
{
return OverviewConfig::organizedGrid();
}
void OverviewEffect::setLayout(int layout)
{
if (m_layout != layout) {
@ -121,7 +263,7 @@ int OverviewEffect::requestedEffectChainPosition() const
bool OverviewEffect::borderActivated(ElectricBorder border)
{
if (m_borderActivate.contains(border)) {
m_state->toggle();
cycle();
return true;
}
return false;
@ -133,7 +275,7 @@ void OverviewEffect::activate()
return;
}
m_state->activate();
m_overviewState->activate();
}
void OverviewEffect::deactivate()
@ -145,26 +287,89 @@ void OverviewEffect::deactivate()
}
}
m_shutdownTimer->start(animationDuration());
m_state->deactivate();
m_overviewState->deactivate();
}
void OverviewEffect::realDeactivate()
{
m_state->setStatus(EffectTogglableState::Status::Inactive);
if (m_overviewState->status() == EffectTogglableState::Status::Inactive) {
setRunning(false);
}
}
void OverviewEffect::cycle()
{
if (m_overviewState->status() == EffectTogglableState::Status::Inactive) {
m_overviewState->activate();
} else if (m_transitionState->status() == EffectTogglableState::Status::Inactive) {
m_transitionState->activate();
} else if (m_gridState->status() == EffectTogglableState::Status::Active) {
m_overviewState->deactivate();
}
}
void OverviewEffect::reverseCycle()
{
if (m_overviewState->status() == EffectTogglableState::Status::Active) {
m_overviewState->deactivate();
} else if (m_transitionState->status() == EffectTogglableState::Status::Active) {
m_transitionState->deactivate();
} else if (m_gridState->status() == EffectTogglableState::Status::Inactive) {
m_gridState->activate();
}
}
void OverviewEffect::grabbedKeyboardEvent(QKeyEvent *keyEvent)
{
if (m_toggleShortcut.contains(keyEvent->key() | keyEvent->modifiers())) {
if (m_cycleShortcut.contains(keyEvent->key() | keyEvent->modifiers())) {
if (keyEvent->type() == QEvent::KeyPress) {
m_state->toggle();
cycle();
}
return;
}
if (m_reverseCycleShortcut.contains(keyEvent->key() | keyEvent->modifiers())) {
if (keyEvent->type() == QEvent::KeyPress) {
reverseCycle();
}
return;
}
if (m_overviewShortcut.contains(keyEvent->key() | keyEvent->modifiers())) {
if (keyEvent->type() == QEvent::KeyPress) {
m_overviewState->toggleAction();
}
return;
}
if (m_gridShortcut.contains(keyEvent->key() | keyEvent->modifiers())) {
if (keyEvent->type() == QEvent::KeyPress) {
m_gridState->toggleAction();
}
return;
}
QuickSceneEffect::grabbedKeyboardEvent(keyEvent);
}
void OverviewEffect::swapDesktops(int from, int to)
{
QList<EffectWindow *> fromList;
QList<EffectWindow *> toList;
for (auto *w : effects->stackingOrder()) {
if (!w->isNormalWindow() || !w->isOnCurrentActivity()) {
continue;
}
if (w->isOnDesktop(from)) {
fromList << w;
} else if (w->isOnDesktop(to)) {
toList << w;
}
}
for (auto *w : fromList) {
effects->windowToDesktop(w, to);
}
for (auto *w : toList) {
effects->windowToDesktop(w, from);
}
}
} // namespace KWin
#include "moc_overvieweffect.cpp"

View file

@ -18,9 +18,15 @@ class OverviewEffect : public QuickSceneEffect
Q_PROPERTY(int animationDuration READ animationDuration NOTIFY animationDurationChanged)
Q_PROPERTY(int layout READ layout NOTIFY layoutChanged)
Q_PROPERTY(bool ignoreMinimized READ ignoreMinimized NOTIFY ignoreMinimizedChanged)
Q_PROPERTY(qreal partialActivationFactor READ partialActivationFactor NOTIFY partialActivationFactorChanged)
// More efficient from a property binding pov rather than binding to partialActivationFactor !== 0
Q_PROPERTY(bool gestureInProgress READ gestureInProgress NOTIFY gestureInProgressChanged)
Q_PROPERTY(bool organizedGrid READ organizedGrid NOTIFY organizedGridChanged)
Q_PROPERTY(qreal overviewPartialActivationFactor READ overviewPartialActivationFactor NOTIFY overviewPartialActivationFactorChanged)
// More efficient from a property binding pov rather than checking if partialActivationFactor is strictly between 0 and 1
Q_PROPERTY(bool overviewGestureInProgress READ overviewGestureInProgress NOTIFY overviewGestureInProgressChanged)
Q_PROPERTY(qreal transitionPartialActivationFactor READ transitionPartialActivationFactor NOTIFY transitionPartialActivationFactorChanged)
Q_PROPERTY(bool transitionGestureInProgress READ transitionGestureInProgress NOTIFY transitionGestureInProgressChanged)
Q_PROPERTY(qreal gridPartialActivationFactor READ gridPartialActivationFactor NOTIFY gridPartialActivationFactorChanged)
Q_PROPERTY(bool gridGestureInProgress READ gridGestureInProgress NOTIFY gridGestureInProgressChanged)
Q_PROPERTY(QPointF desktopOffset READ desktopOffset NOTIFY desktopOffsetChanged)
Q_PROPERTY(QString searchText MEMBER m_searchText NOTIFY searchTextChanged)
public:
@ -31,31 +37,38 @@ public:
void setLayout(int layout);
bool ignoreMinimized() const;
bool organizedGrid() const;
int animationDuration() const;
void setAnimationDuration(int duration);
qreal partialActivationFactor() const
{
return m_state->partialActivationFactor();
}
bool gestureInProgress() const
{
return m_state->inProgress();
}
qreal overviewPartialActivationFactor() const;
bool overviewGestureInProgress() const;
qreal transitionPartialActivationFactor() const;
bool transitionGestureInProgress() const;
qreal gridPartialActivationFactor() const;
bool gridGestureInProgress() const;
QPointF desktopOffset() const;
int requestedEffectChainPosition() const override;
bool borderActivated(ElectricBorder border) override;
void reconfigure(ReconfigureFlags flags) override;
void grabbedKeyboardEvent(QKeyEvent *keyEvent) override;
Q_INVOKABLE void swapDesktops(int from, int to);
Q_SIGNALS:
void animationDurationChanged();
void layoutChanged();
void partialActivationFactorChanged();
void gestureInProgressChanged();
void overviewPartialActivationFactorChanged();
void overviewGestureInProgressChanged();
void transitionPartialActivationFactorChanged();
void transitionGestureInProgressChanged();
void gridPartialActivationFactorChanged();
void gridGestureInProgressChanged();
void ignoreMinimizedChanged();
void organizedGridChanged();
void desktopOffsetChanged();
void searchTextChanged();
public Q_SLOTS:
@ -64,14 +77,22 @@ public Q_SLOTS:
private:
void realDeactivate();
void cycle();
void reverseCycle();
EffectTogglableState *const m_state;
EffectTogglableState *const m_overviewState;
EffectTogglableState *const m_transitionState;
EffectTogglableState *const m_gridState;
EffectTogglableTouchBorder *const m_border;
QTimer *m_shutdownTimer;
QList<QKeySequence> m_toggleShortcut;
QList<QKeySequence> m_cycleShortcut;
QList<QKeySequence> m_reverseCycleShortcut;
QList<QKeySequence> m_overviewShortcut;
QList<QKeySequence> m_gridShortcut;
QList<ElectricBorder> m_borderActivate;
QString m_searchText;
QPointF m_desktopOffset;
int m_animationDuration = 400;
int m_layout = 1;
};

View file

@ -6,28 +6,31 @@
*/
import QtQuick
import Qt5Compat.GraphicalEffects
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import org.kde.kirigami 2.20 as Kirigami
import org.kde.kwin as KWinComponents
import org.kde.kwin.private.effects
import org.kde.plasma.components 3.0 as PC3
import org.kde.kirigami 2.20 as Kirigami
import org.kde.kwin.private.effects 1.0
import org.kde.plasma.components as PC3
Item {
id: bar
readonly property real desktopHeight: Kirigami.Units.gridUnit * 4
readonly property real desktopHeight: Kirigami.Units.gridUnit * 5
readonly property real desktopWidth: desktopHeight * targetScreen.geometry.width / targetScreen.geometry.height
readonly property real columnHeight: desktopHeight + Kirigami.Units.gridUnit
readonly property real columnWidth: desktopWidth + Kirigami.Units.gridUnit
readonly property int desktopCount: desktopRepeater.count
property bool verticalDesktopBar
property QtObject windowModel
property alias desktopModel: desktopRepeater.model
property QtObject selectedDesktop: null
property WindowHeap heap
property var heap
implicitHeight: columnHeight + 2 * Kirigami.Units.smallSpacing
implicitWidth: columnWidth + 2 * Kirigami.Units.smallSpacing
Flickable {
anchors.fill: parent
@ -39,8 +42,9 @@ Item {
clip: true
flickableDirection: Flickable.HorizontalFlick
Row {
spacing: Kirigami.Units.gridUnit
Grid {
spacing: Kirigami.Units.largeSpacing
columns: verticalDesktopBar ? 1 : desktopCount + 1
Repeater {
id: desktopRepeater
@ -71,7 +75,11 @@ Item {
Keys.onRightPressed: nextItemInFocusChain(!LayoutMirroring.enabled).forceActiveFocus(Qt.TabFocusReason);
function activate() {
KWinComponents.Workspace.currentDesktop = delegate.desktop;
if (KWinComponents.Workspace.currentDesktop === delegate.desktop) {
effect.deactivate()
} else {
KWinComponents.Workspace.currentDesktop = delegate.desktop;
}
}
function remove() {
@ -92,40 +100,48 @@ Item {
width: bar.desktopWidth
height: bar.desktopHeight
scale: thumbnailHover.hovered ? 1.03 : 1
Behavior on scale {
NumberAnimation {
duration: Kirigami.Units.Kirigami.Units.shortDuration
}
}
HoverHandler {
id: thumbnailHover
}
DesktopView {
id: thumbnail
width: targetScreen.geometry.width
height: targetScreen.geometry.height
visible: false
windowModel: bar.windowModel
desktop: delegate.desktop
scale: bar.desktopHeight / targetScreen.geometry.height
transformOrigin: Item.TopLeft
// Disable the item layer while being scaled.
layer.enabled: true
layer.textureSize: Qt.size(bar.desktopWidth, bar.desktopHeight)
}
OpacityMask {
anchors.fill: parent
cached: true
source: thumbnail
maskSource: Rectangle {
width: bar.desktopWidth
height: bar.desktopHeight
radius: 3
layer.textureSize: Qt.size(bar.desktopWidth, bar.desktopHeight)
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
anchors.centerIn: parent
width: thumbnail.width
height: thumbnail.height
// Using 5% of width since that's constant even under scaling:
radius: width / 20
}
}
}
Rectangle {
readonly property bool active: delegate.activeFocus || dropArea.containsDrag || mouseArea.containsPress || bar.selectedDesktop === delegate.desktop
readonly property bool active: (delegate.activeFocus || dropArea.containsDrag || mouseArea.containsPress || bar.selectedDesktop === delegate.desktop)
anchors.fill: parent
anchors.margins: -border.width
radius: 3
radius: width / 20
color: "transparent"
border.width: 2
border.width: active ? 2 : 1
border.color: active ? Kirigami.Theme.highlightColor : Kirigami.Theme.textColor
opacity: dropArea.containsDrag || !active ? 0.5 : 1.0
}
@ -145,18 +161,11 @@ Item {
break;
}
}
onDoubleClicked: {
if (mouse.button == Qt.LeftButton) {
mouse.accepted = true;
delegate.activate();
effect.deactivate();
}
}
}
Loader {
LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
active: !heap.dragActive && (hoverHandler.hovered || Kirigami.Settings.tabletMode || Kirigami.Settings.hasTransientTouchInput) && desktopRepeater.count > 1
active: !bar.heap.dragActive && (hoverHandler.hovered || Kirigami.Settings.tabletMode || Kirigami.Settings.hasTransientTouchInput) && desktopCount > 1
anchors.right: parent.right
anchors.top: parent.top
sourceComponent: PC3.Button {
@ -281,8 +290,7 @@ Item {
drag.accepted = desktopModel.rowCount() < 20
}
onDropped: {
const desktop = desktopModel.create(desktopModel.rowCount());
drag.source.desktops = [desktop];
drag.source.desktops = [desktopModel.create(desktopModel.rowCount())];
}
}
}

View file

@ -1,19 +1,21 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-FileCopyrightText: 2022 ivan tkachenko <me@ratijas.tk>
SPDX-FileCopyrightText: 2023 Niccolò Venerandi <niccolo@venerandi.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls
import Qt5Compat.GraphicalEffects
import QtQuick.Layouts
import org.kde.kirigami 2.20 as Kirigami
import org.kde.kwin as KWinComponents
import org.kde.kwin.private.effects
import org.kde.milou as Milou
import org.kde.kirigami 2.20 as Kirigami
import org.kde.plasma.components as PC3
import org.kde.plasma.extras as PlasmaExtras
import org.kde.kcmutils as KCM
FocusScope {
id: container
@ -29,135 +31,176 @@ FocusScope {
property bool animationEnabled: false
property bool organized: false
property alias currentHeap: heapView.currentItem
property bool verticalDesktopBar: KWinComponents.Workspace.desktopGridHeight >= bar.desktopCount && KWinComponents.Workspace.desktopGridHeight != 1
property bool anyDesktopBar: verticalDesktopBar || KWinComponents.Workspace.desktopGridHeight == 1
// The values of overviewVal and gridVal might not be 0 on startup,
// but we always want to animate from 0 to those values. So, we initially
// always set them to 0 and bind their full values when the effect starts.
// See start()
property real overviewVal: 0
property real gridVal: 0
Behavior on overviewVal {
NumberAnimation {
duration: Kirigami.Units.shortDuration
}
}
Behavior on gridVal {
NumberAnimation {
duration: Kirigami.Units.shortDuration
}
}
function start() {
animationEnabled = true;
organized = true;
overviewVal = Qt.binding(() => effect.overviewGestureInProgress ? effect.overviewPartialActivationFactor :
effect.transitionGestureInProgress ? 1 - effect.transitionPartialActivationFactor :
effect.overviewPartialActivationFactor == 1 && effect.transitionPartialActivationFactor == 0)
gridVal = Qt.binding(() => effect.transitionGestureInProgress ? effect.transitionPartialActivationFactor :
effect.gridGestureInProgress ? effect.gridPartialActivationFactor :
effect.transitionPartialActivationFactor == 1 && effect.gridPartialActivationFactor == 1)
}
function stop() {
organized = false;
}
Keys.onEscapePressed: effect.deactivate();
Keys.forwardTo: searchField
Keys.onEnterPressed: {
currentHeap.forceActiveFocus();
if (currentHeap.count === 1) {
currentHeap.activateCurrentClient();
}
function switchTo(desktop) {
KWinComponents.Workspace.currentDesktop = desktop;
effect.deactivate();
}
Keys.onLeftPressed: {
let view = effect.getView(Qt.LeftEdge)
if (view) {
effect.activateView(view)
}
}
Keys.onRightPressed: {
let view = effect.getView(Qt.RightEdge)
if (view) {
effect.activateView(view)
}
}
Keys.onUpPressed: {
let view = effect.getView(Qt.TopEdge)
if (view) {
effect.activateView(view)
}
}
Keys.onDownPressed: {
let view = effect.getView(Qt.BottomEdge)
if (view) {
effect.activateView(view)
function selectNext(direction) {
if (effect.searchText !== "") return false;
let currentIndex = 0
for (let i = 0; i < allDesktopHeaps.count; i++) {
if (allDesktopHeaps.itemAt(i).current) {
currentIndex = i;
break;
}
}
let x = currentIndex % container.columns;
let y = Math.floor(currentIndex / container.columns);
// the direction we move in is the opposite of the window to select
// i.e pressing left should select the rightmost window on the desktop
// to the left
let invertedDirection;
switch(direction) {
case WindowHeap.Direction.Up:
y--;
invertedDirection = WindowHeap.Direction.Down;
break;
case WindowHeap.Direction.Down:
y++
invertedDirection = WindowHeap.Direction.Up;
break;
case WindowHeap.Direction.Left:
x--;
invertedDirection = WindowHeap.Direction.Right;
break;
case WindowHeap.Direction.Right:
x++;
invertedDirection = WindowHeap.Direction.Left;
break;
}
if (x < 0 || x >= container.columns) {
return false;
}
if (y < 0 || y >= container.rows) {
return false;
}
let newIndex = y * container.columns + x;
KWinComponents.Workspace.currentDesktop = allDesktopHeaps.itemAt(newIndex).desktop
allDesktopHeaps.itemAt(newIndex).nestedHeap.focus = true
allDesktopHeaps.itemAt(newIndex).selectLastItem(invertedDirection);
return true;
}
Keys.onPressed: (event) => {
if (event.key === Qt.Key_Escape) {
if (effect.searchText !== "") {
effect.searchText = ""
} else {
effect.deactivate();
}
} else if (event.key === Qt.Key_Plus || event.key === Qt.Key_Equal) {
desktopModel.create(desktopModel.rowCount());
} else if (event.key === Qt.Key_Minus) {
desktopModel.remove(desktopModel.rowCount() - 1);
} else if (event.key >= Qt.Key_F1 && event.key <= Qt.Key_F12) {
const desktopId = event.key - Qt.Key_F1;
if (desktopId < allDesktopHeaps.count) {
switchTo(allDesktopHeaps.itemAt(desktopId).desktop);
}
} else if (event.key >= Qt.Key_0 && event.key <= Qt.Key_9) {
const desktopId = event.key === Qt.Key_0 ? 9 : (event.key - Qt.Key_1);
if (desktopId < allDesktopHeaps.count) {
switchTo(allDesktopHeaps.itemAt(desktopId).desktop);
}
} else if (event.key === Qt.Key_Up) {
event.accepted = selectNext(WindowHeap.Direction.Up);
if (!event.accepted) {
let view = effect.getView(Qt.TopEdge)
if (view) {
effect.activateView(view)
}
}
} else if (event.key === Qt.Key_Down) {
event.accepted = selectNext(WindowHeap.Direction.Down);
if (!event.accepted) {
let view = effect.getView(Qt.BottomEdge)
if (view) {
effect.activateView(view)
}
}
} else if (event.key === Qt.Key_Left) {
event.accepted = selectNext(WindowHeap.Direction.Left);
if (!event.accepted) {
let view = effect.getView(Qt.LeftEdge)
if (view) {
effect.activateView(view)
}
}
} else if (event.key === Qt.Key_Right) {
event.accepted = selectNext(WindowHeap.Direction.Right);
if (!event.accepted) {
let view = effect.getView(Qt.RightEdge)
if (view) {
effect.activateView(view)
}
}
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Space) {
for (let i = 0; i < allDesktopHeaps.count; i++) {
if (allDesktopHeaps.itemAt(i).current) {
switchTo(allDesktopHeaps.itemAt(i).desktop)
break;
}
}
}
}
Keys.priority: Keys.AfterItem
KWinComponents.DesktopBackground {
id: backgroundItem
activity: KWinComponents.Workspace.currentActivity
desktop: KWinComponents.Workspace.currentDesktop
outputName: targetScreen.name
property real blurRadius: 0
layer.enabled: true
layer.effect: FastBlur {
radius: backgroundItem.blurRadius
}
}
state: {
if (effect.gestureInProgress) {
return "partial";
} else if (organized) {
return "active";
} else {
return "initial";
}
}
states: [
State {
name: "initial"
PropertyChanges {
target: underlay
opacity: 0
}
PropertyChanges {
target: topBar
opacity: 0
}
PropertyChanges {
target: backgroundItem
blurRadius: 0
}
},
State {
name: "partial"
PropertyChanges {
target: underlay
opacity: 0.75 * effect.partialActivationFactor
}
PropertyChanges {
target: topBar
opacity: effect.partialActivationFactor
}
PropertyChanges {
target: backgroundItem
blurRadius: 64 * effect.partialActivationFactor
}
},
State {
name: "active"
PropertyChanges {
target: underlay
opacity: 0.75
}
PropertyChanges {
target: topBar
opacity: 1
}
PropertyChanges {
target: backgroundItem
blurRadius: 64
}
}
]
transitions: Transition {
to: "initial, active"
NumberAnimation {
duration: effect.animationDuration
properties: "opacity, blurRadius"
easing.type: Easing.OutCubic
}
layer.effect: FastBlur {radius: 64}
}
Rectangle {
id: underlay
anchors.fill: parent
opacity: 0.7
color: Kirigami.Theme.backgroundColor
TapHandler {
@ -165,112 +208,355 @@ FocusScope {
}
}
Column {
anchors.fill: parent
Item {
id: desktopBar
visible: container.anyDesktopBar
Item {
id: topBar
width: parent.width
height: searchBar.height + desktopBar.height
// (overviewVal, gridVal) represents the state of the overview
// in a 2D coordinate plane. Math.atan2 returns the angle between
// the x axis and that point. By using this to set the opacity,
// we can have an opaque desktopBar when the point moves from
// the origin to the "overviewVal" direction, which has angle pi/2,
// and a transparent desktopBar when the point moves from
// the origin to the "gridVal" direction, which has angle 0,
// whilst still animating when the point moves from overviewVal to
// gridVal too.
opacity: Math.atan2(overviewVal, gridVal) / Math.PI * 2
Rectangle {
id: desktopBar
width: parent.width
implicitHeight: bar.implicitHeight + 2 * Kirigami.Units.smallSpacing
color: container.lightBackground ? Qt.rgba(Kirigami.Theme.backgroundColor.r,
Kirigami.Theme.backgroundColor.g,
Kirigami.Theme.backgroundColor.b, 0.75)
: Qt.rgba(0, 0, 0, 0.25)
anchors.top: parent.top
anchors.left: parent.left
anchors.right: container.verticalDesktopBar ? undefined : parent.right
anchors.bottom: container.verticalDesktopBar ? parent.bottom : undefined
height: bar.implicitHeight + 2 * Kirigami.Units.smallSpacing
width: bar.implicitWidth + 2 * Kirigami.Units.smallSpacing
DesktopBar {
id: bar
anchors.fill: parent
windowModel: stackModel
desktopModel: desktopModel
selectedDesktop: KWinComponents.Workspace.currentDesktop
heap: currentHeap
}
DesktopBar {
id: bar
anchors.fill: parent
windowModel: stackModel
desktopModel: desktopModel
verticalDesktopBar: container.verticalDesktopBar
selectedDesktop: KWinComponents.Workspace.currentDesktop
heap: allDesktopHeaps.currentHeap
}
}
Item {
id: topBar
opacity: desktopBar.opacity
anchors.left: container.verticalDesktopBar ? desktopBar.right : parent.left
anchors.right: parent.right
anchors.top: container.verticalDesktopBar || !container.anyDesktopBar ? parent.top : desktopBar.bottom
anchors.topMargin: Kirigami.Units.largeSpacing
height: searchField.height + 1 * Kirigami.Units.largeSpacing
PlasmaExtras.SearchField {
id: searchField
anchors.centerIn: parent
width: Math.min(parent.width, 20 * Kirigami.Units.gridUnit)
focus: enabled
readOnly: gridVal == 1
onReadOnlyChanged: {
text = ""
effect.searchText = ""
effect.searchTextChanged()
}
Item {
id: searchBar
anchors.top: desktopBar.bottom
width: parent.width
height: searchField.height + 2 * Kirigami.Units.gridUnit
PlasmaExtras.SearchField {
id: searchField
anchors.centerIn: parent
width: Math.min(parent.width, 20 * Kirigami.Units.gridUnit)
focus: true
Keys.priority: Keys.BeforeItem
Keys.forwardTo: text && currentHeap.count === 0 ? searchResults : currentHeap
text: effect.searchText
onTextEdited: {
effect.searchText = text;
currentHeap.resetSelected();
currentHeap.selectNextItem(WindowHeap.Direction.Down);
searchField.focus = true;
}
}
Keys.priority: Keys.BeforeItem
Keys.forwardTo: text && allDesktopHeaps.currentHeap.count === 0 ? searchResults : allDesktopHeaps.currentHeap
text: effect.searchText
onTextEdited: {
effect.searchText = text;
allDesktopHeaps.currentHeap.resetSelected();
allDesktopHeaps.currentHeap.selectNextItem(WindowHeap.Direction.Down);
searchField.focus = true;
}
}
}
Item {
width: parent.width
height: parent.height - topBar.height
property var currentGeometry: targetScreen.geometry
PlasmaExtras.PlaceholderMessage {
id: placeholderMessage
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
visible: container.organized && effect.searchText.length > 0 && currentHeap.count === 0
text: i18nd("kwin", "No matching windows")
// These are the minimum position of maximum size of the desktop preview in the overview
property int minX: Kirigami.Units.largeSpacing + (container.verticalDesktopBar ? desktopBar.width : 0)
property int minY: Kirigami.Units.largeSpacing + topBar.height + (container.verticalDesktopBar || !container.anyDesktopBar ? 0 : desktopBar.height)
property int maxWidth: currentGeometry.width - minX - Kirigami.Units.gridUnit * 2
property int maxHeight: currentGeometry.height - minY - Kirigami.Units.gridUnit * 2
property int desktops: Math.max(bar.desktopCount, 2)
property int columns: Math.ceil(desktops / rows)
property int rows: KWinComponents.Workspace.desktopGridHeight
// The desktop might shuffle around as soon as it's
// created since the rows/columns are updated after
// the desktop is added. We don't want to see that.
property bool desktopJustCreated: false
onDesktopsChanged: {
desktopJustCreated = true
startTimer.running = true
}
Timer {
id: startTimer
interval: effect.animationDuration
running: false
onTriggered: desktopJustCreated = false
}
Item {
id: desktopGrid
anchors.fill: parent
property var dndManagerStore: ({})
ColumnLayout {
x: Math.round(parent.width / 2) + Math.round(parent.width / 8)
width: Math.round(parent.width / 2) - Math.round(parent.width / 8) * 2
anchors.verticalCenter: parent.verticalCenter
visible: bar.desktopCount === 1
opacity: gridVal
spacing: Kirigami.Units.largeSpacing
Kirigami.PlaceholderMessage {
text: i18ndc("kwin", "@info:placeholder", "No other Virtual Desktops to show")
icon.name: "virtual-desktops-symbolic"
}
StackView {
id: heapView
PC3.Button {
Layout.alignment: Qt.AlignHCenter
text: i18ndc("kwin", "@action:button", "Add Virtual Desktop")
icon.name: "list-add-symbolic"
onClicked: desktopModel.create(desktopModel.rowCount())
}
PC3.Button {
Layout.alignment: Qt.AlignHCenter
text: i18ndc("kwin", "@action:button", "Configure Virtual Desktops…")
icon.name: "preferences-virtual-desktops"
onClicked: {
KCM.KCMLauncher.openSystemSettings("kcm_kwin_virtualdesktops")
effect.deactivate();
}
}
}
Repeater {
id: allDesktopHeaps
model: desktopModel
property Item currentHeap
property Item currentBackgroundItem
Kirigami.ShadowedRectangle {
id: mainBackground
color: Kirigami.Theme.highlightColor
visible: gridVal > 0 || nearCurrent
anchors.fill: parent
property bool shouldBeVisibleInOverview: !(container.organized && effect.searchText.length > 0 && current) || heap.count !== 0
opacity: 1 - overviewVal * (shouldBeVisibleInOverview ? 0 : 1)
function switchTo(desktop) {
container.animationEnabled = false;
heapView.replace(heapTemplate, { desktop: desktop });
currentItem.layout.forceLayout();
container.animationEnabled = true;
function selectLastItem(direction) {
heap.selectLastItem(direction);
}
Component.onCompleted: {
push(heapTemplate, { desktop: KWinComponents.Workspace.currentDesktop });
}
}
Connections {
target: KWinComponents.Workspace
function onCurrentDesktopChanged() {
heapView.switchTo(KWinComponents.Workspace.currentDesktop);
}
}
required property QtObject desktop
required property int index
readonly property bool current: KWinComponents.Workspace.currentDesktop === desktop
readonly property bool nearCurrent: Math.abs(deltaColumn) <= 1 && Math.abs(deltaRow) <= 1
readonly property var nestedHeap: heap
Component {
id: heapTemplate
z: dragActive ? 1 : 0
readonly property bool dragActive: heap.dragActive || dragHandler.active
shadow {
size: Kirigami.Units.gridUnit * 2
color: Qt.rgba(0, 0, 0, 0.3)
yOffset: 3
}
radius: Kirigami.Units.largeSpacing * 2 * (overviewVal + gridVal * 2)
property int gridSize: Math.max(rows, columns)
property real row: (index - column) / columns
property real column: index % columns
// deltaX and deltaY are used to move all the desktops together to 1:1 animate the
// switching between different desktops
property real deltaX: (!current ? effect.desktopOffset.x :
column == 0 ? Math.max(0, effect.desktopOffset.x) :
column == columns - 1 ? Math.min(0, effect.desktopOffset.x) :
effect.desktopOffset.x)
property real deltaY: (!current ? effect.desktopOffset.y :
row == 0 ? Math.max(0, effect.desktopOffset.y) :
row == rows - 1 ? Math.min(0, effect.desktopOffset.y) :
effect.desktopOffset.y)
// deltaColumn and deltaRows are the difference between the column/row of this desktop
// compared to the column/row of the active one
property real deltaColumn: column - allDesktopHeaps.currentBackgroundItem.column - deltaX
property real deltaRow: row - allDesktopHeaps.currentBackgroundItem.row - deltaY
Behavior on deltaColumn {
enabled: overviewVal > 0 && !container.desktopJustCreated
NumberAnimation {
duration: effect.animationDuration
easing.type: Easing.OutCubic
}
}
Behavior on deltaRow {
enabled: overviewVal > 0 && !container.desktopJustCreated
NumberAnimation {
duration: effect.animationDuration
easing.type: Easing.OutCubic
}
}
// Note that transforms should be read from the last one to the first one
transform: [
// Scales down further, still in grid, to have some gaps between
// the desktops.
Scale {
origin.x: width / 2
origin.y: height / 2
xScale: 1 - 0.02 * gridVal
yScale: xScale
},
// Scales down the desktops so that they do not overlap in the grid
Scale {
id: gridScale
xScale: 1 + (1 / gridSize - 1) * gridVal
yScale: xScale
},
// Further little translation in the grids to align the elements
// to the center of their cell
Translate{
x: (gridSize / columns - 1) * (width / gridSize) / 2 * gridVal
y: (gridSize / rows - 1) * (height / gridSize) / 2 * gridVal
},
// When in desktop grid, translates the desktop so that the whole
// grid fits in the view
Translate {
x: column * (width / columns) * gridVal
y: row * (height / rows) * gridVal
},
// Scales down the preview slighly when in Overview mode
Scale {
origin.x: width / 2
origin.y: height / 2
property real scale: Math.min(maxWidth / width, maxHeight / height)
xScale: 1 + (scale - 1) * overviewVal
yScale:1 + (scale - 1) * overviewVal
},
// Initially places transition desktops in a grid around the current one,
// and moves them slighly to avoid overlapping the UI
Translate {
x: minX * 0.5 * overviewVal + deltaColumn * width * (1 - gridVal)
y: minY * 0.5 * overviewVal + deltaRow * height * (1 - gridVal)
}
]
KWinComponents.DesktopBackground {
id: desktopElement
anchors.fill: parent
anchors.margins: gridVal !== 0 ? Math.round(mainBackground.current * gridVal * (1.5 / gridScale.xScale)) : 0
activity: KWinComponents.Workspace.currentActivity
desktop: KWinComponents.Workspace.currentDesktop
outputName: targetScreen.name
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
anchors.centerIn: parent
width: desktopElement.width
height: desktopElement.height
radius: mainBackground.radius
}
}
}
DropArea {
anchors.fill: parent
onEntered: (drag) => {
drag.accepted = true;
}
onDropped: (drop) => {
drop.accepted = true;
if (drag.source instanceof Kirigami.ShadowedRectangle) {
// dragging a desktop as a whole
if (drag.source === mainBackground) {
drop.action = Qt.IgnoreAction;
return;
}
effect.swapDesktops(drag.source.desktop.x11DesktopNumber, desktop.x11DesktopNumber);
} else {
// dragging a KWin::Window
if (drag.source.desktops.length === 0 || drag.source.desktops.indexOf(mainBackground.desktop) !== -1) {
drop.action = Qt.IgnoreAction;
return;
}
drag.source.desktops = [mainBackground.desktop];
}
}
}
Connections {
target: effect
function onItemDroppedOutOfScreen(globalPos, item, screen) {
if (screen !== targetScreen) {
return;
}
const pos = screen.mapFromGlobal(globalPos);
if (!mainBackground.contains(mainBackground.mapFromItem(null, pos.x, pos.y))) {
return;
}
item.client.desktops = [mainBackground.desktop];
}
}
DragHandler {
id: dragHandler
target: heap
enabled: gridVal !== 0
grabPermissions: PointerHandler.ApprovesTakeOverByHandlersOfSameType
onActiveChanged: {
if (!active) {
heap.Drag.drop();
Qt.callLater(heap.resetPosition)
}
}
}
MouseArea {
anchors.fill: heap
acceptedButtons: Qt.NoButton
cursorShape: dragHandler.active ? Qt.ClosedHandCursor : Qt.ArrowCursor
}
WindowHeap {
id: heap
width: parent.width
height: parent.height
x: 0
y: 0
required property QtObject desktop
function resetPosition() {
x = 0;
y = 0;
}
z: 9999
Drag.active: dragHandler.active
Drag.proposedAction: Qt.MoveAction
Drag.supportedActions: Qt.MoveAction
Drag.source: mainBackground
Drag.hotSpot: Qt.point(width * 0.5, height * 0.5)
visible: !(container.organized && effect.searchText.length > 0) || heap.count !== 0
layout.mode: effect.layout
focus: true
padding: Kirigami.Units.gridUnit
focus: current
padding: Kirigami.Units.largeSpacing
animationDuration: effect.animationDuration
animationEnabled: container.animationEnabled
animationEnabled: container.animationEnabled && (gridVal !== 0 || mainBackground.current)
organized: container.organized
dndManagerStore: desktopGrid.dndManagerStore
Keys.priority: Keys.AfterItem
Keys.forwardTo: searchResults
Keys.forwardTo: [searchResults, searchField]
model: KWinComponents.WindowFilterModel {
activity: KWinComponents.Workspace.currentActivity
desktop: heap.desktop
desktop: mainBackground.desktop
screenName: targetScreen.name
windowModel: stackModel
filter: effect.searchText
@ -283,30 +569,93 @@ FocusScope {
delegate: WindowHeapDelegate {
windowHeap: heap
// This is preferable over using gestureInProgress values since gridVal and
// overviewVal are animated even after the gesture ends, and since the partial
// activation factor follows those two values, this results in a more
// fluent animation.
gestureInProgress: !Number.isInteger(gridVal) || !Number.isInteger(overviewVal)
partialActivationFactor: container.overviewVal + container.gridVal * effect.organizedGrid
targetScale: {
if (!container.anyDesktopBar) return targetScale;
if (overviewVal != 1) return targetScale;
let coordinate = container.verticalDesktopBar ? 'x' : 'y'
if (!activeDragHandler.active) {
return targetScale; // leave it alone, so it won't affect transitions before they start
}
var localPressPosition = activeDragHandler.centroid.scenePressPosition.y - heap.layout.Kirigami.ScenePosition.y;
var localPressPosition = activeDragHandler.centroid.scenePressPosition[coordinate] - heap.layout.Kirigami.ScenePosition[coordinate];
if (localPressPosition === 0) {
return 0.1;
} else {
var localPosition = activeDragHandler.centroid.scenePosition.y - heap.layout.Kirigami.ScenePosition.y;
var localPosition = activeDragHandler.centroid.scenePosition[coordinate] - heap.layout.Kirigami.ScenePosition[coordinate];
return Math.max(0.1, Math.min(localPosition / localPressPosition, 1));
}
}
opacity: 1 - downGestureProgress
onDownGestureTriggered: window.closeWindow()
TapHandler {
acceptedPointerTypes: PointerDevice.GenericPointer | PointerDevice.Pen
acceptedButtons: Qt.MiddleButton
onTapped: window.closeWindow()
acceptedButtons: Qt.MiddleButton | Qt.RightButton
onTapped: (eventPoint, button) => {
if (button === Qt.MiddleButton) {
window.closeWindow();
} else if (button === Qt.RightButton) {
if (window.desktops.length > 0) {
window.desktops = [];
} else {
window.desktops = [desktopView.desktop];
}
}
}
}
}
onActivated: effect.deactivate();
onActivated: effect.deactivate()
}
TapHandler {
acceptedButtons: Qt.LeftButton
onTapped: {
KWinComponents.Workspace.currentDesktop = mainBackground.desktop;
container.effect.deactivate();
}
}
onCurrentChanged: {
if (current) {
allDesktopHeaps.currentHeap = heap;
allDesktopHeaps.currentBackgroundItem = mainBackground;
}
}
Component.onCompleted: {
if (current) {
allDesktopHeaps.currentHeap = heap;
allDesktopHeaps.currentBackgroundItem = mainBackground;
}
startTimer.running = true
}
}
}
}
Column {
anchors.left: container.verticalDesktopBar ? desktopBar.right : parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.top: topBar.bottom
Item {
width: parent.width
height: parent.height - topBar.height
visible: container.organized && effect.searchText.length > 0 && allDesktopHeaps.currentHeap.count === 0
opacity: overviewVal
PlasmaExtras.PlaceholderMessage {
id: placeholderMessage
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
text: i18ndc("kwin", "@info:placeholder", "No matching windows")
}
Milou.ResultsView {
@ -314,9 +663,8 @@ FocusScope {
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width / 2
height: parent.height - placeholderMessage.height - Kirigami.Units.gridUnit
height: parent.height - placeholderMessage.height - Kirigami.Units.largeSpacing
queryString: effect.searchText
visible: container.organized && effect.searchText.length > 0 && currentHeap.count === 0
onActivated: {
effect.deactivate();
@ -342,14 +690,7 @@ FocusScope {
z: model.window.stackingOrder
width: model.window.width
height: model.window.height
opacity: container.effect.gestureInProgress
? 1 - container.effect.partialActivationFactor
: (model.window.hidden || container.organized) ? 0 : 1
Behavior on opacity {
enabled: !container.effect.gestureInProgress
NumberAnimation { duration: effect.animationDuration; easing.type: Easing.OutCubic }
}
opacity: 1 - (gridVal + overviewVal)
}
}
@ -361,5 +702,12 @@ FocusScope {
id: desktopModel
}
Component.onCompleted: start();
Component.onCompleted: {
// The following line unbinds the verticalDesktopBar, meaning that it
// won't react to changes in number of desktops or rows. This is beacuse we
// don't want the desktop bar changing screenside whilst the user is
// interacting with it, e.g. by adding desktops
container.verticalDesktopBar = container.verticalDesktopBar
start();
}
}

View file

@ -7,10 +7,12 @@
import QtQuick
import QtQuick.Window
import Qt5Compat.GraphicalEffects
import org.kde.kirigami 2.20 as Kirigami
import org.kde.kwin as KWinComponents
import org.kde.kwin.private.effects
import org.kde.plasma.components 3.0 as PC3
import org.kde.plasma.workspace.components 2.0 as WorkspaceComponents
import org.kde.ksvg 1.0 as KSvg
Item {
@ -21,6 +23,8 @@ Item {
required property Item windowHeap
readonly property bool selected: windowHeap.selectedIndex === index
property real partialActivationFactor: effect.partialActivationFactor
property bool gestureInProgress: effect.gestureInProgress
// no desktops is a special value which means "All Desktops"
readonly property bool presentOnCurrentDesktop: !window.desktops.length || window.desktops.indexOf(KWinComponents.Workspace.currentDesktop) !== -1
@ -59,10 +63,10 @@ Item {
property string substate: "normal"
state: {
if (effect.gestureInProgress) {
if (thumb.gestureInProgress) {
return "partial";
}
if (windowHeap.effectiveOrganized) {
if (thumb.partialActivationFactor > 0.5 && cell.isReady) {
return activeHidden ? "active-hidden" : `active-${substate}`;
}
return initialHidden ? "initial-hidden" : "initial";
@ -163,10 +167,11 @@ Item {
anchors.bottomMargin: -Math.round(height / 4)
visible: !thumb.activeHidden && !activeDragHandler.active
PC3.Label {
WorkspaceComponents.ShadowedLabel {
id: caption
visible: thumb.windowTitleVisible
width: Math.min(implicitWidth, thumbSource.width)
width: Math.min(implicitWidth, thumb.width)
anchors.top: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
elide: Text.ElideRight
@ -187,6 +192,7 @@ Item {
naturalHeight: thumb.window.height
persistentKey: thumb.window.internalId
bottomMargin: icon.height / 4 + (thumb.windowTitleVisible ? caption.height : 0)
property bool isReady: width !== 0 && height !== 0
}
states: [
@ -219,13 +225,13 @@ Item {
name: "partial"
PropertyChanges {
target: thumb
x: (thumb.window.x - targetScreen.geometry.x - (thumb.windowHeap.absolutePositioning ? windowHeap.layout.Kirigami.ScenePosition.x : 0)) * (1 - effect.partialActivationFactor) + cell.x * effect.partialActivationFactor
y: (thumb.window.y - targetScreen.geometry.y - (thumb.windowHeap.absolutePositioning ? windowHeap.layout.Kirigami.ScenePosition.y : 0)) * (1 - effect.partialActivationFactor) + cell.y * effect.partialActivationFactor
width: thumb.window.width * (1 - effect.partialActivationFactor) + cell.width * effect.partialActivationFactor
height: thumb.window.height * (1 - effect.partialActivationFactor) + cell.height * effect.partialActivationFactor
x: (thumb.window.x - targetScreen.geometry.x - (thumb.windowHeap.absolutePositioning ? windowHeap.layout.Kirigami.ScenePosition.x : 0)) * (1 - thumb.partialActivationFactor) + cell.x * thumb.partialActivationFactor
y: (thumb.window.y - targetScreen.geometry.y - (thumb.windowHeap.absolutePositioning ? windowHeap.layout.Kirigami.ScenePosition.y : 0)) * (1 - thumb.partialActivationFactor) + cell.y * thumb.partialActivationFactor
width: thumb.window.width * (1 - thumb.partialActivationFactor) + cell.width * thumb.partialActivationFactor
height: thumb.window.height * (1 - thumb.partialActivationFactor) + cell.height * thumb.partialActivationFactor
opacity: thumb.initialHidden
? (thumb.activeHidden ? 0 : effect.partialActivationFactor)
: (thumb.activeHidden ? 1 - effect.partialActivationFactor : 1)
? (thumb.activeHidden ? 0 : thumb.partialActivationFactor)
: (thumb.activeHidden ? 1 - thumb.partialActivationFactor : 1)
}
PropertyChanges {
target: thumbSource
@ -236,11 +242,11 @@ Item {
}
PropertyChanges {
target: icon
opacity: effect.partialActivationFactor
opacity: thumb.partialActivationFactor
}
PropertyChanges {
target: closeButton
opacity: effect.partialActivationFactor
opacity: thumb.partialActivationFactor
}
},
State {
@ -289,8 +295,8 @@ Item {
target: thumbSource
x: 0
y: 0
width: cell.width
height: cell.height
width: thumb.width
height: thumb.height
}
},
State {
@ -298,8 +304,8 @@ Item {
extend: "active"
PropertyChanges {
target: thumbSource
width: cell.width
height: cell.height
width: thumb.width
height: thumb.height
}
},
State {
@ -311,8 +317,8 @@ Item {
thumb.activeDragHandler.centroid.position.x
y: -thumb.activeDragHandler.centroid.pressPosition.y * thumb.targetScale +
thumb.activeDragHandler.centroid.position.y
width: cell.width * thumb.targetScale
height: cell.height * thumb.targetScale
width: thumb.width * thumb.targetScale
height: thumb.height * thumb.targetScale
}
},
State {

View file

@ -201,6 +201,7 @@ Item {
}
delegate: WindowHeapDelegate {
windowHeap: heap
partialActivationFactor: container.organized ? 1 : 0
opacity: 1 - downGestureProgress
onDownGestureTriggered: window.closeWindow()

View file

@ -125,8 +125,6 @@ WindowViewEffect::WindowViewEffect()
}
}
};
effects->registerTouchpadSwipeShortcut(SwipeDirection::Down, 4, m_realtimeToggleAction, gestureCallback);
effects->registerTouchscreenSwipeShortcut(SwipeDirection::Down, 3, m_realtimeToggleAction, gestureCallback);
reconfigure(ReconfigureAll);
}