Rework app drawer layout and open/close behaviour logic

This commit is contained in:
Devin Lin 2021-10-18 14:56:37 -04:00 committed by Marco Martin
parent d7b7290451
commit b091ce2ac3
13 changed files with 871 additions and 361 deletions

View file

@ -0,0 +1,258 @@
/*
* SPDX-FileCopyrightText: 2021 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.15 as Controls
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PC3
import org.kde.plasma.extras 2.0 as PlasmaExtra
import org.kde.kirigami 2.10 as Kirigami
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
import "private"
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
property real leftPadding: 0
property real topPadding: 0
property real bottomPadding: 100
property real rightPadding: 0
property alias flickable: view
property var contentItem
property real contentWidth: holdingColumn.width
required property int headerHeight
required property var headerItem
signal launched
signal dragStarted
readonly property int reservedSpaceForLabel: metrics.height
property int availableCellHeight: PlasmaCore.Units.iconSizes.huge + reservedSpaceForLabel
readonly property real openFactor: factorNormalize(flickable.contentY / (units.gridUnit * 10))
// height from top of screen that the drawer starts
readonly property real drawerTopMargin: height - topPadding - bottomPadding - closedPositionOffset
//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));
}
//END functions
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"
visible: false
font.pointSize: PlasmaCore.Theme.defaultFont.pointSize * 0.9
}
// bottom divider
GradientBar {
opacity: root.status !== AbstractAppDrawer.Status.Closed ? 0.6 : 0
visible: root.bottomPadding > 0
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.bottomMargin: root.bottomPadding - height
}
// physical position of drawer is handled through this flickable
Flickable {
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
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
spacing: 0
// margin of the drawer from the top
Rectangle {
id: topMargin
color: "transparent"
Layout.fillWidth: true
Layout.preferredHeight: root.drawerTopMargin
OpenDrawerButton {
id: openDrawerButton
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
factor: root.openFactor
flickable: view
onOpenRequested: root.open();
onCloseRequested: root.close();
}
}
// actual drawer
Controls.Control {
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
// drawer background
background: Rectangle {
id: scrim
color: "black"
opacity: 0.6 * root.openFactor
// remove radius
radius: view.contentY > (topMargin.height - PlasmaCore.Units.gridUnit) ? 0 : PlasmaCore.Units.gridUnit
Behavior on radius {
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.InOutQuad }
}
}
opacity: root.openFactor
contentItem: ColumnLayout {
id: holdingColumn
width: view.width
spacing: 0
// drawer header
Controls.Control {
id: flickableHeader
Layout.preferredHeight: root.headerHeight
Layout.fillWidth: true
leftPadding: 0; rightPadding: 0; topPadding: 0; bottomPadding: 0
contentItem: root.headerItem
}
// drawer body
Controls.Control {
id: flickableBody
Layout.fillHeight: true
Layout.fillWidth: true
leftPadding: 0; rightPadding: 0; topPadding: 0; bottomPadding: 0
contentItem: root.contentItem
}
}
}
}
}
}

View file

@ -1,306 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.14
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 PC3
//import org.kde.kquickcontrolsaddons 2.0
import org.kde.kirigami 2.10 as Kirigami
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
import "private"
Item {
id: root
enum Status {
Closed,
Peeking,
Open
}
enum MovementDirection {
None = 0,
Up,
Down
}
readonly property int status: {
if (view.contentY >= -view.originY - view.height) {
return AppDrawer.Status.Open;
} else if (view.contentY > -view.originY - view.height*2 + closedPositionOffset*2) {
return AppDrawer.Status.Peeking;
} else {
return AppDrawer.Status.Closed;
}
}
property real offset: 0
property real closedPositionOffset: 0
property real leftPadding: 0
property real topPadding: 0
property real bottomPadding: 100
property real rightPadding: 0
readonly property int columns: Math.floor(view.width / cellWidth)
property alias cellWidth: view.cellWidth
property alias cellHeight: view.cellHeight
signal launched
signal dragStarted
readonly property int reservedSpaceForLabel: metrics.height
property int availableCellHeight: PlasmaCore.Units.iconSizes.huge + reservedSpaceForLabel
property alias flickable: view
readonly property real openFactor: Math.min(1, Math.max(0, Math.min(1, (view.contentY + view.originY + view.height*2 - root.closedPositionOffset*2) / (PlasmaCore.Units.gridUnit * 10))))
function open() {
if (root.status === AppDrawer.Status.Open) {
view.flick(0,1);
} else {
scrollAnim.to = 0
scrollAnim.restart();
}
}
function close() {
if (root.status !== AppDrawer.Status.Closed) {
scrollAnim.to = -view.height + closedPositionOffset;
scrollAnim.restart();
}
}
function snapDrawerStatus() {
if (root.status !== AppDrawer.Status.Peeking) {
return;
}
if (view.movementDirection === AppDrawer.MovementDirection.Up) {
if (view.contentY > 7 * -view.height / 8) { // over one eighth of the screen
open();
} else {
close();
}
} else {
if (view.contentY < -view.height / 8) { // over one eighth of the screen
close();
} else {
open();
}
}
}
Drag.dragType: Drag.Automatic
onOffsetChanged: {
if (!view.moving) {
view.contentY = Math.max(0, offset) - view.originY - view.height*2 + closedPositionOffset*2
}
}
NumberAnimation {
id: scrollAnim
target: view
properties: "contentY"
duration: PlasmaCore.Units.longDuration * 2
easing.type: Easing.OutQuad
easing.amplitude: 2.0
}
PC3.Label {
id: metrics
text: "M\nM"
visible: false
font.pointSize: PlasmaCore.Theme.defaultFont.pointSize * 0.9
}
OpenDrawerButton {
id: openDrawerButton
anchors {
left: parent.left
right: parent.right
bottom: scrim.top
}
factor: root.openFactor
flickable: view
onOpenRequested: root.open();
onCloseRequested: root.close();
}
Rectangle {
id: scrim
anchors {
left: view.left
right: view.right
leftMargin: -1
rightMargin: -1
}
border.color: Qt.rgba(1, 1, 1, 0.5)
radius: PlasmaCore.Units.gridUnit
color: "black"
opacity: 0.4 * root.openFactor
height: root.height + radius * 2
y: Math.min(view.height, Math.max(-radius, -view.contentY - view.originY - root.height + root.topPadding + root.bottomPadding + root.closedPositionOffset))
}
Timer {
id: closeTimer
interval: 1000
onTriggered: root.close();
}
GridView {
id: view
anchors {
fill: parent
leftMargin: root.leftPadding
topMargin: root.topPadding
rightMargin: root.rightPadding
bottomMargin: root.bottomPadding
}
opacity: {
if (root.status == AppDrawer.Status.Open) {
return 1;
} else if (root.status == AppDrawer.Status.Closed) {
return 0;
} else { // peeking
return root.openFactor;
}
}
visible: root.status !== AppDrawer.Status.Closed
cellWidth: view.width / Math.floor(view.width / ((root.availableCellHeight - root.reservedSpaceForLabel) + PlasmaCore.Units.smallSpacing*4))
cellHeight: root.availableCellHeight
clip: true
cacheBuffer: contentHeight
property real oldContentY: contentY
property int movementDirection: AppDrawer.MovementDirection.None
onContentYChanged: {
if (contentY > oldContentY) {
movementDirection = AppDrawer.MovementDirection.Up;
} else {
movementDirection = AppDrawer.MovementDirection.Down;
}
oldContentY = contentY;
root.offset = contentY + view.originY + view.height*2 - root.closedPositionOffset*2
}
onMovementEnded: root.snapDrawerStatus()
onFlickEnded: movementEnded()
// boundsBehavior: Flickable.StopAtBounds
Connections {
target: HomeScreenComponents.ApplicationListModel
onLaunchError: NanoShell.StartupFeedback.close()
}
model: HomeScreenComponents.ApplicationListModel
header: Rectangle {
height: root.height - root.topPadding - root.bottomPadding - root.closedPositionOffset
property real oldHeight: height
onHeightChanged: {
if (root.status !== AppDrawer.Status.Open) {
view.contentY = -view.height + root.closedPositionOffset;
}
oldHeight = height;
}
}
delegate: DrawerDelegate {
id: delegate
width: view.cellWidth
height: view.cellHeight
reservedSpaceForLabel: root.reservedSpaceForLabel
onDragStarted: (imageSource, x, y, mimeData) => {
root.Drag.imageSource = imageSource;
root.Drag.hotSpot.x = x;
root.Drag.hotSpot.y = y;
root.Drag.mimeData = { "text/x-plasma-phone-homescreen-launcher": mimeData };
root.close()
root.dragStarted()
root.Drag.active = true;
}
onLaunch: (x, y, icon, title, storageId) => {
if (icon !== "") {
NanoShell.StartupFeedback.open(
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));
}
HomeScreenComponents.ApplicationListModel.setMinimizedDelegate(index, delegate);
HomeScreenComponents.ApplicationListModel.runApplication(storageId);
root.launched();
closeTimer.restart();
}
}
PC3.ScrollBar.vertical: PC3.ScrollBar {
id: scrollabr
opacity: view.moving
interactive: false
enabled: false
Behavior on opacity {
OpacityAnimator {
duration: PlasmaCore.Units.longDuration * 2
easing.type: Easing.InOutQuad
}
}
implicitWidth: Math.round(PlasmaCore.Units.gridUnit/3)
contentItem: Rectangle {
radius: width/2
color: Qt.rgba(1, 1, 1, 0.3)
border.color: Qt.rgba(0, 0, 0, 0.4)
}
}
}
Rectangle {
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
leftMargin: PlasmaCore.Units.gridUnit + root.leftPadding
rightMargin: PlasmaCore.Units.gridUnit + root.rightPadding
bottomMargin: root.bottomPadding - height
}
height: 1
visible: root.bottomPadding > 0
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: Qt.rgba(1, 1, 1, 0) }
GradientStop { position: 0.15; color: Qt.rgba(1, 1, 1, 0.5) }
GradientStop { position: 0.5; color: Qt.rgba(1, 1, 1, 1) }
GradientStop { position: 0.85; color: Qt.rgba(1, 1, 1, 0.5) }
GradientStop { position: 1.0; color: Qt.rgba(1, 1, 1, 0) }
}
opacity: root.status !== AppDrawer.Status.Closed ? 0.6 : 0
Behavior on opacity {
OpacityAnimator {
duration: PlasmaCore.Units.longDuration * 2
easing.type: Easing.InOutQuad
}
}
}
}

View file

@ -0,0 +1,103 @@
/*
* 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 2.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)
})
}
onClicked: {
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: 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
maximumLineCount: 2
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignTop
elide: Text.ElideRight
text: model.applicationName
//FIXME: export smallestReadableFont
font.pointSize: theme.defaultFont.pointSize * 0.9
color: "white"
}
}
}

View file

@ -0,0 +1,108 @@
/*
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
*
* 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 2.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
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)
})
}
onClicked: {
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);
}
}
hoverEnabled: true
Rectangle {
anchors.fill: parent
color: delegate.pressed ? Qt.rgba(255, 255, 255, 0.2) : (delegate.containsMouse ? Qt.rgba(255, 255, 255, 0.05) : "transparent")
Behavior on color {
ColorAnimation { duration: PlasmaCore.Units.shortDuration }
}
}
RowLayout {
anchors {
fill: parent
leftMargin: PlasmaCore.Units.smallSpacing * 2
topMargin: PlasmaCore.Units.smallSpacing
rightMargin: PlasmaCore.Units.smallSpacing * 2
bottomMargin: PlasmaCore.Units.smallSpacing
}
spacing: 0
PlasmaCore.IconItem {
id: icon
Layout.alignment: Qt.AlignLeft
Layout.minimumWidth: Layout.minimumHeight
Layout.preferredWidth: Layout.minimumHeight
Layout.minimumHeight: parent.height
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: theme.highlightColor
}
}
PlasmaComponents.Label {
id: label
visible: text.length > 0
Layout.fillWidth: true
wrapMode: Text.WordWrap
Layout.leftMargin: PlasmaCore.Units.smallSpacing * 2
Layout.rightMargin: PlasmaCore.Units.largeSpacing
maximumLineCount: 1
elide: Text.ElideRight
text: model.applicationName
//FIXME: export smallestReadableFont
font.pointSize: Math.round(theme.defaultFont.pointSize * 1.1)
color: "white"
}
}
}

View file

@ -24,7 +24,7 @@ import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenCompon
Flickable {
id: mainFlickable
property AppDrawer appDrawer
property AbstractAppDrawer appDrawer
readonly property int totalPages: Math.ceil(contentWidth / width)
property int currentIndex: 0
@ -35,9 +35,8 @@ Flickable {
property alias dragGestureEnabled: gestureHandler.enabled
opacity: appDrawer ? 1 - appDrawer.openFactor : 1
transform: Translate {
y: appDrawer ? -mainFlickable.height/10 * appDrawer.openFactor : 0
y: appDrawer ? (-mainFlickable.height / 20) * appDrawer.openFactor : 0
}
scale: appDrawer ? (3 - appDrawer.openFactor) /3 : 1
clip: true
property bool showAddPageIndicator: false

View file

@ -0,0 +1,120 @@
/*
* SPDX-FileCopyrightText: 2021 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.15 as Controls
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PC3
import org.kde.plasma.extras 2.0 as PlasmaExtra
import org.kde.kirigami 2.10 as Kirigami
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
import "private"
AbstractAppDrawer {
id: root
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;
}
}
cellWidth: root.contentWidth / Math.floor(root.contentWidth / ((root.availableCellHeight - root.reservedSpaceForLabel) + PlasmaCore.Units.smallSpacing*4))
cellHeight: root.availableCellHeight
property int columns: Math.floor(root.contentWidth / cellWidth)
property int rows: Math.ceil(model.count / columns)
cacheBuffer: rows * cellHeight
model: HomeScreenComponents.ApplicationListModel
delegate: DrawerGridDelegate {
id: delegate
width: gridView.cellWidth
height: gridView.cellHeight
reservedSpaceForLabel: root.reservedSpaceForLabel
onDragStarted: (imageSource, x, y, mimeData) => {
root.Drag.imageSource = imageSource;
root.Drag.hotSpot.x = x;
root.Drag.hotSpot.y = y;
root.Drag.mimeData = { "text/x-plasma-phone-homescreen-launcher": mimeData };
root.close()
root.dragStarted()
root.Drag.active = true;
}
onLaunch: (x, y, icon, title, storageId) => {
if (icon !== "") {
NanoShell.StartupFeedback.open(
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));
}
HomeScreenComponents.ApplicationListModel.setMinimizedDelegate(index, delegate);
HomeScreenComponents.ApplicationListModel.runApplication(storageId);
root.launched();
closeTimer.restart();
}
}
PC3.ScrollBar.vertical: PC3.ScrollBar {
id: scrollBar
interactive: true
enabled: true
Behavior on opacity {
OpacityAnimator {
duration: PlasmaCore.Units.longDuration * 2
easing.type: Easing.InOutQuad
}
}
implicitWidth: PlasmaCore.Units.smallSpacing
contentItem: Rectangle {
radius: width/2
color: Qt.rgba(1, 1, 1, 0.3)
}
}
}
}

View file

@ -160,7 +160,7 @@ DragDrop.DropArea {
TapHandler {
target: mainFlickable
enabled: appDrawer.status !== AppDrawer.Status.Open
enabled: appDrawer.status !== AbstractAppDrawer.Status.Open
onTapped: {
//Hides icons close button
appletsLayout.appletsLayoutInteracted();

View file

@ -0,0 +1,116 @@
/*
* SPDX-FileCopyrightText: 2021 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.15 as Controls
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PC3
import org.kde.plasma.extras 2.0 as PlasmaExtra
import org.kde.kirigami 2.10 as Kirigami
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
import "private"
AbstractAppDrawer {
id: root
contentItem: ListView {
id: listView
clip: true
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;
}
}
property int delegateHeight: PlasmaCore.Units.gridUnit * 3
model: HomeScreenComponents.ApplicationListModel
delegate: DrawerListDelegate {
id: delegate
width: listView.width
height: listView.delegateHeight
reservedSpaceForLabel: root.reservedSpaceForLabel
onDragStarted: (imageSource, x, y, mimeData) => {
root.Drag.imageSource = imageSource;
root.Drag.hotSpot.x = x;
root.Drag.hotSpot.y = y;
root.Drag.mimeData = { "text/x-plasma-phone-homescreen-launcher": mimeData };
root.close()
root.dragStarted()
root.Drag.active = true;
}
onLaunch: (x, y, icon, title, storageId) => {
if (icon !== "") {
NanoShell.StartupFeedback.open(
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));
}
HomeScreenComponents.ApplicationListModel.setMinimizedDelegate(index, delegate);
HomeScreenComponents.ApplicationListModel.runApplication(storageId);
root.launched();
closeTimer.restart();
}
}
PC3.ScrollBar.vertical: PC3.ScrollBar {
id: scrollBar
interactive: true
enabled: true
Behavior on opacity {
OpacityAnimator {
duration: PlasmaCore.Units.longDuration * 2
easing.type: Easing.InOutQuad
}
}
implicitWidth: PlasmaCore.Units.smallSpacing
contentItem: Rectangle {
radius: width/2
color: Qt.rgba(1, 1, 1, 0.3)
}
}
}
}

View file

@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2021 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
@ -15,7 +16,7 @@ DragHandler {
yAxis.enabled: enabled
xAxis.enabled: enabled
property Flickable mainFlickable
property Launcher.AppDrawer appDrawer
property Launcher.AbstractAppDrawer appDrawer
signal snapPage
signal snapNextPage
signal snapPrevPage
@ -33,7 +34,7 @@ DragHandler {
if (active) {
if (root.appDrawer) {
if (__scrollDirection === DragGestureHandler.None) {
if (root.appDrawer.offset > PlasmaCore.Units.gridUnit) {
if (root.appDrawer.flickable.contentY > PlasmaCore.Units.gridUnit * 2) {
__scrollDirection = DragGestureHandler.Vertical;
snapPage();
@ -49,7 +50,7 @@ DragHandler {
}
if (__scrollDirection !== DragGestureHandler.Left && __scrollDirection !== DragGestureHandler.Right) {
root.appDrawer.offset = -translation.y;
root.appDrawer.flickable.contentY = Math.min(root.appDrawer.drawerTopMargin, Math.max(0, -translation.y));
}
}
if (__scrollDirection !== DragGestureHandler.Vertical) {

View file

@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2021 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.15
import org.kde.plasma.core 2.0 as PlasmaCore
Item {
Behavior on opacity {
OpacityAnimator {
duration: PlasmaCore.Units.longDuration * 2
easing.type: Easing.InOutQuad
}
}
Rectangle {
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
leftMargin: PlasmaCore.Units.gridUnit + root.leftPadding
rightMargin: PlasmaCore.Units.gridUnit + root.rightPadding
}
height: 1
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: Qt.rgba(1, 1, 1, 0) }
GradientStop { position: 0.15; color: Qt.rgba(1, 1, 1, 0.5) }
GradientStop { position: 0.5; color: Qt.rgba(1, 1, 1, 1) }
GradientStop { position: 0.85; color: Qt.rgba(1, 1, 1, 0.5) }
GradientStop { position: 1.0; color: Qt.rgba(1, 1, 1, 0) }
}
}
}

View file

@ -25,23 +25,20 @@ MouseArea {
property Flickable flickable
property real factor: 0
height: PlasmaCore.Units.iconSizes.medium
cursorShape: Qt.PointingHandCursor
height: PlasmaCore.Units.iconSizes.smallMedium
signal openRequested
signal closeRequested
onClicked: {
if ((arrowUpIcon.flickable.contentY + arrowUpIcon.flickable.originY + arrowUpIcon.flickable.height*2) >= arrowUpIcon.flickable.height/2) {
closeRequested();
} else {
openRequested();
}
openRequested();
scrollAnim.restart();
}
Item {
anchors.centerIn: parent
width: PlasmaCore.Units.iconSizes.medium
width: PlasmaCore.Units.iconSizes.smallMedium
height: width
Rectangle {
@ -51,7 +48,7 @@ MouseArea {
left: parent.left
verticalCenterOffset: -arrowUpIcon.height/4 + (arrowUpIcon.height/4) * arrowUpIcon.factor
}
color: PlasmaCore.Theme.backgroundColor
color: "white"
transformOrigin: Item.Right
rotation: -45 + 90 * arrowUpIcon.factor
antialiasing: true
@ -64,7 +61,7 @@ MouseArea {
right: parent.right
verticalCenterOffset: -arrowUpIcon.height/4 + (arrowUpIcon.height/4) * arrowUpIcon.factor
}
color: PlasmaCore.Theme.backgroundColor
color: "white"
transformOrigin: Item.Left
rotation: 45 - 90 * arrowUpIcon.factor
antialiasing: true

View file

@ -2,8 +2,11 @@ module org.kde.plasma.private.mobilehomescreencomponents
plugin mobilehomescreencomponentsplugin
AppDrawer 0.1 AppDrawer.qml
DrawerDelegate 0.1 DrawerDelegate.qml
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
FavoriteStrip 0.1 FavoriteStrip.qml
FlickablePages 0.1 FlickablePages.qml
HomeDelegate 0.1 HomeDelegate.qml

View file

@ -12,6 +12,7 @@ import QtGraphicalEffects 1.0
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.extras 2.0 as PlasmaExtra
import org.kde.draganddrop 2.0 as DragDrop
import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenComponents
@ -53,6 +54,9 @@ FocusScope {
}
componentComplete = true;
recalculateMaxFavoriteCount()
// ensure the gestures work immediately on load
forceActiveFocus();
}
Plasmoid.onScreenChanged: {
@ -76,13 +80,13 @@ FocusScope {
}
function onSnapHomeScreenPosition() {
if (lastRequestedPosition < 0) {
appDrawer.open();
root.appDrawer.open();
} else {
appDrawer.close();
root.appDrawer.close();
}
}
function onRequestRelativeScroll(pos) {
appDrawer.offset -= pos.y;
root.appDrawer.offset -= pos.y;
lastRequestedPosition = pos.y;
}
}
@ -96,23 +100,45 @@ FocusScope {
bottomMargin: plasmoid.screenGeometry.height - plasmoid.availableScreenRect.height - plasmoid.availableScreenRect.y
}
//TODO: favorite strip disappearing with everything else
footer: favoriteStrip
appletsLayout: homeScreenContents.appletsLayout
appDrawer: appDrawer
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.focus && appDrawer.status !== HomeScreenComponents.AppDrawer.Status.Open && !appletsLayout.editMode && !plasmoid.editMode && !homeScreenContents.launcherDragManager.active
dragGestureEnabled: root.focus && (!appDrawer || appDrawer.status !== HomeScreenComponents.AbstractAppDrawer.Status.Open) && !appletsLayout.editMode && !plasmoid.editMode && !homeScreenContents.launcherDragManager.active
HomeScreenComponents.HomeScreenContents {
id: homeScreenContents
width: mainFlickable.width * 100
favoriteStrip: favoriteStrip
}
footer: HomeScreenComponents.FavoriteStrip {
id: favoriteStrip
appletsLayout: homeScreenContents.appletsLayout
visible: 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.focus = true;
}
}
}
// listview/gridview header
property int headerHeight: Math.round(PlasmaCore.Units.gridUnit * 3)
property string appDrawerType: "gridview" // gridview/listview
property alias appDrawer: appDrawerLoader.item
Plasmoid.onActivated: {
console.log("Triggered!", plasmoid.nativeInterface.showingDesktop)
@ -124,41 +150,90 @@ FocusScope {
plasmoid.nativeInterface.showingDesktop = true
} else if (appDrawer.status !== HomeScreenComponents.AppDrawer.Status.Open) {
mainFlickable.currentIndex = 0
appDrawer.open()
root.appDrawer.open()
} else {
plasmoid.nativeInterface.showingDesktop = false
appDrawer.close()
root.appDrawer.close()
}
}
HomeScreenComponents.AppDrawer {
id: appDrawer
anchors.fill: parent
topPadding: plasmoid.availableScreenRect.y
bottomPadding: plasmoid.screenGeometry.height - plasmoid.availableScreenRect.height - plasmoid.availableScreenRect.y
rightPadding: plasmoid.screenGeometry.width - plasmoid.availableScreenRect.width - plasmoid.availableScreenRect.x
closedPositionOffset: favoriteStrip.height
}
HomeScreenComponents.FavoriteStrip {
id: favoriteStrip
appletsLayout: homeScreenContents.appletsLayout
visible: 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;
Component {
id: headerComponent
PlasmaCore.ColorScope {
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
RowLayout {
anchors.topMargin: PlasmaCore.Units.smallSpacing
anchors.leftMargin: PlasmaCore.Units.largeSpacing
anchors.rightMargin: PlasmaCore.Units.largeSpacing
anchors.fill: parent
spacing: PlasmaCore.Units.smallSpacing
PlasmaExtra.Heading {
color: "white"
level: 1
text: i18n("Applications")
}
Item { Layout.fillWidth: true }
PlasmaComponents.ToolButton {
icon.name: "view-list-symbolic"
implicitWidth: Math.round(PlasmaCore.Units.gridUnit * 2.1)
implicitHeight: Math.round(PlasmaCore.Units.gridUnit * 2.1)
onClicked: {
if (root.appDrawerType !== "listview") {
root.appDrawerType = "listview";
appDrawer.flickable.goToBeginning(); // jump to top
}
}
}
PlasmaComponents.ToolButton {
icon.name: "view-grid-symbolic"
implicitWidth: Math.round(PlasmaCore.Units.gridUnit * 2.1)
implicitHeight: Math.round(PlasmaCore.Units.gridUnit * 2.1)
onClicked: {
if (root.appDrawerType !== "gridview") {
root.appDrawerType = "gridview";
appDrawer.flickable.goToBeginning(); // jump to top
}
}
}
}
onLongPressed: homeScreenContents.appletsLayout.editMode = true;
onPressedChanged: root.focus = true;
}
}
Component {
id: listViewDrawer
HomeScreenComponents.ListViewAppDrawer {
anchors.fill: parent
topPadding: plasmoid.availableScreenRect.y
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
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
}
}