taskswitcher: Port to kwin effect

This commit is contained in:
Devin Lin 2023-03-05 22:38:43 -08:00
parent f87c7c5526
commit eb03fe8c94
27 changed files with 912 additions and 675 deletions

View file

@ -14,7 +14,7 @@ set(QT_MIN_VERSION "6.4.0")
set(KF6_MIN_VERSION "5.240.0")
set(KDE_COMPILERSETTINGS_LEVEL "5.82")
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(ECM ${KF6_MIN_VERSION} REQUIRED NO_MODULE)
@ -53,6 +53,7 @@ find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} CONFIG REQUIRED
find_package(KF6 ${KF6_MIN_VERSION} REQUIRED COMPONENTS
I18n
GlobalAccel
KIO
Config
DBusAddons
@ -74,6 +75,12 @@ pkg_check_modules(GOBJECT gobject-2.0 REQUIRED IMPORTED_TARGET)
pkg_check_modules(GIO gio-2.0 REQUIRED IMPORTED_TARGET)
find_package(KF6KirigamiAddons 0.6 REQUIRED)
find_package(epoxy REQUIRED)
find_package(XCB REQUIRED COMPONENTS XCB)
find_package(KWinEffects 5.27.0 REQUIRED COMPONENTS
kwineffects
)
find_package(LibKWorkspace CONFIG REQUIRED)
find_package(KWinDBusInterface)

View file

@ -95,9 +95,6 @@ void MobileShellPlugin::registerTypes(const char *uri)
// /statusbar
qmlRegisterType(resolvePath("statusbar/StatusBar.qml"), uri, 1, 0, "StatusBar");
// /taskswitcher
qmlRegisterType(resolvePath("taskswitcher/TaskSwitcher.qml"), uri, 1, 0, "TaskSwitcher");
// /widgets
qmlRegisterType(resolvePath("widgets/krunner/KRunnerWidget.qml"), uri, 1, 0, "KRunnerWidget");
qmlRegisterType(resolvePath("widgets/mediacontrols/MediaControlsWidget.qml"), uri, 1, 0, "MediaControlsWidget");

View file

@ -40,8 +40,6 @@ MouseArea { // use mousearea to ensure clicks don't go behind
}
background.state = "open";
MobileShellState.HomeScreenControls.taskSwitcher.minimizeAll();
}
function close() {

View file

@ -44,7 +44,7 @@ Item {
* Whether a component is being shown on top of the homescreen within the same
* window.
*/
readonly property bool overlayShown: taskSwitcher.visible || startupFeedback.visible
readonly property bool overlayShown: startupFeedback.visible
/**
* Margins for the homescreen, taking panels into account.
@ -82,8 +82,9 @@ Item {
}
MobileShellState.HomeScreenControls.resetHomeScreenPosition();
taskSwitcher.visible = false; // will trigger homescreen open
taskSwitcher.minimizeAll();
MobileShell.WindowUtil.unsetAllMinimizedGeometries(root);
MobileShell.WindowUtil.minimizeAll();
root.homeTriggered();
}
@ -109,7 +110,6 @@ Item {
Plasmoid.onScreenChanged: {
if (plasmoid.screen == 0) {
MobileShellState.HomeScreenControls.taskSwitcher = taskSwitcher;
MobileShellState.HomeScreenControls.homeScreenWindow = root.Window.window;
}
}
@ -127,7 +127,6 @@ Item {
// set API variables
if (plasmoid.screen == 0) {
MobileShellState.HomeScreenControls.taskSwitcher = taskSwitcher;
MobileShellState.HomeScreenControls.homeScreenWindow = root.Window.window;
}
}
@ -203,14 +202,12 @@ Item {
function evaluateAnimChange() {
// only animate if homescreen is visible
if (!taskSwitcher.visible) {
if (!visibleMaximizedWindowsModel.isWindowMaximized || MobileShell.WindowUtil.activeWindowIsShell) {
itemContainer.zoomIn();
} else {
itemContainer.zoomOut();
}
}
}
Connections {
target: MobileShell.WindowUtil
@ -234,45 +231,6 @@ Item {
}
}
// task switcher component
MobileShell.TaskSwitcher {
id: taskSwitcher
z: 999999
topMargin: root.topMargin
bottomMargin: root.bottomMargin
leftMargin: root.leftMargin
rightMargin: root.rightMargin
tasksModel: TaskManager.TasksModel {
groupMode: TaskManager.TasksModel.GroupDisabled
screenGeometry: plasmoid.screenGeometry
sortMode: TaskManager.TasksModel.SortLastActivated
virtualDesktop: virtualDesktopInfo.currentDesktop
activity: activityInfo.currentActivity
}
anchors.fill: parent
// hide homescreen elements to make use of wallpaper
onVisibleChanged: {
if (visible) {
startupFeedback.visible = false;
// hide immediately when going from homescreen
if (!taskSwitcher.wasInActiveTask) {
itemContainer.opacity = 0;
}
itemContainer.zoomOut();
} else {
itemContainer.zoomIn();
}
}
}
// start app animation component
MobileShell.StartupFeedback {
id: startupFeedback

View file

@ -1,17 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <notmart@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.0
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
PlasmaCore.IconItem {
implicitWidth: PlasmaCore.Units.iconSizes.enormous
implicitHeight: PlasmaCore.Units.iconSizes.enormous
usesPlasmaTheme: false
source: model.decoration
}

View file

@ -1,283 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <notmart@gmail.com>
* SPDX-FileCopyrightText: 2021-2022 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.1
import QtQuick.Window 2.15
import org.kde.taskmanager 0.1 as TaskManager
import org.kde.plasma.core 2.1 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.private.nanoshell 2.0 as NanoShell
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState
import "../components" as Components
/**
* Component that provides a task switcher.
*/
Item {
id: root
visible: false
opacity: 0
/**
* Margins for the content (taking shell panels into account).
*/
required property real topMargin
required property real bottomMargin
required property real leftMargin
required property real rightMargin
// state object
property var taskSwitcherState: TaskSwitcherState {
taskSwitcher: root
}
/**
* The task manager model to use for the tasks switcher.
*/
property TaskManager.TasksModel tasksModel
/**
* The number of tasks in the given task manager model.
*/
readonly property int tasksCount: tasksModel.count
/**
* The screen model to be used for moving windows between screens.
*/
property var displaysModel: MobileShell.DisplaysModel {}
/**
* Whether the window is active.
*/
property bool windowActive: Window.active
onWindowActiveChanged: {
// if a window has popped up in front, close the task switcher
if (visible && !windowActive) {
hide();
}
}
// update API property
onVisibleChanged: MobileShellState.HomeScreenControls.taskSwitcherVisible = visible;
// keep track of task list events
property int oldTasksCount: tasksCount
onTasksCountChanged: {
if (tasksCount == 0) {
hide();
} else if (tasksCount < oldTasksCount && taskSwitcherState.currentTaskIndex >= tasksCount - 1) {
// if the user is on the last task, and it is closed, scroll left
taskSwitcherState.animateGoToTaskIndex(tasksCount - 1, PlasmaCore.Units.longDuration);
}
oldTasksCount = tasksCount;
}
Timer {
id: reorderTimer
interval: 5000
onTriggered: tasksModel.taskReorderingEnabled = true
}
//BEGIN functions
function show(animation) {
// reset values
taskSwitcherState.cancelAnimations();
taskSwitcherState.yPosition = 0;
taskSwitcherState.xPosition = 0;
taskSwitcherState.wasInActiveTask = tasksModel.activeTask.row >= 0;
taskSwitcherState.currentlyBeingOpened = true;
reorderTimer.stop();
tasksModel.taskReorderingEnabled = false;
// skip to first active task
if (taskSwitcherState.wasInActiveTask) {
taskSwitcherState.goToTaskIndex(tasksModel.activeTask.row);
} else {
taskSwitcherState.goToTaskIndex(0);
}
// show task switcher, hide all running apps
visible = true;
opacity = 1;
minimizeAll();
// fully open the panel (if this is a button press, not gesture)
if (animation) {
taskSwitcherState.open();
}
}
function instantHide() {
opacity = 0;
visible = false;
closeAllButton.closeRequested = false;
}
function hide() {
closeAnim.restart();
}
// scroll to delegate index, and activate it
function activateWindow(id) {
taskSwitcherState.openApp(id);
}
function setSingleActiveWindow(id) {
if (id < 0) {
return;
}
var newActiveIdx = tasksModel.index(id, 0)
var newActiveGeo = tasksModel.data(newActiveIdx, TaskManager.AbstractTasksModel.ScreenGeometry)
for (var i = 0 ; i < tasksModel.count; i++) {
var idx = tasksModel.index(i, 0)
if (i == id) {
tasksModel.requestActivate(idx);
// ensure the window is in maximized state
if (!tasksModel.data(idx, TaskManager.AbstractTasksModel.IsMaximized)) {
tasksModel.requestToggleMaximized(idx);
}
} else if (!tasksModel.data(idx, TaskManager.AbstractTasksModel.IsMinimized)) {
var geo = tasksModel.data(idx, TaskManager.AbstractTasksModel.ScreenGeometry)
// only minimize the other windows in the same screen
if (geo === newActiveGeo) {
tasksModel.requestToggleMinimized(idx);
}
}
}
instantHide();
if (taskSwitcherState.wasInActiveTask) {
reorderTimer.restart();
} else {
tasksModel.taskReorderingEnabled = true;
}
}
function minimizeAll() {
MobileShell.WindowUtil.unsetAllMinimizedGeometries(root);
MobileShell.WindowUtil.minimizeAll();
}
//END functions
NumberAnimation on opacity {
id: closeAnim
to: 0
duration: PlasmaCore.Units.shortDuration
easing.type: Easing.InOutQuad
onFinished: {
root.visible = false;
tasksModel.taskReorderingEnabled = true;
closeAllButton.closeRequested = false;
}
}
// background colour
Rectangle {
id: backgroundRect
anchors.fill: parent
color: {
// animate background colour only if we are *not* opening from the homescreen
if (taskSwitcherState.wasInActiveTask || !taskSwitcherState.currentlyBeingOpened) {
return Qt.rgba(0, 0, 0, 0.6);
} else {
return Qt.rgba(0, 0, 0, 0.6 * Math.min(1, taskSwitcherState.yPosition / taskSwitcherState.openedYPosition));
}
}
}
Item {
id: container
// provide shell margins
anchors.fill: parent
anchors.leftMargin: root.leftMargin
anchors.rightMargin: root.rightMargin
anchors.bottomMargin: root.bottomMargin
anchors.topMargin: root.topMargin
FlickContainer {
id: flickable
anchors.fill: parent
taskSwitcherState: root.taskSwitcherState
// the item is effectively anchored to the flickable bounds
TaskList {
id: taskList
shellTopMargin: root.topMargin
shellBottomMargin: root.bottomMargin
taskSwitcher: root
opacity: {
// animate opacity only if we are *not* opening from the homescreen
if (taskSwitcherState.wasInActiveTask || !taskSwitcherState.currentlyBeingOpened) {
return 1;
} else {
Math.min(1, taskSwitcherState.yPosition / taskSwitcherState.openedYPosition);
}
}
x: flickable.contentX
width: flickable.width
height: flickable.height
PlasmaComponents.ToolButton {
id: closeAllButton
property bool closeRequested: false
anchors {
bottom: parent.bottom
bottomMargin: taskList.taskY / 2
horizontalCenter: parent.horizontalCenter
}
PlasmaCore.ColorScope.colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
PlasmaCore.ColorScope.inherit: false
opacity: taskSwitcherState.currentlyBeingOpened || taskSwitcherState.currentlyBeingClosed || !root.visible ? 0.0 : 1.0
Behavior on opacity {
NumberAnimation {
duration: PlasmaCore.Units.shortDuration
}
}
icon.name: "edit-clear-history"
font.bold: true
text: closeRequested ? "Confirm Close All" : "Close All"
onClicked: {
if (closeRequested) {
taskList.closeAll();
} else {
closeRequested = true;
}
}
}
}
}
}
}

View file

@ -1,33 +0,0 @@
/*
* SPDX-FileCopyrightText: 2020 Marco Martin <notmart@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.0
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
import org.kde.pipewire 0.1 as PipeWire
import org.kde.taskmanager 0.1 as TaskManager
PipeWire.PipeWireSourceItem {
id: root
visible: nodeId > 0
nodeId: waylandItem.nodeId
readonly property alias uuid: waylandItem.uuid
function refresh() {
if (model.WinIdList) {
waylandItem.uuid = model.WinIdList[0];
}
}
TaskManager.ScreencastingRequest {
id: waylandItem
uuid: ""
}
}

View file

@ -63,14 +63,6 @@
<file>qml/statusbar/StatusBar.qml</file>
<file>qml/statusbar/TaskWidget.qml</file>
<file>qml/taskswitcher/FlickContainer.qml</file>
<file>qml/taskswitcher/Task.qml</file>
<file>qml/taskswitcher/TaskIcon.qml</file>
<file>qml/taskswitcher/TaskList.qml</file>
<file>qml/taskswitcher/TaskSwitcher.qml</file>
<file>qml/taskswitcher/TaskSwitcherState.qml</file>
<file>qml/taskswitcher/Thumbnail.qml</file>
<file>qml/widgets/krunner/KRunnerWidget.qml</file>
<file>qml/widgets/mediacontrols/BlurredBackground.qml</file>

View file

@ -12,11 +12,6 @@ pragma Singleton
QtObject {
id: delegate
/**
* Whether the task switcher is open.
*/
readonly property bool taskSwitcherVisible: HomeScreenControls.taskSwitcherVisible
/**
* Whether the homescreen is currently visible.
*/

View file

@ -52,11 +52,7 @@ ContainmentLayoutManager.ItemContainer {
return;
}
if (!MobileShellState.Shell.taskSwitcherVisible) {
desktopModel.setMinimizedDelegate(index, delegate);
} else {
desktopModel.unsetMinimizedDelegate(index, delegate);
}
}
function launchApp() {

View file

@ -37,11 +37,9 @@ Item {
}
function openConfigure() {
if (!MobileShellState.Shell.taskSwitcherVisible) {
plasmoid.action("configure").trigger();
plasmoid.editMode = false;
}
}
Connections {
target: MobileShellState.HomeScreenControls

View file

@ -48,7 +48,12 @@ MobileShell.HomeScreen {
if (!MobileShell.WindowUtil.showDesktop && !MobileShellState.Shell.homeScreenVisible
|| search.isOpen
) {
// Always close the search widget
// Always close action drawer
if (MobileShellState.Shell.actionDrawerVisible) {
MobileShellState.Shell.closeActionDrawer();
}
// Always close the search widget as well
if (search.isOpen) {
search.close();
}
@ -90,14 +95,6 @@ MobileShell.HomeScreen {
bottomMargin: root.bottomMargin
leftMargin: root.leftMargin
rightMargin: root.rightMargin
// close search component when task switcher is shown or hidden
Connections {
target: MobileShellState.HomeScreenControls.taskSwitcher
function onVisibleChanged() {
search.close();
}
}
}
}
}

View file

@ -1,8 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
// SPDX-FileCopyrightText: 2021-2023 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.4
import QtQuick.Layouts 1.1
@ -14,6 +11,7 @@ import org.kde.plasma.workspace.keyboardlayout 1.0 as Keyboards
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState
import org.kde.taskmanager 0.1 as TaskManager
MobileShell.NavigationPanel {
id: root
@ -23,19 +21,35 @@ MobileShell.NavigationPanel {
// - opaque if an app is shown or vkbd is shown
// - translucent if the task switcher is open
// - transparent if on the homescreen
backgroundColor: {
if (root.taskSwitcher.visible) {
return Qt.rgba(0, 0, 0, 0.1);
} else {
return (Keyboards.KWinVirtualKeyboard.visible || opaqueBar) ? PlasmaCore.ColorScope.backgroundColor : "transparent";
}
}
foregroundColorGroup: (!root.taskSwitcher.visible && opaqueBar) ? PlasmaCore.Theme.NormalColorGroup : PlasmaCore.Theme.ComplementaryColorGroup
backgroundColor: (Keyboards.KWinVirtualKeyboard.visible || opaqueBar) ? PlasmaCore.ColorScope.backgroundColor : "transparent";
foregroundColorGroup: opaqueBar ? PlasmaCore.Theme.NormalColorGroup : PlasmaCore.Theme.ComplementaryColorGroup
shadow: !opaqueBar
// do not enable drag gesture when task switcher is already open
// also don't disable drag gesture mid-drag
dragGestureEnabled: !root.taskSwitcher.visible || root.taskSwitcher.taskSwitcherState.currentlyBeingOpened
dragGestureEnabled: false // !root.taskSwitcher.visible || root.taskSwitcher.taskSwitcherState.currentlyBeingOpened
TaskManager.VirtualDesktopInfo {
id: virtualDesktopInfo
}
TaskManager.ActivityInfo {
id: activityInfo
}
TaskManager.TasksModel {
id: tasksModel
filterByVirtualDesktop: true
filterByActivity: true
filterNotMaximized: true
filterByScreen: true
filterHidden: true
virtualDesktop: virtualDesktopInfo.currentDesktop
activity: activityInfo.currentActivity
groupMode: TaskManager.TasksModel.GroupDisabled
}
// ~~~~
// navigation panel actions
@ -44,24 +58,12 @@ MobileShell.NavigationPanel {
leftAction: MobileShell.NavigationPanelAction {
id: taskSwitcherAction
enabled: (root.taskSwitcher.tasksCount > 0) || root.taskSwitcher.visible
enabled: true
iconSource: "mobile-task-switcher"
iconSizeFactor: 0.75
onTriggered: {
MobileShell.WindowUtil.showDesktop = false;
if (!root.taskSwitcher.visible) {
root.taskSwitcher.show(true);
} else {
// when task switcher is open
if (root.taskSwitcher.taskSwitcherState.wasInActiveTask) {
// restore active window
root.taskSwitcher.activateWindow(taskSwitcher.taskSwitcherState.currentTaskIndex);
} else {
root.taskSwitcher.hide();
}
}
plasmoid.nativeInterface.triggerTaskSwitcher();
}
}
@ -83,7 +85,7 @@ MobileShell.NavigationPanel {
rightAction: MobileShell.NavigationPanelAction {
id: closeAppAction
enabled: Keyboards.KWinVirtualKeyboard.visible || root.taskSwitcher.visible || MobileShell.WindowUtil.hasCloseableActiveWindow
enabled: Keyboards.KWinVirtualKeyboard.visible || MobileShell.WindowUtil.hasCloseableActiveWindow
iconSource: Keyboards.KWinVirtualKeyboard.visible ? "go-down-symbolic" : "mobile-close-app"
// mobile-close-app (from plasma-frameworks) seems to have less margins than icons from breeze-icons
iconSizeFactor: Keyboards.KWinVirtualKeyboard.visible ? 1 : 0.75
@ -92,15 +94,10 @@ MobileShell.NavigationPanel {
if (Keyboards.KWinVirtualKeyboard.active) {
// close keyboard if it is open
Keyboards.KWinVirtualKeyboard.active = false;
} else if (taskSwitcher.visible) {
// if task switcher is open, close the current window shown
let indexToClose = root.taskSwitcher.tasksModel.index(root.taskSwitcher.currentTaskIndex, 0);
root.taskSwitcher.tasksModel.requestClose(indexToClose);
} else if (MobileShell.WindowUtil.hasCloseableActiveWindow) {
// if task switcher is closed, but there is an active window
if (root.taskSwitcher.tasksModel.activeTask !== 0) {
root.taskSwitcher.tasksModel.requestClose(root.taskSwitcher.tasksModel.activeTask);
if (tasksModel.activeTask !== 0) {
tasksModel.requestClose(tasksModel.activeTask);
}
MobileShellState.Shell.closeAppLaunchAnimation();
}

View file

@ -142,7 +142,6 @@ PlasmaCore.ColorScope {
Component {
id: navigationPanel
NavigationPanelComponent {
taskSwitcher: MobileShellState.HomeScreenControls.taskSwitcher
opaqueBar: root.opaqueBar
}
}
@ -150,9 +149,7 @@ PlasmaCore.ColorScope {
// bottom navigation gesture area component
Component {
id: navigationGesture
MobileShell.NavigationGestureArea {
taskSwitcher: MobileShellState.HomeScreenControls.taskSwitcher
}
MobileShell.NavigationGestureArea {}
}
// load appropriate system navigation component

View file

@ -120,6 +120,15 @@ void TaskPanel::updatePanelVisibility()
}
}
void TaskPanel::triggerTaskSwitcher() const
{
QDBusMessage message = QDBusMessage::createMethodCall("org.kde.kglobalaccel", "/component/kwin", "org.kde.kglobalaccel.Component", "invokeShortcut");
message.setArguments({QStringLiteral("Mobile Task Switcher")});
// this does not block, so it won't necessarily be called before the method returns
QDBusConnection::sessionBus().send(message);
}
K_PLUGIN_CLASS_WITH_JSON(TaskPanel, "package/metadata.json")
#include "taskpanel.moc"

View file

@ -41,6 +41,8 @@ public:
QAbstractItemModel *outputs() const;
Q_INVOKABLE void triggerTaskSwitcher() const;
Q_SIGNALS:
void panelChanged();
void locationChanged();

View file

@ -2,3 +2,4 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
add_subdirectory(scripts)
add_subdirectory(mobiletaskswitcher)

View file

@ -0,0 +1,25 @@
# SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
# SPDX-License-Identifier: GPL-2.0-or-later
kcoreaddons_add_plugin(kwin4_effect_mobiletaskswitcher INSTALL_NAMESPACE "kwin/effects/plugins")
target_sources(kwin4_effect_mobiletaskswitcher PRIVATE
main.cpp
mobiletaskswitchereffect.cpp
)
install(FILES metadata.json DESTINATION ${KDE_INSTALL_DATADIR}/kwin/builtin-effects/kwin4_effect_mobiletaskswitcher/)
target_link_libraries(kwin4_effect_mobiletaskswitcher
KF6::ConfigGui
KF6::GlobalAccel
KF6::I18n
KF6::CoreAddons
KF6::WindowSystem
Qt::Quick
Qt::Core
KWinEffects::kwineffects
)
# install(TARGETS kwin4_effect_taskswitcher DESTINATION ${PLUGIN_INSTALL_DIR}/kwin/effects/plugins)
install(DIRECTORY qml DESTINATION ${KDE_INSTALL_DATADIR}/kwin/effects/mobiletaskswitcher)

View file

@ -0,0 +1,13 @@
// 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, "metadata.json", return MobileTaskSwitcherEffect::supported();)
} // namespace KWin
#include "main.moc"

View file

@ -0,0 +1,12 @@
{
"KPlugin": {
"Category": "Window Management",
"Description": "Allows you to switch between running tasks with a mobile interface.",
"EnabledByDefault": true,
"Id": "mobiletaskswitcher",
"License": "GPL",
"Name": "Mobile Task Switcher"
},
"X-KWin-Border-Activate": true
}

View file

@ -0,0 +1,252 @@
// SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "mobiletaskswitchereffect.h"
#include <QKeyEvent>
#include <QMetaObject>
#include <QQuickItem>
namespace KWin
{
MobileTaskSwitcherEffect::MobileTaskSwitcherEffect()
: m_shutdownTimer(new QTimer(this))
{
m_shutdownTimer->setSingleShot(true);
connect(m_shutdownTimer, &QTimer::timeout, this, &MobileTaskSwitcherEffect::realDeactivate);
const QKeySequence defaultToggleShortcut = Qt::META | Qt::Key_C;
m_toggleAction = new QAction(this);
m_toggleAction->setObjectName(QStringLiteral("Mobile Task Switcher"));
m_toggleAction->setText(i18n("Toggle Mobile Task Switcher"));
connect(m_toggleAction, &QAction::triggered, this, &MobileTaskSwitcherEffect::toggle);
KGlobalAccel::self()->setDefaultShortcut(m_toggleAction, {defaultToggleShortcut});
KGlobalAccel::self()->setShortcut(m_toggleAction, {defaultToggleShortcut});
m_realtimeToggleAction = new QAction(this);
connect(m_realtimeToggleAction, &QAction::triggered, this, [this]() {
if (m_status == Status::Deactivating) {
if (m_partialActivationFactor < 0.5) {
deactivate(false);
} else {
cancelPartialDeactivate();
}
} else if (m_status == Status::Activating) {
if (m_partialActivationFactor > 0.5) {
activate();
} else {
cancelPartialActivate();
}
}
});
auto progressCallback = [this](qreal progress) {
if (!effects->hasActiveFullScreenEffect() || effects->activeFullScreenEffect() == this) {
switch (m_status) {
case Status::Inactive:
case Status::Activating:
partialActivate(progress);
break;
case Status::Active:
case Status::Deactivating:
partialDeactivate(progress);
break;
}
}
};
effects->registerTouchpadPinchShortcut(PinchDirection::Contracting, 4, m_realtimeToggleAction, progressCallback);
effects->registerTouchscreenSwipeShortcut(SwipeDirection::Up, 3, m_realtimeToggleAction, progressCallback);
connect(effects, &EffectsHandler::screenAboutToLock, this, &MobileTaskSwitcherEffect::realDeactivate);
setSource(QUrl::fromLocalFile(
QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kwin/effects/mobiletaskswitcher/qml/TaskSwitcher.qml"))));
}
MobileTaskSwitcherEffect::~MobileTaskSwitcherEffect()
{
}
void MobileTaskSwitcherEffect::reconfigure(ReconfigureFlags)
{
setAnimationDuration(animationTime(300));
for (const ElectricBorder &border : std::as_const(m_borderActivate)) {
effects->unreserveElectricBorder(border, this);
}
for (const ElectricBorder &border : std::as_const(m_touchBorderActivate)) {
effects->unregisterTouchBorder(border, m_toggleAction);
}
m_borderActivate.clear();
m_touchBorderActivate.clear();
const QList<int> activateBorders = {ElectricBorder::ElectricBottom};
for (const int &border : activateBorders) {
m_borderActivate.append(ElectricBorder(border));
effects->reserveElectricBorder(ElectricBorder(border), this);
}
const QList<int> touchActivateBorders = {ElectricBorder::ElectricBottom};
for (const int &border : touchActivateBorders) {
m_touchBorderActivate.append(ElectricBorder(border));
effects->registerRealtimeTouchBorder(ElectricBorder(border),
m_realtimeToggleAction,
[this](ElectricBorder border, const QPointF &deltaProgress, const EffectScreen *screen) {
if (m_status == Status::Active) {
return;
}
const int maxDelta = 500; // Arbitrary logical pixels value seems to behave better than scaledScreenSize
if (border == ElectricTop || border == ElectricBottom) {
partialActivate(std::min(1.0, std::abs(deltaProgress.y()) / maxDelta));
} else {
partialActivate(std::min(1.0, std::abs(deltaProgress.x()) / maxDelta));
}
});
}
}
int MobileTaskSwitcherEffect::requestedEffectChainPosition() const
{
return 70;
}
bool MobileTaskSwitcherEffect::borderActivated(ElectricBorder border)
{
return false;
}
void MobileTaskSwitcherEffect::grabbedKeyboardEvent(QKeyEvent *keyEvent)
{
if (m_toggleShortcut.contains(keyEvent->key() | keyEvent->modifiers())) {
if (keyEvent->type() == QEvent::KeyPress) {
toggle();
}
return;
}
QuickSceneEffect::grabbedKeyboardEvent(keyEvent);
}
void MobileTaskSwitcherEffect::toggle()
{
if (!isRunning()) {
activate();
} else {
deactivate(false);
}
}
void MobileTaskSwitcherEffect::activate()
{
if (effects->isScreenLocked()) {
return;
}
m_status = Status::Active;
setRunning(true);
}
void MobileTaskSwitcherEffect::deactivate(bool deactivateInstantly)
{
const auto screens = effects->screens();
for (const auto screen : screens) {
if (QuickSceneView *view = viewForScreen(screen)) {
QMetaObject::invokeMethod(view->rootItem(), "hideAnimation");
}
}
m_shutdownTimer->start(animationTime(deactivateInstantly ? 0 : 200));
setGestureInProgress(false);
setPartialActivationFactor(0.0);
}
void MobileTaskSwitcherEffect::partialActivate(qreal factor)
{
if (effects->isScreenLocked()) {
return;
}
m_status = Status::Activating;
setPartialActivationFactor(factor);
setGestureInProgress(true);
setRunning(true);
}
void MobileTaskSwitcherEffect::partialDeactivate(qreal factor)
{
m_status = Status::Deactivating;
setPartialActivationFactor(1.0 - factor);
setGestureInProgress(true);
}
void MobileTaskSwitcherEffect::cancelPartialDeactivate()
{
activate();
}
void MobileTaskSwitcherEffect::cancelPartialActivate()
{
deactivate(false);
}
void MobileTaskSwitcherEffect::realDeactivate()
{
setRunning(false);
m_status = Status::Inactive;
}
void MobileTaskSwitcherEffect::quickDeactivate()
{
m_shutdownTimer->start(0);
}
int MobileTaskSwitcherEffect::animationDuration() const
{
return m_animationDuration;
}
void MobileTaskSwitcherEffect::setAnimationDuration(int duration)
{
if (m_animationDuration != duration) {
m_animationDuration = duration;
Q_EMIT animationDurationChanged();
}
}
bool MobileTaskSwitcherEffect::gestureInProgress() const
{
return m_gestureInProgress;
}
void MobileTaskSwitcherEffect::setGestureInProgress(bool gesture)
{
if (m_gestureInProgress != gesture) {
m_gestureInProgress = gesture;
Q_EMIT gestureInProgressChanged();
}
}
qreal MobileTaskSwitcherEffect::partialActivationFactor() const
{
return m_partialActivationFactor;
}
void MobileTaskSwitcherEffect::setPartialActivationFactor(qreal factor)
{
if (m_partialActivationFactor != factor) {
qDebug() << factor;
m_partialActivationFactor = factor;
Q_EMIT partialActivationFactorChanged();
}
}
}

View file

@ -0,0 +1,77 @@
// SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <kwinquickeffect.h>
#include <span>
#include <QAction>
#include <QKeySequence>
#include <QTimer>
#include <KGlobalAccel>
#include <KLocalizedString>
namespace KWin
{
class MobileTaskSwitcherEffect : public QuickSceneEffect
{
Q_OBJECT
Q_PROPERTY(qreal partialActivationFactor READ partialActivationFactor NOTIFY partialActivationFactorChanged)
Q_PROPERTY(bool gestureInProgress READ gestureInProgress NOTIFY gestureInProgressChanged)
public:
enum class Status { Inactive, Activating, Deactivating, Active };
MobileTaskSwitcherEffect();
~MobileTaskSwitcherEffect() override;
int animationDuration() const;
void setAnimationDuration(int duration);
bool gestureInProgress() const;
void setGestureInProgress(bool gesture);
qreal partialActivationFactor() const;
void setPartialActivationFactor(qreal factor);
int requestedEffectChainPosition() const override;
bool borderActivated(ElectricBorder border) override;
void reconfigure(ReconfigureFlags flags) override;
void grabbedKeyboardEvent(QKeyEvent *keyEvent) override;
public Q_SLOTS:
void activate();
void realDeactivate();
void partialActivate(qreal factor);
void cancelPartialActivate();
void partialDeactivate(qreal factor);
void cancelPartialDeactivate();
void deactivate(bool deactivateInstantly);
void quickDeactivate();
void toggle();
Q_SIGNALS:
void animationDurationChanged();
void gestureInProgressChanged();
void partialActivationFactorChanged();
private:
QAction *m_realtimeToggleAction = nullptr;
QAction *m_toggleAction = nullptr;
QList<QKeySequence> m_toggleShortcut;
Status m_status = Status::Inactive;
;
QTimer *m_shutdownTimer;
QList<ElectricBorder> m_borderActivate;
QList<ElectricBorder> m_touchBorderActivate;
int m_animationDuration = 400;
qreal m_partialActivationFactor = 0;
bool m_gestureInProgress = false;
};
} // namespace KWin

View file

@ -1,8 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
// SPDX-FileCopyrightText: 2021-2023 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Layouts 1.15
@ -10,8 +7,6 @@ import QtQuick.Layouts 1.15
import org.kde.taskmanager 0.1 as TaskManager
import org.kde.plasma.core 2.1 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.private.nanoshell 2.0 as NanoShell
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
Flickable {
id: root

View file

@ -1,31 +1,30 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <notmart@gmail.com>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
// SPDX-FileCopyrightText: 2015 Marco Martin <notmart@gmail.com>
// SPDX-FileCopyrightText: 2021-2023 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
import QtQuick.Controls 2.2 as QQC2
import Qt5Compat.GraphicalEffects
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.kwin 3.0 as KWinComponents
Item {
id: delegate
required property var taskSwitcher
required property QtObject window
required property int index
required property var model
required property var displaysModel
required property real previewHeight
required property real previewWidth
readonly property point taskScreenPoint: (model && model.ScreenGeometry) ? Qt.point(model.ScreenGeometry.x, model.ScreenGeometry.y) : Qt.point(0, 0)
readonly property real dragOffset: -control.y
property bool showHeader: true
@ -34,32 +33,21 @@ Item {
opacity: 1 - dragOffset / taskSwitcher.height
//BEGIN functions
function syncDelegateGeometry() {
let pos = pipeWireLoader.mapToItem(delegate, 0, 0);
if (taskSwitcher.visible) {
tasksModel.requestPublishDelegateGeometry(tasksModel.index(model.index, 0), Qt.rect(pos.x, pos.y, pipeWireLoader.width, pipeWireLoader.height), pipeWireLoader);
}
}
function closeApp() {
tasksModel.requestClose(tasksModel.index(model.index, 0));
delegate.window.closeWindow();
}
function activateApp() {
taskSwitcherState.wasInActiveTask = false;
taskSwitcher.activateWindow(model.index);
taskSwitcher.activateWindow(model.index, delegate.window);
window.setMaximize(true, true);
}
function minimizeApp() {
delegate.window.minimized = true;
}
//END functions
Component.onCompleted: syncDelegateGeometry();
Connections {
target: taskSwitcher
function onVisibleChanged() {
syncDelegateGeometry();
}
}
MouseArea {
id: control
width: parent.width
@ -149,33 +137,17 @@ Item {
Layout.preferredWidth: PlasmaCore.Units.iconSizes.smallMedium
Layout.alignment: Qt.AlignVCenter
usesPlasmaTheme: false
source: model.decoration
source: delegate.window.icon
}
PlasmaComponents.Label {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
elide: Text.ElideRight
text: model.AppName
text: delegate.window.caption
color: "white"
}
Repeater {
id: rep
model: displaysModel
delegate: PlasmaComponents.ToolButton {
Layout.alignment: Qt.AlignVCenter
text: model.modelName
visible: model.position !== delegate.taskScreenPoint
display: rep.count < 3 ? QQC2.Button.IconOnly : QQC2.Button.TextBesideIcon
icon.name: "tv" //TODO provide a more adequate icon
onClicked: {
displaysModel.sendWindowToOutput(delegate.model.WinIdList[0], model.output)
}
}
}
PlasmaComponents.ToolButton {
Layout.alignment: Qt.AlignVCenter
z: 99
@ -194,11 +166,11 @@ Item {
Layout.maximumWidth: delegate.previewWidth
Layout.maximumHeight: delegate.previewHeight
color: PlasmaCore.Theme.backgroundColor
color: "transparent"
clip: true
// scale animation on press
property real zoomScale: (MobileShell.MobileShellSettings.animationsEnabled && tapHandler.pressed) ? 0.9 : 1
property real zoomScale: tapHandler.pressed ? 0.9 : 1
Behavior on zoomScale {
NumberAnimation {
duration: 200
@ -217,30 +189,17 @@ Item {
id: item
anchors.fill: parent
// app icon (behind window preview in-case it doesn't load)
TaskIcon {
// decrease the opacity faster
opacity: pipeWireLoader.item && pipeWireLoader.item.uuid ? 0 : delegate.opacity
anchors.centerIn: parent
}
// attempt to load window preview
Loader {
id: pipeWireLoader
active: (taskSwitcher.visible || taskSwitcher.tasksModel.taskReorderingEnabled) && MobileShell.MobileShellSettings.taskSwitcherPreviewsEnabled
KWinComponents.WindowThumbnail {
id: thumbSource
wId: delegate.window.internalId
anchors.fill: parent
source: Qt.resolvedUrl("./Thumbnail.qml")
asynchronous: true
onLoaded: this.item.refresh()
layer {
enabled: true
effect: ColorOverlay {
color: Qt.rgba(0, 0, 0, delegate.darken)
}
}
// darken effect
Rectangle {
anchors.fill: parent
color: "black"
opacity: delegate.darken
}
TapHandler {
@ -254,3 +213,4 @@ Item {
}
}

View file

@ -1,8 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
// SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.12
import QtQuick.Layouts 1.1
@ -10,10 +7,13 @@ import QtQuick.Layouts 1.1
import org.kde.taskmanager 0.1 as TaskManager
import org.kde.plasma.core 2.1 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState
import org.kde.kwin 3.0 as KWinComponents
Item {
id: root
readonly property int count: repeater.count
required property real shellTopMargin
required property real shellBottomMargin
@ -28,7 +28,38 @@ Item {
let baseY = (taskSwitcher.height / 2) - (taskSwitcherState.taskHeight / 2) - (taskSwitcherState.taskHeaderHeight / 2)
return baseY + diff / 2 - MobileShellState.TopPanelControls.panelHeight;
return baseY + diff / 2;
}
function closeAll() {
for (var i = 0; i < repeater.count; i++) {
repeater.itemAt(i).closeApp();
}
}
function minimizeAll() {
for (var i = 0; i < repeater.count; i++) {
let item = repeater.itemAt(i);
// update property
if (!item.window.minimized) {
taskSwitcherState.wasInActiveTask = true;
}
// minimize window immediately if it shows up
item.minimizeApp();
}
}
function jumpToFirstVisibleWindow() {
for (var i = 0; i < repeater.count; i++) {
let item = repeater.itemAt(i);
if (!item.window.minimized) {
taskSwitcherState.goToTaskIndex(i);
break;
}
}
}
transform: Scale {
@ -38,12 +69,6 @@ Item {
yScale: taskSwitcherState.currentScale
}
function closeAll() {
for (var i = 0; i < repeater.count; i++) {
repeater.itemAt(i).closeApp();
}
}
// taphandler activates even if delegate touched
TapHandler {
enabled: !taskSwitcherState.currentlyBeingOpened
@ -54,6 +79,7 @@ Item {
taskSwitcher.hide();
}
}
onPressedChanged: {
if (pressed) {
// ensure animations aren't running when finger is pressed
@ -71,7 +97,6 @@ Item {
delegate: Task {
id: task
readonly property int currentIndex: model.index
// this is the x-position with respect to the list
@ -86,7 +111,6 @@ Item {
// this is the actual displayed x-position on screen
x: listX + repeater.leftMargin - taskSwitcherState.xPosition
y: root.taskY
// ensure current task is above others
@ -108,7 +132,6 @@ Item {
previewHeight: taskSwitcherState.previewHeight
taskSwitcher: root.taskSwitcher
displaysModel: root.taskSwitcher.displaysModel
}
}
}

View file

@ -0,0 +1,269 @@
// SPDX-FileCopyrightText: 2015 Marco Martin <notmart@gmail.com>
// SPDX-FileCopyrightText: 2021-2023 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.1
import QtQuick.Window 2.15
import Qt5Compat.GraphicalEffects
import org.kde.taskmanager 0.1 as TaskManager
import org.kde.plasma.core 2.1 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.kirigami 2.19 as Kirigami
import org.kde.plasma.extras 2.0 as PlasmaExtras
import org.kde.kwin 3.0 as KWinComponents
import org.kde.kwin.private.effects 1.0
/**
* Component that provides a task switcher.
*/
FocusScope {
id: root
focus: true
readonly property QtObject effect: KWinComponents.SceneView.effect
readonly property QtObject targetScreen: KWinComponents.SceneView.screen
readonly property real topMargin: 0
readonly property real bottomMargin: 0
readonly property real leftMargin: 0
readonly property real rightMargin: 0
property var taskSwitcherState: TaskSwitcherState {
taskSwitcher: root
}
KWinComponents.WindowModel {
id: stackModel
}
KWinComponents.VirtualDesktopModel {
id: desktopModel
}
property var tasksModel: KWinComponents.WindowFilterModel {
activity: KWinComponents.Workspace.currentActivity
desktop: KWinComponents.Workspace.currentDesktop
screenName: root.targetScreen.name
windowModel: stackModel
minimizedWindows: true
windowType: ~KWinComponents.WindowFilterModel.Dock &
~KWinComponents.WindowFilterModel.Desktop &
~KWinComponents.WindowFilterModel.Notification &
~KWinComponents.WindowFilterModel.CriticalNotification
}
readonly property int tasksCount: taskList.count
// keep track of task list events
property int oldTasksCount: tasksCount
onTasksCountChanged: {
if (tasksCount === 0 && oldTasksCount !== 0) {
hide();
} else if (tasksCount < oldTasksCount && taskSwitcherState.currentTaskIndex >= tasksCount - 1) {
// if the user is on the last task, and it is closed, scroll left
taskSwitcherState.animateGoToTaskIndex(tasksCount - 1, PlasmaCore.Units.longDuration);
}
oldTasksCount = tasksCount;
}
Keys.onEscapePressed: hide();
Component.onCompleted: {
taskList.jumpToFirstVisibleWindow();
taskList.minimizeAll();
taskSwitcherState.currentlyBeingOpened = true;
// fully open the panel (if this is a button press, not gesture)
if (!root.effect.gestureInProgress) {
taskSwitcherState.open();
}
}
// called by c++ plugin
function hideAnimation() {
closeAnim.restart();
}
function instantHide() {
root.effect.deactivate(true);
}
function hide() {
root.effect.deactivate(false);
}
// scroll to delegate index, and activate it
function activateWindow(index, window) {
KWinComponents.Workspace.activeClient = window;
taskSwitcherState.openApp(index, window);
}
function setSingleActiveWindow(id) {
instantHide();
}
Connections {
target: root.effect
function onPartialActivationFactorChanged() {
taskSwitcherState.positionY = taskSwitcherState.openedYPosition * root.effect.partialActivationFactor;
}
function onGestureInProgressChanged() {
if (!root.effect.gestureInProgress) {
taskSwitcherState.updateState();
}
}
}
KWinComponents.DesktopBackground {
id: backgroundItem
activity: KWinComponents.Workspace.currentActivity
desktop: KWinComponents.Workspace.currentDesktop
outputName: targetScreen.name
}
FastBlur {
source: backgroundItem
anchors.fill: parent
opacity: container.opacity
radius: 50
cached: true
}
// background colour
Rectangle {
id: backgroundRect
anchors.fill: parent
opacity: container.opacity
color: {
// animate background colour only if we are *not* opening from the homescreen
if (taskSwitcherState.wasInActiveTask || !taskSwitcherState.currentlyBeingOpened) {
return Qt.rgba(0, 0, 0, 0.6);
} else {
return Qt.rgba(0, 0, 0, 0.6 * Math.min(1, taskSwitcherState.yPosition / taskSwitcherState.openedYPosition));
}
}
}
Item {
id: container
// provide shell margins
anchors.fill: parent
anchors.leftMargin: root.leftMargin
anchors.rightMargin: root.rightMargin
anchors.bottomMargin: root.bottomMargin
anchors.topMargin: root.topMargin
NumberAnimation on opacity {
id: closeAnim
running: false
to: 0
duration: 200
easing.type: Easing.InOutQuad
onFinished: {
closeAllButton.closeRequested = false;
}
}
// placeholder message
ColumnLayout {
id: placeholder
spacing: PlasmaCore.Units.gridUnit
opacity: (root.tasksCount === 0 && !taskSwitcherState.currentlyBeingClosed) ? 0.9 : 0
Behavior on opacity { NumberAnimation { duration: 500 } }
anchors.centerIn: parent
Kirigami.Icon {
id: icon
Layout.alignment: Qt.AlignHCenter
implicitWidth: PlasmaCore.Units.iconSizes.large
implicitHeight: PlasmaCore.Units.iconSizes.large
source: "window"
color: "white"
}
PlasmaExtras.Heading {
Layout.fillWidth: true
Layout.maximumWidth: root.width * 0.75
Layout.alignment: Qt.AlignHCenter
color: "white"
level: 3
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter
text: i18n("No applications are running.")
}
}
// flicking area for task switcher
FlickContainer {
id: flickable
anchors.fill: parent
taskSwitcherState: root.taskSwitcherState
// the item is effectively anchored to the flickable bounds
TaskList {
id: taskList
taskSwitcher: root
shellTopMargin: root.topMargin
shellBottomMargin: root.bottomMargin
opacity: {
// animate opacity only if we are *not* opening from the homescreen
if (taskSwitcherState.wasInActiveTask || !taskSwitcherState.currentlyBeingOpened) {
return 1;
} else {
return Math.min(1, taskSwitcherState.yPosition / taskSwitcherState.openedYPosition);
}
}
x: flickable.contentX
width: flickable.width
height: flickable.height
PlasmaComponents.ToolButton {
id: closeAllButton
property bool closeRequested: false
visible: root.tasksCount !== 0
anchors {
bottom: parent.bottom
bottomMargin: taskList.taskY / 2
horizontalCenter: parent.horizontalCenter
}
PlasmaCore.ColorScope.colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
PlasmaCore.ColorScope.inherit: false
opacity: (taskSwitcherState.currentlyBeingOpened || taskSwitcherState.currentlyBeingClosed) ? 0.0 : 1.0
Behavior on opacity { NumberAnimation { duration: PlasmaCore.Units.shortDuration } }
icon.name: "edit-clear-history"
font.bold: true
text: closeRequested ? i18n("Confirm Close All") : i18n("Close All")
onClicked: {
if (closeRequested) {
taskList.closeAll();
} else {
closeRequested = true;
}
}
}
}
}
}
}

View file

@ -1,15 +1,11 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
// SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import org.kde.plasma.core 2.1 as PlasmaCore
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState
import org.kde.kwin 3.0 as KWinComponents
/**
* State object for the task switcher.
@ -139,9 +135,10 @@ QtObject {
closeAnim.restart();
}
function openApp(index) {
function openApp(index, window) {
animateGoToTaskIndex(index, PlasmaCore.Units.shortDuration);
openAppAnim.restart();
KWinComponents.Workspace.activeClient = window
}
// get the xPosition where the task will be centered on the screen
@ -208,7 +205,7 @@ QtObject {
target: root
property: "yPosition"
to: openedYPosition
duration: MobileShell.MobileShellSettings.animationsEnabled ? 300 : 0
duration: 300
easing.type: Easing.OutBack
onFinished: {
@ -220,10 +217,13 @@ QtObject {
target: root
property: "yPosition"
to: 0
duration: MobileShell.MobileShellSettings.animationsEnabled ? PlasmaCore.Units.longDuration : 0
duration: PlasmaCore.Units.longDuration
easing.type: Easing.InOutQuad
onStarted: root.currentlyBeingClosed = true
onFinished: {
root.currentlyBeingClosed = false;
root.currentlyBeingOpened = false;
scrollingTasks = false;
taskSwitcher.instantHide();
@ -238,7 +238,7 @@ QtObject {
target: root
property: "yPosition"
to: 0
duration: MobileShell.MobileShellSettings.animationsEnabled ? 300 : 0
duration: 300
easing.type: Easing.OutQuint
onStarted: root.currentlyBeingClosed = true