mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23:09 +00:00
taskswitcher: Port to kwin effect
This commit is contained in:
parent
f87c7c5526
commit
eb03fe8c94
27 changed files with 912 additions and 675 deletions
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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: ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
25
kwin/mobiletaskswitcher/CMakeLists.txt
Normal file
25
kwin/mobiletaskswitcher/CMakeLists.txt
Normal 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)
|
||||||
13
kwin/mobiletaskswitcher/main.cpp
Normal file
13
kwin/mobiletaskswitcher/main.cpp
Normal 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"
|
||||||
12
kwin/mobiletaskswitcher/metadata.json
Normal file
12
kwin/mobiletaskswitcher/metadata.json
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
252
kwin/mobiletaskswitcher/mobiletaskswitchereffect.cpp
Normal file
252
kwin/mobiletaskswitcher/mobiletaskswitchereffect.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
77
kwin/mobiletaskswitcher/mobiletaskswitchereffect.h
Normal file
77
kwin/mobiletaskswitcher/mobiletaskswitchereffect.h
Normal 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
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
269
kwin/mobiletaskswitcher/qml/TaskSwitcher.qml
Normal file
269
kwin/mobiletaskswitcher/qml/TaskSwitcher.qml
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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;
|
||||||
Loading…
Reference in a new issue