mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-29 07:03:08 +00:00
folio: Rewrite
fix landscape favourites bar drag and drop, and cleanup folder fix drawer scrolling add settings
This commit is contained in:
parent
0d6e17e247
commit
580afdfc9c
91 changed files with 8247 additions and 4052 deletions
|
|
@ -22,7 +22,7 @@ SwipeArea::SwipeArea(QQuickItem *parent)
|
|||
setFiltersChildMouseEvents(true);
|
||||
}
|
||||
|
||||
SwipeArea::Mode SwipeArea::mode()
|
||||
SwipeArea::Mode SwipeArea::mode() const
|
||||
{
|
||||
return m_mode;
|
||||
}
|
||||
|
|
@ -33,21 +33,32 @@ void SwipeArea::setMode(Mode mode)
|
|||
Q_EMIT modeChanged();
|
||||
}
|
||||
|
||||
bool SwipeArea::interactive()
|
||||
bool SwipeArea::interactive() const
|
||||
{
|
||||
return m_interactive;
|
||||
}
|
||||
|
||||
bool SwipeArea::moving()
|
||||
void SwipeArea::setInteractive(bool interactive)
|
||||
{
|
||||
m_interactive = interactive;
|
||||
Q_EMIT interactiveChanged();
|
||||
}
|
||||
|
||||
bool SwipeArea::moving() const
|
||||
{
|
||||
return m_moving;
|
||||
}
|
||||
|
||||
bool SwipeArea::pressed()
|
||||
bool SwipeArea::pressed() const
|
||||
{
|
||||
return m_pressed;
|
||||
}
|
||||
|
||||
void SwipeArea::setSkipSwipeThreshold(bool value)
|
||||
{
|
||||
m_skipSwipeThreshold = value;
|
||||
}
|
||||
|
||||
bool SwipeArea::childMouseEventFilter(QQuickItem *item, QEvent *event)
|
||||
{
|
||||
if (!isVisible() || !isEnabled() || !m_interactive) {
|
||||
|
|
@ -205,12 +216,6 @@ void SwipeArea::touchUngrabEvent()
|
|||
QQuickItem::touchUngrabEvent();
|
||||
}
|
||||
|
||||
void SwipeArea::setInteractive(bool interactive)
|
||||
{
|
||||
m_interactive = interactive;
|
||||
Q_EMIT interactiveChanged();
|
||||
}
|
||||
|
||||
void SwipeArea::setMoving(bool moving)
|
||||
{
|
||||
m_moving = moving;
|
||||
|
|
@ -225,6 +230,7 @@ void SwipeArea::setPressed(bool pressed)
|
|||
|
||||
void SwipeArea::resetSwipe()
|
||||
{
|
||||
m_skipSwipeThreshold = false;
|
||||
m_stealMouse = false;
|
||||
if (m_pressed) {
|
||||
setPressed(false);
|
||||
|
|
@ -253,34 +259,38 @@ void SwipeArea::handleReleaseEvent(QPointerEvent *event, QPointF point)
|
|||
if (m_moving) {
|
||||
Q_EMIT swipeEnded();
|
||||
}
|
||||
|
||||
resetSwipe();
|
||||
}
|
||||
|
||||
void SwipeArea::handleMoveEvent(QPointerEvent *event, QPointF point)
|
||||
{
|
||||
const QVector2D totalDelta = QVector2D(point - m_startPos);
|
||||
const QVector2D delta = QVector2D(point - m_lastPos);
|
||||
|
||||
m_lastPos = point;
|
||||
|
||||
if (!m_stealMouse) {
|
||||
// if we haven't reached the swipe registering threshold yet, don't start the swipe
|
||||
if (m_mode == Mode::VerticalOnly && qAbs(point.y() - m_pressPos.y()) < SWIPE_REGISTER_THRESHOLD) {
|
||||
return;
|
||||
} else if (m_mode == Mode::HorizontalOnly && qAbs(point.x() - m_pressPos.x()) < SWIPE_REGISTER_THRESHOLD) {
|
||||
return;
|
||||
} else if (m_mode == Mode::BothAxis && qAbs(point.manhattanLength() - m_pressPos.manhattanLength()) < SWIPE_REGISTER_THRESHOLD) {
|
||||
return;
|
||||
if (!m_skipSwipeThreshold) {
|
||||
// if we haven't reached the swipe registering threshold yet, don't start the swipe
|
||||
if (m_mode == Mode::VerticalOnly && qAbs(point.y() - m_pressPos.y()) < SWIPE_REGISTER_THRESHOLD) {
|
||||
return;
|
||||
} else if (m_mode == Mode::HorizontalOnly && qAbs(point.x() - m_pressPos.x()) < SWIPE_REGISTER_THRESHOLD) {
|
||||
return;
|
||||
} else if (m_mode == Mode::BothAxis && qAbs(point.manhattanLength() - m_pressPos.manhattanLength()) < SWIPE_REGISTER_THRESHOLD) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_skipSwipeThreshold = false;
|
||||
|
||||
// we now start the swipe, stealing it from children
|
||||
|
||||
m_startPos = point;
|
||||
m_lastPos = point;
|
||||
m_stealMouse = true;
|
||||
setMoving(true);
|
||||
Q_EMIT swipeStarted(m_startPos);
|
||||
}
|
||||
|
||||
const QVector2D totalDelta = QVector2D(point - m_startPos);
|
||||
const QVector2D delta = QVector2D(point - m_lastPos);
|
||||
m_lastPos = point;
|
||||
|
||||
// ensure it's called AFTER swipeStarted()
|
||||
Q_EMIT swipeMove(totalDelta.x(), totalDelta.y(), delta.x(), delta.y());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class SwipeArea : public QQuickItem
|
|||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(SwipeArea::Mode mode READ mode WRITE setMode NOTIFY modeChanged)
|
||||
Q_PROPERTY(bool interactive READ interactive NOTIFY interactiveChanged)
|
||||
Q_PROPERTY(bool interactive READ interactive WRITE setInteractive NOTIFY interactiveChanged)
|
||||
Q_PROPERTY(bool moving READ moving NOTIFY movingChanged)
|
||||
Q_PROPERTY(bool pressed READ pressed NOTIFY pressedChanged)
|
||||
|
||||
|
|
@ -35,12 +35,16 @@ public:
|
|||
enum Mode { BothAxis = 0, VerticalOnly, HorizontalOnly };
|
||||
Q_ENUM(Mode)
|
||||
|
||||
Mode mode();
|
||||
Mode mode() const;
|
||||
void setMode(Mode mode);
|
||||
|
||||
bool interactive();
|
||||
bool moving();
|
||||
bool pressed();
|
||||
bool interactive() const;
|
||||
void setInteractive(bool interactive);
|
||||
|
||||
bool moving() const;
|
||||
bool pressed() const;
|
||||
|
||||
Q_INVOKABLE void setSkipSwipeThreshold(bool value);
|
||||
|
||||
Q_SIGNALS:
|
||||
void modeChanged();
|
||||
|
|
@ -65,7 +69,6 @@ protected:
|
|||
void touchUngrabEvent() override;
|
||||
|
||||
private:
|
||||
void setInteractive(bool interactive);
|
||||
void setMoving(bool moving);
|
||||
void setPressed(bool pressed);
|
||||
|
||||
|
|
@ -95,6 +98,9 @@ private:
|
|||
|
||||
// the previous point where interaction was at
|
||||
QPointF m_lastPos;
|
||||
|
||||
// whether to skip trying to measure the swipe threshold
|
||||
bool m_skipSwipeThreshold;
|
||||
};
|
||||
|
||||
QML_DECLARE_TYPE(SwipeArea)
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ void MobileShellPlugin::registerTypes(const char *uri)
|
|||
qmlRegisterSingletonType(resolvePath("volumeosd/VolumeOSDProviderLoader.qml"), uri, 1, 0, "VolumeOSDProviderLoader");
|
||||
|
||||
// /widgets
|
||||
qmlRegisterType(resolvePath("widgets/krunner/KRunnerScreen.qml"), uri, 1, 0, "KRunnerScreen");
|
||||
qmlRegisterType(resolvePath("widgets/krunner/KRunnerWidget.qml"), uri, 1, 0, "KRunnerWidget");
|
||||
qmlRegisterType(resolvePath("widgets/mediacontrols/MediaControlsWidget.qml"), uri, 1, 0, "MediaControlsWidget");
|
||||
qmlRegisterType(resolvePath("widgets/notifications/NotificationsWidget.qml"), uri, 1, 0, "NotificationsWidget");
|
||||
|
|
|
|||
221
components/mobileshell/qml/widgets/krunner/KRunnerScreen.qml
Normal file
221
components/mobileshell/qml/widgets/krunner/KRunnerScreen.qml
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2014 Aaron Seigo <aseigo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2015 Marco Martin <notmart@gmail.com>
|
||||
* SPDX-FileCopyrightText: 2021-2023 Devin Lin <devin@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Controls as Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.plasma.core as PlasmaCore
|
||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||
import org.kde.plasma.extras 2.0 as PlasmaExtras
|
||||
|
||||
import org.kde.milou as Milou
|
||||
import org.kde.kirigami 2.19 as Kirigami
|
||||
|
||||
import "../../components" as Components
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
function requestFocus() {
|
||||
queryField.forceActiveFocus();
|
||||
}
|
||||
|
||||
signal requestedClose()
|
||||
|
||||
ColumnLayout {
|
||||
id: column
|
||||
anchors.fill: parent
|
||||
|
||||
Controls.Control {
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: Kirigami.Units.gridUnit * 30
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: Kirigami.Units.gridUnit
|
||||
Layout.leftMargin: Kirigami.Units.gridUnit
|
||||
Layout.rightMargin: Kirigami.Units.gridUnit
|
||||
|
||||
leftPadding: Kirigami.Units.smallSpacing
|
||||
rightPadding: Kirigami.Units.smallSpacing
|
||||
topPadding: Kirigami.Units.smallSpacing
|
||||
bottomPadding: Kirigami.Units.smallSpacing
|
||||
|
||||
background: Item {
|
||||
|
||||
// shadow for search window
|
||||
MultiEffect {
|
||||
anchors.fill: parent
|
||||
source: rectBackground
|
||||
blurMax: 16
|
||||
shadowEnabled: true
|
||||
shadowVerticalOffset: 1
|
||||
shadowOpacity: 0.15
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: rectBackground
|
||||
anchors.fill: parent
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
radius: Kirigami.Units.smallSpacing
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: RowLayout {
|
||||
Item {
|
||||
implicitHeight: queryField.height
|
||||
implicitWidth: height
|
||||
Kirigami.Icon {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Math.round(Kirigami.Units.smallSpacing)
|
||||
source: "start-here-symbolic"
|
||||
}
|
||||
}
|
||||
PlasmaComponents.TextField {
|
||||
id: queryField
|
||||
Layout.fillWidth: true
|
||||
placeholderText: i18n("Search…")
|
||||
inputMethodHints: Qt.ImhNoPredictiveText // don't need to press "enter" to update text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Controls.ScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: listView.contentHeight > availableHeight
|
||||
|
||||
Milou.ResultsListView {
|
||||
id: listView
|
||||
queryString: queryField.text
|
||||
clip: true
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||
|
||||
highlight: activeFocus ? highlightComponent : null
|
||||
Component {
|
||||
id: highlightComponent
|
||||
|
||||
PlasmaExtras.Highlight {}
|
||||
}
|
||||
|
||||
onActivated: {
|
||||
root.requestedClose();
|
||||
}
|
||||
onUpdateQueryString: {
|
||||
queryField.text = text
|
||||
queryField.cursorPosition = cursorPosition
|
||||
}
|
||||
|
||||
delegate: MouseArea {
|
||||
id: delegate
|
||||
height: rowLayout.height
|
||||
width: listView.width
|
||||
|
||||
onClicked: {
|
||||
listView.currentIndex = model.index;
|
||||
listView.runCurrentIndex();
|
||||
|
||||
root.requestedClose();
|
||||
}
|
||||
hoverEnabled: true
|
||||
|
||||
function activateNextAction() {
|
||||
queryField.forceActiveFocus();
|
||||
queryField.selectAll();
|
||||
listView.currentIndex = -1;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: delegate.pressed ? Qt.rgba(255, 255, 255, 0.2) : (delegate.containsMouse ? Qt.rgba(255, 255, 255, 0.05) : "transparent")
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Kirigami.Units.shortDuration }
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
height: Kirigami.Units.gridUnit * 3
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
leftMargin: Kirigami.Units.gridUnit
|
||||
rightMargin: Kirigami.Units.gridUnit
|
||||
}
|
||||
|
||||
Kirigami.Icon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
source: model.decoration
|
||||
implicitWidth: Kirigami.Units.iconSizes.medium
|
||||
implicitHeight: Kirigami.Units.iconSizes.medium
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
PlasmaComponents.Label {
|
||||
id: title
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Kirigami.Units.smallSpacing * 2
|
||||
Layout.rightMargin: Kirigami.Units.gridUnit
|
||||
|
||||
maximumLineCount: 1
|
||||
elide: Text.ElideRight
|
||||
text: typeof modelData !== "undefined" ? modelData : model.display
|
||||
color: "white"
|
||||
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize
|
||||
}
|
||||
PlasmaComponents.Label {
|
||||
id: subtitle
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Kirigami.Units.smallSpacing * 2
|
||||
Layout.rightMargin: Kirigami.Units.gridUnit
|
||||
|
||||
maximumLineCount: 1
|
||||
elide: Text.ElideRight
|
||||
text: model.subtext || ""
|
||||
color: "white"
|
||||
opacity: 0.8
|
||||
|
||||
font.pointSize: Math.round(Kirigami.Theme.defaultFont.pointSize * 0.8)
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: actionsRepeater
|
||||
model: typeof actions !== "undefined" ? actions : []
|
||||
|
||||
Controls.ToolButton {
|
||||
icon: modelData.icon || ""
|
||||
visible: modelData.visible || true
|
||||
enabled: modelData.enabled || true
|
||||
|
||||
Accessible.role: Accessible.Button
|
||||
Accessible.name: modelData.text
|
||||
checkable: checked
|
||||
checked: delegate.activeAction === index
|
||||
focus: delegate.activeAction === index
|
||||
onClicked: delegate.ListView.view.runAction(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
onClicked: root.requestedClose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -202,7 +202,7 @@ Item {
|
|||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||
|
||||
highlight: activeFocus ? highlightComponent : null
|
||||
Component{
|
||||
Component {
|
||||
id: highlightComponent
|
||||
|
||||
PlasmaExtras.Highlight {}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@
|
|||
<file>qml/statusbar/StatusBar.qml</file>
|
||||
<file>qml/statusbar/TaskWidget.qml</file>
|
||||
|
||||
<file>qml/widgets/krunner/KRunnerScreen.qml</file>
|
||||
<file>qml/widgets/krunner/KRunnerWidget.qml</file>
|
||||
|
||||
<file>qml/widgets/mediacontrols/BlurredBackground.qml</file>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,20 @@
|
|||
# SPDX-FileCopyrightText: 2015-2021 Marco Martin <mart@kde.org>
|
||||
# SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(homescreen_SRCS
|
||||
homescreen.cpp
|
||||
applicationlistmodel.cpp
|
||||
homescreenstate.cpp
|
||||
windowlistener.cpp
|
||||
favouritesmodel.cpp
|
||||
folioapplication.cpp
|
||||
folioapplicationfolder.cpp
|
||||
foliodelegate.cpp
|
||||
foliosettings.cpp
|
||||
pagemodel.cpp
|
||||
pagelistmodel.cpp
|
||||
delegatetoucharea.cpp
|
||||
dragstate.cpp
|
||||
)
|
||||
|
||||
add_library(org.kde.plasma.mobile.homescreen.folio MODULE ${homescreen_SRCS})
|
||||
|
|
@ -19,11 +31,9 @@ target_link_libraries(org.kde.plasma.mobile.homescreen.folio
|
|||
KF6::Notifications
|
||||
KF6::WaylandClient
|
||||
KF6::WindowSystem
|
||||
KF6::JobWidgets
|
||||
)
|
||||
|
||||
|
||||
install(TARGETS org.kde.plasma.mobile.homescreen.folio DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasma/applets)
|
||||
|
||||
plasma_install_package(package org.kde.plasma.mobile.homescreen.folio)
|
||||
|
||||
add_subdirectory(plugin)
|
||||
|
|
|
|||
26
containments/homescreens/folio/README.md
Normal file
26
containments/homescreens/folio/README.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: None
|
||||
- SPDX-License-Identifier: CC0-1.0
|
||||
-->
|
||||
|
||||
# Folio Homescreen
|
||||
|
||||
This is the paged homescreen for Plasma Mobile.
|
||||
|
||||
### How it works
|
||||
|
||||
Most of the homescreen is in C++ in order to keep logic together, with QML only responsible for the display and user input.
|
||||
|
||||
As such, all of the positioning and placement of delegates on the screen are top down from the model, as well as drag and drop behaviour.
|
||||
|
||||
#### TODO
|
||||
- Add folio/halcyon switcher in initial-start
|
||||
- If an app gets uninstalled, the homescreen UI needs to ensure that delegates are updated
|
||||
- BUG: the position of where things think the dragged icon is during drag-and-drop is slightly off because of the label
|
||||
- BUG: landscape favourites bar duplication when dragging icon from it sometimes
|
||||
- BUG: can't insert delegates in-between very well in landscape favourites bar
|
||||
- can make the touch area only the icon?
|
||||
- FEATURE: add import/export
|
||||
- FEATURE: keyboard navigation
|
||||
- FEATURE: touchpad navigation
|
||||
- BUG: it's possible to get stuck in an unswipeable state after swiping down from the app drawer
|
||||
121
containments/homescreens/folio/applicationlistmodel.cpp
Normal file
121
containments/homescreens/folio/applicationlistmodel.cpp
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
// SPDX-FileCopyrightText: 2014 Antonis Tsiapaliokas <antonis.tsiapaliokas@kde.org>
|
||||
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "applicationlistmodel.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
#include <QModelIndex>
|
||||
#include <QProcess>
|
||||
#include <QQuickWindow>
|
||||
|
||||
#include <KApplicationTrader>
|
||||
#include <KConfigGroup>
|
||||
#include <KIO/ApplicationLauncherJob>
|
||||
#include <KNotificationJobUiDelegate>
|
||||
#include <KService>
|
||||
#include <KSharedConfig>
|
||||
#include <KSycoca>
|
||||
|
||||
ApplicationListModel::ApplicationListModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
connect(KSycoca::self(), &KSycoca::databaseChanged, this, &ApplicationListModel::sycocaDbChanged);
|
||||
|
||||
// initialize wayland window checking
|
||||
KWayland::Client::ConnectionThread *connection = KWayland::Client::ConnectionThread::fromApplication(this);
|
||||
if (!connection) {
|
||||
return;
|
||||
}
|
||||
|
||||
load();
|
||||
}
|
||||
|
||||
ApplicationListModel::~ApplicationListModel() = default;
|
||||
|
||||
ApplicationListModel *ApplicationListModel::self()
|
||||
{
|
||||
static ApplicationListModel *inst = new ApplicationListModel(nullptr);
|
||||
return inst;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ApplicationListModel::roleNames() const
|
||||
{
|
||||
return {{DelegateRole, QByteArrayLiteral("delegate")}};
|
||||
}
|
||||
|
||||
void ApplicationListModel::sycocaDbChanged()
|
||||
{
|
||||
load();
|
||||
}
|
||||
|
||||
void ApplicationListModel::load()
|
||||
{
|
||||
auto cfg = KSharedConfig::openConfig(QStringLiteral("applications-blacklistrc"));
|
||||
auto blgroup = KConfigGroup(cfg, QStringLiteral("Applications"));
|
||||
|
||||
const QStringList blacklist = blgroup.readEntry("blacklist", QStringList());
|
||||
|
||||
beginResetModel();
|
||||
|
||||
m_delegates.clear();
|
||||
|
||||
QList<FolioDelegate *> unorderedList;
|
||||
|
||||
auto filter = [blacklist](const KService::Ptr &service) -> bool {
|
||||
if (service->noDisplay()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!service->showOnCurrentPlatform()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (blacklist.contains(service->desktopEntryName())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const KService::List apps = KApplicationTrader::query(filter);
|
||||
|
||||
for (const KService::Ptr &service : apps) {
|
||||
FolioApplication *app = new FolioApplication{this, service};
|
||||
FolioDelegate *delegate = new FolioDelegate{app, this};
|
||||
unorderedList << delegate;
|
||||
}
|
||||
|
||||
std::sort(unorderedList.begin(), unorderedList.end(), [](FolioDelegate *a1, FolioDelegate *a2) {
|
||||
return a1->application()->name().compare(a2->application()->name(), Qt::CaseInsensitive) < 0;
|
||||
});
|
||||
|
||||
m_delegates << unorderedList;
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QVariant ApplicationListModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
case DelegateRole:
|
||||
return QVariant::fromValue(m_delegates.at(index.row()));
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
int ApplicationListModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (parent.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return m_delegates.count();
|
||||
}
|
||||
45
containments/homescreens/folio/applicationlistmodel.h
Normal file
45
containments/homescreens/folio/applicationlistmodel.h
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// SPDX-FileCopyrightText: 2014 Antonis Tsiapaliokas <antonis.tsiapaliokas@kde.org>
|
||||
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QQuickItem>
|
||||
#include <QSet>
|
||||
|
||||
#include "foliodelegate.h"
|
||||
|
||||
/**
|
||||
* @short The base application list, used directly by the app drawer.
|
||||
*/
|
||||
class ApplicationListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
DelegateRole = Qt::UserRole + 1,
|
||||
};
|
||||
|
||||
ApplicationListModel(QObject *parent = nullptr);
|
||||
~ApplicationListModel() override;
|
||||
static ApplicationListModel *self();
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
|
||||
QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE;
|
||||
|
||||
Q_INVOKABLE void load();
|
||||
|
||||
public Q_SLOTS:
|
||||
void sycocaDbChanged();
|
||||
|
||||
Q_SIGNALS:
|
||||
void launchError(const QString &msg);
|
||||
|
||||
protected:
|
||||
QList<FolioDelegate *> m_delegates;
|
||||
};
|
||||
302
containments/homescreens/folio/delegatetoucharea.cpp
Normal file
302
containments/homescreens/folio/delegatetoucharea.cpp
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
// SPDX-FileCopyrightText: 2016 The Qt Company Ltd.
|
||||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "delegatetoucharea.h"
|
||||
|
||||
#include <QCursor>
|
||||
|
||||
// Some code taken from MouseArea
|
||||
|
||||
DelegateTouchArea::DelegateTouchArea(QQuickItem *parent)
|
||||
: QQuickItem{parent}
|
||||
, m_pressAndHoldTimer{new QTimer{this}}
|
||||
{
|
||||
// TODO: currently hardcoded 2s press and hold interval
|
||||
m_pressAndHoldTimer->setInterval(600);
|
||||
m_pressAndHoldTimer->setSingleShot(true);
|
||||
connect(m_pressAndHoldTimer, &QTimer::timeout, this, &DelegateTouchArea::startPressAndHold);
|
||||
|
||||
// Explcitly call setCursor on QQuickItem since
|
||||
// it internally keeps a boolean hasCursor that doesn't
|
||||
// get set to true unless you call setCursor
|
||||
setCursor(Qt::ArrowCursor);
|
||||
|
||||
setAcceptHoverEvents(true);
|
||||
setAcceptTouchEvents(true);
|
||||
// setFiltersChildMouseEvents(true);
|
||||
setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
|
||||
}
|
||||
|
||||
bool DelegateTouchArea::pressed()
|
||||
{
|
||||
return m_pressed;
|
||||
}
|
||||
|
||||
void DelegateTouchArea::setPressed(bool pressed)
|
||||
{
|
||||
if (pressed != m_pressed) {
|
||||
m_pressed = pressed;
|
||||
Q_EMIT pressedChanged(pressed);
|
||||
}
|
||||
}
|
||||
|
||||
bool DelegateTouchArea::hovered()
|
||||
{
|
||||
return m_hovered;
|
||||
}
|
||||
|
||||
void DelegateTouchArea::setHovered(bool hovered)
|
||||
{
|
||||
if (hovered != m_hovered) {
|
||||
m_hovered = hovered;
|
||||
Q_EMIT hoveredChanged(hovered);
|
||||
}
|
||||
}
|
||||
|
||||
bool DelegateTouchArea::dragging()
|
||||
{
|
||||
return m_dragging;
|
||||
}
|
||||
|
||||
void DelegateTouchArea::setDragging(bool dragging)
|
||||
{
|
||||
if (dragging != m_dragging) {
|
||||
m_dragging = dragging;
|
||||
Q_EMIT draggingChanged(dragging);
|
||||
}
|
||||
}
|
||||
|
||||
Qt::CursorShape DelegateTouchArea::cursorShape()
|
||||
{
|
||||
return cursor().shape();
|
||||
}
|
||||
|
||||
void DelegateTouchArea::setCursorShape(Qt::CursorShape cursorShape)
|
||||
{
|
||||
if (cursor().shape() == cursorShape) {
|
||||
return;
|
||||
}
|
||||
|
||||
setCursor(cursorShape);
|
||||
Q_EMIT cursorShapeChanged();
|
||||
}
|
||||
|
||||
void DelegateTouchArea::unsetCursor()
|
||||
{
|
||||
setCursorShape(Qt::ArrowCursor);
|
||||
}
|
||||
|
||||
void DelegateTouchArea::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() & Qt::RightButton) {
|
||||
Q_EMIT rightMousePress();
|
||||
} else if (event->button() & Qt::LeftButton) {
|
||||
handlePressEvent(event, event->points().first().position());
|
||||
event->accept();
|
||||
} else {
|
||||
QQuickItem::mousePressEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void DelegateTouchArea::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
handleMoveEvent(event, event->points().first().position());
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void DelegateTouchArea::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() & Qt::LeftButton) {
|
||||
handleReleaseEvent(event, true);
|
||||
event->accept();
|
||||
} else {
|
||||
QQuickItem::mouseReleaseEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void DelegateTouchArea::mouseUngrabEvent()
|
||||
{
|
||||
if (m_pressed) {
|
||||
handleReleaseEvent(nullptr, false);
|
||||
}
|
||||
QQuickItem::mouseUngrabEvent();
|
||||
}
|
||||
|
||||
void DelegateTouchArea::touchEvent(QTouchEvent *event)
|
||||
{
|
||||
bool unhandled = true;
|
||||
const auto &firstPoint = event->points().first();
|
||||
|
||||
switch (firstPoint.state()) {
|
||||
case QEventPoint::State::Pressed:
|
||||
handlePressEvent(event, firstPoint.position());
|
||||
event->accept();
|
||||
unhandled = false;
|
||||
break;
|
||||
case QEventPoint::State::Updated:
|
||||
handleMoveEvent(event, firstPoint.position());
|
||||
event->accept();
|
||||
unhandled = false;
|
||||
break;
|
||||
case QEventPoint::State::Released:
|
||||
handleReleaseEvent(event, true);
|
||||
event->accept();
|
||||
unhandled = false;
|
||||
break;
|
||||
case QEventPoint::State::Stationary:
|
||||
case QEventPoint::State::Unknown:
|
||||
break;
|
||||
}
|
||||
|
||||
if (unhandled) {
|
||||
QQuickItem::touchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void DelegateTouchArea::touchUngrabEvent()
|
||||
{
|
||||
if (m_pressed) {
|
||||
handleReleaseEvent(nullptr, false);
|
||||
}
|
||||
QQuickItem::touchUngrabEvent();
|
||||
}
|
||||
|
||||
void DelegateTouchArea::hoverEnterEvent(QHoverEvent *event)
|
||||
{
|
||||
setHovered(true);
|
||||
|
||||
// don't block hover events
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
void DelegateTouchArea::hoverLeaveEvent(QHoverEvent *event)
|
||||
{
|
||||
setHovered(false);
|
||||
|
||||
// don't block hover events
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
// bool DelegateTouchArea::childMouseEventFilter(QQuickItem *item, QEvent *event)
|
||||
// {
|
||||
// if (!isVisible() || !isEnabled()) {
|
||||
// handleReleaseEvent(nullptr, false);
|
||||
// return QQuickItem::childMouseEventFilter(item, event);
|
||||
// }
|
||||
//
|
||||
// if (event->isPointerEvent() && event->type() != QEvent::UngrabMouse) {
|
||||
// return filterPointerEvent(item, static_cast<QPointerEvent *>(event));
|
||||
// }
|
||||
//
|
||||
// return QQuickItem::childMouseEventFilter(item, event);
|
||||
// }
|
||||
//
|
||||
// // take exclusive grab from children
|
||||
// bool DelegateTouchArea::filterPointerEvent(QQuickItem *receiver, QPointerEvent *event)
|
||||
// {
|
||||
// // only filter mouse, touch or tablet events
|
||||
// if (!dynamic_cast<QMouseEvent *>(event) && !dynamic_cast<QTabletEvent *>(event) && !dynamic_cast<QTouchEvent *>(event)) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// const auto &firstPoint = event->points().first();
|
||||
//
|
||||
// if (event->pointCount() == 1 && event->exclusiveGrabber(firstPoint) == this) {
|
||||
// // We have an exclusive grab (since we're e.g dragging), but at the same time, we have
|
||||
// // a child with a passive grab (which is why this filter is being called). And because
|
||||
// // of that, we end up getting the same pointer events twice; First in our own event
|
||||
// // handlers (because of the grab), then once more in here, since we filter the child.
|
||||
// // To avoid processing the event twice (e.g avoid calling handleReleaseEvent once more
|
||||
// // from below), we mark the event as filtered, and simply return.
|
||||
// event->setAccepted(true);
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// QPointF localPos = mapFromScene(firstPoint.scenePosition());
|
||||
// bool receiverDisabled = receiver && !receiver->isEnabled();
|
||||
// bool receiverKeepsGrab = receiver && (receiver->keepMouseGrab() || receiver->keepTouchGrab());
|
||||
//
|
||||
// if ((m_pressAndHeld || contains(localPos)) && (!receiver || !receiverKeepsGrab || receiverDisabled)) {
|
||||
// // clone the event, and set the first point's local position
|
||||
// // HACK: we can't change QPointerEvent's points since it's const, so we have to pass localPos into the handlers
|
||||
// QPointerEvent *localizedEvent = event->clone();
|
||||
// localizedEvent->setAccepted(false);
|
||||
//
|
||||
// switch (firstPoint.state()) {
|
||||
// case QEventPoint::State::Updated:
|
||||
// handleMoveEvent(localizedEvent, localPos);
|
||||
// break;
|
||||
// case QEventPoint::State::Pressed:
|
||||
// handlePressEvent(localizedEvent, localPos);
|
||||
// break;
|
||||
// case QEventPoint::State::Released:
|
||||
// handleReleaseEvent(localizedEvent, true);
|
||||
// break;
|
||||
// case QEventPoint::State::Stationary:
|
||||
// case QEventPoint::State::Unknown:
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// if ((receiver && m_pressAndHeld && !receiverKeepsGrab && receiver != this) || receiverDisabled) {
|
||||
// event->setExclusiveGrabber(firstPoint, this);
|
||||
// }
|
||||
//
|
||||
// bool filtered = m_pressAndHeld || receiverDisabled;
|
||||
// if (filtered) {
|
||||
// event->setAccepted(true);
|
||||
// }
|
||||
//
|
||||
// return filtered;
|
||||
// }
|
||||
//
|
||||
// if (firstPoint.state() == QEventPoint::State::Released || (receiverKeepsGrab && !receiverDisabled)) {
|
||||
// // mouse released, or another item has claimed the grab
|
||||
// handleReleaseEvent(nullptr, false);
|
||||
// }
|
||||
//
|
||||
// return false;
|
||||
// }
|
||||
|
||||
void DelegateTouchArea::handlePressEvent(QPointerEvent *event, QPointF point)
|
||||
{
|
||||
// ignore multiple press events
|
||||
if (m_pressed) {
|
||||
return;
|
||||
}
|
||||
|
||||
setPressed(true);
|
||||
m_pressAndHoldTimer->start();
|
||||
}
|
||||
|
||||
void DelegateTouchArea::handleReleaseEvent(QPointerEvent *event, bool click)
|
||||
{
|
||||
// NOTE: event can be nullptr!
|
||||
|
||||
setPressed(false);
|
||||
setDragging(false);
|
||||
|
||||
if (!m_pressAndHeld && click) {
|
||||
Q_EMIT clicked();
|
||||
}
|
||||
|
||||
if (m_pressAndHeld) {
|
||||
Q_EMIT pressAndHoldReleased();
|
||||
}
|
||||
|
||||
m_pressAndHoldTimer->stop();
|
||||
m_pressAndHeld = false;
|
||||
}
|
||||
|
||||
void DelegateTouchArea::handleMoveEvent(QPointerEvent *event, QPointF point)
|
||||
{
|
||||
if (m_pressAndHeld) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
void DelegateTouchArea::startPressAndHold()
|
||||
{
|
||||
m_pressAndHeld = true;
|
||||
Q_EMIT pressAndHold();
|
||||
}
|
||||
81
containments/homescreens/folio/delegatetoucharea.h
Normal file
81
containments/homescreens/folio/delegatetoucharea.h
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QQuickItem>
|
||||
#include <QTimer>
|
||||
#include <Qt>
|
||||
|
||||
/**
|
||||
* @short A component that is similar to MouseArea but allows for a
|
||||
* simpler tracking of dragging movements after pressing and holding.
|
||||
*
|
||||
* @author Devin Lin <devin@kde.org>
|
||||
*/
|
||||
class DelegateTouchArea : public QQuickItem
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(bool pressed READ pressed NOTIFY pressedChanged FINAL)
|
||||
Q_PROPERTY(bool hovered READ hovered NOTIFY hoveredChanged FINAL)
|
||||
Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged FINAL)
|
||||
Q_PROPERTY(Qt::CursorShape cursorShape READ cursorShape WRITE setCursorShape RESET unsetCursor NOTIFY cursorShapeChanged FINAL)
|
||||
|
||||
QML_NAMED_ELEMENT(DelegateTouchArea)
|
||||
|
||||
public:
|
||||
DelegateTouchArea(QQuickItem *parent = nullptr);
|
||||
|
||||
bool pressed();
|
||||
bool hovered();
|
||||
bool dragging();
|
||||
Qt::CursorShape cursorShape();
|
||||
void setCursorShape(Qt::CursorShape cursorShape);
|
||||
void unsetCursor();
|
||||
|
||||
Q_SIGNALS:
|
||||
void clicked();
|
||||
void rightMousePress();
|
||||
void pressAndHold();
|
||||
void pressAndHoldReleased();
|
||||
void drag(qreal x, qreal y);
|
||||
void pressedChanged(bool pressed);
|
||||
void hoveredChanged(bool hovered);
|
||||
void draggingChanged(bool dragging);
|
||||
void cursorShapeChanged();
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void mouseUngrabEvent() override;
|
||||
void touchEvent(QTouchEvent *event) override;
|
||||
void touchUngrabEvent() override;
|
||||
void hoverEnterEvent(QHoverEvent *event) override;
|
||||
void hoverLeaveEvent(QHoverEvent *event) override;
|
||||
// bool childMouseEventFilter(QQuickItem *i, QEvent *e) override;
|
||||
|
||||
private Q_SLOTS:
|
||||
void startPressAndHold();
|
||||
|
||||
private:
|
||||
// bool filterPointerEvent(QQuickItem *receiver, QPointerEvent *event);
|
||||
void setPressed(bool pressed);
|
||||
void setHovered(bool hovered);
|
||||
void setDragging(bool dragging);
|
||||
|
||||
void handlePressEvent(QPointerEvent *event, QPointF point);
|
||||
void handleReleaseEvent(QPointerEvent *event, bool click);
|
||||
void handleMoveEvent(QPointerEvent *event, QPointF point);
|
||||
|
||||
bool m_pressed{false};
|
||||
bool m_hovered{false};
|
||||
bool m_dragging{false};
|
||||
bool m_pressAndHeld{false};
|
||||
Qt::CursorShape m_cursorShape{Qt::ArrowCursor};
|
||||
|
||||
QTimer *m_pressAndHoldTimer{nullptr};
|
||||
};
|
||||
|
||||
QML_DECLARE_TYPE(DelegateTouchArea)
|
||||
883
containments/homescreens/folio/dragstate.cpp
Normal file
883
containments/homescreens/folio/dragstate.cpp
Normal file
|
|
@ -0,0 +1,883 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "dragstate.h"
|
||||
#include "favouritesmodel.h"
|
||||
#include "pagelistmodel.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
#include <algorithm>
|
||||
|
||||
// TODO don't hardcode, use page widths
|
||||
const int PAGE_CHANGE_THRESHOLD = 30;
|
||||
|
||||
const QString DEFAULT_FOLDER_NAME = i18n("Folder");
|
||||
|
||||
DelegateDragPosition::DelegateDragPosition(QObject *parent)
|
||||
: QObject{parent}
|
||||
{
|
||||
}
|
||||
|
||||
DelegateDragPosition::~DelegateDragPosition() = default;
|
||||
|
||||
void DelegateDragPosition::copyFrom(DelegateDragPosition *position)
|
||||
{
|
||||
setPage(position->page());
|
||||
setPageRow(position->pageRow());
|
||||
setPageColumn(position->pageColumn());
|
||||
setFavouritesPosition(position->favouritesPosition());
|
||||
setFolderPosition(position->folderPosition());
|
||||
setFolder(position->folder());
|
||||
setLocation(position->location());
|
||||
}
|
||||
|
||||
DelegateDragPosition::Location DelegateDragPosition::location() const
|
||||
{
|
||||
return m_location;
|
||||
}
|
||||
|
||||
void DelegateDragPosition::setLocation(Location location)
|
||||
{
|
||||
if (m_location != location) {
|
||||
m_location = location;
|
||||
Q_EMIT locationChanged();
|
||||
}
|
||||
}
|
||||
|
||||
int DelegateDragPosition::page() const
|
||||
{
|
||||
return m_page;
|
||||
}
|
||||
|
||||
void DelegateDragPosition::setPage(int page)
|
||||
{
|
||||
if (m_page != page) {
|
||||
m_page = page;
|
||||
Q_EMIT pageChanged();
|
||||
}
|
||||
}
|
||||
|
||||
int DelegateDragPosition::pageRow() const
|
||||
{
|
||||
return m_pageRow;
|
||||
}
|
||||
|
||||
void DelegateDragPosition::setPageRow(int pageRow)
|
||||
{
|
||||
if (m_pageRow != pageRow) {
|
||||
m_pageRow = pageRow;
|
||||
Q_EMIT pageRowChanged();
|
||||
}
|
||||
}
|
||||
|
||||
int DelegateDragPosition::pageColumn() const
|
||||
{
|
||||
return m_pageColumn;
|
||||
}
|
||||
|
||||
void DelegateDragPosition::setPageColumn(int pageColumn)
|
||||
{
|
||||
if (m_pageColumn != pageColumn) {
|
||||
m_pageColumn = pageColumn;
|
||||
Q_EMIT pageColumnChanged();
|
||||
}
|
||||
}
|
||||
|
||||
int DelegateDragPosition::favouritesPosition() const
|
||||
{
|
||||
return m_favouritesPosition;
|
||||
}
|
||||
|
||||
void DelegateDragPosition::setFavouritesPosition(int favouritesPosition)
|
||||
{
|
||||
if (m_favouritesPosition != favouritesPosition) {
|
||||
m_favouritesPosition = favouritesPosition;
|
||||
Q_EMIT favouritesPositionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
int DelegateDragPosition::folderPosition() const
|
||||
{
|
||||
return m_folderPosition;
|
||||
}
|
||||
|
||||
void DelegateDragPosition::setFolderPosition(int folderPosition)
|
||||
{
|
||||
if (m_folderPosition != folderPosition) {
|
||||
m_folderPosition = folderPosition;
|
||||
Q_EMIT folderPositionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
FolioApplicationFolder *DelegateDragPosition::folder() const
|
||||
{
|
||||
return m_folder;
|
||||
}
|
||||
|
||||
void DelegateDragPosition::setFolder(FolioApplicationFolder *folder)
|
||||
{
|
||||
if (m_folder != folder) {
|
||||
m_folder = folder;
|
||||
Q_EMIT folderChanged();
|
||||
}
|
||||
}
|
||||
|
||||
DragState::DragState(HomeScreenState *state, QObject *parent)
|
||||
: QObject{parent}
|
||||
, m_changePageTimer{new QTimer{this}}
|
||||
, m_openFolderTimer{new QTimer{this}}
|
||||
, m_leaveFolderTimer{new QTimer{this}}
|
||||
, m_changeFolderPageTimer{new QTimer{this}}
|
||||
, m_folderInsertBetweenTimer{new QTimer{this}}
|
||||
, m_favouritesInsertBetweenTimer{new QTimer{this}}
|
||||
, m_candidateDropPosition{new DelegateDragPosition{this}}
|
||||
, m_startPosition{new DelegateDragPosition{this}}
|
||||
, m_state{state}
|
||||
{
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 500 ms hold before page timer changes
|
||||
m_changePageTimer->setInterval(500);
|
||||
m_changePageTimer->setSingleShot(true);
|
||||
|
||||
m_openFolderTimer->setInterval(1000);
|
||||
m_openFolderTimer->setSingleShot(true);
|
||||
|
||||
m_leaveFolderTimer->setInterval(500);
|
||||
m_leaveFolderTimer->setSingleShot(true);
|
||||
|
||||
m_changeFolderPageTimer->setInterval(500);
|
||||
m_changeFolderPageTimer->setSingleShot(true);
|
||||
|
||||
m_folderInsertBetweenTimer->setInterval(250);
|
||||
m_folderInsertBetweenTimer->setSingleShot(true);
|
||||
|
||||
m_favouritesInsertBetweenTimer->setInterval(250);
|
||||
m_favouritesInsertBetweenTimer->setSingleShot(true);
|
||||
|
||||
connect(m_changePageTimer, &QTimer::timeout, this, &DragState::onChangePageTimerFinished);
|
||||
connect(m_openFolderTimer, &QTimer::timeout, this, &DragState::onOpenFolderTimerFinished);
|
||||
connect(m_leaveFolderTimer, &QTimer::timeout, this, &DragState::onLeaveFolderTimerFinished);
|
||||
connect(m_changeFolderPageTimer, &QTimer::timeout, this, &DragState::onChangeFolderPageTimerFinished);
|
||||
connect(m_folderInsertBetweenTimer, &QTimer::timeout, this, &DragState::onFolderInsertBetweenTimerFinished);
|
||||
connect(m_favouritesInsertBetweenTimer, &QTimer::timeout, this, &DragState::onFavouritesInsertBetweenTimerFinished);
|
||||
|
||||
connect(m_state, &HomeScreenState::delegateDragFromPageStarted, this, &DragState::onDelegateDragFromPageStarted);
|
||||
connect(m_state, &HomeScreenState::delegateDragFromAppDrawerStarted, this, &DragState::onDelegateDragFromAppDrawerStarted);
|
||||
connect(m_state, &HomeScreenState::delegateDragFromFavouritesStarted, this, &DragState::onDelegateDragFromFavouritesStarted);
|
||||
connect(m_state, &HomeScreenState::delegateDragFromFolderStarted, this, &DragState::onDelegateDragFromFolderStarted);
|
||||
connect(m_state, &HomeScreenState::swipeStateChanged, this, [this]() {
|
||||
if (HomeScreenState::self()->swipeState() == HomeScreenState::DraggingDelegate) {
|
||||
onDelegateDraggingStarted();
|
||||
}
|
||||
});
|
||||
connect(m_state, &HomeScreenState::delegateDragEnded, this, &DragState::onDelegateDropped);
|
||||
|
||||
connect(m_state, &HomeScreenState::pageNumChanged, this, [this]() {
|
||||
m_candidateDropPosition->setPageRow(m_state->currentPage());
|
||||
});
|
||||
|
||||
connect(m_state, &HomeScreenState::delegateDragXChanged, this, &DragState::onDelegateDragPositionChanged);
|
||||
connect(m_state, &HomeScreenState::delegateDragYChanged, this, &DragState::onDelegateDragPositionChanged);
|
||||
|
||||
connect(m_state, &HomeScreenState::leftCurrentFolder, this, &DragState::onLeaveCurrentFolder);
|
||||
}
|
||||
|
||||
DelegateDragPosition *DragState::candidateDropPosition() const
|
||||
{
|
||||
return m_candidateDropPosition;
|
||||
}
|
||||
|
||||
DelegateDragPosition *DragState::startPosition() const
|
||||
{
|
||||
return m_startPosition;
|
||||
}
|
||||
|
||||
FolioDelegate *DragState::dropDelegate() const
|
||||
{
|
||||
return m_dropDelegate;
|
||||
}
|
||||
|
||||
void DragState::setDropDelegate(FolioDelegate *dropDelegate)
|
||||
{
|
||||
m_dropDelegate = dropDelegate;
|
||||
Q_EMIT dropDelegateChanged();
|
||||
}
|
||||
|
||||
void DragState::onDelegateDragPositionChanged()
|
||||
{
|
||||
if (!m_state) {
|
||||
return;
|
||||
}
|
||||
|
||||
// we want to update the candidate drop position variable in this function!
|
||||
|
||||
qreal x = getDraggedDelegateX();
|
||||
qreal y = getDraggedDelegateY();
|
||||
|
||||
bool inFolder = m_state->viewState() == HomeScreenState::FolderView;
|
||||
bool inFavouritesArea = !inFolder;
|
||||
|
||||
// the favourites bar can be in different locations, so account for each
|
||||
switch (m_state->favouritesBarLocation()) {
|
||||
case HomeScreenState::Bottom:
|
||||
inFavouritesArea = inFavouritesArea && y > m_state->pageHeight();
|
||||
break;
|
||||
case HomeScreenState::Left:
|
||||
inFavouritesArea = inFavouritesArea && x < m_state->viewWidth() - m_state->pageHeight();
|
||||
break;
|
||||
case HomeScreenState::Right:
|
||||
inFavouritesArea = inFavouritesArea && x > m_state->pageWidth();
|
||||
break;
|
||||
}
|
||||
|
||||
// stop the favourites insertion timer if the delegate has moved out
|
||||
if (!inFavouritesArea) {
|
||||
m_favouritesInsertBetweenTimer->stop();
|
||||
}
|
||||
|
||||
if (inFavouritesArea || inFolder) {
|
||||
m_openFolderTimer->stop();
|
||||
}
|
||||
|
||||
if (m_state->viewState() == HomeScreenState::FolderView) {
|
||||
// if we are in a folder
|
||||
onDelegateDragPositionOverFolderViewChanged();
|
||||
|
||||
} else if (inFavouritesArea) {
|
||||
// we are in the favourites bar area
|
||||
onDelegateDragPositionOverFavouritesChanged();
|
||||
} else {
|
||||
// we are in the homescreen pages area
|
||||
onDelegateDragPositionOverPageViewChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void DragState::onDelegateDragPositionOverFolderViewChanged()
|
||||
{
|
||||
// if the drag position changes while in the folder view
|
||||
qreal x = getDraggedDelegateX();
|
||||
qreal y = getDraggedDelegateY();
|
||||
|
||||
auto *folder = m_state->currentFolder();
|
||||
if (!folder) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if the drop position is not in the folder, but outside (going to page view)
|
||||
if (folder->isDropPositionOutside(x, y)) {
|
||||
if (!m_leaveFolderTimer->isActive()) {
|
||||
m_leaveFolderTimer->start();
|
||||
}
|
||||
return;
|
||||
} else if (m_leaveFolderTimer->isActive()) {
|
||||
// cancel timer if we are back in the folder
|
||||
m_leaveFolderTimer->stop();
|
||||
}
|
||||
|
||||
// the potential folder index that can be dropped at
|
||||
int dropIndex = folder->dropInsertPosition(m_state->currentFolderPage(), x, y);
|
||||
|
||||
// if the delegate has moved to another position, cancel the insert timer
|
||||
if (dropIndex != m_folderInsertBetweenIndex) {
|
||||
m_folderInsertBetweenTimer->stop();
|
||||
}
|
||||
|
||||
// start the insertion timer (so that the user has time to move the delegate away)
|
||||
if (!m_folderInsertBetweenTimer->isActive()) {
|
||||
m_folderInsertBetweenTimer->start();
|
||||
m_folderInsertBetweenIndex = dropIndex;
|
||||
}
|
||||
|
||||
const qreal leftPagePosition = folder->applications()->leftMarginFromScreenEdge();
|
||||
const qreal rightPagePosition = m_state->viewWidth() - leftPagePosition;
|
||||
|
||||
// determine if the delegate is near the edge of a page (to switch pages).
|
||||
// -> start the change page timer if we at the page edge.
|
||||
if (x <= leftPagePosition + PAGE_CHANGE_THRESHOLD || x >= rightPagePosition - PAGE_CHANGE_THRESHOLD) {
|
||||
if (!m_changeFolderPageTimer->isActive()) {
|
||||
m_changeFolderPageTimer->start();
|
||||
}
|
||||
} else {
|
||||
if (m_changeFolderPageTimer->isActive()) {
|
||||
m_changeFolderPageTimer->stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DragState::onDelegateDragPositionOverFavouritesChanged()
|
||||
{
|
||||
// the drag position changed while over the favourites strip
|
||||
|
||||
qreal x = getDraggedDelegateX();
|
||||
qreal y = getDraggedDelegateY();
|
||||
int dropIndex = FavouritesModel::self()->dropInsertPosition(x, y);
|
||||
|
||||
// if the drop position changed, cancel the open folder timer
|
||||
if (m_candidateDropPosition->location() != DelegateDragPosition::Favourites || m_candidateDropPosition->favouritesPosition() != dropIndex) {
|
||||
if (m_openFolderTimer->isActive()) {
|
||||
m_openFolderTimer->stop();
|
||||
}
|
||||
}
|
||||
|
||||
// if the delegate has moved to another position, cancel the insert timer
|
||||
if (dropIndex != m_favouritesInsertBetweenIndex) {
|
||||
m_favouritesInsertBetweenTimer->stop();
|
||||
}
|
||||
|
||||
if (FavouritesModel::self()->dropPositionIsEdge(x, y)) {
|
||||
// if we need to make space for the delegate
|
||||
|
||||
// start the insertion timer (so that the user has time to move the delegate away)
|
||||
if (!m_favouritesInsertBetweenTimer->isActive()) {
|
||||
m_favouritesInsertBetweenTimer->start();
|
||||
m_favouritesInsertBetweenIndex = dropIndex;
|
||||
}
|
||||
} else {
|
||||
// if we are hovering over the center of a folder or app
|
||||
|
||||
// delete ghost entry if there is one
|
||||
int ghostEntryPosition = FavouritesModel::self()->getGhostEntryPosition();
|
||||
if (ghostEntryPosition != -1 && ghostEntryPosition != dropIndex) {
|
||||
if (dropIndex > ghostEntryPosition) {
|
||||
// correct index if deleting the ghost will change the index
|
||||
dropIndex--;
|
||||
}
|
||||
FavouritesModel::self()->deleteGhostEntry();
|
||||
}
|
||||
|
||||
// update the current drop position
|
||||
m_candidateDropPosition->setFavouritesPosition(dropIndex);
|
||||
m_candidateDropPosition->setLocation(DelegateDragPosition::Favourites);
|
||||
|
||||
// start folder open timer if hovering over a folder
|
||||
// get delegate being hovered over
|
||||
FolioDelegate *delegate = FavouritesModel::self()->getEntryAt(dropIndex);
|
||||
|
||||
// check delegate is a folder and the drop delegate is an app
|
||||
if (delegate && delegate->type() == FolioDelegate::Folder && m_dropDelegate && m_dropDelegate->type() == FolioDelegate::Application) {
|
||||
if (!m_openFolderTimer->isActive()) {
|
||||
m_openFolderTimer->start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DragState::onDelegateDragPositionOverPageViewChanged()
|
||||
{
|
||||
// the drag position changed while over the homescreen pages strip
|
||||
|
||||
qreal x = getDraggedDelegateX();
|
||||
qreal y = getDraggedDelegateY();
|
||||
int page = m_state->currentPage();
|
||||
|
||||
// calculate the row and column the delegate is over
|
||||
qreal pageHorizontalMargin = (m_state->pageWidth() - m_state->pageContentWidth()) / 2;
|
||||
qreal pageVerticalMargin = (m_state->pageHeight() - m_state->pageContentHeight()) / 2;
|
||||
|
||||
int row = (y - pageVerticalMargin) / m_state->pageCellHeight();
|
||||
int column = (x - pageHorizontalMargin) / m_state->pageCellWidth();
|
||||
|
||||
// ensure it's in bounds
|
||||
row = std::max(0, std::min(m_state->pageRows() - 1, row));
|
||||
column = std::max(0, std::min(m_state->pageColumns() - 1, column));
|
||||
|
||||
// if the drop position changed, cancel the open folder timer
|
||||
if (m_candidateDropPosition->location() != DelegateDragPosition::Pages || m_candidateDropPosition->pageRow() != row
|
||||
|| m_candidateDropPosition->pageColumn() != column) {
|
||||
if (m_openFolderTimer->isActive()) {
|
||||
m_openFolderTimer->stop();
|
||||
}
|
||||
}
|
||||
|
||||
// update the current drop position
|
||||
m_candidateDropPosition->setPage(page);
|
||||
m_candidateDropPosition->setPageRow(row);
|
||||
m_candidateDropPosition->setPageColumn(column);
|
||||
m_candidateDropPosition->setLocation(DelegateDragPosition::Pages);
|
||||
|
||||
// start folder open timer if hovering over a folder
|
||||
PageModel *pageModel = PageListModel::self()->getPage(page);
|
||||
if (pageModel) {
|
||||
// get delegate being hovered over
|
||||
FolioDelegate *delegate = pageModel->getDelegate(row, column);
|
||||
|
||||
// check delegate is a folder and the drop delegate is an app
|
||||
if (delegate && delegate->type() == FolioDelegate::Folder && m_dropDelegate && m_dropDelegate->type() == FolioDelegate::Application) {
|
||||
if (!m_openFolderTimer->isActive()) {
|
||||
m_openFolderTimer->start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int leftPagePosition = 0;
|
||||
const int rightPagePosition = m_state->pageWidth();
|
||||
|
||||
// determine if the delegate is near the edge of a page (to switch pages).
|
||||
// -> start the change page timer if we at the page edge.
|
||||
if (qAbs(leftPagePosition - x) <= PAGE_CHANGE_THRESHOLD || qAbs(rightPagePosition - x) <= PAGE_CHANGE_THRESHOLD) {
|
||||
if (!m_changePageTimer->isActive()) {
|
||||
m_changePageTimer->start();
|
||||
}
|
||||
} else {
|
||||
if (m_changePageTimer->isActive()) {
|
||||
m_changePageTimer->stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DragState::onDelegateDraggingStarted()
|
||||
{
|
||||
// remove the delegate from the model
|
||||
// NOTE: we only delete here (and not from the event trigger, ex. onDelegateDragFromPageStarted)
|
||||
// because the actual dragging only started when this is called
|
||||
deleteStartPositionDelegate();
|
||||
}
|
||||
|
||||
void DragState::onDelegateDragFromPageStarted(int page, int row, int column)
|
||||
{
|
||||
// fetch delegate at start position
|
||||
PageModel *pageModel = PageListModel::self()->getPage(page);
|
||||
if (pageModel) {
|
||||
setDropDelegate(pageModel->getDelegate(row, column));
|
||||
} else {
|
||||
setDropDelegate(nullptr);
|
||||
}
|
||||
|
||||
// set start location
|
||||
m_startPosition->setPage(page);
|
||||
m_startPosition->setPageRow(row);
|
||||
m_startPosition->setPageColumn(column);
|
||||
m_startPosition->setLocation(DelegateDragPosition::Pages);
|
||||
}
|
||||
|
||||
void DragState::onDelegateDragFromFavouritesStarted(int position)
|
||||
{
|
||||
// fetch delegate at start position
|
||||
setDropDelegate(FavouritesModel::self()->getEntryAt(position));
|
||||
|
||||
// set start location
|
||||
m_startPosition->setFavouritesPosition(position);
|
||||
m_startPosition->setLocation(DelegateDragPosition::Favourites);
|
||||
}
|
||||
|
||||
void DragState::onDelegateDragFromAppDrawerStarted(QString storageId)
|
||||
{
|
||||
// fetch delegate at start position
|
||||
if (KService::Ptr service = KService::serviceByStorageId(storageId)) {
|
||||
FolioApplication *app = new FolioApplication{this, service};
|
||||
setDropDelegate(new FolioDelegate{app, this});
|
||||
} else {
|
||||
setDropDelegate(nullptr);
|
||||
}
|
||||
|
||||
// set start location
|
||||
m_startPosition->setLocation(DelegateDragPosition::AppDrawer);
|
||||
}
|
||||
|
||||
void DragState::onDelegateDragFromFolderStarted(FolioApplicationFolder *folder, int position)
|
||||
{
|
||||
// fetch delegate at start position
|
||||
setDropDelegate(folder->applications()->getDelegate(position));
|
||||
|
||||
// set start location
|
||||
m_startPosition->setFolder(folder);
|
||||
m_startPosition->setFolderPosition(position);
|
||||
m_startPosition->setLocation(DelegateDragPosition::Folder);
|
||||
}
|
||||
|
||||
void DragState::onDelegateDropped()
|
||||
{
|
||||
if (!m_dropDelegate) {
|
||||
return;
|
||||
}
|
||||
|
||||
// add dropped delegate
|
||||
createDropPositionDelegate();
|
||||
|
||||
// delete empty pages at the end if they exist
|
||||
// (it can be created if user drags app to new page, but doesn't place it there)
|
||||
while (PageListModel::self()->isLastPageEmpty() && PageListModel::self()->rowCount() > 1) {
|
||||
PageListModel::self()->removePage(PageListModel::self()->rowCount() - 1);
|
||||
}
|
||||
|
||||
// clear ghost position if there is one
|
||||
FavouritesModel::self()->deleteGhostEntry();
|
||||
|
||||
// reset timers
|
||||
m_folderInsertBetweenTimer->stop();
|
||||
m_changeFolderPageTimer->stop();
|
||||
m_leaveFolderTimer->stop();
|
||||
m_changePageTimer->stop();
|
||||
m_favouritesInsertBetweenTimer->stop();
|
||||
|
||||
// emit signal
|
||||
Q_EMIT delegateDroppedAndPlaced();
|
||||
}
|
||||
|
||||
void DragState::onLeaveCurrentFolder()
|
||||
{
|
||||
if (!m_state) {
|
||||
return;
|
||||
}
|
||||
|
||||
// reset timers
|
||||
m_folderInsertBetweenTimer->stop();
|
||||
m_changeFolderPageTimer->stop();
|
||||
m_leaveFolderTimer->stop();
|
||||
|
||||
if (m_candidateDropPosition->location() == DelegateDragPosition::Folder && m_candidateDropPosition->folder()) {
|
||||
// clear ghost entry
|
||||
m_candidateDropPosition->folder()->applications()->deleteGhostEntry();
|
||||
}
|
||||
}
|
||||
|
||||
void DragState::onChangePageTimerFinished()
|
||||
{
|
||||
if (!m_state || (m_state->swipeState() != HomeScreenState::DraggingDelegate)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int leftPagePosition = 0;
|
||||
const int rightPagePosition = m_state->pageWidth();
|
||||
|
||||
qreal x = getDraggedDelegateX();
|
||||
if (qAbs(leftPagePosition - x) <= PAGE_CHANGE_THRESHOLD) {
|
||||
// if we are at the left edge, go left
|
||||
int page = m_state->currentPage() - 1;
|
||||
if (page >= 0) {
|
||||
m_state->goToPage(page);
|
||||
}
|
||||
|
||||
} else if (qAbs(rightPagePosition - x) <= PAGE_CHANGE_THRESHOLD) {
|
||||
// if we are at the right edge, go right
|
||||
int page = m_state->currentPage() + 1;
|
||||
|
||||
// if we are at the right-most page, try to create a new one if the current page isn't empty
|
||||
if (page == PageListModel::self()->rowCount() && !PageListModel::self()->isLastPageEmpty()) {
|
||||
PageListModel::self()->addPageAtEnd();
|
||||
}
|
||||
|
||||
// go to page if it exists
|
||||
if (page < PageListModel::self()->rowCount()) {
|
||||
m_state->goToPage(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DragState::onOpenFolderTimerFinished()
|
||||
{
|
||||
if (!m_state || m_state->swipeState() != HomeScreenState::DraggingDelegate || m_state->viewState() != HomeScreenState::PageView
|
||||
|| (m_candidateDropPosition->location() != DelegateDragPosition::Pages && m_candidateDropPosition->location() != DelegateDragPosition::Favourites)) {
|
||||
return;
|
||||
}
|
||||
|
||||
FolioApplicationFolder *folder = nullptr;
|
||||
QPointF screenPosition;
|
||||
|
||||
switch (m_candidateDropPosition->location()) {
|
||||
case DelegateDragPosition::Pages: {
|
||||
// get current page
|
||||
PageModel *page = PageListModel::self()->getPage(m_candidateDropPosition->page());
|
||||
if (!page) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get delegate being hovered over
|
||||
FolioDelegate *delegate = page->getDelegate(m_candidateDropPosition->pageRow(), m_candidateDropPosition->pageColumn());
|
||||
if (!delegate || delegate->type() != FolioDelegate::Folder) {
|
||||
return;
|
||||
}
|
||||
|
||||
folder = delegate->folder();
|
||||
screenPosition = HomeScreenState::self()->getPageDelegateScreenPosition(m_candidateDropPosition->page(),
|
||||
m_candidateDropPosition->pageRow(),
|
||||
m_candidateDropPosition->pageColumn());
|
||||
break;
|
||||
}
|
||||
case DelegateDragPosition::Favourites: {
|
||||
// get delegate being hovered over in favourites bar
|
||||
FolioDelegate *delegate = FavouritesModel::self()->getEntryAt(m_candidateDropPosition->favouritesPosition());
|
||||
if (!delegate || delegate->type() != FolioDelegate::Folder) {
|
||||
return;
|
||||
}
|
||||
|
||||
folder = delegate->folder();
|
||||
screenPosition = HomeScreenState::self()->getFavouritesDelegateScreenPosition(m_candidateDropPosition->favouritesPosition());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// open the folder
|
||||
m_state->openFolder(screenPosition.x(), screenPosition.y(), folder);
|
||||
}
|
||||
|
||||
void DragState::onLeaveFolderTimerFinished()
|
||||
{
|
||||
if (!m_state || (m_state->swipeState() != HomeScreenState::DraggingDelegate) || !m_state->currentFolder()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if the drag position is outside of the folder
|
||||
if (m_state->currentFolder()->isDropPositionOutside(getDraggedDelegateX(), getDraggedDelegateY())) {
|
||||
m_state->closeFolder();
|
||||
}
|
||||
}
|
||||
|
||||
void DragState::onChangeFolderPageTimerFinished()
|
||||
{
|
||||
if (!m_state || (m_state->swipeState() != HomeScreenState::DraggingDelegate) || !m_state->currentFolder()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto *folder = m_state->currentFolder();
|
||||
|
||||
// check if the drag position is outside of the folder
|
||||
if (folder->isDropPositionOutside(getDraggedDelegateX(), getDraggedDelegateY())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const qreal leftPagePosition = folder->applications()->leftMarginFromScreenEdge();
|
||||
const qreal rightPagePosition = m_state->viewWidth() - leftPagePosition;
|
||||
|
||||
qreal x = getDraggedDelegateX();
|
||||
if (x <= leftPagePosition + PAGE_CHANGE_THRESHOLD) {
|
||||
// if we are at the left edge, go left
|
||||
int page = m_state->currentFolderPage() - 1;
|
||||
if (page >= 0) {
|
||||
m_state->goToFolderPage(page);
|
||||
}
|
||||
|
||||
} else if (x >= rightPagePosition - PAGE_CHANGE_THRESHOLD) {
|
||||
// if we are at the right edge, go right
|
||||
int page = m_state->currentFolderPage() + 1;
|
||||
|
||||
// TODO!!!!
|
||||
// if we are at the right-most page, try to create a new one if the current page isn't empty
|
||||
// if (page == folder->applications()->rowCount() && !PageListModel::self()->isLastPageEmpty()) {
|
||||
// PageListModel::self()->addPageAtEnd();
|
||||
// }
|
||||
|
||||
// go to page if it exists
|
||||
if (page < folder->applications()->numTotalPages()) {
|
||||
m_state->goToFolderPage(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DragState::onFolderInsertBetweenTimerFinished()
|
||||
{
|
||||
if (!m_state || (m_state->swipeState() != HomeScreenState::DraggingDelegate) || !m_state->currentFolder()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto *folder = m_state->currentFolder();
|
||||
|
||||
// update the candidate drop position
|
||||
m_candidateDropPosition->setFolder(folder);
|
||||
m_candidateDropPosition->setFolderPosition(m_folderInsertBetweenIndex);
|
||||
m_candidateDropPosition->setLocation(DelegateDragPosition::Folder);
|
||||
|
||||
// insert it at this position, shifting existing apps to the side
|
||||
// TODO the ghost entry may shift the m_folderInsertBetweenIndex, we should update??
|
||||
folder->applications()->setGhostEntry(m_folderInsertBetweenIndex);
|
||||
}
|
||||
|
||||
void DragState::onFavouritesInsertBetweenTimerFinished()
|
||||
{
|
||||
// update the candidate drop position
|
||||
m_candidateDropPosition->setFavouritesPosition(m_favouritesInsertBetweenIndex);
|
||||
m_candidateDropPosition->setLocation(DelegateDragPosition::Favourites);
|
||||
|
||||
// insert it at this position, shifting existing apps to the side
|
||||
FavouritesModel::self()->setGhostEntry(m_favouritesInsertBetweenIndex);
|
||||
}
|
||||
|
||||
void DragState::deleteStartPositionDelegate()
|
||||
{
|
||||
// delete the delegate at the start position
|
||||
switch (m_startPosition->location()) {
|
||||
case DelegateDragPosition::Pages: {
|
||||
PageModel *page = PageListModel::self()->getPage(m_startPosition->page());
|
||||
if (page) {
|
||||
page->removeDelegate(m_startPosition->pageRow(), m_startPosition->pageColumn());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DelegateDragPosition::Favourites:
|
||||
FavouritesModel::self()->removeEntry(m_startPosition->favouritesPosition());
|
||||
break;
|
||||
case DelegateDragPosition::Folder:
|
||||
m_startPosition->folder()->removeDelegate(m_startPosition->folderPosition());
|
||||
break;
|
||||
case DelegateDragPosition::AppDrawer:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DragState::createDropPositionDelegate()
|
||||
{
|
||||
if (!m_dropDelegate) {
|
||||
return;
|
||||
}
|
||||
|
||||
// creates the delegate at the drop position
|
||||
switch (m_candidateDropPosition->location()) {
|
||||
case DelegateDragPosition::Pages: {
|
||||
// locate the page we are dropping on
|
||||
PageModel *page = PageListModel::self()->getPage(m_candidateDropPosition->page());
|
||||
if (!page) {
|
||||
break;
|
||||
}
|
||||
|
||||
int row = m_candidateDropPosition->pageRow();
|
||||
int column = m_candidateDropPosition->pageColumn();
|
||||
|
||||
// delegate to add
|
||||
FolioPageDelegate *delegate = new FolioPageDelegate{row, column, m_dropDelegate, page};
|
||||
|
||||
// delegate that exists at the drop position
|
||||
FolioPageDelegate *existingDelegate = page->getDelegate(row, column);
|
||||
|
||||
// if a delegate already exists at the spot, check if we can insert/create a folder
|
||||
if (existingDelegate) {
|
||||
if (delegate->type() == FolioDelegate::Application) {
|
||||
if (existingDelegate->type() == FolioDelegate::Folder) {
|
||||
// add the app to the existing folder
|
||||
|
||||
auto existingFolder = existingDelegate->folder();
|
||||
existingFolder->addDelegate(delegate, existingFolder->applications()->rowCount());
|
||||
|
||||
break;
|
||||
} else if (existingDelegate->type() == FolioDelegate::Application && !isStartPositionEqualDropPosition()) {
|
||||
// create a folder from the two apps
|
||||
|
||||
FolioApplicationFolder *folder = new FolioApplicationFolder(this, DEFAULT_FOLDER_NAME);
|
||||
folder->addDelegate(delegate, 0);
|
||||
folder->addDelegate(existingDelegate, 0);
|
||||
FolioPageDelegate *folderDelegate = new FolioPageDelegate{row, column, folder, this};
|
||||
|
||||
page->removeDelegate(row, column);
|
||||
page->addDelegate(folderDelegate);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// default behavior for folders or dropping an app at an empty spot
|
||||
|
||||
bool added = page->addDelegate(delegate);
|
||||
|
||||
// if we couldn't add the delegate, try again but at the start position (return to start)
|
||||
if (!added && !isStartPositionEqualDropPosition()) {
|
||||
m_candidateDropPosition->copyFrom(m_startPosition);
|
||||
createDropPositionDelegate();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DelegateDragPosition::Favourites: {
|
||||
// delegate that exists at the drop position
|
||||
FolioDelegate *existingDelegate = FavouritesModel::self()->getEntryAt(m_candidateDropPosition->favouritesPosition());
|
||||
|
||||
// if a delegate already exists at the spot, check if we can insert/create a folder
|
||||
if (existingDelegate) {
|
||||
if (m_dropDelegate->type() == FolioDelegate::Application) {
|
||||
if (existingDelegate->type() == FolioDelegate::Folder) {
|
||||
// add the app to the existing folder
|
||||
|
||||
auto existingFolder = existingDelegate->folder();
|
||||
existingFolder->addDelegate(m_dropDelegate, existingFolder->applications()->rowCount());
|
||||
|
||||
break;
|
||||
} else if (existingDelegate->type() == FolioDelegate::Application && !isStartPositionEqualDropPosition()) {
|
||||
// create a folder from the two apps
|
||||
|
||||
FolioApplicationFolder *folder = new FolioApplicationFolder(this, DEFAULT_FOLDER_NAME);
|
||||
folder->addDelegate(m_dropDelegate, 0);
|
||||
folder->addDelegate(existingDelegate, 0);
|
||||
FolioDelegate *folderDelegate = new FolioDelegate{folder, this};
|
||||
|
||||
FavouritesModel::self()->removeEntry(m_candidateDropPosition->favouritesPosition());
|
||||
FavouritesModel::self()->addEntry(m_candidateDropPosition->favouritesPosition(), folderDelegate);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise, just add the delegate at this position
|
||||
|
||||
bool added = FavouritesModel::self()->addEntry(m_candidateDropPosition->favouritesPosition(), m_dropDelegate);
|
||||
|
||||
// if we couldn't add the delegate, try again but at the start position
|
||||
if (!added && !isStartPositionEqualDropPosition()) {
|
||||
m_candidateDropPosition->copyFrom(m_startPosition);
|
||||
createDropPositionDelegate();
|
||||
}
|
||||
|
||||
// correct position when we delete from an entry earlier in the favourites
|
||||
if (added) {
|
||||
if (m_startPosition->location() == DelegateDragPosition::Favourites
|
||||
&& m_startPosition->favouritesPosition() > m_candidateDropPosition->favouritesPosition()) {
|
||||
m_startPosition->setFavouritesPosition(m_startPosition->favouritesPosition() - 1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DelegateDragPosition::Folder: {
|
||||
auto *folder = m_candidateDropPosition->folder();
|
||||
if (!folder) {
|
||||
return;
|
||||
}
|
||||
|
||||
// only support dropping apps into folders
|
||||
if (m_dropDelegate->type() != FolioDelegate::Application) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool added = folder->addDelegate(m_dropDelegate, m_candidateDropPosition->folderPosition());
|
||||
|
||||
// if we couldn't add the delegate, try again but at the start position
|
||||
if (!added && !isStartPositionEqualDropPosition()) {
|
||||
m_candidateDropPosition->copyFrom(m_startPosition);
|
||||
createDropPositionDelegate();
|
||||
}
|
||||
|
||||
if (added) {
|
||||
folder->applications()->deleteGhostEntry();
|
||||
|
||||
// TODO correct m_startPosition?
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DelegateDragPosition::AppDrawer:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool DragState::isStartPositionEqualDropPosition()
|
||||
{
|
||||
return m_startPosition->location() == m_candidateDropPosition->location() && m_startPosition->page() == m_candidateDropPosition->page()
|
||||
&& m_startPosition->pageRow() == m_candidateDropPosition->pageRow() && m_startPosition->pageColumn() == m_candidateDropPosition->pageColumn()
|
||||
&& m_startPosition->favouritesPosition() == m_candidateDropPosition->favouritesPosition()
|
||||
&& m_startPosition->folder() == m_candidateDropPosition->folder() && m_startPosition->folderPosition() == m_candidateDropPosition->folderPosition();
|
||||
}
|
||||
|
||||
qreal DragState::getDraggedDelegateX()
|
||||
{
|
||||
// adjust to get the position of the center of the delegate
|
||||
return m_state->delegateDragX() + m_state->pageCellWidth() / 2;
|
||||
}
|
||||
|
||||
qreal DragState::getDraggedDelegateY()
|
||||
{
|
||||
// adjust to get the position of the center of the delegate
|
||||
return m_state->delegateDragY() + m_state->pageCellHeight() / 2;
|
||||
}
|
||||
156
containments/homescreens/folio/dragstate.h
Normal file
156
containments/homescreens/folio/dragstate.h
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
#include "folioapplicationfolder.h"
|
||||
#include "foliodelegate.h"
|
||||
#include "homescreenstate.h"
|
||||
|
||||
class HomeScreenState;
|
||||
|
||||
class DelegateDragPosition : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(DelegateDragPosition::Location location READ location NOTIFY locationChanged)
|
||||
Q_PROPERTY(int page READ page NOTIFY pageChanged)
|
||||
Q_PROPERTY(int pageRow READ pageRow NOTIFY pageRowChanged)
|
||||
Q_PROPERTY(int pageColumn READ pageColumn NOTIFY pageColumnChanged)
|
||||
Q_PROPERTY(int favouritesPosition READ favouritesPosition NOTIFY favouritesPositionChanged)
|
||||
Q_PROPERTY(int folderPosition READ folderPosition NOTIFY folderPositionChanged)
|
||||
Q_PROPERTY(FolioApplicationFolder *folder READ folder NOTIFY folderChanged)
|
||||
|
||||
public:
|
||||
enum Location { Pages, Favourites, AppDrawer, Folder };
|
||||
Q_ENUM(Location)
|
||||
|
||||
DelegateDragPosition(QObject *parent = nullptr);
|
||||
~DelegateDragPosition();
|
||||
|
||||
void copyFrom(DelegateDragPosition *position);
|
||||
|
||||
Location location() const;
|
||||
void setLocation(Location location);
|
||||
|
||||
int page() const;
|
||||
void setPage(int page);
|
||||
|
||||
int pageRow() const;
|
||||
void setPageRow(int pageRow);
|
||||
|
||||
int pageColumn() const;
|
||||
void setPageColumn(int pageColumn);
|
||||
|
||||
int favouritesPosition() const;
|
||||
void setFavouritesPosition(int favouritesPosition);
|
||||
|
||||
int folderPosition() const;
|
||||
void setFolderPosition(int folderPosition);
|
||||
|
||||
// TODO: what if the folder becomes invalid? we need to clear it
|
||||
FolioApplicationFolder *folder() const;
|
||||
void setFolder(FolioApplicationFolder *folder);
|
||||
|
||||
Q_SIGNALS:
|
||||
void locationChanged();
|
||||
void pageChanged();
|
||||
void pageRowChanged();
|
||||
void pageColumnChanged();
|
||||
void favouritesPositionChanged();
|
||||
void folderPositionChanged();
|
||||
void folderChanged();
|
||||
|
||||
private:
|
||||
Location m_location{DelegateDragPosition::Pages};
|
||||
int m_page{0};
|
||||
int m_pageRow{0};
|
||||
int m_pageColumn{0};
|
||||
int m_favouritesPosition{0};
|
||||
int m_folderPosition{0};
|
||||
FolioApplicationFolder *m_folder{nullptr};
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(DelegateDragPosition);
|
||||
|
||||
class DragState : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(DelegateDragPosition *candidateDropPosition READ candidateDropPosition CONSTANT)
|
||||
Q_PROPERTY(DelegateDragPosition *startPosition READ startPosition CONSTANT)
|
||||
Q_PROPERTY(FolioDelegate *dropDelegate READ dropDelegate NOTIFY dropDelegateChanged)
|
||||
|
||||
public:
|
||||
DragState(HomeScreenState *state = nullptr, QObject *parent = nullptr);
|
||||
|
||||
DelegateDragPosition *candidateDropPosition() const;
|
||||
DelegateDragPosition *startPosition() const;
|
||||
FolioDelegate *dropDelegate() const;
|
||||
void setDropDelegate(FolioDelegate *dropDelegate);
|
||||
|
||||
Q_SIGNALS:
|
||||
void dropDelegateChanged();
|
||||
void delegateDroppedAndPlaced();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onDelegateDragPositionChanged();
|
||||
void onDelegateDragPositionOverFolderViewChanged();
|
||||
void onDelegateDragPositionOverFavouritesChanged();
|
||||
void onDelegateDragPositionOverPageViewChanged();
|
||||
|
||||
void onDelegateDraggingStarted();
|
||||
void onDelegateDragFromPageStarted(int page, int row, int column);
|
||||
void onDelegateDragFromFavouritesStarted(int position);
|
||||
void onDelegateDragFromAppDrawerStarted(QString storageId);
|
||||
void onDelegateDragFromFolderStarted(FolioApplicationFolder *folder, int position);
|
||||
void onDelegateDropped();
|
||||
|
||||
void onLeaveCurrentFolder();
|
||||
|
||||
void onChangePageTimerFinished();
|
||||
void onOpenFolderTimerFinished();
|
||||
void onLeaveFolderTimerFinished();
|
||||
void onChangeFolderPageTimerFinished();
|
||||
void onFolderInsertBetweenTimerFinished();
|
||||
void onFavouritesInsertBetweenTimerFinished();
|
||||
|
||||
private:
|
||||
// deletes the delegate at m_startPosition
|
||||
void deleteStartPositionDelegate();
|
||||
|
||||
// deletes the delegate at m_candidateDropPosition
|
||||
void createDropPositionDelegate();
|
||||
|
||||
// whether m_startPosition = m_candidateDropPosition
|
||||
bool isStartPositionEqualDropPosition();
|
||||
|
||||
// we need to adjust so that the coord is in the center of the delegate
|
||||
qreal getDraggedDelegateX();
|
||||
qreal getDraggedDelegateY();
|
||||
|
||||
QTimer *m_changePageTimer{nullptr};
|
||||
QTimer *m_openFolderTimer{nullptr};
|
||||
QTimer *m_leaveFolderTimer{nullptr};
|
||||
QTimer *m_changeFolderPageTimer{nullptr};
|
||||
|
||||
// inserting between apps in a folder
|
||||
QTimer *m_folderInsertBetweenTimer{nullptr};
|
||||
int m_folderInsertBetweenIndex{0};
|
||||
|
||||
// inserting between apps in the favourites strip
|
||||
QTimer *m_favouritesInsertBetweenTimer{nullptr};
|
||||
int m_favouritesInsertBetweenIndex{0};
|
||||
|
||||
// the delegate that is being dropped
|
||||
FolioDelegate *m_dropDelegate{nullptr};
|
||||
|
||||
// where we are hovering over, potentially to drop the delegate
|
||||
DelegateDragPosition *const m_candidateDropPosition{nullptr};
|
||||
|
||||
// this is the original start position of the drag
|
||||
DelegateDragPosition *const m_startPosition{nullptr};
|
||||
|
||||
HomeScreenState *m_state{nullptr};
|
||||
};
|
||||
403
containments/homescreens/folio/favouritesmodel.cpp
Normal file
403
containments/homescreens/folio/favouritesmodel.cpp
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "favouritesmodel.h"
|
||||
#include "homescreenstate.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QModelIndex>
|
||||
#include <QProcess>
|
||||
#include <QQuickWindow>
|
||||
|
||||
#include <KApplicationTrader>
|
||||
#include <KConfigGroup>
|
||||
#include <KIO/ApplicationLauncherJob>
|
||||
#include <KNotificationJobUiDelegate>
|
||||
#include <KService>
|
||||
#include <KSharedConfig>
|
||||
#include <KSycoca>
|
||||
|
||||
FavouritesModel *FavouritesModel::self()
|
||||
{
|
||||
static FavouritesModel *inst = new FavouritesModel();
|
||||
return inst;
|
||||
}
|
||||
|
||||
FavouritesModel::FavouritesModel(QObject *parent)
|
||||
: QAbstractListModel{parent}
|
||||
{
|
||||
connect(HomeScreenState::self(), &HomeScreenState::pageWidthChanged, this, [this]() {
|
||||
evaluateDelegatePositions(true);
|
||||
});
|
||||
connect(HomeScreenState::self(), &HomeScreenState::pageHeightChanged, this, [this]() {
|
||||
evaluateDelegatePositions(true);
|
||||
});
|
||||
connect(HomeScreenState::self(), &HomeScreenState::pageCellWidthChanged, this, [this]() {
|
||||
evaluateDelegatePositions(true);
|
||||
});
|
||||
connect(HomeScreenState::self(), &HomeScreenState::pageCellHeightChanged, this, [this]() {
|
||||
evaluateDelegatePositions(true);
|
||||
});
|
||||
connect(HomeScreenState::self(), &HomeScreenState::favouritesBarLocationChanged, this, [this]() {
|
||||
evaluateDelegatePositions(true);
|
||||
});
|
||||
connect(HomeScreenState::self(), &HomeScreenState::pageOrientationChanged, this, [this]() {
|
||||
evaluateDelegatePositions(true);
|
||||
});
|
||||
}
|
||||
|
||||
int FavouritesModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return m_delegates.count();
|
||||
}
|
||||
|
||||
QVariant FavouritesModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= m_delegates.size()) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
switch (role) {
|
||||
case DelegateRole:
|
||||
return QVariant::fromValue(m_delegates.at(index.row()).delegate);
|
||||
case XPositionRole:
|
||||
return QVariant::fromValue(m_delegates.at(index.row()).xPosition);
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> FavouritesModel::roleNames() const
|
||||
{
|
||||
return {{DelegateRole, "delegate"}, {XPositionRole, "xPosition"}};
|
||||
}
|
||||
|
||||
void FavouritesModel::removeEntry(int row)
|
||||
{
|
||||
if (row < 0 || row >= m_delegates.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
// HACK: do not deleteLater(), because the delegate might still be used somewhere else
|
||||
// m_delegates[row].delegate->deleteLater();
|
||||
m_delegates.removeAt(row);
|
||||
endRemoveRows();
|
||||
|
||||
evaluateDelegatePositions();
|
||||
|
||||
save();
|
||||
}
|
||||
|
||||
void FavouritesModel::moveEntry(int fromRow, int toRow)
|
||||
{
|
||||
if (fromRow < 0 || toRow < 0 || fromRow >= m_delegates.size() || toRow >= m_delegates.size() || fromRow == toRow) {
|
||||
return;
|
||||
}
|
||||
if (toRow > fromRow) {
|
||||
++toRow;
|
||||
}
|
||||
|
||||
beginMoveRows(QModelIndex(), fromRow, fromRow, QModelIndex(), toRow);
|
||||
if (toRow > fromRow) {
|
||||
auto delegate = m_delegates.at(fromRow);
|
||||
m_delegates.insert(toRow, delegate);
|
||||
m_delegates.takeAt(fromRow);
|
||||
|
||||
} else {
|
||||
auto delegate = m_delegates.takeAt(fromRow);
|
||||
m_delegates.insert(toRow, delegate);
|
||||
}
|
||||
endMoveRows();
|
||||
|
||||
evaluateDelegatePositions();
|
||||
|
||||
save();
|
||||
}
|
||||
|
||||
bool FavouritesModel::addEntry(int row, FolioDelegate *delegate)
|
||||
{
|
||||
if (!delegate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (row < 0 || row > m_delegates.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (row == m_delegates.size()) {
|
||||
beginInsertRows(QModelIndex(), row, row);
|
||||
m_delegates.append({delegate, 0});
|
||||
evaluateDelegatePositions(false);
|
||||
endInsertRows();
|
||||
} else if (m_delegates[row].delegate->type() == FolioDelegate::None) {
|
||||
replaceGhostEntry(delegate);
|
||||
} else {
|
||||
beginInsertRows(QModelIndex(), row, row);
|
||||
m_delegates.insert(row, {delegate, 0});
|
||||
evaluateDelegatePositions(false);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
evaluateDelegatePositions();
|
||||
|
||||
save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FolioDelegate *FavouritesModel::getEntryAt(int row)
|
||||
{
|
||||
if (row < 0 || row >= m_delegates.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return m_delegates[row].delegate;
|
||||
}
|
||||
|
||||
int FavouritesModel::getGhostEntryPosition()
|
||||
{
|
||||
for (int i = 0; i < m_delegates.size(); i++) {
|
||||
if (m_delegates[i].delegate->type() == FolioDelegate::None) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void FavouritesModel::setGhostEntry(int row)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
// check if a ghost entry already exists, then swap them
|
||||
for (int i = 0; i < m_delegates.size(); i++) {
|
||||
if (m_delegates[i].delegate->type() == FolioDelegate::None) {
|
||||
found = true;
|
||||
|
||||
if (row != i) {
|
||||
moveEntry(i, row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if it doesn't, add a new empty delegate
|
||||
if (!found) {
|
||||
FolioDelegate *ghost = new FolioDelegate{this};
|
||||
addEntry(row, ghost);
|
||||
}
|
||||
}
|
||||
|
||||
void FavouritesModel::replaceGhostEntry(FolioDelegate *delegate)
|
||||
{
|
||||
for (int i = 0; i < m_delegates.size(); i++) {
|
||||
if (m_delegates[i].delegate->type() == FolioDelegate::None) {
|
||||
m_delegates[i].delegate = delegate;
|
||||
|
||||
Q_EMIT dataChanged(createIndex(i, 0), createIndex(i, 0), {DelegateRole});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FavouritesModel::deleteGhostEntry()
|
||||
{
|
||||
for (int i = 0; i < m_delegates.size(); i++) {
|
||||
if (m_delegates[i].delegate->type() == FolioDelegate::None) {
|
||||
removeEntry(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FavouritesModel::save()
|
||||
{
|
||||
if (!m_applet) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray arr;
|
||||
for (int i = 0; i < m_delegates.size(); i++) {
|
||||
FolioDelegate *delegate = m_delegates[i].delegate;
|
||||
|
||||
// if this delegate is empty, ignore it
|
||||
if (!delegate || delegate->type() == FolioDelegate::None) {
|
||||
continue;
|
||||
}
|
||||
|
||||
arr.append(delegate->toJson());
|
||||
}
|
||||
QByteArray data = QJsonDocument(arr).toJson(QJsonDocument::Compact);
|
||||
|
||||
m_applet->config().writeEntry("Favourites", QString::fromStdString(data.toStdString()));
|
||||
Q_EMIT m_applet->configNeedsSaving();
|
||||
}
|
||||
|
||||
void FavouritesModel::load()
|
||||
{
|
||||
if (!m_applet) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(m_applet->config().readEntry("Favourites", "{}").toUtf8());
|
||||
|
||||
beginResetModel();
|
||||
|
||||
m_delegates.clear();
|
||||
|
||||
for (QJsonValueRef r : doc.array()) {
|
||||
QJsonObject obj = r.toObject();
|
||||
FolioDelegate *delegate = FolioDelegate::fromJson(obj, this);
|
||||
|
||||
if (delegate) {
|
||||
if (delegate->type() == FolioDelegate::Folder) {
|
||||
connect(delegate->folder(), &FolioApplicationFolder::saveRequested, this, &FavouritesModel::save);
|
||||
}
|
||||
|
||||
m_delegates.append({delegate, 0});
|
||||
}
|
||||
}
|
||||
|
||||
evaluateDelegatePositions(false);
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void FavouritesModel::setApplet(Plasma::Applet *applet)
|
||||
{
|
||||
m_applet = applet;
|
||||
}
|
||||
|
||||
bool FavouritesModel::dropPositionIsEdge(qreal x, qreal y) const
|
||||
{
|
||||
qreal startPosition = getDelegateRowStartPos();
|
||||
bool isLocationBottom = HomeScreenState::self()->favouritesBarLocation() == HomeScreenState::Bottom;
|
||||
qreal cellLength = isLocationBottom ? HomeScreenState::self()->pageCellWidth() : HomeScreenState::self()->pageCellHeight();
|
||||
|
||||
qreal pos = isLocationBottom ? x : y;
|
||||
|
||||
if (pos < startPosition) {
|
||||
return true;
|
||||
}
|
||||
|
||||
qreal currentPos = startPosition;
|
||||
|
||||
for (int i = 0; i < m_delegates.size(); i++) {
|
||||
// if it is within the centre 70% of a delegate, it is not at an edge
|
||||
if (pos >= (currentPos + cellLength * 0.15) && pos <= (currentPos + cellLength * 0.85)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
currentPos += cellLength;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int FavouritesModel::dropInsertPosition(qreal x, qreal y) const
|
||||
{
|
||||
qreal startPosition = getDelegateRowStartPos();
|
||||
bool isLocationBottom = HomeScreenState::self()->favouritesBarLocation() == HomeScreenState::Bottom;
|
||||
qreal cellLength = isLocationBottom ? HomeScreenState::self()->pageCellWidth() : HomeScreenState::self()->pageCellHeight();
|
||||
|
||||
qreal pos = isLocationBottom ? x : y;
|
||||
|
||||
if (pos < startPosition) {
|
||||
return adjustIndex(0);
|
||||
}
|
||||
|
||||
qreal currentPos = startPosition;
|
||||
for (int i = 0; i < m_delegates.size(); i++) {
|
||||
if (pos < currentPos + cellLength * 0.85) {
|
||||
return adjustIndex(i);
|
||||
} else if (pos < currentPos + cellLength) {
|
||||
return adjustIndex(i + 1);
|
||||
}
|
||||
|
||||
currentPos += cellLength;
|
||||
}
|
||||
return adjustIndex(m_delegates.size());
|
||||
}
|
||||
|
||||
QPointF FavouritesModel::getDelegateScreenPosition(int position) const
|
||||
{
|
||||
position = adjustIndex(position);
|
||||
|
||||
qreal screenHeight = HomeScreenState::self()->viewHeight();
|
||||
qreal screenWidth = HomeScreenState::self()->viewWidth();
|
||||
qreal pageHeight = HomeScreenState::self()->pageHeight();
|
||||
qreal pageWidth = HomeScreenState::self()->pageWidth();
|
||||
qreal screenTopPadding = HomeScreenState::self()->viewTopPadding();
|
||||
qreal screenBottomPadding = HomeScreenState::self()->viewBottomPadding();
|
||||
qreal screenLeftPadding = HomeScreenState::self()->viewLeftPadding();
|
||||
qreal screenRightPadding = HomeScreenState::self()->viewRightPadding();
|
||||
qreal cellHeight = HomeScreenState::self()->pageCellHeight();
|
||||
qreal cellWidth = HomeScreenState::self()->pageCellWidth();
|
||||
|
||||
qreal startPosition = getDelegateRowStartPos();
|
||||
|
||||
switch (HomeScreenState::self()->favouritesBarLocation()) {
|
||||
case HomeScreenState::Bottom: {
|
||||
qreal favouritesHeight = screenHeight - pageHeight - screenBottomPadding - screenTopPadding;
|
||||
qreal x = screenLeftPadding + startPosition + cellWidth * position;
|
||||
qreal y = screenTopPadding + pageHeight + (favouritesHeight / 2) - (cellHeight / 2);
|
||||
return {x, y};
|
||||
}
|
||||
case HomeScreenState::Left: {
|
||||
qreal favouritesWidth = screenWidth - screenLeftPadding - pageWidth - screenRightPadding;
|
||||
qreal x = screenLeftPadding + (favouritesWidth / 2) - (cellWidth / 2);
|
||||
qreal y = startPosition + cellHeight * position;
|
||||
return {x, y};
|
||||
}
|
||||
case HomeScreenState::Right: {
|
||||
qreal favouritesWidth = screenWidth - screenLeftPadding - pageWidth - screenRightPadding;
|
||||
qreal x = screenLeftPadding + pageWidth + (favouritesWidth / 2) - (cellWidth / 2);
|
||||
qreal y = startPosition + cellHeight * position;
|
||||
return {x, y};
|
||||
}
|
||||
}
|
||||
return {0, 0};
|
||||
}
|
||||
|
||||
void FavouritesModel::evaluateDelegatePositions(bool emitSignal)
|
||||
{
|
||||
bool isLocationBottom = HomeScreenState::self()->favouritesBarLocation() == HomeScreenState::Bottom;
|
||||
qreal cellLength = isLocationBottom ? HomeScreenState::self()->pageCellWidth() : HomeScreenState::self()->pageCellHeight();
|
||||
qreal startPosition = getDelegateRowStartPos();
|
||||
qreal currentPos = startPosition;
|
||||
|
||||
for (int i = 0; i < m_delegates.size(); ++i) {
|
||||
m_delegates[adjustIndex(i)].xPosition = qRound(currentPos);
|
||||
currentPos += cellLength;
|
||||
}
|
||||
|
||||
if (emitSignal) {
|
||||
Q_EMIT dataChanged(createIndex(0, 0), createIndex(m_delegates.size() - 1, 0), {XPositionRole});
|
||||
}
|
||||
}
|
||||
|
||||
qreal FavouritesModel::getDelegateRowStartPos() const
|
||||
{
|
||||
const int length = m_delegates.size();
|
||||
const bool isLocationBottom = HomeScreenState::self()->favouritesBarLocation() == HomeScreenState::Bottom;
|
||||
const qreal cellLength = isLocationBottom ? HomeScreenState::self()->pageCellWidth() : HomeScreenState::self()->pageCellHeight();
|
||||
const qreal pageLength = isLocationBottom ? HomeScreenState::self()->pageWidth() : HomeScreenState::self()->pageHeight();
|
||||
|
||||
const qreal topMargin = HomeScreenState::self()->viewTopPadding();
|
||||
const qreal leftMargin = HomeScreenState::self()->viewLeftPadding();
|
||||
const qreal panelOffset = isLocationBottom ? leftMargin : topMargin;
|
||||
|
||||
return (pageLength / 2) - (((qreal)length) / 2) * cellLength + panelOffset;
|
||||
}
|
||||
|
||||
int FavouritesModel::adjustIndex(int index) const
|
||||
{
|
||||
if (HomeScreenState::self()->favouritesBarLocation() == HomeScreenState::Bottom
|
||||
|| HomeScreenState::self()->favouritesBarLocation() == HomeScreenState::Left) {
|
||||
return index;
|
||||
} else {
|
||||
// if it's on the right side of the screen, we flip the order of the delegates
|
||||
return qMax(0, m_delegates.size() - index - 1);
|
||||
}
|
||||
}
|
||||
80
containments/homescreens/folio/favouritesmodel.h
Normal file
80
containments/homescreens/folio/favouritesmodel.h
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QQuickItem>
|
||||
#include <QSet>
|
||||
|
||||
#include <Plasma/Applet>
|
||||
|
||||
#include "foliodelegate.h"
|
||||
|
||||
struct FavouritesDelegate {
|
||||
FolioDelegate *delegate;
|
||||
qreal xPosition;
|
||||
};
|
||||
|
||||
class FavouritesModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
DelegateRole = Qt::UserRole + 1,
|
||||
XPositionRole,
|
||||
};
|
||||
|
||||
FavouritesModel(QObject *parent = nullptr);
|
||||
static FavouritesModel *self();
|
||||
|
||||
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;
|
||||
|
||||
Q_INVOKABLE void removeEntry(int row);
|
||||
void moveEntry(int fromRow, int toRow);
|
||||
bool addEntry(int row, FolioDelegate *delegate);
|
||||
FolioDelegate *getEntryAt(int row);
|
||||
|
||||
// for use with drag and drop, as the delegate is dragged around
|
||||
// ghost - fake delegate exists at an index, so a gap is created
|
||||
// invisible - existing delegate looks like it doesn't exist
|
||||
int getGhostEntryPosition();
|
||||
void setGhostEntry(int row);
|
||||
void replaceGhostEntry(FolioDelegate *delegate);
|
||||
void deleteGhostEntry();
|
||||
|
||||
// whether the position given is in between 2 delegates, or at the edge.
|
||||
// this would return false if dropping should place the delegate into a folder/create a folder.
|
||||
bool dropPositionIsEdge(qreal x, qreal y) const;
|
||||
|
||||
// the index that dropping at the position given would place the delegate at.
|
||||
int dropInsertPosition(qreal x, qreal y) const;
|
||||
|
||||
QPointF getDelegateScreenPosition(int position) const;
|
||||
|
||||
Q_INVOKABLE void load();
|
||||
|
||||
void setApplet(Plasma::Applet *applet);
|
||||
|
||||
private:
|
||||
void save();
|
||||
void evaluateDelegatePositions(bool emitSignal = true);
|
||||
|
||||
// get the x (or y) position where delegates start being placed
|
||||
qreal getDelegateRowStartPos() const;
|
||||
|
||||
// adjusts the index in relation to the page orientation
|
||||
// this is so that we only have to calculate positions assuming one orientation
|
||||
int adjustIndex(int index) const;
|
||||
|
||||
QList<FavouritesDelegate> m_delegates;
|
||||
|
||||
Plasma::Applet *m_applet{nullptr};
|
||||
};
|
||||
139
containments/homescreens/folio/folioapplication.cpp
Normal file
139
containments/homescreens/folio/folioapplication.cpp
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
|
||||
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "folioapplication.h"
|
||||
#include "windowlistener.h"
|
||||
|
||||
#include <QQuickWindow>
|
||||
|
||||
#include <KNotificationJobUiDelegate>
|
||||
|
||||
FolioApplication::FolioApplication(QObject *parent, KService::Ptr service)
|
||||
: QObject{parent}
|
||||
, m_running{false}
|
||||
, m_name{service->name()}
|
||||
, m_icon{service->icon()}
|
||||
, m_storageId{service->storageId()}
|
||||
{
|
||||
auto windows = WindowListener::instance()->windowsFromStorageId(m_storageId);
|
||||
if (windows.empty()) {
|
||||
m_window = nullptr;
|
||||
} else {
|
||||
m_window = windows[0];
|
||||
}
|
||||
|
||||
connect(WindowListener::instance(), &WindowListener::windowChanged, this, [this](QString storageId) {
|
||||
if (storageId == m_storageId) {
|
||||
auto windows = WindowListener::instance()->windowsFromStorageId(m_storageId);
|
||||
if (windows.empty()) {
|
||||
setWindow(nullptr);
|
||||
} else {
|
||||
setWindow(windows[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
FolioApplication *FolioApplication::fromJson(QJsonObject &obj, QObject *parent)
|
||||
{
|
||||
QString storageId = obj[QStringLiteral("storageId")].toString();
|
||||
if (KService::Ptr service = KService::serviceByStorageId(storageId)) {
|
||||
return new FolioApplication(parent, service);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QJsonObject FolioApplication::toJson()
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj[QStringLiteral("type")] = "application";
|
||||
obj[QStringLiteral("storageId")] = m_storageId;
|
||||
return obj;
|
||||
}
|
||||
|
||||
bool FolioApplication::running() const
|
||||
{
|
||||
return m_window != nullptr;
|
||||
}
|
||||
|
||||
QString FolioApplication::name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
QString FolioApplication::icon() const
|
||||
{
|
||||
return m_icon;
|
||||
}
|
||||
|
||||
QString FolioApplication::storageId() const
|
||||
{
|
||||
return m_storageId;
|
||||
}
|
||||
|
||||
KWayland::Client::PlasmaWindow *FolioApplication::window() const
|
||||
{
|
||||
return m_window;
|
||||
}
|
||||
|
||||
void FolioApplication::setName(QString &name)
|
||||
{
|
||||
m_name = name;
|
||||
Q_EMIT nameChanged();
|
||||
}
|
||||
|
||||
void FolioApplication::setIcon(QString &icon)
|
||||
{
|
||||
m_icon = icon;
|
||||
Q_EMIT iconChanged();
|
||||
}
|
||||
|
||||
void FolioApplication::setStorageId(QString &storageId)
|
||||
{
|
||||
m_storageId = storageId;
|
||||
Q_EMIT storageIdChanged();
|
||||
}
|
||||
|
||||
void FolioApplication::setWindow(KWayland::Client::PlasmaWindow *window)
|
||||
{
|
||||
m_window = window;
|
||||
Q_EMIT windowChanged();
|
||||
}
|
||||
|
||||
void FolioApplication::setMinimizedDelegate(QQuickItem *delegate)
|
||||
{
|
||||
QWindow *delegateWindow = delegate->window();
|
||||
if (!delegateWindow) {
|
||||
return;
|
||||
}
|
||||
if (!m_window) {
|
||||
return;
|
||||
}
|
||||
|
||||
KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(delegateWindow);
|
||||
if (!surface) {
|
||||
return;
|
||||
}
|
||||
|
||||
QRect rect = delegate->mapRectToScene(QRectF(0, 0, delegate->width(), delegate->height())).toRect();
|
||||
m_window->setMinimizedGeometry(surface, rect);
|
||||
}
|
||||
|
||||
void FolioApplication::unsetMinimizedDelegate(QQuickItem *delegate)
|
||||
{
|
||||
QWindow *delegateWindow = delegate->window();
|
||||
if (!delegateWindow) {
|
||||
return;
|
||||
}
|
||||
if (!m_window) {
|
||||
return;
|
||||
}
|
||||
|
||||
KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(delegateWindow);
|
||||
if (!surface) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_window->unsetMinimizedGeometry(surface);
|
||||
}
|
||||
62
containments/homescreens/folio/folioapplication.h
Normal file
62
containments/homescreens/folio/folioapplication.h
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QObject>
|
||||
#include <QQuickItem>
|
||||
#include <QString>
|
||||
|
||||
#include <KIO/ApplicationLauncherJob>
|
||||
#include <KService>
|
||||
|
||||
#include <KWayland/Client/connection_thread.h>
|
||||
#include <KWayland/Client/plasmawindowmanagement.h>
|
||||
#include <KWayland/Client/registry.h>
|
||||
#include <KWayland/Client/surface.h>
|
||||
|
||||
/**
|
||||
* @short Object that represents an application.
|
||||
*/
|
||||
class FolioApplication : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool running READ running NOTIFY windowChanged)
|
||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
||||
Q_PROPERTY(QString icon READ icon NOTIFY iconChanged)
|
||||
Q_PROPERTY(QString storageId READ storageId NOTIFY storageIdChanged)
|
||||
|
||||
public:
|
||||
FolioApplication(QObject *parent = nullptr, KService::Ptr service = QExplicitlySharedDataPointer<KService>{nullptr});
|
||||
|
||||
static FolioApplication *fromJson(QJsonObject &obj, QObject *parent); // may return nullptr
|
||||
QJsonObject toJson();
|
||||
|
||||
bool running() const;
|
||||
QString name() const;
|
||||
QString icon() const;
|
||||
QString storageId() const;
|
||||
KWayland::Client::PlasmaWindow *window() const;
|
||||
|
||||
void setName(QString &name);
|
||||
void setIcon(QString &icon);
|
||||
void setStorageId(QString &storageId);
|
||||
void setWindow(KWayland::Client::PlasmaWindow *window);
|
||||
|
||||
Q_INVOKABLE void setMinimizedDelegate(QQuickItem *delegate);
|
||||
Q_INVOKABLE void unsetMinimizedDelegate(QQuickItem *delegate);
|
||||
|
||||
Q_SIGNALS:
|
||||
void nameChanged();
|
||||
void iconChanged();
|
||||
void storageIdChanged();
|
||||
void windowChanged();
|
||||
|
||||
private:
|
||||
bool m_running;
|
||||
QString m_name;
|
||||
QString m_icon;
|
||||
QString m_storageId;
|
||||
KWayland::Client::PlasmaWindow *m_window{nullptr};
|
||||
};
|
||||
468
containments/homescreens/folio/folioapplicationfolder.cpp
Normal file
468
containments/homescreens/folio/folioapplicationfolder.cpp
Normal file
|
|
@ -0,0 +1,468 @@
|
|||
// SPDX-FileCopyrightText: 2022-2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "folioapplicationfolder.h"
|
||||
#include "homescreenstate.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <algorithm>
|
||||
|
||||
FolioApplicationFolder::FolioApplicationFolder(QObject *parent, QString name)
|
||||
: QObject{parent}
|
||||
, m_name{name}
|
||||
, m_applicationFolderModel{new ApplicationFolderModel{this}}
|
||||
{
|
||||
}
|
||||
|
||||
FolioApplicationFolder *FolioApplicationFolder::fromJson(QJsonObject &obj, QObject *parent)
|
||||
{
|
||||
QString name = obj[QStringLiteral("name")].toString();
|
||||
QList<FolioApplication *> apps;
|
||||
for (auto storageId : obj[QStringLiteral("apps")].toArray()) {
|
||||
if (KService::Ptr service = KService::serviceByStorageId(storageId.toString())) {
|
||||
apps.append(new FolioApplication(parent, service));
|
||||
}
|
||||
}
|
||||
|
||||
FolioApplicationFolder *folder = new FolioApplicationFolder(parent, name);
|
||||
folder->setApplications(apps);
|
||||
return folder;
|
||||
}
|
||||
|
||||
QJsonObject FolioApplicationFolder::toJson()
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj[QStringLiteral("type")] = "folder";
|
||||
obj[QStringLiteral("name")] = m_name;
|
||||
|
||||
QJsonArray arr;
|
||||
for (auto delegate : m_delegates) {
|
||||
if (delegate.delegate->type() != FolioDelegate::Application) {
|
||||
continue;
|
||||
}
|
||||
arr.append(QJsonValue::fromVariant(delegate.delegate->application()->storageId()));
|
||||
}
|
||||
|
||||
obj[QStringLiteral("apps")] = arr;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
QString FolioApplicationFolder::name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void FolioApplicationFolder::setName(QString &name)
|
||||
{
|
||||
m_name = name;
|
||||
Q_EMIT nameChanged();
|
||||
Q_EMIT saveRequested();
|
||||
}
|
||||
|
||||
QList<FolioApplication *> FolioApplicationFolder::appPreviews()
|
||||
{
|
||||
QList<FolioApplication *> previews;
|
||||
// we give a maximum of 4 icons
|
||||
for (int i = 0; i < std::min<int>(m_delegates.size(), 4); ++i) {
|
||||
if (!m_delegates[i].delegate->application()) {
|
||||
continue;
|
||||
}
|
||||
previews.push_back(m_delegates[i].delegate->application());
|
||||
}
|
||||
return previews;
|
||||
}
|
||||
|
||||
ApplicationFolderModel *FolioApplicationFolder::applications()
|
||||
{
|
||||
return m_applicationFolderModel;
|
||||
}
|
||||
|
||||
void FolioApplicationFolder::setApplications(QList<FolioApplication *> applications)
|
||||
{
|
||||
if (m_applicationFolderModel) {
|
||||
m_applicationFolderModel->deleteLater();
|
||||
}
|
||||
|
||||
m_delegates.clear();
|
||||
for (auto *app : applications) {
|
||||
m_delegates.append({new FolioDelegate{app, this}, 0, 0});
|
||||
}
|
||||
m_applicationFolderModel = new ApplicationFolderModel{this};
|
||||
m_applicationFolderModel->evaluateDelegatePositions();
|
||||
|
||||
Q_EMIT applicationsChanged();
|
||||
Q_EMIT applicationsReset();
|
||||
Q_EMIT saveRequested();
|
||||
}
|
||||
|
||||
void FolioApplicationFolder::moveEntry(int fromRow, int toRow)
|
||||
{
|
||||
m_applicationFolderModel->moveEntry(fromRow, toRow);
|
||||
}
|
||||
|
||||
bool FolioApplicationFolder::addDelegate(FolioDelegate *delegate, int row)
|
||||
{
|
||||
return m_applicationFolderModel->addDelegate(delegate, row);
|
||||
}
|
||||
|
||||
void FolioApplicationFolder::removeDelegate(int row)
|
||||
{
|
||||
m_applicationFolderModel->removeDelegate(row);
|
||||
}
|
||||
|
||||
int FolioApplicationFolder::dropInsertPosition(int page, qreal x, qreal y)
|
||||
{
|
||||
return m_applicationFolderModel->dropInsertPosition(page, x, y);
|
||||
}
|
||||
|
||||
bool FolioApplicationFolder::isDropPositionOutside(qreal x, qreal y)
|
||||
{
|
||||
return m_applicationFolderModel->isDropPositionOutside(x, y);
|
||||
}
|
||||
|
||||
ApplicationFolderModel::ApplicationFolderModel(FolioApplicationFolder *folder)
|
||||
: QAbstractListModel{folder}
|
||||
, m_folder{folder}
|
||||
{
|
||||
connect(HomeScreenState::self(), &HomeScreenState::folderPageWidthChanged, this, [this]() {
|
||||
evaluateDelegatePositions();
|
||||
});
|
||||
connect(HomeScreenState::self(), &HomeScreenState::folderPageHeightChanged, this, [this]() {
|
||||
evaluateDelegatePositions();
|
||||
});
|
||||
connect(HomeScreenState::self(), &HomeScreenState::folderPageContentWidthChanged, this, [this]() {
|
||||
evaluateDelegatePositions();
|
||||
});
|
||||
connect(HomeScreenState::self(), &HomeScreenState::folderPageContentHeightChanged, this, [this]() {
|
||||
evaluateDelegatePositions();
|
||||
});
|
||||
connect(HomeScreenState::self(), &HomeScreenState::viewWidthChanged, this, [this]() {
|
||||
evaluateDelegatePositions();
|
||||
});
|
||||
connect(HomeScreenState::self(), &HomeScreenState::viewHeightChanged, this, [this]() {
|
||||
evaluateDelegatePositions();
|
||||
});
|
||||
connect(HomeScreenState::self(), &HomeScreenState::pageCellWidthChanged, this, [this]() {
|
||||
evaluateDelegatePositions();
|
||||
});
|
||||
connect(HomeScreenState::self(), &HomeScreenState::pageCellHeightChanged, this, [this]() {
|
||||
evaluateDelegatePositions();
|
||||
});
|
||||
}
|
||||
|
||||
int ApplicationFolderModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return m_folder->m_delegates.size();
|
||||
}
|
||||
|
||||
QVariant ApplicationFolderModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
switch (role) {
|
||||
case DelegateRole:
|
||||
return QVariant::fromValue(m_folder->m_delegates.at(index.row()).delegate);
|
||||
case XPositionRole:
|
||||
return QVariant::fromValue(m_folder->m_delegates.at(index.row()).xPosition);
|
||||
case YPositionRole:
|
||||
return QVariant::fromValue(m_folder->m_delegates.at(index.row()).yPosition);
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ApplicationFolderModel::roleNames() const
|
||||
{
|
||||
return {{DelegateRole, "delegate"}, {XPositionRole, "xPosition"}, {YPositionRole, "yPosition"}};
|
||||
}
|
||||
|
||||
FolioDelegate *ApplicationFolderModel::getDelegate(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_folder->m_delegates.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return m_folder->m_delegates[index].delegate;
|
||||
}
|
||||
|
||||
void ApplicationFolderModel::moveEntry(int fromRow, int toRow)
|
||||
{
|
||||
if (fromRow < 0 || toRow < 0 || fromRow >= m_folder->m_delegates.size() || toRow >= m_folder->m_delegates.size() || fromRow == toRow) {
|
||||
return;
|
||||
}
|
||||
if (toRow > fromRow) {
|
||||
++toRow;
|
||||
}
|
||||
|
||||
beginMoveRows(QModelIndex(), fromRow, fromRow, QModelIndex(), toRow);
|
||||
if (toRow > fromRow) {
|
||||
auto delegate = m_folder->m_delegates.at(fromRow);
|
||||
m_folder->m_delegates.insert(toRow, delegate);
|
||||
m_folder->m_delegates.takeAt(fromRow);
|
||||
} else {
|
||||
auto delegate = m_folder->m_delegates.takeAt(fromRow);
|
||||
m_folder->m_delegates.insert(toRow, delegate);
|
||||
}
|
||||
endMoveRows();
|
||||
|
||||
evaluateDelegatePositions();
|
||||
|
||||
Q_EMIT m_folder->applicationsChanged();
|
||||
Q_EMIT m_folder->saveRequested();
|
||||
}
|
||||
|
||||
bool ApplicationFolderModel::addDelegate(FolioDelegate *delegate, int index)
|
||||
{
|
||||
if (index < 0 || index > m_folder->m_delegates.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!delegate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index == m_folder->m_delegates.size()) {
|
||||
beginInsertRows(QModelIndex(), index, index);
|
||||
m_folder->m_delegates.append({delegate, 0, 0});
|
||||
evaluateDelegatePositions(false);
|
||||
endInsertRows();
|
||||
} else if (m_folder->m_delegates[index].delegate->type() == FolioDelegate::None) {
|
||||
replaceGhostEntry(delegate);
|
||||
} else {
|
||||
beginInsertRows(QModelIndex(), index, index);
|
||||
m_folder->m_delegates.insert(index, {delegate, 0, 0});
|
||||
evaluateDelegatePositions(false);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
evaluateDelegatePositions();
|
||||
|
||||
Q_EMIT m_folder->applicationsChanged();
|
||||
Q_EMIT m_folder->saveRequested();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ApplicationFolderModel::removeDelegate(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_folder->m_delegates.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginRemoveRows(QModelIndex(), index, index);
|
||||
// HACK: do not deleteLater(), because the delegate might still be used somewhere else
|
||||
// m_folder->m_delegates[index].app->deleteLater();
|
||||
m_folder->m_delegates.removeAt(index);
|
||||
endRemoveRows();
|
||||
|
||||
evaluateDelegatePositions();
|
||||
|
||||
Q_EMIT m_folder->applicationsChanged();
|
||||
Q_EMIT m_folder->saveRequested();
|
||||
}
|
||||
|
||||
QPointF ApplicationFolderModel::getDelegatePosition(int index)
|
||||
{
|
||||
auto delegate = m_folder->m_delegates[index];
|
||||
return {delegate.xPosition, delegate.yPosition};
|
||||
}
|
||||
|
||||
int ApplicationFolderModel::getGhostEntryPosition()
|
||||
{
|
||||
for (int i = 0; i < m_folder->m_delegates.size(); i++) {
|
||||
if (m_folder->m_delegates[i].delegate->type() == FolioDelegate::None) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ApplicationFolderModel::setGhostEntry(int index)
|
||||
{
|
||||
FolioDelegate *ghost = nullptr;
|
||||
|
||||
// check if a ghost entry already exists
|
||||
for (int i = 0; i < m_folder->m_delegates.size(); i++) {
|
||||
auto delegate = m_folder->m_delegates[i].delegate;
|
||||
if (delegate->type() == FolioDelegate::None) {
|
||||
ghost = delegate;
|
||||
|
||||
// remove it
|
||||
removeDelegate(i);
|
||||
|
||||
// correct index if necessary due to deletion
|
||||
if (index > i) {
|
||||
index--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ghost) {
|
||||
ghost = new FolioDelegate{HomeScreenState::self()};
|
||||
}
|
||||
|
||||
// add empty delegate at new position
|
||||
addDelegate(ghost, index);
|
||||
}
|
||||
|
||||
void ApplicationFolderModel::replaceGhostEntry(FolioDelegate *delegate)
|
||||
{
|
||||
for (int i = 0; i < m_folder->m_delegates.size(); i++) {
|
||||
if (m_folder->m_delegates[i].delegate->type() == FolioDelegate::None) {
|
||||
m_folder->m_delegates[i].delegate = delegate;
|
||||
|
||||
Q_EMIT dataChanged(createIndex(i, 0), createIndex(i, 0), {DelegateRole});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ApplicationFolderModel::deleteGhostEntry()
|
||||
{
|
||||
for (int i = 0; i < m_folder->m_delegates.size(); i++) {
|
||||
if (m_folder->m_delegates[i].delegate->type() == FolioDelegate::None) {
|
||||
removeDelegate(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ApplicationFolderModel::dropInsertPosition(int page, qreal x, qreal y)
|
||||
{
|
||||
qreal cellWidth = HomeScreenState::self()->pageCellWidth();
|
||||
qreal cellHeight = HomeScreenState::self()->pageCellHeight();
|
||||
|
||||
int row = (y - topMarginFromScreenEdge()) / cellHeight;
|
||||
row = std::max(0, std::min(numRowsOnPage(), row));
|
||||
|
||||
// the index that the position is over
|
||||
int leftColumn = std::max(0.0, x - leftMarginFromScreenEdge()) / cellWidth;
|
||||
leftColumn = std::min(numColumnsOnPage() - 1, leftColumn);
|
||||
|
||||
qreal leftColumnPosition = leftColumn * cellWidth + leftMarginFromScreenEdge();
|
||||
|
||||
int column = leftColumn + 1;
|
||||
|
||||
// if it's the left half of this position or it's the last column on this row, return itself
|
||||
if ((x < leftColumnPosition + cellWidth * 0.5) || (leftColumn == numColumnsOnPage() - 1)) {
|
||||
column = leftColumn;
|
||||
}
|
||||
|
||||
// calculate the position based on the page, row and column it is at
|
||||
int pos = (page * numRowsOnPage() * numColumnsOnPage()) + (row * numColumnsOnPage()) + column;
|
||||
// make sure it's in bounds
|
||||
return std::min((int)m_folder->m_delegates.size(), std::max(0, pos));
|
||||
}
|
||||
|
||||
bool ApplicationFolderModel::isDropPositionOutside(qreal x, qreal y)
|
||||
{
|
||||
return (x < leftMarginFromScreenEdge()) || (x > (HomeScreenState::self()->viewWidth() - leftMarginFromScreenEdge())) || (y < topMarginFromScreenEdge())
|
||||
|| (y > HomeScreenState::self()->viewHeight() - topMarginFromScreenEdge());
|
||||
}
|
||||
|
||||
void ApplicationFolderModel::evaluateDelegatePositions(bool emitSignal)
|
||||
{
|
||||
qreal pageWidth = HomeScreenState::self()->folderPageWidth();
|
||||
qreal topMargin = verticalPageMargin();
|
||||
qreal leftMargin = horizontalPageMargin();
|
||||
|
||||
qreal cellWidth = HomeScreenState::self()->pageCellWidth();
|
||||
qreal cellHeight = HomeScreenState::self()->pageCellHeight();
|
||||
|
||||
int rows = numRowsOnPage();
|
||||
int columns = numColumnsOnPage();
|
||||
int numOfDelegates = m_folder->m_delegates.size();
|
||||
|
||||
int index = 0;
|
||||
int page = 0;
|
||||
|
||||
while (index < m_folder->m_delegates.size()) {
|
||||
int prevIndex = index;
|
||||
|
||||
// determine positions page-by-page
|
||||
for (int row = 0; row < rows && index < numOfDelegates; row++) {
|
||||
for (int column = 0; column < columns && index < numOfDelegates; column++) {
|
||||
m_folder->m_delegates[index].xPosition = qRound(page * pageWidth + leftMargin + column * cellWidth);
|
||||
m_folder->m_delegates[index].yPosition = qRound(topMargin + row * cellHeight);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// prevent infinite loop
|
||||
if (prevIndex == index) {
|
||||
break;
|
||||
}
|
||||
page++;
|
||||
}
|
||||
|
||||
if (emitSignal) {
|
||||
Q_EMIT dataChanged(createIndex(0, 0), createIndex(m_folder->m_delegates.size() - 1, 0), {XPositionRole});
|
||||
Q_EMIT dataChanged(createIndex(0, 0), createIndex(m_folder->m_delegates.size() - 1, 0), {YPositionRole});
|
||||
}
|
||||
|
||||
Q_EMIT numberOfPagesChanged();
|
||||
}
|
||||
|
||||
QPointF ApplicationFolderModel::getDelegateStartPosition(int page)
|
||||
{
|
||||
qreal pageWidth = HomeScreenState::self()->folderPageWidth();
|
||||
|
||||
qreal x = pageWidth * page + leftMarginFromScreenEdge();
|
||||
qreal y = topMarginFromScreenEdge();
|
||||
return QPointF{x, y};
|
||||
}
|
||||
|
||||
int ApplicationFolderModel::numTotalPages()
|
||||
{
|
||||
int numOfDelegatesOnPage = numRowsOnPage() * numColumnsOnPage();
|
||||
return std::ceil(((qreal)m_folder->m_delegates.size()) / numOfDelegatesOnPage);
|
||||
}
|
||||
|
||||
int ApplicationFolderModel::numRowsOnPage()
|
||||
{
|
||||
qreal contentHeight = HomeScreenState::self()->folderPageContentHeight();
|
||||
qreal cellHeight = HomeScreenState::self()->pageCellHeight();
|
||||
|
||||
return std::max(0.0, contentHeight / cellHeight);
|
||||
}
|
||||
|
||||
int ApplicationFolderModel::numColumnsOnPage()
|
||||
{
|
||||
qreal contentWidth = HomeScreenState::self()->folderPageContentWidth();
|
||||
qreal cellWidth = HomeScreenState::self()->pageCellWidth();
|
||||
|
||||
return std::max(0.0, contentWidth / cellWidth);
|
||||
}
|
||||
|
||||
qreal ApplicationFolderModel::leftMarginFromScreenEdge()
|
||||
{
|
||||
qreal viewWidth = HomeScreenState::self()->viewWidth();
|
||||
qreal folderPageWidth = HomeScreenState::self()->folderPageWidth();
|
||||
|
||||
return (viewWidth - folderPageWidth) / 2 + horizontalPageMargin();
|
||||
}
|
||||
|
||||
qreal ApplicationFolderModel::topMarginFromScreenEdge()
|
||||
{
|
||||
qreal viewHeight = HomeScreenState::self()->viewHeight();
|
||||
qreal folderPageHeight = HomeScreenState::self()->folderPageHeight();
|
||||
|
||||
return (viewHeight - folderPageHeight) / 2 + verticalPageMargin();
|
||||
}
|
||||
|
||||
qreal ApplicationFolderModel::horizontalPageMargin()
|
||||
{
|
||||
qreal pageWidth = HomeScreenState::self()->folderPageWidth();
|
||||
qreal pageContentWidth = HomeScreenState::self()->folderPageContentWidth();
|
||||
|
||||
return (pageWidth - pageContentWidth) / 2;
|
||||
}
|
||||
|
||||
qreal ApplicationFolderModel::verticalPageMargin()
|
||||
{
|
||||
qreal pageHeight = HomeScreenState::self()->folderPageHeight();
|
||||
qreal pageContentHeight = HomeScreenState::self()->folderPageContentHeight();
|
||||
|
||||
return (pageHeight - pageContentHeight) / 2;
|
||||
}
|
||||
138
containments/homescreens/folio/folioapplicationfolder.h
Normal file
138
containments/homescreens/folio/folioapplicationfolder.h
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
// SPDX-FileCopyrightText: 2022-2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "folioapplication.h"
|
||||
#include "foliodelegate.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include <KService>
|
||||
|
||||
#include <KWayland/Client/connection_thread.h>
|
||||
#include <KWayland/Client/plasmawindowmanagement.h>
|
||||
#include <KWayland/Client/registry.h>
|
||||
#include <KWayland/Client/surface.h>
|
||||
|
||||
struct ApplicationDelegate;
|
||||
class ApplicationFolderModel;
|
||||
class FolioDelegate;
|
||||
|
||||
/**
|
||||
* @short Object that represents an application folder.
|
||||
*/
|
||||
|
||||
class FolioApplicationFolder : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
|
||||
Q_PROPERTY(QList<FolioApplication *> appPreviews READ appPreviews NOTIFY applicationsChanged)
|
||||
Q_PROPERTY(ApplicationFolderModel *applications READ applications NOTIFY applicationsReset)
|
||||
|
||||
public:
|
||||
FolioApplicationFolder(QObject *parent = nullptr, QString name = QString{});
|
||||
|
||||
static FolioApplicationFolder *fromJson(QJsonObject &obj, QObject *parent);
|
||||
QJsonObject toJson();
|
||||
|
||||
QString name() const;
|
||||
void setName(QString &name);
|
||||
|
||||
QList<FolioApplication *> appPreviews();
|
||||
|
||||
ApplicationFolderModel *applications();
|
||||
void setApplications(QList<FolioApplication *> applications);
|
||||
|
||||
void moveEntry(int fromRow, int toRow);
|
||||
bool addDelegate(FolioDelegate *delegate, int row);
|
||||
Q_INVOKABLE void removeDelegate(int row);
|
||||
|
||||
int dropInsertPosition(int page, qreal x, qreal y);
|
||||
bool isDropPositionOutside(qreal x, qreal y);
|
||||
|
||||
Q_SIGNALS:
|
||||
void nameChanged();
|
||||
void saveRequested();
|
||||
void applicationsChanged();
|
||||
void applicationsReset();
|
||||
|
||||
private:
|
||||
QString m_name;
|
||||
QList<ApplicationDelegate> m_delegates;
|
||||
ApplicationFolderModel *m_applicationFolderModel;
|
||||
|
||||
friend class ApplicationFolderModel;
|
||||
};
|
||||
|
||||
struct ApplicationDelegate {
|
||||
FolioDelegate *delegate;
|
||||
qreal xPosition;
|
||||
qreal yPosition;
|
||||
};
|
||||
|
||||
class ApplicationFolderModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int numberOfPages READ numTotalPages NOTIFY numberOfPagesChanged)
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
DelegateRole = Qt::UserRole + 1,
|
||||
XPositionRole,
|
||||
YPositionRole,
|
||||
};
|
||||
ApplicationFolderModel(FolioApplicationFolder *folder);
|
||||
|
||||
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;
|
||||
|
||||
FolioDelegate *getDelegate(int index);
|
||||
void moveEntry(int fromRow, int toRow);
|
||||
bool addDelegate(FolioDelegate *delegate, int index);
|
||||
void removeDelegate(int index);
|
||||
QPointF getDelegatePosition(int index);
|
||||
|
||||
// for use with drag and drop, as the delegate is dragged around
|
||||
// ghost - fake delegate exists at an index, so a gap is created
|
||||
// invisible - existing delegate looks like it doesn't exist
|
||||
int getGhostEntryPosition();
|
||||
void setGhostEntry(int index);
|
||||
void replaceGhostEntry(FolioDelegate *delegate);
|
||||
void deleteGhostEntry();
|
||||
|
||||
// the index that dropping at the position given would place the delegate at.
|
||||
int dropInsertPosition(int page, qreal x, qreal y);
|
||||
|
||||
// whether this position is outside of the folder area
|
||||
bool isDropPositionOutside(qreal x, qreal y);
|
||||
|
||||
// distance between page content to screen edge
|
||||
qreal leftMarginFromScreenEdge();
|
||||
qreal topMarginFromScreenEdge();
|
||||
|
||||
int numTotalPages();
|
||||
|
||||
Q_SIGNALS:
|
||||
void numberOfPagesChanged();
|
||||
|
||||
private:
|
||||
void evaluateDelegatePositions(bool emitSignal = true);
|
||||
|
||||
// get the position where delegates start being placed
|
||||
QPointF getDelegateStartPosition(int page);
|
||||
|
||||
int numRowsOnPage();
|
||||
int numColumnsOnPage();
|
||||
|
||||
// distance between folder edge and page content
|
||||
qreal horizontalPageMargin();
|
||||
qreal verticalPageMargin();
|
||||
|
||||
FolioApplicationFolder *m_folder{nullptr};
|
||||
|
||||
friend class FolioApplicationFolder;
|
||||
};
|
||||
88
containments/homescreens/folio/foliodelegate.cpp
Normal file
88
containments/homescreens/folio/foliodelegate.cpp
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "foliodelegate.h"
|
||||
#include "homescreenstate.h"
|
||||
|
||||
FolioDelegate::FolioDelegate(QObject *parent)
|
||||
: QObject{parent}
|
||||
, m_type{FolioDelegate::None}
|
||||
, m_application{nullptr}
|
||||
, m_folder{nullptr}
|
||||
{
|
||||
}
|
||||
|
||||
FolioDelegate::FolioDelegate(FolioApplication *application, QObject *parent)
|
||||
: QObject{parent}
|
||||
, m_type{FolioDelegate::Application}
|
||||
, m_application{application}
|
||||
, m_folder{nullptr}
|
||||
{
|
||||
}
|
||||
|
||||
FolioDelegate::FolioDelegate(FolioApplicationFolder *folder, QObject *parent)
|
||||
: QObject{parent}
|
||||
, m_type{FolioDelegate::Folder}
|
||||
, m_application{nullptr}
|
||||
, m_folder{folder}
|
||||
{
|
||||
}
|
||||
|
||||
FolioDelegate *FolioDelegate::fromJson(QJsonObject &obj, QObject *parent)
|
||||
{
|
||||
const QString type = obj[QStringLiteral("type")].toString();
|
||||
if (type == "application") {
|
||||
// read application
|
||||
FolioApplication *app = FolioApplication::fromJson(obj, parent);
|
||||
|
||||
if (app) {
|
||||
return new FolioDelegate{app, parent};
|
||||
}
|
||||
|
||||
} else if (type == "folder") {
|
||||
// read folder
|
||||
FolioApplicationFolder *folder = FolioApplicationFolder::fromJson(obj, parent);
|
||||
|
||||
if (folder) {
|
||||
return new FolioDelegate{folder, parent};
|
||||
}
|
||||
|
||||
} else if (type == "none") {
|
||||
return new FolioDelegate{parent};
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QJsonObject FolioDelegate::toJson() const
|
||||
{
|
||||
switch (m_type) {
|
||||
case FolioDelegate::Application:
|
||||
return m_application->toJson();
|
||||
case FolioDelegate::Folder:
|
||||
return m_folder->toJson();
|
||||
case FolioDelegate::None: {
|
||||
QJsonObject obj;
|
||||
obj[QStringLiteral("type")] = "none";
|
||||
return obj;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QJsonObject{};
|
||||
}
|
||||
|
||||
FolioDelegate::Type FolioDelegate::type()
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
|
||||
FolioApplication *FolioDelegate::application()
|
||||
{
|
||||
return m_application;
|
||||
}
|
||||
|
||||
FolioApplicationFolder *FolioDelegate::folder()
|
||||
{
|
||||
return m_folder;
|
||||
}
|
||||
44
containments/homescreens/folio/foliodelegate.h
Normal file
44
containments/homescreens/folio/foliodelegate.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "folioapplication.h"
|
||||
#include "folioapplicationfolder.h"
|
||||
|
||||
class FolioApplication;
|
||||
class FolioApplicationFolder;
|
||||
class FolioDelegate : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(FolioDelegate::Type type READ type CONSTANT)
|
||||
Q_PROPERTY(FolioApplication *application READ application CONSTANT)
|
||||
Q_PROPERTY(FolioApplicationFolder *folder READ folder CONSTANT)
|
||||
|
||||
public:
|
||||
enum Type {
|
||||
None,
|
||||
Application,
|
||||
Folder,
|
||||
};
|
||||
Q_ENUM(Type)
|
||||
|
||||
FolioDelegate(QObject *parent = nullptr);
|
||||
FolioDelegate(FolioApplication *application, QObject *parent);
|
||||
FolioDelegate(FolioApplicationFolder *folder, QObject *parent);
|
||||
|
||||
static FolioDelegate *fromJson(QJsonObject &obj, QObject *parent);
|
||||
|
||||
virtual QJsonObject toJson() const;
|
||||
|
||||
FolioDelegate::Type type();
|
||||
FolioApplication *application();
|
||||
FolioApplicationFolder *folder();
|
||||
|
||||
protected:
|
||||
FolioDelegate::Type m_type;
|
||||
FolioApplication *m_application{nullptr};
|
||||
FolioApplicationFolder *m_folder{nullptr};
|
||||
};
|
||||
141
containments/homescreens/folio/foliosettings.cpp
Normal file
141
containments/homescreens/folio/foliosettings.cpp
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "foliosettings.h"
|
||||
|
||||
FolioSettings::FolioSettings(QObject *parent)
|
||||
: QObject{parent}
|
||||
{
|
||||
}
|
||||
|
||||
FolioSettings *FolioSettings::self()
|
||||
{
|
||||
static FolioSettings *settings = new FolioSettings;
|
||||
return settings;
|
||||
}
|
||||
|
||||
int FolioSettings::homeScreenRows() const
|
||||
{
|
||||
// ensure that this is fetched fast and cached (it is called extremely often)
|
||||
return m_homeScreenRows;
|
||||
}
|
||||
|
||||
void FolioSettings::setHomeScreenRows(int homeScreenRows)
|
||||
{
|
||||
if (m_homeScreenRows != homeScreenRows) {
|
||||
m_homeScreenRows = homeScreenRows;
|
||||
Q_EMIT homeScreenRowsChanged();
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
int FolioSettings::homeScreenColumns() const
|
||||
{
|
||||
return m_homeScreenColumns;
|
||||
}
|
||||
|
||||
void FolioSettings::setHomeScreenColumns(int homeScreenColumns)
|
||||
{
|
||||
if (m_homeScreenColumns != homeScreenColumns) {
|
||||
m_homeScreenColumns = homeScreenColumns;
|
||||
Q_EMIT homeScreenColumnsChanged();
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
bool FolioSettings::showPagesAppLabels() const
|
||||
{
|
||||
return m_showPagesAppLabels;
|
||||
}
|
||||
|
||||
void FolioSettings::setShowPagesAppLabels(bool showPagesAppLabels)
|
||||
{
|
||||
if (m_showPagesAppLabels != showPagesAppLabels) {
|
||||
m_showPagesAppLabels = showPagesAppLabels;
|
||||
Q_EMIT showPagesAppLabelsChanged();
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
bool FolioSettings::showFavouritesAppLabels() const
|
||||
{
|
||||
return m_showFavouritesAppLabels;
|
||||
}
|
||||
|
||||
void FolioSettings::setShowFavouritesAppLabels(bool showFavouritesAppLabels)
|
||||
{
|
||||
if (m_showFavouritesAppLabels != showFavouritesAppLabels) {
|
||||
m_showFavouritesAppLabels = showFavouritesAppLabels;
|
||||
Q_EMIT showFavouritesAppLabelsChanged();
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
int FolioSettings::delegateIconSize() const
|
||||
{
|
||||
return m_delegateIconSize;
|
||||
}
|
||||
|
||||
void FolioSettings::setDelegateIconSize(int delegateIconSize)
|
||||
{
|
||||
if (m_delegateIconSize != delegateIconSize) {
|
||||
m_delegateIconSize = delegateIconSize;
|
||||
Q_EMIT delegateIconSizeChanged();
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
bool FolioSettings::showFavouritesBarBackground() const
|
||||
{
|
||||
return m_showFavouritesBarBackground;
|
||||
}
|
||||
|
||||
void FolioSettings::setShowFavouritesBarBackground(bool showFavouritesBarBackground)
|
||||
{
|
||||
if (m_showFavouritesBarBackground != showFavouritesBarBackground) {
|
||||
m_showFavouritesBarBackground = showFavouritesBarBackground;
|
||||
Q_EMIT showFavouritesBarBackgroundChanged();
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
void FolioSettings::setApplet(Plasma::Applet *applet)
|
||||
{
|
||||
m_applet = applet;
|
||||
}
|
||||
|
||||
void FolioSettings::save()
|
||||
{
|
||||
if (!m_applet) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_applet->config().writeEntry("homeScreenRows", m_homeScreenRows);
|
||||
m_applet->config().writeEntry("homeScreenColumns", m_homeScreenColumns);
|
||||
m_applet->config().writeEntry("showPagesAppLabels", m_showPagesAppLabels);
|
||||
m_applet->config().writeEntry("showFavouritesAppLabels", m_showFavouritesAppLabels);
|
||||
m_applet->config().writeEntry("delegateIconSize", m_delegateIconSize);
|
||||
m_applet->config().writeEntry("showFavouritesBarBackground", m_showFavouritesBarBackground);
|
||||
|
||||
Q_EMIT m_applet->configNeedsSaving();
|
||||
}
|
||||
|
||||
void FolioSettings::load()
|
||||
{
|
||||
if (!m_applet) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_homeScreenRows = m_applet->config().readEntry("homeScreenRows", 5);
|
||||
m_homeScreenColumns = m_applet->config().readEntry("homeScreenColumns", 4);
|
||||
m_showPagesAppLabels = m_applet->config().readEntry("showPagesAppLabels", true);
|
||||
m_showFavouritesAppLabels = m_applet->config().readEntry("showFavoritesAppLabels", false);
|
||||
m_delegateIconSize = m_applet->config().readEntry("delegateIconSize", 48);
|
||||
m_showFavouritesBarBackground = m_applet->config().readEntry("showFavoritesBarBackground", true);
|
||||
|
||||
Q_EMIT homeScreenRowsChanged();
|
||||
Q_EMIT homeScreenColumnsChanged();
|
||||
Q_EMIT showPagesAppLabels();
|
||||
Q_EMIT showFavouritesAppLabelsChanged();
|
||||
Q_EMIT delegateIconSizeChanged();
|
||||
}
|
||||
69
containments/homescreens/folio/foliosettings.h
Normal file
69
containments/homescreens/folio/foliosettings.h
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <Plasma/Applet>
|
||||
|
||||
class FolioSettings : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int homeScreenRows READ homeScreenRows WRITE setHomeScreenRows NOTIFY homeScreenRowsChanged)
|
||||
Q_PROPERTY(int homeScreenColumns READ homeScreenColumns WRITE setHomeScreenColumns NOTIFY homeScreenColumnsChanged)
|
||||
Q_PROPERTY(bool showPagesAppLabels READ showPagesAppLabels WRITE setShowPagesAppLabels NOTIFY showPagesAppLabelsChanged)
|
||||
Q_PROPERTY(bool showFavouritesAppLabels READ showFavouritesAppLabels WRITE setShowFavouritesAppLabels NOTIFY showFavouritesAppLabelsChanged)
|
||||
Q_PROPERTY(int delegateIconSize READ delegateIconSize WRITE setDelegateIconSize NOTIFY delegateIconSizeChanged)
|
||||
Q_PROPERTY(bool showFavouritesBarBackground READ showFavouritesBarBackground WRITE setShowFavouritesBarBackground NOTIFY showFavouritesBarBackgroundChanged)
|
||||
|
||||
public:
|
||||
FolioSettings(QObject *parent = nullptr);
|
||||
|
||||
static FolioSettings *self();
|
||||
|
||||
// number of rows and columns in the config for the homescreen
|
||||
// NOTE: use HomeScreenState.pageRows() instead in UI logic since we may have the rows and
|
||||
// columns swapped (in landscape layouts)
|
||||
int homeScreenRows() const;
|
||||
void setHomeScreenRows(int homeScreenRows);
|
||||
|
||||
int homeScreenColumns() const;
|
||||
void setHomeScreenColumns(int homeScreenColumns);
|
||||
|
||||
bool showPagesAppLabels() const;
|
||||
void setShowPagesAppLabels(bool showPagesAppLabels);
|
||||
|
||||
bool showFavouritesAppLabels() const;
|
||||
void setShowFavouritesAppLabels(bool showFavouritesAppLabels);
|
||||
|
||||
int delegateIconSize() const;
|
||||
void setDelegateIconSize(int delegateIconSize);
|
||||
|
||||
bool showFavouritesBarBackground() const;
|
||||
void setShowFavouritesBarBackground(bool showFavouritesBarBackground);
|
||||
|
||||
Q_INVOKABLE void load();
|
||||
|
||||
Q_INVOKABLE void setApplet(Plasma::Applet *applet);
|
||||
|
||||
Q_SIGNALS:
|
||||
void homeScreenRowsChanged();
|
||||
void homeScreenColumnsChanged();
|
||||
void showPagesAppLabelsChanged();
|
||||
void showFavouritesAppLabelsChanged();
|
||||
void delegateIconSizeChanged();
|
||||
void showFavouritesBarBackgroundChanged();
|
||||
|
||||
private:
|
||||
void save();
|
||||
|
||||
int m_homeScreenRows{5};
|
||||
int m_homeScreenColumns{4};
|
||||
bool m_showPagesAppLabels{false};
|
||||
bool m_showFavouritesAppLabels{false};
|
||||
qreal m_delegateIconSize{48};
|
||||
bool m_showFavouritesBarBackground{false};
|
||||
|
||||
Plasma::Applet *m_applet{nullptr};
|
||||
};
|
||||
|
|
@ -1,12 +1,25 @@
|
|||
// SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
|
||||
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
|
||||
// SPDX-FileCopyrightText: 2022-2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "homescreen.h"
|
||||
|
||||
#include "applicationlistmodel.h"
|
||||
#include "delegatetoucharea.h"
|
||||
#include "favouritesmodel.h"
|
||||
#include "folioapplication.h"
|
||||
#include "folioapplicationfolder.h"
|
||||
#include "foliodelegate.h"
|
||||
#include "foliosettings.h"
|
||||
#include "homescreenstate.h"
|
||||
#include "pagelistmodel.h"
|
||||
#include "pagemodel.h"
|
||||
|
||||
#include <KWindowSystem>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QQmlEngine>
|
||||
#include <QQmlExtensionPlugin>
|
||||
#include <QQuickItem>
|
||||
#include <QtQml>
|
||||
|
||||
|
|
@ -14,6 +27,45 @@ HomeScreen::HomeScreen(QObject *parent, const KPluginMetaData &data, const QVari
|
|||
: Plasma::Containment{parent, data, args}
|
||||
{
|
||||
setHasConfigurationInterface(true);
|
||||
const char *uri = "org.kde.private.mobile.homescreen.folio";
|
||||
|
||||
// pre-initialize
|
||||
FolioSettings::self()->setApplet(this);
|
||||
HomeScreenState::self();
|
||||
|
||||
// models are loaded in main.qml
|
||||
ApplicationListModel::self();
|
||||
FavouritesModel::self()->setApplet(this);
|
||||
PageListModel::self()->setApplet(this);
|
||||
|
||||
qmlRegisterSingletonType<ApplicationListModel>(uri, 1, 0, "ApplicationListModel", [](QQmlEngine *, QJSEngine *) -> QObject * {
|
||||
return ApplicationListModel::self();
|
||||
});
|
||||
|
||||
qmlRegisterSingletonType<FavouritesModel>(uri, 1, 0, "FavouritesModel", [](QQmlEngine *, QJSEngine *) -> QObject * {
|
||||
return FavouritesModel::self();
|
||||
});
|
||||
|
||||
qmlRegisterSingletonType<PageListModel>(uri, 1, 0, "PageListModel", [](QQmlEngine *, QJSEngine *) -> QObject * {
|
||||
return PageListModel::self();
|
||||
});
|
||||
|
||||
qmlRegisterSingletonType<FolioSettings>(uri, 1, 0, "FolioSettings", [](QQmlEngine *, QJSEngine *) -> QObject * {
|
||||
return FolioSettings::self();
|
||||
});
|
||||
|
||||
qmlRegisterSingletonType<HomeScreenState>(uri, 1, 0, "HomeScreenState", [](QQmlEngine *, QJSEngine *) -> QObject * {
|
||||
return HomeScreenState::self();
|
||||
});
|
||||
|
||||
qmlRegisterType<FolioApplication>(uri, 1, 0, "FolioApplication");
|
||||
qmlRegisterType<FolioApplicationFolder>(uri, 1, 0, "FolioApplicationFolder");
|
||||
qmlRegisterType<FolioDelegate>(uri, 1, 0, "FolioDelegate");
|
||||
qmlRegisterType<PageModel>(uri, 1, 0, "PageModel");
|
||||
qmlRegisterType<FolioPageDelegate>(uri, 1, 0, "FolioPageDelegate");
|
||||
qmlRegisterType<DelegateTouchArea>(uri, 1, 0, "DelegateTouchArea");
|
||||
qmlRegisterType<DelegateDragPosition>(uri, 1, 0, "DelegateDragPosition");
|
||||
|
||||
connect(KWindowSystem::self(), &KWindowSystem::showingDesktopChanged, this, &HomeScreen::showingDesktopChanged);
|
||||
}
|
||||
|
||||
|
|
|
|||
1027
containments/homescreens/folio/homescreenstate.cpp
Normal file
1027
containments/homescreens/folio/homescreenstate.cpp
Normal file
File diff suppressed because it is too large
Load diff
400
containments/homescreens/folio/homescreenstate.h
Normal file
400
containments/homescreens/folio/homescreenstate.h
Normal file
|
|
@ -0,0 +1,400 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "qqml.h"
|
||||
#include <QObject>
|
||||
#include <QPropertyAnimation>
|
||||
|
||||
#include <Plasma/Applet>
|
||||
|
||||
#include "dragstate.h"
|
||||
|
||||
class DragState;
|
||||
|
||||
/**
|
||||
* @short The homescreen state, containing information on positioning panels as well as any swipe events.
|
||||
*
|
||||
* @author Devin Lin <devin@kde.org>
|
||||
*/
|
||||
|
||||
class HomeScreenState : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(HomeScreenState::SwipeState swipeState READ swipeState NOTIFY swipeStateChanged)
|
||||
Q_PROPERTY(HomeScreenState::ViewState viewState READ viewState NOTIFY viewStateChanged)
|
||||
Q_PROPERTY(DragState *dragState READ dragState CONSTANT)
|
||||
|
||||
Q_PROPERTY(qreal viewWidth READ viewWidth WRITE setViewWidth NOTIFY viewWidthChanged)
|
||||
Q_PROPERTY(qreal viewHeight READ viewHeight WRITE setViewHeight NOTIFY viewHeightChanged)
|
||||
Q_PROPERTY(qreal viewTopPadding READ viewTopPadding WRITE setViewTopPadding NOTIFY viewTopPaddingChanged)
|
||||
Q_PROPERTY(qreal viewBottomPadding READ viewBottomPadding WRITE setViewBottomPadding NOTIFY viewBottomPaddingChanged)
|
||||
Q_PROPERTY(qreal viewLeftPadding READ viewLeftPadding WRITE setViewLeftPadding NOTIFY viewLeftPaddingChanged)
|
||||
Q_PROPERTY(qreal viewRightPadding READ viewRightPadding WRITE setViewRightPadding NOTIFY viewRightPaddingChanged)
|
||||
|
||||
Q_PROPERTY(HomeScreenState::PageOrientation pageOrientation READ pageOrientation NOTIFY pageOrientationChanged)
|
||||
Q_PROPERTY(HomeScreenState::FavouritesBarLocation favouritesBarLocation READ favouritesBarLocation NOTIFY favouritesBarLocationChanged)
|
||||
Q_PROPERTY(int pageRows READ pageRows NOTIFY pageRowsChanged)
|
||||
Q_PROPERTY(int pageColumns READ pageColumns NOTIFY pageColumnsChanged)
|
||||
|
||||
// page measurements
|
||||
Q_PROPERTY(qreal pageViewX READ pageViewX WRITE setPageViewX NOTIFY pageViewXChanged)
|
||||
Q_PROPERTY(qreal pageWidth READ pageWidth WRITE setPageWidth NOTIFY pageWidthChanged)
|
||||
Q_PROPERTY(qreal pageHeight READ pageHeight WRITE setPageHeight NOTIFY pageHeightChanged)
|
||||
Q_PROPERTY(qreal pageContentWidth READ pageContentWidth NOTIFY pageContentWidthChanged)
|
||||
Q_PROPERTY(qreal pageContentHeight READ pageContentHeight NOTIFY pageContentHeightChanged)
|
||||
|
||||
// cell measurements
|
||||
Q_PROPERTY(qreal pageCellWidth READ pageCellWidth NOTIFY pageCellWidthChanged)
|
||||
Q_PROPERTY(qreal pageCellHeight READ pageCellHeight NOTIFY pageCellHeightChanged)
|
||||
Q_PROPERTY(qreal pageDelegateLabelHeight READ pageDelegateLabelHeight WRITE setPageDelegateLabelHeight NOTIFY pageDelegateLabelHeightChanged)
|
||||
Q_PROPERTY(qreal pageDelegateLabelSpacing READ pageDelegateLabelSpacing WRITE setPageDelegateLabelSpacing NOTIFY pageDelegateLabelSpacingChanged)
|
||||
|
||||
// folder measurements and state
|
||||
Q_PROPERTY(qreal folderViewX READ folderViewX WRITE setFolderViewX NOTIFY folderViewXChanged)
|
||||
Q_PROPERTY(qreal folderPageWidth READ folderPageWidth WRITE setFolderPageWidth NOTIFY folderPageWidthChanged)
|
||||
Q_PROPERTY(qreal folderPageHeight READ folderPageHeight WRITE setFolderPageHeight NOTIFY folderPageHeightChanged)
|
||||
Q_PROPERTY(qreal folderPageContentWidth READ folderPageContentWidth WRITE setFolderPageContentWidth NOTIFY folderPageContentWidthChanged)
|
||||
Q_PROPERTY(qreal folderPageContentHeight READ folderPageContentHeight WRITE setFolderPageContentHeight NOTIFY folderPageContentHeightChanged)
|
||||
Q_PROPERTY(qreal folderOpenProgress READ folderOpenProgress WRITE setFolderOpenProgress NOTIFY folderOpenProgressChanged)
|
||||
Q_PROPERTY(FolioApplicationFolder *currentFolder READ currentFolder NOTIFY currentFolderChanged)
|
||||
|
||||
Q_PROPERTY(qreal settingsOpenProgress READ settingsOpenProgress WRITE setSettingsOpenProgress NOTIFY settingsOpenProgressChanged)
|
||||
|
||||
Q_PROPERTY(qreal appDrawerOpenProgress READ appDrawerOpenProgress NOTIFY appDrawerOpenProgressChanged)
|
||||
Q_PROPERTY(qreal appDrawerY READ appDrawerY WRITE setAppDrawerY NOTIFY appDrawerYChanged)
|
||||
|
||||
Q_PROPERTY(qreal searchWidgetOpenProgress READ searchWidgetOpenProgress NOTIFY searchWidgetOpenProgressChanged)
|
||||
Q_PROPERTY(qreal searchWidgetY READ searchWidgetY WRITE setSearchWidgetY NOTIFY searchWidgetYChanged)
|
||||
|
||||
Q_PROPERTY(qreal delegateDragX READ delegateDragX NOTIFY delegateDragXChanged)
|
||||
Q_PROPERTY(qreal delegateDragY READ delegateDragY NOTIFY delegateDragYChanged)
|
||||
|
||||
Q_PROPERTY(int currentPage READ currentPage NOTIFY pageNumChanged)
|
||||
Q_PROPERTY(int currentFolderPage READ currentFolderPage NOTIFY folderPageNumChanged)
|
||||
|
||||
public:
|
||||
enum SwipeState {
|
||||
None,
|
||||
DeterminingSwipeType,
|
||||
SwipingPages, // main homescreen view
|
||||
SwipingOpenAppDrawer,
|
||||
SwipingCloseAppDrawer,
|
||||
SwipingAppDrawerGrid,
|
||||
SwipingOpenSearchWidget,
|
||||
SwipingCloseSearchWidget,
|
||||
SwipingFolderPages,
|
||||
AwaitingDraggingDelegate,
|
||||
DraggingDelegate,
|
||||
};
|
||||
Q_ENUM(SwipeState)
|
||||
|
||||
enum ViewState {
|
||||
SearchWidgetView,
|
||||
PageView,
|
||||
AppDrawerView,
|
||||
FolderView,
|
||||
SettingsView,
|
||||
};
|
||||
Q_ENUM(ViewState)
|
||||
|
||||
enum FavouritesBarLocation { Bottom, Left, Right };
|
||||
Q_ENUM(FavouritesBarLocation)
|
||||
|
||||
enum PageOrientation {
|
||||
RegularPosition, // rows and columns are read as normal
|
||||
RotateClockwise, // swap the rows and columns
|
||||
RotateCounterClockwise, // swap the rows and columns, and then flip the rows
|
||||
RotateUpsideDown, // flip the rows and flip the columns
|
||||
};
|
||||
Q_ENUM(PageOrientation)
|
||||
|
||||
static HomeScreenState *self();
|
||||
|
||||
HomeScreenState(QObject *parent = nullptr);
|
||||
|
||||
// the current state of swipe interaction
|
||||
SwipeState swipeState() const;
|
||||
|
||||
// the current view
|
||||
ViewState viewState() const;
|
||||
|
||||
// drag state object
|
||||
DragState *dragState() const;
|
||||
|
||||
qreal viewWidth() const;
|
||||
void setViewWidth(qreal viewWidth);
|
||||
|
||||
qreal viewHeight() const;
|
||||
void setViewHeight(qreal viewHeight);
|
||||
|
||||
qreal viewTopPadding() const;
|
||||
void setViewTopPadding(qreal viewTopPadding);
|
||||
|
||||
qreal viewBottomPadding() const;
|
||||
void setViewBottomPadding(qreal viewBottomPadding);
|
||||
|
||||
qreal viewLeftPadding() const;
|
||||
void setViewLeftPadding(qreal viewLeftPadding);
|
||||
|
||||
qreal viewRightPadding() const;
|
||||
void setViewRightPadding(qreal viewRightPadding);
|
||||
|
||||
// whether to swap rows and columns in the layout
|
||||
// this happens if the width of the screen is larger than the height
|
||||
PageOrientation pageOrientation() const;
|
||||
void setPageOrientation(PageOrientation pageOrientation);
|
||||
|
||||
FavouritesBarLocation favouritesBarLocation() const;
|
||||
|
||||
// the number of rows on a page
|
||||
int pageRows() const;
|
||||
|
||||
// the number of columns on a page
|
||||
int pageColumns() const;
|
||||
|
||||
// the current horizontal position of the pageview
|
||||
// starts at 0, each page is m_pageWidth wide
|
||||
// first page is at -m_pageWidth, second is at -m_pageWidth * 2, etc.
|
||||
qreal pageViewX() const;
|
||||
void setPageViewX(qreal pageViewX);
|
||||
|
||||
// the width of a single pageview page (set from QML)
|
||||
qreal pageWidth() const;
|
||||
void setPageWidth(qreal pageWidth);
|
||||
|
||||
qreal pageHeight() const;
|
||||
void setPageHeight(qreal pageHeight);
|
||||
|
||||
qreal pageContentWidth() const;
|
||||
void calculatePageContentWidth();
|
||||
|
||||
qreal pageContentHeight() const;
|
||||
void calculatePageContentHeight();
|
||||
|
||||
qreal pageCellWidth() const;
|
||||
void calculatePageCellWidth();
|
||||
|
||||
qreal pageCellHeight() const;
|
||||
void calculatePageCellHeight();
|
||||
|
||||
qreal pageDelegateLabelHeight() const;
|
||||
void setPageDelegateLabelHeight(qreal pageDelegateLabelHeight);
|
||||
|
||||
qreal pageDelegateLabelSpacing() const;
|
||||
void setPageDelegateLabelSpacing(qreal pageDelegateLabelSpacing);
|
||||
|
||||
qreal folderViewX() const;
|
||||
void setFolderViewX(qreal folderViewX);
|
||||
|
||||
qreal folderPageWidth() const;
|
||||
void setFolderPageWidth(qreal folderPageWidth);
|
||||
|
||||
qreal folderPageHeight() const;
|
||||
void setFolderPageHeight(qreal folderPageHeight);
|
||||
|
||||
qreal folderPageContentWidth() const;
|
||||
void setFolderPageContentWidth(qreal folderPageContentWidth);
|
||||
|
||||
qreal folderPageContentHeight() const;
|
||||
void setFolderPageContentHeight(qreal folderPageContentHeight);
|
||||
|
||||
qreal folderOpenProgress() const;
|
||||
void setFolderOpenProgress(qreal folderOpenProgress);
|
||||
|
||||
FolioApplicationFolder *currentFolder() const;
|
||||
void setCurrentFolder(FolioApplicationFolder *folder);
|
||||
|
||||
// the progress for the opening of the settings view
|
||||
qreal settingsOpenProgress();
|
||||
void setSettingsOpenProgress(qreal settingsOpenProgress);
|
||||
|
||||
// between 0-1, the progress for the opening of the app drawer
|
||||
qreal appDrawerOpenProgress();
|
||||
|
||||
// the position of the app drawer
|
||||
// 0: the app drawer is open
|
||||
// APP_DRAWER_OPEN_DIST: - the app drawer is closed
|
||||
qreal appDrawerY();
|
||||
void setAppDrawerY(qreal appDrawerY);
|
||||
|
||||
// between 0-1, the progress for the opening of the search widget
|
||||
qreal searchWidgetOpenProgress();
|
||||
|
||||
// the position of the search widget
|
||||
// 0: the search widget
|
||||
// SEARCH_WIDGET_OPEN_DIST: - the app drawer is closed
|
||||
qreal searchWidgetY();
|
||||
void setSearchWidgetY(qreal searchWidgetY);
|
||||
|
||||
qreal delegateDragX();
|
||||
void setDelegateDragX(qreal delegateDragX);
|
||||
|
||||
qreal delegateDragY();
|
||||
void setDelegateDragY(qreal delegateDragY);
|
||||
|
||||
int currentPage();
|
||||
void setCurrentPage(int currentPage);
|
||||
|
||||
int currentFolderPage();
|
||||
|
||||
// QML helpers
|
||||
Q_INVOKABLE FolioDelegate *getPageDelegateAt(int page, int row, int column);
|
||||
Q_INVOKABLE FolioDelegate *getFavouritesDelegateAt(int position);
|
||||
Q_INVOKABLE FolioDelegate *getFolderDelegateAt(int position);
|
||||
Q_INVOKABLE QPointF getPageDelegateScreenPosition(int page, int row, int column);
|
||||
Q_INVOKABLE QPointF getFavouritesDelegateScreenPosition(int position);
|
||||
Q_INVOKABLE QPointF getFolderDelegateScreenPosition(int position);
|
||||
|
||||
Q_SIGNALS:
|
||||
void swipeStateChanged();
|
||||
void viewStateChanged();
|
||||
void viewWidthChanged();
|
||||
void viewHeightChanged();
|
||||
void viewTopPaddingChanged();
|
||||
void viewBottomPaddingChanged();
|
||||
void viewLeftPaddingChanged();
|
||||
void viewRightPaddingChanged();
|
||||
void pageOrientationChanged();
|
||||
void favouritesBarLocationChanged();
|
||||
void pageRowsChanged();
|
||||
void pageColumnsChanged();
|
||||
void pageViewXChanged();
|
||||
void pageWidthChanged();
|
||||
void pageHeightChanged();
|
||||
void pageContentWidthChanged();
|
||||
void pageContentHeightChanged();
|
||||
void pageCellWidthChanged();
|
||||
void pageCellHeightChanged();
|
||||
void pageDelegateLabelHeightChanged();
|
||||
void pageDelegateLabelSpacingChanged();
|
||||
void folderViewXChanged();
|
||||
void folderPageWidthChanged();
|
||||
void folderPageHeightChanged();
|
||||
void folderPageContentWidthChanged();
|
||||
void folderPageContentHeightChanged();
|
||||
void folderOpenProgressChanged();
|
||||
void currentFolderChanged();
|
||||
void settingsOpenProgressChanged();
|
||||
void appDrawerOpenProgressChanged();
|
||||
void appDrawerYChanged();
|
||||
void appDrawerClosed();
|
||||
void appDrawerOpened();
|
||||
void searchWidgetOpenProgressChanged();
|
||||
void searchWidgetYChanged();
|
||||
void delegateDragXChanged();
|
||||
void delegateDragYChanged();
|
||||
void delegateDragEnded();
|
||||
void delegateDragFromPageStarted(int page, int row, int column);
|
||||
void delegateDragFromFavouritesStarted(int position);
|
||||
void delegateDragFromAppDrawerStarted(QString storageId);
|
||||
void delegateDragFromFolderStarted(FolioApplicationFolder *folder, int position);
|
||||
void pageNumChanged();
|
||||
void folderPageNumChanged();
|
||||
|
||||
void leftCurrentFolder();
|
||||
void folderAboutToOpen(qreal x, qreal y); // the position on the screen where the delegate is at, for animations
|
||||
void appDrawerGridYChanged(qreal y);
|
||||
void appDrawerGridFlickRequested();
|
||||
|
||||
public Q_SLOTS:
|
||||
void openAppDrawer();
|
||||
void closeAppDrawer();
|
||||
void openSearchWidget();
|
||||
void closeSearchWidget();
|
||||
|
||||
void snapPage(); // snaps to closest page
|
||||
void goToPage(int page);
|
||||
|
||||
void goToFolderPage(int page);
|
||||
void openFolder(qreal delegateX, qreal delegateY, FolioApplicationFolder *folder);
|
||||
void closeFolder();
|
||||
|
||||
void openSettingsView();
|
||||
void closeSettingsView();
|
||||
|
||||
void startDelegatePageDrag(qreal startX, qreal startY, int page, int row, int column);
|
||||
void startDelegateFavouritesDrag(qreal startX, qreal startY, int position);
|
||||
void startDelegateAppDrawerDrag(qreal startX, qreal startY, QString storageId);
|
||||
void startDelegateFolderDrag(qreal startX, qreal startY, FolioApplicationFolder *folder, int position);
|
||||
void cancelDelegateDrag();
|
||||
|
||||
// from SwipeArea
|
||||
void swipeStarted();
|
||||
void swipeEnded();
|
||||
void swipeMoved(qreal totalDeltaX, qreal totalDeltaY, qreal deltaX, qreal deltaY);
|
||||
|
||||
private:
|
||||
void setViewState(ViewState viewState);
|
||||
void setSwipeState(SwipeState swipeState);
|
||||
|
||||
void startDelegateDrag(qreal startX, qreal startY);
|
||||
|
||||
void cancelAppDrawerAnimations();
|
||||
void cancelSearchWidgetAnimations();
|
||||
|
||||
// check if we passed the swipe threshold, and determine the swipe type after
|
||||
void determineSwipeTypeAfterThreshold(qreal totalDeltaX, qreal totalDeltaY);
|
||||
|
||||
QPropertyAnimation *setupAnimation(QByteArray property, int duration, QEasingCurve::Type curve, qreal endValue);
|
||||
|
||||
SwipeState m_swipeState{SwipeState::None};
|
||||
ViewState m_viewState{ViewState::PageView};
|
||||
|
||||
DragState *m_dragState{nullptr};
|
||||
|
||||
qreal m_viewWidth{0};
|
||||
qreal m_viewHeight{0};
|
||||
qreal m_viewTopPadding{0};
|
||||
qreal m_viewBottomPadding{0};
|
||||
qreal m_viewLeftPadding{0};
|
||||
qreal m_viewRightPadding{0};
|
||||
|
||||
PageOrientation m_pageOrientation{PageOrientation::RegularPosition};
|
||||
|
||||
qreal m_pageViewX{0};
|
||||
qreal m_pageWidth{0};
|
||||
qreal m_pageHeight{0};
|
||||
qreal m_pageContentWidth{0};
|
||||
qreal m_pageContentHeight{0};
|
||||
|
||||
qreal m_pageCellWidth{0};
|
||||
qreal m_pageCellHeight{0};
|
||||
qreal m_pageDelegateLabelHeight{0};
|
||||
qreal m_pageDelegateLabelSpacing{0};
|
||||
|
||||
qreal m_folderViewX{0};
|
||||
qreal m_folderPageWidth{0};
|
||||
qreal m_folderPageHeight{0};
|
||||
qreal m_folderPageContentWidth{0};
|
||||
qreal m_folderPageContentHeight{0};
|
||||
qreal m_folderOpenProgress{0};
|
||||
FolioApplicationFolder *m_currentFolder{nullptr};
|
||||
|
||||
qreal m_settingsOpenProgress{0};
|
||||
|
||||
qreal m_appDrawerOpenProgress{0};
|
||||
qreal m_appDrawerY{0};
|
||||
qreal m_searchWidgetOpenProgress{0};
|
||||
qreal m_searchWidgetY{0};
|
||||
qreal m_delegateDragX{0};
|
||||
qreal m_delegateDragY{0};
|
||||
|
||||
int m_pageNum{0};
|
||||
int m_folderPageNum{0};
|
||||
|
||||
bool m_movingUp{false};
|
||||
bool m_movingRight{false};
|
||||
|
||||
QPropertyAnimation *m_openAppDrawerAnim{nullptr};
|
||||
QPropertyAnimation *m_closeAppDrawerAnim{nullptr};
|
||||
QPropertyAnimation *m_openSearchWidgetAnim{nullptr};
|
||||
QPropertyAnimation *m_closeSearchWidgetAnim{nullptr};
|
||||
QPropertyAnimation *m_pageAnim{nullptr};
|
||||
QPropertyAnimation *m_openFolderAnim{nullptr};
|
||||
QPropertyAnimation *m_closeFolderAnim{nullptr};
|
||||
QPropertyAnimation *m_folderPageAnim{nullptr};
|
||||
QPropertyAnimation *m_openSettingsAnim{nullptr};
|
||||
QPropertyAnimation *m_closeSettingsAnim{nullptr};
|
||||
};
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
- SPDX-FileCopyrightText: 2015-2019 Marco Martin <mart@kde.org>
|
||||
- SPDX-License-Identifier: GPL-2.0-or-later
|
||||
-->
|
||||
|
||||
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
|
||||
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
|
||||
<kcfgfile name=""/>
|
||||
|
||||
<group name="General">
|
||||
<entry name="AppOrder" type="StringList">
|
||||
<label>order of apps</label>
|
||||
<default>org.kde.phone.dialer.desktop</default>
|
||||
</entry>
|
||||
</group>
|
||||
|
||||
</kcfg>
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls as Controls
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import org.kde.plasma.plasmoid 2.0
|
||||
import org.kde.plasma.components 3.0 as PC3
|
||||
import org.kde.kirigami 2.10 as Kirigami
|
||||
|
||||
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var homeScreen
|
||||
|
||||
property real leftPadding: 0
|
||||
property real topPadding: 0
|
||||
property real bottomPadding: 0
|
||||
property real rightPadding: 0
|
||||
|
||||
required property int headerHeight
|
||||
required property var headerItem
|
||||
|
||||
// height from top of screen that the drawer starts
|
||||
readonly property real drawerTopMargin: height - topPadding - bottomPadding
|
||||
|
||||
property alias flickable: appDrawerGrid
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
anchors.leftMargin: root.leftPadding
|
||||
anchors.topMargin: root.topPadding
|
||||
anchors.rightMargin: root.rightPadding
|
||||
anchors.bottomMargin: root.bottomPadding
|
||||
|
||||
// drawer header
|
||||
MobileShell.BaseItem {
|
||||
id: drawerHeader
|
||||
height: root.headerHeight
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
contentItem: root.headerItem
|
||||
}
|
||||
|
||||
AppDrawerGrid {
|
||||
id: appDrawerGrid
|
||||
homeScreen: root.homeScreen
|
||||
height: parent.height - drawerHeader.height
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
// opacity gradient at grid edges
|
||||
OpacityMask {
|
||||
anchors.fill: appDrawerGrid
|
||||
source: appDrawerGrid
|
||||
maskSource: Rectangle {
|
||||
id: mask
|
||||
width: appDrawerGrid.width
|
||||
height: appDrawerGrid.height
|
||||
|
||||
property real gradientPct: (Kirigami.Units.gridUnit * 2) / appDrawerGrid.height
|
||||
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: appDrawerGrid.atYBeginning ? 'white' : 'transparent' }
|
||||
GradientStop { position: mask.gradientPct; color: 'white' }
|
||||
GradientStop { position: 1.0 - mask.gradientPct; color: 'white' }
|
||||
GradientStop { position: 1.0; color: appDrawerGrid.atYEnd ? 'white' : 'transparent' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls as Controls
|
||||
|
||||
import org.kde.plasma.plasmoid 2.0
|
||||
import org.kde.plasma.components 3.0 as PC3
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
||||
import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
||||
import "./delegate"
|
||||
|
||||
MobileShell.GridView {
|
||||
id: root
|
||||
cacheBuffer: cellHeight * 20
|
||||
reuseItems: true
|
||||
layer.enabled: true
|
||||
|
||||
property var homeScreen
|
||||
|
||||
|
||||
readonly property int reservedSpaceForLabel: Folio.HomeScreenState.pageDelegateLabelHeight
|
||||
readonly property real effectiveContentWidth: width - leftMargin - rightMargin
|
||||
readonly property real horizontalMargin: Math.round(width * 0.05)
|
||||
|
||||
leftMargin: horizontalMargin
|
||||
rightMargin: horizontalMargin
|
||||
|
||||
cellWidth: effectiveContentWidth / Math.min(Math.floor(effectiveContentWidth / (Folio.FolioSettings.delegateIconSize + Kirigami.Units.largeSpacing * 3)), 8)
|
||||
cellHeight: cellWidth + reservedSpaceForLabel
|
||||
|
||||
boundsBehavior: Flickable.OvershootBounds
|
||||
|
||||
readonly property int columns: Math.floor(effectiveContentWidth / cellWidth)
|
||||
readonly property int rows: Math.ceil(root.count / columns)
|
||||
|
||||
// HACK: the first swipe from the top of the app drawer is done from HomeScreenState, not the flickable
|
||||
// due to issues with Flickable getting its swipe stolen by SwipeArea
|
||||
interactive: !atYBeginning && Folio.HomeScreenState.swipeState !== Folio.HomeScreenState.SwipingAppDrawerGrid
|
||||
Connections {
|
||||
target: Folio.HomeScreenState
|
||||
|
||||
function onSwipeStateChanged() {
|
||||
if (Folio.HomeScreenState.swipeState === Folio.HomeScreenState.SwipingAppDrawerGrid) {
|
||||
velocityCalculator.startMeasure();
|
||||
velocityCalculator.changePosition(root.contentY);
|
||||
}
|
||||
}
|
||||
|
||||
function onAppDrawerGridYChanged(y) {
|
||||
root.contentY = Math.max(0, root.contentY - y);
|
||||
velocityCalculator.changePosition(root.contentY);
|
||||
}
|
||||
|
||||
function onAppDrawerGridFlickRequested() {
|
||||
root.returnToBounds();
|
||||
root.flick(0, -velocityCalculator.velocity);
|
||||
}
|
||||
}
|
||||
|
||||
MobileShell.VelocityCalculator {
|
||||
id: velocityCalculator
|
||||
}
|
||||
|
||||
model: Folio.ApplicationListModel
|
||||
|
||||
delegate: AppDelegate {
|
||||
id: delegate
|
||||
shadow: false
|
||||
application: model.delegate.application
|
||||
|
||||
width: root.cellWidth
|
||||
height: root.cellHeight
|
||||
|
||||
onPressAndHold: {
|
||||
Folio.HomeScreenState.closeAppDrawer();
|
||||
let mappedCoords = root.homeScreen.prepareStartDelegateDrag(model.delegate, delegate.delegateItem);
|
||||
Folio.HomeScreenState.startDelegateAppDrawerDrag(
|
||||
mappedCoords.x,
|
||||
mappedCoords.y,
|
||||
model.delegate.application.storageId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PC3.ScrollBar.vertical: PC3.ScrollBar {
|
||||
id: scrollBar
|
||||
interactive: true
|
||||
enabled: true
|
||||
implicitWidth: Kirigami.Units.smallSpacing
|
||||
|
||||
Behavior on opacity {
|
||||
OpacityAnimator {
|
||||
duration: Kirigami.Units.longDuration * 2
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Rectangle {
|
||||
radius: width / 2
|
||||
color: Qt.rgba(1, 1, 1, 0.3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
// SPDX-FileCopyrightText: 2021-2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||
Kirigami.Theme.inherit: false
|
||||
|
||||
RowLayout {
|
||||
anchors.topMargin: Kirigami.Units.smallSpacing
|
||||
anchors.leftMargin: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
|
||||
anchors.rightMargin: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
|
||||
anchors.fill: parent
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
QQC2.Label {
|
||||
color: "white"
|
||||
text: i18n("Applications")
|
||||
font.weight: Font.Bold
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1.5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.plasma.components 3.0 as PC3
|
||||
import org.kde.kirigami 2.10 as Kirigami
|
||||
|
||||
Loader {
|
||||
id: root
|
||||
active: false
|
||||
|
||||
property list<Kirigami.Action> actions
|
||||
|
||||
function open() {
|
||||
root.active = true;
|
||||
root.item.open();
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (root.item) {
|
||||
root.item.close();
|
||||
}
|
||||
}
|
||||
|
||||
sourceComponent: PC3.Menu {
|
||||
id: menu
|
||||
title: "Context Menu"
|
||||
closePolicy: PC3.Menu.CloseOnReleaseOutside | PC3.Menu.CloseOnEscape
|
||||
|
||||
Repeater {
|
||||
model: root.actions
|
||||
delegate: PC3.MenuItem {
|
||||
icon.name: modelData.iconName
|
||||
text: modelData.text
|
||||
onClicked: modelData.triggered()
|
||||
}
|
||||
}
|
||||
|
||||
onClosed: root.active = false
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
||||
import "./delegate"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
width: Folio.HomeScreenState.pageCellWidth
|
||||
height: Folio.HomeScreenState.pageCellHeight
|
||||
|
||||
property Folio.FolioDelegate delegate
|
||||
|
||||
readonly property real dropAnimationRunning: dragXAnim.running || dragYAnim.running
|
||||
|
||||
visible: false
|
||||
x: Math.round(Folio.HomeScreenState.delegateDragX)
|
||||
y: Math.round(Folio.HomeScreenState.delegateDragY)
|
||||
|
||||
function setXBinding() {
|
||||
x = Qt.binding(() => Math.round(Folio.HomeScreenState.delegateDragX));
|
||||
}
|
||||
function setYBinding() {
|
||||
y = Qt.binding(() => Math.round(Folio.HomeScreenState.delegateDragY));
|
||||
}
|
||||
|
||||
// animate drop x
|
||||
XAnimator on x {
|
||||
id: dragXAnim
|
||||
running: false
|
||||
duration: Kirigami.Units.longDuration
|
||||
easing.type: Easing.OutCubic
|
||||
onFinished: {
|
||||
root.visible = false;
|
||||
root.setXBinding();
|
||||
}
|
||||
}
|
||||
|
||||
// animate drop y
|
||||
YAnimator on y {
|
||||
id: dragYAnim
|
||||
running: false
|
||||
duration: Kirigami.Units.longDuration
|
||||
easing.type: Easing.OutCubic
|
||||
onFinished: {
|
||||
root.visible = false;
|
||||
root.setYBinding();
|
||||
}
|
||||
}
|
||||
|
||||
// animate scale if it's an app being placed into a folder
|
||||
ScaleAnimator on scale {
|
||||
id: scaleAnim
|
||||
to: 0
|
||||
running: false
|
||||
duration: Kirigami.Units.longDuration
|
||||
easing.type: Easing.InOutCubic
|
||||
}
|
||||
|
||||
Connections {
|
||||
id: stateWatcher
|
||||
target: Folio.HomeScreenState
|
||||
|
||||
property var delegateDroppedOn: null
|
||||
|
||||
// reset and show drag item
|
||||
function onSwipeStateChanged() {
|
||||
if (Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate) {
|
||||
root.scale = 1.0;
|
||||
root.visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
// save the existing delegate at the spot (this is called before the delegate is dropped)
|
||||
function onDelegateDragEnded() {
|
||||
let dragState = Folio.HomeScreenState.dragState;
|
||||
let dropPosition = dragState.candidateDropPosition;
|
||||
|
||||
switch (dropPosition.location) {
|
||||
case Folio.DelegateDragPosition.Pages:
|
||||
stateWatcher.delegateDroppedOn = Folio.HomeScreenState.getPageDelegateAt(dropPosition.page, dropPosition.pageRow, dropPosition.pageColumn);
|
||||
break;
|
||||
case Folio.DelegateDragPosition.Favourites:
|
||||
stateWatcher.delegateDroppedOn = Folio.HomeScreenState.getFavouritesDelegateAt(dropPosition.favouritesPosition);
|
||||
break;
|
||||
case Folio.DelegateDragPosition.Folder:
|
||||
stateWatcher.delegateDroppedOn = null
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Folio.HomeScreenState.dragState
|
||||
|
||||
// animate from when the delegate is dropped to its drop position
|
||||
function onDelegateDroppedAndPlaced() {
|
||||
let dragState = Folio.HomeScreenState.dragState;
|
||||
let dropPosition = dragState.candidateDropPosition;
|
||||
|
||||
let pos = null;
|
||||
|
||||
switch (dropPosition.location) {
|
||||
case Folio.DelegateDragPosition.Pages:
|
||||
pos = Folio.HomeScreenState.getPageDelegateScreenPosition(dropPosition.page, dropPosition.pageRow, dropPosition.pageColumn);
|
||||
break;
|
||||
case Folio.DelegateDragPosition.Favourites:
|
||||
pos = Folio.HomeScreenState.getFavouritesDelegateScreenPosition(dropPosition.favouritesPosition);
|
||||
break;
|
||||
case Folio.DelegateDragPosition.Folder:
|
||||
pos = Folio.HomeScreenState.getFolderDelegateScreenPosition(dropPosition.folderPosition);
|
||||
break;
|
||||
}
|
||||
|
||||
dragXAnim.to = pos.x;
|
||||
dragYAnim.to = pos.y;
|
||||
dragXAnim.restart();
|
||||
dragYAnim.restart();
|
||||
|
||||
if (stateWatcher.delegateDroppedOn &&
|
||||
stateWatcher.delegateDroppedOn.type != Folio.FolioDelegate.None &&
|
||||
dragState.dropDelegate.type === Folio.FolioDelegate.Application) {
|
||||
|
||||
// scale animation if we are creating, or inserting into a folder
|
||||
scaleAnim.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// simulate an icon delegate
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
// icon
|
||||
DelegateIconLoader {
|
||||
id: loader
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
|
||||
Layout.minimumWidth: Folio.FolioSettings.delegateIconSize
|
||||
Layout.minimumHeight: Folio.FolioSettings.delegateIconSize
|
||||
Layout.preferredHeight: Layout.minimumHeight
|
||||
|
||||
delegate: root.delegate
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: DelegateShadow {}
|
||||
}
|
||||
|
||||
// simulate the delegate label for positioning purposes
|
||||
DelegateLabel {
|
||||
id: label
|
||||
opacity: 0
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Folio.HomeScreenState.pageDelegateLabelHeight
|
||||
Layout.topMargin: Folio.HomeScreenState.pageDelegateLabelSpacing
|
||||
Layout.leftMargin: -parent.anchors.leftMargin + Kirigami.Units.smallSpacing
|
||||
Layout.rightMargin: -parent.anchors.rightMargin + Kirigami.Units.smallSpacing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.4
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls 2.3 as Controls
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.plasma.plasmoid 2.0
|
||||
import org.kde.kquickcontrolsaddons 2.0
|
||||
|
||||
|
||||
LauncherContainer {
|
||||
id: root
|
||||
|
||||
readonly property int count: flow.width / cellWidth
|
||||
|
||||
flow.flow: Flow.TopToBottom
|
||||
|
||||
height: visible ? cellHeight : 0
|
||||
|
||||
frame.implicitWidth: cellWidth * Math.max(1, flow.children.length) + frame.leftPadding + frame.rightPadding
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Kirigami.Units.longDuration
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
Behavior on opacity {
|
||||
OpacityAnimator {
|
||||
duration: Kirigami.Units.longDuration * 4
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Window 2.12
|
||||
import QtQuick.Layouts 1.1
|
||||
|
||||
import org.kde.plasma.components 3.0 as PC3
|
||||
import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
||||
import org.kde.kirigami 2.10 as Kirigami
|
||||
|
||||
import "./delegate"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var homeScreen
|
||||
|
||||
// use to account for x-y positioning, because delegate x and y will include the screen margins
|
||||
property real leftMargin
|
||||
property real topMargin
|
||||
|
||||
signal delegateDragRequested(var item)
|
||||
|
||||
Repeater {
|
||||
model: Folio.FavouritesModel
|
||||
|
||||
delegate: Item {
|
||||
id: delegate
|
||||
|
||||
property var delegateModel: model.delegate
|
||||
property int index: model.index
|
||||
|
||||
property var dragState: Folio.HomeScreenState.dragState
|
||||
property bool isDropPositionThis: dragState.candidateDropPosition.location === Folio.DelegateDragPosition.Favourites &&
|
||||
dragState.candidateDropPosition.favouritesPosition === delegate.index
|
||||
property bool isAppHoveredOver: Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate &&
|
||||
dragState.dropDelegate &&
|
||||
dragState.dropDelegate.type === Folio.FolioDelegate.Application &&
|
||||
isDropPositionThis
|
||||
|
||||
// only one of them will be used, because of the anchors below
|
||||
// this is used due to the ability for the favourites bar to be in multiple locations
|
||||
x: model.xPosition - leftMargin
|
||||
y: model.xPosition - topMargin
|
||||
|
||||
anchors.verticalCenter: Folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Bottom ? parent.verticalCenter : undefined
|
||||
anchors.horizontalCenter: Folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Bottom ? undefined : parent.horizontalCenter
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation { duration: 250; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
Behavior on y {
|
||||
NumberAnimation { duration: 250; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
|
||||
implicitWidth: Folio.HomeScreenState.pageCellWidth
|
||||
implicitHeight: Folio.HomeScreenState.pageCellHeight
|
||||
width: Folio.HomeScreenState.pageCellWidth
|
||||
height: Folio.HomeScreenState.pageCellHeight
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
|
||||
sourceComponent: {
|
||||
if (delegate.delegateModel.type === Folio.FolioDelegate.Application) {
|
||||
return appComponent;
|
||||
} else if (delegate.delegateModel.type === Folio.FolioDelegate.Folder) {
|
||||
return folderComponent;
|
||||
} else {
|
||||
// ghost entry
|
||||
return placeholderComponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: placeholderComponent
|
||||
|
||||
Item {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: appComponent
|
||||
|
||||
AppDelegate {
|
||||
id: appDelegate
|
||||
application: delegate.delegateModel.application
|
||||
name: Folio.FolioSettings.showFavouritesAppLabels ? delegate.delegateModel.application.name : ""
|
||||
shadow: true
|
||||
|
||||
turnToFolder: delegate.isAppHoveredOver
|
||||
turnToFolderAnimEnabled: Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate
|
||||
|
||||
// do not show if the drop animation is running to this delegate
|
||||
visible: !(root.homeScreen.dropAnimationRunning && delegate.isDropPositionThis)
|
||||
|
||||
// don't show label in drag and drop mode
|
||||
labelOpacity: delegate.opacity
|
||||
|
||||
onPressAndHold: {
|
||||
let mappedCoords = root.homeScreen.prepareStartDelegateDrag(delegate.delegateModel, appDelegate.delegateItem);
|
||||
Folio.HomeScreenState.startDelegateFavouritesDrag(
|
||||
mappedCoords.x,
|
||||
mappedCoords.y,
|
||||
delegate.index
|
||||
);
|
||||
|
||||
contextMenu.open();
|
||||
}
|
||||
|
||||
onPressAndHoldReleased: {
|
||||
// cancel the event if the delegate is not dragged
|
||||
if (Folio.HomeScreenState.swipeState === Folio.HomeScreenState.AwaitingDraggingDelegate) {
|
||||
homeScreen.cancelDelegateDrag();
|
||||
}
|
||||
}
|
||||
|
||||
onRightMousePress: {
|
||||
contextMenu.open();
|
||||
}
|
||||
|
||||
ContextMenuLoader {
|
||||
id: contextMenu
|
||||
|
||||
// close menu when drag starts
|
||||
Connections {
|
||||
target: Folio.HomeScreenState
|
||||
|
||||
function onSwipeStateChanged() {
|
||||
if (Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate) {
|
||||
contextMenu.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
icon.name: "emblem-favorite"
|
||||
text: i18n("Remove")
|
||||
onTriggered: Folio.FavouritesModel.removeEntry(delegate.index)
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: folderComponent
|
||||
|
||||
AppFolderDelegate {
|
||||
id: appFolderDelegate
|
||||
shadow: true
|
||||
folder: delegate.delegateModel.folder
|
||||
name: Folio.FolioSettings.showFavouritesAppLabels ? delegate.delegateModel.folder.name : ""
|
||||
|
||||
// do not show if the drop animation is running to this delegate, and the drop delegate is a folder
|
||||
visible: !(root.homeScreen.dropAnimationRunning &&
|
||||
delegate.isDropPositionThis &&
|
||||
delegate.dragState.dropDelegate.type === Folio.FolioDelegate.Folder)
|
||||
|
||||
appHoveredOver: delegate.isAppHoveredOver
|
||||
|
||||
// don't show label in drag and drop mode
|
||||
labelOpacity: delegate.opacity
|
||||
|
||||
onAfterClickAnimation: {
|
||||
const pos = homeScreen.prepareFolderOpen(appFolderDelegate.contentItem);
|
||||
Folio.HomeScreenState.openFolder(pos.x, pos.y, delegate.delegateModel.folder);
|
||||
}
|
||||
|
||||
onPressAndHold: {
|
||||
let mappedCoords = root.homeScreen.prepareStartDelegateDrag(delegate.delegateModel, appFolderDelegate.delegateItem);
|
||||
Folio.HomeScreenState.startDelegateFavouritesDrag(
|
||||
mappedCoords.x,
|
||||
mappedCoords.y,
|
||||
delegate.index
|
||||
);
|
||||
|
||||
contextMenu.open();
|
||||
}
|
||||
|
||||
onPressAndHoldReleased: {
|
||||
// cancel the event if the delegate is not dragged
|
||||
if (Folio.HomeScreenState.swipeState === Folio.HomeScreenState.AwaitingDraggingDelegate) {
|
||||
root.homeScreen.cancelDelegateDrag();
|
||||
}
|
||||
}
|
||||
|
||||
onRightMousePress: {
|
||||
contextMenu.open();
|
||||
}
|
||||
|
||||
// TODO don't use loader, and move outside to a page to make it more performant
|
||||
ContextMenuLoader {
|
||||
id: contextMenu
|
||||
|
||||
// close menu when drag starts
|
||||
Connections {
|
||||
target: Folio.HomeScreenState
|
||||
|
||||
function onSwipeStateChanged() {
|
||||
if (Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate) {
|
||||
contextMenu.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
icon.name: "emblem-favorite"
|
||||
text: i18n("Remove")
|
||||
onTriggered: Folio.FavouritesModel.removeEntry(delegate.index)
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.taskmanager 0.1 as TaskManager
|
||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
||||
|
||||
MobileShell.Flickable {
|
||||
id: root
|
||||
|
||||
required property var homeScreenState
|
||||
|
||||
// we use flickable solely for capturing flicks, not positioning elements
|
||||
contentWidth: width + 99999
|
||||
contentHeight: height + 99999
|
||||
contentX: startContentX
|
||||
contentY: startContentY
|
||||
|
||||
readonly property real startContentX: contentWidth / 2
|
||||
readonly property real startContentY: contentHeight / 2
|
||||
|
||||
// update position from flickable movement
|
||||
property real oldContentX
|
||||
property real oldContentY
|
||||
onContentXChanged: {
|
||||
homeScreenState.updatePositionWithOffset(contentX - oldContentX, 0);
|
||||
oldContentX = contentX;
|
||||
}
|
||||
onContentYChanged: {
|
||||
homeScreenState.updatePositionWithOffset(0, -(contentY - oldContentY));
|
||||
oldContentY = contentY;
|
||||
}
|
||||
|
||||
onMovementStarted: homeScreenState.cancelAnimations();
|
||||
onMovementEnded: {
|
||||
if (!homeScreenState.animationsRunning) {
|
||||
homeScreenState.updateState();
|
||||
}
|
||||
resetPosition();
|
||||
}
|
||||
onFlickEnded: {
|
||||
homeScreenState.cancelEditModeForItemsRequested()
|
||||
resetPosition();
|
||||
}
|
||||
|
||||
onDragStarted: homeScreenState.cancelEditModeForItemsRequested()
|
||||
onDragEnded: homeScreenState.cancelEditModeForItemsRequested()
|
||||
onFlickStarted: {
|
||||
homeScreenState.cancelEditModeForItemsRequested();
|
||||
root.cancelFlick();
|
||||
}
|
||||
|
||||
onDraggingChanged: {
|
||||
if (!dragging) {
|
||||
resetPosition();
|
||||
if (!homeScreenState.animationsRunning) {
|
||||
homeScreenState.updateState();
|
||||
}
|
||||
} else {
|
||||
homeScreenState.cancelAnimations();
|
||||
}
|
||||
}
|
||||
|
||||
function resetPosition() {
|
||||
oldContentX = startContentX;
|
||||
contentX = startContentX;
|
||||
oldContentY = startContentY;
|
||||
contentY = startContentY;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,297 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Controls as QQC2
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
||||
import "./delegate"
|
||||
|
||||
Folio.DelegateTouchArea {
|
||||
id: root
|
||||
|
||||
property var homeScreen
|
||||
|
||||
// the position on the screen for animations to start from
|
||||
property real folderPositionX
|
||||
property real folderPositionY
|
||||
|
||||
property Folio.FolioApplicationFolder folder: Folio.HomeScreenState.currentFolder
|
||||
|
||||
onClicked: close();
|
||||
|
||||
function close() {
|
||||
Folio.HomeScreenState.closeFolder();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Folio.HomeScreenState
|
||||
|
||||
function onFolderAboutToOpen(x, y) {
|
||||
root.folderPositionX = x - Folio.HomeScreenState.viewLeftPadding;
|
||||
root.folderPositionY = y - Folio.HomeScreenState.viewRightPadding;
|
||||
}
|
||||
}
|
||||
|
||||
FolderViewTitle {
|
||||
id: titleText
|
||||
width: root.width
|
||||
|
||||
// have to use y instead of anchors to avoid animations
|
||||
y: Math.round((root.height / 2) - (folderBackground.height / 2) - Kirigami.Units.gridUnit - height)
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
folder: root.folder
|
||||
|
||||
opacity: (root.opacity === 1) ? 1 : 0
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: Kirigami.Units.shortDuration }
|
||||
}
|
||||
}
|
||||
|
||||
function updateContentWidth() {
|
||||
let margin = folderBackground.margin;
|
||||
let columns = Math.floor((folderBackground.width - margin * 2) / Folio.HomeScreenState.pageCellWidth);
|
||||
Folio.HomeScreenState.folderPageContentWidth = columns * Folio.HomeScreenState.pageCellWidth;
|
||||
}
|
||||
|
||||
function updateContentHeight() {
|
||||
let margin = folderBackground.margin;
|
||||
let rows = Math.floor((folderBackground.height - margin * 2) / Folio.HomeScreenState.pageCellHeight);
|
||||
Folio.HomeScreenState.folderPageContentHeight = rows * Folio.HomeScreenState.pageCellHeight;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Folio.HomeScreenState
|
||||
|
||||
function onPageCellWidthChanged() {
|
||||
root.updateContentWidth();
|
||||
root.updateContentHeight();
|
||||
}
|
||||
|
||||
function onPageCellHeightChanged() {
|
||||
root.updateContentWidth();
|
||||
root.updateContentHeight();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: folderBackground
|
||||
color: Qt.rgba(255, 255, 255, 0.3)
|
||||
radius: Kirigami.Units.gridUnit
|
||||
|
||||
readonly property real margin: Kirigami.Units.largeSpacing
|
||||
readonly property real maxLength: Math.min(root.width * 0.9, root.height * 0.9)
|
||||
|
||||
width: {
|
||||
let perRow = 0;
|
||||
if (root.width < root.height) {
|
||||
perRow = Math.floor((maxLength - margin * 2) / Folio.HomeScreenState.pageCellWidth);
|
||||
} else {
|
||||
// try to get the same number of rows as columns
|
||||
perRow = Math.floor((maxLength - margin * 2) / Folio.HomeScreenState.pageCellHeight);
|
||||
}
|
||||
return Math.min(root.width * 0.9, perRow * Folio.HomeScreenState.pageCellWidth + margin * 2);
|
||||
}
|
||||
height: {
|
||||
let perRow = 0;
|
||||
if (root.width < root.height) {
|
||||
// try to get the same number of rows as columns
|
||||
perRow = Math.floor((maxLength - margin * 2) / Folio.HomeScreenState.pageCellWidth);
|
||||
} else {
|
||||
perRow = Math.floor((maxLength - margin * 2) / Folio.HomeScreenState.pageCellHeight);
|
||||
}
|
||||
return Math.min(root.height * 0.9, perRow * Folio.HomeScreenState.pageCellHeight + margin * 2);
|
||||
}
|
||||
|
||||
onWidthChanged: {
|
||||
Folio.HomeScreenState.folderPageWidth = width;
|
||||
root.updateContentHeight();
|
||||
root.updateContentHeight();
|
||||
}
|
||||
onHeightChanged: {
|
||||
Folio.HomeScreenState.folderPageHeight = height;
|
||||
root.updateContentWidth();
|
||||
root.updateContentHeight();
|
||||
}
|
||||
|
||||
x: {
|
||||
const folderPos = root.folderPositionX;
|
||||
const centerX = (root.width / 2) - (width / 2);
|
||||
return Math.round(folderPos + (centerX - folderPos) * Folio.HomeScreenState.folderOpenProgress);
|
||||
}
|
||||
y: {
|
||||
const folderPos = root.folderPositionY;
|
||||
const centerY = (root.height / 2) - (height / 2);
|
||||
return Math.round(folderPos + (centerY - folderPos) * Folio.HomeScreenState.folderOpenProgress);
|
||||
}
|
||||
|
||||
transform: [
|
||||
Scale {
|
||||
origin.x: 0
|
||||
origin.y: 0
|
||||
|
||||
xScale: {
|
||||
const iconSize = Folio.FolioSettings.delegateIconSize;
|
||||
const fullWidth = folderBackground.width;
|
||||
const candidate = iconSize + (fullWidth - iconSize) * Folio.HomeScreenState.folderOpenProgress;
|
||||
return Math.max(0, Math.min(1, candidate / fullWidth));
|
||||
}
|
||||
yScale: {
|
||||
const iconSize = Folio.FolioSettings.delegateIconSize;
|
||||
const fullHeight = folderBackground.height;
|
||||
const candidate = iconSize + (fullHeight - iconSize) * Folio.HomeScreenState.folderOpenProgress;
|
||||
return Math.max(0, Math.min(1, candidate / fullHeight));
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
MouseArea {
|
||||
id: captureTouches
|
||||
anchors.fill: parent
|
||||
|
||||
// clip the pages
|
||||
layer.enabled: true
|
||||
|
||||
Item {
|
||||
id: contentContainer
|
||||
x: Folio.HomeScreenState.folderViewX
|
||||
|
||||
Repeater {
|
||||
model: root.folder ? root.folder.applications : []
|
||||
|
||||
delegate: Item {
|
||||
id: delegate
|
||||
|
||||
property var delegateModel: model.delegate
|
||||
property int index: model.index
|
||||
|
||||
property var dragState: Folio.HomeScreenState.dragState
|
||||
property bool isDropPositionThis: dragState.candidateDropPosition.location === Folio.DelegateDragPosition.Folder &&
|
||||
dragState.candidateDropPosition.folderPosition === index
|
||||
|
||||
x: model.xPosition
|
||||
y: model.yPosition
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation { duration: 250; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
Behavior on y {
|
||||
NumberAnimation { duration: 250; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
|
||||
implicitWidth: Folio.HomeScreenState.pageCellWidth
|
||||
implicitHeight: Folio.HomeScreenState.pageCellHeight
|
||||
width: Folio.HomeScreenState.pageCellWidth
|
||||
height: Folio.HomeScreenState.pageCellHeight
|
||||
|
||||
Loader {
|
||||
id: delegateLoader
|
||||
anchors.fill: parent
|
||||
|
||||
sourceComponent: {
|
||||
if (delegate.delegateModel.type === Folio.FolioDelegate.Application) {
|
||||
return appComponent;
|
||||
} else {
|
||||
return noneComponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: noneComponent
|
||||
|
||||
Item {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: appComponent
|
||||
|
||||
AppDelegate {
|
||||
id: appDelegate
|
||||
application: delegate.delegateModel.application
|
||||
|
||||
// do not show if the drop animation is running to this delegate
|
||||
visible: !(root.homeScreen.dropAnimationRunning && delegate.isDropPositionThis)
|
||||
|
||||
// don't show label in drag and drop mode
|
||||
labelOpacity: delegate.opacity
|
||||
|
||||
onPressAndHold: {
|
||||
let mappedCoords = root.homeScreen.prepareStartDelegateDrag(delegate.delegateModel, appDelegate.delegateItem);
|
||||
Folio.HomeScreenState.startDelegateFolderDrag(
|
||||
mappedCoords.x,
|
||||
mappedCoords.y,
|
||||
root.folder,
|
||||
delegate.index
|
||||
);
|
||||
|
||||
contextMenu.open();
|
||||
}
|
||||
|
||||
onPressAndHoldReleased: {
|
||||
// cancel the event if the delegate is not dragged
|
||||
if (Folio.HomeScreenState.swipeState === Folio.HomeScreenState.AwaitingDraggingDelegate) {
|
||||
homeScreen.cancelDelegateDrag();
|
||||
}
|
||||
}
|
||||
|
||||
onRightMousePress: {
|
||||
contextMenu.open();
|
||||
}
|
||||
|
||||
ContextMenuLoader {
|
||||
id: contextMenu
|
||||
|
||||
// close menu when drag starts
|
||||
Connections {
|
||||
target: Folio.HomeScreenState
|
||||
|
||||
function onSwipeStateChanged() {
|
||||
if (Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate) {
|
||||
contextMenu.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
icon.name: "emblem-favorite"
|
||||
text: i18n("Remove")
|
||||
onTriggered: root.folder.removeApp(delegate.index)
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.PageIndicator {
|
||||
visible: count > 1
|
||||
Kirigami.Theme.inherit: false
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||
|
||||
// have to use y instead of anchors to avoid animations
|
||||
y: Math.round((root.height / 2) + (folderBackground.height / 2) + Kirigami.Units.largeSpacing)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
currentIndex: Folio.HomeScreenState.currentFolderPage
|
||||
count: Folio.HomeScreenState.currentFolder ? Folio.HomeScreenState.currentFolder.applications.numberOfPages : 0
|
||||
|
||||
opacity: (root.opacity === 1) ? 1 : 0
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: Kirigami.Units.shortDuration }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Controls as QQC2
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
||||
MobileShell.BaseItem {
|
||||
id: root
|
||||
|
||||
property Folio.FolioApplicationFolder folder
|
||||
property bool inFolderTitleEditMode: false
|
||||
|
||||
Connections {
|
||||
target: Folio.HomeScreenState
|
||||
|
||||
function onLeftCurrentFolder() {
|
||||
root.inFolderTitleEditMode = false;
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: 'transparent'
|
||||
TapHandler {
|
||||
onTapped: {
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: folderTitleEdit
|
||||
|
||||
TextEdit {
|
||||
text: root.folder ? root.folder.name : ""
|
||||
color: "white"
|
||||
selectByMouse: true
|
||||
wrapMode: TextEdit.Wrap
|
||||
horizontalAlignment: TextEdit.AlignHCenter
|
||||
|
||||
Component.onCompleted: {
|
||||
forceActiveFocus();
|
||||
cursorPosition = text.length;
|
||||
}
|
||||
|
||||
font.weight: Font.Bold
|
||||
font.pointSize: 18
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: MobileShell.TextDropShadow {}
|
||||
|
||||
onTextChanged: {
|
||||
if (text.includes('\n')) {
|
||||
// exit text edit mode when new line is entered
|
||||
root.inFolderTitleEditMode = false;
|
||||
} else if (root.folder) {
|
||||
root.folder.name = text;
|
||||
}
|
||||
}
|
||||
onEditingFinished: root.inFolderTitleEditMode = false
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: folderTitleLabel
|
||||
|
||||
QQC2.Label {
|
||||
text: root.folder ? root.folder.name : ""
|
||||
color: "white"
|
||||
style: Text.Normal
|
||||
styleColor: "transparent"
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
textFormat: Text.MarkdownText
|
||||
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.Wrap
|
||||
maximumLineCount: 2
|
||||
|
||||
font.weight: Font.Bold
|
||||
font.pointSize: 18
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: MobileShell.TextDropShadow {}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.inFolderTitleEditMode = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// folder title
|
||||
contentItem: Loader {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
sourceComponent: root.inFolderTitleEditMode ? folderTitleEdit : folderTitleLabel
|
||||
}
|
||||
}
|
||||
|
|
@ -1,233 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls 2.3 as Controls
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.plasma.plasmoid 2.0
|
||||
import org.kde.plasma.core 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.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
|
||||
import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
||||
import "private" as Private
|
||||
|
||||
ContainmentLayoutManager.ItemContainer {
|
||||
id: delegate
|
||||
required property Folio.DesktopModel desktopModel
|
||||
|
||||
enabled: homeScreenState.currentView === HomeScreenState.PageView || homeScreenState.currentSwipeState === HomeScreenState.SwipingAppDrawerVisibility
|
||||
|
||||
property var homeScreenState
|
||||
|
||||
z: dragActive ? 1 : 0
|
||||
|
||||
property var modelData: typeof model !== "undefined" ? model : null
|
||||
|
||||
Layout.minimumWidth: appletsLayout.cellWidth
|
||||
Layout.minimumHeight: appletsLayout.cellHeight
|
||||
|
||||
key: model.applicationUniqueId
|
||||
property ContainmentLayoutManager.AppletsLayout appletsLayout
|
||||
property int reservedSpaceForLabel
|
||||
property real dragCenterX
|
||||
property real dragCenterY
|
||||
property alias iconItem: icon
|
||||
|
||||
editModeCondition: ContainmentLayoutManager.ItemContainer.AfterPressAndHold
|
||||
|
||||
signal launch(int x, int y, var source, string title)
|
||||
|
||||
function syncDelegateGeometry() {
|
||||
if (!applicationRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
desktopModel.setMinimizedDelegate(index, delegate);
|
||||
}
|
||||
|
||||
function launchApp() {
|
||||
if (modelData.applicationRunning) {
|
||||
delegate.launch(0, 0, "", modelData.applicationName);
|
||||
} else {
|
||||
delegate.launch(delegate.x + (Kirigami.Units.smallSpacing * 2), delegate.y + (Kirigami.Units.smallSpacing * 2), icon.source, modelData.applicationName);
|
||||
}
|
||||
|
||||
desktopModel.setMinimizedDelegate(index, delegate);
|
||||
MobileShell.AppLaunch.launchOrActivateApp(modelData.applicationStorageId);
|
||||
}
|
||||
|
||||
readonly property bool applicationRunning: model.applicationRunning
|
||||
onApplicationRunningChanged: {
|
||||
syncDelegateGeometry();
|
||||
}
|
||||
onDragActiveChanged: {
|
||||
if (dragActive) {
|
||||
removeButton.show();
|
||||
mouseArea.enabled = true;
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: homeScreenState
|
||||
function onCancelEditModeForItemsRequested() {
|
||||
cancelEdit()
|
||||
}
|
||||
function onXPositionChanged() {
|
||||
syncDelegateGeometry()
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: appletsLayout
|
||||
function onAppletsLayoutInteracted() {
|
||||
removeButton.hide();
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
// grow/shrink animation
|
||||
property real zoomScale: 1
|
||||
transform: Scale {
|
||||
origin.x: mouseArea.width / 2;
|
||||
origin.y: mouseArea.height / 2;
|
||||
xScale: mouseArea.zoomScale
|
||||
yScale: mouseArea.zoomScale
|
||||
}
|
||||
|
||||
property bool launchAppRequested: false
|
||||
|
||||
NumberAnimation on zoomScale {
|
||||
id: shrinkAnim
|
||||
running: false
|
||||
duration: ShellSettings.Settings.animationsEnabled ? 80 : 1
|
||||
to: ShellSettings.Settings.animationsEnabled ? 0.8 : 1
|
||||
onFinished: {
|
||||
if (!mouseArea.pressed) {
|
||||
growAnim.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation on zoomScale {
|
||||
id: growAnim
|
||||
running: false
|
||||
duration: ShellSettings.Settings.animationsEnabled ? 80 : 1
|
||||
to: 1
|
||||
onFinished: {
|
||||
if (mouseArea.launchAppRequested) {
|
||||
delegate.launchApp();
|
||||
mouseArea.launchAppRequested = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
onPressedChanged: {
|
||||
if (pressed) {
|
||||
growAnim.stop();
|
||||
shrinkAnim.restart();
|
||||
} else if (!pressed && !shrinkAnim.running) {
|
||||
growAnim.restart();
|
||||
}
|
||||
}
|
||||
// launch app handled by press animation
|
||||
onClicked: launchAppRequested = true;
|
||||
|
||||
ColumnLayout {
|
||||
anchors {
|
||||
fill: parent
|
||||
leftMargin: Kirigami.Units.smallSpacing * 2
|
||||
topMargin: Kirigami.Units.smallSpacing * 2
|
||||
rightMargin: Kirigami.Units.smallSpacing * 2
|
||||
bottomMargin: Kirigami.Units.smallSpacing * 2
|
||||
}
|
||||
spacing: 0
|
||||
|
||||
Kirigami.Icon {
|
||||
id: icon
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: Math.min(Kirigami.Units.iconSizes.large, parent.height - delegate.reservedSpaceForLabel)
|
||||
Layout.preferredHeight: Layout.minimumHeight
|
||||
|
||||
source: modelData ? modelData.applicationIcon : ""
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
bottom: parent.bottom
|
||||
}
|
||||
visible: model.applicationRunning
|
||||
radius: width
|
||||
width: Kirigami.Units.smallSpacing
|
||||
height: width
|
||||
color: Kirigami.Theme.highlightColor
|
||||
}
|
||||
|
||||
// darken effect when hovered/pressed
|
||||
layer {
|
||||
enabled: mouseArea.pressed || mouseArea.containsMouse
|
||||
effect: ColorOverlay {
|
||||
color: Qt.rgba(0, 0, 0, 0.3)
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: in loader?
|
||||
Private.DelegateRemoveButton {
|
||||
id: removeButton
|
||||
desktopModel: delegate.desktopModel
|
||||
}
|
||||
}
|
||||
|
||||
PC3.Label {
|
||||
id: label
|
||||
visible: text.length > 0
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: delegate.reservedSpaceForLabel
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
Layout.leftMargin: -parent.anchors.leftMargin + Kirigami.Units.smallSpacing * 2
|
||||
Layout.rightMargin: -parent.anchors.rightMargin + Kirigami.Units.smallSpacing * 2
|
||||
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignTop
|
||||
maximumLineCount: 2
|
||||
elide: Text.ElideRight
|
||||
|
||||
text: model.applicationName
|
||||
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.8
|
||||
font.weight: Font.Bold
|
||||
color: "white"
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: DropShadow {
|
||||
horizontalOffset: 0
|
||||
verticalOffset: 2
|
||||
radius: 6.0
|
||||
samples: 10
|
||||
cached: true
|
||||
color: Qt.rgba(0, 0, 0, 0.3)
|
||||
}
|
||||
}
|
||||
Item { Layout.fillHeight: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,148 +1,407 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
|
||||
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Window 2.12
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Controls as QQC2
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.plasma.plasmoid 2.0
|
||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||
import org.kde.draganddrop 2.0 as DragDrop
|
||||
|
||||
import "private" as Private
|
||||
import "appdrawer"
|
||||
|
||||
import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager
|
||||
|
||||
import org.kde.plasma.components 3.0 as PC3
|
||||
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
||||
import "./delegate"
|
||||
import "./settings"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property real topMargin
|
||||
required property real bottomMargin
|
||||
required property real leftMargin
|
||||
required property real rightMargin
|
||||
|
||||
property real topMargin: 0
|
||||
property real bottomMargin: 0
|
||||
property real leftMargin: 0
|
||||
property real rightMargin: 0
|
||||
|
||||
property bool interactive: true
|
||||
|
||||
property var homeScreenState: HomeScreenState {
|
||||
interactive: root.interactive
|
||||
property Folio.HomeScreenState homeScreenState: Folio.HomeScreenState
|
||||
|
||||
totalPagesWidth: pages.contentWidth
|
||||
|
||||
appDrawerFlickable: appDrawer.flickable
|
||||
|
||||
availableScreenHeight: height - root.topMargin - root.bottomMargin
|
||||
availableScreenWidth: width - root.leftMargin - root.rightMargin
|
||||
|
||||
appDrawerBottomOffset: favoriteStrip.height
|
||||
}
|
||||
|
||||
property alias appDrawer: appDrawerLoader.item
|
||||
property alias homeScreenContents: contents
|
||||
|
||||
Component.onCompleted: {
|
||||
// ensure that homescreen is on first page
|
||||
homeScreenState.goToPageIndex(0);
|
||||
homeScreenState.resetSwipeState();
|
||||
readonly property bool dropAnimationRunning: delegateDragItem.dropAnimationRunning
|
||||
|
||||
readonly property real settingsModeHomeScreenScale: 0.8
|
||||
|
||||
onTopMarginChanged: Folio.HomeScreenState.viewTopPadding = root.topMargin
|
||||
onBottomMarginChanged: Folio.HomeScreenState.viewBottomPadding = root.bottomMargin
|
||||
onLeftMarginChanged: Folio.HomeScreenState.viewLeftPadding = root.leftMargin
|
||||
onRightMarginChanged: Folio.HomeScreenState.viewRightPadding = root.rightMargin
|
||||
|
||||
// called by any delegates when starting drag
|
||||
// returns the mapped coordinates to be used in the home screen state
|
||||
function prepareStartDelegateDrag(delegate, item) {
|
||||
swipeArea.setSkipSwipeThreshold(true);
|
||||
|
||||
delegateDragItem.delegate = delegate;
|
||||
return root.mapFromItem(item, 0, 0);
|
||||
}
|
||||
|
||||
property var desktopModel: Folio.DesktopModel {}
|
||||
|
||||
// the parent of the homescreen is a flickable that captures all flicks
|
||||
FlickContainer {
|
||||
id: flickContainer
|
||||
function cancelDelegateDrag() {
|
||||
homeScreenState.cancelDelegateDrag();
|
||||
}
|
||||
|
||||
// sets the coordinates for the folder opening/closing animation
|
||||
function prepareFolderOpen(item) {
|
||||
return root.mapFromItem(item, 0, 0);
|
||||
}
|
||||
|
||||
function openConfigure() {
|
||||
Plasmoid.internalAction("configure").trigger();
|
||||
}
|
||||
|
||||
// determine how tall an app label is, for delegate measurements
|
||||
DelegateLabel {
|
||||
id: appLabelMetrics
|
||||
text: "M\nM"
|
||||
visible: false
|
||||
|
||||
onHeightChanged: Folio.HomeScreenState.pageDelegateLabelHeight = appLabelMetrics.height
|
||||
|
||||
Component.onCompleted: {
|
||||
Folio.HomeScreenState.pageDelegateLabelWidth = Kirigami.Units.smallSpacing;
|
||||
}
|
||||
}
|
||||
|
||||
// determine screen dimensions
|
||||
Item {
|
||||
id: screenDimensions
|
||||
anchors.fill: parent
|
||||
|
||||
homeScreenState: root.homeScreenState
|
||||
|
||||
// disable flick tracking when necessary
|
||||
interactive: root.interactive && homeScreenState.currentView !== HomeScreenState.AppDrawerView && !contents.inAppletEditMode
|
||||
|
||||
// item is effectively anchored to root, while allowing flickContainer
|
||||
// to keep track of flicks
|
||||
onWidthChanged: Folio.HomeScreenState.viewWidth = width;
|
||||
onHeightChanged: Folio.HomeScreenState.viewHeight = height;
|
||||
}
|
||||
|
||||
// area that can be swiped
|
||||
MobileShell.SwipeArea {
|
||||
id: swipeArea
|
||||
anchors.fill: parent
|
||||
|
||||
interactive: root.interactive &&
|
||||
!appDrawer.flickable.moving &&
|
||||
(appDrawer.flickable.atYBeginning || // disable the swipe area when we are swiping in the app drawer, and not in drag-and-drop
|
||||
Folio.HomeScreenState.swipeState === Folio.HomeScreenState.AwaitingDraggingDelegate ||
|
||||
Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate ||
|
||||
Folio.HomeScreenState.swipeState === Folio.HomeScreenState.SwipingAppDrawerGrid)
|
||||
|
||||
onSwipeStarted: {
|
||||
homeScreenState.swipeStarted();
|
||||
}
|
||||
onSwipeEnded: {
|
||||
homeScreenState.swipeEnded();
|
||||
}
|
||||
onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => {
|
||||
homeScreenState.swipeMoved(totalDeltaX, totalDeltaY, deltaX, deltaY);
|
||||
}
|
||||
|
||||
SettingsComponent {
|
||||
id: settings
|
||||
anchors.fill: parent
|
||||
opacity: Folio.HomeScreenState.settingsOpenProgress
|
||||
visible: opacity > 0
|
||||
z: 1
|
||||
|
||||
settingsModeHomeScreenScale: root.settingsModeHomeScreenScale
|
||||
homeScreen: root
|
||||
|
||||
onRequestLeaveSettingsMode: root.leaveSettingsMode();
|
||||
}
|
||||
|
||||
Item {
|
||||
x: flickContainer.contentX
|
||||
y: flickContainer.contentY
|
||||
width: flickContainer.width
|
||||
height: flickContainer.height
|
||||
|
||||
// horizontal pages
|
||||
HomeScreenPages {
|
||||
id: pages
|
||||
homeScreenState: root.homeScreenState
|
||||
id: mainHomeScreen
|
||||
anchors.fill: parent
|
||||
|
||||
// we stop showing halfway through the animation
|
||||
opacity: 1 - Math.max(homeScreenState.appDrawerOpenProgress, homeScreenState.searchWidgetOpenProgress, homeScreenState.folderOpenProgress) * 2
|
||||
visible: opacity > 0 // prevent handlers from picking up events
|
||||
|
||||
transform: [
|
||||
Scale {
|
||||
origin.x: mainHomeScreen.width / 2
|
||||
origin.y: mainHomeScreen.height / 2
|
||||
yScale: 1 - (homeScreenState.appDrawerOpenProgress * 2) * 0.1
|
||||
xScale: 1 - (homeScreenState.appDrawerOpenProgress * 2) * 0.1
|
||||
}
|
||||
]
|
||||
|
||||
HomeScreenPages {
|
||||
id: homeScreenPages
|
||||
homeScreen: root
|
||||
|
||||
anchors.topMargin: root.topMargin
|
||||
anchors.leftMargin: Folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Left ? 0 : root.leftMargin
|
||||
anchors.rightMargin: Folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Right ? 0 : root.rightMargin
|
||||
anchors.bottomMargin: Folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Bottom ? 0 : root.bottomMargin
|
||||
|
||||
// update the model with page dimensions
|
||||
onWidthChanged: {
|
||||
homeScreenState.pageWidth = homeScreenPages.width;
|
||||
}
|
||||
onHeightChanged: {
|
||||
homeScreenState.pageHeight = homeScreenPages.height;
|
||||
}
|
||||
|
||||
transform: [
|
||||
Scale {
|
||||
// animation when settings opens
|
||||
property real scaleFactor: 1 - Folio.HomeScreenState.settingsOpenProgress * (1 - settingsModeHomeScreenScale)
|
||||
origin.x: root.leftMargin + (root.width - root.rightMargin - root.leftMargin) / 2
|
||||
origin.y: root.height * settingsModeHomeScreenScale / 2
|
||||
xScale: scaleFactor
|
||||
yScale: scaleFactor
|
||||
}
|
||||
]
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "bottom"
|
||||
when: Folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Bottom
|
||||
AnchorChanges {
|
||||
target: homeScreenPages
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: favouritesBar.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
}
|
||||
}, State {
|
||||
name: "left"
|
||||
when: Folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Left
|
||||
AnchorChanges {
|
||||
target: homeScreenPages
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: favouritesBar.right
|
||||
anchors.right: parent.right
|
||||
}
|
||||
}, State {
|
||||
name: "right"
|
||||
when: Folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Right
|
||||
AnchorChanges {
|
||||
target: homeScreenPages
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: favouritesBar.left
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: favouritesBarScrim
|
||||
color: Qt.rgba(255, 255, 255, 0.2)
|
||||
|
||||
// don't show in settings mode
|
||||
opacity: 1 - Folio.HomeScreenState.settingsOpenProgress
|
||||
visible: Folio.FolioSettings.showFavouritesBarBackground
|
||||
|
||||
anchors.top: Folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Bottom ? favouritesBar.top : parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: Folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Right ? favouritesBar.left : parent.left
|
||||
anchors.right: Folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Left ? favouritesBar.right : parent.right
|
||||
|
||||
// because of the scale animation, we need to extend the panel out a bit
|
||||
anchors.topMargin: Folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Bottom ? 0 : -Kirigami.Units.gridUnit * 5
|
||||
anchors.bottomMargin: -Kirigami.Units.gridUnit * 5
|
||||
anchors.leftMargin: Folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Right ? 0 : -Kirigami.Units.gridUnit * 5
|
||||
anchors.rightMargin: Folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Left ? 0 : -Kirigami.Units.gridUnit * 5
|
||||
}
|
||||
|
||||
FavouritesBar {
|
||||
id: favouritesBar
|
||||
homeScreen: root
|
||||
leftMargin: root.leftMargin
|
||||
topMargin: root.topMargin
|
||||
|
||||
// don't show in settings mode
|
||||
opacity: 1 - Folio.HomeScreenState.settingsOpenProgress
|
||||
visible: opacity > 0
|
||||
|
||||
// one is ignored as anchors are set
|
||||
height: Kirigami.Units.gridUnit * 6
|
||||
width: Kirigami.Units.gridUnit * 6
|
||||
|
||||
// account for panels
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: root.topMargin
|
||||
anchors.bottomMargin: root.bottomMargin
|
||||
anchors.leftMargin: root.leftMargin
|
||||
anchors.rightMargin: root.rightMargin
|
||||
|
||||
// animation when app drawer is being shown
|
||||
opacity: root.appDrawer ? 1 - root.appDrawer.openFactor : 1
|
||||
transform: Translate {
|
||||
y: root.appDrawer ? (-pages.height / 20) * root.appDrawer.openFactor : 0
|
||||
}
|
||||
|
||||
contentWidth: Math.max(width, width * Math.ceil(contents.itemsBoundingRect.width/width)) + (contents.launcherDragManager.active ? width : 0)
|
||||
showAddPageIndicator: contents.launcherDragManager.active
|
||||
|
||||
HomeScreenContents {
|
||||
id: contents
|
||||
desktopModel: root.desktopModel
|
||||
homeScreenState: root.homeScreenState
|
||||
|
||||
height: pages.height
|
||||
width: pages.width * 100
|
||||
|
||||
favoriteStrip: favoriteStrip
|
||||
homeScreenPages: pages
|
||||
}
|
||||
|
||||
footer: FavoriteStrip {
|
||||
id: favoriteStrip
|
||||
|
||||
appletsLayout: contents.appletsLayout
|
||||
visible: favoriteStrip.flow.children.length > 0 || contents.launcherDragManager.active || contents.containsDrag
|
||||
opacity: contents.launcherDragManager.active && root.desktopModel.favoriteCount >= root.desktopModel.maxFavoriteCount ? 0.3 : 1
|
||||
|
||||
TapHandler {
|
||||
target: favoriteStrip
|
||||
enabled: flickContainer.interactive // only interactive when we flick on homescreen
|
||||
onTapped: {
|
||||
//Hides icons close button
|
||||
contents.appletsLayout.appletsLayoutInteracted();
|
||||
contents.appletsLayout.editMode = false;
|
||||
states: [
|
||||
State {
|
||||
name: "bottom"
|
||||
when: Folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Bottom
|
||||
AnchorChanges {
|
||||
target: favouritesBar
|
||||
anchors.top: undefined
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
}
|
||||
onLongPressed: {
|
||||
if (homeScreenState.currentSwipeState === HomeScreenState.DeterminingType) {
|
||||
// only go into edit mode when not in a swipe
|
||||
contents.appletsLayout.editMode = true;
|
||||
}
|
||||
PropertyChanges {
|
||||
target: favouritesBar
|
||||
height: Kirigami.Units.gridUnit * 6
|
||||
}
|
||||
onPressedChanged: root.parent.focus = true;
|
||||
}, State {
|
||||
name: "left"
|
||||
when: Folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Left
|
||||
AnchorChanges {
|
||||
target: favouritesBar
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: undefined
|
||||
}
|
||||
PropertyChanges {
|
||||
target: favouritesBar
|
||||
width: Kirigami.Units.gridUnit * 6
|
||||
}
|
||||
}, State {
|
||||
name: "right"
|
||||
when: Folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Right
|
||||
AnchorChanges {
|
||||
target: favouritesBar
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: undefined
|
||||
anchors.right: parent.right
|
||||
}
|
||||
PropertyChanges {
|
||||
target: favouritesBar
|
||||
width: Kirigami.Units.gridUnit * 6
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Item {
|
||||
id: pageIndicatorWrapper
|
||||
property bool favouritesBarAtBottom: Folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Bottom
|
||||
|
||||
// don't show in settings mode
|
||||
opacity: 1 - Folio.HomeScreenState.settingsOpenProgress
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: favouritesBarAtBottom ? favouritesBar.top : parent.bottom
|
||||
|
||||
anchors.topMargin: root.topMargin
|
||||
anchors.leftMargin: root.leftMargin
|
||||
anchors.rightMargin: root.rightMargin
|
||||
anchors.bottomMargin: favouritesBarAtBottom ? 0 : (root.bottomMargin + Kirigami.Units.largeSpacing)
|
||||
|
||||
QQC2.PageIndicator {
|
||||
visible: count > 1
|
||||
Kirigami.Theme.inherit: false
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
currentIndex: Folio.HomeScreenState.currentPage
|
||||
count: Folio.PageListModel.length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// folder view
|
||||
FolderView {
|
||||
id: folderView
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: root.topMargin
|
||||
anchors.leftMargin: root.leftMargin
|
||||
anchors.rightMargin: root.rightMargin
|
||||
anchors.bottomMargin: root.bottomMargin
|
||||
|
||||
homeScreen: root
|
||||
opacity: homeScreenState.folderOpenProgress
|
||||
transform: Translate { y: folderView.opacity > 0 ? 0 : folderView.height }
|
||||
}
|
||||
|
||||
// drag and drop component
|
||||
DelegateDragItem {
|
||||
id: delegateDragItem
|
||||
}
|
||||
|
||||
// bottom app drawer
|
||||
AppDrawer {
|
||||
id: appDrawer
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
homeScreen: root
|
||||
|
||||
// we only start showing it halfway through
|
||||
opacity: homeScreenState.appDrawerOpenProgress < 0.5 ? 0 : (homeScreenState.appDrawerOpenProgress - 0.5) * 2
|
||||
|
||||
// position for animation
|
||||
property real animationY: (1 - homeScreenState.appDrawerOpenProgress) * (Kirigami.Units.gridUnit * 2)
|
||||
|
||||
// move the app drawer out of the way if it is not visible
|
||||
// NOTE: we do this instead of setting visible to false, because
|
||||
// it doesn't mess with app drag and drop from the app drawer
|
||||
y: (opacity > 0) ? animationY : parent.height
|
||||
|
||||
headerHeight: Math.round(Kirigami.Units.gridUnit * 5)
|
||||
headerItem: AppDrawerHeader {}
|
||||
|
||||
// account for panels
|
||||
topPadding: root.topMargin
|
||||
bottomPadding: root.bottomMargin
|
||||
leftPadding: root.leftMargin
|
||||
rightPadding: root.rightMargin
|
||||
|
||||
Connections {
|
||||
target: Folio.HomeScreenState
|
||||
|
||||
function onAppDrawerClosed() {
|
||||
// reset app drawer position when closed
|
||||
appDrawer.flickable.contentY = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// search component
|
||||
MobileShell.KRunnerScreen {
|
||||
id: searchWidget
|
||||
anchors.fill: parent
|
||||
|
||||
opacity: homeScreenState.searchWidgetOpenProgress
|
||||
visible: opacity > 0
|
||||
transform: Translate { y: (1 - homeScreenState.searchWidgetOpenProgress) * (-Kirigami.Units.gridUnit * 2) }
|
||||
|
||||
// focus the search bar if it opens
|
||||
Connections {
|
||||
target: Folio.HomeScreenState
|
||||
|
||||
function onSearchWidgetOpenProgressChanged() {
|
||||
if (homeScreenState.searchWidgetOpenProgress === 1.0) {
|
||||
searchWidget.requestFocus();
|
||||
} else {
|
||||
// TODO this gets called a lot, can we have a more performant way?
|
||||
root.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// app drawer
|
||||
AppDrawerLoader {
|
||||
id: appDrawerLoader
|
||||
anchors.fill: parent
|
||||
homeScreenState: root.homeScreenState
|
||||
|
||||
// account for panels
|
||||
topPadding: root.topMargin
|
||||
bottomPadding: root.bottomMargin
|
||||
leftPadding: root.leftMargin
|
||||
rightPadding: root.rightMargin
|
||||
|
||||
onRequestedClose: {
|
||||
homeScreenState.closeSearchWidget();
|
||||
}
|
||||
|
||||
anchors.topMargin: root.topMargin
|
||||
anchors.bottomMargin: root.bottomMargin
|
||||
anchors.leftMargin: root.leftMargin
|
||||
anchors.rightMargin: root.rightMargin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,239 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Window 2.12
|
||||
import QtQuick.Layouts 1.1
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.plasma.plasmoid 2.0
|
||||
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.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
||||
import "private" as Private
|
||||
|
||||
DragDrop.DropArea {
|
||||
id: dropArea
|
||||
|
||||
required property var homeScreenState
|
||||
|
||||
required property Folio.DesktopModel desktopModel
|
||||
property var applicationListModel: Folio.ApplicationListModel
|
||||
|
||||
property alias launcherDelegate: launcherRepeater.delegate
|
||||
property alias launcherModel: launcherRepeater.model
|
||||
property alias launcherRepeater: launcherRepeater
|
||||
property alias itemsBoundingRect: appletsLayout.childrenRect
|
||||
|
||||
property alias appletsLayout: appletsLayout
|
||||
|
||||
property FavoriteStrip favoriteStrip
|
||||
property HomeScreenPages homeScreenPages
|
||||
|
||||
property LauncherDragManager launcherDragManager: LauncherDragManager {
|
||||
id: launcherDragManager
|
||||
parent: {
|
||||
let candidate = dropArea;
|
||||
while (candidate.parent) {
|
||||
candidate = candidate.parent;
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
anchors.fill: parent
|
||||
z: 999999
|
||||
appletsLayout: dropArea.appletsLayout
|
||||
favoriteStrip: dropArea.favoriteStrip
|
||||
desktopModel: dropArea.desktopModel
|
||||
}
|
||||
|
||||
property bool inAppletEditMode: false
|
||||
|
||||
|
||||
Connections {
|
||||
target: plasmoid
|
||||
function onEditModeChanged() {
|
||||
appletsLayout.editMode = plasmoid.editMode
|
||||
}
|
||||
}
|
||||
|
||||
onDragEnter: {
|
||||
event.accept(event.proposedAction);
|
||||
launcherDragManager.active = true;
|
||||
}
|
||||
|
||||
onDragMove: {
|
||||
let posInFavorites = favoriteStrip.mapFromItem(this, event.x, event.y);
|
||||
if (posInFavorites.y > 0) {
|
||||
if (desktopModel.favoriteCount >= desktopModel.maxFavoriteCount) {
|
||||
launcherDragManager.hideSpacer();
|
||||
} else {
|
||||
launcherDragManager.showSpacerAtPos(event.x, event.y, favoriteStrip);
|
||||
}
|
||||
appletsLayout.hidePlaceHolder();
|
||||
} else {
|
||||
appletsLayout.showPlaceHolderAt(
|
||||
Qt.rect(event.x - appletsLayout.defaultItemWidth / 2,
|
||||
event.y - appletsLayout.defaultItemHeight / 2,
|
||||
appletsLayout.defaultItemWidth,
|
||||
appletsLayout.defaultItemHeight)
|
||||
);
|
||||
launcherDragManager.hideSpacer();
|
||||
|
||||
let scenePos = mapToItem(null, event.x, event.y);
|
||||
//SCROLL LEFT
|
||||
if (scenePos.x < Kirigami.Units.gridUnit) {
|
||||
homeScreenPages.scrollLeft();
|
||||
//SCROLL RIGHT
|
||||
} else if (scenePos.x > homeScreenPages.width - Kirigami.Units.gridUnit) {
|
||||
homeScreenPages.scrollRight();
|
||||
//DON't SCROLL
|
||||
} else {
|
||||
homeScreenPages.stopScroll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onDragLeave: {
|
||||
appletsLayout.hidePlaceHolder();
|
||||
launcherDragManager.active = false;
|
||||
}
|
||||
|
||||
preventStealing: true
|
||||
|
||||
onDrop: {
|
||||
launcherDragManager.active = false;
|
||||
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) {
|
||||
if (desktopModel.favoriteCount >= desktopModel.maxFavoriteCount ) {
|
||||
return;
|
||||
}
|
||||
|
||||
let pos = Math.min(desktopModel.count, Math.floor(posInFavorites.x/favoriteStrip.cellWidth))
|
||||
desktopModel.addFavorite(storageId, pos, Folio.ApplicationListModel.Favorites)
|
||||
let item = launcherRepeater.itemAt(pos);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
let pos = desktopModel.count;
|
||||
desktopModel.addFavorite(storageId, pos, Folio.ApplicationListModel.Desktop)
|
||||
let item = launcherRepeater.itemAt(pos);
|
||||
|
||||
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();
|
||||
} else {
|
||||
plasmoid.processMimeData(event.mimeData,
|
||||
event.x - appletsLayout.placeHolder.width / 2, event.y - appletsLayout.placeHolder.height / 2);
|
||||
event.accept(event.proposedAction);
|
||||
appletsLayout.hidePlaceHolder();
|
||||
}
|
||||
}
|
||||
|
||||
ContainmentLayoutManager.AppletsLayout {
|
||||
id: appletsLayout
|
||||
|
||||
anchors {
|
||||
fill: parent
|
||||
bottomMargin: dropArea.favoriteStrip ? dropArea.favoriteStrip.height : 0
|
||||
}
|
||||
|
||||
signal appletsLayoutInteracted
|
||||
|
||||
TapHandler {
|
||||
target: homeScreenPages
|
||||
enabled: homeScreenState.currentView === HomeScreenState.PageView && homeScreenState.interactive
|
||||
onTapped: {
|
||||
//Hides icons close button
|
||||
appletsLayout.appletsLayoutInteracted();
|
||||
appletsLayout.editMode = false;
|
||||
appletsLayout.forceActiveFocus();
|
||||
}
|
||||
onLongPressed: {
|
||||
if (homeScreenState.currentSwipeState === HomeScreenState.DeterminingType) {
|
||||
// only go into edit mode when not in a swipe
|
||||
appletsLayout.editMode = true;
|
||||
}
|
||||
}
|
||||
onPressedChanged: appletsLayout.focus = 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: Kirigami.Units.gridUnit * 3
|
||||
minimumItemHeight: minimumItemWidth
|
||||
|
||||
defaultItemWidth: Kirigami.Units.gridUnit * 6
|
||||
defaultItemHeight: defaultItemWidth
|
||||
|
||||
acceptsAppletCallback: function(applet, x, y) {
|
||||
print("Applet: "+applet+" "+x+" "+y)
|
||||
return true;
|
||||
}
|
||||
appletContainerComponent: MobileAppletContainer {
|
||||
homeScreenState: dropArea.homeScreenState
|
||||
launcherDragManager: dropArea.launcherDragManager
|
||||
|
||||
onEditModeChanged: {
|
||||
inAppletEditMode = editMode;
|
||||
}
|
||||
}
|
||||
|
||||
placeHolder: ContainmentLayoutManager.PlaceHolder {}
|
||||
//FIXME: move
|
||||
PlasmaComponents.Label {
|
||||
id: metrics
|
||||
text: "M\nM"
|
||||
visible: false
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.9
|
||||
}
|
||||
LauncherRepeater {
|
||||
id: launcherRepeater
|
||||
desktopModel: dropArea.desktopModel
|
||||
homeScreenState: dropArea.homeScreenState
|
||||
cellWidth: appletsLayout.cellWidth
|
||||
cellHeight: appletsLayout.cellHeight
|
||||
appletsLayout: appletsLayout
|
||||
favoriteStrip: dropArea.favoriteStrip
|
||||
onScrollLeftRequested: homeScreenPages.scrollLeft()
|
||||
onScrollRightRequested: homeScreenPages.scrollRight()
|
||||
onStopScrollRequested: homeScreenPages.stopScroll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState
|
||||
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
import org.kde.kirigami 2.10 as Kirigami
|
||||
|
||||
import "./delegate"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property int pageNum
|
||||
|
||||
property var pageModel
|
||||
property var homeScreen
|
||||
|
||||
// background when in settings view (for rearranging pages)
|
||||
Rectangle {
|
||||
id: settingsViewBackground
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(255, 255, 255, 0.2)
|
||||
opacity: Folio.HomeScreenState.settingsOpenProgress
|
||||
radius: Kirigami.Units.largeSpacing
|
||||
}
|
||||
|
||||
// square that shows when hovering over a spot to drop a delegate on
|
||||
PlaceholderDelegate {
|
||||
id: dragDropFeedback
|
||||
width: Folio.HomeScreenState.pageCellWidth
|
||||
height: Folio.HomeScreenState.pageCellHeight
|
||||
|
||||
property var dropPosition: Folio.HomeScreenState.dragState.candidateDropPosition
|
||||
|
||||
// only show if it is an empty spot on this page
|
||||
visible: Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate &&
|
||||
dropPosition.location === Folio.DelegateDragPosition.Pages &&
|
||||
dropPosition.page === root.pageNum &&
|
||||
Folio.HomeScreenState.getPageDelegateAt(root.pageNum, dropPosition.pageRow, dropPosition.pageColumn) === null
|
||||
|
||||
x: dropPosition.pageColumn * Folio.HomeScreenState.pageCellWidth
|
||||
y: dropPosition.pageRow * Folio.HomeScreenState.pageCellHeight
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.pageModel
|
||||
|
||||
delegate: Item {
|
||||
id: delegate
|
||||
|
||||
property Folio.FolioPageDelegate pageDelegate: model.delegate
|
||||
property int row: pageDelegate.row
|
||||
property int column: pageDelegate.column
|
||||
|
||||
property var dragState: Folio.HomeScreenState.dragState
|
||||
|
||||
property bool isDropPositionThis: dragState.candidateDropPosition.location === Folio.DelegateDragPosition.Pages &&
|
||||
dragState.candidateDropPosition.page === root.pageNum &&
|
||||
dragState.candidateDropPosition.pageRow === delegate.pageDelegate.row &&
|
||||
dragState.candidateDropPosition.pageColumn === delegate.pageDelegate.column
|
||||
|
||||
property bool isAppHoveredOver: Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate &&
|
||||
dragState.dropDelegate &&
|
||||
dragState.dropDelegate.type === Folio.FolioDelegate.Application &&
|
||||
isDropPositionThis
|
||||
|
||||
implicitWidth: Folio.HomeScreenState.pageCellWidth
|
||||
implicitHeight: Folio.HomeScreenState.pageCellHeight
|
||||
width: Folio.HomeScreenState.pageCellWidth
|
||||
height: Folio.HomeScreenState.pageCellHeight
|
||||
|
||||
x: column * Folio.HomeScreenState.pageCellWidth
|
||||
y: row * Folio.HomeScreenState.pageCellHeight
|
||||
|
||||
visible: row >= 0 && row < Folio.HomeScreenState.pageRows &&
|
||||
column >= 0 && column < Folio.HomeScreenState.pageColumns
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
|
||||
sourceComponent: {
|
||||
if (delegate.pageDelegate.type === Folio.FolioDelegate.Application) {
|
||||
return appComponent;
|
||||
} else if (delegate.pageDelegate.type === Folio.FolioDelegate.Folder) {
|
||||
return folderComponent;
|
||||
} else {
|
||||
return noneComponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: noneComponent
|
||||
|
||||
Item {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: appComponent
|
||||
|
||||
AppDelegate {
|
||||
id: appDelegate
|
||||
name: Folio.FolioSettings.showPagesAppLabels ? delegate.pageDelegate.application.name : ""
|
||||
application: delegate.pageDelegate.application
|
||||
turnToFolder: delegate.isAppHoveredOver
|
||||
turnToFolderAnimEnabled: Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate
|
||||
|
||||
// do not show if the drop animation is running to this delegate
|
||||
visible: !(root.homeScreen.dropAnimationRunning && delegate.isDropPositionThis)
|
||||
|
||||
// don't show label in drag and drop mode
|
||||
labelOpacity: delegate.opacity
|
||||
|
||||
onPressAndHold: {
|
||||
let mappedCoords = root.homeScreen.prepareStartDelegateDrag(delegate.pageDelegate, appDelegate.delegateItem);
|
||||
Folio.HomeScreenState.startDelegatePageDrag(
|
||||
mappedCoords.x,
|
||||
mappedCoords.y,
|
||||
root.pageNum,
|
||||
delegate.pageDelegate.row,
|
||||
delegate.pageDelegate.column
|
||||
);
|
||||
|
||||
contextMenu.open();
|
||||
}
|
||||
onPressAndHoldReleased: {
|
||||
// cancel the event if the delegate is not dragged
|
||||
if (Folio.HomeScreenState.swipeState === Folio.HomeScreenState.AwaitingDraggingDelegate) {
|
||||
homeScreen.cancelDelegateDrag();
|
||||
}
|
||||
}
|
||||
|
||||
onRightMousePress: {
|
||||
contextMenu.open();
|
||||
}
|
||||
|
||||
// TODO don't use loader, and move outside to a page to make it more performant
|
||||
ContextMenuLoader {
|
||||
id: contextMenu
|
||||
|
||||
// close menu when drag starts
|
||||
Connections {
|
||||
target: Folio.HomeScreenState
|
||||
|
||||
function onSwipeStateChanged() {
|
||||
if (Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate) {
|
||||
contextMenu.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
icon.name: "emblem-favorite"
|
||||
text: i18n("Remove")
|
||||
onTriggered: root.pageModel.removeDelegate(delegate.row, delegate.column)
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: folderComponent
|
||||
|
||||
AppFolderDelegate {
|
||||
id: appFolderDelegate
|
||||
name: Folio.FolioSettings.showPagesAppLabels ? delegate.pageDelegate.folder.name : ""
|
||||
folder: delegate.pageDelegate.folder
|
||||
|
||||
// do not show if the drop animation is running to this delegate, and the drop delegate is a folder
|
||||
visible: !(root.homeScreen.dropAnimationRunning &&
|
||||
delegate.isDropPositionThis &&
|
||||
delegate.dragState.dropDelegate.type === Folio.FolioDelegate.Folder)
|
||||
|
||||
// don't show label in drag and drop mode
|
||||
labelOpacity: delegate.opacity
|
||||
|
||||
appHoveredOver: delegate.isAppHoveredOver
|
||||
|
||||
onAfterClickAnimation: {
|
||||
const pos = homeScreen.prepareFolderOpen(appFolderDelegate.contentItem);
|
||||
Folio.HomeScreenState.openFolder(pos.x, pos.y, folder);
|
||||
}
|
||||
|
||||
onPressAndHold: {
|
||||
let mappedCoords = root.homeScreen.prepareStartDelegateDrag(delegate.pageDelegate, appFolderDelegate.delegateItem);
|
||||
Folio.HomeScreenState.startDelegatePageDrag(
|
||||
mappedCoords.x,
|
||||
mappedCoords.y,
|
||||
root.pageNum,
|
||||
delegate.pageDelegate.row,
|
||||
delegate.pageDelegate.column
|
||||
);
|
||||
|
||||
contextMenu.open();
|
||||
}
|
||||
|
||||
onPressAndHoldReleased: {
|
||||
// cancel the event if the delegate is not dragged
|
||||
if (Folio.HomeScreenState.swipeState === Folio.HomeScreenState.AwaitingDraggingDelegate) {
|
||||
homeScreen.cancelDelegateDrag();
|
||||
}
|
||||
}
|
||||
|
||||
onRightMousePress: {
|
||||
contextMenu.open();
|
||||
}
|
||||
|
||||
// TODO don't use loader, and move outside to a page to make it more performant
|
||||
ContextMenuLoader {
|
||||
id: contextMenu
|
||||
|
||||
// close menu when drag starts
|
||||
Connections {
|
||||
target: Folio.HomeScreenState
|
||||
|
||||
function onSwipeStateChanged() {
|
||||
if (Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate) {
|
||||
contextMenu.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
icon.name: "emblem-favorite"
|
||||
text: i18n("Remove")
|
||||
onTriggered: root.pageModel.removeDelegate(delegate.row, delegate.column)
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,161 +1,46 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
|
||||
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Window 2.12
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
import org.kde.plasma.components 3.0 as PC3
|
||||
import org.kde.kirigami 2.10 as Kirigami
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
||||
import org.kde.plasma.plasmoid 2.0
|
||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||
import org.kde.draganddrop 2.0 as DragDrop
|
||||
MouseArea {
|
||||
id: root
|
||||
|
||||
import "private" as Private
|
||||
property var homeScreen
|
||||
|
||||
import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager
|
||||
readonly property real verticalMargin: Math.round((Folio.HomeScreenState.pageHeight - Folio.HomeScreenState.pageContentHeight) / 2)
|
||||
readonly property real horizontalMargin: Math.round((Folio.HomeScreenState.pageWidth - Folio.HomeScreenState.pageContentWidth) / 2)
|
||||
|
||||
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
||||
onPressAndHold: Folio.HomeScreenState.openSettingsView()
|
||||
|
||||
MobileShell.Flickable {
|
||||
id: mainFlickable
|
||||
|
||||
required property var homeScreenState
|
||||
|
||||
property Item footer
|
||||
Repeater {
|
||||
model: Folio.PageListModel
|
||||
|
||||
property bool showAddPageIndicator: false
|
||||
delegate: HomeScreenPage {
|
||||
id: homeScreenPage
|
||||
pageNum: model.index
|
||||
pageModel: model.delegate
|
||||
homeScreen: root.homeScreen
|
||||
|
||||
contentX: homeScreenState.xPosition
|
||||
|
||||
contentHeight: height
|
||||
interactive: false
|
||||
anchors.fill: root
|
||||
anchors.leftMargin: root.horizontalMargin
|
||||
anchors.rightMargin: root.horizontalMargin
|
||||
anchors.topMargin: root.verticalMargin
|
||||
anchors.bottomMargin: root.verticalMargin
|
||||
|
||||
signal cancelEditModeForItemsRequested
|
||||
onDragStarted: cancelEditModeForItemsRequested()
|
||||
onDragEnded: cancelEditModeForItemsRequested()
|
||||
onFlickStarted: cancelEditModeForItemsRequested()
|
||||
onFlickEnded: cancelEditModeForItemsRequested()
|
||||
// animation so that full opacity is only when the page is in view
|
||||
opacity: 1 - Math.min(1, Math.max(0, Math.abs(-Folio.HomeScreenState.pageViewX - root.width * pageNum) / root.width))
|
||||
|
||||
onFooterChanged: {
|
||||
if (footer) {
|
||||
footer.parent = mainFlickable;
|
||||
footer.anchors.left = mainFlickable.left;
|
||||
footer.anchors.bottom = mainFlickable.bottom;
|
||||
footer.anchors.right = mainFlickable.right;
|
||||
}
|
||||
}
|
||||
|
||||
// autoscroll between pages (when holding a delegate to go to a new page)
|
||||
function scrollLeft() {
|
||||
if (mainFlickable.atXBeginning) {
|
||||
return;
|
||||
}
|
||||
autoScrollTimer.scrollRight = false;
|
||||
autoScrollTimer.running = true;
|
||||
scrollLeftIndicator.opacity = 1;
|
||||
scrollRightIndicator.opacity = 0;
|
||||
}
|
||||
|
||||
function scrollRight() {
|
||||
if (mainFlickable.atXEnd) {
|
||||
return;
|
||||
}
|
||||
autoScrollTimer.scrollRight = true;
|
||||
autoScrollTimer.running = true;
|
||||
scrollLeftIndicator.opacity = 0;
|
||||
scrollRightIndicator.opacity = 1;
|
||||
}
|
||||
|
||||
function stopScroll() {
|
||||
autoScrollTimer.running = false;
|
||||
scrollLeftIndicator.opacity = 0;
|
||||
scrollRightIndicator.opacity = 0;
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: autoScrollTimer
|
||||
property bool scrollRight: true
|
||||
repeat: true
|
||||
interval: 1500
|
||||
onTriggered: {
|
||||
homeScreenState.animateGoToPageIndex(Math.max(0, homeScreenState.currentPageIndex + (scrollRight ? 1 : -1)), Kirigami.Units.longDuration * 2);
|
||||
}
|
||||
}
|
||||
|
||||
PlasmaComponents.PageIndicator {
|
||||
id: pageIndicator
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
bottomMargin: mainFlickable.footer ? mainFlickable.footer.height : 0
|
||||
}
|
||||
|
||||
Kirigami.Theme.inherit: false
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||
|
||||
parent: mainFlickable
|
||||
visible: count > 1
|
||||
|
||||
count: homeScreenState.pagesCount
|
||||
currentIndex: homeScreenState.currentPageIndex
|
||||
|
||||
delegate: Rectangle {
|
||||
property bool isAddPageIndicator: index === pageIndicator.count-1 && mainFlickable.showAddPageIndicator
|
||||
implicitWidth: Kirigami.Units.gridUnit/2
|
||||
implicitHeight: implicitWidth
|
||||
|
||||
radius: width
|
||||
color: isAddPageIndicator ? "transparent" : Kirigami.Theme.textColor
|
||||
|
||||
PlasmaComponents.Label {
|
||||
anchors.centerIn: parent
|
||||
visible: parent.isAddPageIndicator
|
||||
text: "⊕"
|
||||
// x position of page
|
||||
transform: Translate {
|
||||
x: root.width * index + Folio.HomeScreenState.pageViewX
|
||||
}
|
||||
|
||||
opacity: index === pageIndicator.currentIndex ? 0.9 : pressed ? 0.7 : 0.5
|
||||
Behavior on opacity {
|
||||
OpacityAnimator {
|
||||
duration: Kirigami.Units.longDuration
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
z: 9999999
|
||||
anchors.fill: parent
|
||||
parent: {
|
||||
let candidate = mainFlickable;
|
||||
while (candidate.parent) {
|
||||
candidate = candidate.parent;
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
Private.ScrollIndicator {
|
||||
id: scrollLeftIndicator
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: Kirigami.Units.smallSpacing
|
||||
}
|
||||
elementId: "left-arrow"
|
||||
}
|
||||
Private.ScrollIndicator {
|
||||
id: scrollRightIndicator
|
||||
anchors {
|
||||
right: parent.right
|
||||
rightMargin: Kirigami.Units.smallSpacing
|
||||
}
|
||||
elementId: "right-arrow"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,434 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
||||
|
||||
/**
|
||||
* State object for the homescreen.
|
||||
*
|
||||
* We expose the data necessary to make custom "swipe-down" gestures from the page view.
|
||||
*/
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
// whether the homescreen elements should be interactive, or disabled
|
||||
required property bool interactive
|
||||
|
||||
required property real totalPagesWidth
|
||||
|
||||
required property var appDrawerFlickable
|
||||
|
||||
// dimensions of the homescreen area (not including top panel and task panel)
|
||||
required property real availableScreenHeight
|
||||
required property real availableScreenWidth
|
||||
|
||||
// offset from the bottom of the screen that the app drawer starts from,
|
||||
// would be the height favourites strip
|
||||
required property real appDrawerBottomOffset
|
||||
|
||||
// ~~ positioning ~~
|
||||
|
||||
// xPosition:
|
||||
// We start at 0, which is the beginning x position of the row of pages (left-most side).
|
||||
// Increasing x moves *right* toward the next page.
|
||||
//
|
||||
// yPosition:
|
||||
// Increasing y results in moving *up* in the view.
|
||||
// appDrawerOpenYPosition - The app drawer is opened (app drawer flickable is active iff it's not at the beginning).
|
||||
// pagesYPosition - The app drawer is closed. Homescreen pages are visible, can swipe left/right between pages.
|
||||
property real xPosition: 0
|
||||
property real yPosition: pagesYPosition
|
||||
|
||||
// direction of the movement
|
||||
property bool movingRight: false
|
||||
property bool movingUp: false
|
||||
|
||||
// used for calculating movement direction
|
||||
property real oldXPosition: 0
|
||||
property real oldYPosition: 0
|
||||
onXPositionChanged: {
|
||||
movingRight = xPosition > oldXPosition;
|
||||
oldXPosition = xPosition;
|
||||
}
|
||||
onYPositionChanged: {
|
||||
movingUp = yPosition > oldYPosition;
|
||||
oldYPosition = yPosition;
|
||||
|
||||
// speed up the animation
|
||||
if (currentSwipeState == HomeScreenState.SwipingAppDrawerVisibility && yPosition <= 0) {
|
||||
root.currentView = HomeScreenState.AppDrawerBeginningView;
|
||||
root.resetSwipeState();
|
||||
openDrawerAnim.stop();
|
||||
}
|
||||
}
|
||||
|
||||
// yPosition when the homescreen pages are visible
|
||||
readonly property real pagesYPosition: availableScreenHeight - appDrawerBottomOffset
|
||||
|
||||
// yPosition when drawer is open
|
||||
readonly property real appDrawerOpenYPosition: 0
|
||||
|
||||
// ~~ active state ~~
|
||||
|
||||
enum View {
|
||||
PageView, // we are viewing the horizontal row of pages
|
||||
AppDrawerBeginningView, // we are at the top of the app drawer (could either close it or scroll down)
|
||||
AppDrawerView // we are in the app drawer, and not at the top of it
|
||||
}
|
||||
|
||||
// the current view of the homescreen
|
||||
property var currentView: HomeScreenState.PageView
|
||||
|
||||
// number of homescreen pages
|
||||
readonly property int pagesCount: Math.floor(totalPagesWidth / pageWidth)
|
||||
|
||||
// current homescreen page index
|
||||
readonly property int currentPageIndex: {
|
||||
let candidateIndex = Math.round(xPosition / (pageSpacing + pageWidth));
|
||||
return Math.max(0, Math.min(pagesCount - 1, candidateIndex));
|
||||
}
|
||||
|
||||
enum PageViewSwipeState {
|
||||
SwipingPages, // horizontal movement between pages
|
||||
SwipingAppDrawerVisibility, // opening/closing app drawer
|
||||
SwipingAppDrawerList, // scrolling app drawer
|
||||
SwipingPagesDown, // custom gesture can be implemented for swiping down on the page view
|
||||
DeterminingType
|
||||
}
|
||||
|
||||
// when we are at the PageView view, we need to distinguish horizontal swipes (changing pages)
|
||||
// and vertical swipes (opening drawer)
|
||||
property var currentSwipeState: HomeScreenState.DeterminingType
|
||||
|
||||
// threshold of movement in a direction before we count that as the defining SwipeState
|
||||
readonly property real horizontalSwipeStateDetermineThreshold: 2
|
||||
readonly property real verticalSwipeStateDetermineThreshold: 2
|
||||
|
||||
// we put the offset position here when determining the swipe type, before we
|
||||
// transfer movement over to xPosition and yPosition
|
||||
property real xDetermineSwipePosition: 0
|
||||
property real yDetermineSwipePosition: 0
|
||||
|
||||
// whether animations are currently running
|
||||
property bool animationsRunning: openDrawerAnim.running || closeDrawerAnim.running || xAnim.running
|
||||
|
||||
// whether the app drawer flickable should be interactive
|
||||
property bool appDrawerInteractive: currentView === HomeScreenState.AppDrawerView
|
||||
|
||||
// ~~ measurement constants ~~
|
||||
|
||||
// dimensions of a page
|
||||
readonly property real pageHeight: availableScreenHeight
|
||||
readonly property real pageWidth: availableScreenWidth
|
||||
|
||||
// spacing between each homescreen page
|
||||
readonly property real pageSpacing: 0
|
||||
|
||||
// ~~ signals and functions ~~
|
||||
|
||||
// cancel edit mode
|
||||
signal cancelEditModeForItemsRequested
|
||||
|
||||
// cancel all animated moving, as another flick source is taking over
|
||||
signal cancelAnimations()
|
||||
onCancelAnimations: {
|
||||
openDrawerAnim.stop();
|
||||
closeDrawerAnim.stop();
|
||||
xAnim.stop();
|
||||
}
|
||||
|
||||
// expose signals necessary to implement any behaviour for the "swipe-down" action on the page view
|
||||
signal swipeDownGestureBegin
|
||||
signal swipeDownGestureEnd
|
||||
signal swipeDownGestureOffset(real value)
|
||||
|
||||
// be very careful when resetting the swipe state
|
||||
// ensure that we aren't in the middle of a gesture
|
||||
function resetSwipeState() {
|
||||
currentSwipeState = HomeScreenState.DeterminingType;
|
||||
xDetermineSwipePosition = 0;
|
||||
yDetermineSwipePosition = 0;
|
||||
}
|
||||
|
||||
function openAppDrawer() {
|
||||
openDrawerAnim.restart();
|
||||
}
|
||||
|
||||
function openAppDrawerInstantly() {
|
||||
yPosition = appDrawerOpenYPosition;
|
||||
currentView = HomeScreenState.AppDrawerBeginningView;
|
||||
}
|
||||
|
||||
function closeAppDrawer() {
|
||||
closeDrawerAnim.restart();
|
||||
}
|
||||
|
||||
function closeAppDrawerInstantly() {
|
||||
yPosition = pagesYPosition;
|
||||
currentView = HomeScreenState.PageView;
|
||||
}
|
||||
|
||||
// get the xPosition where the page will be centered on the screen
|
||||
function xPositionFromPageIndex(index) {
|
||||
return index * (pageWidth + pageSpacing);
|
||||
}
|
||||
|
||||
// instantly go to the page index
|
||||
function goToPageIndex(index) {
|
||||
xPosition = xPositionFromPageIndex(index);
|
||||
}
|
||||
|
||||
// go to the page index, animated
|
||||
function animateGoToPageIndex(index, duration) {
|
||||
xAnim.duration = duration;
|
||||
xAnim.to = xPositionFromPageIndex(index);
|
||||
xAnim.restart();
|
||||
}
|
||||
|
||||
// update the position using an offset
|
||||
// called by swipe provider flickable
|
||||
function updatePositionWithOffset(x, y) {
|
||||
switch (currentView) {
|
||||
case HomeScreenState.PageView: {
|
||||
switch (currentSwipeState) {
|
||||
case HomeScreenState.DeterminingType:
|
||||
xDetermineSwipePosition += x;
|
||||
yDetermineSwipePosition += y;
|
||||
|
||||
// check if a swipetype can be determined and started
|
||||
if (Math.abs(xDetermineSwipePosition) >= horizontalSwipeStateDetermineThreshold) {
|
||||
currentSwipeState = HomeScreenState.SwipingPages;
|
||||
xDetermineSwipePosition = 0;
|
||||
yDetermineSwipePosition = 0;
|
||||
} else if (yDetermineSwipePosition >= verticalSwipeStateDetermineThreshold) {
|
||||
currentSwipeState = HomeScreenState.SwipingPagesDown;
|
||||
root.swipeDownGestureBegin();
|
||||
xDetermineSwipePosition = 0;
|
||||
yDetermineSwipePosition = 0;
|
||||
} else if (-yDetermineSwipePosition >= verticalSwipeStateDetermineThreshold) {
|
||||
currentSwipeState = HomeScreenState.SwipingAppDrawerVisibility;
|
||||
xDetermineSwipePosition = 0;
|
||||
yDetermineSwipePosition = 0;
|
||||
|
||||
// reset app drawer position to top
|
||||
appDrawerFlickable.contentY = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case HomeScreenState.SwipingPages:
|
||||
xPosition += x;
|
||||
break;
|
||||
|
||||
case HomeScreenState.SwipingPagesDown:
|
||||
yPosition = pagesYPosition;
|
||||
if (y !== 0) {
|
||||
root.swipeDownGestureOffset(y);
|
||||
}
|
||||
break;
|
||||
|
||||
case HomeScreenState.SwipingAppDrawerVisibility:
|
||||
yPosition = Math.max(appDrawerOpenYPosition, Math.min(pagesYPosition, yPosition + y));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case HomeScreenState.AppDrawerBeginningView: {
|
||||
switch (currentSwipeState) {
|
||||
case HomeScreenState.DeterminingType:
|
||||
xDetermineSwipePosition += x;
|
||||
yDetermineSwipePosition += y;
|
||||
|
||||
// check if a swipetype can be determined and started
|
||||
if (yDetermineSwipePosition >= verticalSwipeStateDetermineThreshold) {
|
||||
currentSwipeState = HomeScreenState.SwipingAppDrawerVisibility;
|
||||
xDetermineSwipePosition = 0;
|
||||
yDetermineSwipePosition = 0;
|
||||
} else if (-yDetermineSwipePosition >= verticalSwipeStateDetermineThreshold) {
|
||||
currentSwipeState = HomeScreenState.SwipingAppDrawerList;
|
||||
yVelocityCalculator.startMeasure(appDrawerFlickable.contentY);
|
||||
xDetermineSwipePosition = 0;
|
||||
yDetermineSwipePosition = 0;
|
||||
}
|
||||
break;
|
||||
case HomeScreenState.SwipingAppDrawerVisibility:
|
||||
yPosition = Math.max(appDrawerOpenYPosition, Math.min(pagesYPosition, yPosition + y));
|
||||
break;
|
||||
|
||||
case HomeScreenState.SwipingAppDrawerList:
|
||||
// app drawer scrolling
|
||||
let candidateNewPos = appDrawerFlickable.contentY - y;
|
||||
appDrawerFlickable.contentY = candidateNewPos;
|
||||
// update velocity
|
||||
yVelocityCalculator.changePosition(appDrawerFlickable.contentY);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HomeScreenState.AppDrawerView: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// called after a user finishes an interaction (ex. lets go of the screen)
|
||||
// called by swipe provider flickable
|
||||
function updateState() {
|
||||
cancelAnimations();
|
||||
|
||||
// we need to always call resetSwipeState() after each interaction.
|
||||
// if we have an animation to run, we rely on the animation to call the function.
|
||||
// otherwise, we do it directly here.
|
||||
|
||||
switch (currentView) {
|
||||
case HomeScreenState.PageView: {
|
||||
|
||||
// update vertical position
|
||||
switch (currentSwipeState) {
|
||||
case HomeScreenState.DeterminingType: {
|
||||
movingUp ? closeAppDrawer() : openAppDrawer();
|
||||
break;
|
||||
}
|
||||
|
||||
case HomeScreenState.SwipingPagesDown: {
|
||||
root.swipeDownGestureEnd();
|
||||
root.resetSwipeState();
|
||||
break;
|
||||
}
|
||||
|
||||
case HomeScreenState.SwipingAppDrawerVisibility: {
|
||||
movingUp ? closeAppDrawer() : openAppDrawer();
|
||||
break;
|
||||
}
|
||||
|
||||
case HomeScreenState.SwipingPages: {
|
||||
// update pages position
|
||||
let currentPageIndexPosition = xPositionFromPageIndex(currentPageIndex);
|
||||
let duration = Kirigami.Units.longDuration * 2;
|
||||
|
||||
if (xPosition < currentPageIndexPosition) {
|
||||
if (movingRight) {
|
||||
animateGoToPageIndex(currentPageIndex, duration);
|
||||
} else {
|
||||
animateGoToPageIndex(Math.max(0, currentPageIndex - 1), duration);
|
||||
}
|
||||
} else {
|
||||
if (movingRight) {
|
||||
animateGoToPageIndex(Math.min(pagesCount - 1, currentPageIndex + 1), duration);
|
||||
} else {
|
||||
animateGoToPageIndex(currentPageIndex, duration);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
// this shouldn't occur, but keeps consistent state if it does
|
||||
root.resetSwipeState();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case HomeScreenState.AppDrawerBeginningView: {
|
||||
|
||||
switch (currentSwipeState) {
|
||||
case HomeScreenState.DeterminingType:
|
||||
case HomeScreenState.SwipingAppDrawerVisibility: {
|
||||
movingUp ? closeAppDrawer() : openAppDrawer();
|
||||
break;
|
||||
}
|
||||
case HomeScreenState.SwipingAppDrawerList: {
|
||||
currentView = HomeScreenState.AppDrawerView;
|
||||
appDrawerFlickable.flick(0, -yVelocityCalculator.velocity);
|
||||
root.resetSwipeState();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// this shouldn't occur, but keeps consistent state if it does
|
||||
root.resetSwipeState();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case HomeScreenState.AppDrawerView: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// measure velocity of our swipe in the app drawer, so that we can flick
|
||||
property var yVelocityCalculator: MobileShell.VelocityCalculator {}
|
||||
|
||||
// listen to the app drawer's flickable for if it goes to the top of the list
|
||||
// we then update our view state
|
||||
property var appDrawerFlickableListener: Connections {
|
||||
target: appDrawerFlickable
|
||||
|
||||
function onMovementEnded() {
|
||||
if (root.currentView === HomeScreenState.AppDrawerView) {
|
||||
if (appDrawerFlickable.contentY <= 0) {
|
||||
root.currentView = HomeScreenState.AppDrawerBeginningView;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onDraggingChanged() {
|
||||
if (!appDrawerFlickable.dragging) {
|
||||
if (root.currentView === HomeScreenState.AppDrawerView) {
|
||||
if (appDrawerFlickable.contentY <= 0) {
|
||||
root.currentView = HomeScreenState.AppDrawerBeginningView;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ~~ property animators ~~
|
||||
|
||||
property var xAnim: NumberAnimation {
|
||||
target: root
|
||||
property: "xPosition"
|
||||
easing.type: Easing.OutBack
|
||||
onFinished: {
|
||||
root.resetSwipeState();
|
||||
}
|
||||
}
|
||||
|
||||
property var openDrawerAnim: NumberAnimation {
|
||||
target: root
|
||||
property: "yPosition"
|
||||
to: appDrawerOpenYPosition
|
||||
duration: Kirigami.Units.longDuration * 2
|
||||
easing.type: Easing.OutCubic
|
||||
|
||||
onFinished: {
|
||||
root.currentView = HomeScreenState.AppDrawerBeginningView;
|
||||
root.resetSwipeState();
|
||||
}
|
||||
}
|
||||
|
||||
property var closeDrawerAnim: NumberAnimation {
|
||||
target: root
|
||||
property: "yPosition"
|
||||
to: pagesYPosition
|
||||
duration: Kirigami.Units.longDuration * 2
|
||||
easing.type: Easing.OutCubic
|
||||
|
||||
onFinished: {
|
||||
root.currentView = HomeScreenState.PageView;
|
||||
root.resetSwipeState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.4
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls 2.3 as Controls
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.plasma.plasmoid 2.0
|
||||
import org.kde.kquickcontrolsaddons 2.0
|
||||
|
||||
import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
readonly property int reservedSpaceForLabel: metrics.height
|
||||
readonly property int cellWidth: root.width / Math.floor(root.width / ((availableCellHeight - reservedSpaceForLabel) + Kirigami.Units.smallSpacing*4))
|
||||
readonly property int cellHeight: availableCellHeight
|
||||
property int availableCellHeight: Kirigami.Units.iconSizes.huge + reservedSpaceForLabel
|
||||
|
||||
property ContainmentLayoutManager.AppletsLayout appletsLayout
|
||||
|
||||
property alias frame: frame
|
||||
property alias flow: applicationsFlow
|
||||
|
||||
implicitWidth: frame.implicitWidth
|
||||
implicitHeight: Math.max(Kirigami.Units.gridUnit*3, frame.implicitHeight)
|
||||
|
||||
Controls.Label {
|
||||
id: metrics
|
||||
text: "M\nM"
|
||||
visible: false
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.9
|
||||
}
|
||||
|
||||
Item {
|
||||
id: spacer
|
||||
width: Kirigami.Units.gridUnit * 4
|
||||
height: width
|
||||
}
|
||||
|
||||
Controls.Control {
|
||||
id: frame
|
||||
anchors.centerIn: parent
|
||||
implicitWidth: contentItem.implicitWidth
|
||||
implicitHeight: contentItem.implicitHeight
|
||||
height: parent.height
|
||||
|
||||
leftPadding: 0
|
||||
topPadding: 0
|
||||
rightPadding: 0
|
||||
bottomPadding: 0
|
||||
|
||||
// With a mousearea, it will be possible to drag with touch also on empty places
|
||||
contentItem: MouseArea {
|
||||
implicitWidth: applicationsFlow.implicitWidth
|
||||
implicitHeight: applicationsFlow.implicitHeight
|
||||
Flow {
|
||||
id: applicationsFlow
|
||||
|
||||
spacing: 0
|
||||
anchors.fill: parent
|
||||
|
||||
move: Transition {
|
||||
NumberAnimation {
|
||||
duration: Kirigami.Units.longDuration
|
||||
easing.type: Easing.InOutQuad
|
||||
properties: "x,y"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Behavior on implicitWidth {
|
||||
NumberAnimation {
|
||||
duration: Kirigami.Units.longDuration
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,298 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.4
|
||||
|
||||
import org.kde.plasma.plasmoid 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.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property ContainmentLayoutManager.AppletsLayout appletsLayout
|
||||
property FavoriteStrip favoriteStrip
|
||||
property ContainmentLayoutManager.ItemContainer currentlyDraggedDelegate
|
||||
property bool active
|
||||
|
||||
required property Folio.DesktopModel desktopModel
|
||||
|
||||
readonly property Item spacer: Item {
|
||||
width: favoriteStrip.cellWidth
|
||||
height: favoriteStrip.cellHeight
|
||||
}
|
||||
|
||||
function startDrag(item) {
|
||||
showSpacer(item, 0, 0);
|
||||
}
|
||||
|
||||
function dragItem(delegate, dragCenterX, dragCenterY) {
|
||||
// newPosition
|
||||
var newRow = 0;
|
||||
|
||||
var newContainer = internal.containerForItem(delegate, dragCenterX, dragCenterY);
|
||||
if (!newContainer) {
|
||||
newContainer = appletsLayout;
|
||||
}
|
||||
|
||||
// Put it in the favorites strip
|
||||
if (newContainer == favoriteStrip) {
|
||||
var pos = favoriteStrip.flow.mapFromItem(delegate, 0, 0);
|
||||
newRow = Math.floor((pos.x + dragCenterX) / delegate.width);
|
||||
|
||||
showSpacer(delegate, dragCenterX, dragCenterY);
|
||||
desktopModel.moveItem(delegate.modelData.index, newRow);
|
||||
|
||||
// Put it on desktop
|
||||
} else {
|
||||
var pos = appletsLayout.mapFromItem(delegate, 0, 0);
|
||||
|
||||
showSpacer(delegate, dragCenterX, dragCenterY);
|
||||
return;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function dropItem(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) {
|
||||
MobileShell.ShellUtil.stackItemBefore(spacer, child);
|
||||
} else {
|
||||
MobileShell.ShellUtil.stackItemAfter(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) {
|
||||
MobileShell.ShellUtil.stackItemBefore(spacer, child);
|
||||
} else {
|
||||
MobileShell.ShellUtil.stackItemAfter(spacer, child);
|
||||
}
|
||||
|
||||
spacer.visible = true;
|
||||
}
|
||||
|
||||
function hideSpacer () {
|
||||
spacer.visible = false;
|
||||
spacer.parent = root;
|
||||
}
|
||||
|
||||
// Those should never be accessed from outside
|
||||
QtObject {
|
||||
id: internal
|
||||
function raiseContainer(container) {
|
||||
container.z = 1;
|
||||
|
||||
if (container == appletsLayout) {
|
||||
favoriteStrip.z = 0;
|
||||
} else if (container == favoriteStrip) {
|
||||
appletsLayout.z = 0;
|
||||
} else {
|
||||
appletsLayout.z = 0;
|
||||
favoriteStrip.z = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function containerForItem(item, dragCenterX, dragCenterY) {
|
||||
if (!item.modelData) {
|
||||
return appletsLayout;
|
||||
} else if (favoriteStrip.contains(Qt.point(0,favoriteStrip.frame.mapFromItem(item, dragCenterX, dragCenterY).y))
|
||||
&& (item.modelData.applicationLocation == Folio.DesktopModel.Favorites
|
||||
|| desktopModel.favoriteCount < desktopModel.maxFavoriteCount)) {
|
||||
return favoriteStrip;
|
||||
} else {
|
||||
return appletsLayout;
|
||||
}
|
||||
}
|
||||
|
||||
function putItemInDragSpace(item) {
|
||||
var pos = root.mapFromItem(item, 0, 0);
|
||||
item.parent = root;
|
||||
|
||||
item.x = pos.x;
|
||||
item.y = pos.y;
|
||||
}
|
||||
|
||||
function putInContainerLayout(item, container) {
|
||||
var pos = container.flow.mapFromItem(item, 0, 0);
|
||||
|
||||
if (container == appletsLayout) {
|
||||
item.parent = container;
|
||||
} else {
|
||||
item.parent = container.flow;
|
||||
}
|
||||
|
||||
item.x = pos.x;
|
||||
item.y = pos.y;
|
||||
}
|
||||
|
||||
function nearestChild(item, dragCenterX, dragCenterY, container) {
|
||||
var distance = Number.POSITIVE_INFINITY;
|
||||
var child;
|
||||
var pos = container.flow.mapFromItem(item, dragCenterX, dragCenterY);
|
||||
|
||||
// Search Right
|
||||
for (var i = 0; i < item.width * 2; i += item.width/2) {
|
||||
var candidate = container.flow.childAt(
|
||||
Math.min(container.flow.width, Math.max(0, pos.x + i)),
|
||||
Math.min(container.flow.height-1, Math.max(0, pos.y)));
|
||||
|
||||
if (candidate && i < distance) {
|
||||
child = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Search Left
|
||||
for (var i = 0; i < item.width * 2; i += item.width/2) {
|
||||
var candidate = container.flow.childAt(Math.min(container.flow.width, Math.max(0, pos.x - i)), Math.min(container.flow.height-1, Math.max(0, pos.y)));
|
||||
|
||||
if (candidate && i < distance) {
|
||||
child = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!child) {
|
||||
if (item.y < container.flow.height/2) {
|
||||
child = container.flow.children[0];
|
||||
} else {
|
||||
child = container.flow.children[container.flow.children.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
|
||||
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)));
|
||||
|
||||
if (candidate && i < distance) {
|
||||
child = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
|
||||
function positionItem(item, dragCenterX, dragCenterY) {
|
||||
hideSpacer();
|
||||
var container = containerForItem(item, dragCenterX, dragCenterY);
|
||||
|
||||
raiseContainer(container);
|
||||
|
||||
if (container == appletsLayout) {
|
||||
if (item.modelData) {
|
||||
desktopModel.setLocation(item.modelData.index, Folio.DesktopModel.Desktop);
|
||||
}
|
||||
var pos = appletsLayout.mapFromItem(item, 0, 0);
|
||||
item.parent = appletsLayout;
|
||||
item.x = pos.x;
|
||||
item.y = pos.y;
|
||||
appletsLayout.hidePlaceHolder();
|
||||
appletsLayout.positionItem(item);
|
||||
|
||||
return;
|
||||
} else if (container == favoriteStrip) {
|
||||
desktopModel.setLocation(item.modelData.index, Folio.DesktopModel.Favorites);
|
||||
} else {
|
||||
desktopModel.setLocation(item.modelData.index, Folio.DesktopModel.None);
|
||||
}
|
||||
|
||||
var child = nearestChild(item, dragCenterX, dragCenterY, container);
|
||||
|
||||
putInContainerLayout(item, container);
|
||||
MobileShell.ShellUtil.stackItemBefore(item, spacer);
|
||||
spacer.visible = false;
|
||||
spacer.parent = root;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Marco Martin <mart@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls 2.3 as Controls
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import org.kde.plasma.plasmoid 2.0
|
||||
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.mobileshell.state 1.0 as MobileShellState
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
import org.kde.kirigami 2.14 as Kirigami
|
||||
|
||||
Repeater {
|
||||
id: launcherRepeater
|
||||
|
||||
required property var homeScreenState
|
||||
required property Folio.DesktopModel desktopModel
|
||||
|
||||
property ContainmentLayoutManager.AppletsLayout appletsLayout
|
||||
property FavoriteStrip favoriteStrip
|
||||
property int cellWidth
|
||||
property int cellHeight
|
||||
|
||||
signal scrollLeftRequested
|
||||
signal scrollRightRequested
|
||||
signal stopScrollRequested
|
||||
|
||||
delegate: HomeDelegate {
|
||||
id: delegate
|
||||
desktopModel: launcherRepeater.desktopModel
|
||||
homeScreenState: launcherRepeater.homeScreenState
|
||||
|
||||
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(delegate.parent.height, launcherRepeater.cellHeight)
|
||||
}
|
||||
parent: parentFromLocation
|
||||
reservedSpaceForLabel: metrics.height
|
||||
property Item parentFromLocation: {
|
||||
switch (model.applicationLocation) {
|
||||
case Folio.DesktopModel.Favorites:
|
||||
return favoriteStrip.flow;
|
||||
case Folio.DesktopModel.Desktop:
|
||||
default:
|
||||
return appletsLayout;
|
||||
}
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (model.applicationLocation === Folio.DesktopModel.Desktop) {
|
||||
appletsLayout.restoreItem(delegate);
|
||||
}
|
||||
}
|
||||
|
||||
onUserDrag: {
|
||||
dragCenterX = dragCenter.x;
|
||||
dragCenterY = dragCenter.y;
|
||||
launcherDragManager.dragItem(delegate, dragCenter.x, dragCenter.y);
|
||||
|
||||
delegate.width = appletsLayout.cellWidth;
|
||||
delegate.height = appletsLayout.cellHeight;
|
||||
|
||||
var pos = plasmoid.fullRepresentationItem.mapFromItem(delegate, dragCenter.x, dragCenter.y);
|
||||
|
||||
//SCROLL LEFT
|
||||
if (pos.x < Kirigami.Units.gridUnit) {
|
||||
launcherRepeater.scrollLeftRequested();
|
||||
//SCROLL RIGHT
|
||||
} else if (pos.x > homeScreenState.pageWidth - Kirigami.Units.gridUnit) {
|
||||
launcherRepeater.scrollRightRequested();
|
||||
//DON't SCROLL
|
||||
} else {
|
||||
launcherRepeater.stopScrollRequested();
|
||||
}
|
||||
}
|
||||
|
||||
onDragActiveChanged: {
|
||||
launcherDragManager.active = dragActive
|
||||
if (dragActive) {
|
||||
// Must be 0, 0 as at this point dragCenterX and dragCenterY are on the drag before"
|
||||
launcherDragManager.startDrag(delegate);
|
||||
launcherDragManager.currentlyDraggedDelegate = delegate;
|
||||
} else {
|
||||
launcherDragManager.dropItem(delegate, dragCenterX, dragCenterY);
|
||||
plasmoid.editMode = false;
|
||||
editMode = false;
|
||||
launcherRepeater.stopScrollRequested();
|
||||
launcherDragManager.currentlyDraggedDelegate = null;
|
||||
forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
onLaunch: (x, y, icon, title) => {
|
||||
if (icon !== "") {
|
||||
MobileShellState.ShellDBusClient.openAppLaunchAnimation(
|
||||
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));
|
||||
}
|
||||
}
|
||||
onParentFromLocationChanged: {
|
||||
if (!launcherDragManager.active && parent != parentFromLocation) {
|
||||
parent = parentFromLocation;
|
||||
if (model.applicationLocation === Folio.DesktopModel.Favorites) {
|
||||
MobileShell.ShellUtil.stackItemBefore(delegate, parentFromLocation.children[index]);
|
||||
} else if (model.applicationLocation === Folio.DesktopModel.None) {
|
||||
MobileShell.ShellUtil.stackItemBefore(delegate, parentFromLocation.children[Math.max(0, index - desktopModel.favoriteCount)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Window 2.12
|
||||
import QtQuick.Layouts 1.1
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.plasma.plasmoid 2.0
|
||||
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 "private" as Private
|
||||
|
||||
ContainmentLayoutManager.BasicAppletContainer {
|
||||
id: appletContainer
|
||||
|
||||
property var homeScreenState
|
||||
property real screenWidth: homeScreenState.availableScreenWidth
|
||||
property real screenHeight: homeScreenState.availableScreenHeight
|
||||
|
||||
// HACK: for some reason configOverlayComponent access the appletContainer id
|
||||
property var container: appletContainer
|
||||
|
||||
configOverlayComponent: Private.ConfigOverlay {
|
||||
onRequestEditModeClose: container.editMode = false;
|
||||
onRequestRemoveTrigger: container.applet.action("remove").trigger();
|
||||
}
|
||||
|
||||
property LauncherDragManager launcherDragManager
|
||||
|
||||
onEditModeChanged: {
|
||||
launcherDragManager.active = dragActive || editMode;
|
||||
}
|
||||
|
||||
property real dragCenterX
|
||||
property real dragCenterY
|
||||
|
||||
editModeCondition: ContainmentLayoutManager.ItemContainer.AfterPressAndHold
|
||||
|
||||
onDragActiveChanged: {
|
||||
launcherDragManager.active = dragActive || editMode;
|
||||
if (dragActive) {
|
||||
// Must be 0, 0 as at this point dragCenterX and dragCenterY are on the drag before"
|
||||
launcherDragManager.startDrag(appletContainer);
|
||||
launcherDragManager.currentlyDraggedDelegate = appletContainer;
|
||||
// Reparenting removed focus
|
||||
appletContainer.forceActiveFocus();
|
||||
} else {
|
||||
launcherDragManager.dropItem(appletContainer, dragCenterX, dragCenterY);
|
||||
plasmoid.editMode = false;
|
||||
launcherRepeater.stopScrollRequested();
|
||||
launcherDragManager.currentlyDraggedDelegate = null;
|
||||
forceActiveFocus();
|
||||
}
|
||||
}
|
||||
onUserDrag: {
|
||||
dragCenterX = dragCenter.x;
|
||||
dragCenterY = dragCenter.y;
|
||||
launcherDragManager.dragItem(appletContainer, dragCenter.x, dragCenter.y);
|
||||
|
||||
var pos = plasmoid.fullRepresentationItem.mapFromItem(appletContainer, dragCenter.x, dragCenter.y);
|
||||
|
||||
//SCROLL LEFT
|
||||
if (pos.x < Kirigami.Units.gridUnit) {
|
||||
launcherRepeater.scrollLeftRequested();
|
||||
//SCROLL RIGHT
|
||||
} else if (pos.x > screenWidth - Kirigami.Units.gridUnit) {
|
||||
launcherRepeater.scrollRightRequested();
|
||||
//DON't SCROLL
|
||||
} else {
|
||||
launcherRepeater.stopScrollRequested();
|
||||
}
|
||||
|
||||
appletContainer.x = Math.max(0, Math.min(screenWidth - appletContainer.width, appletContainer.x));
|
||||
}
|
||||
onWidthChanged: {
|
||||
if (appletContainer.x + appletContainer.width > screenWidth * Math.max(1, Math.ceil(appletContainer.x / screenWidth))) {
|
||||
appletsLayout.releaseSpace(appletContainer);
|
||||
appletContainer.width = (screenWidth * Math.max(1, Math.ceil(appletContainer.x / screenWidth)) - appletContainer.x);
|
||||
appletsLayout.positionItem(appletContainer);
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: appletsLayout
|
||||
function onAppletsLayoutInteracted() {
|
||||
appletContainer.editMode = false;
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: dropArea
|
||||
function onWidthChanged () {
|
||||
let spaceReleased = false;
|
||||
if (appletContainer.width > screenWidth || appletContainer.height > screenHeight) {
|
||||
appletsLayout.releaseSpace(appletContainer);
|
||||
appletContainer.width = Math.min(appletContainer.width, screenWidth);
|
||||
appletContainer.height = Math.min(appletContainer.height, screenHeight);
|
||||
spaceReleased = true;
|
||||
}
|
||||
if (Math.floor((appletContainer.x) / screenWidth) < Math.floor((appletContainer.x + appletContainer.width/2) / screenWidth)) {
|
||||
appletsLayout.releaseSpace(appletContainer);
|
||||
appletContainer.x = Math.floor((appletContainer.x + appletContainer.width) / screenWidth) * screenWidth;
|
||||
appletsLayout.positionItem(appletContainer);
|
||||
spaceReleased = false;
|
||||
|
||||
} else if (Math.floor((appletContainer.x + appletContainer.width/2) / screenWidth) < Math.floor((appletContainer.x + appletContainer.width) / screenWidth)) {
|
||||
appletsLayout.releaseSpace(appletContainer);
|
||||
appletContainer.x = Math.ceil(appletContainer.x / screenWidth) * screenWidth - screenWidth;
|
||||
appletsLayout.positionItem(appletContainer);
|
||||
spaceReleased = false;
|
||||
}
|
||||
if (spaceReleased) {
|
||||
appletsLayout.positionItem(appletContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
import org.kde.kirigami 2.10 as Kirigami
|
||||
|
||||
import "./delegate"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
width: Folio.HomeScreenState.pageCellWidth
|
||||
height: Folio.HomeScreenState.pageCellHeight
|
||||
|
||||
// we need to simulate the position of the icon if it is placed at this spot
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
// icon position placement
|
||||
Rectangle {
|
||||
id: loader
|
||||
color: Qt.rgba(255, 255, 255, 0.3)
|
||||
radius: Kirigami.Units.largeSpacing
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
|
||||
Layout.minimumWidth: Folio.FolioSettings.delegateIconSize
|
||||
Layout.minimumHeight: Folio.FolioSettings.delegateIconSize
|
||||
Layout.preferredHeight: Layout.minimumHeight
|
||||
layer.enabled: true
|
||||
layer.effect: DelegateShadow {}
|
||||
}
|
||||
|
||||
// simulate a delegate's label for positioning purposes
|
||||
DelegateLabel {
|
||||
id: label
|
||||
opacity: 0
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Folio.HomeScreenState.pageDelegateLabelHeight
|
||||
Layout.topMargin: Folio.HomeScreenState.pageDelegateLabelSpacing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,175 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Marco Martin <mart@kde.org>
|
||||
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls 2.15 as Controls
|
||||
|
||||
import org.kde.plasma.plasmoid 2.0
|
||||
import org.kde.plasma.components 3.0 as PC3
|
||||
import org.kde.kirigami 2.10 as Kirigami
|
||||
|
||||
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
||||
|
||||
import "../private"
|
||||
import "../"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
required property var homeScreenState
|
||||
|
||||
property real leftPadding: 0
|
||||
property real topPadding: 0
|
||||
property real bottomPadding: 100
|
||||
property real rightPadding: 0
|
||||
|
||||
property alias flickable: flickableBody.contentItem
|
||||
|
||||
property Flickable contentItem
|
||||
property real contentWidth: holdingColumn.width
|
||||
|
||||
required property int headerHeight
|
||||
required property var headerItem
|
||||
|
||||
signal launched
|
||||
signal dragStarted
|
||||
|
||||
readonly property int reservedSpaceForLabel: metrics.height
|
||||
property int availableCellHeight: Kirigami.Units.iconSizes.huge + reservedSpaceForLabel
|
||||
|
||||
readonly property real openFactor: factorNormalize(view.contentY / (Kirigami.Units.gridUnit * 10))
|
||||
|
||||
// height from top of screen that the drawer starts
|
||||
readonly property real drawerTopMargin: height - topPadding - bottomPadding - closedPositionOffset
|
||||
readonly property real closedPositionOffset: homeScreenState.appDrawerBottomOffset
|
||||
|
||||
//BEGIN functions
|
||||
|
||||
function factorNormalize(num) {
|
||||
return Math.min(1, Math.max(0, num));
|
||||
}
|
||||
|
||||
//END functions
|
||||
|
||||
Drag.dragType: Drag.Automatic
|
||||
|
||||
PC3.Label {
|
||||
id: metrics
|
||||
text: "M\nM"
|
||||
visible: false
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.8
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
|
||||
// bottom divider
|
||||
GradientBar {
|
||||
opacity: (homeScreenState.currentView !== HomeScreenState.PageView || homeScreenState.currentSwipeState === HomeScreenState.SwipingAppDrawerVisibility) ? 0.6 : 0
|
||||
visible: root.bottomPadding > 0
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: root.bottomPadding - height
|
||||
}
|
||||
|
||||
// physical position of drawer is handled through this flickable
|
||||
Flickable {
|
||||
id: view
|
||||
anchors.fill: parent
|
||||
|
||||
// scroll events are handled by our flick container, we are only using this for positioning
|
||||
interactive: false
|
||||
contentY: Math.max(0, Math.min(root.drawerTopMargin, root.drawerTopMargin - homeScreenState.yPosition))
|
||||
|
||||
contentHeight: column.implicitHeight
|
||||
contentWidth: -1
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
ColumnLayout {
|
||||
id: column
|
||||
width: view.width
|
||||
spacing: 0
|
||||
|
||||
// margin of the drawer from the top
|
||||
Rectangle {
|
||||
id: topMargin
|
||||
color: "transparent"
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: root.drawerTopMargin
|
||||
|
||||
OpenDrawerButton {
|
||||
id: openDrawerButton
|
||||
anchors {
|
||||
leftMargin: root.leftPadding
|
||||
left: parent.left
|
||||
rightMargin: root.rightPadding
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
factor: root.openFactor
|
||||
flickable: view
|
||||
onOpenRequested: {
|
||||
contentItem.positionViewAtBeginning();
|
||||
homeScreenState.openAppDrawer();
|
||||
}
|
||||
onCloseRequested: homeScreenState.closeAppDrawer();
|
||||
}
|
||||
}
|
||||
|
||||
// actual drawer
|
||||
MobileShell.BaseItem {
|
||||
visible: root.openFactor > 0 // prevent handlers from picking up events
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: root.height
|
||||
leftPadding: root.leftPadding; topPadding: root.topPadding
|
||||
rightPadding: root.rightPadding; bottomPadding: root.bottomPadding
|
||||
|
||||
// drawer background
|
||||
background: Rectangle {
|
||||
id: scrim
|
||||
color: "black"
|
||||
opacity: 0.6 * root.openFactor
|
||||
|
||||
// remove radius
|
||||
radius: view.contentY > (topMargin.height - Kirigami.Units.gridUnit) ? 0 : Kirigami.Units.gridUnit
|
||||
Behavior on radius {
|
||||
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
}
|
||||
|
||||
opacity: root.openFactor
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
id: holdingColumn
|
||||
width: view.width
|
||||
spacing: 0
|
||||
|
||||
// drawer header
|
||||
MobileShell.BaseItem {
|
||||
id: flickableHeader
|
||||
Layout.preferredHeight: root.headerHeight
|
||||
Layout.fillWidth: true
|
||||
leftPadding: 0; rightPadding: 0; topPadding: 0; bottomPadding: 0
|
||||
|
||||
contentItem: root.headerItem
|
||||
}
|
||||
|
||||
// drawer body
|
||||
MobileShell.BaseItem {
|
||||
id: flickableBody
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
leftPadding: 0; rightPadding: 0; topPadding: 0; bottomPadding: 0
|
||||
|
||||
contentItem: root.contentItem
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.1
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
signal switchToListRequested()
|
||||
signal switchToGridRequested()
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||
Kirigami.Theme.inherit: false
|
||||
|
||||
// HACK: Here only to steal inputs the would normally be delivered to home
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.topMargin: Kirigami.Units.smallSpacing
|
||||
anchors.leftMargin: Kirigami.Units.gridUnit
|
||||
anchors.rightMargin: Kirigami.Units.gridUnit
|
||||
anchors.fill: parent
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
Kirigami.Heading {
|
||||
color: "white"
|
||||
level: 1
|
||||
text: i18n("Applications")
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
PlasmaComponents.ToolButton {
|
||||
icon.name: "view-list-symbolic"
|
||||
implicitWidth: Math.round(Kirigami.Units.gridUnit * 2.1)
|
||||
implicitHeight: Math.round(Kirigami.Units.gridUnit * 2.1)
|
||||
onClicked: root.switchToListRequested()
|
||||
}
|
||||
PlasmaComponents.ToolButton {
|
||||
icon.name: "view-grid-symbolic"
|
||||
implicitWidth: Math.round(Kirigami.Units.gridUnit * 2.1)
|
||||
implicitHeight: Math.round(Kirigami.Units.gridUnit * 2.1)
|
||||
onClicked: root.switchToGridRequested()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
|
||||
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Window 2.12
|
||||
import QtQuick.Layouts 1.1
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.plasma.plasmoid 2.0
|
||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||
|
||||
Loader {
|
||||
id: root
|
||||
|
||||
required property var homeScreenState
|
||||
|
||||
property real topPadding: 0
|
||||
property real bottomPadding: 0
|
||||
property real leftPadding: 0
|
||||
property real rightPadding: 0
|
||||
|
||||
property string appDrawerType: "gridview" // gridview/listview
|
||||
|
||||
readonly property real headerHeight: Math.round(Kirigami.Units.gridUnit * 3)
|
||||
|
||||
sourceComponent: appDrawerType === "gridview" ? gridViewDrawer : listViewDrawer
|
||||
|
||||
Component {
|
||||
id: headerComponent
|
||||
|
||||
AppDrawerHeader {
|
||||
onSwitchToListRequested: {
|
||||
if (root.appDrawerType !== "listview") {
|
||||
root.appDrawerType = "listview";
|
||||
}
|
||||
}
|
||||
|
||||
onSwitchToGridRequested: {
|
||||
if (root.appDrawerType !== "gridview") {
|
||||
root.appDrawerType = "gridview";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: listViewDrawer
|
||||
ListViewAppDrawer {
|
||||
anchors.fill: parent
|
||||
topPadding: root.topPadding
|
||||
bottomPadding: root.bottomPadding
|
||||
leftPadding: root.leftPadding
|
||||
rightPadding: root.rightPadding
|
||||
|
||||
homeScreenState: root.homeScreenState
|
||||
headerItem: Loader { sourceComponent: headerComponent }
|
||||
headerHeight: root.headerHeight
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: gridViewDrawer
|
||||
GridViewAppDrawer {
|
||||
anchors.fill: parent
|
||||
topPadding: root.topPadding
|
||||
bottomPadding: root.bottomPadding
|
||||
leftPadding: root.leftPadding
|
||||
rightPadding: root.rightPadding
|
||||
|
||||
homeScreenState: root.homeScreenState
|
||||
headerItem: Loader { sourceComponent: headerComponent }
|
||||
headerHeight: root.headerHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls 2.3 as Controls
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.plasma.plasmoid 2.0
|
||||
import org.kde.plasma.core as PlasmaCore
|
||||
import org.kde.plasma.components 3.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.shellsettingsplugin as ShellSettings
|
||||
|
||||
MouseArea {
|
||||
id: delegate
|
||||
width: GridView.view.cellWidth
|
||||
height: GridView.view.cellHeight
|
||||
|
||||
property int reservedSpaceForLabel
|
||||
property alias iconItem: icon
|
||||
|
||||
readonly property real margins: Math.floor(width * 0.2)
|
||||
|
||||
signal launch(int x, int y, var source, string title, string storageId)
|
||||
signal dragStarted(string imageSource, int x, int y, string mimeData)
|
||||
|
||||
function launchApp() {
|
||||
// launch app
|
||||
if (model.applicationRunning) {
|
||||
delegate.launch(0, 0, "", model.applicationName, model.applicationStorageId);
|
||||
} else {
|
||||
delegate.launch(delegate.x + (Kirigami.Units.smallSpacing * 2), delegate.y + (Kirigami.Units.smallSpacing * 2), icon.source, model.applicationName, model.applicationStorageId);
|
||||
}
|
||||
}
|
||||
|
||||
onPressAndHold: {
|
||||
delegate.grabToImage(function(result) {
|
||||
delegate.Drag.imageSource = result.url
|
||||
dragStarted(result.url, width/2, height/2, model.applicationStorageId)
|
||||
})
|
||||
}
|
||||
|
||||
// grow/shrink animation
|
||||
property real zoomScale: 1
|
||||
transform: Scale {
|
||||
origin.x: delegate.width / 2;
|
||||
origin.y: delegate.height / 2;
|
||||
xScale: delegate.zoomScale
|
||||
yScale: delegate.zoomScale
|
||||
}
|
||||
|
||||
property bool launchAppRequested: false
|
||||
|
||||
NumberAnimation on zoomScale {
|
||||
id: shrinkAnim
|
||||
running: false
|
||||
duration: ShellSettings.Settings.animationsEnabled ? 80 : 1
|
||||
to: ShellSettings.Settings.animationsEnabled ? 0.8 : 1
|
||||
onFinished: {
|
||||
if (!delegate.pressed) {
|
||||
growAnim.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation on zoomScale {
|
||||
id: growAnim
|
||||
running: false
|
||||
duration: ShellSettings.Settings.animationsEnabled ? 80 : 1
|
||||
to: 1
|
||||
onFinished: {
|
||||
if (delegate.launchAppRequested) {
|
||||
delegate.launchApp();
|
||||
delegate.launchAppRequested = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
onPressedChanged: {
|
||||
if (pressed) {
|
||||
growAnim.stop();
|
||||
shrinkAnim.restart();
|
||||
} else if (!pressed && !shrinkAnim.running) {
|
||||
growAnim.restart();
|
||||
}
|
||||
}
|
||||
// launch app handled by press animation
|
||||
onClicked: launchAppRequested = true;
|
||||
|
||||
//preventStealing: true
|
||||
ColumnLayout {
|
||||
anchors {
|
||||
fill: parent
|
||||
leftMargin: margins
|
||||
topMargin: margins
|
||||
rightMargin: margins
|
||||
bottomMargin: margins
|
||||
}
|
||||
spacing: 0
|
||||
|
||||
Kirigami.Icon {
|
||||
id: icon
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: Math.floor(parent.height - delegate.reservedSpaceForLabel)
|
||||
Layout.preferredHeight: Layout.minimumHeight
|
||||
|
||||
source: model.applicationIcon
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
bottom: parent.bottom
|
||||
}
|
||||
visible: model.applicationRunning
|
||||
radius: width
|
||||
width: Kirigami.Units.smallSpacing
|
||||
height: width
|
||||
color: theme.highlightColor
|
||||
}
|
||||
|
||||
// darken effect when hovered/pressed
|
||||
layer {
|
||||
enabled: delegate.pressed || delegate.containsMouse
|
||||
effect: ColorOverlay {
|
||||
color: Qt.rgba(0, 0, 0, 0.3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PlasmaComponents.Label {
|
||||
id: label
|
||||
visible: text.length > 0
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: delegate.reservedSpaceForLabel
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
Layout.leftMargin: -parent.anchors.leftMargin + Kirigami.Units.smallSpacing
|
||||
Layout.rightMargin: -parent.anchors.rightMargin + Kirigami.Units.smallSpacing
|
||||
|
||||
wrapMode: Text.WordWrap
|
||||
maximumLineCount: 2
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignTop
|
||||
elide: Text.ElideRight
|
||||
|
||||
text: model.applicationName
|
||||
|
||||
font.pointSize: theme.defaultFont.pointSize * 0.8
|
||||
font.weight: Font.Bold
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
|
||||
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.4
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls 2.3 as Controls
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.plasma.plasmoid 2.0
|
||||
import org.kde.plasma.core as PlasmaCore
|
||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||
import org.kde.kquickcontrolsaddons 2.0
|
||||
|
||||
import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager
|
||||
|
||||
MouseArea {
|
||||
id: delegate
|
||||
property int reservedSpaceForLabel
|
||||
property alias iconItem: icon
|
||||
|
||||
signal launch(int x, int y, var source, string title, string storageId)
|
||||
signal dragStarted(string imageSource, int x, int y, string mimeData)
|
||||
|
||||
onPressAndHold: {
|
||||
delegate.grabToImage(function(result) {
|
||||
delegate.Drag.imageSource = result.url
|
||||
dragStarted(result.url, width/2, height/2, model.applicationStorageId)
|
||||
})
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
// launch app
|
||||
if (model.applicationRunning) {
|
||||
delegate.launch(0, 0, "", model.applicationName, model.applicationStorageId);
|
||||
} else {
|
||||
delegate.launch(delegate.x + (Kirigami.Units.smallSpacing * 2), delegate.y + (Kirigami.Units.smallSpacing * 2), icon.source, model.applicationName, model.applicationStorageId);
|
||||
}
|
||||
}
|
||||
hoverEnabled: true
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: delegate.pressed ? Qt.rgba(255, 255, 255, 0.2) : (delegate.containsMouse ? Qt.rgba(255, 255, 255, 0.05) : "transparent")
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Kirigami.Units.shortDuration }
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors {
|
||||
fill: parent
|
||||
leftMargin: Kirigami.Units.smallSpacing * 2
|
||||
topMargin: Kirigami.Units.smallSpacing
|
||||
rightMargin: Kirigami.Units.smallSpacing * 2
|
||||
bottomMargin: Kirigami.Units.smallSpacing
|
||||
}
|
||||
spacing: 0
|
||||
|
||||
Kirigami.Icon {
|
||||
id: icon
|
||||
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.minimumWidth: Layout.minimumHeight
|
||||
Layout.preferredWidth: Layout.minimumHeight
|
||||
Layout.minimumHeight: parent.height
|
||||
Layout.preferredHeight: Layout.minimumHeight
|
||||
|
||||
source: model.applicationIcon
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
bottom: parent.bottom
|
||||
}
|
||||
visible: model.applicationRunning
|
||||
radius: width
|
||||
width: Kirigami.Units.smallSpacing
|
||||
height: width
|
||||
color: theme.highlightColor
|
||||
}
|
||||
}
|
||||
|
||||
PlasmaComponents.Label {
|
||||
id: label
|
||||
visible: text.length > 0
|
||||
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.leftMargin: Kirigami.Units.smallSpacing * 2
|
||||
Layout.rightMargin: Kirigami.Units.gridUnit
|
||||
maximumLineCount: 1
|
||||
elide: Text.ElideRight
|
||||
|
||||
text: model.applicationName
|
||||
|
||||
font.pointSize: Math.round(theme.defaultFont.pointSize * 1.1)
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Marco Martin <mart@kde.org>
|
||||
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls 2.15 as Controls
|
||||
|
||||
import org.kde.plasma.plasmoid 2.0
|
||||
import org.kde.plasma.components 3.0 as PC3
|
||||
import org.kde.kirigami 2.10 as Kirigami
|
||||
|
||||
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
||||
import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
||||
import "../private"
|
||||
|
||||
AbstractAppDrawer {
|
||||
id: root
|
||||
|
||||
contentItem: MobileShell.GridView {
|
||||
id: gridView
|
||||
clip: true
|
||||
|
||||
/*
|
||||
* HACK: When the number of apps is less than the one that would fit in the first shown part of the drawer, make
|
||||
* this flickable interactive, in order to steal inputs that would normally be delivered to home.
|
||||
*/
|
||||
interactive: contentHeight <= height ? true : root.homeScreenState.appDrawerInteractive
|
||||
|
||||
readonly property real effectiveContentWidth: root.contentWidth - 2 * horizontalMargin
|
||||
readonly property real horizontalMargin: root.width * 0.1 / 2
|
||||
leftMargin: horizontalMargin
|
||||
rightMargin: horizontalMargin
|
||||
|
||||
cellWidth: effectiveContentWidth / Math.min(Math.floor(effectiveContentWidth / (Kirigami.Units.iconSizes.huge + Kirigami.Units.gridUnit * 2)), 8)
|
||||
cellHeight: cellWidth + root.reservedSpaceForLabel
|
||||
|
||||
readonly property int columns: Math.floor(effectiveContentWidth / cellWidth)
|
||||
readonly property int rows: Math.ceil(gridView.count / columns)
|
||||
|
||||
cacheBuffer: Math.max(0, rows * cellHeight)
|
||||
|
||||
model: Folio.ApplicationListModel
|
||||
|
||||
delegate: DrawerGridDelegate {
|
||||
id: delegate
|
||||
|
||||
width: gridView.cellWidth
|
||||
height: gridView.cellHeight
|
||||
reservedSpaceForLabel: root.reservedSpaceForLabel
|
||||
|
||||
onDragStarted: (imageSource, x, y, mimeData) => {
|
||||
root.Drag.imageSource = imageSource;
|
||||
root.Drag.hotSpot.x = x;
|
||||
root.Drag.hotSpot.y = y;
|
||||
root.Drag.mimeData = { "text/x-plasma-phone-homescreen-launcher": mimeData };
|
||||
|
||||
root.homeScreenState.closeAppDrawer()
|
||||
|
||||
root.dragStarted()
|
||||
root.Drag.active = true;
|
||||
}
|
||||
onLaunch: (x, y, icon, title, storageId) => {
|
||||
if (icon !== "") {
|
||||
MobileShellState.ShellDBusClient.openAppLaunchAnimation(
|
||||
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));
|
||||
}
|
||||
|
||||
Folio.ApplicationListModel.setMinimizedDelegate(index, delegate);
|
||||
MobileShell.AppLaunch.launchOrActivateApp(storageId);
|
||||
root.launched();
|
||||
}
|
||||
}
|
||||
|
||||
PC3.ScrollBar.vertical: PC3.ScrollBar {
|
||||
id: scrollBar
|
||||
interactive: true
|
||||
enabled: true
|
||||
Behavior on opacity {
|
||||
OpacityAnimator {
|
||||
duration: Kirigami.Units.longDuration * 2
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
implicitWidth: Kirigami.Units.smallSpacing
|
||||
contentItem: Rectangle {
|
||||
radius: width/2
|
||||
color: Qt.rgba(1, 1, 1, 0.3)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Marco Martin <mart@kde.org>
|
||||
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls 2.15 as Controls
|
||||
|
||||
import org.kde.plasma.plasmoid 2.0
|
||||
import org.kde.plasma.components 3.0 as PC3
|
||||
import org.kde.kirigami 2.10 as Kirigami
|
||||
|
||||
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
||||
import "../private"
|
||||
|
||||
AbstractAppDrawer {
|
||||
id: root
|
||||
|
||||
contentItem: MobileShell.ListView {
|
||||
id: listView
|
||||
clip: true
|
||||
reuseItems: true
|
||||
cacheBuffer: model.count * delegateHeight // delegate height
|
||||
|
||||
interactive: root.homeScreenState.appDrawerInteractive
|
||||
|
||||
property int delegateHeight: Kirigami.Units.gridUnit * 3
|
||||
|
||||
model: Folio.ApplicationListModel
|
||||
|
||||
delegate: DrawerListDelegate {
|
||||
id: delegate
|
||||
|
||||
width: listView.width
|
||||
height: listView.delegateHeight
|
||||
reservedSpaceForLabel: root.reservedSpaceForLabel
|
||||
|
||||
onDragStarted: (imageSource, x, y, mimeData) => {
|
||||
root.Drag.imageSource = imageSource;
|
||||
root.Drag.hotSpot.x = x;
|
||||
root.Drag.hotSpot.y = y;
|
||||
root.Drag.mimeData = { "text/x-plasma-phone-homescreen-launcher": mimeData };
|
||||
|
||||
root.homeScreenState.closeAppDrawer()
|
||||
|
||||
root.dragStarted()
|
||||
root.Drag.active = true;
|
||||
}
|
||||
onLaunch: (x, y, icon, title, storageId) => {
|
||||
if (icon !== "") {
|
||||
MobileShellState.ShellDBusClient.openAppLaunchAnimation(
|
||||
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));
|
||||
}
|
||||
|
||||
Folio.ApplicationListModel.setMinimizedDelegate(index, delegate);
|
||||
MobileShell.AppLaunch.launchOrActivateApp(storageId);
|
||||
root.launched();
|
||||
}
|
||||
}
|
||||
|
||||
PC3.ScrollBar.vertical: PC3.ScrollBar {
|
||||
id: scrollBar
|
||||
interactive: true
|
||||
enabled: true
|
||||
Behavior on opacity {
|
||||
OpacityAnimator {
|
||||
duration: Kirigami.Units.longDuration * 2
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
implicitWidth: Kirigami.Units.smallSpacing
|
||||
contentItem: Rectangle {
|
||||
radius: width/2
|
||||
color: Qt.rgba(1, 1, 1, 0.3)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls 2.3 as Controls
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick.Effects
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||
import org.kde.kquickcontrolsaddons 2.0
|
||||
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
|
||||
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
||||
|
||||
Folio.DelegateTouchArea {
|
||||
id: delegate
|
||||
|
||||
property string name
|
||||
property bool shadow: false
|
||||
|
||||
property alias contentItem: visualItem.contentItem
|
||||
property alias delegateItem: delegateWrapper
|
||||
property alias labelOpacity: label.opacity
|
||||
|
||||
signal afterClickAnimation()
|
||||
|
||||
// grow/shrink animation
|
||||
property real zoomScale: 1
|
||||
property bool clickRequested: false
|
||||
|
||||
NumberAnimation on zoomScale {
|
||||
id: shrinkAnim
|
||||
running: false
|
||||
duration: ShellSettings.Settings.animationsEnabled ? 80 : 1
|
||||
to: ShellSettings.Settings.animationsEnabled ? 0.8 : 1
|
||||
onFinished: {
|
||||
if (!delegate.pressed) {
|
||||
growAnim.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation on zoomScale {
|
||||
id: growAnim
|
||||
running: false
|
||||
duration: ShellSettings.Settings.animationsEnabled ? 80 : 1
|
||||
to: 1
|
||||
onFinished: {
|
||||
if (delegate.clickRequested) {
|
||||
delegate.afterClickAnimation();
|
||||
delegate.clickRequested = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressedChanged: (pressed) => {
|
||||
if (pressed) {
|
||||
growAnim.stop();
|
||||
shrinkAnim.restart();
|
||||
} else if (!pressed && !shrinkAnim.running) {
|
||||
growAnim.restart();
|
||||
}
|
||||
}
|
||||
// trigger handled by press animation
|
||||
onClicked: clickRequested = true;
|
||||
|
||||
layer.enabled: delegate.shadow
|
||||
layer.effect: DelegateShadow {}
|
||||
|
||||
Item {
|
||||
id: delegateWrapper
|
||||
anchors.fill: parent
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
// transform is not on delegateWrapper because when it's zoomed in, it apparently
|
||||
// affects the delegate's x and y position, which messes up the starting drag and drop
|
||||
// position (for mapFromItem in HomeScreen.qml)
|
||||
transform: Scale {
|
||||
origin.x: delegate.width / 2;
|
||||
origin.y: delegate.height / 2;
|
||||
xScale: delegate.zoomScale
|
||||
yScale: delegate.zoomScale
|
||||
}
|
||||
|
||||
MobileShell.BaseItem {
|
||||
id: visualItem
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
|
||||
Layout.minimumWidth: Folio.FolioSettings.delegateIconSize
|
||||
Layout.minimumHeight: Folio.FolioSettings.delegateIconSize
|
||||
Layout.preferredHeight: Layout.minimumHeight
|
||||
|
||||
// darken effect when hovered
|
||||
// TODO: removed for now, since hovered property seems to overlap with the touch pressed event
|
||||
// layer {
|
||||
// enabled: delegate.hovered
|
||||
// effect: ColorOverlay {
|
||||
// color: Qt.rgba(0, 0, 0, 0.3)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
DelegateLabel {
|
||||
id: label
|
||||
opacity: text.length > 0
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Folio.HomeScreenState.pageDelegateLabelHeight
|
||||
Layout.topMargin: Folio.HomeScreenState.pageDelegateLabelSpacing
|
||||
Layout.leftMargin: -parent.anchors.leftMargin + Kirigami.Units.smallSpacing
|
||||
Layout.rightMargin: -parent.anchors.rightMargin + Kirigami.Units.smallSpacing
|
||||
|
||||
text: delegate.name
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls as Controls
|
||||
import QtQuick.Effects
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState
|
||||
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
||||
|
||||
AbstractDelegate {
|
||||
id: root
|
||||
shadow: true
|
||||
name: application.name
|
||||
|
||||
property Folio.FolioApplication application
|
||||
|
||||
property alias iconItem: icon
|
||||
|
||||
property bool turnToFolder: false
|
||||
property bool turnToFolderAnimEnabled: false
|
||||
|
||||
function launchApp() {
|
||||
if (application.icon !== "") {
|
||||
MobileShellState.ShellDBusClient.openAppLaunchAnimation(
|
||||
application.icon,
|
||||
application.name,
|
||||
root.iconItem.Kirigami.ScenePosition.x + root.iconItem.width/2,
|
||||
root.iconItem.Kirigami.ScenePosition.y + root.iconItem.height/2,
|
||||
Math.min(root.iconItem.width, root.iconItem.height));
|
||||
}
|
||||
|
||||
application.setMinimizedDelegate(root);
|
||||
MobileShell.AppLaunch.launchOrActivateApp(application.storageId);
|
||||
}
|
||||
|
||||
onAfterClickAnimation: {
|
||||
launchApp();
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
height: Folio.FolioSettings.delegateIconSize
|
||||
width: Folio.FolioSettings.delegateIconSize
|
||||
|
||||
// background for folder creation animation
|
||||
Rectangle {
|
||||
id: rect
|
||||
radius: Kirigami.Units.largeSpacing
|
||||
color: Qt.rgba(255, 255, 255, 0.3)
|
||||
anchors.fill: parent
|
||||
|
||||
opacity: root.turnToFolder ? 1 : 0
|
||||
property real scaleAmount: root.turnToFolder ? 1.2 : 1.0
|
||||
|
||||
Behavior on scaleAmount {
|
||||
enabled: root.turnToFolderAnimEnabled
|
||||
NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
Behavior on opacity {
|
||||
enabled: root.turnToFolderAnimEnabled
|
||||
NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
|
||||
transform: Scale {
|
||||
origin.x: rect.width / 2
|
||||
origin.y: rect.height / 2
|
||||
xScale: rect.scaleAmount
|
||||
yScale: rect.scaleAmount
|
||||
}
|
||||
}
|
||||
|
||||
// app icon
|
||||
DelegateAppIcon {
|
||||
id: icon
|
||||
anchors.fill: parent
|
||||
source: root.application.icon
|
||||
|
||||
property real scaleAmount: root.turnToFolder ? 0.3 : 1.0
|
||||
Behavior on scaleAmount {
|
||||
enabled: root.turnToFolderAnimEnabled
|
||||
NumberAnimation { duration: root.turnToFolderAnimEnabled ? Kirigami.Units.longDuration : 0; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
|
||||
transform: Scale {
|
||||
origin.x: icon.width / 2
|
||||
origin.y: icon.height / 2
|
||||
xScale: icon.scaleAmount
|
||||
yScale: icon.scaleAmount
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
bottom: parent.bottom
|
||||
bottomMargin: Kirigami.Units.smallSpacing
|
||||
}
|
||||
visible: root.application.running
|
||||
radius: width
|
||||
width: Kirigami.Units.smallSpacing
|
||||
height: width
|
||||
color: Kirigami.Theme.highlightColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls as Controls
|
||||
import QtQuick.Effects
|
||||
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
||||
AbstractDelegate {
|
||||
id: root
|
||||
name: folder.name
|
||||
shadow: true
|
||||
|
||||
property Folio.FolioApplicationFolder folder
|
||||
|
||||
property bool appHoveredOver: false
|
||||
|
||||
contentItem: DelegateFolderIcon {
|
||||
folder: root.folder
|
||||
expandBackground: root.appHoveredOver
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls as Controls
|
||||
import QtQuick.Effects
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
||||
Kirigami.Icon {
|
||||
id: root
|
||||
|
||||
height: Folio.FolioSettings.delegateIconSize
|
||||
width: Folio.FolioSettings.delegateIconSize
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls as Controls
|
||||
import QtQuick.Effects
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property Folio.FolioApplicationFolder folder
|
||||
|
||||
property bool expandBackground: false
|
||||
|
||||
height: Folio.FolioSettings.delegateIconSize
|
||||
width: Folio.FolioSettings.delegateIconSize
|
||||
|
||||
Rectangle {
|
||||
id: rect
|
||||
radius: Kirigami.Units.largeSpacing
|
||||
color: Qt.rgba(255, 255, 255, 0.3)
|
||||
anchors.fill: parent
|
||||
|
||||
property real scaleAmount: root.expandBackground ? 1.2 : 1.0
|
||||
|
||||
Behavior on scaleAmount { NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad } }
|
||||
|
||||
transform: Scale {
|
||||
origin.x: root.width / 2
|
||||
origin.y: root.height / 2
|
||||
xScale: rect.scaleAmount
|
||||
yScale: rect.scaleAmount
|
||||
}
|
||||
}
|
||||
|
||||
Grid {
|
||||
id: previewGrid
|
||||
anchors.fill: parent
|
||||
anchors.margins: Kirigami.Units.smallSpacing * 2
|
||||
columns: 2
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
property var previews: root.folder.appPreviews
|
||||
|
||||
Repeater {
|
||||
model: previewGrid.previews
|
||||
delegate: Kirigami.Icon {
|
||||
implicitWidth: Math.round((previewGrid.width - previewGrid.spacing) / 2)
|
||||
implicitHeight: Math.round((previewGrid.width - previewGrid.spacing) / 2)
|
||||
source: modelData.icon
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls as Controls
|
||||
import QtQuick.Effects
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
||||
Loader {
|
||||
id: root
|
||||
|
||||
height: Folio.FolioSettings.delegateIconSize
|
||||
width: Folio.FolioSettings.delegateIconSize
|
||||
|
||||
property Folio.FolioDelegate delegate
|
||||
|
||||
sourceComponent: {
|
||||
if (!delegate) {
|
||||
return noIcon;
|
||||
} else if (delegate.type === Folio.FolioDelegate.Application) {
|
||||
return appIcon;
|
||||
} else if (delegate.type === Folio.FolioDelegate.Folder) {
|
||||
return folderIcon;
|
||||
} else {
|
||||
return noIcon;
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: noIcon
|
||||
Item {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: appIcon
|
||||
|
||||
DelegateAppIcon {
|
||||
source: delegate.application.icon
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: folderIcon
|
||||
|
||||
DelegateFolderIcon {
|
||||
folder: delegate.folder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.plasma.components 3.0 as PC3
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
||||
PC3.Label {
|
||||
id: label
|
||||
wrapMode: Text.WordWrap
|
||||
maximumLineCount: 2
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignTop
|
||||
elide: Text.ElideRight
|
||||
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.8
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
|
||||
MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowVerticalOffset: 1
|
||||
blurMax: 16
|
||||
shadowOpacity: 0.5
|
||||
}
|
||||
|
|
@ -1,9 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
|
||||
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
|
|
@ -19,59 +15,88 @@ import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState
|
|||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
import org.kde.plasma.private.mobileshell.windowplugin as WindowPlugin
|
||||
|
||||
|
||||
ContainmentItem {
|
||||
id: root
|
||||
|
||||
Component.onCompleted: {
|
||||
Folio.FolioSettings.load();
|
||||
Folio.ApplicationListModel.load();
|
||||
Folio.FavouritesModel.load();
|
||||
Folio.PageListModel.load();
|
||||
|
||||
// ensure the gestures work immediately on load
|
||||
forceActiveFocus();
|
||||
}
|
||||
|
||||
Plasmoid.onActivated: {
|
||||
// there's a couple of steps:
|
||||
// - minimize windows (only if we are in an app)
|
||||
// - open app drawer
|
||||
// - close app drawer and, if necessary, restore windows
|
||||
function homeAction() {
|
||||
const isInWindow = (!WindowPlugin.WindowUtil.isShowingDesktop && WindowPlugin.WindowMaximizedTracker.showingWindow);
|
||||
|
||||
// Always close action drawer
|
||||
if (MobileShellState.ShellDBusClient.isActionDrawerOpen) {
|
||||
MobileShellState.ShellDBusClient.closeActionDrawer();
|
||||
}
|
||||
|
||||
if (!WindowPlugin.WindowUtil.isShowingDesktop && WindowPlugin.WindowMaximizedTracker.showingWindow
|
||||
|| MobileShellState.ShellDBusClient.isActionDrawerOpen
|
||||
|| searchWidget.isOpen
|
||||
) {
|
||||
|
||||
// Always close the search widget as well
|
||||
if (searchWidget.isOpen) {
|
||||
searchWidget.close();
|
||||
}
|
||||
|
||||
} else if (folioHomeScreen.homeScreenState.currentView === HomeScreenState.PageView) {
|
||||
folioHomeScreen.homeScreenState.openAppDrawer();
|
||||
if (isInWindow) {
|
||||
Folio.HomeScreenState.closeFolder();
|
||||
Folio.HomeScreenState.closeSearchWidget();
|
||||
Folio.HomeScreenState.closeAppDrawer();
|
||||
Folio.HomeScreenState.goToPage(0);
|
||||
} else {
|
||||
folioHomeScreen.homeScreenState.closeAppDrawer();
|
||||
switch (Folio.HomeScreenState.viewState) {
|
||||
case Folio.HomeScreenState.PageView:
|
||||
if (Folio.HomeScreenState.currentPage === 0) {
|
||||
Folio.HomeScreenState.openAppDrawer();
|
||||
} else {
|
||||
Folio.HomeScreenState.goToPage(0);
|
||||
}
|
||||
break;
|
||||
case Folio.HomeScreenState.AppDrawerView:
|
||||
Folio.HomeScreenState.closeAppDrawer();
|
||||
break;
|
||||
case Folio.HomeScreenState.SearchWidgetView:
|
||||
Folio.HomeScreenState.closeSearchWidget();
|
||||
break;
|
||||
case Folio.HomeScreenState.FolderView:
|
||||
Folio.HomeScreenState.closeFolder();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Plasmoid.onActivated: homeAction()
|
||||
|
||||
Rectangle {
|
||||
id: appDrawerBackground
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(0, 0, 0, 0.6)
|
||||
|
||||
opacity: Folio.HomeScreenState.appDrawerOpenProgress
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: searchWidgetBackground
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(0, 0, 0, 0.3)
|
||||
|
||||
opacity: Folio.HomeScreenState.searchWidgetOpenProgress
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: settingsViewBackground
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(0, 0, 0, 0.3)
|
||||
|
||||
opacity: Folio.HomeScreenState.settingsOpenProgress
|
||||
}
|
||||
|
||||
MobileShell.HomeScreen {
|
||||
id: homeScreen
|
||||
anchors.fill: parent
|
||||
|
||||
plasmoidItem: root
|
||||
onResetHomeScreenPosition: {
|
||||
folioHomeScreen.homeScreenState.animateGoToPageIndex(0, Kirigami.Units.longDuration);
|
||||
folioHomeScreen.homeScreenState.closeAppDrawer();
|
||||
// NOTE: empty, because this is handled by homeAction()
|
||||
}
|
||||
|
||||
onHomeTriggered: {
|
||||
searchWidget.close();
|
||||
}
|
||||
|
||||
property bool componentComplete: false
|
||||
onHomeTriggered: root.homeAction()
|
||||
|
||||
contentItem: Item {
|
||||
|
||||
// homescreen component
|
||||
HomeScreen {
|
||||
id: folioHomeScreen
|
||||
|
|
@ -82,38 +107,9 @@ ContainmentItem {
|
|||
leftMargin: homeScreen.leftMargin
|
||||
rightMargin: homeScreen.rightMargin
|
||||
|
||||
opacity: (1 - searchWidget.openFactor)
|
||||
|
||||
// make the homescreen not interactable when task switcher or startup feedback is on
|
||||
interactive: !homeScreen.overlayShown
|
||||
}
|
||||
|
||||
// search component
|
||||
MobileShell.KRunnerWidget {
|
||||
id: searchWidget
|
||||
anchors.fill: parent
|
||||
|
||||
visible: openFactor > 0
|
||||
|
||||
topMargin: homeScreen.topMargin
|
||||
bottomMargin: homeScreen.bottomMargin
|
||||
leftMargin: homeScreen.leftMargin
|
||||
rightMargin: homeScreen.rightMargin
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: folioHomeScreen.homeScreenState
|
||||
|
||||
function onSwipeDownGestureBegin() {
|
||||
searchWidget.startGesture();
|
||||
}
|
||||
function onSwipeDownGestureEnd() {
|
||||
searchWidget.endGesture();
|
||||
}
|
||||
function onSwipeDownGestureOffset(offset) {
|
||||
searchWidget.updateGestureOffset(-offset);
|
||||
}
|
||||
}
|
||||
|
||||
// listen to app launch errors
|
||||
|
|
@ -125,3 +121,4 @@ ContainmentItem {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,138 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org>
|
||||
* SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.0
|
||||
import org.kde.plasma.core as PlasmaCore
|
||||
import org.kde.ksvg 1.0 as KSvg
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
PlasmaCore.ToolTipArea {
|
||||
id: button
|
||||
|
||||
location: PlasmaCore.Types.LeftEdge
|
||||
mainText: action !== undefined ? action.text : ""
|
||||
mainItem: toolTipDelegate
|
||||
|
||||
//API
|
||||
property KSvg.Svg svg
|
||||
property alias elementId: icon.elementId
|
||||
property QtObject action
|
||||
property bool backgroundVisible: false
|
||||
property int iconSize: Kirigami.Units.iconSizes.large
|
||||
property int pressedOffset: 1
|
||||
property bool checked: false
|
||||
property bool toggle: false
|
||||
property string text
|
||||
|
||||
signal clicked
|
||||
signal requestEditModeClose
|
||||
|
||||
implicitWidth: buttonRow.implicitWidth
|
||||
implicitHeight: buttonRow.implicitHeight
|
||||
|
||||
opacity: action==undefined||action.enabled?1:0.6
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Kirigami.Units.longDuration
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
onCheckedChanged: {
|
||||
if (checked) {
|
||||
buttonItem.elementId = "pressed"
|
||||
shadowItem.opacity = 0
|
||||
} else {
|
||||
buttonItem.elementId = "normal"
|
||||
shadowItem.opacity = 1
|
||||
}
|
||||
}
|
||||
|
||||
KSvg.Svg {
|
||||
id: buttonSvg
|
||||
imagePath: "widgets/actionbutton"
|
||||
}
|
||||
|
||||
KSvg.SvgItem {
|
||||
id: shadowItem
|
||||
svg: buttonSvg
|
||||
elementId: "shadow"
|
||||
width: iconSize+13//button.backgroundVisible?iconSize+8:iconSize
|
||||
height: width
|
||||
visible: button.backgroundVisible
|
||||
}
|
||||
|
||||
Row {
|
||||
id: buttonRow
|
||||
|
||||
Item {
|
||||
width: buttonItem.visible?buttonItem.width:iconSize
|
||||
height: buttonItem.visible?buttonItem.height:iconSize
|
||||
|
||||
KSvg.SvgItem {
|
||||
id: buttonItem
|
||||
svg: buttonSvg
|
||||
elementId: "normal"
|
||||
width: shadowItem.width
|
||||
height: shadowItem.height
|
||||
visible: backgroundVisible
|
||||
}
|
||||
|
||||
KSvg.SvgItem {
|
||||
id: icon
|
||||
width: iconSize
|
||||
height: iconSize
|
||||
svg: button.svg
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: actionText
|
||||
text: button.text
|
||||
style: Text.Outline
|
||||
color: Kirigami.Theme.textColor
|
||||
styleColor: Qt.rgba(1,1,1,0.4)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: -10
|
||||
anchors.topMargin: -10
|
||||
anchors.rightMargin: -10
|
||||
anchors.bottomMargin: -10
|
||||
preventStealing: true
|
||||
onPressed: {
|
||||
buttonItem.elementId = "pressed"
|
||||
shadowItem.opacity = 0;
|
||||
button.x = button.x + button.pressedOffset;
|
||||
button.y = button.y + button.pressedOffset;
|
||||
}
|
||||
onReleased: {
|
||||
if (button.checked || !button.toggle) {
|
||||
buttonItem.elementId = "normal"
|
||||
shadowItem.opacity = 1
|
||||
button.checked = false
|
||||
} else {
|
||||
button.checked = true
|
||||
}
|
||||
button.x = button.x - button.pressedOffset;
|
||||
button.y = button.y - button.pressedOffset;
|
||||
}
|
||||
onClicked: {
|
||||
if (action) {
|
||||
action.trigger()
|
||||
} else {
|
||||
button.clicked()
|
||||
}
|
||||
requestEditModeClose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.1
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.plasma.plasmoid 2.0
|
||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||
import org.kde.ksvg 1.0 as KSvg
|
||||
|
||||
import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager
|
||||
|
||||
ContainmentLayoutManager.ConfigOverlayWithHandles {
|
||||
id: overlay
|
||||
property var appletContainer
|
||||
|
||||
signal requestRemoveTrigger
|
||||
signal requestEditModeClose
|
||||
|
||||
readonly property int iconSize: Kirigami.Units.iconSizes.medium
|
||||
KSvg.Svg {
|
||||
id: configIconsSvg
|
||||
imagePath: "widgets/configuration-icons"
|
||||
}
|
||||
|
||||
PlasmaComponents.Label {
|
||||
id: toolTipDelegate
|
||||
|
||||
width: contentWidth
|
||||
height: undefined
|
||||
|
||||
property Item toolTip
|
||||
|
||||
text: (toolTip != null) ? toolTip.mainText : ""
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: removeAnim
|
||||
NumberAnimation {
|
||||
target: overlay.itemContainer
|
||||
property: "scale"
|
||||
from: 1
|
||||
to: 0
|
||||
duration: Kirigami.Units.longDuration
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
ScriptAction {
|
||||
script: {
|
||||
overlay.requestRemoveTrigger();
|
||||
overlay.requestEditModeClose();
|
||||
}
|
||||
}
|
||||
}
|
||||
KSvg.FrameSvgItem {
|
||||
id: frame
|
||||
anchors.centerIn: parent
|
||||
|
||||
width: layout.implicitWidth + margins.left + margins.right
|
||||
height: layout.implicitHeight + margins.top + margins.bottom
|
||||
imagePath: "widgets/background"
|
||||
|
||||
RowLayout {
|
||||
id: layout
|
||||
spacing: Kirigami.Units.gridUnit
|
||||
anchors {
|
||||
fill: parent
|
||||
topMargin: parent.margins.top
|
||||
leftMargin: parent.margins.left
|
||||
bottomMargin: parent.margins.bottom
|
||||
rightMargin: parent.margins.right
|
||||
}
|
||||
|
||||
ActionButton {
|
||||
svg: configIconsSvg
|
||||
elementId: "configure"
|
||||
iconSize: overlay.iconSize
|
||||
visible: (action && typeof(action) != "undefined") ? action.enabled : false
|
||||
action: (applet) ? applet.action("configure") : null
|
||||
|
||||
onRequestEditModeClose: {
|
||||
overlay.requestEditModeClose();
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (action && typeof(action) != "undefined") {
|
||||
action.enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ActionButton {
|
||||
id: closeButton
|
||||
svg: configIconsSvg
|
||||
elementId: "delete"
|
||||
mainText: i18n("Remove")
|
||||
iconSize: overlay.iconSize
|
||||
visible: {
|
||||
if (!applet) {
|
||||
return false;
|
||||
}
|
||||
var a = applet.action("remove");
|
||||
return (a && typeof(a) != "undefined") ? a.enabled : false;
|
||||
}
|
||||
// we don't set action, since we want to catch the button click,
|
||||
// animate, and then trigger the "remove" action
|
||||
// Triggering the action is handled in the overlay.itemContainer, we just
|
||||
// emit a signal here to avoid the applet-gets-removed-before-we-
|
||||
// can-animate it race condition.
|
||||
onClicked: {
|
||||
removeAnim.restart();
|
||||
}
|
||||
|
||||
onRequestEditModeClose: {
|
||||
overlay.requestEditModeClose();
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
var a = applet.action("remove");
|
||||
if (a && typeof(a) != "undefined") {
|
||||
a.enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.4
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls 2.3 as Controls
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.plasma.plasmoid 2.0
|
||||
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.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
||||
PC3.RoundButton {
|
||||
id: removeButton
|
||||
|
||||
required property Folio.DesktopModel desktopModel
|
||||
|
||||
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: Kirigami.Units.longDuration
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
ScriptAction {
|
||||
script: {
|
||||
appletsLayout.releaseSpace(delegate);
|
||||
desktopModel.removeFavorite(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
SequentialAnimation {
|
||||
id: removeButtonAnim
|
||||
NumberAnimation {
|
||||
id: removeButtonScaleAnim
|
||||
target: removeButton
|
||||
property: "scale"
|
||||
duration: Kirigami.Units.longDuration
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
ScriptAction {
|
||||
script: {
|
||||
if (removeButton.scale === 0) {
|
||||
removeButton.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Marco Martin <mart@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
Item {
|
||||
Behavior on opacity {
|
||||
OpacityAnimator {
|
||||
duration: Kirigami.Units.longDuration * 2
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
leftMargin: Kirigami.Units.gridUnit + root.leftPadding
|
||||
rightMargin: Kirigami.Units.gridUnit + root.rightPadding
|
||||
}
|
||||
height: 1
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Horizontal
|
||||
GradientStop { position: 0.0; color: Qt.rgba(1, 1, 1, 0) }
|
||||
GradientStop { position: 0.15; color: Qt.rgba(1, 1, 1, 0.5) }
|
||||
GradientStop { position: 0.5; color: Qt.rgba(1, 1, 1, 1) }
|
||||
GradientStop { position: 0.85; color: Qt.rgba(1, 1, 1, 0.5) }
|
||||
GradientStop { position: 1.0; color: Qt.rgba(1, 1, 1, 0) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Window 2.12
|
||||
import QtQuick.Layouts 1.1
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.plasma.plasmoid 2.0
|
||||
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
|
||||
|
||||
MouseArea {
|
||||
id: arrowUpIcon
|
||||
z: 9
|
||||
|
||||
property Flickable flickable
|
||||
property real factor: 0
|
||||
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
height: Kirigami.Units.iconSizes.smallMedium
|
||||
signal openRequested
|
||||
signal closeRequested
|
||||
|
||||
onClicked: {
|
||||
openRequested();
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.centerIn: parent
|
||||
|
||||
width: Kirigami.Units.iconSizes.smallMedium
|
||||
height: width
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
right: parent.horizontalCenter
|
||||
left: parent.left
|
||||
verticalCenterOffset: -arrowUpIcon.height/4 + (arrowUpIcon.height/4) * arrowUpIcon.factor
|
||||
}
|
||||
color: "white"
|
||||
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: "white"
|
||||
transformOrigin: Item.Left
|
||||
rotation: 45 - 90 * arrowUpIcon.factor
|
||||
antialiasing: true
|
||||
height: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.4
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import org.kde.ksvg 1.0 as KSvg
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
KSvg.SvgItem {
|
||||
id: scrollIndicator
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
z: 2
|
||||
opacity: 0
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||
Kirigami.Theme.inherit: false
|
||||
|
||||
imagePath: "widgets/arrows"
|
||||
elementId: "left-arrow"
|
||||
width: Kirigami.Units.iconSizes.large
|
||||
height: width
|
||||
layer.enabled: true
|
||||
layer.effect: DropShadow {
|
||||
cached: true
|
||||
horizontalOffset: 0
|
||||
verticalOffset: 2
|
||||
radius: 8.0
|
||||
samples: 16
|
||||
color: Qt.rgba(0, 0, 0, 0.8)
|
||||
}
|
||||
Behavior on opacity {
|
||||
OpacityAnimator {
|
||||
duration: Kirigami.Units.longDuration * 2
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls as QQC2
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.plasma.components 3.0 as PC3
|
||||
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
||||
import '../delegate'
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var homeScreen
|
||||
property real settingsModeHomeScreenScale
|
||||
|
||||
signal requestLeaveSettingsMode()
|
||||
|
||||
MouseArea {
|
||||
id: closeSettings
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: settingsBar.top
|
||||
|
||||
onClicked: {
|
||||
Folio.HomeScreenState.closeSettingsView();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: settingsBar
|
||||
|
||||
Kirigami.Theme.inherit: false
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: root.height * (1 - settingsModeHomeScreenScale)
|
||||
|
||||
RowLayout {
|
||||
id: settingsOptions
|
||||
anchors.centerIn: parent
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
QQC2.ToolButton {
|
||||
icon.source: 'edit-image'
|
||||
text: i18n('Wallpapers')
|
||||
enabled: false
|
||||
display: QQC2.ToolButton.TextUnderIcon
|
||||
|
||||
implicitHeight: Kirigami.Units.gridUnit * 4
|
||||
implicitWidth: Kirigami.Units.gridUnit * 5
|
||||
}
|
||||
|
||||
QQC2.ToolButton {
|
||||
icon.source: 'settings-configure'
|
||||
text: ('Settings')
|
||||
display: QQC2.ToolButton.TextUnderIcon
|
||||
|
||||
implicitHeight: Kirigami.Units.gridUnit * 4
|
||||
implicitWidth: Kirigami.Units.gridUnit * 5
|
||||
|
||||
onClicked: settingsWindow.showMaximized()
|
||||
}
|
||||
|
||||
QQC2.ToolButton {
|
||||
icon.source: 'widget-alternatives'
|
||||
text: 'Widgets'
|
||||
enabled: false
|
||||
display: QQC2.ToolButton.TextUnderIcon
|
||||
|
||||
implicitHeight: Kirigami.Units.gridUnit * 4
|
||||
implicitWidth: Kirigami.Units.gridUnit * 5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsWindow {
|
||||
id: settingsWindow
|
||||
visible: false
|
||||
|
||||
onRequestConfigureMenu: {
|
||||
homeScreen.openConfigure()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls as QQC2
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import '../delegate'
|
||||
|
||||
Kirigami.ApplicationWindow {
|
||||
id: root
|
||||
flags: Qt.FramelessWindowHint
|
||||
|
||||
pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.ToolBar
|
||||
pageStack.globalToolBar.showNavigationButtons: Kirigami.ApplicationHeaderStyle.NoNavigationButtons;
|
||||
|
||||
signal requestConfigureMenu()
|
||||
|
||||
pageStack.initialPage: Kirigami.ScrollablePage {
|
||||
id: page
|
||||
opacity: root.opacity
|
||||
|
||||
titleDelegate: RowLayout {
|
||||
QQC2.ToolButton {
|
||||
Layout.leftMargin: -Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing
|
||||
icon.name: "arrow-left"
|
||||
onClicked: root.close()
|
||||
}
|
||||
|
||||
Kirigami.Heading {
|
||||
level: 1
|
||||
text: page.title
|
||||
}
|
||||
}
|
||||
|
||||
title: i18n("Homescreen Settings")
|
||||
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
|
||||
ColumnLayout {
|
||||
FormCard.FormHeader {
|
||||
title: i18n("Icons")
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
Kirigami.Theme.inherit: false
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||
|
||||
Item {
|
||||
Layout.preferredHeight: Folio.HomeScreenState.pageCellHeight
|
||||
Layout.fillWidth: true
|
||||
|
||||
AbstractDelegate {
|
||||
anchors.centerIn: parent
|
||||
implicitHeight: Folio.HomeScreenState.pageCellHeight
|
||||
implicitWidth: Folio.HomeScreenState.pageCellWidth
|
||||
name: i18n('Application')
|
||||
|
||||
contentItem: DelegateAppIcon {
|
||||
height: Folio.FolioSettings.delegateIconSize
|
||||
width: Folio.FolioSettings.delegateIconSize
|
||||
source: 'applications-system'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
id: iconsCard
|
||||
readonly property bool isVerticalOrientation: Folio.HomeScreenState.pageOrientation === Folio.HomeScreenState.RegularPosition ||
|
||||
Folio.HomeScreenState.pageOrientation === Folio.HomeScreenState.RotateUpsideDown
|
||||
|
||||
readonly property string numOfRowsText: i18n("Number of rows")
|
||||
readonly property string numOfColumnsText: i18n("Number of columns")
|
||||
|
||||
FormCard.FormSpinBoxDelegate {
|
||||
id: iconSizeSpinBox
|
||||
label: i18n("Size of icons on homescreen")
|
||||
from: 16
|
||||
to: 128
|
||||
value: Folio.FolioSettings.delegateIconSize
|
||||
onValueChanged: {
|
||||
if (value !== Folio.FolioSettings.delegateIconSize) {
|
||||
Folio.FolioSettings.delegateIconSize = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormSpinBoxDelegate {
|
||||
id: rowsSpinBox
|
||||
label: iconsCard.isVerticalOrientation ? iconsCard.numOfRowsText : iconsCard.numOfColumnsText
|
||||
from: 3
|
||||
to: 10
|
||||
value: Folio.FolioSettings.homeScreenRows
|
||||
onValueChanged: {
|
||||
if (value !== Folio.FolioSettings.homeScreenRows) {
|
||||
Folio.FolioSettings.homeScreenRows = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormSpinBoxDelegate {
|
||||
id: columnsSpinBox
|
||||
label: iconsCard.isVerticalOrientation ? iconsCard.numOfColumnsText : iconsCard.numOfRowsText
|
||||
from: 3
|
||||
to: 10
|
||||
value: Folio.FolioSettings.homeScreenColumns
|
||||
onValueChanged: {
|
||||
if (value !== Folio.FolioSettings.homeScreenColumns) {
|
||||
Folio.FolioSettings.homeScreenColumns = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormSectionText {
|
||||
text: i18n("The rows and columns will swap depending on the screen rotation.")
|
||||
}
|
||||
|
||||
FormCard.FormHeader {
|
||||
title: i18n("Labels")
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
FormCard.FormSwitchDelegate {
|
||||
id: showLabelsOnHomeScreen
|
||||
text: i18n("Show labels on homescreen")
|
||||
checked: Folio.FolioSettings.showPagesAppLabels
|
||||
onCheckedChanged: {
|
||||
if (checked != Folio.FolioSettings.showPagesAppLabels) {
|
||||
Folio.FolioSettings.showPagesAppLabels = checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormDelegateSeparator { above: showLabelsOnHomeScreen; below: showLabelsInFavourites }
|
||||
|
||||
FormCard.FormSwitchDelegate {
|
||||
id: showLabelsInFavourites
|
||||
text: i18n("Show labels in favorites bar")
|
||||
checked: Folio.FolioSettings.showFavouritesAppLabels
|
||||
onCheckedChanged: {
|
||||
if (checked != Folio.FolioSettings.showFavouritesAppLabels) {
|
||||
Folio.FolioSettings.showFavouritesAppLabels = checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormHeader {
|
||||
title: i18n("Favorites Bar")
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
FormCard.FormSwitchDelegate {
|
||||
text: i18n('Show background')
|
||||
icon.name: 'draw-rectangle'
|
||||
checked: Folio.FolioSettings.showFavouritesBarBackground
|
||||
onCheckedChanged: {
|
||||
if (checked !== Folio.FolioSettings.showFavouritesBarBackground) {
|
||||
Folio.FolioSettings.showFavouritesBarBackground = checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormHeader {
|
||||
title: i18n("General")
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
FormCard.FormButtonDelegate {
|
||||
id: containmentSettings
|
||||
text: i18n('Switch Homescreen')
|
||||
icon.name: 'settings-configure'
|
||||
onClicked: root.requestConfigureMenu()
|
||||
}
|
||||
|
||||
FormCard.FormDelegateSeparator { above: containmentSettings; below: exportSettings }
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
id: exportSettings
|
||||
enabled: false
|
||||
text: 'Export layout (in development)'
|
||||
icon.name: 'document-export'
|
||||
}
|
||||
|
||||
FormCard.FormDelegateSeparator { above: exportSettings; below: importSettings }
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
id: importSettings
|
||||
enabled: false
|
||||
text: 'Import layout (in development)'
|
||||
icon.name: 'document-import'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
148
containments/homescreens/folio/pagelistmodel.cpp
Normal file
148
containments/homescreens/folio/pagelistmodel.cpp
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "pagelistmodel.h"
|
||||
#include "homescreenstate.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
|
||||
PageListModel *PageListModel::self()
|
||||
{
|
||||
static PageListModel *model = new PageListModel;
|
||||
return model;
|
||||
}
|
||||
|
||||
PageListModel::PageListModel(QObject *parent)
|
||||
: QAbstractListModel{parent}
|
||||
{
|
||||
}
|
||||
|
||||
int PageListModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return m_pages.size();
|
||||
}
|
||||
|
||||
QVariant PageListModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
switch (role) {
|
||||
case PageRole:
|
||||
return QVariant::fromValue(m_pages.at(index.row()));
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> PageListModel::roleNames() const
|
||||
{
|
||||
return {{PageRole, "delegate"}};
|
||||
}
|
||||
|
||||
int PageListModel::length()
|
||||
{
|
||||
return m_pages.size();
|
||||
}
|
||||
|
||||
PageModel *PageListModel::getPage(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_pages.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return m_pages[index];
|
||||
}
|
||||
|
||||
void PageListModel::removePage(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_pages.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginRemoveRows(QModelIndex(), index, index);
|
||||
m_pages[index]->deleteLater();
|
||||
m_pages.removeAt(index);
|
||||
endRemoveRows();
|
||||
|
||||
Q_EMIT lengthChanged();
|
||||
|
||||
save();
|
||||
}
|
||||
|
||||
Q_INVOKABLE void PageListModel::addPageAtEnd()
|
||||
{
|
||||
beginInsertRows(QModelIndex(), m_pages.size(), m_pages.size());
|
||||
|
||||
PageModel *page = new PageModel{{}, this};
|
||||
connect(page, &PageModel::saveRequested, this, &PageListModel::save);
|
||||
|
||||
m_pages.append(page);
|
||||
|
||||
endInsertRows();
|
||||
|
||||
Q_EMIT lengthChanged();
|
||||
|
||||
save();
|
||||
}
|
||||
|
||||
bool PageListModel::isLastPageEmpty()
|
||||
{
|
||||
return m_pages.size() == 0 ? true : m_pages[m_pages.size() - 1]->isPageEmpty();
|
||||
}
|
||||
|
||||
void PageListModel::save()
|
||||
{
|
||||
if (!m_applet) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray arr;
|
||||
for (auto &page : m_pages) {
|
||||
arr.push_back(page->toJson());
|
||||
}
|
||||
QByteArray data = QJsonDocument(arr).toJson(QJsonDocument::Compact);
|
||||
|
||||
m_applet->config().writeEntry("Pages", QString::fromStdString(data.toStdString()));
|
||||
Q_EMIT m_applet->configNeedsSaving();
|
||||
}
|
||||
|
||||
void PageListModel::load()
|
||||
{
|
||||
if (!m_applet) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(m_applet->config().readEntry("Pages", "{}").toUtf8());
|
||||
|
||||
beginResetModel();
|
||||
|
||||
m_pages.clear();
|
||||
|
||||
for (QJsonValueRef r : doc.array()) {
|
||||
QJsonArray obj = r.toArray();
|
||||
|
||||
PageModel *page = PageModel::fromJson(obj, this);
|
||||
if (page) {
|
||||
connect(page, &PageModel::saveRequested, this, &PageListModel::save);
|
||||
m_pages.append(page);
|
||||
}
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
|
||||
Q_EMIT lengthChanged();
|
||||
|
||||
// add page if there are no pages
|
||||
if (m_pages.size() == 0) {
|
||||
addPageAtEnd();
|
||||
}
|
||||
}
|
||||
|
||||
void PageListModel::setApplet(Plasma::Applet *applet)
|
||||
{
|
||||
m_applet = applet;
|
||||
}
|
||||
48
containments/homescreens/folio/pagelistmodel.h
Normal file
48
containments/homescreens/folio/pagelistmodel.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pagemodel.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QList>
|
||||
|
||||
#include <Plasma/Applet>
|
||||
|
||||
class PageListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int length READ length NOTIFY lengthChanged)
|
||||
|
||||
public:
|
||||
enum Roles { PageRole = Qt::UserRole + 1 };
|
||||
|
||||
PageListModel(QObject *parent = nullptr);
|
||||
|
||||
static PageListModel *self();
|
||||
|
||||
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;
|
||||
|
||||
int length();
|
||||
|
||||
PageModel *getPage(int index);
|
||||
void removePage(int index);
|
||||
Q_INVOKABLE void addPageAtEnd();
|
||||
bool isLastPageEmpty();
|
||||
|
||||
void save();
|
||||
Q_INVOKABLE void load();
|
||||
|
||||
void setApplet(Plasma::Applet *applet);
|
||||
|
||||
Q_SIGNALS:
|
||||
void lengthChanged();
|
||||
|
||||
private:
|
||||
QList<PageModel *> m_pages;
|
||||
|
||||
Plasma::Applet *m_applet{nullptr};
|
||||
};
|
||||
291
containments/homescreens/folio/pagemodel.cpp
Normal file
291
containments/homescreens/folio/pagemodel.cpp
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
// SPDX-FileCopyrightText: 2022-2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "pagemodel.h"
|
||||
#include "foliosettings.h"
|
||||
#include "homescreenstate.h"
|
||||
|
||||
FolioPageDelegate::FolioPageDelegate(int row, int column, QObject *parent)
|
||||
: FolioDelegate{parent}
|
||||
, m_row{row}
|
||||
, m_column{column}
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
FolioPageDelegate::FolioPageDelegate(int row, int column, FolioApplication *application, QObject *parent)
|
||||
: FolioDelegate{application, parent}
|
||||
, m_row{row}
|
||||
, m_column{column}
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
FolioPageDelegate::FolioPageDelegate(int row, int column, FolioApplicationFolder *folder, QObject *parent)
|
||||
: FolioDelegate{folder, parent}
|
||||
, m_row{row}
|
||||
, m_column{column}
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
FolioPageDelegate::FolioPageDelegate(int row, int column, FolioDelegate *delegate, QObject *parent)
|
||||
: FolioDelegate{parent}
|
||||
, m_row{row}
|
||||
, m_column{column}
|
||||
{
|
||||
m_type = delegate->type();
|
||||
m_application = delegate->application();
|
||||
m_folder = delegate->folder();
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
void FolioPageDelegate::init()
|
||||
{
|
||||
// we have to use the "real" rows and columns, so fetch them from FolioSettings instead of HomeScreenState
|
||||
switch (HomeScreenState::self()->pageOrientation()) {
|
||||
case HomeScreenState::RegularPosition:
|
||||
m_realRow = m_row;
|
||||
m_realColumn = m_column;
|
||||
break;
|
||||
case HomeScreenState::RotateClockwise:
|
||||
m_realRow = HomeScreenState::self()->pageColumns() - m_column - 1;
|
||||
m_realColumn = m_row;
|
||||
break;
|
||||
case HomeScreenState::RotateCounterClockwise: // (0, 4) -> (4, 3)
|
||||
m_realRow = m_column;
|
||||
m_realColumn = HomeScreenState::self()->pageRows() - m_row - 1;
|
||||
break;
|
||||
case HomeScreenState::RotateUpsideDown:
|
||||
m_realRow = HomeScreenState::self()->pageRows() - m_row - 1;
|
||||
m_realColumn = HomeScreenState::self()->pageColumns() - m_column - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
connect(HomeScreenState::self(), &HomeScreenState::pageOrientationChanged, this, [this]() {
|
||||
setRow(getTranslatedRow(m_realRow, m_realColumn));
|
||||
setColumn(getTranslatedColumn(m_realRow, m_realColumn));
|
||||
});
|
||||
}
|
||||
|
||||
FolioPageDelegate *FolioPageDelegate::fromJson(QJsonObject &obj, QObject *parent)
|
||||
{
|
||||
FolioDelegate *fd = FolioDelegate::fromJson(obj, parent);
|
||||
|
||||
if (!fd) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int realRow = obj[QStringLiteral("row")].toInt();
|
||||
int realColumn = obj[QStringLiteral("column")].toInt();
|
||||
|
||||
int row = getTranslatedRow(realRow, realColumn);
|
||||
int column = getTranslatedColumn(realRow, realColumn);
|
||||
|
||||
FolioPageDelegate *delegate = new FolioPageDelegate{row, column, fd, parent};
|
||||
fd->deleteLater();
|
||||
|
||||
return delegate;
|
||||
}
|
||||
|
||||
int FolioPageDelegate::getTranslatedRow(int realRow, int realColumn)
|
||||
{
|
||||
// we have to use the "real" rows and columns, so fetch them from FolioSettings instead of HomeScreenState
|
||||
switch (HomeScreenState::self()->pageOrientation()) {
|
||||
case HomeScreenState::RegularPosition:
|
||||
return realRow;
|
||||
case HomeScreenState::RotateClockwise:
|
||||
return realColumn;
|
||||
case HomeScreenState::RotateCounterClockwise:
|
||||
return FolioSettings::self()->homeScreenColumns() - realColumn - 1;
|
||||
case HomeScreenState::RotateUpsideDown:
|
||||
return FolioSettings::self()->homeScreenRows() - realRow - 1;
|
||||
}
|
||||
return realRow;
|
||||
}
|
||||
|
||||
int FolioPageDelegate::getTranslatedColumn(int realRow, int realColumn)
|
||||
{
|
||||
// we have to use the "real" rows and columns, so fetch them from FolioSettings instead of HomeScreenState
|
||||
switch (HomeScreenState::self()->pageOrientation()) {
|
||||
case HomeScreenState::RegularPosition:
|
||||
return realColumn;
|
||||
case HomeScreenState::RotateClockwise:
|
||||
return FolioSettings::self()->homeScreenRows() - realRow - 1;
|
||||
case HomeScreenState::RotateCounterClockwise:
|
||||
return realRow;
|
||||
case HomeScreenState::RotateUpsideDown:
|
||||
return FolioSettings::self()->homeScreenColumns() - realColumn - 1;
|
||||
}
|
||||
return realRow;
|
||||
}
|
||||
|
||||
QJsonObject FolioPageDelegate::toJson() const
|
||||
{
|
||||
QJsonObject o = FolioDelegate::toJson();
|
||||
o[QStringLiteral("row")] = m_realRow;
|
||||
o[QStringLiteral("column")] = m_realColumn;
|
||||
return o;
|
||||
}
|
||||
|
||||
int FolioPageDelegate::row()
|
||||
{
|
||||
return m_row;
|
||||
}
|
||||
|
||||
void FolioPageDelegate::setRow(int row)
|
||||
{
|
||||
m_row = row;
|
||||
Q_EMIT rowChanged();
|
||||
}
|
||||
|
||||
int FolioPageDelegate::column()
|
||||
{
|
||||
return m_column;
|
||||
}
|
||||
|
||||
void FolioPageDelegate::setColumn(int column)
|
||||
{
|
||||
m_column = column;
|
||||
Q_EMIT columnChanged();
|
||||
}
|
||||
|
||||
PageModel::PageModel(QList<FolioPageDelegate *> delegates, QObject *parent)
|
||||
: QAbstractListModel{parent}
|
||||
, m_delegates{delegates}
|
||||
{
|
||||
}
|
||||
|
||||
PageModel::~PageModel() = default;
|
||||
|
||||
PageModel *PageModel::fromJson(QJsonArray &arr, QObject *parent)
|
||||
{
|
||||
QList<FolioPageDelegate *> delegates;
|
||||
QList<FolioPageDelegate *> folderDelegates;
|
||||
|
||||
for (QJsonValueRef r : arr) {
|
||||
QJsonObject obj = r.toObject();
|
||||
|
||||
FolioPageDelegate *delegate = FolioPageDelegate::fromJson(obj, parent);
|
||||
if (delegate) {
|
||||
delegates.append(delegate);
|
||||
|
||||
if (delegate->type() == FolioDelegate::Folder) {
|
||||
folderDelegates.append(delegate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PageModel *model = new PageModel{delegates, parent};
|
||||
|
||||
// ensure folders request saves
|
||||
for (auto *delegate : folderDelegates) {
|
||||
connect(delegate->folder(), &FolioApplicationFolder::saveRequested, model, &PageModel::save);
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
QJsonArray PageModel::toJson() const
|
||||
{
|
||||
QJsonArray arr;
|
||||
|
||||
for (FolioPageDelegate *delegate : m_delegates) {
|
||||
if (!delegate) {
|
||||
continue;
|
||||
}
|
||||
|
||||
arr.append(delegate->toJson());
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
int PageModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return m_delegates.size();
|
||||
}
|
||||
|
||||
QVariant PageModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
switch (role) {
|
||||
case DelegateRole:
|
||||
return QVariant::fromValue(m_delegates.at(index.row()));
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> PageModel::roleNames() const
|
||||
{
|
||||
return {{DelegateRole, "delegate"}};
|
||||
}
|
||||
|
||||
void PageModel::removeDelegate(int row, int col)
|
||||
{
|
||||
bool removed = false;
|
||||
|
||||
for (int i = 0; i < m_delegates.size(); ++i) {
|
||||
if (m_delegates[i]->row() == row && m_delegates[i]->column() == col) {
|
||||
beginRemoveRows(QModelIndex(), i, i);
|
||||
// HACK: do not deleteLater(), because the delegate might still be used somewhere else
|
||||
m_delegates.removeAt(i);
|
||||
endRemoveRows();
|
||||
|
||||
removed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (removed) {
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
bool PageModel::addDelegate(FolioPageDelegate *delegate)
|
||||
{
|
||||
if (delegate->row() < 0 || delegate->row() >= HomeScreenState::self()->pageRows() || delegate->column() < 0
|
||||
|| delegate->column() >= HomeScreenState::self()->pageColumns()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if there already exists a delegate in this space
|
||||
for (FolioPageDelegate *d : m_delegates) {
|
||||
if (d->row() == delegate->row() && d->column() == delegate->column()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
beginInsertRows(QModelIndex(), m_delegates.size(), m_delegates.size());
|
||||
m_delegates.append(delegate);
|
||||
endInsertRows();
|
||||
|
||||
save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FolioPageDelegate *PageModel::getDelegate(int row, int col)
|
||||
{
|
||||
for (FolioPageDelegate *d : m_delegates) {
|
||||
if (d->row() == row && d->column() == col) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool PageModel::isPageEmpty()
|
||||
{
|
||||
return m_delegates.size() == 0;
|
||||
}
|
||||
|
||||
void PageModel::save()
|
||||
{
|
||||
Q_EMIT saveRequested();
|
||||
}
|
||||
89
containments/homescreens/folio/pagemodel.h
Normal file
89
containments/homescreens/folio/pagemodel.h
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
// SPDX-FileCopyrightText: 2022-2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "folioapplication.h"
|
||||
#include "folioapplicationfolder.h"
|
||||
#include "foliodelegate.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QJsonArray>
|
||||
#include <QList>
|
||||
|
||||
#include <Plasma/Applet>
|
||||
|
||||
class FolioPageDelegate : public FolioDelegate
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int row READ row NOTIFY rowChanged)
|
||||
Q_PROPERTY(int column READ column NOTIFY columnChanged)
|
||||
|
||||
public:
|
||||
FolioPageDelegate(int row = 0, int column = 0, QObject *parent = nullptr);
|
||||
FolioPageDelegate(int row, int column, FolioApplication *application, QObject *parent);
|
||||
FolioPageDelegate(int row, int column, FolioApplicationFolder *folder, QObject *parent);
|
||||
FolioPageDelegate(int row, int column, FolioDelegate *delegate, QObject *parent);
|
||||
|
||||
static FolioPageDelegate *fromJson(QJsonObject &obj, QObject *parent);
|
||||
static int getTranslatedRow(int realRow, int realColumn);
|
||||
static int getTranslatedColumn(int realRow, int realColumn);
|
||||
|
||||
virtual QJsonObject toJson() const override;
|
||||
|
||||
int row();
|
||||
void setRow(int row);
|
||||
|
||||
int column();
|
||||
void setColumn(int column);
|
||||
|
||||
Q_SIGNALS:
|
||||
void rowChanged();
|
||||
void columnChanged();
|
||||
|
||||
private:
|
||||
void init();
|
||||
|
||||
int m_realRow;
|
||||
int m_realColumn;
|
||||
int m_row;
|
||||
int m_column;
|
||||
};
|
||||
|
||||
class PageModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Roles {
|
||||
DelegateRole = Qt::UserRole + 1,
|
||||
XPositionRole,
|
||||
YPositionRole,
|
||||
ShownRole,
|
||||
};
|
||||
|
||||
PageModel(QList<FolioPageDelegate *> delegates = QList<FolioPageDelegate *>{}, QObject *parent = nullptr);
|
||||
~PageModel();
|
||||
|
||||
static PageModel *fromJson(QJsonArray &arr, QObject *parent);
|
||||
|
||||
QJsonArray toJson() const;
|
||||
|
||||
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;
|
||||
|
||||
Q_INVOKABLE void removeDelegate(int row, int col);
|
||||
bool addDelegate(FolioPageDelegate *delegate);
|
||||
FolioPageDelegate *getDelegate(int row, int col);
|
||||
|
||||
bool isPageEmpty();
|
||||
|
||||
public Q_SLOTS:
|
||||
void save();
|
||||
|
||||
Q_SIGNALS:
|
||||
void saveRequested();
|
||||
|
||||
private:
|
||||
QList<FolioPageDelegate *> m_delegates;
|
||||
};
|
||||
|
|
@ -4,7 +4,17 @@
|
|||
set(folioplugin_SRCS
|
||||
folioplugin.cpp
|
||||
applicationlistmodel.cpp
|
||||
desktopmodel.cpp
|
||||
homescreenstate.cpp
|
||||
windowlistener.cpp
|
||||
favouritesmodel.cpp
|
||||
folioapplication.cpp
|
||||
folioapplicationfolder.cpp
|
||||
foliodelegate.cpp
|
||||
foliosettings.cpp
|
||||
pagemodel.cpp
|
||||
pagelistmodel.cpp
|
||||
delegatetoucharea.cpp
|
||||
dragstate.cpp
|
||||
)
|
||||
|
||||
install(FILES qmldir DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/private/mobile/homescreen/folio)
|
||||
|
|
@ -26,4 +36,3 @@ target_link_libraries(folioplugin
|
|||
|
||||
set_property(TARGET folioplugin PROPERTY LIBRARY_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/org/kde/private/mobile/homescreen/folio)
|
||||
install(TARGETS folioplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/private/mobile/homescreen/folio)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,236 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2014 Antonis Tsiapaliokas <antonis.tsiapaliokas@kde.org>
|
||||
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "applicationlistmodel.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
#include <QModelIndex>
|
||||
#include <QProcess>
|
||||
#include <QQuickWindow>
|
||||
|
||||
#include <KApplicationTrader>
|
||||
#include <KConfigGroup>
|
||||
#include <KIO/ApplicationLauncherJob>
|
||||
#include <KNotificationJobUiDelegate>
|
||||
#include <KService>
|
||||
#include <KSharedConfig>
|
||||
#include <KSycoca>
|
||||
|
||||
ApplicationListModel::ApplicationListModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
connect(KSycoca::self(), &KSycoca::databaseChanged, this, &ApplicationListModel::sycocaDbChanged);
|
||||
|
||||
// initialize wayland window checking
|
||||
KWayland::Client::ConnectionThread *connection = KWayland::Client::ConnectionThread::fromApplication(this);
|
||||
if (!connection) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto *registry = new KWayland::Client::Registry(this);
|
||||
registry->create(connection);
|
||||
|
||||
connect(registry, &KWayland::Client::Registry::plasmaWindowManagementAnnounced, this, [this, registry](quint32 name, quint32 version) {
|
||||
m_windowManagement = registry->createPlasmaWindowManagement(name, version, this);
|
||||
qRegisterMetaType<QVector<int>>("QVector<int>");
|
||||
connect(m_windowManagement, &KWayland::Client::PlasmaWindowManagement::windowCreated, this, &ApplicationListModel::windowCreated);
|
||||
});
|
||||
|
||||
registry->setup();
|
||||
connection->roundtrip();
|
||||
|
||||
load();
|
||||
}
|
||||
|
||||
ApplicationListModel::~ApplicationListModel() = default;
|
||||
|
||||
ApplicationListModel *ApplicationListModel::self()
|
||||
{
|
||||
static ApplicationListModel *inst = new ApplicationListModel(nullptr);
|
||||
return inst;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ApplicationListModel::roleNames() const
|
||||
{
|
||||
return {{ApplicationNameRole, QByteArrayLiteral("applicationName")},
|
||||
{ApplicationIconRole, QByteArrayLiteral("applicationIcon")},
|
||||
{ApplicationStorageIdRole, QByteArrayLiteral("applicationStorageId")},
|
||||
{ApplicationEntryPathRole, QByteArrayLiteral("applicationEntryPath")},
|
||||
{ApplicationStartupNotifyRole, QByteArrayLiteral("applicationStartupNotify")},
|
||||
{ApplicationRunningRole, QByteArrayLiteral("applicationRunning")},
|
||||
{ApplicationUniqueIdRole, QByteArrayLiteral("applicationUniqueId")},
|
||||
{ApplicationLocationRole, QByteArrayLiteral("applicationLocation")}};
|
||||
}
|
||||
|
||||
void ApplicationListModel::sycocaDbChanged()
|
||||
{
|
||||
load();
|
||||
}
|
||||
|
||||
void ApplicationListModel::windowCreated(KWayland::Client::PlasmaWindow *window)
|
||||
{
|
||||
if (window->appId() == QStringLiteral("org.kde.plasmashell")) {
|
||||
return;
|
||||
}
|
||||
int idx = 0;
|
||||
for (auto i = m_applicationList.begin(); i != m_applicationList.end(); i++) {
|
||||
if ((*i).storageId == window->appId() + QStringLiteral(".desktop")) {
|
||||
(*i).window = window;
|
||||
Q_EMIT dataChanged(index(idx, 0), index(idx, 0));
|
||||
connect(window, &KWayland::Client::PlasmaWindow::unmapped, this, [this, window]() {
|
||||
int idx = 0;
|
||||
for (auto i = m_applicationList.begin(); i != m_applicationList.end(); i++) {
|
||||
if ((*i).storageId == window->appId() + QStringLiteral(".desktop")) {
|
||||
(*i).window = nullptr;
|
||||
Q_EMIT dataChanged(index(idx, 0), index(idx, 0));
|
||||
break;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
void ApplicationListModel::load()
|
||||
{
|
||||
auto cfg = KSharedConfig::openConfig(QStringLiteral("applications-blacklistrc"));
|
||||
auto blgroup = KConfigGroup(cfg, QStringLiteral("Applications"));
|
||||
|
||||
const QStringList blacklist = blgroup.readEntry("blacklist", QStringList());
|
||||
|
||||
beginResetModel();
|
||||
|
||||
m_applicationList.clear();
|
||||
|
||||
QList<ApplicationData> unorderedList;
|
||||
|
||||
auto filter = [blacklist](const KService::Ptr &service) -> bool {
|
||||
if (service->noDisplay()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!service->showOnCurrentPlatform()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (blacklist.contains(service->desktopEntryName())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const KService::List apps = KApplicationTrader::query(filter);
|
||||
|
||||
for (const KService::Ptr &service : apps) {
|
||||
ApplicationData data;
|
||||
data.name = service->name();
|
||||
data.icon = service->icon();
|
||||
data.storageId = service->storageId();
|
||||
data.uniqueId = service->storageId();
|
||||
data.entryPath = service->exec();
|
||||
data.startupNotify = service->startupNotify().value_or(false);
|
||||
unorderedList << data;
|
||||
}
|
||||
|
||||
std::sort(unorderedList.begin(), unorderedList.end(), [](const ApplicationListModel::ApplicationData &a1, const ApplicationListModel::ApplicationData &a2) {
|
||||
return a1.name.compare(a2.name, Qt::CaseInsensitive) < 0;
|
||||
});
|
||||
|
||||
m_applicationList << unorderedList;
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QVariant ApplicationListModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
case ApplicationNameRole:
|
||||
return m_applicationList.at(index.row()).name;
|
||||
case ApplicationIconRole:
|
||||
return m_applicationList.at(index.row()).icon;
|
||||
case ApplicationStorageIdRole:
|
||||
return m_applicationList.at(index.row()).storageId;
|
||||
case ApplicationEntryPathRole:
|
||||
return m_applicationList.at(index.row()).entryPath;
|
||||
case ApplicationStartupNotifyRole:
|
||||
return m_applicationList.at(index.row()).startupNotify;
|
||||
case ApplicationRunningRole:
|
||||
return m_applicationList.at(index.row()).window != nullptr;
|
||||
case ApplicationUniqueIdRole:
|
||||
return m_applicationList.at(index.row()).uniqueId;
|
||||
case ApplicationLocationRole:
|
||||
return m_applicationList.at(index.row()).location;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
int ApplicationListModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (parent.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return m_applicationList.count();
|
||||
}
|
||||
|
||||
void ApplicationListModel::setMinimizedDelegate(int row, QQuickItem *delegate)
|
||||
{
|
||||
if (row < 0 || row >= m_applicationList.count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QWindow *delegateWindow = delegate->window();
|
||||
if (!delegateWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
KWayland::Client::PlasmaWindow *window = m_applicationList[row].window;
|
||||
if (!window) {
|
||||
return;
|
||||
}
|
||||
|
||||
KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(delegateWindow);
|
||||
if (!surface) {
|
||||
return;
|
||||
}
|
||||
|
||||
QRect rect = delegate->mapRectToScene(QRectF(0, 0, delegate->width(), delegate->height())).toRect();
|
||||
|
||||
window->setMinimizedGeometry(surface, rect);
|
||||
}
|
||||
|
||||
void ApplicationListModel::unsetMinimizedDelegate(int row, QQuickItem *delegate)
|
||||
{
|
||||
if (row < 0 || row >= m_applicationList.count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QWindow *delegateWindow = delegate->window();
|
||||
if (!delegateWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
KWayland::Client::PlasmaWindow *window = m_applicationList[row].window;
|
||||
if (!window) {
|
||||
return;
|
||||
}
|
||||
|
||||
KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(delegateWindow);
|
||||
if (!surface) {
|
||||
return;
|
||||
}
|
||||
|
||||
window->unsetMinimizedGeometry(surface);
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2014 Antonis Tsiapaliokas <antonis.tsiapaliokas@kde.org>
|
||||
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QQuickItem>
|
||||
#include <QSet>
|
||||
|
||||
#include <KWayland/Client/connection_thread.h>
|
||||
#include <KWayland/Client/plasmawindowmanagement.h>
|
||||
#include <KWayland/Client/registry.h>
|
||||
#include <KWayland/Client/surface.h>
|
||||
|
||||
/**
|
||||
* @short The base application list, used directly by the app drawer.
|
||||
*
|
||||
* Items that are displayed on the desktop/pinned are done by DesktopModel, which is a subclass.
|
||||
*/
|
||||
class ApplicationListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
// this enum is solely used by DesktopModel
|
||||
enum LauncherLocation { None = 0, Favorites, Desktop };
|
||||
Q_ENUM(LauncherLocation)
|
||||
|
||||
struct ApplicationData {
|
||||
QString uniqueId;
|
||||
QString name;
|
||||
QString icon;
|
||||
QString storageId;
|
||||
QString entryPath;
|
||||
bool startupNotify = true;
|
||||
KWayland::Client::PlasmaWindow *window = nullptr;
|
||||
LauncherLocation location = LauncherLocation::None; // only for DesktopModel
|
||||
};
|
||||
|
||||
enum Roles {
|
||||
ApplicationNameRole = Qt::UserRole + 1,
|
||||
ApplicationIconRole,
|
||||
ApplicationStorageIdRole,
|
||||
ApplicationEntryPathRole,
|
||||
ApplicationStartupNotifyRole,
|
||||
ApplicationRunningRole,
|
||||
ApplicationUniqueIdRole,
|
||||
ApplicationLocationRole // only valid for DesktopModel
|
||||
};
|
||||
|
||||
ApplicationListModel(QObject *parent = nullptr);
|
||||
~ApplicationListModel() override;
|
||||
static ApplicationListModel *self();
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
|
||||
QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE;
|
||||
|
||||
Q_INVOKABLE void setMinimizedDelegate(int row, QQuickItem *delegate);
|
||||
Q_INVOKABLE void unsetMinimizedDelegate(int row, QQuickItem *delegate);
|
||||
|
||||
public Q_SLOTS:
|
||||
void sycocaDbChanged();
|
||||
void windowCreated(KWayland::Client::PlasmaWindow *window);
|
||||
|
||||
Q_SIGNALS:
|
||||
void launchError(const QString &msg);
|
||||
|
||||
protected:
|
||||
virtual void load();
|
||||
|
||||
QList<ApplicationData> m_applicationList;
|
||||
|
||||
KWayland::Client::PlasmaWindowManagement *m_windowManagement = nullptr;
|
||||
};
|
||||
|
|
@ -1,309 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2014 Antonis Tsiapaliokas <antonis.tsiapaliokas@kde.org>
|
||||
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Self
|
||||
#include "desktopmodel.h"
|
||||
|
||||
// Qt
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
#include <QModelIndex>
|
||||
|
||||
// KDE
|
||||
#include <KService>
|
||||
#include <KSharedConfig>
|
||||
|
||||
#include <PlasmaQuick/AppletQuickItem>
|
||||
|
||||
const int MAX_FAVORITES = 5;
|
||||
|
||||
DesktopModel::DesktopModel(QObject *parent, Plasma::Applet *applet)
|
||||
: ApplicationListModel(parent) // constructor calls load()
|
||||
, m_applet{applet}
|
||||
{
|
||||
}
|
||||
|
||||
DesktopModel::~DesktopModel() = default;
|
||||
|
||||
QString DesktopModel::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 DesktopModel::uniqueToStorageId(const QString &uniqueId) const
|
||||
{
|
||||
if (uniqueId.isEmpty()) {
|
||||
return uniqueId;
|
||||
}
|
||||
|
||||
return uniqueId.split(QLatin1Char('-')).first();
|
||||
}
|
||||
|
||||
void DesktopModel::loadSettings()
|
||||
{
|
||||
if (!m_applet) {
|
||||
return;
|
||||
}
|
||||
m_favorites = m_applet->config().readEntry("Favorites", QStringList());
|
||||
const auto di = m_applet->config().readEntry("DesktopItems", QStringList());
|
||||
m_desktopItems = QSet<QString>(di.begin(), di.end());
|
||||
m_appOrder = m_applet->config().readEntry("AppOrder", QStringList());
|
||||
|
||||
int i = 0;
|
||||
for (const QString &app : std::as_const(m_appOrder)) {
|
||||
m_appPositions[app] = i;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
void DesktopModel::load()
|
||||
{
|
||||
loadSettings();
|
||||
|
||||
// load applications
|
||||
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->startupNotify().value_or(false);
|
||||
|
||||
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();
|
||||
|
||||
Q_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());
|
||||
Q_EMIT m_applet->configNeedsSaving();
|
||||
}
|
||||
|
||||
if (favChanged) {
|
||||
Q_EMIT favoriteCountChanged();
|
||||
}
|
||||
}
|
||||
|
||||
int DesktopModel::count()
|
||||
{
|
||||
return m_applicationList.count();
|
||||
}
|
||||
|
||||
int DesktopModel::favoriteCount()
|
||||
{
|
||||
return m_favorites.count();
|
||||
}
|
||||
|
||||
int DesktopModel::maxFavoriteCount()
|
||||
{
|
||||
return MAX_FAVORITES;
|
||||
}
|
||||
|
||||
void DesktopModel::setLocation(int row, LauncherLocation location)
|
||||
{
|
||||
if (row < 0 || row >= m_applicationList.length()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ApplicationData data = m_applicationList.at(row);
|
||||
if (data.location == location) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (location == Favorites) {
|
||||
qWarning() << "favoriting" << row << data.name;
|
||||
// Deny favorites when full
|
||||
if (row >= maxFavoriteCount() || m_favorites.count() >= maxFavoriteCount() || m_favorites.contains(data.uniqueId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_favorites.insert(row, data.uniqueId);
|
||||
|
||||
if (m_applet) {
|
||||
m_applet->config().writeEntry("Favorites", m_favorites);
|
||||
}
|
||||
Q_EMIT favoriteCountChanged();
|
||||
|
||||
// Out of favorites
|
||||
} else if (data.location == Favorites) {
|
||||
m_favorites.removeAll(data.uniqueId);
|
||||
if (m_applet) {
|
||||
m_applet->config().writeEntry("Favorites", m_favorites);
|
||||
}
|
||||
Q_EMIT favoriteCountChanged();
|
||||
}
|
||||
|
||||
// In Desktop
|
||||
if (location == Desktop) {
|
||||
m_desktopItems.insert(data.uniqueId);
|
||||
if (m_applet) {
|
||||
m_applet->config().writeEntry("DesktopItems", m_desktopItems.values());
|
||||
}
|
||||
|
||||
// Out of Desktop
|
||||
} else if (data.location == Desktop) {
|
||||
m_desktopItems.remove(data.uniqueId);
|
||||
if (m_applet) {
|
||||
m_applet->config().writeEntry(QStringLiteral("DesktopItems"), m_desktopItems.values());
|
||||
}
|
||||
}
|
||||
|
||||
data.location = location;
|
||||
if (m_applet) {
|
||||
Q_EMIT m_applet->configNeedsSaving();
|
||||
}
|
||||
Q_EMIT dataChanged(index(row, 0), index(row, 0));
|
||||
}
|
||||
|
||||
void DesktopModel::moveItem(int row, int destination)
|
||||
{
|
||||
if (row < 0 || destination < 0 || row >= m_applicationList.length() || destination >= m_applicationList.length() || row == destination) {
|
||||
return;
|
||||
}
|
||||
if (destination > row) {
|
||||
++destination;
|
||||
}
|
||||
|
||||
beginMoveRows(QModelIndex(), row, row, QModelIndex(), destination);
|
||||
if (destination > row) {
|
||||
ApplicationData data = m_applicationList.at(row);
|
||||
m_applicationList.insert(destination, data);
|
||||
m_applicationList.takeAt(row);
|
||||
|
||||
} else {
|
||||
ApplicationData data = m_applicationList.takeAt(row);
|
||||
m_applicationList.insert(destination, data);
|
||||
}
|
||||
|
||||
m_appOrder.clear();
|
||||
m_appPositions.clear();
|
||||
int i = 0;
|
||||
for (const ApplicationData &app : std::as_const(m_applicationList)) {
|
||||
m_appOrder << app.uniqueId;
|
||||
m_appPositions[app.uniqueId] = i;
|
||||
++i;
|
||||
}
|
||||
|
||||
if (m_applet) {
|
||||
m_applet->config().writeEntry("AppOrder", m_appOrder);
|
||||
}
|
||||
|
||||
endMoveRows();
|
||||
}
|
||||
|
||||
void DesktopModel::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->startupNotify().value_or(false);
|
||||
|
||||
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) {
|
||||
Q_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());
|
||||
Q_EMIT m_applet->configNeedsSaving();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DesktopModel::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) {
|
||||
Q_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());
|
||||
Q_EMIT m_applet->configNeedsSaving();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Marco Martin <mart@kde.org>
|
||||
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
// Qt
|
||||
#include <QAbstractListModel>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
|
||||
// KDE
|
||||
#include <Plasma/Applet>
|
||||
|
||||
#include "applicationlistmodel.h"
|
||||
|
||||
/**
|
||||
* @short Filtered application list for applications on the desktop and pinned bar.
|
||||
*/
|
||||
class DesktopModel : public ApplicationListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int count READ count NOTIFY countChanged)
|
||||
Q_PROPERTY(int favoriteCount READ favoriteCount NOTIFY favoriteCountChanged)
|
||||
Q_PROPERTY(int maxFavoriteCount READ maxFavoriteCount CONSTANT)
|
||||
|
||||
public:
|
||||
DesktopModel(QObject *parent = nullptr, Plasma::Applet *applet = nullptr);
|
||||
~DesktopModel() override;
|
||||
|
||||
QString storageToUniqueId(const QString &storageId) const;
|
||||
QString uniqueToStorageId(const QString &uniqueId) const;
|
||||
|
||||
void loadSettings();
|
||||
|
||||
int count();
|
||||
int favoriteCount();
|
||||
int maxFavoriteCount();
|
||||
|
||||
Q_INVOKABLE void setLocation(int row, LauncherLocation location);
|
||||
Q_INVOKABLE void moveItem(int row, int destination);
|
||||
|
||||
Q_INVOKABLE void addFavorite(const QString &storageId, int row, LauncherLocation location);
|
||||
Q_INVOKABLE void removeFavorite(int row);
|
||||
|
||||
Q_SIGNALS:
|
||||
void countChanged();
|
||||
void favoriteCountChanged();
|
||||
|
||||
private:
|
||||
void load() override;
|
||||
|
||||
QStringList m_appOrder;
|
||||
QStringList m_favorites;
|
||||
QSet<QString> m_desktopItems;
|
||||
QHash<QString, int> m_appPositions;
|
||||
|
||||
Plasma::Applet *m_applet = nullptr;
|
||||
};
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "folioplugin.h"
|
||||
#include "applicationlistmodel.h"
|
||||
#include "desktopmodel.h"
|
||||
|
||||
void HalcyonPlugin::registerTypes(const char *uri)
|
||||
{
|
||||
Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.private.mobile.homescreen.folio"));
|
||||
|
||||
qmlRegisterSingletonType<ApplicationListModel>(uri, 1, 0, "ApplicationListModel", [](QQmlEngine *, QJSEngine *) -> QObject * {
|
||||
return ApplicationListModel::self();
|
||||
});
|
||||
|
||||
qmlRegisterType<DesktopModel>(uri, 1, 0, "DesktopModel");
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QQmlEngine>
|
||||
#include <QQmlExtensionPlugin>
|
||||
|
||||
class HalcyonPlugin : public QQmlExtensionPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
|
||||
|
||||
public:
|
||||
void registerTypes(const char *uri) override;
|
||||
};
|
||||
62
containments/homescreens/folio/windowlistener.cpp
Normal file
62
containments/homescreens/folio/windowlistener.cpp
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "windowlistener.h"
|
||||
|
||||
WindowListener::WindowListener(QObject *parent)
|
||||
: QObject{parent}
|
||||
{
|
||||
// initialize wayland window checking
|
||||
KWayland::Client::ConnectionThread *connection = KWayland::Client::ConnectionThread::fromApplication(this);
|
||||
if (!connection) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto *registry = new KWayland::Client::Registry(this);
|
||||
registry->create(connection);
|
||||
|
||||
connect(registry, &KWayland::Client::Registry::plasmaWindowManagementAnnounced, this, [this, registry](quint32 name, quint32 version) {
|
||||
m_windowManagement = registry->createPlasmaWindowManagement(name, version, this);
|
||||
connect(m_windowManagement, &KWayland::Client::PlasmaWindowManagement::windowCreated, this, &WindowListener::windowCreated);
|
||||
});
|
||||
|
||||
registry->setup();
|
||||
connection->roundtrip();
|
||||
}
|
||||
|
||||
WindowListener *WindowListener::instance()
|
||||
{
|
||||
static WindowListener *listener = new WindowListener();
|
||||
return listener;
|
||||
}
|
||||
|
||||
QList<KWayland::Client::PlasmaWindow *> WindowListener::windowsFromStorageId(QString &storageId) const
|
||||
{
|
||||
if (!m_windows.contains(storageId)) {
|
||||
return {};
|
||||
}
|
||||
return m_windows[storageId];
|
||||
}
|
||||
|
||||
void WindowListener::windowCreated(KWayland::Client::PlasmaWindow *window)
|
||||
{
|
||||
QString storageId = window->appId() + QStringLiteral(".desktop");
|
||||
|
||||
// ignore empty windows
|
||||
if (storageId == ".desktop" || storageId == "org.kde.plasmashell.desktop") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_windows.contains(storageId)) {
|
||||
m_windows[storageId] = {};
|
||||
}
|
||||
m_windows[storageId].push_back(window);
|
||||
|
||||
// listen for window close
|
||||
connect(window, &KWayland::Client::PlasmaWindow::unmapped, this, [this, storageId]() {
|
||||
m_windows.remove(storageId);
|
||||
Q_EMIT windowChanged(storageId);
|
||||
});
|
||||
|
||||
Q_EMIT windowChanged(storageId);
|
||||
}
|
||||
34
containments/homescreens/folio/windowlistener.h
Normal file
34
containments/homescreens/folio/windowlistener.h
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
|
||||
#include <KWayland/Client/connection_thread.h>
|
||||
#include <KWayland/Client/plasmawindowmanagement.h>
|
||||
#include <KWayland/Client/registry.h>
|
||||
#include <KWayland/Client/surface.h>
|
||||
|
||||
class WindowListener : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
WindowListener(QObject *parent = nullptr);
|
||||
|
||||
static WindowListener *instance();
|
||||
|
||||
QList<KWayland::Client::PlasmaWindow *> windowsFromStorageId(QString &storageId) const;
|
||||
|
||||
public Q_SLOTS:
|
||||
void windowCreated(KWayland::Client::PlasmaWindow *window);
|
||||
|
||||
Q_SIGNALS:
|
||||
void windowChanged(QString storageId);
|
||||
|
||||
private:
|
||||
KWayland::Client::PlasmaWindowManagement *m_windowManagement{nullptr};
|
||||
QHash<QString, QList<KWayland::Client::PlasmaWindow *>> m_windows; // <storageId, window>
|
||||
};
|
||||
|
|
@ -110,7 +110,7 @@ Item {
|
|||
|
||||
anchors.fill: parent
|
||||
|
||||
property int horizontalMargin: Math.round(swipeView.width * 0.05)
|
||||
property int horizontalMargin: Math.round(swipeView.width * 0.05)
|
||||
interactive: root.interactive
|
||||
leftMargin: horizontalMargin
|
||||
rightMargin: horizontalMargin
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ MobileShell.SwipeArea {
|
|||
}
|
||||
|
||||
onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => {
|
||||
position = Math.max(0, Math.min(keypadHeight, position + deltaY));
|
||||
position = Math.max(0, Math.min(keypadHeight, position - deltaY));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2019-2020 Marco Martin <mart@kde.org>
|
||||
// SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
applet.wallpaperPlugin = 'org.kde.image'
|
||||
applet.writeConfig("AppOrder", ["org.kde.phone.dialer.desktop", "org.kde.spacebar.desktop", "org.kde.angelfish.desktop"])
|
||||
applet.writeConfig("Favorites", ["org.kde.phone.dialer.desktop", "org.kde.spacebar.desktop", "org.kde.angelfish.desktop"])
|
||||
applet.reloadConfig()
|
||||
|
||||
|
|
@ -46,9 +46,10 @@ Controls.Drawer {
|
|||
keyNavigationEnabled: true
|
||||
highlightFollowsCurrentItem: true
|
||||
snapMode: ListView.SnapToItem
|
||||
model: imageWallpaper.wallpaperModel
|
||||
onCountChanged: currentIndex = Math.min(model.indexOf(configDialog.wallpaperConfiguration["Image"]), model.rowCount()-1)
|
||||
model: imageWallpaper.slidePaths
|
||||
// onCountChanged: currentIndex = Math.min(model.indexOf(configDialog.wallpaperConfiguration["Image"]), model.rowCount()-1)
|
||||
headerPositioning: ListView.PullBackHeader
|
||||
|
||||
delegate: Controls.ItemDelegate {
|
||||
width: imageWallpaperDrawer.horizontal ? parent.width : height * (imageWallpaperDrawer.width / imageWallpaperDrawer.Screen.height)
|
||||
height: imageWallpaperDrawer.horizontal ? width / (imageWallpaperDrawer.Screen.width / imageWallpaperDrawer.Screen.height) : parent.height
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
LookAndFeelPackage=org.kde.breeze.mobile
|
||||
|
||||
[Desktop]
|
||||
Containment=org.kde.plasma.mobile.homescreen.halcyon
|
||||
Containment=org.kde.plasma.mobile.homescreen.folio
|
||||
ToolBox=org.kde.plasma.nano.desktoptoolbox
|
||||
|
||||
[Desktop][ContainmentActions]
|
||||
|
|
|
|||
Loading…
Reference in a new issue