From 00d63af2dac2d7f9d4b283d7866efde8ad48968f Mon Sep 17 00:00:00 2001 From: Marco Martin Date: Mon, 15 Feb 2021 10:44:48 +0000 Subject: [PATCH] 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 --- containments/homescreen/CMakeLists.txt | 1 + .../homescreen/applicationlistmodel.cpp | 88 +++- .../homescreen/applicationlistmodel.h | 13 +- containments/homescreen/favoritesmodel.cpp | 199 ++++++++ containments/homescreen/favoritesmodel.h | 62 +++ containments/homescreen/homescreen.cpp | 12 +- containments/homescreen/homescreen.h | 3 +- .../package/contents/ui/ScrollIndicator.qml | 6 +- .../contents/ui/launcher/AppDrawer.qml | 293 +++++++++++ .../contents/ui/launcher/DrawerDelegate.qml | 116 +++++ .../contents/ui/launcher/FavoriteStrip.qml | 9 +- .../{Delegate.qml => HomeDelegate.qml} | 54 +- .../ui/launcher/LauncherContainer.qml | 5 +- .../ui/launcher/LauncherDragManager.qml | 194 ++++--- .../contents/ui/launcher/LauncherGrid.qml | 3 +- .../contents/ui/launcher/LauncherRepeater.qml | 97 ++++ .../launcher/private/DelegateRemoveButton.qml | 93 ++++ .../ui/launcher/private/OpenDrawerButton.qml | 90 ++++ .../homescreen/package/contents/ui/main.qml | 473 +++++++++--------- 19 files changed, 1441 insertions(+), 370 deletions(-) create mode 100644 containments/homescreen/favoritesmodel.cpp create mode 100644 containments/homescreen/favoritesmodel.h create mode 100644 containments/homescreen/package/contents/ui/launcher/AppDrawer.qml create mode 100644 containments/homescreen/package/contents/ui/launcher/DrawerDelegate.qml rename containments/homescreen/package/contents/ui/launcher/{Delegate.qml => HomeDelegate.qml} (80%) create mode 100644 containments/homescreen/package/contents/ui/launcher/LauncherRepeater.qml create mode 100644 containments/homescreen/package/contents/ui/launcher/private/DelegateRemoveButton.qml create mode 100644 containments/homescreen/package/contents/ui/launcher/private/OpenDrawerButton.qml diff --git a/containments/homescreen/CMakeLists.txt b/containments/homescreen/CMakeLists.txt index 8c3bc43c..251c7d59 100644 --- a/containments/homescreen/CMakeLists.txt +++ b/containments/homescreen/CMakeLists.txt @@ -1,6 +1,7 @@ set(homescreen_SRCS homescreen.cpp applicationlistmodel.cpp + favoritesmodel.cpp ) add_library(plasma_containment_phone_homescreen MODULE ${homescreen_SRCS}) diff --git a/containments/homescreen/applicationlistmodel.cpp b/containments/homescreen/applicationlistmodel.cpp index d80a081d..5f761d0e 100644 --- a/containments/homescreen/applicationlistmodel.cpp +++ b/containments/homescreen/applicationlistmodel.cpp @@ -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(&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(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 ApplicationListModel::roleNames() const @@ -88,7 +91,8 @@ QHash 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()) { diff --git a/containments/homescreen/applicationlistmodel.h b/containments/homescreen/applicationlistmodel.h index 2e957dad..61b577f6 100644 --- a/containments/homescreen/applicationlistmodel.h +++ b/containments/homescreen/applicationlistmodel.h @@ -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 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; diff --git a/containments/homescreen/favoritesmodel.cpp b/containments/homescreen/favoritesmodel.cpp new file mode 100644 index 00000000..d795bbbb --- /dev/null +++ b/containments/homescreen/favoritesmodel.cpp @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2014 Antonis Tsiapaliokas + * + * 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 +#include +#include + +// KDE +#include +#include + + +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 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" + diff --git a/containments/homescreen/favoritesmodel.h b/containments/homescreen/favoritesmodel.h new file mode 100644 index 00000000..44119172 --- /dev/null +++ b/containments/homescreen/favoritesmodel.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 Marco Martin + * + * 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 +#include +#include +#include + +#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; + + +}; + diff --git a/containments/homescreen/homescreen.cpp b/containments/homescreen/homescreen.cpp index 343695fb..5331ae7d 100644 --- a/containments/homescreen/homescreen.cpp +++ b/containments/homescreen/homescreen.cpp @@ -19,6 +19,7 @@ #include "homescreen.h" #include "applicationlistmodel.h" +#include "favoritesmodel.h" #include #include @@ -27,7 +28,8 @@ HomeScreen::HomeScreen(QObject *parent, const QVariantList &args) : Plasma::Containment(parent, args) { - qmlRegisterUncreatableType("org.kde.phone.homescreen", 1, 0, "ApplicationListModel", QStringLiteral("Cannot create item of type ApplicationListModel")); + qmlRegisterType("org.kde.phone.homescreen", 1, 0, "ApplicationListModel"); + qmlRegisterType("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; } diff --git a/containments/homescreen/homescreen.h b/containments/homescreen/homescreen.h index 7cbfaf4e..080cfe7c 100644 --- a/containments/homescreen/homescreen.h +++ b/containments/homescreen/homescreen.h @@ -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 diff --git a/containments/homescreen/package/contents/ui/ScrollIndicator.qml b/containments/homescreen/package/contents/ui/ScrollIndicator.qml index a0fc6299..8ba77cf7 100644 --- a/containments/homescreen/package/contents/ui/ScrollIndicator.qml +++ b/containments/homescreen/package/contents/ui/ScrollIndicator.qml @@ -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 diff --git a/containments/homescreen/package/contents/ui/launcher/AppDrawer.qml b/containments/homescreen/package/contents/ui/launcher/AppDrawer.qml new file mode 100644 index 00000000..6e7e2ee5 --- /dev/null +++ b/containments/homescreen/package/contents/ui/launcher/AppDrawer.qml @@ -0,0 +1,293 @@ +/* + * Copyright 2021 Marco Martin + * + * 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 + } + } + } +} diff --git a/containments/homescreen/package/contents/ui/launcher/DrawerDelegate.qml b/containments/homescreen/package/contents/ui/launcher/DrawerDelegate.qml new file mode 100644 index 00000000..3265155a --- /dev/null +++ b/containments/homescreen/package/contents/ui/launcher/DrawerDelegate.qml @@ -0,0 +1,116 @@ +/* + * Copyright 2019 Marco Martin + * + * 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 + } + } +} + diff --git a/containments/homescreen/package/contents/ui/launcher/FavoriteStrip.qml b/containments/homescreen/package/contents/ui/launcher/FavoriteStrip.qml index 28cb8327..4dc27cdb 100644 --- a/containments/homescreen/package/contents/ui/launcher/FavoriteStrip.qml +++ b/containments/homescreen/package/contents/ui/launcher/FavoriteStrip.qml @@ -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 { diff --git a/containments/homescreen/package/contents/ui/launcher/Delegate.qml b/containments/homescreen/package/contents/ui/launcher/HomeDelegate.qml similarity index 80% rename from containments/homescreen/package/contents/ui/launcher/Delegate.qml rename to containments/homescreen/package/contents/ui/launcher/HomeDelegate.qml index e9ca464d..5b2a1d94 100644 --- a/containments/homescreen/package/contents/ui/launcher/Delegate.qml +++ b/containments/homescreen/package/contents/ui/launcher/HomeDelegate.qml @@ -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} } } } diff --git a/containments/homescreen/package/contents/ui/launcher/LauncherContainer.qml b/containments/homescreen/package/contents/ui/launcher/LauncherContainer.qml index 3a9bf0ca..9855e94a 100644 --- a/containments/homescreen/package/contents/ui/launcher/LauncherContainer.qml +++ b/containments/homescreen/package/contents/ui/launcher/LauncherContainer.qml @@ -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 diff --git a/containments/homescreen/package/contents/ui/launcher/LauncherDragManager.qml b/containments/homescreen/package/contents/ui/launcher/LauncherDragManager.qml index ab8ddd4d..dc59a622 100644 --- a/containments/homescreen/package/contents/ui/launcher/LauncherDragManager.qml +++ b/containments/homescreen/package/contents/ui/launcher/LauncherDragManager.qml @@ -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; } } } diff --git a/containments/homescreen/package/contents/ui/launcher/LauncherGrid.qml b/containments/homescreen/package/contents/ui/launcher/LauncherGrid.qml index a92f42b9..7ccd664c 100644 --- a/containments/homescreen/package/contents/ui/launcher/LauncherGrid.qml +++ b/containments/homescreen/package/contents/ui/launcher/LauncherGrid.qml @@ -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 diff --git a/containments/homescreen/package/contents/ui/launcher/LauncherRepeater.qml b/containments/homescreen/package/contents/ui/launcher/LauncherRepeater.qml new file mode 100644 index 00000000..536fa922 --- /dev/null +++ b/containments/homescreen/package/contents/ui/launcher/LauncherRepeater.qml @@ -0,0 +1,97 @@ +/* + * Copyright 2021 Marco Martin + * + * 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)]); + } + } + } + } +} + diff --git a/containments/homescreen/package/contents/ui/launcher/private/DelegateRemoveButton.qml b/containments/homescreen/package/contents/ui/launcher/private/DelegateRemoveButton.qml new file mode 100644 index 00000000..89fb2176 --- /dev/null +++ b/containments/homescreen/package/contents/ui/launcher/private/DelegateRemoveButton.qml @@ -0,0 +1,93 @@ +/* + * Copyright 2019 Marco Martin + * + * 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; + } + } + } + } +} diff --git a/containments/homescreen/package/contents/ui/launcher/private/OpenDrawerButton.qml b/containments/homescreen/package/contents/ui/launcher/private/OpenDrawerButton.qml new file mode 100644 index 00000000..a02a04e5 --- /dev/null +++ b/containments/homescreen/package/contents/ui/launcher/private/OpenDrawerButton.qml @@ -0,0 +1,90 @@ +/* + * Copyright 2019 Marco Martin + * + * 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 + } + } +} + diff --git a/containments/homescreen/package/contents/ui/main.qml b/containments/homescreen/package/contents/ui/main.qml index 7d237991..ccb7c86a 100644 --- a/containments/homescreen/package/contents/ui/main.qml +++ b/containments/homescreen/package/contents/ui/main.qml @@ -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; + } } }