homescreen: Extract out state management to HomeScreenState and use single flickable

This commit is contained in:
Devin Lin 2021-12-30 19:49:08 -05:00
parent 46c1aa8fc8
commit 57a4a28fe3
22 changed files with 922 additions and 605 deletions

View file

@ -1,105 +0,0 @@
/*
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.4
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.3 as Controls
import QtGraphicalEffects 1.6
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.kquickcontrolsaddons 2.0
import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
MouseArea {
id: delegate
width: GridView.view.cellWidth
height: GridView.view.cellHeight
property int reservedSpaceForLabel
property alias iconItem: icon
signal launch(int x, int y, var source, string title, string storageId)
signal dragStarted(string imageSource, int x, int y, string mimeData)
onPressAndHold: {
delegate.grabToImage(function(result) {
delegate.Drag.imageSource = result.url
dragStarted(result.url, width/2, height/2, model.applicationStorageId)
})
}
propagateComposedEvents: true
onClicked: {
mouse.accepted = true
if (model.applicationRunning) {
delegate.launch(0, 0, "", model.applicationName, model.applicationStorageId);
} else {
delegate.launch(delegate.x + (PlasmaCore.Units.smallSpacing * 2), delegate.y + (PlasmaCore.Units.smallSpacing * 2), icon.source, model.applicationName, model.applicationStorageId);
}
}
//preventStealing: true
ColumnLayout {
anchors {
fill: parent
leftMargin: PlasmaCore.Units.smallSpacing * 2
topMargin: PlasmaCore.Units.smallSpacing * 2
rightMargin: PlasmaCore.Units.smallSpacing * 2
bottomMargin: PlasmaCore.Units.smallSpacing * 2
}
spacing: 0
PlasmaCore.IconItem {
id: icon
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
Layout.fillWidth: true
Layout.minimumHeight: parent.height - delegate.reservedSpaceForLabel
Layout.preferredHeight: Layout.minimumHeight
usesPlasmaTheme: false
source: model.applicationIcon
Rectangle {
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
}
visible: model.applicationRunning
radius: width
width: PlasmaCore.Units.smallSpacing
height: width
color: PlasmaCore.Theme.highlightColor
}
}
PlasmaComponents.Label {
id: label
visible: text.length > 0
Layout.fillWidth: true
Layout.preferredHeight: delegate.reservedSpaceForLabel
wrapMode: Text.WordWrap
Layout.leftMargin: -parent.anchors.leftMargin + PlasmaCore.Units.smallSpacing
Layout.rightMargin: -parent.anchors.rightMargin + PlasmaCore.Units.smallSpacing
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignTop
maximumLineCount: 2
elide: Text.ElideRight
text: model.applicationName
//FIXME: export smallestReadableFont
font.pointSize: PlasmaCore.Theme.defaultFont.pointSize * 0.9
color: "white"
}
}
}

View file

@ -0,0 +1,107 @@
/*
* 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 homeScreenState
// we use flickable solely for capturing flicks, not positioning elements
contentWidth: width + 99999
contentHeight: height + 99999
contentX: startContentX
contentY: startContentY
readonly property real startContentX: contentWidth / 2
readonly property real startContentY: contentHeight / 2
property bool positionChangedDueToFlickable: false
// ensure that flickable is not moving when other sources are changing position
Connections {
target: root.homeScreenState
onXPositionChanged: {
if (!root.positionChangedDueToFlickable) {
root.cancelMovement();
}
root.positionChangedDueToFlickable = true;
}
onYPositionChanged: {
if (!root.positionChangedDueToFlickable) {
root.cancelMovement();
}
root.positionChangedDueToFlickable = true;
}
}
// update position from flickable movement
property real oldContentX
property real oldContentY
onContentXChanged: {
positionChangedDueToFlickable = true;
homeScreenState.updatePositionWithOffset(contentX - oldContentX, 0);
oldContentX = contentX;
}
onContentYChanged: {
positionChangedDueToFlickable = true;
homeScreenState.updatePositionWithOffset(0, -(contentY - oldContentY));
oldContentY = contentY;
}
onMovementStarted: homeScreenState.cancelAnimations();
onMovementEnded: {
if (!homeScreenState.animationsRunning) {
homeScreenState.updateState();
}
resetPosition();
}
onFlickEnded: {
homeScreenState.cancelEditModeForItemsRequested()
resetPosition();
}
onDragStarted: homeScreenState.cancelEditModeForItemsRequested()
onDragEnded: homeScreenState.cancelEditModeForItemsRequested()
onFlickStarted: homeScreenState.cancelEditModeForItemsRequested()
onDraggingChanged: {
if (!dragging) {
cancelMovement();
resetPosition();
if (!homeScreenState.animationsRunning) {
homeScreenState.updateState();
}
} else {
homeScreenState.cancelAnimations();
}
}
function cancelMovement() {
root.cancelFlick();
// HACK: cancelFlick() doesn't seem to cancel flicks...
root.flick(-horizontalVelocity, -verticalVelocity);
}
function resetPosition() {
positionChangedDueToFlickable = true;
oldContentX = startContentX;
contentX = startContentX;
oldContentY = startContentY;
contentY = startContentY;
}
}

View file

@ -23,6 +23,8 @@ import "private" as Private
ContainmentLayoutManager.ItemContainer {
id: delegate
property var homeScreenState
z: dragActive ? 1 : 0
property var modelData: typeof model !== "undefined" ? model : null
@ -64,11 +66,11 @@ ContainmentLayoutManager.ItemContainer {
}
}
Connections {
target: mainFlickable
target: homeScreenState
function onCancelEditModeForItemsRequested() {
cancelEdit()
}
function onContentYChanged() {
function onXPositionChanged() {
syncDelegateGeometry()
}
}

View file

@ -0,0 +1,135 @@
/*
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Layouts 1.1
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.draganddrop 2.0 as DragDrop
import "private" as Private
import "appdrawer"
import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenComponents
Item {
id: root
property bool interactive: true
property var homeScreenState: HomeScreenState {
totalPagesWidth: pages.contentWidth
appDrawerFlickable: appDrawer.flickable
availableScreenHeight: height - (MobileShell.TaskPanelControls.isPortrait ? MobileShell.TaskPanelControls.panelHeight : 0)
availableScreenWidth: width - (MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth)
appDrawerBottomOffset: favoriteStrip.height
}
property alias appDrawer: appDrawerLoader.item
property alias homeScreenContents: contents
Component.onCompleted: homeScreenState.goToPageIndex(0)
// the parent of the homescreen is a flickable that captures all flicks
FlickContainer {
id: flickContainer
anchors.fill: parent
homeScreenState: root.homeScreenState
// disable flick tracking when necessary
interactive: root.interactive && homeScreenState.currentView !== HomeScreenState.AppDrawerView &&
root.parent.focus && !contents.appletsLayout.editMode && !plasmoid.editMode && !contents.launcherDragManager.active
// item is effectively anchored to root, while allowing flickContainer
// to keep track of flicks
Item {
x: flickContainer.contentX
y: flickContainer.contentY
width: flickContainer.width
height: flickContainer.height
// horizontal pages
HomeScreenPages {
id: pages
homeScreenState: root.homeScreenState
// account for panels
anchors.fill: parent
anchors.topMargin: MobileShell.TopPanelControls.panelHeight
anchors.rightMargin: MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth
anchors.bottomMargin: MobileShell.TaskPanelControls.isPortrait ? MobileShell.TaskPanelControls.panelHeight : 0
// animation when app drawer is being shown
opacity: root.appDrawer ? 1 - root.appDrawer.openFactor : 1
transform: Translate {
y: root.appDrawer ? (-pages.height / 20) * root.appDrawer.openFactor : 0
}
contentWidth: Math.max(width, width * Math.ceil(contents.itemsBoundingRect.width/width)) + (contents.launcherDragManager.active ? width : 0)
showAddPageIndicator: contents.launcherDragManager.active
HomeScreenContents {
id: contents
homeScreenState: root.homeScreenState
height: pages.height
width: pages.width * 100
favoriteStrip: favoriteStrip
homeScreenPages: pages
}
footer: FavoriteStrip {
id: favoriteStrip
appletsLayout: contents.appletsLayout
visible: favoriteStrip.flow.children.length > 0 || contents.launcherDragManager.active || contents.containsDrag
opacity: contents.launcherDragManager.active && HomeScreenComponents.ApplicationListModel.favoriteCount >= HomeScreenComponents.ApplicationListModel.maxFavoriteCount ? 0.3 : 1
TapHandler {
target: favoriteStrip
onTapped: {
//Hides icons close button
contents.appletsLayout.appletsLayoutInteracted();
contents.appletsLayout.editMode = false;
}
onLongPressed: {
if (homeScreenState.currentSwipeState === HomeScreenState.DeterminingType) {
// only go into edit mode when not in a swipe
contents.appletsLayout.editMode = true;
}
}
onPressedChanged: root.parent.focus = true;
}
}
}
// app drawer
AppDrawerLoader {
id: appDrawerLoader
anchors.fill: parent
homeScreenState: root.homeScreenState
// account for panels
topPadding: MobileShell.TopPanelControls.panelHeight
rightPadding: MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth
bottomPadding: MobileShell.TaskPanelControls.isPortrait ? MobileShell.TaskPanelControls.panelHeight : 0
}
}
}
}

View file

@ -24,10 +24,9 @@ import "private" as Private
DragDrop.DropArea {
id: dropArea
width: mainFlickable.width * 100
//width: Math.max(mainFlickable.width, mainFlickable.width * Math.ceil(appletsLayout.childrenRect.width/mainFlickable.width))
height: mainFlickable.height
required property var homeScreenState
property alias launcherDelegate: launcherRepeater.delegate
property alias launcherModel: launcherRepeater.model
property alias launcherRepeater: launcherRepeater
@ -36,6 +35,7 @@ DragDrop.DropArea {
property alias appletsLayout: appletsLayout
property FavoriteStrip favoriteStrip
property HomeScreenPages homeScreenPages
property LauncherDragManager launcherDragManager: LauncherDragManager {
id: launcherDragManager
@ -48,7 +48,7 @@ DragDrop.DropArea {
}
anchors.fill: parent
z: 999999
appletsLayout: homeScreenContents.appletsLayout
appletsLayout: dropArea.appletsLayout
favoriteStrip: dropArea.favoriteStrip
}
@ -63,6 +63,7 @@ DragDrop.DropArea {
event.accept(event.proposedAction);
launcherDragManager.active = true;
}
onDragMove: {
let posInFavorites = favoriteStrip.mapFromItem(this, event.x, event.y);
if (posInFavorites.y > 0) {
@ -84,13 +85,13 @@ DragDrop.DropArea {
let scenePos = mapToItem(null, event.x, event.y);
//SCROLL LEFT
if (scenePos.x < PlasmaCore.Units.gridUnit) {
mainFlickable.scrollLeft();
homeScreenPages.scrollLeft();
//SCROLL RIGHT
} else if (scenePos.x > mainFlickable.width - PlasmaCore.Units.gridUnit) {
mainFlickable.scrollRight();
} else if (scenePos.x > homeScreenPages.width - PlasmaCore.Units.gridUnit) {
homeScreenPages.scrollRight();
//DON't SCROLL
} else {
mainFlickable.stopScroll();
homeScreenPages.stopScroll();
}
}
}
@ -159,15 +160,20 @@ DragDrop.DropArea {
signal appletsLayoutInteracted
TapHandler {
target: mainFlickable
enabled: appDrawer.status !== AbstractAppDrawer.Status.Open
target: homeScreenPages
enabled: homeScreenState.currentView === HomeScreenState.PageView
onTapped: {
//Hides icons close button
appletsLayout.appletsLayoutInteracted();
appletsLayout.editMode = false;
appletsLayout.forceActiveFocus();
}
onLongPressed: appletsLayout.editMode = true;
onLongPressed: {
if (homeScreenState.currentSwipeState === HomeScreenState.DeterminingType) {
// only go into edit mode when not in a swipe
appletsLayout.editMode = true;
}
}
onPressedChanged: appletsLayout.focus = true;
}
@ -200,20 +206,21 @@ DragDrop.DropArea {
placeHolder: ContainmentLayoutManager.PlaceHolder {}
//FIXME: move
PlasmaComponents.Label {
id: metrics
text: "M\nM"
visible: false
font.pointSize: PlasmaCore.Theme.defaultFont.pointSize * 0.9
}
id: metrics
text: "M\nM"
visible: false
font.pointSize: PlasmaCore.Theme.defaultFont.pointSize * 0.9
}
LauncherRepeater {
id: launcherRepeater
homeScreenState: dropArea.homeScreenState
cellWidth: appletsLayout.cellWidth
cellHeight: appletsLayout.cellHeight
appletsLayout: appletsLayout
favoriteStrip: dropArea.favoriteStrip
onScrollLeftRequested: mainFlickable.scrollLeft()
onScrollRightRequested: mainFlickable.scrollRight()
onStopScrollRequested: mainFlickable.stopScroll()
onScrollLeftRequested: homeScreenPages.scrollLeft()
onScrollRightRequested: homeScreenPages.scrollRight()
onStopScrollRequested: homeScreenPages.stopScroll()
}
}
}

View file

@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
@ -23,23 +24,15 @@ import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenCompon
Flickable {
id: mainFlickable
property AbstractAppDrawer appDrawer
readonly property int totalPages: Math.ceil(contentWidth / width)
property int currentIndex: 0
property ContainmentLayoutManager.AppletsLayout appletsLayout: null
required property var homeScreenState
property Item footer
property alias dragGestureEnabled: gestureHandler.enabled
opacity: appDrawer ? 1 - appDrawer.openFactor : 1
transform: Translate {
y: appDrawer ? (-mainFlickable.height / 20) * appDrawer.openFactor : 0
}
clip: true
property bool showAddPageIndicator: false
contentX: homeScreenState.xPosition
contentHeight: height
interactive: false
@ -49,9 +42,6 @@ Flickable {
onFlickStarted: cancelEditModeForItemsRequested()
onFlickEnded: cancelEditModeForItemsRequested()
//onCurrentIndexChanged: contentX = width * currentIndex;
onContentXChanged: mainFlickable.currentIndex = Math.floor(contentX / width)
onFooterChanged: {
if (footer) {
footer.parent = mainFlickable;
@ -61,7 +51,7 @@ Flickable {
}
}
//Autoscroll related functions
// autoscroll between pages (when holding a delegate to go to a new page)
function scrollLeft() {
if (mainFlickable.atXBeginning) {
return;
@ -88,63 +78,16 @@ Flickable {
scrollRightIndicator.opacity = 0;
}
function snapPage() {
scrollAnim.running = false;
scrollAnim.to = mainFlickable.width * Math.round(mainFlickable.contentX / mainFlickable.width)
scrollAnim.running = true;
}
function snapNextPage() {
scrollAnim.running = false;
scrollAnim.to = mainFlickable.width * Math.ceil(mainFlickable.contentX / mainFlickable.width)
scrollAnim.running = true;
}
function snapPrevPage() {
scrollAnim.running = false;
scrollAnim.to = mainFlickable.width * Math.floor(mainFlickable.contentX / mainFlickable.width)
scrollAnim.running = true;
}
function scrollToPage(index) {
scrollAnim.running = false;
scrollAnim.to = mainFlickable.width * Math.max(0, Math.min(index, mainFlickable.contentWidth - mainFlickable.width))
scrollAnim.running = true;
}
Timer {
id: autoScrollTimer
property bool scrollRight: true
repeat: true
interval: 1500
onTriggered: {
scrollAnim.to = scrollRight ?
//Scroll Right
Math.min(mainFlickable.contentItem.width - mainFlickable.width, mainFlickable.contentX + mainFlickable.width) :
//Scroll Left
Math.max(0, mainFlickable.contentX - mainFlickable.width);
scrollAnim.running = true;
homeScreenState.animateGoToPageIndex(Math.max(0, homeScreenState.currentPageIndex + (scrollRight ? 1 : -1)), PlasmaCore.Units.longDuration * 2);
}
}
Private.DragGestureHandler {
id: gestureHandler
target: appletsLayout
appDrawer: mainFlickable.appDrawer
mainFlickable: mainFlickable
onSnapPage: mainFlickable.snapPage();
onSnapNextPage: mainFlickable.snapNextPage();
onSnapPrevPage: mainFlickable.snapPrevPage();
}
NumberAnimation {
id: scrollAnim
target: mainFlickable
properties: "contentX"
duration: PlasmaCore.Units.longDuration
easing.type: Easing.InOutQuad
}
PlasmaComponents.PageIndicator {
id: pageIndicator
anchors {
@ -152,12 +95,16 @@ Flickable {
horizontalCenter: parent.horizontalCenter
bottomMargin: mainFlickable.footer ? mainFlickable.footer.height : 0
}
PlasmaCore.ColorScope.inherit: false
PlasmaCore.ColorScope.colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
parent: mainFlickable
count: mainFlickable.totalPages
visible: count > 1
currentIndex: Math.round(mainFlickable.contentX / mainFlickable.width)
count: homeScreenState.pagesCount
currentIndex: homeScreenState.currentPageIndex
delegate: Rectangle {
property bool isAddPageIndicator: index === pageIndicator.count-1 && mainFlickable.showAddPageIndicator
implicitWidth: PlasmaCore.Units.gridUnit/2
@ -166,14 +113,13 @@ Flickable {
radius: width
color: isAddPageIndicator ? "transparent" : PlasmaCore.ColorScope.textColor
PlasmaComponents.Label {
anchors.centerIn: parent
visible: parent.isAddPageIndicator
text: "⊕"
}
opacity: index === currentIndex ? 0.9 : pressed ? 0.7 : 0.5
opacity: index === pageIndicator.currentIndex ? 0.9 : pressed ? 0.7 : 0.5
Behavior on opacity {
OpacityAnimator {
duration: PlasmaCore.Units.longDuration

View file

@ -0,0 +1,413 @@
/*
* 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 homescreen.
*/
QtObject {
id: root
required property real totalPagesWidth
required property var appDrawerFlickable
// dimensions of the homescreen area (not including top panel and task panel)
required property real availableScreenHeight
required property real availableScreenWidth
// offset from the bottom of the screen that the app drawer starts from,
// would be the height favourites strip
required property real appDrawerBottomOffset
// ~~ positioning ~~
// xPosition:
// We start at 0, which is the beginning x position of the row of pages (left-most side).
// Increasing x moves *right* toward the next page.
//
// yPosition:
// Increasing y results in moving *up* in the view.
// appDrawerOpenYPosition - The app drawer is opened (app drawer flickable is active iff it's not at the beginning).
// pagesYPosition - The app drawer is closed. Homescreen pages are visible, can swipe left/right between pages.
property real xPosition: 0
property real yPosition: pagesYPosition
// 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 homescreen pages are visible
readonly property real pagesYPosition: availableScreenHeight - appDrawerBottomOffset
// yPosition when drawer is open
readonly property real appDrawerOpenYPosition: 0
// ~~ active state ~~
enum View {
PageView, // we are viewing the horizontal row of pages
AppDrawerBeginningView, // we are at the top of the app drawer (could either close it or scroll down)
AppDrawerView // we are in the app drawer, and not at the top of it
}
// the current view of the homescreen
property var currentView: HomeScreenState.PageView
// number of homescreen pages
readonly property int pagesCount: Math.floor(totalPagesWidth / pageWidth)
// current homescreen page index
readonly property int currentPageIndex: {
let candidateIndex = Math.round(xPosition / (pageSpacing + pageWidth));
return Math.max(0, Math.min(pagesCount - 1, candidateIndex));
}
enum PageViewSwipeState {
SwipingPages, // horizontal movement between pages
SwipingAppDrawerVisibility, // opening/closing app drawer
SwipingAppDrawerList, // scrolling app drawer
SwipingActionPanel, // pulling down action panel
DeterminingType
}
// when we are at the PageView view, we need to distinguish horizontal swipes (changing pages)
// and vertical swipes (opening drawer)
property var currentSwipeState: HomeScreenState.DeterminingType
// threshold of movement in a direction before we count that as the defining SwipeState
readonly property real swipeStateDetermineThreshold: PlasmaCore.Units.smallSpacing
// we put the offset position here when determining the swipe type, before we
// transfer movement over to xPosition and yPosition
property real xDetermineSwipePosition: 0
property real yDetermineSwipePosition: 0
// whether animations are currently running
property bool animationsRunning: openDrawerAnim.running || closeDrawerAnim.running || xAnim.running
// whether the app drawer flickable should be interactive
property bool appDrawerInteractive: currentView === HomeScreenState.AppDrawerView
// ~~ measurement constants ~~
// dimensions of a page
readonly property real pageHeight: availableScreenHeight
readonly property real pageWidth: availableScreenWidth
// spacing between each homescreen page
readonly property real pageSpacing: 0
// ~~ signals and functions ~~
// cancel edit mode
signal cancelEditModeForItemsRequested
// cancel all animated moving, as another flick source is taking over
signal cancelAnimations()
onCancelAnimations: {
openDrawerAnim.stop();
closeDrawerAnim.stop();
xAnim.stop();
}
// be very careful when resetting the swipe state
// ensure that we aren't in the middle of a gesture
function resetSwipeState() {
currentSwipeState = HomeScreenState.DeterminingType;
xDetermineSwipePosition = 0;
yDetermineSwipePosition = 0;
}
function openAppDrawer() {
openDrawerAnim.restart();
}
function openAppDrawerInstantly() {
yPosition = appDrawerOpenYPosition;
currentView = HomeScreenState.AppDrawerBeginningView;
}
function closeAppDrawer() {
closeDrawerAnim.restart();
}
function closeAppDrawerInstantly() {
yPosition = pagesYPosition;
currentView = HomeScreenState.PageView;
}
// get the xPosition where the page will be centered on the screen
function xPositionFromPageIndex(index) {
return index * (pageWidth + pageSpacing);
}
// instantly go to the page index
function goToPageIndex(index) {
xPosition = xPositionFromPageIndex(index);
}
// go to the page index, animated
function animateGoToPageIndex(index, duration) {
xAnim.duration = duration;
xAnim.to = xPositionFromPageIndex(index);
xAnim.restart();
}
// update the position using an offset
// called by swipe provider flickable
function updatePositionWithOffset(x, y) {
switch (currentView) {
case HomeScreenState.PageView: {
switch (currentSwipeState) {
case HomeScreenState.DeterminingType:
xDetermineSwipePosition += x;
yDetermineSwipePosition += y;
// check if a swipetype can be determined and started
if (Math.abs(xDetermineSwipePosition) >= swipeStateDetermineThreshold) {
currentSwipeState = HomeScreenState.SwipingPages;
xDetermineSwipePosition = 0;
yDetermineSwipePosition = 0;
} else if (yDetermineSwipePosition >= swipeStateDetermineThreshold) {
currentSwipeState = HomeScreenState.SwipingActionPanel;
MobileShell.TopPanelControls.startSwipe();
xDetermineSwipePosition = 0;
yDetermineSwipePosition = 0;
} else if (-yDetermineSwipePosition >= swipeStateDetermineThreshold) {
currentSwipeState = HomeScreenState.SwipingAppDrawerVisibility;
xDetermineSwipePosition = 0;
yDetermineSwipePosition = 0;
}
break;
case HomeScreenState.SwipingPages:
xPosition += x;
break;
case HomeScreenState.SwipingActionPanel:
yPosition = pagesYPosition;
if (y !== 0) {
MobileShell.TopPanelControls.requestRelativeScroll(y);
}
break;
case HomeScreenState.SwipingAppDrawerVisibility:
yPosition = Math.max(appDrawerOpenYPosition, Math.min(pagesYPosition, yPosition + y));
break;
}
break;
}
case HomeScreenState.AppDrawerBeginningView: {
switch (currentSwipeState) {
case HomeScreenState.DeterminingType:
xDetermineSwipePosition += x;
yDetermineSwipePosition += y;
// check if a swipetype can be determined and started
if (yDetermineSwipePosition >= swipeStateDetermineThreshold) {
currentSwipeState = HomeScreenState.SwipingAppDrawerVisibility;
xDetermineSwipePosition = 0;
yDetermineSwipePosition = 0;
} else if (-yDetermineSwipePosition >= swipeStateDetermineThreshold) {
currentSwipeState = HomeScreenState.SwipingAppDrawerList;
yVelocityCalculator.startMeasure(appDrawerFlickable.contentY);
xDetermineSwipePosition = 0;
yDetermineSwipePosition = 0;
}
break;
case HomeScreenState.SwipingAppDrawerVisibility:
yPosition = Math.max(appDrawerOpenYPosition, Math.min(pagesYPosition, yPosition + y));
break;
case HomeScreenState.SwipingAppDrawerList:
// app drawer scrolling
let candidateNewPos = appDrawerFlickable.contentY - y;
appDrawerFlickable.contentY = candidateNewPos;
// update velocity
yVelocityCalculator.changePosition(appDrawerFlickable.contentY);
break;
}
break;
}
case HomeScreenState.AppDrawerView: {
break;
}
}
}
// called after a user finishes an interaction (ex. lets go of the screen)
// called by swipe provider flickable
function updateState() {
cancelAnimations();
// we need to always call resetSwipeState() after each interaction.
// if we have an animation to run, we rely on the animation to call the function.
// otherwise, we do it directly here.
switch (currentView) {
case HomeScreenState.PageView: {
// update vertical position
switch (currentSwipeState) {
case HomeScreenState.DeterminingType: {
movingUp ? closeAppDrawer() : openAppDrawer();
break;
}
case HomeScreenState.SwipingActionPanel: {
MobileShell.TopPanelControls.endSwipe();
root.resetSwipeState();
break;
}
case HomeScreenState.SwipingAppDrawerVisibility: {
movingUp ? closeAppDrawer() : openAppDrawer();
break;
}
case HomeScreenState.SwipingPages: {
// update pages position
let currentPageIndexPosition = xPositionFromPageIndex(currentPageIndex);
let duration = PlasmaCore.Units.longDuration * 2;
if (xPosition < currentPageIndexPosition) {
if (movingRight) {
animateGoToPageIndex(currentPageIndex, duration);
} else {
animateGoToPageIndex(Math.max(0, currentPageIndex - 1), duration);
}
} else {
if (movingRight) {
animateGoToPageIndex(Math.min(pagesCount - 1, currentPageIndex + 1), duration);
} else {
animateGoToPageIndex(currentPageIndex, duration);
}
}
break;
}
default: {
// this shouldn't occur, but keeps consistent state if it does
root.resetSwipeState();
break;
}
}
break;
}
case HomeScreenState.AppDrawerBeginningView: {
switch (currentSwipeState) {
case HomeScreenState.DeterminingType:
case HomeScreenState.SwipingAppDrawerVisibility: {
movingUp ? closeAppDrawer() : openAppDrawer();
break;
}
case HomeScreenState.SwipingAppDrawerList: {
currentView = HomeScreenState.AppDrawerView;
appDrawerFlickable.flick(0, -yVelocityCalculator.velocity);
root.resetSwipeState();
break;
}
default: {
// this shouldn't occur, but keeps consistent state if it does
root.resetSwipeState();
break;
}
}
break;
}
case HomeScreenState.AppDrawerView: {
break;
}
}
}
// measure velocity of our swipe in the app drawer, so that we can flick
property var yVelocityCalculator: MobileShell.VelocityCalculator {}
// listen to the app drawer's flickable for if it goes to the top of the list
// we then update our view state
property var appDrawerFlickableListener: Connections {
target: appDrawerFlickable
function onMovementEnded() {
if (root.currentView === HomeScreenState.AppDrawerView) {
if (appDrawerFlickable.contentY <= 0) {
root.currentView = HomeScreenState.AppDrawerBeginningView;
}
}
}
function onDraggingChanged() {
if (!appDrawerFlickable.dragging) {
if (root.currentView === HomeScreenState.AppDrawerView) {
if (appDrawerFlickable.contentY <= 0) {
root.currentView = HomeScreenState.AppDrawerBeginningView;
}
}
}
}
}
// ~~ property animators ~~
property var xAnim: NumberAnimation {
target: root
property: "xPosition"
easing.type: Easing.OutBack
onFinished: {
root.resetSwipeState();
}
}
property var openDrawerAnim: NumberAnimation {
target: root
property: "yPosition"
to: appDrawerOpenYPosition
duration: PlasmaCore.Units.longDuration * 2
easing.type: Easing.OutCubic
onFinished: {
root.currentView = HomeScreenState.AppDrawerBeginningView;
root.resetSwipeState();
}
}
property var closeDrawerAnim: NumberAnimation {
target: root
property: "yPosition"
to: pagesYPosition
duration: PlasmaCore.Units.longDuration * 2
easing.type: Easing.OutCubic
onFinished: {
root.currentView = HomeScreenState.PageView;
root.resetSwipeState();
}
}
}

View file

@ -1,82 +0,0 @@
/*
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.4
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.3 as Controls
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.kquickcontrolsaddons 2.0
import org.kde.kirigami 2.10 as Kirigami
import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager
import org.kde.plasma.private.nanoshell 2.0 as NanoShell
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenComponents
LauncherContainer {
id: root
readonly property int columns: Math.floor(root.flow.width / cellWidth)
readonly property int cellWidth: root.flow.width / Math.floor(root.flow.width / ((availableCellHeight - reservedSpaceForLabel) + PlasmaCore.Units.smallSpacing*4))
readonly property int cellHeight: availableCellHeight
signal launched
frame.width: width
Repeater {
parent: root.flow
model: HomeScreenComponents.ApplicationListModel
delegate: HomeDelegate {
id: delegate
width: root.cellWidth
height: root.cellHeight
parent: parentFromLocation
property Item parentFromLocation: {
switch (model.applicationLocation) {
case HomeScreenComponents.ApplicationListModel.Desktop:
return appletsLayout;
case HomeScreenComponents.ApplicationListModel.Favorites:
return favoriteStrip.flow;
default:
return root.flow;
}
}
Component.onCompleted: {
if (model.applicationLocation === HomeScreenComponents.ApplicationListModel.Desktop) {
appletsLayout.restoreItem(delegate);
}
}
onLaunch: (x, y, icon, title) => {
if (icon !== "") {
MobileShell.HomeScreenControls.openAppAnimation(
icon,
title,
delegate.iconItem.Kirigami.ScenePosition.x + delegate.iconItem.width/2,
delegate.iconItem.Kirigami.ScenePosition.y + delegate.iconItem.height/2,
Math.min(delegate.iconItem.width, delegate.iconItem.height));
}
root.launched();
}
onParentFromLocationChanged: {
if (!launcherDragManager.active && parent != parentFromLocation) {
parent = parentFromLocation;
if (model.applicationLocation === HomeScreenComponents.ApplicationListModel.Favorites) {
plasmoid.nativeInterface.stackBefore(delegate, parentFromLocation.children[index]);
} else if (model.applicationLocation === HomeScreenComponents.ApplicationListModel.Grid) {
plasmoid.nativeInterface.stackBefore(delegate, parentFromLocation.children[Math.max(0, index - HomeScreenComponents.ApplicationListModel.favoriteCount)]);
}
}
}
}
}
}

View file

@ -22,6 +22,9 @@ import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenCompon
Repeater {
id: launcherRepeater
model: HomeScreenComponents.FavoritesModel
required property var homeScreenState
property ContainmentLayoutManager.AppletsLayout appletsLayout
property FavoriteStrip favoriteStrip
property int cellWidth
@ -33,6 +36,8 @@ Repeater {
delegate: HomeDelegate {
id: delegate
homeScreenState: launcherRepeater.homeScreenState
width: launcherRepeater.cellWidth
height: Math.min(parent.height, launcherRepeater.cellHeight)
appletsLayout: launcherRepeater.appletsLayout
@ -74,7 +79,7 @@ Repeater {
if (pos.x < PlasmaCore.Units.gridUnit) {
launcherRepeater.scrollLeftRequested();
//SCROLL RIGHT
} else if (pos.x > mainFlickable.width - PlasmaCore.Units.gridUnit) {
} else if (pos.x > homeScreenState.pageWidth - PlasmaCore.Units.gridUnit) {
launcherRepeater.scrollRightRequested();
//DON't SCROLL
} else {

View file

@ -20,44 +20,21 @@ import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenComponents
import "private"
import "../private"
import "../"
Item {
id: root
enum Status {
Closed,
Peeking,
Open
}
enum MovementDirection {
None = 0,
Up,
Down
}
readonly property int status: {
if (flickable.contentY >= topMargin.height) {
return AbstractAppDrawer.Status.Open;
} else if (flickable.contentY > 0) {
return AbstractAppDrawer.Status.Peeking;
} else {
return AbstractAppDrawer.Status.Closed;
}
}
property real offset: 0
property real closedPositionOffset: 0
required property var homeScreenState
property real leftPadding: 0
property real topPadding: 0
property real bottomPadding: 100
property real rightPadding: 0
property alias flickable: view
property alias flickable: flickableBody.contentItem
property var contentItem
property Flickable contentItem
property real contentWidth: holdingColumn.width
required property int headerHeight
@ -69,54 +46,14 @@ Item {
readonly property int reservedSpaceForLabel: metrics.height
property int availableCellHeight: PlasmaCore.Units.iconSizes.huge + reservedSpaceForLabel
readonly property real openFactor: factorNormalize(flickable.contentY / (units.gridUnit * 10))
readonly property real openFactor: factorNormalize(view.contentY / (PlasmaCore.Units.gridUnit * 10))
// height from top of screen that the drawer starts
readonly property real drawerTopMargin: height - topPadding - bottomPadding - closedPositionOffset
readonly property real closedPositionOffset: homeScreenState.appDrawerBottomOffset
//BEGIN functions
function goToBeginning() {
scrollAnim.to = drawerTopMargin;
scrollAnim.restart();
}
function open() {
if (root.status === AbstractAppDrawer.Status.Open) {
flickable.flick(0,1);
} else {
goToBeginning();
}
}
function close() {
if (root.status !== AbstractAppDrawer.Status.Closed) {
scrollAnim.to = 0;
scrollAnim.restart();
}
}
// snap the drawer to an open or close position
function snapDrawerStatus() {
if (flickable.contentY > topMargin.height) {
return;
}
if (flickable.movementDirection === AbstractAppDrawer.MovementDirection.Up) {
if (flickable.contentY > topMargin.height / 8) { // over one eighth of the screen
open();
} else {
close();
}
} else {
if (flickable.contentY < 7 * topMargin.height / 8) { // over one eighth of the screen
close();
} else {
open();
}
}
}
function factorNormalize(num) {
return Math.min(1, Math.max(0, num));
}
@ -125,15 +62,6 @@ Item {
Drag.dragType: Drag.Automatic
NumberAnimation {
id: scrollAnim
target: flickable
properties: "contentY"
duration: PlasmaCore.Units.longDuration * 2
easing.type: Easing.OutQuad
easing.amplitude: 2.0
}
PC3.Label {
id: metrics
text: "M\nM"
@ -143,7 +71,7 @@ Item {
// bottom divider
GradientBar {
opacity: root.status !== AbstractAppDrawer.Status.Closed ? 0.6 : 0
opacity: (homeScreenState.currentView !== HomeScreenState.PageView || homeScreenState.currentSwipeState === HomeScreenState.SwipingAppDrawerVisibility) ? 0.6 : 0
visible: root.bottomPadding > 0
anchors.left: parent.left
anchors.right: parent.right
@ -156,25 +84,14 @@ Item {
id: view
anchors.fill: parent
// We have a situation where this vertical flickable conflicts with the horizontal flickable used for homescreen pages.
// This flickable is on top of the other, so we disable it when it isn't open.
// We do the initial open gesture in private/DragGestureHandler.qml
interactive: contentY > PlasmaCore.Units.gridUnit
// scroll events are handled by our flick container, we are only using this for positioning
interactive: false
contentY: Math.max(0, Math.min(root.drawerTopMargin, root.drawerTopMargin - homeScreenState.yPosition))
contentHeight: column.implicitHeight
contentWidth: -1
boundsBehavior: Flickable.StopAtBounds
// snap
onMovementEnded: root.snapDrawerStatus()
property int movementDirection: AbstractAppDrawer.MovementDirection.None
property real oldContentY
onContentYChanged: { // update state
movementDirection = oldContentY > contentY ? AbstractAppDrawer.MovementDirection.Down : AbstractAppDrawer.MovementDirection.Up;
oldContentY = contentY;
}
ColumnLayout {
id: column
width: view.width
@ -196,8 +113,8 @@ Item {
}
factor: root.openFactor
flickable: view
onOpenRequested: root.open();
onCloseRequested: root.close();
onOpenRequested: homeScreenState.openAppDrawer();
onCloseRequested: homeScreenState.closeAppDrawer();
}
}
@ -206,8 +123,6 @@ Item {
id: drawerFlickable
Layout.fillWidth: true
Layout.preferredHeight: root.height
visible: view.interactive // this is so that the favourites strip can be interacted with
leftPadding: root.leftPadding; topPadding: root.topPadding
rightPadding: root.rightPadding; bottomPadding: root.bottomPadding

View file

@ -0,0 +1,82 @@
/*
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Layouts 1.1
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenComponents
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
Loader {
id: root
required property var homeScreenState
property real topPadding: 0
property real bottomPadding: 0
property real leftPadding: 0
property real rightPadding: 0
property string appDrawerType: "gridview" // gridview/listview
readonly property real headerHeight: Math.round(PlasmaCore.Units.gridUnit * 3)
sourceComponent: appDrawerType === "gridview" ? gridViewDrawer : listViewDrawer
Component {
id: headerComponent
AppDrawerHeader {
onSwitchToListRequested: {
if (root.appDrawerType !== "listview") {
root.appDrawerType = "listview";
}
}
onSwitchToGridRequested: {
if (root.appDrawerType !== "gridview") {
root.appDrawerType = "gridview";
}
}
}
}
Component {
id: listViewDrawer
ListViewAppDrawer {
anchors.fill: parent
topPadding: root.topPadding
bottomPadding: root.bottomPadding
leftPadding: root.leftPadding
rightPadding: root.rightPadding
homeScreenState: root.homeScreenState
headerItem: Loader { sourceComponent: headerComponent }
headerHeight: root.headerHeight
}
}
Component {
id: gridViewDrawer
GridViewAppDrawer {
anchors.fill: parent
topPadding: root.topPadding
bottomPadding: root.bottomPadding
leftPadding: root.leftPadding
rightPadding: root.rightPadding
homeScreenState: root.homeScreenState
headerItem: Loader { sourceComponent: headerComponent }
headerHeight: root.headerHeight
}
}
}

View file

@ -20,7 +20,7 @@ import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenComponents
import "private"
import "../private"
AbstractAppDrawer {
id: root
@ -28,32 +28,7 @@ AbstractAppDrawer {
contentItem: GridView {
id: gridView
clip: true
// start location of dragging
property real startDragContentY
onMovementStarted: {
oldContentY = contentY;
startDragContentY = contentY;
}
// move drawer down when at the top of the app list
property real oldContentY
property bool movingDrawerDown: false
onContentYChanged: {
let candidateContentY = root.flickable.contentY - (oldContentY - contentY);
if (dragging && startDragContentY <= 0 && oldContentY <= 0 && candidateContentY <= root.drawerTopMargin) {
root.flickable.contentY = candidateContentY;
contentY = 0;
movingDrawerDown = true;
}
oldContentY = contentY;
}
onMovementEnded: {
if (movingDrawerDown) {
root.snapDrawerStatus();
movingDrawerDown = false;
}
}
interactive: root.homeScreenState.appDrawerInteractive
cellWidth: root.contentWidth / Math.floor(root.contentWidth / ((root.availableCellHeight - root.reservedSpaceForLabel) + PlasmaCore.Units.smallSpacing*4))
cellHeight: root.availableCellHeight
@ -78,7 +53,7 @@ AbstractAppDrawer {
root.Drag.hotSpot.y = y;
root.Drag.mimeData = { "text/x-plasma-phone-homescreen-launcher": mimeData };
root.close()
root.homeScreenState.closeAppDrawer()
root.dragStarted()
root.Drag.active = true;

View file

@ -20,7 +20,7 @@ import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenComponents
import "private"
import "../private"
AbstractAppDrawer {
id: root
@ -31,31 +31,7 @@ AbstractAppDrawer {
reuseItems: true
cacheBuffer: model.count * delegateHeight // delegate height
// start location of dragging
property real startDragContentY
onMovementStarted: {
oldContentY = contentY;
startDragContentY = contentY;
}
// move drawer down when at the top of the app list
property real oldContentY
property bool movingDrawerDown: false
onContentYChanged: {
let candidateContentY = root.flickable.contentY - (oldContentY - contentY);
if (dragging && startDragContentY <= 0 && oldContentY <= 0 && candidateContentY <= root.drawerTopMargin) {
root.flickable.contentY = candidateContentY;
contentY = 0;
movingDrawerDown = true;
}
oldContentY = contentY;
}
onMovementEnded: {
if (movingDrawerDown) {
root.snapDrawerStatus();
movingDrawerDown = false;
}
}
interactive: root.homeScreenState.appDrawerInteractive
property int delegateHeight: PlasmaCore.Units.gridUnit * 3
@ -74,7 +50,7 @@ AbstractAppDrawer {
root.Drag.hotSpot.y = y;
root.Drag.mimeData = { "text/x-plasma-phone-homescreen-launcher": mimeData };
root.close()
root.homeScreenState.closeAppDrawer()
root.dragStarted()
root.Drag.active = true;

View file

@ -22,6 +22,7 @@ import org.kde.plasma.private.mobileshell 1.0 as MobileShell
MouseArea {
id: arrowUpIcon
z: 9
property Flickable flickable
property real factor: 0
@ -32,7 +33,6 @@ MouseArea {
onClicked: {
openRequested();
scrollAnim.restart();
}
Item {

View file

@ -6,18 +6,23 @@ module org.kde.plasma.private.mobilehomescreencomponents
plugin mobilehomescreencomponentsplugin
AbstractAppDrawer 0.1 AbstractAppDrawer.qml
GridViewAppDrawer 0.1 GridViewAppDrawer.qml
ListViewAppDrawer 0.1 ListViewAppDrawer.qml
DrawerListDelegate 0.1 DrawerListDelegate.qml
DrawerGridDelegate 0.1 DrawerGridDelegate.qml
AbstractAppDrawer 0.1 appdrawer/AbstractAppDrawer.qml
AppDrawerHeader 0.1 appdrawer/AppDrawerHeader.qml
AppDrawerLoader 0.1 appdrawer/AppDrawerLoader.qml
DrawerGridDelegate 0.1 appdrawer/DrawerGridDelegate.qml
DrawerListDelegate 0.1 appdrawer/DrawerListDelegate.qml
GridViewAppDrawer 0.1 appdrawer/GridViewAppDrawer.qml
ListViewAppDrawer 0.1 appdrawer/ListViewAppDrawer.qml
FavoriteStrip 0.1 FavoriteStrip.qml
FlickablePages 0.1 FlickablePages.qml
FlickContainer 0.1 FlickContainer.qml
HomeDelegate 0.1 HomeDelegate.qml
HomeScreen 0.1 HomeScreen.qml
HomeScreenContents 0.1 HomeScreenContents.qml
HomeScreenPages 0.1 HomeScreenPages.qml
HomeScreenState 0.1 HomeScreenState.qml
LauncherContainer 0.1 LauncherContainer.qml
LauncherDragManager 0.1 LauncherDragManager.qml
LauncherGrid 0.1 LauncherGrid.qml
LauncherRepeater 0.1 LauncherRepeater.qml
MobileAppletContainer 0.1 MobileAppletContainer.qml

View file

@ -0,0 +1,72 @@
/*
* SPDX-FileCopyrightText: 2013 Canonical Ltd. <legal@canonical.com>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import QtQuick 2.15
QtObject {
id: root
property bool zeroVelocityCounts: false
property real velocity: 0
function changePosition(position) {
__pushDragEvent(position);
}
function startMeasure(position) {
__dragEvents = [];
__pushDragEvent(position);
}
//BEGIN internal
property var __dragEvents: []
property var __dateTime: new function() {
this.getCurrentTimeMs = function() {return new Date().getTime()}
}
function __updateSpeed() {
var totalSpeed = 0;
for (var i = 0; i < __dragEvents.length; i++) {
totalSpeed += __dragEvents[i][2];
}
if (zeroVelocityCounts || Math.abs(totalSpeed) > 0.001) {
velocity = totalSpeed / __dragEvents.length * 1000;
}
}
function __cullOldDragEvents(currentTime) {
// cull events older than 50 ms but always keep the latest 2 events
for (var numberOfCulledEvents = 0; numberOfCulledEvents < __dragEvents.length-2; numberOfCulledEvents++) {
// __dragEvents[numberOfCulledEvents][0] is the dragTime
if (currentTime - __dragEvents[numberOfCulledEvents][0] <= 50) break;
}
__dragEvents.splice(0, numberOfCulledEvents);
}
function __getEventSpeed(currentTime, position) {
if (__dragEvents.length != 0) {
var lastDrag = __dragEvents[__dragEvents.length-1];
var duration = Math.max(1, currentTime - lastDrag[0]);
return (position - lastDrag[1]) / duration;
} else {
return 0;
}
}
function __pushDragEvent(position) {
let currentTime = __dateTime.getCurrentTimeMs();
__dragEvents.push([currentTime, position, __getEventSpeed(currentTime, position)]);
__cullOldDragEvents(currentTime);
__updateSpeed();
}
//END internal
}

View file

@ -13,6 +13,7 @@ ActionDrawerOpenSurface 1.0 actiondrawer/ActionDrawerOpenSurface.qml
BaseItem 1.0 components/BaseItem.qml
Direction 1.0 components/Direction.qml
StartupFeedback 1.0 components/StartupFeedback.qml
VelocityCalculator 1.0 components/VelocityCalculator.qml
# /dataproviders
singleton BatteryProvider 1.0 dataproviders/BatteryProvider.qml

View file

@ -1,153 +0,0 @@
/*
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Layouts 1.1
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenComponents
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
Item {
id: root
property alias flickablePages: mainFlickable
property alias homeScreenContents: contents
// listview/gridview header
property string appDrawerType: "gridview" // gridview/listview
property alias appDrawer: appDrawerLoader.item
readonly property real headerHeight: Math.round(PlasmaCore.Units.gridUnit * 3)
//BEGIN functions
function activate() {
// there's a couple of steps:
// - minimize windows
// - open app drawer
// - restore windows
if (!plasmoid.nativeInterface.showingDesktop) {
plasmoid.nativeInterface.showingDesktop = true
} else if (appDrawer.status !== HomeScreenComponents.AbstractAppDrawer.Status.Open) {
mainFlickable.currentIndex = 0
root.appDrawer.open()
} else {
plasmoid.nativeInterface.showingDesktop = false
root.appDrawer.close()
}
}
//END functions
HomeScreenComponents.FlickablePages {
id: mainFlickable
anchors {
fill: parent
topMargin: plasmoid.availableScreenRect.y
bottomMargin: plasmoid.screenGeometry.height - plasmoid.availableScreenRect.height - plasmoid.availableScreenRect.y
}
appletsLayout: homeScreenContents.appletsLayout
appDrawer: root.appDrawer
contentWidth: Math.max(width, width * Math.ceil(homeScreenContents.itemsBoundingRect.width/width)) + (homeScreenContents.launcherDragManager.active ? width : 0)
showAddPageIndicator: homeScreenContents.launcherDragManager.active
dragGestureEnabled: root.parent.focus && (!root.appDrawer || root.appDrawer.status !== HomeScreenComponents.AbstractAppDrawer.Status.Open) && !appletsLayout.editMode && !plasmoid.editMode && !homeScreenContents.launcherDragManager.active
HomeScreenComponents.HomeScreenContents {
id: contents
width: mainFlickable.width * 100
favoriteStrip: favoriteStrip
}
footer: HomeScreenComponents.FavoriteStrip {
id: favoriteStrip
appletsLayout: homeScreenContents.appletsLayout
visible: favoriteStrip.flow.children.length > 0 || homeScreenContents.launcherDragManager.active || homeScreenContents.containsDrag
opacity: homeScreenContents.launcherDragManager.active && HomeScreenComponents.ApplicationListModel.favoriteCount >= HomeScreenComponents.ApplicationListModel.maxFavoriteCount ? 0.3 : 1
TapHandler {
target: favoriteStrip
onTapped: {
//Hides icons close button
homeScreenContents.appletsLayout.appletsLayoutInteracted();
homeScreenContents.appletsLayout.editMode = false;
}
onLongPressed: homeScreenContents.appletsLayout.editMode = true;
onPressedChanged: root.parent.focus = true;
}
}
}
Component {
id: headerComponent
AppDrawerHeader {
onSwitchToListRequested: {
if (root.appDrawerType !== "listview") {
root.appDrawerType = "listview";
appDrawer.flickable.goToBeginning(); // jump to top
}
}
onSwitchToGridRequested: {
if (root.appDrawerType !== "gridview") {
root.appDrawerType = "gridview";
appDrawer.flickable.goToBeginning(); // jump to top
}
}
}
}
Component {
id: listViewDrawer
HomeScreenComponents.ListViewAppDrawer {
anchors.fill: parent
topPadding: plasmoid.availableScreenRect.y
// pad for navbar
rightPadding: MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth
bottomPadding: plasmoid.screenGeometry.height - plasmoid.availableScreenRect.height - plasmoid.availableScreenRect.y
closedPositionOffset: favoriteStrip.height
headerItem: Loader { sourceComponent: headerComponent }
headerHeight: root.headerHeight
}
}
Component {
id: gridViewDrawer
HomeScreenComponents.GridViewAppDrawer {
anchors.fill: parent
topPadding: plasmoid.availableScreenRect.y
// pad for navbar
rightPadding: MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth
bottomPadding: plasmoid.screenGeometry.height - plasmoid.availableScreenRect.height - plasmoid.availableScreenRect.y
closedPositionOffset: favoriteStrip.height
headerItem: Loader { sourceComponent: headerComponent }
headerHeight: root.headerHeight
}
}
Loader {
id: appDrawerLoader
anchors.fill: parent
sourceComponent: appDrawerType === "gridview" ? gridViewDrawer : listViewDrawer
}
}

View file

@ -28,7 +28,7 @@ FocusScope {
if (!componentComplete) {
return;
}
HomeScreenComponents.ApplicationListModel.maxFavoriteCount = Math.max(4, Math.floor(Math.min(width, height) / homescreen.homeScreenContents.appletsLayout.cellWidth));
HomeScreenComponents.ApplicationListModel.maxFavoriteCount = Math.max(4, Math.floor(Math.min(width, height) / homescreen.homeScreenContents.favoriteStrip.cellWidth));
}
function triggerHomeScreen() {
@ -50,21 +50,22 @@ FocusScope {
}
function onResetHomeScreenPosition() {
homescreen.flickablePages.scrollToPage(0);
homescreen.appDrawer.close();
homescreen.homeScreenState.goToPageIndex(0);
homescreen.homeScreenState.closeAppDrawer();
}
function onSnapHomeScreenPosition() {
if (lastRequestedPosition < 0) {
homescreen.appDrawer.open();
homescreen.homeScreenState.openAppDrawer();
} else {
homescreen.appDrawer.close();
homescreen.homeScreenState.closeAppDrawer();
}
}
function onRequestRelativeScroll(pos) {
homescreen.appDrawer.offset -= pos.y;
lastRequestedPosition = pos.y;
// TODO
//homescreen.appDrawer.offset -= pos.y;
//lastRequestedPosition = pos.y;
}
function onOpenAppAnimation(splashIcon, title, x, y, sourceIconSize) {
@ -110,14 +111,29 @@ FocusScope {
Plasmoid.onActivated: {
console.log("Triggered!", plasmoid.nativeInterface.showingDesktop)
homescreen.activate();
// there's a couple of steps:
// - minimize windows
// - open app drawer
// - restore windows
if (!plasmoid.nativeInterface.showingDesktop) {
plasmoid.nativeInterface.showingDesktop = true;
} else if (homescreen.homeScreenState.currentView === MobileShell.HomeScreenState.PageView) {
homescreen.homeScreenState.openAppDrawer()
} else {
plasmoid.nativeInterface.showingDesktop = false
homescreen.homeScreenState.closeAppDrawer()
}
}
// homescreen component
HomeScreen {
HomeScreenComponents.HomeScreen {
id: homescreen
anchors.fill: parent
// make the homescreen not interactable when task switcher or startup feedback is on
interactive: !taskSwitcher.visible && !startupFeedback.visible
opacity: 1
NumberAnimation on opacity {
id: opacityAnimation