mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-06-24 23:27:43 +00:00
Compare commits
6 commits
30e3006e3f
...
f09a1db84a
| Author | SHA1 | Date | |
|---|---|---|---|
| f09a1db84a | |||
| 495f76900f | |||
| 97abf33597 | |||
| 1702027f7e | |||
| d0262d0dd8 | |||
| 1a70b24c06 |
28 changed files with 1915 additions and 165 deletions
|
|
@ -10,9 +10,12 @@
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QTabletEvent>
|
#include <QTabletEvent>
|
||||||
#include <QTouchEvent>
|
#include <QTouchEvent>
|
||||||
|
#include <QWheelEvent>
|
||||||
|
|
||||||
// how many pixels to move before it starts being registered as a swipe
|
// how many pixels to move before it starts being registered as a swipe
|
||||||
const int SWIPE_REGISTER_THRESHOLD = 10;
|
const int SWIPE_REGISTER_THRESHOLD = 10;
|
||||||
|
const qreal WHEEL_STEP_PIXEL_DELTA = 48.0;
|
||||||
|
const int TOUCHPAD_SCROLL_END_TIMEOUT = 160;
|
||||||
|
|
||||||
SwipeArea::SwipeArea(QQuickItem *parent)
|
SwipeArea::SwipeArea(QQuickItem *parent)
|
||||||
: QQuickItem{parent}
|
: QQuickItem{parent}
|
||||||
|
|
@ -20,6 +23,10 @@ SwipeArea::SwipeArea(QQuickItem *parent)
|
||||||
setAcceptTouchEvents(true);
|
setAcceptTouchEvents(true);
|
||||||
setAcceptedMouseButtons(Qt::LeftButton);
|
setAcceptedMouseButtons(Qt::LeftButton);
|
||||||
setFiltersChildMouseEvents(true);
|
setFiltersChildMouseEvents(true);
|
||||||
|
|
||||||
|
m_touchpadScrollEndTimer.setSingleShot(true);
|
||||||
|
m_touchpadScrollEndTimer.setInterval(TOUCHPAD_SCROLL_END_TIMEOUT);
|
||||||
|
connect(&m_touchpadScrollEndTimer, &QTimer::timeout, this, &SwipeArea::endTouchpadScroll);
|
||||||
}
|
}
|
||||||
|
|
||||||
SwipeArea::Mode SwipeArea::mode() const
|
SwipeArea::Mode SwipeArea::mode() const
|
||||||
|
|
@ -225,43 +232,71 @@ void SwipeArea::wheelEvent(QWheelEvent *event)
|
||||||
|
|
||||||
event->setAccepted(false);
|
event->setAccepted(false);
|
||||||
|
|
||||||
|
const bool isTouchpad = event->deviceType() == QInputDevice::DeviceType::TouchPad;
|
||||||
|
QPointF scrollDelta = event->pixelDelta();
|
||||||
|
if (scrollDelta.isNull() && !event->angleDelta().isNull()) {
|
||||||
|
scrollDelta = QPointF(event->angleDelta()) / QWheelEvent::DefaultDeltasPerStep * WHEEL_STEP_PIXEL_DELTA;
|
||||||
|
}
|
||||||
|
|
||||||
switch (event->phase()) {
|
switch (event->phase()) {
|
||||||
case Qt::ScrollBegin:
|
case Qt::ScrollBegin:
|
||||||
if (!m_touchpadScrolling) {
|
if (isTouchpad && !m_touchpadScrolling) {
|
||||||
event->accept();
|
event->accept();
|
||||||
|
startTouchpadScroll(event->points().first().position());
|
||||||
m_touchpadScrolling = true;
|
|
||||||
m_totalScrollDelta = QPointF{0, 0};
|
|
||||||
Q_EMIT touchpadScrollStarted(event->points().first().position());
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Qt::ScrollEnd:
|
case Qt::ScrollEnd:
|
||||||
if (m_touchpadScrolling) {
|
if (m_touchpadScrolling) {
|
||||||
m_touchpadScrolling = false;
|
endTouchpadScroll();
|
||||||
m_totalScrollDelta = QPointF{0, 0};
|
event->accept();
|
||||||
Q_EMIT touchpadScrollEnded();
|
|
||||||
}
|
}
|
||||||
break;
|
return;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// HACK: if it isn't the touchpad, we never get the isBeginEvent() and isEndEvent() events
|
|
||||||
if (!m_touchpadScrolling) {
|
if (!m_touchpadScrolling) {
|
||||||
|
if (!isTouchpad || scrollDelta.isNull()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
startTouchpadScroll(event->points().first().position());
|
||||||
|
}
|
||||||
|
|
||||||
for (auto &point : event->points()) {
|
for (auto &point : event->points()) {
|
||||||
event->addPassiveGrabber(point, this);
|
event->addPassiveGrabber(point, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pixelDelta = event->pixelDelta();
|
m_totalScrollDelta = QPointF{m_totalScrollDelta + scrollDelta};
|
||||||
m_totalScrollDelta = QPointF{m_totalScrollDelta + pixelDelta};
|
Q_EMIT touchpadScrollMove(m_totalScrollDelta.x(), m_totalScrollDelta.y(), scrollDelta.x(), scrollDelta.y());
|
||||||
Q_EMIT touchpadScrollMove(m_totalScrollDelta.x(), m_totalScrollDelta.y(), pixelDelta.x(), pixelDelta.y());
|
|
||||||
|
m_touchpadScrollEndTimer.start();
|
||||||
|
|
||||||
event->accept();
|
event->accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SwipeArea::startTouchpadScroll(QPointF point)
|
||||||
|
{
|
||||||
|
if (m_touchpadScrolling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_touchpadScrolling = true;
|
||||||
|
m_totalScrollDelta = QPointF{0, 0};
|
||||||
|
Q_EMIT touchpadScrollStarted(point);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SwipeArea::endTouchpadScroll()
|
||||||
|
{
|
||||||
|
if (!m_touchpadScrolling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_touchpadScrollEndTimer.stop();
|
||||||
|
m_touchpadScrolling = false;
|
||||||
|
m_totalScrollDelta = QPointF{0, 0};
|
||||||
|
Q_EMIT touchpadScrollEnded();
|
||||||
|
}
|
||||||
|
|
||||||
void SwipeArea::setMoving(bool moving)
|
void SwipeArea::setMoving(bool moving)
|
||||||
{
|
{
|
||||||
m_moving = moving;
|
m_moving = moving;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#include <QPointerEvent>
|
#include <QPointerEvent>
|
||||||
#include <QQmlListProperty>
|
#include <QQmlListProperty>
|
||||||
#include <QQuickItem>
|
#include <QQuickItem>
|
||||||
|
#include <QTimer>
|
||||||
#include <QTouchEvent>
|
#include <QTouchEvent>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -84,6 +85,8 @@ private:
|
||||||
void handlePressEvent(QPointerEvent *event, QPointF point);
|
void handlePressEvent(QPointerEvent *event, QPointF point);
|
||||||
void handleReleaseEvent(QPointerEvent *event, QPointF point);
|
void handleReleaseEvent(QPointerEvent *event, QPointF point);
|
||||||
void handleMoveEvent(QPointerEvent *event, QPointF point);
|
void handleMoveEvent(QPointerEvent *event, QPointF point);
|
||||||
|
void startTouchpadScroll(QPointF point);
|
||||||
|
void endTouchpadScroll();
|
||||||
|
|
||||||
Mode m_mode = Mode::BothAxis;
|
Mode m_mode = Mode::BothAxis;
|
||||||
bool m_interactive = true;
|
bool m_interactive = true;
|
||||||
|
|
@ -110,6 +113,8 @@ private:
|
||||||
|
|
||||||
// the total amount of distance scrolled
|
// the total amount of distance scrolled
|
||||||
QPointF m_totalScrollDelta;
|
QPointF m_totalScrollDelta;
|
||||||
|
|
||||||
|
QTimer m_touchpadScrollEndTimer;
|
||||||
};
|
};
|
||||||
|
|
||||||
QML_DECLARE_TYPE(SwipeArea)
|
QML_DECLARE_TYPE(SwipeArea)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,15 @@ MobileShell.SwipeArea {
|
||||||
mode: MobileShell.SwipeArea.VerticalOnly
|
mode: MobileShell.SwipeArea.VerticalOnly
|
||||||
|
|
||||||
required property ActionDrawer actionDrawer
|
required property ActionDrawer actionDrawer
|
||||||
|
property var virtualDesktopInfo: null
|
||||||
|
|
||||||
|
readonly property real touchpadDirectionLockThreshold: 12
|
||||||
|
readonly property real touchpadWorkspaceSwitchThreshold: 80
|
||||||
|
readonly property real touchpadAxisDominance: 1.25
|
||||||
|
|
||||||
|
property point touchpadStartPoint: Qt.point(0, 0)
|
||||||
|
property string touchpadGestureMode: ""
|
||||||
|
property bool touchpadWorkspaceSwitched: false
|
||||||
|
|
||||||
function startSwipe() {
|
function startSwipe() {
|
||||||
if (actionDrawer.intendedToBeVisible) {
|
if (actionDrawer.intendedToBeVisible) {
|
||||||
|
|
@ -54,12 +63,96 @@ MobileShell.SwipeArea {
|
||||||
actionDrawer.offset += offsetY;
|
actionDrawer.offset += offsetY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function workspaceScrollAvailable() {
|
||||||
|
return ShellSettings.Settings.convergenceModeEnabled
|
||||||
|
&& virtualDesktopInfo !== null
|
||||||
|
&& virtualDesktopInfo.numberOfDesktops > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function desktopIndexForId(desktopId) {
|
||||||
|
if (virtualDesktopInfo === null || !virtualDesktopInfo.desktopIds) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < virtualDesktopInfo.desktopIds.length; ++i) {
|
||||||
|
if (String(virtualDesktopInfo.desktopIds[i]) === String(desktopId)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function activateAdjacentWorkspace(direction) {
|
||||||
|
if (!workspaceScrollAvailable()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentIndex = desktopIndexForId(virtualDesktopInfo.currentDesktop);
|
||||||
|
if (currentIndex < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetIndex = Math.max(0, Math.min(virtualDesktopInfo.desktopIds.length - 1, currentIndex + direction));
|
||||||
|
if (targetIndex !== currentIndex) {
|
||||||
|
virtualDesktopInfo.requestActivate(virtualDesktopInfo.desktopIds[targetIndex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startTouchpadScroll(point) {
|
||||||
|
touchpadStartPoint = point;
|
||||||
|
touchpadGestureMode = "";
|
||||||
|
touchpadWorkspaceSwitched = false;
|
||||||
|
|
||||||
|
if (!ShellSettings.Settings.convergenceModeEnabled) {
|
||||||
|
touchpadGestureMode = "drawer";
|
||||||
|
startSwipeWithPoint(point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveTouchpadScroll(totalDeltaX, totalDeltaY, deltaX, deltaY) {
|
||||||
|
if (touchpadGestureMode === "") {
|
||||||
|
const absX = Math.abs(totalDeltaX);
|
||||||
|
const absY = Math.abs(totalDeltaY);
|
||||||
|
|
||||||
|
if (absY >= touchpadDirectionLockThreshold && absY > absX * touchpadAxisDominance) {
|
||||||
|
touchpadGestureMode = "drawer";
|
||||||
|
startSwipeWithPoint(touchpadStartPoint);
|
||||||
|
updateOffset(totalDeltaY);
|
||||||
|
return;
|
||||||
|
} else if (workspaceScrollAvailable() && absX >= touchpadDirectionLockThreshold && absX > absY * touchpadAxisDominance) {
|
||||||
|
touchpadGestureMode = "workspace";
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (touchpadGestureMode === "drawer") {
|
||||||
|
updateOffset(deltaY);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!touchpadWorkspaceSwitched && Math.abs(totalDeltaX) >= touchpadWorkspaceSwitchThreshold) {
|
||||||
|
touchpadWorkspaceSwitched = true;
|
||||||
|
activateAdjacentWorkspace(totalDeltaX < 0 ? 1 : -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function endTouchpadScroll() {
|
||||||
|
if (touchpadGestureMode === "drawer") {
|
||||||
|
endSwipe();
|
||||||
|
}
|
||||||
|
|
||||||
|
touchpadGestureMode = "";
|
||||||
|
touchpadWorkspaceSwitched = false;
|
||||||
|
}
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.NoButton
|
acceptedButtons: Qt.NoButton
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
|
scrollGestureEnabled: false
|
||||||
cursorShape: ShellSettings.Settings.convergenceModeEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
cursorShape: ShellSettings.Settings.convergenceModeEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,9 +160,9 @@ MobileShell.SwipeArea {
|
||||||
onSwipeEnded: endSwipe()
|
onSwipeEnded: endSwipe()
|
||||||
onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => updateOffset(deltaY);
|
onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => updateOffset(deltaY);
|
||||||
|
|
||||||
onTouchpadScrollStarted: (point) => startSwipeWithPoint(point)
|
onTouchpadScrollStarted: (point) => startTouchpadScroll(point)
|
||||||
onTouchpadScrollEnded: endSwipe()
|
onTouchpadScrollEnded: endTouchpadScroll()
|
||||||
onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => updateOffset(deltaY);
|
onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => moveTouchpadScroll(totalDeltaX, totalDeltaY, deltaX, deltaY);
|
||||||
|
|
||||||
// In convergence mode, allow click to toggle the action drawer (mouse-friendly)
|
// In convergence mode, allow click to toggle the action drawer (mouse-friendly)
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,8 @@ MobileShellSettings::MobileShellSettings(QObject *parent)
|
||||||
Q_EMIT dynamicTilingEnabledChanged();
|
Q_EMIT dynamicTilingEnabledChanged();
|
||||||
Q_EMIT dynamicTilingWindowRequestChanged();
|
Q_EMIT dynamicTilingWindowRequestChanged();
|
||||||
Q_EMIT dynamicTilingWindowStateChanged();
|
Q_EMIT dynamicTilingWindowStateChanged();
|
||||||
|
Q_EMIT dynamicTilingLayoutRequestChanged();
|
||||||
|
Q_EMIT dynamicTilingLayoutStateChanged();
|
||||||
Q_EMIT snapLayoutsEnabledChanged();
|
Q_EMIT snapLayoutsEnabledChanged();
|
||||||
Q_EMIT allowLogoutChanged();
|
Q_EMIT allowLogoutChanged();
|
||||||
}
|
}
|
||||||
|
|
@ -371,6 +373,69 @@ void MobileShellSettings::reportDynamicTilingWindowState(const QStringList &maxi
|
||||||
Q_EMIT dynamicTilingWindowStateChanged();
|
Q_EMIT dynamicTilingWindowStateChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString MobileShellSettings::dynamicTilingLayoutRequestMode() const
|
||||||
|
{
|
||||||
|
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
|
||||||
|
return group.readEntry("dynamicTilingLayoutRequestMode", QString{});
|
||||||
|
}
|
||||||
|
|
||||||
|
int MobileShellSettings::dynamicTilingLayoutRequestSerial() const
|
||||||
|
{
|
||||||
|
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
|
||||||
|
return group.readEntry("dynamicTilingLayoutRequestSerial", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MobileShellSettings::requestDynamicTilingLayoutMode(const QString &mode)
|
||||||
|
{
|
||||||
|
if (mode.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
|
||||||
|
const int serial = group.readEntry("dynamicTilingLayoutRequestSerial", 0) + 1;
|
||||||
|
group.writeEntry("dynamicTilingLayoutRequestMode", mode, KConfigGroup::Notify);
|
||||||
|
group.writeEntry("dynamicTilingLayoutRequestSerial", serial, KConfigGroup::Notify);
|
||||||
|
m_config->sync();
|
||||||
|
|
||||||
|
Q_EMIT dynamicTilingLayoutRequestChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString MobileShellSettings::dynamicTilingLayoutMode() const
|
||||||
|
{
|
||||||
|
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
|
||||||
|
return group.readEntry("dynamicTilingLayoutMode", QString{});
|
||||||
|
}
|
||||||
|
|
||||||
|
int MobileShellSettings::dynamicTilingLayoutWindowCount() const
|
||||||
|
{
|
||||||
|
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
|
||||||
|
return group.readEntry("dynamicTilingLayoutWindowCount", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int MobileShellSettings::dynamicTilingLayoutStateSerial() const
|
||||||
|
{
|
||||||
|
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
|
||||||
|
return group.readEntry("dynamicTilingLayoutStateSerial", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MobileShellSettings::reportDynamicTilingLayoutState(const QString &mode, int windowCount)
|
||||||
|
{
|
||||||
|
const int normalizedWindowCount = windowCount < 0 ? 0 : windowCount;
|
||||||
|
|
||||||
|
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
|
||||||
|
if (group.readEntry("dynamicTilingLayoutMode", QString{}) == mode && group.readEntry("dynamicTilingLayoutWindowCount", 0) == normalizedWindowCount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int serial = group.readEntry("dynamicTilingLayoutStateSerial", 0) + 1;
|
||||||
|
group.writeEntry("dynamicTilingLayoutMode", mode, KConfigGroup::Notify);
|
||||||
|
group.writeEntry("dynamicTilingLayoutWindowCount", normalizedWindowCount, KConfigGroup::Notify);
|
||||||
|
group.writeEntry("dynamicTilingLayoutStateSerial", serial, KConfigGroup::Notify);
|
||||||
|
m_config->sync();
|
||||||
|
|
||||||
|
Q_EMIT dynamicTilingLayoutStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
bool MobileShellSettings::snapLayoutsEnabled() const
|
bool MobileShellSettings::snapLayoutsEnabled() const
|
||||||
{
|
{
|
||||||
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
|
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,11 @@ class MobileShellSettings : public QObject
|
||||||
Q_PROPERTY(int dynamicTilingWindowRequestSerial READ dynamicTilingWindowRequestSerial NOTIFY dynamicTilingWindowRequestChanged)
|
Q_PROPERTY(int dynamicTilingWindowRequestSerial READ dynamicTilingWindowRequestSerial NOTIFY dynamicTilingWindowRequestChanged)
|
||||||
Q_PROPERTY(QStringList dynamicTilingMaximizedWindowIds READ dynamicTilingMaximizedWindowIds NOTIFY dynamicTilingWindowStateChanged)
|
Q_PROPERTY(QStringList dynamicTilingMaximizedWindowIds READ dynamicTilingMaximizedWindowIds NOTIFY dynamicTilingWindowStateChanged)
|
||||||
Q_PROPERTY(int dynamicTilingWindowStateSerial READ dynamicTilingWindowStateSerial NOTIFY dynamicTilingWindowStateChanged)
|
Q_PROPERTY(int dynamicTilingWindowStateSerial READ dynamicTilingWindowStateSerial NOTIFY dynamicTilingWindowStateChanged)
|
||||||
|
Q_PROPERTY(QString dynamicTilingLayoutRequestMode READ dynamicTilingLayoutRequestMode NOTIFY dynamicTilingLayoutRequestChanged)
|
||||||
|
Q_PROPERTY(int dynamicTilingLayoutRequestSerial READ dynamicTilingLayoutRequestSerial NOTIFY dynamicTilingLayoutRequestChanged)
|
||||||
|
Q_PROPERTY(QString dynamicTilingLayoutMode READ dynamicTilingLayoutMode NOTIFY dynamicTilingLayoutStateChanged)
|
||||||
|
Q_PROPERTY(int dynamicTilingLayoutWindowCount READ dynamicTilingLayoutWindowCount NOTIFY dynamicTilingLayoutStateChanged)
|
||||||
|
Q_PROPERTY(int dynamicTilingLayoutStateSerial READ dynamicTilingLayoutStateSerial NOTIFY dynamicTilingLayoutStateChanged)
|
||||||
|
|
||||||
// Snap layout picker — only meaningful in convergence mode when dynamic tiling is off.
|
// Snap layout picker — only meaningful in convergence mode when dynamic tiling is off.
|
||||||
Q_PROPERTY(bool snapLayoutsEnabled READ snapLayoutsEnabled WRITE setSnapLayoutsEnabled NOTIFY snapLayoutsEnabledChanged)
|
Q_PROPERTY(bool snapLayoutsEnabled READ snapLayoutsEnabled WRITE setSnapLayoutsEnabled NOTIFY snapLayoutsEnabledChanged)
|
||||||
|
|
@ -302,6 +307,13 @@ public:
|
||||||
int dynamicTilingWindowStateSerial() const;
|
int dynamicTilingWindowStateSerial() const;
|
||||||
Q_INVOKABLE bool isDynamicTilingWindowMaximized(const QString &windowId) const;
|
Q_INVOKABLE bool isDynamicTilingWindowMaximized(const QString &windowId) const;
|
||||||
Q_INVOKABLE void reportDynamicTilingWindowState(const QStringList &maximizedWindowIds);
|
Q_INVOKABLE void reportDynamicTilingWindowState(const QStringList &maximizedWindowIds);
|
||||||
|
QString dynamicTilingLayoutRequestMode() const;
|
||||||
|
int dynamicTilingLayoutRequestSerial() const;
|
||||||
|
Q_INVOKABLE void requestDynamicTilingLayoutMode(const QString &mode);
|
||||||
|
QString dynamicTilingLayoutMode() const;
|
||||||
|
int dynamicTilingLayoutWindowCount() const;
|
||||||
|
int dynamicTilingLayoutStateSerial() const;
|
||||||
|
Q_INVOKABLE void reportDynamicTilingLayoutState(const QString &mode, int windowCount);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the SHIFT snap layout picker is enabled.
|
* Whether the SHIFT snap layout picker is enabled.
|
||||||
|
|
@ -362,6 +374,8 @@ Q_SIGNALS:
|
||||||
void dynamicTilingEnabledChanged();
|
void dynamicTilingEnabledChanged();
|
||||||
void dynamicTilingWindowRequestChanged();
|
void dynamicTilingWindowRequestChanged();
|
||||||
void dynamicTilingWindowStateChanged();
|
void dynamicTilingWindowStateChanged();
|
||||||
|
void dynamicTilingLayoutRequestChanged();
|
||||||
|
void dynamicTilingLayoutStateChanged();
|
||||||
void snapLayoutsEnabledChanged();
|
void snapLayoutsEnabledChanged();
|
||||||
void allowLogoutChanged();
|
void allowLogoutChanged();
|
||||||
void lockscreenLeftButtonActionChanged();
|
void lockscreenLeftButtonActionChanged();
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ plasma_add_applet(org.kde.plasma.mobile.homescreen.folio
|
||||||
qml/CategoryPanel.qml
|
qml/CategoryPanel.qml
|
||||||
qml/DelegateDragItem.qml
|
qml/DelegateDragItem.qml
|
||||||
qml/DelegateDropArea.qml
|
qml/DelegateDropArea.qml
|
||||||
|
qml/DynamicTilingLayoutMenu.qml
|
||||||
qml/FavouritesBar.qml
|
qml/FavouritesBar.qml
|
||||||
qml/FolderView.qml
|
qml/FolderView.qml
|
||||||
qml/FolderViewTitle.qml
|
qml/FolderViewTitle.qml
|
||||||
|
|
@ -26,6 +27,7 @@ plasma_add_applet(org.kde.plasma.mobile.homescreen.folio
|
||||||
qml/config.qml
|
qml/config.qml
|
||||||
CPP_SOURCES
|
CPP_SOURCES
|
||||||
applicationlistmodel.cpp
|
applicationlistmodel.cpp
|
||||||
|
applicationusagemodel.cpp
|
||||||
delegatetoucharea.cpp
|
delegatetoucharea.cpp
|
||||||
dragstate.cpp
|
dragstate.cpp
|
||||||
favouritesmodel.cpp
|
favouritesmodel.cpp
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,17 @@ int ApplicationListModel::rowCount(const QModelIndex &parent) const
|
||||||
return m_delegates.count();
|
return m_delegates.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<FolioDelegate> ApplicationListModel::delegateForStorageId(const QString &storageId) const
|
||||||
|
{
|
||||||
|
for (const auto &delegate : m_delegates) {
|
||||||
|
if (delegate && delegate->application() && delegate->application()->storageId() == storageId) {
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// Sub-categories merged into their canonical parent, mirroring Kickoff's grouping.
|
// Sub-categories merged into their canonical parent, mirroring Kickoff's grouping.
|
||||||
static QString normalizeCategory(const QString &cat)
|
static QString normalizeCategory(const QString &cat)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@
|
||||||
#include <QSortFilterProxyModel>
|
#include <QSortFilterProxyModel>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <KService>
|
#include <KService>
|
||||||
|
|
||||||
#include "foliodelegate.h"
|
#include "foliodelegate.h"
|
||||||
|
|
@ -45,6 +47,8 @@ public:
|
||||||
|
|
||||||
void load();
|
void load();
|
||||||
|
|
||||||
|
std::shared_ptr<FolioDelegate> delegateForStorageId(const QString &storageId) const;
|
||||||
|
|
||||||
Q_INVOKABLE QStringList allCategories() const;
|
Q_INVOKABLE QStringList allCategories() const;
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
|
|
|
||||||
208
containments/homescreens/folio/applicationusagemodel.cpp
Normal file
208
containments/homescreens/folio/applicationusagemodel.cpp
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
// SPDX-FileCopyrightText: 2026 Marco Allegretti
|
||||||
|
// SPDX-License-Identifier: EUPL-1.2
|
||||||
|
|
||||||
|
#include "applicationusagemodel.h"
|
||||||
|
|
||||||
|
#include "applicationlistmodel.h"
|
||||||
|
#include "foliodelegate.h"
|
||||||
|
#include "foliosettings.h"
|
||||||
|
#include "homescreen.h"
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
constexpr qsizetype s_maxStoredEntries = 24;
|
||||||
|
|
||||||
|
QString normalizedStorageId(const QString &storageId)
|
||||||
|
{
|
||||||
|
if (storageId.isEmpty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storageId.endsWith(QLatin1String(".desktop"))) {
|
||||||
|
return storageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return storageId + QStringLiteral(".desktop");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationUsageStore::ApplicationUsageStore(HomeScreen *parent)
|
||||||
|
: QObject{parent}
|
||||||
|
, m_homeScreen{parent}
|
||||||
|
{
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<ApplicationUsageEntry> ApplicationUsageStore::entries() const
|
||||||
|
{
|
||||||
|
return m_entries.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationUsageStore::recordUsage(const QString &storageId)
|
||||||
|
{
|
||||||
|
const QString normalizedId = normalizedStorageId(storageId);
|
||||||
|
if (normalizedId.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &entry = m_entries[normalizedId];
|
||||||
|
entry.storageId = normalizedId;
|
||||||
|
entry.launchCount = std::max(0, entry.launchCount) + 1;
|
||||||
|
entry.lastUsed = QDateTime::currentDateTimeUtc();
|
||||||
|
|
||||||
|
save();
|
||||||
|
Q_EMIT usageChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationUsageStore::load()
|
||||||
|
{
|
||||||
|
m_entries.clear();
|
||||||
|
|
||||||
|
if (!m_homeScreen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QJsonDocument doc = QJsonDocument::fromJson(m_homeScreen->folioSettings()->applicationUsage().toUtf8());
|
||||||
|
const QJsonArray usageArray = doc.array();
|
||||||
|
|
||||||
|
for (const QJsonValue &value : usageArray) {
|
||||||
|
const QJsonObject object = value.toObject();
|
||||||
|
const QString storageId = normalizedStorageId(object.value(QStringLiteral("storageId")).toString());
|
||||||
|
if (storageId.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationUsageEntry entry;
|
||||||
|
entry.storageId = storageId;
|
||||||
|
entry.launchCount = object.value(QStringLiteral("launchCount")).toInt();
|
||||||
|
entry.lastUsed = QDateTime::fromString(object.value(QStringLiteral("lastUsed")).toString(), Qt::ISODateWithMs);
|
||||||
|
|
||||||
|
if (entry.launchCount <= 0 || !entry.lastUsed.isValid()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_entries.insert(storageId, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationUsageStore::save()
|
||||||
|
{
|
||||||
|
if (!m_homeScreen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<ApplicationUsageEntry> entries = m_entries.values();
|
||||||
|
std::sort(entries.begin(), entries.end(), [](const ApplicationUsageEntry &left, const ApplicationUsageEntry &right) {
|
||||||
|
return left.lastUsed > right.lastUsed;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (entries.size() > s_maxStoredEntries) {
|
||||||
|
entries.resize(s_maxStoredEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray usageArray;
|
||||||
|
for (const ApplicationUsageEntry &entry : std::as_const(entries)) {
|
||||||
|
QJsonObject object;
|
||||||
|
object.insert(QStringLiteral("storageId"), entry.storageId);
|
||||||
|
object.insert(QStringLiteral("launchCount"), entry.launchCount);
|
||||||
|
object.insert(QStringLiteral("lastUsed"), entry.lastUsed.toString(Qt::ISODateWithMs));
|
||||||
|
usageArray.append(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_homeScreen->folioSettings()->setApplicationUsage(QString::fromUtf8(QJsonDocument(usageArray).toJson(QJsonDocument::Compact)));
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationUsageModel::ApplicationUsageModel(HomeScreen *homeScreen, ApplicationUsageStore *store, Mode mode)
|
||||||
|
: QAbstractListModel{homeScreen}
|
||||||
|
, m_homeScreen{homeScreen}
|
||||||
|
, m_store{store}
|
||||||
|
, m_mode{mode}
|
||||||
|
{
|
||||||
|
if (m_store) {
|
||||||
|
connect(m_store, &ApplicationUsageStore::usageChanged, this, &ApplicationUsageModel::rebuild);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_homeScreen && m_homeScreen->applicationListModel()) {
|
||||||
|
auto *applicationListModel = m_homeScreen->applicationListModel();
|
||||||
|
connect(applicationListModel, &QAbstractItemModel::rowsInserted, this, &ApplicationUsageModel::rebuild);
|
||||||
|
connect(applicationListModel, &QAbstractItemModel::rowsRemoved, this, &ApplicationUsageModel::rebuild);
|
||||||
|
connect(applicationListModel, &QAbstractItemModel::modelReset, this, &ApplicationUsageModel::rebuild);
|
||||||
|
}
|
||||||
|
|
||||||
|
rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ApplicationUsageModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
if (parent.isValid()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_entries.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant ApplicationUsageModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid() || index.row() < 0 || index.row() >= m_entries.size()) {
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
const ApplicationUsageEntry &entry = m_entries.at(index.row());
|
||||||
|
switch (role) {
|
||||||
|
case Qt::DisplayRole:
|
||||||
|
case DelegateRole:
|
||||||
|
return QVariant::fromValue(m_delegates.at(index.row()).get());
|
||||||
|
case LaunchCountRole:
|
||||||
|
return entry.launchCount;
|
||||||
|
case LastUsedRole:
|
||||||
|
return entry.lastUsed;
|
||||||
|
default:
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> ApplicationUsageModel::roleNames() const
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
{DelegateRole, QByteArrayLiteral("delegate")},
|
||||||
|
{LaunchCountRole, QByteArrayLiteral("launchCount")},
|
||||||
|
{LastUsedRole, QByteArrayLiteral("lastUsed")},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationUsageModel::rebuild()
|
||||||
|
{
|
||||||
|
QList<ApplicationUsageEntry> entries = m_store ? m_store->entries() : QList<ApplicationUsageEntry>{};
|
||||||
|
|
||||||
|
std::sort(entries.begin(), entries.end(), [this](const ApplicationUsageEntry &left, const ApplicationUsageEntry &right) {
|
||||||
|
if (m_mode == MostUsed && left.launchCount != right.launchCount) {
|
||||||
|
return left.launchCount > right.launchCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return left.lastUsed > right.lastUsed;
|
||||||
|
});
|
||||||
|
|
||||||
|
QList<ApplicationUsageEntry> nextEntries;
|
||||||
|
QList<std::shared_ptr<FolioDelegate>> nextDelegates;
|
||||||
|
|
||||||
|
if (m_homeScreen && m_homeScreen->applicationListModel()) {
|
||||||
|
for (const ApplicationUsageEntry &entry : std::as_const(entries)) {
|
||||||
|
auto delegate = m_homeScreen->applicationListModel()->delegateForStorageId(entry.storageId);
|
||||||
|
if (!delegate || !delegate->application()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextEntries.append(entry);
|
||||||
|
nextDelegates.append(delegate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beginResetModel();
|
||||||
|
m_entries = nextEntries;
|
||||||
|
m_delegates = nextDelegates;
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
80
containments/homescreens/folio/applicationusagemodel.h
Normal file
80
containments/homescreens/folio/applicationusagemodel.h
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
// SPDX-FileCopyrightText: 2026 Marco Allegretti
|
||||||
|
// SPDX-License-Identifier: EUPL-1.2
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QList>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include <QtQmlIntegration/qqmlintegration.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class FolioDelegate;
|
||||||
|
class HomeScreen;
|
||||||
|
|
||||||
|
struct ApplicationUsageEntry {
|
||||||
|
QString storageId;
|
||||||
|
int launchCount = 0;
|
||||||
|
QDateTime lastUsed;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ApplicationUsageStore : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ApplicationUsageStore(HomeScreen *parent = nullptr);
|
||||||
|
|
||||||
|
QList<ApplicationUsageEntry> entries() const;
|
||||||
|
void recordUsage(const QString &storageId);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void usageChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void load();
|
||||||
|
void save();
|
||||||
|
|
||||||
|
HomeScreen *m_homeScreen{nullptr};
|
||||||
|
QHash<QString, ApplicationUsageEntry> m_entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ApplicationUsageModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
QML_UNCREATABLE("")
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Mode {
|
||||||
|
RecentUsage,
|
||||||
|
MostUsed,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Roles {
|
||||||
|
DelegateRole = Qt::UserRole + 1,
|
||||||
|
LaunchCountRole,
|
||||||
|
LastUsedRole,
|
||||||
|
};
|
||||||
|
|
||||||
|
ApplicationUsageModel(HomeScreen *homeScreen = nullptr, ApplicationUsageStore *store = nullptr, Mode mode = RecentUsage);
|
||||||
|
|
||||||
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
void rebuild();
|
||||||
|
|
||||||
|
private:
|
||||||
|
HomeScreen *m_homeScreen{nullptr};
|
||||||
|
ApplicationUsageStore *m_store{nullptr};
|
||||||
|
Mode m_mode{RecentUsage};
|
||||||
|
QList<ApplicationUsageEntry> m_entries;
|
||||||
|
QList<std::shared_ptr<FolioDelegate>> m_delegates;
|
||||||
|
};
|
||||||
|
|
@ -16,6 +16,7 @@ using namespace Qt::Literals::StringLiterals;
|
||||||
const QString CFG_GROUP_FOLIO = QStringLiteral("Folio");
|
const QString CFG_GROUP_FOLIO = QStringLiteral("Folio");
|
||||||
|
|
||||||
const QString CFG_KEY_FAVORITES = QStringLiteral("favorites");
|
const QString CFG_KEY_FAVORITES = QStringLiteral("favorites");
|
||||||
|
const QString CFG_KEY_APPLICATION_USAGE = QStringLiteral("applicationUsage");
|
||||||
const QString CFG_KEY_PAGES = QStringLiteral("pages");
|
const QString CFG_KEY_PAGES = QStringLiteral("pages");
|
||||||
|
|
||||||
const QString CFG_KEY_HOMESCREEN_ROWS = QStringLiteral("homeScreenRows");
|
const QString CFG_KEY_HOMESCREEN_ROWS = QStringLiteral("homeScreenRows");
|
||||||
|
|
@ -47,6 +48,17 @@ void FolioSettings::setFavorites(const QString &favoritesJson)
|
||||||
Q_EMIT m_homeScreen->configNeedsSaving();
|
Q_EMIT m_homeScreen->configNeedsSaving();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString FolioSettings::applicationUsage() const
|
||||||
|
{
|
||||||
|
return generalConfigGroup().readEntry(CFG_KEY_APPLICATION_USAGE, QStringLiteral("[]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FolioSettings::setApplicationUsage(const QString &applicationUsageJson)
|
||||||
|
{
|
||||||
|
generalConfigGroup().writeEntry(CFG_KEY_APPLICATION_USAGE, applicationUsageJson);
|
||||||
|
Q_EMIT m_homeScreen->configNeedsSaving();
|
||||||
|
}
|
||||||
|
|
||||||
QString FolioSettings::pages() const
|
QString FolioSettings::pages() const
|
||||||
{
|
{
|
||||||
return generalConfigGroup().readEntry(CFG_KEY_PAGES, u"{}"_s);
|
return generalConfigGroup().readEntry(CFG_KEY_PAGES, u"{}"_s);
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,10 @@ public:
|
||||||
QString favorites() const;
|
QString favorites() const;
|
||||||
void setFavorites(const QString &favoritesJson);
|
void setFavorites(const QString &favoritesJson);
|
||||||
|
|
||||||
|
// JSON array
|
||||||
|
QString applicationUsage() const;
|
||||||
|
void setApplicationUsage(const QString &applicationUsageJson);
|
||||||
|
|
||||||
// JSON object
|
// JSON object
|
||||||
QString pages() const;
|
QString pages() const;
|
||||||
void setPages(const QString &pagesJson);
|
void setPages(const QString &pagesJson);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
#include "homescreen.h"
|
#include "homescreen.h"
|
||||||
|
|
||||||
|
#include "windowlistener.h"
|
||||||
|
|
||||||
#include <virtualdesktopinfo.h>
|
#include <virtualdesktopinfo.h>
|
||||||
|
|
||||||
#include <KWindowSystem>
|
#include <KWindowSystem>
|
||||||
|
|
@ -58,6 +60,9 @@ HomeScreen::HomeScreen(QObject *parent, const KPluginMetaData &data, const QVari
|
||||||
, m_widgetsManager{new WidgetsManager{this}}
|
, m_widgetsManager{new WidgetsManager{this}}
|
||||||
, m_applicationListModel{new ApplicationListModel{this}}
|
, m_applicationListModel{new ApplicationListModel{this}}
|
||||||
, m_applicationListSearchModel{new ApplicationListSearchModel{this, m_applicationListModel}}
|
, m_applicationListSearchModel{new ApplicationListSearchModel{this, m_applicationListModel}}
|
||||||
|
, m_applicationUsageStore{new ApplicationUsageStore{this}}
|
||||||
|
, m_recentApplicationsModel{new ApplicationUsageModel{this, m_applicationUsageStore, ApplicationUsageModel::RecentUsage}}
|
||||||
|
, m_mostUsedApplicationsModel{new ApplicationUsageModel{this, m_applicationUsageStore, ApplicationUsageModel::MostUsed}}
|
||||||
, m_favouritesModel{new FavouritesModel{this}}
|
, m_favouritesModel{new FavouritesModel{this}}
|
||||||
, m_pageListModel{new PageListModel{this}}
|
, m_pageListModel{new PageListModel{this}}
|
||||||
{
|
{
|
||||||
|
|
@ -84,6 +89,11 @@ HomeScreen::HomeScreen(QObject *parent, const KPluginMetaData &data, const QVari
|
||||||
|
|
||||||
connect(this, &Plasma::Containment::appletAdded, this, &HomeScreen::onAppletAdded);
|
connect(this, &Plasma::Containment::appletAdded, this, &HomeScreen::onAppletAdded);
|
||||||
connect(this, &Plasma::Containment::appletAboutToBeRemoved, this, &HomeScreen::onAppletAboutToBeRemoved);
|
connect(this, &Plasma::Containment::appletAboutToBeRemoved, this, &HomeScreen::onAppletAboutToBeRemoved);
|
||||||
|
connect(WindowListener::instance(), &WindowListener::windowChanged, this, [this](QString storageId) {
|
||||||
|
if (!WindowListener::instance()->windowsFromStorageId(storageId).empty()) {
|
||||||
|
m_applicationUsageStore->recordUsage(storageId);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
HomeScreen::~HomeScreen() = default;
|
HomeScreen::~HomeScreen() = default;
|
||||||
|
|
@ -129,6 +139,16 @@ ApplicationListSearchModel *HomeScreen::applicationListSearchModel()
|
||||||
return m_applicationListSearchModel;
|
return m_applicationListSearchModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ApplicationUsageModel *HomeScreen::recentApplicationsModel()
|
||||||
|
{
|
||||||
|
return m_recentApplicationsModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationUsageModel *HomeScreen::mostUsedApplicationsModel()
|
||||||
|
{
|
||||||
|
return m_mostUsedApplicationsModel;
|
||||||
|
}
|
||||||
|
|
||||||
FavouritesModel *HomeScreen::favouritesModel()
|
FavouritesModel *HomeScreen::favouritesModel()
|
||||||
{
|
{
|
||||||
return m_favouritesModel;
|
return m_favouritesModel;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
|
||||||
#include "applicationlistmodel.h"
|
#include "applicationlistmodel.h"
|
||||||
|
#include "applicationusagemodel.h"
|
||||||
#include "delegatetoucharea.h"
|
#include "delegatetoucharea.h"
|
||||||
#include "favouritesmodel.h"
|
#include "favouritesmodel.h"
|
||||||
#include "folioapplication.h"
|
#include "folioapplication.h"
|
||||||
|
|
@ -29,6 +30,8 @@ class HomeScreenState;
|
||||||
class FavouritesModel;
|
class FavouritesModel;
|
||||||
class ApplicationListModel;
|
class ApplicationListModel;
|
||||||
class ApplicationListSearchModel;
|
class ApplicationListSearchModel;
|
||||||
|
class ApplicationUsageModel;
|
||||||
|
class ApplicationUsageStore;
|
||||||
|
|
||||||
class HomeScreen : public Plasma::Containment
|
class HomeScreen : public Plasma::Containment
|
||||||
{
|
{
|
||||||
|
|
@ -41,6 +44,8 @@ class HomeScreen : public Plasma::Containment
|
||||||
Q_PROPERTY(WidgetsManager *WidgetsManager READ widgetsManager CONSTANT)
|
Q_PROPERTY(WidgetsManager *WidgetsManager READ widgetsManager CONSTANT)
|
||||||
Q_PROPERTY(ApplicationListModel *ApplicationListModel READ applicationListModel CONSTANT)
|
Q_PROPERTY(ApplicationListModel *ApplicationListModel READ applicationListModel CONSTANT)
|
||||||
Q_PROPERTY(ApplicationListSearchModel *ApplicationListSearchModel READ applicationListSearchModel CONSTANT)
|
Q_PROPERTY(ApplicationListSearchModel *ApplicationListSearchModel READ applicationListSearchModel CONSTANT)
|
||||||
|
Q_PROPERTY(ApplicationUsageModel *RecentApplicationsModel READ recentApplicationsModel CONSTANT)
|
||||||
|
Q_PROPERTY(ApplicationUsageModel *MostUsedApplicationsModel READ mostUsedApplicationsModel CONSTANT)
|
||||||
Q_PROPERTY(FavouritesModel *FavouritesModel READ favouritesModel CONSTANT)
|
Q_PROPERTY(FavouritesModel *FavouritesModel READ favouritesModel CONSTANT)
|
||||||
Q_PROPERTY(PageListModel *PageListModel READ pageListModel CONSTANT)
|
Q_PROPERTY(PageListModel *PageListModel READ pageListModel CONSTANT)
|
||||||
Q_PROPERTY(bool overviewActive READ overviewActive NOTIFY overviewActiveChanged)
|
Q_PROPERTY(bool overviewActive READ overviewActive NOTIFY overviewActiveChanged)
|
||||||
|
|
@ -63,6 +68,8 @@ public:
|
||||||
WidgetsManager *widgetsManager();
|
WidgetsManager *widgetsManager();
|
||||||
ApplicationListModel *applicationListModel();
|
ApplicationListModel *applicationListModel();
|
||||||
ApplicationListSearchModel *applicationListSearchModel();
|
ApplicationListSearchModel *applicationListSearchModel();
|
||||||
|
ApplicationUsageModel *recentApplicationsModel();
|
||||||
|
ApplicationUsageModel *mostUsedApplicationsModel();
|
||||||
FavouritesModel *favouritesModel();
|
FavouritesModel *favouritesModel();
|
||||||
PageListModel *pageListModel();
|
PageListModel *pageListModel();
|
||||||
bool overviewActive() const;
|
bool overviewActive() const;
|
||||||
|
|
@ -85,6 +92,9 @@ private:
|
||||||
WidgetsManager *m_widgetsManager{nullptr};
|
WidgetsManager *m_widgetsManager{nullptr};
|
||||||
ApplicationListModel *m_applicationListModel{nullptr};
|
ApplicationListModel *m_applicationListModel{nullptr};
|
||||||
ApplicationListSearchModel *m_applicationListSearchModel{nullptr};
|
ApplicationListSearchModel *m_applicationListSearchModel{nullptr};
|
||||||
|
ApplicationUsageStore *m_applicationUsageStore{nullptr};
|
||||||
|
ApplicationUsageModel *m_recentApplicationsModel{nullptr};
|
||||||
|
ApplicationUsageModel *m_mostUsedApplicationsModel{nullptr};
|
||||||
FavouritesModel *m_favouritesModel{nullptr};
|
FavouritesModel *m_favouritesModel{nullptr};
|
||||||
PageListModel *m_pageListModel{nullptr};
|
PageListModel *m_pageListModel{nullptr};
|
||||||
bool m_overviewActive{false};
|
bool m_overviewActive{false};
|
||||||
|
|
|
||||||
298
containments/homescreens/folio/qml/DynamicTilingLayoutMenu.qml
Normal file
298
containments/homescreens/folio/qml/DynamicTilingLayoutMenu.qml
Normal file
|
|
@ -0,0 +1,298 @@
|
||||||
|
// 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,6 +94,8 @@ MouseArea {
|
||||||
// Virtual desktop pager (convergence mode, 2+ desktops)
|
// Virtual desktop pager (convergence mode, 2+ desktops)
|
||||||
readonly property bool showPager: convergenceMode && virtualDesktopInfo.numberOfDesktops > 1
|
readonly property bool showPager: convergenceMode && virtualDesktopInfo.numberOfDesktops > 1
|
||||||
property real pagerButtonWidth: showPager ? Math.min(root.height, Kirigami.Units.gridUnit * 2.5) : 0
|
property real pagerButtonWidth: showPager ? Math.min(root.height, Kirigami.Units.gridUnit * 2.5) : 0
|
||||||
|
property int pagerWheelDelta: 0
|
||||||
|
property bool pagerWheelLocked: false
|
||||||
readonly property int pagerLeftCount: showPager ? Math.ceil(virtualDesktopInfo.numberOfDesktops / 2) : 0
|
readonly property int pagerLeftCount: showPager ? Math.ceil(virtualDesktopInfo.numberOfDesktops / 2) : 0
|
||||||
readonly property int pagerRightCount: showPager ? virtualDesktopInfo.numberOfDesktops - pagerLeftCount : 0
|
readonly property int pagerRightCount: showPager ? virtualDesktopInfo.numberOfDesktops - pagerLeftCount : 0
|
||||||
property real desktopButtonWidth: convergenceMode ? root.height : 0
|
property real desktopButtonWidth: convergenceMode ? root.height : 0
|
||||||
|
|
@ -128,6 +130,15 @@ MouseArea {
|
||||||
onTriggered: root.hideDockToolTip(root.activeDockToolTipItem)
|
onTriggered: root.hideDockToolTip(root.activeDockToolTipItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: pagerWheelEndTimer
|
||||||
|
interval: 160
|
||||||
|
onTriggered: {
|
||||||
|
root.pagerWheelDelta = 0
|
||||||
|
root.pagerWheelLocked = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function requestDockToolTip(item) {
|
function requestDockToolTip(item) {
|
||||||
activeDockToolTipItem = null
|
activeDockToolTipItem = null
|
||||||
pendingDockToolTipItem = item
|
pendingDockToolTipItem = item
|
||||||
|
|
@ -192,6 +203,50 @@ MouseArea {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function activateAdjacentDesktop(direction) {
|
||||||
|
let ids = virtualDesktopInfo.desktopIds
|
||||||
|
if (!ids || ids.length <= 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentIndex = root.desktopIndexForId(virtualDesktopInfo.currentDesktop)
|
||||||
|
if (currentIndex < 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let targetIndex = Math.max(0, Math.min(ids.length - 1, currentIndex + direction))
|
||||||
|
if (targetIndex !== currentIndex) {
|
||||||
|
root.folio.activateVirtualDesktop(String(ids[targetIndex]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePagerWheel(wheel) {
|
||||||
|
if (!root.showPager) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const axisDelta = wheel.angleDelta.y || -wheel.angleDelta.x
|
||||||
|
if (axisDelta === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pagerWheelEndTimer.restart()
|
||||||
|
if (root.pagerWheelLocked) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
root.pagerWheelDelta += axisDelta * (wheel.inverted ? -1 : 1)
|
||||||
|
if (root.pagerWheelDelta >= 120) {
|
||||||
|
root.pagerWheelLocked = true
|
||||||
|
root.pagerWheelDelta = 0
|
||||||
|
root.activateAdjacentDesktop(-1)
|
||||||
|
} else if (root.pagerWheelDelta <= -120) {
|
||||||
|
root.pagerWheelLocked = true
|
||||||
|
root.pagerWheelDelta = 0
|
||||||
|
root.activateAdjacentDesktop(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function dynamicTilingMoveToDesktopAction(desktopId) {
|
function dynamicTilingMoveToDesktopAction(desktopId) {
|
||||||
let index = root.desktopIndexForId(desktopId)
|
let index = root.desktopIndexForId(desktopId)
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
|
|
@ -685,6 +740,7 @@ MouseArea {
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onWheel: (wheel) => root.handlePagerWheel(wheel)
|
||||||
onClicked: (mouse) => {
|
onClicked: (mouse) => {
|
||||||
root.hideDockToolTip(leftDesktopBtn)
|
root.hideDockToolTip(leftDesktopBtn)
|
||||||
if (mouse.button === Qt.RightButton) {
|
if (mouse.button === Qt.RightButton) {
|
||||||
|
|
@ -794,6 +850,7 @@ MouseArea {
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onWheel: (wheel) => root.handlePagerWheel(wheel)
|
||||||
onClicked: (mouse) => {
|
onClicked: (mouse) => {
|
||||||
root.hideDockToolTip(rightDesktopBtn)
|
root.hideDockToolTip(rightDesktopBtn)
|
||||||
if (mouse.button === Qt.RightButton) {
|
if (mouse.button === Qt.RightButton) {
|
||||||
|
|
|
||||||
|
|
@ -188,9 +188,21 @@ Item {
|
||||||
homeScreenState.swipeMoved(totalDeltaX, totalDeltaY, deltaX, deltaY);
|
homeScreenState.swipeMoved(totalDeltaX, totalDeltaY, deltaX, deltaY);
|
||||||
}
|
}
|
||||||
|
|
||||||
onTouchpadScrollStarted: homeScreenState.swipeStarted(0, 0);
|
onTouchpadScrollStarted: {
|
||||||
onTouchpadScrollEnded: homeScreenState.swipeEnded();
|
if (!ShellSettings.Settings.convergenceModeEnabled) {
|
||||||
onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => homeScreenState.swipeMoved(totalDeltaX, totalDeltaY, deltaX, deltaY);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onPressedChanged: {
|
onPressedChanged: {
|
||||||
if (pressed) {
|
if (pressed) {
|
||||||
|
|
|
||||||
|
|
@ -305,6 +305,43 @@ ContainmentItem {
|
||||||
readonly property real workAreaY: topBarHitHeight
|
readonly property real workAreaY: topBarHitHeight
|
||||||
readonly property real workAreaWidth: Math.max(0, width - frameThickness * 2)
|
readonly property real workAreaWidth: Math.max(0, width - frameThickness * 2)
|
||||||
readonly property real workAreaHeight: Math.max(0, height - topBarHeight - dockHeight - frameThickness * 2)
|
readonly property real workAreaHeight: Math.max(0, height - topBarHeight - dockHeight - frameThickness * 2)
|
||||||
|
readonly property real leftEdgeHotzoneWidth: Math.max(frameThickness, Math.round(Kirigami.Units.gridUnit * 0.7))
|
||||||
|
readonly property real rightEdgeHotzoneWidth: leftEdgeHotzoneWidth
|
||||||
|
readonly property real leftLauncherWidth: Math.min(Kirigami.Units.gridUnit * 22, width * 0.42)
|
||||||
|
readonly property real leftLauncherHeight: Math.min(Kirigami.Units.gridUnit * 16, workAreaHeight * 0.66)
|
||||||
|
readonly property bool leftLauncherEnabled: root.folio.HomeScreenState.appDrawerOpenProgress <= 0
|
||||||
|
readonly property real layoutMenuWidth: Math.min(Kirigami.Units.gridUnit * 16, width * 0.34)
|
||||||
|
readonly property int layoutMenuWindowCount: Math.max(0, ShellSettings.Settings.dynamicTilingLayoutWindowCount)
|
||||||
|
readonly property bool layoutMenuEnabled: ShellSettings.Settings.dynamicTilingEnabled
|
||||||
|
&& layoutMenuWindowCount >= 2
|
||||||
|
&& root.folio.HomeScreenState.appDrawerOpenProgress <= 0
|
||||||
|
readonly property real leftFrameBulgeIdleDepth: Math.max(frameThickness * 0.45, Kirigami.Units.gridUnit * 0.16)
|
||||||
|
readonly property real leftFrameBulgeHoverDepth: 0
|
||||||
|
property real leftFrameBulgeDepth: !leftLauncherEnabled || leftLauncherOpen || leftEdgeHovered
|
||||||
|
? leftFrameBulgeHoverDepth
|
||||||
|
: leftFrameBulgeIdleDepth
|
||||||
|
property real rightFrameBulgeDepth: !layoutMenuEnabled || layoutMenuOpen || rightEdgeHovered
|
||||||
|
? leftFrameBulgeHoverDepth
|
||||||
|
: leftFrameBulgeIdleDepth
|
||||||
|
// Long, thin thickening of the lower-left workspace wall. Vertical
|
||||||
|
// tangents at all three anchors keep the curve smooth as it blends
|
||||||
|
// into the straight wall above and below.
|
||||||
|
readonly property real leftFrameBulgeEffectiveDepth: Math.max(leftFrameBulgeDepth, 0.01)
|
||||||
|
readonly property real leftFrameBulgeApexX: workAreaX + leftFrameBulgeEffectiveDepth
|
||||||
|
readonly property real leftFrameBulgeHalfLength: Kirigami.Units.gridUnit * 7.5
|
||||||
|
readonly property real leftFrameBulgeApexY: workAreaY + workAreaHeight * 0.7
|
||||||
|
readonly property real leftFrameBulgeEdgeTopY: leftFrameBulgeApexY - leftFrameBulgeHalfLength
|
||||||
|
readonly property real leftFrameBulgeEdgeBottomY: leftFrameBulgeApexY + leftFrameBulgeHalfLength
|
||||||
|
// Bezier control-handle length along the vertical tangent at each
|
||||||
|
// anchor. ~0.55 of the half-length gives a clean, taut oval profile.
|
||||||
|
readonly property real leftFrameBulgeTangent: leftFrameBulgeHalfLength * 0.55
|
||||||
|
readonly property real rightFrameBulgeEffectiveDepth: Math.max(rightFrameBulgeDepth, 0.01)
|
||||||
|
readonly property real rightFrameBulgeApexX: workAreaX + workAreaWidth - rightFrameBulgeEffectiveDepth
|
||||||
|
readonly property real rightFrameBulgeHalfLength: leftFrameBulgeHalfLength
|
||||||
|
readonly property real rightFrameBulgeApexY: leftFrameBulgeApexY
|
||||||
|
readonly property real rightFrameBulgeEdgeTopY: rightFrameBulgeApexY - rightFrameBulgeHalfLength
|
||||||
|
readonly property real rightFrameBulgeEdgeBottomY: rightFrameBulgeApexY + rightFrameBulgeHalfLength
|
||||||
|
readonly property real rightFrameBulgeTangent: rightFrameBulgeHalfLength * 0.55
|
||||||
readonly property color chromeColor: Kirigami.Theme.backgroundColor
|
readonly property color chromeColor: Kirigami.Theme.backgroundColor
|
||||||
readonly property color edgeColor: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.1)
|
readonly property color edgeColor: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.1)
|
||||||
readonly property int dockAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.SpatialDefault)
|
readonly property int dockAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.SpatialDefault)
|
||||||
|
|
@ -315,6 +352,12 @@ ContainmentItem {
|
||||||
|
|
||||||
// True once the hover-reveal timer fires; cleared on hover-exit.
|
// True once the hover-reveal timer fires; cleared on hover-exit.
|
||||||
property bool hoverRevealing: false
|
property bool hoverRevealing: false
|
||||||
|
property bool leftEdgeHovered: false
|
||||||
|
property bool leftLauncherHovered: false
|
||||||
|
property bool leftLauncherOpen: false
|
||||||
|
property bool rightEdgeHovered: false
|
||||||
|
property bool layoutMenuHovered: false
|
||||||
|
property bool layoutMenuOpen: false
|
||||||
|
|
||||||
readonly property bool shouldHide: ShellSettings.Settings.autoHidePanelsEnabled
|
readonly property bool shouldHide: ShellSettings.Settings.autoHidePanelsEnabled
|
||||||
&& windowMaximizedTracker.showingWindow && !hoverRevealing
|
&& windowMaximizedTracker.showingWindow && !hoverRevealing
|
||||||
|
|
@ -324,17 +367,87 @@ ContainmentItem {
|
||||||
|
|
||||||
function updateInputRegion() {
|
function updateInputRegion() {
|
||||||
const topBarRegion = Qt.rect(0, 0, width, topBarHitHeight)
|
const topBarRegion = Qt.rect(0, 0, width, topBarHitHeight)
|
||||||
if (shouldHide && dockOffset >= dockHeight) {
|
const leftEdgeRegion = Qt.rect(0, topBarHitHeight, leftEdgeHotzoneWidth, Math.max(0, height - topBarHitHeight - dockHeight))
|
||||||
MobileShell.ShellUtil.setInputRegions(convergenceChrome, [
|
const rightEdgeRegion = Qt.rect(width - rightEdgeHotzoneWidth, topBarHitHeight, rightEdgeHotzoneWidth, Math.max(0, height - topBarHitHeight - dockHeight))
|
||||||
topBarRegion,
|
const leftLauncherRegion = Qt.rect(0,
|
||||||
Qt.rect(0, height - revealStripHeight, width, revealStripHeight)
|
Math.max(0, height - dockHeight - leftLauncherHeight),
|
||||||
])
|
leftLauncherWidth,
|
||||||
} else {
|
leftLauncherHeight)
|
||||||
MobileShell.ShellUtil.setInputRegions(convergenceChrome, [
|
const layoutMenuRegion = Qt.rect(rightLayoutMenu.x,
|
||||||
topBarRegion,
|
rightLayoutMenu.y,
|
||||||
Qt.rect(0, height - dockHeight, width, dockHeight)
|
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))
|
||||||
|
} else {
|
||||||
|
regions.push(Qt.rect(0, height - dockHeight, width, dockHeight))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leftLauncherOpen) {
|
||||||
|
regions.push(leftLauncherRegion)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layoutMenuEnabled && layoutMenuOpen) {
|
||||||
|
regions.push(layoutMenuRegion)
|
||||||
|
}
|
||||||
|
|
||||||
|
MobileShell.ShellUtil.setInputRegions(convergenceChrome, regions)
|
||||||
|
}
|
||||||
|
|
||||||
|
function launchStorageId(storageId) {
|
||||||
|
if (!storageId || String(storageId).length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalizedId = String(storageId)
|
||||||
|
if (!normalizedId.endsWith(".desktop")) {
|
||||||
|
normalizedId += ".desktop"
|
||||||
|
}
|
||||||
|
MobileShell.AppLaunch.launchOrActivateApp(normalizedId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshLeftLauncherVisibility() {
|
||||||
|
if (!leftLauncherEnabled) {
|
||||||
|
leftLauncherCloseTimer.stop()
|
||||||
|
leftEdgeHovered = false
|
||||||
|
leftLauncherHovered = false
|
||||||
|
leftLauncherOpen = false
|
||||||
|
inputRegionTimer.restart()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leftEdgeHovered || leftLauncherHovered) {
|
||||||
|
leftLauncherCloseTimer.stop()
|
||||||
|
leftLauncherOpen = true
|
||||||
|
} else {
|
||||||
|
leftLauncherCloseTimer.restart()
|
||||||
|
}
|
||||||
|
inputRegionTimer.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshLayoutMenuVisibility() {
|
||||||
|
if (!layoutMenuEnabled) {
|
||||||
|
layoutMenuCloseTimer.stop()
|
||||||
|
rightEdgeHovered = false
|
||||||
|
layoutMenuHovered = false
|
||||||
|
layoutMenuOpen = false
|
||||||
|
inputRegionTimer.restart()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rightEdgeHovered || layoutMenuHovered) {
|
||||||
|
layoutMenuCloseTimer.stop()
|
||||||
|
layoutMenuOpen = true
|
||||||
|
} else {
|
||||||
|
layoutMenuCloseTimer.restart()
|
||||||
|
}
|
||||||
|
inputRegionTimer.restart()
|
||||||
}
|
}
|
||||||
|
|
||||||
onActiveChanged: {
|
onActiveChanged: {
|
||||||
|
|
@ -353,6 +466,8 @@ ContainmentItem {
|
||||||
}
|
}
|
||||||
inputRegionTimer.restart()
|
inputRegionTimer.restart()
|
||||||
}
|
}
|
||||||
|
onLeftLauncherEnabledChanged: refreshLeftLauncherVisibility()
|
||||||
|
onLayoutMenuEnabledChanged: refreshLayoutMenuVisibility()
|
||||||
|
|
||||||
// Narrow the input region to a strip at the screen edge when hidden
|
// Narrow the input region to a strip at the screen edge when hidden
|
||||||
// so that app controls near the bottom edge are not accidentally
|
// so that app controls near the bottom edge are not accidentally
|
||||||
|
|
@ -385,6 +500,34 @@ ContainmentItem {
|
||||||
onTriggered: convergenceChrome.hoverRevealing = true
|
onTriggered: convergenceChrome.hoverRevealing = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: leftLauncherCloseTimer
|
||||||
|
interval: Kirigami.Units.shortDuration
|
||||||
|
repeat: false
|
||||||
|
onTriggered: {
|
||||||
|
if (!convergenceChrome.leftEdgeHovered
|
||||||
|
&& !convergenceChrome.leftLauncherHovered
|
||||||
|
&& convergenceChrome.leftLauncherOpen) {
|
||||||
|
convergenceChrome.leftLauncherOpen = false
|
||||||
|
inputRegionTimer.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: layoutMenuCloseTimer
|
||||||
|
interval: Kirigami.Units.shortDuration
|
||||||
|
repeat: false
|
||||||
|
onTriggered: {
|
||||||
|
if (!convergenceChrome.rightEdgeHovered
|
||||||
|
&& !convergenceChrome.layoutMenuHovered
|
||||||
|
&& convergenceChrome.layoutMenuOpen) {
|
||||||
|
convergenceChrome.layoutMenuOpen = false
|
||||||
|
inputRegionTimer.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Behavior on dockOffset {
|
Behavior on dockOffset {
|
||||||
MobileShell.MotionNumberAnimation {
|
MobileShell.MotionNumberAnimation {
|
||||||
type: MobileShell.Motion.SpatialDefault
|
type: MobileShell.Motion.SpatialDefault
|
||||||
|
|
@ -392,6 +535,20 @@ ContainmentItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Behavior on leftFrameBulgeDepth {
|
||||||
|
MobileShell.MotionNumberAnimation {
|
||||||
|
type: MobileShell.Motion.SpatialDefault
|
||||||
|
duration: root.shortAnimationDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on rightFrameBulgeDepth {
|
||||||
|
MobileShell.MotionNumberAnimation {
|
||||||
|
type: MobileShell.Motion.SpatialDefault
|
||||||
|
duration: root.shortAnimationDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: topBarSurface
|
id: topBarSurface
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
|
|
@ -411,6 +568,7 @@ ContainmentItem {
|
||||||
Shape {
|
Shape {
|
||||||
id: workspaceFrame
|
id: workspaceFrame
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
fillColor: convergenceChrome.chromeColor
|
fillColor: convergenceChrome.chromeColor
|
||||||
|
|
@ -427,24 +585,125 @@ ContainmentItem {
|
||||||
PathMove { x: convergenceChrome.workAreaX + convergenceChrome.frameRadius; y: convergenceChrome.workAreaY }
|
PathMove { x: convergenceChrome.workAreaX + convergenceChrome.frameRadius; y: convergenceChrome.workAreaY }
|
||||||
PathLine { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth - convergenceChrome.frameRadius; y: convergenceChrome.workAreaY }
|
PathLine { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth - convergenceChrome.frameRadius; y: convergenceChrome.workAreaY }
|
||||||
PathArc { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth; y: convergenceChrome.workAreaY + convergenceChrome.frameRadius; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }
|
PathArc { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth; y: convergenceChrome.workAreaY + convergenceChrome.frameRadius; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }
|
||||||
|
PathLine { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth; y: convergenceChrome.rightFrameBulgeEdgeTopY }
|
||||||
|
PathCubic {
|
||||||
|
x: convergenceChrome.rightFrameBulgeApexX
|
||||||
|
y: convergenceChrome.rightFrameBulgeApexY
|
||||||
|
control1X: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth
|
||||||
|
control1Y: convergenceChrome.rightFrameBulgeEdgeTopY + convergenceChrome.rightFrameBulgeTangent
|
||||||
|
control2X: convergenceChrome.rightFrameBulgeApexX
|
||||||
|
control2Y: convergenceChrome.rightFrameBulgeApexY - convergenceChrome.rightFrameBulgeTangent
|
||||||
|
}
|
||||||
|
PathCubic {
|
||||||
|
x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth
|
||||||
|
y: convergenceChrome.rightFrameBulgeEdgeBottomY
|
||||||
|
control1X: convergenceChrome.rightFrameBulgeApexX
|
||||||
|
control1Y: convergenceChrome.rightFrameBulgeApexY + convergenceChrome.rightFrameBulgeTangent
|
||||||
|
control2X: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth
|
||||||
|
control2Y: convergenceChrome.rightFrameBulgeEdgeBottomY - convergenceChrome.rightFrameBulgeTangent
|
||||||
|
}
|
||||||
PathLine { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight - convergenceChrome.frameRadius }
|
PathLine { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight - convergenceChrome.frameRadius }
|
||||||
PathArc { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth - convergenceChrome.frameRadius; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }
|
PathArc { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth - convergenceChrome.frameRadius; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }
|
||||||
PathLine { x: convergenceChrome.workAreaX + convergenceChrome.frameRadius; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight }
|
PathLine { x: convergenceChrome.workAreaX + convergenceChrome.frameRadius; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight }
|
||||||
PathArc { x: convergenceChrome.workAreaX; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight - convergenceChrome.frameRadius; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }
|
PathArc { x: convergenceChrome.workAreaX; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight - convergenceChrome.frameRadius; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }
|
||||||
|
PathLine { x: convergenceChrome.workAreaX; y: convergenceChrome.leftFrameBulgeEdgeBottomY }
|
||||||
|
PathCubic {
|
||||||
|
x: convergenceChrome.leftFrameBulgeApexX
|
||||||
|
y: convergenceChrome.leftFrameBulgeApexY
|
||||||
|
control1X: convergenceChrome.workAreaX
|
||||||
|
control1Y: convergenceChrome.leftFrameBulgeEdgeBottomY - convergenceChrome.leftFrameBulgeTangent
|
||||||
|
control2X: convergenceChrome.leftFrameBulgeApexX
|
||||||
|
control2Y: convergenceChrome.leftFrameBulgeApexY + convergenceChrome.leftFrameBulgeTangent
|
||||||
|
}
|
||||||
|
PathCubic {
|
||||||
|
x: convergenceChrome.workAreaX
|
||||||
|
y: convergenceChrome.leftFrameBulgeEdgeTopY
|
||||||
|
control1X: convergenceChrome.leftFrameBulgeApexX
|
||||||
|
control1Y: convergenceChrome.leftFrameBulgeApexY - convergenceChrome.leftFrameBulgeTangent
|
||||||
|
control2X: convergenceChrome.workAreaX
|
||||||
|
control2Y: convergenceChrome.leftFrameBulgeEdgeTopY + convergenceChrome.leftFrameBulgeTangent
|
||||||
|
}
|
||||||
PathLine { x: convergenceChrome.workAreaX; y: convergenceChrome.workAreaY + convergenceChrome.frameRadius }
|
PathLine { x: convergenceChrome.workAreaX; y: convergenceChrome.workAreaY + convergenceChrome.frameRadius }
|
||||||
PathArc { x: convergenceChrome.workAreaX + convergenceChrome.frameRadius; y: convergenceChrome.workAreaY; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }
|
PathArc { x: convergenceChrome.workAreaX + convergenceChrome.frameRadius; y: convergenceChrome.workAreaY; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
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
|
x: convergenceChrome.workAreaX
|
||||||
|
y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight - convergenceChrome.frameRadius
|
||||||
|
controlX: convergenceChrome.workAreaX
|
||||||
|
controlY: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight
|
||||||
|
}
|
||||||
|
PathLine { x: convergenceChrome.workAreaX; y: convergenceChrome.leftFrameBulgeEdgeBottomY }
|
||||||
|
PathCubic {
|
||||||
|
x: convergenceChrome.leftFrameBulgeApexX
|
||||||
|
y: convergenceChrome.leftFrameBulgeApexY
|
||||||
|
control1X: convergenceChrome.workAreaX
|
||||||
|
control1Y: convergenceChrome.leftFrameBulgeEdgeBottomY - convergenceChrome.leftFrameBulgeTangent
|
||||||
|
control2X: convergenceChrome.leftFrameBulgeApexX
|
||||||
|
control2Y: convergenceChrome.leftFrameBulgeApexY + convergenceChrome.leftFrameBulgeTangent
|
||||||
|
}
|
||||||
|
PathCubic {
|
||||||
|
x: convergenceChrome.workAreaX
|
||||||
|
y: convergenceChrome.leftFrameBulgeEdgeTopY
|
||||||
|
control1X: convergenceChrome.leftFrameBulgeApexX
|
||||||
|
control1Y: convergenceChrome.leftFrameBulgeApexY - convergenceChrome.leftFrameBulgeTangent
|
||||||
|
control2X: convergenceChrome.workAreaX
|
||||||
|
control2Y: convergenceChrome.leftFrameBulgeEdgeTopY + convergenceChrome.leftFrameBulgeTangent
|
||||||
|
}
|
||||||
|
PathLine { x: convergenceChrome.workAreaX; y: convergenceChrome.workAreaY + convergenceChrome.frameRadius }
|
||||||
|
PathQuad {
|
||||||
|
x: convergenceChrome.workAreaX + convergenceChrome.frameRadius
|
||||||
y: convergenceChrome.workAreaY
|
y: convergenceChrome.workAreaY
|
||||||
width: convergenceChrome.workAreaWidth
|
controlX: convergenceChrome.workAreaX
|
||||||
height: convergenceChrome.workAreaHeight
|
controlY: convergenceChrome.workAreaY
|
||||||
radius: convergenceChrome.frameRadius
|
}
|
||||||
color: "transparent"
|
}
|
||||||
border.width: 1
|
|
||||||
border.color: convergenceChrome.edgeColor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|
@ -488,6 +747,307 @@ ContainmentItem {
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: leftEdgeStrip
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.top: topBarSurface.bottom
|
||||||
|
anchors.bottom: dockSurface.top
|
||||||
|
width: convergenceChrome.leftEdgeHotzoneWidth
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: leftEdgeHoverArea
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
enabled: convergenceChrome.leftLauncherEnabled
|
||||||
|
hoverEnabled: enabled
|
||||||
|
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
|
||||||
|
onContainsMouseChanged: {
|
||||||
|
convergenceChrome.leftEdgeHovered = containsMouse
|
||||||
|
convergenceChrome.refreshLeftLauncherVisibility()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: rightEdgeStrip
|
||||||
|
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: topBarSurface.bottom
|
||||||
|
anchors.bottom: dockSurface.top
|
||||||
|
width: convergenceChrome.layoutMenuEnabled ? convergenceChrome.rightEdgeHotzoneWidth : 0
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: rightEdgeHoverArea
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
enabled: convergenceChrome.layoutMenuEnabled
|
||||||
|
hoverEnabled: enabled
|
||||||
|
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
|
||||||
|
onContainsMouseChanged: {
|
||||||
|
convergenceChrome.rightEdgeHovered = containsMouse
|
||||||
|
convergenceChrome.refreshLayoutMenuVisibility()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DynamicTilingLayoutMenu {
|
||||||
|
id: rightLayoutMenu
|
||||||
|
|
||||||
|
width: convergenceChrome.layoutMenuWidth
|
||||||
|
height: preferredHeight
|
||||||
|
x: convergenceChrome.width - width
|
||||||
|
y: convergenceChrome.height - convergenceChrome.dockHeight - height
|
||||||
|
visible: convergenceChrome.layoutMenuOpen
|
||||||
|
opacity: convergenceChrome.layoutMenuOpen ? 1 : 0
|
||||||
|
maxHeight: convergenceChrome.workAreaHeight * 0.5
|
||||||
|
windowCount: convergenceChrome.layoutMenuWindowCount
|
||||||
|
currentMode: ShellSettings.Settings.dynamicTilingLayoutMode
|
||||||
|
surfaceColor: convergenceChrome.chromeColor
|
||||||
|
animationDuration: root.shortAnimationDuration
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
enabled: convergenceChrome.layoutMenuOpen
|
||||||
|
|
||||||
|
onHoveredChanged: {
|
||||||
|
convergenceChrome.layoutMenuHovered = hovered
|
||||||
|
convergenceChrome.refreshLayoutMenuVisibility()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transform: Translate {
|
||||||
|
y: convergenceChrome.layoutMenuOpen ? 0 : Kirigami.Units.gridUnit
|
||||||
|
x: convergenceChrome.layoutMenuOpen ? 0 : rightLayoutMenu.width - convergenceChrome.rightEdgeHotzoneWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
onLayoutModeRequested: (mode) => {
|
||||||
|
if (ShellSettings.Settings.requestDynamicTilingLayoutMode !== undefined) {
|
||||||
|
ShellSettings.Settings.requestDynamicTilingLayoutMode(mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDismissRequested: {
|
||||||
|
convergenceChrome.layoutMenuOpen = false
|
||||||
|
inputRegionTimer.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: leftEdgeLauncher
|
||||||
|
|
||||||
|
width: convergenceChrome.leftLauncherWidth
|
||||||
|
height: convergenceChrome.leftLauncherHeight
|
||||||
|
x: 0
|
||||||
|
y: convergenceChrome.height - convergenceChrome.dockHeight - height
|
||||||
|
visible: convergenceChrome.leftLauncherOpen
|
||||||
|
opacity: convergenceChrome.leftLauncherOpen ? 1 : 0
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
transform: Translate {
|
||||||
|
y: convergenceChrome.leftLauncherOpen ? 0 : Kirigami.Units.gridUnit
|
||||||
|
x: convergenceChrome.leftLauncherOpen ? 0 : -leftEdgeLauncher.width + convergenceChrome.leftEdgeHotzoneWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
MobileShell.MotionNumberAnimation {
|
||||||
|
type: MobileShell.Motion.EffectsFast
|
||||||
|
duration: root.shortAnimationDuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property real cornerRadius: Math.min(MobileShell.Constants.convergenceWorkspaceFrameRadius, height * 0.24)
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
enabled: convergenceChrome.leftLauncherOpen
|
||||||
|
|
||||||
|
onHoveredChanged: {
|
||||||
|
convergenceChrome.leftLauncherHovered = hovered
|
||||||
|
convergenceChrome.refreshLeftLauncherVisibility()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Shape {
|
||||||
|
id: leftLauncherSurface
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
ShapePath {
|
||||||
|
fillColor: convergenceChrome.chromeColor
|
||||||
|
strokeWidth: 0
|
||||||
|
|
||||||
|
startX: 0
|
||||||
|
startY: 0
|
||||||
|
PathLine { x: leftEdgeLauncher.width - leftEdgeLauncher.cornerRadius; y: 0 }
|
||||||
|
PathArc {
|
||||||
|
x: leftEdgeLauncher.width
|
||||||
|
y: leftEdgeLauncher.cornerRadius
|
||||||
|
radiusX: leftEdgeLauncher.cornerRadius
|
||||||
|
radiusY: leftEdgeLauncher.cornerRadius
|
||||||
|
}
|
||||||
|
PathLine { x: leftEdgeLauncher.width; y: leftEdgeLauncher.height }
|
||||||
|
PathLine { x: 0; y: leftEdgeLauncher.height }
|
||||||
|
PathLine { x: 0; y: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Kirigami.Units.smallSpacing
|
||||||
|
spacing: Kirigami.Units.gridUnit * 0.65
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
PlasmaComponents.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: i18n("Recently Used")
|
||||||
|
font.weight: Font.Medium
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: recentAppsList
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 6.8
|
||||||
|
clip: true
|
||||||
|
interactive: false
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
model: folio.RecentApplicationsModel
|
||||||
|
|
||||||
|
delegate: MouseArea {
|
||||||
|
required property int index
|
||||||
|
required property var model
|
||||||
|
|
||||||
|
readonly property var delegateObject: model.delegate
|
||||||
|
readonly property var application: delegateObject ? delegateObject.application : null
|
||||||
|
readonly property bool validEntry: index < 5 && application !== null
|
||||||
|
|
||||||
|
width: recentAppsList.width
|
||||||
|
height: validEntry ? Kirigami.Units.gridUnit * 1.35 : 0
|
||||||
|
enabled: validEntry
|
||||||
|
hoverEnabled: validEntry
|
||||||
|
cursorShape: validEntry ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (application) {
|
||||||
|
convergenceChrome.launchStorageId(application.storageId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Kirigami.Units.cornerRadius
|
||||||
|
color: parent.containsMouse
|
||||||
|
? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.08)
|
||||||
|
: "transparent"
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: Kirigami.Units.smallSpacing
|
||||||
|
anchors.rightMargin: Kirigami.Units.smallSpacing
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
Kirigami.Icon {
|
||||||
|
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
Layout.preferredHeight: Layout.preferredWidth
|
||||||
|
source: application ? application.icon : ""
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaComponents.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: application ? application.name : ""
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
PlasmaComponents.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: i18n("Most Used")
|
||||||
|
font.weight: Font.Medium
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: favouritesQuickList
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
clip: true
|
||||||
|
interactive: false
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
model: folio.MostUsedApplicationsModel
|
||||||
|
|
||||||
|
delegate: MouseArea {
|
||||||
|
required property int index
|
||||||
|
required property var model
|
||||||
|
|
||||||
|
readonly property var delegateObject: model.delegate
|
||||||
|
readonly property var application: delegateObject ? delegateObject.application : null
|
||||||
|
readonly property bool validEntry: index < 6 && application !== null
|
||||||
|
|
||||||
|
width: favouritesQuickList.width
|
||||||
|
height: validEntry ? Kirigami.Units.gridUnit * 1.35 : 0
|
||||||
|
enabled: validEntry
|
||||||
|
hoverEnabled: validEntry
|
||||||
|
cursorShape: validEntry ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (application) {
|
||||||
|
convergenceChrome.launchStorageId(application.storageId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Kirigami.Units.cornerRadius
|
||||||
|
color: parent.containsMouse
|
||||||
|
? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.08)
|
||||||
|
: "transparent"
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: Kirigami.Units.smallSpacing
|
||||||
|
anchors.rightMargin: Kirigami.Units.smallSpacing
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
Kirigami.Icon {
|
||||||
|
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
Layout.preferredHeight: Layout.preferredWidth
|
||||||
|
source: application ? application.icon : ""
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaComponents.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: application ? application.name : ""
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// App-drawer overlay — renders the popup drawer above application
|
// App-drawer overlay — renders the popup drawer above application
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,10 @@ Item {
|
||||||
value: drawer.visible
|
value: drawer.visible
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TaskManager.VirtualDesktopInfo {
|
||||||
|
id: virtualDesktopInfo
|
||||||
|
}
|
||||||
|
|
||||||
//END API implementation
|
//END API implementation
|
||||||
|
|
||||||
// Startup feedback fill animation
|
// Startup feedback fill animation
|
||||||
|
|
@ -155,6 +159,7 @@ Item {
|
||||||
MobileShell.ActionDrawerOpenSurface {
|
MobileShell.ActionDrawerOpenSurface {
|
||||||
id: swipeArea
|
id: swipeArea
|
||||||
actionDrawer: drawer.actionDrawer
|
actionDrawer: drawer.actionDrawer
|
||||||
|
virtualDesktopInfo: virtualDesktopInfo
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
readonly property alias drawerVisible: drawer.visible
|
readonly property alias drawerVisible: drawer.visible
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ ContainmentItem {
|
||||||
root.panel.thickness = root.panelHeight;
|
root.panel.thickness = root.panelHeight;
|
||||||
root.panel.thickness = root.panelHeight;
|
root.panel.thickness = root.panelHeight;
|
||||||
|
|
||||||
root.panel.visibilityMode = (!ShellSettings.Settings.convergenceModeEnabled && ShellSettings.Settings.autoHidePanelsEnabled) ? 3 : 0;
|
root.panel.visibilityMode = (ShellSettings.Settings.convergenceModeEnabled || ShellSettings.Settings.autoHidePanelsEnabled) ? 3 : 0;
|
||||||
MobileShell.ShellUtil.setWindowLayer(root.panel, LayerShell.Window.LayerOverlay)
|
MobileShell.ShellUtil.setWindowLayer(root.panel, LayerShell.Window.LayerOverlay)
|
||||||
root.updateTouchArea();
|
root.updateTouchArea();
|
||||||
}
|
}
|
||||||
|
|
@ -152,9 +152,8 @@ ContainmentItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invisible layer-shell surface that reserves screen space for the
|
// Invisible layer-shell surface that reserves screen space for the
|
||||||
// status bar in convergence mode. The visible convergence top bar is
|
// convergence status bar and one frame inset. The panel window itself
|
||||||
// rendered by Folio's unified chrome surface; this window only shrinks
|
// stays non-reserving so this is the only top strut.
|
||||||
// KWin's MaximizeArea.
|
|
||||||
Window {
|
Window {
|
||||||
id: topBarSpaceReserver
|
id: topBarSpaceReserver
|
||||||
visible: ShellSettings.Settings.convergenceModeEnabled && !ShellSettings.Settings.gamingModeEnabled
|
visible: ShellSettings.Settings.convergenceModeEnabled && !ShellSettings.Settings.gamingModeEnabled
|
||||||
|
|
|
||||||
|
|
@ -54,15 +54,14 @@ QMap<QString, QMap<QString, QVariant>> getKwinrcSettings(KSharedConfig::Ptr m_mo
|
||||||
{"ElectricBorderTiling", convergenceModeEnabled} // drag to left/right edges to tile half-screen in convergence mode
|
{"ElectricBorderTiling", convergenceModeEnabled} // drag to left/right edges to tile half-screen in convergence mode
|
||||||
}},
|
}},
|
||||||
{"Plugins",
|
{"Plugins",
|
||||||
{
|
{{"blurEnabled", false}, // disable blur for performance reasons, we could reconsider in the future for more powerful devices
|
||||||
{"blurEnabled", false}, // disable blur for performance reasons, we could reconsider in the future for more powerful devices
|
|
||||||
{"convergentwindowsEnabled", true}, // enable our convergent window plugin
|
{"convergentwindowsEnabled", true}, // enable our convergent window plugin
|
||||||
{"mobiletaskswitcherEnabled", !convergenceModeEnabled}, // mobile task switcher on phone only; convergence uses standard Alt-Tab tabbox
|
{"mobiletaskswitcherEnabled", !convergenceModeEnabled}, // mobile task switcher on phone only; convergence uses standard Alt-Tab tabbox
|
||||||
{"overviewEnabled", convergenceModeEnabled}, // enable KWin Overview effect in convergence mode for desktop-style task switching
|
{"overviewEnabled", convergenceModeEnabled}, // enable KWin Overview effect in convergence mode for desktop-style task switching
|
||||||
{"screenedgeEnabled", convergenceModeEnabled}, // enable screen edge visual feedback in convergence mode (mouse hot corners)
|
{"screenedgeEnabled", convergenceModeEnabled}, // enable screen edge visual feedback in convergence mode (mouse hot corners)
|
||||||
|
{"shift-tile-animationsEnabled", convergenceModeEnabled},
|
||||||
{"shift-tilingEnabled", convergenceModeEnabled},
|
{"shift-tilingEnabled", convergenceModeEnabled},
|
||||||
{"shift-tile-previewEnabled", convergenceModeEnabled}
|
{"shift-tile-previewEnabled", convergenceModeEnabled}}},
|
||||||
}},
|
|
||||||
{"Wayland",
|
{"Wayland",
|
||||||
{
|
{
|
||||||
{"VirtualKeyboardEnabled", true} // enable vkbd
|
{"VirtualKeyboardEnabled", true} // enable vkbd
|
||||||
|
|
@ -80,7 +79,7 @@ QMap<QString, QMap<QString, QVariant>> getKwinrcSettings(KSharedConfig::Ptr m_mo
|
||||||
|
|
||||||
// Have a separate list here because we need to trigger DBus calls to load/unload each effect/script.
|
// Have a separate list here because we need to trigger DBus calls to load/unload each effect/script.
|
||||||
// Make sure that the effect/script is added to the kwinrc "Plugins" section above!
|
// Make sure that the effect/script is added to the kwinrc "Plugins" section above!
|
||||||
const QList<QString> KWIN_EFFECTS = {"blur", "mobiletaskswitcher", "overview", "screenedge", "shift-tile-preview"};
|
const QList<QString> KWIN_EFFECTS = {"blur", "mobiletaskswitcher", "overview", "screenedge", "shift-tile-animations", "shift-tile-preview"};
|
||||||
const QList<QString> KWIN_SCRIPTS = {"convergentwindows", "shift-tiling"};
|
const QList<QString> KWIN_SCRIPTS = {"convergentwindows", "shift-tiling"};
|
||||||
|
|
||||||
// .config/plasma-mobile/ksmserver - immutable settings:
|
// .config/plasma-mobile/ksmserver - immutable settings:
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,5 @@ function(add_kwin_effect name source)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
add_kwin_effect(shift-snap-assist shift-snap-assist)
|
add_kwin_effect(shift-snap-assist shift-snap-assist)
|
||||||
|
add_kwin_effect(shift-tile-animations shift-tile-animations)
|
||||||
add_kwin_effect(shift-tile-preview shift-tile-preview)
|
add_kwin_effect(shift-tile-preview shift-tile-preview)
|
||||||
|
|
|
||||||
155
kwin/effects/shift-tile-animations/contents/code/main.js
Normal file
155
kwin/effects/shift-tile-animations/contents/code/main.js
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
// 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();
|
||||||
20
kwin/effects/shift-tile-animations/metadata.json
Normal file
20
kwin/effects/shift-tile-animations/metadata.json
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"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,7 +3,6 @@
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import org.kde.kwin as KWinComponents
|
import org.kde.kwin as KWinComponents
|
||||||
import org.kde.plasma.private.mobileshell as MobileShell
|
|
||||||
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
|
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
|
||||||
|
|
||||||
KWinComponents.SceneEffect {
|
KWinComponents.SceneEffect {
|
||||||
|
|
@ -13,14 +12,11 @@ KWinComponents.SceneEffect {
|
||||||
|
|
||||||
readonly property int outerGap: 8
|
readonly property int outerGap: 8
|
||||||
readonly property int floatEscapeMargin: 32
|
readonly property int floatEscapeMargin: 32
|
||||||
readonly property int previewAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.SpatialDefault)
|
|
||||||
readonly property int previewFadeDuration: MobileShell.Motion.duration(MobileShell.Motion.EffectsFast)
|
|
||||||
|
|
||||||
property var dragConnectedWindows: ({})
|
property var dragConnectedWindows: ({})
|
||||||
property var draggingWindow: null
|
property var draggingWindow: null
|
||||||
property rect dragSourceGeometry: Qt.rect(0, 0, 0, 0)
|
property rect dragSourceGeometry: Qt.rect(0, 0, 0, 0)
|
||||||
|
|
||||||
property bool animatePreview: false
|
|
||||||
property bool previewVisible: false
|
property bool previewVisible: false
|
||||||
property string previewMode: ""
|
property string previewMode: ""
|
||||||
property string previewScreenName: ""
|
property string previewScreenName: ""
|
||||||
|
|
@ -126,14 +122,26 @@ KWinComponents.SceneEffect {
|
||||||
}
|
}
|
||||||
|
|
||||||
function showPreview(mode, geometry, screenName) {
|
function showPreview(mode, geometry, screenName) {
|
||||||
|
if (!isActive() || !validRect(geometry)) {
|
||||||
hidePreview();
|
hidePreview();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
previewMode = mode;
|
||||||
|
previewScreenName = screenName;
|
||||||
|
previewGeometry = insetPreviewGeometry(geometry);
|
||||||
|
previewVisible = true;
|
||||||
|
KWinComponents.Workspace.showOutline(previewGeometry);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hidePreview() {
|
function hidePreview() {
|
||||||
previewVisible = false;
|
previewVisible = false;
|
||||||
draggingWindow = null;
|
draggingWindow = null;
|
||||||
dragSourceGeometry = Qt.rect(0, 0, 0, 0);
|
dragSourceGeometry = Qt.rect(0, 0, 0, 0);
|
||||||
disableEffectTimer.restart();
|
previewMode = "";
|
||||||
|
previewScreenName = "";
|
||||||
|
previewGeometry = Qt.rect(0, 0, 0, 0);
|
||||||
|
KWinComponents.Workspace.hideOutline();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePreview(window, dragGeometry) {
|
function updatePreview(window, dragGeometry) {
|
||||||
|
|
@ -189,44 +197,6 @@ KWinComponents.SceneEffect {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function previewFillColor(mode) {
|
|
||||||
if (mode === "float") {
|
|
||||||
return Qt.rgba(1.0, 0.62, 0.24, 0.18);
|
|
||||||
}
|
|
||||||
if (mode === "restore") {
|
|
||||||
return Qt.rgba(1.0, 1.0, 1.0, 0.10);
|
|
||||||
}
|
|
||||||
return Qt.rgba(0.18, 0.72, 0.66, 0.22);
|
|
||||||
}
|
|
||||||
|
|
||||||
function previewBorderColor(mode) {
|
|
||||||
if (mode === "float") {
|
|
||||||
return Qt.rgba(1.0, 0.72, 0.36, 0.72);
|
|
||||||
}
|
|
||||||
if (mode === "restore") {
|
|
||||||
return Qt.rgba(1.0, 1.0, 1.0, 0.36);
|
|
||||||
}
|
|
||||||
return Qt.rgba(0.64, 0.90, 0.86, 0.82);
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: enableAnimationTimer
|
|
||||||
interval: 1
|
|
||||||
repeat: false
|
|
||||||
onTriggered: effect.animatePreview = true
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: disableEffectTimer
|
|
||||||
interval: effect.previewFadeDuration
|
|
||||||
repeat: false
|
|
||||||
onTriggered: {
|
|
||||||
if (!effect.previewVisible) {
|
|
||||||
effect.visible = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: KWinComponents.Workspace
|
target: KWinComponents.Workspace
|
||||||
|
|
||||||
|
|
@ -267,65 +237,5 @@ KWinComponents.SceneEffect {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate: Rectangle {
|
|
||||||
id: screenDelegate
|
|
||||||
|
|
||||||
readonly property var targetScreen: KWinComponents.SceneView.screen
|
|
||||||
readonly property bool previewOnScreen: effect.previewScreenName === targetScreen.name
|
|
||||||
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: previewSurface
|
|
||||||
|
|
||||||
visible: opacity > 0
|
|
||||||
x: effect.previewGeometry.x - screenDelegate.targetScreen.geometry.x
|
|
||||||
y: effect.previewGeometry.y - screenDelegate.targetScreen.geometry.y
|
|
||||||
width: effect.previewGeometry.width
|
|
||||||
height: effect.previewGeometry.height
|
|
||||||
radius: 14
|
|
||||||
opacity: effect.previewVisible && screenDelegate.previewOnScreen ? 1 : 0
|
|
||||||
color: effect.previewFillColor(effect.previewMode)
|
|
||||||
border.width: 2
|
|
||||||
border.color: effect.previewBorderColor(effect.previewMode)
|
|
||||||
|
|
||||||
Behavior on x {
|
|
||||||
enabled: effect.animatePreview
|
|
||||||
MobileShell.MotionNumberAnimation { type: MobileShell.Motion.SpatialDefault; duration: effect.previewAnimationDuration }
|
|
||||||
}
|
|
||||||
Behavior on y {
|
|
||||||
enabled: effect.animatePreview
|
|
||||||
MobileShell.MotionNumberAnimation { type: MobileShell.Motion.SpatialDefault; duration: effect.previewAnimationDuration }
|
|
||||||
}
|
|
||||||
Behavior on width {
|
|
||||||
enabled: effect.animatePreview
|
|
||||||
MobileShell.MotionNumberAnimation { type: MobileShell.Motion.SpatialDefault; duration: effect.previewAnimationDuration }
|
|
||||||
}
|
|
||||||
Behavior on height {
|
|
||||||
enabled: effect.animatePreview
|
|
||||||
MobileShell.MotionNumberAnimation { type: MobileShell.Motion.SpatialDefault; duration: effect.previewAnimationDuration }
|
|
||||||
}
|
|
||||||
Behavior on opacity {
|
|
||||||
MobileShell.MotionNumberAnimation { type: MobileShell.Motion.EffectsFast; duration: effect.previewFadeDuration }
|
|
||||||
}
|
|
||||||
Behavior on color {
|
|
||||||
MobileShell.MotionColorAnimation { type: MobileShell.Motion.EffectsFast; duration: effect.previewFadeDuration }
|
|
||||||
}
|
|
||||||
Behavior on border.color {
|
|
||||||
MobileShell.MotionColorAnimation { type: MobileShell.Motion.EffectsFast; duration: effect.previewFadeDuration }
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 5
|
|
||||||
radius: Math.max(0, parent.radius - anchors.margins)
|
|
||||||
color: "transparent"
|
|
||||||
border.width: 1
|
|
||||||
border.color: Qt.rgba(1, 1, 1, 0.14)
|
|
||||||
opacity: effect.previewMode === "insert" ? 1 : 0.45
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: connectExistingWindows()
|
Component.onCompleted: connectExistingWindows()
|
||||||
}
|
}
|
||||||
|
|
@ -71,8 +71,10 @@ Item {
|
||||||
// Values keep callback references so KWin script reloads can disconnect them.
|
// Values keep callback references so KWin script reloads can disconnect them.
|
||||||
property var dragConnectedWindows: ({})
|
property var dragConnectedWindows: ({})
|
||||||
|
|
||||||
property int lastWindowRequestSerial: ShellSettings.Settings.dynamicTilingWindowRequestSerial
|
property int lastWindowRequestSerial: -1
|
||||||
property string lastPublishedMaximizedWindowIds: "__unpublished__"
|
property string lastPublishedMaximizedWindowIds: "__unpublished__"
|
||||||
|
property int lastLayoutRequestSerial: -1
|
||||||
|
property string lastPublishedLayoutState: "__unpublished__"
|
||||||
|
|
||||||
// Drag state.
|
// Drag state.
|
||||||
//
|
//
|
||||||
|
|
@ -504,6 +506,7 @@ Item {
|
||||||
const focused = Object.assign({}, lastFocusedWindowKeys);
|
const focused = Object.assign({}, lastFocusedWindowKeys);
|
||||||
delete focused[outputName];
|
delete focused[outputName];
|
||||||
lastFocusedWindowKeys = focused;
|
lastFocusedWindowKeys = focused;
|
||||||
|
publishDynamicTilingLayoutState();
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearDisplacedWindowOwnersForLayout(outputName) {
|
function clearDisplacedWindowOwnersForLayout(outputName) {
|
||||||
|
|
@ -654,6 +657,8 @@ Item {
|
||||||
for (let i = 0; i < cleanupLayouts.length; i++) {
|
for (let i = 0; i < cleanupLayouts.length; i++) {
|
||||||
cleanupEmptyLayout(cleanupLayouts[i]);
|
cleanupEmptyLayout(cleanupLayouts[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
publishDynamicTilingLayoutState();
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeLeaf(win) {
|
function makeLeaf(win) {
|
||||||
|
|
@ -842,6 +847,15 @@ Item {
|
||||||
const win = KWinComponents.Workspace.activeWindow;
|
const win = KWinComponents.Workspace.activeWindow;
|
||||||
const activeName = layoutKeyForWindow(win);
|
const activeName = layoutKeyForWindow(win);
|
||||||
if (activeName !== "") return activeName;
|
if (activeName !== "") return activeName;
|
||||||
|
const desktop = KWinComponents.Workspace.currentDesktop;
|
||||||
|
const screens = KWinComponents.Workspace.screens;
|
||||||
|
if (desktop && screens && screens.length > 0) {
|
||||||
|
for (let i = 0; i < screens.length; i++) {
|
||||||
|
const currentDesktopName = layoutKeyFor(screens[i].name, desktop);
|
||||||
|
if (screenLayouts[currentDesktopName]) return currentDesktopName;
|
||||||
|
}
|
||||||
|
return layoutKeyFor(screens[0].name, desktop);
|
||||||
|
}
|
||||||
for (const name in screenLayouts) {
|
for (const name in screenLayouts) {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
@ -863,6 +877,21 @@ Item {
|
||||||
markLayoutChanged(transaction, outputName);
|
markLayoutChanged(transaction, outputName);
|
||||||
applyLayoutTransaction(transaction);
|
applyLayoutTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
publishDynamicTilingLayoutState();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLayoutMode(outputName, mode) {
|
||||||
|
if (!outputName || layoutModes.indexOf(mode) < 0) return;
|
||||||
|
setLayoutModeForScreen(outputName, mode);
|
||||||
|
|
||||||
|
const windows = orderedWindowsForScreen(outputName);
|
||||||
|
if (windows.length > 0) {
|
||||||
|
setStableLayout(outputName, windows);
|
||||||
|
const transaction = createLayoutTransaction();
|
||||||
|
markLayoutChanged(transaction, outputName);
|
||||||
|
applyLayoutTransaction(transaction);
|
||||||
|
}
|
||||||
|
publishDynamicTilingLayoutState();
|
||||||
}
|
}
|
||||||
|
|
||||||
function containsLeaf(node, key) {
|
function containsLeaf(node, key) {
|
||||||
|
|
@ -1281,6 +1310,37 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function publishDynamicTilingLayoutState() {
|
||||||
|
if (!isConvergence()) {
|
||||||
|
const disabledState = "|0";
|
||||||
|
if (disabledState === lastPublishedLayoutState) return;
|
||||||
|
|
||||||
|
lastPublishedLayoutState = disabledState;
|
||||||
|
if (ShellSettings.Settings.reportDynamicTilingLayoutState !== undefined) {
|
||||||
|
ShellSettings.Settings.reportDynamicTilingLayoutState("", 0);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const outputName = outputNameForActiveWindow();
|
||||||
|
const mode = outputName !== "" ? layoutModeForScreen(outputName) : "";
|
||||||
|
const windowCount = outputName !== "" ? windowCountForLayout(outputName) : 0;
|
||||||
|
const serialized = mode + "|" + windowCount;
|
||||||
|
if (serialized === lastPublishedLayoutState) return;
|
||||||
|
|
||||||
|
lastPublishedLayoutState = serialized;
|
||||||
|
if (ShellSettings.Settings.reportDynamicTilingLayoutState !== undefined) {
|
||||||
|
ShellSettings.Settings.reportDynamicTilingLayoutState(mode, windowCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearDynamicTilingLayoutState() {
|
||||||
|
lastPublishedLayoutState = "__unpublished__";
|
||||||
|
if (ShellSettings.Settings.reportDynamicTilingLayoutState !== undefined) {
|
||||||
|
ShellSettings.Settings.reportDynamicTilingLayoutState("", 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function maximizedLayoutNameForWindow(win) {
|
function maximizedLayoutNameForWindow(win) {
|
||||||
const key = windowKey(win);
|
const key = windowKey(win);
|
||||||
if (!key) return "";
|
if (!key) return "";
|
||||||
|
|
@ -1682,6 +1742,19 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleLayoutModeRequest() {
|
||||||
|
const serial = ShellSettings.Settings.dynamicTilingLayoutRequestSerial;
|
||||||
|
if (serial === lastLayoutRequestSerial) return;
|
||||||
|
lastLayoutRequestSerial = serial;
|
||||||
|
|
||||||
|
if (!isConvergence()) return;
|
||||||
|
|
||||||
|
const mode = ShellSettings.Settings.dynamicTilingLayoutRequestMode;
|
||||||
|
if (layoutModes.indexOf(mode) < 0) return;
|
||||||
|
|
||||||
|
setLayoutMode(outputNameForActiveWindow(), mode);
|
||||||
|
}
|
||||||
|
|
||||||
function promoteWindow(win) {
|
function promoteWindow(win) {
|
||||||
if (!isTileable(win)) return;
|
if (!isTileable(win)) return;
|
||||||
|
|
||||||
|
|
@ -1771,10 +1844,12 @@ Item {
|
||||||
|
|
||||||
function onActiveWindowChanged() {
|
function onActiveWindowChanged() {
|
||||||
root.rememberFocusedWindow(KWinComponents.Workspace.activeWindow);
|
root.rememberFocusedWindow(KWinComponents.Workspace.activeWindow);
|
||||||
|
root.publishDynamicTilingLayoutState();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCurrentDesktopChanged() {
|
function onCurrentDesktopChanged() {
|
||||||
root.retileCurrentDesktopLayouts();
|
root.retileCurrentDesktopLayouts();
|
||||||
|
root.publishDynamicTilingLayoutState();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onScreensChanged() {
|
function onScreensChanged() {
|
||||||
|
|
@ -1788,6 +1863,7 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
root.scheduleRetileAll();
|
root.scheduleRetileAll();
|
||||||
|
root.publishDynamicTilingLayoutState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1801,6 +1877,7 @@ Item {
|
||||||
for (let i = 0; i < wins.length; i++) {
|
for (let i = 0; i < wins.length; i++) {
|
||||||
adoptWindow(wins[i]);
|
adoptWindow(wins[i]);
|
||||||
}
|
}
|
||||||
|
publishDynamicTilingLayoutState();
|
||||||
} else {
|
} else {
|
||||||
// Clear all tiles — the convergentwindows script will re-maximize
|
// Clear all tiles — the convergentwindows script will re-maximize
|
||||||
restoreAllMaximizedLayouts();
|
restoreAllMaximizedLayouts();
|
||||||
|
|
@ -1809,6 +1886,7 @@ Item {
|
||||||
screenLayoutModes = {};
|
screenLayoutModes = {};
|
||||||
lastFocusedWindowKeys = {};
|
lastFocusedWindowKeys = {};
|
||||||
maximizedLayouts = {};
|
maximizedLayouts = {};
|
||||||
|
clearDynamicTilingLayoutState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1820,11 +1898,13 @@ Item {
|
||||||
screenLayoutModes = {};
|
screenLayoutModes = {};
|
||||||
lastFocusedWindowKeys = {};
|
lastFocusedWindowKeys = {};
|
||||||
maximizedLayouts = {};
|
maximizedLayouts = {};
|
||||||
|
clearDynamicTilingLayoutState();
|
||||||
} else if (isConvergence()) {
|
} else if (isConvergence()) {
|
||||||
const wins = KWinComponents.Workspace.windows;
|
const wins = KWinComponents.Workspace.windows;
|
||||||
for (let i = 0; i < wins.length; i++) {
|
for (let i = 0; i < wins.length; i++) {
|
||||||
adoptWindow(wins[i]);
|
adoptWindow(wins[i]);
|
||||||
}
|
}
|
||||||
|
publishDynamicTilingLayoutState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1834,6 +1914,7 @@ Item {
|
||||||
for (let i = 0; i < wins.length; i++) {
|
for (let i = 0; i < wins.length; i++) {
|
||||||
adoptWindow(wins[i]);
|
adoptWindow(wins[i]);
|
||||||
}
|
}
|
||||||
|
publishDynamicTilingLayoutState();
|
||||||
} else {
|
} else {
|
||||||
// Tiling turned off — leave windows where they are.
|
// Tiling turned off — leave windows where they are.
|
||||||
restoreAllMaximizedLayouts();
|
restoreAllMaximizedLayouts();
|
||||||
|
|
@ -1842,12 +1923,17 @@ Item {
|
||||||
screenLayoutModes = {};
|
screenLayoutModes = {};
|
||||||
lastFocusedWindowKeys = {};
|
lastFocusedWindowKeys = {};
|
||||||
maximizedLayouts = {};
|
maximizedLayouts = {};
|
||||||
|
clearDynamicTilingLayoutState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDynamicTilingWindowRequestChanged() {
|
function onDynamicTilingWindowRequestChanged() {
|
||||||
root.handleWindowTilingRequest();
|
root.handleWindowTilingRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onDynamicTilingLayoutRequestChanged() {
|
||||||
|
root.handleLayoutModeRequest();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Drag handlers ─────────────────────────────────────────────────────
|
// ── Drag handlers ─────────────────────────────────────────────────────
|
||||||
|
|
@ -2165,6 +2251,9 @@ Item {
|
||||||
// ── Component setup ───────────────────────────────────────────────────
|
// ── Component setup ───────────────────────────────────────────────────
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
lastWindowRequestSerial = ShellSettings.Settings.dynamicTilingWindowRequestSerial;
|
||||||
|
lastLayoutRequestSerial = ShellSettings.Settings.dynamicTilingLayoutRequestSerial;
|
||||||
|
|
||||||
// Connect to existing windows
|
// Connect to existing windows
|
||||||
const wins = KWinComponents.Workspace.windows;
|
const wins = KWinComponents.Workspace.windows;
|
||||||
for (let i = 0; i < wins.length; i++) {
|
for (let i = 0; i < wins.length; i++) {
|
||||||
|
|
@ -2172,10 +2261,12 @@ Item {
|
||||||
}
|
}
|
||||||
rememberFocusedWindow(KWinComponents.Workspace.activeWindow);
|
rememberFocusedWindow(KWinComponents.Workspace.activeWindow);
|
||||||
publishDynamicTilingWindowState();
|
publishDynamicTilingWindowState();
|
||||||
|
publishDynamicTilingLayoutState();
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onDestruction: {
|
Component.onDestruction: {
|
||||||
disconnectDragHandlers();
|
disconnectDragHandlers();
|
||||||
clearDynamicTilingWindowState();
|
clearDynamicTilingWindowState();
|
||||||
|
clearDynamicTilingLayoutState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,10 @@ taskpanel="$repo_root/containments/taskpanel/qml/main.qml"
|
||||||
folio_main="$repo_root/containments/homescreens/folio/qml/main.qml"
|
folio_main="$repo_root/containments/homescreens/folio/qml/main.qml"
|
||||||
shellutil_header="$repo_root/components/mobileshell/shellutil.h"
|
shellutil_header="$repo_root/components/mobileshell/shellutil.h"
|
||||||
shellutil_cpp="$repo_root/components/mobileshell/shellutil.cpp"
|
shellutil_cpp="$repo_root/components/mobileshell/shellutil.cpp"
|
||||||
|
swipearea_header="$repo_root/components/mobileshell/components/swipearea.h"
|
||||||
|
swipearea_cpp="$repo_root/components/mobileshell/components/swipearea.cpp"
|
||||||
folio_home="$repo_root/containments/homescreens/folio/qml/FolioHomeScreen.qml"
|
folio_home="$repo_root/containments/homescreens/folio/qml/FolioHomeScreen.qml"
|
||||||
|
favourites_bar="$repo_root/containments/homescreens/folio/qml/FavouritesBar.qml"
|
||||||
folio_backend="$repo_root/containments/homescreens/folio/homescreen.h"
|
folio_backend="$repo_root/containments/homescreens/folio/homescreen.h"
|
||||||
folio_backend_cpp="$repo_root/containments/homescreens/folio/homescreen.cpp"
|
folio_backend_cpp="$repo_root/containments/homescreens/folio/homescreen.cpp"
|
||||||
action_content="$repo_root/components/mobileshell/qml/actiondrawer/private/ContentContainer.qml"
|
action_content="$repo_root/components/mobileshell/qml/actiondrawer/private/ContentContainer.qml"
|
||||||
|
|
@ -48,7 +51,7 @@ require_line "$constants" "readonly property real convergenceWorkspaceFrameRadiu
|
||||||
require_line "$panel" "readonly property real convergenceWorkspaceFrameThickness:"
|
require_line "$panel" "readonly property real convergenceWorkspaceFrameThickness:"
|
||||||
require_line "$panel" "root.panel.location = PlasmaCore.Types.TopEdge"
|
require_line "$panel" "root.panel.location = PlasmaCore.Types.TopEdge"
|
||||||
require_line "$panel" "root.panel.offset = 0"
|
require_line "$panel" "root.panel.offset = 0"
|
||||||
require_line "$panel" "root.panel.visibilityMode = (!ShellSettings.Settings.convergenceModeEnabled && ShellSettings.Settings.autoHidePanelsEnabled) ? 3 : 0"
|
require_line "$panel" "root.panel.visibilityMode = (ShellSettings.Settings.convergenceModeEnabled || ShellSettings.Settings.autoHidePanelsEnabled) ? 3 : 0"
|
||||||
require_line "$panel" "readonly property real topBarHeight: gamingMode ? 0 : MobileShell.Constants.topPanelHeight"
|
require_line "$panel" "readonly property real topBarHeight: gamingMode ? 0 : MobileShell.Constants.topPanelHeight"
|
||||||
require_line "$panel" "readonly property real topBarInputHeight: topBarHeight + convergenceWorkspaceFrameThickness"
|
require_line "$panel" "readonly property real topBarInputHeight: topBarHeight + convergenceWorkspaceFrameThickness"
|
||||||
require_line "$panel" "? 0"
|
require_line "$panel" "? 0"
|
||||||
|
|
@ -60,11 +63,34 @@ require_line "$panel" "visible: !ShellSettings.Settings.gamingModeEnabled"
|
||||||
|
|
||||||
require_line "$status_panel" "&& !ShellSettings.Settings.convergenceModeEnabled"
|
require_line "$status_panel" "&& !ShellSettings.Settings.convergenceModeEnabled"
|
||||||
require_line "$status_panel" "visible: !ShellSettings.Settings.convergenceModeEnabled"
|
require_line "$status_panel" "visible: !ShellSettings.Settings.convergenceModeEnabled"
|
||||||
|
require_line "$status_panel" "TaskManager.VirtualDesktopInfo {"
|
||||||
require_line "$status_panel" "MobileShell.ActionDrawerOpenSurface {"
|
require_line "$status_panel" "MobileShell.ActionDrawerOpenSurface {"
|
||||||
|
require_line "$status_panel" "virtualDesktopInfo: virtualDesktopInfo"
|
||||||
|
require_line "$action_open_surface" "property var virtualDesktopInfo: null"
|
||||||
|
require_line "$action_open_surface" "touchpadWorkspaceSwitchThreshold"
|
||||||
|
require_line "$action_open_surface" "function activateAdjacentWorkspace(direction)"
|
||||||
|
require_line "$action_open_surface" "virtualDesktopInfo.requestActivate(virtualDesktopInfo.desktopIds[targetIndex]);"
|
||||||
|
require_line "$action_open_surface" "absY >= touchpadDirectionLockThreshold && absY > absX * touchpadAxisDominance"
|
||||||
|
require_line "$action_open_surface" "workspaceScrollAvailable() && absX >= touchpadDirectionLockThreshold && absX > absY * touchpadAxisDominance"
|
||||||
require_line "$action_open_surface" "MouseArea {"
|
require_line "$action_open_surface" "MouseArea {"
|
||||||
require_line "$action_open_surface" "acceptedButtons: Qt.NoButton"
|
require_line "$action_open_surface" "acceptedButtons: Qt.NoButton"
|
||||||
require_line "$action_open_surface" "hoverEnabled: true"
|
require_line "$action_open_surface" "hoverEnabled: true"
|
||||||
|
require_line "$action_open_surface" "scrollGestureEnabled: false"
|
||||||
require_line "$action_open_surface" "cursorShape: ShellSettings.Settings.convergenceModeEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor"
|
require_line "$action_open_surface" "cursorShape: ShellSettings.Settings.convergenceModeEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor"
|
||||||
|
require_line "$swipearea_cpp" "const qreal WHEEL_STEP_PIXEL_DELTA = 48.0;"
|
||||||
|
require_line "$swipearea_cpp" "const int TOUCHPAD_SCROLL_END_TIMEOUT = 160;"
|
||||||
|
require_line "$swipearea_cpp" "const bool isTouchpad = event->deviceType() == QInputDevice::DeviceType::TouchPad;"
|
||||||
|
require_line "$swipearea_cpp" "startTouchpadScroll(event->points().first().position());"
|
||||||
|
require_line "$swipearea_cpp" "m_touchpadScrollEndTimer.start();"
|
||||||
|
require_line "$swipearea_cpp" "if (scrollDelta.isNull() && !event->angleDelta().isNull())"
|
||||||
|
require_line "$swipearea_cpp" "QWheelEvent::DefaultDeltasPerStep * WHEEL_STEP_PIXEL_DELTA"
|
||||||
|
require_line "$swipearea_header" "QTimer m_touchpadScrollEndTimer;"
|
||||||
|
require_line "$favourites_bar" "function handlePagerWheel(wheel)"
|
||||||
|
require_line "$favourites_bar" "onWheel: (wheel) => root.handlePagerWheel(wheel)"
|
||||||
|
require_line "$favourites_bar" "root.activateAdjacentDesktop(1)"
|
||||||
|
require_line "$folio_home" "onTouchpadScrollStarted: {"
|
||||||
|
require_line "$folio_home" "onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => {"
|
||||||
|
require_line "$folio_home" "if (!ShellSettings.Settings.convergenceModeEnabled) {"
|
||||||
|
|
||||||
require_line "$status_bar_template" "panel.location = \"top\";"
|
require_line "$status_bar_template" "panel.location = \"top\";"
|
||||||
|
|
||||||
|
|
@ -79,8 +105,7 @@ require_line "$folio_main" "id: convergenceChrome"
|
||||||
require_line "$folio_main" "LayerShell.Window.scope: \"convergence-chrome\""
|
require_line "$folio_main" "LayerShell.Window.scope: \"convergence-chrome\""
|
||||||
require_line "$folio_main" "height: Screen.height"
|
require_line "$folio_main" "height: Screen.height"
|
||||||
require_line "$folio_main" "MobileShell.StatusBar {"
|
require_line "$folio_main" "MobileShell.StatusBar {"
|
||||||
require_line "$folio_main" "MobileShell.ShellUtil.setInputRegions(convergenceChrome, ["
|
require_line "$folio_main" "MobileShell.ShellUtil.setInputRegions(convergenceChrome, regions)"
|
||||||
require_line "$folio_main" "readonly property real topBarHitHeight: topBarHeight + frameThickness"
|
|
||||||
require_line "$folio_main" "const topBarRegion = Qt.rect(0, 0, width, topBarHitHeight)"
|
require_line "$folio_main" "const topBarRegion = Qt.rect(0, 0, width, topBarHitHeight)"
|
||||||
require_line "$folio_main" "readonly property real dockHeight: MobileShell.Constants.convergenceDockHeight"
|
require_line "$folio_main" "readonly property real dockHeight: MobileShell.Constants.convergenceDockHeight"
|
||||||
require_line "$folio_main" "readonly property real revealStripHeight: MobileShell.Constants.convergenceDockRevealHeight"
|
require_line "$folio_main" "readonly property real revealStripHeight: MobileShell.Constants.convergenceDockRevealHeight"
|
||||||
|
|
@ -89,10 +114,29 @@ require_line "$folio_main" "id: workspaceFrame"
|
||||||
require_line "$folio_main" "readonly property real frameThickness: MobileShell.Constants.convergenceWorkspaceFrameThickness"
|
require_line "$folio_main" "readonly property real frameThickness: MobileShell.Constants.convergenceWorkspaceFrameThickness"
|
||||||
require_line "$folio_main" "readonly property real frameRadius:"
|
require_line "$folio_main" "readonly property real frameRadius:"
|
||||||
require_line "$folio_main" "readonly property real workAreaX: frameThickness"
|
require_line "$folio_main" "readonly property real workAreaX: frameThickness"
|
||||||
|
require_line "$folio_main" "readonly property real topBarHitHeight: topBarHeight + frameThickness"
|
||||||
require_line "$folio_main" "readonly property real workAreaY: topBarHitHeight"
|
require_line "$folio_main" "readonly property real workAreaY: topBarHitHeight"
|
||||||
require_line "$folio_main" "readonly property real workAreaWidth: Math.max(0, width - frameThickness * 2)"
|
require_line "$folio_main" "readonly property real workAreaWidth: Math.max(0, width - frameThickness * 2)"
|
||||||
require_line "$folio_main" "readonly property real workAreaHeight: Math.max(0, height - topBarHeight - dockHeight - frameThickness * 2)"
|
require_line "$folio_main" "readonly property real workAreaHeight: Math.max(0, height - topBarHeight - dockHeight - frameThickness * 2)"
|
||||||
require_line "$folio_main" "fillRule: ShapePath.OddEvenFill"
|
require_line "$folio_main" "fillRule: ShapePath.OddEvenFill"
|
||||||
|
require_line "$folio_main" "id: leftEdgeHoverArea"
|
||||||
|
require_line "$folio_main" "id: rightEdgeHoverArea"
|
||||||
|
require_line "$folio_main" "cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor"
|
||||||
|
require_line "$folio_main" "convergenceChrome.leftEdgeHovered = containsMouse"
|
||||||
|
require_line "$folio_main" "convergenceChrome.rightEdgeHovered = containsMouse"
|
||||||
|
require_line "$folio_main" "convergenceChrome.leftLauncherHovered = hovered"
|
||||||
|
require_line "$folio_main" "convergenceChrome.layoutMenuHovered = hovered"
|
||||||
|
|
||||||
|
if grep -Fq "root.panel.visibilityMode = (!ShellSettings.Settings.convergenceModeEnabled && ShellSettings.Settings.autoHidePanelsEnabled) ? 3 : 0" "$panel"; then
|
||||||
|
echo "Convergence panel hit area must not reserve extra top space" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -Fq "id: leftLauncherPointerTracker" "$folio_main"; then
|
||||||
|
echo "Folio convergence chrome must not cover the dock with a fullscreen hover tracker" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
require_line "$folio_main" "PathLine { x: convergenceChrome.width; y: convergenceChrome.height - convergenceChrome.dockHeight }"
|
require_line "$folio_main" "PathLine { x: convergenceChrome.width; y: convergenceChrome.height - convergenceChrome.dockHeight }"
|
||||||
require_line "$folio_main" "PathLine { x: 0; y: convergenceChrome.height - convergenceChrome.dockHeight }"
|
require_line "$folio_main" "PathLine { x: 0; y: convergenceChrome.height - convergenceChrome.dockHeight }"
|
||||||
require_line "$folio_main" "PathArc { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth - convergenceChrome.frameRadius; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }"
|
require_line "$folio_main" "PathArc { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth - convergenceChrome.frameRadius; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }"
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,13 @@ repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
|
||||||
effect_qml="$repo_root/kwin/effects/shift-tile-preview/contents/ui/main.qml"
|
effect_qml="$repo_root/kwin/effects/shift-tile-preview/contents/ui/main.qml"
|
||||||
effect_metadata="$repo_root/kwin/effects/shift-tile-preview/metadata.json"
|
effect_metadata="$repo_root/kwin/effects/shift-tile-preview/metadata.json"
|
||||||
|
animation_effect_js="$repo_root/kwin/effects/shift-tile-animations/contents/code/main.js"
|
||||||
|
animation_effect_metadata="$repo_root/kwin/effects/shift-tile-animations/metadata.json"
|
||||||
effects_cmake="$repo_root/kwin/effects/CMakeLists.txt"
|
effects_cmake="$repo_root/kwin/effects/CMakeLists.txt"
|
||||||
tiling_script="$repo_root/kwin/scripts/shift-tiling/contents/ui/main.qml"
|
tiling_script="$repo_root/kwin/scripts/shift-tiling/contents/ui/main.qml"
|
||||||
decoration_qml="$repo_root/kwin/decorations/org.shift.decoration/contents/ui/main.qml"
|
decoration_qml="$repo_root/kwin/decorations/org.shift.decoration/contents/ui/main.qml"
|
||||||
running_apps_panel="$repo_root/containments/homescreens/folio/qml/RunningAppsPanel.qml"
|
running_apps_panel="$repo_root/containments/homescreens/folio/qml/RunningAppsPanel.qml"
|
||||||
|
folio_main="$repo_root/containments/homescreens/folio/qml/main.qml"
|
||||||
env_config="$repo_root/envmanager/config.h"
|
env_config="$repo_root/envmanager/config.h"
|
||||||
|
|
||||||
require_line() {
|
require_line() {
|
||||||
|
|
@ -35,12 +38,17 @@ reject_line() {
|
||||||
}
|
}
|
||||||
|
|
||||||
require_line "$effects_cmake" "add_kwin_effect(shift-tile-preview shift-tile-preview)"
|
require_line "$effects_cmake" "add_kwin_effect(shift-tile-preview shift-tile-preview)"
|
||||||
|
require_line "$effects_cmake" "add_kwin_effect(shift-tile-animations shift-tile-animations)"
|
||||||
require_line "$effect_metadata" '"Id": "shift-tile-preview"'
|
require_line "$effect_metadata" '"Id": "shift-tile-preview"'
|
||||||
require_line "$effect_metadata" '"X-Plasma-API": "declarativescript"'
|
require_line "$effect_metadata" '"X-Plasma-API": "declarativescript"'
|
||||||
|
require_line "$animation_effect_metadata" '"Id": "shift-tile-animations"'
|
||||||
|
require_line "$animation_effect_metadata" '"X-Plasma-API": "javascript"'
|
||||||
|
|
||||||
require_line "$env_config" '{"shift-tilingEnabled", convergenceModeEnabled}'
|
require_line "$env_config" '{"shift-tilingEnabled", convergenceModeEnabled}'
|
||||||
require_line "$env_config" '{"shift-tile-previewEnabled", convergenceModeEnabled}'
|
require_line "$env_config" '{"shift-tile-previewEnabled", convergenceModeEnabled}'
|
||||||
|
require_line "$env_config" '{"shift-tile-animationsEnabled", convergenceModeEnabled}'
|
||||||
require_line "$env_config" '"shift-tile-preview"'
|
require_line "$env_config" '"shift-tile-preview"'
|
||||||
|
require_line "$env_config" '"shift-tile-animations"'
|
||||||
require_line "$env_config" '"shift-tiling"'
|
require_line "$env_config" '"shift-tiling"'
|
||||||
|
|
||||||
require_line "$effect_qml" "KWinComponents.SceneEffect"
|
require_line "$effect_qml" "KWinComponents.SceneEffect"
|
||||||
|
|
@ -49,17 +57,32 @@ require_line "$effect_qml" "ShellSettings.Settings.dynamicTilingEnabled"
|
||||||
require_line "$effect_qml" "interactiveMoveResizeStarted.connect"
|
require_line "$effect_qml" "interactiveMoveResizeStarted.connect"
|
||||||
require_line "$effect_qml" "interactiveMoveResizeStepped.connect"
|
require_line "$effect_qml" "interactiveMoveResizeStepped.connect"
|
||||||
require_line "$effect_qml" "interactiveMoveResizeFinished.connect"
|
require_line "$effect_qml" "interactiveMoveResizeFinished.connect"
|
||||||
require_line "$effect_qml" "Behavior on x"
|
|
||||||
require_line "$effect_qml" "Behavior on y"
|
|
||||||
require_line "$effect_qml" "Behavior on width"
|
|
||||||
require_line "$effect_qml" "Behavior on height"
|
|
||||||
require_line "$effect_qml" "showPreview(\"swap\""
|
require_line "$effect_qml" "showPreview(\"swap\""
|
||||||
require_line "$effect_qml" "showPreview(\"float\""
|
require_line "$effect_qml" "showPreview(\"float\""
|
||||||
require_line "$effect_qml" "showPreview(\"restore\""
|
require_line "$effect_qml" "showPreview(\"restore\""
|
||||||
|
require_line "$effect_qml" "KWinComponents.Workspace.showOutline(previewGeometry)"
|
||||||
|
require_line "$effect_qml" "KWinComponents.Workspace.hideOutline()"
|
||||||
|
reject_line "$effect_qml" "MobileShell.MotionNumberAnimation"
|
||||||
|
reject_line "$effect_qml" "MobileShell.MotionColorAnimation"
|
||||||
|
reject_line "$effect_qml" "disableEffectTimer"
|
||||||
|
|
||||||
|
require_line "$animation_effect_js" "class ShiftTileAnimationsEffect"
|
||||||
|
require_line "$animation_effect_js" "window.windowFrameGeometryChanged.connect"
|
||||||
|
require_line "$animation_effect_js" "window.windowStartUserMovedResized.connect"
|
||||||
|
require_line "$animation_effect_js" "window.windowFinishUserMovedResized.connect"
|
||||||
|
require_line "$animation_effect_js" "animationTime("
|
||||||
|
require_line "$animation_effect_js" "type: Effect.Size"
|
||||||
|
require_line "$animation_effect_js" "type: Effect.Translation"
|
||||||
|
require_line "$animation_effect_js" "curve: QEasingCurve.OutCubic"
|
||||||
|
require_line "$animation_effect_js" "window.shiftTileUserMoveResize"
|
||||||
|
|
||||||
require_line "$tiling_script" "readonly property int maxWindowsPerPage: 4"
|
require_line "$tiling_script" "readonly property int maxWindowsPerPage: 4"
|
||||||
require_line "$tiling_script" "readonly property real stablePrimaryRatio: 0.58"
|
require_line "$tiling_script" "readonly property real stablePrimaryRatio: 0.58"
|
||||||
require_line "$tiling_script" "readonly property var layoutModes: [\"master\", \"columns\", \"rows\"]"
|
require_line "$tiling_script" "readonly property var layoutModes: [\"master\", \"columns\", \"rows\"]"
|
||||||
|
require_line "$tiling_script" "property int lastWindowRequestSerial: -1"
|
||||||
|
require_line "$tiling_script" "property int lastLayoutRequestSerial: -1"
|
||||||
|
require_line "$tiling_script" "lastWindowRequestSerial = ShellSettings.Settings.dynamicTilingWindowRequestSerial"
|
||||||
|
require_line "$tiling_script" "lastLayoutRequestSerial = ShellSettings.Settings.dynamicTilingLayoutRequestSerial"
|
||||||
require_line "$tiling_script" "function desktopKey(desktop)"
|
require_line "$tiling_script" "function desktopKey(desktop)"
|
||||||
require_line "$tiling_script" "function desktopForWindow(win)"
|
require_line "$tiling_script" "function desktopForWindow(win)"
|
||||||
require_line "$tiling_script" "function normalizeWindowDesktopScope(win)"
|
require_line "$tiling_script" "function normalizeWindowDesktopScope(win)"
|
||||||
|
|
@ -126,6 +149,17 @@ require_line "$decoration_qml" "borders.bottom = normalCornerRadius;"
|
||||||
require_line "$decoration_qml" "PathArc { x: root.width - root.cornerRadius; y: root.height; radiusX: root.cornerRadius; radiusY: root.cornerRadius }"
|
require_line "$decoration_qml" "PathArc { x: root.width - root.cornerRadius; y: root.height; radiusX: root.cornerRadius; radiusY: root.cornerRadius }"
|
||||||
require_line "$decoration_qml" "PathArc { x: 0; y: root.height - root.cornerRadius; radiusX: root.cornerRadius; radiusY: root.cornerRadius }"
|
require_line "$decoration_qml" "PathArc { x: 0; y: root.height - root.cornerRadius; radiusX: root.cornerRadius; radiusY: root.cornerRadius }"
|
||||||
|
|
||||||
|
require_line "$folio_main" "readonly property int layoutMenuWindowCount: Math.max(0, ShellSettings.Settings.dynamicTilingLayoutWindowCount)"
|
||||||
|
require_line "$folio_main" "&& layoutMenuWindowCount >= 2"
|
||||||
|
require_line "$folio_main" "if (layoutMenuEnabled) {"
|
||||||
|
require_line "$folio_main" "regions.push(rightEdgeRegion)"
|
||||||
|
require_line "$folio_main" "if (layoutMenuEnabled && layoutMenuOpen)"
|
||||||
|
require_line "$folio_main" "rightEdgeHovered = false"
|
||||||
|
require_line "$folio_main" "layoutMenuHovered = false"
|
||||||
|
require_line "$folio_main" "id: rightEdgeHoverArea"
|
||||||
|
require_line "$folio_main" "convergenceChrome.rightEdgeHovered = containsMouse"
|
||||||
|
require_line "$folio_main" "width: convergenceChrome.layoutMenuEnabled ? convergenceChrome.rightEdgeHotzoneWidth : 0"
|
||||||
|
|
||||||
running_panel_group_disabled_count="$(grep -F "groupMode: TaskManager.TasksModel.GroupDisabled" "$running_apps_panel" | wc -l)"
|
running_panel_group_disabled_count="$(grep -F "groupMode: TaskManager.TasksModel.GroupDisabled" "$running_apps_panel" | wc -l)"
|
||||||
if [[ "$running_panel_group_disabled_count" -ne 2 ]]; then
|
if [[ "$running_panel_group_disabled_count" -ne 2 ]]; then
|
||||||
echo "Expected the Folio Running panel to disable grouping for both task models; found $running_panel_group_disabled_count" >&2
|
echo "Expected the Folio Running panel to disable grouping for both task models; found $running_panel_group_disabled_count" >&2
|
||||||
|
|
@ -155,6 +189,8 @@ reject_line "$tiling_script" "function tileInsertDirection(cursor, rect)"
|
||||||
reject_line "$tiling_script" "showDragOutline(\"insert\""
|
reject_line "$tiling_script" "showDragOutline(\"insert\""
|
||||||
reject_line "$tiling_script" "KWinComponents.Workspace.showOutline(dragOutlineRect)"
|
reject_line "$tiling_script" "KWinComponents.Workspace.showOutline(dragOutlineRect)"
|
||||||
reject_line "$tiling_script" "KWinComponents.Workspace.hideOutline()"
|
reject_line "$tiling_script" "KWinComponents.Workspace.hideOutline()"
|
||||||
|
reject_line "$tiling_script" "property int lastWindowRequestSerial: ShellSettings.Settings.dynamicTilingWindowRequestSerial"
|
||||||
|
reject_line "$tiling_script" "property int lastLayoutRequestSerial: ShellSettings.Settings.dynamicTilingLayoutRequestSerial"
|
||||||
reject_line "$effect_qml" "effect.visible = true"
|
reject_line "$effect_qml" "effect.visible = true"
|
||||||
|
|
||||||
printf '%s\n' 'dynamic-tiles-motion-ok'
|
printf '%s\n' 'dynamic-tiles-motion-ok'
|
||||||
Loading…
Reference in a new issue