/* * SPDX-FileCopyrightText: 2015 Marco Martin * SPDX-FileCopyrightText: 2021 Devin Lin * * SPDX-License-Identifier: LGPL-2.0-or-later */ import QtQuick 2.12 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 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 NanoShell.FullScreenOverlay { id: window visible: false width: Screen.width height: Screen.height required property real taskPanelHeight // height of task panel, provided by main.qml property int tasksCount: window.model.count property int currentTaskIndex: tasksView.currentIndexInView // Math.round(tasksView.contentX / (tasksView.width + tasksView.spacing)) property TaskManager.TasksModel model // properties controlled from main.qml MouseArea (swipe to open gesture) property real oldYOffset: 0 property real yOffset: 0 // offset constants readonly property real targetYOffsetDist: window.height - tasksView.height // offset distance to perfect opening readonly property real dismissYOffsetDist: window.height // set from main.qml property bool wasInActiveTask: false // whether we were in an app before opening the task switcher property bool currentlyDragging: false // whether we are in a swipe up gesture Component.onCompleted: plasmoid.nativeInterface.panel = window; enum MovementDirection { None = 0, Left, Right } onVisibleChanged: { if (!visible) { window.contentItem.opacity = 1; } // hide homescreen elements to make use of wallpaper if (visible) { MobileShell.HomeScreenControls.hideHomeScreen(!window.wasInActiveTask); // only animate if going from homescreen } else { MobileShell.HomeScreenControls.showHomeScreen(true); } MobileShell.HomeScreenControls.taskSwitcherVisible = visible; } onTasksCountChanged: { if (tasksCount == 0) { hide(); } } // background color: "transparent" Rectangle { id: backgroundRect anchors.fill: parent color: Qt.rgba(0, 0, 0, 0.6 * (window.wasInActiveTask ? 1 : Math.min(1, window.yOffset / window.targetYOffsetDist))) MouseArea { anchors.fill: parent onClicked: hide() } } //BEGIN functions function show(animation) { window.yOffset = 0; window.wasInActiveTask = window.model.activeTask.row >= 0; // skip to first active task if (window.wasInActiveTask) { tasksView.contentX = Math.max(0, Math.min(tasksView.contentWidth, window.model.activeTask.row * (tasksView.width + tasksView.spacing))); } window.visible = true; root.minimizeAll(); // animate app shrink if (animation) { offsetAnimator.to = window.targetYOffsetDist; offsetAnimator.restart(); } } function hide() { if (!window.visible) return; window.visible = false; } function snapOffset() { let movingUp = window.yOffset > window.oldYOffset; if (movingUp || window.yOffset >= window.targetYOffsetDist) { // open task switcher and stay offsetAnimator.to = window.targetYOffsetDist; offsetAnimator.restart(); } else { // close task switcher and return to app if (!window.wasInActiveTask) { // if pulled up from homescreen, don't activate app offsetAnimator.activateApp = false; } offsetAnimator.to = 0; offsetAnimator.restart(); } } // scroll to delegate index, and activate it function activateWindow(id) { offsetAnimator.to = 0; offsetAnimator.restart(); } function setSingleActiveWindow(id, delegate) { if (id < 0) { return; } var newActiveIdx = window.model.index(id, 0) var newActiveGeo = tasksModel.data(newActiveIdx, TaskManager.AbstractTasksModel.ScreenGeometry) for (var i = 0 ; i < tasksModel.count; i++) { var idx = window.model.index(i, 0) if (i == id) { window.model.requestActivate(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); } } } window.visible = false; } //END functions // animate app grow and shrink NumberAnimation on yOffset { id: offsetAnimator duration: PlasmaCore.Units.longDuration easing.type: Easing.InOutQuad property bool activateApp: true // states of to: // 0 - open/resume app (zoom up the thumbnail) // window.targetYOffsetDist - animate shrinking of thumbnail, to listview (open task switcher) to: 0 onFinished: { if (to === 0) { // close task switcher, and switch to current app if (!window.visible) return; window.visible = false; if (activateApp) { setSingleActiveWindow(window.currentTaskIndex); } activateApp = true; } else if (to == window.dismissYOffsetDist) { window.hide(); } } } ListView { id: tasksView z: 100 opacity: window.wasInActiveTask ? 1 : Math.min(1, window.yOffset / window.targetYOffsetDist) property real horizontalMargin: PlasmaCore.Units.gridUnit * 3 anchors.centerIn: parent width: window.width - horizontalMargin * 2 height: window.height - (MobileShell.TopPanelControls.panelHeight + window.taskPanelHeight + PlasmaCore.Units.gridUnit * 2 + PlasmaCore.Units.largeSpacing * 2) // ensure that window previews are exactly to the scale of the device screen property real windowHeight: window.height - window.taskPanelHeight - MobileShell.TopPanelControls.panelHeight property real scalingFactor: { let candidateWidth = tasksView.width; let candidateHeight = (tasksView.width / window.width) * windowHeight; if (candidateHeight > tasksView.height) { return tasksView.height / windowHeight; } else { return tasksView.width / window.width; } } model: window.model snapMode: ListView.SnapToItem orientation: ListView.Horizontal spacing: PlasmaCore.Units.largeSpacing displayMarginBeginning: 2 * (width + spacing) displayMarginEnd: 2 * (width + spacing) displaced: Transition { NumberAnimation { properties: "x,y"; duration: PlasmaCore.Units.longDuration; easing.type: Easing.InOutQuad } } property real currentIndexInView: indexAt(contentX, contentY) MouseArea { z: -1 anchors.fill: parent visible: tasksView.count === 0 enabled: visible onClicked: { // close window on tap if there are no delegates if (tasksView.count === 0) { window.hide() } } PlasmaComponents.Label { anchors.centerIn: parent text: i18n("No applications are open") color: "white" } } delegate: Task { id: task property int curIndex: model.index z: curIndex === tasksView.currentIndexInView ? 1 : 0 width: tasksView.width height: tasksView.height // account for header offset (center the preview) y: task.headerHeight / 2 // scale gesture property bool preventOverJump: false scale: { let maxScale = 1 / tasksView.scalingFactor; let subtract = (maxScale - 1) * (window.yOffset / window.targetYOffsetDist); let finalScale = Math.max(0, Math.min(maxScale, maxScale - subtract)); // prevent y "jump" when letting go of gesture from homescreen if (window.wasInActiveTask) { preventOverJump = false; } else { if (!taskSwitcher.currentlyDragging && finalScale === 1) { preventOverJump = false; } if (window.wasInActiveTask && taskSwitcher.currentlyDragging) { preventOverJump = true; } } if ((window.wasInActiveTask || !taskSwitcher.currentlyDragging) && !preventOverJump && window.currentTaskIndex === task.curIndex) { return finalScale; } return 1; } // ensure that window previews are exactly to the scale of the device screen previewWidth: tasksView.scalingFactor * window.width previewHeight: tasksView.scalingFactor * tasksView.windowHeight } } // top panel swipe down gesture MouseArea { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right height: MobileShell.TopPanelControls.panelHeight property int oldMouseY: 0 onPositionChanged: { MobileShell.TopPanelControls.requestRelativeScroll(mouse.y - oldMouseY); oldMouseY = mouse.y; } onPressed: { oldMouseY = mouse.y; MobileShell.TopPanelControls.startSwipe(); } onReleased: MobileShell.TopPanelControls.endSwipe(); } // task panel NavigationPanel { id: navPanel property bool isPortrait: Screen.width <= Screen.height width: isPortrait ? implicitWidth : window.taskPanelHeight height: isPortrait ? window.taskPanelHeight : implicitWidth anchors.left: isPortrait ? parent.left : undefined anchors.right: parent.right anchors.top: isPortrait ? undefined: parent.top anchors.bottom: parent.bottom taskSwitcher: window backgroundColor: window.visible ? Qt.rgba(0, 0, 0, 0.1) : "transparent" foregroundColorGroup: PlasmaCore.Theme.ComplementaryColorGroup dragGestureEnabled: false Behavior on backgroundColor { ColorAnimation {} } leftAction: NavigationPanelAction { enabled: true iconSource: "mobile-task-switcher" iconSizeFactor: 0.75 onTriggered: { if (window.wasInActiveTask) { window.activateWindow(window.currentTaskIndex); } else { window.hide(); } } } middleAction: NavigationPanelAction { enabled: true iconSource: "start-here-kde" iconSizeFactor: 1 onTriggered: { window.hide(); root.triggerHomescreen(); } } rightAction: NavigationPanelAction { enabled: true iconSource: "mobile-close-app" iconSizeFactor: 0.75 onTriggered: { tasksModel.requestClose(tasksModel.index(window.currentTaskIndex, 0)); } } } // RowLayout { // id: footerButtons // anchors.left: parent.left // anchors.right: parent.right // anchors.bottom: parent.bottom // anchors.bottomMargin: PlasmaCore.Units.largeSpacing + window.taskPanelHeight // anchors.topMargin: PlasmaCore.Units.largeSpacing // // spacing: PlasmaCore.Units.largeSpacing // // PlasmaComponents.ToolButton { // Layout.alignment: Qt.AlignRight // icon.width: PlasmaCore.Units.iconSizes.medium // icon.height: PlasmaCore.Units.iconSizes.medium // icon.name: "view-list-symbolic" // "view-grid-symbolic" // text: i18n("Switch to list view") // display: PlasmaComponents.ToolButton.IconOnly // } // // PlasmaComponents.ToolButton { // Layout.alignment: Qt.AlignHCenter // icon.width: PlasmaCore.Units.iconSizes.medium // icon.height: PlasmaCore.Units.iconSizes.medium // icon.name: "trash-empty" // text: i18n("Clear All") // display: PlasmaComponents.ToolButton.IconOnly // } // // PlasmaComponents.ToolButton { // Layout.alignment: Qt.AlignLeft // icon.width: PlasmaCore.Units.iconSizes.medium // icon.height: PlasmaCore.Units.iconSizes.medium // icon.name: "system-search" // text: i18n("Search") // display: PlasmaComponents.ToolButton.IconOnly // } // } }