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 { Flickable {
id: mainFlickable id: mainFlickable
property AppDrawer appDrawer property AbstractAppDrawer appDrawer
readonly property int totalPages: Math.ceil(contentWidth / width) readonly property int totalPages: Math.ceil(contentWidth / width)
property int currentIndex: 0 property int currentIndex: 0
@ -35,9 +35,8 @@ Flickable {
property alias dragGestureEnabled: gestureHandler.enabled property alias dragGestureEnabled: gestureHandler.enabled
opacity: appDrawer ? 1 - appDrawer.openFactor : 1 opacity: appDrawer ? 1 - appDrawer.openFactor : 1
transform: Translate { 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 clip: true
property bool showAddPageIndicator: false 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 { TapHandler {
target: mainFlickable target: mainFlickable
enabled: appDrawer.status !== AppDrawer.Status.Open enabled: appDrawer.status !== AbstractAppDrawer.Status.Open
onTapped: { onTapped: {
//Hides icons close button //Hides icons close button
appletsLayout.appletsLayoutInteracted(); 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 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
* *
* SPDX-License-Identifier: LGPL-2.0-or-later * SPDX-License-Identifier: LGPL-2.0-or-later
*/ */
@ -15,7 +16,7 @@ DragHandler {
yAxis.enabled: enabled yAxis.enabled: enabled
xAxis.enabled: enabled xAxis.enabled: enabled
property Flickable mainFlickable property Flickable mainFlickable
property Launcher.AppDrawer appDrawer property Launcher.AbstractAppDrawer appDrawer
signal snapPage signal snapPage
signal snapNextPage signal snapNextPage
signal snapPrevPage signal snapPrevPage
@ -33,7 +34,7 @@ DragHandler {
if (active) { if (active) {
if (root.appDrawer) { if (root.appDrawer) {
if (__scrollDirection === DragGestureHandler.None) { if (__scrollDirection === DragGestureHandler.None) {
if (root.appDrawer.offset > PlasmaCore.Units.gridUnit) { if (root.appDrawer.flickable.contentY > PlasmaCore.Units.gridUnit * 2) {
__scrollDirection = DragGestureHandler.Vertical; __scrollDirection = DragGestureHandler.Vertical;
snapPage(); snapPage();
@ -49,7 +50,7 @@ DragHandler {
} }
if (__scrollDirection !== DragGestureHandler.Left && __scrollDirection !== DragGestureHandler.Right) { 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) { 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 Flickable flickable
property real factor: 0 property real factor: 0
height: PlasmaCore.Units.iconSizes.medium cursorShape: Qt.PointingHandCursor
height: PlasmaCore.Units.iconSizes.smallMedium
signal openRequested signal openRequested
signal closeRequested signal closeRequested
onClicked: { onClicked: {
if ((arrowUpIcon.flickable.contentY + arrowUpIcon.flickable.originY + arrowUpIcon.flickable.height*2) >= arrowUpIcon.flickable.height/2) {
closeRequested();
} else {
openRequested(); openRequested();
}
scrollAnim.restart(); scrollAnim.restart();
} }
Item { Item {
anchors.centerIn: parent anchors.centerIn: parent
width: PlasmaCore.Units.iconSizes.medium width: PlasmaCore.Units.iconSizes.smallMedium
height: width height: width
Rectangle { Rectangle {
@ -51,7 +48,7 @@ MouseArea {
left: parent.left left: parent.left
verticalCenterOffset: -arrowUpIcon.height/4 + (arrowUpIcon.height/4) * arrowUpIcon.factor verticalCenterOffset: -arrowUpIcon.height/4 + (arrowUpIcon.height/4) * arrowUpIcon.factor
} }
color: PlasmaCore.Theme.backgroundColor color: "white"
transformOrigin: Item.Right transformOrigin: Item.Right
rotation: -45 + 90 * arrowUpIcon.factor rotation: -45 + 90 * arrowUpIcon.factor
antialiasing: true antialiasing: true
@ -64,7 +61,7 @@ MouseArea {
right: parent.right right: parent.right
verticalCenterOffset: -arrowUpIcon.height/4 + (arrowUpIcon.height/4) * arrowUpIcon.factor verticalCenterOffset: -arrowUpIcon.height/4 + (arrowUpIcon.height/4) * arrowUpIcon.factor
} }
color: PlasmaCore.Theme.backgroundColor color: "white"
transformOrigin: Item.Left transformOrigin: Item.Left
rotation: 45 - 90 * arrowUpIcon.factor rotation: 45 - 90 * arrowUpIcon.factor
antialiasing: true antialiasing: true

View file

@ -2,8 +2,11 @@ module org.kde.plasma.private.mobilehomescreencomponents
plugin mobilehomescreencomponentsplugin plugin mobilehomescreencomponentsplugin
AppDrawer 0.1 AppDrawer.qml AbstractAppDrawer 0.1 AbstractAppDrawer.qml
DrawerDelegate 0.1 DrawerDelegate.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 FavoriteStrip 0.1 FavoriteStrip.qml
FlickablePages 0.1 FlickablePages.qml FlickablePages 0.1 FlickablePages.qml
HomeDelegate 0.1 HomeDelegate.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.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents 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.draganddrop 2.0 as DragDrop
import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenComponents import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenComponents
@ -53,6 +54,9 @@ FocusScope {
} }
componentComplete = true; componentComplete = true;
recalculateMaxFavoriteCount() recalculateMaxFavoriteCount()
// ensure the gestures work immediately on load
forceActiveFocus();
} }
Plasmoid.onScreenChanged: { Plasmoid.onScreenChanged: {
@ -76,13 +80,13 @@ FocusScope {
} }
function onSnapHomeScreenPosition() { function onSnapHomeScreenPosition() {
if (lastRequestedPosition < 0) { if (lastRequestedPosition < 0) {
appDrawer.open(); root.appDrawer.open();
} else { } else {
appDrawer.close(); root.appDrawer.close();
} }
} }
function onRequestRelativeScroll(pos) { function onRequestRelativeScroll(pos) {
appDrawer.offset -= pos.y; root.appDrawer.offset -= pos.y;
lastRequestedPosition = pos.y; lastRequestedPosition = pos.y;
} }
} }
@ -96,57 +100,25 @@ FocusScope {
bottomMargin: plasmoid.screenGeometry.height - plasmoid.availableScreenRect.height - plasmoid.availableScreenRect.y bottomMargin: plasmoid.screenGeometry.height - plasmoid.availableScreenRect.height - plasmoid.availableScreenRect.y
} }
//TODO: favorite strip disappearing with everything else
footer: favoriteStrip
appletsLayout: homeScreenContents.appletsLayout appletsLayout: homeScreenContents.appletsLayout
appDrawer: appDrawer appDrawer: root.appDrawer
contentWidth: Math.max(width, width * Math.ceil(homeScreenContents.itemsBoundingRect.width/width)) + (homeScreenContents.launcherDragManager.active ? width : 0) contentWidth: Math.max(width, width * Math.ceil(homeScreenContents.itemsBoundingRect.width/width)) + (homeScreenContents.launcherDragManager.active ? width : 0)
showAddPageIndicator: homeScreenContents.launcherDragManager.active 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 { HomeScreenComponents.HomeScreenContents {
id: homeScreenContents id: homeScreenContents
width: mainFlickable.width * 100 width: mainFlickable.width * 100
favoriteStrip: favoriteStrip favoriteStrip: favoriteStrip
} }
}
Plasmoid.onActivated: { footer: HomeScreenComponents.FavoriteStrip {
console.log("Triggered!", plasmoid.nativeInterface.showingDesktop)
// 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.AppDrawer.Status.Open) {
mainFlickable.currentIndex = 0
appDrawer.open()
} else {
plasmoid.nativeInterface.showingDesktop = false
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 id: favoriteStrip
appletsLayout: homeScreenContents.appletsLayout appletsLayout: homeScreenContents.appletsLayout
visible: flow.children.length > 0 || homeScreenContents.launcherDragManager.active || homeScreenContents.containsDrag visible: flow.children.length > 0 || homeScreenContents.launcherDragManager.active || homeScreenContents.containsDrag
opacity: homeScreenContents.launcherDragManager.active && HomeScreenComponents.ApplicationListModel.favoriteCount >= HomeScreenComponents.ApplicationListModel.maxFavoriteCount ? 0.3 : 1 opacity: homeScreenContents.launcherDragManager.active && HomeScreenComponents.ApplicationListModel.favoriteCount >= HomeScreenComponents.ApplicationListModel.maxFavoriteCount ? 0.3 : 1
TapHandler { TapHandler {
@ -160,5 +132,108 @@ FocusScope {
onPressedChanged: root.focus = 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)
// 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.AppDrawer.Status.Open) {
mainFlickable.currentIndex = 0
root.appDrawer.open()
} else {
plasmoid.nativeInterface.showingDesktop = false
root.appDrawer.close()
}
}
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
}
}
}
}
}
}
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
}
} }