shift-shell/kwin/mobiletaskswitcher/qml/TaskSwitcherState.qml

248 lines
8 KiB
QML

// 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.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.
//
// 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: true // assume that task switcher is loaded on open
// 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;
} 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));
// 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: {
openAnim.stop();
openAppAnim.stop();
closeAnim.stop();
xAnim.stop();
}
function open() {
openAnim.restart();
}
function close() {
closeAnim.restart();
}
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();
// update vertical state
if ((movingUp || root.yPosition >= openedYPosition) && !scrollingTasks) {
// open task switcher and stay
openAnim.restart();
} else {
// close task switcher and return to app
closeAnim.restart();
}
// update horizontal state
let duration = PlasmaCore.Units.longDuration * 2;
if (currentlyBeingOpened) {
animateGoToTaskIndex(currentTaskIndex, duration);
} else {
let currentTaskIndexPosition = xPositionFromTaskIndex(currentTaskIndex);
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: 300
easing.type: Easing.OutBack
onFinished: {
root.currentlyBeingOpened = false;
}
}
property var closeAnim: NumberAnimation {
target: root
property: "yPosition"
to: 0
duration: PlasmaCore.Units.longDuration
easing.type: Easing.InOutQuad
onStarted: root.currentlyBeingClosed = true
onFinished: {
root.currentlyBeingClosed = false;
root.currentlyBeingOpened = false;
scrollingTasks = false;
taskSwitcher.instantHide();
}
}
property var openAppAnim: NumberAnimation {
target: root
property: "yPosition"
to: 0
duration: 300
easing.type: Easing.OutQuint
onStarted: root.currentlyBeingClosed = true
onFinished: {
root.currentlyBeingClosed = false;
root.currentlyBeingOpened = false;
taskSwitcher.instantHide();
}
}
}