mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23:09 +00:00
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:
parent
7e07038a69
commit
bbac7e98b4
19 changed files with 235 additions and 220 deletions
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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"
|
|
||||||
|
|
@ -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();
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
38
kwin/mobiletaskswitcher/package/contents/ui/main.qml
Normal file
38
kwin/mobiletaskswitcher/package/contents/ui/main.qml
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
26
kwin/mobiletaskswitcher/plugin/CMakeLists.txt
Normal file
26
kwin/mobiletaskswitcher/plugin/CMakeLists.txt
Normal 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)
|
||||||
|
|
@ -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) {
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
Loading…
Reference in a new issue