mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-06-11 00:47:22 +00:00
Add convergence launcher app lists
Record app launches from window creation so recent and most-used lists survive after windows close. Use those models in the convergence launcher and keep the hover surface in the unified chrome.
This commit is contained in:
parent
d0262d0dd8
commit
1702027f7e
11 changed files with 773 additions and 19 deletions
|
|
@ -26,6 +26,7 @@ plasma_add_applet(org.kde.plasma.mobile.homescreen.folio
|
||||||
qml/config.qml
|
qml/config.qml
|
||||||
CPP_SOURCES
|
CPP_SOURCES
|
||||||
applicationlistmodel.cpp
|
applicationlistmodel.cpp
|
||||||
|
applicationusagemodel.cpp
|
||||||
delegatetoucharea.cpp
|
delegatetoucharea.cpp
|
||||||
dragstate.cpp
|
dragstate.cpp
|
||||||
favouritesmodel.cpp
|
favouritesmodel.cpp
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,17 @@ int ApplicationListModel::rowCount(const QModelIndex &parent) const
|
||||||
return m_delegates.count();
|
return m_delegates.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<FolioDelegate> ApplicationListModel::delegateForStorageId(const QString &storageId) const
|
||||||
|
{
|
||||||
|
for (const auto &delegate : m_delegates) {
|
||||||
|
if (delegate && delegate->application() && delegate->application()->storageId() == storageId) {
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// Sub-categories merged into their canonical parent, mirroring Kickoff's grouping.
|
// Sub-categories merged into their canonical parent, mirroring Kickoff's grouping.
|
||||||
static QString normalizeCategory(const QString &cat)
|
static QString normalizeCategory(const QString &cat)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@
|
||||||
#include <QSortFilterProxyModel>
|
#include <QSortFilterProxyModel>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <KService>
|
#include <KService>
|
||||||
|
|
||||||
#include "foliodelegate.h"
|
#include "foliodelegate.h"
|
||||||
|
|
@ -45,6 +47,8 @@ public:
|
||||||
|
|
||||||
void load();
|
void load();
|
||||||
|
|
||||||
|
std::shared_ptr<FolioDelegate> delegateForStorageId(const QString &storageId) const;
|
||||||
|
|
||||||
Q_INVOKABLE QStringList allCategories() const;
|
Q_INVOKABLE QStringList allCategories() const;
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
|
|
|
||||||
208
containments/homescreens/folio/applicationusagemodel.cpp
Normal file
208
containments/homescreens/folio/applicationusagemodel.cpp
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
// SPDX-FileCopyrightText: 2026 Marco Allegretti
|
||||||
|
// SPDX-License-Identifier: EUPL-1.2
|
||||||
|
|
||||||
|
#include "applicationusagemodel.h"
|
||||||
|
|
||||||
|
#include "applicationlistmodel.h"
|
||||||
|
#include "foliodelegate.h"
|
||||||
|
#include "foliosettings.h"
|
||||||
|
#include "homescreen.h"
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
constexpr qsizetype s_maxStoredEntries = 24;
|
||||||
|
|
||||||
|
QString normalizedStorageId(const QString &storageId)
|
||||||
|
{
|
||||||
|
if (storageId.isEmpty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storageId.endsWith(QLatin1String(".desktop"))) {
|
||||||
|
return storageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return storageId + QStringLiteral(".desktop");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationUsageStore::ApplicationUsageStore(HomeScreen *parent)
|
||||||
|
: QObject{parent}
|
||||||
|
, m_homeScreen{parent}
|
||||||
|
{
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<ApplicationUsageEntry> ApplicationUsageStore::entries() const
|
||||||
|
{
|
||||||
|
return m_entries.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationUsageStore::recordUsage(const QString &storageId)
|
||||||
|
{
|
||||||
|
const QString normalizedId = normalizedStorageId(storageId);
|
||||||
|
if (normalizedId.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &entry = m_entries[normalizedId];
|
||||||
|
entry.storageId = normalizedId;
|
||||||
|
entry.launchCount = std::max(0, entry.launchCount) + 1;
|
||||||
|
entry.lastUsed = QDateTime::currentDateTimeUtc();
|
||||||
|
|
||||||
|
save();
|
||||||
|
Q_EMIT usageChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationUsageStore::load()
|
||||||
|
{
|
||||||
|
m_entries.clear();
|
||||||
|
|
||||||
|
if (!m_homeScreen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QJsonDocument doc = QJsonDocument::fromJson(m_homeScreen->folioSettings()->applicationUsage().toUtf8());
|
||||||
|
const QJsonArray usageArray = doc.array();
|
||||||
|
|
||||||
|
for (const QJsonValue &value : usageArray) {
|
||||||
|
const QJsonObject object = value.toObject();
|
||||||
|
const QString storageId = normalizedStorageId(object.value(QStringLiteral("storageId")).toString());
|
||||||
|
if (storageId.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationUsageEntry entry;
|
||||||
|
entry.storageId = storageId;
|
||||||
|
entry.launchCount = object.value(QStringLiteral("launchCount")).toInt();
|
||||||
|
entry.lastUsed = QDateTime::fromString(object.value(QStringLiteral("lastUsed")).toString(), Qt::ISODateWithMs);
|
||||||
|
|
||||||
|
if (entry.launchCount <= 0 || !entry.lastUsed.isValid()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_entries.insert(storageId, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationUsageStore::save()
|
||||||
|
{
|
||||||
|
if (!m_homeScreen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<ApplicationUsageEntry> entries = m_entries.values();
|
||||||
|
std::sort(entries.begin(), entries.end(), [](const ApplicationUsageEntry &left, const ApplicationUsageEntry &right) {
|
||||||
|
return left.lastUsed > right.lastUsed;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (entries.size() > s_maxStoredEntries) {
|
||||||
|
entries.resize(s_maxStoredEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray usageArray;
|
||||||
|
for (const ApplicationUsageEntry &entry : std::as_const(entries)) {
|
||||||
|
QJsonObject object;
|
||||||
|
object.insert(QStringLiteral("storageId"), entry.storageId);
|
||||||
|
object.insert(QStringLiteral("launchCount"), entry.launchCount);
|
||||||
|
object.insert(QStringLiteral("lastUsed"), entry.lastUsed.toString(Qt::ISODateWithMs));
|
||||||
|
usageArray.append(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_homeScreen->folioSettings()->setApplicationUsage(QString::fromUtf8(QJsonDocument(usageArray).toJson(QJsonDocument::Compact)));
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationUsageModel::ApplicationUsageModel(HomeScreen *homeScreen, ApplicationUsageStore *store, Mode mode)
|
||||||
|
: QAbstractListModel{homeScreen}
|
||||||
|
, m_homeScreen{homeScreen}
|
||||||
|
, m_store{store}
|
||||||
|
, m_mode{mode}
|
||||||
|
{
|
||||||
|
if (m_store) {
|
||||||
|
connect(m_store, &ApplicationUsageStore::usageChanged, this, &ApplicationUsageModel::rebuild);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_homeScreen && m_homeScreen->applicationListModel()) {
|
||||||
|
auto *applicationListModel = m_homeScreen->applicationListModel();
|
||||||
|
connect(applicationListModel, &QAbstractItemModel::rowsInserted, this, &ApplicationUsageModel::rebuild);
|
||||||
|
connect(applicationListModel, &QAbstractItemModel::rowsRemoved, this, &ApplicationUsageModel::rebuild);
|
||||||
|
connect(applicationListModel, &QAbstractItemModel::modelReset, this, &ApplicationUsageModel::rebuild);
|
||||||
|
}
|
||||||
|
|
||||||
|
rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ApplicationUsageModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
if (parent.isValid()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_entries.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant ApplicationUsageModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid() || index.row() < 0 || index.row() >= m_entries.size()) {
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
const ApplicationUsageEntry &entry = m_entries.at(index.row());
|
||||||
|
switch (role) {
|
||||||
|
case Qt::DisplayRole:
|
||||||
|
case DelegateRole:
|
||||||
|
return QVariant::fromValue(m_delegates.at(index.row()).get());
|
||||||
|
case LaunchCountRole:
|
||||||
|
return entry.launchCount;
|
||||||
|
case LastUsedRole:
|
||||||
|
return entry.lastUsed;
|
||||||
|
default:
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> ApplicationUsageModel::roleNames() const
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
{DelegateRole, QByteArrayLiteral("delegate")},
|
||||||
|
{LaunchCountRole, QByteArrayLiteral("launchCount")},
|
||||||
|
{LastUsedRole, QByteArrayLiteral("lastUsed")},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationUsageModel::rebuild()
|
||||||
|
{
|
||||||
|
QList<ApplicationUsageEntry> entries = m_store ? m_store->entries() : QList<ApplicationUsageEntry>{};
|
||||||
|
|
||||||
|
std::sort(entries.begin(), entries.end(), [this](const ApplicationUsageEntry &left, const ApplicationUsageEntry &right) {
|
||||||
|
if (m_mode == MostUsed && left.launchCount != right.launchCount) {
|
||||||
|
return left.launchCount > right.launchCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return left.lastUsed > right.lastUsed;
|
||||||
|
});
|
||||||
|
|
||||||
|
QList<ApplicationUsageEntry> nextEntries;
|
||||||
|
QList<std::shared_ptr<FolioDelegate>> nextDelegates;
|
||||||
|
|
||||||
|
if (m_homeScreen && m_homeScreen->applicationListModel()) {
|
||||||
|
for (const ApplicationUsageEntry &entry : std::as_const(entries)) {
|
||||||
|
auto delegate = m_homeScreen->applicationListModel()->delegateForStorageId(entry.storageId);
|
||||||
|
if (!delegate || !delegate->application()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextEntries.append(entry);
|
||||||
|
nextDelegates.append(delegate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beginResetModel();
|
||||||
|
m_entries = nextEntries;
|
||||||
|
m_delegates = nextDelegates;
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
80
containments/homescreens/folio/applicationusagemodel.h
Normal file
80
containments/homescreens/folio/applicationusagemodel.h
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
// SPDX-FileCopyrightText: 2026 Marco Allegretti
|
||||||
|
// SPDX-License-Identifier: EUPL-1.2
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QList>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include <QtQmlIntegration/qqmlintegration.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class FolioDelegate;
|
||||||
|
class HomeScreen;
|
||||||
|
|
||||||
|
struct ApplicationUsageEntry {
|
||||||
|
QString storageId;
|
||||||
|
int launchCount = 0;
|
||||||
|
QDateTime lastUsed;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ApplicationUsageStore : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ApplicationUsageStore(HomeScreen *parent = nullptr);
|
||||||
|
|
||||||
|
QList<ApplicationUsageEntry> entries() const;
|
||||||
|
void recordUsage(const QString &storageId);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void usageChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void load();
|
||||||
|
void save();
|
||||||
|
|
||||||
|
HomeScreen *m_homeScreen{nullptr};
|
||||||
|
QHash<QString, ApplicationUsageEntry> m_entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ApplicationUsageModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
QML_UNCREATABLE("")
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Mode {
|
||||||
|
RecentUsage,
|
||||||
|
MostUsed,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Roles {
|
||||||
|
DelegateRole = Qt::UserRole + 1,
|
||||||
|
LaunchCountRole,
|
||||||
|
LastUsedRole,
|
||||||
|
};
|
||||||
|
|
||||||
|
ApplicationUsageModel(HomeScreen *homeScreen = nullptr, ApplicationUsageStore *store = nullptr, Mode mode = RecentUsage);
|
||||||
|
|
||||||
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
void rebuild();
|
||||||
|
|
||||||
|
private:
|
||||||
|
HomeScreen *m_homeScreen{nullptr};
|
||||||
|
ApplicationUsageStore *m_store{nullptr};
|
||||||
|
Mode m_mode{RecentUsage};
|
||||||
|
QList<ApplicationUsageEntry> m_entries;
|
||||||
|
QList<std::shared_ptr<FolioDelegate>> m_delegates;
|
||||||
|
};
|
||||||
|
|
@ -16,6 +16,7 @@ using namespace Qt::Literals::StringLiterals;
|
||||||
const QString CFG_GROUP_FOLIO = QStringLiteral("Folio");
|
const QString CFG_GROUP_FOLIO = QStringLiteral("Folio");
|
||||||
|
|
||||||
const QString CFG_KEY_FAVORITES = QStringLiteral("favorites");
|
const QString CFG_KEY_FAVORITES = QStringLiteral("favorites");
|
||||||
|
const QString CFG_KEY_APPLICATION_USAGE = QStringLiteral("applicationUsage");
|
||||||
const QString CFG_KEY_PAGES = QStringLiteral("pages");
|
const QString CFG_KEY_PAGES = QStringLiteral("pages");
|
||||||
|
|
||||||
const QString CFG_KEY_HOMESCREEN_ROWS = QStringLiteral("homeScreenRows");
|
const QString CFG_KEY_HOMESCREEN_ROWS = QStringLiteral("homeScreenRows");
|
||||||
|
|
@ -47,6 +48,17 @@ void FolioSettings::setFavorites(const QString &favoritesJson)
|
||||||
Q_EMIT m_homeScreen->configNeedsSaving();
|
Q_EMIT m_homeScreen->configNeedsSaving();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString FolioSettings::applicationUsage() const
|
||||||
|
{
|
||||||
|
return generalConfigGroup().readEntry(CFG_KEY_APPLICATION_USAGE, QStringLiteral("[]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FolioSettings::setApplicationUsage(const QString &applicationUsageJson)
|
||||||
|
{
|
||||||
|
generalConfigGroup().writeEntry(CFG_KEY_APPLICATION_USAGE, applicationUsageJson);
|
||||||
|
Q_EMIT m_homeScreen->configNeedsSaving();
|
||||||
|
}
|
||||||
|
|
||||||
QString FolioSettings::pages() const
|
QString FolioSettings::pages() const
|
||||||
{
|
{
|
||||||
return generalConfigGroup().readEntry(CFG_KEY_PAGES, u"{}"_s);
|
return generalConfigGroup().readEntry(CFG_KEY_PAGES, u"{}"_s);
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,10 @@ public:
|
||||||
QString favorites() const;
|
QString favorites() const;
|
||||||
void setFavorites(const QString &favoritesJson);
|
void setFavorites(const QString &favoritesJson);
|
||||||
|
|
||||||
|
// JSON array
|
||||||
|
QString applicationUsage() const;
|
||||||
|
void setApplicationUsage(const QString &applicationUsageJson);
|
||||||
|
|
||||||
// JSON object
|
// JSON object
|
||||||
QString pages() const;
|
QString pages() const;
|
||||||
void setPages(const QString &pagesJson);
|
void setPages(const QString &pagesJson);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
#include "homescreen.h"
|
#include "homescreen.h"
|
||||||
|
|
||||||
|
#include "windowlistener.h"
|
||||||
|
|
||||||
#include <virtualdesktopinfo.h>
|
#include <virtualdesktopinfo.h>
|
||||||
|
|
||||||
#include <KWindowSystem>
|
#include <KWindowSystem>
|
||||||
|
|
@ -58,6 +60,9 @@ HomeScreen::HomeScreen(QObject *parent, const KPluginMetaData &data, const QVari
|
||||||
, m_widgetsManager{new WidgetsManager{this}}
|
, m_widgetsManager{new WidgetsManager{this}}
|
||||||
, m_applicationListModel{new ApplicationListModel{this}}
|
, m_applicationListModel{new ApplicationListModel{this}}
|
||||||
, m_applicationListSearchModel{new ApplicationListSearchModel{this, m_applicationListModel}}
|
, m_applicationListSearchModel{new ApplicationListSearchModel{this, m_applicationListModel}}
|
||||||
|
, m_applicationUsageStore{new ApplicationUsageStore{this}}
|
||||||
|
, m_recentApplicationsModel{new ApplicationUsageModel{this, m_applicationUsageStore, ApplicationUsageModel::RecentUsage}}
|
||||||
|
, m_mostUsedApplicationsModel{new ApplicationUsageModel{this, m_applicationUsageStore, ApplicationUsageModel::MostUsed}}
|
||||||
, m_favouritesModel{new FavouritesModel{this}}
|
, m_favouritesModel{new FavouritesModel{this}}
|
||||||
, m_pageListModel{new PageListModel{this}}
|
, m_pageListModel{new PageListModel{this}}
|
||||||
{
|
{
|
||||||
|
|
@ -84,6 +89,11 @@ HomeScreen::HomeScreen(QObject *parent, const KPluginMetaData &data, const QVari
|
||||||
|
|
||||||
connect(this, &Plasma::Containment::appletAdded, this, &HomeScreen::onAppletAdded);
|
connect(this, &Plasma::Containment::appletAdded, this, &HomeScreen::onAppletAdded);
|
||||||
connect(this, &Plasma::Containment::appletAboutToBeRemoved, this, &HomeScreen::onAppletAboutToBeRemoved);
|
connect(this, &Plasma::Containment::appletAboutToBeRemoved, this, &HomeScreen::onAppletAboutToBeRemoved);
|
||||||
|
connect(WindowListener::instance(), &WindowListener::windowChanged, this, [this](QString storageId) {
|
||||||
|
if (!WindowListener::instance()->windowsFromStorageId(storageId).empty()) {
|
||||||
|
m_applicationUsageStore->recordUsage(storageId);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
HomeScreen::~HomeScreen() = default;
|
HomeScreen::~HomeScreen() = default;
|
||||||
|
|
@ -129,6 +139,16 @@ ApplicationListSearchModel *HomeScreen::applicationListSearchModel()
|
||||||
return m_applicationListSearchModel;
|
return m_applicationListSearchModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ApplicationUsageModel *HomeScreen::recentApplicationsModel()
|
||||||
|
{
|
||||||
|
return m_recentApplicationsModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationUsageModel *HomeScreen::mostUsedApplicationsModel()
|
||||||
|
{
|
||||||
|
return m_mostUsedApplicationsModel;
|
||||||
|
}
|
||||||
|
|
||||||
FavouritesModel *HomeScreen::favouritesModel()
|
FavouritesModel *HomeScreen::favouritesModel()
|
||||||
{
|
{
|
||||||
return m_favouritesModel;
|
return m_favouritesModel;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
|
||||||
#include "applicationlistmodel.h"
|
#include "applicationlistmodel.h"
|
||||||
|
#include "applicationusagemodel.h"
|
||||||
#include "delegatetoucharea.h"
|
#include "delegatetoucharea.h"
|
||||||
#include "favouritesmodel.h"
|
#include "favouritesmodel.h"
|
||||||
#include "folioapplication.h"
|
#include "folioapplication.h"
|
||||||
|
|
@ -29,6 +30,8 @@ class HomeScreenState;
|
||||||
class FavouritesModel;
|
class FavouritesModel;
|
||||||
class ApplicationListModel;
|
class ApplicationListModel;
|
||||||
class ApplicationListSearchModel;
|
class ApplicationListSearchModel;
|
||||||
|
class ApplicationUsageModel;
|
||||||
|
class ApplicationUsageStore;
|
||||||
|
|
||||||
class HomeScreen : public Plasma::Containment
|
class HomeScreen : public Plasma::Containment
|
||||||
{
|
{
|
||||||
|
|
@ -41,6 +44,8 @@ class HomeScreen : public Plasma::Containment
|
||||||
Q_PROPERTY(WidgetsManager *WidgetsManager READ widgetsManager CONSTANT)
|
Q_PROPERTY(WidgetsManager *WidgetsManager READ widgetsManager CONSTANT)
|
||||||
Q_PROPERTY(ApplicationListModel *ApplicationListModel READ applicationListModel CONSTANT)
|
Q_PROPERTY(ApplicationListModel *ApplicationListModel READ applicationListModel CONSTANT)
|
||||||
Q_PROPERTY(ApplicationListSearchModel *ApplicationListSearchModel READ applicationListSearchModel CONSTANT)
|
Q_PROPERTY(ApplicationListSearchModel *ApplicationListSearchModel READ applicationListSearchModel CONSTANT)
|
||||||
|
Q_PROPERTY(ApplicationUsageModel *RecentApplicationsModel READ recentApplicationsModel CONSTANT)
|
||||||
|
Q_PROPERTY(ApplicationUsageModel *MostUsedApplicationsModel READ mostUsedApplicationsModel CONSTANT)
|
||||||
Q_PROPERTY(FavouritesModel *FavouritesModel READ favouritesModel CONSTANT)
|
Q_PROPERTY(FavouritesModel *FavouritesModel READ favouritesModel CONSTANT)
|
||||||
Q_PROPERTY(PageListModel *PageListModel READ pageListModel CONSTANT)
|
Q_PROPERTY(PageListModel *PageListModel READ pageListModel CONSTANT)
|
||||||
Q_PROPERTY(bool overviewActive READ overviewActive NOTIFY overviewActiveChanged)
|
Q_PROPERTY(bool overviewActive READ overviewActive NOTIFY overviewActiveChanged)
|
||||||
|
|
@ -63,6 +68,8 @@ public:
|
||||||
WidgetsManager *widgetsManager();
|
WidgetsManager *widgetsManager();
|
||||||
ApplicationListModel *applicationListModel();
|
ApplicationListModel *applicationListModel();
|
||||||
ApplicationListSearchModel *applicationListSearchModel();
|
ApplicationListSearchModel *applicationListSearchModel();
|
||||||
|
ApplicationUsageModel *recentApplicationsModel();
|
||||||
|
ApplicationUsageModel *mostUsedApplicationsModel();
|
||||||
FavouritesModel *favouritesModel();
|
FavouritesModel *favouritesModel();
|
||||||
PageListModel *pageListModel();
|
PageListModel *pageListModel();
|
||||||
bool overviewActive() const;
|
bool overviewActive() const;
|
||||||
|
|
@ -85,6 +92,9 @@ private:
|
||||||
WidgetsManager *m_widgetsManager{nullptr};
|
WidgetsManager *m_widgetsManager{nullptr};
|
||||||
ApplicationListModel *m_applicationListModel{nullptr};
|
ApplicationListModel *m_applicationListModel{nullptr};
|
||||||
ApplicationListSearchModel *m_applicationListSearchModel{nullptr};
|
ApplicationListSearchModel *m_applicationListSearchModel{nullptr};
|
||||||
|
ApplicationUsageStore *m_applicationUsageStore{nullptr};
|
||||||
|
ApplicationUsageModel *m_recentApplicationsModel{nullptr};
|
||||||
|
ApplicationUsageModel *m_mostUsedApplicationsModel{nullptr};
|
||||||
FavouritesModel *m_favouritesModel{nullptr};
|
FavouritesModel *m_favouritesModel{nullptr};
|
||||||
PageListModel *m_pageListModel{nullptr};
|
PageListModel *m_pageListModel{nullptr};
|
||||||
bool m_overviewActive{false};
|
bool m_overviewActive{false};
|
||||||
|
|
|
||||||
|
|
@ -305,6 +305,27 @@ ContainmentItem {
|
||||||
readonly property real workAreaY: topBarHitHeight
|
readonly property real workAreaY: topBarHitHeight
|
||||||
readonly property real workAreaWidth: Math.max(0, width - frameThickness * 2)
|
readonly property real workAreaWidth: Math.max(0, width - frameThickness * 2)
|
||||||
readonly property real workAreaHeight: Math.max(0, height - topBarHeight - dockHeight - frameThickness * 2)
|
readonly property real workAreaHeight: Math.max(0, height - topBarHeight - dockHeight - frameThickness * 2)
|
||||||
|
readonly property real leftEdgeHotzoneWidth: Math.max(frameThickness, Math.round(Kirigami.Units.gridUnit * 0.7))
|
||||||
|
readonly property real leftLauncherWidth: Math.min(Kirigami.Units.gridUnit * 22, width * 0.42)
|
||||||
|
readonly property real leftLauncherHeight: Math.min(Kirigami.Units.gridUnit * 16, workAreaHeight * 0.66)
|
||||||
|
readonly property bool leftLauncherEnabled: root.folio.HomeScreenState.appDrawerOpenProgress <= 0
|
||||||
|
readonly property real leftFrameBulgeIdleDepth: Math.max(frameThickness * 1.2, Kirigami.Units.gridUnit * 0.35)
|
||||||
|
readonly property real leftFrameBulgeHoverDepth: 0
|
||||||
|
property real leftFrameBulgeDepth: !leftLauncherEnabled || leftLauncherOpen || leftEdgeHovered
|
||||||
|
? leftFrameBulgeHoverDepth
|
||||||
|
: leftFrameBulgeIdleDepth
|
||||||
|
// Long, thin thickening of the lower-left workspace wall. Vertical
|
||||||
|
// tangents at all three anchors keep the curve smooth as it blends
|
||||||
|
// into the straight wall above and below.
|
||||||
|
readonly property real leftFrameBulgeEffectiveDepth: Math.max(leftFrameBulgeDepth, 0.01)
|
||||||
|
readonly property real leftFrameBulgeApexX: workAreaX + leftFrameBulgeEffectiveDepth
|
||||||
|
readonly property real leftFrameBulgeHalfLength: Kirigami.Units.gridUnit * 7.5
|
||||||
|
readonly property real leftFrameBulgeApexY: workAreaY + workAreaHeight * 0.7
|
||||||
|
readonly property real leftFrameBulgeEdgeTopY: leftFrameBulgeApexY - leftFrameBulgeHalfLength
|
||||||
|
readonly property real leftFrameBulgeEdgeBottomY: leftFrameBulgeApexY + leftFrameBulgeHalfLength
|
||||||
|
// Bezier control-handle length along the vertical tangent at each
|
||||||
|
// anchor. ~0.55 of the half-length gives a clean, taut oval profile.
|
||||||
|
readonly property real leftFrameBulgeTangent: leftFrameBulgeHalfLength * 0.55
|
||||||
readonly property color chromeColor: Kirigami.Theme.backgroundColor
|
readonly property color chromeColor: Kirigami.Theme.backgroundColor
|
||||||
readonly property color edgeColor: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.1)
|
readonly property color edgeColor: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.1)
|
||||||
readonly property int dockAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.SpatialDefault)
|
readonly property int dockAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.SpatialDefault)
|
||||||
|
|
@ -315,6 +336,9 @@ ContainmentItem {
|
||||||
|
|
||||||
// True once the hover-reveal timer fires; cleared on hover-exit.
|
// True once the hover-reveal timer fires; cleared on hover-exit.
|
||||||
property bool hoverRevealing: false
|
property bool hoverRevealing: false
|
||||||
|
property bool leftEdgeHovered: false
|
||||||
|
property bool leftLauncherHovered: false
|
||||||
|
property bool leftLauncherOpen: false
|
||||||
|
|
||||||
readonly property bool shouldHide: ShellSettings.Settings.autoHidePanelsEnabled
|
readonly property bool shouldHide: ShellSettings.Settings.autoHidePanelsEnabled
|
||||||
&& windowMaximizedTracker.showingWindow && !hoverRevealing
|
&& windowMaximizedTracker.showingWindow && !hoverRevealing
|
||||||
|
|
@ -324,17 +348,77 @@ ContainmentItem {
|
||||||
|
|
||||||
function updateInputRegion() {
|
function updateInputRegion() {
|
||||||
const topBarRegion = Qt.rect(0, 0, width, topBarHitHeight)
|
const topBarRegion = Qt.rect(0, 0, width, topBarHitHeight)
|
||||||
|
const leftEdgeRegion = Qt.rect(0, topBarHitHeight, leftEdgeHotzoneWidth, Math.max(0, height - topBarHitHeight - dockHeight))
|
||||||
|
const leftLauncherRegion = Qt.rect(0,
|
||||||
|
Math.max(0, height - dockHeight - leftLauncherHeight),
|
||||||
|
leftLauncherWidth,
|
||||||
|
leftLauncherHeight)
|
||||||
|
let regions = [topBarRegion, leftEdgeRegion]
|
||||||
|
|
||||||
if (shouldHide && dockOffset >= dockHeight) {
|
if (shouldHide && dockOffset >= dockHeight) {
|
||||||
MobileShell.ShellUtil.setInputRegions(convergenceChrome, [
|
regions.push(Qt.rect(0, height - revealStripHeight, width, revealStripHeight))
|
||||||
topBarRegion,
|
|
||||||
Qt.rect(0, height - revealStripHeight, width, revealStripHeight)
|
|
||||||
])
|
|
||||||
} else {
|
} else {
|
||||||
MobileShell.ShellUtil.setInputRegions(convergenceChrome, [
|
regions.push(Qt.rect(0, height - dockHeight, width, dockHeight))
|
||||||
topBarRegion,
|
|
||||||
Qt.rect(0, height - dockHeight, width, dockHeight)
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (leftLauncherOpen) {
|
||||||
|
regions.push(leftLauncherRegion)
|
||||||
|
}
|
||||||
|
|
||||||
|
MobileShell.ShellUtil.setInputRegions(convergenceChrome, regions)
|
||||||
|
}
|
||||||
|
|
||||||
|
function launchStorageId(storageId) {
|
||||||
|
if (!storageId || String(storageId).length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalizedId = String(storageId)
|
||||||
|
if (!normalizedId.endsWith(".desktop")) {
|
||||||
|
normalizedId += ".desktop"
|
||||||
|
}
|
||||||
|
MobileShell.AppLaunch.launchOrActivateApp(normalizedId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshLeftLauncherVisibility() {
|
||||||
|
if (!leftLauncherEnabled) {
|
||||||
|
leftLauncherCloseTimer.stop()
|
||||||
|
leftLauncherOpen = false
|
||||||
|
inputRegionTimer.restart()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leftEdgeHovered || leftLauncherHovered) {
|
||||||
|
leftLauncherCloseTimer.stop()
|
||||||
|
leftLauncherOpen = true
|
||||||
|
} else {
|
||||||
|
leftLauncherCloseTimer.restart()
|
||||||
|
}
|
||||||
|
inputRegionTimer.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLeftLauncherHoverState(pointerX, pointerY, withinWindow) {
|
||||||
|
const insideEdge = withinWindow
|
||||||
|
&& pointerX >= 0
|
||||||
|
&& pointerX <= leftEdgeHotzoneWidth
|
||||||
|
&& pointerY >= topBarHitHeight
|
||||||
|
&& pointerY <= (height - dockHeight)
|
||||||
|
|
||||||
|
const launcherTop = height - dockHeight - leftLauncherHeight
|
||||||
|
const insideLauncher = withinWindow
|
||||||
|
&& leftLauncherOpen
|
||||||
|
&& pointerX >= 0
|
||||||
|
&& pointerX <= leftLauncherWidth
|
||||||
|
&& pointerY >= launcherTop
|
||||||
|
&& pointerY <= (launcherTop + leftLauncherHeight)
|
||||||
|
|
||||||
|
if (leftEdgeHovered !== insideEdge) {
|
||||||
|
leftEdgeHovered = insideEdge
|
||||||
|
}
|
||||||
|
if (leftLauncherHovered !== insideLauncher) {
|
||||||
|
leftLauncherHovered = insideLauncher
|
||||||
|
}
|
||||||
|
refreshLeftLauncherVisibility()
|
||||||
}
|
}
|
||||||
|
|
||||||
onActiveChanged: {
|
onActiveChanged: {
|
||||||
|
|
@ -353,6 +437,7 @@ ContainmentItem {
|
||||||
}
|
}
|
||||||
inputRegionTimer.restart()
|
inputRegionTimer.restart()
|
||||||
}
|
}
|
||||||
|
onLeftLauncherEnabledChanged: refreshLeftLauncherVisibility()
|
||||||
|
|
||||||
// Narrow the input region to a strip at the screen edge when hidden
|
// Narrow the input region to a strip at the screen edge when hidden
|
||||||
// so that app controls near the bottom edge are not accidentally
|
// so that app controls near the bottom edge are not accidentally
|
||||||
|
|
@ -385,6 +470,20 @@ ContainmentItem {
|
||||||
onTriggered: convergenceChrome.hoverRevealing = true
|
onTriggered: convergenceChrome.hoverRevealing = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: leftLauncherCloseTimer
|
||||||
|
interval: Kirigami.Units.shortDuration
|
||||||
|
repeat: false
|
||||||
|
onTriggered: {
|
||||||
|
if (!convergenceChrome.leftEdgeHovered
|
||||||
|
&& !convergenceChrome.leftLauncherHovered
|
||||||
|
&& convergenceChrome.leftLauncherOpen) {
|
||||||
|
convergenceChrome.leftLauncherOpen = false
|
||||||
|
inputRegionTimer.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Behavior on dockOffset {
|
Behavior on dockOffset {
|
||||||
MobileShell.MotionNumberAnimation {
|
MobileShell.MotionNumberAnimation {
|
||||||
type: MobileShell.Motion.SpatialDefault
|
type: MobileShell.Motion.SpatialDefault
|
||||||
|
|
@ -392,6 +491,13 @@ ContainmentItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Behavior on leftFrameBulgeDepth {
|
||||||
|
MobileShell.MotionNumberAnimation {
|
||||||
|
type: MobileShell.Motion.SpatialDefault
|
||||||
|
duration: root.shortAnimationDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: topBarSurface
|
id: topBarSurface
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
|
|
@ -411,6 +517,7 @@ ContainmentItem {
|
||||||
Shape {
|
Shape {
|
||||||
id: workspaceFrame
|
id: workspaceFrame
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
fillColor: convergenceChrome.chromeColor
|
fillColor: convergenceChrome.chromeColor
|
||||||
|
|
@ -431,20 +538,87 @@ ContainmentItem {
|
||||||
PathArc { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth - convergenceChrome.frameRadius; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }
|
PathArc { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth - convergenceChrome.frameRadius; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }
|
||||||
PathLine { x: convergenceChrome.workAreaX + convergenceChrome.frameRadius; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight }
|
PathLine { x: convergenceChrome.workAreaX + convergenceChrome.frameRadius; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight }
|
||||||
PathArc { x: convergenceChrome.workAreaX; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight - convergenceChrome.frameRadius; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }
|
PathArc { x: convergenceChrome.workAreaX; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight - convergenceChrome.frameRadius; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }
|
||||||
|
PathLine { x: convergenceChrome.workAreaX; y: convergenceChrome.leftFrameBulgeEdgeBottomY }
|
||||||
|
PathCubic {
|
||||||
|
x: convergenceChrome.leftFrameBulgeApexX
|
||||||
|
y: convergenceChrome.leftFrameBulgeApexY
|
||||||
|
control1X: convergenceChrome.workAreaX
|
||||||
|
control1Y: convergenceChrome.leftFrameBulgeEdgeBottomY - convergenceChrome.leftFrameBulgeTangent
|
||||||
|
control2X: convergenceChrome.leftFrameBulgeApexX
|
||||||
|
control2Y: convergenceChrome.leftFrameBulgeApexY + convergenceChrome.leftFrameBulgeTangent
|
||||||
|
}
|
||||||
|
PathCubic {
|
||||||
|
x: convergenceChrome.workAreaX
|
||||||
|
y: convergenceChrome.leftFrameBulgeEdgeTopY
|
||||||
|
control1X: convergenceChrome.leftFrameBulgeApexX
|
||||||
|
control1Y: convergenceChrome.leftFrameBulgeApexY - convergenceChrome.leftFrameBulgeTangent
|
||||||
|
control2X: convergenceChrome.workAreaX
|
||||||
|
control2Y: convergenceChrome.leftFrameBulgeEdgeTopY + convergenceChrome.leftFrameBulgeTangent
|
||||||
|
}
|
||||||
PathLine { x: convergenceChrome.workAreaX; y: convergenceChrome.workAreaY + convergenceChrome.frameRadius }
|
PathLine { x: convergenceChrome.workAreaX; y: convergenceChrome.workAreaY + convergenceChrome.frameRadius }
|
||||||
PathArc { x: convergenceChrome.workAreaX + convergenceChrome.frameRadius; y: convergenceChrome.workAreaY; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }
|
PathArc { x: convergenceChrome.workAreaX + convergenceChrome.frameRadius; y: convergenceChrome.workAreaY; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Shape {
|
||||||
x: convergenceChrome.workAreaX
|
id: workspaceFrameBorder
|
||||||
y: convergenceChrome.workAreaY
|
anchors.fill: parent
|
||||||
width: convergenceChrome.workAreaWidth
|
preferredRendererType: Shape.CurveRenderer
|
||||||
height: convergenceChrome.workAreaHeight
|
|
||||||
radius: convergenceChrome.frameRadius
|
ShapePath {
|
||||||
color: "transparent"
|
fillColor: "transparent"
|
||||||
border.width: 1
|
strokeColor: convergenceChrome.edgeColor
|
||||||
border.color: convergenceChrome.edgeColor
|
strokeWidth: 0.85
|
||||||
|
joinStyle: ShapePath.RoundJoin
|
||||||
|
|
||||||
|
startX: convergenceChrome.workAreaX + convergenceChrome.frameRadius
|
||||||
|
startY: convergenceChrome.workAreaY
|
||||||
|
PathLine { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth - convergenceChrome.frameRadius; y: convergenceChrome.workAreaY }
|
||||||
|
PathQuad {
|
||||||
|
x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth
|
||||||
|
y: convergenceChrome.workAreaY + convergenceChrome.frameRadius
|
||||||
|
controlX: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth
|
||||||
|
controlY: convergenceChrome.workAreaY
|
||||||
|
}
|
||||||
|
PathLine { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight - convergenceChrome.frameRadius }
|
||||||
|
PathQuad {
|
||||||
|
x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth - convergenceChrome.frameRadius
|
||||||
|
y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight
|
||||||
|
controlX: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth
|
||||||
|
controlY: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight
|
||||||
|
}
|
||||||
|
PathLine { x: convergenceChrome.workAreaX + convergenceChrome.frameRadius; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight }
|
||||||
|
PathQuad {
|
||||||
|
x: convergenceChrome.workAreaX
|
||||||
|
y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight - convergenceChrome.frameRadius
|
||||||
|
controlX: convergenceChrome.workAreaX
|
||||||
|
controlY: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight
|
||||||
|
}
|
||||||
|
PathLine { x: convergenceChrome.workAreaX; y: convergenceChrome.leftFrameBulgeEdgeBottomY }
|
||||||
|
PathCubic {
|
||||||
|
x: convergenceChrome.leftFrameBulgeApexX
|
||||||
|
y: convergenceChrome.leftFrameBulgeApexY
|
||||||
|
control1X: convergenceChrome.workAreaX
|
||||||
|
control1Y: convergenceChrome.leftFrameBulgeEdgeBottomY - convergenceChrome.leftFrameBulgeTangent
|
||||||
|
control2X: convergenceChrome.leftFrameBulgeApexX
|
||||||
|
control2Y: convergenceChrome.leftFrameBulgeApexY + convergenceChrome.leftFrameBulgeTangent
|
||||||
|
}
|
||||||
|
PathCubic {
|
||||||
|
x: convergenceChrome.workAreaX
|
||||||
|
y: convergenceChrome.leftFrameBulgeEdgeTopY
|
||||||
|
control1X: convergenceChrome.leftFrameBulgeApexX
|
||||||
|
control1Y: convergenceChrome.leftFrameBulgeApexY - convergenceChrome.leftFrameBulgeTangent
|
||||||
|
control2X: convergenceChrome.workAreaX
|
||||||
|
control2Y: convergenceChrome.leftFrameBulgeEdgeTopY + convergenceChrome.leftFrameBulgeTangent
|
||||||
|
}
|
||||||
|
PathLine { x: convergenceChrome.workAreaX; y: convergenceChrome.workAreaY + convergenceChrome.frameRadius }
|
||||||
|
PathQuad {
|
||||||
|
x: convergenceChrome.workAreaX + convergenceChrome.frameRadius
|
||||||
|
y: convergenceChrome.workAreaY
|
||||||
|
controlX: convergenceChrome.workAreaX
|
||||||
|
controlY: convergenceChrome.workAreaY
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|
@ -488,6 +662,236 @@ ContainmentItem {
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: leftEdgeStrip
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.top: topBarSurface.bottom
|
||||||
|
anchors.bottom: dockSurface.top
|
||||||
|
width: convergenceChrome.leftEdgeHotzoneWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: leftLauncherPointerTracker
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
hoverEnabled: true
|
||||||
|
z: 10
|
||||||
|
|
||||||
|
onPositionChanged: (mouse) => {
|
||||||
|
convergenceChrome.updateLeftLauncherHoverState(mouse.x, mouse.y, true)
|
||||||
|
}
|
||||||
|
onExited: {
|
||||||
|
convergenceChrome.updateLeftLauncherHoverState(-1, -1, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: leftEdgeLauncher
|
||||||
|
|
||||||
|
width: convergenceChrome.leftLauncherWidth
|
||||||
|
height: convergenceChrome.leftLauncherHeight
|
||||||
|
x: 0
|
||||||
|
y: convergenceChrome.height - convergenceChrome.dockHeight - height
|
||||||
|
visible: convergenceChrome.leftLauncherOpen
|
||||||
|
opacity: convergenceChrome.leftLauncherOpen ? 1 : 0
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
transform: Translate {
|
||||||
|
y: convergenceChrome.leftLauncherOpen ? 0 : Kirigami.Units.gridUnit
|
||||||
|
x: convergenceChrome.leftLauncherOpen ? 0 : -leftEdgeLauncher.width + convergenceChrome.leftEdgeHotzoneWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
MobileShell.MotionNumberAnimation {
|
||||||
|
type: MobileShell.Motion.EffectsFast
|
||||||
|
duration: root.shortAnimationDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property real cornerRadius: Math.min(MobileShell.Constants.convergenceWorkspaceFrameRadius, height * 0.24)
|
||||||
|
|
||||||
|
Shape {
|
||||||
|
id: leftLauncherSurface
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
ShapePath {
|
||||||
|
fillColor: convergenceChrome.chromeColor
|
||||||
|
strokeWidth: 0
|
||||||
|
|
||||||
|
startX: 0
|
||||||
|
startY: 0
|
||||||
|
PathLine { x: leftEdgeLauncher.width - leftEdgeLauncher.cornerRadius; y: 0 }
|
||||||
|
PathArc {
|
||||||
|
x: leftEdgeLauncher.width
|
||||||
|
y: leftEdgeLauncher.cornerRadius
|
||||||
|
radiusX: leftEdgeLauncher.cornerRadius
|
||||||
|
radiusY: leftEdgeLauncher.cornerRadius
|
||||||
|
}
|
||||||
|
PathLine { x: leftEdgeLauncher.width; y: leftEdgeLauncher.height }
|
||||||
|
PathLine { x: 0; y: leftEdgeLauncher.height }
|
||||||
|
PathLine { x: 0; y: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Kirigami.Units.smallSpacing
|
||||||
|
spacing: Kirigami.Units.gridUnit * 0.65
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
PlasmaComponents.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: i18n("Recently Used")
|
||||||
|
font.weight: Font.Medium
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: recentAppsList
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 6.8
|
||||||
|
clip: true
|
||||||
|
interactive: false
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
model: folio.RecentApplicationsModel
|
||||||
|
|
||||||
|
delegate: MouseArea {
|
||||||
|
required property int index
|
||||||
|
required property var model
|
||||||
|
|
||||||
|
readonly property var delegateObject: model.delegate
|
||||||
|
readonly property var application: delegateObject ? delegateObject.application : null
|
||||||
|
readonly property bool validEntry: index < 5 && application !== null
|
||||||
|
|
||||||
|
width: recentAppsList.width
|
||||||
|
height: validEntry ? Kirigami.Units.gridUnit * 1.35 : 0
|
||||||
|
enabled: validEntry
|
||||||
|
hoverEnabled: validEntry
|
||||||
|
cursorShape: validEntry ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (application) {
|
||||||
|
convergenceChrome.launchStorageId(application.storageId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Kirigami.Units.cornerRadius
|
||||||
|
color: parent.containsMouse
|
||||||
|
? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.08)
|
||||||
|
: "transparent"
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: Kirigami.Units.smallSpacing
|
||||||
|
anchors.rightMargin: Kirigami.Units.smallSpacing
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
Kirigami.Icon {
|
||||||
|
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
Layout.preferredHeight: Layout.preferredWidth
|
||||||
|
source: application ? application.icon : ""
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaComponents.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: application ? application.name : ""
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
PlasmaComponents.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: i18n("Most Used")
|
||||||
|
font.weight: Font.Medium
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: favouritesQuickList
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
clip: true
|
||||||
|
interactive: false
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
model: folio.MostUsedApplicationsModel
|
||||||
|
|
||||||
|
delegate: MouseArea {
|
||||||
|
required property int index
|
||||||
|
required property var model
|
||||||
|
|
||||||
|
readonly property var delegateObject: model.delegate
|
||||||
|
readonly property var application: delegateObject ? delegateObject.application : null
|
||||||
|
readonly property bool validEntry: index < 6 && application !== null
|
||||||
|
|
||||||
|
width: favouritesQuickList.width
|
||||||
|
height: validEntry ? Kirigami.Units.gridUnit * 1.35 : 0
|
||||||
|
enabled: validEntry
|
||||||
|
hoverEnabled: validEntry
|
||||||
|
cursorShape: validEntry ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (application) {
|
||||||
|
convergenceChrome.launchStorageId(application.storageId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Kirigami.Units.cornerRadius
|
||||||
|
color: parent.containsMouse
|
||||||
|
? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.08)
|
||||||
|
: "transparent"
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: Kirigami.Units.smallSpacing
|
||||||
|
anchors.rightMargin: Kirigami.Units.smallSpacing
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
Kirigami.Icon {
|
||||||
|
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
Layout.preferredHeight: Layout.preferredWidth
|
||||||
|
source: application ? application.icon : ""
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaComponents.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: application ? application.name : ""
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// App-drawer overlay — renders the popup drawer above application
|
// App-drawer overlay — renders the popup drawer above application
|
||||||
|
|
|
||||||
|
|
@ -105,8 +105,7 @@ require_line "$folio_main" "id: convergenceChrome"
|
||||||
require_line "$folio_main" "LayerShell.Window.scope: \"convergence-chrome\""
|
require_line "$folio_main" "LayerShell.Window.scope: \"convergence-chrome\""
|
||||||
require_line "$folio_main" "height: Screen.height"
|
require_line "$folio_main" "height: Screen.height"
|
||||||
require_line "$folio_main" "MobileShell.StatusBar {"
|
require_line "$folio_main" "MobileShell.StatusBar {"
|
||||||
require_line "$folio_main" "MobileShell.ShellUtil.setInputRegions(convergenceChrome, ["
|
require_line "$folio_main" "MobileShell.ShellUtil.setInputRegions(convergenceChrome, regions)"
|
||||||
require_line "$folio_main" "readonly property real topBarHitHeight: topBarHeight + frameThickness"
|
|
||||||
require_line "$folio_main" "const topBarRegion = Qt.rect(0, 0, width, topBarHitHeight)"
|
require_line "$folio_main" "const topBarRegion = Qt.rect(0, 0, width, topBarHitHeight)"
|
||||||
require_line "$folio_main" "readonly property real dockHeight: MobileShell.Constants.convergenceDockHeight"
|
require_line "$folio_main" "readonly property real dockHeight: MobileShell.Constants.convergenceDockHeight"
|
||||||
require_line "$folio_main" "readonly property real revealStripHeight: MobileShell.Constants.convergenceDockRevealHeight"
|
require_line "$folio_main" "readonly property real revealStripHeight: MobileShell.Constants.convergenceDockRevealHeight"
|
||||||
|
|
@ -115,6 +114,7 @@ require_line "$folio_main" "id: workspaceFrame"
|
||||||
require_line "$folio_main" "readonly property real frameThickness: MobileShell.Constants.convergenceWorkspaceFrameThickness"
|
require_line "$folio_main" "readonly property real frameThickness: MobileShell.Constants.convergenceWorkspaceFrameThickness"
|
||||||
require_line "$folio_main" "readonly property real frameRadius:"
|
require_line "$folio_main" "readonly property real frameRadius:"
|
||||||
require_line "$folio_main" "readonly property real workAreaX: frameThickness"
|
require_line "$folio_main" "readonly property real workAreaX: frameThickness"
|
||||||
|
require_line "$folio_main" "readonly property real topBarHitHeight: topBarHeight + frameThickness"
|
||||||
require_line "$folio_main" "readonly property real workAreaY: topBarHitHeight"
|
require_line "$folio_main" "readonly property real workAreaY: topBarHitHeight"
|
||||||
require_line "$folio_main" "readonly property real workAreaWidth: Math.max(0, width - frameThickness * 2)"
|
require_line "$folio_main" "readonly property real workAreaWidth: Math.max(0, width - frameThickness * 2)"
|
||||||
require_line "$folio_main" "readonly property real workAreaHeight: Math.max(0, height - topBarHeight - dockHeight - frameThickness * 2)"
|
require_line "$folio_main" "readonly property real workAreaHeight: Math.max(0, height - topBarHeight - dockHeight - frameThickness * 2)"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue