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 set(homescreen_SRCS
homescreen.cpp homescreen.cpp
applicationlistmodel.cpp applicationlistmodel.cpp
favoritesmodel.cpp
) )
add_library(plasma_containment_phone_homescreen MODULE ${homescreen_SRCS}) add_library(plasma_containment_phone_homescreen MODULE ${homescreen_SRCS})

View file

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

View file

@ -57,6 +57,7 @@ public:
Q_ENUM(LauncherLocation) Q_ENUM(LauncherLocation)
struct ApplicationData { struct ApplicationData {
QString uniqueId;
QString name; QString name;
QString icon; QString icon;
QString storageId; QString storageId;
@ -74,7 +75,8 @@ public:
ApplicationOriginalRowRole, ApplicationOriginalRowRole,
ApplicationStartupNotifyRole, ApplicationStartupNotifyRole,
ApplicationLocationRole, ApplicationLocationRole,
ApplicationRunningRole ApplicationRunningRole,
ApplicationUniqueIdRole
}; };
ApplicationListModel(HomeScreen *parent = nullptr); ApplicationListModel(HomeScreen *parent = nullptr);
@ -92,6 +94,9 @@ public:
int maxFavoriteCount() const; int maxFavoriteCount() const;
void setMaxFavoriteCount(int count); 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; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
Qt::ItemFlags flags(const QModelIndex &index) const override; Qt::ItemFlags flags(const QModelIndex &index) const override;
@ -104,7 +109,7 @@ public:
Q_INVOKABLE void runApplication(const QString &storageId); 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 setMinimizedDelegate(int row, QQuickItem *delegate);
Q_INVOKABLE void unsetMinimizedDelegate(int row, QQuickItem *delegate); Q_INVOKABLE void unsetMinimizedDelegate(int row, QQuickItem *delegate);
@ -117,13 +122,13 @@ Q_SIGNALS:
void favoriteCountChanged(); void favoriteCountChanged();
void maxFavoriteCountChanged(); void maxFavoriteCountChanged();
private: protected:
void initWayland(); void initWayland();
QList<ApplicationData> m_applicationList; QList<ApplicationData> m_applicationList;
KWayland::Client::PlasmaWindowManagement *m_windowManagement = nullptr; KWayland::Client::PlasmaWindowManagement *m_windowManagement = nullptr;
HomeScreen *m_homeScreen = nullptr; Plasma::Applet *m_applet = nullptr;
int m_maxFavoriteCount = 0; int m_maxFavoriteCount = 0;
QStringList m_appOrder; QStringList m_appOrder;
QStringList m_favorites; 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 "homescreen.h"
#include "applicationlistmodel.h" #include "applicationlistmodel.h"
#include "favoritesmodel.h"
#include <QtQml> #include <QtQml>
#include <QDebug> #include <QDebug>
@ -27,7 +28,8 @@
HomeScreen::HomeScreen(QObject *parent, const QVariantList &args) HomeScreen::HomeScreen(QObject *parent, const QVariantList &args)
: Plasma::Containment(parent, 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); setHasConfigurationInterface(true);
} }
@ -45,7 +47,13 @@ void HomeScreen::configChanged()
ApplicationListModel *HomeScreen::applicationListModel() ApplicationListModel *HomeScreen::applicationListModel()
{ {
if (!m_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; return m_applicationListModel;
} }

View file

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

View file

@ -21,14 +21,14 @@ import QtGraphicalEffects 1.6
import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.core 2.0 as PlasmaCore
PlasmaCore.SvgItem { PlasmaCore.SvgItem {
id: scrollDownIndicator id: scrollIndicator
anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter
z: 2 z: 2
opacity: 0 opacity: 0
svg: arrowsSvg svg: arrowsSvg
elementId: "down-arrow" elementId: "left-arrow"
width: units.iconSizes.large width: units.iconSizes.large
height: width height: width
layer.enabled: true 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 { LauncherContainer {
id: root id: root
readonly property int count: flow.width / launcherGrid.cellWidth readonly property int count: flow.width / cellWidth
flow.flow: Flow.TopToBottom 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 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 { Behavior on height {
NumberAnimation { NumberAnimation {

View file

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

View file

@ -31,11 +31,11 @@ Item {
id: root id: root
readonly property int reservedSpaceForLabel: metrics.height 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 int availableCellHeight: units.iconSizes.huge + reservedSpaceForLabel
property ContainmentLayoutManager.AppletsLayout appletsLayout property ContainmentLayoutManager.AppletsLayout appletsLayout
property Item launcherGrid
property Item favoriteStrip
property alias frame: frame property alias frame: frame
property alias flow: applicationsFlow property alias flow: applicationsFlow
@ -61,6 +61,7 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
implicitWidth: contentItem.implicitWidth implicitWidth: contentItem.implicitWidth
implicitHeight: contentItem.implicitHeight implicitHeight: contentItem.implicitHeight
height: parent.height
leftPadding: 0 leftPadding: 0
topPadding: 0 topPadding: 0

View file

@ -27,18 +27,18 @@ Item {
id: root id: root
property ContainmentLayoutManager.AppletsLayout appletsLayout property ContainmentLayoutManager.AppletsLayout appletsLayout
property LauncherGrid launcherGrid
property FavoriteStrip favoriteStrip property FavoriteStrip favoriteStrip
property Delegate currentlyDraggedDelegate property HomeDelegate currentlyDraggedDelegate
property bool active property bool active
property QtObject model: plasmoid.nativeInterface.applicationListModel
readonly property Item spacer: Item { readonly property Item spacer: Item {
width: launcherGrid.cellWidth width: favoriteStrip.cellWidth
height: launcherGrid.cellHeight height: favoriteStrip.cellHeight
} }
function startDrag(item) { function startDrag(item) {
internal.showSpacer(item, 0, 0); showSpacer(item, 0, 0);
} }
function dragItem(delegate, dragCenterX, dragCenterY) { function dragItem(delegate, dragCenterX, dragCenterY) {
@ -46,34 +46,28 @@ Item {
var newRow = 0; var newRow = 0;
var newContainer = internal.containerForItem(delegate, dragCenterX, dragCenterY); var newContainer = internal.containerForItem(delegate, dragCenterX, dragCenterY);
if (!newContainer) {
newContainer = appletsLayout;
}
// Put it in the favorites strip // Put it in the favorites strip
if (newContainer == favoriteStrip) { if (newContainer == favoriteStrip) {
var pos = favoriteStrip.flow.mapFromItem(delegate, 0, 0); var pos = favoriteStrip.flow.mapFromItem(delegate, 0, 0);
newRow = Math.floor((pos.x + dragCenterX) / delegate.width); 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); showSpacer(delegate, dragCenterX, dragCenterY);
plasmoid.nativeInterface.applicationListModel.moveItem(delegate.modelData.index, newRow); root.model.moveItem(delegate.modelData.index, newRow);
// Put it on desktop // Put it on desktop
} else if (newContainer == appletsLayout) { } else {
var pos = appletsLayout.mapFromItem(delegate, 0, 0); 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; 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); 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 // Those should never be accessed from outside
QtObject { QtObject {
id: internal id: internal
@ -88,11 +163,9 @@ Item {
container.z = 1; container.z = 1;
if (container == appletsLayout) { if (container == appletsLayout) {
launcherGrid.z = 0;
favoriteStrip.z = 0; favoriteStrip.z = 0;
} else if (container == favoriteStrip) { } else if (container == favoriteStrip) {
appletsLayout.z = 0; appletsLayout.z = 0;
launcherGrid.z = 0;
} else { } else {
appletsLayout.z = 0; appletsLayout.z = 0;
favoriteStrip.z = 0; favoriteStrip.z = 0;
@ -101,12 +174,11 @@ Item {
function containerForItem(item, dragCenterX, dragCenterY) { function containerForItem(item, dragCenterX, dragCenterY) {
if (favoriteStrip.contains(Qt.point(0,favoriteStrip.frame.mapFromItem(item, dragCenterX, dragCenterY).y)) 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; return favoriteStrip;
} else if (appletsLayout.contains(appletsLayout.mapFromItem(item, dragCenterX, dragCenterY))) {
return appletsLayout;
} else { } else {
return launcherGrid; return appletsLayout;
} }
} }
@ -141,6 +213,7 @@ Item {
var candidate = container.flow.childAt( var candidate = container.flow.childAt(
Math.min(container.flow.width, Math.max(0, pos.x + i)), Math.min(container.flow.width, Math.max(0, pos.x + i)),
Math.min(container.flow.height-1, Math.max(0, pos.y))); Math.min(container.flow.height-1, Math.max(0, pos.y)));
if (candidate && i < distance) { if (candidate && i < distance) {
child = candidate; child = candidate;
break; break;
@ -150,6 +223,7 @@ Item {
// Search Left // Search Left
for (var i = 0; i < item.width * 2; i += item.width/2) { 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))); 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) { if (candidate && i < distance) {
child = candidate; child = candidate;
break; break;
@ -167,53 +241,53 @@ Item {
return child; 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 (candidate && i < distance) {
child = candidate;
if (container == appletsLayout) { break;
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;
} }
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) { if (!child) {
spacer.visible = false; /* if (item.y < container.flow.height/2) {
spacer.parent = container.flow child = container.flow.children[0];
return; } else {
child = container.flow.children[container.flow.children.length - 1];
}*/
} }
spacer.visible = false; return child;
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;
} }
function positionItem(item, dragCenterX, dragCenterY) { function positionItem(item, dragCenterX, dragCenterY) {
var container = containerForItem(item, dragCenterX, dragCenterY); var container = containerForItem(item, dragCenterX, dragCenterY);
raiseContainer(container); raiseContainer(container);
if (container == appletsLayout) { 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); var pos = appletsLayout.mapFromItem(item, 0, 0);
item.parent = appletsLayout; item.parent = appletsLayout;
item.x = pos.x; item.x = pos.x;
@ -223,9 +297,9 @@ Item {
return; return;
} else if (container == favoriteStrip) { } else if (container == favoriteStrip) {
plasmoid.nativeInterface.applicationListModel.setLocation(item.modelData.index, ApplicationListModel.Favorites); root.model.setLocation(item.modelData.index, ApplicationListModel.Favorites);
} else { } 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); var child = nearestChild(item, dragCenterX, dragCenterY, container);
@ -233,7 +307,7 @@ Item {
putInContainerLayout(item, container); putInContainerLayout(item, container);
plasmoid.nativeInterface.stackBefore(item, spacer); plasmoid.nativeInterface.stackBefore(item, spacer);
spacer.visible = false; 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 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 cellWidth: root.flow.width / Math.floor(root.flow.width / ((availableCellHeight - reservedSpaceForLabel) + units.smallSpacing*4))
readonly property int cellHeight: availableCellHeight readonly property int cellHeight: availableCellHeight
launcherGrid: root
signal launched signal launched
@ -46,7 +45,7 @@ LauncherContainer {
Repeater { Repeater {
parent: root.flow parent: root.flow
model: plasmoid.nativeInterface.applicationListModel model: plasmoid.nativeInterface.applicationListModel
delegate: Delegate { delegate: HomeDelegate {
id: delegate id: delegate
width: root.cellWidth width: root.cellWidth
height: root.cellHeight 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 //BEGIN functions
//Autoscroll related functions //Autoscroll related functions
function scrollUp() { function scrollLeft() {
autoScrollTimer.scrollDown = false; if (mainFlickable.atXBeginning) {
return;
}
autoScrollTimer.scrollRight = false;
autoScrollTimer.running = true; autoScrollTimer.running = true;
scrollUpIndicator.opacity = 1; scrollLeftIndicator.opacity = 1;
scrollDownIndicator.opacity = 0; scrollRightIndicator.opacity = 0;
} }
function scrollDown() { function scrollRight() {
autoScrollTimer.scrollDown = true; if (mainFlickable.atXEnd) {
return;
}
autoScrollTimer.scrollRight = true;
autoScrollTimer.running = true; autoScrollTimer.running = true;
scrollUpIndicator.opacity = 0; scrollLeftIndicator.opacity = 0;
scrollDownIndicator.opacity = 1; scrollRightIndicator.opacity = 1;
} }
function stopScroll() { function stopScroll() {
autoScrollTimer.running = false; autoScrollTimer.running = false;
scrollUpIndicator.opacity = 0; scrollLeftIndicator.opacity = 0;
scrollDownIndicator.opacity = 0; scrollRightIndicator.opacity = 0;
} }
function recalculateMaxFavoriteCount() { function recalculateMaxFavoriteCount() {
@ -69,8 +75,9 @@ Item {
return; 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 //END functions
property bool componentComplete: false property bool componentComplete: false
@ -98,30 +105,37 @@ Item {
} }
Connections { Connections {
property real lastRequestedPosition: 0
target: MobileShell.HomeScreenControls target: MobileShell.HomeScreenControls
function onResetHomeScreenPosition() { function onResetHomeScreenPosition() {
scrollAnim.to = 0; scrollAnim.to = 0;
scrollAnim.restart(); scrollAnim.restart();
appDrawer.close();
} }
function onSnapHomeScreenPosition() { function onSnapHomeScreenPosition() {
mainFlickable.flick(0, 1); if (lastRequestedPosition > 0) {
appDrawer.open();
} else {
appDrawer.close();
}
} }
function onRequestHomeScreenPosition(y) { function onRequestHomeScreenPosition(y) {
mainFlickable.contentY = y; appDrawer.offset += y;
lastRequestedPosition = y;
} }
} }
Timer { Timer {
id: autoScrollTimer id: autoScrollTimer
property bool scrollDown: true property bool scrollRight: true
repeat: true repeat: true
interval: 1500 interval: 1500
onTriggered: { onTriggered: {
scrollAnim.to = scrollDown ? scrollAnim.to = scrollRight ?
//Scroll down //Scroll Right
Math.min(mainFlickable.contentItem.height - root.height, mainFlickable.contentY + root.height/2) : Math.min(mainFlickable.contentItem.width - mainFlickable.width, mainFlickable.contentX + mainFlickable.width) :
//Scroll up //Scroll Left
Math.max(0, mainFlickable.contentY - root.height/2); Math.max(0, mainFlickable.contentX - mainFlickable.width);
scrollAnim.running = true; scrollAnim.running = true;
} }
@ -139,38 +153,28 @@ Item {
anchors.fill: parent anchors.fill: parent
z: 2 z: 2
appletsLayout: appletsLayout appletsLayout: appletsLayout
launcherGrid: launcher
favoriteStrip: favoriteStrip favoriteStrip: favoriteStrip
} }
Rectangle { //TODO: this flickable does nothing for now, will be used for horizontal paging
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)
}
Flickable { Flickable {
id: mainFlickable id: mainFlickable
width: parent.width
clip: true
anchors { anchors {
fill: parent fill: parent
//topMargin: plasmoid.availableScreenRect.y topMargin: plasmoid.availableScreenRect.y
bottomMargin: favoriteStrip.height + plasmoid.screenGeometry.height - plasmoid.availableScreenRect.height - 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 //bottomMargin: favoriteStrip.height
contentWidth: width contentWidth: appletsLayout.width
contentHeight: flickableContents.height contentHeight: height
interactive: !plasmoid.editMode && !launcherDragManager.active interactive: !plasmoid.editMode && !launcherDragManager.active
signal cancelEditModeForItemsRequested signal cancelEditModeForItemsRequested
@ -181,256 +185,208 @@ Item {
onContentYChanged: MobileShell.HomeScreenControls.homeScreenPosition = contentY onContentYChanged: MobileShell.HomeScreenControls.homeScreenPosition = contentY
PlasmaComponents.ScrollBar.vertical: PlasmaComponents.ScrollBar { DragHandler {
id: scrollabr target: mainFlickable
opacity: mainFlickable.moving yAxis.enabled: !appletsLayout.editMode
interactive: false enabled: appDrawer.status !== Launcher.AppDrawer.Status.Open
enabled: false onTranslationChanged: {
Behavior on opacity { if (active) {
OpacityAnimator { appDrawer.offset = -translation.y
duration: units.longDuration * 2
easing.type: Easing.InOutQuad
} }
} }
implicitWidth: Math.round(units.gridUnit/3) onActiveChanged: {
contentItem: Rectangle { if (!active) {
radius: width/2 appDrawer.snapDrawerStatus();
color: Qt.rgba(1, 1, 1, 0.3) }
border.color: Qt.rgba(0, 0, 0, 0.4)
} }
} }
NumberAnimation { NumberAnimation {
id: scrollAnim id: scrollAnim
target: mainFlickable target: mainFlickable
properties: "contentY" properties: "contentX"
duration: units.longDuration duration: units.longDuration
easing.type: Easing.InOutQuad easing.type: Easing.InOutQuad
} }
Column {
id: flickableContents // TODO: span on multiple pages
DragDrop.DropArea {
id: dropArea
width: mainFlickable.width width: mainFlickable.width
spacing: 0 height: mainFlickable.height + favoriteStrip.height
Item { onDragEnter: {
width: 1 event.accept(event.proposedAction);
height: plasmoid.availableScreenRect.y
} }
DragDrop.DropArea { onDragMove: {
anchors { let posInFavorites = favoriteStrip.mapFromItem(this, event.x, event.y);
left: parent.left if (posInFavorites.y > 0) {
right: parent.right launcherDragManager.showSpacerAtPos(event.x, event.y, favoriteStrip);
} appletsLayout.hidePlaceHolder();
height: mainFlickable.height - plasmoid.availableScreenRect.y //TODO: multiple widgets pages } else {
onDragEnter: {
event.accept(event.proposedAction);
}
onDragMove: {
appletsLayout.showPlaceHolderAt( appletsLayout.showPlaceHolderAt(
Qt.rect(event.x - appletsLayout.defaultItemWidth / 2, Qt.rect(event.x - appletsLayout.defaultItemWidth / 2,
event.y - appletsLayout.defaultItemHeight / 2, event.y - appletsLayout.defaultItemHeight / 2,
appletsLayout.defaultItemWidth, appletsLayout.defaultItemWidth,
appletsLayout.defaultItemHeight) 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(); appletsLayout.hidePlaceHolder();
} } else {
preventStealing: true
onDrop: {
plasmoid.processMimeData(event.mimeData, plasmoid.processMimeData(event.mimeData,
event.x - appletsLayout.placeHolder.width / 2, event.y - appletsLayout.placeHolder.height / 2); event.x - appletsLayout.placeHolder.width / 2, event.y - appletsLayout.placeHolder.height / 2);
event.accept(event.proposedAction); event.accept(event.proposedAction);
appletsLayout.hidePlaceHolder(); 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 { PlasmaCore.Svg {
id: launcher id: arrowsSvg
imagePath: "widgets/arrows"
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
}
ContainmentLayoutManager.AppletsLayout {
id: appletsLayout
anchors { anchors {
left: parent.left fill: parent
right: parent.right bottomMargin: favoriteStrip.height
} }
onLaunched: scrollResetTimer.restart();
favoriteStrip: favoriteStrip signal appletsLayoutInteracted
appletsLayout: appletsLayout
} TapHandler {
Timer { target: mainFlickable
id: scrollResetTimer enabled: appDrawer.status !== Launcher.AppDrawer.Status.Open
interval: 1000 onTapped: {
onTriggered: { //Hides icons close button
scrollAnim.to = 0; appletsLayout.appletsLayoutInteracted();
scrollAnim.restart(); 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 { Launcher.AppDrawer {
id: scrollUpIndicator id: appDrawer
anchors { anchors.fill: parent
top: parent.top
topMargin: units.gridUnit * 2 topPadding: plasmoid.availableScreenRect.y
} bottomPadding: favoriteStrip.height + plasmoid.screenGeometry.height - plasmoid.availableScreenRect.height - plasmoid.availableScreenRect.y
elementId: "up-arrow"
}
ScrollIndicator {
id: scrollDownIndicator
anchors {
bottom: favoriteStrip.top
bottomMargin: units.gridUnit
}
elementId: "down-arrow"
} }
Rectangle { ScrollIndicator {
id: scrollLeftIndicator
anchors { anchors {
left: parent.left left: parent.left
leftMargin: units.smallSpacing
}
elementId: "left-arrow"
}
ScrollIndicator {
id: scrollRightIndicator
anchors {
right: parent.right right: parent.right
bottom: favoriteStrip.top rightMargin: units.smallSpacing
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
}
} }
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 { Launcher.FavoriteStrip {
id: favoriteStrip id: favoriteStrip
anchors { anchors {
@ -440,8 +396,31 @@ Item {
bottomMargin: plasmoid.screenGeometry.height - plasmoid.availableScreenRect.height - plasmoid.availableScreenRect.y bottomMargin: plasmoid.screenGeometry.height - plasmoid.availableScreenRect.height - plasmoid.availableScreenRect.y
} }
appletsLayout: appletsLayout 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;
}
} }
} }