Compare commits

..

No commits in common. "f09a1db84ae4c0ba419eb789f65cc55d4a94721e" and "30e3006e3fa539897ffe38b04397cae3b48db324" have entirely different histories.

28 changed files with 163 additions and 1913 deletions

View file

@ -10,12 +10,9 @@
#include <QObject> #include <QObject>
#include <QTabletEvent> #include <QTabletEvent>
#include <QTouchEvent> #include <QTouchEvent>
#include <QWheelEvent>
// how many pixels to move before it starts being registered as a swipe // how many pixels to move before it starts being registered as a swipe
const int SWIPE_REGISTER_THRESHOLD = 10; const int SWIPE_REGISTER_THRESHOLD = 10;
const qreal WHEEL_STEP_PIXEL_DELTA = 48.0;
const int TOUCHPAD_SCROLL_END_TIMEOUT = 160;
SwipeArea::SwipeArea(QQuickItem *parent) SwipeArea::SwipeArea(QQuickItem *parent)
: QQuickItem{parent} : QQuickItem{parent}
@ -23,10 +20,6 @@ SwipeArea::SwipeArea(QQuickItem *parent)
setAcceptTouchEvents(true); setAcceptTouchEvents(true);
setAcceptedMouseButtons(Qt::LeftButton); setAcceptedMouseButtons(Qt::LeftButton);
setFiltersChildMouseEvents(true); setFiltersChildMouseEvents(true);
m_touchpadScrollEndTimer.setSingleShot(true);
m_touchpadScrollEndTimer.setInterval(TOUCHPAD_SCROLL_END_TIMEOUT);
connect(&m_touchpadScrollEndTimer, &QTimer::timeout, this, &SwipeArea::endTouchpadScroll);
} }
SwipeArea::Mode SwipeArea::mode() const SwipeArea::Mode SwipeArea::mode() const
@ -232,71 +225,43 @@ void SwipeArea::wheelEvent(QWheelEvent *event)
event->setAccepted(false); event->setAccepted(false);
const bool isTouchpad = event->deviceType() == QInputDevice::DeviceType::TouchPad;
QPointF scrollDelta = event->pixelDelta();
if (scrollDelta.isNull() && !event->angleDelta().isNull()) {
scrollDelta = QPointF(event->angleDelta()) / QWheelEvent::DefaultDeltasPerStep * WHEEL_STEP_PIXEL_DELTA;
}
switch (event->phase()) { switch (event->phase()) {
case Qt::ScrollBegin: case Qt::ScrollBegin:
if (isTouchpad && !m_touchpadScrolling) { if (!m_touchpadScrolling) {
event->accept(); event->accept();
startTouchpadScroll(event->points().first().position());
m_touchpadScrolling = true;
m_totalScrollDelta = QPointF{0, 0};
Q_EMIT touchpadScrollStarted(event->points().first().position());
} }
break; break;
case Qt::ScrollEnd: case Qt::ScrollEnd:
if (m_touchpadScrolling) { if (m_touchpadScrolling) {
endTouchpadScroll(); m_touchpadScrolling = false;
event->accept(); m_totalScrollDelta = QPointF{0, 0};
Q_EMIT touchpadScrollEnded();
} }
return; break;
default: default:
break; break;
} }
// HACK: if it isn't the touchpad, we never get the isBeginEvent() and isEndEvent() events
if (!m_touchpadScrolling) { if (!m_touchpadScrolling) {
if (!isTouchpad || scrollDelta.isNull()) {
return; return;
} }
startTouchpadScroll(event->points().first().position());
}
for (auto &point : event->points()) { for (auto &point : event->points()) {
event->addPassiveGrabber(point, this); event->addPassiveGrabber(point, this);
} }
m_totalScrollDelta = QPointF{m_totalScrollDelta + scrollDelta}; auto pixelDelta = event->pixelDelta();
Q_EMIT touchpadScrollMove(m_totalScrollDelta.x(), m_totalScrollDelta.y(), scrollDelta.x(), scrollDelta.y()); m_totalScrollDelta = QPointF{m_totalScrollDelta + pixelDelta};
Q_EMIT touchpadScrollMove(m_totalScrollDelta.x(), m_totalScrollDelta.y(), pixelDelta.x(), pixelDelta.y());
m_touchpadScrollEndTimer.start();
event->accept(); event->accept();
} }
void SwipeArea::startTouchpadScroll(QPointF point)
{
if (m_touchpadScrolling) {
return;
}
m_touchpadScrolling = true;
m_totalScrollDelta = QPointF{0, 0};
Q_EMIT touchpadScrollStarted(point);
}
void SwipeArea::endTouchpadScroll()
{
if (!m_touchpadScrolling) {
return;
}
m_touchpadScrollEndTimer.stop();
m_touchpadScrolling = false;
m_totalScrollDelta = QPointF{0, 0};
Q_EMIT touchpadScrollEnded();
}
void SwipeArea::setMoving(bool moving) void SwipeArea::setMoving(bool moving)
{ {
m_moving = moving; m_moving = moving;

View file

@ -8,7 +8,6 @@
#include <QPointerEvent> #include <QPointerEvent>
#include <QQmlListProperty> #include <QQmlListProperty>
#include <QQuickItem> #include <QQuickItem>
#include <QTimer>
#include <QTouchEvent> #include <QTouchEvent>
/** /**
@ -85,8 +84,6 @@ private:
void handlePressEvent(QPointerEvent *event, QPointF point); void handlePressEvent(QPointerEvent *event, QPointF point);
void handleReleaseEvent(QPointerEvent *event, QPointF point); void handleReleaseEvent(QPointerEvent *event, QPointF point);
void handleMoveEvent(QPointerEvent *event, QPointF point); void handleMoveEvent(QPointerEvent *event, QPointF point);
void startTouchpadScroll(QPointF point);
void endTouchpadScroll();
Mode m_mode = Mode::BothAxis; Mode m_mode = Mode::BothAxis;
bool m_interactive = true; bool m_interactive = true;
@ -113,8 +110,6 @@ private:
// the total amount of distance scrolled // the total amount of distance scrolled
QPointF m_totalScrollDelta; QPointF m_totalScrollDelta;
QTimer m_touchpadScrollEndTimer;
}; };
QML_DECLARE_TYPE(SwipeArea) QML_DECLARE_TYPE(SwipeArea)

View file

@ -17,15 +17,6 @@ MobileShell.SwipeArea {
mode: MobileShell.SwipeArea.VerticalOnly mode: MobileShell.SwipeArea.VerticalOnly
required property ActionDrawer actionDrawer required property ActionDrawer actionDrawer
property var virtualDesktopInfo: null
readonly property real touchpadDirectionLockThreshold: 12
readonly property real touchpadWorkspaceSwitchThreshold: 80
readonly property real touchpadAxisDominance: 1.25
property point touchpadStartPoint: Qt.point(0, 0)
property string touchpadGestureMode: ""
property bool touchpadWorkspaceSwitched: false
function startSwipe() { function startSwipe() {
if (actionDrawer.intendedToBeVisible) { if (actionDrawer.intendedToBeVisible) {
@ -63,96 +54,12 @@ MobileShell.SwipeArea {
actionDrawer.offset += offsetY; actionDrawer.offset += offsetY;
} }
function workspaceScrollAvailable() {
return ShellSettings.Settings.convergenceModeEnabled
&& virtualDesktopInfo !== null
&& virtualDesktopInfo.numberOfDesktops > 1;
}
function desktopIndexForId(desktopId) {
if (virtualDesktopInfo === null || !virtualDesktopInfo.desktopIds) {
return -1;
}
for (let i = 0; i < virtualDesktopInfo.desktopIds.length; ++i) {
if (String(virtualDesktopInfo.desktopIds[i]) === String(desktopId)) {
return i;
}
}
return -1;
}
function activateAdjacentWorkspace(direction) {
if (!workspaceScrollAvailable()) {
return;
}
const currentIndex = desktopIndexForId(virtualDesktopInfo.currentDesktop);
if (currentIndex < 0) {
return;
}
const targetIndex = Math.max(0, Math.min(virtualDesktopInfo.desktopIds.length - 1, currentIndex + direction));
if (targetIndex !== currentIndex) {
virtualDesktopInfo.requestActivate(virtualDesktopInfo.desktopIds[targetIndex]);
}
}
function startTouchpadScroll(point) {
touchpadStartPoint = point;
touchpadGestureMode = "";
touchpadWorkspaceSwitched = false;
if (!ShellSettings.Settings.convergenceModeEnabled) {
touchpadGestureMode = "drawer";
startSwipeWithPoint(point);
}
}
function moveTouchpadScroll(totalDeltaX, totalDeltaY, deltaX, deltaY) {
if (touchpadGestureMode === "") {
const absX = Math.abs(totalDeltaX);
const absY = Math.abs(totalDeltaY);
if (absY >= touchpadDirectionLockThreshold && absY > absX * touchpadAxisDominance) {
touchpadGestureMode = "drawer";
startSwipeWithPoint(touchpadStartPoint);
updateOffset(totalDeltaY);
return;
} else if (workspaceScrollAvailable() && absX >= touchpadDirectionLockThreshold && absX > absY * touchpadAxisDominance) {
touchpadGestureMode = "workspace";
} else {
return;
}
}
if (touchpadGestureMode === "drawer") {
updateOffset(deltaY);
return;
}
if (!touchpadWorkspaceSwitched && Math.abs(totalDeltaX) >= touchpadWorkspaceSwitchThreshold) {
touchpadWorkspaceSwitched = true;
activateAdjacentWorkspace(totalDeltaX < 0 ? 1 : -1);
}
}
function endTouchpadScroll() {
if (touchpadGestureMode === "drawer") {
endSwipe();
}
touchpadGestureMode = "";
touchpadWorkspaceSwitched = false;
}
anchors.fill: parent anchors.fill: parent
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.NoButton acceptedButtons: Qt.NoButton
hoverEnabled: true hoverEnabled: true
scrollGestureEnabled: false
cursorShape: ShellSettings.Settings.convergenceModeEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: ShellSettings.Settings.convergenceModeEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor
} }
@ -160,9 +67,9 @@ MobileShell.SwipeArea {
onSwipeEnded: endSwipe() onSwipeEnded: endSwipe()
onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => updateOffset(deltaY); onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => updateOffset(deltaY);
onTouchpadScrollStarted: (point) => startTouchpadScroll(point) onTouchpadScrollStarted: (point) => startSwipeWithPoint(point)
onTouchpadScrollEnded: endTouchpadScroll() onTouchpadScrollEnded: endSwipe()
onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => moveTouchpadScroll(totalDeltaX, totalDeltaY, deltaX, deltaY); onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => updateOffset(deltaY);
// In convergence mode, allow click to toggle the action drawer (mouse-friendly) // In convergence mode, allow click to toggle the action drawer (mouse-friendly)
onClicked: { onClicked: {

View file

@ -49,8 +49,6 @@ MobileShellSettings::MobileShellSettings(QObject *parent)
Q_EMIT dynamicTilingEnabledChanged(); Q_EMIT dynamicTilingEnabledChanged();
Q_EMIT dynamicTilingWindowRequestChanged(); Q_EMIT dynamicTilingWindowRequestChanged();
Q_EMIT dynamicTilingWindowStateChanged(); Q_EMIT dynamicTilingWindowStateChanged();
Q_EMIT dynamicTilingLayoutRequestChanged();
Q_EMIT dynamicTilingLayoutStateChanged();
Q_EMIT snapLayoutsEnabledChanged(); Q_EMIT snapLayoutsEnabledChanged();
Q_EMIT allowLogoutChanged(); Q_EMIT allowLogoutChanged();
} }
@ -373,69 +371,6 @@ void MobileShellSettings::reportDynamicTilingWindowState(const QStringList &maxi
Q_EMIT dynamicTilingWindowStateChanged(); Q_EMIT dynamicTilingWindowStateChanged();
} }
QString MobileShellSettings::dynamicTilingLayoutRequestMode() const
{
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
return group.readEntry("dynamicTilingLayoutRequestMode", QString{});
}
int MobileShellSettings::dynamicTilingLayoutRequestSerial() const
{
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
return group.readEntry("dynamicTilingLayoutRequestSerial", 0);
}
void MobileShellSettings::requestDynamicTilingLayoutMode(const QString &mode)
{
if (mode.isEmpty()) {
return;
}
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
const int serial = group.readEntry("dynamicTilingLayoutRequestSerial", 0) + 1;
group.writeEntry("dynamicTilingLayoutRequestMode", mode, KConfigGroup::Notify);
group.writeEntry("dynamicTilingLayoutRequestSerial", serial, KConfigGroup::Notify);
m_config->sync();
Q_EMIT dynamicTilingLayoutRequestChanged();
}
QString MobileShellSettings::dynamicTilingLayoutMode() const
{
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
return group.readEntry("dynamicTilingLayoutMode", QString{});
}
int MobileShellSettings::dynamicTilingLayoutWindowCount() const
{
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
return group.readEntry("dynamicTilingLayoutWindowCount", 0);
}
int MobileShellSettings::dynamicTilingLayoutStateSerial() const
{
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
return group.readEntry("dynamicTilingLayoutStateSerial", 0);
}
void MobileShellSettings::reportDynamicTilingLayoutState(const QString &mode, int windowCount)
{
const int normalizedWindowCount = windowCount < 0 ? 0 : windowCount;
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
if (group.readEntry("dynamicTilingLayoutMode", QString{}) == mode && group.readEntry("dynamicTilingLayoutWindowCount", 0) == normalizedWindowCount) {
return;
}
const int serial = group.readEntry("dynamicTilingLayoutStateSerial", 0) + 1;
group.writeEntry("dynamicTilingLayoutMode", mode, KConfigGroup::Notify);
group.writeEntry("dynamicTilingLayoutWindowCount", normalizedWindowCount, KConfigGroup::Notify);
group.writeEntry("dynamicTilingLayoutStateSerial", serial, KConfigGroup::Notify);
m_config->sync();
Q_EMIT dynamicTilingLayoutStateChanged();
}
bool MobileShellSettings::snapLayoutsEnabled() const bool MobileShellSettings::snapLayoutsEnabled() const
{ {
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};

View file

@ -65,11 +65,6 @@ class MobileShellSettings : public QObject
Q_PROPERTY(int dynamicTilingWindowRequestSerial READ dynamicTilingWindowRequestSerial NOTIFY dynamicTilingWindowRequestChanged) Q_PROPERTY(int dynamicTilingWindowRequestSerial READ dynamicTilingWindowRequestSerial NOTIFY dynamicTilingWindowRequestChanged)
Q_PROPERTY(QStringList dynamicTilingMaximizedWindowIds READ dynamicTilingMaximizedWindowIds NOTIFY dynamicTilingWindowStateChanged) Q_PROPERTY(QStringList dynamicTilingMaximizedWindowIds READ dynamicTilingMaximizedWindowIds NOTIFY dynamicTilingWindowStateChanged)
Q_PROPERTY(int dynamicTilingWindowStateSerial READ dynamicTilingWindowStateSerial NOTIFY dynamicTilingWindowStateChanged) Q_PROPERTY(int dynamicTilingWindowStateSerial READ dynamicTilingWindowStateSerial NOTIFY dynamicTilingWindowStateChanged)
Q_PROPERTY(QString dynamicTilingLayoutRequestMode READ dynamicTilingLayoutRequestMode NOTIFY dynamicTilingLayoutRequestChanged)
Q_PROPERTY(int dynamicTilingLayoutRequestSerial READ dynamicTilingLayoutRequestSerial NOTIFY dynamicTilingLayoutRequestChanged)
Q_PROPERTY(QString dynamicTilingLayoutMode READ dynamicTilingLayoutMode NOTIFY dynamicTilingLayoutStateChanged)
Q_PROPERTY(int dynamicTilingLayoutWindowCount READ dynamicTilingLayoutWindowCount NOTIFY dynamicTilingLayoutStateChanged)
Q_PROPERTY(int dynamicTilingLayoutStateSerial READ dynamicTilingLayoutStateSerial NOTIFY dynamicTilingLayoutStateChanged)
// Snap layout picker — only meaningful in convergence mode when dynamic tiling is off. // Snap layout picker — only meaningful in convergence mode when dynamic tiling is off.
Q_PROPERTY(bool snapLayoutsEnabled READ snapLayoutsEnabled WRITE setSnapLayoutsEnabled NOTIFY snapLayoutsEnabledChanged) Q_PROPERTY(bool snapLayoutsEnabled READ snapLayoutsEnabled WRITE setSnapLayoutsEnabled NOTIFY snapLayoutsEnabledChanged)
@ -307,13 +302,6 @@ public:
int dynamicTilingWindowStateSerial() const; int dynamicTilingWindowStateSerial() const;
Q_INVOKABLE bool isDynamicTilingWindowMaximized(const QString &windowId) const; Q_INVOKABLE bool isDynamicTilingWindowMaximized(const QString &windowId) const;
Q_INVOKABLE void reportDynamicTilingWindowState(const QStringList &maximizedWindowIds); Q_INVOKABLE void reportDynamicTilingWindowState(const QStringList &maximizedWindowIds);
QString dynamicTilingLayoutRequestMode() const;
int dynamicTilingLayoutRequestSerial() const;
Q_INVOKABLE void requestDynamicTilingLayoutMode(const QString &mode);
QString dynamicTilingLayoutMode() const;
int dynamicTilingLayoutWindowCount() const;
int dynamicTilingLayoutStateSerial() const;
Q_INVOKABLE void reportDynamicTilingLayoutState(const QString &mode, int windowCount);
/** /**
* Whether the SHIFT snap layout picker is enabled. * Whether the SHIFT snap layout picker is enabled.
@ -374,8 +362,6 @@ Q_SIGNALS:
void dynamicTilingEnabledChanged(); void dynamicTilingEnabledChanged();
void dynamicTilingWindowRequestChanged(); void dynamicTilingWindowRequestChanged();
void dynamicTilingWindowStateChanged(); void dynamicTilingWindowStateChanged();
void dynamicTilingLayoutRequestChanged();
void dynamicTilingLayoutStateChanged();
void snapLayoutsEnabledChanged(); void snapLayoutsEnabledChanged();
void allowLogoutChanged(); void allowLogoutChanged();
void lockscreenLeftButtonActionChanged(); void lockscreenLeftButtonActionChanged();

View file

@ -11,7 +11,6 @@ plasma_add_applet(org.kde.plasma.mobile.homescreen.folio
qml/CategoryPanel.qml qml/CategoryPanel.qml
qml/DelegateDragItem.qml qml/DelegateDragItem.qml
qml/DelegateDropArea.qml qml/DelegateDropArea.qml
qml/DynamicTilingLayoutMenu.qml
qml/FavouritesBar.qml qml/FavouritesBar.qml
qml/FolderView.qml qml/FolderView.qml
qml/FolderViewTitle.qml qml/FolderViewTitle.qml
@ -27,7 +26,6 @@ plasma_add_applet(org.kde.plasma.mobile.homescreen.folio
qml/config.qml qml/config.qml
CPP_SOURCES CPP_SOURCES
applicationlistmodel.cpp applicationlistmodel.cpp
applicationusagemodel.cpp
delegatetoucharea.cpp delegatetoucharea.cpp
dragstate.cpp dragstate.cpp
favouritesmodel.cpp favouritesmodel.cpp

View file

@ -182,17 +182,6 @@ int ApplicationListModel::rowCount(const QModelIndex &parent) const
return m_delegates.count(); return m_delegates.count();
} }
std::shared_ptr<FolioDelegate> ApplicationListModel::delegateForStorageId(const QString &storageId) const
{
for (const auto &delegate : m_delegates) {
if (delegate && delegate->application() && delegate->application()->storageId() == storageId) {
return delegate;
}
}
return nullptr;
}
// Sub-categories merged into their canonical parent, mirroring Kickoff's grouping. // Sub-categories merged into their canonical parent, mirroring Kickoff's grouping.
static QString normalizeCategory(const QString &cat) static QString normalizeCategory(const QString &cat)
{ {

View file

@ -12,8 +12,6 @@
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include <QStringList> #include <QStringList>
#include <memory>
#include <KService> #include <KService>
#include "foliodelegate.h" #include "foliodelegate.h"
@ -47,8 +45,6 @@ public:
void load(); void load();
std::shared_ptr<FolioDelegate> delegateForStorageId(const QString &storageId) const;
Q_INVOKABLE QStringList allCategories() const; Q_INVOKABLE QStringList allCategories() const;
Q_SIGNALS: Q_SIGNALS:

View file

@ -1,208 +0,0 @@
// SPDX-FileCopyrightText: 2026 Marco Allegretti
// SPDX-License-Identifier: EUPL-1.2
#include "applicationusagemodel.h"
#include "applicationlistmodel.h"
#include "foliodelegate.h"
#include "foliosettings.h"
#include "homescreen.h"
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
namespace
{
constexpr qsizetype s_maxStoredEntries = 24;
QString normalizedStorageId(const QString &storageId)
{
if (storageId.isEmpty()) {
return {};
}
if (storageId.endsWith(QLatin1String(".desktop"))) {
return storageId;
}
return storageId + QStringLiteral(".desktop");
}
}
ApplicationUsageStore::ApplicationUsageStore(HomeScreen *parent)
: QObject{parent}
, m_homeScreen{parent}
{
load();
}
QList<ApplicationUsageEntry> ApplicationUsageStore::entries() const
{
return m_entries.values();
}
void ApplicationUsageStore::recordUsage(const QString &storageId)
{
const QString normalizedId = normalizedStorageId(storageId);
if (normalizedId.isEmpty()) {
return;
}
auto &entry = m_entries[normalizedId];
entry.storageId = normalizedId;
entry.launchCount = std::max(0, entry.launchCount) + 1;
entry.lastUsed = QDateTime::currentDateTimeUtc();
save();
Q_EMIT usageChanged();
}
void ApplicationUsageStore::load()
{
m_entries.clear();
if (!m_homeScreen) {
return;
}
const QJsonDocument doc = QJsonDocument::fromJson(m_homeScreen->folioSettings()->applicationUsage().toUtf8());
const QJsonArray usageArray = doc.array();
for (const QJsonValue &value : usageArray) {
const QJsonObject object = value.toObject();
const QString storageId = normalizedStorageId(object.value(QStringLiteral("storageId")).toString());
if (storageId.isEmpty()) {
continue;
}
ApplicationUsageEntry entry;
entry.storageId = storageId;
entry.launchCount = object.value(QStringLiteral("launchCount")).toInt();
entry.lastUsed = QDateTime::fromString(object.value(QStringLiteral("lastUsed")).toString(), Qt::ISODateWithMs);
if (entry.launchCount <= 0 || !entry.lastUsed.isValid()) {
continue;
}
m_entries.insert(storageId, entry);
}
}
void ApplicationUsageStore::save()
{
if (!m_homeScreen) {
return;
}
QList<ApplicationUsageEntry> entries = m_entries.values();
std::sort(entries.begin(), entries.end(), [](const ApplicationUsageEntry &left, const ApplicationUsageEntry &right) {
return left.lastUsed > right.lastUsed;
});
if (entries.size() > s_maxStoredEntries) {
entries.resize(s_maxStoredEntries);
}
QJsonArray usageArray;
for (const ApplicationUsageEntry &entry : std::as_const(entries)) {
QJsonObject object;
object.insert(QStringLiteral("storageId"), entry.storageId);
object.insert(QStringLiteral("launchCount"), entry.launchCount);
object.insert(QStringLiteral("lastUsed"), entry.lastUsed.toString(Qt::ISODateWithMs));
usageArray.append(object);
}
m_homeScreen->folioSettings()->setApplicationUsage(QString::fromUtf8(QJsonDocument(usageArray).toJson(QJsonDocument::Compact)));
}
ApplicationUsageModel::ApplicationUsageModel(HomeScreen *homeScreen, ApplicationUsageStore *store, Mode mode)
: QAbstractListModel{homeScreen}
, m_homeScreen{homeScreen}
, m_store{store}
, m_mode{mode}
{
if (m_store) {
connect(m_store, &ApplicationUsageStore::usageChanged, this, &ApplicationUsageModel::rebuild);
}
if (m_homeScreen && m_homeScreen->applicationListModel()) {
auto *applicationListModel = m_homeScreen->applicationListModel();
connect(applicationListModel, &QAbstractItemModel::rowsInserted, this, &ApplicationUsageModel::rebuild);
connect(applicationListModel, &QAbstractItemModel::rowsRemoved, this, &ApplicationUsageModel::rebuild);
connect(applicationListModel, &QAbstractItemModel::modelReset, this, &ApplicationUsageModel::rebuild);
}
rebuild();
}
int ApplicationUsageModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return 0;
}
return m_entries.size();
}
QVariant ApplicationUsageModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() < 0 || index.row() >= m_entries.size()) {
return QVariant();
}
const ApplicationUsageEntry &entry = m_entries.at(index.row());
switch (role) {
case Qt::DisplayRole:
case DelegateRole:
return QVariant::fromValue(m_delegates.at(index.row()).get());
case LaunchCountRole:
return entry.launchCount;
case LastUsedRole:
return entry.lastUsed;
default:
return QVariant();
}
}
QHash<int, QByteArray> ApplicationUsageModel::roleNames() const
{
return {
{DelegateRole, QByteArrayLiteral("delegate")},
{LaunchCountRole, QByteArrayLiteral("launchCount")},
{LastUsedRole, QByteArrayLiteral("lastUsed")},
};
}
void ApplicationUsageModel::rebuild()
{
QList<ApplicationUsageEntry> entries = m_store ? m_store->entries() : QList<ApplicationUsageEntry>{};
std::sort(entries.begin(), entries.end(), [this](const ApplicationUsageEntry &left, const ApplicationUsageEntry &right) {
if (m_mode == MostUsed && left.launchCount != right.launchCount) {
return left.launchCount > right.launchCount;
}
return left.lastUsed > right.lastUsed;
});
QList<ApplicationUsageEntry> nextEntries;
QList<std::shared_ptr<FolioDelegate>> nextDelegates;
if (m_homeScreen && m_homeScreen->applicationListModel()) {
for (const ApplicationUsageEntry &entry : std::as_const(entries)) {
auto delegate = m_homeScreen->applicationListModel()->delegateForStorageId(entry.storageId);
if (!delegate || !delegate->application()) {
continue;
}
nextEntries.append(entry);
nextDelegates.append(delegate);
}
}
beginResetModel();
m_entries = nextEntries;
m_delegates = nextDelegates;
endResetModel();
}

View file

@ -1,80 +0,0 @@
// SPDX-FileCopyrightText: 2026 Marco Allegretti
// SPDX-License-Identifier: EUPL-1.2
#pragma once
#include <QAbstractListModel>
#include <QDateTime>
#include <QHash>
#include <QList>
#include <QObject>
#include <QString>
#include <QtQmlIntegration/qqmlintegration.h>
#include <memory>
class FolioDelegate;
class HomeScreen;
struct ApplicationUsageEntry {
QString storageId;
int launchCount = 0;
QDateTime lastUsed;
};
class ApplicationUsageStore : public QObject
{
Q_OBJECT
public:
explicit ApplicationUsageStore(HomeScreen *parent = nullptr);
QList<ApplicationUsageEntry> entries() const;
void recordUsage(const QString &storageId);
Q_SIGNALS:
void usageChanged();
private:
void load();
void save();
HomeScreen *m_homeScreen{nullptr};
QHash<QString, ApplicationUsageEntry> m_entries;
};
class ApplicationUsageModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
public:
enum Mode {
RecentUsage,
MostUsed,
};
enum Roles {
DelegateRole = Qt::UserRole + 1,
LaunchCountRole,
LastUsedRole,
};
ApplicationUsageModel(HomeScreen *homeScreen = nullptr, ApplicationUsageStore *store = nullptr, Mode mode = RecentUsage);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
public Q_SLOTS:
void rebuild();
private:
HomeScreen *m_homeScreen{nullptr};
ApplicationUsageStore *m_store{nullptr};
Mode m_mode{RecentUsage};
QList<ApplicationUsageEntry> m_entries;
QList<std::shared_ptr<FolioDelegate>> m_delegates;
};

View file

@ -16,7 +16,6 @@ using namespace Qt::Literals::StringLiterals;
const QString CFG_GROUP_FOLIO = QStringLiteral("Folio"); const QString CFG_GROUP_FOLIO = QStringLiteral("Folio");
const QString CFG_KEY_FAVORITES = QStringLiteral("favorites"); const QString CFG_KEY_FAVORITES = QStringLiteral("favorites");
const QString CFG_KEY_APPLICATION_USAGE = QStringLiteral("applicationUsage");
const QString CFG_KEY_PAGES = QStringLiteral("pages"); const QString CFG_KEY_PAGES = QStringLiteral("pages");
const QString CFG_KEY_HOMESCREEN_ROWS = QStringLiteral("homeScreenRows"); const QString CFG_KEY_HOMESCREEN_ROWS = QStringLiteral("homeScreenRows");
@ -48,17 +47,6 @@ void FolioSettings::setFavorites(const QString &favoritesJson)
Q_EMIT m_homeScreen->configNeedsSaving(); Q_EMIT m_homeScreen->configNeedsSaving();
} }
QString FolioSettings::applicationUsage() const
{
return generalConfigGroup().readEntry(CFG_KEY_APPLICATION_USAGE, QStringLiteral("[]"));
}
void FolioSettings::setApplicationUsage(const QString &applicationUsageJson)
{
generalConfigGroup().writeEntry(CFG_KEY_APPLICATION_USAGE, applicationUsageJson);
Q_EMIT m_homeScreen->configNeedsSaving();
}
QString FolioSettings::pages() const QString FolioSettings::pages() const
{ {
return generalConfigGroup().readEntry(CFG_KEY_PAGES, u"{}"_s); return generalConfigGroup().readEntry(CFG_KEY_PAGES, u"{}"_s);

View file

@ -53,10 +53,6 @@ public:
QString favorites() const; QString favorites() const;
void setFavorites(const QString &favoritesJson); void setFavorites(const QString &favoritesJson);
// JSON array
QString applicationUsage() const;
void setApplicationUsage(const QString &applicationUsageJson);
// JSON object // JSON object
QString pages() const; QString pages() const;
void setPages(const QString &pagesJson); void setPages(const QString &pagesJson);

View file

@ -4,8 +4,6 @@
#include "homescreen.h" #include "homescreen.h"
#include "windowlistener.h"
#include <virtualdesktopinfo.h> #include <virtualdesktopinfo.h>
#include <KWindowSystem> #include <KWindowSystem>
@ -60,9 +58,6 @@ HomeScreen::HomeScreen(QObject *parent, const KPluginMetaData &data, const QVari
, m_widgetsManager{new WidgetsManager{this}} , m_widgetsManager{new WidgetsManager{this}}
, m_applicationListModel{new ApplicationListModel{this}} , m_applicationListModel{new ApplicationListModel{this}}
, m_applicationListSearchModel{new ApplicationListSearchModel{this, m_applicationListModel}} , m_applicationListSearchModel{new ApplicationListSearchModel{this, m_applicationListModel}}
, m_applicationUsageStore{new ApplicationUsageStore{this}}
, m_recentApplicationsModel{new ApplicationUsageModel{this, m_applicationUsageStore, ApplicationUsageModel::RecentUsage}}
, m_mostUsedApplicationsModel{new ApplicationUsageModel{this, m_applicationUsageStore, ApplicationUsageModel::MostUsed}}
, m_favouritesModel{new FavouritesModel{this}} , m_favouritesModel{new FavouritesModel{this}}
, m_pageListModel{new PageListModel{this}} , m_pageListModel{new PageListModel{this}}
{ {
@ -89,11 +84,6 @@ HomeScreen::HomeScreen(QObject *parent, const KPluginMetaData &data, const QVari
connect(this, &Plasma::Containment::appletAdded, this, &HomeScreen::onAppletAdded); connect(this, &Plasma::Containment::appletAdded, this, &HomeScreen::onAppletAdded);
connect(this, &Plasma::Containment::appletAboutToBeRemoved, this, &HomeScreen::onAppletAboutToBeRemoved); connect(this, &Plasma::Containment::appletAboutToBeRemoved, this, &HomeScreen::onAppletAboutToBeRemoved);
connect(WindowListener::instance(), &WindowListener::windowChanged, this, [this](QString storageId) {
if (!WindowListener::instance()->windowsFromStorageId(storageId).empty()) {
m_applicationUsageStore->recordUsage(storageId);
}
});
} }
HomeScreen::~HomeScreen() = default; HomeScreen::~HomeScreen() = default;
@ -139,16 +129,6 @@ ApplicationListSearchModel *HomeScreen::applicationListSearchModel()
return m_applicationListSearchModel; return m_applicationListSearchModel;
} }
ApplicationUsageModel *HomeScreen::recentApplicationsModel()
{
return m_recentApplicationsModel;
}
ApplicationUsageModel *HomeScreen::mostUsedApplicationsModel()
{
return m_mostUsedApplicationsModel;
}
FavouritesModel *HomeScreen::favouritesModel() FavouritesModel *HomeScreen::favouritesModel()
{ {
return m_favouritesModel; return m_favouritesModel;

View file

@ -9,7 +9,6 @@
#include <QVariant> #include <QVariant>
#include "applicationlistmodel.h" #include "applicationlistmodel.h"
#include "applicationusagemodel.h"
#include "delegatetoucharea.h" #include "delegatetoucharea.h"
#include "favouritesmodel.h" #include "favouritesmodel.h"
#include "folioapplication.h" #include "folioapplication.h"
@ -30,8 +29,6 @@ class HomeScreenState;
class FavouritesModel; class FavouritesModel;
class ApplicationListModel; class ApplicationListModel;
class ApplicationListSearchModel; class ApplicationListSearchModel;
class ApplicationUsageModel;
class ApplicationUsageStore;
class HomeScreen : public Plasma::Containment class HomeScreen : public Plasma::Containment
{ {
@ -44,8 +41,6 @@ class HomeScreen : public Plasma::Containment
Q_PROPERTY(WidgetsManager *WidgetsManager READ widgetsManager CONSTANT) Q_PROPERTY(WidgetsManager *WidgetsManager READ widgetsManager CONSTANT)
Q_PROPERTY(ApplicationListModel *ApplicationListModel READ applicationListModel CONSTANT) Q_PROPERTY(ApplicationListModel *ApplicationListModel READ applicationListModel CONSTANT)
Q_PROPERTY(ApplicationListSearchModel *ApplicationListSearchModel READ applicationListSearchModel CONSTANT) Q_PROPERTY(ApplicationListSearchModel *ApplicationListSearchModel READ applicationListSearchModel CONSTANT)
Q_PROPERTY(ApplicationUsageModel *RecentApplicationsModel READ recentApplicationsModel CONSTANT)
Q_PROPERTY(ApplicationUsageModel *MostUsedApplicationsModel READ mostUsedApplicationsModel CONSTANT)
Q_PROPERTY(FavouritesModel *FavouritesModel READ favouritesModel CONSTANT) Q_PROPERTY(FavouritesModel *FavouritesModel READ favouritesModel CONSTANT)
Q_PROPERTY(PageListModel *PageListModel READ pageListModel CONSTANT) Q_PROPERTY(PageListModel *PageListModel READ pageListModel CONSTANT)
Q_PROPERTY(bool overviewActive READ overviewActive NOTIFY overviewActiveChanged) Q_PROPERTY(bool overviewActive READ overviewActive NOTIFY overviewActiveChanged)
@ -68,8 +63,6 @@ public:
WidgetsManager *widgetsManager(); WidgetsManager *widgetsManager();
ApplicationListModel *applicationListModel(); ApplicationListModel *applicationListModel();
ApplicationListSearchModel *applicationListSearchModel(); ApplicationListSearchModel *applicationListSearchModel();
ApplicationUsageModel *recentApplicationsModel();
ApplicationUsageModel *mostUsedApplicationsModel();
FavouritesModel *favouritesModel(); FavouritesModel *favouritesModel();
PageListModel *pageListModel(); PageListModel *pageListModel();
bool overviewActive() const; bool overviewActive() const;
@ -92,9 +85,6 @@ private:
WidgetsManager *m_widgetsManager{nullptr}; WidgetsManager *m_widgetsManager{nullptr};
ApplicationListModel *m_applicationListModel{nullptr}; ApplicationListModel *m_applicationListModel{nullptr};
ApplicationListSearchModel *m_applicationListSearchModel{nullptr}; ApplicationListSearchModel *m_applicationListSearchModel{nullptr};
ApplicationUsageStore *m_applicationUsageStore{nullptr};
ApplicationUsageModel *m_recentApplicationsModel{nullptr};
ApplicationUsageModel *m_mostUsedApplicationsModel{nullptr};
FavouritesModel *m_favouritesModel{nullptr}; FavouritesModel *m_favouritesModel{nullptr};
PageListModel *m_pageListModel{nullptr}; PageListModel *m_pageListModel{nullptr};
bool m_overviewActive{false}; bool m_overviewActive{false};

View file

@ -1,298 +0,0 @@
// SPDX-FileCopyrightText: 2026 Marco Allegretti
// SPDX-License-Identifier: EUPL-1.2
import QtQuick
import QtQuick.Layouts
import QtQuick.Shapes 1.8
import org.kde.kirigami as Kirigami
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.private.mobileshell as MobileShell
Item {
id: root
property int windowCount: 0
property string currentMode: ""
property color surfaceColor: Kirigami.Theme.backgroundColor
property int animationDuration: Kirigami.Units.shortDuration
property real maxHeight: 0
signal layoutModeRequested(string mode)
signal dismissRequested()
readonly property int clampedWindowCount: clampedLayoutWindowCount(windowCount)
readonly property var layoutOptions: layoutOptionsForWindowCount(clampedWindowCount)
readonly property int optionCount: layoutOptions.length
readonly property real rowHeight: Math.max(Kirigami.Units.gridUnit * 2.4,
Kirigami.Units.iconSizes.medium + Kirigami.Units.smallSpacing * 2)
readonly property real naturalHeight: Kirigami.Units.gridUnit * 2.2
+ Math.max(1, optionCount) * rowHeight
+ Kirigami.Units.smallSpacing * 3
readonly property real preferredHeight: maxHeight > 0 ? Math.min(naturalHeight, maxHeight) : naturalHeight
readonly property real cornerRadius: Math.min(MobileShell.Constants.convergenceWorkspaceFrameRadius, height * 0.24)
clip: true
function clampedLayoutWindowCount(windowCount) {
const count = Math.round(Number(windowCount) || 0)
return Math.max(0, Math.min(4, count))
}
function linearLayoutZones(windowCount, orientation) {
const count = Math.max(1, clampedLayoutWindowCount(windowCount))
let zones = []
for (let i = 0; i < count; i++) {
if (orientation === "horizontal") {
zones.push({ x: 0, y: i / count, w: 1, h: 1 / count })
} else {
zones.push({ x: i / count, y: 0, w: 1 / count, h: 1 })
}
}
return zones
}
function masterLayoutZones(windowCount) {
const count = clampedLayoutWindowCount(windowCount)
if (count <= 2) {
return linearLayoutZones(Math.max(1, count), "vertical")
}
let zones = [{ x: 0, y: 0, w: 0.58, h: 1 }]
const stackCount = count - 1
for (let i = 0; i < stackCount; i++) {
zones.push({ x: 0.58, y: i / stackCount, w: 0.42, h: 1 / stackCount })
}
return zones
}
function layoutOptionsForWindowCount(windowCount) {
const count = clampedLayoutWindowCount(windowCount)
if (count < 2) {
return []
}
if (count === 2) {
return [
{
mode: "columns",
selectedModes: ["master", "columns"],
name: i18n("Side by Side"),
summary: i18n("2 columns"),
zones: linearLayoutZones(count, "vertical")
},
{
mode: "rows",
selectedModes: ["rows"],
name: i18n("Stacked"),
summary: i18n("2 rows"),
zones: linearLayoutZones(count, "horizontal")
}
]
}
return [
{
mode: "master",
selectedModes: ["master"],
name: i18n("Master Stack"),
summary: i18n("1 + %1 stack", count - 1),
zones: masterLayoutZones(count)
},
{
mode: "columns",
selectedModes: ["columns"],
name: i18n("Columns"),
summary: i18n("%1 columns", count),
zones: linearLayoutZones(count, "vertical")
},
{
mode: "rows",
selectedModes: ["rows"],
name: i18n("Rows"),
summary: i18n("%1 rows", count),
zones: linearLayoutZones(count, "horizontal")
}
]
}
function emptyLayoutSummary() {
return clampedWindowCount === 1 ? i18n("1 window") : i18n("0 windows")
}
Behavior on opacity {
MobileShell.MotionNumberAnimation {
type: MobileShell.Motion.EffectsFast
duration: root.animationDuration
}
}
Shape {
anchors.fill: parent
ShapePath {
fillColor: root.surfaceColor
strokeWidth: 0
startX: root.width
startY: 0
PathLine { x: root.cornerRadius; y: 0 }
PathArc {
x: 0
y: root.cornerRadius
radiusX: root.cornerRadius
radiusY: root.cornerRadius
direction: PathArc.Counterclockwise
}
PathLine { x: 0; y: root.height - root.cornerRadius }
PathArc {
x: root.cornerRadius
y: root.height
radiusX: root.cornerRadius
radiusY: root.cornerRadius
direction: PathArc.Counterclockwise
}
PathLine { x: root.width; y: root.height }
PathLine { x: root.width; y: 0 }
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
PlasmaComponents.Label {
Layout.fillWidth: true
text: i18n("Tiling Layout")
font.weight: Font.Medium
elide: Text.ElideRight
maximumLineCount: 1
}
Repeater {
model: root.layoutOptions
delegate: MouseArea {
id: optionButton
required property var modelData
Layout.fillWidth: true
Layout.preferredHeight: root.rowHeight
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
readonly property bool selected: modelData.selectedModes.indexOf(root.currentMode) >= 0
onClicked: {
if (!selected) {
root.layoutModeRequested(modelData.mode)
}
root.dismissRequested()
}
Rectangle {
anchors.fill: parent
radius: Kirigami.Units.cornerRadius
color: optionButton.selected
? Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b, 0.24)
: optionButton.containsMouse
? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.08)
: "transparent"
border.width: optionButton.selected ? 1 : 0
border.color: Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b, 0.5)
}
RowLayout {
anchors.fill: parent
anchors.leftMargin: Kirigami.Units.smallSpacing
anchors.rightMargin: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
Rectangle {
id: layoutPreviewFrame
Layout.preferredWidth: Kirigami.Units.gridUnit * 2.5
Layout.preferredHeight: Kirigami.Units.gridUnit * 1.65
radius: Kirigami.Units.cornerRadius
color: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.06)
border.width: 1
border.color: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.18)
Repeater {
model: optionButton.modelData.zones
delegate: Rectangle {
required property var modelData
x: Math.round(layoutPreviewFrame.width * modelData.x) + Kirigami.Units.smallSpacing / 2
y: Math.round(layoutPreviewFrame.height * modelData.y) + Kirigami.Units.smallSpacing / 2
width: Math.max(1, Math.round(layoutPreviewFrame.width * modelData.w) - Kirigami.Units.smallSpacing)
height: Math.max(1, Math.round(layoutPreviewFrame.height * modelData.h) - Kirigami.Units.smallSpacing)
radius: Math.max(1, Kirigami.Units.cornerRadius - 1)
color: Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b, 0.58)
}
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
PlasmaComponents.Label {
Layout.fillWidth: true
text: optionButton.modelData.name
elide: Text.ElideRight
maximumLineCount: 1
}
PlasmaComponents.Label {
Layout.fillWidth: true
text: optionButton.modelData.summary
opacity: 0.62
font: Kirigami.Theme.smallFont
elide: Text.ElideRight
maximumLineCount: 1
}
}
}
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
visible: root.optionCount === 0
spacing: Kirigami.Units.smallSpacing
Item { Layout.fillHeight: true }
PlasmaComponents.Label {
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
text: i18n("No Alternatives")
font.weight: Font.Medium
elide: Text.ElideRight
maximumLineCount: 1
}
PlasmaComponents.Label {
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
text: root.emptyLayoutSummary()
opacity: 0.62
font: Kirigami.Theme.smallFont
elide: Text.ElideRight
maximumLineCount: 1
}
Item { Layout.fillHeight: true }
}
}
}

View file

@ -94,8 +94,6 @@ MouseArea {
// Virtual desktop pager (convergence mode, 2+ desktops) // Virtual desktop pager (convergence mode, 2+ desktops)
readonly property bool showPager: convergenceMode && virtualDesktopInfo.numberOfDesktops > 1 readonly property bool showPager: convergenceMode && virtualDesktopInfo.numberOfDesktops > 1
property real pagerButtonWidth: showPager ? Math.min(root.height, Kirigami.Units.gridUnit * 2.5) : 0 property real pagerButtonWidth: showPager ? Math.min(root.height, Kirigami.Units.gridUnit * 2.5) : 0
property int pagerWheelDelta: 0
property bool pagerWheelLocked: false
readonly property int pagerLeftCount: showPager ? Math.ceil(virtualDesktopInfo.numberOfDesktops / 2) : 0 readonly property int pagerLeftCount: showPager ? Math.ceil(virtualDesktopInfo.numberOfDesktops / 2) : 0
readonly property int pagerRightCount: showPager ? virtualDesktopInfo.numberOfDesktops - pagerLeftCount : 0 readonly property int pagerRightCount: showPager ? virtualDesktopInfo.numberOfDesktops - pagerLeftCount : 0
property real desktopButtonWidth: convergenceMode ? root.height : 0 property real desktopButtonWidth: convergenceMode ? root.height : 0
@ -130,15 +128,6 @@ MouseArea {
onTriggered: root.hideDockToolTip(root.activeDockToolTipItem) onTriggered: root.hideDockToolTip(root.activeDockToolTipItem)
} }
Timer {
id: pagerWheelEndTimer
interval: 160
onTriggered: {
root.pagerWheelDelta = 0
root.pagerWheelLocked = false
}
}
function requestDockToolTip(item) { function requestDockToolTip(item) {
activeDockToolTipItem = null activeDockToolTipItem = null
pendingDockToolTipItem = item pendingDockToolTipItem = item
@ -203,50 +192,6 @@ MouseArea {
return -1 return -1
} }
function activateAdjacentDesktop(direction) {
let ids = virtualDesktopInfo.desktopIds
if (!ids || ids.length <= 1) {
return
}
let currentIndex = root.desktopIndexForId(virtualDesktopInfo.currentDesktop)
if (currentIndex < 0) {
return
}
let targetIndex = Math.max(0, Math.min(ids.length - 1, currentIndex + direction))
if (targetIndex !== currentIndex) {
root.folio.activateVirtualDesktop(String(ids[targetIndex]))
}
}
function handlePagerWheel(wheel) {
if (!root.showPager) {
return
}
const axisDelta = wheel.angleDelta.y || -wheel.angleDelta.x
if (axisDelta === 0) {
return
}
pagerWheelEndTimer.restart()
if (root.pagerWheelLocked) {
return
}
root.pagerWheelDelta += axisDelta * (wheel.inverted ? -1 : 1)
if (root.pagerWheelDelta >= 120) {
root.pagerWheelLocked = true
root.pagerWheelDelta = 0
root.activateAdjacentDesktop(-1)
} else if (root.pagerWheelDelta <= -120) {
root.pagerWheelLocked = true
root.pagerWheelDelta = 0
root.activateAdjacentDesktop(1)
}
}
function dynamicTilingMoveToDesktopAction(desktopId) { function dynamicTilingMoveToDesktopAction(desktopId) {
let index = root.desktopIndexForId(desktopId) let index = root.desktopIndexForId(desktopId)
if (index < 0) { if (index < 0) {
@ -740,7 +685,6 @@ MouseArea {
hoverEnabled: true hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onWheel: (wheel) => root.handlePagerWheel(wheel)
onClicked: (mouse) => { onClicked: (mouse) => {
root.hideDockToolTip(leftDesktopBtn) root.hideDockToolTip(leftDesktopBtn)
if (mouse.button === Qt.RightButton) { if (mouse.button === Qt.RightButton) {
@ -850,7 +794,6 @@ MouseArea {
hoverEnabled: true hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onWheel: (wheel) => root.handlePagerWheel(wheel)
onClicked: (mouse) => { onClicked: (mouse) => {
root.hideDockToolTip(rightDesktopBtn) root.hideDockToolTip(rightDesktopBtn)
if (mouse.button === Qt.RightButton) { if (mouse.button === Qt.RightButton) {

View file

@ -188,21 +188,9 @@ Item {
homeScreenState.swipeMoved(totalDeltaX, totalDeltaY, deltaX, deltaY); homeScreenState.swipeMoved(totalDeltaX, totalDeltaY, deltaX, deltaY);
} }
onTouchpadScrollStarted: { onTouchpadScrollStarted: homeScreenState.swipeStarted(0, 0);
if (!ShellSettings.Settings.convergenceModeEnabled) { onTouchpadScrollEnded: homeScreenState.swipeEnded();
root.homeScreenState.swipeStarted(0, 0); onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => homeScreenState.swipeMoved(totalDeltaX, totalDeltaY, deltaX, deltaY);
}
}
onTouchpadScrollEnded: {
if (!ShellSettings.Settings.convergenceModeEnabled) {
root.homeScreenState.swipeEnded();
}
}
onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => {
if (!ShellSettings.Settings.convergenceModeEnabled) {
root.homeScreenState.swipeMoved(totalDeltaX, totalDeltaY, deltaX, deltaY);
}
}
onPressedChanged: { onPressedChanged: {
if (pressed) { if (pressed) {

View file

@ -305,43 +305,6 @@ ContainmentItem {
readonly property real workAreaY: topBarHitHeight readonly property real workAreaY: topBarHitHeight
readonly property real workAreaWidth: Math.max(0, width - frameThickness * 2) readonly property real workAreaWidth: Math.max(0, width - frameThickness * 2)
readonly property real workAreaHeight: Math.max(0, height - topBarHeight - dockHeight - frameThickness * 2) readonly property real workAreaHeight: Math.max(0, height - topBarHeight - dockHeight - frameThickness * 2)
readonly property real leftEdgeHotzoneWidth: Math.max(frameThickness, Math.round(Kirigami.Units.gridUnit * 0.7))
readonly property real rightEdgeHotzoneWidth: leftEdgeHotzoneWidth
readonly property real leftLauncherWidth: Math.min(Kirigami.Units.gridUnit * 22, width * 0.42)
readonly property real leftLauncherHeight: Math.min(Kirigami.Units.gridUnit * 16, workAreaHeight * 0.66)
readonly property bool leftLauncherEnabled: root.folio.HomeScreenState.appDrawerOpenProgress <= 0
readonly property real layoutMenuWidth: Math.min(Kirigami.Units.gridUnit * 16, width * 0.34)
readonly property int layoutMenuWindowCount: Math.max(0, ShellSettings.Settings.dynamicTilingLayoutWindowCount)
readonly property bool layoutMenuEnabled: ShellSettings.Settings.dynamicTilingEnabled
&& layoutMenuWindowCount >= 2
&& root.folio.HomeScreenState.appDrawerOpenProgress <= 0
readonly property real leftFrameBulgeIdleDepth: Math.max(frameThickness * 0.45, Kirigami.Units.gridUnit * 0.16)
readonly property real leftFrameBulgeHoverDepth: 0
property real leftFrameBulgeDepth: !leftLauncherEnabled || leftLauncherOpen || leftEdgeHovered
? leftFrameBulgeHoverDepth
: leftFrameBulgeIdleDepth
property real rightFrameBulgeDepth: !layoutMenuEnabled || layoutMenuOpen || rightEdgeHovered
? leftFrameBulgeHoverDepth
: leftFrameBulgeIdleDepth
// Long, thin thickening of the lower-left workspace wall. Vertical
// tangents at all three anchors keep the curve smooth as it blends
// into the straight wall above and below.
readonly property real leftFrameBulgeEffectiveDepth: Math.max(leftFrameBulgeDepth, 0.01)
readonly property real leftFrameBulgeApexX: workAreaX + leftFrameBulgeEffectiveDepth
readonly property real leftFrameBulgeHalfLength: Kirigami.Units.gridUnit * 7.5
readonly property real leftFrameBulgeApexY: workAreaY + workAreaHeight * 0.7
readonly property real leftFrameBulgeEdgeTopY: leftFrameBulgeApexY - leftFrameBulgeHalfLength
readonly property real leftFrameBulgeEdgeBottomY: leftFrameBulgeApexY + leftFrameBulgeHalfLength
// Bezier control-handle length along the vertical tangent at each
// anchor. ~0.55 of the half-length gives a clean, taut oval profile.
readonly property real leftFrameBulgeTangent: leftFrameBulgeHalfLength * 0.55
readonly property real rightFrameBulgeEffectiveDepth: Math.max(rightFrameBulgeDepth, 0.01)
readonly property real rightFrameBulgeApexX: workAreaX + workAreaWidth - rightFrameBulgeEffectiveDepth
readonly property real rightFrameBulgeHalfLength: leftFrameBulgeHalfLength
readonly property real rightFrameBulgeApexY: leftFrameBulgeApexY
readonly property real rightFrameBulgeEdgeTopY: rightFrameBulgeApexY - rightFrameBulgeHalfLength
readonly property real rightFrameBulgeEdgeBottomY: rightFrameBulgeApexY + rightFrameBulgeHalfLength
readonly property real rightFrameBulgeTangent: rightFrameBulgeHalfLength * 0.55
readonly property color chromeColor: Kirigami.Theme.backgroundColor readonly property color chromeColor: Kirigami.Theme.backgroundColor
readonly property color edgeColor: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.1) readonly property color edgeColor: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.1)
readonly property int dockAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.SpatialDefault) readonly property int dockAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.SpatialDefault)
@ -352,12 +315,6 @@ ContainmentItem {
// True once the hover-reveal timer fires; cleared on hover-exit. // True once the hover-reveal timer fires; cleared on hover-exit.
property bool hoverRevealing: false property bool hoverRevealing: false
property bool leftEdgeHovered: false
property bool leftLauncherHovered: false
property bool leftLauncherOpen: false
property bool rightEdgeHovered: false
property bool layoutMenuHovered: false
property bool layoutMenuOpen: false
readonly property bool shouldHide: ShellSettings.Settings.autoHidePanelsEnabled readonly property bool shouldHide: ShellSettings.Settings.autoHidePanelsEnabled
&& windowMaximizedTracker.showingWindow && !hoverRevealing && windowMaximizedTracker.showingWindow && !hoverRevealing
@ -367,87 +324,17 @@ ContainmentItem {
function updateInputRegion() { function updateInputRegion() {
const topBarRegion = Qt.rect(0, 0, width, topBarHitHeight) const topBarRegion = Qt.rect(0, 0, width, topBarHitHeight)
const leftEdgeRegion = Qt.rect(0, topBarHitHeight, leftEdgeHotzoneWidth, Math.max(0, height - topBarHitHeight - dockHeight))
const rightEdgeRegion = Qt.rect(width - rightEdgeHotzoneWidth, topBarHitHeight, rightEdgeHotzoneWidth, Math.max(0, height - topBarHitHeight - dockHeight))
const leftLauncherRegion = Qt.rect(0,
Math.max(0, height - dockHeight - leftLauncherHeight),
leftLauncherWidth,
leftLauncherHeight)
const layoutMenuRegion = Qt.rect(rightLayoutMenu.x,
rightLayoutMenu.y,
rightLayoutMenu.width,
rightLayoutMenu.height)
let regions = [topBarRegion, leftEdgeRegion]
if (layoutMenuEnabled) {
regions.push(rightEdgeRegion)
}
if (shouldHide && dockOffset >= dockHeight) { if (shouldHide && dockOffset >= dockHeight) {
regions.push(Qt.rect(0, height - revealStripHeight, width, revealStripHeight)) MobileShell.ShellUtil.setInputRegions(convergenceChrome, [
topBarRegion,
Qt.rect(0, height - revealStripHeight, width, revealStripHeight)
])
} else { } else {
regions.push(Qt.rect(0, height - dockHeight, width, dockHeight)) MobileShell.ShellUtil.setInputRegions(convergenceChrome, [
topBarRegion,
Qt.rect(0, height - dockHeight, width, dockHeight)
])
} }
if (leftLauncherOpen) {
regions.push(leftLauncherRegion)
}
if (layoutMenuEnabled && layoutMenuOpen) {
regions.push(layoutMenuRegion)
}
MobileShell.ShellUtil.setInputRegions(convergenceChrome, regions)
}
function launchStorageId(storageId) {
if (!storageId || String(storageId).length === 0) {
return
}
var normalizedId = String(storageId)
if (!normalizedId.endsWith(".desktop")) {
normalizedId += ".desktop"
}
MobileShell.AppLaunch.launchOrActivateApp(normalizedId)
}
function refreshLeftLauncherVisibility() {
if (!leftLauncherEnabled) {
leftLauncherCloseTimer.stop()
leftEdgeHovered = false
leftLauncherHovered = false
leftLauncherOpen = false
inputRegionTimer.restart()
return
}
if (leftEdgeHovered || leftLauncherHovered) {
leftLauncherCloseTimer.stop()
leftLauncherOpen = true
} else {
leftLauncherCloseTimer.restart()
}
inputRegionTimer.restart()
}
function refreshLayoutMenuVisibility() {
if (!layoutMenuEnabled) {
layoutMenuCloseTimer.stop()
rightEdgeHovered = false
layoutMenuHovered = false
layoutMenuOpen = false
inputRegionTimer.restart()
return
}
if (rightEdgeHovered || layoutMenuHovered) {
layoutMenuCloseTimer.stop()
layoutMenuOpen = true
} else {
layoutMenuCloseTimer.restart()
}
inputRegionTimer.restart()
} }
onActiveChanged: { onActiveChanged: {
@ -466,8 +353,6 @@ ContainmentItem {
} }
inputRegionTimer.restart() inputRegionTimer.restart()
} }
onLeftLauncherEnabledChanged: refreshLeftLauncherVisibility()
onLayoutMenuEnabledChanged: refreshLayoutMenuVisibility()
// Narrow the input region to a strip at the screen edge when hidden // Narrow the input region to a strip at the screen edge when hidden
// so that app controls near the bottom edge are not accidentally // so that app controls near the bottom edge are not accidentally
@ -500,34 +385,6 @@ ContainmentItem {
onTriggered: convergenceChrome.hoverRevealing = true onTriggered: convergenceChrome.hoverRevealing = true
} }
Timer {
id: leftLauncherCloseTimer
interval: Kirigami.Units.shortDuration
repeat: false
onTriggered: {
if (!convergenceChrome.leftEdgeHovered
&& !convergenceChrome.leftLauncherHovered
&& convergenceChrome.leftLauncherOpen) {
convergenceChrome.leftLauncherOpen = false
inputRegionTimer.restart()
}
}
}
Timer {
id: layoutMenuCloseTimer
interval: Kirigami.Units.shortDuration
repeat: false
onTriggered: {
if (!convergenceChrome.rightEdgeHovered
&& !convergenceChrome.layoutMenuHovered
&& convergenceChrome.layoutMenuOpen) {
convergenceChrome.layoutMenuOpen = false
inputRegionTimer.restart()
}
}
}
Behavior on dockOffset { Behavior on dockOffset {
MobileShell.MotionNumberAnimation { MobileShell.MotionNumberAnimation {
type: MobileShell.Motion.SpatialDefault type: MobileShell.Motion.SpatialDefault
@ -535,20 +392,6 @@ ContainmentItem {
} }
} }
Behavior on leftFrameBulgeDepth {
MobileShell.MotionNumberAnimation {
type: MobileShell.Motion.SpatialDefault
duration: root.shortAnimationDuration
}
}
Behavior on rightFrameBulgeDepth {
MobileShell.MotionNumberAnimation {
type: MobileShell.Motion.SpatialDefault
duration: root.shortAnimationDuration
}
}
Rectangle { Rectangle {
id: topBarSurface id: topBarSurface
anchors.top: parent.top anchors.top: parent.top
@ -568,7 +411,6 @@ ContainmentItem {
Shape { Shape {
id: workspaceFrame id: workspaceFrame
anchors.fill: parent anchors.fill: parent
preferredRendererType: Shape.CurveRenderer
ShapePath { ShapePath {
fillColor: convergenceChrome.chromeColor fillColor: convergenceChrome.chromeColor
@ -585,125 +427,24 @@ ContainmentItem {
PathMove { x: convergenceChrome.workAreaX + convergenceChrome.frameRadius; y: convergenceChrome.workAreaY } PathMove { x: convergenceChrome.workAreaX + convergenceChrome.frameRadius; y: convergenceChrome.workAreaY }
PathLine { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth - convergenceChrome.frameRadius; y: convergenceChrome.workAreaY } PathLine { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth - convergenceChrome.frameRadius; y: convergenceChrome.workAreaY }
PathArc { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth; y: convergenceChrome.workAreaY + convergenceChrome.frameRadius; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius } PathArc { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth; y: convergenceChrome.workAreaY + convergenceChrome.frameRadius; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }
PathLine { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth; y: convergenceChrome.rightFrameBulgeEdgeTopY }
PathCubic {
x: convergenceChrome.rightFrameBulgeApexX
y: convergenceChrome.rightFrameBulgeApexY
control1X: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth
control1Y: convergenceChrome.rightFrameBulgeEdgeTopY + convergenceChrome.rightFrameBulgeTangent
control2X: convergenceChrome.rightFrameBulgeApexX
control2Y: convergenceChrome.rightFrameBulgeApexY - convergenceChrome.rightFrameBulgeTangent
}
PathCubic {
x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth
y: convergenceChrome.rightFrameBulgeEdgeBottomY
control1X: convergenceChrome.rightFrameBulgeApexX
control1Y: convergenceChrome.rightFrameBulgeApexY + convergenceChrome.rightFrameBulgeTangent
control2X: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth
control2Y: convergenceChrome.rightFrameBulgeEdgeBottomY - convergenceChrome.rightFrameBulgeTangent
}
PathLine { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight - convergenceChrome.frameRadius } PathLine { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight - convergenceChrome.frameRadius }
PathArc { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth - convergenceChrome.frameRadius; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius } PathArc { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth - convergenceChrome.frameRadius; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }
PathLine { x: convergenceChrome.workAreaX + convergenceChrome.frameRadius; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight } PathLine { x: convergenceChrome.workAreaX + convergenceChrome.frameRadius; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight }
PathArc { x: convergenceChrome.workAreaX; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight - convergenceChrome.frameRadius; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius } PathArc { x: convergenceChrome.workAreaX; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight - convergenceChrome.frameRadius; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }
PathLine { x: convergenceChrome.workAreaX; y: convergenceChrome.leftFrameBulgeEdgeBottomY }
PathCubic {
x: convergenceChrome.leftFrameBulgeApexX
y: convergenceChrome.leftFrameBulgeApexY
control1X: convergenceChrome.workAreaX
control1Y: convergenceChrome.leftFrameBulgeEdgeBottomY - convergenceChrome.leftFrameBulgeTangent
control2X: convergenceChrome.leftFrameBulgeApexX
control2Y: convergenceChrome.leftFrameBulgeApexY + convergenceChrome.leftFrameBulgeTangent
}
PathCubic {
x: convergenceChrome.workAreaX
y: convergenceChrome.leftFrameBulgeEdgeTopY
control1X: convergenceChrome.leftFrameBulgeApexX
control1Y: convergenceChrome.leftFrameBulgeApexY - convergenceChrome.leftFrameBulgeTangent
control2X: convergenceChrome.workAreaX
control2Y: convergenceChrome.leftFrameBulgeEdgeTopY + convergenceChrome.leftFrameBulgeTangent
}
PathLine { x: convergenceChrome.workAreaX; y: convergenceChrome.workAreaY + convergenceChrome.frameRadius } PathLine { x: convergenceChrome.workAreaX; y: convergenceChrome.workAreaY + convergenceChrome.frameRadius }
PathArc { x: convergenceChrome.workAreaX + convergenceChrome.frameRadius; y: convergenceChrome.workAreaY; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius } PathArc { x: convergenceChrome.workAreaX + convergenceChrome.frameRadius; y: convergenceChrome.workAreaY; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }
} }
} }
Shape { Rectangle {
id: workspaceFrameBorder
anchors.fill: parent
preferredRendererType: Shape.CurveRenderer
ShapePath {
fillColor: "transparent"
strokeColor: convergenceChrome.edgeColor
strokeWidth: 0.85
joinStyle: ShapePath.RoundJoin
startX: convergenceChrome.workAreaX + convergenceChrome.frameRadius
startY: convergenceChrome.workAreaY
PathLine { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth - convergenceChrome.frameRadius; y: convergenceChrome.workAreaY }
PathQuad {
x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth
y: convergenceChrome.workAreaY + convergenceChrome.frameRadius
controlX: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth
controlY: convergenceChrome.workAreaY
}
PathLine { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth; y: convergenceChrome.rightFrameBulgeEdgeTopY }
PathCubic {
x: convergenceChrome.rightFrameBulgeApexX
y: convergenceChrome.rightFrameBulgeApexY
control1X: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth
control1Y: convergenceChrome.rightFrameBulgeEdgeTopY + convergenceChrome.rightFrameBulgeTangent
control2X: convergenceChrome.rightFrameBulgeApexX
control2Y: convergenceChrome.rightFrameBulgeApexY - convergenceChrome.rightFrameBulgeTangent
}
PathCubic {
x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth
y: convergenceChrome.rightFrameBulgeEdgeBottomY
control1X: convergenceChrome.rightFrameBulgeApexX
control1Y: convergenceChrome.rightFrameBulgeApexY + convergenceChrome.rightFrameBulgeTangent
control2X: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth
control2Y: convergenceChrome.rightFrameBulgeEdgeBottomY - convergenceChrome.rightFrameBulgeTangent
}
PathLine { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight - convergenceChrome.frameRadius }
PathQuad {
x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth - convergenceChrome.frameRadius
y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight
controlX: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth
controlY: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight
}
PathLine { x: convergenceChrome.workAreaX + convergenceChrome.frameRadius; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight }
PathQuad {
x: convergenceChrome.workAreaX x: convergenceChrome.workAreaX
y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight - convergenceChrome.frameRadius
controlX: convergenceChrome.workAreaX
controlY: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight
}
PathLine { x: convergenceChrome.workAreaX; y: convergenceChrome.leftFrameBulgeEdgeBottomY }
PathCubic {
x: convergenceChrome.leftFrameBulgeApexX
y: convergenceChrome.leftFrameBulgeApexY
control1X: convergenceChrome.workAreaX
control1Y: convergenceChrome.leftFrameBulgeEdgeBottomY - convergenceChrome.leftFrameBulgeTangent
control2X: convergenceChrome.leftFrameBulgeApexX
control2Y: convergenceChrome.leftFrameBulgeApexY + convergenceChrome.leftFrameBulgeTangent
}
PathCubic {
x: convergenceChrome.workAreaX
y: convergenceChrome.leftFrameBulgeEdgeTopY
control1X: convergenceChrome.leftFrameBulgeApexX
control1Y: convergenceChrome.leftFrameBulgeApexY - convergenceChrome.leftFrameBulgeTangent
control2X: convergenceChrome.workAreaX
control2Y: convergenceChrome.leftFrameBulgeEdgeTopY + convergenceChrome.leftFrameBulgeTangent
}
PathLine { x: convergenceChrome.workAreaX; y: convergenceChrome.workAreaY + convergenceChrome.frameRadius }
PathQuad {
x: convergenceChrome.workAreaX + convergenceChrome.frameRadius
y: convergenceChrome.workAreaY y: convergenceChrome.workAreaY
controlX: convergenceChrome.workAreaX width: convergenceChrome.workAreaWidth
controlY: convergenceChrome.workAreaY height: convergenceChrome.workAreaHeight
} radius: convergenceChrome.frameRadius
} color: "transparent"
border.width: 1
border.color: convergenceChrome.edgeColor
} }
Rectangle { Rectangle {
@ -747,307 +488,6 @@ ContainmentItem {
Kirigami.Theme.colorSet: Kirigami.Theme.Window Kirigami.Theme.colorSet: Kirigami.Theme.Window
} }
} }
Item {
id: leftEdgeStrip
anchors.left: parent.left
anchors.top: topBarSurface.bottom
anchors.bottom: dockSurface.top
width: convergenceChrome.leftEdgeHotzoneWidth
MouseArea {
id: leftEdgeHoverArea
anchors.fill: parent
acceptedButtons: Qt.NoButton
enabled: convergenceChrome.leftLauncherEnabled
hoverEnabled: enabled
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onContainsMouseChanged: {
convergenceChrome.leftEdgeHovered = containsMouse
convergenceChrome.refreshLeftLauncherVisibility()
}
}
}
Item {
id: rightEdgeStrip
anchors.right: parent.right
anchors.top: topBarSurface.bottom
anchors.bottom: dockSurface.top
width: convergenceChrome.layoutMenuEnabled ? convergenceChrome.rightEdgeHotzoneWidth : 0
MouseArea {
id: rightEdgeHoverArea
anchors.fill: parent
acceptedButtons: Qt.NoButton
enabled: convergenceChrome.layoutMenuEnabled
hoverEnabled: enabled
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onContainsMouseChanged: {
convergenceChrome.rightEdgeHovered = containsMouse
convergenceChrome.refreshLayoutMenuVisibility()
}
}
}
DynamicTilingLayoutMenu {
id: rightLayoutMenu
width: convergenceChrome.layoutMenuWidth
height: preferredHeight
x: convergenceChrome.width - width
y: convergenceChrome.height - convergenceChrome.dockHeight - height
visible: convergenceChrome.layoutMenuOpen
opacity: convergenceChrome.layoutMenuOpen ? 1 : 0
maxHeight: convergenceChrome.workAreaHeight * 0.5
windowCount: convergenceChrome.layoutMenuWindowCount
currentMode: ShellSettings.Settings.dynamicTilingLayoutMode
surfaceColor: convergenceChrome.chromeColor
animationDuration: root.shortAnimationDuration
HoverHandler {
enabled: convergenceChrome.layoutMenuOpen
onHoveredChanged: {
convergenceChrome.layoutMenuHovered = hovered
convergenceChrome.refreshLayoutMenuVisibility()
}
}
transform: Translate {
y: convergenceChrome.layoutMenuOpen ? 0 : Kirigami.Units.gridUnit
x: convergenceChrome.layoutMenuOpen ? 0 : rightLayoutMenu.width - convergenceChrome.rightEdgeHotzoneWidth
}
onLayoutModeRequested: (mode) => {
if (ShellSettings.Settings.requestDynamicTilingLayoutMode !== undefined) {
ShellSettings.Settings.requestDynamicTilingLayoutMode(mode)
}
}
onDismissRequested: {
convergenceChrome.layoutMenuOpen = false
inputRegionTimer.restart()
}
}
Item {
id: leftEdgeLauncher
width: convergenceChrome.leftLauncherWidth
height: convergenceChrome.leftLauncherHeight
x: 0
y: convergenceChrome.height - convergenceChrome.dockHeight - height
visible: convergenceChrome.leftLauncherOpen
opacity: convergenceChrome.leftLauncherOpen ? 1 : 0
clip: true
transform: Translate {
y: convergenceChrome.leftLauncherOpen ? 0 : Kirigami.Units.gridUnit
x: convergenceChrome.leftLauncherOpen ? 0 : -leftEdgeLauncher.width + convergenceChrome.leftEdgeHotzoneWidth
}
Behavior on opacity {
MobileShell.MotionNumberAnimation {
type: MobileShell.Motion.EffectsFast
duration: root.shortAnimationDuration
}
}
readonly property real cornerRadius: Math.min(MobileShell.Constants.convergenceWorkspaceFrameRadius, height * 0.24)
HoverHandler {
enabled: convergenceChrome.leftLauncherOpen
onHoveredChanged: {
convergenceChrome.leftLauncherHovered = hovered
convergenceChrome.refreshLeftLauncherVisibility()
}
}
Shape {
id: leftLauncherSurface
anchors.fill: parent
ShapePath {
fillColor: convergenceChrome.chromeColor
strokeWidth: 0
startX: 0
startY: 0
PathLine { x: leftEdgeLauncher.width - leftEdgeLauncher.cornerRadius; y: 0 }
PathArc {
x: leftEdgeLauncher.width
y: leftEdgeLauncher.cornerRadius
radiusX: leftEdgeLauncher.cornerRadius
radiusY: leftEdgeLauncher.cornerRadius
}
PathLine { x: leftEdgeLauncher.width; y: leftEdgeLauncher.height }
PathLine { x: 0; y: leftEdgeLauncher.height }
PathLine { x: 0; y: 0 }
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.gridUnit * 0.65
ColumnLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing
PlasmaComponents.Label {
Layout.fillWidth: true
text: i18n("Recently Used")
font.weight: Font.Medium
elide: Text.ElideRight
}
ListView {
id: recentAppsList
Layout.fillWidth: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 6.8
clip: true
interactive: false
boundsBehavior: Flickable.StopAtBounds
model: folio.RecentApplicationsModel
delegate: MouseArea {
required property int index
required property var model
readonly property var delegateObject: model.delegate
readonly property var application: delegateObject ? delegateObject.application : null
readonly property bool validEntry: index < 5 && application !== null
width: recentAppsList.width
height: validEntry ? Kirigami.Units.gridUnit * 1.35 : 0
enabled: validEntry
hoverEnabled: validEntry
cursorShape: validEntry ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: {
if (application) {
convergenceChrome.launchStorageId(application.storageId)
}
}
Rectangle {
anchors.fill: parent
radius: Kirigami.Units.cornerRadius
color: parent.containsMouse
? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.08)
: "transparent"
}
RowLayout {
anchors.fill: parent
anchors.leftMargin: Kirigami.Units.smallSpacing
anchors.rightMargin: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
Kirigami.Icon {
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
Layout.preferredHeight: Layout.preferredWidth
source: application ? application.icon : ""
}
PlasmaComponents.Label {
Layout.fillWidth: true
text: application ? application.name : ""
elide: Text.ElideRight
maximumLineCount: 1
}
}
}
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
spacing: Kirigami.Units.smallSpacing
PlasmaComponents.Label {
Layout.fillWidth: true
text: i18n("Most Used")
font.weight: Font.Medium
elide: Text.ElideRight
}
ListView {
id: favouritesQuickList
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
interactive: false
boundsBehavior: Flickable.StopAtBounds
model: folio.MostUsedApplicationsModel
delegate: MouseArea {
required property int index
required property var model
readonly property var delegateObject: model.delegate
readonly property var application: delegateObject ? delegateObject.application : null
readonly property bool validEntry: index < 6 && application !== null
width: favouritesQuickList.width
height: validEntry ? Kirigami.Units.gridUnit * 1.35 : 0
enabled: validEntry
hoverEnabled: validEntry
cursorShape: validEntry ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: {
if (application) {
convergenceChrome.launchStorageId(application.storageId)
}
}
Rectangle {
anchors.fill: parent
radius: Kirigami.Units.cornerRadius
color: parent.containsMouse
? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.08)
: "transparent"
}
RowLayout {
anchors.fill: parent
anchors.leftMargin: Kirigami.Units.smallSpacing
anchors.rightMargin: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
Kirigami.Icon {
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
Layout.preferredHeight: Layout.preferredWidth
source: application ? application.icon : ""
}
PlasmaComponents.Label {
Layout.fillWidth: true
text: application ? application.name : ""
elide: Text.ElideRight
maximumLineCount: 1
}
}
}
}
}
}
}
} }
// App-drawer overlay renders the popup drawer above application // App-drawer overlay renders the popup drawer above application

View file

@ -51,10 +51,6 @@ Item {
value: drawer.visible value: drawer.visible
} }
TaskManager.VirtualDesktopInfo {
id: virtualDesktopInfo
}
//END API implementation //END API implementation
// Startup feedback fill animation // Startup feedback fill animation
@ -159,7 +155,6 @@ Item {
MobileShell.ActionDrawerOpenSurface { MobileShell.ActionDrawerOpenSurface {
id: swipeArea id: swipeArea
actionDrawer: drawer.actionDrawer actionDrawer: drawer.actionDrawer
virtualDesktopInfo: virtualDesktopInfo
anchors.fill: parent anchors.fill: parent
readonly property alias drawerVisible: drawer.visible readonly property alias drawerVisible: drawer.visible

View file

@ -96,7 +96,7 @@ ContainmentItem {
root.panel.thickness = root.panelHeight; root.panel.thickness = root.panelHeight;
root.panel.thickness = root.panelHeight; root.panel.thickness = root.panelHeight;
root.panel.visibilityMode = (ShellSettings.Settings.convergenceModeEnabled || ShellSettings.Settings.autoHidePanelsEnabled) ? 3 : 0; root.panel.visibilityMode = (!ShellSettings.Settings.convergenceModeEnabled && ShellSettings.Settings.autoHidePanelsEnabled) ? 3 : 0;
MobileShell.ShellUtil.setWindowLayer(root.panel, LayerShell.Window.LayerOverlay) MobileShell.ShellUtil.setWindowLayer(root.panel, LayerShell.Window.LayerOverlay)
root.updateTouchArea(); root.updateTouchArea();
} }
@ -152,8 +152,9 @@ ContainmentItem {
} }
// Invisible layer-shell surface that reserves screen space for the // Invisible layer-shell surface that reserves screen space for the
// convergence status bar and one frame inset. The panel window itself // status bar in convergence mode. The visible convergence top bar is
// stays non-reserving so this is the only top strut. // rendered by Folio's unified chrome surface; this window only shrinks
// KWin's MaximizeArea.
Window { Window {
id: topBarSpaceReserver id: topBarSpaceReserver
visible: ShellSettings.Settings.convergenceModeEnabled && !ShellSettings.Settings.gamingModeEnabled visible: ShellSettings.Settings.convergenceModeEnabled && !ShellSettings.Settings.gamingModeEnabled

View file

@ -54,14 +54,15 @@ QMap<QString, QMap<QString, QVariant>> getKwinrcSettings(KSharedConfig::Ptr m_mo
{"ElectricBorderTiling", convergenceModeEnabled} // drag to left/right edges to tile half-screen in convergence mode {"ElectricBorderTiling", convergenceModeEnabled} // drag to left/right edges to tile half-screen in convergence mode
}}, }},
{"Plugins", {"Plugins",
{{"blurEnabled", false}, // disable blur for performance reasons, we could reconsider in the future for more powerful devices {
{"blurEnabled", false}, // disable blur for performance reasons, we could reconsider in the future for more powerful devices
{"convergentwindowsEnabled", true}, // enable our convergent window plugin {"convergentwindowsEnabled", true}, // enable our convergent window plugin
{"mobiletaskswitcherEnabled", !convergenceModeEnabled}, // mobile task switcher on phone only; convergence uses standard Alt-Tab tabbox {"mobiletaskswitcherEnabled", !convergenceModeEnabled}, // mobile task switcher on phone only; convergence uses standard Alt-Tab tabbox
{"overviewEnabled", convergenceModeEnabled}, // enable KWin Overview effect in convergence mode for desktop-style task switching {"overviewEnabled", convergenceModeEnabled}, // enable KWin Overview effect in convergence mode for desktop-style task switching
{"screenedgeEnabled", convergenceModeEnabled}, // enable screen edge visual feedback in convergence mode (mouse hot corners) {"screenedgeEnabled", convergenceModeEnabled}, // enable screen edge visual feedback in convergence mode (mouse hot corners)
{"shift-tile-animationsEnabled", convergenceModeEnabled},
{"shift-tilingEnabled", convergenceModeEnabled}, {"shift-tilingEnabled", convergenceModeEnabled},
{"shift-tile-previewEnabled", convergenceModeEnabled}}}, {"shift-tile-previewEnabled", convergenceModeEnabled}
}},
{"Wayland", {"Wayland",
{ {
{"VirtualKeyboardEnabled", true} // enable vkbd {"VirtualKeyboardEnabled", true} // enable vkbd
@ -79,7 +80,7 @@ QMap<QString, QMap<QString, QVariant>> getKwinrcSettings(KSharedConfig::Ptr m_mo
// Have a separate list here because we need to trigger DBus calls to load/unload each effect/script. // Have a separate list here because we need to trigger DBus calls to load/unload each effect/script.
// Make sure that the effect/script is added to the kwinrc "Plugins" section above! // Make sure that the effect/script is added to the kwinrc "Plugins" section above!
const QList<QString> KWIN_EFFECTS = {"blur", "mobiletaskswitcher", "overview", "screenedge", "shift-tile-animations", "shift-tile-preview"}; const QList<QString> KWIN_EFFECTS = {"blur", "mobiletaskswitcher", "overview", "screenedge", "shift-tile-preview"};
const QList<QString> KWIN_SCRIPTS = {"convergentwindows", "shift-tiling"}; const QList<QString> KWIN_SCRIPTS = {"convergentwindows", "shift-tiling"};
// .config/plasma-mobile/ksmserver - immutable settings: // .config/plasma-mobile/ksmserver - immutable settings:

View file

@ -8,5 +8,4 @@ function(add_kwin_effect name source)
endfunction() endfunction()
add_kwin_effect(shift-snap-assist shift-snap-assist) add_kwin_effect(shift-snap-assist shift-snap-assist)
add_kwin_effect(shift-tile-animations shift-tile-animations)
add_kwin_effect(shift-tile-preview shift-tile-preview) add_kwin_effect(shift-tile-preview shift-tile-preview)

View file

@ -1,155 +0,0 @@
// SPDX-FileCopyrightText: 2026 Marco Allegretti
// SPDX-License-Identifier: EUPL-1.2
/*global effect, effects, animate, animationTime, cancel, Effect, QEasingCurve */
"use strict";
class ShiftTileAnimationsEffect {
constructor() {
effect.configChanged.connect(this.loadConfig.bind(this));
effect.animationEnded.connect(this.cleanupWindow.bind(this));
effects.windowAdded.connect(this.manage.bind(this));
effects.windowClosed.connect(this.cleanupWindow.bind(this));
this.loadConfig();
for (const window of effects.stackingOrder) {
this.manage(window);
}
}
loadConfig() {
this.baseDuration = effect.readConfig("Duration", 185) || 185;
this.minimumDelta = effect.readConfig("MinimumDelta", 3) || 3;
}
manage(window) {
if (!window || window.shiftTileAnimationsManaged) {
return;
}
window.shiftTileAnimationsManaged = true;
window.shiftTileUserMoveResize = false;
window.windowFrameGeometryChanged.connect(this.onWindowFrameGeometryChanged.bind(this));
window.windowStartUserMovedResized.connect(this.onWindowStartUserMovedResized.bind(this));
window.windowFinishUserMovedResized.connect(this.onWindowFinishUserMovedResized.bind(this));
}
eligibleWindow(window) {
return window
&& !effects.hasActiveFullScreenEffect
&& window.visible
&& !window.deleted
&& window.managed
&& window.normalWindow
&& !window.fullScreen
&& !window.desktopWindow
&& !window.dock
&& !window.popup
&& !window.popupWindow
&& !window.outline;
}
validGeometry(geometry) {
return geometry && geometry.width > 0 && geometry.height > 0;
}
movedEnough(oldGeometry, newGeometry) {
return Math.abs(oldGeometry.x - newGeometry.x) >= this.minimumDelta
|| Math.abs(oldGeometry.y - newGeometry.y) >= this.minimumDelta
|| Math.abs(oldGeometry.width - newGeometry.width) >= this.minimumDelta
|| Math.abs(oldGeometry.height - newGeometry.height) >= this.minimumDelta;
}
durationFor(oldGeometry, newGeometry) {
const oldCenterX = oldGeometry.x + oldGeometry.width / 2;
const oldCenterY = oldGeometry.y + oldGeometry.height / 2;
const newCenterX = newGeometry.x + newGeometry.width / 2;
const newCenterY = newGeometry.y + newGeometry.height / 2;
const distanceX = newCenterX - oldCenterX;
const distanceY = newCenterY - oldCenterY;
const distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
const resize = Math.abs(newGeometry.width - oldGeometry.width) + Math.abs(newGeometry.height - oldGeometry.height);
const travelAllowance = Math.min(80, Math.round(Math.max(distance / 12, resize / 18)));
return animationTime(Math.max(135, Math.min(260, this.baseDuration + travelAllowance)));
}
cancelWindowAnimation(window) {
if (!window || window.shiftTileMoveAnimation === undefined) {
return;
}
cancel(window.shiftTileMoveAnimation);
window.shiftTileMoveAnimation = undefined;
window.setData(Effect.WindowForceBlurRole, null);
}
cleanupWindow(window) {
if (!window) {
return;
}
window.shiftTileMoveAnimation = undefined;
window.setData(Effect.WindowForceBlurRole, null);
}
onWindowStartUserMovedResized(window) {
if (!window) {
return;
}
window.shiftTileUserMoveResize = true;
this.cancelWindowAnimation(window);
}
onWindowFinishUserMovedResized(window) {
if (window) {
window.shiftTileUserMoveResize = false;
}
}
onWindowFrameGeometryChanged(window, oldGeometry) {
if (!this.eligibleWindow(window) || window.shiftTileUserMoveResize) {
return;
}
const newGeometry = window.geometry;
if (!this.validGeometry(oldGeometry) || !this.validGeometry(newGeometry) || !this.movedEnough(oldGeometry, newGeometry)) {
return;
}
this.cancelWindowAnimation(window);
window.setData(Effect.WindowForceBlurRole, true);
window.shiftTileMoveAnimation = animate({
window: window,
duration: this.durationFor(oldGeometry, newGeometry),
keepAlive: false,
animations: [{
type: Effect.Size,
from: {
value1: oldGeometry.width,
value2: oldGeometry.height
},
to: {
value1: newGeometry.width,
value2: newGeometry.height
},
curve: QEasingCurve.OutCubic
}, {
type: Effect.Translation,
from: {
value1: oldGeometry.x - newGeometry.x - (newGeometry.width / 2 - oldGeometry.width / 2),
value2: oldGeometry.y - newGeometry.y - (newGeometry.height / 2 - oldGeometry.height / 2)
},
to: {
value1: 0,
value2: 0
},
curve: QEasingCurve.OutCubic
}]
});
}
}
new ShiftTileAnimationsEffect();

View file

@ -1,20 +0,0 @@
{
"KPackageStructure": "KWin/Effect",
"KPlugin": {
"Authors": [
{
"Email": "marcoa@example.com",
"Name": "Marco Allegretti"
}
],
"Category": "Appearance",
"Description": "Animates SHIFT convergence window layout moves.",
"EnabledByDefault": false,
"Id": "shift-tile-animations",
"License": "EUPL-1.2",
"Name": "SHIFT Tile Animations",
"Version": "1.0"
},
"X-KDE-Ordering": 62,
"X-Plasma-API": "javascript"
}

View file

@ -3,6 +3,7 @@
import QtQuick import QtQuick
import org.kde.kwin as KWinComponents import org.kde.kwin as KWinComponents
import org.kde.plasma.private.mobileshell as MobileShell
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
KWinComponents.SceneEffect { KWinComponents.SceneEffect {
@ -12,11 +13,14 @@ KWinComponents.SceneEffect {
readonly property int outerGap: 8 readonly property int outerGap: 8
readonly property int floatEscapeMargin: 32 readonly property int floatEscapeMargin: 32
readonly property int previewAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.SpatialDefault)
readonly property int previewFadeDuration: MobileShell.Motion.duration(MobileShell.Motion.EffectsFast)
property var dragConnectedWindows: ({}) property var dragConnectedWindows: ({})
property var draggingWindow: null property var draggingWindow: null
property rect dragSourceGeometry: Qt.rect(0, 0, 0, 0) property rect dragSourceGeometry: Qt.rect(0, 0, 0, 0)
property bool animatePreview: false
property bool previewVisible: false property bool previewVisible: false
property string previewMode: "" property string previewMode: ""
property string previewScreenName: "" property string previewScreenName: ""
@ -122,26 +126,14 @@ KWinComponents.SceneEffect {
} }
function showPreview(mode, geometry, screenName) { function showPreview(mode, geometry, screenName) {
if (!isActive() || !validRect(geometry)) {
hidePreview(); hidePreview();
return;
}
previewMode = mode;
previewScreenName = screenName;
previewGeometry = insetPreviewGeometry(geometry);
previewVisible = true;
KWinComponents.Workspace.showOutline(previewGeometry);
} }
function hidePreview() { function hidePreview() {
previewVisible = false; previewVisible = false;
draggingWindow = null; draggingWindow = null;
dragSourceGeometry = Qt.rect(0, 0, 0, 0); dragSourceGeometry = Qt.rect(0, 0, 0, 0);
previewMode = ""; disableEffectTimer.restart();
previewScreenName = "";
previewGeometry = Qt.rect(0, 0, 0, 0);
KWinComponents.Workspace.hideOutline();
} }
function updatePreview(window, dragGeometry) { function updatePreview(window, dragGeometry) {
@ -197,6 +189,44 @@ KWinComponents.SceneEffect {
} }
} }
function previewFillColor(mode) {
if (mode === "float") {
return Qt.rgba(1.0, 0.62, 0.24, 0.18);
}
if (mode === "restore") {
return Qt.rgba(1.0, 1.0, 1.0, 0.10);
}
return Qt.rgba(0.18, 0.72, 0.66, 0.22);
}
function previewBorderColor(mode) {
if (mode === "float") {
return Qt.rgba(1.0, 0.72, 0.36, 0.72);
}
if (mode === "restore") {
return Qt.rgba(1.0, 1.0, 1.0, 0.36);
}
return Qt.rgba(0.64, 0.90, 0.86, 0.82);
}
Timer {
id: enableAnimationTimer
interval: 1
repeat: false
onTriggered: effect.animatePreview = true
}
Timer {
id: disableEffectTimer
interval: effect.previewFadeDuration
repeat: false
onTriggered: {
if (!effect.previewVisible) {
effect.visible = false;
}
}
}
Connections { Connections {
target: KWinComponents.Workspace target: KWinComponents.Workspace
@ -237,5 +267,65 @@ KWinComponents.SceneEffect {
} }
} }
delegate: Rectangle {
id: screenDelegate
readonly property var targetScreen: KWinComponents.SceneView.screen
readonly property bool previewOnScreen: effect.previewScreenName === targetScreen.name
color: "transparent"
Rectangle {
id: previewSurface
visible: opacity > 0
x: effect.previewGeometry.x - screenDelegate.targetScreen.geometry.x
y: effect.previewGeometry.y - screenDelegate.targetScreen.geometry.y
width: effect.previewGeometry.width
height: effect.previewGeometry.height
radius: 14
opacity: effect.previewVisible && screenDelegate.previewOnScreen ? 1 : 0
color: effect.previewFillColor(effect.previewMode)
border.width: 2
border.color: effect.previewBorderColor(effect.previewMode)
Behavior on x {
enabled: effect.animatePreview
MobileShell.MotionNumberAnimation { type: MobileShell.Motion.SpatialDefault; duration: effect.previewAnimationDuration }
}
Behavior on y {
enabled: effect.animatePreview
MobileShell.MotionNumberAnimation { type: MobileShell.Motion.SpatialDefault; duration: effect.previewAnimationDuration }
}
Behavior on width {
enabled: effect.animatePreview
MobileShell.MotionNumberAnimation { type: MobileShell.Motion.SpatialDefault; duration: effect.previewAnimationDuration }
}
Behavior on height {
enabled: effect.animatePreview
MobileShell.MotionNumberAnimation { type: MobileShell.Motion.SpatialDefault; duration: effect.previewAnimationDuration }
}
Behavior on opacity {
MobileShell.MotionNumberAnimation { type: MobileShell.Motion.EffectsFast; duration: effect.previewFadeDuration }
}
Behavior on color {
MobileShell.MotionColorAnimation { type: MobileShell.Motion.EffectsFast; duration: effect.previewFadeDuration }
}
Behavior on border.color {
MobileShell.MotionColorAnimation { type: MobileShell.Motion.EffectsFast; duration: effect.previewFadeDuration }
}
Rectangle {
anchors.fill: parent
anchors.margins: 5
radius: Math.max(0, parent.radius - anchors.margins)
color: "transparent"
border.width: 1
border.color: Qt.rgba(1, 1, 1, 0.14)
opacity: effect.previewMode === "insert" ? 1 : 0.45
}
}
}
Component.onCompleted: connectExistingWindows() Component.onCompleted: connectExistingWindows()
} }

View file

@ -71,10 +71,8 @@ Item {
// Values keep callback references so KWin script reloads can disconnect them. // Values keep callback references so KWin script reloads can disconnect them.
property var dragConnectedWindows: ({}) property var dragConnectedWindows: ({})
property int lastWindowRequestSerial: -1 property int lastWindowRequestSerial: ShellSettings.Settings.dynamicTilingWindowRequestSerial
property string lastPublishedMaximizedWindowIds: "__unpublished__" property string lastPublishedMaximizedWindowIds: "__unpublished__"
property int lastLayoutRequestSerial: -1
property string lastPublishedLayoutState: "__unpublished__"
// Drag state. // Drag state.
// //
@ -506,7 +504,6 @@ Item {
const focused = Object.assign({}, lastFocusedWindowKeys); const focused = Object.assign({}, lastFocusedWindowKeys);
delete focused[outputName]; delete focused[outputName];
lastFocusedWindowKeys = focused; lastFocusedWindowKeys = focused;
publishDynamicTilingLayoutState();
} }
function clearDisplacedWindowOwnersForLayout(outputName) { function clearDisplacedWindowOwnersForLayout(outputName) {
@ -657,8 +654,6 @@ Item {
for (let i = 0; i < cleanupLayouts.length; i++) { for (let i = 0; i < cleanupLayouts.length; i++) {
cleanupEmptyLayout(cleanupLayouts[i]); cleanupEmptyLayout(cleanupLayouts[i]);
} }
publishDynamicTilingLayoutState();
} }
function makeLeaf(win) { function makeLeaf(win) {
@ -847,15 +842,6 @@ Item {
const win = KWinComponents.Workspace.activeWindow; const win = KWinComponents.Workspace.activeWindow;
const activeName = layoutKeyForWindow(win); const activeName = layoutKeyForWindow(win);
if (activeName !== "") return activeName; if (activeName !== "") return activeName;
const desktop = KWinComponents.Workspace.currentDesktop;
const screens = KWinComponents.Workspace.screens;
if (desktop && screens && screens.length > 0) {
for (let i = 0; i < screens.length; i++) {
const currentDesktopName = layoutKeyFor(screens[i].name, desktop);
if (screenLayouts[currentDesktopName]) return currentDesktopName;
}
return layoutKeyFor(screens[0].name, desktop);
}
for (const name in screenLayouts) { for (const name in screenLayouts) {
return name; return name;
} }
@ -877,21 +863,6 @@ Item {
markLayoutChanged(transaction, outputName); markLayoutChanged(transaction, outputName);
applyLayoutTransaction(transaction); applyLayoutTransaction(transaction);
} }
publishDynamicTilingLayoutState();
}
function setLayoutMode(outputName, mode) {
if (!outputName || layoutModes.indexOf(mode) < 0) return;
setLayoutModeForScreen(outputName, mode);
const windows = orderedWindowsForScreen(outputName);
if (windows.length > 0) {
setStableLayout(outputName, windows);
const transaction = createLayoutTransaction();
markLayoutChanged(transaction, outputName);
applyLayoutTransaction(transaction);
}
publishDynamicTilingLayoutState();
} }
function containsLeaf(node, key) { function containsLeaf(node, key) {
@ -1310,37 +1281,6 @@ Item {
} }
} }
function publishDynamicTilingLayoutState() {
if (!isConvergence()) {
const disabledState = "|0";
if (disabledState === lastPublishedLayoutState) return;
lastPublishedLayoutState = disabledState;
if (ShellSettings.Settings.reportDynamicTilingLayoutState !== undefined) {
ShellSettings.Settings.reportDynamicTilingLayoutState("", 0);
}
return;
}
const outputName = outputNameForActiveWindow();
const mode = outputName !== "" ? layoutModeForScreen(outputName) : "";
const windowCount = outputName !== "" ? windowCountForLayout(outputName) : 0;
const serialized = mode + "|" + windowCount;
if (serialized === lastPublishedLayoutState) return;
lastPublishedLayoutState = serialized;
if (ShellSettings.Settings.reportDynamicTilingLayoutState !== undefined) {
ShellSettings.Settings.reportDynamicTilingLayoutState(mode, windowCount);
}
}
function clearDynamicTilingLayoutState() {
lastPublishedLayoutState = "__unpublished__";
if (ShellSettings.Settings.reportDynamicTilingLayoutState !== undefined) {
ShellSettings.Settings.reportDynamicTilingLayoutState("", 0);
}
}
function maximizedLayoutNameForWindow(win) { function maximizedLayoutNameForWindow(win) {
const key = windowKey(win); const key = windowKey(win);
if (!key) return ""; if (!key) return "";
@ -1742,19 +1682,6 @@ Item {
} }
} }
function handleLayoutModeRequest() {
const serial = ShellSettings.Settings.dynamicTilingLayoutRequestSerial;
if (serial === lastLayoutRequestSerial) return;
lastLayoutRequestSerial = serial;
if (!isConvergence()) return;
const mode = ShellSettings.Settings.dynamicTilingLayoutRequestMode;
if (layoutModes.indexOf(mode) < 0) return;
setLayoutMode(outputNameForActiveWindow(), mode);
}
function promoteWindow(win) { function promoteWindow(win) {
if (!isTileable(win)) return; if (!isTileable(win)) return;
@ -1844,12 +1771,10 @@ Item {
function onActiveWindowChanged() { function onActiveWindowChanged() {
root.rememberFocusedWindow(KWinComponents.Workspace.activeWindow); root.rememberFocusedWindow(KWinComponents.Workspace.activeWindow);
root.publishDynamicTilingLayoutState();
} }
function onCurrentDesktopChanged() { function onCurrentDesktopChanged() {
root.retileCurrentDesktopLayouts(); root.retileCurrentDesktopLayouts();
root.publishDynamicTilingLayoutState();
} }
function onScreensChanged() { function onScreensChanged() {
@ -1863,7 +1788,6 @@ Item {
} }
root.scheduleRetileAll(); root.scheduleRetileAll();
root.publishDynamicTilingLayoutState();
} }
} }
@ -1877,7 +1801,6 @@ Item {
for (let i = 0; i < wins.length; i++) { for (let i = 0; i < wins.length; i++) {
adoptWindow(wins[i]); adoptWindow(wins[i]);
} }
publishDynamicTilingLayoutState();
} else { } else {
// Clear all tiles the convergentwindows script will re-maximize // Clear all tiles the convergentwindows script will re-maximize
restoreAllMaximizedLayouts(); restoreAllMaximizedLayouts();
@ -1886,7 +1809,6 @@ Item {
screenLayoutModes = {}; screenLayoutModes = {};
lastFocusedWindowKeys = {}; lastFocusedWindowKeys = {};
maximizedLayouts = {}; maximizedLayouts = {};
clearDynamicTilingLayoutState();
} }
} }
@ -1898,13 +1820,11 @@ Item {
screenLayoutModes = {}; screenLayoutModes = {};
lastFocusedWindowKeys = {}; lastFocusedWindowKeys = {};
maximizedLayouts = {}; maximizedLayouts = {};
clearDynamicTilingLayoutState();
} else if (isConvergence()) { } else if (isConvergence()) {
const wins = KWinComponents.Workspace.windows; const wins = KWinComponents.Workspace.windows;
for (let i = 0; i < wins.length; i++) { for (let i = 0; i < wins.length; i++) {
adoptWindow(wins[i]); adoptWindow(wins[i]);
} }
publishDynamicTilingLayoutState();
} }
} }
@ -1914,7 +1834,6 @@ Item {
for (let i = 0; i < wins.length; i++) { for (let i = 0; i < wins.length; i++) {
adoptWindow(wins[i]); adoptWindow(wins[i]);
} }
publishDynamicTilingLayoutState();
} else { } else {
// Tiling turned off leave windows where they are. // Tiling turned off leave windows where they are.
restoreAllMaximizedLayouts(); restoreAllMaximizedLayouts();
@ -1923,17 +1842,12 @@ Item {
screenLayoutModes = {}; screenLayoutModes = {};
lastFocusedWindowKeys = {}; lastFocusedWindowKeys = {};
maximizedLayouts = {}; maximizedLayouts = {};
clearDynamicTilingLayoutState();
} }
} }
function onDynamicTilingWindowRequestChanged() { function onDynamicTilingWindowRequestChanged() {
root.handleWindowTilingRequest(); root.handleWindowTilingRequest();
} }
function onDynamicTilingLayoutRequestChanged() {
root.handleLayoutModeRequest();
}
} }
// Drag handlers // Drag handlers
@ -2251,9 +2165,6 @@ Item {
// Component setup // Component setup
Component.onCompleted: { Component.onCompleted: {
lastWindowRequestSerial = ShellSettings.Settings.dynamicTilingWindowRequestSerial;
lastLayoutRequestSerial = ShellSettings.Settings.dynamicTilingLayoutRequestSerial;
// Connect to existing windows // Connect to existing windows
const wins = KWinComponents.Workspace.windows; const wins = KWinComponents.Workspace.windows;
for (let i = 0; i < wins.length; i++) { for (let i = 0; i < wins.length; i++) {
@ -2261,12 +2172,10 @@ Item {
} }
rememberFocusedWindow(KWinComponents.Workspace.activeWindow); rememberFocusedWindow(KWinComponents.Workspace.activeWindow);
publishDynamicTilingWindowState(); publishDynamicTilingWindowState();
publishDynamicTilingLayoutState();
} }
Component.onDestruction: { Component.onDestruction: {
disconnectDragHandlers(); disconnectDragHandlers();
clearDynamicTilingWindowState(); clearDynamicTilingWindowState();
clearDynamicTilingLayoutState();
} }
} }

View file

@ -14,10 +14,7 @@ taskpanel="$repo_root/containments/taskpanel/qml/main.qml"
folio_main="$repo_root/containments/homescreens/folio/qml/main.qml" folio_main="$repo_root/containments/homescreens/folio/qml/main.qml"
shellutil_header="$repo_root/components/mobileshell/shellutil.h" shellutil_header="$repo_root/components/mobileshell/shellutil.h"
shellutil_cpp="$repo_root/components/mobileshell/shellutil.cpp" shellutil_cpp="$repo_root/components/mobileshell/shellutil.cpp"
swipearea_header="$repo_root/components/mobileshell/components/swipearea.h"
swipearea_cpp="$repo_root/components/mobileshell/components/swipearea.cpp"
folio_home="$repo_root/containments/homescreens/folio/qml/FolioHomeScreen.qml" folio_home="$repo_root/containments/homescreens/folio/qml/FolioHomeScreen.qml"
favourites_bar="$repo_root/containments/homescreens/folio/qml/FavouritesBar.qml"
folio_backend="$repo_root/containments/homescreens/folio/homescreen.h" folio_backend="$repo_root/containments/homescreens/folio/homescreen.h"
folio_backend_cpp="$repo_root/containments/homescreens/folio/homescreen.cpp" folio_backend_cpp="$repo_root/containments/homescreens/folio/homescreen.cpp"
action_content="$repo_root/components/mobileshell/qml/actiondrawer/private/ContentContainer.qml" action_content="$repo_root/components/mobileshell/qml/actiondrawer/private/ContentContainer.qml"
@ -51,7 +48,7 @@ require_line "$constants" "readonly property real convergenceWorkspaceFrameRadiu
require_line "$panel" "readonly property real convergenceWorkspaceFrameThickness:" require_line "$panel" "readonly property real convergenceWorkspaceFrameThickness:"
require_line "$panel" "root.panel.location = PlasmaCore.Types.TopEdge" require_line "$panel" "root.panel.location = PlasmaCore.Types.TopEdge"
require_line "$panel" "root.panel.offset = 0" require_line "$panel" "root.panel.offset = 0"
require_line "$panel" "root.panel.visibilityMode = (ShellSettings.Settings.convergenceModeEnabled || ShellSettings.Settings.autoHidePanelsEnabled) ? 3 : 0" require_line "$panel" "root.panel.visibilityMode = (!ShellSettings.Settings.convergenceModeEnabled && ShellSettings.Settings.autoHidePanelsEnabled) ? 3 : 0"
require_line "$panel" "readonly property real topBarHeight: gamingMode ? 0 : MobileShell.Constants.topPanelHeight" require_line "$panel" "readonly property real topBarHeight: gamingMode ? 0 : MobileShell.Constants.topPanelHeight"
require_line "$panel" "readonly property real topBarInputHeight: topBarHeight + convergenceWorkspaceFrameThickness" require_line "$panel" "readonly property real topBarInputHeight: topBarHeight + convergenceWorkspaceFrameThickness"
require_line "$panel" "? 0" require_line "$panel" "? 0"
@ -63,34 +60,11 @@ require_line "$panel" "visible: !ShellSettings.Settings.gamingModeEnabled"
require_line "$status_panel" "&& !ShellSettings.Settings.convergenceModeEnabled" require_line "$status_panel" "&& !ShellSettings.Settings.convergenceModeEnabled"
require_line "$status_panel" "visible: !ShellSettings.Settings.convergenceModeEnabled" require_line "$status_panel" "visible: !ShellSettings.Settings.convergenceModeEnabled"
require_line "$status_panel" "TaskManager.VirtualDesktopInfo {"
require_line "$status_panel" "MobileShell.ActionDrawerOpenSurface {" require_line "$status_panel" "MobileShell.ActionDrawerOpenSurface {"
require_line "$status_panel" "virtualDesktopInfo: virtualDesktopInfo"
require_line "$action_open_surface" "property var virtualDesktopInfo: null"
require_line "$action_open_surface" "touchpadWorkspaceSwitchThreshold"
require_line "$action_open_surface" "function activateAdjacentWorkspace(direction)"
require_line "$action_open_surface" "virtualDesktopInfo.requestActivate(virtualDesktopInfo.desktopIds[targetIndex]);"
require_line "$action_open_surface" "absY >= touchpadDirectionLockThreshold && absY > absX * touchpadAxisDominance"
require_line "$action_open_surface" "workspaceScrollAvailable() && absX >= touchpadDirectionLockThreshold && absX > absY * touchpadAxisDominance"
require_line "$action_open_surface" "MouseArea {" require_line "$action_open_surface" "MouseArea {"
require_line "$action_open_surface" "acceptedButtons: Qt.NoButton" require_line "$action_open_surface" "acceptedButtons: Qt.NoButton"
require_line "$action_open_surface" "hoverEnabled: true" require_line "$action_open_surface" "hoverEnabled: true"
require_line "$action_open_surface" "scrollGestureEnabled: false"
require_line "$action_open_surface" "cursorShape: ShellSettings.Settings.convergenceModeEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor" require_line "$action_open_surface" "cursorShape: ShellSettings.Settings.convergenceModeEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor"
require_line "$swipearea_cpp" "const qreal WHEEL_STEP_PIXEL_DELTA = 48.0;"
require_line "$swipearea_cpp" "const int TOUCHPAD_SCROLL_END_TIMEOUT = 160;"
require_line "$swipearea_cpp" "const bool isTouchpad = event->deviceType() == QInputDevice::DeviceType::TouchPad;"
require_line "$swipearea_cpp" "startTouchpadScroll(event->points().first().position());"
require_line "$swipearea_cpp" "m_touchpadScrollEndTimer.start();"
require_line "$swipearea_cpp" "if (scrollDelta.isNull() && !event->angleDelta().isNull())"
require_line "$swipearea_cpp" "QWheelEvent::DefaultDeltasPerStep * WHEEL_STEP_PIXEL_DELTA"
require_line "$swipearea_header" "QTimer m_touchpadScrollEndTimer;"
require_line "$favourites_bar" "function handlePagerWheel(wheel)"
require_line "$favourites_bar" "onWheel: (wheel) => root.handlePagerWheel(wheel)"
require_line "$favourites_bar" "root.activateAdjacentDesktop(1)"
require_line "$folio_home" "onTouchpadScrollStarted: {"
require_line "$folio_home" "onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => {"
require_line "$folio_home" "if (!ShellSettings.Settings.convergenceModeEnabled) {"
require_line "$status_bar_template" "panel.location = \"top\";" require_line "$status_bar_template" "panel.location = \"top\";"
@ -105,7 +79,8 @@ require_line "$folio_main" "id: convergenceChrome"
require_line "$folio_main" "LayerShell.Window.scope: \"convergence-chrome\"" require_line "$folio_main" "LayerShell.Window.scope: \"convergence-chrome\""
require_line "$folio_main" "height: Screen.height" require_line "$folio_main" "height: Screen.height"
require_line "$folio_main" "MobileShell.StatusBar {" require_line "$folio_main" "MobileShell.StatusBar {"
require_line "$folio_main" "MobileShell.ShellUtil.setInputRegions(convergenceChrome, regions)" require_line "$folio_main" "MobileShell.ShellUtil.setInputRegions(convergenceChrome, ["
require_line "$folio_main" "readonly property real topBarHitHeight: topBarHeight + frameThickness"
require_line "$folio_main" "const topBarRegion = Qt.rect(0, 0, width, topBarHitHeight)" require_line "$folio_main" "const topBarRegion = Qt.rect(0, 0, width, topBarHitHeight)"
require_line "$folio_main" "readonly property real dockHeight: MobileShell.Constants.convergenceDockHeight" require_line "$folio_main" "readonly property real dockHeight: MobileShell.Constants.convergenceDockHeight"
require_line "$folio_main" "readonly property real revealStripHeight: MobileShell.Constants.convergenceDockRevealHeight" require_line "$folio_main" "readonly property real revealStripHeight: MobileShell.Constants.convergenceDockRevealHeight"
@ -114,29 +89,10 @@ require_line "$folio_main" "id: workspaceFrame"
require_line "$folio_main" "readonly property real frameThickness: MobileShell.Constants.convergenceWorkspaceFrameThickness" require_line "$folio_main" "readonly property real frameThickness: MobileShell.Constants.convergenceWorkspaceFrameThickness"
require_line "$folio_main" "readonly property real frameRadius:" require_line "$folio_main" "readonly property real frameRadius:"
require_line "$folio_main" "readonly property real workAreaX: frameThickness" require_line "$folio_main" "readonly property real workAreaX: frameThickness"
require_line "$folio_main" "readonly property real topBarHitHeight: topBarHeight + frameThickness"
require_line "$folio_main" "readonly property real workAreaY: topBarHitHeight" require_line "$folio_main" "readonly property real workAreaY: topBarHitHeight"
require_line "$folio_main" "readonly property real workAreaWidth: Math.max(0, width - frameThickness * 2)" require_line "$folio_main" "readonly property real workAreaWidth: Math.max(0, width - frameThickness * 2)"
require_line "$folio_main" "readonly property real workAreaHeight: Math.max(0, height - topBarHeight - dockHeight - frameThickness * 2)" require_line "$folio_main" "readonly property real workAreaHeight: Math.max(0, height - topBarHeight - dockHeight - frameThickness * 2)"
require_line "$folio_main" "fillRule: ShapePath.OddEvenFill" require_line "$folio_main" "fillRule: ShapePath.OddEvenFill"
require_line "$folio_main" "id: leftEdgeHoverArea"
require_line "$folio_main" "id: rightEdgeHoverArea"
require_line "$folio_main" "cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor"
require_line "$folio_main" "convergenceChrome.leftEdgeHovered = containsMouse"
require_line "$folio_main" "convergenceChrome.rightEdgeHovered = containsMouse"
require_line "$folio_main" "convergenceChrome.leftLauncherHovered = hovered"
require_line "$folio_main" "convergenceChrome.layoutMenuHovered = hovered"
if grep -Fq "root.panel.visibilityMode = (!ShellSettings.Settings.convergenceModeEnabled && ShellSettings.Settings.autoHidePanelsEnabled) ? 3 : 0" "$panel"; then
echo "Convergence panel hit area must not reserve extra top space" >&2
exit 1
fi
if grep -Fq "id: leftLauncherPointerTracker" "$folio_main"; then
echo "Folio convergence chrome must not cover the dock with a fullscreen hover tracker" >&2
exit 1
fi
require_line "$folio_main" "PathLine { x: convergenceChrome.width; y: convergenceChrome.height - convergenceChrome.dockHeight }" require_line "$folio_main" "PathLine { x: convergenceChrome.width; y: convergenceChrome.height - convergenceChrome.dockHeight }"
require_line "$folio_main" "PathLine { x: 0; y: convergenceChrome.height - convergenceChrome.dockHeight }" require_line "$folio_main" "PathLine { x: 0; y: convergenceChrome.height - convergenceChrome.dockHeight }"
require_line "$folio_main" "PathArc { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth - convergenceChrome.frameRadius; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }" require_line "$folio_main" "PathArc { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth - convergenceChrome.frameRadius; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }"

