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(KF6_MIN_VERSION "5.240.0")
set(KDE_COMPILERSETTINGS_LEVEL "5.82") set(KDE_COMPILERSETTINGS_LEVEL "5.82")
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(ECM ${KF6_MIN_VERSION} REQUIRED NO_MODULE) 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 find_package(KF6 ${KF6_MIN_VERSION} REQUIRED COMPONENTS
I18n I18n
GlobalAccel
KIO KIO
Config Config
DBusAddons 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) pkg_check_modules(GIO gio-2.0 REQUIRED IMPORTED_TARGET)
find_package(KF6KirigamiAddons 0.6 REQUIRED) 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(LibKWorkspace CONFIG REQUIRED)
find_package(KWinDBusInterface) find_package(KWinDBusInterface)

View file

@ -95,9 +95,6 @@ void MobileShellPlugin::registerTypes(const char *uri)
// /statusbar // /statusbar
qmlRegisterType(resolvePath("statusbar/StatusBar.qml"), uri, 1, 0, "StatusBar"); qmlRegisterType(resolvePath("statusbar/StatusBar.qml"), uri, 1, 0, "StatusBar");
// /taskswitcher
qmlRegisterType(resolvePath("taskswitcher/TaskSwitcher.qml"), uri, 1, 0, "TaskSwitcher");
// /widgets // /widgets
qmlRegisterType(resolvePath("widgets/krunner/KRunnerWidget.qml"), uri, 1, 0, "KRunnerWidget"); qmlRegisterType(resolvePath("widgets/krunner/KRunnerWidget.qml"), uri, 1, 0, "KRunnerWidget");
qmlRegisterType(resolvePath("widgets/mediacontrols/MediaControlsWidget.qml"), uri, 1, 0, "MediaControlsWidget"); 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"; background.state = "open";
MobileShellState.HomeScreenControls.taskSwitcher.minimizeAll();
} }
function close() { function close() {

View file

@ -44,7 +44,7 @@ Item {
* Whether a component is being shown on top of the homescreen within the same * Whether a component is being shown on top of the homescreen within the same
* window. * window.
*/ */
readonly property bool overlayShown: taskSwitcher.visible || startupFeedback.visible readonly property bool overlayShown: startupFeedback.visible
/** /**
* Margins for the homescreen, taking panels into account. * Margins for the homescreen, taking panels into account.
@ -82,9 +82,10 @@ Item {
} }
MobileShellState.HomeScreenControls.resetHomeScreenPosition(); MobileShellState.HomeScreenControls.resetHomeScreenPosition();
taskSwitcher.visible = false; // will trigger homescreen open
taskSwitcher.minimizeAll(); MobileShell.WindowUtil.unsetAllMinimizedGeometries(root);
MobileShell.WindowUtil.minimizeAll();
root.homeTriggered(); root.homeTriggered();
} }
@ -109,7 +110,6 @@ Item {
Plasmoid.onScreenChanged: { Plasmoid.onScreenChanged: {
if (plasmoid.screen == 0) { if (plasmoid.screen == 0) {
MobileShellState.HomeScreenControls.taskSwitcher = taskSwitcher;
MobileShellState.HomeScreenControls.homeScreenWindow = root.Window.window; MobileShellState.HomeScreenControls.homeScreenWindow = root.Window.window;
} }
} }
@ -127,7 +127,6 @@ Item {
// set API variables // set API variables
if (plasmoid.screen == 0) { if (plasmoid.screen == 0) {
MobileShellState.HomeScreenControls.taskSwitcher = taskSwitcher;
MobileShellState.HomeScreenControls.homeScreenWindow = root.Window.window; MobileShellState.HomeScreenControls.homeScreenWindow = root.Window.window;
} }
} }
@ -203,12 +202,10 @@ Item {
function evaluateAnimChange() { function evaluateAnimChange() {
// only animate if homescreen is visible // only animate if homescreen is visible
if (!taskSwitcher.visible) { if (!visibleMaximizedWindowsModel.isWindowMaximized || MobileShell.WindowUtil.activeWindowIsShell) {
if (!visibleMaximizedWindowsModel.isWindowMaximized || MobileShell.WindowUtil.activeWindowIsShell) { itemContainer.zoomIn();
itemContainer.zoomIn(); } else {
} else { itemContainer.zoomOut();
itemContainer.zoomOut();
}
} }
} }
@ -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 // start app animation component
MobileShell.StartupFeedback { MobileShell.StartupFeedback {
id: 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/StatusBar.qml</file>
<file>qml/statusbar/TaskWidget.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/krunner/KRunnerWidget.qml</file>
<file>qml/widgets/mediacontrols/BlurredBackground.qml</file> <file>qml/widgets/mediacontrols/BlurredBackground.qml</file>

View file

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

View file

@ -14,7 +14,7 @@ import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PC3 import org.kde.plasma.components 3.0 as PC3
import org.kde.kquickcontrolsaddons 2.0 import org.kde.kquickcontrolsaddons 2.0
import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager
import org.kde.plasma.private.mobileshell 1.0 as MobileShell import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState
import org.kde.private.mobile.homescreen.folio 1.0 as Folio import org.kde.private.mobile.homescreen.folio 1.0 as Folio
@ -52,11 +52,7 @@ ContainmentLayoutManager.ItemContainer {
return; return;
} }
if (!MobileShellState.Shell.taskSwitcherVisible) { desktopModel.setMinimizedDelegate(index, delegate);
desktopModel.setMinimizedDelegate(index, delegate);
} else {
desktopModel.unsetMinimizedDelegate(index, delegate);
}
} }
function launchApp() { function launchApp() {

View file

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

View file

@ -48,7 +48,12 @@ MobileShell.HomeScreen {
if (!MobileShell.WindowUtil.showDesktop && !MobileShellState.Shell.homeScreenVisible if (!MobileShell.WindowUtil.showDesktop && !MobileShellState.Shell.homeScreenVisible
|| search.isOpen || 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) { if (search.isOpen) {
search.close(); search.close();
} }
@ -90,14 +95,6 @@ MobileShell.HomeScreen {
bottomMargin: root.bottomMargin bottomMargin: root.bottomMargin
leftMargin: root.leftMargin leftMargin: root.leftMargin
rightMargin: root.rightMargin 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-2023 Devin Lin <devin@kde.org>
* SPDX-FileCopyrightText: 2021-2022 Devin Lin <devin@kde.org> // SPDX-License-Identifier: GPL-2.0-or-later
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.4 import QtQuick 2.4
import QtQuick.Layouts 1.1 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 1.0 as MobileShell
import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState
import org.kde.taskmanager 0.1 as TaskManager
MobileShell.NavigationPanel { MobileShell.NavigationPanel {
id: root id: root
@ -23,20 +21,36 @@ MobileShell.NavigationPanel {
// - opaque if an app is shown or vkbd is shown // - opaque if an app is shown or vkbd is shown
// - translucent if the task switcher is open // - translucent if the task switcher is open
// - transparent if on the homescreen // - transparent if on the homescreen
backgroundColor: { backgroundColor: (Keyboards.KWinVirtualKeyboard.visible || opaqueBar) ? PlasmaCore.ColorScope.backgroundColor : "transparent";
if (root.taskSwitcher.visible) { foregroundColorGroup: opaqueBar ? PlasmaCore.Theme.NormalColorGroup : PlasmaCore.Theme.ComplementaryColorGroup
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
shadow: !opaqueBar shadow: !opaqueBar
// do not enable drag gesture when task switcher is already open // do not enable drag gesture when task switcher is already open
// also don't disable drag gesture mid-drag // 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 // navigation panel actions
@ -44,24 +58,12 @@ MobileShell.NavigationPanel {
leftAction: MobileShell.NavigationPanelAction { leftAction: MobileShell.NavigationPanelAction {
id: taskSwitcherAction id: taskSwitcherAction
enabled: (root.taskSwitcher.tasksCount > 0) || root.taskSwitcher.visible enabled: true
iconSource: "mobile-task-switcher" iconSource: "mobile-task-switcher"
iconSizeFactor: 0.75 iconSizeFactor: 0.75
onTriggered: { onTriggered: {
MobileShell.WindowUtil.showDesktop = false; plasmoid.nativeInterface.triggerTaskSwitcher();
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();
}
}
} }
} }
@ -83,7 +85,7 @@ MobileShell.NavigationPanel {
rightAction: MobileShell.NavigationPanelAction { rightAction: MobileShell.NavigationPanelAction {
id: closeAppAction 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" 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 // mobile-close-app (from plasma-frameworks) seems to have less margins than icons from breeze-icons
iconSizeFactor: Keyboards.KWinVirtualKeyboard.visible ? 1 : 0.75 iconSizeFactor: Keyboards.KWinVirtualKeyboard.visible ? 1 : 0.75
@ -92,15 +94,10 @@ MobileShell.NavigationPanel {
if (Keyboards.KWinVirtualKeyboard.active) { if (Keyboards.KWinVirtualKeyboard.active) {
// close keyboard if it is open // close keyboard if it is open
Keyboards.KWinVirtualKeyboard.active = false; 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) { } else if (MobileShell.WindowUtil.hasCloseableActiveWindow) {
// if task switcher is closed, but there is an active window // if task switcher is closed, but there is an active window
if (root.taskSwitcher.tasksModel.activeTask !== 0) { if (tasksModel.activeTask !== 0) {
root.taskSwitcher.tasksModel.requestClose(root.taskSwitcher.tasksModel.activeTask); tasksModel.requestClose(tasksModel.activeTask);
} }
MobileShellState.Shell.closeAppLaunchAnimation(); MobileShellState.Shell.closeAppLaunchAnimation();
} }

View file

@ -142,17 +142,14 @@ PlasmaCore.ColorScope {
Component { Component {
id: navigationPanel id: navigationPanel
NavigationPanelComponent { NavigationPanelComponent {
taskSwitcher: MobileShellState.HomeScreenControls.taskSwitcher
opaqueBar: root.opaqueBar opaqueBar: root.opaqueBar
} }
} }
// bottom navigation gesture area component // bottom navigation gesture area component
Component { Component {
id: navigationGesture id: navigationGesture
MobileShell.NavigationGestureArea { MobileShell.NavigationGestureArea {}
taskSwitcher: MobileShellState.HomeScreenControls.taskSwitcher
}
} }
// load appropriate system navigation component // 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") K_PLUGIN_CLASS_WITH_JSON(TaskPanel, "package/metadata.json")
#include "taskpanel.moc" #include "taskpanel.moc"

View file

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

View file

@ -2,3 +2,4 @@
# SPDX-License-Identifier: LGPL-2.1-or-later # SPDX-License-Identifier: LGPL-2.1-or-later
add_subdirectory(scripts) 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-2023 Devin Lin <devin@kde.org>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org> // SPDX-License-Identifier: LGPL-2.0-or-later
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
@ -10,34 +7,32 @@ import QtQuick.Layouts 1.15
import org.kde.taskmanager 0.1 as TaskManager import org.kde.taskmanager 0.1 as TaskManager
import org.kde.plasma.core 2.1 as PlasmaCore import org.kde.plasma.core 2.1 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents 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 { Flickable {
id: root id: root
required property var taskSwitcherState required property var taskSwitcherState
// we use flickable solely for capturing flicks, not positioning elements // we use flickable solely for capturing flicks, not positioning elements
contentWidth: width * tasksCount contentWidth: width * tasksCount
contentHeight: height contentHeight: height
contentX: startContentX contentX: startContentX
readonly property real startContentX: 0 readonly property real startContentX: 0
// update position from horizontal flickable movement // update position from horizontal flickable movement
property real oldContentX property real oldContentX
onContentXChanged: { onContentXChanged: {
taskSwitcherState.xPosition += contentX - oldContentX; taskSwitcherState.xPosition += contentX - oldContentX;
oldContentX = contentX; oldContentX = contentX;
} }
onMovementStarted: taskSwitcherState.cancelAnimations(); onMovementStarted: taskSwitcherState.cancelAnimations();
onMovementEnded: { onMovementEnded: {
resetPosition(); resetPosition();
taskSwitcherState.updateState(); taskSwitcherState.updateState();
} }
onFlickStarted: { onFlickStarted: {
root.cancelFlick(); root.cancelFlick();
} }
@ -45,7 +40,7 @@ Flickable {
resetPosition(); resetPosition();
taskSwitcherState.updateState(); taskSwitcherState.updateState();
} }
onDraggingChanged: { onDraggingChanged: {
if (!dragging) { if (!dragging) {
resetPosition(); resetPosition();
@ -54,7 +49,7 @@ Flickable {
taskSwitcherState.cancelAnimations(); taskSwitcherState.cancelAnimations();
} }
} }
function resetPosition() { function resetPosition() {
oldContentX = startContentX; oldContentX = startContentX;
contentX = startContentX; contentX = startContentX;

View file

@ -1,97 +1,85 @@
/* // SPDX-FileCopyrightText: 2015 Marco Martin <notmart@gmail.com>
* SPDX-FileCopyrightText: 2015 Marco Martin <notmart@gmail.com> // SPDX-FileCopyrightText: 2021-2023 Devin Lin <devin@kde.org>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org> // SPDX-License-Identifier: GPL-2.0-or-later
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Layouts 1.1 import QtQuick.Layouts 1.1
import QtQuick.Window 2.2 import QtQuick.Window 2.2
import QtQuick.Controls 2.2 as QQC2 import QtQuick.Controls 2.2 as QQC2
import Qt5Compat.GraphicalEffects
import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents 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 { Item {
id: delegate id: delegate
required property var taskSwitcher required property var taskSwitcher
required property QtObject window
required property int index
required property var model required property var model
required property var displaysModel
required property real previewHeight required property real previewHeight
required property real previewWidth 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 readonly property real dragOffset: -control.y
property bool showHeader: true property bool showHeader: true
property real darken: 0 property real darken: 0
opacity: 1 - dragOffset / taskSwitcher.height opacity: 1 - dragOffset / taskSwitcher.height
//BEGIN functions //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() { function closeApp() {
tasksModel.requestClose(tasksModel.index(model.index, 0)); delegate.window.closeWindow();
} }
function activateApp() { function activateApp() {
taskSwitcherState.wasInActiveTask = false; 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 //END functions
Component.onCompleted: syncDelegateGeometry();
Connections {
target: taskSwitcher
function onVisibleChanged() {
syncDelegateGeometry();
}
}
MouseArea { MouseArea {
id: control id: control
width: parent.width width: parent.width
height: parent.height height: parent.height
enabled: !taskSwitcher.taskSwitcherState.currentlyBeingOpened enabled: !taskSwitcher.taskSwitcherState.currentlyBeingOpened
// set cursor shape here, since taphandler seems to not be able to do it // set cursor shape here, since taphandler seems to not be able to do it
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
property bool movingUp: false property bool movingUp: false
property real oldY: y property real oldY: y
onYChanged: { onYChanged: {
movingUp = y < oldY; movingUp = y < oldY;
oldY = y; oldY = y;
} }
// drag up gesture // drag up gesture
DragHandler { DragHandler {
id: dragHandler id: dragHandler
target: parent target: parent
enabled: !taskSwitcher.taskSwitcherState.currentlyBeingOpened enabled: !taskSwitcher.taskSwitcherState.currentlyBeingOpened
yAxis.enabled: true yAxis.enabled: true
xAxis.enabled: false xAxis.enabled: false
yAxis.maximum: 0 yAxis.maximum: 0
// y > 0 - dragging down (opening the app) // y > 0 - dragging down (opening the app)
// y < 0 - dragging up (dismissing the app) // y < 0 - dragging up (dismissing the app)
onActiveChanged: { onActiveChanged: {
yAnimator.stop(); yAnimator.stop();
if (control.movingUp && parent.y < -PlasmaCore.Units.gridUnit * 2) { if (control.movingUp && parent.y < -PlasmaCore.Units.gridUnit * 2) {
yAnimator.to = -root.height; yAnimator.to = -root.height;
} else { } else {
@ -100,7 +88,7 @@ Item {
yAnimator.start(); yAnimator.start();
} }
} }
// if the app doesn't close within a certain time, drag it back // if the app doesn't close within a certain time, drag it back
Timer { Timer {
id: uncloseTimer id: uncloseTimer
@ -110,7 +98,7 @@ Item {
yAnimator.restart(); yAnimator.restart();
} }
} }
NumberAnimation on y { NumberAnimation on y {
id: yAnimator id: yAnimator
running: !dragHandler.active running: !dragHandler.active
@ -130,7 +118,7 @@ Item {
id: column id: column
anchors.fill: parent anchors.fill: parent
spacing: 0 spacing: 0
// header // header
RowLayout { RowLayout {
id: appHeader id: appHeader
@ -139,43 +127,27 @@ Item {
Layout.minimumHeight: column.height - appView.height Layout.minimumHeight: column.height - appView.height
spacing: PlasmaCore.Units.smallSpacing * 2 spacing: PlasmaCore.Units.smallSpacing * 2
opacity: delegate.showHeader ? 1 : 0 opacity: delegate.showHeader ? 1 : 0
Behavior on opacity { Behavior on opacity {
NumberAnimation { duration: PlasmaCore.Units.shortDuration } NumberAnimation { duration: PlasmaCore.Units.shortDuration }
} }
PlasmaCore.IconItem { PlasmaCore.IconItem {
Layout.preferredHeight: PlasmaCore.Units.iconSizes.smallMedium Layout.preferredHeight: PlasmaCore.Units.iconSizes.smallMedium
Layout.preferredWidth: PlasmaCore.Units.iconSizes.smallMedium Layout.preferredWidth: PlasmaCore.Units.iconSizes.smallMedium
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
usesPlasmaTheme: false usesPlasmaTheme: false
source: model.decoration source: delegate.window.icon
} }
PlasmaComponents.Label { PlasmaComponents.Label {
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
elide: Text.ElideRight elide: Text.ElideRight
text: model.AppName text: delegate.window.caption
color: "white" 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 { PlasmaComponents.ToolButton {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
z: 99 z: 99
@ -185,7 +157,7 @@ Item {
onClicked: delegate.closeApp() onClicked: delegate.closeApp()
} }
} }
// app preview // app preview
Rectangle { Rectangle {
id: appView id: appView
@ -193,56 +165,43 @@ Item {
Layout.preferredHeight: delegate.previewHeight Layout.preferredHeight: delegate.previewHeight
Layout.maximumWidth: delegate.previewWidth Layout.maximumWidth: delegate.previewWidth
Layout.maximumHeight: delegate.previewHeight Layout.maximumHeight: delegate.previewHeight
color: PlasmaCore.Theme.backgroundColor color: "transparent"
clip: true clip: true
// scale animation on press // 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 { Behavior on zoomScale {
NumberAnimation { NumberAnimation {
duration: 200 duration: 200
easing.type: Easing.OutExpo easing.type: Easing.OutExpo
} }
} }
transform: Scale { transform: Scale {
origin.x: appView.width / 2; origin.x: appView.width / 2;
origin.y: appView.height / 2; origin.y: appView.height / 2;
xScale: appView.zoomScale xScale: appView.zoomScale
yScale: appView.zoomScale yScale: appView.zoomScale
} }
Item { Item {
id: item id: item
anchors.fill: parent anchors.fill: parent
// app icon (behind window preview in-case it doesn't load) KWinComponents.WindowThumbnail {
TaskIcon { id: thumbSource
// decrease the opacity faster wId: delegate.window.internalId
opacity: pipeWireLoader.item && pipeWireLoader.item.uuid ? 0 : delegate.opacity anchors.fill: parent
anchors.centerIn: parent
layer {
enabled: true
effect: ColorOverlay {
color: Qt.rgba(0, 0, 0, delegate.darken)
}
}
} }
// attempt to load window preview
Loader {
id: pipeWireLoader
active: (taskSwitcher.visible || taskSwitcher.tasksModel.taskReorderingEnabled) && MobileShell.MobileShellSettings.taskSwitcherPreviewsEnabled
anchors.fill: parent
source: Qt.resolvedUrl("./Thumbnail.qml")
asynchronous: true
onLoaded: this.item.refresh()
}
// darken effect
Rectangle {
anchors.fill: parent
color: "black"
opacity: delegate.darken
}
TapHandler { TapHandler {
id: tapHandler id: tapHandler
enabled: !taskSwitcher.taskSwitcherState.currentlyBeingOpened enabled: !taskSwitcher.taskSwitcherState.currentlyBeingOpened
@ -254,3 +213,4 @@ Item {
} }
} }

View file

@ -1,8 +1,5 @@
/* // SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org> // SPDX-License-Identifier: GPL-2.0-or-later
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.12 import QtQuick 2.12
import QtQuick.Layouts 1.1 import QtQuick.Layouts 1.1
@ -10,50 +7,79 @@ import QtQuick.Layouts 1.1
import org.kde.taskmanager 0.1 as TaskManager import org.kde.taskmanager 0.1 as TaskManager
import org.kde.plasma.core 2.1 as PlasmaCore import org.kde.plasma.core 2.1 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents 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 { Item {
id: root id: root
readonly property int count: repeater.count
required property real shellTopMargin required property real shellTopMargin
required property real shellBottomMargin required property real shellBottomMargin
required property var taskSwitcher required property var taskSwitcher
readonly property var taskSwitcherState: taskSwitcher.taskSwitcherState readonly property var taskSwitcherState: taskSwitcher.taskSwitcherState
// account for system header and footer offset (center the preview image) // account for system header and footer offset (center the preview image)
readonly property real taskY: { readonly property real taskY: {
let headerHeight = shellTopMargin; let headerHeight = shellTopMargin;
let footerHeight = shellBottomMargin; let footerHeight = shellBottomMargin;
let diff = headerHeight - footerHeight; let diff = headerHeight - footerHeight;
let baseY = (taskSwitcher.height / 2) - (taskSwitcherState.taskHeight / 2) - (taskSwitcherState.taskHeaderHeight / 2) 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 { transform: Scale {
origin.x: root.width / 2 origin.x: root.width / 2
origin.y: root.height / 2 origin.y: root.height / 2
xScale: taskSwitcherState.currentScale xScale: taskSwitcherState.currentScale
yScale: taskSwitcherState.currentScale yScale: taskSwitcherState.currentScale
} }
function closeAll() {
for (var i = 0; i < repeater.count; i++) {
repeater.itemAt(i).closeApp();
}
}
// taphandler activates even if delegate touched // taphandler activates even if delegate touched
TapHandler { TapHandler {
enabled: !taskSwitcherState.currentlyBeingOpened enabled: !taskSwitcherState.currentlyBeingOpened
onTapped: { onTapped: {
// if tapped on the background, then hide // if tapped on the background, then hide
if (root.childAt(eventPoint.position.x, eventPoint.position.y) === null) { if (root.childAt(eventPoint.position.x, eventPoint.position.y) === null) {
taskSwitcher.hide(); taskSwitcher.hide();
} }
} }
onPressedChanged: { onPressedChanged: {
if (pressed) { if (pressed) {
// ensure animations aren't running when finger is pressed // ensure animations aren't running when finger is pressed
@ -61,54 +87,51 @@ Item {
} }
} }
} }
Repeater { Repeater {
id: repeater id: repeater
model: taskSwitcher.tasksModel model: taskSwitcher.tasksModel
// left margin from root edge such that the task is centered // left margin from root edge such that the task is centered
readonly property real leftMargin: (root.width / 2) - (taskSwitcherState.taskWidth / 2) readonly property real leftMargin: (root.width / 2) - (taskSwitcherState.taskWidth / 2)
delegate: Task { delegate: Task {
id: task id: task
readonly property int currentIndex: model.index readonly property int currentIndex: model.index
// this is the x-position with respect to the list // this is the x-position with respect to the list
property real listX: taskSwitcherState.xPositionFromTaskIndex(currentIndex); property real listX: taskSwitcherState.xPositionFromTaskIndex(currentIndex);
Behavior on listX { Behavior on listX {
NumberAnimation { NumberAnimation {
duration: PlasmaCore.Units.longDuration duration: PlasmaCore.Units.longDuration
easing.type: Easing.InOutQuad easing.type: Easing.InOutQuad
} }
} }
// this is the actual displayed x-position on screen // this is the actual displayed x-position on screen
x: listX + repeater.leftMargin - taskSwitcherState.xPosition x: listX + repeater.leftMargin - taskSwitcherState.xPosition
y: root.taskY y: root.taskY
// ensure current task is above others // ensure current task is above others
z: taskSwitcherState.currentTaskIndex === currentIndex ? 1 : 0 z: taskSwitcherState.currentTaskIndex === currentIndex ? 1 : 0
// only show header once task switcher is opened // only show header once task switcher is opened
showHeader: !taskSwitcherState.currentlyBeingOpened showHeader: !taskSwitcherState.currentlyBeingOpened
// darken effect as task gets away from the centre of the screen // darken effect as task gets away from the centre of the screen
darken: { darken: {
let distFromCentreProgress = Math.abs(x - repeater.leftMargin) / taskSwitcherState.taskWidth; let distFromCentreProgress = Math.abs(x - repeater.leftMargin) / taskSwitcherState.taskWidth;
let upperBoundAdjust = Math.min(0.5, distFromCentreProgress) - 0.2; let upperBoundAdjust = Math.min(0.5, distFromCentreProgress) - 0.2;
return Math.max(0, upperBoundAdjust); return Math.max(0, upperBoundAdjust);
} }
width: taskSwitcherState.taskWidth width: taskSwitcherState.taskWidth
height: taskSwitcherState.taskHeight height: taskSwitcherState.taskHeight
previewWidth: taskSwitcherState.previewWidth previewWidth: taskSwitcherState.previewWidth
previewHeight: taskSwitcherState.previewHeight previewHeight: taskSwitcherState.previewHeight
taskSwitcher: root.taskSwitcher 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,31 +1,27 @@
/* // SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org> // SPDX-License-Identifier: GPL-2.0-or-later
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.15 import QtQuick 2.15
import org.kde.plasma.core 2.1 as PlasmaCore import org.kde.plasma.core 2.1 as PlasmaCore
import org.kde.plasma.private.mobileshell 1.0 as MobileShell import org.kde.kwin 3.0 as KWinComponents
import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState
/** /**
* State object for the task switcher. * State object for the task switcher.
*/ */
QtObject { QtObject {
id: root id: root
// TaskSwitcher item component // TaskSwitcher item component
// We assume that the taskSwitcher the size of the entire screen. // We assume that the taskSwitcher the size of the entire screen.
required property var taskSwitcher required property var taskSwitcher
// ~~ positioning ~~ // ~~ positioning ~~
// Position of the list view: // Position of the list view:
// //
// xPosition: // xPosition:
// We start at 0, which is the position at which the first task in the task switcher is centered on the screen. // We start at 0, which is the position at which the first task in the task switcher is centered on the screen.
// Decreasing xPosition results in the task switcher moving forward (to the second task, third task, etc), being the layout direction Right to Left. // Decreasing xPosition results in the task switcher moving forward (to the second task, third task, etc), being the layout direction Right to Left.
@ -35,11 +31,11 @@ QtObject {
// Increasing yPosition results in the task switcher moving up (and thumbnails shrinking) // Increasing yPosition results in the task switcher moving up (and thumbnails shrinking)
property real xPosition: 0 property real xPosition: 0
property real yPosition: 0 property real yPosition: 0
// direction of the movement // direction of the movement
property bool movingRight: false property bool movingRight: false
property bool movingUp: false property bool movingUp: false
// used for calculating movement direction // used for calculating movement direction
property real oldXPosition: 0 property real oldXPosition: 0
property real oldYPosition: 0 property real oldYPosition: 0
@ -51,54 +47,54 @@ QtObject {
movingUp = yPosition > oldYPosition; movingUp = yPosition > oldYPosition;
oldYPosition = yPosition; oldYPosition = yPosition;
} }
// yPosition when the task switcher is completely open // yPosition when the task switcher is completely open
readonly property real openedYPosition: (taskSwitcher.height - taskHeight) / 2 readonly property real openedYPosition: (taskSwitcher.height - taskHeight) / 2
// ~~ active state ~~ // ~~ active state ~~
// whether the user was in an active task before the task switcher was opened // whether the user was in an active task before the task switcher was opened
property bool wasInActiveTask: false property bool wasInActiveTask: false
// whether we are in a swipe up gesture to open the task switcher // whether we are in a swipe up gesture to open the task switcher
property bool currentlyBeingOpened: false property bool currentlyBeingOpened: false
// whether the task switcher is being closed: an animation is running // whether the task switcher is being closed: an animation is running
property bool currentlyBeingClosed: false property bool currentlyBeingClosed: false
// whether we are in a swipe left/right gesture to walk through tasks // whether we are in a swipe left/right gesture to walk through tasks
property bool scrollingTasks: false property bool scrollingTasks: false
readonly property int currentTaskIndex: { readonly property int currentTaskIndex: {
let candidateIndex = Math.round(-xPosition / (taskSpacing + taskWidth)); let candidateIndex = Math.round(-xPosition / (taskSpacing + taskWidth));
return Math.max(0, Math.min(taskSwitcher.tasksCount - 1, candidateIndex)); return Math.max(0, Math.min(taskSwitcher.tasksCount - 1, candidateIndex));
} }
// ~~ measurement constants ~~ // ~~ measurement constants ~~
// dimensions of a real window on the screen // dimensions of a real window on the screen
readonly property real windowHeight: taskSwitcher.height - taskSwitcher.topMargin - taskSwitcher.bottomMargin readonly property real windowHeight: taskSwitcher.height - taskSwitcher.topMargin - taskSwitcher.bottomMargin
readonly property real windowWidth: taskSwitcher.width - taskSwitcher.leftMargin - taskSwitcher.rightMargin readonly property real windowWidth: taskSwitcher.width - taskSwitcher.leftMargin - taskSwitcher.rightMargin
// dimensions of the task previews // dimensions of the task previews
readonly property real previewHeight: windowHeight * scalingFactor readonly property real previewHeight: windowHeight * scalingFactor
readonly property real previewWidth: windowWidth * scalingFactor readonly property real previewWidth: windowWidth * scalingFactor
readonly property real taskHeight: previewHeight + taskHeaderHeight readonly property real taskHeight: previewHeight + taskHeaderHeight
readonly property real taskWidth: previewWidth readonly property real taskWidth: previewWidth
// spacing between each task preview // spacing between each task preview
readonly property real taskSpacing: PlasmaCore.Units.largeSpacing readonly property real taskSpacing: PlasmaCore.Units.largeSpacing
// height of the task preview header // height of the task preview header
readonly property real taskHeaderHeight: PlasmaCore.Units.gridUnit * 2 + PlasmaCore.Units.smallSpacing * 2 readonly property real taskHeaderHeight: PlasmaCore.Units.gridUnit * 2 + PlasmaCore.Units.smallSpacing * 2
// the scaling factor of the window preview compared to the actual window // the scaling factor of the window preview compared to the actual window
// we need to ensure that window previews always fit on screen // we need to ensure that window previews always fit on screen
readonly property real scalingFactor: { readonly property real scalingFactor: {
let candidateFactor = 0.6; let candidateFactor = 0.6;
let candidateTaskHeight = windowHeight * candidateFactor + taskHeaderHeight; let candidateTaskHeight = windowHeight * candidateFactor + taskHeaderHeight;
let candidateTaskWidth = windowWidth * candidateFactor; let candidateTaskWidth = windowWidth * candidateFactor;
let candidateHeight = (candidateTaskWidth / windowWidth) * windowHeight; let candidateHeight = (candidateTaskWidth / windowWidth) * windowHeight;
if (candidateHeight > windowHeight) { if (candidateHeight > windowHeight) {
return candidateTaskHeight / windowHeight; return candidateTaskHeight / windowHeight;
@ -106,22 +102,22 @@ QtObject {
return candidateTaskWidth / windowWidth; return candidateTaskWidth / windowWidth;
} }
} }
// scale of the task list (based on the progress of the swipe up gesture) // scale of the task list (based on the progress of the swipe up gesture)
readonly property real currentScale: { readonly property real currentScale: {
let maxScale = 1 / scalingFactor; let maxScale = 1 / scalingFactor;
let subtract = (maxScale - 1) * (yPosition / openedYPosition); let subtract = (maxScale - 1) * (yPosition / openedYPosition);
let finalScale = Math.max(0, Math.min(maxScale, maxScale - subtract)); let finalScale = Math.max(0, Math.min(maxScale, maxScale - subtract));
// animate scale only if we are *not* opening from the homescreen // animate scale only if we are *not* opening from the homescreen
if ((wasInActiveTask || !currentlyBeingOpened) && !scrollingTasks) { if ((wasInActiveTask || !currentlyBeingOpened) && !scrollingTasks) {
return finalScale; return finalScale;
} }
return scrollingTasks ? maxScale : 1; return scrollingTasks ? maxScale : 1;
} }
// ~~ signals and functions ~~ // ~~ signals and functions ~~
// cancel all animated moving, as another flick source is taking over // cancel all animated moving, as another flick source is taking over
signal cancelAnimations() signal cancelAnimations()
onCancelAnimations: { onCancelAnimations: {
@ -130,37 +126,38 @@ QtObject {
closeAnim.stop(); closeAnim.stop();
xAnim.stop(); xAnim.stop();
} }
function open() { function open() {
openAnim.restart(); openAnim.restart();
} }
function close() { function close() {
closeAnim.restart(); closeAnim.restart();
} }
function openApp(index) { function openApp(index, window) {
animateGoToTaskIndex(index, PlasmaCore.Units.shortDuration); animateGoToTaskIndex(index, PlasmaCore.Units.shortDuration);
openAppAnim.restart(); openAppAnim.restart();
KWinComponents.Workspace.activeClient = window
} }
// get the xPosition where the task will be centered on the screen // get the xPosition where the task will be centered on the screen
function xPositionFromTaskIndex(index) { function xPositionFromTaskIndex(index) {
return -index * (taskWidth + taskSpacing); return -index * (taskWidth + taskSpacing);
} }
// instantly go to the task index // instantly go to the task index
function goToTaskIndex(index) { function goToTaskIndex(index) {
xPosition = xPositionFromTaskIndex(index); xPosition = xPositionFromTaskIndex(index);
} }
// go to the task index, animated // go to the task index, animated
function animateGoToTaskIndex(index, duration) { function animateGoToTaskIndex(index, duration) {
xAnim.duration = duration; xAnim.duration = duration;
xAnim.to = xPositionFromTaskIndex(index); xAnim.to = xPositionFromTaskIndex(index);
xAnim.restart(); xAnim.restart();
} }
// called after a user finishes an interaction (ex. lets go of the screen) // called after a user finishes an interaction (ex. lets go of the screen)
function updateState() { function updateState() {
cancelAnimations(); cancelAnimations();
@ -173,7 +170,7 @@ QtObject {
// close task switcher and return to app // close task switcher and return to app
closeAnim.restart(); closeAnim.restart();
} }
// update horizontal state // update horizontal state
let duration = PlasmaCore.Units.longDuration * 2; let duration = PlasmaCore.Units.longDuration * 2;
if (currentlyBeingOpened) { if (currentlyBeingOpened) {
@ -195,35 +192,38 @@ QtObject {
} }
} }
} }
// ~~ property animators ~~ // ~~ property animators ~~
property var xAnim: NumberAnimation { property var xAnim: NumberAnimation {
target: root target: root
property: "xPosition" property: "xPosition"
easing.type: Easing.OutBack easing.type: Easing.OutBack
} }
property var openAnim: NumberAnimation { property var openAnim: NumberAnimation {
target: root target: root
property: "yPosition" property: "yPosition"
to: openedYPosition to: openedYPosition
duration: MobileShell.MobileShellSettings.animationsEnabled ? 300 : 0 duration: 300
easing.type: Easing.OutBack easing.type: Easing.OutBack
onFinished: { onFinished: {
root.currentlyBeingOpened = false; root.currentlyBeingOpened = false;
} }
} }
property var closeAnim: NumberAnimation { property var closeAnim: NumberAnimation {
target: root target: root
property: "yPosition" property: "yPosition"
to: 0 to: 0
duration: MobileShell.MobileShellSettings.animationsEnabled ? PlasmaCore.Units.longDuration : 0 duration: PlasmaCore.Units.longDuration
easing.type: Easing.InOutQuad easing.type: Easing.InOutQuad
onStarted: root.currentlyBeingClosed = true
onFinished: { onFinished: {
root.currentlyBeingClosed = false;
root.currentlyBeingOpened = false; root.currentlyBeingOpened = false;
scrollingTasks = false; scrollingTasks = false;
taskSwitcher.instantHide(); taskSwitcher.instantHide();
@ -233,16 +233,16 @@ QtObject {
} }
} }
} }
property var openAppAnim: NumberAnimation { property var openAppAnim: NumberAnimation {
target: root target: root
property: "yPosition" property: "yPosition"
to: 0 to: 0
duration: MobileShell.MobileShellSettings.animationsEnabled ? 300 : 0 duration: 300
easing.type: Easing.OutQuint easing.type: Easing.OutQuint
onStarted: root.currentlyBeingClosed = true onStarted: root.currentlyBeingClosed = true
onFinished: { onFinished: {
root.currentlyBeingClosed = false; root.currentlyBeingClosed = false;
root.currentlyBeingOpened = false; root.currentlyBeingOpened = false;