taskswitcher: Rewrite without listview and extract state to TaskSwitcherState

This commit is contained in:
Devin Lin 2021-12-27 01:32:24 -05:00
parent 7a1a32724b
commit 1c81cd34b4
8 changed files with 542 additions and 192 deletions

View file

@ -53,10 +53,12 @@ Flickable {
onContentXChanged: mainFlickable.currentIndex = Math.floor(contentX / width)
onFooterChanged: {
footer.parent = mainFlickable;
footer.anchors.left = mainFlickable.left;
footer.anchors.bottom = mainFlickable.bottom;
footer.anchors.right = mainFlickable.right;
if (footer) {
footer.parent = mainFlickable;
footer.anchors.left = mainFlickable.left;
footer.anchors.bottom = mainFlickable.bottom;
footer.anchors.right = mainFlickable.right;
}
}
//Autoscroll related functions

View file

@ -69,20 +69,24 @@ Item {
}
if (root.dragGestureEnabled) {
if (!taskSwitcher.currentlyDragging && Math.abs(startMouseY - oldMouseY) < root.height) {
if (!opening && Math.abs(startMouseY - oldMouseY) < root.height) {
oldMouseY = mouse.y;
return;
} else if (mouseArea.pressed) {
taskSwitcher.currentlyDragging = true;
opening = true;
}
// update offsets with drags
root.taskSwitcher.oldYOffset = root.taskSwitcher.yOffset;
root.taskSwitcher.yOffset = Math.max(0, root.taskSwitcher.yOffset - (mouse.y - oldMouseY));
if (root.taskSwitcher.visible) {
// update task switcher drag
let offsetY = (mouse.y - oldMouseY) * 0.5; // we want to make the gesture take a longer swipe than it being pixel perfect
let offsetX = (mouse.x - oldMouseX) * 0.5;
taskSwitcher.taskSwitcherState.yPosition = Math.max(0, taskSwitcher.taskSwitcherState.yPosition - offsetY);
opening = oldMouseY > mouse.y;
// TODO add x swipe
//taskSwitcher.taskSwitcherState.xPosition -= offsetX;
}
if (!root.taskSwitcher.visible && Math.abs(startMouseY - mouse.y) > PlasmaCore.Units.gridUnit && root.taskSwitcher.tasksCount) {
if (!root.taskSwitcher.visible && Math.abs(startMouseY - mouse.y) > PlasmaCore.Units.gridUnit && taskSwitcher.tasksCount) {
// start task switcher gesture
activeButton = null;
root.taskSwitcher.show(false);
@ -98,11 +102,8 @@ Item {
onReleased: {
if (activeButton) {
activeButton.clicked();
}
if (root.dragGestureEnabled && root.taskSwitcher.currentlyDragging) {
root.taskSwitcher.currentlyDragging = false;
root.taskSwitcher.snapOffset();
} else if (root.dragGestureEnabled && taskSwitcher.taskSwitcherState.currentlyBeingOpened) {
taskSwitcher.taskSwitcherState.updateState();
}
}

View file

@ -0,0 +1,88 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.15
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 + 99999
contentHeight: height
contentX: startContentX
readonly property real startContentX: contentWidth / 2
property bool positionChangedDueToFlickable: false
// ensure that flickable is not moving when other sources are changing position
Connections {
target: root.taskSwitcherState
onXPositionChanged: {
if (!root.positionChangedDueToFlickable) {
root.cancelMovement();
}
root.positionChangedDueToFlickable = true;
}
onYPositionChanged: {
if (!root.positionChangedDueToFlickable) {
root.cancelMovement();
}
root.positionChangedDueToFlickable = true;
}
}
// update position from horizontal flickable movement
property real oldContentX
onContentXChanged: {
positionChangedDueToFlickable = true;
taskSwitcherState.xPosition += contentX - oldContentX;
oldContentX = contentX;
}
onMovementStarted: taskSwitcherState.cancelAnimations();
onMovementEnded: {
resetPosition();
taskSwitcherState.updateState();
}
onFlickEnded: {
resetPosition();
taskSwitcherState.updateState();
}
onDraggingChanged: {
if (!dragging) {
cancelMovement();
resetPosition();
taskSwitcherState.updateState();
} else {
taskSwitcherState.cancelAnimations();
}
}
function cancelMovement() {
root.cancelFlick();
// HACK: cancelFlick() doesn't seem to cancel flicks...
root.flick(-horizontalVelocity, 0);
}
function resetPosition() {
positionChangedDueToFlickable = true;
oldContentX = startContentX;
contentX = startContentX;
}
}

View file

@ -2,7 +2,7 @@
* SPDX-FileCopyrightText: 2015 Marco Martin <notmart@gmail.com>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.15
@ -22,20 +22,23 @@ Item {
required property var model
required property var displaysModel
readonly property point taskScreenPoint: Qt.point(model.ScreenGeometry.x, model.ScreenGeometry.y)
readonly property point taskScreenPoint: model ? Qt.point(model.ScreenGeometry.x, model.ScreenGeometry.y) : Qt.point(0, 0)
readonly property real dragOffset: -control.y
property bool active: model.IsActive
property bool active: model ? model.IsActive : false
required property real previewHeight
required property real previewWidth
property bool showHeader: true
property real scale: 1
opacity: 1 - dragOffset / taskSwitcher.height
//BEGIN functions
function syncDelegateGeometry() {
let pos = pipeWireLoader.mapToItem(tasksView, 0, 0);
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);
}
@ -113,6 +116,11 @@ Item {
Layout.fillHeight: true
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
@ -164,6 +172,9 @@ Item {
Layout.maximumWidth: delegate.previewWidth
Layout.maximumHeight: delegate.previewHeight
// prevent thumbnails from "leaking" out of the control
clip: true
leftPadding: 0
rightPadding: 0
topPadding: 0

View file

@ -0,0 +1,91 @@
/*
* 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
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
Item {
id: root
required property var taskSwitcher
readonly property var taskSwitcherState: taskSwitcher.taskSwitcherState
opacity: taskSwitcherState.wasInActiveTask ? 1 : Math.min(1, taskSwitcherState.yPosition / taskSwitcherState.openedYPosition)
// taphandler activates even if delegate touched
TapHandler {
onPressedChanged: {
if (pressed) {
// ensure animations aren't running when finger is pressed
taskSwitcherState.cancelAnimations();
}
}
}
Repeater {
id: repeater
model: taskSwitcher.model
// left margin from root edge such that the task is centered
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 {
duration: PlasmaCore.Units.longDuration
easing.type: Easing.InOutQuad
}
}
// this is the actual displayed x-position on screen
x: listX + repeater.leftMargin - taskSwitcherState.xPosition
// account for system header and footer offset (center the preview image)
y: {
let headerHeight = MobileShell.TopPanelControls.panelHeight;
let footerHeight = MobileShell.TaskPanelControls.isPortrait ? MobileShell.TaskPanelControls.panelHeight : 0;
let diff = headerHeight - footerHeight;
let baseY = (taskSwitcher.height / 2) - (height / 2) - (taskSwitcherState.taskHeaderHeight / 2)
return baseY + diff / 2 - MobileShell.TopPanelControls.panelHeight;
}
// ensure current task is above others
z: taskSwitcherState.currentTaskIndex === currentIndex ? 1 : 0
showHeader: currentIndex !== taskSwitcherState.currentTaskIndex || !taskSwitcherState.currentlyBeingOpened
width: taskSwitcherState.taskWidth
height: taskSwitcherState.taskHeight
previewWidth: taskSwitcherState.previewWidth
previewHeight: taskSwitcherState.previewHeight
taskSwitcher: root.taskSwitcher
displaysModel: root.taskSwitcher.displaysModel
scale: {
if (taskSwitcherState.currentTaskIndex == currentIndex) {
return taskSwitcherState.currentScale;
}
return 1;
}
}
}
}

View file

@ -2,12 +2,12 @@
* SPDX-FileCopyrightText: 2015 Marco Martin <notmart@gmail.com>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.12
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
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
@ -15,109 +15,82 @@ 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 "../components" as Components
Item {
id: root
visible: false
opacity: 0
readonly property real taskPanelHeight: MobileShell.TaskPanelControls.panelHeight
readonly property real taskPanelWidth: MobileShell.TaskPanelControls.panelWidth
readonly property bool isPortrait: MobileShell.TaskPanelControls.isPortrait
// state object
property var taskSwitcherState: TaskSwitcherState {
taskSwitcher: root
}
// dimensions of a window on the screen
readonly property real windowHeight: root.height - (root.isPortrait ? root.taskPanelHeight : 0) - MobileShell.TopPanelControls.panelHeight
readonly property real windowWidth: root.width - (root.isPortrait ? 0 : root.taskPanelWidth)
readonly property int tasksCount: root.model.count
readonly property int currentTaskIndex: tasksView.currentIndex
// task list model
property TaskManager.TasksModel model
// offset constants
readonly property real targetYOffsetDist: root.height - tasksView.height // offset distance to perfect opening
readonly property real dismissYOffsetDist: root.height
// properties controlled from NavigationPanel (swipe to open gesture)
property real oldYOffset: 0
property real yOffset: 0
// set from NavigationPanel in taskpanel containment
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
readonly property int tasksCount: model.count
property var displaysModel: MobileShell.DisplaysModel {}
enum MovementDirection {
None = 0,
Left,
Right
}
onVisibleChanged: MobileShell.HomeScreenControls.taskSwitcherVisible = visible;
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;
}
Rectangle {
id: backgroundRect
anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.6 * (root.wasInActiveTask ? 1 : Math.min(1, root.yOffset / root.targetYOffsetDist)))
MouseArea {
anchors.fill: parent
onClicked: root.hide()
}
}
// TODO close task switcher when an app opens while it is open, otherwise the navbar becomes glitched
// TODO click outside of delegate to close
//BEGIN functions
function show(animation) {
root.yOffset = 0;
root.wasInActiveTask = root.model.activeTask.row >= 0;
// reset values
taskSwitcherState.cancelAnimations();
taskSwitcherState.yPosition = 0;
taskSwitcherState.xPosition = 0;
taskSwitcherState.wasInActiveTask = root.model.activeTask.row >= 0;
taskSwitcherState.currentlyBeingOpened = true;
// skip to first active task
if (root.wasInActiveTask) {
tasksView.currentIndex = root.model.activeTask.row;
tasksView.positionViewAtIndex(root.model.activeTask.row, ListView.SnapPosition);
if (taskSwitcherState.wasInActiveTask) {
taskSwitcherState.goToTaskIndex(root.model.activeTask.row);
}
root.visible = true;
// show task switcher, hide all running apps
visible = true;
opacity = 1;
minimizeAll();
// animate app shrink
// fully open the panel (if this is a button press, not gesture)
if (animation) {
offsetAnimator.to = root.targetYOffsetDist;
offsetAnimator.restart();
taskSwitcherState.open();
}
}
function instantHide() {
opacity = 0;
visible = false;
}
function hide() {
if (!root.visible) return;
root.visible = false;
}
function snapOffset() {
let movingUp = root.yOffset > root.oldYOffset;
if (movingUp || root.yOffset >= root.targetYOffsetDist) { // open task switcher and stay
offsetAnimator.to = root.targetYOffsetDist;
offsetAnimator.restart();
} else { // close task switcher and return to app
if (!root.wasInActiveTask) { // if pulled up from homescreen, don't activate app
offsetAnimator.activateApp = false;
}
offsetAnimator.to = 0;
offsetAnimator.restart();
}
closeAnim.restart();
}
// scroll to delegate index, and activate it
function activateWindow(id) {
offsetAnimator.to = 0;
offsetAnimator.restart();
taskSwitcherState.openApp(id);
}
function setSingleActiveWindow(id, delegate) {
function setSingleActiveWindow(id) {
if (id < 0) {
return;
}
@ -130,14 +103,14 @@ Item {
root.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
// only minimize the other windows in the same screen
if (geo === newActiveGeo) {
tasksModel.requestToggleMinimized(idx);
}
}
}
root.visible = false;
instantHide();
}
function minimizeAll() {
@ -149,40 +122,29 @@ Item {
}
}
function restoreAll() {
for (var i = 0 ; i < tasksModel.count; i++) {
var idx = tasksModel.makeModelIndex(i);
if (tasksModel.data(idx, TaskManager.AbstractTasksModel.IsMinimized)) {
tasksModel.requestToggleMinimized(idx);
}
//END functions
NumberAnimation on opacity {
id: closeAnim
to: 0
duration: PlasmaCore.Units.shortDuration
easing.type: Easing.InOutQuad
onFinished: {
root.visible = false;
}
}
//END functions
// background colour
Rectangle {
id: backgroundRect
anchors.fill: parent
// 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)
// root.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 (!root.visible) return;
root.visible = false;
if (activateApp) {
setSingleActiveWindow(root.currentTaskIndex);
}
activateApp = true;
} else if (to == root.dismissYOffsetDist) {
root.hide();
color: {
// animate background colour only if opening from the homescreen
if (taskSwitcherState.wasInActiveTask) {
return Qt.rgba(0, 0, 0, 0.6);
} else {
return Qt.rgba(0, 0, 0, 0.6 * Math.min(1, taskSwitcherState.yPosition / taskSwitcherState.openedYPosition));
}
}
}
@ -192,74 +154,30 @@ Item {
// provide shell margins
anchors.fill: parent
anchors.rightMargin: root.isPortrait ? 0 : root.taskPanelWidth
anchors.bottomMargin: root.isPortrait ? root.taskPanelHeight : 0
anchors.rightMargin: MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth
anchors.bottomMargin: MobileShell.TaskPanelControls.isPortrait ? MobileShell.TaskPanelControls.panelHeight : 0
anchors.topMargin: MobileShell.TopPanelControls.panelHeight
// applications list
ListView {
id: tasksView
opacity: root.wasInActiveTask ? 1 : Math.min(1, root.yOffset / root.targetYOffsetDist)
anchors.centerIn: parent
FlickContainer {
id: flickable
anchors.fill: parent
taskSwitcherState: root.taskSwitcherState
readonly property real sizeFactor: 0.75
readonly property real taskHeaderHeight: PlasmaCore.Units.gridUnit * 2 + PlasmaCore.Units.smallSpacing * 2
// the item is effectively anchored to the flickable bounds
QQC2.Control {
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
width: root.windowWidth * sizeFactor
height: root.windowHeight * sizeFactor + taskHeaderHeight
x: flickable.contentX
width: flickable.width
height: flickable.height
model: root.model
orientation: ListView.Horizontal
highlightRangeMode: ListView.StrictlyEnforceRange // ensures currentIndex is updated
snapMode: ListView.SnapToItem
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 }
}
// ensure that window previews are exactly to the scale of the device screen
property real scalingFactor: {
let candidateHeight = (tasksView.width / root.windowWidth) * root.windowHeight;
if (candidateHeight > tasksView.height) {
return tasksView.height / root.windowHeight;
} else {
return tasksView.width / root.windowWidth;
contentItem: TaskList {
taskSwitcher: root
}
}
delegate: Task {
id: task
property int curIndex: model.index
z: root.currentTaskIndex === curIndex ? 1 : 0
width: tasksView.width
height: tasksView.height
taskSwitcher: root
displaysModel: root.displaysModel
// account for header offset (center the preview)
y: -tasksView.taskHeaderHeight / 2
// scale gesture
scale: {
let maxScale = 1 / tasksView.scalingFactor;
let subtract = (maxScale - 1) * (root.yOffset / root.targetYOffsetDist);
let finalScale = Math.max(0, Math.min(maxScale, maxScale - subtract));
if ((root.wasInActiveTask || !taskSwitcher.currentlyDragging) && root.currentTaskIndex === task.curIndex) {
return finalScale;
}
return 1;
}
// ensure that window previews are exactly to the scale of the device screen
previewWidth: tasksView.scalingFactor * root.windowWidth
previewHeight: tasksView.scalingFactor * root.windowHeight
}
}
}
}

View file

@ -0,0 +1,239 @@
/*
* 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
/**
* 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.
// Increasing xPosition results in the task switcher moving forward (to the second task, third task, etc).
//
// yPosition:
// 0 - Start of swipe up gesture, if window was showing, the thumbnail is the size of it
// 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
onXPositionChanged: {
movingRight = xPosition > oldXPosition;
oldXPosition = xPosition;
}
onYPositionChanged: {
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
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 - (MobileShell.TaskPanelControls.isPortrait ? MobileShell.TaskPanelControls.panelHeight : 0) - MobileShell.TopPanelControls.panelHeight
readonly property real windowWidth: taskSwitcher.width - (MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth)
// 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;
} else {
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));
if (wasInActiveTask || !currentlyBeingOpened) {
return finalScale;
}
return 1;
}
// ~~ signals and functions ~~
// cancel all animated moving, as another flick source is taking over
signal cancelAnimations()
onCancelAnimations: {
openAnim.stop();
openAppAnim.stop();
closeAnim.stop();
xAnim.stop();
}
function open() {
openAnim.restart();
}
function close() {
closeAnim.restart();
}
function openApp(index) {
animateGoToTaskIndex(index, PlasmaCore.Units.shortDuration);
openAppAnim.restart();
}
// 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();
// update vertical state
if (movingUp || root.yPosition >= openedYPosition) {
// open task switcher and stay
openAnim.restart();
} else {
// close task switcher and return to app
closeAnim.restart();
}
// update horizontal state
if (!currentlyBeingOpened) {
let currentTaskIndexPosition = xPositionFromTaskIndex(currentTaskIndex);
let duration = PlasmaCore.Units.longDuration * 2;
if (xPosition < currentTaskIndexPosition) {
if (movingRight) {
animateGoToTaskIndex(currentTaskIndex, duration);
} else {
animateGoToTaskIndex(Math.max(0, currentTaskIndex - 1), duration);
}
} else {
if (movingRight) {
animateGoToTaskIndex(Math.min(taskSwitcher.tasksCount - 1, currentTaskIndex + 1), duration);
} else {
animateGoToTaskIndex(currentTaskIndex, duration);
}
}
}
}
// ~~ property animators ~~
property var xAnim: NumberAnimation {
target: root
property: "xPosition"
easing.type: Easing.OutBack
}
property var openAnim: NumberAnimation {
target: root
property: "yPosition"
to: openedYPosition
duration: PlasmaCore.Units.longDuration
easing.type: Easing.InOutQuad
onFinished: {
root.currentlyBeingOpened = false;
}
}
property var closeAnim: NumberAnimation {
target: root
property: "yPosition"
to: 0
duration: PlasmaCore.Units.longDuration
easing.type: Easing.InOutQuad
onFinished: {
root.currentlyBeingOpened = false;
taskSwitcher.instantHide();
if (root.wasInActiveTask) {
taskSwitcher.setSingleActiveWindow(root.currentTaskIndex);
}
}
}
property var openAppAnim: NumberAnimation {
target: root
property: "yPosition"
to: 0
duration: PlasmaCore.Units.longDuration
easing.type: Easing.InOutQuad
onFinished: {
root.currentlyBeingOpened = false;
taskSwitcher.setSingleActiveWindow(root.currentTaskIndex);
taskSwitcher.instantHide();
}
}
}

View file

@ -102,9 +102,9 @@ PlasmaCore.ColorScope {
taskSwitcher.show(true);
} else {
// when task switcher is open
if (taskSwitcher.wasInActiveTask) {
if (taskSwitcher.taskSwitcherState.wasInActiveTask) {
// restore active window
taskSwitcher.activateWindow(root.currentTaskIndex);
taskSwitcher.activateWindow(taskSwitcher.taskSwitcherState.currentTaskIndex);
} else {
taskSwitcher.hide();
}
@ -169,7 +169,7 @@ PlasmaCore.ColorScope {
// do not enable drag gesture when task switcher is already open
// also don't disable drag gesture mid-drag
dragGestureEnabled: !taskSwitcher.visible || taskSwitcher.currentlyDragging
dragGestureEnabled: !taskSwitcher.visible || taskSwitcher.taskSwitcherState.currentlyBeingOpened
leftAction: taskSwitcherAction
middleAction: homeAction