View file

@ -8,13 +8,10 @@ repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
effect_qml="$repo_root/kwin/effects/shift-tile-preview/contents/ui/main.qml" effect_qml="$repo_root/kwin/effects/shift-tile-preview/contents/ui/main.qml"
effect_metadata="$repo_root/kwin/effects/shift-tile-preview/metadata.json" effect_metadata="$repo_root/kwin/effects/shift-tile-preview/metadata.json"
animation_effect_js="$repo_root/kwin/effects/shift-tile-animations/contents/code/main.js"
animation_effect_metadata="$repo_root/kwin/effects/shift-tile-animations/metadata.json"
effects_cmake="$repo_root/kwin/effects/CMakeLists.txt" effects_cmake="$repo_root/kwin/effects/CMakeLists.txt"
tiling_script="$repo_root/kwin/scripts/shift-tiling/contents/ui/main.qml" tiling_script="$repo_root/kwin/scripts/shift-tiling/contents/ui/main.qml"
decoration_qml="$repo_root/kwin/decorations/org.shift.decoration/contents/ui/main.qml" decoration_qml="$repo_root/kwin/decorations/org.shift.decoration/contents/ui/main.qml"
running_apps_panel="$repo_root/containments/homescreens/folio/qml/RunningAppsPanel.qml" running_apps_panel="$repo_root/containments/homescreens/folio/qml/RunningAppsPanel.qml"
folio_main="$repo_root/containments/homescreens/folio/qml/main.qml"
env_config="$repo_root/envmanager/config.h" env_config="$repo_root/envmanager/config.h"
require_line() { require_line() {
@ -38,17 +35,12 @@ reject_line() {
} }
require_line "$effects_cmake" "add_kwin_effect(shift-tile-preview shift-tile-preview)" require_line "$effects_cmake" "add_kwin_effect(shift-tile-preview shift-tile-preview)"
require_line "$effects_cmake" "add_kwin_effect(shift-tile-animations shift-tile-animations)"
require_line "$effect_metadata" '"Id": "shift-tile-preview"' require_line "$effect_metadata" '"Id": "shift-tile-preview"'
require_line "$effect_metadata" '"X-Plasma-API": "declarativescript"' require_line "$effect_metadata" '"X-Plasma-API": "declarativescript"'
require_line "$animation_effect_metadata" '"Id": "shift-tile-animations"'
require_line "$animation_effect_metadata" '"X-Plasma-API": "javascript"'
require_line "$env_config" '{"shift-tilingEnabled", convergenceModeEnabled}' require_line "$env_config" '{"shift-tilingEnabled", convergenceModeEnabled}'
require_line "$env_config" '{"shift-tile-previewEnabled", convergenceModeEnabled}' require_line "$env_config" '{"shift-tile-previewEnabled", convergenceModeEnabled}'
require_line "$env_config" '{"shift-tile-animationsEnabled", convergenceModeEnabled}'
require_line "$env_config" '"shift-tile-preview"' require_line "$env_config" '"shift-tile-preview"'
require_line "$env_config" '"shift-tile-animations"'
require_line "$env_config" '"shift-tiling"' require_line "$env_config" '"shift-tiling"'
require_line "$effect_qml" "KWinComponents.SceneEffect" require_line "$effect_qml" "KWinComponents.SceneEffect"
@ -57,32 +49,17 @@ require_line "$effect_qml" "ShellSettings.Settings.dynamicTilingEnabled"
require_line "$effect_qml" "interactiveMoveResizeStarted.connect" require_line "$effect_qml" "interactiveMoveResizeStarted.connect"
require_line "$effect_qml" "interactiveMoveResizeStepped.connect" require_line "$effect_qml" "interactiveMoveResizeStepped.connect"
require_line "$effect_qml" "interactiveMoveResizeFinished.connect" require_line "$effect_qml" "interactiveMoveResizeFinished.connect"
require_line "$effect_qml" "Behavior on x"
require_line "$effect_qml" "Behavior on y"
require_line "$effect_qml" "Behavior on width"
require_line "$effect_qml" "Behavior on height"
require_line "$effect_qml" "showPreview(\"swap\"" require_line "$effect_qml" "showPreview(\"swap\""
require_line "$effect_qml" "showPreview(\"float\"" require_line "$effect_qml" "showPreview(\"float\""
require_line "$effect_qml" "showPreview(\"restore\"" require_line "$effect_qml" "showPreview(\"restore\""
require_line "$effect_qml" "KWinComponents.Workspace.showOutline(previewGeometry)"
require_line "$effect_qml" "KWinComponents.Workspace.hideOutline()"
reject_line "$effect_qml" "MobileShell.MotionNumberAnimation"
reject_line "$effect_qml" "MobileShell.MotionColorAnimation"
reject_line "$effect_qml" "disableEffectTimer"
require_line "$animation_effect_js" "class ShiftTileAnimationsEffect"
require_line "$animation_effect_js" "window.windowFrameGeometryChanged.connect"
require_line "$animation_effect_js" "window.windowStartUserMovedResized.connect"
require_line "$animation_effect_js" "window.windowFinishUserMovedResized.connect"
require_line "$animation_effect_js" "animationTime("
require_line "$animation_effect_js" "type: Effect.Size"
require_line "$animation_effect_js" "type: Effect.Translation"
require_line "$animation_effect_js" "curve: QEasingCurve.OutCubic"
require_line "$animation_effect_js" "window.shiftTileUserMoveResize"
require_line "$tiling_script" "readonly property int maxWindowsPerPage: 4" require_line "$tiling_script" "readonly property int maxWindowsPerPage: 4"
require_line "$tiling_script" "readonly property real stablePrimaryRatio: 0.58" require_line "$tiling_script" "readonly property real stablePrimaryRatio: 0.58"
require_line "$tiling_script" "readonly property var layoutModes: [\"master\", \"columns\", \"rows\"]" require_line "$tiling_script" "readonly property var layoutModes: [\"master\", \"columns\", \"rows\"]"
require_line "$tiling_script" "property int lastWindowRequestSerial: -1"
require_line "$tiling_script" "property int lastLayoutRequestSerial: -1"
require_line "$tiling_script" "lastWindowRequestSerial = ShellSettings.Settings.dynamicTilingWindowRequestSerial"
require_line "$tiling_script" "lastLayoutRequestSerial = ShellSettings.Settings.dynamicTilingLayoutRequestSerial"
require_line "$tiling_script" "function desktopKey(desktop)" require_line "$tiling_script" "function desktopKey(desktop)"
require_line "$tiling_script" "function desktopForWindow(win)" require_line "$tiling_script" "function desktopForWindow(win)"
require_line "$tiling_script" "function normalizeWindowDesktopScope(win)" require_line "$tiling_script" "function normalizeWindowDesktopScope(win)"
@ -149,17 +126,6 @@ require_line "$decoration_qml" "borders.bottom = normalCornerRadius;"
require_line "$decoration_qml" "PathArc { x: root.width - root.cornerRadius; y: root.height; radiusX: root.cornerRadius; radiusY: root.cornerRadius }" require_line "$decoration_qml" "PathArc { x: root.width - root.cornerRadius; y: root.height; radiusX: root.cornerRadius; radiusY: root.cornerRadius }"
require_line "$decoration_qml" "PathArc { x: 0; y: root.height - root.cornerRadius; radiusX: root.cornerRadius; radiusY: root.cornerRadius }" require_line "$decoration_qml" "PathArc { x: 0; y: root.height - root.cornerRadius; radiusX: root.cornerRadius; radiusY: root.cornerRadius }"
require_line "$folio_main" "readonly property int layoutMenuWindowCount: Math.max(0, ShellSettings.Settings.dynamicTilingLayoutWindowCount)"
require_line "$folio_main" "&& layoutMenuWindowCount >= 2"
require_line "$folio_main" "if (layoutMenuEnabled) {"
require_line "$folio_main" "regions.push(rightEdgeRegion)"
require_line "$folio_main" "if (layoutMenuEnabled && layoutMenuOpen)"
require_line "$folio_main" "rightEdgeHovered = false"
require_line "$folio_main" "layoutMenuHovered = false"
require_line "$folio_main" "id: rightEdgeHoverArea"
require_line "$folio_main" "convergenceChrome.rightEdgeHovered = containsMouse"
require_line "$folio_main" "width: convergenceChrome.layoutMenuEnabled ? convergenceChrome.rightEdgeHotzoneWidth : 0"
running_panel_group_disabled_count="$(grep -F "groupMode: TaskManager.TasksModel.GroupDisabled" "$running_apps_panel" | wc -l)" running_panel_group_disabled_count="$(grep -F "groupMode: TaskManager.TasksModel.GroupDisabled" "$running_apps_panel" | wc -l)"
if [[ "$running_panel_group_disabled_count" -ne 2 ]]; then if [[ "$running_panel_group_disabled_count" -ne 2 ]]; then
echo "Expected the Folio Running panel to disable grouping for both task models; found $running_panel_group_disabled_count" >&2 echo "Expected the Folio Running panel to disable grouping for both task models; found $running_panel_group_disabled_count" >&2
@ -189,8 +155,6 @@ reject_line "$tiling_script" "function tileInsertDirection(cursor, rect)"
reject_line "$tiling_script" "showDragOutline(\"insert\"" reject_line "$tiling_script" "showDragOutline(\"insert\""
reject_line "$tiling_script" "KWinComponents.Workspace.showOutline(dragOutlineRect)" reject_line "$tiling_script" "KWinComponents.Workspace.showOutline(dragOutlineRect)"
reject_line "$tiling_script" "KWinComponents.Workspace.hideOutline()" reject_line "$tiling_script" "KWinComponents.Workspace.hideOutline()"
reject_line "$tiling_script" "property int lastWindowRequestSerial: ShellSettings.Settings.dynamicTilingWindowRequestSerial"
reject_line "$tiling_script" "property int lastLayoutRequestSerial: ShellSettings.Settings.dynamicTilingLayoutRequestSerial"
reject_line "$effect_qml" "effect.visible = true" reject_line "$effect_qml" "effect.visible = true"
printf '%s\n' 'dynamic-tiles-motion-ok' printf '%s\n' 'dynamic-tiles-motion-ok'