mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23:09 +00:00
242 lines
7.8 KiB
QML
242 lines
7.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.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.Shell.topMargin - MobileShell.Shell.bottomMargin
|
|
readonly property real windowWidth: taskSwitcher.width - MobileShell.Shell.leftMargin - MobileShell.Shell.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) {
|
|
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
|
|
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
|
|
|
|
onFinished: {
|
|
root.currentlyBeingOpened = false;
|
|
taskSwitcher.instantHide();
|
|
|
|
if (root.wasInActiveTask) {
|
|
taskSwitcher.setSingleActiveWindow(root.currentTaskIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
property var openAppAnim: NumberAnimation {
|
|
target: root
|
|
property: "yPosition"
|
|
to: 0
|
|
duration: 300
|
|
easing.type: Easing.OutQuint
|
|
|
|
onFinished: {
|
|
root.currentlyBeingOpened = false;
|
|
taskSwitcher.setSingleActiveWindow(root.currentTaskIndex);
|
|
taskSwitcher.instantHide();
|
|
}
|
|
}
|
|
}
|