mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 06:13: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(KDE_COMPILERSETTINGS_LEVEL "5.82")
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
find_package(ECM ${KF6_MIN_VERSION} REQUIRED NO_MODULE)
|
||||
|
|
@ -53,6 +53,7 @@ find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} CONFIG REQUIRED
|
|||
|
||||
find_package(KF6 ${KF6_MIN_VERSION} REQUIRED COMPONENTS
|
||||
I18n
|
||||
GlobalAccel
|
||||
KIO
|
||||
Config
|
||||
DBusAddons
|
||||
|
|
@ -74,6 +75,12 @@ pkg_check_modules(GOBJECT gobject-2.0 REQUIRED IMPORTED_TARGET)
|
|||
pkg_check_modules(GIO gio-2.0 REQUIRED IMPORTED_TARGET)
|
||||
|
||||
find_package(KF6KirigamiAddons 0.6 REQUIRED)
|
||||
find_package(epoxy REQUIRED)
|
||||
find_package(XCB REQUIRED COMPONENTS XCB)
|
||||
find_package(KWinEffects 5.27.0 REQUIRED COMPONENTS
|
||||
kwineffects
|
||||
)
|
||||
|
||||
find_package(LibKWorkspace CONFIG REQUIRED)
|
||||
|
||||
find_package(KWinDBusInterface)
|
||||
|
|
|
|||
|
|
@ -95,9 +95,6 @@ void MobileShellPlugin::registerTypes(const char *uri)
|
|||
// /statusbar
|
||||
qmlRegisterType(resolvePath("statusbar/StatusBar.qml"), uri, 1, 0, "StatusBar");
|
||||
|
||||
// /taskswitcher
|
||||
qmlRegisterType(resolvePath("taskswitcher/TaskSwitcher.qml"), uri, 1, 0, "TaskSwitcher");
|
||||
|
||||
// /widgets
|
||||
qmlRegisterType(resolvePath("widgets/krunner/KRunnerWidget.qml"), uri, 1, 0, "KRunnerWidget");
|
||||
qmlRegisterType(resolvePath("widgets/mediacontrols/MediaControlsWidget.qml"), uri, 1, 0, "MediaControlsWidget");
|
||||
|
|
|
|||
|
|
@ -40,8 +40,6 @@ MouseArea { // use mousearea to ensure clicks don't go behind
|
|||
}
|
||||
|
||||
background.state = "open";
|
||||
|
||||
MobileShellState.HomeScreenControls.taskSwitcher.minimizeAll();
|
||||
}
|
||||
|
||||
function close() {
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ Item {
|
|||
* Whether a component is being shown on top of the homescreen within the same
|
||||
* window.
|
||||
*/
|
||||
readonly property bool overlayShown: taskSwitcher.visible || startupFeedback.visible
|
||||
readonly property bool overlayShown: startupFeedback.visible
|
||||
|
||||
/**
|
||||
* Margins for the homescreen, taking panels into account.
|
||||
|
|
@ -82,9 +82,10 @@ Item {
|
|||
}
|
||||
|
||||
MobileShellState.HomeScreenControls.resetHomeScreenPosition();
|
||||
taskSwitcher.visible = false; // will trigger homescreen open
|
||||
taskSwitcher.minimizeAll();
|
||||
|
||||
|
||||
MobileShell.WindowUtil.unsetAllMinimizedGeometries(root);
|
||||
MobileShell.WindowUtil.minimizeAll();
|
||||
|
||||
root.homeTriggered();
|
||||
}
|
||||
|
||||
|
|
@ -109,7 +110,6 @@ Item {
|
|||
|
||||
Plasmoid.onScreenChanged: {
|
||||
if (plasmoid.screen == 0) {
|
||||
MobileShellState.HomeScreenControls.taskSwitcher = taskSwitcher;
|
||||
MobileShellState.HomeScreenControls.homeScreenWindow = root.Window.window;
|
||||
}
|
||||
}
|
||||
|
|
@ -127,7 +127,6 @@ Item {
|
|||
|
||||
// set API variables
|
||||
if (plasmoid.screen == 0) {
|
||||
MobileShellState.HomeScreenControls.taskSwitcher = taskSwitcher;
|
||||
MobileShellState.HomeScreenControls.homeScreenWindow = root.Window.window;
|
||||
}
|
||||
}
|
||||
|
|
@ -203,12 +202,10 @@ Item {
|
|||
|
||||
function evaluateAnimChange() {
|
||||
// only animate if homescreen is visible
|
||||
if (!taskSwitcher.visible) {
|
||||
if (!visibleMaximizedWindowsModel.isWindowMaximized || MobileShell.WindowUtil.activeWindowIsShell) {
|
||||
itemContainer.zoomIn();
|
||||
} else {
|
||||
itemContainer.zoomOut();
|
||||
}
|
||||
if (!visibleMaximizedWindowsModel.isWindowMaximized || MobileShell.WindowUtil.activeWindowIsShell) {
|
||||
itemContainer.zoomIn();
|
||||
} else {
|
||||
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
|
||||
MobileShell.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/TaskWidget.qml</file>
|
||||
|
||||
<file>qml/taskswitcher/FlickContainer.qml</file>
|
||||
<file>qml/taskswitcher/Task.qml</file>
|
||||
<file>qml/taskswitcher/TaskIcon.qml</file>
|
||||
<file>qml/taskswitcher/TaskList.qml</file>
|
||||
<file>qml/taskswitcher/TaskSwitcher.qml</file>
|
||||
<file>qml/taskswitcher/TaskSwitcherState.qml</file>
|
||||
<file>qml/taskswitcher/Thumbnail.qml</file>
|
||||
|
||||
<file>qml/widgets/krunner/KRunnerWidget.qml</file>
|
||||
|
||||
<file>qml/widgets/mediacontrols/BlurredBackground.qml</file>
|
||||
|
|
|
|||
|
|
@ -12,11 +12,6 @@ pragma Singleton
|
|||
QtObject {
|
||||
id: delegate
|
||||
|
||||
/**
|
||||
* Whether the task switcher is open.
|
||||
*/
|
||||
readonly property bool taskSwitcherVisible: HomeScreenControls.taskSwitcherVisible
|
||||
|
||||
/**
|
||||
* Whether the homescreen is currently visible.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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.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.state 1.0 as MobileShellState
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
|
@ -52,11 +52,7 @@ ContainmentLayoutManager.ItemContainer {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!MobileShellState.Shell.taskSwitcherVisible) {
|
||||
desktopModel.setMinimizedDelegate(index, delegate);
|
||||
} else {
|
||||
desktopModel.unsetMinimizedDelegate(index, delegate);
|
||||
}
|
||||
desktopModel.setMinimizedDelegate(index, delegate);
|
||||
}
|
||||
|
||||
function launchApp() {
|
||||
|
|
|
|||
|
|
@ -37,10 +37,8 @@ Item {
|
|||
}
|
||||
|
||||
function openConfigure() {
|
||||
if (!MobileShellState.Shell.taskSwitcherVisible) {
|
||||
plasmoid.action("configure").trigger();
|
||||
plasmoid.editMode = false;
|
||||
}
|
||||
plasmoid.action("configure").trigger();
|
||||
plasmoid.editMode = false;
|
||||
}
|
||||
|
||||
Connections {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,12 @@ MobileShell.HomeScreen {
|
|||
if (!MobileShell.WindowUtil.showDesktop && !MobileShellState.Shell.homeScreenVisible
|
||||
|| search.isOpen
|
||||
) {
|
||||
// Always close the search widget
|
||||
// Always close action drawer
|
||||
if (MobileShellState.Shell.actionDrawerVisible) {
|
||||
MobileShellState.Shell.closeActionDrawer();
|
||||
}
|
||||
|
||||
// Always close the search widget as well
|
||||
if (search.isOpen) {
|
||||
search.close();
|
||||
}
|
||||
|
|
@ -90,14 +95,6 @@ MobileShell.HomeScreen {
|
|||
bottomMargin: root.bottomMargin
|
||||
leftMargin: root.leftMargin
|
||||
rightMargin: root.rightMargin
|
||||
|
||||
// close search component when task switcher is shown or hidden
|
||||
Connections {
|
||||
target: MobileShellState.HomeScreenControls.taskSwitcher
|
||||
function onVisibleChanged() {
|
||||
search.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2022 Devin Lin <devin@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
// SPDX-FileCopyrightText: 2021-2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.4
|
||||
import QtQuick.Layouts 1.1
|
||||
|
|
@ -14,6 +11,7 @@ import org.kde.plasma.workspace.keyboardlayout 1.0 as Keyboards
|
|||
|
||||
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
||||
import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState
|
||||
import org.kde.taskmanager 0.1 as TaskManager
|
||||
|
||||
MobileShell.NavigationPanel {
|
||||
id: root
|
||||
|
|
@ -23,20 +21,36 @@ MobileShell.NavigationPanel {
|
|||
// - opaque if an app is shown or vkbd is shown
|
||||
// - translucent if the task switcher is open
|
||||
// - transparent if on the homescreen
|
||||
backgroundColor: {
|
||||
if (root.taskSwitcher.visible) {
|
||||
return Qt.rgba(0, 0, 0, 0.1);
|
||||
} else {
|
||||
return (Keyboards.KWinVirtualKeyboard.visible || opaqueBar) ? PlasmaCore.ColorScope.backgroundColor : "transparent";
|
||||
}
|
||||
}
|
||||
foregroundColorGroup: (!root.taskSwitcher.visible && opaqueBar) ? PlasmaCore.Theme.NormalColorGroup : PlasmaCore.Theme.ComplementaryColorGroup
|
||||
backgroundColor: (Keyboards.KWinVirtualKeyboard.visible || opaqueBar) ? PlasmaCore.ColorScope.backgroundColor : "transparent";
|
||||
foregroundColorGroup: opaqueBar ? PlasmaCore.Theme.NormalColorGroup : PlasmaCore.Theme.ComplementaryColorGroup
|
||||
shadow: !opaqueBar
|
||||
|
||||
// do not enable drag gesture when task switcher is already open
|
||||
// also don't disable drag gesture mid-drag
|
||||
dragGestureEnabled: !root.taskSwitcher.visible || root.taskSwitcher.taskSwitcherState.currentlyBeingOpened
|
||||
dragGestureEnabled: false // !root.taskSwitcher.visible || root.taskSwitcher.taskSwitcherState.currentlyBeingOpened
|
||||
|
||||
TaskManager.VirtualDesktopInfo {
|
||||
id: virtualDesktopInfo
|
||||
}
|
||||
|
||||
TaskManager.ActivityInfo {
|
||||
id: activityInfo
|
||||
}
|
||||
|
||||
TaskManager.TasksModel {
|
||||
id: tasksModel
|
||||
filterByVirtualDesktop: true
|
||||
filterByActivity: true
|
||||
filterNotMaximized: true
|
||||
filterByScreen: true
|
||||
filterHidden: true
|
||||
|
||||
virtualDesktop: virtualDesktopInfo.currentDesktop
|
||||
activity: activityInfo.currentActivity
|
||||
|
||||
groupMode: TaskManager.TasksModel.GroupDisabled
|
||||
}
|
||||
|
||||
// ~~~~
|
||||
// navigation panel actions
|
||||
|
||||
|
|
@ -44,24 +58,12 @@ MobileShell.NavigationPanel {
|
|||
leftAction: MobileShell.NavigationPanelAction {
|
||||
id: taskSwitcherAction
|
||||
|
||||
enabled: (root.taskSwitcher.tasksCount > 0) || root.taskSwitcher.visible
|
||||
enabled: true
|
||||
iconSource: "mobile-task-switcher"
|
||||
iconSizeFactor: 0.75
|
||||
|
||||
onTriggered: {
|
||||
MobileShell.WindowUtil.showDesktop = false;
|
||||
|
||||
if (!root.taskSwitcher.visible) {
|
||||
root.taskSwitcher.show(true);
|
||||
} else {
|
||||
// when task switcher is open
|
||||
if (root.taskSwitcher.taskSwitcherState.wasInActiveTask) {
|
||||
// restore active window
|
||||
root.taskSwitcher.activateWindow(taskSwitcher.taskSwitcherState.currentTaskIndex);
|
||||
} else {
|
||||
root.taskSwitcher.hide();
|
||||
}
|
||||
}
|
||||
plasmoid.nativeInterface.triggerTaskSwitcher();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -83,7 +85,7 @@ MobileShell.NavigationPanel {
|
|||
rightAction: MobileShell.NavigationPanelAction {
|
||||
id: closeAppAction
|
||||
|
||||
enabled: Keyboards.KWinVirtualKeyboard.visible || root.taskSwitcher.visible || MobileShell.WindowUtil.hasCloseableActiveWindow
|
||||
enabled: Keyboards.KWinVirtualKeyboard.visible || MobileShell.WindowUtil.hasCloseableActiveWindow
|
||||
iconSource: Keyboards.KWinVirtualKeyboard.visible ? "go-down-symbolic" : "mobile-close-app"
|
||||
// mobile-close-app (from plasma-frameworks) seems to have less margins than icons from breeze-icons
|
||||
iconSizeFactor: Keyboards.KWinVirtualKeyboard.visible ? 1 : 0.75
|
||||
|
|
@ -92,15 +94,10 @@ MobileShell.NavigationPanel {
|
|||
if (Keyboards.KWinVirtualKeyboard.active) {
|
||||
// close keyboard if it is open
|
||||
Keyboards.KWinVirtualKeyboard.active = false;
|
||||
} else if (taskSwitcher.visible) {
|
||||
// if task switcher is open, close the current window shown
|
||||
let indexToClose = root.taskSwitcher.tasksModel.index(root.taskSwitcher.currentTaskIndex, 0);
|
||||
root.taskSwitcher.tasksModel.requestClose(indexToClose);
|
||||
|
||||
} else if (MobileShell.WindowUtil.hasCloseableActiveWindow) {
|
||||
// if task switcher is closed, but there is an active window
|
||||
if (root.taskSwitcher.tasksModel.activeTask !== 0) {
|
||||
root.taskSwitcher.tasksModel.requestClose(root.taskSwitcher.tasksModel.activeTask);
|
||||
if (tasksModel.activeTask !== 0) {
|
||||
tasksModel.requestClose(tasksModel.activeTask);
|
||||
}
|
||||
MobileShellState.Shell.closeAppLaunchAnimation();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,17 +142,14 @@ PlasmaCore.ColorScope {
|
|||
Component {
|
||||
id: navigationPanel
|
||||
NavigationPanelComponent {
|
||||
taskSwitcher: MobileShellState.HomeScreenControls.taskSwitcher
|
||||
opaqueBar: root.opaqueBar
|
||||
}
|
||||
}
|
||||
|
||||
// bottom navigation gesture area component
|
||||
Component {
|
||||
id: navigationGesture
|
||||
MobileShell.NavigationGestureArea {
|
||||
taskSwitcher: MobileShellState.HomeScreenControls.taskSwitcher
|
||||
}
|
||||
id: navigationGesture
|
||||
MobileShell.NavigationGestureArea {}
|
||||
}
|
||||
|
||||
// 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")
|
||||
|
||||
#include "taskpanel.moc"
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ public:
|
|||
|
||||
QAbstractItemModel *outputs() const;
|
||||
|
||||
Q_INVOKABLE void triggerTaskSwitcher() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void panelChanged();
|
||||
void locationChanged();
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
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 Devin Lin <devin@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
// SPDX-FileCopyrightText: 2021-2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
|
@ -10,34 +7,32 @@ import QtQuick.Layouts 1.15
|
|||
import org.kde.taskmanager 0.1 as TaskManager
|
||||
import org.kde.plasma.core 2.1 as PlasmaCore
|
||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||
import org.kde.plasma.private.nanoshell 2.0 as NanoShell
|
||||
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
||||
|
||||
Flickable {
|
||||
id: root
|
||||
|
||||
|
||||
required property var taskSwitcherState
|
||||
|
||||
|
||||
// we use flickable solely for capturing flicks, not positioning elements
|
||||
contentWidth: width * tasksCount
|
||||
contentHeight: height
|
||||
contentX: startContentX
|
||||
|
||||
|
||||
readonly property real startContentX: 0
|
||||
|
||||
|
||||
// update position from horizontal flickable movement
|
||||
property real oldContentX
|
||||
onContentXChanged: {
|
||||
taskSwitcherState.xPosition += contentX - oldContentX;
|
||||
oldContentX = contentX;
|
||||
}
|
||||
|
||||
|
||||
onMovementStarted: taskSwitcherState.cancelAnimations();
|
||||
onMovementEnded: {
|
||||
resetPosition();
|
||||
taskSwitcherState.updateState();
|
||||
}
|
||||
|
||||
|
||||
onFlickStarted: {
|
||||
root.cancelFlick();
|
||||
}
|
||||
|
|
@ -45,7 +40,7 @@ Flickable {
|
|||
resetPosition();
|
||||
taskSwitcherState.updateState();
|
||||
}
|
||||
|
||||
|
||||
onDraggingChanged: {
|
||||
if (!dragging) {
|
||||
resetPosition();
|
||||
|
|
@ -54,7 +49,7 @@ Flickable {
|
|||
taskSwitcherState.cancelAnimations();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function resetPosition() {
|
||||
oldContentX = startContentX;
|
||||
contentX = startContentX;
|
||||
|
|
@ -1,97 +1,85 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2015 Marco Martin <notmart@gmail.com>
|
||||
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
// SPDX-FileCopyrightText: 2015 Marco Martin <notmart@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2021-2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Controls 2.2 as QQC2
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import org.kde.plasma.core 2.0 as PlasmaCore
|
||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
||||
import org.kde.kwin 3.0 as KWinComponents
|
||||
|
||||
Item {
|
||||
id: delegate
|
||||
|
||||
required property var taskSwitcher
|
||||
|
||||
|
||||
required property QtObject window
|
||||
required property int index
|
||||
|
||||
required property var model
|
||||
required property var displaysModel
|
||||
|
||||
required property real previewHeight
|
||||
required property real previewWidth
|
||||
|
||||
readonly property point taskScreenPoint: (model && model.ScreenGeometry) ? Qt.point(model.ScreenGeometry.x, model.ScreenGeometry.y) : Qt.point(0, 0)
|
||||
|
||||
readonly property real dragOffset: -control.y
|
||||
|
||||
|
||||
property bool showHeader: true
|
||||
property real darken: 0
|
||||
|
||||
|
||||
opacity: 1 - dragOffset / taskSwitcher.height
|
||||
|
||||
|
||||
//BEGIN functions
|
||||
function syncDelegateGeometry() {
|
||||
let pos = pipeWireLoader.mapToItem(delegate, 0, 0);
|
||||
if (taskSwitcher.visible) {
|
||||
tasksModel.requestPublishDelegateGeometry(tasksModel.index(model.index, 0), Qt.rect(pos.x, pos.y, pipeWireLoader.width, pipeWireLoader.height), pipeWireLoader);
|
||||
}
|
||||
}
|
||||
|
||||
function closeApp() {
|
||||
tasksModel.requestClose(tasksModel.index(model.index, 0));
|
||||
delegate.window.closeWindow();
|
||||
}
|
||||
|
||||
|
||||
function activateApp() {
|
||||
taskSwitcherState.wasInActiveTask = false;
|
||||
taskSwitcher.activateWindow(model.index);
|
||||
taskSwitcher.activateWindow(model.index, delegate.window);
|
||||
window.setMaximize(true, true);
|
||||
}
|
||||
|
||||
function minimizeApp() {
|
||||
delegate.window.minimized = true;
|
||||
}
|
||||
//END functions
|
||||
|
||||
Component.onCompleted: syncDelegateGeometry();
|
||||
|
||||
Connections {
|
||||
target: taskSwitcher
|
||||
function onVisibleChanged() {
|
||||
syncDelegateGeometry();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
id: control
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
enabled: !taskSwitcher.taskSwitcherState.currentlyBeingOpened
|
||||
|
||||
|
||||
// set cursor shape here, since taphandler seems to not be able to do it
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
|
||||
property bool movingUp: false
|
||||
property real oldY: y
|
||||
onYChanged: {
|
||||
movingUp = y < oldY;
|
||||
oldY = y;
|
||||
}
|
||||
|
||||
|
||||
// drag up gesture
|
||||
DragHandler {
|
||||
id: dragHandler
|
||||
target: parent
|
||||
|
||||
|
||||
enabled: !taskSwitcher.taskSwitcherState.currentlyBeingOpened
|
||||
|
||||
|
||||
yAxis.enabled: true
|
||||
xAxis.enabled: false
|
||||
yAxis.maximum: 0
|
||||
|
||||
|
||||
// y > 0 - dragging down (opening the app)
|
||||
// y < 0 - dragging up (dismissing the app)
|
||||
onActiveChanged: {
|
||||
yAnimator.stop();
|
||||
|
||||
|
||||
if (control.movingUp && parent.y < -PlasmaCore.Units.gridUnit * 2) {
|
||||
yAnimator.to = -root.height;
|
||||
} else {
|
||||
|
|
@ -100,7 +88,7 @@ Item {
|
|||
yAnimator.start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if the app doesn't close within a certain time, drag it back
|
||||
Timer {
|
||||
id: uncloseTimer
|
||||
|
|
@ -110,7 +98,7 @@ Item {
|
|||
yAnimator.restart();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
NumberAnimation on y {
|
||||
id: yAnimator
|
||||
running: !dragHandler.active
|
||||
|
|
@ -130,7 +118,7 @@ Item {
|
|||
id: column
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
|
||||
// header
|
||||
RowLayout {
|
||||
id: appHeader
|
||||
|
|
@ -139,43 +127,27 @@ Item {
|
|||
Layout.minimumHeight: column.height - appView.height
|
||||
spacing: PlasmaCore.Units.smallSpacing * 2
|
||||
opacity: delegate.showHeader ? 1 : 0
|
||||
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: PlasmaCore.Units.shortDuration }
|
||||
}
|
||||
|
||||
|
||||
PlasmaCore.IconItem {
|
||||
Layout.preferredHeight: PlasmaCore.Units.iconSizes.smallMedium
|
||||
Layout.preferredWidth: PlasmaCore.Units.iconSizes.smallMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
usesPlasmaTheme: false
|
||||
source: model.decoration
|
||||
source: delegate.window.icon
|
||||
}
|
||||
|
||||
|
||||
PlasmaComponents.Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
text: model.AppName
|
||||
text: delegate.window.caption
|
||||
color: "white"
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: rep
|
||||
model: displaysModel
|
||||
delegate: PlasmaComponents.ToolButton {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: model.modelName
|
||||
visible: model.position !== delegate.taskScreenPoint
|
||||
display: rep.count < 3 ? QQC2.Button.IconOnly : QQC2.Button.TextBesideIcon
|
||||
icon.name: "tv" //TODO provide a more adequate icon
|
||||
|
||||
onClicked: {
|
||||
displaysModel.sendWindowToOutput(delegate.model.WinIdList[0], model.output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PlasmaComponents.ToolButton {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
z: 99
|
||||
|
|
@ -185,7 +157,7 @@ Item {
|
|||
onClicked: delegate.closeApp()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// app preview
|
||||
Rectangle {
|
||||
id: appView
|
||||
|
|
@ -193,56 +165,43 @@ Item {
|
|||
Layout.preferredHeight: delegate.previewHeight
|
||||
Layout.maximumWidth: delegate.previewWidth
|
||||
Layout.maximumHeight: delegate.previewHeight
|
||||
|
||||
color: PlasmaCore.Theme.backgroundColor
|
||||
|
||||
color: "transparent"
|
||||
clip: true
|
||||
|
||||
|
||||
// scale animation on press
|
||||
property real zoomScale: (MobileShell.MobileShellSettings.animationsEnabled && tapHandler.pressed) ? 0.9 : 1
|
||||
property real zoomScale: tapHandler.pressed ? 0.9 : 1
|
||||
Behavior on zoomScale {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.OutExpo
|
||||
}
|
||||
}
|
||||
|
||||
transform: Scale {
|
||||
origin.x: appView.width / 2;
|
||||
origin.y: appView.height / 2;
|
||||
|
||||
transform: Scale {
|
||||
origin.x: appView.width / 2;
|
||||
origin.y: appView.height / 2;
|
||||
xScale: appView.zoomScale
|
||||
yScale: appView.zoomScale
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
id: item
|
||||
anchors.fill: parent
|
||||
|
||||
// app icon (behind window preview in-case it doesn't load)
|
||||
TaskIcon {
|
||||
// decrease the opacity faster
|
||||
opacity: pipeWireLoader.item && pipeWireLoader.item.uuid ? 0 : delegate.opacity
|
||||
anchors.centerIn: parent
|
||||
|
||||
KWinComponents.WindowThumbnail {
|
||||
id: thumbSource
|
||||
wId: delegate.window.internalId
|
||||
anchors.fill: 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 {
|
||||
id: tapHandler
|
||||
enabled: !taskSwitcher.taskSwitcherState.currentlyBeingOpened
|
||||
|
|
@ -254,3 +213,4 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,8 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
// SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.1
|
||||
|
|
@ -10,50 +7,79 @@ import QtQuick.Layouts 1.1
|
|||
import org.kde.taskmanager 0.1 as TaskManager
|
||||
import org.kde.plasma.core 2.1 as PlasmaCore
|
||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||
import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState
|
||||
|
||||
import org.kde.kwin 3.0 as KWinComponents
|
||||
|
||||
Item {
|
||||
id: root
|
||||
readonly property int count: repeater.count
|
||||
|
||||
required property real shellTopMargin
|
||||
required property real shellBottomMargin
|
||||
|
||||
|
||||
required property var taskSwitcher
|
||||
readonly property var taskSwitcherState: taskSwitcher.taskSwitcherState
|
||||
|
||||
|
||||
// account for system header and footer offset (center the preview image)
|
||||
readonly property real taskY: {
|
||||
let headerHeight = shellTopMargin;
|
||||
let footerHeight = shellBottomMargin;
|
||||
let diff = headerHeight - footerHeight;
|
||||
|
||||
|
||||
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 {
|
||||
origin.x: root.width / 2
|
||||
origin.y: root.height / 2
|
||||
xScale: 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 {
|
||||
enabled: !taskSwitcherState.currentlyBeingOpened
|
||||
|
||||
|
||||
onTapped: {
|
||||
// if tapped on the background, then hide
|
||||
if (root.childAt(eventPoint.position.x, eventPoint.position.y) === null) {
|
||||
taskSwitcher.hide();
|
||||
}
|
||||
}
|
||||
|
||||
onPressedChanged: {
|
||||
if (pressed) {
|
||||
// ensure animations aren't running when finger is pressed
|
||||
|
|
@ -61,54 +87,51 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
model: taskSwitcher.tasksModel
|
||||
|
||||
|
||||
// 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 {
|
||||
id: task
|
||||
|
||||
readonly property int currentIndex: model.index
|
||||
|
||||
|
||||
// this is the x-position with respect to the list
|
||||
property real listX: taskSwitcherState.xPositionFromTaskIndex(currentIndex);
|
||||
|
||||
Behavior on listX {
|
||||
NumberAnimation {
|
||||
|
||||
Behavior on listX {
|
||||
NumberAnimation {
|
||||
duration: PlasmaCore.Units.longDuration
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// this is the actual displayed x-position on screen
|
||||
x: listX + repeater.leftMargin - taskSwitcherState.xPosition
|
||||
|
||||
y: root.taskY
|
||||
|
||||
|
||||
// ensure current task is above others
|
||||
z: taskSwitcherState.currentTaskIndex === currentIndex ? 1 : 0
|
||||
|
||||
|
||||
// only show header once task switcher is opened
|
||||
showHeader: !taskSwitcherState.currentlyBeingOpened
|
||||
|
||||
|
||||
// darken effect as task gets away from the centre of the screen
|
||||
darken: {
|
||||
let distFromCentreProgress = Math.abs(x - repeater.leftMargin) / taskSwitcherState.taskWidth;
|
||||
let upperBoundAdjust = Math.min(0.5, distFromCentreProgress) - 0.2;
|
||||
return Math.max(0, upperBoundAdjust);
|
||||
}
|
||||
|
||||
|
||||
width: taskSwitcherState.taskWidth
|
||||
height: taskSwitcherState.taskHeight
|
||||
previewWidth: taskSwitcherState.previewWidth
|
||||
previewHeight: taskSwitcherState.previewHeight
|
||||
|
||||
|
||||
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-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
// SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
|
||||
import org.kde.plasma.core 2.1 as PlasmaCore
|
||||
|
||||
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
||||
import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState
|
||||
import org.kde.kwin 3.0 as KWinComponents
|
||||
|
||||
/**
|
||||
* State object for the task switcher.
|
||||
*/
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
|
||||
// TaskSwitcher item component
|
||||
// We assume that the taskSwitcher the size of the entire screen.
|
||||
required property var taskSwitcher
|
||||
|
||||
|
||||
|
||||
|
||||
// ~~ positioning ~~
|
||||
|
||||
|
||||
// Position of the list view:
|
||||
//
|
||||
//
|
||||
// xPosition:
|
||||
// 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.
|
||||
|
|
@ -35,11 +31,11 @@ QtObject {
|
|||
// Increasing yPosition results in the task switcher moving up (and thumbnails shrinking)
|
||||
property real xPosition: 0
|
||||
property real yPosition: 0
|
||||
|
||||
|
||||
// direction of the movement
|
||||
property bool movingRight: false
|
||||
property bool movingUp: false
|
||||
|
||||
|
||||
// used for calculating movement direction
|
||||
property real oldXPosition: 0
|
||||
property real oldYPosition: 0
|
||||
|
|
@ -51,54 +47,54 @@ QtObject {
|
|||
movingUp = yPosition > oldYPosition;
|
||||
oldYPosition = yPosition;
|
||||
}
|
||||
|
||||
|
||||
// yPosition when the task switcher is completely open
|
||||
readonly property real openedYPosition: (taskSwitcher.height - taskHeight) / 2
|
||||
|
||||
|
||||
// ~~ active state ~~
|
||||
|
||||
|
||||
// whether the user was in an active task before the task switcher was opened
|
||||
property bool wasInActiveTask: false
|
||||
|
||||
|
||||
// whether we are in a swipe up gesture to open the task switcher
|
||||
property bool currentlyBeingOpened: false
|
||||
|
||||
|
||||
// whether the task switcher is being closed: an animation is running
|
||||
property bool currentlyBeingClosed: false
|
||||
|
||||
|
||||
// whether we are in a swipe left/right gesture to walk through tasks
|
||||
property bool scrollingTasks: false
|
||||
|
||||
|
||||
readonly property int currentTaskIndex: {
|
||||
let candidateIndex = Math.round(-xPosition / (taskSpacing + taskWidth));
|
||||
return Math.max(0, Math.min(taskSwitcher.tasksCount - 1, candidateIndex));
|
||||
}
|
||||
|
||||
|
||||
// ~~ measurement constants ~~
|
||||
|
||||
|
||||
// dimensions of a real window on the screen
|
||||
readonly property real windowHeight: taskSwitcher.height - taskSwitcher.topMargin - taskSwitcher.bottomMargin
|
||||
readonly property real windowWidth: taskSwitcher.width - taskSwitcher.leftMargin - taskSwitcher.rightMargin
|
||||
|
||||
|
||||
// dimensions of the task previews
|
||||
readonly property real previewHeight: windowHeight * scalingFactor
|
||||
readonly property real previewWidth: windowWidth * scalingFactor
|
||||
readonly property real taskHeight: previewHeight + taskHeaderHeight
|
||||
readonly property real taskWidth: previewWidth
|
||||
|
||||
|
||||
// spacing between each task preview
|
||||
readonly property real taskSpacing: PlasmaCore.Units.largeSpacing
|
||||
|
||||
|
||||
// height of the task preview header
|
||||
readonly property real taskHeaderHeight: PlasmaCore.Units.gridUnit * 2 + PlasmaCore.Units.smallSpacing * 2
|
||||
|
||||
|
||||
// the scaling factor of the window preview compared to the actual window
|
||||
// we need to ensure that window previews always fit on screen
|
||||
readonly property real scalingFactor: {
|
||||
let candidateFactor = 0.6;
|
||||
let candidateTaskHeight = windowHeight * candidateFactor + taskHeaderHeight;
|
||||
let candidateTaskWidth = windowWidth * candidateFactor;
|
||||
|
||||
|
||||
let candidateHeight = (candidateTaskWidth / windowWidth) * windowHeight;
|
||||
if (candidateHeight > windowHeight) {
|
||||
return candidateTaskHeight / windowHeight;
|
||||
|
|
@ -106,22 +102,22 @@ QtObject {
|
|||
return candidateTaskWidth / windowWidth;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// scale of the task list (based on the progress of the swipe up gesture)
|
||||
readonly property real currentScale: {
|
||||
let maxScale = 1 / scalingFactor;
|
||||
let subtract = (maxScale - 1) * (yPosition / openedYPosition);
|
||||
let finalScale = Math.max(0, Math.min(maxScale, maxScale - subtract));
|
||||
|
||||
|
||||
// animate scale only if we are *not* opening from the homescreen
|
||||
if ((wasInActiveTask || !currentlyBeingOpened) && !scrollingTasks) {
|
||||
return finalScale;
|
||||
}
|
||||
return scrollingTasks ? maxScale : 1;
|
||||
}
|
||||
|
||||
|
||||
// ~~ signals and functions ~~
|
||||
|
||||
|
||||
// cancel all animated moving, as another flick source is taking over
|
||||
signal cancelAnimations()
|
||||
onCancelAnimations: {
|
||||
|
|
@ -130,37 +126,38 @@ QtObject {
|
|||
closeAnim.stop();
|
||||
xAnim.stop();
|
||||
}
|
||||
|
||||
|
||||
function open() {
|
||||
openAnim.restart();
|
||||
}
|
||||
|
||||
|
||||
function close() {
|
||||
closeAnim.restart();
|
||||
}
|
||||
|
||||
function openApp(index) {
|
||||
|
||||
function openApp(index, window) {
|
||||
animateGoToTaskIndex(index, PlasmaCore.Units.shortDuration);
|
||||
openAppAnim.restart();
|
||||
KWinComponents.Workspace.activeClient = window
|
||||
}
|
||||
|
||||
|
||||
// get the xPosition where the task will be centered on the screen
|
||||
function xPositionFromTaskIndex(index) {
|
||||
return -index * (taskWidth + taskSpacing);
|
||||
}
|
||||
|
||||
|
||||
// instantly go to the task index
|
||||
function goToTaskIndex(index) {
|
||||
xPosition = xPositionFromTaskIndex(index);
|
||||
}
|
||||
|
||||
|
||||
// go to the task index, animated
|
||||
function animateGoToTaskIndex(index, duration) {
|
||||
xAnim.duration = duration;
|
||||
xAnim.to = xPositionFromTaskIndex(index);
|
||||
xAnim.restart();
|
||||
}
|
||||
|
||||
|
||||
// called after a user finishes an interaction (ex. lets go of the screen)
|
||||
function updateState() {
|
||||
cancelAnimations();
|
||||
|
|
@ -173,7 +170,7 @@ QtObject {
|
|||
// close task switcher and return to app
|
||||
closeAnim.restart();
|
||||
}
|
||||
|
||||
|
||||
// update horizontal state
|
||||
let duration = PlasmaCore.Units.longDuration * 2;
|
||||
if (currentlyBeingOpened) {
|
||||
|
|
@ -195,35 +192,38 @@ QtObject {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ~~ property animators ~~
|
||||
|
||||
|
||||
property var xAnim: NumberAnimation {
|
||||
target: root
|
||||
property: "xPosition"
|
||||
easing.type: Easing.OutBack
|
||||
}
|
||||
|
||||
property var openAnim: NumberAnimation {
|
||||
|
||||
property var openAnim: NumberAnimation {
|
||||
target: root
|
||||
property: "yPosition"
|
||||
to: openedYPosition
|
||||
duration: MobileShell.MobileShellSettings.animationsEnabled ? 300 : 0
|
||||
easing.type: Easing.OutBack
|
||||
|
||||
duration: 300
|
||||
easing.type: Easing.OutBack
|
||||
|
||||
onFinished: {
|
||||
root.currentlyBeingOpened = false;
|
||||
}
|
||||
}
|
||||
|
||||
property var closeAnim: NumberAnimation {
|
||||
|
||||
property var closeAnim: NumberAnimation {
|
||||
target: root
|
||||
property: "yPosition"
|
||||
to: 0
|
||||
duration: MobileShell.MobileShellSettings.animationsEnabled ? PlasmaCore.Units.longDuration : 0
|
||||
duration: PlasmaCore.Units.longDuration
|
||||
easing.type: Easing.InOutQuad
|
||||
|
||||
|
||||
onStarted: root.currentlyBeingClosed = true
|
||||
|
||||
onFinished: {
|
||||
root.currentlyBeingClosed = false;
|
||||
root.currentlyBeingOpened = false;
|
||||
scrollingTasks = false;
|
||||
taskSwitcher.instantHide();
|
||||
|
|
@ -233,16 +233,16 @@ QtObject {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
property var openAppAnim: NumberAnimation {
|
||||
target: root
|
||||
|
||||
property var openAppAnim: NumberAnimation {
|
||||
target: root
|
||||
property: "yPosition"
|
||||
to: 0
|
||||
duration: MobileShell.MobileShellSettings.animationsEnabled ? 300 : 0
|
||||
duration: 300
|
||||
easing.type: Easing.OutQuint
|
||||
|
||||
|
||||
onStarted: root.currentlyBeingClosed = true
|
||||
|
||||
|
||||
onFinished: {
|
||||
root.currentlyBeingClosed = false;
|
||||
root.currentlyBeingOpened = false;
|
||||
Loading…
Reference in a new issue