mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-29 15:03:09 +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);
|
setFiltersChildMouseEvents(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
SwipeArea::Mode SwipeArea::mode()
|
SwipeArea::Mode SwipeArea::mode() const
|
||||||
{
|
{
|
||||||
return m_mode;
|
return m_mode;
|
||||||
}
|
}
|
||||||
|
|
@ -33,21 +33,32 @@ void SwipeArea::setMode(Mode mode)
|
||||||
Q_EMIT modeChanged();
|
Q_EMIT modeChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SwipeArea::interactive()
|
bool SwipeArea::interactive() const
|
||||||
{
|
{
|
||||||
return m_interactive;
|
return m_interactive;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SwipeArea::moving()
|
void SwipeArea::setInteractive(bool interactive)
|
||||||
|
{
|
||||||
|
m_interactive = interactive;
|
||||||
|
Q_EMIT interactiveChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SwipeArea::moving() const
|
||||||
{
|
{
|
||||||
return m_moving;
|
return m_moving;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SwipeArea::pressed()
|
bool SwipeArea::pressed() const
|
||||||
{
|
{
|
||||||
return m_pressed;
|
return m_pressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SwipeArea::setSkipSwipeThreshold(bool value)
|
||||||
|
{
|
||||||
|
m_skipSwipeThreshold = value;
|
||||||
|
}
|
||||||
|
|
||||||
bool SwipeArea::childMouseEventFilter(QQuickItem *item, QEvent *event)
|
bool SwipeArea::childMouseEventFilter(QQuickItem *item, QEvent *event)
|
||||||
{
|
{
|
||||||
if (!isVisible() || !isEnabled() || !m_interactive) {
|
if (!isVisible() || !isEnabled() || !m_interactive) {
|
||||||
|
|
@ -205,12 +216,6 @@ void SwipeArea::touchUngrabEvent()
|
||||||
QQuickItem::touchUngrabEvent();
|
QQuickItem::touchUngrabEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SwipeArea::setInteractive(bool interactive)
|
|
||||||
{
|
|
||||||
m_interactive = interactive;
|
|
||||||
Q_EMIT interactiveChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SwipeArea::setMoving(bool moving)
|
void SwipeArea::setMoving(bool moving)
|
||||||
{
|
{
|
||||||
m_moving = moving;
|
m_moving = moving;
|
||||||
|
|
@ -225,6 +230,7 @@ void SwipeArea::setPressed(bool pressed)
|
||||||
|
|
||||||
void SwipeArea::resetSwipe()
|
void SwipeArea::resetSwipe()
|
||||||
{
|
{
|
||||||
|
m_skipSwipeThreshold = false;
|
||||||
m_stealMouse = false;
|
m_stealMouse = false;
|
||||||
if (m_pressed) {
|
if (m_pressed) {
|
||||||
setPressed(false);
|
setPressed(false);
|
||||||
|
|
@ -253,17 +259,14 @@ void SwipeArea::handleReleaseEvent(QPointerEvent *event, QPointF point)
|
||||||
if (m_moving) {
|
if (m_moving) {
|
||||||
Q_EMIT swipeEnded();
|
Q_EMIT swipeEnded();
|
||||||
}
|
}
|
||||||
|
|
||||||
resetSwipe();
|
resetSwipe();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SwipeArea::handleMoveEvent(QPointerEvent *event, QPointF point)
|
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 (!m_stealMouse) {
|
||||||
|
if (!m_skipSwipeThreshold) {
|
||||||
// if we haven't reached the swipe registering threshold yet, don't start the swipe
|
// 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) {
|
if (m_mode == Mode::VerticalOnly && qAbs(point.y() - m_pressPos.y()) < SWIPE_REGISTER_THRESHOLD) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -272,15 +275,22 @@ void SwipeArea::handleMoveEvent(QPointerEvent *event, QPointF point)
|
||||||
} else if (m_mode == Mode::BothAxis && qAbs(point.manhattanLength() - m_pressPos.manhattanLength()) < SWIPE_REGISTER_THRESHOLD) {
|
} else if (m_mode == Mode::BothAxis && qAbs(point.manhattanLength() - m_pressPos.manhattanLength()) < SWIPE_REGISTER_THRESHOLD) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
m_skipSwipeThreshold = false;
|
||||||
|
|
||||||
// we now start the swipe, stealing it from children
|
// we now start the swipe, stealing it from children
|
||||||
|
|
||||||
m_startPos = point;
|
m_startPos = point;
|
||||||
|
m_lastPos = point;
|
||||||
m_stealMouse = true;
|
m_stealMouse = true;
|
||||||
setMoving(true);
|
setMoving(true);
|
||||||
Q_EMIT swipeStarted(m_startPos);
|
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()
|
// ensure it's called AFTER swipeStarted()
|
||||||
Q_EMIT swipeMove(totalDelta.x(), totalDelta.y(), delta.x(), delta.y());
|
Q_EMIT swipeMove(totalDelta.x(), totalDelta.y(), delta.x(), delta.y());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ class SwipeArea : public QQuickItem
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(SwipeArea::Mode mode READ mode WRITE setMode NOTIFY modeChanged)
|
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 moving READ moving NOTIFY movingChanged)
|
||||||
Q_PROPERTY(bool pressed READ pressed NOTIFY pressedChanged)
|
Q_PROPERTY(bool pressed READ pressed NOTIFY pressedChanged)
|
||||||
|
|
||||||
|
|
@ -35,12 +35,16 @@ public:
|
||||||
enum Mode { BothAxis = 0, VerticalOnly, HorizontalOnly };
|
enum Mode { BothAxis = 0, VerticalOnly, HorizontalOnly };
|
||||||
Q_ENUM(Mode)
|
Q_ENUM(Mode)
|
||||||
|
|
||||||
Mode mode();
|
Mode mode() const;
|
||||||
void setMode(Mode mode);
|
void setMode(Mode mode);
|
||||||
|
|
||||||
bool interactive();
|
bool interactive() const;
|
||||||
bool moving();
|
void setInteractive(bool interactive);
|
||||||
bool pressed();
|
|
||||||
|
bool moving() const;
|
||||||
|
bool pressed() const;
|
||||||
|
|
||||||
|
Q_INVOKABLE void setSkipSwipeThreshold(bool value);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void modeChanged();
|
void modeChanged();
|
||||||
|
|
@ -65,7 +69,6 @@ protected:
|
||||||
void touchUngrabEvent() override;
|
void touchUngrabEvent() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setInteractive(bool interactive);
|
|
||||||
void setMoving(bool moving);
|
void setMoving(bool moving);
|
||||||
void setPressed(bool pressed);
|
void setPressed(bool pressed);
|
||||||
|
|
||||||
|
|
@ -95,6 +98,9 @@ private:
|
||||||
|
|
||||||
// the previous point where interaction was at
|
// the previous point where interaction was at
|
||||||
QPointF m_lastPos;
|
QPointF m_lastPos;
|
||||||
|
|
||||||
|
// whether to skip trying to measure the swipe threshold
|
||||||
|
bool m_skipSwipeThreshold;
|
||||||
};
|
};
|
||||||
|
|
||||||
QML_DECLARE_TYPE(SwipeArea)
|
QML_DECLARE_TYPE(SwipeArea)
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ void MobileShellPlugin::registerTypes(const char *uri)
|
||||||
qmlRegisterSingletonType(resolvePath("volumeosd/VolumeOSDProviderLoader.qml"), uri, 1, 0, "VolumeOSDProviderLoader");
|
qmlRegisterSingletonType(resolvePath("volumeosd/VolumeOSDProviderLoader.qml"), uri, 1, 0, "VolumeOSDProviderLoader");
|
||||||
|
|
||||||
// /widgets
|
// /widgets
|
||||||
|
qmlRegisterType(resolvePath("widgets/krunner/KRunnerScreen.qml"), uri, 1, 0, "KRunnerScreen");
|
||||||
qmlRegisterType(resolvePath("widgets/krunner/KRunnerWidget.qml"), uri, 1, 0, "KRunnerWidget");
|
qmlRegisterType(resolvePath("widgets/krunner/KRunnerWidget.qml"), uri, 1, 0, "KRunnerWidget");
|
||||||
qmlRegisterType(resolvePath("widgets/mediacontrols/MediaControlsWidget.qml"), uri, 1, 0, "MediaControlsWidget");
|
qmlRegisterType(resolvePath("widgets/mediacontrols/MediaControlsWidget.qml"), uri, 1, 0, "MediaControlsWidget");
|
||||||
qmlRegisterType(resolvePath("widgets/notifications/NotificationsWidget.qml"), uri, 1, 0, "NotificationsWidget");
|
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
|
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||||
|
|
||||||
highlight: activeFocus ? highlightComponent : null
|
highlight: activeFocus ? highlightComponent : null
|
||||||
Component{
|
Component {
|
||||||
id: highlightComponent
|
id: highlightComponent
|
||||||
|
|
||||||
PlasmaExtras.Highlight {}
|
PlasmaExtras.Highlight {}
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@
|
||||||
<file>qml/statusbar/StatusBar.qml</file>
|
<file>qml/statusbar/StatusBar.qml</file>
|
||||||
<file>qml/statusbar/TaskWidget.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/krunner/KRunnerWidget.qml</file>
|
||||||
|
|
||||||
<file>qml/widgets/mediacontrols/BlurredBackground.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
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
set(homescreen_SRCS
|
set(homescreen_SRCS
|
||||||
homescreen.cpp
|
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})
|
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::Notifications
|
||||||
KF6::WaylandClient
|
KF6::WaylandClient
|
||||||
KF6::WindowSystem
|
KF6::WindowSystem
|
||||||
|
KF6::JobWidgets
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
install(TARGETS org.kde.plasma.mobile.homescreen.folio DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasma/applets)
|
install(TARGETS org.kde.plasma.mobile.homescreen.folio DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasma/applets)
|
||||||
|
|
||||||
plasma_install_package(package org.kde.plasma.mobile.homescreen.folio)
|
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: 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
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include "homescreen.h"
|
#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 <KWindowSystem>
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QQmlEngine>
|
||||||
|
#include <QQmlExtensionPlugin>
|
||||||
#include <QQuickItem>
|
#include <QQuickItem>
|
||||||
#include <QtQml>
|
#include <QtQml>
|
||||||
|
|
||||||
|
|
@ -14,6 +27,45 @@ HomeScreen::HomeScreen(QObject *parent, const KPluginMetaData &data, const QVari
|
||||||
: Plasma::Containment{parent, data, args}
|
: Plasma::Containment{parent, data, args}
|
||||||
{
|
{
|
||||||
setHasConfigurationInterface(true);
|
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);
|
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: 2023 Devin Lin <devin@kde.org>
|
||||||
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick 2.12
|
import QtQuick
|
||||||
import QtQuick.Window 2.12
|
import QtQuick.Window
|
||||||
import QtQuick.Layouts 1.1
|
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.plasmoid 2.0
|
||||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
import org.kde.plasma.components 3.0 as PC3
|
||||||
import org.kde.draganddrop 2.0 as DragDrop
|
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
||||||
|
|
||||||
import "private" as Private
|
|
||||||
import "appdrawer"
|
|
||||||
|
|
||||||
import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager
|
|
||||||
|
|
||||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||||
|
|
||||||
|
import "./delegate"
|
||||||
|
import "./settings"
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property real topMargin
|
property real topMargin: 0
|
||||||
required property real bottomMargin
|
property real bottomMargin: 0
|
||||||
required property real leftMargin
|
property real leftMargin: 0
|
||||||
required property real rightMargin
|
property real rightMargin: 0
|
||||||
|
|
||||||
property bool interactive: true
|
property bool interactive: true
|
||||||
|
|
||||||
property var homeScreenState: HomeScreenState {
|
property Folio.HomeScreenState homeScreenState: Folio.HomeScreenState
|
||||||
interactive: root.interactive
|
|
||||||
|
|
||||||
totalPagesWidth: pages.contentWidth
|
readonly property bool dropAnimationRunning: delegateDragItem.dropAnimationRunning
|
||||||
|
|
||||||
appDrawerFlickable: appDrawer.flickable
|
readonly property real settingsModeHomeScreenScale: 0.8
|
||||||
|
|
||||||
availableScreenHeight: height - root.topMargin - root.bottomMargin
|
onTopMarginChanged: Folio.HomeScreenState.viewTopPadding = root.topMargin
|
||||||
availableScreenWidth: width - root.leftMargin - root.rightMargin
|
onBottomMarginChanged: Folio.HomeScreenState.viewBottomPadding = root.bottomMargin
|
||||||
|
onLeftMarginChanged: Folio.HomeScreenState.viewLeftPadding = root.leftMargin
|
||||||
|
onRightMarginChanged: Folio.HomeScreenState.viewRightPadding = root.rightMargin
|
||||||
|
|
||||||
appDrawerBottomOffset: favoriteStrip.height
|
// 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 alias appDrawer: appDrawerLoader.item
|
function cancelDelegateDrag() {
|
||||||
property alias homeScreenContents: contents
|
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: {
|
Component.onCompleted: {
|
||||||
// ensure that homescreen is on first page
|
Folio.HomeScreenState.pageDelegateLabelWidth = Kirigami.Units.smallSpacing;
|
||||||
homeScreenState.goToPageIndex(0);
|
}
|
||||||
homeScreenState.resetSwipeState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
property var desktopModel: Folio.DesktopModel {}
|
// determine screen dimensions
|
||||||
|
|
||||||
// the parent of the homescreen is a flickable that captures all flicks
|
|
||||||
FlickContainer {
|
|
||||||
id: flickContainer
|
|
||||||
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
|
|
||||||
Item {
|
Item {
|
||||||
x: flickContainer.contentX
|
id: screenDimensions
|
||||||
y: flickContainer.contentY
|
|
||||||
width: flickContainer.width
|
|
||||||
height: flickContainer.height
|
|
||||||
|
|
||||||
// horizontal pages
|
|
||||||
HomeScreenPages {
|
|
||||||
id: pages
|
|
||||||
homeScreenState: root.homeScreenState
|
|
||||||
|
|
||||||
// account for panels
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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
|
||||||
|
|
||||||
anchors.topMargin: root.topMargin
|
anchors.topMargin: root.topMargin
|
||||||
anchors.bottomMargin: root.bottomMargin
|
anchors.bottomMargin: root.bottomMargin
|
||||||
anchors.leftMargin: root.leftMargin
|
anchors.leftMargin: root.leftMargin
|
||||||
anchors.rightMargin: root.rightMargin
|
anchors.rightMargin: root.rightMargin
|
||||||
|
|
||||||
// animation when app drawer is being shown
|
states: [
|
||||||
opacity: root.appDrawer ? 1 - root.appDrawer.openFactor : 1
|
State {
|
||||||
transform: Translate {
|
name: "bottom"
|
||||||
y: root.appDrawer ? (-pages.height / 20) * root.appDrawer.openFactor : 0
|
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
|
||||||
}
|
}
|
||||||
|
PropertyChanges {
|
||||||
contentWidth: Math.max(width, width * Math.ceil(contents.itemsBoundingRect.width/width)) + (contents.launcherDragManager.active ? width : 0)
|
target: favouritesBar
|
||||||
showAddPageIndicator: contents.launcherDragManager.active
|
height: Kirigami.Units.gridUnit * 6
|
||||||
|
|
||||||
HomeScreenContents {
|
|
||||||
id: contents
|
|
||||||
desktopModel: root.desktopModel
|
|
||||||
homeScreenState: root.homeScreenState
|
|
||||||
|
|
||||||
height: pages.height
|
|
||||||
width: pages.width * 100
|
|
||||||
|
|
||||||
favoriteStrip: favoriteStrip
|
|
||||||
homeScreenPages: pages
|
|
||||||
}
|
}
|
||||||
|
}, State {
|
||||||
footer: FavoriteStrip {
|
name: "left"
|
||||||
id: favoriteStrip
|
when: Folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Left
|
||||||
|
AnchorChanges {
|
||||||
appletsLayout: contents.appletsLayout
|
target: favouritesBar
|
||||||
visible: favoriteStrip.flow.children.length > 0 || contents.launcherDragManager.active || contents.containsDrag
|
anchors.top: parent.top
|
||||||
opacity: contents.launcherDragManager.active && root.desktopModel.favoriteCount >= root.desktopModel.maxFavoriteCount ? 0.3 : 1
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
TapHandler {
|
anchors.right: undefined
|
||||||
target: favoriteStrip
|
|
||||||
enabled: flickContainer.interactive // only interactive when we flick on homescreen
|
|
||||||
onTapped: {
|
|
||||||
//Hides icons close button
|
|
||||||
contents.appletsLayout.appletsLayoutInteracted();
|
|
||||||
contents.appletsLayout.editMode = false;
|
|
||||||
}
|
}
|
||||||
onLongPressed: {
|
PropertyChanges {
|
||||||
if (homeScreenState.currentSwipeState === HomeScreenState.DeterminingType) {
|
target: favouritesBar
|
||||||
// only go into edit mode when not in a swipe
|
width: Kirigami.Units.gridUnit * 6
|
||||||
contents.appletsLayout.editMode = true;
|
}
|
||||||
|
}, 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onPressedChanged: root.parent.focus = true;
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// app drawer
|
// folder view
|
||||||
AppDrawerLoader {
|
FolderView {
|
||||||
id: appDrawerLoader
|
id: folderView
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
homeScreenState: root.homeScreenState
|
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
|
// account for panels
|
||||||
topPadding: root.topMargin
|
topPadding: root.topMargin
|
||||||
bottomPadding: root.bottomMargin
|
bottomPadding: root.bottomMargin
|
||||||
leftPadding: root.leftMargin
|
leftPadding: root.leftMargin
|
||||||
rightPadding: root.rightMargin
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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: 2023 Devin Lin <devin@kde.org>
|
||||||
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick 2.12
|
import QtQuick
|
||||||
import QtQuick.Window 2.12
|
import QtQuick.Window
|
||||||
import QtQuick.Layouts 1.1
|
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
|
MouseArea {
|
||||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
id: root
|
||||||
import org.kde.draganddrop 2.0 as DragDrop
|
|
||||||
|
|
||||||
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 {
|
Repeater {
|
||||||
id: mainFlickable
|
model: Folio.PageListModel
|
||||||
|
|
||||||
required property var homeScreenState
|
delegate: HomeScreenPage {
|
||||||
|
id: homeScreenPage
|
||||||
|
pageNum: model.index
|
||||||
|
pageModel: model.delegate
|
||||||
|
homeScreen: root.homeScreen
|
||||||
|
|
||||||
property Item footer
|
anchors.fill: root
|
||||||
|
anchors.leftMargin: root.horizontalMargin
|
||||||
|
anchors.rightMargin: root.horizontalMargin
|
||||||
|
anchors.topMargin: root.verticalMargin
|
||||||
|
anchors.bottomMargin: root.verticalMargin
|
||||||
|
|
||||||
property bool showAddPageIndicator: false
|
// 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))
|
||||||
|
|
||||||
contentX: homeScreenState.xPosition
|
// x position of page
|
||||||
|
transform: Translate {
|
||||||
contentHeight: height
|
x: root.width * index + Folio.HomeScreenState.pageViewX
|
||||||
interactive: false
|
|
||||||
|
|
||||||
signal cancelEditModeForItemsRequested
|
|
||||||
onDragStarted: cancelEditModeForItemsRequested()
|
|
||||||
onDragEnded: cancelEditModeForItemsRequested()
|
|
||||||
onFlickStarted: cancelEditModeForItemsRequested()
|
|
||||||
onFlickEnded: cancelEditModeForItemsRequested()
|
|
||||||
|
|
||||||
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: "⊕"
|
|
||||||
}
|
|
||||||
|
|
||||||
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: 2023 Devin Lin <devin@kde.org>
|
||||||
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
import QtQuick.Window 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.private.mobile.homescreen.folio 1.0 as Folio
|
||||||
import org.kde.plasma.private.mobileshell.windowplugin as WindowPlugin
|
import org.kde.plasma.private.mobileshell.windowplugin as WindowPlugin
|
||||||
|
|
||||||
|
|
||||||
ContainmentItem {
|
ContainmentItem {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
Folio.FolioSettings.load();
|
||||||
|
Folio.ApplicationListModel.load();
|
||||||
|
Folio.FavouritesModel.load();
|
||||||
|
Folio.PageListModel.load();
|
||||||
|
|
||||||
// ensure the gestures work immediately on load
|
// ensure the gestures work immediately on load
|
||||||
forceActiveFocus();
|
forceActiveFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
Plasmoid.onActivated: {
|
function homeAction() {
|
||||||
// there's a couple of steps:
|
const isInWindow = (!WindowPlugin.WindowUtil.isShowingDesktop && WindowPlugin.WindowMaximizedTracker.showingWindow);
|
||||||
// - minimize windows (only if we are in an app)
|
|
||||||
// - open app drawer
|
|
||||||
// - close app drawer and, if necessary, restore windows
|
|
||||||
|
|
||||||
// Always close action drawer
|
if (isInWindow) {
|
||||||
if (MobileShellState.ShellDBusClient.isActionDrawerOpen) {
|
Folio.HomeScreenState.closeFolder();
|
||||||
MobileShellState.ShellDBusClient.closeActionDrawer();
|
Folio.HomeScreenState.closeSearchWidget();
|
||||||
}
|
Folio.HomeScreenState.closeAppDrawer();
|
||||||
|
Folio.HomeScreenState.goToPage(0);
|
||||||
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();
|
|
||||||
} else {
|
} 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 {
|
MobileShell.HomeScreen {
|
||||||
id: homeScreen
|
id: homeScreen
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
plasmoidItem: root
|
plasmoidItem: root
|
||||||
onResetHomeScreenPosition: {
|
onResetHomeScreenPosition: {
|
||||||
folioHomeScreen.homeScreenState.animateGoToPageIndex(0, Kirigami.Units.longDuration);
|
// NOTE: empty, because this is handled by homeAction()
|
||||||
folioHomeScreen.homeScreenState.closeAppDrawer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onHomeTriggered: {
|
onHomeTriggered: root.homeAction()
|
||||||
searchWidget.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
property bool componentComplete: false
|
|
||||||
|
|
||||||
contentItem: Item {
|
contentItem: Item {
|
||||||
|
|
||||||
// homescreen component
|
// homescreen component
|
||||||
HomeScreen {
|
HomeScreen {
|
||||||
id: folioHomeScreen
|
id: folioHomeScreen
|
||||||
|
|
@ -82,38 +107,9 @@ ContainmentItem {
|
||||||
leftMargin: homeScreen.leftMargin
|
leftMargin: homeScreen.leftMargin
|
||||||
rightMargin: homeScreen.rightMargin
|
rightMargin: homeScreen.rightMargin
|
||||||
|
|
||||||
opacity: (1 - searchWidget.openFactor)
|
|
||||||
|
|
||||||
// make the homescreen not interactable when task switcher or startup feedback is on
|
// make the homescreen not interactable when task switcher or startup feedback is on
|
||||||
interactive: !homeScreen.overlayShown
|
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
|
// 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
|
set(folioplugin_SRCS
|
||||||
folioplugin.cpp
|
folioplugin.cpp
|
||||||
applicationlistmodel.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)
|
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)
|
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)
|
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>
|
||||||
|
};
|
||||||
|
|
@ -70,7 +70,7 @@ MobileShell.SwipeArea {
|
||||||
}
|
}
|
||||||
|
|
||||||
onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => {
|
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
|
keyNavigationEnabled: true
|
||||||
highlightFollowsCurrentItem: true
|
highlightFollowsCurrentItem: true
|
||||||
snapMode: ListView.SnapToItem
|
snapMode: ListView.SnapToItem
|
||||||
model: imageWallpaper.wallpaperModel
|
model: imageWallpaper.slidePaths
|
||||||
onCountChanged: currentIndex = Math.min(model.indexOf(configDialog.wallpaperConfiguration["Image"]), model.rowCount()-1)
|
// onCountChanged: currentIndex = Math.min(model.indexOf(configDialog.wallpaperConfiguration["Image"]), model.rowCount()-1)
|
||||||
headerPositioning: ListView.PullBackHeader
|
headerPositioning: ListView.PullBackHeader
|
||||||
|
|
||||||
delegate: Controls.ItemDelegate {
|
delegate: Controls.ItemDelegate {
|
||||||
width: imageWallpaperDrawer.horizontal ? parent.width : height * (imageWallpaperDrawer.width / imageWallpaperDrawer.Screen.height)
|
width: imageWallpaperDrawer.horizontal ? parent.width : height * (imageWallpaperDrawer.width / imageWallpaperDrawer.Screen.height)
|
||||||
height: imageWallpaperDrawer.horizontal ? width / (imageWallpaperDrawer.Screen.width / imageWallpaperDrawer.Screen.height) : parent.height
|
height: imageWallpaperDrawer.horizontal ? width / (imageWallpaperDrawer.Screen.width / imageWallpaperDrawer.Screen.height) : parent.height
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
LookAndFeelPackage=org.kde.breeze.mobile
|
LookAndFeelPackage=org.kde.breeze.mobile
|
||||||
|
|
||||||
[Desktop]
|
[Desktop]
|
||||||
Containment=org.kde.plasma.mobile.homescreen.halcyon
|
Containment=org.kde.plasma.mobile.homescreen.folio
|
||||||
ToolBox=org.kde.plasma.nano.desktoptoolbox
|
ToolBox=org.kde.plasma.nano.desktoptoolbox
|
||||||
|
|
||||||
[Desktop][ContainmentActions]
|
[Desktop][ContainmentActions]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue