mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-06-25 07:37:42 +00:00
Compare commits
No commits in common. "f09a1db84ae4c0ba419eb789f65cc55d4a94721e" and "30e3006e3fa539897ffe38b04397cae3b48db324" have entirely different histories.
f09a1db84a
...
30e3006e3f
28 changed files with 163 additions and 1913 deletions
|
|
@ -10,12 +10,9 @@
|
|||
#include <QObject>
|
||||
#include <QTabletEvent>
|
||||
#include <QTouchEvent>
|
||||
#include <QWheelEvent>
|
||||
|
||||
// how many pixels to move before it starts being registered as a swipe
|
||||
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)
|
||||
: QQuickItem{parent}
|
||||
|
|
@ -23,10 +20,6 @@ SwipeArea::SwipeArea(QQuickItem *parent)
|
|||
setAcceptTouchEvents(true);
|
||||
setAcceptedMouseButtons(Qt::LeftButton);
|
||||
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
|
||||
|
|
@ -232,71 +225,43 @@ void SwipeArea::wheelEvent(QWheelEvent *event)
|
|||
|
||||
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()) {
|
||||
case Qt::ScrollBegin:
|
||||
if (isTouchpad && !m_touchpadScrolling) {
|
||||
if (!m_touchpadScrolling) {
|
||||
event->accept();
|
||||
startTouchpadScroll(event->points().first().position());
|
||||
|
||||
m_touchpadScrolling = true;
|
||||
m_totalScrollDelta = QPointF{0, 0};
|
||||
Q_EMIT touchpadScrollStarted(event->points().first().position());
|
||||
}
|
||||
break;
|
||||
case Qt::ScrollEnd:
|
||||
if (m_touchpadScrolling) {
|
||||
endTouchpadScroll();
|
||||
event->accept();
|
||||
m_touchpadScrolling = false;
|
||||
m_totalScrollDelta = QPointF{0, 0};
|
||||
Q_EMIT touchpadScrollEnded();
|
||||
}
|
||||
return;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// HACK: if it isn't the touchpad, we never get the isBeginEvent() and isEndEvent() events
|
||||
if (!m_touchpadScrolling) {
|
||||
if (!isTouchpad || scrollDelta.isNull()) {
|
||||
return;
|
||||
}
|
||||
startTouchpadScroll(event->points().first().position());
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &point : event->points()) {
|
||||
event->addPassiveGrabber(point, this);
|
||||
}
|
||||
|
||||
m_totalScrollDelta = QPointF{m_totalScrollDelta + scrollDelta};
|
||||
Q_EMIT touchpadScrollMove(m_totalScrollDelta.x(), m_totalScrollDelta.y(), scrollDelta.x(), scrollDelta.y());
|
||||
|
||||
m_touchpadScrollEndTimer.start();
|
||||
auto pixelDelta = event->pixelDelta();
|
||||
m_totalScrollDelta = QPointF{m_totalScrollDelta + pixelDelta};
|
||||
Q_EMIT touchpadScrollMove(m_totalScrollDelta.x(), m_totalScrollDelta.y(), pixelDelta.x(), pixelDelta.y());
|
||||
|
||||
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)
|
||||
{
|
||||
m_moving = moving;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
#include <QPointerEvent>
|
||||
#include <QQmlListProperty>
|
||||
#include <QQuickItem>
|
||||
#include <QTimer>
|
||||
#include <QTouchEvent>
|
||||
|
||||
/**
|
||||
|
|
@ -85,8 +84,6 @@ private:
|
|||
void handlePressEvent(QPointerEvent *event, QPointF point);
|
||||
void handleReleaseEvent(QPointerEvent *event, QPointF point);
|
||||
void handleMoveEvent(QPointerEvent *event, QPointF point);
|
||||
void startTouchpadScroll(QPointF point);
|
||||
void endTouchpadScroll();
|
||||
|
||||
Mode m_mode = Mode::BothAxis;
|
||||
bool m_interactive = true;
|
||||
|
|
@ -113,8 +110,6 @@ private:
|
|||
|
||||
// the total amount of distance scrolled
|
||||
QPointF m_totalScrollDelta;
|
||||
|
||||
QTimer m_touchpadScrollEndTimer;
|
||||
};
|
||||
|
||||
QML_DECLARE_TYPE(SwipeArea)
|
||||
|
|
|
|||
|
|
@ -17,15 +17,6 @@ MobileShell.SwipeArea {
|
|||
mode: MobileShell.SwipeArea.VerticalOnly
|
||||
|
||||
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() {
|
||||
if (actionDrawer.intendedToBeVisible) {
|
||||
|
|
@ -63,96 +54,12 @@ MobileShell.SwipeArea {
|
|||
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
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
hoverEnabled: true
|
||||
scrollGestureEnabled: false
|
||||
cursorShape: ShellSettings.Settings.convergenceModeEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
|
||||
|
|
@ -160,9 +67,9 @@ MobileShell.SwipeArea {
|
|||
onSwipeEnded: endSwipe()
|
||||
onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => updateOffset(deltaY);
|
||||
|
||||
onTouchpadScrollStarted: (point) => startTouchpadScroll(point)
|
||||
onTouchpadScrollEnded: endTouchpadScroll()
|
||||
onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => moveTouchpadScroll(totalDeltaX, totalDeltaY, deltaX, deltaY);
|
||||
onTouchpadScrollStarted: (point) => startSwipeWithPoint(point)
|
||||
onTouchpadScrollEnded: endSwipe()
|
||||
onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => updateOffset(deltaY);
|
||||
|
||||
// In convergence mode, allow click to toggle the action drawer (mouse-friendly)
|
||||
onClicked: {
|
||||
|
|
|
|||
|
|
@ -49,8 +49,6 @@ MobileShellSettings::MobileShellSettings(QObject *parent)
|
|||
Q_EMIT dynamicTilingEnabledChanged();
|
||||
Q_EMIT dynamicTilingWindowRequestChanged();
|
||||
Q_EMIT dynamicTilingWindowStateChanged();
|
||||
Q_EMIT dynamicTilingLayoutRequestChanged();
|
||||
Q_EMIT dynamicTilingLayoutStateChanged();
|
||||
Q_EMIT snapLayoutsEnabledChanged();
|
||||
Q_EMIT allowLogoutChanged();
|
||||
}
|
||||
|
|
@ -373,69 +371,6 @@ void MobileShellSettings::reportDynamicTilingWindowState(const QStringList &maxi
|
|||
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
|
||||
{
|
||||
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
|
||||
|
|
|
|||
|
|
@ -65,11 +65,6 @@ class MobileShellSettings : public QObject
|
|||
Q_PROPERTY(int dynamicTilingWindowRequestSerial READ dynamicTilingWindowRequestSerial NOTIFY dynamicTilingWindowRequestChanged)
|
||||
Q_PROPERTY(QStringList dynamicTilingMaximizedWindowIds READ dynamicTilingMaximizedWindowIds 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.
|
||||
Q_PROPERTY(bool snapLayoutsEnabled READ snapLayoutsEnabled WRITE setSnapLayoutsEnabled NOTIFY snapLayoutsEnabledChanged)
|
||||
|
|
@ -307,13 +302,6 @@ public:
|
|||
int dynamicTilingWindowStateSerial() const;
|
||||
Q_INVOKABLE bool isDynamicTilingWindowMaximized(const QString &windowId) const;
|
||||
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.
|
||||
|
|
@ -374,8 +362,6 @@ Q_SIGNALS:
|
|||
void dynamicTilingEnabledChanged();
|
||||
void dynamicTilingWindowRequestChanged();
|
||||
void dynamicTilingWindowStateChanged();
|
||||
void dynamicTilingLayoutRequestChanged();
|
||||
void dynamicTilingLayoutStateChanged();
|
||||
void snapLayoutsEnabledChanged();
|
||||
void allowLogoutChanged();
|
||||
void lockscreenLeftButtonActionChanged();
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ plasma_add_applet(org.kde.plasma.mobile.homescreen.folio
|
|||
qml/CategoryPanel.qml
|
||||
qml/DelegateDragItem.qml
|
||||
qml/DelegateDropArea.qml
|
||||
qml/DynamicTilingLayoutMenu.qml
|
||||
qml/FavouritesBar.qml
|
||||
qml/FolderView.qml
|
||||
qml/FolderViewTitle.qml
|
||||
|
|
@ -27,7 +26,6 @@ plasma_add_applet(org.kde.plasma.mobile.homescreen.folio
|
|||
qml/config.qml
|
||||
CPP_SOURCES
|
||||
applicationlistmodel.cpp
|
||||
applicationusagemodel.cpp
|
||||
delegatetoucharea.cpp
|
||||
dragstate.cpp
|
||||
favouritesmodel.cpp
|
||||
|
|
|
|||
|
|
@ -182,17 +182,6 @@ int ApplicationListModel::rowCount(const QModelIndex &parent) const
|
|||
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.
|
||||
static QString normalizeCategory(const QString &cat)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@
|
|||
#include <QSortFilterProxyModel>
|
||||
#include <QStringList>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <KService>
|
||||
|
||||
#include "foliodelegate.h"
|
||||
|
|
@ -47,8 +45,6 @@ public:
|
|||
|
||||
void load();
|
||||
|
||||
std::shared_ptr<FolioDelegate> delegateForStorageId(const QString &storageId) const;
|
||||
|
||||
Q_INVOKABLE QStringList allCategories() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -16,7 +16,6 @@ using namespace Qt::Literals::StringLiterals;
|
|||
const QString CFG_GROUP_FOLIO = QStringLiteral("Folio");
|
||||
|
||||
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_HOMESCREEN_ROWS = QStringLiteral("homeScreenRows");
|
||||
|
|
@ -48,17 +47,6 @@ void FolioSettings::setFavorites(const QString &favoritesJson)
|
|||
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
|
||||
{
|
||||
return generalConfigGroup().readEntry(CFG_KEY_PAGES, u"{}"_s);
|
||||
|
|
|
|||
|
|
@ -53,10 +53,6 @@ public:
|
|||
QString favorites() const;
|
||||
void setFavorites(const QString &favoritesJson);
|
||||
|
||||
// JSON array
|
||||
QString applicationUsage() const;
|
||||
void setApplicationUsage(const QString &applicationUsageJson);
|
||||
|
||||
// JSON object
|
||||
QString pages() const;
|
||||
void setPages(const QString &pagesJson);
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@
|
|||
|
||||
#include "homescreen.h"
|
||||
|
||||
#include "windowlistener.h"
|
||||
|
||||
#include <virtualdesktopinfo.h>
|
||||
|
||||
#include <KWindowSystem>
|
||||
|
|
@ -60,9 +58,6 @@ HomeScreen::HomeScreen(QObject *parent, const KPluginMetaData &data, const QVari
|
|||
, m_widgetsManager{new WidgetsManager{this}}
|
||||
, m_applicationListModel{new ApplicationListModel{this}}
|
||||
, 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_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::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;
|
||||
|
|
@ -139,16 +129,6 @@ ApplicationListSearchModel *HomeScreen::applicationListSearchModel()
|
|||
return m_applicationListSearchModel;
|
||||
}
|
||||
|
||||
ApplicationUsageModel *HomeScreen::recentApplicationsModel()
|
||||
{
|
||||
return m_recentApplicationsModel;
|
||||
}
|
||||
|
||||
ApplicationUsageModel *HomeScreen::mostUsedApplicationsModel()
|
||||
{
|
||||
return m_mostUsedApplicationsModel;
|
||||
}
|
||||
|
||||
FavouritesModel *HomeScreen::favouritesModel()
|
||||
{
|
||||
return m_favouritesModel;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
#include <QVariant>
|
||||
|
||||
#include "applicationlistmodel.h"
|
||||
#include "applicationusagemodel.h"
|
||||
#include "delegatetoucharea.h"
|
||||
#include "favouritesmodel.h"
|
||||
#include "folioapplication.h"
|
||||
|
|
@ -30,8 +29,6 @@ class HomeScreenState;
|
|||
class FavouritesModel;
|
||||
class ApplicationListModel;
|
||||
class ApplicationListSearchModel;
|
||||
class ApplicationUsageModel;
|
||||
class ApplicationUsageStore;
|
||||
|
||||
class HomeScreen : public Plasma::Containment
|
||||
{
|
||||
|
|
@ -44,8 +41,6 @@ class HomeScreen : public Plasma::Containment
|
|||
Q_PROPERTY(WidgetsManager *WidgetsManager READ widgetsManager CONSTANT)
|
||||
Q_PROPERTY(ApplicationListModel *ApplicationListModel READ applicationListModel 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(PageListModel *PageListModel READ pageListModel CONSTANT)
|
||||
Q_PROPERTY(bool overviewActive READ overviewActive NOTIFY overviewActiveChanged)
|
||||
|
|
@ -68,8 +63,6 @@ public:
|
|||
WidgetsManager *widgetsManager();
|
||||
ApplicationListModel *applicationListModel();
|
||||
ApplicationListSearchModel *applicationListSearchModel();
|
||||
ApplicationUsageModel *recentApplicationsModel();
|
||||
ApplicationUsageModel *mostUsedApplicationsModel();
|
||||
FavouritesModel *favouritesModel();
|
||||
PageListModel *pageListModel();
|
||||
bool overviewActive() const;
|
||||
|
|
@ -92,9 +85,6 @@ private:
|
|||
WidgetsManager *m_widgetsManager{nullptr};
|
||||
ApplicationListModel *m_applicationListModel{nullptr};
|
||||
ApplicationListSearchModel *m_applicationListSearchModel{nullptr};
|
||||
ApplicationUsageStore *m_applicationUsageStore{nullptr};
|
||||
ApplicationUsageModel *m_recentApplicationsModel{nullptr};
|
||||
ApplicationUsageModel *m_mostUsedApplicationsModel{nullptr};
|
||||
FavouritesModel *m_favouritesModel{nullptr};
|
||||
PageListModel *m_pageListModel{nullptr};
|
||||
bool m_overviewActive{false};
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -94,8 +94,6 @@ MouseArea {
|
|||
// Virtual desktop pager (convergence mode, 2+ desktops)
|
||||
readonly property bool showPager: convergenceMode && virtualDesktopInfo.numberOfDesktops > 1
|
||||
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 pagerRightCount: showPager ? virtualDesktopInfo.numberOfDesktops - pagerLeftCount : 0
|
||||
property real desktopButtonWidth: convergenceMode ? root.height : 0
|
||||
|
|
@ -130,15 +128,6 @@ MouseArea {
|
|||
onTriggered: root.hideDockToolTip(root.activeDockToolTipItem)
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: pagerWheelEndTimer
|
||||
interval: 160
|
||||
onTriggered: {
|
||||
root.pagerWheelDelta = 0
|
||||
root.pagerWheelLocked = false
|
||||
}
|
||||
}
|
||||
|
||||
function requestDockToolTip(item) {
|
||||
activeDockToolTipItem = null
|
||||
pendingDockToolTipItem = item
|
||||
|
|
@ -203,50 +192,6 @@ MouseArea {
|
|||
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) {
|
||||
let index = root.desktopIndexForId(desktopId)
|
||||
if (index < 0) {
|
||||
|
|
@ -740,7 +685,6 @@ MouseArea {
|
|||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onWheel: (wheel) => root.handlePagerWheel(wheel)
|
||||
onClicked: (mouse) => {
|
||||
root.hideDockToolTip(leftDesktopBtn)
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
|
|
@ -850,7 +794,6 @@ MouseArea {
|
|||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onWheel: (wheel) => root.handlePagerWheel(wheel)
|
||||
onClicked: (mouse) => {
|
||||
root.hideDockToolTip(rightDesktopBtn)
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
|
|
|
|||
|
|
@ -188,21 +188,9 @@ Item {
|
|||
homeScreenState.swipeMoved(totalDeltaX, totalDeltaY, deltaX, deltaY);
|
||||
}
|
||||
|
||||
onTouchpadScrollStarted: {
|
||||
if (!ShellSettings.Settings.convergenceModeEnabled) {
|
||||
root.homeScreenState.swipeStarted(0, 0);
|
||||
}
|
||||
}
|
||||
onTouchpadScrollEnded: {
|
||||
if (!ShellSettings.Settings.convergenceModeEnabled) {
|
||||
root.homeScreenState.swipeEnded();
|
||||
}
|
||||
}
|
||||
onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => {
|
||||
if (!ShellSettings.Settings.convergenceModeEnabled) {
|
||||
root.homeScreenState.swipeMoved(totalDeltaX, totalDeltaY, deltaX, deltaY);
|
||||
}
|
||||
}
|
||||
onTouchpadScrollStarted: homeScreenState.swipeStarted(0, 0);
|
||||
onTouchpadScrollEnded: homeScreenState.swipeEnded();
|
||||
onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => homeScreenState.swipeMoved(totalDeltaX, totalDeltaY, deltaX, deltaY);
|
||||
|
||||
onPressedChanged: {
|
||||
if (pressed) {
|
||||
|
|
|
|||
|
|
@ -305,43 +305,6 @@ ContainmentItem {
|
|||
readonly property real workAreaY: topBarHitHeight
|
||||
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 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 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)
|
||||
|
|
@ -352,12 +315,6 @@ ContainmentItem {
|
|||
|
||||
// True once the hover-reveal timer fires; cleared on hover-exit.
|
||||
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
|
||||
&& windowMaximizedTracker.showingWindow && !hoverRevealing
|
||||
|
|
@ -367,87 +324,17 @@ ContainmentItem {
|
|||
|
||||
function updateInputRegion() {
|
||||
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) {
|
||||
regions.push(Qt.rect(0, height - revealStripHeight, width, revealStripHeight))
|
||||
MobileShell.ShellUtil.setInputRegions(convergenceChrome, [
|
||||
topBarRegion,
|
||||
Qt.rect(0, height - revealStripHeight, width, revealStripHeight)
|
||||
])
|
||||
} 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: {
|
||||
|
|
@ -466,8 +353,6 @@ ContainmentItem {
|
|||
}
|
||||
inputRegionTimer.restart()
|
||||
}
|
||||
onLeftLauncherEnabledChanged: refreshLeftLauncherVisibility()
|
||||
onLayoutMenuEnabledChanged: refreshLayoutMenuVisibility()
|
||||
|
||||
// Narrow the input region to a strip at the screen edge when hidden
|
||||
// so that app controls near the bottom edge are not accidentally
|
||||
|
|
@ -500,34 +385,6 @@ ContainmentItem {
|
|||
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 {
|
||||
MobileShell.MotionNumberAnimation {
|
||||
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 {
|
||||
id: topBarSurface
|
||||
anchors.top: parent.top
|
||||
|
|
@ -568,7 +411,6 @@ ContainmentItem {
|
|||
Shape {
|
||||
id: workspaceFrame
|
||||
anchors.fill: parent
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
|
||||
ShapePath {
|
||||
fillColor: convergenceChrome.chromeColor
|
||||
|
|
@ -585,125 +427,24 @@ ContainmentItem {
|
|||
PathMove { x: convergenceChrome.workAreaX + 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 }
|
||||
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 }
|
||||
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 }
|
||||
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 }
|
||||
PathArc { x: convergenceChrome.workAreaX + convergenceChrome.frameRadius; y: convergenceChrome.workAreaY; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }
|
||||
}
|
||||
}
|
||||
|
||||
Shape {
|
||||
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
|
||||
y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight - convergenceChrome.frameRadius
|
||||
controlX: convergenceChrome.workAreaX
|
||||
controlY: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight
|
||||
}
|
||||
PathLine { x: convergenceChrome.workAreaX; y: convergenceChrome.leftFrameBulgeEdgeBottomY }
|
||||
PathCubic {
|
||||
x: convergenceChrome.leftFrameBulgeApexX
|
||||
y: convergenceChrome.leftFrameBulgeApexY
|
||||
control1X: convergenceChrome.workAreaX
|
||||
control1Y: convergenceChrome.leftFrameBulgeEdgeBottomY - convergenceChrome.leftFrameBulgeTangent
|
||||
control2X: convergenceChrome.leftFrameBulgeApexX
|
||||
control2Y: convergenceChrome.leftFrameBulgeApexY + convergenceChrome.leftFrameBulgeTangent
|
||||
}
|
||||
PathCubic {
|
||||
x: convergenceChrome.workAreaX
|
||||
y: convergenceChrome.leftFrameBulgeEdgeTopY
|
||||
control1X: convergenceChrome.leftFrameBulgeApexX
|
||||
control1Y: convergenceChrome.leftFrameBulgeApexY - convergenceChrome.leftFrameBulgeTangent
|
||||
control2X: convergenceChrome.workAreaX
|
||||
control2Y: convergenceChrome.leftFrameBulgeEdgeTopY + convergenceChrome.leftFrameBulgeTangent
|
||||
}
|
||||
PathLine { x: convergenceChrome.workAreaX; y: convergenceChrome.workAreaY + convergenceChrome.frameRadius }
|
||||
PathQuad {
|
||||
x: convergenceChrome.workAreaX + convergenceChrome.frameRadius
|
||||
y: convergenceChrome.workAreaY
|
||||
controlX: convergenceChrome.workAreaX
|
||||
controlY: convergenceChrome.workAreaY
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
x: convergenceChrome.workAreaX
|
||||
y: convergenceChrome.workAreaY
|
||||
width: convergenceChrome.workAreaWidth
|
||||
height: convergenceChrome.workAreaHeight
|
||||
radius: convergenceChrome.frameRadius
|
||||
color: "transparent"
|
||||
border.width: 1
|
||||
border.color: convergenceChrome.edgeColor
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
|
@ -747,307 +488,6 @@ ContainmentItem {
|
|||
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
|
||||
|
|
|
|||
|
|
@ -51,10 +51,6 @@ Item {
|
|||
value: drawer.visible
|
||||
}
|
||||
|
||||
TaskManager.VirtualDesktopInfo {
|
||||
id: virtualDesktopInfo
|
||||
}
|
||||
|
||||
//END API implementation
|
||||
|
||||
// Startup feedback fill animation
|
||||
|
|
@ -159,7 +155,6 @@ Item {
|
|||
MobileShell.ActionDrawerOpenSurface {
|
||||
id: swipeArea
|
||||
actionDrawer: drawer.actionDrawer
|
||||
virtualDesktopInfo: virtualDesktopInfo
|
||||
anchors.fill: parent
|
||||
|
||||
readonly property alias drawerVisible: drawer.visible
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ ContainmentItem {
|
|||
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)
|
||||
root.updateTouchArea();
|
||||
}
|
||||
|
|
@ -152,8 +152,9 @@ ContainmentItem {
|
|||
}
|
||||
|
||||
// Invisible layer-shell surface that reserves screen space for the
|
||||
// convergence status bar and one frame inset. The panel window itself
|
||||
// stays non-reserving so this is the only top strut.
|
||||
// status bar in convergence mode. The visible convergence top bar is
|
||||
// rendered by Folio's unified chrome surface; this window only shrinks
|
||||
// KWin's MaximizeArea.
|
||||
Window {
|
||||
id: topBarSpaceReserver
|
||||
visible: ShellSettings.Settings.convergenceModeEnabled && !ShellSettings.Settings.gamingModeEnabled
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}},
|
||||
{"Plugins",
|
||||
{{"blurEnabled", false}, // disable blur for performance reasons, we could reconsider in the future for more powerful devices
|
||||
{"convergentwindowsEnabled", true}, // enable our convergent window plugin
|
||||
{"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
|
||||
{"screenedgeEnabled", convergenceModeEnabled}, // enable screen edge visual feedback in convergence mode (mouse hot corners)
|
||||
{"shift-tile-animationsEnabled", convergenceModeEnabled},
|
||||
{"shift-tilingEnabled", convergenceModeEnabled},
|
||||
{"shift-tile-previewEnabled", convergenceModeEnabled}}},
|
||||
{
|
||||
{"blurEnabled", false}, // disable blur for performance reasons, we could reconsider in the future for more powerful devices
|
||||
{"convergentwindowsEnabled", true}, // enable our convergent window plugin
|
||||
{"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
|
||||
{"screenedgeEnabled", convergenceModeEnabled}, // enable screen edge visual feedback in convergence mode (mouse hot corners)
|
||||
{"shift-tilingEnabled", convergenceModeEnabled},
|
||||
{"shift-tile-previewEnabled", convergenceModeEnabled}
|
||||
}},
|
||||
{"Wayland",
|
||||
{
|
||||
{"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.
|
||||
// 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"};
|
||||
|
||||
// .config/plasma-mobile/ksmserver - immutable settings:
|
||||
|
|
|
|||
|
|
@ -8,5 +8,4 @@ function(add_kwin_effect name source)
|
|||
endfunction()
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import QtQuick
|
||||
import org.kde.kwin as KWinComponents
|
||||
import org.kde.plasma.private.mobileshell as MobileShell
|
||||
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
|
||||
|
||||
KWinComponents.SceneEffect {
|
||||
|
|
@ -12,11 +13,14 @@ KWinComponents.SceneEffect {
|
|||
|
||||
readonly property int outerGap: 8
|
||||
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 draggingWindow: null
|
||||
property rect dragSourceGeometry: Qt.rect(0, 0, 0, 0)
|
||||
|
||||
property bool animatePreview: false
|
||||
property bool previewVisible: false
|
||||
property string previewMode: ""
|
||||
property string previewScreenName: ""
|
||||
|
|
@ -122,26 +126,14 @@ KWinComponents.SceneEffect {
|
|||
}
|
||||
|
||||
function showPreview(mode, geometry, screenName) {
|
||||
if (!isActive() || !validRect(geometry)) {
|
||||
hidePreview();
|
||||
return;
|
||||
}
|
||||
|
||||
previewMode = mode;
|
||||
previewScreenName = screenName;
|
||||
previewGeometry = insetPreviewGeometry(geometry);
|
||||
previewVisible = true;
|
||||
KWinComponents.Workspace.showOutline(previewGeometry);
|
||||
hidePreview();
|
||||
}
|
||||
|
||||
function hidePreview() {
|
||||
previewVisible = false;
|
||||
draggingWindow = null;
|
||||
dragSourceGeometry = Qt.rect(0, 0, 0, 0);
|
||||
previewMode = "";
|
||||
previewScreenName = "";
|
||||
previewGeometry = Qt.rect(0, 0, 0, 0);
|
||||
KWinComponents.Workspace.hideOutline();
|
||||
disableEffectTimer.restart();
|
||||
}
|
||||
|
||||
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 {
|
||||
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()
|
||||
}
|
||||
|
|
@ -71,10 +71,8 @@ Item {
|
|||
// Values keep callback references so KWin script reloads can disconnect them.
|
||||
property var dragConnectedWindows: ({})
|
||||
|
||||
property int lastWindowRequestSerial: -1
|
||||
property int lastWindowRequestSerial: ShellSettings.Settings.dynamicTilingWindowRequestSerial
|
||||
property string lastPublishedMaximizedWindowIds: "__unpublished__"
|
||||
property int lastLayoutRequestSerial: -1
|
||||
property string lastPublishedLayoutState: "__unpublished__"
|
||||
|
||||
// Drag state.
|
||||
//
|
||||
|
|
@ -506,7 +504,6 @@ Item {
|
|||
const focused = Object.assign({}, lastFocusedWindowKeys);
|
||||
delete focused[outputName];
|
||||
lastFocusedWindowKeys = focused;
|
||||
publishDynamicTilingLayoutState();
|
||||
}
|
||||
|
||||
function clearDisplacedWindowOwnersForLayout(outputName) {
|
||||
|
|
@ -657,8 +654,6 @@ Item {
|
|||
for (let i = 0; i < cleanupLayouts.length; i++) {
|
||||
cleanupEmptyLayout(cleanupLayouts[i]);
|
||||
}
|
||||
|
||||
publishDynamicTilingLayoutState();
|
||||
}
|
||||
|
||||
function makeLeaf(win) {
|
||||
|
|
@ -847,15 +842,6 @@ Item {
|
|||
const win = KWinComponents.Workspace.activeWindow;
|
||||
const activeName = layoutKeyForWindow(win);
|
||||
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) {
|
||||
return name;
|
||||
}
|
||||
|
|
@ -877,21 +863,6 @@ Item {
|
|||
markLayoutChanged(transaction, outputName);
|
||||
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) {
|
||||
|
|
@ -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) {
|
||||
const key = windowKey(win);
|
||||
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) {
|
||||
if (!isTileable(win)) return;
|
||||
|
||||
|
|
@ -1844,12 +1771,10 @@ Item {
|
|||
|
||||
function onActiveWindowChanged() {
|
||||
root.rememberFocusedWindow(KWinComponents.Workspace.activeWindow);
|
||||
root.publishDynamicTilingLayoutState();
|
||||
}
|
||||
|
||||
function onCurrentDesktopChanged() {
|
||||
root.retileCurrentDesktopLayouts();
|
||||
root.publishDynamicTilingLayoutState();
|
||||
}
|
||||
|
||||
function onScreensChanged() {
|
||||
|
|
@ -1863,7 +1788,6 @@ Item {
|
|||
}
|
||||
|
||||
root.scheduleRetileAll();
|
||||
root.publishDynamicTilingLayoutState();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1877,7 +1801,6 @@ Item {
|
|||
for (let i = 0; i < wins.length; i++) {
|
||||
adoptWindow(wins[i]);
|
||||
}
|
||||
publishDynamicTilingLayoutState();
|
||||
} else {
|
||||
// Clear all tiles — the convergentwindows script will re-maximize
|
||||
restoreAllMaximizedLayouts();
|
||||
|
|
@ -1886,7 +1809,6 @@ Item {
|
|||
screenLayoutModes = {};
|
||||
lastFocusedWindowKeys = {};
|
||||
maximizedLayouts = {};
|
||||
clearDynamicTilingLayoutState();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1898,13 +1820,11 @@ Item {
|
|||
screenLayoutModes = {};
|
||||
lastFocusedWindowKeys = {};
|
||||
maximizedLayouts = {};
|
||||
clearDynamicTilingLayoutState();
|
||||
} else if (isConvergence()) {
|
||||
const wins = KWinComponents.Workspace.windows;
|
||||
for (let i = 0; i < wins.length; i++) {
|
||||
adoptWindow(wins[i]);
|
||||
}
|
||||
publishDynamicTilingLayoutState();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1914,7 +1834,6 @@ Item {
|
|||
for (let i = 0; i < wins.length; i++) {
|
||||
adoptWindow(wins[i]);
|
||||
}
|
||||
publishDynamicTilingLayoutState();
|
||||
} else {
|
||||
// Tiling turned off — leave windows where they are.
|
||||
restoreAllMaximizedLayouts();
|
||||
|
|
@ -1923,17 +1842,12 @@ Item {
|
|||
screenLayoutModes = {};
|
||||
lastFocusedWindowKeys = {};
|
||||
maximizedLayouts = {};
|
||||
clearDynamicTilingLayoutState();
|
||||
}
|
||||
}
|
||||
|
||||
function onDynamicTilingWindowRequestChanged() {
|
||||
root.handleWindowTilingRequest();
|
||||
}
|
||||
|
||||
function onDynamicTilingLayoutRequestChanged() {
|
||||
root.handleLayoutModeRequest();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Drag handlers ─────────────────────────────────────────────────────
|
||||
|
|
@ -2251,9 +2165,6 @@ Item {
|
|||
// ── Component setup ───────────────────────────────────────────────────
|
||||
|
||||
Component.onCompleted: {
|
||||
lastWindowRequestSerial = ShellSettings.Settings.dynamicTilingWindowRequestSerial;
|
||||
lastLayoutRequestSerial = ShellSettings.Settings.dynamicTilingLayoutRequestSerial;
|
||||
|
||||
// Connect to existing windows
|
||||
const wins = KWinComponents.Workspace.windows;
|
||||
for (let i = 0; i < wins.length; i++) {
|
||||
|
|
@ -2261,12 +2172,10 @@ Item {
|
|||
}
|
||||
rememberFocusedWindow(KWinComponents.Workspace.activeWindow);
|
||||
publishDynamicTilingWindowState();
|
||||
publishDynamicTilingLayoutState();
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
disconnectDragHandlers();
|
||||
clearDynamicTilingWindowState();
|
||||
clearDynamicTilingLayoutState();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,10 +14,7 @@ taskpanel="$repo_root/containments/taskpanel/qml/main.qml"
|
|||
folio_main="$repo_root/containments/homescreens/folio/qml/main.qml"
|
||||
shellutil_header="$repo_root/components/mobileshell/shellutil.h"
|
||||
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"
|
||||
favourites_bar="$repo_root/containments/homescreens/folio/qml/FavouritesBar.qml"
|
||||
folio_backend="$repo_root/containments/homescreens/folio/homescreen.h"
|
||||
folio_backend_cpp="$repo_root/containments/homescreens/folio/homescreen.cpp"
|
||||
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" "root.panel.location = PlasmaCore.Types.TopEdge"
|
||||
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 topBarInputHeight: topBarHeight + convergenceWorkspaceFrameThickness"
|
||||
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" "visible: !ShellSettings.Settings.convergenceModeEnabled"
|
||||
require_line "$status_panel" "TaskManager.VirtualDesktopInfo {"
|
||||
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" "acceptedButtons: Qt.NoButton"
|
||||
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 "$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\";"
|
||||
|
||||
|
|
@ -105,7 +79,8 @@ require_line "$folio_main" "id: convergenceChrome"
|
|||
require_line "$folio_main" "LayerShell.Window.scope: \"convergence-chrome\""
|
||||
require_line "$folio_main" "height: Screen.height"
|
||||
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" "readonly property real dockHeight: MobileShell.Constants.convergenceDockHeight"
|
||||
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 frameRadius:"
|
||||
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 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" "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: 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 }"
|
||||
|
|
|
|||
|
|
@ -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_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"
|
||||
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"
|
||||
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"
|
||||
|
||||
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-animations shift-tile-animations)"
|
||||
require_line "$effect_metadata" '"Id": "shift-tile-preview"'
|
||||
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-tile-previewEnabled", convergenceModeEnabled}'
|
||||
require_line "$env_config" '{"shift-tile-animationsEnabled", convergenceModeEnabled}'
|
||||
require_line "$env_config" '"shift-tile-preview"'
|
||||
require_line "$env_config" '"shift-tile-animations"'
|
||||
require_line "$env_config" '"shift-tiling"'
|
||||
|
||||
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" "interactiveMoveResizeStepped.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(\"float\""
|
||||
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 real stablePrimaryRatio: 0.58"
|
||||
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 desktopForWindow(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: 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)"
|
||||
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
|
||||
|
|
@ -189,8 +155,6 @@ reject_line "$tiling_script" "function tileInsertDirection(cursor, rect)"
|
|||
reject_line "$tiling_script" "showDragOutline(\"insert\""
|
||||
reject_line "$tiling_script" "KWinComponents.Workspace.showOutline(dragOutlineRect)"
|
||||
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"
|
||||
|
||||
printf '%s\n' 'dynamic-tiles-motion-ok'
|
||||
Loading…
Reference in a new issue