taskswitcher: Switch to declarative effect

This switches the kwin effect to be a declarative effect. However, for
now I have retained much of the logic that exists in cpp as a QML
plugin.

Fixes https://invent.kde.org/plasma/plasma-mobile/-/issues/448

Also implements https://invent.kde.org/plasma/plasma-mobile/-/issues/408 now that we can access the QML api
This commit is contained in:
Devin Lin 2025-03-01 21:26:47 +00:00
parent 7e07038a69
commit bbac7e98b4
19 changed files with 235 additions and 220 deletions

View file

@ -4,7 +4,7 @@
# SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org> # SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.24)
project(plasma-mobile) project(plasma-mobile)
set(PROJECT_VERSION "6.3.80") set(PROJECT_VERSION "6.3.80")

View file

@ -1,28 +1,10 @@
# SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org> # SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
kcoreaddons_add_plugin(mobiletaskswitcher INSTALL_NAMESPACE "kwin/effects/plugins") kpackage_install_package(package mobiletaskswitcher effects kwin)
target_sources(mobiletaskswitcher PRIVATE
main.cpp
mobiletaskswitchereffect.cpp
effecttouchborder.cpp
taskfiltermodel.cpp
taskmodel.cpp
)
target_link_libraries(mobiletaskswitcher # Copy the script to the build directory so one can run tests without prior
KF6::ConfigGui # make install.
KF6::GlobalAccel file(COPY package/contents package/metadata.json DESTINATION ${CMAKE_BINARY_DIR}/bin/kwin/effects/mobiletaskswitcher)
KF6::I18n
KF6::CoreAddons
KF6::WindowSystem
Qt::Quick add_subdirectory(plugin)
Qt::Core
KWin::kwin
Plasma::Activities
)
install(DIRECTORY qml DESTINATION ${KDE_INSTALL_DATADIR}/kwin/effects/mobiletaskswitcher)
install(FILES mobiletaskswitcher.json DESTINATION ${KDE_INSTALL_DATADIR}/kwin/effects/mobiletaskswitcher)

View file

@ -1,13 +0,0 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "mobiletaskswitchereffect.h"
namespace KWin
{
KWIN_EFFECT_FACTORY_SUPPORTED(MobileTaskSwitcherEffect, "mobiletaskswitcher.json", return MobileTaskSwitcherEffect::supported();)
} // namespace KWin
#include "main.moc"

View file

