mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23:09 +00:00
taskswitcher: Gesture Navigation: Quality of Life Improvements
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.
This commit is contained in:
parent
971879f10f
commit
fdc8958ce5
5 changed files with 523 additions and 157 deletions
|
|
@ -25,6 +25,9 @@ Flickable {
|
|||
// update position from horizontal flickable movement
|
||||
property real oldContentX
|
||||
onContentXChanged: {
|
||||
// disable if animations are running to prevent bugs
|
||||
if (taskSwitcherHelpers.currentlyBeingClosed) {return}
|
||||
|
||||
if (moving) {
|
||||
// TODO whenever flicking actually works this should probably be swapped with
|
||||
// a minimum velocity after which it should snap to the nearest task
|
||||
|
|
@ -35,14 +38,17 @@ Flickable {
|
|||
}
|
||||
|
||||
onMovementStarted: {
|
||||
if (taskSwitcherHelpers.currentlyBeingClosed) {return}
|
||||
taskSwitcherHelpers.cancelAnimations();
|
||||
}
|
||||
onMovementEnded: {
|
||||
if (taskSwitcherHelpers.currentlyBeingClosed) {return}
|
||||
taskSwitcherHelpers.snapToNearestTaskWorkaround(movingRight);
|
||||
resetPosition();
|
||||
}
|
||||
|
||||
onFlickStarted: {
|
||||
if (taskSwitcherHelpers.currentlyBeingClosed) {return}
|
||||
root.cancelFlick();
|
||||
}
|
||||
onFlickEnded: {
|
||||
|
|
@ -51,6 +57,7 @@ Flickable {
|
|||
}
|
||||
|
||||
onDraggingChanged: {
|
||||
if (taskSwitcherHelpers.currentlyBeingClosed) {return}
|
||||
if (dragging) {
|
||||
taskSwitcherHelpers.cancelAnimations();
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ Item {
|
|||
|
||||
readonly property real dragOffset: -control.y
|
||||
|
||||
readonly property int currentIndex: model.index
|
||||
|
||||
// whether this task is being interacted with
|
||||
readonly property bool interactingActive: control.pressed && control.passedDragThreshold
|
||||
|
||||
|
|
@ -136,6 +138,7 @@ Item {
|
|||
to: 0
|
||||
onFinished: {
|
||||
if (to != 0) { // close app
|
||||
taskSwitcherHelpers.lastClosedTask = currentIndex;
|
||||
delegate.closeApp();
|
||||
uncloseTimer.start();
|
||||
}
|
||||
|
|
@ -182,7 +185,10 @@ Item {
|
|||
icon.name: "window-close"
|
||||
icon.width: Kirigami.Units.iconSizes.smallMedium
|
||||
icon.height: Kirigami.Units.iconSizes.smallMedium
|
||||
onClicked: delegate.closeApp()
|
||||
onClicked: {
|
||||
taskSwitcherHelpers.lastClosedTask = currentIndex;
|
||||
delegate.closeApp()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,31 +23,22 @@ MouseArea {
|
|||
property int taskInteractingCount: 0
|
||||
|
||||
// account for system header and footer offset (center the preview image)
|
||||
readonly property real taskY: {
|
||||
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 - trackFingerYOffset;
|
||||
return baseY + diff / 2 - shellTopMargin;
|
||||
}
|
||||
readonly property real trackFingerYOffset: {
|
||||
if (taskSwitcherHelpers.isScaleClamped) {
|
||||
let openedPos = taskSwitcherHelpers.openedYPosition;
|
||||
let directTrackingOffset = openedPos * 0.2
|
||||
if (taskSwitcherState.yPosition < openedPos + directTrackingOffset) {
|
||||
// Allow the task list to move further up than the fully opened position
|
||||
return taskSwitcherState.yPosition - openedPos;
|
||||
} else {
|
||||
// but make it more reluctant the further up it goes
|
||||
let overDragProgress = (taskSwitcherState.yPosition - directTrackingOffset - openedPos) / openedPos;
|
||||
// Base formula is 1-2.3^(-progress) which asymptotically approaches 1
|
||||
return (1 - Math.pow(2.3, -overDragProgress)) * openedPos + directTrackingOffset;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
readonly property real taskY: {
|
||||
let trackFingerYOffsetClamped = 0;
|
||||
if (taskSwitcherHelpers.isScaleClamped && (taskSwitcherState.wasInActiveTask || taskSwitcherHelpers.taskDrawerOpened)) {
|
||||
trackFingerYOffsetClamped = taskSwitcherHelpers.trackFingerYOffset;
|
||||
}
|
||||
|
||||
return taskYBase - trackFingerYOffsetClamped;
|
||||
}
|
||||
|
||||
function getTaskAt(index) {
|
||||
|
|
@ -82,6 +73,59 @@ MouseArea {
|
|||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
@ -95,7 +139,8 @@ MouseArea {
|
|||
}
|
||||
|
||||
onPressedChanged: {
|
||||
if (!taskSwitcherState.currentlyBeingOpened && pressed) {
|
||||
// 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();
|
||||
}
|
||||
|
|
@ -111,9 +156,10 @@ MouseArea {
|
|||
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);
|
||||
property real listX: taskSwitcherHelpers.xPositionFromTaskIndex(currentIndex)
|
||||
Behavior on listX {
|
||||
NumberAnimation {
|
||||
duration: Kirigami.Units.longDuration
|
||||
|
|
@ -121,23 +167,62 @@ MouseArea {
|
|||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
y: root.taskY
|
||||
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: taskSwitcherState.currentTaskIndex === currentIndex ? 1 : 0
|
||||
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.taskWidth;
|
||||
const upperBoundAdjust = Math.min(0.5, distFromCentreProgress) - 0.2;
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -53,12 +53,27 @@ FocusScope {
|
|||
// keep track of task list events
|
||||
property int oldTasksCount: tasksCount
|
||||
onTasksCountChanged: {
|
||||
// we need to subtract 1 from the current index when the closed task index is smaller
|
||||
// this is because this part of the list has been shifted down by 1 when the closed task was removed.
|
||||
if (taskSwitcherHelpers.lastClosedTask < state.currentTaskIndex) {
|
||||
state.currentTaskIndex -= 1;
|
||||
|
||||
// animated at the same speed as the task x position in the TaskList so that the task appears not to move from the perspective of the user.
|
||||
taskSwitcherHelpers.animateGoToTaskIndex(state.currentTaskIndex, Kirigami.Units.longDuration, Easing.InOutQuad);
|
||||
taskSwitcherHelpers.lastClosedTask = -1;
|
||||
}
|
||||
|
||||
if (tasksCount === 0 && oldTasksCount !== 0) {
|
||||
hide();
|
||||
} else if (tasksCount < oldTasksCount && state.currentTaskIndex >= tasksCount) {
|
||||
} else if (tasksCount < oldTasksCount) {
|
||||
if (state.currentTaskIndex < 0) {
|
||||
// if the user is on the frist task, and it is closed, scroll right
|
||||
taskSwitcherHelpers.animateGoToTaskIndex(0, Kirigami.Units.longDuration);
|
||||
} else if (state.currentTaskIndex >= tasksCount) {
|
||||
// if the user is on the last task, and it is closed, scroll left
|
||||
taskSwitcherHelpers.animateGoToTaskIndex(tasksCount - 1, Kirigami.Units.longDuration);
|
||||
}
|
||||
}
|
||||
|
||||
oldTasksCount = tasksCount;
|
||||
}
|
||||
|
|
@ -66,20 +81,60 @@ FocusScope {
|
|||
Keys.onEscapePressed: hide();
|
||||
|
||||
Component.onCompleted: {
|
||||
initialSetup();
|
||||
}
|
||||
|
||||
function initialSetup() {
|
||||
taskSwitcherHelpers.cancelAnimations();
|
||||
state.updateWasInActiveTask(KWinComponents.Workspace.activeWindow);
|
||||
|
||||
// task index from last time using the switcher
|
||||
// ensure the task drawer is not opened and reset values to defaults
|
||||
taskSwitcherHelpers.reachedHeightThreshold = false;
|
||||
taskSwitcherHelpers.gestureState = TaskSwitcherHelpers.GestureStates.Undecided;
|
||||
taskSwitcherHelpers.isInTaskScrubMode = false;
|
||||
|
||||
taskSwitcherHelpers.hasVibrated = false;
|
||||
|
||||
taskSwitcherHelpers.closingFactor = 1;
|
||||
|
||||
taskSwitcherHelpers.taskSwitchCanLaunch = false;
|
||||
taskSwitchCanLaunchTimer.restart()
|
||||
|
||||
taskList.taskOffsetEasing = Easing.InOutQuart;
|
||||
taskList.homeTouchPositionX = 0;
|
||||
|
||||
backgroundColorOpacityAn.enabled = false;
|
||||
backgroundColorOpacity = state.wasInActiveTask ? 1 : 0;
|
||||
backgroundColorOpacityAn.enabled = true;
|
||||
|
||||
// reset the offset to have the task drawer off screen
|
||||
taskList.setTaskOffsetValue(state.wasInActiveTask ? taskSwitcherHelpers.taskOffsetValue : taskSwitcherHelpers.homeOffsetValue, true);
|
||||
|
||||
// task index from the last time using the switcher
|
||||
state.initialTaskIndex = Math.min(state.currentTaskIndex, tasksCount - 1);
|
||||
if (state.wasInActiveTask) {
|
||||
// if we were in an active task instead set initial task index to the position of that task
|
||||
state.initialTaskIndex = taskSwitcherHelpers.getTaskIndexFromWindow(KWinComponents.Workspace.activeWindow);
|
||||
} else {
|
||||
// reset the task index to the start if on home screen
|
||||
state.initialTaskIndex = 0
|
||||
}
|
||||
state.currentTaskIndex = state.initialTaskIndex
|
||||
|
||||
taskSwitcherHelpers.currentDisplayTask = state.currentTaskIndex;
|
||||
|
||||
taskSwitcherHelpers.goToTaskIndex(state.initialTaskIndex);
|
||||
taskList.minimizeAll();
|
||||
|
||||
// fully open the switcher (if this is a button press, not gesture)
|
||||
if (!root.state.gestureInProgress) {
|
||||
taskSwitcherHelpers.fromButton = true;
|
||||
if (state.wasInActiveTask) {
|
||||
taskList.setTaskOffsetValue(0, true);
|
||||
} else {
|
||||
taskList.setTaskOffsetValue(0);
|
||||
}
|
||||
backgroundColorOpacity = 1;
|
||||
taskSwitcherHelpers.open();
|
||||
}
|
||||
}
|
||||
|
|
@ -102,8 +157,9 @@ FocusScope {
|
|||
|
||||
// task scrub mode allows scrubbing through a number of tasks with a mostly horizontal motion
|
||||
function taskScrubMode() {
|
||||
taskList.setTaskOffsetValue(0, false, Easing.OutQuart);
|
||||
if (!taskSwitcherHelpers.isInTaskScrubMode) {
|
||||
taskSwitcherHelpers.isInTaskScrubMode = true;
|
||||
backgroundColorOpacity = 1;
|
||||
taskSwitcherHelpers.cancelAnimations();
|
||||
taskSwitcherHelpers.open();
|
||||
if (!taskSwitcherHelpers.hasVibrated) {
|
||||
|
|
@ -111,43 +167,140 @@ FocusScope {
|
|||
haptics.buttonVibrate();
|
||||
taskSwitcherHelpers.hasVibrated = true;
|
||||
}
|
||||
|
||||
}
|
||||
let newTaskIndex = Math.max(0, Math.min(tasksCount - 1, Math.floor(state.touchXPosition / taskSwitcherHelpers.taskScrubDistance) + state.initialTaskIndex));
|
||||
if (newTaskIndex != state.currentTaskIndex) {
|
||||
// TODO this makes sense, but makes scrub mode feel a bit weird
|
||||
// improve trigger distance logic for task scrub mode to fix
|
||||
let newTaskIndex = Math.max(0, Math.min(tasksCount - 1, Math.floor(state.touchXPosition / taskSwitcherHelpers.taskScrubDistance) + state.initialTaskIndex - (state.wasInActiveTask ? 0 : 1)));
|
||||
if (newTaskIndex != state.currentTaskIndex || !taskSwitcherHelpers.isInTaskScrubMode) {
|
||||
taskSwitcherHelpers.animateGoToTaskIndex(newTaskIndex);
|
||||
taskSwitcherHelpers.isInTaskScrubMode = true;
|
||||
}
|
||||
}
|
||||
|
||||
function onTouchPositionChanged() {
|
||||
let unmodifiedYposition = Math.abs(state.touchYPosition)
|
||||
if (taskSwitcherHelpers.isInTaskScrubMode || // once in scrub mode, let's not allow to go out, that can result in inconsistent UX
|
||||
(Math.abs(state.xVelocity) > Math.abs(state.yVelocity) * 3 && // gesture needs to be almost completely horizontal
|
||||
Math.abs(state.xVelocity) < 2.5 && // and not with a fast flick TODO! evaluate whether to keep this, it's kinda awkward
|
||||
Math.abs(state.touchXPosition) > taskSwitcherHelpers.taskScrubDistance * 0.95 && // and have moved far enough sideways
|
||||
state.yPosition < taskSwitcherHelpers.undoYThreshold && // and be close to the screen edge
|
||||
tasksCount > 1 // and there needs to be more than one task open
|
||||
unmodifiedYposition < Kirigami.Units.largeSpacing * 2 && // and be close to the screen edge
|
||||
tasksCount > 0 && // and there needs to be more than none task open
|
||||
!taskSwitcherHelpers.taskDrawerOpened // and the task drawer must not be open
|
||||
)) {
|
||||
taskScrubMode();
|
||||
} else {
|
||||
if (state.status == TaskSwitcherData.TaskSwitcherState.Active) {
|
||||
// task switcher is already open
|
||||
// TODO add some sort of feedback for dismissing task switcher (maybe opacity reduction?)
|
||||
return;
|
||||
if (taskSwitcherHelpers.currentlyBeingClosed) {
|
||||
// if the task switch is still open but playing the close animation
|
||||
// setup some values and return to the initial setup so that the user can always navigate with no down time
|
||||
state.wasInActiveTask = taskSwitcherHelpers.openAppAnim.running ? true : false
|
||||
taskList.setTaskOffsetValue(state.wasInActiveTask ? taskSwitcherHelpers.taskOffsetValue : taskSwitcherHelpers.homeOffsetValue, true);
|
||||
state.status = !state.wasInActiveTask ? (taskSwitcherHelpers.openAppAnim.closeAnim && !taskSwitcherHelpers.taskDrawerWillOpen ? TaskSwitcherData.TaskSwitcherState.Active : TaskSwitcherData.TaskSwitcherState.Inactive) : TaskSwitcherData.TaskSwitcherState.Inactive
|
||||
initialSetup();
|
||||
} else if (taskSwitcherHelpers.openAnim.running) {
|
||||
taskSwitcherHelpers.cancelAnimations();
|
||||
state.status = taskSwitcherHelpers.stateClass.Active;
|
||||
}
|
||||
state.yPosition = Math.abs(state.touchYPosition);
|
||||
state.xPosition = taskSwitcherHelpers.xPositionFromTaskIndex(state.initialTaskIndex) - state.touchXPosition;
|
||||
|
||||
state.yPosition = unmodifiedYposition + (taskSwitcherHelpers.taskDrawerOpened || !state.wasInActiveTask ? taskSwitcherHelpers.openedYPosition : 0);
|
||||
|
||||
let newXPosition = taskSwitcherHelpers.xPositionFromTaskIndex(state.initialTaskIndex);
|
||||
if (taskSwitcherHelpers.notHomeScreenState && !taskSwitcherHelpers.currentlyBeingClosed) {
|
||||
newXPosition = newXPosition - (state.touchXPosition / taskSwitcherHelpers.currentScale);
|
||||
}
|
||||
state.xPosition = newXPosition;
|
||||
|
||||
// allows the user to move the task drawer left and right when on the home screen
|
||||
taskList.homeTouchPositionX = taskSwitcherHelpers.notHomeScreenState ? 0 : (state.touchXPosition * 0.35);
|
||||
|
||||
// dynamically update the task switcher state based off of the touch position and velocity
|
||||
updateTaskSwitcherState()
|
||||
}
|
||||
}
|
||||
|
||||
// actions on an upwards flick
|
||||
function upwardsFlick() {
|
||||
if (state.wasInActiveTask) {
|
||||
// go to homescreen if we were in an active task
|
||||
taskSwitcherHelpers.close();
|
||||
function updateTaskSwitcherState() {
|
||||
let unmodifiedYposition = Math.abs(state.touchYPosition)
|
||||
|
||||
// if the touch is above heightThreshold, set reachedHeightThreshold to true
|
||||
if (unmodifiedYposition > taskSwitcherHelpers.heightThreshold) {
|
||||
// set reachedHeightThreshold when above or below two separate points to helps prevent flickering when the task switcher moves in and out of view
|
||||
taskSwitcherHelpers.reachedHeightThreshold = true;
|
||||
backgroundColorOpacity = taskSwitcherHelpers.notHomeScreenState ? 0 : 1;
|
||||
} else if (unmodifiedYposition > taskSwitcherHelpers.undoYThreshold) {
|
||||
backgroundColorOpacity = 1;
|
||||
} else {
|
||||
// or normally open task switcher if we were on the homescreen already
|
||||
taskSwitcherHelpers.animateGoToTaskIndex(state.currentTaskIndex);
|
||||
taskSwitcherHelpers.open();
|
||||
backgroundColorOpacity = taskSwitcherHelpers.notHomeScreenState ? 1 : 0;
|
||||
}
|
||||
|
||||
if (state.totalSquaredVelocity > state.flickVelocityThreshold) {
|
||||
// flick
|
||||
// ratio between y and x velocity as threshold between vertical and horizontal flick
|
||||
let xyVelocityRatio = 1.7; // with 1.7 swipes up to ~60° from horizontal are counted as horizontal
|
||||
if (state.yVelocity > Math.abs(state.xVelocity) * xyVelocityRatio) {
|
||||
// downwards flick
|
||||
setTaskDrawerState(TaskSwitcherHelpers.GestureStates.Undecided);
|
||||
if (unmodifiedYposition < taskSwitcherHelpers.undoYThreshold) {
|
||||
taskList.setTaskOffsetValue(taskSwitcherHelpers.notHomeScreenState ? 0 : taskSwitcherHelpers.homeOffsetValue);
|
||||
}
|
||||
} else if (-state.yVelocity > Math.abs(state.xVelocity) * xyVelocityRatio || (taskSwitcherHelpers.reachedHeightThreshold && taskSwitcherHelpers.notHomeScreenState)) {
|
||||
// upwards flick or if the touch is above heightThreshold
|
||||
if (taskSwitcherHelpers.notHomeScreenState) {
|
||||
// if in app or task switcher, go home
|
||||
setTaskDrawerState(TaskSwitcherHelpers.GestureStates.Home);
|
||||
if (taskSwitcherHelpers.reachedHeightThreshold) {
|
||||
taskList.setTaskOffsetValue(taskSwitcherHelpers.taskOffsetValue);
|
||||
}
|
||||
} else if (unmodifiedYposition > taskSwitcherHelpers.undoYThreshold) {
|
||||
// else, keep the task switcher in view
|
||||
setTaskDrawerState(TaskSwitcherHelpers.GestureStates.TaskSwitcher);
|
||||
taskList.setTaskOffsetValue(taskSwitcherHelpers.peekOffsetValue);
|
||||
}
|
||||
} else if (!taskSwitcherHelpers.reachedHeightThreshold && !taskSwitcherHelpers.isInTaskScrubMode) {
|
||||
// sideways flick
|
||||
if (taskSwitcherHelpers.notHomeScreenState) {
|
||||
taskList.setTaskOffsetValue(0, unmodifiedYposition < taskSwitcherHelpers.openedYPosition ? true : false);
|
||||
}
|
||||
setTaskDrawerState(TaskSwitcherHelpers.GestureStates.HorizontalSwipe);
|
||||
}
|
||||
} else {
|
||||
if (unmodifiedYposition > taskSwitcherHelpers.undoYThreshold) {
|
||||
// if just moveing out of undoYThreshold, set the state to home
|
||||
if (taskSwitcherHelpers.gestureState < TaskSwitcherHelpers.GestureStates.TaskSwitcher) {
|
||||
setTaskDrawerState(TaskSwitcherHelpers.GestureStates.Home)
|
||||
}
|
||||
// if the touch is above heightThreshold, it will retrun home
|
||||
if (unmodifiedYposition > taskSwitcherHelpers.heightThreshold) {
|
||||
taskSwitcherHelpers.hasVibrated = true;
|
||||
if (taskSwitcherHelpers.notHomeScreenState) {
|
||||
// move the task switcher out of view
|
||||
setTaskDrawerState(TaskSwitcherHelpers.GestureStates.Home);
|
||||
taskList.setTaskOffsetValue(taskSwitcherHelpers.taskOffsetValue);
|
||||
} else {
|
||||
// keep the task switcher in view when above heightThreshold and from home
|
||||
setTaskDrawerState(TaskSwitcherHelpers.GestureStates.TaskSwitcher);
|
||||
taskList.setTaskOffsetValue(taskSwitcherHelpers.peekOffsetValue);
|
||||
}
|
||||
// minus largeSpacing from the heightThreshold to help prevent flickering when the task switcher moves in and out of view
|
||||
} else if ((unmodifiedYposition < taskSwitcherHelpers.heightThreshold - Kirigami.Units.largeSpacing) || taskSwitcherHelpers.reachedHeightThreshold == false) {
|
||||
// set reachedHeightThreshold when above or below two separate points to helps prevent flickering when the task switcher moves in and out of view
|
||||
taskSwitcherHelpers.reachedHeightThreshold = false;
|
||||
if (state.totalSquaredVelocity < state.flickVelocityThreshold && taskSwitcherHelpers.taskSwitchCanLaunch) {
|
||||
// if velocity is small enough, move the task switcher into view
|
||||
setTaskDrawerState(TaskSwitcherHelpers.GestureStates.TaskSwitcher);
|
||||
taskList.setTaskOffsetValue(taskSwitcherHelpers.notHomeScreenState ? 0 : taskSwitcherHelpers.peekOffsetValue);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if under the undo threshold, it will go back to the task switcher if it is open
|
||||
if (taskSwitcherHelpers.taskDrawerOpened) {
|
||||
taskSwitcherHelpers.reachedHeightThreshold = false;
|
||||
setTaskDrawerState(TaskSwitcherHelpers.GestureStates.TaskSwitcher)
|
||||
taskList.setTaskOffsetValue(0);
|
||||
} else {
|
||||
taskSwitcherHelpers.reachedHeightThreshold = false;
|
||||
setTaskDrawerState(TaskSwitcherHelpers.GestureStates.Undecided)
|
||||
taskList.setTaskOffsetValue(taskSwitcherHelpers.notHomeScreenState ? 0 : taskSwitcherHelpers.homeOffsetValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -162,26 +315,43 @@ FocusScope {
|
|||
// diagonal quick switch gesture logic
|
||||
function quickSwitch() {
|
||||
// should "quick switch" to adjacent app in task switcher, but only if we were in an app before
|
||||
let unmodifiedYposition = Math.abs(state.touchYPosition)
|
||||
let newIndex = state.currentTaskIndex;
|
||||
let shouldSwitch = false;
|
||||
if (state.xVelocity > 0 && state.wasInActiveTask) {
|
||||
// flick to the right, go to app to the left
|
||||
if (state.xVelocity > 0) {
|
||||
if (taskSwitcherHelpers.notHomeScreenState) {
|
||||
// flick to the right, go to the app on the left
|
||||
newIndex = state.currentTaskIndex + 1;
|
||||
}
|
||||
if (newIndex < tasksCount) {
|
||||
// switch only if flick doesn't go over end of list
|
||||
shouldSwitch = true;
|
||||
}
|
||||
} else if (state.xVelocity < 0 && state.wasInActiveTask) {
|
||||
} else if (state.xVelocity < 0) {
|
||||
if (taskSwitcherHelpers.notHomeScreenState) {
|
||||
// flick to the left, go to app to the right
|
||||
newIndex = state.currentTaskIndex - 1;
|
||||
if (newIndex >= 0) {
|
||||
// switch only if flick doesn't go over end of list
|
||||
shouldSwitch = true;
|
||||
}
|
||||
} else {
|
||||
// flick to the left on the home screen, dismiss the gesture
|
||||
taskSwitcherHelpers.close();
|
||||
retrun;
|
||||
}
|
||||
}
|
||||
if (shouldSwitch) {
|
||||
if (!taskSwitcherHelpers.taskDrawerOpened && unmodifiedYposition < taskSwitcherHelpers.openedYPosition) {
|
||||
// if in a app, switch it to the new task when it is under the openedYPosition
|
||||
taskList.setTaskOffsetValue(0, unmodifiedYposition < taskSwitcherHelpers.openedYPosition && taskSwitcherHelpers.notHomeScreenState);
|
||||
let appAtNewIndex = taskList.getTaskAt(newIndex).window;
|
||||
taskSwitcherHelpers.openApp(newIndex, appAtNewIndex, Kirigami.Units.longDuration * 4, Easing.OutExpo);
|
||||
} else {
|
||||
// if already in the task switcher or above the openedYPosition, only change the focus to the new task
|
||||
taskSwitcherHelpers.animateGoToTaskIndex(newIndex);
|
||||
taskSwitcherHelpers.open();
|
||||
}
|
||||
} else {
|
||||
// if not switching, just open task switcher
|
||||
taskSwitcherHelpers.animateGoToTaskIndex(state.currentTaskIndex);
|
||||
|
|
@ -191,75 +361,82 @@ FocusScope {
|
|||
|
||||
// Logic for deciding how to handle the end of a gesture input
|
||||
function onGestureInProgressChanged() {
|
||||
taskSwitcherHelpers.fromButton = false;
|
||||
if (state.gestureInProgress) {
|
||||
taskSwitcherHelpers.currentDisplayTask = state.currentTaskIndex;
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.status == TaskSwitcherData.TaskSwitcherState.Active) {
|
||||
if (taskSwitcherHelpers.isInTaskScrubMode) {
|
||||
if (taskList.count === 0) {
|
||||
// dismiss the gesture if the task list is empty
|
||||
taskSwitcherHelpers.close();
|
||||
} if (taskSwitcherHelpers.isInTaskScrubMode) {
|
||||
// TODO! do we want to handle upwards flick to dismiss in task scrub mode?
|
||||
// TODO do we want to show a list of thumbnails in task scrub mode?
|
||||
let unmodifiedYposition = Math.abs(state.touchYPosition)
|
||||
backgroundColorOpacity = 1;
|
||||
if (taskSwitcherHelpers.taskDrawerOpened || unmodifiedYposition > taskSwitcherHelpers.undoYThreshold) {
|
||||
taskSwitcherHelpers.animateGoToTaskIndex(state.currentTaskIndex);
|
||||
taskSwitcherHelpers.open();
|
||||
taskSwitcherHelpers.isInTaskScrubMode = false;
|
||||
} else {
|
||||
taskSwitcherHelpers.openApp(state.currentTaskIndex, taskList.getTaskAt(state.currentTaskIndex).window);
|
||||
} else if (state.yPosition > taskSwitcherHelpers.undoYThreshold) {
|
||||
// close task switcher if it was already open but only if swipe was higher than the undo threshold
|
||||
}
|
||||
} else if (taskSwitcherHelpers.gestureState == TaskSwitcherHelpers.GestureStates.Undecided) {
|
||||
if (taskSwitcherHelpers.taskDrawerOpened) {
|
||||
// if in the task switcher, return to it
|
||||
taskSwitcherHelpers.animateGoToTaskIndex(state.currentTaskIndex);
|
||||
taskSwitcherHelpers.open();
|
||||
} else if (state.wasInActiveTask) {
|
||||
// if inside a app, return to it
|
||||
returnToApp();
|
||||
} else {
|
||||
// else dismiss the gesture
|
||||
taskSwitcherHelpers.close();
|
||||
return;
|
||||
}
|
||||
} else if (state.status == TaskSwitcherData.TaskSwitcherState.Inactive) {
|
||||
if (state.totalSquaredVelocity > state.flickVelocityThreshold) {
|
||||
// flick
|
||||
// ratio between y and x velocity as threshold between vertical and horizontal flick
|
||||
let xyVelocityRatio = 1.7; // with 1.7 swipes up to ~60° from horizontal are counted as horizontal
|
||||
if (-state.yVelocity > Math.abs(state.xVelocity) * xyVelocityRatio) {
|
||||
upwardsFlick();
|
||||
} else if (state.yVelocity > Math.abs(state.xVelocity) * xyVelocityRatio) {
|
||||
// downwards flick
|
||||
returnToApp();
|
||||
} else {
|
||||
} else if (taskSwitcherHelpers.gestureState == TaskSwitcherHelpers.GestureStates.HorizontalSwipe) {
|
||||
// sideways flick
|
||||
backgroundColorOpacity = 1;
|
||||
quickSwitch();
|
||||
}
|
||||
} else {
|
||||
// no flick
|
||||
if (state.yPosition > taskSwitcherHelpers.undoYThreshold) {
|
||||
// normal task switcher open
|
||||
} else if (taskSwitcherHelpers.gestureState == TaskSwitcherHelpers.GestureStates.TaskSwitcher) {
|
||||
// open the task drawer
|
||||
backgroundColorOpacity = 1;
|
||||
taskSwitcherHelpers.animateGoToTaskIndex(state.currentTaskIndex);
|
||||
taskSwitcherHelpers.open();
|
||||
} else {
|
||||
// no flick and not enough activation to go to task switcher
|
||||
if (state.wasInActiveTask) {
|
||||
returnToApp();
|
||||
} else {
|
||||
// do open switcher in case we were on homescreen before
|
||||
taskSwitcherHelpers.animateGoToTaskIndex(state.currentTaskIndex);
|
||||
state.yPosition = taskSwitcherHelpers.openedYPosition;
|
||||
taskSwitcherHelpers.open();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} else if (taskSwitcherHelpers.gestureState == TaskSwitcherHelpers.GestureStates.Home) {
|
||||
taskSwitcherHelpers.close();
|
||||
}
|
||||
}
|
||||
|
||||
function onVelocityChanged() {
|
||||
if (!taskSwitcherHelpers.hasVibrated) {
|
||||
if (!state.wasInActiveTask ||
|
||||
(state.wasInActiveTask &&
|
||||
state.yPosition > taskSwitcherHelpers.undoYThreshold &&
|
||||
state.totalSquaredVelocity < state.flickVelocityThreshold)) {
|
||||
// Haptic feedback when conditions are met for the task switcher to open
|
||||
haptics.buttonVibrate();
|
||||
taskSwitcherHelpers.hasVibrated = true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function onXPositionChanged() {
|
||||
taskSwitcherHelpers.updateTaskIndex();
|
||||
}
|
||||
}
|
||||
|
||||
// kind of a hack, but this prevents the gesture from immediately activting the task switcher when it is not supposed to
|
||||
Timer {
|
||||
id: taskSwitchCanLaunchTimer
|
||||
interval: 1; running: true; repeat: false
|
||||
onTriggered: taskSwitcherHelpers.taskSwitchCanLaunch = true;
|
||||
}
|
||||
|
||||
function setTaskDrawerState(value) {
|
||||
if (taskSwitcherHelpers.gestureState != TaskSwitcherHelpers.GestureStates.TaskSwitcher && value == TaskSwitcherHelpers.GestureStates.TaskSwitcher) {
|
||||
// vibrate only if switching to task drawer
|
||||
if (!taskSwitcherHelpers.hasVibrated) {
|
||||
// Haptic feedback when the task scrub mode engages
|
||||
haptics.buttonVibrate();
|
||||
taskSwitcherHelpers.hasVibrated = true;
|
||||
}
|
||||
|
||||
}
|
||||
taskSwitcherHelpers.gestureState = value;
|
||||
}
|
||||
|
||||
// view of the desktop background
|
||||
KWinComponents.DesktopBackground {
|
||||
id: backgroundItem
|
||||
|
|
@ -275,13 +452,17 @@ FocusScope {
|
|||
|
||||
opacity: container.opacity
|
||||
color: {
|
||||
// animate background colour only if we are *not* opening from the homescreen
|
||||
if (state.wasInActiveTask || !state.currentlyBeingOpened) {
|
||||
return Qt.rgba(0, 0, 0, 0.6);
|
||||
} else {
|
||||
return Qt.rgba(0, 0, 0, 0.6 * Math.min(1, state.yPosition / state.openedYPosition));
|
||||
return Qt.rgba(0, 0, 0, 0.6 * taskSwitcherHelpers.closingFactor * backgroundColorOpacity);
|
||||
}
|
||||
}
|
||||
|
||||
// animate the background opacity based off of the state.
|
||||
property real backgroundColorOpacity: 1
|
||||
Behavior on backgroundColorOpacity {
|
||||
id: backgroundColorOpacityAn
|
||||
NumberAnimation {
|
||||
duration: Kirigami.Units.longDuration
|
||||
}
|
||||
}
|
||||
|
||||
// status bar
|
||||
|
|
@ -413,7 +594,11 @@ FocusScope {
|
|||
ColumnLayout {
|
||||
id: placeholder
|
||||
spacing: Kirigami.Units.gridUnit
|
||||
opacity: (root.tasksCount === 0 && !taskSwitcherHelpers.currentlyBeingClosed) ? 0.9 : 0
|
||||
|
||||
opacity: {
|
||||
let baseOpacity = ((root.tasksCount === 0 && !taskSwitcherHelpers.currentlyBeingClosed) ? 0.9 : 0);
|
||||
return taskSwitcherHelpers.gestureState == TaskSwitcherHelpers.GestureStates.TaskSwitcher ? baseOpacity : 0;
|
||||
}
|
||||
Behavior on opacity { NumberAnimation { duration: 500 } }
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
|
@ -549,22 +734,22 @@ FocusScope {
|
|||
x: flickable.contentX
|
||||
width: flickable.width
|
||||
height: flickable.height
|
||||
z: 1
|
||||
}
|
||||
|
||||
PlasmaComponents.ToolButton {
|
||||
id: closeAllButton
|
||||
property bool closeRequested: false
|
||||
visible: root.tasksCount !== 0 && !taskSwitcherHelpers.isInTaskScrubMode
|
||||
enabled: !taskSwitcherHelpers.currentlyBeingClosed && !root.state.gestureInProgress
|
||||
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
bottomMargin: (taskList.taskY + taskList.trackFingerYOffset) / 2
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
bottomMargin: (taskList.taskYBase) * 0.75
|
||||
horizontalCenter: taskList.horizontalCenter
|
||||
}
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||
Kirigami.Theme.inherit: false
|
||||
|
||||
opacity: (taskSwitcherHelpers.currentlyBeingClosed) ? 0.0 : 1.0
|
||||
opacity: (taskSwitcherHelpers.currentlyBeingClosed || root.state.gestureInProgress || !taskSwitcherHelpers.taskDrawerOpened) ? 0.0 : 1.0
|
||||
Behavior on opacity { NumberAnimation { duration: Kirigami.Units.shortDuration } }
|
||||
|
||||
icon.name: "edit-clear-history"
|
||||
|
|
@ -582,6 +767,5 @@ FocusScope {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import QtQuick 2.15
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
import org.kde.private.mobileshell.taskswitcher 1.0 as TaskSwitcherData
|
||||
|
||||
import org.kde.kwin 3.0 as KWinComponents
|
||||
|
||||
|
|
@ -14,11 +15,27 @@ QtObject {
|
|||
id: root
|
||||
|
||||
// TaskSwitcher item component
|
||||
// We assume that the taskSwitcher the size of the entire screen.
|
||||
// We assume that the taskSwitcher is the size of the entire screen.
|
||||
required property var taskSwitcher
|
||||
property var state: taskSwitcher.state
|
||||
required property var stateClass
|
||||
|
||||
// task switcher peek and pop setting for when it is toggled from the home screen
|
||||
readonly property real peekOffsetValue: 1.85
|
||||
readonly property real homeOffsetValue: 2.6
|
||||
readonly property real taskOffsetValue: 1.5
|
||||
|
||||
// the index of the last task to be closed
|
||||
// this will get reset to -1 in TaskSwitcher.qml when the TasksCount changes
|
||||
property int lastClosedTask: -1
|
||||
|
||||
// this will only gets set to the currentTaskIndex when the gesture starts
|
||||
// this helps remove visual glitches as the gesture animations play out and the currentTaskIndex changes
|
||||
property int currentDisplayTask: state.currentTaskIndex
|
||||
|
||||
// how much the the task will resist shrinking
|
||||
// value of 1 will match the y position resistance one to one
|
||||
readonly property real scaleResistance: 0.5
|
||||
|
||||
// direction of the movement
|
||||
readonly property bool gestureMovingRight: state.xVelocity > 0
|
||||
|
|
@ -33,10 +50,37 @@ QtObject {
|
|||
// yPosition threshold below which opening the task switcher should be undone and returned to the previously active task
|
||||
readonly property real undoYThreshold: openedYPosition / 2
|
||||
|
||||
// the height threshold where if the yPosition is above this value the task switch will return home
|
||||
readonly property real heightThreshold: windowHeight * 0.55
|
||||
|
||||
// whether the switcher is opened or not
|
||||
readonly property bool taskDrawerOpened: state.status == TaskSwitcherData.TaskSwitcherState.Active
|
||||
|
||||
// This is true when the task drawer is already opened or if within an app
|
||||
readonly property bool notHomeScreenState: state.wasInActiveTask || taskDrawerOpened
|
||||
|
||||
// set to true if the taskSwitcher is opened by the navbar button
|
||||
property bool fromButton: false
|
||||
|
||||
// gets set to true after 1 milliseconds
|
||||
property bool taskSwitchCanLaunch: false
|
||||
|
||||
// whether the switcher has already triggered haptic feedback or not
|
||||
// we don't want to continuously send haptics, just once is enough
|
||||
property bool hasVibrated: false
|
||||
|
||||
// The current gesture state to decide what will happpen when it is completed
|
||||
enum GestureStates {
|
||||
Undecided,
|
||||
HorizontalSwipe,
|
||||
TaskSwitcher,
|
||||
Home
|
||||
}
|
||||
property int gestureState: TaskSwitcherHelpers.GestureStates.Undecided
|
||||
|
||||
// if the touch has reached the height threshold
|
||||
property bool reachedHeightThreshold: false
|
||||
|
||||
// made as variables to keep x anim in task list and task scrub icon list in sync
|
||||
property int xAnimDuration: Kirigami.Units.longDuration * 2
|
||||
property int xAnimEasingType: Easing.OutExpo
|
||||
|
|
@ -60,6 +104,24 @@ QtObject {
|
|||
// height of the task preview header
|
||||
readonly property real taskHeaderHeight: Kirigami.Units.gridUnit * 2 + Kirigami.Units.smallSpacing * 2
|
||||
|
||||
// finger position y with resistance
|
||||
readonly property real trackFingerYOffset: {
|
||||
if (taskSwitcherHelpers.isScaleClamped) {
|
||||
let directTrackingOffset = openedYPosition * 0.2
|
||||
if (root.state.yPosition < openedYPosition + directTrackingOffset) {
|
||||
// Allow the task list to move further up than the fully opened position
|
||||
return root.state.yPosition - openedYPosition;
|
||||
} else {
|
||||
// but make it more reluctant the further up it goes
|
||||
let overDragProgress = (root.state.yPosition - directTrackingOffset - openedYPosition) / openedYPosition;
|
||||
// Base formula is 1-2.3^(-progress) which asymptotically approaches 1
|
||||
return (1 - Math.pow(2.3, -overDragProgress)) * openedYPosition + directTrackingOffset;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 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: {
|
||||
|
|
@ -75,27 +137,26 @@ QtObject {
|
|||
}
|
||||
}
|
||||
|
||||
// scaling factor during closing of the switcher
|
||||
// the closing factor during the closing of the switcher
|
||||
property real closingFactor: 1
|
||||
|
||||
// scaling factor during the closing of the switcher
|
||||
property real closingScalingFactor: 1
|
||||
|
||||
// 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) * Math.min(root.state.yPosition / openedYPosition, 1);
|
||||
let subtract = (maxScale - 1) * ((Math.min(root.state.yPosition, openedYPosition) + trackFingerYOffset * scaleResistance) / openedYPosition)
|
||||
let finalScale = Math.min(maxScale, maxScale - subtract);
|
||||
|
||||
// if closing scaling factor is below 1 we want it to override the other scale
|
||||
// to allow for a smoother closing animation
|
||||
if (closingScalingFactor < 1) {
|
||||
if (closingScalingFactor < 1 && (root.state.wasInActiveTask || root.taskDrawerOpened)) {
|
||||
return closingScalingFactor;
|
||||
}
|
||||
|
||||
// animate scale only if we are *not* opening from the homescreen
|
||||
if (root.state.wasInActiveTask || !root.state.gestureInProgress) {
|
||||
return finalScale;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
readonly property bool isScaleClamped: root.state.yPosition > openedYPosition
|
||||
|
||||
readonly property real taskScrubDistance: windowWidth / (2 * 6) // formula says how many tasks can be scrubbed through in half of the window width
|
||||
|
|
@ -106,10 +167,10 @@ QtObject {
|
|||
// cancel all animated moving, as another flick source is taking over
|
||||
signal cancelAnimations()
|
||||
onCancelAnimations: {
|
||||
openAnim.stop();
|
||||
openAppAnim.stop();
|
||||
closeAnim.stop();
|
||||
closeScaleAnim.stop();
|
||||
closeFactorAnim.stop();
|
||||
xAnim.stop();
|
||||
}
|
||||
|
||||
|
|
@ -130,22 +191,35 @@ QtObject {
|
|||
|
||||
// TODO either use updateTaskIndex to always have the "newest current task index" in the state var or use "getNearestTaskIndex", not both it's redundant
|
||||
function updateTaskIndex() {
|
||||
// only set if not gesture currently in progress to prevent glitching
|
||||
if (!(state.gestureInProgress || root.closeAnim.running || root.openAppAnim.running) || root.isInTaskScrubMode) {
|
||||
root.state.currentTaskIndex = getTaskIndexFromXPosition();
|
||||
}
|
||||
}
|
||||
|
||||
function open() {
|
||||
root.gestureState = TaskSwitcherHelpers.GestureStates.TaskSwitcher;
|
||||
openAnim.restart();
|
||||
|
||||
// update the task offset position
|
||||
taskList.setTaskOffsetValue(0, false, Easing.OutQuart);
|
||||
}
|
||||
|
||||
function close() {
|
||||
// update the task offset position
|
||||
taskList.setTaskOffsetValue(homeOffsetValue + 0.25, false, Easing.Linear);
|
||||
|
||||
root.gestureState = TaskSwitcherHelpers.GestureStates.Undecided;
|
||||
cancelAnimations();
|
||||
closingScalingFactor = currentScale;
|
||||
closeAnim.restart();
|
||||
closeScaleAnim.restart();
|
||||
closeFactorAnim.restart();
|
||||
}
|
||||
|
||||
function openApp(index, window, duration = Kirigami.Units.shortDuration, horizontalEasing = Easing.OutBack) {
|
||||
// cancel any opening animations ongoing
|
||||
openAnim.stop();
|
||||
cancelAnimations();
|
||||
|
||||
animateGoToTaskIndex(index, duration);
|
||||
|
|
@ -167,7 +241,7 @@ QtObject {
|
|||
function animateGoToTaskIndex(index, duration = Kirigami.Units.longDuration * 2, easing = Easing.OutExpo) {
|
||||
xAnimDuration = duration;
|
||||
xAnimEasingType = easing;
|
||||
xAnim.to = xPositionFromTaskIndex(index);
|
||||
xAnim.to = xPositionFromTaskIndex(index) - (gestureState == TaskSwitcherHelpers.GestureStates.HorizontalSwipe && !state.gestureInProgress && notHomeScreenState ? taskSpacing / 2 : 0);
|
||||
xAnim.restart();
|
||||
}
|
||||
|
||||
|
|
@ -232,12 +306,14 @@ QtObject {
|
|||
property: "yPosition"
|
||||
to: openedYPosition
|
||||
duration: 250
|
||||
easing.type: Easing.OutExpo
|
||||
easing.type: Easing.OutQuart
|
||||
|
||||
onFinished: {
|
||||
if (!isInTaskScrubMode) {
|
||||
root.state.status = stateClass.Active;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This animation should ideally be replaced by some
|
||||
// speed tracking to track finger movement better. Until then
|
||||
|
|
@ -267,6 +343,14 @@ QtObject {
|
|||
}
|
||||
}
|
||||
|
||||
property var closeFactorAnim: NumberAnimation {
|
||||
target: root
|
||||
property: "closingFactor"
|
||||
to: 0
|
||||
duration: Kirigami.Units.longDuration
|
||||
easing.type: Easing.InQuad
|
||||
}
|
||||
|
||||
property var openAppAnim: NumberAnimation {
|
||||
target: root.state
|
||||
property: "yPosition"
|
||||
|
|
|
|||
Loading…
Reference in a new issue