More familiar behavior for the App Drawer

This makes the behavior of the app drawer a bit more familiar and
slightly more similar to Android for now:

* The drawer always contains every application
* Applications are always alphabetically ordered
* The drawer opens completely, not staying stuck in "in between" states
* is possible to drag more copies of a single app on the homescreen/favorites
* possible to remove an icon from the homescreen or favorites

Two things have been prepared in there (but are material for 5.22 only,
so not finished)

* Things have been reordered such in a way that makes easy for most of
the qml files to become components to make easy for people to build
their own customized homescreen
* basic infrastructure is there to allow for multiple horizontal pages
scroll, though not implemented yet as needs changes to plasma-workspace
layouting code beforehand
This commit is contained in:
Marco Martin 2021-02-15 10:44:48 +00:00 committed by Bhushan Shah
parent cd4675b33e
commit 00d63af2da
19 changed files with 1441 additions and 370 deletions

View file

@ -1,6 +1,7 @@
set(homescreen_SRCS
homescreen.cpp
applicationlistmodel.cpp
favoritesmodel.cpp
)
add_library(plasma_containment_phone_homescreen MODULE ${homescreen_SRCS})

View file

@ -46,7 +46,7 @@ constexpr int MAX_FAVOURITES = 5;
ApplicationListModel::ApplicationListModel(HomeScreen *parent)
: QAbstractListModel(parent),
m_homeScreen(parent)
m_applet(parent)
{
connect(KSycoca::self(), qOverload<const QStringList &>(&KSycoca::databaseChanged),
this, &ApplicationListModel::sycocaDbChanged);
@ -59,15 +59,18 @@ ApplicationListModel::~ApplicationListModel() = default;
void ApplicationListModel::loadSettings()
{
m_favorites = m_homeScreen->config().readEntry("Favorites", QStringList());
if (!m_applet) {
return;
}
m_favorites = m_applet->config().readEntry("Favorites", QStringList());
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
const auto di = m_homeScreen->config().readEntry("DesktopItems", QStringList());
const auto di = m_applet->config().readEntry("DesktopItems", QStringList());
m_desktopItems = QSet<QString>(di.begin(), di.end());
#else
m_desktopItems = m_homeScreen->config().readEntry("DesktopItems", QStringList()).toSet();
m_desktopItems = m_applet->config().readEntry("DesktopItems", QStringList()).toSet();
#endif
m_appOrder = m_homeScreen->config().readEntry("AppOrder", QStringList());
m_maxFavoriteCount = m_homeScreen->config().readEntry("MaxFavoriteCount", MAX_FAVOURITES);
m_appOrder = m_applet->config().readEntry("AppOrder", QStringList());
m_maxFavoriteCount = m_applet->config().readEntry("MaxFavoriteCount", MAX_FAVOURITES);
int i = 0;
for (const QString &app : qAsConst(m_appOrder)) {
@ -75,7 +78,7 @@ void ApplicationListModel::loadSettings()
++i;
}
loadApplications();
//loadApplications();
}
QHash<int, QByteArray> ApplicationListModel::roleNames() const
@ -88,7 +91,8 @@ QHash<int, QByteArray> ApplicationListModel::roleNames() const
{ApplicationOriginalRowRole, QByteArrayLiteral("applicationOriginalRow")},
{ApplicationStartupNotifyRole, QByteArrayLiteral("applicationStartupNotify")},
{ApplicationLocationRole, QByteArrayLiteral("applicationLocation")},
{ApplicationRunningRole, QByteArrayLiteral("applicationRunning")}
{ApplicationRunningRole, QByteArrayLiteral("applicationRunning")},
{ApplicationUniqueIdRole, QByteArrayLiteral("applicationUniqueId")}
};
}
@ -208,17 +212,18 @@ void ApplicationListModel::loadApplications()
data.name = service->name();
data.icon = service->icon();
data.storageId = service->storageId();
data.uniqueId = service->storageId();
data.entryPath = service->exec();
data.startupNotify = service->property(QStringLiteral("StartupNotify")).toBool();
if (m_favorites.contains(data.storageId)) {
if (m_favorites.contains(data.uniqueId)) {
data.location = Favorites;
foundFavorites.insert(data.storageId);
} else if (m_desktopItems.contains(data.storageId)) {
foundFavorites.insert(data.uniqueId);
} else if (m_desktopItems.contains(data.uniqueId)) {
data.location = Desktop;
}
auto it = m_appPositions.constFind(service->storageId());
auto it = m_appPositions.constFind(data.uniqueId);
if (it != m_appPositions.constEnd()) {
orderedList[*it] = data;
} else {
@ -249,7 +254,9 @@ void ApplicationListModel::loadApplications()
}
}
if (favChanged) {
m_homeScreen->config().writeEntry("Favorites", m_favorites);
if (m_applet) {
m_applet->config().writeEntry("Favorites", m_favorites);
}
emit favoriteCountChanged();
}
}
@ -278,6 +285,8 @@ QVariant ApplicationListModel::data(const QModelIndex &index, int role) const
return m_applicationList.at(index.row()).location;
case ApplicationRunningRole:
return m_applicationList.at(index.row()).window != nullptr;
case ApplicationUniqueIdRole:
return m_applicationList.at(index.row()).uniqueId;
default:
return QVariant();
@ -319,35 +328,46 @@ void ApplicationListModel::setLocation(int row, LauncherLocation location)
if (location == Favorites) {
qWarning() << "favoriting" << row << data.name;
// Deny favorites when full
if (row >= m_maxFavoriteCount || m_favorites.count() >= m_maxFavoriteCount) {
if (row >= m_maxFavoriteCount || m_favorites.count() >= m_maxFavoriteCount ||
m_favorites.contains(data.uniqueId)) {
return;
}
m_favorites.insert(row, data.storageId);
m_favorites.insert(row, data.uniqueId);
m_homeScreen->config().writeEntry("Favorites", m_favorites);
if (m_applet) {
m_applet->config().writeEntry("Favorites", m_favorites);
}
emit favoriteCountChanged();
// Out of favorites
} else if (data.location == Favorites) {
m_favorites.removeAll(data.storageId);
m_homeScreen->config().writeEntry("Favorites", m_favorites);
m_favorites.removeAll(data.uniqueId);
if (m_applet) {
m_applet->config().writeEntry("Favorites", m_favorites);
}
emit favoriteCountChanged();
}
// In Desktop
if (location == Desktop) {
m_desktopItems.insert(data.storageId);
m_homeScreen->config().writeEntry("DesktopItems", m_desktopItems.values());
m_desktopItems.insert(data.uniqueId);
if (m_applet) {
m_applet->config().writeEntry("DesktopItems", m_desktopItems.values());
}
// Out of Desktop
} else if (data.location == Desktop) {
m_desktopItems.remove(data.storageId);
m_homeScreen->config().writeEntry(QStringLiteral("DesktopItems"), m_desktopItems.values());
m_desktopItems.remove(data.uniqueId);
if (m_applet) {
m_applet->config().writeEntry(QStringLiteral("DesktopItems"), m_desktopItems.values());
}
}
data.location = location;
emit m_homeScreen->configNeedsSaving();
if (m_applet) {
emit m_applet->configNeedsSaving();
}
emit dataChanged(index(row, 0), index(row, 0));
}
@ -377,12 +397,14 @@ void ApplicationListModel::moveItem(int row, int destination)
m_appPositions.clear();
int i = 0;
for (const ApplicationData &app : qAsConst(m_applicationList)) {
m_appOrder << app.storageId;
m_appPositions[app.storageId] = i;
m_appOrder << app.uniqueId;
m_appPositions[app.uniqueId] = i;
++i;
}
m_homeScreen->config().writeEntry("AppOrder", m_appOrder);
if (m_applet) {
m_applet->config().writeEntry("AppOrder", m_appOrder);
}
endMoveRows();
}
@ -434,11 +456,23 @@ void ApplicationListModel::setMaxFavoriteCount(int count)
}
m_maxFavoriteCount = count;
m_homeScreen->config().writeEntry("MaxFavoriteCount", m_maxFavoriteCount);
if (m_applet) {
m_applet->config().writeEntry("MaxFavoriteCount", m_maxFavoriteCount);
}
emit maxFavoriteCountChanged();
}
Plasma::Applet *ApplicationListModel::applet() const
{
return m_applet;
}
void ApplicationListModel::setApplet(Plasma::Applet *applet)
{
m_applet = applet;
}
void ApplicationListModel::setMinimizedDelegate(int row, QQuickItem *delegate)
{
if (row < 0 || row >= m_applicationList.count()) {

View file

@ -57,6 +57,7 @@ public:
Q_ENUM(LauncherLocation)
struct ApplicationData {
QString uniqueId;
QString name;
QString icon;
QString storageId;
@ -74,7 +75,8 @@ public:
ApplicationOriginalRowRole,
ApplicationStartupNotifyRole,
ApplicationLocationRole,
ApplicationRunningRole
ApplicationRunningRole,
ApplicationUniqueIdRole
};
ApplicationListModel(HomeScreen *parent = nullptr);
@ -92,6 +94,9 @@ public:
int maxFavoriteCount() const;
void setMaxFavoriteCount(int count);
void setApplet(Plasma::Applet *applet);
Plasma::Applet *applet() const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
Qt::ItemFlags flags(const QModelIndex &index) const override;
@ -104,7 +109,7 @@ public:
Q_INVOKABLE void runApplication(const QString &storageId);
Q_INVOKABLE void loadApplications();
Q_INVOKABLE virtual void loadApplications();
Q_INVOKABLE void setMinimizedDelegate(int row, QQuickItem *delegate);
Q_INVOKABLE void unsetMinimizedDelegate(int row, QQuickItem *delegate);
@ -117,13 +122,13 @@ Q_SIGNALS:
void favoriteCountChanged();
void maxFavoriteCountChanged();
private:
protected:
void initWayland();
QList<ApplicationData> m_applicationList;
KWayland::Client::PlasmaWindowManagement *m_windowManagement = nullptr;
HomeScreen *m_homeScreen = nullptr;
Plasma::Applet *m_applet = nullptr;
int m_maxFavoriteCount = 0;
QStringList m_appOrder;
QStringList m_favorites;

View file

@ -0,0 +1,199 @@
/*
* Copyright (C) 2014 Antonis Tsiapaliokas <antonis.tsiapaliokas@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* or (at your option) any later version, as published by the Free
* Software Foundation
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// Self
#include "favoritesmodel.h"
// Qt
#include <QByteArray>
#include <QModelIndex>
#include <QDebug>
// KDE
#include <KService>
#include <KSharedConfig>
constexpr int MAX_FAVOURITES = 5;
FavoritesModel::FavoritesModel(HomeScreen *parent)
: ApplicationListModel(parent)
{
}
FavoritesModel::~FavoritesModel() = default;
QString FavoritesModel::storageToUniqueId(const QString &storageId) const
{
if (storageId.isEmpty()) {
return storageId;
}
int id = 0;
QString uniqueId = storageId + QStringLiteral("-") + QString::number(id);
while (m_appOrder.contains(uniqueId)) {
uniqueId = storageId + QStringLiteral("-") + QString::number(++id);
}
return uniqueId;
}
QString FavoritesModel::uniqueToStorageId(const QString &uniqueId) const
{
if (uniqueId.isEmpty()) {
return uniqueId;
}
return uniqueId.split(QLatin1Char('-')).first();
}
void FavoritesModel::loadApplications()
{
beginResetModel();
m_applicationList.clear();
QSet<QString> appsToRemove;
for (const auto &uniqueId : m_appOrder) {
const QString storageId = uniqueToStorageId(uniqueId);
if (KService::Ptr service = KService::serviceByStorageId(storageId)) {
ApplicationData data;
data.name = service->name();
data.icon = service->icon();
data.storageId = service->storageId();
data.uniqueId = uniqueId;
data.entryPath = service->exec();
data.startupNotify = service->property(QStringLiteral("StartupNotify")).toBool();
if (m_favorites.contains(uniqueId)) {
data.location = Favorites;
} else if (m_desktopItems.contains(uniqueId)) {
data.location = Desktop;
}
m_applicationList << data;
} else {
appsToRemove.insert(uniqueId);
}
}
bool favChanged = false;
for (const auto &uniqueId : appsToRemove) {
m_appOrder.removeAll(uniqueId);
if (m_favorites.contains(uniqueId)) {
favChanged = true;
m_favorites.removeAll(uniqueId);
}
m_desktopItems.remove(uniqueId);
}
endResetModel();
emit countChanged();
if (m_applet) {
m_applet->config().writeEntry("Favorites", m_favorites);
m_applet->config().writeEntry("AppOrder", m_appOrder);
m_applet->config().writeEntry("DesktopItems", m_desktopItems.values());
emit m_applet->configNeedsSaving();
}
if (favChanged) {
emit favoriteCountChanged();
}
}
void FavoritesModel::addFavorite(const QString &storageId, int row, LauncherLocation location)
{
if (row < 0 || row > m_applicationList.count()) {
return;
}
if (KService::Ptr service = KService::serviceByStorageId(storageId)) {
const QString uniqueId = storageToUniqueId(service->storageId());
ApplicationData data;
data.name = service->name();
data.icon = service->icon();
data.storageId = service->storageId();
data.uniqueId = uniqueId;
data.entryPath = service->exec();
data.startupNotify = service->property(QStringLiteral("StartupNotify")).toBool();
bool favChanged = false;
if (location == Favorites) {
data.location = Favorites;
m_favorites.insert(qMin(row, m_favorites.count()), uniqueId);
favChanged = true;
} else {
data.location = location;
m_desktopItems.insert(data.uniqueId);
}
beginInsertRows(QModelIndex(), row, row);
m_applicationList.insert(row, data);
m_appOrder.insert(row, uniqueId);
endInsertRows();
if (favChanged) {
emit favoriteCountChanged();
}
if (m_applet) {
m_applet->config().writeEntry("Favorites", m_favorites);
m_applet->config().writeEntry("AppOrder", m_appOrder);
m_applet->config().writeEntry("DesktopItems", m_desktopItems.values());
emit m_applet->configNeedsSaving();
}
}
}
void FavoritesModel::removeFavorite(int row)
{
if (row < 0 || row >= m_applicationList.count()) {
return;
}
beginRemoveRows(QModelIndex(), row, row);
const QString uniqueId = m_applicationList[row].uniqueId;
m_appOrder.removeAll(uniqueId);
const bool favChanged = m_favorites.contains(uniqueId);
m_favorites.removeAll(uniqueId);
m_desktopItems.remove(uniqueId);
m_appPositions.remove(uniqueId);
m_applicationList.removeAt(row);
endRemoveRows();
if (favChanged) {
emit favoriteCountChanged();
}
if (m_applet) {
m_applet->config().writeEntry("Favorites", m_favorites);
m_applet->config().writeEntry("AppOrder", m_appOrder);
m_applet->config().writeEntry("DesktopItems", m_desktopItems.values());
emit m_applet->configNeedsSaving();
}
}
#include "moc_favoritesmodel.cpp"

View file

@ -0,0 +1,62 @@
/*
* Copyright (C) 2021 Marco Martin <mart@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* or (at your option) any later version, as published by the Free
* Software Foundation
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
// Qt
#include <QObject>
#include <QAbstractListModel>
#include <QList>
#include <QSet>
#include "homescreen.h"
#include "applicationlistmodel.h"
class QString;
namespace KWayland
{
namespace Client
{
class PlasmaWindowManagement;
class PlasmaWindow;
}
}
class FavoritesModel;
class FavoritesModel : public ApplicationListModel {
Q_OBJECT
public:
FavoritesModel(HomeScreen *parent = nullptr);
~FavoritesModel() override;
QString storageToUniqueId(const QString &storageId) const;
QString uniqueToStorageId(const QString &uniqueId) const;
Q_INVOKABLE void addFavorite(const QString &storageId, int row, LauncherLocation location);
Q_INVOKABLE void removeFavorite(int row);
Q_INVOKABLE void loadApplications() override;
};

View file

@ -19,6 +19,7 @@
#include "homescreen.h"
#include "applicationlistmodel.h"
#include "favoritesmodel.h"
#include <QtQml>
#include <QDebug>
@ -27,7 +28,8 @@
HomeScreen::HomeScreen(QObject *parent, const QVariantList &args)
: Plasma::Containment(parent, args)
{
qmlRegisterUncreatableType<ApplicationListModel>("org.kde.phone.homescreen", 1, 0, "ApplicationListModel", QStringLiteral("Cannot create item of type ApplicationListModel"));
qmlRegisterType<ApplicationListModel>("org.kde.phone.homescreen", 1, 0, "ApplicationListModel");
qmlRegisterType<FavoritesModel>("org.kde.phone.homescreen", 1, 0, "FavoritesModel");
setHasConfigurationInterface(true);
}
@ -45,7 +47,13 @@ void HomeScreen::configChanged()
ApplicationListModel *HomeScreen::applicationListModel()
{
if (!m_applicationListModel) {
m_applicationListModel = new ApplicationListModel(this);
if (m_showAllApps) {
m_applicationListModel = new ApplicationListModel(this);
} else {
m_applicationListModel = new FavoritesModel(this);
}
m_applicationListModel->setApplet(this);
m_applicationListModel->loadApplications();
}
return m_applicationListModel;
}

View file

@ -26,6 +26,7 @@
class QQuickItem;
class ApplicationListModel;
class FavoritesModel;
class HomeScreen : public Plasma::Containment
{
@ -48,7 +49,7 @@ protected:
private:
ApplicationListModel *m_applicationListModel = nullptr;
bool m_showAllApps = false;
};
#endif

View file

@ -21,14 +21,14 @@ import QtGraphicalEffects 1.6
import org.kde.plasma.core 2.0 as PlasmaCore
PlasmaCore.SvgItem {
id: scrollDownIndicator
id: scrollIndicator
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
z: 2
opacity: 0
svg: arrowsSvg
elementId: "down-arrow"
elementId: "left-arrow"
width: units.iconSizes.large
height: width
layer.enabled: true

View file

@ -0,0 +1,293 @@
/*
* Copyright 2021 Marco Martin <mart@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA.
*/
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.phone.homescreen 1.0
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) {
return AppDrawer.Status.Peeking;
} else {
return AppDrawer.Status.Closed;
}
}
property real offset: 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: 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) / (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;
scrollAnim.restart();
}
}
function snapDrawerStatus() {
if (root.status !== AppDrawer.Status.Peeking) {
return;
}
if (view.movementDirection === AppDrawer.MovementDirection.Up) {
open();
} else {
close();
}
}
Drag.dragType: Drag.Automatic
onOffsetChanged: {
if (!view.moving) {
view.contentY = Math.max(0, offset) - view.originY - view.height*2
}
}
NumberAnimation {
id: scrollAnim
target: view
properties: "contentY"
duration: units.longDuration
easing.type: Easing.InOutQuad
}
PC3.Label {
id: metrics
text: "M\nM"
visible: false
font.pointSize: theme.defaultFont.pointSize * 0.9
}
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: 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))
}
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
}
visible: root.status !== AppDrawer.Status.Closed
cellWidth: view.width / Math.floor(view.width / ((root.availableCellHeight - root.reservedSpaceForLabel) + 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
}
onMovementEnded: root.snapDrawerStatus()
onFlickEnded: movementEnded()
// boundsBehavior: Flickable.StopAtBounds
model: ApplicationListModel {
id: allApplicationsModel
Component.onCompleted: loadApplications()
}
header: Item {
height: root.height - root.topPadding - root.bottomPadding
property real oldHeight: height
onHeightChanged: {
if (root.status !== AppDrawer.Status.Open) {
view.contentY = -view.height;
}
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));
}
allApplicationsModel.setMinimizedDelegate(index, delegate);
allApplicationsModel.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: units.longDuration * 2
easing.type: Easing.InOutQuad
}
}
implicitWidth: Math.round(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: units.gridUnit + root.leftPadding
rightMargin: 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: units.longDuration * 2
easing.type: Easing.InOutQuad
}
}
}
}