@ -12,7 +12,7 @@ import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.private.mobileshell as MobileShell import org.kde.plasma.private.mobileshell as MobileShell
import org.kde.plasma.private.mobileshell.state as MobileShellState import org.kde.plasma.private.mobileshell.state as MobileShellState
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
import org.kde.private.mobileshell.taskswitcher 1.0 as TaskSwitcherData import org.kde.plasma.private.mobileshell.taskswitcherplugin as TaskSwitcherPlugin
import org.kde.kwin 3.0 as KWinComponents import org.kde.kwin 3.0 as KWinComponents
import org.kde.kwin.private.effects 1.0 import org.kde.kwin.private.effects 1.0
@ -26,8 +26,8 @@ FocusScope {
id: root id: root
focus: true focus: true
property TaskSwitcherPlugin.MobileTaskSwitcherState state
readonly property QtObject effect: KWinComponents.SceneView.effect readonly property QtObject effect: KWinComponents.SceneView.effect
readonly property TaskSwitcherData.TaskSwitcherState state: TaskSwitcherData.TaskSwitcherState
readonly property QtObject targetScreen: KWinComponents.SceneView.screen readonly property QtObject targetScreen: KWinComponents.SceneView.screen
readonly property real topMargin: MobileShell.Constants.topPanelHeight readonly property real topMargin: MobileShell.Constants.topPanelHeight
@ -37,7 +37,6 @@ FocusScope {
property var taskSwitcherHelpers: TaskSwitcherHelpers { property var taskSwitcherHelpers: TaskSwitcherHelpers {
taskSwitcher: root taskSwitcher: root
stateClass: TaskSwitcherData.TaskSwitcherState
taskList: taskList taskList: taskList
} }
@ -45,9 +44,9 @@ FocusScope {
id: haptics id: haptics
} }
property var tasksModel: TaskSwitcherData.TaskFilterModel { property var tasksModel: TaskSwitcherPlugin.TaskFilterModel {
screenName: root.targetScreen.name screenName: root.targetScreen.name
windowModel: TaskSwitcherData.TaskModel windowModel: root.state.taskModel
} }
readonly property int tasksCount: taskList.count readonly property int tasksCount: taskList.count
@ -147,11 +146,11 @@ FocusScope {
} }
function instantHide() { function instantHide() {
root.effect.deactivate(true); root.state.deactivate(true);
} }
function hide() { function hide() {
root.effect.deactivate(false); root.state.deactivate(false);
} }
Connections { Connections {
@ -196,7 +195,7 @@ FocusScope {
// setup some values and return to the initial setup so that the user can always navigate with no down time // setup some values and return to the initial setup so that the user can always navigate with no down time
state.wasInActiveTask = taskSwitcherHelpers.openAppAnim.running ? true : false state.wasInActiveTask = taskSwitcherHelpers.openAppAnim.running ? true : false
taskList.setTaskOffsetValue(state.wasInActiveTask ? taskSwitcherHelpers.taskOffsetValue : taskSwitcherHelpers.homeOffsetValue, true); taskList.setTaskOffsetValue(state.wasInActiveTask ? taskSwitcherHelpers.taskOffsetValue : taskSwitcherHelpers.homeOffsetValue, true);
state.status = !state.wasInActiveTask ? (taskSwitcherHelpers.openAppAnim.closeAnim && !taskSwitcherHelpers.taskDrawerWillOpen ? TaskSwitcherData.TaskSwitcherState.Active : TaskSwitcherData.TaskSwitcherState.Inactive) : TaskSwitcherData.TaskSwitcherState.Inactive state.status = !state.wasInActiveTask ? (taskSwitcherHelpers.openAppAnim.closeAnim && !taskSwitcherHelpers.taskDrawerWillOpen ? TaskSwitcherPlugin.TaskSwitcherState.Active : TaskSwitcherPlugin.TaskSwitcherState.Inactive) : TaskSwitcherPlugin.TaskSwitcherState.Inactive
initialSetup(); initialSetup();
} else if (taskSwitcherHelpers.openAnim.running) { } else if (taskSwitcherHelpers.openAnim.running) {
taskSwitcherHelpers.cancelAnimations(); taskSwitcherHelpers.cancelAnimations();

View file

@ -5,7 +5,7 @@
import QtQuick 2.15 import QtQuick 2.15
import org.kde.kirigami 2.20 as Kirigami import org.kde.kirigami 2.20 as Kirigami
import org.kde.private.mobileshell.taskswitcher 1.0 as TaskSwitcherData import org.kde.plasma.private.mobileshell.taskswitcherplugin as TaskSwitcherPlugin
import org.kde.kwin 3.0 as KWinComponents import org.kde.kwin 3.0 as KWinComponents
@ -19,7 +19,6 @@ QtObject {
// We assume that the taskSwitcher is the size of the entire screen. // We assume that the taskSwitcher is the size of the entire screen.
required property var taskSwitcher required property var taskSwitcher
property var state: taskSwitcher.state property var state: taskSwitcher.state
required property var stateClass
required property var taskList required property var taskList
// task switcher peek and pop setting for when it is toggled from the home screen // task switcher peek and pop setting for when it is toggled from the home screen
@ -56,7 +55,7 @@ QtObject {
readonly property real heightThreshold: windowHeight * 0.55 readonly property real heightThreshold: windowHeight * 0.55
// whether the switcher is opened or not // whether the switcher is opened or not
readonly property bool taskDrawerOpened: state.status == TaskSwitcherData.TaskSwitcherState.Active readonly property bool taskDrawerOpened: state.status == TaskSwitcherPlugin.MobileTaskSwitcherState.Active
// This is true when the task drawer is already opened or if within an app // This is true when the task drawer is already opened or if within an app
readonly property bool notHomeScreenState: state.wasInActiveTask || taskDrawerOpened readonly property bool notHomeScreenState: state.wasInActiveTask || taskDrawerOpened
@ -312,7 +311,7 @@ QtObject {
onFinished: { onFinished: {
if (!isInTaskScrubMode) { if (!isInTaskScrubMode) {
root.state.status = stateClass.Active; root.state.status = TaskSwitcherPlugin.MobileTaskSwitcherState.Active;
} }
} }
} }
@ -328,7 +327,7 @@ QtObject {
easing.type: Easing.InBack easing.type: Easing.InBack
onFinished: { onFinished: {
root.state.status = stateClass.Inactive; root.state.status = TaskSwitcherPlugin.MobileTaskSwitcherState.Inactive;
taskSwitcher.instantHide(); taskSwitcher.instantHide();
} }
} }
@ -360,7 +359,7 @@ QtObject {
duration: 300 duration: 300
easing.type: Easing.OutQuint easing.type: Easing.OutQuint
onFinished: { onFinished: {
root.state.status = stateClass.Inactive; root.state.status = TaskSwitcherPlugin.MobileTaskSwitcherState.Inactive;
taskSwitcher.instantHide(); taskSwitcher.instantHide();
} }
} }

View file

@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: 2025 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick
import org.kde.kwin
import org.kde.plasma.private.mobileshell.taskswitcherplugin as TaskSwitcherPlugin
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
SceneEffect {
id: root
// Created per screen
delegate: TaskSwitcher {
id: taskSwitcher
state: taskSwitcherState
}
ShortcutHandler {
name: 'Mobile Task Switcher'
text: i18n('Toggle Mobile Task Switcher')
sequence: 'Meta+C'
onActivated: taskSwitcherState.toggle()
}
TaskSwitcherPlugin.MobileTaskSwitcherState {
id: taskSwitcherState
gestureEnabled: !ShellSettings.Settings.navigationPanelEnabled
Component.onCompleted: {
// Initialize with effect
taskSwitcherState.init(root);
}
}
}

View file

@ -1,5 +1,12 @@
{ {
"KPackageStructure": "KWin/Effect",
"KPlugin": { "KPlugin": {
"Authors": [
{
"Email": "devin@kde.org",
"Name": "Devin Lin"
}
],
"Category": "Window Management", "Category": "Window Management",
"Description": "Allows you to switch between running tasks with a mobile interface.", "Description": "Allows you to switch between running tasks with a mobile interface.",
"Description[ca@valencia]": "Us permet canviar entre les tasques diferents en execució amb una interfície mòbil.", "Description[ca@valencia]": "Us permet canviar entre les tasques diferents en execució amb una interfície mòbil.",
@ -34,8 +41,10 @@
"Description[x-test]": "xxAllows you to switch between running tasks with a mobile interface.xx", "Description[x-test]": "xxAllows you to switch between running tasks with a mobile interface.xx",
"Description[zh_CN]": "允许您使用移动界面在正在运行的任务之间切换。", "Description[zh_CN]": "允许您使用移动界面在正在运行的任务之间切换。",
"Description[zh_TW]": "讓您用手機介面在執行中的工作項目之間切換。", "Description[zh_TW]": "讓您用手機介面在執行中的工作項目之間切換。",
"Icon": "preferences-system-windows",
"Id": "mobiletaskswitcher",
"EnabledByDefault": false, "EnabledByDefault": false,
"License": "GPL", "License": "GPL-2.0-or-later",
"Name": "Mobile Task Switcher", "Name": "Mobile Task Switcher",
"Name[ca@valencia]": "Commutador de tasques del mòbil", "Name[ca@valencia]": "Commutador de tasques del mòbil",
"Name[ca]": "Commutador de tasques del mòbil", "Name[ca]": "Commutador de tasques del mòbil",
@ -71,5 +80,7 @@
"Name[zh_CN]": "移动任务切换", "Name[zh_CN]": "移动任务切换",
"Name[zh_TW]": "行動工作切換器" "Name[zh_TW]": "行動工作切換器"
}, },
"X-KWin-Border-Activate": true "X-KWin-Border-Activate": true,
"X-KDE-Ordering": 60,
"X-Plasma-API": "declarativescript"
} }

View file

@ -0,0 +1,26 @@
# SPDX-FileCopyrightText: 2025 Devin Lin <devin@kde.org>
# SPDX-License-Identifier: GPL-2.0-or-later
ecm_add_qml_module(mobiletaskswitcherplugin URI org.kde.plasma.private.mobileshell.taskswitcherplugin GENERATE_PLUGIN_SOURCE)
target_sources(mobiletaskswitcherplugin PRIVATE
mobiletaskswitchereffect.cpp
effecttouchborder.cpp
taskfiltermodel.cpp
taskmodel.cpp)
target_link_libraries(mobiletaskswitcherplugin PRIVATE
KF6::ConfigGui
KF6::GlobalAccel
KF6::I18n
KF6::CoreAddons
KF6::WindowSystem
Qt::Quick
Qt::Core
KWin::kwin
Plasma::Activities
)
ecm_finalize_qml_module(mobiletaskswitcherplugin)

View file

@ -57,7 +57,7 @@ void EffectTouchBorder::setBorders(const QList<int> &touchActivateBorders)
effects->registerRealtimeTouchBorder(ElectricBorder(border), effects->registerRealtimeTouchBorder(ElectricBorder(border),
m_state->activateAction(), m_state->activateAction(),
[this](ElectricBorder border, const QPointF &deltaProgress, const Output *screen) { [this](ElectricBorder border, const QPointF &deltaProgress, const Output *screen) {
Q_UNUSED(screen) Q_UNUSED(screen)
m_state->setInProgress(true); m_state->setInProgress(true);
if (border == ElectricTop || border == ElectricBottom) { if (border == ElectricTop || border == ElectricBottom) {

View file

@ -18,11 +18,56 @@ using namespace std::chrono_literals;
namespace KWin namespace KWin
{ {
MobileTaskSwitcherState::MobileTaskSwitcherState(EffectTouchBorderState *effectState) MobileTaskSwitcherState::MobileTaskSwitcherState(QObject *parent)
: m_effectState{effectState} : QObject{parent}
, m_doubleClickTimer{new QElapsedTimer{}} , m_doubleClickTimer{new QElapsedTimer{}}
, m_shutdownTimer{new QTimer{this}}
{ {
// Configure close timer
m_shutdownTimer->setSingleShot(true);
m_shutdownTimer->setInterval(300ms);
connect(m_shutdownTimer, &QTimer::timeout, this, &MobileTaskSwitcherState::realDeactivate);
}
void MobileTaskSwitcherState::init(KWin::QuickSceneEffect *parent)
{
m_effectState = new EffectTouchBorderState(parent);
m_border = new EffectTouchBorder{m_effectState};
m_taskModel = new TaskModel{parent};
m_effect = parent;
// Connect signals
connect(this, &MobileTaskSwitcherState::gestureEnabledChanged, this, &MobileTaskSwitcherState::refreshBorders);
connect(m_border, &EffectTouchBorder::touchPositionChanged, this, &MobileTaskSwitcherState::processTouchPositionChanged);
connect(this, &MobileTaskSwitcherState::gestureInProgressChanged, this, [this]() {
if (gestureInProgress()) {
invokeEffect();
}
});
connect(m_effectState, &EffectTouchBorderState::inProgressChanged, this, &MobileTaskSwitcherState::gestureInProgressChanged); connect(m_effectState, &EffectTouchBorderState::inProgressChanged, this, &MobileTaskSwitcherState::gestureInProgressChanged);
connect(effects, &EffectsHandler::screenAboutToLock, this, &MobileTaskSwitcherState::realDeactivate);
refreshBorders();
}
bool MobileTaskSwitcherState::gestureEnabled() const
{
return m_gestureEnabled;
}
void MobileTaskSwitcherState::setGestureEnabled(bool gestureEnabled)
{
m_gestureEnabled = gestureEnabled;
Q_EMIT gestureEnabledChanged();
}
void MobileTaskSwitcherState::refreshBorders()
{
if (m_gestureEnabled) {
m_border->setBorders({ElectricBorder::ElectricBottom});
} else {
m_border->setBorders({});
}
} }
bool MobileTaskSwitcherState::gestureInProgress() const bool MobileTaskSwitcherState::gestureInProgress() const
@ -124,6 +169,11 @@ void MobileTaskSwitcherState::setYPosition(qreal yPosition)
} }
} }
MobileTaskSwitcherState::Status MobileTaskSwitcherState::status() const
{
return m_status;
}
void MobileTaskSwitcherState::setStatus(Status status) void MobileTaskSwitcherState::setStatus(Status status)
{ {
if (m_status != status) { if (m_status != status) {
@ -135,6 +185,11 @@ void MobileTaskSwitcherState::setStatus(Status status)
} }
} }
int MobileTaskSwitcherState::currentTaskIndex() const
{
return m_currentTaskIndex;
}
void MobileTaskSwitcherState::setCurrentTaskIndex(int newTaskIndex) void MobileTaskSwitcherState::setCurrentTaskIndex(int newTaskIndex)
{ {
if (m_currentTaskIndex != newTaskIndex) { if (m_currentTaskIndex != newTaskIndex) {
@ -143,6 +198,11 @@ void MobileTaskSwitcherState::setCurrentTaskIndex(int newTaskIndex)
} }
} }
int MobileTaskSwitcherState::initialTaskIndex() const
{
return m_initialTaskIndex;
}
void MobileTaskSwitcherState::setInitialTaskIndex(int newTaskIndex) void MobileTaskSwitcherState::setInitialTaskIndex(int newTaskIndex)
{ {
if (m_initialTaskIndex != newTaskIndex) { if (m_initialTaskIndex != newTaskIndex) {
@ -151,6 +211,11 @@ void MobileTaskSwitcherState::setInitialTaskIndex(int newTaskIndex)
} }
} }
TaskModel *MobileTaskSwitcherState::taskModel() const
{
return m_taskModel;
}
void MobileTaskSwitcherState::restartDoubleClickTimer() void MobileTaskSwitcherState::restartDoubleClickTimer()
{ {
m_doubleClickTimer->restart(); m_doubleClickTimer->restart();
@ -198,100 +263,27 @@ void MobileTaskSwitcherState::processTouchPositionChanged(qreal primaryDelta, qr
qint64 MobileTaskSwitcherState::getElapsedTimeSinceStart() qint64 MobileTaskSwitcherState::getElapsedTimeSinceStart()
{ {
if (m_doubleClickTimer->isValid()) if (m_doubleClickTimer->isValid()) {
{
return m_doubleClickTimer->elapsed(); return m_doubleClickTimer->elapsed();
} }
return -1; return -1;
} }
MobileTaskSwitcherEffect::MobileTaskSwitcherEffect() void MobileTaskSwitcherState::toggle()
: m_effectState{new EffectTouchBorderState(this)}
, m_taskSwitcherState{new MobileTaskSwitcherState(m_effectState)}
, m_taskModel{new TaskModel{this}}
, m_border{new EffectTouchBorder{m_effectState}}
, m_toggleAction{std::make_unique<QAction>()}
, m_shutdownTimer{new QTimer{this}}
{ {
const char *uri = "org.kde.private.mobileshell.taskswitcher"; if (!m_effect) {
qmlRegisterType<TaskFilterModel>(uri, 1, 0, "TaskFilterModel");
qmlRegisterSingletonType<TaskModel>(uri, 1, 0, "TaskModel", [this](QQmlEngine *, QJSEngine *) -> QObject * {
return m_taskModel;
});
qmlRegisterSingletonType<MobileTaskSwitcherState>(uri, 1, 0, "TaskSwitcherState", [this](QQmlEngine *, QJSEngine *) -> QObject * {
return m_taskSwitcherState;
});
connect(m_border, &EffectTouchBorder::touchPositionChanged, m_taskSwitcherState, &MobileTaskSwitcherState::processTouchPositionChanged);
connect(m_taskSwitcherState, &MobileTaskSwitcherState::gestureInProgressChanged, this, [this]() {
if (m_taskSwitcherState->gestureInProgress()) {
invokeEffect();
}
});
// configure close timer
m_shutdownTimer->setSingleShot(true);
connect(m_shutdownTimer, &QTimer::timeout, this, &MobileTaskSwitcherEffect::realDeactivate);
// toggle action
const QKeySequence defaultToggleShortcut = Qt::META | Qt::Key_C;
m_toggleAction.get()->setObjectName(QStringLiteral("Mobile Task Switcher"));
m_toggleAction.get()->setText(i18n("Toggle Mobile Task Switcher"));
KGlobalAccel::self()->setDefaultShortcut(m_toggleAction.get(), {defaultToggleShortcut});
KGlobalAccel::self()->setShortcut(m_toggleAction.get(), {defaultToggleShortcut});
connect(m_toggleAction.get(), &QAction::triggered, this, &MobileTaskSwitcherEffect::toggle);
connect(effects, &EffectsHandler::screenAboutToLock, this, &MobileTaskSwitcherEffect::realDeactivate);
setSource(QUrl::fromLocalFile(
QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kwin/effects/mobiletaskswitcher/qml/TaskSwitcher.qml"))));
reconfigure(ReconfigureFlag::ReconfigureAll);
}
MobileTaskSwitcherEffect::~MobileTaskSwitcherEffect()
{
}
void MobileTaskSwitcherEffect::reconfigure(ReconfigureFlags)
{
setAnimationDuration(animationTime(300ms));
m_border->setBorders(m_borderActivate);
}
int MobileTaskSwitcherEffect::requestedEffectChainPosition() const
{
return 70;
}
bool MobileTaskSwitcherEffect::borderActivated(ElectricBorder border)
{
return m_borderActivate.contains(border);
}
void MobileTaskSwitcherEffect::grabbedKeyboardEvent(QKeyEvent *keyEvent)
{
if (m_toggleShortcut.contains(keyEvent->key() | keyEvent->modifiers())) {
if (keyEvent->type() == QEvent::KeyPress) {
toggle();
}
return; return;
} }
QuickSceneEffect::grabbedKeyboardEvent(keyEvent);
}
void MobileTaskSwitcherEffect::toggle() if (!m_effect->isRunning()) {
{ restartDoubleClickTimer();
if (!isRunning()) {
m_taskSwitcherState->restartDoubleClickTimer();
activate(); activate();
} else { } else {
deactivate(false); deactivate(false);
} }
} }
void MobileTaskSwitcherEffect::activate() void MobileTaskSwitcherState::activate()
{ {
if (effects->isScreenLocked()) { if (effects->isScreenLocked()) {
return; return;
@ -301,44 +293,39 @@ void MobileTaskSwitcherEffect::activate()
invokeEffect(); invokeEffect();
} }
void MobileTaskSwitcherEffect::deactivate(bool deactivateInstantly) void MobileTaskSwitcherState::deactivate(bool deactivateInstantly)
{ {
if (!m_effect) {
return;
}
const auto screens = effects->screens(); const auto screens = effects->screens();
for (const auto screen : screens) { for (const auto screen : screens) {
if (QuickSceneView *view = viewForScreen(screen)) { if (QuickSceneView *view = m_effect->viewForScreen(screen)) {
QMetaObject::invokeMethod(view->rootItem(), "hideAnimation"); QMetaObject::invokeMethod(view->rootItem(), "hideAnimation");
} }
} }
m_shutdownTimer->start(animationTime(deactivateInstantly ? 0ms : 200ms)); m_shutdownTimer->start(m_effect->animationTime(deactivateInstantly ? 0ms : 200ms));
} }
void MobileTaskSwitcherEffect::realDeactivate() void MobileTaskSwitcherState::realDeactivate()
{ {
if (!m_effect || !m_effectState) {
return;
}
m_effectState->setInProgress(false); m_effectState->setInProgress(false);
m_taskSwitcherState->setStatus(MobileTaskSwitcherState::Status::Inactive); setStatus(MobileTaskSwitcherState::Status::Inactive);
setRunning(false); m_effect->setRunning(false);
setDBusState(false); setDBusState(false);
} }
void MobileTaskSwitcherEffect::quickDeactivate() void MobileTaskSwitcherState::quickDeactivate()
{ {
m_shutdownTimer->start(0); m_shutdownTimer->start(0);
} }
int MobileTaskSwitcherEffect::animationDuration() const void MobileTaskSwitcherState::setDBusState(bool active)
{
return m_animationDuration;
}
void MobileTaskSwitcherEffect::setAnimationDuration(int duration)
{
if (m_animationDuration != duration) {
m_animationDuration = duration;
Q_EMIT animationDurationChanged();
}
}
void MobileTaskSwitcherEffect::setDBusState(bool active)
{ {
QDBusMessage request = QDBusMessage::createMethodCall(QStringLiteral("org.kde.plasmashell"), QDBusMessage request = QDBusMessage::createMethodCall(QStringLiteral("org.kde.plasmashell"),
QStringLiteral("/Mobile"), QStringLiteral("/Mobile"),
@ -350,11 +337,10 @@ void MobileTaskSwitcherEffect::setDBusState(bool active)
QDBusConnection::sessionBus().send(request); QDBusConnection::sessionBus().send(request);
} }
void MobileTaskSwitcherEffect::invokeEffect() void MobileTaskSwitcherState::invokeEffect()
{ {
m_taskSwitcherState->setInitialTaskIndex( setInitialTaskIndex(currentTaskIndex()); // TODO! this is only until the crashing bug is fixed and recency sorting is in
m_taskSwitcherState->currentTaskIndex()); // TODO! this is only until the crashing bug is fixed and recency sorting is in m_effect->setRunning(true);
setRunning(true);
setDBusState(true); setDBusState(true);
} }
} }

View file

@ -30,6 +30,8 @@ namespace KWin
class MobileTaskSwitcherState : public QObject class MobileTaskSwitcherState : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool gestureEnabled READ gestureEnabled WRITE setGestureEnabled NOTIFY gestureEnabledChanged)
Q_PROPERTY(bool wasInActiveTask READ wasInActiveTask WRITE setWasInActiveTask NOTIFY wasInActiveTaskChanged) Q_PROPERTY(bool wasInActiveTask READ wasInActiveTask WRITE setWasInActiveTask NOTIFY wasInActiveTaskChanged)
Q_PROPERTY(int currentTaskIndex READ currentTaskIndex WRITE setCurrentTaskIndex NOTIFY currentTaskIndexChanged) Q_PROPERTY(int currentTaskIndex READ currentTaskIndex WRITE setCurrentTaskIndex NOTIFY currentTaskIndexChanged)
Q_PROPERTY(int initialTaskIndex READ initialTaskIndex WRITE setInitialTaskIndex NOTIFY initialTaskIndexChanged) Q_PROPERTY(int initialTaskIndex READ initialTaskIndex WRITE setInitialTaskIndex NOTIFY initialTaskIndexChanged)
@ -51,6 +53,9 @@ class MobileTaskSwitcherState : public QObject
Q_PROPERTY(qint64 elapsedTimeSinceStart READ getElapsedTimeSinceStart) Q_PROPERTY(qint64 elapsedTimeSinceStart READ getElapsedTimeSinceStart)
Q_PROPERTY(qint64 doubleClickInterval READ getDoubleClickInterval) // is there a better way than to forward this? Q_PROPERTY(qint64 doubleClickInterval READ getDoubleClickInterval) // is there a better way than to forward this?
Q_PROPERTY(TaskModel *taskModel READ taskModel CONSTANT)
QML_ELEMENT
public: public:
enum class Status { enum class Status {
// TODO! I could (should?) re-add the activating and deactivating states again to match EffectTogglableState. could help with/tie into // TODO! I could (should?) re-add the activating and deactivating states again to match EffectTogglableState. could help with/tie into
@ -61,7 +66,12 @@ public:
}; };
Q_ENUM(Status) Q_ENUM(Status)
MobileTaskSwitcherState(EffectTouchBorderState *effectState); MobileTaskSwitcherState(QObject *parent = nullptr);
Q_INVOKABLE void init(KWin::QuickSceneEffect *parent);
bool gestureEnabled() const;
void setGestureEnabled(bool gestureEnabled);
bool gestureInProgress() const; bool gestureInProgress() const;
void setGestureInProgress(bool gestureInProgress); void setGestureInProgress(bool gestureInProgress);
@ -82,30 +92,34 @@ public:
qreal yPosition() const; qreal yPosition() const;
void setYPosition(qreal positionY); void setYPosition(qreal positionY);
Status status() const;
void setStatus(Status status); void setStatus(Status status);
Status status() const
{
return m_status;
}
int currentTaskIndex() const;
void setCurrentTaskIndex(int newTaskIndex); void setCurrentTaskIndex(int newTaskIndex);
int currentTaskIndex() const
{
return m_currentTaskIndex;
}
int initialTaskIndex() const;
void setInitialTaskIndex(int newTaskIndex); void setInitialTaskIndex(int newTaskIndex);
int initialTaskIndex() const
{
return m_initialTaskIndex;
}
void restartDoubleClickTimer(); void restartDoubleClickTimer();
int animationDuration() const;
void setDBusState(bool active);
TaskModel *taskModel() const;
public Q_SLOTS: public Q_SLOTS:
void processTouchPositionChanged(qreal primaryPosition, qreal orthogonalPosition); void processTouchPositionChanged(qreal primaryPosition, qreal orthogonalPosition);
void activate();
void realDeactivate();
void deactivate(bool deactivateInstantly);
void quickDeactivate();
void toggle();
Q_SIGNALS: Q_SIGNALS:
void gestureEnabledChanged();
void activated(); void activated();
void deactivated(); void deactivated();
@ -125,9 +139,19 @@ Q_SIGNALS:
void xPositionChanged(); void xPositionChanged();
void yPositionChanged(); void yPositionChanged();
private Q_SLOTS:
void refreshBorders();
private: private:
void invokeEffect();
bool m_gestureEnabled{false};
EffectTouchBorderState *m_effectState{nullptr};
EffectTouchBorder *m_border{nullptr};
TaskModel *m_taskModel{nullptr};
KWin::QuickSceneEffect *m_effect{nullptr};
Status m_status = Status::Inactive; Status m_status = Status::Inactive;
EffectTouchBorderState *m_effectState;
bool m_gestureInProgress = false; bool m_gestureInProgress = false;
int m_currentTaskIndex; int m_currentTaskIndex;
@ -161,53 +185,8 @@ private:
{ {
return qApp->doubleClickInterval(); return qApp->doubleClickInterval();
} }
};
class MobileTaskSwitcherEffect : public QuickSceneEffect
{
Q_OBJECT
public:
enum class Status { Inactive, Activating, Deactivating, Active };
MobileTaskSwitcherEffect();
~MobileTaskSwitcherEffect() override;
int animationDuration() const;
void setAnimationDuration(int duration);
int requestedEffectChainPosition() const override;
bool borderActivated(ElectricBorder border) override;
void reconfigure(ReconfigureFlags flags) override;
void grabbedKeyboardEvent(QKeyEvent *keyEvent) override;
void setDBusState(bool active);
public Q_SLOTS:
void activate();
void realDeactivate();
void deactivate(bool deactivateInstantly);
void quickDeactivate();
void toggle();
Q_SIGNALS:
void animationDurationChanged();
void gestureInProgressChanged();
private:
void invokeEffect();
EffectTouchBorderState *const m_effectState;
MobileTaskSwitcherState *const m_taskSwitcherState;
TaskModel *const m_taskModel;
EffectTouchBorder *const m_border;
QList<int> m_borderActivate = {ElectricBorder::ElectricBottom};
std::unique_ptr<QAction> m_toggleAction;
QList<QKeySequence> m_toggleShortcut;
QTimer *m_shutdownTimer; QTimer *m_shutdownTimer;
int m_animationDuration = 400;
}; };
} // namespace KWin } // namespace KWin

View file

@ -10,6 +10,7 @@
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QHash> #include <QHash>
#include <QQmlEngine>
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include <QVariant> #include <QVariant>
@ -21,6 +22,7 @@ class TaskFilterModel : public QSortFilterProxyModel
Q_OBJECT Q_OBJECT
Q_PROPERTY(TaskModel *windowModel READ windowModel WRITE setWindowModel NOTIFY windowModelChanged) Q_PROPERTY(TaskModel *windowModel READ windowModel WRITE setWindowModel NOTIFY windowModelChanged)
Q_PROPERTY(QString screenName READ screenName WRITE setScreenName NOTIFY screenNameChanged) Q_PROPERTY(QString screenName READ screenName WRITE setScreenName NOTIFY screenNameChanged)
QML_ELEMENT
public: public:
explicit TaskFilterModel(QObject *parent = nullptr); explicit TaskFilterModel(QObject *parent = nullptr);

View file

@ -19,7 +19,13 @@ class TaskModel : public QAbstractListModel
Q_OBJECT Q_OBJECT
public: public:
enum Roles { WindowRole = Qt::UserRole + 1, OutputRole, DesktopRole, ActivityRole, LastActivatedRole }; enum Roles {
WindowRole = Qt::UserRole + 1,
OutputRole,
DesktopRole,
ActivityRole,
LastActivatedRole
};
explicit TaskModel(QObject *parent = nullptr); explicit TaskModel(QObject *parent = nullptr);