mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23:09 +00:00
Most Notable Changes Here Include: 1. If the user moves and lifts their finger up halfway up the screen, the navigation gesture will now go home. 2. The task drawer will now move in and out of view depending on the gesture navigation state. 3. The app window will now continue to shrink with resistance as the window moves further up. 4. When in the task drawer, if the user drags up from the bottom, the current task will now follow the users finger like the task does when dragging up from within an app. 5. The task drawer will now slide in from the side when it is not within an app. I would upload a video here to showcase these changes. However, I was unable to get OBS to record my screen while in my plasma mobile session.
240 lines
12 KiB
QML
240 lines
12 KiB
QML
// SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
import QtQuick
|
|
import QtQuick.Layouts
|
|
|
|
import org.kde.kirigami 2.20 as Kirigami
|
|
import org.kde.plasma.components 3.0 as PlasmaComponents
|
|
|
|
import org.kde.kwin 3.0 as KWinComponents
|
|
|
|
MouseArea {
|
|
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.state
|
|
readonly property var taskSwitcherHelpers: taskSwitcher.taskSwitcherHelpers
|
|
|
|
property int taskInteractingCount: 0
|
|
|
|
// account for system header and footer offset (center the preview image)
|
|
readonly property real taskYBase: {
|
|
let headerHeight = shellTopMargin;
|
|
let footerHeight = shellBottomMargin;
|
|
let diff = headerHeight - footerHeight;
|
|
|
|
let baseY = (taskSwitcher.height / 2) - (taskSwitcherHelpers.taskHeight / 2) - (taskSwitcherHelpers.taskHeaderHeight / 2);
|
|
|
|
return baseY + diff / 2 - shellTopMargin;
|
|
}
|
|
readonly property real taskY: {
|
|
let trackFingerYOffsetClamped = 0;
|
|
if (taskSwitcherHelpers.isScaleClamped && (taskSwitcherState.wasInActiveTask || taskSwitcherHelpers.taskDrawerOpened)) {
|
|
trackFingerYOffsetClamped = taskSwitcherHelpers.trackFingerYOffset;
|
|
}
|
|
|
|
return taskYBase - trackFingerYOffsetClamped;
|
|
}
|
|
|
|
function getTaskAt(index) {
|
|
return repeater.itemAt(index);
|
|
}
|
|
|
|
function closeAll() {
|
|
for (let i = 0; i < repeater.count; i++) {
|
|
repeater.itemAt(i).closeApp();
|
|
}
|
|
}
|
|
|
|
function minimizeAll() {
|
|
for (let i = 0; i < repeater.count; i++) {
|
|
let item = repeater.itemAt(i);
|
|
|
|
// minimize window
|
|
if (!item.window.minimized) {
|
|
item.minimizeApp();
|
|
}
|
|
}
|
|
}
|
|
|
|
function jumpToFirstVisibleWindow() {
|
|
for (let i = 0; i < repeater.count; i++) {
|
|
let item = repeater.itemAt(i);
|
|
|
|
if (!item.window.minimized) {
|
|
taskSwitcherHelpers.goToTaskIndex(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// the postition offset value for non-active tasks in the task drawer
|
|
// this value is normalized and is usually set to 0, 1, or 2 (larger the number, the further they are from the active task)
|
|
property real baseTaskOffset: 0
|
|
|
|
// the position offset value tracked to the touch x position when opening the task drawer from the home screen
|
|
// this allows the task switcher to move left and right without causing problems with the task offset animations
|
|
property real homeTouchPositionX: 0
|
|
|
|
// the touch x position value normalized between 0 and 1
|
|
// the base value should be 0.5 for when it is on the home screen
|
|
readonly property real touchPosition: {
|
|
let value = 0.5
|
|
if (taskSwitcherState.wasInActiveTask || taskSwitcherHelpers.taskDrawerOpened) {
|
|
// since the touch position starts at 0, we add half the window width and then divide it by the full width to normalize it
|
|
value = ((taskSwitcherHelpers.notHomeScreenState ? taskSwitcherState.touchXPosition : 0) + (taskSwitcherHelpers.windowWidth / 2)) / taskSwitcherHelpers.windowWidth
|
|
value = Math.min(1, Math.max(0, value))
|
|
}
|
|
return value
|
|
}
|
|
|
|
// dynamic task offset animation duration based off of the touch position and task scale
|
|
function dynamicDuration(left = true) {
|
|
// if the close animation is running, use the standard long duration time for consistency
|
|
let duration = Kirigami.Units.longDuration * 1.75
|
|
if (!taskSwitcherHelpers.closeAnim.running && taskSwitcherHelpers.notHomeScreenState && taskSwitcherHelpers.gestureState != TaskSwitcherHelpers.GestureStates.HorizontalSwipe && !taskSwitcherHelpers.isInTaskScrubMode) {
|
|
// max out the scale at 1 so it is not too fast when opening the task drawer with the button
|
|
let taskScale = Math.min(taskSwitcherHelpers.currentScale, 1)
|
|
// change the duration based off of the touch position and task scale
|
|
duration = duration * ((left ? touchPosition : (1 - touchPosition)) + 1)
|
|
}
|
|
return duration
|
|
}
|
|
|
|
// the duration is set to 0 if setOffsetDurationImmediately is true so we can skip the animation
|
|
readonly property real taskOffsetDurationLeft: setOffsetDurationImmediately ? 0 : dynamicDuration(true)
|
|
readonly property real taskOffsetDurationRight: setOffsetDurationImmediately ? 0 : dynamicDuration(false)
|
|
|
|
// the easing type for the task offset animation
|
|
property int taskOffsetEasing: Easing.InOutQuart
|
|
|
|
// skips the animation and sets the task offset value immediately (only should be set by 'setTaskOffsetValue')
|
|
property bool setOffsetDurationImmediately: true
|
|
|
|
// set the task offset value with an animation unless specified otherwise
|
|
function setTaskOffsetValue(value, immediately = false, taskEasing = ((taskSwitcherHelpers.notHomeScreenState || (value != 0)) && (baseTaskOffset != taskSwitcherHelpers.taskOffsetValue)) ? Easing.InOutQuart : Easing.OutQuart) {
|
|
if (baseTaskOffset == value && immediately) {
|
|
baseTaskOffset = value + 1;
|
|
}
|
|
setOffsetDurationImmediately = immediately;
|
|
taskOffsetEasing = taskEasing;
|
|
baseTaskOffset = value;
|
|
}
|
|
|
|
transform: Scale {
|
|
origin.x: root.width / 2
|
|
origin.y: root.height / 2
|
|
xScale: taskSwitcherHelpers.currentScale
|
|
yScale: taskSwitcherHelpers.currentScale
|
|
}
|
|
|
|
onClicked: {
|
|
// if tapped on the background, then hide
|
|
taskSwitcher.hide();
|
|
}
|
|
|
|
onPressedChanged: {
|
|
// disable if being closed or opened to prevent bugs
|
|
if (!taskSwitcherHelpers.currentlyBeingOpened && !taskSwitcherHelpers.currentlyBeingClosed && pressed) {
|
|
// ensure animations aren't running when finger is pressed
|
|
taskSwitcherHelpers.cancelAnimations();
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
id: repeater
|
|
model: taskSwitcher.tasksModel
|
|
|
|
// left margin from root edge such that the task is centered
|
|
readonly property real leftMargin: (root.width / 2) - (taskSwitcherHelpers.taskWidth / 2)
|
|
|
|
delegate: Task {
|
|
id: task
|
|
readonly property int currentIndex: model.index
|
|
readonly property bool isCurrentTask: currentIndex == taskSwitcherHelpers.currentDisplayTask
|
|
|
|
// this is the x-position with respect to the list
|
|
property real listX: taskSwitcherHelpers.xPositionFromTaskIndex(currentIndex)
|
|
Behavior on listX {
|
|
NumberAnimation {
|
|
duration: Kirigami.Units.longDuration
|
|
easing.type: Easing.InOutQuad
|
|
}
|
|
}
|
|
|
|
// the animated task offset value (always will be 0 if it is the current task in the task drawer)
|
|
property real taskOffsetNormalized: (baseTaskOffset * ((taskSwitcherHelpers.taskDrawerOpened && isCurrentTask) ? 0 : 1))
|
|
Behavior on taskOffsetNormalized {
|
|
NumberAnimation {
|
|
duration: taskSwitcherHelpers.currentDisplayTask > currentIndex ? taskOffsetDurationRight : taskOffsetDurationLeft
|
|
easing.type: taskOffsetEasing
|
|
easing.overshoot: 0.85
|
|
}
|
|
}
|
|
|
|
// calculate which direction to offset
|
|
readonly property real offsetDir: (taskSwitcherHelpers.currentDisplayTask > currentIndex ? -1 : 1)
|
|
|
|
// check if this task should be offset
|
|
readonly property real isOffScreenOffset: {
|
|
let isOffsetBase = ((!taskSwitcherState.wasInActiveTask && !taskSwitcherHelpers.taskDrawerOpened) || !isCurrentTask) ? (taskSwitcherHelpers.isInTaskScrubMode && taskSwitcherHelpers.notHomeScreenState ? 0 : 1) : 0
|
|
let isOffsetTaskDrawer = (currentIndex == taskSwitcherHelpers.currentDisplayTask ? 0 : 1)
|
|
return taskSwitcherHelpers.taskDrawerOpened ? isOffsetTaskDrawer : isOffsetBase
|
|
}
|
|
|
|
// how far the task needs to travel to be off screen
|
|
readonly property real scrollXOffset: Math.abs(taskSwitcherHelpers.xPositionFromTaskIndex(taskSwitcherHelpers.currentDisplayTask) - (taskSwitcherState.xPosition + (taskSwitcherState.touchXPosition / taskSwitcherHelpers.currentScale)))
|
|
readonly property real offScreenOffset: (taskSwitcherHelpers.windowWidth * (((taskSwitcherHelpers.notHomeScreenState ? taskSwitcherState.touchXPosition : 0) * offsetDir * ((homeTouchPositionX == 0) ? 1 : 0) + (taskSwitcherHelpers.windowWidth / 2)) / taskSwitcherHelpers.windowWidth));
|
|
|
|
|
|
// calculate the actual task offset
|
|
readonly property real taskOffset: ((offScreenOffset + (taskSwitcherHelpers.notHomeScreenState ? scrollXOffset : 0)) / taskSwitcherHelpers.currentScale - (homeTouchPositionX * (1 - Math.max(0, Math.min(1, (taskOffsetNormalized - taskSwitcherHelpers.peekOffsetValue) / (taskSwitcherHelpers.homeOffsetValue - taskSwitcherHelpers.peekOffsetValue)))))) * taskOffsetNormalized * isOffScreenOffset * offsetDir
|
|
|
|
// extra resistance calculated for non-current task in the task drawer
|
|
readonly property real nonCurrentScaleResistance: ((isCurrentTask && taskSwitcherHelpers.notHomeScreenState) || taskSwitcherHelpers.fromButton) ? 0 : 1 - Math.min(taskSwitcherHelpers.currentScale, 1)
|
|
readonly property real nonCurrentScaleXOffset: (isCurrentTask && taskSwitcherHelpers.notHomeScreenState) ? 0 : ((taskSwitcherHelpers.taskWidth) * (scale - 1) * (currentIndex - taskSwitcherHelpers.currentDisplayTask))
|
|
readonly property real nonCurrentXPositionResistance: (isCurrentTask && taskSwitcherHelpers.notHomeScreenState) ? 0 : (taskSwitcherHelpers.taskWidth * (scale - 1)) * (taskSwitcherHelpers.notHomeScreenState ? 0.25 : 1.0) * offsetDir
|
|
readonly property real nonCurrentYPositionResistance: (isCurrentTask && taskSwitcherHelpers.notHomeScreenState) ? 0 : ((taskSwitcher.height / 2)) * nonCurrentScaleResistance
|
|
|
|
// this is the actual displayed x-position on screen
|
|
x: listX + repeater.leftMargin - taskSwitcherState.xPosition - taskOffset - nonCurrentScaleXOffset + nonCurrentXPositionResistance
|
|
y: ((taskSwitcherState.wasInActiveTask || taskSwitcherHelpers.taskDrawerOpened) ? root.taskY + nonCurrentYPositionResistance * 0.5: root.taskY / (taskSwitcherHelpers.fromButton ? 1 : (1 + taskOffsetNormalized * 0.075))) // add more resistance when not the current task
|
|
|
|
scale: ((isCurrentTask && taskSwitcherHelpers.notHomeScreenState) || taskSwitcherHelpers.fromButton) ? 1 : (1 + nonCurrentScaleResistance) * (1 + taskOffsetNormalized * 0.075) // add more resistance when not the current task and resist even further if the task is offset
|
|
|
|
// ensure current task is above others
|
|
z: isCurrentTask ? 1 : 0
|
|
|
|
// only show header once task switcher is opened
|
|
showHeader: !taskSwitcherState.gestureInProgress && !taskSwitcherHelpers.currentlyBeingClosed && !taskSwitcherHelpers.isInTaskScrubMode
|
|
|
|
// darken effect as task gets away from the center of the screen
|
|
darken: {
|
|
const distFromCentreProgress = Math.abs(x - repeater.leftMargin - (taskSwitcherHelpers.currentlyBeingOpened || taskSwitcherHelpers.currentlyBeingClosed ? (taskSwitcherHelpers.xPositionFromTaskIndex(taskSwitcherHelpers.currentDisplayTask)) - taskSwitcherState.xPosition : 0)) / taskSwitcherHelpers.taskWidth;
|
|
const upperBoundAdjust = Math.min(0.25, distFromCentreProgress) - 0.2;
|
|
return Math.max(0, upperBoundAdjust);
|
|
}
|
|
|
|
// fade out as the task closes
|
|
opacity: taskSwitcherHelpers.closingFactor
|
|
|
|
// update count of tasks being interacted with, so we know whether we are in a swipe up action
|
|
onInteractingActiveChanged: {
|
|
let offset = interactingActive ? 1 : -1;
|
|
taskInteractingCount = Math.max(0, taskInteractingCount + offset);
|
|
}
|
|
|
|
width: taskSwitcherHelpers.taskWidth
|
|
height: taskSwitcherHelpers.taskHeight
|
|
previewWidth: taskSwitcherHelpers.previewWidth
|
|
previewHeight: taskSwitcherHelpers.previewHeight
|
|
|
|
taskSwitcher: root.taskSwitcher
|
|
}
|
|
}
|
|
}
|