View file

@ -0,0 +1,116 @@
/*
* Copyright 2019 Marco Martin <mart@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA.
*/
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
import org.kde.phone.homescreen 1.0
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 + (units.smallSpacing * 2), delegate.y + (units.smallSpacing * 2), icon.source, model.applicationName, model.applicationStorageId);
}
}
//preventStealing: true
ColumnLayout {
anchors {
fill: parent
leftMargin: units.smallSpacing * 2
topMargin: units.smallSpacing * 2
rightMargin: units.smallSpacing * 2
bottomMargin: 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: 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 + units.smallSpacing
Layout.rightMargin: -parent.anchors.rightMargin + units.smallSpacing
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignTop
maximumLineCount: 2
elide: Text.ElideRight
text: model.applicationName
//FIXME: export smallestReadableFont
font.pointSize: theme.defaultFont.pointSize * 0.9
color: "white"//model.applicationLocation == ApplicationListModel.Desktop ? "white" : theme.textColor
}
}
}

View file

@ -29,18 +29,17 @@ import org.kde.kquickcontrolsaddons 2.0
LauncherContainer {
id: root
readonly property int count: flow.width / launcherGrid.cellWidth
readonly property int count: flow.width / cellWidth
flow.flow: Flow.TopToBottom
favoriteStrip: root
visible: flow.children.length > 0 || launcherDragManager.active
visible: flow.children.length > 0 || launcherDragManager.active || dropArea.containsDrag
opacity: launcherDragManager.active && plasmoid.nativeInterface.applicationListModel.favoriteCount >= plasmoid.nativeInterface.applicationListModel.maxFavoriteCount ? 0.3 : 1
height: visible ? launcherGrid.cellHeight : 0
height: visible ? cellHeight : 0
frame.implicitWidth: launcherGrid.cellWidth * Math.max(1, flow.children.length) + frame.leftPadding + frame.rightPadding
frame.implicitWidth: cellWidth * Math.max(1, flow.children.length) + frame.leftPadding + frame.rightPadding
Behavior on height {
NumberAnimation {

View file

@ -23,13 +23,15 @@ 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.plasma.components 3.0 as PC3
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
import org.kde.phone.homescreen 1.0
import "private" as Private
ContainmentLayoutManager.ItemContainer {
id: delegate
@ -37,12 +39,12 @@ ContainmentLayoutManager.ItemContainer {
property var modelData: typeof model !== "undefined" ? model : null
Layout.minimumWidth: launcherGrid.cellWidth
Layout.minimumHeight: launcherGrid.cellHeight
Layout.minimumWidth: appletsLayout.cellWidth
Layout.minimumHeight: appletsLayout.cellHeight
opacity: dragActive ? 0.4 : 1
key: model.applicationStorageId
key: model.applicationUniqueId
property ContainmentLayoutManager.AppletsLayout appletsLayout
property int reservedSpaceForLabel
property real dragCenterX
property real dragCenterY
property alias iconItem: icon
@ -82,18 +84,28 @@ ContainmentLayoutManager.ItemContainer {
syncDelegateGeometry();
}
}
Connections {
target: appletsLayout
function onAppletsLayoutInteracted() {
removeButton.hide();
}
}
onDragActiveChanged: {
launcherDragManager.active = dragActive
if (dragActive) {
// Must be 0, 0 as at this point dragCenterX and dragCenterY are on the drag before"
launcherDragManager.startDrag(delegate);
launcherDragManager.currentlyDraggedDelegate = delegate;
removeButton.show();
mouseArea.enabled = true;
} else {
launcherDragManager.dropItem(delegate, dragCenterX, dragCenterY);
plasmoid.editMode = false;
editMode = false;
plasmoid.fullRepresentationItem.stopScroll();
launcherDragManager.currentlyDraggedDelegate = null;
forceActiveFocus();
}
}
@ -102,16 +114,16 @@ ContainmentLayoutManager.ItemContainer {
dragCenterY = dragCenter.y;
launcherDragManager.dragItem(delegate, dragCenter.x, dragCenter.y);
delegate.width = launcherGrid.cellWidth;
delegate.height = launcherGrid.cellHeight;
delegate.width = appletsLayout.cellWidth;
delegate.height = appletsLayout.cellHeight;
var pos = plasmoid.fullRepresentationItem.mapFromItem(delegate, dragCenter.x, dragCenter.y);
//SCROLL UP
if (pos.y < plasmoid.availableScreenRect.y + units.gridUnit) {
plasmoid.fullRepresentationItem.scrollUp();
//SCROLL DOWN
} else if (pos.y > plasmoid.availableScreenRect.y + plasmoid.availableScreenRect.height - units.gridUnit) {
plasmoid.fullRepresentationItem.scrollDown();
//SCROLL LEFT
if (pos.x < plasmoid.availableScreenRect.x + units.gridUnit) {
plasmoid.fullRepresentationItem.scrollLeft();
//SCROLL RIGHT
} else if (pos.x > plasmoid.availableScreenRect.x + plasmoid.availableScreenRect.width - units.gridUnit) {
plasmoid.fullRepresentationItem.scrollRight();
//DON't SCROLL
} else {
plasmoid.fullRepresentationItem.stopScroll();
@ -119,6 +131,7 @@ ContainmentLayoutManager.ItemContainer {
}
contentItem: MouseArea {
id: mouseArea
onClicked: {
if (modelData.applicationRunning) {
delegate.launch(0, 0, "", modelData.applicationName);
@ -126,6 +139,7 @@ ContainmentLayoutManager.ItemContainer {
delegate.launch(delegate.x + (units.smallSpacing * 2), delegate.y + (units.smallSpacing * 2), icon.source, modelData.applicationName);
}
plasmoid.nativeInterface.applicationListModel.setMinimizedDelegate(index, delegate);
plasmoid.nativeInterface.applicationListModel.runApplication(modelData.applicationStorageId);
}
@ -145,7 +159,7 @@ ContainmentLayoutManager.ItemContainer {
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
Layout.fillWidth: true
Layout.minimumHeight: parent.height - root.reservedSpaceForLabel
Layout.minimumHeight: Math.min(units.iconSizes.large, parent.height - delegate.reservedSpaceForLabel)
Layout.preferredHeight: Layout.minimumHeight
usesPlasmaTheme: false
@ -162,15 +176,20 @@ ContainmentLayoutManager.ItemContainer {
height: width
color: theme.highlightColor
}
//TODO: in loader?
Private.DelegateRemoveButton {
id: removeButton
}
}
PlasmaComponents.Label {
PC3.Label {
id: label
visible: text.length > 0
Layout.fillWidth: true
Layout.preferredHeight: root.reservedSpaceForLabel
Layout.preferredHeight: delegate.reservedSpaceForLabel
wrapMode: Text.WordWrap
Layout.alignment: Qt.AlignTop
Layout.leftMargin: -parent.anchors.leftMargin + units.smallSpacing
Layout.rightMargin: -parent.anchors.rightMargin + units.smallSpacing
horizontalAlignment: Text.AlignHCenter
@ -194,6 +213,7 @@ ContainmentLayoutManager.ItemContainer {
color: Qt.rgba(0, 0, 0, 1)
}
}
Item {Layout.fillHeight:true}
}
}
}

View file

@ -31,11 +31,11 @@ Item {
id: root
readonly property int reservedSpaceForLabel: metrics.height
readonly property int cellWidth: root.width / Math.floor(root.width / ((availableCellHeight - reservedSpaceForLabel) + units.smallSpacing*4))
readonly property int cellHeight: availableCellHeight
property int availableCellHeight: units.iconSizes.huge + reservedSpaceForLabel
property ContainmentLayoutManager.AppletsLayout appletsLayout
property Item launcherGrid
property Item favoriteStrip
property alias frame: frame
property alias flow: applicationsFlow
@ -61,6 +61,7 @@ Item {
anchors.centerIn: parent
implicitWidth: contentItem.implicitWidth
implicitHeight: contentItem.implicitHeight
height: parent.height
leftPadding: 0
topPadding: 0

View file

@ -27,18 +27,18 @@ Item {
id: root
property ContainmentLayoutManager.AppletsLayout appletsLayout
property LauncherGrid launcherGrid
property FavoriteStrip favoriteStrip
property Delegate currentlyDraggedDelegate
property HomeDelegate currentlyDraggedDelegate
property bool active
property QtObject model: plasmoid.nativeInterface.applicationListModel
readonly property Item spacer: Item {
width: launcherGrid.cellWidth
height: launcherGrid.cellHeight
width: favoriteStrip.cellWidth
height: favoriteStrip.cellHeight
}
function startDrag(item) {
internal.showSpacer(item, 0, 0);
showSpacer(item, 0, 0);
}
function dragItem(delegate, dragCenterX, dragCenterY) {
@ -46,34 +46,28 @@ Item {
var newRow = 0;
var newContainer = internal.containerForItem(delegate, dragCenterX, dragCenterY);
if (!newContainer) {
newContainer = appletsLayout;
}
// Put it in the favorites strip
if (newContainer == favoriteStrip) {
var pos = favoriteStrip.flow.mapFromItem(delegate, 0, 0);
newRow = Math.floor((pos.x + dragCenterX) / delegate.width);
//plasmoid.nativeInterface.applicationListModel.setLocation(delegate.modelData.index, ApplicationListModel.Favorites);
//root.model.setLocation(delegate.modelData.index, ApplicationListModel.Favorites);
internal.showSpacer(delegate, dragCenterX, dragCenterY);
plasmoid.nativeInterface.applicationListModel.moveItem(delegate.modelData.index, newRow);
showSpacer(delegate, dragCenterX, dragCenterY);
root.model.moveItem(delegate.modelData.index, newRow);
// Put it on desktop
} else if (newContainer == appletsLayout) {
} else {
var pos = appletsLayout.mapFromItem(delegate, 0, 0);
//plasmoid.nativeInterface.applicationListModel.setLocation(delegate.modelData.index, ApplicationListModel.Desktop);
//root.model.setLocation(delegate.modelData.index, ApplicationListModel.Desktop);
internal.showSpacer(delegate, dragCenterX, dragCenterY);
showSpacer(delegate, dragCenterX, dragCenterY);
return;
// Put it in the general view
} else {
var pos = launcherGrid.flow.mapFromItem(delegate, 0, 0);
newRow = Math.floor(newContainer.flow.width / delegate.width) * Math.floor((pos.y + dragCenterY) / delegate.height) + Math.round((pos.x + dragCenterX) / delegate.width) + favoriteStrip.count;
//plasmoid.nativeInterface.applicationListModel.setLocation(delegate.modelData.index, ApplicationListModel.Grid);
internal.showSpacer(delegate, dragCenterX, dragCenterY);
plasmoid.nativeInterface.applicationListModel.moveItem(delegate.modelData.index, newRow);
}
}
@ -81,6 +75,87 @@ Item {
internal.positionItem(item, dragCenterX, dragCenterY);
}
function showSpacer(item, dragCenterX, dragCenterY) {
var container = internal.containerForItem(item, dragCenterX, dragCenterY);
internal.raiseContainer(container);
appletsLayout.hidePlaceHolder();
if (container == appletsLayout) {
spacer.visible = false;
spacer.parent = root;
appletsLayout.releaseSpace(item);
internal.putItemInDragSpace(item);
var pos = appletsLayout.mapFromItem(item, 0, 0);
appletsLayout.showPlaceHolderAt(Qt.rect(pos.x, pos.y, item.width, item.height));
return;
}
var child = internal.nearestChild(item, dragCenterX, dragCenterY, container);
if (!child) {
spacer.visible = false;
spacer.parent = container.flow
spacer.visible = true;
return;
}
spacer.visible = false;
spacer.parent = container.flow
var pos = container.flow.mapFromItem(item, dragCenterX, dragCenterY);
if (pos.x < child.x + child.width / 2) {
plasmoid.nativeInterface.stackBefore(spacer, child);
} else {
plasmoid.nativeInterface.stackAfter(spacer, child);
}
internal.putItemInDragSpace(item);
spacer.visible = true;
}
function showSpacerAtPos(x, y, container) {
var pos = container.flow.mapFromGlobal(x, y);
internal.raiseContainer(container);
appletsLayout.hidePlaceHolder();
if (container == appletsLayout) {
spacer.visible = false;
spacer.parent = root;
appletsLayout.showPlaceHolderAt(Qt.rect(pos.x, pos.y, appletsLayout.cellWidth, appletsLayout.cellHeight));
return;
}
var child = internal.nearestChildFromPos(x, y, container);
if (!child) {
spacer.visible = false;
spacer.parent = container.flow
spacer.visible = true;
return;
}
spacer.visible = false;
spacer.parent = container.flow
if (pos.x < child.x + child.width / 2) {
plasmoid.nativeInterface.stackBefore(spacer, child);
} else {
plasmoid.nativeInterface.stackAfter(spacer, child);
}
spacer.visible = true;
}
function hideSpacer () {
spacer.visible = false;
spacer.parent = root;
}
// Those should never be accessed from outside
QtObject {
id: internal
@ -88,11 +163,9 @@ Item {
container.z = 1;
if (container == appletsLayout) {
launcherGrid.z = 0;
favoriteStrip.z = 0;
} else if (container == favoriteStrip) {
appletsLayout.z = 0;
launcherGrid.z = 0;
} else {
appletsLayout.z = 0;
favoriteStrip.z = 0;
@ -101,12 +174,11 @@ Item {
function containerForItem(item, dragCenterX, dragCenterY) {
if (favoriteStrip.contains(Qt.point(0,favoriteStrip.frame.mapFromItem(item, dragCenterX, dragCenterY).y))
&& plasmoid.nativeInterface.applicationListModel.favoriteCount < plasmoid.nativeInterface.applicationListModel.maxFavoriteCount) {
&& (item.modelData.applicationLocation == ApplicationListModel.Favorites
|| root.model.favoriteCount < root.model.maxFavoriteCount)) {
return favoriteStrip;
} else if (appletsLayout.contains(appletsLayout.mapFromItem(item, dragCenterX, dragCenterY))) {
return appletsLayout;
} else {
return launcherGrid;
return appletsLayout;
}
}
@ -141,6 +213,7 @@ Item {
var candidate = container.flow.childAt(
Math.min(container.flow.width, Math.max(0, pos.x + i)),
Math.min(container.flow.height-1, Math.max(0, pos.y)));
if (candidate && i < distance) {
child = candidate;
break;
@ -150,6 +223,7 @@ Item {
// Search Left
for (var i = 0; i < item.width * 2; i += item.width/2) {
var candidate = container.flow.childAt(Math.min(container.flow.width, Math.max(0, pos.x - i)), Math.min(container.flow.height-1, Math.max(0, pos.y)));
if (candidate && i < distance) {
child = candidate;
break;
@ -167,53 +241,53 @@ Item {
return child;
}
function showSpacer(item, dragCenterX, dragCenterY) {
var container = containerForItem(item, dragCenterX, dragCenterY);
raiseContainer(container);
function nearestChildFromPos(x, y, container) {
var distance = Number.POSITIVE_INFINITY;
var child;
var pos = container.flow.mapFromGlobal(x, y);
// Search Right
for (var i = 0; i < appletsLayout.cellWidth * 2; i += appletsLayout.cellWidth/2) {
var candidate = container.flow.childAt(
Math.min(container.flow.width, Math.max(0, pos.x + i)),
Math.min(container.flow.height-1, Math.max(0, pos.y)));
appletsLayout.hidePlaceHolder();
if (container == appletsLayout) {
spacer.visible = false;
appletsLayout.releaseSpace(item);
putItemInDragSpace(item);
var pos = appletsLayout.mapFromItem(item, 0, 0);
appletsLayout.showPlaceHolderAt(Qt.rect(pos.x, pos.y, item.width, item.height));
return;
if (candidate && i < distance) {
child = candidate;
break;
}
}
var child = nearestChild(item, dragCenterX, dragCenterY, container);
// Search Left
for (var i = 0; i < appletsLayout.cellWidth * 2; i += appletsLayout.cellWidth/2) {
var candidate = container.flow.childAt(Math.min(container.flow.width, Math.max(0, pos.x - i)), Math.min(container.flow.height-1, Math.max(0, pos.y)));
if (candidate && i < distance) {
child = candidate;
break;
}
}
if (!child) {
spacer.visible = false;
spacer.parent = container.flow
return;
/* if (item.y < container.flow.height/2) {
child = container.flow.children[0];
} else {
child = container.flow.children[container.flow.children.length - 1];
}*/
}
spacer.visible = false;
spacer.parent = container.flow
var pos = container.flow.mapFromItem(item, dragCenterX, dragCenterY);
if (pos.x < child.x + child.width / 2) {
plasmoid.nativeInterface.stackBefore(spacer, child);
} else {
plasmoid.nativeInterface.stackAfter(spacer, child);
}
putItemInDragSpace(item);
spacer.visible = true;
return child;
}
function positionItem(item, dragCenterX, dragCenterY) {
var container = containerForItem(item, dragCenterX, dragCenterY);
raiseContainer(container);
if (container == appletsLayout) {
plasmoid.nativeInterface.applicationListModel.setLocation(item.modelData.index, ApplicationListModel.Desktop);
root.model.setLocation(item.modelData.index, ApplicationListModel.Desktop);
var pos = appletsLayout.mapFromItem(item, 0, 0);
item.parent = appletsLayout;
item.x = pos.x;
@ -223,9 +297,9 @@ Item {
return;
} else if (container == favoriteStrip) {
plasmoid.nativeInterface.applicationListModel.setLocation(item.modelData.index, ApplicationListModel.Favorites);
root.model.setLocation(item.modelData.index, ApplicationListModel.Favorites);
} else {
plasmoid.nativeInterface.applicationListModel.setLocation(item.modelData.index, ApplicationListModel.Grid);
root.model.setLocation(item.modelData.index, ApplicationListModel.Grid);
}
var child = nearestChild(item, dragCenterX, dragCenterY, container);
@ -233,7 +307,7 @@ Item {
putInContainerLayout(item, container);
plasmoid.nativeInterface.stackBefore(item, spacer);
spacer.visible = false;
spacer.parent = container;
spacer.parent = root;
}
}
}

View file

@ -37,7 +37,6 @@ LauncherContainer {
readonly property int columns: Math.floor(root.flow.width / cellWidth)
readonly property int cellWidth: root.flow.width / Math.floor(root.flow.width / ((availableCellHeight - reservedSpaceForLabel) + units.smallSpacing*4))
readonly property int cellHeight: availableCellHeight
launcherGrid: root
signal launched
@ -46,7 +45,7 @@ LauncherContainer {
Repeater {
parent: root.flow
model: plasmoid.nativeInterface.applicationListModel
delegate: Delegate {
delegate: HomeDelegate {
id: delegate
width: root.cellWidth
height: root.cellHeight

View file

@ -0,0 +1,97 @@
/*
* Copyright 2021 Marco Martin <mart@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA.
*/
import QtQuick 2.14
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
import org.kde.plasma.private.nanoshell 2.0 as NanoShell
import org.kde.phone.homescreen 1.0
import org.kde.kirigami 2.14 as Kirigami
Repeater {
id: launcherRepeater
model: plasmoid.nativeInterface.applicationListModel
property ContainmentLayoutManager.AppletsLayout appletsLayout
property FavoriteStrip favoriteStrip
property int cellWidth
property int cellHeight
delegate: HomeDelegate {
id: delegate
width: launcherRepeater.cellWidth
height: Math.min(parent.height, launcherRepeater.cellHeight)
appletsLayout: launcherRepeater.appletsLayout
//just the normal inline binding in height: fails as it gets broken, make it explicit
Binding {
target: delegate
property: "height"
value: Math.min(parent.height, launcherRepeater.cellHeight)
}
parent: parentFromLocation
reservedSpaceForLabel: metrics.height
property Item parentFromLocation: {
switch (model.applicationLocation) {
case ApplicationListModel.Favorites:
return favoriteStrip.flow;
case ApplicationListModel.Desktop:
default:
return appletsLayout;
}
}
Component.onCompleted: {
if (model.applicationLocation === ApplicationListModel.Desktop) {
appletsLayout.restoreItem(delegate);
}
}
onLaunch: (x, y, icon, title) => {
if (icon !== "") {
print(delegate.iconItem)
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));
}
root.launched();
}
onParentFromLocationChanged: {
if (!launcherDragManager.active && parent != parentFromLocation) {
parent = parentFromLocation;
if (model.applicationLocation === ApplicationListModel.Favorites) {
plasmoid.nativeInterface.stackBefore(delegate, parentFromLocation.children[index]);
} else if (model.applicationLocation === ApplicationListModel.Grid) {
plasmoid.nativeInterface.stackBefore(delegate, parentFromLocation.children[Math.max(0, index - plasmoid.nativeInterface.applicationListModel.favoriteCount)]);
}
}
}
}
}

View file

@ -0,0 +1,93 @@
/*
* Copyright 2019 Marco Martin <mart@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA.
*/
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 PC3
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
import org.kde.phone.homescreen 1.0
PC3.RoundButton {
id: removeButton
anchors {
right: parent.right
top: parent.top
}
visible: false
icon.name: "delete"
onClicked: delegateDestructionAnim.restart()
function show() {
scale = 0;
visible = true;
removeButtonScaleAnim.from = 0;
removeButtonScaleAnim.to = 1;
removeButtonAnim.running = true;
}
function hide() {
if (!visible) {
return;
}
removeButtonScaleAnim.from = 1;
removeButtonScaleAnim.to = 0;
removeButtonAnim.running = true;
}
SequentialAnimation {
id: delegateDestructionAnim
NumberAnimation {
target: delegate
property: "scale"
from: 1
to: 0
duration: PlasmaCore.Units.longDuration
easing.type: Easing.InOutQuad
}
ScriptAction {
script: {
appletsLayout.releaseSpace(delegate);
plasmoid.nativeInterface.applicationListModel.removeFavorite(index);
}
}
}
SequentialAnimation {
id: removeButtonAnim
NumberAnimation {
id: removeButtonScaleAnim
target: removeButton
property: "scale"
duration: PlasmaCore.Units.longDuration
easing.type: Easing.InOutQuad
}
ScriptAction {
script: {
if (removeButton.scale === 0) {
removeButton.visible = false;
}
}
}
}
}

View file

@ -0,0 +1,90 @@
/*
* Copyright 2019 Marco Martin <mart@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Layouts 1.1
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.draganddrop 2.0 as DragDrop
import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager
import org.kde.phone.homescreen 1.0
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
MouseArea {
id: arrowUpIcon
z: 9
property Flickable flickable
property real factor: 0
height: units.iconSizes.medium
signal openRequested
signal closeRequested
onClicked: {
if ((arrowUpIcon.flickable.contentY + arrowUpIcon.flickable.originY + arrowUpIcon.flickable.height*2) >= arrowUpIcon.flickable.height/2) {
closeRequested();
} else {
openRequested();
}
scrollAnim.restart();
}
Item {
anchors.centerIn: parent
width: units.iconSizes.medium
height: width
Rectangle {
anchors {
verticalCenter: parent.verticalCenter
right: parent.horizontalCenter
left: parent.left
verticalCenterOffset: -arrowUpIcon.height/4 + (arrowUpIcon.height/4) * arrowUpIcon.factor
}
color: theme.backgroundColor
transformOrigin: Item.Right
rotation: -45 + 90 * arrowUpIcon.factor
antialiasing: true
height: 1
}
Rectangle {
anchors {
verticalCenter: parent.verticalCenter
left: parent.horizontalCenter
right: parent.right
verticalCenterOffset: -arrowUpIcon.height/4 + (arrowUpIcon.height/4) * arrowUpIcon.factor
}
color: theme.backgroundColor
transformOrigin: Item.Left
rotation: 45 - 90 * arrowUpIcon.factor
antialiasing: true
height: 1
}
}
}

View file

@ -44,24 +44,30 @@ Item {
//BEGIN functions
//Autoscroll related functions
function scrollUp() {
autoScrollTimer.scrollDown = false;
function scrollLeft() {
if (mainFlickable.atXBeginning) {
return;
}
autoScrollTimer.scrollRight = false;
autoScrollTimer.running = true;
scrollUpIndicator.opacity = 1;
scrollDownIndicator.opacity = 0;
scrollLeftIndicator.opacity = 1;
scrollRightIndicator.opacity = 0;
}
function scrollDown() {
autoScrollTimer.scrollDown = true;
function scrollRight() {
if (mainFlickable.atXEnd) {
return;
}
autoScrollTimer.scrollRight = true;
autoScrollTimer.running = true;
scrollUpIndicator.opacity = 0;
scrollDownIndicator.opacity = 1;
scrollLeftIndicator.opacity = 0;
scrollRightIndicator.opacity = 1;
}
function stopScroll() {
autoScrollTimer.running = false;
scrollUpIndicator.opacity = 0;
scrollDownIndicator.opacity = 0;
scrollLeftIndicator.opacity = 0;
scrollRightIndicator.opacity = 0;
}
function recalculateMaxFavoriteCount() {
@ -69,8 +75,9 @@ Item {
return;
}
plasmoid.nativeInterface.applicationListModel.maxFavoriteCount = Math.max(4, Math.floor(Math.min(width, height) / launcher.cellWidth));
plasmoid.nativeInterface.applicationListModel.maxFavoriteCount = Math.max(4, Math.floor(Math.min(width, height) / appletsLayout.cellWidth));
}
//END functions
property bool componentComplete: false
@ -98,30 +105,37 @@ Item {
}
Connections {
property real lastRequestedPosition: 0
target: MobileShell.HomeScreenControls
function onResetHomeScreenPosition() {
scrollAnim.to = 0;
scrollAnim.restart();
appDrawer.close();
}
function onSnapHomeScreenPosition() {
mainFlickable.flick(0, 1);
if (lastRequestedPosition > 0) {
appDrawer.open();
} else {
appDrawer.close();
}
}
function onRequestHomeScreenPosition(y) {
mainFlickable.contentY = y;
appDrawer.offset += y;
lastRequestedPosition = y;
}
}
Timer {
id: autoScrollTimer
property bool scrollDown: true
property bool scrollRight: true
repeat: true
interval: 1500
onTriggered: {
scrollAnim.to = scrollDown ?
//Scroll down
Math.min(mainFlickable.contentItem.height - root.height, mainFlickable.contentY + root.height/2) :
//Scroll up
Math.max(0, mainFlickable.contentY - root.height/2);
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;
}
@ -139,38 +153,28 @@ Item {
anchors.fill: parent
z: 2
appletsLayout: appletsLayout
launcherGrid: launcher
favoriteStrip: favoriteStrip
}
Rectangle {
anchors {
left: parent.left
right: parent.right
leftMargin: -1
rightMargin: -1
}
border.color: Qt.rgba(1, 1, 1, 0.5)
radius: units.gridUnit
color: "black"
opacity: 0.4 * Math.min(1, mainFlickable.contentY / (units.gridUnit * 10))
height: root.height + radius * 2
y: Math.max(-radius, -mainFlickable.contentY + arrowUpIcon.y)
}
//TODO: this flickable does nothing for now, will be used for horizontal paging
Flickable {
id: mainFlickable
width: parent.width
clip: true
anchors {
fill: parent
//topMargin: plasmoid.availableScreenRect.y
topMargin: plasmoid.availableScreenRect.y
bottomMargin: favoriteStrip.height + plasmoid.screenGeometry.height - plasmoid.availableScreenRect.height - plasmoid.availableScreenRect.y
}
opacity: 1 - appDrawer.openFactor
transform: Translate {
y: -mainFlickable.height/10 * appDrawer.openFactor
}
scale: (3 - appDrawer.openFactor) /3
//bottomMargin: favoriteStrip.height
contentWidth: width
contentHeight: flickableContents.height
contentWidth: appletsLayout.width
contentHeight: height
interactive: !plasmoid.editMode && !launcherDragManager.active
signal cancelEditModeForItemsRequested
@ -181,256 +185,208 @@ Item {
onContentYChanged: MobileShell.HomeScreenControls.homeScreenPosition = contentY
PlasmaComponents.ScrollBar.vertical: PlasmaComponents.ScrollBar {
id: scrollabr
opacity: mainFlickable.moving
interactive: false
enabled: false
Behavior on opacity {
OpacityAnimator {
duration: units.longDuration * 2
easing.type: Easing.InOutQuad
DragHandler {
target: mainFlickable
yAxis.enabled: !appletsLayout.editMode
enabled: appDrawer.status !== Launcher.AppDrawer.Status.Open
onTranslationChanged: {
if (active) {
appDrawer.offset = -translation.y
}
}
implicitWidth: Math.round(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)
onActiveChanged: {
if (!active) {
appDrawer.snapDrawerStatus();
}
}
}
NumberAnimation {
id: scrollAnim
target: mainFlickable
properties: "contentY"
properties: "contentX"
duration: units.longDuration
easing.type: Easing.InOutQuad
}
Column {
id: flickableContents
// TODO: span on multiple pages
DragDrop.DropArea {
id: dropArea
width: mainFlickable.width
spacing: 0
height: mainFlickable.height + favoriteStrip.height
Item {
width: 1
height: plasmoid.availableScreenRect.y
onDragEnter: {
event.accept(event.proposedAction);
}
DragDrop.DropArea {
anchors {
left: parent.left
right: parent.right
}
height: mainFlickable.height - plasmoid.availableScreenRect.y //TODO: multiple widgets pages
onDragEnter: {
event.accept(event.proposedAction);
}
onDragMove: {
onDragMove: {
let posInFavorites = favoriteStrip.mapFromItem(this, event.x, event.y);
if (posInFavorites.y > 0) {
launcherDragManager.showSpacerAtPos(event.x, event.y, favoriteStrip);
appletsLayout.hidePlaceHolder();
} else {
appletsLayout.showPlaceHolderAt(
Qt.rect(event.x - appletsLayout.defaultItemWidth / 2,
event.y - appletsLayout.defaultItemHeight / 2,
appletsLayout.defaultItemWidth,
appletsLayout.defaultItemHeight)
);
launcherDragManager.hideSpacer();
}
}
onDragLeave: {
onDragLeave: {
appletsLayout.hidePlaceHolder();
}
preventStealing: true
onDrop: {
if (event.mimeData.formats[0] === "text/x-plasma-phone-homescreen-launcher") {
let storageId = event.mimeData.getDataAsByteArray("text/x-plasma-phone-homescreen-launcher");
let posInFavorites = favoriteStrip.flow.mapFromItem(this, event.x, event.y);
if (posInFavorites.y > 0) {
plasmoid.nativeInterface.applicationListModel.addFavorite(storageId, 0, ApplicationListModel.Favorites)
let item = launcherRepeater.itemAt(0);
if (item) {
item.x = posInFavorites.x;
item.y = 0//posInFavorites.y;
//launcherDragManager.showSpacer(item, item.width/2, item.height/2);
launcherDragManager.dropItem(item, item.width/2, item.height/2);
}
return;
}
plasmoid.nativeInterface.applicationListModel.addFavorite(storageId, 0, ApplicationListModel.Desktop)
let item = launcherRepeater.itemAt(0);
event.accept(event.proposedAction);
if (item) {
item.x = appletsLayout.placeHolder.x;
item.y = appletsLayout.placeHolder.y;
appletsLayout.hidePlaceHolder();
launcherDragManager.dropItem(item, appletsLayout.placeHolder.x + appletsLayout.placeHolder.width/2, appletsLayout.placeHolder.y + appletsLayout.placeHolder.height/2);
}
appletsLayout.hidePlaceHolder();
}
preventStealing: true
onDrop: {
} else {
plasmoid.processMimeData(event.mimeData,
event.x - appletsLayout.placeHolder.width / 2, event.y - appletsLayout.placeHolder.height / 2);
event.accept(event.proposedAction);
appletsLayout.hidePlaceHolder();
}
PlasmaCore.Svg {
id: arrowsSvg
imagePath: "widgets/arrows"
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
}
MouseArea {
id: arrowUpIcon
z: 9
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
margins: -units.smallSpacing
}
property real factor: Math.max(0, Math.min(1, mainFlickable.contentY / (mainFlickable.height/2)))
height: units.iconSizes.medium
onClicked: {
if (mainFlickable.contentY >= mainFlickable.height/2) {
scrollAnim.to = 0;
} else {
scrollAnim.to = mainFlickable.height/2
}
scrollAnim.restart();
}
Item {
anchors.centerIn: parent
width: units.iconSizes.medium
height: width
Rectangle {
anchors {
verticalCenter: parent.verticalCenter
right: parent.horizontalCenter
left: parent.left
verticalCenterOffset: -arrowUpIcon.height/4 + (arrowUpIcon.height/4) * arrowUpIcon.factor
}
color: theme.backgroundColor
transformOrigin: Item.Right
rotation: -45 + 90 * arrowUpIcon.factor
antialiasing: true
height: 1
}
Rectangle {
anchors {
verticalCenter: parent.verticalCenter
left: parent.horizontalCenter
right: parent.right
verticalCenterOffset: -arrowUpIcon.height/4 + (arrowUpIcon.height/4) * arrowUpIcon.factor
}
color: theme.backgroundColor
transformOrigin: Item.Left
rotation: 45 - 90 * arrowUpIcon.factor
antialiasing: true
height: 1
}
}
}
ContainmentLayoutManager.AppletsLayout {
id: appletsLayout
anchors.fill: parent
cellWidth: Math.floor(width / launcher.columns)
cellHeight: launcher.cellHeight
configKey: width > height ? "ItemGeometriesHorizontal" : "ItemGeometriesVertical"
containment: plasmoid
editModeCondition: plasmoid.immutable
? ContainmentLayoutManager.AppletsLayout.Manual
: ContainmentLayoutManager.AppletsLayout.AfterPressAndHold
// Sets the containment in edit mode when we go in edit mode as well
onEditModeChanged: plasmoid.editMode = editMode
minimumItemWidth: units.gridUnit * 3
minimumItemHeight: minimumItemWidth
defaultItemWidth: units.gridUnit * 6
defaultItemHeight: defaultItemWidth
//cellWidth: units.iconSizes.small
//cellHeight: cellWidth
acceptsAppletCallback: function(applet, x, y) {
print("Applet: "+applet+" "+x+" "+y)
return true;
}
appletContainerComponent: ContainmentLayoutManager.BasicAppletContainer {
id: appletContainer
configOverlayComponent: ConfigOverlay {}
onEditModeChanged: {
launcherDragManager.active = dragActive || editMode;
}
onDragActiveChanged: {
launcherDragManager.active = dragActive || editMode;
}
}
placeHolder: ContainmentLayoutManager.PlaceHolder {}
}
}
Launcher.LauncherGrid {
id: launcher
PlasmaCore.Svg {
id: arrowsSvg
imagePath: "widgets/arrows"
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
}
ContainmentLayoutManager.AppletsLayout {
id: appletsLayout
anchors {
left: parent.left
right: parent.right
fill: parent
bottomMargin: favoriteStrip.height
}
onLaunched: scrollResetTimer.restart();
favoriteStrip: favoriteStrip
appletsLayout: appletsLayout
}
Timer {
id: scrollResetTimer
interval: 1000
onTriggered: {
scrollAnim.to = 0;
scrollAnim.restart();
signal appletsLayoutInteracted
TapHandler {
target: mainFlickable
enabled: appDrawer.status !== Launcher.AppDrawer.Status.Open
onTapped: {
//Hides icons close button
appletsLayout.appletsLayoutInteracted();
appletsLayout.editMode = false;
}
onLongPressed: appletsLayout.editMode = true;
}
cellWidth: favoriteStrip.cellWidth
cellHeight: Math.floor(height / Math.floor(height / favoriteStrip.cellHeight))
configKey: width > height ? "ItemGeometriesHorizontal" : "ItemGeometriesVertical"
containment: plasmoid
editModeCondition: plasmoid.immutable
? ContainmentLayoutManager.AppletsLayout.Manual
: ContainmentLayoutManager.AppletsLayout.AfterPressAndHold
// Sets the containment in edit mode when we go in edit mode as well
onEditModeChanged: plasmoid.editMode = editMode
minimumItemWidth: units.gridUnit * 3
minimumItemHeight: minimumItemWidth
defaultItemWidth: units.gridUnit * 6
defaultItemHeight: defaultItemWidth
acceptsAppletCallback: function(applet, x, y) {
print("Applet: "+applet+" "+x+" "+y)
return true;
}
appletContainerComponent: ContainmentLayoutManager.BasicAppletContainer {
id: appletContainer
configOverlayComponent: ConfigOverlay {}
onEditModeChanged: {
launcherDragManager.active = dragActive || editMode;
}
onDragActiveChanged: {
launcherDragManager.active = dragActive || editMode;
}
}
placeHolder: ContainmentLayoutManager.PlaceHolder {}
//FIXME: move
PlasmaComponents.Label {
id: metrics
text: "M\nM"
visible: false
font.pointSize: theme.defaultFont.pointSize * 0.9
}
Launcher.LauncherRepeater {
id: launcherRepeater
cellWidth: appletsLayout.cellWidth
cellHeight: appletsLayout.cellHeight
appletsLayout: appletsLayout
favoriteStrip: favoriteStrip
}
}
}
}
ScrollIndicator {
id: scrollUpIndicator
anchors {
top: parent.top
topMargin: units.gridUnit * 2
}
elementId: "up-arrow"
}
ScrollIndicator {
id: scrollDownIndicator
anchors {
bottom: favoriteStrip.top
bottomMargin: units.gridUnit
}
elementId: "down-arrow"
Launcher.AppDrawer {
id: appDrawer
anchors.fill: parent
topPadding: plasmoid.availableScreenRect.y
bottomPadding: favoriteStrip.height + plasmoid.screenGeometry.height - plasmoid.availableScreenRect.height - plasmoid.availableScreenRect.y
}
Rectangle {
ScrollIndicator {
id: scrollLeftIndicator
anchors {
left: parent.left
leftMargin: units.smallSpacing
}
elementId: "left-arrow"
}
ScrollIndicator {
id: scrollRightIndicator
anchors {
right: parent.right
bottom: favoriteStrip.top
leftMargin: units.gridUnit
rightMargin: units.gridUnit
}
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) }
}
opacity: mainFlickable.contentY > 0 ? 0.6 : 0
Behavior on opacity {
OpacityAnimator {
duration: units.longDuration * 2
easing.type: Easing.InOutQuad
}
rightMargin: units.smallSpacing
}
elementId: "right-arrow"
}
MouseArea {
anchors.fill:favoriteStrip
property real oldMouseY
onPressed: oldMouseY = mouse.y
onPositionChanged: {
mainFlickable.contentY -= mouse.y - oldMouseY;
oldMouseY = mouse.y;
}
onReleased: {
mainFlickable.flick(0, 1);
}
}
Launcher.FavoriteStrip {
id: favoriteStrip
anchors {
@ -440,8 +396,31 @@ Item {
bottomMargin: plasmoid.screenGeometry.height - plasmoid.availableScreenRect.height - plasmoid.availableScreenRect.y
}
appletsLayout: appletsLayout
launcherGrid: launcher
//y: Math.max(krunner.inputHeight, root.height - height - mainFlickable.contentY)
DragHandler {
target: favoriteStrip
yAxis.enabled: !appletsLayout.editMode
enabled: appDrawer.status !== Launcher.AppDrawer.Status.Open
onTranslationChanged: {
if (active) {
appDrawer.offset = -translation.y
}
}
onActiveChanged: {
if (!active) {
appDrawer.snapDrawerStatus();
}
}
}
TapHandler {
target: favoriteStrip
onTapped: {
//Hides icons close button
appletsLayout.appletsLayoutInteracted();
appletsLayout.editMode = false;
}
onLongPressed: appletsLayout.editMode = true;
}
}
}