mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-06-25 07:37:42 +00:00
Compare commits
No commits in common. "3d32eaf5b72c6ed66560ecc7a3288d2dd3c085d4" and "5c5d962b3493c9695fde29ee06f449bab69e5618" have entirely different histories.
3d32eaf5b7
...
5c5d962b34
64 changed files with 270 additions and 3408 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -15,12 +15,10 @@ build
|
||||||
/cmake-build*
|
/cmake-build*
|
||||||
.cache
|
.cache
|
||||||
.vscode/
|
.vscode/
|
||||||
*.code-workspace
|
|
||||||
|
|
||||||
.kdev4/
|
.kdev4/
|
||||||
*.kdev4
|
*.kdev4
|
||||||
/build*
|
/build*
|
||||||
.prefix/
|
.prefix/
|
||||||
.preview-config/
|
.preview-config/
|
||||||
.preview-data/
|
|
||||||
preview.sh
|
preview.sh
|
||||||
|
|
|
||||||
|
|
@ -11,3 +11,7 @@ Files: HACKING.md
|
||||||
Copyright: Plasma-Mobile contributors
|
Copyright: Plasma-Mobile contributors
|
||||||
License: CC-BY-SA-4.0
|
License: CC-BY-SA-4.0
|
||||||
|
|
||||||
|
Files: Nebra.code-workspace
|
||||||
|
Copyright: Shift contributors
|
||||||
|
License: CC0-1.0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,6 @@ find_package(KF6 ${KF6_MIN_VERSION} REQUIRED COMPONENTS
|
||||||
find_package(Plasma CONFIG REQUIRED)
|
find_package(Plasma CONFIG REQUIRED)
|
||||||
find_package(PlasmaQuick CONFIG REQUIRED)
|
find_package(PlasmaQuick CONFIG REQUIRED)
|
||||||
find_package(PlasmaActivities CONFIG REQUIRED)
|
find_package(PlasmaActivities CONFIG REQUIRED)
|
||||||
find_package(LibTaskManager CONFIG REQUIRED)
|
|
||||||
find_package(KF6Screen CONFIG REQUIRED)
|
find_package(KF6Screen CONFIG REQUIRED)
|
||||||
find_package(KWayland CONFIG REQUIRED)
|
find_package(KWayland CONFIG REQUIRED)
|
||||||
find_package(KPipeWire ${PROJECT_DEP_VERSION} REQUIRED)
|
find_package(KPipeWire ${PROJECT_DEP_VERSION} REQUIRED)
|
||||||
|
|
@ -138,19 +137,12 @@ include(CheckIncludeFiles)
|
||||||
|
|
||||||
ecm_find_qmlmodule(org.kde.pipewire 0.1)
|
ecm_find_qmlmodule(org.kde.pipewire 0.1)
|
||||||
|
|
||||||
set(SHIFT_DEFAULT_WALLPAPER_URL "file://${KDE_INSTALL_FULL_WALLPAPERDIR}/SHIFT/")
|
plasma_install_package(lookandfeel org.kde.breeze.mobile look-and-feel lookandfeel)
|
||||||
set(SHIFT_SHELL_PACKAGE_DIR "${CMAKE_CURRENT_BINARY_DIR}/shell")
|
plasma_install_package(shell org.kde.plasma.mobileshell shells)
|
||||||
file(REMOVE_RECURSE "${SHIFT_SHELL_PACKAGE_DIR}")
|
|
||||||
file(COPY shell/ DESTINATION "${SHIFT_SHELL_PACKAGE_DIR}" PATTERN "layout.js.in" EXCLUDE)
|
|
||||||
configure_file(shell/contents/layout.js.in "${SHIFT_SHELL_PACKAGE_DIR}/contents/layout.js" @ONLY)
|
|
||||||
|
|
||||||
plasma_install_package(lookandfeel org.shift.mobile look-and-feel lookandfeel)
|
|
||||||
plasma_install_package(${SHIFT_SHELL_PACKAGE_DIR} org.kde.plasma.mobileshell shells)
|
|
||||||
|
|
||||||
add_subdirectory(bin)
|
add_subdirectory(bin)
|
||||||
add_subdirectory(color-schemes)
|
add_subdirectory(color-schemes)
|
||||||
add_subdirectory(icons)
|
add_subdirectory(icons)
|
||||||
add_subdirectory(wallpapers)
|
|
||||||
add_subdirectory(components)
|
add_subdirectory(components)
|
||||||
add_subdirectory(containments)
|
add_subdirectory(containments)
|
||||||
add_subdirectory(devices)
|
add_subdirectory(devices)
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,8 @@ complete Plasma Mobile runtime so all QML imports resolve:
|
||||||
sudo zypper install --no-confirm \
|
sudo zypper install --no-confirm \
|
||||||
plasma6-mobile plasma6-workspace plasma6-nano plasma6-nm plasma6-pa \
|
plasma6-mobile plasma6-workspace plasma6-nano plasma6-nm plasma6-pa \
|
||||||
layer-shell-qt6-imports kf6-bluez-qt-imports \
|
layer-shell-qt6-imports kf6-bluez-qt-imports \
|
||||||
kf6-networkmanager-qt-imports
|
kf6-networkmanager-qt-imports \
|
||||||
|
breeze6-wallpapers plasma6-workspace-wallpapers
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
7
Nebra.code-workspace
Normal file
7
Nebra.code-workspace
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -29,7 +29,7 @@ IntensityEffect=0
|
||||||
|
|
||||||
[Colors:Button]
|
[Colors:Button]
|
||||||
BackgroundAlternate=16,82,74
|
BackgroundAlternate=16,82,74
|
||||||
BackgroundNormal=34,37,50
|
BackgroundNormal=41,44,48
|
||||||
DecorationFocus=46,184,168
|
DecorationFocus=46,184,168
|
||||||
DecorationHover=46,184,168
|
DecorationHover=46,184,168
|
||||||
ForegroundActive=46,184,168
|
ForegroundActive=46,184,168
|
||||||
|
|
@ -43,7 +43,7 @@ ForegroundVisited=155,89,182
|
||||||
|
|
||||||
[Colors:Complementary]
|
[Colors:Complementary]
|
||||||
BackgroundAlternate=16,82,74
|
BackgroundAlternate=16,82,74
|
||||||
BackgroundNormal=24,27,38
|
BackgroundNormal=32,35,38
|
||||||
DecorationFocus=46,184,168
|
DecorationFocus=46,184,168
|
||||||
DecorationHover=46,184,168
|
DecorationHover=46,184,168
|
||||||
ForegroundActive=46,184,168
|
ForegroundActive=46,184,168
|
||||||
|
|
@ -56,8 +56,8 @@ ForegroundPositive=39,174,96
|
||||||
ForegroundVisited=155,89,182
|
ForegroundVisited=155,89,182
|
||||||
|
|
||||||
[Colors:Header]
|
[Colors:Header]
|
||||||
BackgroundAlternate=24,27,38
|
BackgroundAlternate=32,35,38
|
||||||
BackgroundNormal=34,37,50
|
BackgroundNormal=41,44,48
|
||||||
DecorationFocus=46,184,168
|
DecorationFocus=46,184,168
|
||||||
DecorationHover=46,184,168
|
DecorationHover=46,184,168
|
||||||
ForegroundActive=46,184,168
|
ForegroundActive=46,184,168
|
||||||
|
|
@ -70,8 +70,8 @@ ForegroundPositive=39,174,96
|
||||||
ForegroundVisited=155,89,182
|
ForegroundVisited=155,89,182
|
||||||
|
|
||||||
[Colors:Header][Inactive]
|
[Colors:Header][Inactive]
|
||||||
BackgroundAlternate=34,37,50
|
BackgroundAlternate=41,44,48
|
||||||
BackgroundNormal=24,27,38
|
BackgroundNormal=32,35,38
|
||||||
DecorationFocus=46,184,168
|
DecorationFocus=46,184,168
|
||||||
DecorationHover=46,184,168
|
DecorationHover=46,184,168
|
||||||
ForegroundActive=46,184,168
|
ForegroundActive=46,184,168
|
||||||
|
|
@ -98,8 +98,8 @@ ForegroundPositive=23,104,57
|
||||||
ForegroundVisited=155,89,182
|
ForegroundVisited=155,89,182
|
||||||
|
|
||||||
[Colors:Tooltip]
|
[Colors:Tooltip]
|
||||||
BackgroundAlternate=24,27,38
|
BackgroundAlternate=32,35,38
|
||||||
BackgroundNormal=34,37,50
|
BackgroundNormal=41,44,48
|
||||||
DecorationFocus=46,184,168
|
DecorationFocus=46,184,168
|
||||||
DecorationHover=46,184,168
|
DecorationHover=46,184,168
|
||||||
ForegroundActive=46,184,168
|
ForegroundActive=46,184,168
|
||||||
|
|
@ -112,8 +112,8 @@ ForegroundPositive=39,174,96
|
||||||
ForegroundVisited=155,89,182
|
ForegroundVisited=155,89,182
|
||||||
|
|
||||||
[Colors:View]
|
[Colors:View]
|
||||||
BackgroundAlternate=20,22,32
|
BackgroundAlternate=29,31,34
|
||||||
BackgroundNormal=14,15,22
|
BackgroundNormal=20,22,24
|
||||||
DecorationFocus=46,184,168
|
DecorationFocus=46,184,168
|
||||||
DecorationHover=46,184,168
|
DecorationHover=46,184,168
|
||||||
ForegroundActive=46,184,168
|
ForegroundActive=46,184,168
|
||||||
|
|
@ -126,8 +126,8 @@ ForegroundPositive=39,174,96
|
||||||
ForegroundVisited=155,89,182
|
ForegroundVisited=155,89,182
|
||||||
|
|
||||||
[Colors:Window]
|
[Colors:Window]
|
||||||
BackgroundAlternate=34,37,50
|
BackgroundAlternate=41,44,48
|
||||||
BackgroundNormal=24,27,38
|
BackgroundNormal=32,35,38
|
||||||
DecorationFocus=46,184,168
|
DecorationFocus=46,184,168
|
||||||
DecorationHover=46,184,168
|
DecorationHover=46,184,168
|
||||||
ForegroundActive=46,184,168
|
ForegroundActive=46,184,168
|
||||||
|
|
@ -148,9 +148,9 @@ shadeSortColumn=true
|
||||||
contrast=4
|
contrast=4
|
||||||
|
|
||||||
[WM]
|
[WM]
|
||||||
activeBackground=28,33,46
|
activeBackground=39,44,49
|
||||||
activeBlend=252,252,252
|
activeBlend=252,252,252
|
||||||
activeForeground=252,252,252
|
activeForeground=252,252,252
|
||||||
inactiveBackground=22,27,38
|
inactiveBackground=32,36,40
|
||||||
inactiveBlend=161,169,177
|
inactiveBlend=161,169,177
|
||||||
inactiveForeground=161,169,177
|
inactiveForeground=161,169,177
|
||||||
|
|
|
||||||
|
|
@ -357,21 +357,6 @@ void GameLauncherProvider::refresh()
|
||||||
|
|
||||||
m_allGames.clear();
|
m_allGames.clear();
|
||||||
|
|
||||||
// Detect which third-party launchers are installed
|
|
||||||
const bool steamWas = m_steamAvailable;
|
|
||||||
const bool lutrisWas = m_lutrisAvailable;
|
|
||||||
const bool heroicWas = m_heroicAvailable;
|
|
||||||
m_steamAvailable = detectLauncher(QStringLiteral("steam"),
|
|
||||||
{QStringLiteral("com.valvesoftware.Steam")},
|
|
||||||
{QDir::homePath() + QStringLiteral("/.steam/steam"),
|
|
||||||
QDir::homePath() + QStringLiteral("/.local/share/Steam"),
|
|
||||||
QDir::homePath() + QStringLiteral("/.var/app/com.valvesoftware.Steam/.local/share/Steam")});
|
|
||||||
m_lutrisAvailable =
|
|
||||||
detectLauncher(QStringLiteral("lutris"), {QStringLiteral("net.lutris.Lutris")}, {QDir::homePath() + QStringLiteral("/.local/share/lutris")});
|
|
||||||
m_heroicAvailable =
|
|
||||||
detectLauncher(QStringLiteral("heroic"), {QStringLiteral("com.heroicgameslauncher.hgl")}, {QDir::homePath() + QStringLiteral("/.config/heroic")});
|
|
||||||
if (m_steamAvailable != steamWas || m_lutrisAvailable != lutrisWas || m_heroicAvailable != heroicWas)
|
|
||||||
Q_EMIT launcherAvailabilityChanged();
|
|
||||||
loadDesktopGames();
|
loadDesktopGames();
|
||||||
loadSteamGames();
|
loadSteamGames();
|
||||||
loadLutrisGames();
|
loadLutrisGames();
|
||||||
|
|
@ -919,37 +904,6 @@ bool GameLauncherProvider::mangohudAvailable() const
|
||||||
return m_mangohudAvailable;
|
return m_mangohudAvailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GameLauncherProvider::steamAvailable() const
|
|
||||||
{
|
|
||||||
return m_steamAvailable;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GameLauncherProvider::lutrisAvailable() const
|
|
||||||
{
|
|
||||||
return m_lutrisAvailable;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GameLauncherProvider::heroicAvailable() const
|
|
||||||
{
|
|
||||||
return m_heroicAvailable;
|
|
||||||
}
|
|
||||||
|
|
||||||
// static
|
|
||||||
bool GameLauncherProvider::detectLauncher(const QString &executable, const QStringList &flatpakAppIds, const QStringList &dataDirs)
|
|
||||||
{
|
|
||||||
if (!QStandardPaths::findExecutable(executable).isEmpty())
|
|
||||||
return true;
|
|
||||||
for (const QString &appId : flatpakAppIds) {
|
|
||||||
if (QDir(QDir::homePath() + QStringLiteral("/.var/app/") + appId).exists())
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
for (const QString &dir : dataDirs) {
|
|
||||||
if (QDir(dir).exists())
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int GameLauncherProvider::fpsLimit() const
|
int GameLauncherProvider::fpsLimit() const
|
||||||
{
|
{
|
||||||
return m_fpsLimit;
|
return m_fpsLimit;
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,6 @@ class GameLauncherProvider : public QAbstractListModel
|
||||||
Q_PROPERTY(QString sourceFilter READ sourceFilter WRITE setSourceFilter NOTIFY sourceFilterChanged)
|
Q_PROPERTY(QString sourceFilter READ sourceFilter WRITE setSourceFilter NOTIFY sourceFilterChanged)
|
||||||
Q_PROPERTY(bool overlayEnabled READ overlayEnabled WRITE setOverlayEnabled NOTIFY overlayEnabledChanged)
|
Q_PROPERTY(bool overlayEnabled READ overlayEnabled WRITE setOverlayEnabled NOTIFY overlayEnabledChanged)
|
||||||
Q_PROPERTY(bool mangohudAvailable READ mangohudAvailable NOTIFY mangohudAvailableChanged)
|
Q_PROPERTY(bool mangohudAvailable READ mangohudAvailable NOTIFY mangohudAvailableChanged)
|
||||||
Q_PROPERTY(bool steamAvailable READ steamAvailable NOTIFY launcherAvailabilityChanged)
|
|
||||||
Q_PROPERTY(bool lutrisAvailable READ lutrisAvailable NOTIFY launcherAvailabilityChanged)
|
|
||||||
Q_PROPERTY(bool heroicAvailable READ heroicAvailable NOTIFY launcherAvailabilityChanged)
|
|
||||||
Q_PROPERTY(int fpsLimit READ fpsLimit WRITE setFpsLimit NOTIFY fpsLimitChanged)
|
Q_PROPERTY(int fpsLimit READ fpsLimit WRITE setFpsLimit NOTIFY fpsLimitChanged)
|
||||||
Q_PROPERTY(bool launchPending READ launchPending NOTIFY launchPendingChanged)
|
Q_PROPERTY(bool launchPending READ launchPending NOTIFY launchPendingChanged)
|
||||||
Q_PROPERTY(QString pendingLaunchName READ pendingLaunchName NOTIFY launchPendingChanged)
|
Q_PROPERTY(QString pendingLaunchName READ pendingLaunchName NOTIFY launchPendingChanged)
|
||||||
|
|
@ -64,9 +61,6 @@ public:
|
||||||
bool overlayEnabled() const;
|
bool overlayEnabled() const;
|
||||||
void setOverlayEnabled(bool enabled);
|
void setOverlayEnabled(bool enabled);
|
||||||
bool mangohudAvailable() const;
|
bool mangohudAvailable() const;
|
||||||
bool steamAvailable() const;
|
|
||||||
bool lutrisAvailable() const;
|
|
||||||
bool heroicAvailable() const;
|
|
||||||
int fpsLimit() const;
|
int fpsLimit() const;
|
||||||
void setFpsLimit(int limit);
|
void setFpsLimit(int limit);
|
||||||
bool launchPending() const;
|
bool launchPending() const;
|
||||||
|
|
@ -95,7 +89,6 @@ Q_SIGNALS:
|
||||||
void sourceFilterChanged();
|
void sourceFilterChanged();
|
||||||
void overlayEnabledChanged();
|
void overlayEnabledChanged();
|
||||||
void mangohudAvailableChanged();
|
void mangohudAvailableChanged();
|
||||||
void launcherAvailabilityChanged();
|
|
||||||
void fpsLimitChanged();
|
void fpsLimitChanged();
|
||||||
void launchPendingChanged();
|
void launchPendingChanged();
|
||||||
void lastLaunchErrorChanged();
|
void lastLaunchErrorChanged();
|
||||||
|
|
@ -144,11 +137,6 @@ private:
|
||||||
int m_fpsLimit = 0;
|
int m_fpsLimit = 0;
|
||||||
bool m_mangohudAvailable = false;
|
bool m_mangohudAvailable = false;
|
||||||
QString m_mangohudPath;
|
QString m_mangohudPath;
|
||||||
bool m_steamAvailable = false;
|
|
||||||
bool m_lutrisAvailable = false;
|
|
||||||
bool m_heroicAvailable = false;
|
|
||||||
|
|
||||||
static bool detectLauncher(const QString &executable, const QStringList &flatpakAppIds, const QStringList &dataDirs);
|
|
||||||
QSet<QString> m_pinnedGames;
|
QSet<QString> m_pinnedGames;
|
||||||
bool m_launchPending = false;
|
bool m_launchPending = false;
|
||||||
QString m_pendingLaunchName;
|
QString m_pendingLaunchName;
|
||||||
|
|
|
||||||
|
|
@ -41,21 +41,11 @@ MobileShell.BaseItem {
|
||||||
|
|
||||||
readonly property color enabledButtonBorderColor: Qt.darker(Kirigami.Theme.highlightColor, 1.25)
|
readonly property color enabledButtonBorderColor: Qt.darker(Kirigami.Theme.highlightColor, 1.25)
|
||||||
readonly property color disabledButtonBorderColor: separatorColorHelper(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.2)
|
readonly property color disabledButtonBorderColor: separatorColorHelper(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.2)
|
||||||
readonly property color enabledButtonColor: mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.25)
|
readonly property color enabledButtonColor: Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.highlightColor, Kirigami.Theme.backgroundColor, 0.6)
|
||||||
readonly property color enabledButtonHoverColor: mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.32)
|
readonly property color enabledButtonPressedColor: Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.highlightColor, Kirigami.Theme.backgroundColor, 0.4);
|
||||||
readonly property color enabledButtonPressedColor: mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.12);
|
readonly property color disabledButtonColor: Kirigami.Theme.backgroundColor
|
||||||
readonly property color disabledButtonColor: Kirigami.Theme.alternateBackgroundColor
|
|
||||||
readonly property color disabledButtonHoverColor: mixColor(Kirigami.Theme.alternateBackgroundColor, Kirigami.Theme.textColor, 0.06)
|
|
||||||
readonly property color disabledButtonPressedColor: Qt.darker(disabledButtonColor, 1.1)
|
readonly property color disabledButtonPressedColor: Qt.darker(disabledButtonColor, 1.1)
|
||||||
|
|
||||||
function mixColor(base, overlay, ratio) {
|
|
||||||
return Qt.rgba(
|
|
||||||
base.r + (overlay.r - base.r) * ratio,
|
|
||||||
base.g + (overlay.g - base.g) * ratio,
|
|
||||||
base.b + (overlay.b - base.b) * ratio,
|
|
||||||
base.a + (overlay.a - base.a) * ratio)
|
|
||||||
}
|
|
||||||
|
|
||||||
function separatorColorHelper(bg, fg, baseRatio) {
|
function separatorColorHelper(bg, fg, baseRatio) {
|
||||||
if (Kirigami.ColorUtils.brightnessForColor(bg) === Kirigami.ColorUtils.Light) {
|
if (Kirigami.ColorUtils.brightnessForColor(bg) === Kirigami.ColorUtils.Light) {
|
||||||
return Kirigami.ColorUtils.linearInterpolation(bg, fg, baseRatio);
|
return Kirigami.ColorUtils.linearInterpolation(bg, fg, baseRatio);
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ QuickSettingsDelegate {
|
||||||
|
|
||||||
padding: Kirigami.Units.smallSpacing * 2
|
padding: Kirigami.Units.smallSpacing * 2
|
||||||
iconItem: icon
|
iconItem: icon
|
||||||
readonly property int tileRadius: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
// scale animation on press
|
// scale animation on press
|
||||||
zoomScale: (ShellSettings.Settings.animationsEnabled && mouseArea.pressed) ? 0.9 : 1
|
zoomScale: (ShellSettings.Settings.animationsEnabled && mouseArea.pressed) ? 0.9 : 1
|
||||||
|
|
@ -34,34 +33,23 @@ QuickSettingsDelegate {
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
height: parent.height
|
height: parent.height
|
||||||
|
|
||||||
radius: root.tileRadius
|
radius: Kirigami.Units.cornerRadius
|
||||||
color: Qt.rgba(0, 0, 0, root.enabled ? 0.12 : 0.08)
|
color: Qt.rgba(0, 0, 0, 0.075)
|
||||||
}
|
}
|
||||||
|
|
||||||
// background color
|
// background color
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: tileRect
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
radius: root.tileRadius
|
radius: Kirigami.Units.cornerRadius
|
||||||
border.pixelAligned: false
|
border.pixelAligned: false
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: root.enabled ? root.enabledButtonBorderColor : root.disabledButtonBorderColor
|
border.color: root.enabled ? root.enabledButtonBorderColor : root.disabledButtonBorderColor
|
||||||
color: {
|
color: {
|
||||||
if (root.enabled) {
|
if (root.enabled) {
|
||||||
if (mouseArea.pressed) {
|
return mouseArea.pressed ? root.enabledButtonPressedColor : root.enabledButtonColor
|
||||||
return root.enabledButtonPressedColor
|
|
||||||
}
|
|
||||||
return mouseArea.containsMouse ? root.enabledButtonHoverColor : root.enabledButtonColor
|
|
||||||
} else {
|
} else {
|
||||||
if (mouseArea.pressed) {
|
return mouseArea.pressed ? root.disabledButtonPressedColor : root.disabledButtonColor
|
||||||
return root.disabledButtonPressedColor
|
|
||||||
}
|
}
|
||||||
return mouseArea.containsMouse ? root.disabledButtonHoverColor : root.disabledButtonColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation { duration: ShellSettings.Settings.animationsEnabled ? Kirigami.Units.shortDuration : 0; easing.type: Easing.OutCubic }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +60,6 @@ QuickSettingsDelegate {
|
||||||
|
|
||||||
contentItem: MouseArea {
|
contentItem: MouseArea {
|
||||||
id: mouseArea
|
id: mouseArea
|
||||||
hoverEnabled: true
|
|
||||||
|
|
||||||
onPressed: haptics.buttonVibrate()
|
onPressed: haptics.buttonVibrate()
|
||||||
onClicked: root.delegateClick()
|
onClicked: root.delegateClick()
|
||||||
|
|
|
||||||
|
|
@ -45,20 +45,10 @@ QuickSettingsDelegate {
|
||||||
border.color: root.enabled ? root.enabledButtonBorderColor : root.disabledButtonBorderColor
|
border.color: root.enabled ? root.enabledButtonBorderColor : root.disabledButtonBorderColor
|
||||||
color: {
|
color: {
|
||||||
if (root.enabled) {
|
if (root.enabled) {
|
||||||
if (mouseArea.pressed) {
|
return mouseArea.pressed ? root.enabledButtonPressedColor : root.enabledButtonColor
|
||||||
return root.enabledButtonPressedColor
|
|
||||||
}
|
|
||||||
return mouseArea.containsMouse ? root.enabledButtonHoverColor : root.enabledButtonColor
|
|
||||||
} else {
|
} else {
|
||||||
if (mouseArea.pressed) {
|
return mouseArea.pressed ? root.disabledButtonPressedColor : root.disabledButtonColor
|
||||||
return root.disabledButtonPressedColor
|
|
||||||
}
|
}
|
||||||
return mouseArea.containsMouse ? root.disabledButtonHoverColor : root.disabledButtonColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation { duration: ShellSettings.Settings.animationsEnabled ? Kirigami.Units.shortDuration : 0; easing.type: Easing.OutCubic }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -69,7 +59,6 @@ QuickSettingsDelegate {
|
||||||
|
|
||||||
contentItem: MouseArea {
|
contentItem: MouseArea {
|
||||||
id: mouseArea
|
id: mouseArea
|
||||||
hoverEnabled: true
|
|
||||||
|
|
||||||
onPressed: haptics.buttonVibrate();
|
onPressed: haptics.buttonVibrate();
|
||||||
onClicked: root.delegateClick()
|
onClicked: root.delegateClick()
|
||||||
|
|
|
||||||
|
|
@ -30,14 +30,14 @@ Item {
|
||||||
Kirigami.Theme.inherit: false
|
Kirigami.Theme.inherit: false
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.Button
|
Kirigami.Theme.colorSet: Kirigami.Theme.Button
|
||||||
|
|
||||||
readonly property int rowRadius: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
|
// ── Palette (shared with tile delegates) ────────────────────────────
|
||||||
readonly property color enabledBg: mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.25)
|
readonly property color enabledBg: Kirigami.ColorUtils.tintWithAlpha(
|
||||||
readonly property color enabledBgHover: mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.32)
|
Kirigami.Theme.highlightColor, Kirigami.Theme.backgroundColor, 0.6)
|
||||||
readonly property color enabledBgPressed: mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.12)
|
readonly property color enabledBgPressed: Kirigami.ColorUtils.tintWithAlpha(
|
||||||
|
Kirigami.Theme.highlightColor, Kirigami.Theme.backgroundColor, 0.4)
|
||||||
readonly property color enabledBorder: Qt.darker(Kirigami.Theme.highlightColor, 1.25)
|
readonly property color enabledBorder: Qt.darker(Kirigami.Theme.highlightColor, 1.25)
|
||||||
|
|
||||||
readonly property color disabledBg: Kirigami.Theme.alternateBackgroundColor
|
readonly property color disabledBg: Kirigami.Theme.backgroundColor
|
||||||
readonly property color disabledBgHover: mixColor(Kirigami.Theme.alternateBackgroundColor, Kirigami.Theme.textColor, 0.06)
|
|
||||||
readonly property color disabledBgPressed: Qt.darker(disabledBg, 1.1)
|
readonly property color disabledBgPressed: Qt.darker(disabledBg, 1.1)
|
||||||
readonly property color disabledBorder: {
|
readonly property color disabledBorder: {
|
||||||
let bg = Kirigami.Theme.backgroundColor;
|
let bg = Kirigami.Theme.backgroundColor;
|
||||||
|
|
@ -49,14 +49,6 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mixColor(base, overlay, ratio) {
|
|
||||||
return Qt.rgba(
|
|
||||||
base.r + (overlay.r - base.r) * ratio,
|
|
||||||
base.g + (overlay.g - base.g) * ratio,
|
|
||||||
base.b + (overlay.b - base.b) * ratio,
|
|
||||||
base.a + (overlay.a - base.a) * ratio)
|
|
||||||
}
|
|
||||||
|
|
||||||
MobileShell.HapticsEffect { id: haptics }
|
MobileShell.HapticsEffect { id: haptics }
|
||||||
|
|
||||||
// ── Outer card ──────────────────────────────────────────────────────
|
// ── Outer card ──────────────────────────────────────────────────────
|
||||||
|
|
@ -67,8 +59,8 @@ Item {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
height: parent.height
|
height: parent.height
|
||||||
radius: root.rowRadius
|
radius: Kirigami.Units.cornerRadius
|
||||||
color: Qt.rgba(0, 0, 0, root.enabled ? 0.12 : 0.08)
|
color: Qt.rgba(0, 0, 0, 0.075)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Card background — always neutral base (the toggle pill carries the
|
// Card background — always neutral base (the toggle pill carries the
|
||||||
|
|
@ -76,7 +68,7 @@ Item {
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: cardBg
|
id: cardBg
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
radius: root.rowRadius
|
radius: Kirigami.Units.cornerRadius
|
||||||
border.pixelAligned: false
|
border.pixelAligned: false
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: root.disabledBorder
|
border.color: root.disabledBorder
|
||||||
|
|
@ -104,15 +96,9 @@ Item {
|
||||||
border.color: root.enabled ? root.enabledBorder : root.disabledBorder
|
border.color: root.enabled ? root.enabledBorder : root.disabledBorder
|
||||||
color: {
|
color: {
|
||||||
if (root.enabled) {
|
if (root.enabled) {
|
||||||
if (toggleMouse.pressed) {
|
return toggleMouse.pressed ? root.enabledBgPressed : root.enabledBg;
|
||||||
return root.enabledBgPressed;
|
|
||||||
}
|
}
|
||||||
return toggleMouse.containsMouse ? root.enabledBgHover : root.enabledBg;
|
return toggleMouse.pressed ? root.disabledBgPressed : root.disabledBg;
|
||||||
}
|
|
||||||
if (toggleMouse.pressed) {
|
|
||||||
return root.disabledBgPressed;
|
|
||||||
}
|
|
||||||
return toggleMouse.containsMouse ? root.disabledBgHover : root.disabledBg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
|
|
@ -143,25 +129,20 @@ Item {
|
||||||
source: root.icon
|
source: root.icon
|
||||||
}
|
}
|
||||||
|
|
||||||
// Indicator bar
|
// Indicator dot
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
width: root.enabled ? Kirigami.Units.smallSpacing * 3 : Kirigami.Units.smallSpacing * 1.5
|
width: Kirigami.Units.smallSpacing * 1.5
|
||||||
height: Math.max(2, Math.round(Kirigami.Units.devicePixelRatio))
|
height: width
|
||||||
radius: height / 2
|
radius: width / 2
|
||||||
color: root.enabled ? Kirigami.Theme.highlightColor : Kirigami.Theme.disabledTextColor
|
color: root.enabled ? Kirigami.Theme.highlightColor : Kirigami.Theme.disabledTextColor
|
||||||
opacity: root.enabled ? 1.0 : 0.4
|
opacity: root.enabled ? 1.0 : 0.4
|
||||||
|
|
||||||
Behavior on width {
|
|
||||||
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: toggleMouse
|
id: toggleMouse
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onPressed: haptics.buttonVibrate()
|
onPressed: haptics.buttonVibrate()
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,6 @@ QtObject {
|
||||||
? 0
|
? 0
|
||||||
: Kirigami.Units.gridUnit * 2
|
: Kirigami.Units.gridUnit * 2
|
||||||
readonly property real defaultGesturePanelThickness: Kirigami.Units.gridUnit
|
readonly property real defaultGesturePanelThickness: Kirigami.Units.gridUnit
|
||||||
readonly property real convergenceDockHeight: Kirigami.Units.gridUnit * 3
|
|
||||||
readonly property real convergenceDockRevealHeight: Kirigami.Units.gridUnit
|
|
||||||
|
|
||||||
readonly property real navigationPanelThickness: {
|
readonly property real navigationPanelThickness: {
|
||||||
if (!ShellSettings.Settings.navigationPanelEnabled) {
|
if (!ShellSettings.Settings.navigationPanelEnabled) {
|
||||||
|
|
|
||||||
|
|
@ -25,29 +25,6 @@ Rectangle {
|
||||||
easing.type: Easing.OutExpo
|
easing.type: Easing.OutExpo
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-clear safety net.
|
|
||||||
//
|
|
||||||
// The colored fill is normally cleared by onShowingWindowChanged when
|
|
||||||
// the launched app's maximized state toggles. In convergence mode apps
|
|
||||||
// launch centered (kwinrc Placement=Centered), so showingWindow may
|
|
||||||
// never flip to true and the change-based cleanup never fires — the
|
|
||||||
// band would otherwise remain on the panel indefinitely.
|
|
||||||
//
|
|
||||||
// This timer runs after every panel-fill animation and clears the
|
|
||||||
// rectangle if no maximized/fullscreen window is present, restoring
|
|
||||||
// the original mobile behaviour while fixing the convergence path.
|
|
||||||
Timer {
|
|
||||||
id: autoClearTimer
|
|
||||||
interval: 600 // animation duration (200) + settle time
|
|
||||||
repeat: false
|
|
||||||
onTriggered: {
|
|
||||||
if (!root.maximizedTracker || !root.maximizedTracker.showingWindow) {
|
|
||||||
root.color = 'transparent';
|
|
||||||
root.height = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset when maximized window state changes
|
// Reset when maximized window state changes
|
||||||
Connections {
|
Connections {
|
||||||
target: maximizedTracker
|
target: maximizedTracker
|
||||||
|
|
@ -69,7 +46,6 @@ Rectangle {
|
||||||
|
|
||||||
root.color = color;
|
root.color = color;
|
||||||
heightAnim.restart();
|
heightAnim.restart();
|
||||||
autoClearTimer.restart();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,14 +63,7 @@ Controls.Drawer {
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
radius: Kirigami.Units.cornerRadius
|
radius: Kirigami.Units.cornerRadius
|
||||||
Kirigami.Theme.inherit: false
|
color: Qt.rgba(255, 255, 255, (openSettings.down || openSettings.highlighted) ? 0.3 : 0.2)
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
|
||||||
color: Qt.rgba(
|
|
||||||
Kirigami.Theme.backgroundColor.r,
|
|
||||||
Kirigami.Theme.backgroundColor.g,
|
|
||||||
Kirigami.Theme.backgroundColor.b,
|
|
||||||
(openSettings.down || openSettings.highlighted) ? 0.9 : 0.75
|
|
||||||
)
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (maskManager) {
|
if (maskManager) {
|
||||||
|
|
@ -84,7 +77,8 @@ Controls.Drawer {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
implicitHeight: Kirigami.Units.iconSizes.large
|
implicitHeight: Kirigami.Units.iconSizes.large
|
||||||
implicitWidth: Kirigami.Units.iconSizes.large
|
implicitWidth: Kirigami.Units.iconSizes.large
|
||||||
source: 'configure'
|
source: 'list-add'
|
||||||
|
color: 'white'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,14 +151,7 @@ Controls.Drawer {
|
||||||
}
|
}
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
Kirigami.Theme.inherit: false
|
color: Qt.rgba(255, 255, 255, (delegate.down || delegate.highlighted) ? 0.4 : 0.2)
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
|
||||||
color: Qt.rgba(
|
|
||||||
Kirigami.Theme.backgroundColor.r,
|
|
||||||
Kirigami.Theme.backgroundColor.g,
|
|
||||||
Kirigami.Theme.backgroundColor.b,
|
|
||||||
(delegate.down || delegate.highlighted) ? 0.9 : 0.75
|
|
||||||
)
|
|
||||||
radius: Kirigami.Units.cornerRadius
|
radius: Kirigami.Units.cornerRadius
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
|
|
||||||
|
|
@ -16,22 +16,22 @@ QtObject {
|
||||||
id: component
|
id: component
|
||||||
|
|
||||||
property bool initialConvergenceMode: false
|
property bool initialConvergenceMode: false
|
||||||
property bool wasDocked: false
|
|
||||||
|
|
||||||
property var apiListener: Connections {
|
property var apiListener: Connections {
|
||||||
target: KScreenOSDUtil
|
target: KScreenOSDUtil
|
||||||
function onOutputsChanged() {
|
function onOutputsChanged() {
|
||||||
const docked = KScreenOSDUtil.outputs > 1;
|
if (KScreenOSDUtil.outputs > 1) {
|
||||||
if (docked && !wasDocked) {
|
|
||||||
initialConvergenceMode = ShellSettings.Settings.convergenceModeEnabled;
|
initialConvergenceMode = ShellSettings.Settings.convergenceModeEnabled;
|
||||||
}
|
}
|
||||||
ShellSettings.Settings.convergenceModeEnabled = docked ? true : initialConvergenceMode;
|
- console.log("KScreenOSDProvider convergenceModeEnabled: "
|
||||||
wasDocked = docked;
|
- + (KScreenOSDUtil.outputs > 1 ? "true" : (initialConvergenceMode ? "TRUE" : "FALSE")));
|
||||||
|
ShellSettings.Settings.convergenceModeEnabled = KScreenOSDUtil.outputs > 1 ? true : initialConvergenceMode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
wasDocked = KScreenOSDUtil.outputs > 1;
|
if (KScreenOSDUtil.outputs < 2) {
|
||||||
initialConvergenceMode = ShellSettings.Settings.convergenceModeEnabled;
|
initialConvergenceMode = ShellSettings.Settings.convergenceModeEnabled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ const QString CONFIG_FILE = QStringLiteral("kwinrc");
|
||||||
const QString OVERLAY_CONFIG_FILE = QStringLiteral("plasma-mobile/kwinrc");
|
const QString OVERLAY_CONFIG_FILE = QStringLiteral("plasma-mobile/kwinrc");
|
||||||
const QString WAYLAND_CONFIG_GROUP = QStringLiteral("Wayland");
|
const QString WAYLAND_CONFIG_GROUP = QStringLiteral("Wayland");
|
||||||
const QString SCREEN_EDGES_CONFIG_GROUP = QStringLiteral("ScreenEdges");
|
const QString SCREEN_EDGES_CONFIG_GROUP = QStringLiteral("ScreenEdges");
|
||||||
const QString DECORATION_CONFIG_GROUP = QStringLiteral("org.kde.kdecoration2");
|
|
||||||
|
|
||||||
KWinSettings::KWinSettings(QObject *parent)
|
KWinSettings::KWinSettings(QObject *parent)
|
||||||
: QObject{parent}
|
: QObject{parent}
|
||||||
|
|
@ -27,8 +26,6 @@ KWinSettings::KWinSettings(QObject *parent)
|
||||||
Q_EMIT doubleTapWakeupChanged();
|
Q_EMIT doubleTapWakeupChanged();
|
||||||
} else if (group.name() == SCREEN_EDGES_CONFIG_GROUP) {
|
} else if (group.name() == SCREEN_EDGES_CONFIG_GROUP) {
|
||||||
Q_EMIT screenEdgeTouchTargetChanged();
|
Q_EMIT screenEdgeTouchTargetChanged();
|
||||||
} else if (group.name() == DECORATION_CONFIG_GROUP) {
|
|
||||||
Q_EMIT titleButtonsChanged();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -67,15 +64,3 @@ void KWinSettings::setScreenEdgeTouchTarget(int target)
|
||||||
QDBusConnection::sessionBus().send(message);
|
QDBusConnection::sessionBus().send(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString KWinSettings::titleButtonsOnLeft() const
|
|
||||||
{
|
|
||||||
auto group = KConfigGroup{m_config, DECORATION_CONFIG_GROUP};
|
|
||||||
return group.readEntry("ButtonsOnLeft", QStringLiteral("MSE"));
|
|
||||||
}
|
|
||||||
|
|
||||||
QString KWinSettings::titleButtonsOnRight() const
|
|
||||||
{
|
|
||||||
auto group = KConfigGroup{m_config, DECORATION_CONFIG_GROUP};
|
|
||||||
return group.readEntry("ButtonsOnRight", QStringLiteral("HIAX"));
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,6 @@ class KWinSettings : public QObject
|
||||||
|
|
||||||
Q_PROPERTY(bool doubleTapWakeup READ doubleTapWakeup WRITE setDoubleTapWakeup NOTIFY doubleTapWakeupChanged)
|
Q_PROPERTY(bool doubleTapWakeup READ doubleTapWakeup WRITE setDoubleTapWakeup NOTIFY doubleTapWakeupChanged)
|
||||||
Q_PROPERTY(int screenEdgeTouchTarget READ screenEdgeTouchTarget WRITE setScreenEdgeTouchTarget NOTIFY screenEdgeTouchTargetChanged)
|
Q_PROPERTY(int screenEdgeTouchTarget READ screenEdgeTouchTarget WRITE setScreenEdgeTouchTarget NOTIFY screenEdgeTouchTargetChanged)
|
||||||
Q_PROPERTY(QString titleButtonsOnLeft READ titleButtonsOnLeft NOTIFY titleButtonsChanged)
|
|
||||||
Q_PROPERTY(QString titleButtonsOnRight READ titleButtonsOnRight NOTIFY titleButtonsChanged)
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
KWinSettings(QObject *parent = nullptr);
|
KWinSettings(QObject *parent = nullptr);
|
||||||
|
|
@ -50,20 +48,9 @@ public:
|
||||||
*/
|
*/
|
||||||
void setScreenEdgeTouchTarget(int target);
|
void setScreenEdgeTouchTarget(int target);
|
||||||
|
|
||||||
/**
|
|
||||||
* Configured KWin titlebar buttons on the left side.
|
|
||||||
*/
|
|
||||||
QString titleButtonsOnLeft() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configured KWin titlebar buttons on the right side.
|
|
||||||
*/
|
|
||||||
QString titleButtonsOnRight() const;
|
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void doubleTapWakeupChanged();
|
void doubleTapWakeupChanged();
|
||||||
void screenEdgeTouchTargetChanged();
|
void screenEdgeTouchTargetChanged();
|
||||||
void titleButtonsChanged();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
KConfigWatcher::Ptr m_configWatcher;
|
KConfigWatcher::Ptr m_configWatcher;
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,6 @@ MobileShellSettings::MobileShellSettings(QObject *parent)
|
||||||
Q_EMIT autoHidePanelsEnabledChanged();
|
Q_EMIT autoHidePanelsEnabledChanged();
|
||||||
Q_EMIT gamingModeEnabledChanged();
|
Q_EMIT gamingModeEnabledChanged();
|
||||||
Q_EMIT gamingDismissHintEnabledChanged();
|
Q_EMIT gamingDismissHintEnabledChanged();
|
||||||
Q_EMIT dynamicTilingEnabledChanged();
|
|
||||||
Q_EMIT allowLogoutChanged();
|
Q_EMIT allowLogoutChanged();
|
||||||
}
|
}
|
||||||
if (group.name() == LOCKSCREEN_CONFIG_GROUP) {
|
if (group.name() == LOCKSCREEN_CONFIG_GROUP) {
|
||||||
|
|
@ -277,19 +276,6 @@ void MobileShellSettings::setGamingDismissHintEnabled(bool enabled)
|
||||||
m_config->sync();
|
m_config->sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MobileShellSettings::dynamicTilingEnabled() const
|
|
||||||
{
|
|
||||||
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
|
|
||||||
return group.readEntry("dynamicTilingEnabled", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MobileShellSettings::setDynamicTilingEnabled(bool enabled)
|
|
||||||
{
|
|
||||||
auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP};
|
|
||||||
group.writeEntry("dynamicTilingEnabled", enabled, KConfigGroup::Notify);
|
|
||||||
m_config->sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MobileShellSettings::updateNavigationBarsInPlasma()
|
void MobileShellSettings::updateNavigationBarsInPlasma()
|
||||||
{
|
{
|
||||||
// Do not update panels when not in Plasma Mobile
|
// Do not update panels when not in Plasma Mobile
|
||||||
|
|
|
||||||
|
|
@ -56,10 +56,6 @@ class MobileShellSettings : public QObject
|
||||||
Q_PROPERTY(bool gamingModeEnabled READ gamingModeEnabled WRITE setGamingModeEnabled NOTIFY gamingModeEnabledChanged)
|
Q_PROPERTY(bool gamingModeEnabled READ gamingModeEnabled WRITE setGamingModeEnabled NOTIFY gamingModeEnabledChanged)
|
||||||
Q_PROPERTY(bool gamingDismissHintEnabled READ gamingDismissHintEnabled WRITE setGamingDismissHintEnabled NOTIFY gamingDismissHintEnabledChanged)
|
Q_PROPERTY(bool gamingDismissHintEnabled READ gamingDismissHintEnabled WRITE setGamingDismissHintEnabled NOTIFY gamingDismissHintEnabledChanged)
|
||||||
|
|
||||||
// Dynamic (BSP) window tiling — only meaningful in convergence mode.
|
|
||||||
// When false, KWin's native quick-tile behaviour is used unmodified.
|
|
||||||
Q_PROPERTY(bool dynamicTilingEnabled READ dynamicTilingEnabled WRITE setDynamicTilingEnabled NOTIFY dynamicTilingEnabledChanged)
|
|
||||||
|
|
||||||
// logout dialog
|
// logout dialog
|
||||||
Q_PROPERTY(bool allowLogout READ allowLogout READ allowLogout NOTIFY allowLogoutChanged)
|
Q_PROPERTY(bool allowLogout READ allowLogout READ allowLogout NOTIFY allowLogoutChanged)
|
||||||
|
|
||||||
|
|
@ -278,14 +274,6 @@ public:
|
||||||
bool gamingDismissHintEnabled() const;
|
bool gamingDismissHintEnabled() const;
|
||||||
void setGamingDismissHintEnabled(bool enabled);
|
void setGamingDismissHintEnabled(bool enabled);
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the SHIFT BSP dynamic tiling script is active.
|
|
||||||
* Defaults to true; only takes effect in convergence mode (and when
|
|
||||||
* gaming mode is off).
|
|
||||||
*/
|
|
||||||
bool dynamicTilingEnabled() const;
|
|
||||||
void setDynamicTilingEnabled(bool enabled);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether logout button is shown in the logout/shutdown dialog.
|
* Whether logout button is shown in the logout/shutdown dialog.
|
||||||
*/
|
*/
|
||||||
|
|
@ -334,7 +322,6 @@ Q_SIGNALS:
|
||||||
void autoHidePanelsEnabledChanged();
|
void autoHidePanelsEnabledChanged();
|
||||||
void gamingModeEnabledChanged();
|
void gamingModeEnabledChanged();
|
||||||
void gamingDismissHintEnabledChanged();
|
void gamingDismissHintEnabledChanged();
|
||||||
void dynamicTilingEnabledChanged();
|
|
||||||
void allowLogoutChanged();
|
void allowLogoutChanged();
|
||||||
void lockscreenLeftButtonActionChanged();
|
void lockscreenLeftButtonActionChanged();
|
||||||
void lockscreenRightButtonActionChanged();
|
void lockscreenRightButtonActionChanged();
|
||||||
|
|
|
||||||
|
|
@ -20,15 +20,6 @@
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
bool isLegacyNextWallpaperPath(const QString &path)
|
|
||||||
{
|
|
||||||
return path == QStringLiteral("Next") || path.startsWith(QStringLiteral("/usr/share/wallpapers/Next/"))
|
|
||||||
|| path.startsWith(QStringLiteral("file:///usr/share/wallpapers/Next/"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WallpaperPlugin::WallpaperPlugin(QObject *parent)
|
WallpaperPlugin::WallpaperPlugin(QObject *parent)
|
||||||
: QObject{parent}
|
: QObject{parent}
|
||||||
, m_homescreenConfig{new QQmlPropertyMap{this}}
|
, m_homescreenConfig{new QQmlPropertyMap{this}}
|
||||||
|
|
@ -192,11 +183,6 @@ QCoro::Task<void> WallpaperPlugin::setHomescreenWallpaper(const QString &path)
|
||||||
qWarning() << "Failed to set wallpaper for screen" << screen << ":" << reply.error();
|
qWarning() << "Failed to set wallpaper for screen" << screen << ":" << reply.error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep lockscreen wallpaper aligned with homescreen wallpaper selections.
|
|
||||||
if (!path.isEmpty()) {
|
|
||||||
setLockscreenWallpaper(path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WallpaperPlugin::setLockscreenWallpaper(const QString &path)
|
void WallpaperPlugin::setLockscreenWallpaper(const QString &path)
|
||||||
|
|
@ -270,14 +256,6 @@ QCoro::Task<void> WallpaperPlugin::loadHomescreenSettings()
|
||||||
// parse image configuration
|
// parse image configuration
|
||||||
if (m_homescreenWallpaperPlugin == QStringLiteral("org.kde.image")) {
|
if (m_homescreenWallpaperPlugin == QStringLiteral("org.kde.image")) {
|
||||||
m_homescreenWallpaperPath = map["Image"].toString();
|
m_homescreenWallpaperPath = map["Image"].toString();
|
||||||
|
|
||||||
// One-time migration for stale lockscreen wallpaper defaults.
|
|
||||||
const bool lockscreenUnset = m_lockscreenWallpaperPlugin.isEmpty() || m_lockscreenWallpaperPath.isEmpty();
|
|
||||||
const bool lockscreenLegacyNext =
|
|
||||||
m_lockscreenWallpaperPlugin == QStringLiteral("org.kde.image") && isLegacyNextWallpaperPath(m_lockscreenWallpaperPath);
|
|
||||||
if (!m_homescreenWallpaperPath.isEmpty() && m_lockscreenWallpaperPath != m_homescreenWallpaperPath && (lockscreenUnset || lockscreenLegacyNext)) {
|
|
||||||
setLockscreenWallpaper(m_homescreenWallpaperPath);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_EMIT homescreenConfigurationChanged();
|
Q_EMIT homescreenConfigurationChanged();
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,6 @@ plasma_add_applet(org.kde.plasma.mobile.homescreen.folio
|
||||||
qml/main.qml
|
qml/main.qml
|
||||||
qml/PipeWireThumbnail.qml
|
qml/PipeWireThumbnail.qml
|
||||||
qml/PlaceholderDelegate.qml
|
qml/PlaceholderDelegate.qml
|
||||||
qml/RunningAppsPanel.qml
|
|
||||||
qml/RunningAppsPanelButton.qml
|
|
||||||
qml/WidgetDragItem.qml
|
qml/WidgetDragItem.qml
|
||||||
qml/config.qml
|
qml/config.qml
|
||||||
CPP_SOURCES
|
CPP_SOURCES
|
||||||
|
|
@ -97,7 +95,6 @@ target_link_libraries(org.kde.plasma.mobile.homescreen.folio PRIVATE
|
||||||
KF6::Service
|
KF6::Service
|
||||||
KF6::KIOGui
|
KF6::KIOGui
|
||||||
KF6::Notifications
|
KF6::Notifications
|
||||||
PW::LibTaskManager
|
|
||||||
Plasma::KWaylandClient
|
Plasma::KWaylandClient
|
||||||
KF6::WindowSystem
|
KF6::WindowSystem
|
||||||
KF6::JobWidgets
|
KF6::JobWidgets
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@
|
||||||
|
|
||||||
#include "homescreen.h"
|
#include "homescreen.h"
|
||||||
|
|
||||||
#include <virtualdesktopinfo.h>
|
|
||||||
|
|
||||||
#include <KWindowSystem>
|
#include <KWindowSystem>
|
||||||
|
|
||||||
#include <QDBusConnection>
|
#include <QDBusConnection>
|
||||||
|
|
@ -98,23 +96,4 @@ void HomeScreen::triggerOverview() const
|
||||||
QDBusConnection::sessionBus().send(message);
|
QDBusConnection::sessionBus().send(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HomeScreen::activateVirtualDesktop(const QVariant &desktop) const
|
|
||||||
{
|
|
||||||
if (!desktop.isValid() || desktop.toString().isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskManager::VirtualDesktopInfo virtualDesktopInfo;
|
|
||||||
virtualDesktopInfo.requestActivate(desktop);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HomeScreen::emptyTrash() const
|
|
||||||
{
|
|
||||||
QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kio.trash"),
|
|
||||||
QStringLiteral("/trash"),
|
|
||||||
QStringLiteral("org.kde.KIO.Trash"),
|
|
||||||
QStringLiteral("emptyTrash"));
|
|
||||||
QDBusConnection::sessionBus().send(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
#include "homescreen.moc"
|
#include "homescreen.moc"
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
#include <Plasma/Containment>
|
#include <Plasma/Containment>
|
||||||
#include <QSortFilterProxyModel>
|
#include <QSortFilterProxyModel>
|
||||||
#include <QVariant>
|
|
||||||
|
|
||||||
#include "applicationlistmodel.h"
|
#include "applicationlistmodel.h"
|
||||||
#include "delegatetoucharea.h"
|
#include "delegatetoucharea.h"
|
||||||
|
|
@ -34,7 +33,6 @@ class HomeScreen : public Plasma::Containment
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_UNCREATABLE("HomeScreen is provided by the folio containment")
|
|
||||||
|
|
||||||
Q_PROPERTY(FolioSettings *FolioSettings READ folioSettings CONSTANT)
|
Q_PROPERTY(FolioSettings *FolioSettings READ folioSettings CONSTANT)
|
||||||
Q_PROPERTY(HomeScreenState *HomeScreenState READ homeScreenState CONSTANT)
|
Q_PROPERTY(HomeScreenState *HomeScreenState READ homeScreenState CONSTANT)
|
||||||
|
|
@ -51,8 +49,6 @@ public:
|
||||||
void configChanged() override;
|
void configChanged() override;
|
||||||
|
|
||||||
Q_INVOKABLE void triggerOverview() const;
|
Q_INVOKABLE void triggerOverview() const;
|
||||||
Q_INVOKABLE void activateVirtualDesktop(const QVariant &desktop) const;
|
|
||||||
Q_INVOKABLE void emptyTrash() const;
|
|
||||||
|
|
||||||
FolioSettings *folioSettings();
|
FolioSettings *folioSettings();
|
||||||
HomeScreenState *homeScreenState();
|
HomeScreenState *homeScreenState();
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@
|
||||||
import QtQuick 2.12
|
import QtQuick 2.12
|
||||||
import QtQuick.Window 2.12
|
import QtQuick.Window 2.12
|
||||||
import QtQuick.Layouts 1.1
|
import QtQuick.Layouts 1.1
|
||||||
import QtCore
|
|
||||||
import Qt.labs.folderlistmodel
|
|
||||||
|
|
||||||
import org.kde.plasma.components 3.0 as PC3
|
import org.kde.plasma.components 3.0 as PC3
|
||||||
import org.kde.plasma.private.mobileshell.state as MobileShellState
|
import org.kde.plasma.private.mobileshell.state as MobileShellState
|
||||||
|
|
@ -26,14 +24,12 @@ MouseArea {
|
||||||
property MobileShell.MaskManager maskManager
|
property MobileShell.MaskManager maskManager
|
||||||
|
|
||||||
property var homeScreen
|
property var homeScreen
|
||||||
property bool suppressRunningTasks: false
|
|
||||||
|
|
||||||
signal delegateDragRequested(var item)
|
signal delegateDragRequested(var item)
|
||||||
|
|
||||||
// Convergence mode: show running apps alongside favourites
|
// Convergence mode: show running apps alongside favourites
|
||||||
readonly property bool convergenceMode: ShellSettings.Settings.convergenceModeEnabled
|
readonly property bool convergenceMode: ShellSettings.Settings.convergenceModeEnabled
|
||||||
readonly property bool showRunningTasks: convergenceMode && !suppressRunningTasks
|
readonly property int totalItemCount: repeater.count + (convergenceMode ? taskRepeater.count : 0)
|
||||||
readonly property int totalItemCount: repeater.count + (showRunningTasks ? taskRepeater.count : 0)
|
|
||||||
|
|
||||||
// In convergence mode, size icons to fit the dock bar instead of using page grid cells
|
// In convergence mode, size icons to fit the dock bar instead of using page grid cells
|
||||||
readonly property real dockCellWidth: convergenceMode ? root.height : folio.HomeScreenState.pageCellWidth
|
readonly property real dockCellWidth: convergenceMode ? root.height : folio.HomeScreenState.pageCellWidth
|
||||||
|
|
@ -41,21 +37,6 @@ MouseArea {
|
||||||
|
|
||||||
// Navigation buttons width (used to offset center positioning)
|
// Navigation buttons width (used to offset center positioning)
|
||||||
readonly property real navButtonWidth: convergenceMode ? root.height : 0
|
readonly property real navButtonWidth: convergenceMode ? root.height : 0
|
||||||
readonly property real dockItemInset: convergenceMode ? Math.max(2, Kirigami.Units.smallSpacing / 2) : 0
|
|
||||||
readonly property real dockIconSize: Math.min(root.height * 0.56, Kirigami.Units.iconSizes.large)
|
|
||||||
|
|
||||||
function dockItemColor(pressed, hovered, active) {
|
|
||||||
if (pressed) {
|
|
||||||
return Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.18)
|
|
||||||
}
|
|
||||||
if (active) {
|
|
||||||
return Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b, hovered ? 0.18 : 0.12)
|
|
||||||
}
|
|
||||||
if (hovered) {
|
|
||||||
return Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.08)
|
|
||||||
}
|
|
||||||
return "transparent"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Center x for dock items (offset between nav buttons in convergence mode)
|
// Center x for dock items (offset between nav buttons in convergence mode)
|
||||||
readonly property real dockCenterX: convergenceMode
|
readonly property real dockCenterX: convergenceMode
|
||||||
|
|
@ -63,7 +44,7 @@ MouseArea {
|
||||||
: root.width / 2
|
: root.width / 2
|
||||||
|
|
||||||
// Visible spacer between pinned favourites and running tasks
|
// Visible spacer between pinned favourites and running tasks
|
||||||
readonly property bool showSpacer: showRunningTasks && repeater.count > 0 && taskRepeater.count > 0
|
readonly property bool showSpacer: convergenceMode && repeater.count > 0 && taskRepeater.count > 0
|
||||||
property real spacerWidth: showSpacer ? Kirigami.Units.largeSpacing * 2 : 0
|
property real spacerWidth: showSpacer ? Kirigami.Units.largeSpacing * 2 : 0
|
||||||
Behavior on spacerWidth {
|
Behavior on spacerWidth {
|
||||||
NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad }
|
NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad }
|
||||||
|
|
@ -88,39 +69,6 @@ MouseArea {
|
||||||
property string taskPinStorageId: ""
|
property string taskPinStorageId: ""
|
||||||
readonly property bool taskPinCanDrop: taskPinTargetIndex !== -1 && taskPinStorageId !== ""
|
readonly property bool taskPinCanDrop: taskPinTargetIndex !== -1 && taskPinStorageId !== ""
|
||||||
|
|
||||||
// Virtual desktop pager (convergence mode, 2+ desktops)
|
|
||||||
readonly property bool showPager: convergenceMode && virtualDesktopInfo.numberOfDesktops > 1
|
|
||||||
readonly property real pagerButtonWidth: showPager ? Math.min(root.height, Kirigami.Units.gridUnit * 2.5) : 0
|
|
||||||
readonly property int pagerLeftCount: showPager ? Math.ceil(virtualDesktopInfo.numberOfDesktops / 2) : 0
|
|
||||||
readonly property int pagerRightCount: showPager ? virtualDesktopInfo.numberOfDesktops - pagerLeftCount : 0
|
|
||||||
readonly property real trashButtonWidth: convergenceMode ? root.height : 0
|
|
||||||
|
|
||||||
function pagerDesktopName(index) {
|
|
||||||
let names = virtualDesktopInfo.desktopNames
|
|
||||||
if (names && index < names.length && String(names[index]).length > 0)
|
|
||||||
return String(names[index])
|
|
||||||
return i18n("Desktop %1", index + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the desktop ID of the pager button under screen-space x, or ""
|
|
||||||
function pagerButtonDesktopAt(x) {
|
|
||||||
if (!showPager) return ""
|
|
||||||
let ids = virtualDesktopInfo.desktopIds
|
|
||||||
for (let i = 0; i < pagerLeftCount; ++i) {
|
|
||||||
let bx = navButtonWidth + i * pagerButtonWidth
|
|
||||||
if (x >= bx && x < bx + pagerButtonWidth)
|
|
||||||
return (ids && i < ids.length) ? String(ids[i]) : ""
|
|
||||||
}
|
|
||||||
for (let i = 0; i < pagerRightCount; ++i) {
|
|
||||||
let bx = root.width - navButtonWidth - root.trashButtonWidth - (pagerRightCount - i) * pagerButtonWidth
|
|
||||||
if (x >= bx && x < bx + pagerButtonWidth) {
|
|
||||||
let di = pagerLeftCount + i
|
|
||||||
return (ids && di < ids.length) ? String(ids[di]) : ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
function runningTaskStorageId(taskModel) {
|
function runningTaskStorageId(taskModel) {
|
||||||
var id = taskModel ? taskModel.AppId || "" : ""
|
var id = taskModel ? taskModel.AppId || "" : ""
|
||||||
if (id && !id.endsWith(".desktop"))
|
if (id && !id.endsWith(".desktop"))
|
||||||
|
|
@ -182,7 +130,10 @@ MouseArea {
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
width: root.navButtonWidth
|
width: root.navButtonWidth
|
||||||
color: "transparent"
|
color: homeMouseArea.containsPress
|
||||||
|
? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.2)
|
||||||
|
: (homeMouseArea.containsMouse ? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.1) : "transparent")
|
||||||
|
radius: Kirigami.Units.cornerRadius
|
||||||
|
|
||||||
Accessible.role: Accessible.Button
|
Accessible.role: Accessible.Button
|
||||||
Accessible.name: i18n("Home")
|
Accessible.name: i18n("Home")
|
||||||
|
|
@ -204,20 +155,9 @@ MouseArea {
|
||||||
visible: homeButton.activeFocus
|
visible: homeButton.activeFocus
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: root.dockItemInset
|
|
||||||
radius: Kirigami.Units.cornerRadius
|
|
||||||
color: root.dockItemColor(homeMouseArea.containsPress, homeMouseArea.containsMouse, false)
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.Icon {
|
Kirigami.Icon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: root.dockIconSize
|
width: Math.min(parent.width, parent.height) * 0.75
|
||||||
height: width
|
height: width
|
||||||
source: "start-here-shift"
|
source: "start-here-shift"
|
||||||
active: homeMouseArea.containsMouse
|
active: homeMouseArea.containsMouse
|
||||||
|
|
@ -241,7 +181,10 @@ MouseArea {
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
width: root.navButtonWidth
|
width: root.navButtonWidth
|
||||||
color: "transparent"
|
color: overviewMouseArea.containsPress
|
||||||
|
? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.2)
|
||||||
|
: (overviewMouseArea.containsMouse ? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.1) : "transparent")
|
||||||
|
radius: Kirigami.Units.cornerRadius
|
||||||
|
|
||||||
Accessible.role: Accessible.Button
|
Accessible.role: Accessible.Button
|
||||||
Accessible.name: i18n("Overview")
|
Accessible.name: i18n("Overview")
|
||||||
|
|
@ -263,20 +206,9 @@ MouseArea {
|
||||||
visible: overviewButton.activeFocus
|
visible: overviewButton.activeFocus
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: root.dockItemInset
|
|
||||||
radius: Kirigami.Units.cornerRadius
|
|
||||||
color: root.dockItemColor(overviewMouseArea.containsPress, overviewMouseArea.containsMouse, false)
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.Icon {
|
Kirigami.Icon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: root.dockIconSize
|
width: Math.min(parent.width, parent.height) * 0.75
|
||||||
height: width
|
height: width
|
||||||
source: "view-grid-symbolic"
|
source: "view-grid-symbolic"
|
||||||
active: overviewMouseArea.containsMouse
|
active: overviewMouseArea.containsMouse
|
||||||
|
|
@ -291,246 +223,6 @@ MouseArea {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Virtual desktop pager: left wing (desktops 1 .. ceil(N/2)) ----
|
|
||||||
Repeater {
|
|
||||||
id: leftPagerRepeater
|
|
||||||
model: root.pagerLeftCount
|
|
||||||
|
|
||||||
delegate: Item {
|
|
||||||
id: leftDesktopBtn
|
|
||||||
required property int index
|
|
||||||
|
|
||||||
readonly property string desktopId: {
|
|
||||||
let ids = virtualDesktopInfo.desktopIds
|
|
||||||
return (ids && index < ids.length) ? String(ids[index]) : ""
|
|
||||||
}
|
|
||||||
readonly property bool isCurrent: desktopId !== "" && String(desktopId) === String(virtualDesktopInfo.currentDesktop)
|
|
||||||
readonly property bool isDragTarget: {
|
|
||||||
if (root.taskPinDragIndex < 0) return false
|
|
||||||
let cx = root.taskBaseX(root.taskPinDragIndex) + root.dockCellWidth / 2 + root.taskPinDragOffset
|
|
||||||
return root.pagerButtonDesktopAt(cx) === desktopId
|
|
||||||
}
|
|
||||||
|
|
||||||
x: root.navButtonWidth + index * root.pagerButtonWidth
|
|
||||||
y: 0
|
|
||||||
width: root.pagerButtonWidth
|
|
||||||
height: root.height
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: root.dockItemInset
|
|
||||||
radius: Kirigami.Units.cornerRadius
|
|
||||||
color: leftDesktopBtn.isCurrent || leftDesktopBtn.isDragTarget
|
|
||||||
? Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b,
|
|
||||||
leftPagerHover.containsMouse || leftDesktopBtn.isDragTarget ? 0.25 : 0.18)
|
|
||||||
: root.dockItemColor(leftPagerHover.containsPress, leftPagerHover.containsMouse, false)
|
|
||||||
Behavior on color { ColorAnimation { duration: Kirigami.Units.shortDuration } }
|
|
||||||
}
|
|
||||||
|
|
||||||
PC3.Label {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: (leftDesktopBtn.index + 1).toString()
|
|
||||||
color: leftDesktopBtn.isCurrent ? Kirigami.Theme.highlightColor : Kirigami.Theme.textColor
|
|
||||||
font.pixelSize: Math.round(parent.height * 0.3)
|
|
||||||
font.bold: leftDesktopBtn.isCurrent
|
|
||||||
}
|
|
||||||
|
|
||||||
PC3.ToolTip {
|
|
||||||
visible: leftPagerHover.containsMouse
|
|
||||||
text: root.pagerDesktopName(leftDesktopBtn.index)
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: leftPagerHover
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (leftDesktopBtn.desktopId)
|
|
||||||
root.folio.activateVirtualDesktop(leftDesktopBtn.desktopId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Virtual desktop pager: right wing (desktops ceil(N/2)+1 .. N) ----
|
|
||||||
Repeater {
|
|
||||||
id: rightPagerRepeater
|
|
||||||
model: root.pagerRightCount
|
|
||||||
|
|
||||||
delegate: Item {
|
|
||||||
id: rightDesktopBtn
|
|
||||||
required property int index
|
|
||||||
|
|
||||||
readonly property int desktopIndex: root.pagerLeftCount + index
|
|
||||||
readonly property string desktopId: {
|
|
||||||
let ids = virtualDesktopInfo.desktopIds
|
|
||||||
return (ids && desktopIndex < ids.length) ? String(ids[desktopIndex]) : ""
|
|
||||||
}
|
|
||||||
readonly property bool isCurrent: desktopId !== "" && String(desktopId) === String(virtualDesktopInfo.currentDesktop)
|
|
||||||
readonly property bool isDragTarget: {
|
|
||||||
if (root.taskPinDragIndex < 0) return false
|
|
||||||
let cx = root.taskBaseX(root.taskPinDragIndex) + root.dockCellWidth / 2 + root.taskPinDragOffset
|
|
||||||
return root.pagerButtonDesktopAt(cx) === desktopId
|
|
||||||
}
|
|
||||||
|
|
||||||
x: root.width - root.navButtonWidth - root.trashButtonWidth - (root.pagerRightCount - index) * root.pagerButtonWidth
|
|
||||||
y: 0
|
|
||||||
width: root.pagerButtonWidth
|
|
||||||
height: root.height
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: root.dockItemInset
|
|
||||||
radius: Kirigami.Units.cornerRadius
|
|
||||||
color: rightDesktopBtn.isCurrent || rightDesktopBtn.isDragTarget
|
|
||||||
? Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b,
|
|
||||||
rightPagerHover.containsMouse || rightDesktopBtn.isDragTarget ? 0.25 : 0.18)
|
|
||||||
: root.dockItemColor(rightPagerHover.containsPress, rightPagerHover.containsMouse, false)
|
|
||||||
Behavior on color { ColorAnimation { duration: Kirigami.Units.shortDuration } }
|
|
||||||
}
|
|
||||||
|
|
||||||
PC3.Label {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: (rightDesktopBtn.desktopIndex + 1).toString()
|
|
||||||
color: rightDesktopBtn.isCurrent ? Kirigami.Theme.highlightColor : Kirigami.Theme.textColor
|
|
||||||
font.pixelSize: Math.round(parent.height * 0.3)
|
|
||||||
font.bold: rightDesktopBtn.isCurrent
|
|
||||||
}
|
|
||||||
|
|
||||||
PC3.ToolTip {
|
|
||||||
visible: rightPagerHover.containsMouse
|
|
||||||
text: root.pagerDesktopName(rightDesktopBtn.desktopIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: rightPagerHover
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (rightDesktopBtn.desktopId)
|
|
||||||
root.folio.activateVirtualDesktop(rightDesktopBtn.desktopId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Trash button (convergence mode, sits between the right pager wing and the Overview button) ----
|
|
||||||
|
|
||||||
// Watches ~/.local/share/Trash/files to detect empty/full state.
|
|
||||||
// FolderListModel reacts to directory changes automatically.
|
|
||||||
FolderListModel {
|
|
||||||
id: trashFilesModel
|
|
||||||
folder: StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/.local/share/Trash/files"
|
|
||||||
showFiles: true
|
|
||||||
showDirs: true
|
|
||||||
showDotAndDotDot: false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Confirmation dialog for "Empty Trash" — parented to the homescreen so it
|
|
||||||
// is sized correctly and floats above all dock content.
|
|
||||||
Loader {
|
|
||||||
id: emptyTrashDialogLoader
|
|
||||||
parent: root.homeScreen
|
|
||||||
anchors.fill: parent
|
|
||||||
active: false
|
|
||||||
|
|
||||||
function open() {
|
|
||||||
active = true;
|
|
||||||
item.open();
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceComponent: Kirigami.PromptDialog {
|
|
||||||
title: i18n("Empty Trash")
|
|
||||||
subtitle: i18n("Permanently delete all items in the trash? This action cannot be undone.")
|
|
||||||
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.Cancel
|
|
||||||
onAccepted: root.folio.emptyTrash()
|
|
||||||
onClosed: emptyTrashDialogLoader.active = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: trashButton
|
|
||||||
visible: root.convergenceMode
|
|
||||||
activeFocusOnTab: root.convergenceMode
|
|
||||||
x: root.width - root.navButtonWidth - root.trashButtonWidth
|
|
||||||
y: 0
|
|
||||||
width: root.trashButtonWidth
|
|
||||||
height: root.height
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
Accessible.role: Accessible.Button
|
|
||||||
Accessible.name: i18n("Trash")
|
|
||||||
Accessible.onPressAction: Qt.openUrlExternally("trash:/")
|
|
||||||
|
|
||||||
Keys.onReturnPressed: Qt.openUrlExternally("trash:/")
|
|
||||||
Keys.onEnterPressed: Qt.openUrlExternally("trash:/")
|
|
||||||
Keys.onSpacePressed: Qt.openUrlExternally("trash:/")
|
|
||||||
|
|
||||||
KeyboardHighlight {
|
|
||||||
anchors.fill: parent
|
|
||||||
visible: trashButton.activeFocus
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: root.dockItemInset
|
|
||||||
radius: Kirigami.Units.cornerRadius
|
|
||||||
color: root.dockItemColor(trashMouseArea.containsPress, trashMouseArea.containsMouse, false)
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.Icon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: root.dockIconSize
|
|
||||||
height: width
|
|
||||||
source: trashFilesModel.count > 0 ? "user-trash-full" : "user-trash"
|
|
||||||
active: trashMouseArea.containsMouse
|
|
||||||
}
|
|
||||||
|
|
||||||
PC3.ToolTip {
|
|
||||||
visible: trashMouseArea.containsMouse
|
|
||||||
text: trashFilesModel.count > 0
|
|
||||||
? i18np("Trash — 1 item", "Trash — %1 items", trashFilesModel.count)
|
|
||||||
: i18n("Trash")
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: trashMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
||||||
onClicked: (mouse) => {
|
|
||||||
if (mouse.button === Qt.RightButton) {
|
|
||||||
trashContextMenu.open()
|
|
||||||
} else {
|
|
||||||
Qt.openUrlExternally("trash:/")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PC3.Menu {
|
|
||||||
id: trashContextMenu
|
|
||||||
popupType: T.Popup.Window
|
|
||||||
|
|
||||||
PC3.MenuItem {
|
|
||||||
icon.name: "folder-open"
|
|
||||||
text: i18n("Open Trash")
|
|
||||||
onTriggered: Qt.openUrlExternally("trash:/")
|
|
||||||
}
|
|
||||||
PC3.MenuItem {
|
|
||||||
icon.name: "trash-empty"
|
|
||||||
text: i18n("Empty Trash")
|
|
||||||
enabled: trashFilesModel.count > 0
|
|
||||||
onTriggered: emptyTrashDialogLoader.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskManager.VirtualDesktopInfo {
|
TaskManager.VirtualDesktopInfo {
|
||||||
id: virtualDesktopInfo
|
id: virtualDesktopInfo
|
||||||
}
|
}
|
||||||
|
|
@ -559,14 +251,11 @@ MouseArea {
|
||||||
}
|
}
|
||||||
|
|
||||||
onClicked: (mouse) => {
|
onClicked: (mouse) => {
|
||||||
|
// Right-click opens settings view (wallpaper/widgets), same as long-press
|
||||||
if (mouse.button === Qt.RightButton) {
|
if (mouse.button === Qt.RightButton) {
|
||||||
if (convergenceMode) {
|
|
||||||
root.homeScreen.showDesktopContextMenu();
|
|
||||||
} else {
|
|
||||||
folio.HomeScreenState.openSettingsView();
|
folio.HomeScreenState.openSettingsView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
onDoubleClicked: {
|
onDoubleClicked: {
|
||||||
if (folio.FolioSettings.doubleTapToLock) {
|
if (folio.FolioSettings.doubleTapToLock) {
|
||||||
|
|
@ -746,7 +435,7 @@ MouseArea {
|
||||||
maskManager: root.maskManager
|
maskManager: root.maskManager
|
||||||
application: delegate.delegateModel.application
|
application: delegate.delegateModel.application
|
||||||
name: folio.FolioSettings.showFavouritesAppLabels ? delegate.delegateModel.application.name : ""
|
name: folio.FolioSettings.showFavouritesAppLabels ? delegate.delegateModel.application.name : ""
|
||||||
shadow: !root.convergenceMode
|
shadow: true
|
||||||
|
|
||||||
turnToFolder: delegate.isAppHoveredOver
|
turnToFolder: delegate.isAppHoveredOver
|
||||||
turnToFolderAnimEnabled: folio.HomeScreenState.isDraggingDelegate
|
turnToFolderAnimEnabled: folio.HomeScreenState.isDraggingDelegate
|
||||||
|
|
@ -851,7 +540,7 @@ MouseArea {
|
||||||
id: appFolderDelegate
|
id: appFolderDelegate
|
||||||
folio: root.folio
|
folio: root.folio
|
||||||
maskManager: root.maskManager
|
maskManager: root.maskManager
|
||||||
shadow: !root.convergenceMode
|
shadow: true
|
||||||
folder: delegate.delegateModel.folder
|
folder: delegate.delegateModel.folder
|
||||||
name: folio.FolioSettings.showFavouritesAppLabels ? delegate.delegateModel.folder.name : ""
|
name: folio.FolioSettings.showFavouritesAppLabels ? delegate.delegateModel.folder.name : ""
|
||||||
|
|
||||||
|
|
@ -1016,21 +705,21 @@ MouseArea {
|
||||||
// Position above the hovered dock icon, in global coordinates
|
// Position above the hovered dock icon, in global coordinates
|
||||||
x: {
|
x: {
|
||||||
if (!targetDelegate) return 0
|
if (!targetDelegate) return 0
|
||||||
var win = root.Window.window
|
var delegateGlobal = targetDelegate.mapToGlobal(0, 0)
|
||||||
|
var win = targetDelegate.Window.window
|
||||||
var screenLeft = win && win.screen ? win.screen.virtualX : 0
|
var screenLeft = win && win.screen ? win.screen.virtualX : 0
|
||||||
var screenRight = screenLeft + (win && win.screen ? win.screen.width : Screen.width)
|
var screenRight = screenLeft + (win && win.screen ? win.screen.width : Screen.width)
|
||||||
// The dock window is full-width, anchored to the screen's left edge.
|
var centered = delegateGlobal.x + (targetDelegate.width - width) / 2
|
||||||
// targetDelegate.x is dock-local, so the global center of the icon is:
|
return Math.max(screenLeft, Math.min(screenRight - width, centered))
|
||||||
var globalCenter = screenLeft + targetDelegate.x + targetDelegate.width / 2
|
|
||||||
return Math.max(screenLeft, Math.min(screenRight - width, globalCenter - width / 2))
|
|
||||||
}
|
}
|
||||||
y: {
|
y: {
|
||||||
var win = root.Window.window
|
if (!targetDelegate) return 0
|
||||||
|
var delegateGlobal = targetDelegate.mapToGlobal(0, 0)
|
||||||
|
var win = targetDelegate.Window.window
|
||||||
var screenTop = win && win.screen ? win.screen.virtualY : 0
|
var screenTop = win && win.screen ? win.screen.virtualY : 0
|
||||||
var screenBottom = screenTop + (win && win.screen ? win.screen.height : Screen.height)
|
var screenBottom = screenTop + (win && win.screen ? win.screen.height : Screen.height)
|
||||||
// Dock is bottom-anchored; its top edge is at screenBottom - dock window height.
|
var above = delegateGlobal.y - height - Kirigami.Units.smallSpacing
|
||||||
var dockTop = screenBottom - (win ? win.height : root.height)
|
return Math.max(screenTop, Math.min(screenBottom - height, above))
|
||||||
return Math.max(screenTop, dockTop - height - Kirigami.Units.smallSpacing)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onShowingChanged: {
|
onShowingChanged: {
|
||||||
|
|
@ -1196,11 +885,11 @@ MouseArea {
|
||||||
id: dockSpacer
|
id: dockSpacer
|
||||||
visible: root.showSpacer
|
visible: root.showSpacer
|
||||||
x: (repeater.count - root.totalItemCount / 2) * root.dockCellWidth + root.dockCenterX - width / 2
|
x: (repeater.count - root.totalItemCount / 2) * root.dockCellWidth + root.dockCenterX - width / 2
|
||||||
y: parent.height * 0.28
|
y: parent.height * 0.2
|
||||||
width: Math.round(Kirigami.Units.devicePixelRatio)
|
width: Math.round(Kirigami.Units.devicePixelRatio)
|
||||||
height: parent.height * 0.44
|
height: parent.height * 0.6
|
||||||
color: Kirigami.Theme.textColor
|
color: Kirigami.Theme.textColor
|
||||||
opacity: 0.22
|
opacity: 0.4
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaceholderDelegate {
|
PlaceholderDelegate {
|
||||||
|
|
@ -1216,7 +905,7 @@ MouseArea {
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
id: taskRepeater
|
id: taskRepeater
|
||||||
model: root.showRunningTasks ? tasksModel : null
|
model: root.convergenceMode ? tasksModel : null
|
||||||
|
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
id: taskDelegate
|
id: taskDelegate
|
||||||
|
|
@ -1287,13 +976,10 @@ MouseArea {
|
||||||
// Hover highlight background
|
// Hover highlight background
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: root.dockItemInset
|
|
||||||
radius: Kirigami.Units.cornerRadius
|
radius: Kirigami.Units.cornerRadius
|
||||||
color: root.dockItemColor(taskMouseArea.containsPress, taskMouseArea.containsMouse, taskDelegate.model.IsActive === true)
|
color: taskMouseArea.containsPress
|
||||||
|
? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.2)
|
||||||
Behavior on color {
|
: (taskMouseArea.containsMouse ? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.1) : "transparent")
|
||||||
ColorAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyboardHighlight {
|
KeyboardHighlight {
|
||||||
|
|
@ -1304,7 +990,7 @@ MouseArea {
|
||||||
// Task icon
|
// Task icon
|
||||||
Kirigami.Icon {
|
Kirigami.Icon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: root.dockIconSize
|
width: Math.min(parent.width, parent.height) * 0.6
|
||||||
height: width
|
height: width
|
||||||
source: taskDelegate.model.decoration
|
source: taskDelegate.model.decoration
|
||||||
active: taskMouseArea.containsMouse
|
active: taskMouseArea.containsMouse
|
||||||
|
|
@ -1315,9 +1001,7 @@ MouseArea {
|
||||||
target: null
|
target: null
|
||||||
xAxis.enabled: true
|
xAxis.enabled: true
|
||||||
yAxis.enabled: false
|
yAxis.enabled: false
|
||||||
// Enable for unpinned tasks (pin-to-dock drag) and for ALL tasks
|
enabled: root.convergenceMode && taskDelegate.isLocationBottom && !folio.FolioSettings.lockLayout && taskDelegate.taskStorageId !== "" && !folio.FavouritesModel.containsApplication(taskDelegate.taskStorageId)
|
||||||
// when the pager is showing so windows can be dragged to a desktop button.
|
|
||||||
enabled: root.convergenceMode && taskDelegate.isLocationBottom && !folio.FolioSettings.lockLayout && taskDelegate.taskStorageId !== "" && (root.showPager || !folio.FavouritesModel.containsApplication(taskDelegate.taskStorageId))
|
|
||||||
|
|
||||||
onActiveChanged: {
|
onActiveChanged: {
|
||||||
if (active) {
|
if (active) {
|
||||||
|
|
@ -1330,12 +1014,7 @@ MouseArea {
|
||||||
root.taskPinTargetIndex = -1
|
root.taskPinTargetIndex = -1
|
||||||
root.taskPinStorageId = taskDelegate.taskStorageId
|
root.taskPinStorageId = taskDelegate.taskStorageId
|
||||||
} else if (root.taskPinDragIndex === taskDelegate.index) {
|
} else if (root.taskPinDragIndex === taskDelegate.index) {
|
||||||
// If released over a pager button, move the window to that desktop.
|
if (root.taskPinCanDrop) {
|
||||||
let finalCenterX = root.taskBaseX(taskDelegate.index) + root.dockCellWidth / 2 + root.taskPinDragOffset
|
|
||||||
let pagerDesktop = root.pagerButtonDesktopAt(finalCenterX)
|
|
||||||
if (pagerDesktop && taskDelegate.model.IsVirtualDesktopsChangeable === true) {
|
|
||||||
tasksModel.requestVirtualDesktops(tasksModel.makeModelIndex(taskDelegate.index), [pagerDesktop])
|
|
||||||
} else if (root.taskPinCanDrop && !folio.FavouritesModel.containsApplication(root.taskPinStorageId)) {
|
|
||||||
folio.FavouritesModel.addApplicationAt(root.taskPinTargetIndex, root.taskPinStorageId)
|
folio.FavouritesModel.addApplicationAt(root.taskPinTargetIndex, root.taskPinStorageId)
|
||||||
}
|
}
|
||||||
root.clearTaskPinDrag()
|
root.clearTaskPinDrag()
|
||||||
|
|
@ -1360,7 +1039,7 @@ MouseArea {
|
||||||
Row {
|
Row {
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.bottomMargin: Kirigami.Units.smallSpacing
|
anchors.bottomMargin: Kirigami.Units.smallSpacing / 2
|
||||||
spacing: Kirigami.Units.smallSpacing / 2
|
spacing: Kirigami.Units.smallSpacing / 2
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
|
|
@ -1370,15 +1049,11 @@ MouseArea {
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: taskDelegate.model.IsActive === true ? Kirigami.Units.smallSpacing * 3 : Kirigami.Units.smallSpacing * 1.5
|
width: Kirigami.Units.smallSpacing * 1.5
|
||||||
height: Math.max(2, Math.round(Kirigami.Units.devicePixelRatio))
|
height: width
|
||||||
radius: height / 2
|
radius: width / 2
|
||||||
color: Kirigami.Theme.highlightColor
|
color: Kirigami.Theme.highlightColor
|
||||||
opacity: taskDelegate.model.IsActive === true ? 1.0 : 0.45
|
opacity: taskDelegate.model.IsActive === true ? 1.0 : 0.4
|
||||||
|
|
||||||
Behavior on width {
|
|
||||||
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1457,25 +1132,6 @@ MouseArea {
|
||||||
}
|
}
|
||||||
onClicked: tasksModel.requestClose(tasksModel.makeModelIndex(taskDelegate.index))
|
onClicked: tasksModel.requestClose(tasksModel.makeModelIndex(taskDelegate.index))
|
||||||
}
|
}
|
||||||
|
|
||||||
Controls.MenuSeparator {
|
|
||||||
visible: root.showPager && taskDelegate.model.IsVirtualDesktopsChangeable === true
|
|
||||||
}
|
|
||||||
|
|
||||||
Instantiator {
|
|
||||||
model: root.showPager && taskDelegate.model.IsVirtualDesktopsChangeable === true
|
|
||||||
? virtualDesktopInfo.desktopIds : []
|
|
||||||
delegate: PC3.MenuItem {
|
|
||||||
required property int index
|
|
||||||
required property var modelData
|
|
||||||
text: i18n("Move to %1", root.pagerDesktopName(index))
|
|
||||||
enabled: String(modelData) !== String(virtualDesktopInfo.currentDesktop)
|
|
||||||
onTriggered: tasksModel.requestVirtualDesktops(
|
|
||||||
tasksModel.makeModelIndex(taskDelegate.index), [modelData])
|
|
||||||
}
|
|
||||||
onObjectAdded: (idx, obj) => taskContextMenu.insertItem(taskContextMenu.count, obj)
|
|
||||||
onObjectRemoved: (idx, obj) => taskContextMenu.removeItem(obj)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import org.kde.plasma.plasmoid 2.0
|
||||||
import org.kde.plasma.components 3.0 as PC3
|
import org.kde.plasma.components 3.0 as PC3
|
||||||
import org.kde.plasma.private.mobileshell as MobileShell
|
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
|
||||||
import QtQuick.Templates as T
|
|
||||||
import plasma.applet.org.kde.plasma.mobile.homescreen.folio as Folio
|
import plasma.applet.org.kde.plasma.mobile.homescreen.folio as Folio
|
||||||
|
|
||||||
import "./delegate"
|
import "./delegate"
|
||||||
|
|
@ -79,35 +78,6 @@ Item {
|
||||||
Plasmoid.internalAction("configure").trigger();
|
Plasmoid.internalAction("configure").trigger();
|
||||||
}
|
}
|
||||||
|
|
||||||
function showDesktopContextMenu() {
|
|
||||||
desktopContextMenu.popup();
|
|
||||||
}
|
|
||||||
|
|
||||||
PC3.Menu {
|
|
||||||
id: desktopContextMenu
|
|
||||||
popupType: T.Popup.Window
|
|
||||||
|
|
||||||
PC3.MenuItem {
|
|
||||||
text: i18n("Desktop and Wallpaper...")
|
|
||||||
icon.name: "preferences-desktop-wallpaper"
|
|
||||||
onTriggered: root.wallpaperSelectorTriggered()
|
|
||||||
}
|
|
||||||
PC3.MenuItem {
|
|
||||||
text: i18n("Add Widgets...")
|
|
||||||
icon.name: "widget-alternatives"
|
|
||||||
onTriggered: {
|
|
||||||
folio.HomeScreenState.openSettingsView();
|
|
||||||
settings.openWidgetsBrowser();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PC3.MenuSeparator {}
|
|
||||||
PC3.MenuItem {
|
|
||||||
text: i18n("Configure Desktop...")
|
|
||||||
icon.name: "settings-configure"
|
|
||||||
onTriggered: root.openConfigure()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onPressed: (event) => {
|
Keys.onPressed: (event) => {
|
||||||
// The root is focused when we aren't in key navigation mode
|
// The root is focused when we aren't in key navigation mode
|
||||||
// Begin key navigation when arrow keys are pressed
|
// Begin key navigation when arrow keys are pressed
|
||||||
|
|
@ -382,8 +352,8 @@ Item {
|
||||||
visible: opacity > 0 && !ShellSettings.Settings.convergenceModeEnabled
|
visible: opacity > 0 && !ShellSettings.Settings.convergenceModeEnabled
|
||||||
|
|
||||||
// one is ignored as anchors are set
|
// one is ignored as anchors are set
|
||||||
height: ShellSettings.Settings.convergenceModeEnabled ? MobileShell.Constants.convergenceDockHeight : Kirigami.Units.gridUnit * 6
|
height: ShellSettings.Settings.convergenceModeEnabled ? Kirigami.Units.gridUnit * 3 : Kirigami.Units.gridUnit * 6
|
||||||
width: ShellSettings.Settings.convergenceModeEnabled ? MobileShell.Constants.convergenceDockHeight : Kirigami.Units.gridUnit * 6
|
width: ShellSettings.Settings.convergenceModeEnabled ? Kirigami.Units.gridUnit * 3 : Kirigami.Units.gridUnit * 6
|
||||||
|
|
||||||
anchors.topMargin: root.topMargin
|
anchors.topMargin: root.topMargin
|
||||||
anchors.bottomMargin: ShellSettings.Settings.convergenceModeEnabled ? 0 : root.bottomMargin
|
anchors.bottomMargin: ShellSettings.Settings.convergenceModeEnabled ? 0 : root.bottomMargin
|
||||||
|
|
@ -439,7 +409,7 @@ Item {
|
||||||
}
|
}
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: favouritesBar
|
target: favouritesBar
|
||||||
height: ShellSettings.Settings.convergenceModeEnabled ? MobileShell.Constants.convergenceDockHeight : Kirigami.Units.gridUnit * 6
|
height: ShellSettings.Settings.convergenceModeEnabled ? Kirigami.Units.gridUnit * 3 : Kirigami.Units.gridUnit * 6
|
||||||
}
|
}
|
||||||
}, State {
|
}, State {
|
||||||
name: "left"
|
name: "left"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import QtQuick.Layouts
|
||||||
|
|
||||||
import org.kde.plasma.components 3.0 as PC3
|
import org.kde.plasma.components 3.0 as PC3
|
||||||
import org.kde.plasma.private.mobileshell as MobileShell
|
import org.kde.plasma.private.mobileshell as MobileShell
|
||||||
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
|
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
import plasma.applet.org.kde.plasma.mobile.homescreen.folio as Folio
|
import plasma.applet.org.kde.plasma.mobile.homescreen.folio as Folio
|
||||||
|
|
||||||
|
|
@ -29,14 +28,11 @@ MouseArea {
|
||||||
}
|
}
|
||||||
|
|
||||||
onClicked: (mouse) => {
|
onClicked: (mouse) => {
|
||||||
|
// Right-click opens settings view (wallpaper/widgets), same as long-press
|
||||||
if (mouse.button === Qt.RightButton) {
|
if (mouse.button === Qt.RightButton) {
|
||||||
if (ShellSettings.Settings.convergenceModeEnabled) {
|
|
||||||
root.homeScreen.showDesktopContextMenu();
|
|
||||||
} else {
|
|
||||||
folio.HomeScreenState.openSettingsView();
|
folio.HomeScreenState.openSettingsView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
onDoubleClicked: {
|
onDoubleClicked: {
|
||||||
if (folio.FolioSettings.doubleTapToLock) {
|
if (folio.FolioSettings.doubleTapToLock) {
|
||||||
|
|
|
||||||
|
|
@ -1,715 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2026 Marco Allegretti
|
|
||||||
// SPDX-License-Identifier: EUPL-1.2
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Layouts
|
|
||||||
|
|
||||||
import org.kde.kirigami as Kirigami
|
|
||||||
import org.kde.plasma.components 3.0 as PC3
|
|
||||||
import org.kde.taskmanager as TaskManager
|
|
||||||
|
|
||||||
import plasma.applet.org.kde.plasma.mobile.homescreen.folio as Folio
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
required property var folio
|
|
||||||
|
|
||||||
readonly property bool hasTasks: allTasksModel.count > 0
|
|
||||||
property bool sortByName: false
|
|
||||||
property int dragTargetDesktopIndex: -1
|
|
||||||
property string pendingMoveTaskKey: ""
|
|
||||||
property string pendingMoveTargetName: ""
|
|
||||||
|
|
||||||
signal taskActivated()
|
|
||||||
|
|
||||||
function taskStorageId(taskModel) {
|
|
||||||
var id = taskModel ? taskModel.AppId || "" : ""
|
|
||||||
if (id && !id.endsWith(".desktop")) {
|
|
||||||
id += ".desktop"
|
|
||||||
}
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
function taskKey(taskModel) {
|
|
||||||
const winIds = taskModel && taskModel.WinIdList ? taskModel.WinIdList : []
|
|
||||||
if (winIds.length > 0) {
|
|
||||||
var key = ""
|
|
||||||
for (var i = 0; i < winIds.length; ++i) {
|
|
||||||
key += String(winIds[i]) + "|"
|
|
||||||
}
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
return String(taskModel ? taskModel.AppId || "" : "") + "|" + String(taskModel ? taskModel.display || "" : "")
|
|
||||||
}
|
|
||||||
|
|
||||||
function markTaskMove(taskKey, desktopIndex) {
|
|
||||||
pendingMoveTaskKey = taskKey
|
|
||||||
pendingMoveTargetName = desktopName(desktopIndex)
|
|
||||||
pendingMoveResetTimer.restart()
|
|
||||||
}
|
|
||||||
|
|
||||||
function mixColor(base, overlay, ratio) {
|
|
||||||
return Qt.rgba(
|
|
||||||
base.r + (overlay.r - base.r) * ratio,
|
|
||||||
base.g + (overlay.g - base.g) * ratio,
|
|
||||||
base.b + (overlay.b - base.b) * ratio,
|
|
||||||
base.a + (overlay.a - base.a) * ratio)
|
|
||||||
}
|
|
||||||
|
|
||||||
function desktopName(index) {
|
|
||||||
const names = virtualDesktopInfo.desktopNames
|
|
||||||
if (names && names.length > index && String(names[index]).length > 0) {
|
|
||||||
return String(names[index])
|
|
||||||
}
|
|
||||||
return "Desktop " + (index + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
function isCurrentDesktop(desktopId) {
|
|
||||||
return String(desktopId) === String(virtualDesktopInfo.currentDesktop)
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: pendingMoveResetTimer
|
|
||||||
interval: 1200
|
|
||||||
onTriggered: {
|
|
||||||
root.pendingMoveTaskKey = ""
|
|
||||||
root.pendingMoveTargetName = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskManager.VirtualDesktopInfo { id: virtualDesktopInfo }
|
|
||||||
TaskManager.ActivityInfo { id: activityInfo }
|
|
||||||
|
|
||||||
TaskManager.TasksModel {
|
|
||||||
id: allTasksModel
|
|
||||||
filterByVirtualDesktop: false
|
|
||||||
filterByActivity: true
|
|
||||||
filterNotMaximized: false
|
|
||||||
filterByScreen: true
|
|
||||||
filterHidden: false
|
|
||||||
activity: activityInfo.currentActivity
|
|
||||||
groupMode: TaskManager.TasksModel.GroupApplications
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskManager.TasksModel {
|
|
||||||
id: tasksModel
|
|
||||||
filterByVirtualDesktop: true
|
|
||||||
filterByActivity: true
|
|
||||||
filterNotMaximized: false
|
|
||||||
filterByScreen: true
|
|
||||||
filterHidden: false
|
|
||||||
virtualDesktop: virtualDesktopInfo.currentDesktop
|
|
||||||
activity: activityInfo.currentActivity
|
|
||||||
groupMode: TaskManager.TasksModel.GroupApplications
|
|
||||||
sortMode: root.sortByName ? TaskManager.TasksModel.SortAlpha : TaskManager.TasksModel.SortLastActivated
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: panelShadow
|
|
||||||
anchors.fill: panelBackground
|
|
||||||
anchors.topMargin: 2
|
|
||||||
radius: panelBackground.radius
|
|
||||||
color: Qt.rgba(0, 0, 0, 0.35)
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: panelBackground
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: Kirigami.Units.cornerRadius
|
|
||||||
color: Kirigami.Theme.backgroundColor
|
|
||||||
border.width: 1
|
|
||||||
border.pixelAligned: false
|
|
||||||
border.color: root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.14)
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Kirigami.Units.smallSpacing
|
|
||||||
spacing: Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
PC3.Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: i18n("Running")
|
|
||||||
font.weight: Font.Medium
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: 1
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: [
|
|
||||||
{ label: i18n("Recent"), byName: false },
|
|
||||||
{ label: i18n("Name"), byName: true }
|
|
||||||
]
|
|
||||||
|
|
||||||
delegate: MouseArea {
|
|
||||||
id: sortButton
|
|
||||||
|
|
||||||
required property var modelData
|
|
||||||
readonly property bool checked: root.sortByName === modelData.byName
|
|
||||||
|
|
||||||
width: Math.max(Kirigami.Units.gridUnit * 3.5, label.implicitWidth + Kirigami.Units.smallSpacing * 3)
|
|
||||||
height: Kirigami.Units.gridUnit * 1.6
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: root.sortByName = modelData.byName
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: Kirigami.Units.cornerRadius
|
|
||||||
color: sortButton.checked
|
|
||||||
? root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, sortButton.containsMouse ? 0.28 : 0.2)
|
|
||||||
: sortButton.containsMouse
|
|
||||||
? root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.08)
|
|
||||||
: "transparent"
|
|
||||||
}
|
|
||||||
|
|
||||||
PC3.Label {
|
|
||||||
id: label
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: sortButton.modelData.label
|
|
||||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.85
|
|
||||||
color: sortButton.checked ? Kirigami.Theme.highlightColor : Kirigami.Theme.textColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: desktopStrip
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
visible: virtualDesktopInfo.numberOfDesktops > 1
|
|
||||||
spacing: Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
PC3.Label {
|
|
||||||
text: i18n("Desktops")
|
|
||||||
opacity: 0.7
|
|
||||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.85
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: desktopDropSurface
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2.4
|
|
||||||
|
|
||||||
function desktopIndexAt(x) {
|
|
||||||
if (virtualDesktopInfo.numberOfDesktops <= 0) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
const localX = desktopRow.mapFromItem(desktopDropSurface, x, 0).x
|
|
||||||
var nearestIndex = -1
|
|
||||||
var nearestDistance = Number.MAX_VALUE
|
|
||||||
for (var i = 0; i < virtualDesktopInfo.numberOfDesktops; ++i) {
|
|
||||||
const item = desktopRepeater.itemAt(i)
|
|
||||||
if (!item) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (localX >= item.x && localX <= item.x + item.width) {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
const center = item.x + item.width / 2
|
|
||||||
const distance = Math.abs(localX - center)
|
|
||||||
if (distance < nearestDistance) {
|
|
||||||
nearestIndex = i
|
|
||||||
nearestDistance = distance
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nearestIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: desktopRow
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
spacing: Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
id: desktopRepeater
|
|
||||||
|
|
||||||
model: virtualDesktopInfo.desktopIds
|
|
||||||
|
|
||||||
delegate: MouseArea {
|
|
||||||
id: desktopButton
|
|
||||||
|
|
||||||
required property int index
|
|
||||||
required property var modelData
|
|
||||||
|
|
||||||
readonly property bool checked: root.isCurrentDesktop(modelData)
|
|
||||||
readonly property string desktopLabel: root.desktopName(index)
|
|
||||||
readonly property bool dragHovered: desktopDropArea.containsDrag && root.dragTargetDesktopIndex === index
|
|
||||||
|
|
||||||
width: Math.max(Kirigami.Units.gridUnit * 5.5, (desktopRow.width / Math.max(1, virtualDesktopInfo.numberOfDesktops)) - Kirigami.Units.smallSpacing)
|
|
||||||
height: desktopRow.height
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
|
|
||||||
onClicked: root.folio.activateVirtualDesktop(modelData)
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: Kirigami.Units.cornerRadius
|
|
||||||
scale: desktopButton.dragHovered ? 1.03 : 1
|
|
||||||
color: desktopButton.checked
|
|
||||||
? root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, desktopButton.containsMouse || desktopButton.dragHovered ? 0.32 : 0.24)
|
|
||||||
: desktopButton.dragHovered
|
|
||||||
? root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.18)
|
|
||||||
: desktopButton.containsMouse
|
|
||||||
? root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.08)
|
|
||||||
: root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.045)
|
|
||||||
border.width: 1
|
|
||||||
border.pixelAligned: false
|
|
||||||
border.color: desktopButton.checked || desktopButton.dragHovered
|
|
||||||
? root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.55)
|
|
||||||
: root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.14)
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
|
|
||||||
}
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.margins: Kirigami.Units.smallSpacing / 2
|
|
||||||
height: Math.max(2, Math.round(Kirigami.Units.devicePixelRatio))
|
|
||||||
radius: height / 2
|
|
||||||
visible: desktopButton.checked
|
|
||||||
color: Kirigami.Theme.highlightColor
|
|
||||||
}
|
|
||||||
|
|
||||||
PC3.Label {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: parent.width - Kirigami.Units.smallSpacing * 2
|
|
||||||
text: desktopButton.desktopLabel
|
|
||||||
elide: Text.ElideRight
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
font.weight: desktopButton.checked || desktopButton.dragHovered ? Font.Medium : Font.Normal
|
|
||||||
font.pixelSize: Math.min(Kirigami.Theme.defaultFont.pixelSize, parent.height * 0.42)
|
|
||||||
color: desktopButton.checked || desktopButton.dragHovered ? Kirigami.Theme.highlightColor : Kirigami.Theme.textColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DropArea {
|
|
||||||
id: desktopDropArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
keys: ["folio-running-task"]
|
|
||||||
|
|
||||||
onEntered: (drag) => {
|
|
||||||
root.dragTargetDesktopIndex = desktopDropSurface.desktopIndexAt(drag.x)
|
|
||||||
drag.accept(Qt.MoveAction)
|
|
||||||
}
|
|
||||||
onPositionChanged: (drag) => {
|
|
||||||
root.dragTargetDesktopIndex = desktopDropSurface.desktopIndexAt(drag.x)
|
|
||||||
drag.accept(Qt.MoveAction)
|
|
||||||
}
|
|
||||||
onExited: root.dragTargetDesktopIndex = -1
|
|
||||||
onDropped: (drop) => {
|
|
||||||
const desktopIndex = desktopDropSurface.desktopIndexAt(drop.x)
|
|
||||||
const desktopId = desktopIndex >= 0 ? virtualDesktopInfo.desktopIds[desktopIndex] : ""
|
|
||||||
if (!drop.source || !drop.source.moveToDesktop || String(desktopId).length === 0) {
|
|
||||||
root.dragTargetDesktopIndex = -1
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
drop.source.moveToDesktop(desktopId, desktopIndex)
|
|
||||||
root.dragTargetDesktopIndex = -1
|
|
||||||
drop.accept(Qt.MoveAction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GridView {
|
|
||||||
id: taskGrid
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
clip: true
|
|
||||||
model: tasksModel
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
|
||||||
interactive: contentHeight > height
|
|
||||||
|
|
||||||
readonly property int columns: Math.max(1, Math.floor(width / (Kirigami.Units.gridUnit * 14)))
|
|
||||||
cellWidth: Math.floor(width / columns)
|
|
||||||
cellHeight: Kirigami.Units.gridUnit * 10
|
|
||||||
|
|
||||||
delegate: Item {
|
|
||||||
id: taskCard
|
|
||||||
|
|
||||||
required property int index
|
|
||||||
required property var model
|
|
||||||
|
|
||||||
width: taskGrid.cellWidth - Kirigami.Units.smallSpacing
|
|
||||||
height: taskGrid.cellHeight - Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
readonly property var modelIndex: tasksModel.makeModelIndex(index)
|
|
||||||
readonly property var winIds: model.WinIdList ? model.WinIdList : []
|
|
||||||
readonly property int previewCount: Math.max(1, Math.min(2, winIds.length))
|
|
||||||
readonly property bool activeTask: model.IsActive === true
|
|
||||||
readonly property bool minimizedTask: model.IsMinimized === true
|
|
||||||
readonly property bool maximizedTask: model.IsMaximized === true
|
|
||||||
readonly property bool groupTask: model.IsGroupParent === true
|
|
||||||
readonly property bool desktopsChangeable: model.IsVirtualDesktopsChangeable === true
|
|
||||||
readonly property string storageId: root.taskStorageId(model)
|
|
||||||
readonly property string taskKey: root.taskKey(model)
|
|
||||||
readonly property bool pinned: storageId !== "" && root.folio.FavouritesModel.containsApplication(storageId)
|
|
||||||
readonly property bool pendingMove: root.pendingMoveTaskKey === taskKey
|
|
||||||
|
|
||||||
function taskIndexForPreview(previewIndex) {
|
|
||||||
return taskCard.groupTask
|
|
||||||
? tasksModel.makeModelIndex(taskCard.index, previewIndex)
|
|
||||||
: taskCard.modelIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
function titleForPreview(previewIndex) {
|
|
||||||
if (!taskCard.groupTask) {
|
|
||||||
return taskCard.model.display || ""
|
|
||||||
}
|
|
||||||
return tasksModel.data(tasksModel.makeModelIndex(taskCard.index, previewIndex), 0) || taskCard.model.display || ""
|
|
||||||
}
|
|
||||||
|
|
||||||
function activate(previewIndex) {
|
|
||||||
tasksModel.requestActivate(taskIndexForPreview(previewIndex || 0))
|
|
||||||
root.taskActivated()
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveToDesktop(desktopId, desktopIndex) {
|
|
||||||
if (!taskCard.desktopsChangeable || String(desktopId).length === 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
root.markTaskMove(taskCard.taskKey, desktopIndex)
|
|
||||||
tasksModel.requestVirtualDesktops(taskCard.modelIndex, [desktopId])
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: dragProxy
|
|
||||||
|
|
||||||
parent: root
|
|
||||||
width: taskCard.width
|
|
||||||
height: taskCard.height
|
|
||||||
z: 1000
|
|
||||||
visible: cardArea.drag.active
|
|
||||||
opacity: 0.9
|
|
||||||
|
|
||||||
Drag.active: cardArea.drag.active
|
|
||||||
Drag.hotSpot.x: cardArea.pressX
|
|
||||||
Drag.hotSpot.y: cardArea.pressY
|
|
||||||
Drag.keys: ["folio-running-task"]
|
|
||||||
Drag.proposedAction: Qt.MoveAction
|
|
||||||
Drag.source: taskCard
|
|
||||||
Drag.supportedActions: Qt.MoveAction
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: Kirigami.Units.cornerRadius
|
|
||||||
color: root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.2)
|
|
||||||
border.width: 1
|
|
||||||
border.pixelAligned: false
|
|
||||||
border.color: root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.6)
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Kirigami.Units.smallSpacing
|
|
||||||
spacing: Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
Kirigami.Icon {
|
|
||||||
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
|
|
||||||
Layout.preferredHeight: Layout.preferredWidth
|
|
||||||
source: taskCard.model.decoration
|
|
||||||
}
|
|
||||||
|
|
||||||
PC3.Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: taskCard.model.display || ""
|
|
||||||
elide: Text.ElideRight
|
|
||||||
maximumLineCount: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: cardArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: taskCard.desktopsChangeable ? Qt.OpenHandCursor : Qt.PointingHandCursor
|
|
||||||
enabled: !taskCard.pendingMove
|
|
||||||
property real pressX: width / 2
|
|
||||||
property real pressY: height / 2
|
|
||||||
property bool wasDragged: false
|
|
||||||
drag.target: taskCard.desktopsChangeable ? dragProxy : undefined
|
|
||||||
drag.threshold: Math.max(4, Kirigami.Units.smallSpacing)
|
|
||||||
drag.smoothed: false
|
|
||||||
|
|
||||||
onPressed: (mouse) => {
|
|
||||||
wasDragged = false
|
|
||||||
pressX = mouse.x
|
|
||||||
pressY = mouse.y
|
|
||||||
const pos = taskCard.mapToItem(root, 0, 0)
|
|
||||||
dragProxy.x = pos.x
|
|
||||||
dragProxy.y = pos.y
|
|
||||||
}
|
|
||||||
|
|
||||||
onPositionChanged: {
|
|
||||||
if (drag.active) {
|
|
||||||
wasDragged = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onReleased: {
|
|
||||||
if (wasDragged) {
|
|
||||||
dragProxy.Drag.drop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
if (!wasDragged) {
|
|
||||||
taskCard.activate(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: Kirigami.Units.cornerRadius
|
|
||||||
color: taskCard.activeTask
|
|
||||||
? root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, cardArea.containsMouse ? 0.18 : 0.12)
|
|
||||||
: cardArea.containsMouse
|
|
||||||
? root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.08)
|
|
||||||
: root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.04)
|
|
||||||
border.width: 1
|
|
||||||
border.pixelAligned: false
|
|
||||||
border.color: taskCard.activeTask
|
|
||||||
? root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.5)
|
|
||||||
: root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.12)
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Kirigami.Units.smallSpacing
|
|
||||||
spacing: Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: previewRow
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 5
|
|
||||||
spacing: Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: taskCard.previewCount
|
|
||||||
|
|
||||||
delegate: MouseArea {
|
|
||||||
id: previewArea
|
|
||||||
|
|
||||||
required property int index
|
|
||||||
readonly property string childUuid: taskCard.winIds.length > index ? taskCard.winIds[index] : ""
|
|
||||||
|
|
||||||
width: (previewRow.width - previewRow.spacing * (taskCard.previewCount - 1)) / taskCard.previewCount
|
|
||||||
height: previewRow.height
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: taskCard.activate(index)
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: Kirigami.Units.cornerRadius
|
|
||||||
color: root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, previewArea.containsMouse ? 0.1 : 0.06)
|
|
||||||
border.width: 1
|
|
||||||
border.pixelAligned: false
|
|
||||||
border.color: root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.14)
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
id: thumbnailLoader
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 1
|
|
||||||
active: previewArea.childUuid !== "" && root.visible
|
|
||||||
sourceComponent: PipeWireThumbnail {
|
|
||||||
windowUuid: previewArea.childUuid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.Icon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: Kirigami.Units.iconSizes.large
|
|
||||||
height: width
|
|
||||||
source: taskCard.model.decoration
|
|
||||||
visible: !thumbnailLoader.item || !thumbnailLoader.item.hasThumbnail
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
height: titleLabel.implicitHeight + Kirigami.Units.smallSpacing
|
|
||||||
radius: Kirigami.Units.cornerRadius
|
|
||||||
color: Qt.rgba(0, 0, 0, 0.48)
|
|
||||||
visible: taskCard.previewCount > 1
|
|
||||||
|
|
||||||
PC3.Label {
|
|
||||||
id: titleLabel
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.margins: Kirigami.Units.smallSpacing
|
|
||||||
text: taskCard.titleForPreview(previewArea.index)
|
|
||||||
elide: Text.ElideRight
|
|
||||||
maximumLineCount: 1
|
|
||||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.75
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
Kirigami.Icon {
|
|
||||||
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
|
|
||||||
Layout.preferredHeight: Layout.preferredWidth
|
|
||||||
source: taskCard.model.decoration
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
PC3.Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: taskCard.model.display || ""
|
|
||||||
font.weight: taskCard.activeTask ? Font.Medium : Font.Normal
|
|
||||||
elide: Text.ElideRight
|
|
||||||
maximumLineCount: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
PC3.Label {
|
|
||||||
text: taskCard.activeTask ? i18n("Active") : taskCard.minimizedTask ? i18n("Minimized") : i18n("Open")
|
|
||||||
opacity: 0.65
|
|
||||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.75
|
|
||||||
}
|
|
||||||
|
|
||||||
PC3.Label {
|
|
||||||
visible: taskCard.maximizedTask
|
|
||||||
text: i18n("Maximized")
|
|
||||||
opacity: 0.65
|
|
||||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.75
|
|
||||||
}
|
|
||||||
|
|
||||||
PC3.Label {
|
|
||||||
visible: taskCard.winIds.length > 1
|
|
||||||
text: i18np("%1 window", "%1 windows", taskCard.winIds.length)
|
|
||||||
opacity: 0.65
|
|
||||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.75
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
Item { Layout.fillWidth: true }
|
|
||||||
|
|
||||||
RunningAppsPanelButton {
|
|
||||||
iconName: taskCard.pinned ? "emblem-favorite" : "window-pin"
|
|
||||||
toolTipText: taskCard.pinned ? i18n("Pinned") : i18n("Pin to Dock")
|
|
||||||
checked: taskCard.pinned
|
|
||||||
enabled: taskCard.storageId !== "" && !taskCard.pinned && !root.folio.FolioSettings.lockLayout
|
|
||||||
onTriggered: root.folio.FavouritesModel.addApplication(taskCard.storageId)
|
|
||||||
}
|
|
||||||
|
|
||||||
RunningAppsPanelButton {
|
|
||||||
iconName: taskCard.minimizedTask ? "window-restore" : "window-minimize"
|
|
||||||
toolTipText: taskCard.minimizedTask ? i18n("Restore") : i18n("Minimize")
|
|
||||||
onTriggered: tasksModel.requestToggleMinimized(taskCard.modelIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
RunningAppsPanelButton {
|
|
||||||
iconName: taskCard.maximizedTask ? "window-restore" : "window-maximize"
|
|
||||||
toolTipText: taskCard.maximizedTask ? i18n("Restore") : i18n("Maximize")
|
|
||||||
enabled: !taskCard.groupTask
|
|
||||||
onTriggered: tasksModel.requestToggleMaximized(taskCard.modelIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
RunningAppsPanelButton {
|
|
||||||
iconName: "window-close"
|
|
||||||
toolTipText: taskCard.winIds.length > 1 ? i18n("Close All") : i18n("Close")
|
|
||||||
onTriggered: tasksModel.requestClose(taskCard.modelIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: Kirigami.Units.cornerRadius
|
|
||||||
visible: taskCard.pendingMove
|
|
||||||
color: root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.18)
|
|
||||||
border.width: 1
|
|
||||||
border.pixelAligned: false
|
|
||||||
border.color: root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.55)
|
|
||||||
|
|
||||||
PC3.Label {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: parent.width - Kirigami.Units.gridUnit
|
|
||||||
text: i18n("Moving to %1", root.pendingMoveTargetName)
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
elide: Text.ElideRight
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Kirigami.Theme.highlightColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PC3.ScrollBar.vertical: PC3.ScrollBar {
|
|
||||||
interactive: true
|
|
||||||
enabled: taskGrid.contentHeight > taskGrid.height
|
|
||||||
implicitWidth: Kirigami.Units.smallSpacing
|
|
||||||
}
|
|
||||||
|
|
||||||
PC3.Label {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: parent.width - Kirigami.Units.gridUnit * 2
|
|
||||||
visible: taskGrid.count === 0
|
|
||||||
text: i18n("No windows on this desktop")
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
opacity: 0.65
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2026 Marco Allegretti
|
|
||||||
// SPDX-License-Identifier: EUPL-1.2
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
|
|
||||||
import org.kde.kirigami as Kirigami
|
|
||||||
import org.kde.plasma.components 3.0 as PC3
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: button
|
|
||||||
|
|
||||||
property string iconName
|
|
||||||
property string toolTipText
|
|
||||||
property bool checked: false
|
|
||||||
|
|
||||||
signal triggered()
|
|
||||||
|
|
||||||
function _mix(base, overlay, ratio) {
|
|
||||||
return Qt.rgba(
|
|
||||||
base.r + (overlay.r - base.r) * ratio,
|
|
||||||
base.g + (overlay.g - base.g) * ratio,
|
|
||||||
base.b + (overlay.b - base.b) * ratio,
|
|
||||||
base.a + (overlay.a - base.a) * ratio)
|
|
||||||
}
|
|
||||||
|
|
||||||
width: Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.smallSpacing * 2
|
|
||||||
height: width
|
|
||||||
hoverEnabled: enabled
|
|
||||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
|
||||||
opacity: enabled ? 1 : 0.35
|
|
||||||
|
|
||||||
onClicked: button.triggered()
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: Kirigami.Units.cornerRadius
|
|
||||||
color: button.containsPress
|
|
||||||
? button._mix(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.16)
|
|
||||||
: button.checked
|
|
||||||
? button._mix(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, button.containsMouse ? 0.22 : 0.16)
|
|
||||||
: button.containsMouse
|
|
||||||
? button._mix(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.08)
|
|
||||||
: "transparent"
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.Icon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: Kirigami.Units.iconSizes.small
|
|
||||||
height: width
|
|
||||||
source: button.iconName
|
|
||||||
active: button.containsMouse || button.checked
|
|
||||||
}
|
|
||||||
|
|
||||||
PC3.ToolTip {
|
|
||||||
text: button.toolTipText
|
|
||||||
visible: button.containsMouse && button.toolTipText.length > 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -422,14 +422,8 @@ Window {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cycle through source filter tabs (only includes installed launcher sources).
|
// Cycle through source filter tabs.
|
||||||
readonly property var _sourceFilters: {
|
readonly property var _sourceFilters: ["", "steam", "desktop", "waydroid", "lutris", "heroic"]
|
||||||
var filters = ["", "desktop", "waydroid"]
|
|
||||||
if (GamingShell.GameLauncherProvider.steamAvailable) filters.splice(1, 0, "steam")
|
|
||||||
if (GamingShell.GameLauncherProvider.lutrisAvailable) filters.push("lutris")
|
|
||||||
if (GamingShell.GameLauncherProvider.heroicAvailable) filters.push("heroic")
|
|
||||||
return filters
|
|
||||||
}
|
|
||||||
function cycleSourceFilter(direction) {
|
function cycleSourceFilter(direction) {
|
||||||
var current = _sourceFilters.indexOf(
|
var current = _sourceFilters.indexOf(
|
||||||
GamingShell.GameLauncherProvider.sourceFilter)
|
GamingShell.GameLauncherProvider.sourceFilter)
|
||||||
|
|
@ -755,26 +749,35 @@ Window {
|
||||||
id: sourceFilterBar
|
id: sourceFilterBar
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|
||||||
Repeater {
|
QQC2.TabButton {
|
||||||
model: {
|
text: i18n("All")
|
||||||
var tabs = [
|
width: implicitWidth
|
||||||
{label: i18n("All"), filter: ""},
|
onClicked: GamingShell.GameLauncherProvider.sourceFilter = ""
|
||||||
{label: i18n("Desktop"), filter: "desktop"},
|
|
||||||
{label: i18n("Waydroid"),filter: "waydroid"}
|
|
||||||
]
|
|
||||||
if (GamingShell.GameLauncherProvider.steamAvailable)
|
|
||||||
tabs.splice(1, 0, {label: "Steam", filter: "steam"})
|
|
||||||
if (GamingShell.GameLauncherProvider.lutrisAvailable)
|
|
||||||
tabs.push({label: "Lutris", filter: "lutris"})
|
|
||||||
if (GamingShell.GameLauncherProvider.heroicAvailable)
|
|
||||||
tabs.push({label: "Heroic", filter: "heroic"})
|
|
||||||
return tabs
|
|
||||||
}
|
}
|
||||||
QQC2.TabButton {
|
QQC2.TabButton {
|
||||||
text: modelData.label
|
text: "Steam"
|
||||||
width: implicitWidth
|
width: implicitWidth
|
||||||
onClicked: GamingShell.GameLauncherProvider.sourceFilter = modelData.filter
|
onClicked: GamingShell.GameLauncherProvider.sourceFilter = "steam"
|
||||||
}
|
}
|
||||||
|
QQC2.TabButton {
|
||||||
|
text: i18n("Desktop")
|
||||||
|
width: implicitWidth
|
||||||
|
onClicked: GamingShell.GameLauncherProvider.sourceFilter = "desktop"
|
||||||
|
}
|
||||||
|
QQC2.TabButton {
|
||||||
|
text: i18n("Waydroid")
|
||||||
|
width: implicitWidth
|
||||||
|
onClicked: GamingShell.GameLauncherProvider.sourceFilter = "waydroid"
|
||||||
|
}
|
||||||
|
QQC2.TabButton {
|
||||||
|
text: "Lutris"
|
||||||
|
width: implicitWidth
|
||||||
|
onClicked: GamingShell.GameLauncherProvider.sourceFilter = "lutris"
|
||||||
|
}
|
||||||
|
QQC2.TabButton {
|
||||||
|
text: "Heroic"
|
||||||
|
width: implicitWidth
|
||||||
|
onClicked: GamingShell.GameLauncherProvider.sourceFilter = "heroic"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ import "./private"
|
||||||
|
|
||||||
ContainmentItem {
|
ContainmentItem {
|
||||||
id: root
|
id: root
|
||||||
property var folio: root.plasmoid
|
property Folio.HomeScreen folio: root.plasmoid
|
||||||
|
|
||||||
// Tracks whether the Game Center grid is visible within gaming mode.
|
// Tracks whether the Game Center grid is visible within gaming mode.
|
||||||
// If gaming mode is already enabled at startup, open it immediately so
|
// If gaming mode is already enabled at startup, open it immediately so
|
||||||
|
|
@ -279,7 +279,7 @@ ContainmentItem {
|
||||||
visible: ShellSettings.Settings.convergenceModeEnabled && !ShellSettings.Settings.gamingModeEnabled
|
visible: ShellSettings.Settings.convergenceModeEnabled && !ShellSettings.Settings.gamingModeEnabled
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
width: Screen.width
|
width: Screen.width
|
||||||
height: MobileShell.Constants.convergenceDockHeight
|
height: Kirigami.Units.gridUnit * 3
|
||||||
|
|
||||||
LayerShell.Window.scope: "dock-overlay"
|
LayerShell.Window.scope: "dock-overlay"
|
||||||
LayerShell.Window.layer: LayerShell.Window.LayerTop
|
LayerShell.Window.layer: LayerShell.Window.LayerTop
|
||||||
|
|
@ -290,11 +290,11 @@ ContainmentItem {
|
||||||
// Auto-hide: slide dock content off-screen when a window is
|
// Auto-hide: slide dock content off-screen when a window is
|
||||||
// maximized. The reveal strip at the screen edge brings it back.
|
// maximized. The reveal strip at the screen edge brings it back.
|
||||||
property real dockOffset: 0
|
property real dockOffset: 0
|
||||||
readonly property real dockHeight: MobileShell.Constants.convergenceDockHeight
|
readonly property real dockHeight: Kirigami.Units.gridUnit * 3
|
||||||
|
|
||||||
// Height of the input-receive strip kept at the screen edge when
|
// Height of the input-receive strip kept at the screen edge when
|
||||||
// the dock is hidden. Matches the navigation panel convention.
|
// the dock is hidden. Matches the navigation panel convention.
|
||||||
readonly property real revealStripHeight: MobileShell.Constants.convergenceDockRevealHeight
|
readonly property real revealStripHeight: Kirigami.Units.gridUnit
|
||||||
|
|
||||||
// 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
|
||||||
|
|
@ -353,10 +353,10 @@ ContainmentItem {
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: !dockOverlay.shouldHide || dockOverlay.dockOffset < dockOverlay.dockHeight
|
|
||||||
Kirigami.Theme.inherit: false
|
Kirigami.Theme.inherit: false
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||||
color: Kirigami.Theme.backgroundColor
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
transform: Translate { y: dockOverlay.dockOffset }
|
||||||
}
|
}
|
||||||
|
|
||||||
FavouritesBar {
|
FavouritesBar {
|
||||||
|
|
@ -365,7 +365,6 @@ ContainmentItem {
|
||||||
folio: root.folio
|
folio: root.folio
|
||||||
maskManager: root.maskManager
|
maskManager: root.maskManager
|
||||||
homeScreen: folioHomeScreen
|
homeScreen: folioHomeScreen
|
||||||
suppressRunningTasks: runningAppsPanel.visible
|
|
||||||
transform: Translate { y: dockOverlay.dockOffset }
|
transform: Translate { y: dockOverlay.dockOffset }
|
||||||
// Dock is an opaque panel — use Window colorset so all content
|
// Dock is an opaque panel — use Window colorset so all content
|
||||||
// (labels, hover highlights, icon tints) follows the system theme
|
// (labels, hover highlights, icon tints) follows the system theme
|
||||||
|
|
@ -408,7 +407,7 @@ ContainmentItem {
|
||||||
|
|
||||||
readonly property real popupWidth: Math.min(Kirigami.Units.gridUnit * 28, parent.width * 0.5)
|
readonly property real popupWidth: Math.min(Kirigami.Units.gridUnit * 28, parent.width * 0.5)
|
||||||
readonly property real popupHeight: Math.min(Kirigami.Units.gridUnit * 32, parent.height * 0.7)
|
readonly property real popupHeight: Math.min(Kirigami.Units.gridUnit * 32, parent.height * 0.7)
|
||||||
readonly property real dockHeight: MobileShell.Constants.convergenceDockHeight
|
readonly property real dockHeight: Kirigami.Units.gridUnit * 3
|
||||||
|
|
||||||
width: popupWidth
|
width: popupWidth
|
||||||
height: popupHeight
|
height: popupHeight
|
||||||
|
|
@ -520,9 +519,7 @@ ContainmentItem {
|
||||||
|
|
||||||
width: tileSize
|
width: tileSize
|
||||||
height: overlayDrawer.popupHeight
|
height: overlayDrawer.popupHeight
|
||||||
x: runningAppsPanel.visible
|
x: categoryPanel.x + categoryPanel.width + Kirigami.Units.smallSpacing
|
||||||
? runningAppsPanel.x + runningAppsPanel.width + Kirigami.Units.smallSpacing
|
|
||||||
: categoryPanel.x + categoryPanel.width + Kirigami.Units.smallSpacing
|
|
||||||
y: overlayDrawer.y
|
y: overlayDrawer.y
|
||||||
opacity: overlayDrawer.opacity
|
opacity: overlayDrawer.opacity
|
||||||
radius: Kirigami.Units.cornerRadius
|
radius: Kirigami.Units.cornerRadius
|
||||||
|
|
@ -742,20 +739,6 @@ ContainmentItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RunningAppsPanel {
|
|
||||||
id: runningAppsPanel
|
|
||||||
folio: root.folio
|
|
||||||
|
|
||||||
x: categoryPanel.x + categoryPanel.width + Kirigami.Units.smallSpacing
|
|
||||||
y: overlayDrawer.y
|
|
||||||
width: Math.max(0, parent.width - x - powerPanel.width - Kirigami.Units.smallSpacing * 2)
|
|
||||||
height: overlayDrawer.popupHeight
|
|
||||||
opacity: overlayDrawer.opacity
|
|
||||||
visible: hasTasks && opacity > 0
|
|
||||||
|
|
||||||
onTaskActivated: folio.HomeScreenState.closeAppDrawer()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Game Center overlay — full-screen grid of games shown when gaming mode
|
// Game Center overlay — full-screen grid of games shown when gaming mode
|
||||||
|
|
@ -924,7 +907,6 @@ ContainmentItem {
|
||||||
maskManager: root.frontMaskManager
|
maskManager: root.frontMaskManager
|
||||||
horizontal: root.width > root.height
|
horizontal: root.width > root.height
|
||||||
edge: horizontal ? Qt.LeftEdge : Qt.BottomEdge
|
edge: horizontal ? Qt.LeftEdge : Qt.BottomEdge
|
||||||
topMargin: horizontal ? folioHomeScreen.topMargin : 0
|
|
||||||
bottomMargin: horizontal ? 0 : folioHomeScreen.bottomMargin
|
bottomMargin: horizontal ? 0 : folioHomeScreen.bottomMargin
|
||||||
leftMargin: horizontal ? folioHomeScreen.leftMargin : 0
|
leftMargin: horizontal ? folioHomeScreen.leftMargin : 0
|
||||||
rightMargin: horizontal ? folioHomeScreen.rightMargin : 0
|
rightMargin: horizontal ? folioHomeScreen.rightMargin : 0
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,6 @@ Item {
|
||||||
|
|
||||||
readonly property bool homeScreenInteractive: !appletListViewer.active
|
readonly property bool homeScreenInteractive: !appletListViewer.active
|
||||||
|
|
||||||
function openWidgetsBrowser() {
|
|
||||||
appletListViewer.active = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
property real bottomMargin: 0
|
property real bottomMargin: 0
|
||||||
property real leftMargin: 0
|
property real leftMargin: 0
|
||||||
property real rightMargin: 0
|
property real rightMargin: 0
|
||||||
|
|
|
||||||
|
|
@ -166,13 +166,13 @@ ContainmentItem {
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
flags: Qt.FramelessWindowHint | Qt.WindowTransparentForInput
|
flags: Qt.FramelessWindowHint | Qt.WindowTransparentForInput
|
||||||
// height is set by layer-shell anchoring; provide a fallback.
|
// height is set by layer-shell anchoring; provide a fallback.
|
||||||
height: MobileShell.Constants.convergenceDockHeight
|
height: Math.max(1, MobileShell.Constants.navigationPanelThickness)
|
||||||
width: 1 // layer-shell stretches it via AnchorLeft|AnchorRight
|
width: 1 // layer-shell stretches it via AnchorLeft|AnchorRight
|
||||||
|
|
||||||
LayerShell.Window.scope: "dock-space"
|
LayerShell.Window.scope: "dock-space"
|
||||||
LayerShell.Window.layer: LayerShell.Window.LayerBottom
|
LayerShell.Window.layer: LayerShell.Window.LayerBottom
|
||||||
LayerShell.Window.anchors: LayerShell.Window.AnchorBottom | LayerShell.Window.AnchorLeft | LayerShell.Window.AnchorRight
|
LayerShell.Window.anchors: LayerShell.Window.AnchorBottom | LayerShell.Window.AnchorLeft | LayerShell.Window.AnchorRight
|
||||||
LayerShell.Window.exclusionZone: MobileShell.Constants.convergenceDockHeight
|
LayerShell.Window.exclusionZone: Math.max(1, MobileShell.Constants.navigationPanelThickness)
|
||||||
LayerShell.Window.keyboardInteractivity: LayerShell.Window.KeyboardInteractivityNone
|
LayerShell.Window.keyboardInteractivity: LayerShell.Window.KeyboardInteractivityNone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ const QMap<QString, QMap<QString, QVariant>> APPLICATIONS_BLACKLIST_DEFAULT_SETT
|
||||||
const QMap<QString, QMap<QString, QVariant>> KDEGLOBALS_DEFAULT_SETTINGS = {{"General", {{"BrowserApplication", "angelfish"}}}};
|
const QMap<QString, QMap<QString, QVariant>> KDEGLOBALS_DEFAULT_SETTINGS = {{"General", {{"BrowserApplication", "angelfish"}}}};
|
||||||
|
|
||||||
// .config/plasma-mobile/kdeglobals - immutable settings:
|
// .config/plasma-mobile/kdeglobals - immutable settings:
|
||||||
const QMap<QString, QMap<QString, QVariant>> KDEGLOBALS_SETTINGS = {{"KDE", {{"LookAndFeelPackage", "org.shift.mobile"}}}};
|
const QMap<QString, QMap<QString, QVariant>> KDEGLOBALS_SETTINGS = {{"KDE", {{"LookAndFeelPackage", "org.kde.breeze.mobile"}}}};
|
||||||
|
|
||||||
// .config/plasma-mobile/kwinrc - non-immutable settings:
|
// .config/plasma-mobile/kwinrc - non-immutable settings:
|
||||||
const QMap<QString, QMap<QString, QVariant>> KWINRC_DEFAULT_SETTINGS = {
|
const QMap<QString, QMap<QString, QVariant>> KWINRC_DEFAULT_SETTINGS = {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ kcoreaddons_add_plugin(kded_plasma_mobile_start SOURCES ${kded_plasma_mobile_sta
|
||||||
target_link_libraries(kded_plasma_mobile_start PRIVATE
|
target_link_libraries(kded_plasma_mobile_start PRIVATE
|
||||||
Qt::Core
|
Qt::Core
|
||||||
KF6::DBusAddons
|
KF6::DBusAddons
|
||||||
KF6::ConfigCore
|
|
||||||
KF6::KIOGui
|
KF6::KIOGui
|
||||||
KF6::JobWidgets
|
KF6::JobWidgets
|
||||||
KF6::Package
|
KF6::Package
|
||||||
|
|
|
||||||
|
|
@ -1,77 +1,17 @@
|
||||||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QStandardPaths>
|
|
||||||
#include <QUrl>
|
|
||||||
|
|
||||||
#include <KConfigGroup>
|
|
||||||
#include <KIO/CommandLauncherJob>
|
#include <KIO/CommandLauncherJob>
|
||||||
#include <KNotificationJobUiDelegate>
|
#include <KNotificationJobUiDelegate>
|
||||||
#include <KPluginFactory>
|
#include <KPluginFactory>
|
||||||
#include <KSharedConfig>
|
|
||||||
|
|
||||||
#include "start.h"
|
#include "start.h"
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
bool isLegacyNextWallpaperPath(const QString &path)
|
|
||||||
{
|
|
||||||
return path == QStringLiteral("Next") || path.startsWith(QStringLiteral("/usr/share/wallpapers/Next/"))
|
|
||||||
|| path.startsWith(QStringLiteral("file:///usr/share/wallpapers/Next/"));
|
|
||||||
}
|
|
||||||
|
|
||||||
QString shiftWallpaperPackageUrl()
|
|
||||||
{
|
|
||||||
const QString metadataPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("wallpapers/SHIFT/metadata.json"));
|
|
||||||
if (metadataPath.isEmpty()) {
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString packageUrl = QUrl::fromLocalFile(QFileInfo(metadataPath).absolutePath()).toString();
|
|
||||||
if (!packageUrl.endsWith(QLatin1Char('/'))) {
|
|
||||||
packageUrl += QLatin1Char('/');
|
|
||||||
}
|
|
||||||
return packageUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ensureLockscreenWallpaperDefaults()
|
|
||||||
{
|
|
||||||
auto config = KSharedConfig::openConfig(QStringLiteral("kscreenlockerrc"));
|
|
||||||
auto greeterGroup = config->group(QStringLiteral("Greeter"));
|
|
||||||
|
|
||||||
const QString wallpaperPlugin = greeterGroup.readEntry(QStringLiteral("WallpaperPlugin"), QString());
|
|
||||||
const QString wallpaperPath =
|
|
||||||
greeterGroup.group(QStringLiteral("Wallpaper")).group(wallpaperPlugin).group(QStringLiteral("General")).readEntry(QStringLiteral("Image"), QString());
|
|
||||||
|
|
||||||
const bool wallpaperUnset = wallpaperPlugin.isEmpty() || wallpaperPath.isEmpty();
|
|
||||||
const bool wallpaperLegacyNext = wallpaperPlugin == QStringLiteral("org.kde.image") && isLegacyNextWallpaperPath(wallpaperPath);
|
|
||||||
if (!wallpaperUnset && !wallpaperLegacyNext) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString defaultWallpaperUrl = shiftWallpaperPackageUrl();
|
|
||||||
if (defaultWallpaperUrl.isEmpty()) {
|
|
||||||
qWarning() << "Could not locate SHIFT wallpaper package for lockscreen defaults";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
greeterGroup.group(QStringLiteral("Wallpaper"))
|
|
||||||
.group(QStringLiteral("org.kde.image"))
|
|
||||||
.group(QStringLiteral("General"))
|
|
||||||
.writeEntry(QStringLiteral("Image"), defaultWallpaperUrl, KConfigGroup::Notify);
|
|
||||||
greeterGroup.writeEntry(QStringLiteral("WallpaperPlugin"), QStringLiteral("org.kde.image"), KConfigGroup::Notify);
|
|
||||||
config->sync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
K_PLUGIN_FACTORY_WITH_JSON(StartFactory, "kded_plasma_mobile_start.json", registerPlugin<Start>();)
|
K_PLUGIN_FACTORY_WITH_JSON(StartFactory, "kded_plasma_mobile_start.json", registerPlugin<Start>();)
|
||||||
|
|
||||||
Start::Start(QObject *parent, const QList<QVariant> &)
|
Start::Start(QObject *parent, const QList<QVariant> &)
|
||||||
: KDEDModule{parent}
|
: KDEDModule{parent}
|
||||||
{
|
{
|
||||||
ensureLockscreenWallpaperDefaults();
|
|
||||||
|
|
||||||
auto *envmanagerJob = new KIO::CommandLauncherJob(QStringLiteral("plasma-mobile-envmanager --apply-settings"), {});
|
auto *envmanagerJob = new KIO::CommandLauncherJob(QStringLiteral("plasma-mobile-envmanager --apply-settings"), {});
|
||||||
envmanagerJob->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoErrorHandlingEnabled));
|
envmanagerJob->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoErrorHandlingEnabled));
|
||||||
envmanagerJob->setDesktopName(QStringLiteral("org.kde.plasma-mobile-envmanager"));
|
envmanagerJob->setDesktopName(QStringLiteral("org.kde.plasma-mobile-envmanager"));
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,4 @@
|
||||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
|
||||||
add_subdirectory(scripts)
|
add_subdirectory(scripts)
|
||||||
add_subdirectory(effects)
|
|
||||||
add_subdirectory(decorations)
|
|
||||||
add_subdirectory(mobiletaskswitcher)
|
add_subdirectory(mobiletaskswitcher)
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
# SPDX-FileCopyrightText: 2026 Marco Allegretti <
|
|
||||||
# SPDX-License-Identifier: EUPL-1.2
|
|
||||||
install(
|
|
||||||
DIRECTORY org.shift.decoration
|
|
||||||
DESTINATION ${KDE_INSTALL_DATADIR}/kwin/decorations
|
|
||||||
)
|
|
||||||
|
|
@ -1,211 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2026 Marco Allegretti
|
|
||||||
// SPDX-License-Identifier: EUPL-1.2
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import org.kde.kwin.decoration
|
|
||||||
|
|
||||||
Decoration {
|
|
||||||
id: root
|
|
||||||
alpha: true
|
|
||||||
|
|
||||||
// ── Palette ─────────────────────────────────────────────────────────────
|
|
||||||
readonly property color activeBar: "#1a1d2e"
|
|
||||||
readonly property color inactiveBar: "#141620"
|
|
||||||
readonly property color activeText: "#f0f0f8"
|
|
||||||
readonly property color inactiveText: "#505570"
|
|
||||||
|
|
||||||
readonly property int barHeight: 30
|
|
||||||
readonly property int btnSize: 16
|
|
||||||
readonly property int btnSpacing: 8
|
|
||||||
readonly property int btnSideMargin: 12
|
|
||||||
readonly property int cornerRadius: decoration.client.maximized ? 0 : 8
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
borders.top = barHeight;
|
|
||||||
borders.left = 0;
|
|
||||||
borders.right = 0;
|
|
||||||
borders.bottom = 0;
|
|
||||||
|
|
||||||
// Keep titlebar controls available for maximized windows in desktop
|
|
||||||
// convergence mode. Mobile mode uses noBorder=true and bypasses this.
|
|
||||||
maximizedBorders.top = barHeight;
|
|
||||||
maximizedBorders.left = 0;
|
|
||||||
maximizedBorders.right = 0;
|
|
||||||
maximizedBorders.bottom = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
DecorationOptions {
|
|
||||||
id: options
|
|
||||||
deco: decoration
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Faint window outline ─────────────────────────────────────────────────
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: "transparent"
|
|
||||||
radius: root.cornerRadius
|
|
||||||
border.width: decoration.client.maximized ? 0 : 1
|
|
||||||
border.color: Qt.rgba(1, 1, 1, 0.08)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Title bar ────────────────────────────────────────────────────────────
|
|
||||||
Rectangle {
|
|
||||||
id: bar
|
|
||||||
anchors { left: parent.left; right: parent.right; top: parent.top }
|
|
||||||
height: root.barHeight
|
|
||||||
radius: root.cornerRadius
|
|
||||||
color: decoration.client.active ? root.activeBar : root.inactiveBar
|
|
||||||
Behavior on color { ColorAnimation { duration: 120 } }
|
|
||||||
|
|
||||||
// Square off bottom half — only top corners are rounded
|
|
||||||
Rectangle {
|
|
||||||
anchors { left: parent.left; right: parent.right; bottom: parent.bottom }
|
|
||||||
height: root.cornerRadius
|
|
||||||
color: parent.color
|
|
||||||
visible: !decoration.client.maximized
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Title row ────────────────────────────────────────────────────────
|
|
||||||
Item {
|
|
||||||
id: titleRow
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: leftRow
|
|
||||||
spacing: root.btnSpacing
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
leftMargin: root.btnSideMargin
|
|
||||||
verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
Repeater {
|
|
||||||
model: options.titleButtonsLeft
|
|
||||||
delegate: ShiftButton { btnType: modelData }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors {
|
|
||||||
left: leftRow.right; leftMargin: 6
|
|
||||||
right: rightRow.left; rightMargin: 6
|
|
||||||
verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
text: decoration.client.caption
|
|
||||||
color: decoration.client.active ? root.activeText : root.inactiveText
|
|
||||||
font: options.titleFont
|
|
||||||
elide: Text.ElideMiddle
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
renderType: Text.NativeRendering
|
|
||||||
Behavior on color { ColorAnimation { duration: 120 } }
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: rightRow
|
|
||||||
spacing: root.btnSpacing
|
|
||||||
anchors {
|
|
||||||
right: parent.right
|
|
||||||
rightMargin: root.btnSideMargin
|
|
||||||
verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
Repeater {
|
|
||||||
model: options.titleButtonsRight
|
|
||||||
delegate: ShiftButton { btnType: modelData }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: decoration.installTitleItem(titleRow)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Button component ─────────────────────────────────────────────────────
|
|
||||||
component ShiftButton: DecorationButton {
|
|
||||||
property int btnType: DecorationOptions.DecorationButtonNone
|
|
||||||
readonly property bool isSpacer: btnType === DecorationOptions.DecorationButtonExplicitSpacer
|
|
||||||
readonly property bool supported: {
|
|
||||||
switch (btnType) {
|
|
||||||
case DecorationOptions.DecorationButtonExplicitSpacer:
|
|
||||||
case DecorationOptions.DecorationButtonClose:
|
|
||||||
case DecorationOptions.DecorationButtonMinimize:
|
|
||||||
case DecorationOptions.DecorationButtonMaximizeRestore:
|
|
||||||
case DecorationOptions.DecorationButtonMenu:
|
|
||||||
case DecorationOptions.DecorationButtonApplicationMenu:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buttonType: btnType
|
|
||||||
width: isSpacer ? root.btnSpacing * 2 : (supported ? root.btnSize : 0)
|
|
||||||
height: isSpacer ? 1 : (supported ? root.btnSize : 0)
|
|
||||||
visible: supported
|
|
||||||
|
|
||||||
readonly property color normalColor: {
|
|
||||||
switch (btnType) {
|
|
||||||
case DecorationOptions.DecorationButtonClose: return "#C4455D";
|
|
||||||
case DecorationOptions.DecorationButtonMenu:
|
|
||||||
case DecorationOptions.DecorationButtonApplicationMenu:
|
|
||||||
case DecorationOptions.DecorationButtonMinimize:
|
|
||||||
case DecorationOptions.DecorationButtonMaximizeRestore: return "#2b3246";
|
|
||||||
default: return "#2b3246";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
readonly property color hoverColor: {
|
|
||||||
switch (btnType) {
|
|
||||||
case DecorationOptions.DecorationButtonClose: return "#E05D76";
|
|
||||||
case DecorationOptions.DecorationButtonMinimize:
|
|
||||||
case DecorationOptions.DecorationButtonMaximizeRestore:
|
|
||||||
case DecorationOptions.DecorationButtonMenu:
|
|
||||||
case DecorationOptions.DecorationButtonApplicationMenu: return "#3b435c";
|
|
||||||
default: return "#3b435c";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
readonly property color symbolColor: {
|
|
||||||
switch (btnType) {
|
|
||||||
case DecorationOptions.DecorationButtonClose: return "#ffffff";
|
|
||||||
case DecorationOptions.DecorationButtonMenu:
|
|
||||||
case DecorationOptions.DecorationButtonApplicationMenu:
|
|
||||||
case DecorationOptions.DecorationButtonMinimize:
|
|
||||||
case DecorationOptions.DecorationButtonMaximizeRestore: return "#eaf2ff";
|
|
||||||
default: return "#eaf2ff";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
readonly property string symbol: {
|
|
||||||
switch (btnType) {
|
|
||||||
case DecorationOptions.DecorationButtonClose: return "\u00d7";
|
|
||||||
case DecorationOptions.DecorationButtonMinimize: return "\u2212";
|
|
||||||
case DecorationOptions.DecorationButtonMaximizeRestore:
|
|
||||||
return decoration.client.maximized ? "\u25a3" : "\u25a1";
|
|
||||||
case DecorationOptions.DecorationButtonMenu:
|
|
||||||
case DecorationOptions.DecorationButtonApplicationMenu: return "\u2261";
|
|
||||||
default: return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Snap-layout hover activation lives in the SHIFT Snap Assist effect.
|
|
||||||
// The decoration only renders the maximize button; the effect decides
|
|
||||||
// when maximize-hover is eligible and shows the popup.
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
visible: !isSpacer
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: width / 2
|
|
||||||
antialiasing: true
|
|
||||||
border.width: 1
|
|
||||||
border.color: Qt.rgba(1, 1, 1, 0.18)
|
|
||||||
color: parent.pressed ? Qt.darker(parent.hoverColor, 1.3)
|
|
||||||
: parent.hovered ? parent.hoverColor
|
|
||||||
: parent.normalColor
|
|
||||||
Behavior on color { ColorAnimation { duration: 100 } }
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: parent.parent.symbol
|
|
||||||
color: parent.parent.symbolColor
|
|
||||||
font.pixelSize: Math.round(parent.width * 0.66)
|
|
||||||
font.weight: Font.Bold
|
|
||||||
opacity: 1.0
|
|
||||||
Behavior on opacity { NumberAnimation { duration: 100 } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
{
|
|
||||||
"KPackageStructure": "KWin/Decoration",
|
|
||||||
"KPlugin": {
|
|
||||||
"Authors": [
|
|
||||||
{
|
|
||||||
"Name": "SHIFT Contributors"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"Description": "Shift window decoration",
|
|
||||||
"Id": "org.shift.decoration",
|
|
||||||
"License": "GPL-2.0-or-later",
|
|
||||||
"Name": "Shift"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
# SPDX-FileCopyrightText: 2026 Marco Allegretti
|
|
||||||
# SPDX-License-Identifier: EUPL-1.2
|
|
||||||
|
|
||||||
function(add_kwin_effect name source)
|
|
||||||
kpackage_install_package(${source} ${name} effects kwin)
|
|
||||||
|
|
||||||
file(COPY ${source}/contents ${source}/metadata.json DESTINATION ${CMAKE_BINARY_DIR}/bin/kwin/effects/${name})
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
add_kwin_effect(shift-snap-assist shift-snap-assist)
|
|
||||||
|
|
@ -1,718 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2026 Marco Allegretti
|
|
||||||
// SPDX-License-Identifier: EUPL-1.2
|
|
||||||
//
|
|
||||||
// SHIFT Snap Assist — KWin declarative SceneEffect
|
|
||||||
//
|
|
||||||
// Presents a floating panel of layout preset buttons.
|
|
||||||
// Activated by:
|
|
||||||
// 1. Meta+Shift+S keyboard shortcut
|
|
||||||
// 2. Hovering the maximize button in convergence mode while dynamic tiling is off
|
|
||||||
//
|
|
||||||
// When a preset is clicked, the active window's frameGeometry is set to the
|
|
||||||
// chosen zone (with outer gaps applied).
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import org.kde.kwin as KWinComponents
|
|
||||||
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
|
|
||||||
|
|
||||||
KWinComponents.SceneEffect {
|
|
||||||
id: effect
|
|
||||||
|
|
||||||
visible: false
|
|
||||||
|
|
||||||
readonly property bool snapLayoutsEligible: ShellSettings.Settings.convergenceModeEnabled
|
|
||||||
&& !ShellSettings.Settings.gamingModeEnabled
|
|
||||||
&& !ShellSettings.Settings.dynamicTilingEnabled
|
|
||||||
readonly property int hoverBarHeight: 30
|
|
||||||
readonly property int decorationButtonSize: 16
|
|
||||||
readonly property int decorationButtonSpacing: 8
|
|
||||||
readonly property int decorationButtonSideMargin: 12
|
|
||||||
readonly property int maximizeButtonPadding: 4
|
|
||||||
readonly property int hoverTimerInterval: 75
|
|
||||||
readonly property int hoverDwellTicks: 14
|
|
||||||
readonly property int hoverMoveTolerance: 5
|
|
||||||
readonly property int hoverCooldownMs: 1200
|
|
||||||
readonly property int panelDismissMargin: 24
|
|
||||||
readonly property int panelScreenMargin: 8
|
|
||||||
readonly property int panelCursorGap: 12
|
|
||||||
readonly property int panelCursorRightBias: 34
|
|
||||||
property var hoverWindowId: null
|
|
||||||
property int hoverTicks: 0
|
|
||||||
property string hoverWindowStateKey: ""
|
|
||||||
property bool hoverSuppressedUntilLeave: false
|
|
||||||
property point hoverAnchorPos: Qt.point(0, 0)
|
|
||||||
property double hoverCooldownUntil: 0
|
|
||||||
property var panelAnchorPos: Qt.point(0, 0)
|
|
||||||
property string panelAnchorScreenName: ""
|
|
||||||
property bool previewVisible: false
|
|
||||||
property rect previewGeometry: Qt.rect(0, 0, 0, 0)
|
|
||||||
property rect previewArea: Qt.rect(0, 0, 0, 0)
|
|
||||||
property var previewZones: []
|
|
||||||
property int previewActiveIndex: -1
|
|
||||||
property string previewScreenName: ""
|
|
||||||
|
|
||||||
function resetHoverState() {
|
|
||||||
hoverWindowId = null;
|
|
||||||
hoverTicks = 0;
|
|
||||||
hoverWindowStateKey = "";
|
|
||||||
hoverSuppressedUntilLeave = false;
|
|
||||||
hoverAnchorPos = Qt.point(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetHoverCandidate(win) {
|
|
||||||
hoverWindowId = win ? win.internalId : null;
|
|
||||||
hoverTicks = 1;
|
|
||||||
hoverWindowStateKey = win ? windowStateKey(win) : "";
|
|
||||||
hoverSuppressedUntilLeave = false;
|
|
||||||
hoverAnchorPos = KWinComponents.Workspace.cursorPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setHoverCooldown() {
|
|
||||||
hoverCooldownUntil = Date.now() + hoverCooldownMs;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hoverOnCooldown() {
|
|
||||||
return Date.now() < hoverCooldownUntil;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hoverMovedTooFar() {
|
|
||||||
const cursor = KWinComponents.Workspace.cursorPos;
|
|
||||||
const dx = cursor.x - hoverAnchorPos.x;
|
|
||||||
const dy = cursor.y - hoverAnchorPos.y;
|
|
||||||
return dx * dx + dy * dy > hoverMoveTolerance * hoverMoveTolerance;
|
|
||||||
}
|
|
||||||
|
|
||||||
function windowStateKey(win) {
|
|
||||||
const geometry = win.frameGeometry;
|
|
||||||
const maximized = win.maximized === undefined ? "" : win.maximized;
|
|
||||||
return maximized + ":" + geometry.x + "," + geometry.y + "," + geometry.width + "x" + geometry.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideSnapLayouts() {
|
|
||||||
if (visible) {
|
|
||||||
setHoverCooldown();
|
|
||||||
}
|
|
||||||
visible = false;
|
|
||||||
resetHoverState();
|
|
||||||
panelAnchorScreenName = "";
|
|
||||||
hideSnapPreview();
|
|
||||||
}
|
|
||||||
|
|
||||||
function showSnapLayouts() {
|
|
||||||
const win = KWinComponents.Workspace.activeWindow;
|
|
||||||
panelAnchorPos = KWinComponents.Workspace.cursorPos;
|
|
||||||
panelAnchorScreenName = win && win.output ? win.output.name : "";
|
|
||||||
visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleActiveWindowMaximized() {
|
|
||||||
const win = KWinComponents.Workspace.activeWindow;
|
|
||||||
if (!win || !win.normalWindow || win.fullScreen || !win.maximizable) {
|
|
||||||
hideSnapLayouts();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const maximize = !win.maximized;
|
|
||||||
win.setMaximize(maximize, maximize);
|
|
||||||
hideSnapLayouts();
|
|
||||||
setHoverCooldown();
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideSnapPreview() {
|
|
||||||
previewVisible = false;
|
|
||||||
previewScreenName = "";
|
|
||||||
previewZones = [];
|
|
||||||
previewActiveIndex = -1;
|
|
||||||
KWinComponents.Workspace.hideOutline();
|
|
||||||
}
|
|
||||||
|
|
||||||
function showSnapPreview(preset, activeIndex) {
|
|
||||||
const win = KWinComponents.Workspace.activeWindow;
|
|
||||||
if (!win || !win.output) {
|
|
||||||
hideSnapPreview();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const desktop = win.desktops.length > 0 ? win.desktops[0] : null;
|
|
||||||
if (!desktop) {
|
|
||||||
hideSnapPreview();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const area = KWinComponents.Workspace.clientArea(KWinComponents.Workspace.MaximizeArea, win.output, desktop);
|
|
||||||
const zone = preset.zones[activeIndex];
|
|
||||||
const gap = effect.outerGap;
|
|
||||||
previewGeometry = Qt.rect(
|
|
||||||
area.x + Math.round(zone.x * area.width) + gap,
|
|
||||||
area.y + Math.round(zone.y * area.height) + gap,
|
|
||||||
Math.round(zone.w * area.width) - 2 * gap,
|
|
||||||
Math.round(zone.h * area.height) - 2 * gap
|
|
||||||
);
|
|
||||||
previewArea = area;
|
|
||||||
previewZones = preset.zones;
|
|
||||||
previewActiveIndex = activeIndex;
|
|
||||||
previewScreenName = win.output.name;
|
|
||||||
previewVisible = true;
|
|
||||||
KWinComponents.Workspace.hideOutline();
|
|
||||||
}
|
|
||||||
|
|
||||||
function cursorInActiveWindowMaximizeStrip() {
|
|
||||||
const win = KWinComponents.Workspace.activeWindow;
|
|
||||||
if (!win || !win.normalWindow || win.fullScreen || !win.maximizable) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cursor = KWinComponents.Workspace.cursorPos;
|
|
||||||
const button = maximizeButtonRect(win);
|
|
||||||
if (button.width <= 0 || button.height <= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cursor.x >= button.x - effect.maximizeButtonPadding
|
|
||||||
&& cursor.x <= button.x + button.width + effect.maximizeButtonPadding
|
|
||||||
&& cursor.y >= button.y - effect.maximizeButtonPadding
|
|
||||||
&& cursor.y <= button.y + button.height + effect.maximizeButtonPadding;
|
|
||||||
}
|
|
||||||
|
|
||||||
function decorationButtonVisible(code) {
|
|
||||||
return code === "M" || code === "N" || code === "I" || code === "A" || code === "X";
|
|
||||||
}
|
|
||||||
|
|
||||||
function visibleDecorationButtons(sequence) {
|
|
||||||
const buttons = [];
|
|
||||||
for (let i = 0; i < sequence.length; i++) {
|
|
||||||
const code = sequence[i];
|
|
||||||
if (decorationButtonVisible(code)) {
|
|
||||||
buttons.push(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buttons;
|
|
||||||
}
|
|
||||||
|
|
||||||
function maximizeButtonRect(win) {
|
|
||||||
const fg = win.frameGeometry;
|
|
||||||
const buttonY = fg.y + Math.round((effect.hoverBarHeight - effect.decorationButtonSize) / 2);
|
|
||||||
const leftButtons = visibleDecorationButtons(ShellSettings.KWinSettings.titleButtonsOnLeft);
|
|
||||||
const leftIndex = leftButtons.indexOf("A");
|
|
||||||
if (leftIndex >= 0) {
|
|
||||||
return Qt.rect(
|
|
||||||
fg.x + effect.decorationButtonSideMargin + leftIndex * (effect.decorationButtonSize + effect.decorationButtonSpacing),
|
|
||||||
buttonY,
|
|
||||||
effect.decorationButtonSize,
|
|
||||||
effect.decorationButtonSize
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rightButtons = visibleDecorationButtons(ShellSettings.KWinSettings.titleButtonsOnRight);
|
|
||||||
const rightIndex = rightButtons.indexOf("A");
|
|
||||||
if (rightIndex >= 0) {
|
|
||||||
const rowWidth = rightButtons.length * effect.decorationButtonSize
|
|
||||||
+ Math.max(0, rightButtons.length - 1) * effect.decorationButtonSpacing;
|
|
||||||
return Qt.rect(
|
|
||||||
fg.x + fg.width - effect.decorationButtonSideMargin - rowWidth
|
|
||||||
+ rightIndex * (effect.decorationButtonSize + effect.decorationButtonSpacing),
|
|
||||||
buttonY,
|
|
||||||
effect.decorationButtonSize,
|
|
||||||
effect.decorationButtonSize
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Qt.rect(0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function cursorInPanel(screen) {
|
|
||||||
if (!screen) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (panelAnchorScreenName !== "" && panelAnchorScreenName !== screen.name) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cursor = KWinComponents.Workspace.cursorPos;
|
|
||||||
const panel = panelRect(screen);
|
|
||||||
return cursor.x >= panel.x - panelDismissMargin
|
|
||||||
&& cursor.x <= panel.x + panel.width + panelDismissMargin
|
|
||||||
&& cursor.y >= panel.y - panelDismissMargin
|
|
||||||
&& cursor.y <= panel.y + panel.height + panelDismissMargin;
|
|
||||||
}
|
|
||||||
|
|
||||||
function panelRect(screen) {
|
|
||||||
const minX = screen.geometry.x + panelScreenMargin;
|
|
||||||
const maxX = screen.geometry.x + screen.geometry.width - snapPanelWidth - panelScreenMargin;
|
|
||||||
const minY = screen.geometry.y + panelScreenMargin;
|
|
||||||
const maxY = screen.geometry.y + screen.geometry.height - snapPanelHeight - panelScreenMargin;
|
|
||||||
const wantedX = panelAnchorPos.x - snapPanelWidth + panelCursorRightBias;
|
|
||||||
const wantedY = panelAnchorPos.y + panelCursorGap;
|
|
||||||
return Qt.rect(
|
|
||||||
Math.max(minX, Math.min(maxX, wantedX)),
|
|
||||||
Math.max(minY, Math.min(maxY, wantedY)),
|
|
||||||
snapPanelWidth,
|
|
||||||
snapPanelHeight
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Visibility ────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
// The effect starts invisible; toggle via shortcut.
|
|
||||||
// SceneEffect.visible controls whether delegates are painted.
|
|
||||||
|
|
||||||
KWinComponents.ShortcutHandler {
|
|
||||||
name: "SHIFT Snap Assist"
|
|
||||||
text: "SHIFT Snap Assist: Show snap layout picker"
|
|
||||||
sequence: "Meta+Shift+S"
|
|
||||||
onActivated: {
|
|
||||||
if (!effect.snapLayoutsEligible) {
|
|
||||||
effect.hideSnapLayouts();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (effect.visible) {
|
|
||||||
effect.hideSnapLayouts();
|
|
||||||
} else {
|
|
||||||
effect.showSnapLayouts();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
KWinComponents.ShortcutHandler {
|
|
||||||
name: "SHIFT Snap Assist Escape"
|
|
||||||
text: "SHIFT Snap Assist: Hide snap layout picker"
|
|
||||||
sequence: "Esc"
|
|
||||||
onActivated: effect.hideSnapLayouts()
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: hoverTimer
|
|
||||||
interval: effect.hoverTimerInterval
|
|
||||||
repeat: true
|
|
||||||
running: effect.snapLayoutsEligible
|
|
||||||
|
|
||||||
onTriggered: {
|
|
||||||
if (effect.visible) {
|
|
||||||
const screen = KWinComponents.Workspace.activeWindow ? KWinComponents.Workspace.activeWindow.output : null;
|
|
||||||
if (!effect.cursorInActiveWindowMaximizeStrip() && !effect.cursorInPanel(screen)) {
|
|
||||||
effect.hideSnapLayouts();
|
|
||||||
} else {
|
|
||||||
effect.resetHoverState();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const win = KWinComponents.Workspace.activeWindow;
|
|
||||||
if (!win || !win.normalWindow || win.fullScreen || !win.maximizable) {
|
|
||||||
effect.resetHoverState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!effect.cursorInActiveWindowMaximizeStrip()) {
|
|
||||||
effect.resetHoverState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (effect.hoverOnCooldown()) {
|
|
||||||
effect.resetHoverState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (effect.hoverWindowId !== win.internalId) {
|
|
||||||
effect.resetHoverCandidate(win);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (effect.hoverMovedTooFar()) {
|
|
||||||
effect.resetHoverCandidate(win);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const stateKey = effect.windowStateKey(win);
|
|
||||||
if (stateKey !== effect.hoverWindowStateKey) {
|
|
||||||
effect.hoverWindowStateKey = stateKey;
|
|
||||||
effect.hoverTicks = 0;
|
|
||||||
effect.hoverSuppressedUntilLeave = true;
|
|
||||||
effect.setHoverCooldown();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (effect.hoverSuppressedUntilLeave) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
effect.hoverTicks++;
|
|
||||||
if (effect.hoverTicks >= effect.hoverDwellTicks) {
|
|
||||||
effect.showSnapLayouts();
|
|
||||||
effect.resetHoverState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: KWinComponents.Workspace
|
|
||||||
function onActiveWindowChanged() {
|
|
||||||
effect.resetHoverState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: ShellSettings.Settings
|
|
||||||
|
|
||||||
function onConvergenceModeEnabledChanged() {
|
|
||||||
if (!effect.snapLayoutsEligible) {
|
|
||||||
effect.hideSnapLayouts();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onGamingModeEnabledChanged() {
|
|
||||||
if (!effect.snapLayoutsEligible) {
|
|
||||||
effect.hideSnapLayouts();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDynamicTilingEnabledChanged() {
|
|
||||||
if (!effect.snapLayoutsEligible) {
|
|
||||||
effect.hideSnapLayouts();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Gap constant (must match shift-tiling) ────────────────────────────
|
|
||||||
readonly property int outerGap: 8
|
|
||||||
|
|
||||||
// ── Layout presets ────────────────────────────────────────────────────
|
|
||||||
// Each preset is an array of zone descriptors:
|
|
||||||
// { x, y, w, h } in relative [0..1] coordinates (of work area).
|
|
||||||
// The first zone is where the ACTIVE window will be placed.
|
|
||||||
// Remaining zones are currently visual-only.
|
|
||||||
|
|
||||||
readonly property var presets: [
|
|
||||||
{
|
|
||||||
name: "Half left",
|
|
||||||
zones: [
|
|
||||||
{ x: 0, y: 0, w: 0.5, h: 1 },
|
|
||||||
{ x: 0.5, y: 0, w: 0.5, h: 1 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Thirds",
|
|
||||||
zones: [
|
|
||||||
{ x: 0, y: 0, w: 0.333, h: 1 },
|
|
||||||
{ x: 0.333, y: 0, w: 0.334, h: 1 },
|
|
||||||
{ x: 0.667, y: 0, w: 0.333, h: 1 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Main + side",
|
|
||||||
zones: [
|
|
||||||
{ x: 0, y: 0, w: 0.667, h: 1 },
|
|
||||||
{ x: 0.667, y: 0, w: 0.333, h: 1 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Side + main",
|
|
||||||
zones: [
|
|
||||||
{ x: 0.333, y: 0, w: 0.667, h: 1 },
|
|
||||||
{ x: 0, y: 0, w: 0.333, h: 1 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Quad",
|
|
||||||
zones: [
|
|
||||||
{ x: 0, y: 0, w: 0.5, h: 0.5 },
|
|
||||||
{ x: 0.5, y: 0, w: 0.5, h: 0.5 },
|
|
||||||
{ x: 0, y: 0.5, w: 0.5, h: 0.5 },
|
|
||||||
{ x: 0.5, y: 0.5, w: 0.5, h: 0.5 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Main + two",
|
|
||||||
zones: [
|
|
||||||
{ x: 0, y: 0, w: 0.5, h: 1 },
|
|
||||||
{ x: 0.5, y: 0, w: 0.5, h: 0.5 },
|
|
||||||
{ x: 0.5, y: 0.5, w: 0.5, h: 0.5 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
readonly property int snapButtonWidth: 58
|
|
||||||
readonly property int snapButtonHeight: 38
|
|
||||||
readonly property int snapButtonSpacing: 8
|
|
||||||
readonly property int snapPanelHorizontalPadding: 28
|
|
||||||
readonly property int snapPanelVerticalPadding: 34
|
|
||||||
readonly property int snapPanelWidth: presets.length * snapButtonWidth
|
|
||||||
+ Math.max(0, presets.length - 1) * snapButtonSpacing
|
|
||||||
+ snapPanelHorizontalPadding
|
|
||||||
readonly property int snapPanelHeight: snapButtonHeight + snapPanelVerticalPadding + 23
|
|
||||||
|
|
||||||
function accentColor(alpha) {
|
|
||||||
return Qt.rgba(46 / 255, 184 / 255, 168 / 255, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
function accentPaleColor(alpha) {
|
|
||||||
return Qt.rgba(163 / 255, 218 / 255, 212 / 255, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply a zone (in relative coords) to a window given a work area rect.
|
|
||||||
function applyZone(win, zone, area) {
|
|
||||||
const g = effect.outerGap;
|
|
||||||
win.frameGeometry = Qt.rect(
|
|
||||||
area.x + Math.round(zone.x * area.width) + g,
|
|
||||||
area.y + Math.round(zone.y * area.height) + g,
|
|
||||||
Math.round(zone.w * area.width) - 2 * g,
|
|
||||||
Math.round(zone.h * area.height) - 2 * g
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Per-screen delegate ───────────────────────────────────────────────
|
|
||||||
|
|
||||||
delegate: Rectangle {
|
|
||||||
id: screenDelegate
|
|
||||||
|
|
||||||
readonly property var targetScreen: KWinComponents.SceneView.screen
|
|
||||||
readonly property rect popupRect: effect.panelRect(targetScreen)
|
|
||||||
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
KWinComponents.DesktopBackground {
|
|
||||||
anchors.fill: parent
|
|
||||||
z: -100
|
|
||||||
activity: KWinComponents.Workspace.currentActivity
|
|
||||||
desktop: KWinComponents.Workspace.currentDesktop
|
|
||||||
outputName: screenDelegate.targetScreen.name
|
|
||||||
}
|
|
||||||
|
|
||||||
Instantiator {
|
|
||||||
model: KWinComponents.WindowFilterModel {
|
|
||||||
desktop: KWinComponents.Workspace.currentDesktop
|
|
||||||
screenName: screenDelegate.targetScreen.name
|
|
||||||
windowModel: stackModel
|
|
||||||
minimizedWindows: false
|
|
||||||
windowType: ~KWinComponents.WindowFilterModel.Desktop
|
|
||||||
& ~KWinComponents.WindowFilterModel.Notification
|
|
||||||
& ~KWinComponents.WindowFilterModel.CriticalNotification
|
|
||||||
}
|
|
||||||
|
|
||||||
KWinComponents.WindowThumbnail {
|
|
||||||
wId: model.window.internalId
|
|
||||||
x: model.window.x - screenDelegate.targetScreen.geometry.x
|
|
||||||
y: model.window.y - screenDelegate.targetScreen.geometry.y
|
|
||||||
z: model.window.stackingOrder
|
|
||||||
visible: !model.window.hidden
|
|
||||||
}
|
|
||||||
|
|
||||||
onObjectAdded: (index, object) => {
|
|
||||||
object.parent = screenDelegate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
KWinComponents.WindowModel {
|
|
||||||
id: stackModel
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: effect.previewVisible && effect.previewScreenName === screenDelegate.targetScreen.name ? effect.previewZones : []
|
|
||||||
|
|
||||||
delegate: Rectangle {
|
|
||||||
required property var modelData
|
|
||||||
required property int index
|
|
||||||
|
|
||||||
readonly property bool activeZone: index === effect.previewActiveIndex
|
|
||||||
readonly property int previewGap: effect.outerGap
|
|
||||||
|
|
||||||
x: effect.previewArea.x + Math.round(modelData.x * effect.previewArea.width) + previewGap - screenDelegate.targetScreen.geometry.x
|
|
||||||
y: effect.previewArea.y + Math.round(modelData.y * effect.previewArea.height) + previewGap - screenDelegate.targetScreen.geometry.y
|
|
||||||
width: Math.max(1, Math.round(modelData.w * effect.previewArea.width) - 2 * previewGap)
|
|
||||||
height: Math.max(1, Math.round(modelData.h * effect.previewArea.height) - 2 * previewGap)
|
|
||||||
z: activeZone ? 90001 : 90000
|
|
||||||
radius: 6
|
|
||||||
color: activeZone ? effect.accentColor(0.24)
|
|
||||||
: effect.accentPaleColor(0.08)
|
|
||||||
border.width: activeZone ? 2 : 1
|
|
||||||
border.color: activeZone ? effect.accentPaleColor(0.78)
|
|
||||||
: effect.accentPaleColor(0.26)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onEscapePressed: effect.hideSnapLayouts()
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: maximizeForwarder
|
|
||||||
|
|
||||||
readonly property var activeWindow: KWinComponents.Workspace.activeWindow
|
|
||||||
readonly property rect buttonRect: activeWindow ? effect.maximizeButtonRect(activeWindow) : Qt.rect(0, 0, 0, 0)
|
|
||||||
|
|
||||||
visible: activeWindow
|
|
||||||
&& activeWindow.output
|
|
||||||
&& activeWindow.output.name === screenDelegate.targetScreen.name
|
|
||||||
&& buttonRect.width > 0
|
|
||||||
&& buttonRect.height > 0
|
|
||||||
z: 100001
|
|
||||||
x: buttonRect.x - effect.maximizeButtonPadding - screenDelegate.targetScreen.geometry.x
|
|
||||||
y: buttonRect.y - effect.maximizeButtonPadding - screenDelegate.targetScreen.geometry.y
|
|
||||||
width: buttonRect.width + 2 * effect.maximizeButtonPadding
|
|
||||||
height: buttonRect.height + 2 * effect.maximizeButtonPadding
|
|
||||||
|
|
||||||
TapHandler {
|
|
||||||
acceptedButtons: Qt.LeftButton
|
|
||||||
onTapped: effect.toggleActiveWindowMaximized()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SnapPanel {
|
|
||||||
id: snapPanel
|
|
||||||
visible: effect.panelAnchorScreenName === "" || effect.panelAnchorScreenName === screenDelegate.targetScreen.name
|
|
||||||
z: 100000
|
|
||||||
x: screenDelegate.popupRect.x - screenDelegate.targetScreen.geometry.x
|
|
||||||
y: screenDelegate.popupRect.y - screenDelegate.targetScreen.geometry.y
|
|
||||||
|
|
||||||
screen: KWinComponents.SceneView.screen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Snap panel component ──────────────────────────────────────────────
|
|
||||||
|
|
||||||
component SnapPanel: Rectangle {
|
|
||||||
id: panel
|
|
||||||
|
|
||||||
required property var screen
|
|
||||||
|
|
||||||
width: effect.snapPanelWidth
|
|
||||||
height: effect.snapPanelHeight
|
|
||||||
|
|
||||||
color: Qt.rgba(0.08, 0.10, 0.15, 0.82)
|
|
||||||
radius: 8
|
|
||||||
border.width: 1
|
|
||||||
border.color: Qt.rgba(1, 1, 1, 0.16)
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.topMargin: 3
|
|
||||||
color: Qt.rgba(0, 0, 0, 0.22)
|
|
||||||
radius: parent.radius
|
|
||||||
z: -1
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors { fill: parent; margins: 14 }
|
|
||||||
spacing: 10
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Snap layouts"
|
|
||||||
color: effect.accentPaleColor(0.9)
|
|
||||||
font.pixelSize: 10
|
|
||||||
font.capitalization: Font.AllUppercase
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: presetsRow
|
|
||||||
spacing: effect.snapButtonSpacing
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: effect.presets
|
|
||||||
|
|
||||||
delegate: PresetButton {
|
|
||||||
required property var modelData
|
|
||||||
|
|
||||||
preset: modelData
|
|
||||||
screen: panel.screen
|
|
||||||
onClicked: {
|
|
||||||
effect.hideSnapLayouts();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Preset button ─────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
component PresetButton: Rectangle {
|
|
||||||
id: btn
|
|
||||||
|
|
||||||
required property var preset
|
|
||||||
required property var screen
|
|
||||||
signal clicked
|
|
||||||
|
|
||||||
width: effect.snapButtonWidth
|
|
||||||
height: effect.snapButtonHeight
|
|
||||||
readonly property int previewMargin: 6
|
|
||||||
|
|
||||||
color: hovered ? Qt.rgba(1, 1, 1, 0.18) : Qt.rgba(1, 1, 1, 0.08)
|
|
||||||
radius: 6
|
|
||||||
border.width: 1
|
|
||||||
border.color: hovered ? effect.accentPaleColor(0.62) : Qt.rgba(1, 1, 1, 0.14)
|
|
||||||
|
|
||||||
property bool hovered: false
|
|
||||||
|
|
||||||
Behavior on color { ColorAnimation { duration: 80 } }
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: previewFrame
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: btn.previewMargin
|
|
||||||
color: Qt.rgba(0.06, 0.08, 0.12, 0.72)
|
|
||||||
radius: 4
|
|
||||||
border.width: 1
|
|
||||||
border.color: Qt.rgba(1, 1, 1, 0.08)
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: btn.preset.zones
|
|
||||||
|
|
||||||
delegate: Rectangle {
|
|
||||||
required property var modelData
|
|
||||||
required property int index
|
|
||||||
|
|
||||||
x: Math.round(modelData.x * previewFrame.width)
|
|
||||||
y: Math.round(modelData.y * previewFrame.height)
|
|
||||||
width: Math.max(4, Math.round(modelData.w * previewFrame.width) - 1)
|
|
||||||
height: Math.max(4, Math.round(modelData.h * previewFrame.height) - 1)
|
|
||||||
color: zoneHover.hovered ? effect.accentPaleColor(0.96)
|
|
||||||
: (index === 0 ? effect.accentColor(0.96)
|
|
||||||
: effect.accentPaleColor(0.48))
|
|
||||||
border.width: 0
|
|
||||||
radius: 2
|
|
||||||
|
|
||||||
HoverHandler {
|
|
||||||
id: zoneHover
|
|
||||||
onHoveredChanged: {
|
|
||||||
btn.hovered = hovered;
|
|
||||||
if (hovered) {
|
|
||||||
effect.showSnapPreview(btn.preset, index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TapHandler {
|
|
||||||
onTapped: btn.applyZone(modelData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HoverHandler {
|
|
||||||
onHoveredChanged: {
|
|
||||||
if (!hovered) {
|
|
||||||
btn.hovered = false;
|
|
||||||
effect.hideSnapPreview();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyZone(zone) {
|
|
||||||
const win = KWinComponents.Workspace.activeWindow;
|
|
||||||
if (!win || !win.output) {
|
|
||||||
btn.clicked();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const desktop = win.desktops.length > 0 ? win.desktops[0] : null;
|
|
||||||
if (!desktop) {
|
|
||||||
btn.clicked();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const area = KWinComponents.Workspace.clientArea(
|
|
||||||
KWinComponents.Workspace.MaximizeArea, win.output, desktop);
|
|
||||||
effect.hideSnapPreview();
|
|
||||||
effect.applyZone(win, zone, area);
|
|
||||||
btn.clicked();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"KPackageStructure": "KWin/Effect",
|
|
||||||
"KPlugin": {
|
|
||||||
"Authors": [
|
|
||||||
{
|
|
||||||
"Email": "marcoa@example.com",
|
|
||||||
"Name": "Marco Allegretti"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"Category": "Appearance",
|
|
||||||
"Description": "Windows 11-style snap layout picker for SHIFT convergence mode with native window placement. Activated by hovering the maximize button or pressing Meta+Shift+S when dynamic tiling is off.",
|
|
||||||
"EnabledByDefault": false,
|
|
||||||
"Id": "shift-snap-assist",
|
|
||||||
"License": "EUPL-1.2",
|
|
||||||
"Name": "SHIFT Snap Assist",
|
|
||||||
"Version": "1.0"
|
|
||||||
},
|
|
||||||
"X-KDE-Ordering": 60,
|
|
||||||
"X-Plasma-API": "declarativescript"
|
|
||||||
}
|
|
||||||
|
|
@ -10,5 +10,4 @@ function(add_kwin_script name source)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
add_kwin_script(convergentwindows convergentwindows)
|
add_kwin_script(convergentwindows convergentwindows)
|
||||||
add_kwin_script(shift-tiling shift-tiling)
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,16 +53,16 @@ Loader {
|
||||||
}
|
}
|
||||||
|
|
||||||
function run(window) {
|
function run(window) {
|
||||||
if (!window || window.deleted || !window.normalWindow) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// HACK: don't maximize xwaylandvideobridge
|
// HACK: don't maximize xwaylandvideobridge
|
||||||
// see: https://invent.kde.org/plasma/plasma-mobile/-/issues/324
|
// see: https://invent.kde.org/plasma/plasma-mobile/-/issues/324
|
||||||
if (window.resourceClass === 'xwaylandvideobridge') {
|
if (window.resourceClass === 'xwaylandvideobridge') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!window.normalWindow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (ShellSettings.Settings.gamingModeEnabled) {
|
if (ShellSettings.Settings.gamingModeEnabled) {
|
||||||
window.noBorder = true;
|
window.noBorder = true;
|
||||||
window.setMaximize(true, true);
|
window.setMaximize(true, true);
|
||||||
|
|
|
||||||
|
|
@ -1,616 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2026 Marco Allegretti
|
|
||||||
// SPDX-License-Identifier: EUPL-1.2
|
|
||||||
//
|
|
||||||
// SHIFT Dynamic Tiling — KWin declarative script
|
|
||||||
//
|
|
||||||
// Architecture:
|
|
||||||
// - One ScreenState per output, keyed by output.name
|
|
||||||
// - Each ScreenState holds an ordered list of TileNodes
|
|
||||||
// - A TileNode is { win, rect } where rect is absolute in-screen coordinates
|
|
||||||
// - On any change (add/remove/resize) the layout is recomputed from scratch
|
|
||||||
// for the affected screen using a BSP algorithm
|
|
||||||
// - Drag detection uses interactiveMoveResizeStarted/Stepped/Finished
|
|
||||||
// - Snap zones are the 6 screen-edge regions (left/right/top/corners)
|
|
||||||
// - Gap: outer 8px on screen edges, inner 8px between tiles (4px each side)
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import org.kde.kwin as KWinComponents
|
|
||||||
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
// ── Configuration ───────────────────────────────────────────────────────
|
|
||||||
readonly property int outerGap: 8
|
|
||||||
readonly property int innerGap: 8 // half applied to each edge → 4px per tile
|
|
||||||
|
|
||||||
// ── State ───────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
// Per-screen tile list. Key: output.name Value: [{win, rect}]
|
|
||||||
// rect is a Qt.rect in absolute screen coordinates.
|
|
||||||
property var screenTiles: ({})
|
|
||||||
|
|
||||||
// Windows the user has manually floated (by UUID string).
|
|
||||||
property var floatedWindows: ({})
|
|
||||||
|
|
||||||
// Whether tiling is globally enabled.
|
|
||||||
property bool tilingEnabled: true
|
|
||||||
|
|
||||||
// Drag state.
|
|
||||||
//
|
|
||||||
// Behaviour: dragging a tile and dropping it onto ANOTHER tile swaps
|
|
||||||
// their positions in the BSP layout. Dropping anywhere else does
|
|
||||||
// nothing (the window will be re-tiled into its original slot on the
|
|
||||||
// next layout pass, unless KWin's native quick-tile / electric border
|
|
||||||
// takes over — which is fine; we don't fight it).
|
|
||||||
property var draggingWindow: null
|
|
||||||
property var swapOutlineActive: false
|
|
||||||
|
|
||||||
// Reorder state — kept stable while dragging so the rest of the layout
|
|
||||||
// doesn't shuffle under the cursor.
|
|
||||||
property string dragSourceScreen: ""
|
|
||||||
property int dragSourceIndex: -1
|
|
||||||
property var dragSwapTarget: null // {screen, index, rect} of tile under cursor
|
|
||||||
|
|
||||||
// Deferred retile queue.
|
|
||||||
// The dockSpaceReserver LayerShell exclusive zone needs one Wayland
|
|
||||||
// roundtrip after setMaximize() before KWin updates MaximizeArea.
|
|
||||||
// We queue output names and flush after 200 ms — same pattern as
|
|
||||||
// convergentwindows constrainAfterRestoreTimer.
|
|
||||||
property var pendingRetile: []
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: retileTimer
|
|
||||||
interval: 200
|
|
||||||
repeat: false
|
|
||||||
onTriggered: {
|
|
||||||
const queue = root.pendingRetile.slice();
|
|
||||||
root.pendingRetile = [];
|
|
||||||
const done = {};
|
|
||||||
for (let i = 0; i < queue.length; i++) {
|
|
||||||
if (!done[queue[i]]) {
|
|
||||||
done[queue[i]] = true;
|
|
||||||
root.retileScreen(queue[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function scheduleRetile(outputName) {
|
|
||||||
const q = root.pendingRetile.slice();
|
|
||||||
if (q.indexOf(outputName) < 0) q.push(outputName);
|
|
||||||
root.pendingRetile = q;
|
|
||||||
retileTimer.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Tiling guard ────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
// Active when convergence is on, gaming is off, AND the user has
|
|
||||||
// dynamic tiling enabled in quick settings. When this returns false
|
|
||||||
// the script is fully inert and KWin's native quick-tile behaviour
|
|
||||||
// owns window placement.
|
|
||||||
function isConvergence() {
|
|
||||||
return ShellSettings.Settings.convergenceModeEnabled &&
|
|
||||||
!ShellSettings.Settings.gamingModeEnabled &&
|
|
||||||
ShellSettings.Settings.dynamicTilingEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mirror the same guard used by convergentwindows: only normalWindow is
|
|
||||||
// reliable in the KWin 6 declarative script API. Add maximizable to
|
|
||||||
// avoid calling setMaximize on layer-shell / panel surfaces.
|
|
||||||
function shouldIgnore(win) {
|
|
||||||
if (!win || win.deleted) return true;
|
|
||||||
if (!win.normalWindow) return true; // panels, dock, desktop, layer-shell
|
|
||||||
if (!win.maximizable) return true; // fixed/special surfaces
|
|
||||||
if (win.fullScreen) return true;
|
|
||||||
// Skip xwaylandvideobridge (same as convergentwindows)
|
|
||||||
if (win.resourceClass === "xwaylandvideobridge") return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldFloat(win) {
|
|
||||||
if (!win) return true;
|
|
||||||
// Fixed-size windows (won't tile sensibly)
|
|
||||||
const minW = win.minSize ? win.minSize.width : 0;
|
|
||||||
const maxW = win.maxSize ? win.maxSize.width : 0;
|
|
||||||
const minH = win.minSize ? win.minSize.height : 0;
|
|
||||||
const maxH = win.maxSize ? win.maxSize.height : 0;
|
|
||||||
if (minW > 0 && maxW > 0 && minW >= maxW) return true;
|
|
||||||
if (minH > 0 && maxH > 0 && minH >= maxH) return true;
|
|
||||||
// Manually floated
|
|
||||||
if (floatedWindows[win.internalId]) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isTileable(win) {
|
|
||||||
if (!tilingEnabled) return false;
|
|
||||||
if (!isConvergence()) return false;
|
|
||||||
if (shouldIgnore(win)) return false;
|
|
||||||
if (shouldFloat(win)) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Layout engine ───────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
function workRect(win) {
|
|
||||||
const output = win.output;
|
|
||||||
const desktop = win.desktops[0];
|
|
||||||
if (!output || !desktop) return null;
|
|
||||||
return KWinComponents.Workspace.clientArea(
|
|
||||||
KWinComponents.Workspace.MaximizeArea, output, desktop);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply outer + inner gaps to a list of rects that together tile a screen.
|
|
||||||
// outer: gap between screen edge and tile
|
|
||||||
// inner: total gap between two adjacent tiles (split equally, so 4px each side)
|
|
||||||
function applyGaps(rects, workArea) {
|
|
||||||
if (!rects || rects.length === 0) return rects;
|
|
||||||
const half = innerGap / 2;
|
|
||||||
const result = [];
|
|
||||||
for (let i = 0; i < rects.length; i++) {
|
|
||||||
let r = rects[i];
|
|
||||||
// Determine which edges touch the work area boundary
|
|
||||||
const atLeft = Math.abs(r.x - workArea.x) < 2;
|
|
||||||
const atTop = Math.abs(r.y - workArea.y) < 2;
|
|
||||||
const atRight = Math.abs((r.x + r.width) - (workArea.x + workArea.width)) < 2;
|
|
||||||
const atBottom = Math.abs((r.y + r.height) - (workArea.y + workArea.height)) < 2;
|
|
||||||
|
|
||||||
const left = atLeft ? outerGap : half;
|
|
||||||
const top = atTop ? outerGap : half;
|
|
||||||
const right = atRight ? outerGap : half;
|
|
||||||
const bottom = atBottom ? outerGap : half;
|
|
||||||
|
|
||||||
result.push(Qt.rect(
|
|
||||||
r.x + left,
|
|
||||||
r.y + top,
|
|
||||||
r.width - left - right,
|
|
||||||
r.height - top - bottom
|
|
||||||
));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Binary-space-partition layout.
|
|
||||||
// Splits `area` recursively for `n` windows.
|
|
||||||
// Returns an ordered array of Qt.rect (without gaps applied).
|
|
||||||
function bspRects(area, n) {
|
|
||||||
if (n <= 0) return [];
|
|
||||||
if (n === 1) return [area];
|
|
||||||
|
|
||||||
// Pick split axis: split the longer dimension
|
|
||||||
const splitHorizontally = area.width >= area.height;
|
|
||||||
const rects = [];
|
|
||||||
|
|
||||||
if (splitHorizontally) {
|
|
||||||
// Left half gets one window; right half gets (n-1)
|
|
||||||
const leftW = Math.round(area.width / 2);
|
|
||||||
const left = Qt.rect(area.x, area.y, leftW, area.height);
|
|
||||||
const right = Qt.rect(area.x + leftW, area.y, area.width - leftW, area.height);
|
|
||||||
rects.push(left);
|
|
||||||
const sub = bspRects(right, n - 1);
|
|
||||||
for (let i = 0; i < sub.length; i++) rects.push(sub[i]);
|
|
||||||
} else {
|
|
||||||
// Top half gets one window; bottom half gets (n-1)
|
|
||||||
const topH = Math.round(area.height / 2);
|
|
||||||
const top = Qt.rect(area.x, area.y, area.width, topH);
|
|
||||||
const bottom = Qt.rect(area.x, area.y + topH, area.width, area.height - topH);
|
|
||||||
rects.push(top);
|
|
||||||
const sub = bspRects(bottom, n - 1);
|
|
||||||
for (let i = 0; i < sub.length; i++) rects.push(sub[i]);
|
|
||||||
}
|
|
||||||
return rects;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recompute and apply layout for a single screen.
|
|
||||||
function retileScreen(outputName) {
|
|
||||||
const tiles = screenTiles[outputName];
|
|
||||||
if (!tiles || tiles.length === 0) return;
|
|
||||||
|
|
||||||
// Get work area from the first window's output
|
|
||||||
let area = null;
|
|
||||||
for (let i = 0; i < tiles.length; i++) {
|
|
||||||
const r = workRect(tiles[i].win);
|
|
||||||
if (r) { area = r; break; }
|
|
||||||
}
|
|
||||||
if (!area) return;
|
|
||||||
|
|
||||||
const n = tiles.length;
|
|
||||||
const rawRects = bspRects(area, n);
|
|
||||||
const gappedRects = applyGaps(rawRects, area);
|
|
||||||
|
|
||||||
for (let i = 0; i < tiles.length; i++) {
|
|
||||||
const win = tiles[i].win;
|
|
||||||
if (!win || win.deleted) continue;
|
|
||||||
const r = gappedRects[i];
|
|
||||||
tiles[i].rect = r;
|
|
||||||
win.frameGeometry = r;
|
|
||||||
}
|
|
||||||
// Trigger a binding update
|
|
||||||
screenTiles[outputName] = tiles.slice();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retile all screens.
|
|
||||||
function retileAll() {
|
|
||||||
for (const name in screenTiles) {
|
|
||||||
retileScreen(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a window to its screen's tile list and retile.
|
|
||||||
function addWindow(win) {
|
|
||||||
if (!isTileable(win)) return;
|
|
||||||
|
|
||||||
const output = win.output;
|
|
||||||
if (!output) return;
|
|
||||||
const name = output.name;
|
|
||||||
|
|
||||||
if (!screenTiles[name]) {
|
|
||||||
screenTiles[name] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Avoid duplicates
|
|
||||||
const tiles = screenTiles[name];
|
|
||||||
for (let i = 0; i < tiles.length; i++) {
|
|
||||||
if (tiles[i].win.internalId === win.internalId) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles.push({ win: win, rect: Qt.rect(0, 0, 0, 0) });
|
|
||||||
screenTiles[name] = tiles;
|
|
||||||
|
|
||||||
// Un-maximize now so the exclusive-zone Wayland roundtrip begins;
|
|
||||||
// retileScreen runs 200 ms later when MaximizeArea has settled.
|
|
||||||
// (Same pattern as convergentwindows constrainAfterRestoreTimer.)
|
|
||||||
if (win.maximizable) win.setMaximize(false, false);
|
|
||||||
win.noBorder = false;
|
|
||||||
scheduleRetile(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove a window from its screen's tile list and retile.
|
|
||||||
function removeWindow(win) {
|
|
||||||
if (!win) return;
|
|
||||||
const output = win.output;
|
|
||||||
const name = output ? output.name : null;
|
|
||||||
|
|
||||||
// Search all screens (window may have been moved)
|
|
||||||
for (const sName in screenTiles) {
|
|
||||||
const tiles = screenTiles[sName];
|
|
||||||
for (let i = 0; i < tiles.length; i++) {
|
|
||||||
if (tiles[i].win.internalId === win.internalId) {
|
|
||||||
tiles.splice(i, 1);
|
|
||||||
screenTiles[sName] = tiles;
|
|
||||||
retileScreen(sName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Keyboard navigation helpers ──────────────────────────────────────────
|
|
||||||
|
|
||||||
function centreOf(rect) {
|
|
||||||
return { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the tile on-screen whose centre is most in `direction` from `fromRect`.
|
|
||||||
// direction: "left"|"right"|"up"|"down"
|
|
||||||
function findNeighbour(fromWin, direction) {
|
|
||||||
const outputName = fromWin.output ? fromWin.output.name : null;
|
|
||||||
if (!outputName) return null;
|
|
||||||
const tiles = screenTiles[outputName];
|
|
||||||
if (!tiles) return null;
|
|
||||||
|
|
||||||
const from = fromWin.frameGeometry;
|
|
||||||
const fc = centreOf(from);
|
|
||||||
let best = null;
|
|
||||||
let bestScore = Infinity;
|
|
||||||
|
|
||||||
for (let i = 0; i < tiles.length; i++) {
|
|
||||||
const t = tiles[i];
|
|
||||||
if (t.win.internalId === fromWin.internalId) continue;
|
|
||||||
const tc = centreOf(t.rect);
|
|
||||||
const dx = tc.x - fc.x;
|
|
||||||
const dy = tc.y - fc.y;
|
|
||||||
|
|
||||||
let inDirection = false;
|
|
||||||
let primary = 0;
|
|
||||||
let secondary = 0;
|
|
||||||
switch (direction) {
|
|
||||||
case "left": inDirection = dx < -5; primary = -dx; secondary = Math.abs(dy); break;
|
|
||||||
case "right": inDirection = dx > 5; primary = dx; secondary = Math.abs(dy); break;
|
|
||||||
case "up": inDirection = dy < -5; primary = -dy; secondary = Math.abs(dx); break;
|
|
||||||
case "down": inDirection = dy > 5; primary = dy; secondary = Math.abs(dx); break;
|
|
||||||
}
|
|
||||||
if (!inDirection) continue;
|
|
||||||
// Score: penalise perpendicular distance lightly
|
|
||||||
const score = primary + secondary * 0.3;
|
|
||||||
if (score < bestScore) { bestScore = score; best = t.win; }
|
|
||||||
}
|
|
||||||
return best;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Workspace connections ─────────────────────────────────────────────
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: KWinComponents.Workspace
|
|
||||||
|
|
||||||
function onWindowAdded(win) {
|
|
||||||
if (isTileable(win)) {
|
|
||||||
addWindow(win);
|
|
||||||
win.interactiveMoveResizeStarted.connect(function() { root.onDragStart(win); });
|
|
||||||
win.interactiveMoveResizeStepped.connect(function(geo) { root.onDragStep(win, geo); });
|
|
||||||
win.interactiveMoveResizeFinished.connect(function() { root.onDragEnd(win); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onWindowRemoved(win) {
|
|
||||||
root.removeWindow(win);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: ShellSettings.Settings
|
|
||||||
|
|
||||||
function onConvergenceModeEnabledChanged() {
|
|
||||||
if (isConvergence()) {
|
|
||||||
// Tile all existing normal windows
|
|
||||||
const wins = KWinComponents.Workspace.windows;
|
|
||||||
for (let i = 0; i < wins.length; i++) {
|
|
||||||
addWindow(wins[i]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Clear all tiles — the convergentwindows script will re-maximize
|
|
||||||
screenTiles = {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onGamingModeEnabledChanged() {
|
|
||||||
if (ShellSettings.Settings.gamingModeEnabled) {
|
|
||||||
screenTiles = {};
|
|
||||||
} else if (isConvergence()) {
|
|
||||||
const wins = KWinComponents.Workspace.windows;
|
|
||||||
for (let i = 0; i < wins.length; i++) {
|
|
||||||
addWindow(wins[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDynamicTilingEnabledChanged() {
|
|
||||||
if (isConvergence()) {
|
|
||||||
const wins = KWinComponents.Workspace.windows;
|
|
||||||
for (let i = 0; i < wins.length; i++) {
|
|
||||||
addWindow(wins[i]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Tiling turned off — leave windows where they are.
|
|
||||||
screenTiles = {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Drag handlers ─────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
// Find the (screen, index) of an existing tile holding this window.
|
|
||||||
function findTileSlot(win) {
|
|
||||||
for (const sName in screenTiles) {
|
|
||||||
const tiles = screenTiles[sName];
|
|
||||||
for (let i = 0; i < tiles.length; i++) {
|
|
||||||
if (tiles[i].win && tiles[i].win.internalId === win.internalId) {
|
|
||||||
return { screen: sName, index: i };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the tile under a cursor position, ignoring the dragged window.
|
|
||||||
function findTileAtCursor(cursor, ignoreWin) {
|
|
||||||
for (const sName in screenTiles) {
|
|
||||||
const tiles = screenTiles[sName];
|
|
||||||
for (let i = 0; i < tiles.length; i++) {
|
|
||||||
const t = tiles[i];
|
|
||||||
if (ignoreWin && t.win && t.win.internalId === ignoreWin.internalId) continue;
|
|
||||||
const r = t.rect;
|
|
||||||
if (!r || r.width <= 0 || r.height <= 0) continue;
|
|
||||||
if (cursor.x >= r.x && cursor.x <= r.x + r.width &&
|
|
||||||
cursor.y >= r.y && cursor.y <= r.y + r.height) {
|
|
||||||
return { screen: sName, index: i, rect: r };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDragStart(win) {
|
|
||||||
if (!isConvergence()) return;
|
|
||||||
draggingWindow = win;
|
|
||||||
swapOutlineActive = false;
|
|
||||||
dragSwapTarget = null;
|
|
||||||
|
|
||||||
// Remember the source slot so we can swap on drop.
|
|
||||||
// The tile stays in screenTiles[] during the drag so the rest of
|
|
||||||
// the layout doesn't shuffle.
|
|
||||||
const slot = findTileSlot(win);
|
|
||||||
if (slot) {
|
|
||||||
dragSourceScreen = slot.screen;
|
|
||||||
dragSourceIndex = slot.index;
|
|
||||||
} else {
|
|
||||||
dragSourceScreen = "";
|
|
||||||
dragSourceIndex = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDragStep(win, geo) {
|
|
||||||
if (!isConvergence()) return;
|
|
||||||
if (draggingWindow !== win) return;
|
|
||||||
|
|
||||||
// Only show an outline when the cursor is over another tile —
|
|
||||||
// a clear visual hint that "drop here = swap".
|
|
||||||
const cursor = KWinComponents.Workspace.cursorPos;
|
|
||||||
const target = findTileAtCursor(cursor, win);
|
|
||||||
|
|
||||||
if (target) {
|
|
||||||
if (!dragSwapTarget ||
|
|
||||||
dragSwapTarget.screen !== target.screen ||
|
|
||||||
dragSwapTarget.index !== target.index) {
|
|
||||||
dragSwapTarget = target;
|
|
||||||
KWinComponents.Workspace.showOutline(target.rect);
|
|
||||||
swapOutlineActive = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dragSwapTarget = null;
|
|
||||||
if (swapOutlineActive) {
|
|
||||||
KWinComponents.Workspace.hideOutline();
|
|
||||||
swapOutlineActive = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDragEnd(win) {
|
|
||||||
if (!isConvergence()) return;
|
|
||||||
if (swapOutlineActive) {
|
|
||||||
KWinComponents.Workspace.hideOutline();
|
|
||||||
swapOutlineActive = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dropped on another tile → swap source/target slots.
|
|
||||||
if (dragSwapTarget && dragSourceScreen && dragSourceIndex >= 0) {
|
|
||||||
const sScreen = dragSourceScreen;
|
|
||||||
const sIdx = dragSourceIndex;
|
|
||||||
const tScreen = dragSwapTarget.screen;
|
|
||||||
const tIdx = dragSwapTarget.index;
|
|
||||||
|
|
||||||
const sTiles = screenTiles[sScreen];
|
|
||||||
const tTiles = screenTiles[tScreen];
|
|
||||||
if (sTiles && tTiles && sTiles[sIdx] && tTiles[tIdx]) {
|
|
||||||
const a = sTiles[sIdx];
|
|
||||||
const b = tTiles[tIdx];
|
|
||||||
if (sScreen === tScreen) {
|
|
||||||
sTiles[sIdx] = b;
|
|
||||||
sTiles[tIdx] = a;
|
|
||||||
screenTiles[sScreen] = sTiles.slice();
|
|
||||||
retileScreen(sScreen);
|
|
||||||
} else {
|
|
||||||
sTiles[sIdx] = b;
|
|
||||||
tTiles[tIdx] = a;
|
|
||||||
screenTiles[sScreen] = sTiles.slice();
|
|
||||||
screenTiles[tScreen] = tTiles.slice();
|
|
||||||
retileScreen(sScreen);
|
|
||||||
retileScreen(tScreen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Dropped elsewhere → restore the source tile to its original slot.
|
|
||||||
// (KWin's native quick-tile may have moved the window; retileScreen
|
|
||||||
// sets frameGeometry back to the BSP rect so the layout stays intact.)
|
|
||||||
else if (dragSourceScreen && dragSourceIndex >= 0) {
|
|
||||||
retileScreen(dragSourceScreen);
|
|
||||||
}
|
|
||||||
|
|
||||||
dragSwapTarget = null;
|
|
||||||
dragSourceScreen = "";
|
|
||||||
dragSourceIndex = -1;
|
|
||||||
draggingWindow = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Keyboard shortcuts ─────────────────────────────────────────────────
|
|
||||||
|
|
||||||
// Focus navigation
|
|
||||||
KWinComponents.ShortcutHandler {
|
|
||||||
name: "SHIFT Tiling Focus Left"
|
|
||||||
text: "SHIFT Tiling: Focus window to the left"
|
|
||||||
sequence: "Meta+H"
|
|
||||||
onActivated: {
|
|
||||||
const win = KWinComponents.Workspace.activeWindow;
|
|
||||||
if (!win) return;
|
|
||||||
const target = root.findNeighbour(win, "left");
|
|
||||||
if (target) KWinComponents.Workspace.activeWindow = target;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KWinComponents.ShortcutHandler {
|
|
||||||
name: "SHIFT Tiling Focus Right"
|
|
||||||
text: "SHIFT Tiling: Focus window to the right"
|
|
||||||
sequence: "Meta+L"
|
|
||||||
onActivated: {
|
|
||||||
const win = KWinComponents.Workspace.activeWindow;
|
|
||||||
if (!win) return;
|
|
||||||
const target = root.findNeighbour(win, "right");
|
|
||||||
if (target) KWinComponents.Workspace.activeWindow = target;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KWinComponents.ShortcutHandler {
|
|
||||||
name: "SHIFT Tiling Focus Up"
|
|
||||||
text: "SHIFT Tiling: Focus window above"
|
|
||||||
sequence: "Meta+K"
|
|
||||||
onActivated: {
|
|
||||||
const win = KWinComponents.Workspace.activeWindow;
|
|
||||||
if (!win) return;
|
|
||||||
const target = root.findNeighbour(win, "up");
|
|
||||||
if (target) KWinComponents.Workspace.activeWindow = target;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KWinComponents.ShortcutHandler {
|
|
||||||
name: "SHIFT Tiling Focus Down"
|
|
||||||
text: "SHIFT Tiling: Focus window below"
|
|
||||||
sequence: "Meta+J"
|
|
||||||
onActivated: {
|
|
||||||
const win = KWinComponents.Workspace.activeWindow;
|
|
||||||
if (!win) return;
|
|
||||||
const target = root.findNeighbour(win, "down");
|
|
||||||
if (target) KWinComponents.Workspace.activeWindow = target;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float toggle
|
|
||||||
KWinComponents.ShortcutHandler {
|
|
||||||
name: "SHIFT Tiling Float Toggle"
|
|
||||||
text: "SHIFT Tiling: Toggle float for active window"
|
|
||||||
sequence: "Meta+F"
|
|
||||||
onActivated: {
|
|
||||||
const win = KWinComponents.Workspace.activeWindow;
|
|
||||||
if (!win) return;
|
|
||||||
const id = win.internalId;
|
|
||||||
if (root.floatedWindows[id]) {
|
|
||||||
delete root.floatedWindows[id];
|
|
||||||
root.addWindow(win);
|
|
||||||
} else {
|
|
||||||
root.floatedWindows[id] = true;
|
|
||||||
root.removeWindow(win);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tiling on/off
|
|
||||||
KWinComponents.ShortcutHandler {
|
|
||||||
name: "SHIFT Tiling Toggle"
|
|
||||||
text: "SHIFT Tiling: Toggle tiling on/off"
|
|
||||||
sequence: "Meta+T"
|
|
||||||
onActivated: {
|
|
||||||
root.tilingEnabled = !root.tilingEnabled;
|
|
||||||
if (root.tilingEnabled) {
|
|
||||||
const wins = KWinComponents.Workspace.windows;
|
|
||||||
for (let i = 0; i < wins.length; i++) root.addWindow(wins[i]);
|
|
||||||
} else {
|
|
||||||
root.screenTiles = {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Right-click menu ──────────────────────────────────────────────────
|
|
||||||
|
|
||||||
// Note: registerUserActionsMenu is a global function in KWin JS scripts.
|
|
||||||
// In declarative QML scripts it is exposed via the KWin global object.
|
|
||||||
// We wire it up after the component is complete.
|
|
||||||
Component.onCompleted: {
|
|
||||||
// Connect to existing windows
|
|
||||||
const wins = KWinComponents.Workspace.windows;
|
|
||||||
for (let i = 0; i < wins.length; i++) {
|
|
||||||
const win = wins[i];
|
|
||||||
if (isTileable(win)) {
|
|
||||||
addWindow(win);
|
|
||||||
win.interactiveMoveResizeStarted.connect(function() { root.onDragStart(win); });
|
|
||||||
win.interactiveMoveResizeStepped.connect(function(geo) { root.onDragStep(win, geo); });
|
|
||||||
win.interactiveMoveResizeFinished.connect(function() { root.onDragEnd(win); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"KPackageStructure": "KWin/Script",
|
|
||||||
"KPlugin": {
|
|
||||||
"Authors": [
|
|
||||||
{
|
|
||||||
"Email": "marcoa@example.com",
|
|
||||||
"Name": "Marco Allegretti"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"Description": "Dynamic tiling for SHIFT convergence mode. Automatically tiles windows using a BSP layout, with snap zones on drag and keyboard navigation.",
|
|
||||||
"EnabledByDefault": false,
|
|
||||||
"Id": "shift-tiling",
|
|
||||||
"License": "EUPL-1.2",
|
|
||||||
"Name": "SHIFT Dynamic Tiling",
|
|
||||||
"Version": "1.0"
|
|
||||||
},
|
|
||||||
"X-KDE-ConfigModule": "kwin/effects/configs/kcm_kwin4_genericscripted",
|
|
||||||
"X-Plasma-API": "declarativescript",
|
|
||||||
"X-Plasma-MainScript": "ui/main.qml"
|
|
||||||
}
|
|
||||||
|
|
@ -3,4 +3,4 @@
|
||||||
# SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
# SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
$XGETTEXT `find . -name \*.js -o -name \*.qml -o -name \*.cpp` -o $podir/plasma_lookandfeel_org.shift.mobile.pot
|
$XGETTEXT `find . -name \*.js -o -name \*.qml -o -name \*.cpp` -o $podir/plasma_lookandfeel_org.kde.breeze.mobile.pot
|
||||||
|
|
|
||||||
|
|
@ -18,15 +18,15 @@ Theme=breeze-dark
|
||||||
name=default
|
name=default
|
||||||
|
|
||||||
[Wallpaper]
|
[Wallpaper]
|
||||||
Image=SHIFT
|
Image=Next
|
||||||
|
|
||||||
# Set default cursor theme
|
# Set default cursor theme
|
||||||
[kcminputrc][Mouse]
|
[kcminputrc][Mouse]
|
||||||
cursorTheme=breeze_cursors
|
cursorTheme=breeze_cursors
|
||||||
|
|
||||||
[kwinrc][org.kde.kdecoration2]
|
[kwinrc][org.kde.kdecoration2]
|
||||||
library=org.kde.kwin.aurorae
|
library=org.kde.breeze
|
||||||
theme=org.shift.decoration
|
theme=Shift Dark
|
||||||
|
|
||||||
[KSplash]
|
[KSplash]
|
||||||
Theme=org.kde.Breeze
|
Theme=org.kde.Breeze
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,119 @@
|
||||||
"KPlugin": {
|
"KPlugin": {
|
||||||
"Authors": [
|
"Authors": [
|
||||||
{
|
{
|
||||||
"Name": "SHIFT Contributors"
|
"Email": "plasma-mobile@kde.org",
|
||||||
|
"Name": "KDE Visual Design Group",
|
||||||
|
"Name[ar]": "مجموعة التصميم المرئي لكِيدِي",
|
||||||
|
"Name[ca@valencia]": "Grup de disseny visual de KDE",
|
||||||
|
"Name[ca]": "Grup de disseny visual de KDE",
|
||||||
|
"Name[cs]": "Skupina vizuálního návrhu KDE",
|
||||||
|
"Name[de]": "KDE Visual Design Group",
|
||||||
|
"Name[en_GB]": "KDE Visual Design Group",
|
||||||
|
"Name[eo]": "KDE Vida Dezajna Grupo",
|
||||||
|
"Name[es]": "El grupo de diseño visual de KDE",
|
||||||
|
"Name[eu]": "KDE Ikus-diseinu taldea",
|
||||||
|
"Name[fi]": "KDE:n visuaalinen suunnitteluryhmä",
|
||||||
|
"Name[fr]": "Groupe de conception graphique de KDE",
|
||||||
|
"Name[gl]": "Grupo de deseño visual de KDE",
|
||||||
|
"Name[he]": "קבוצת העיצוב החזותי של KDE",
|
||||||
|
"Name[hu]": "KDE Visual Design Group",
|
||||||
|
"Name[ia]": "KDE Visual Design Group (Gruppo de Designo Visual de KDE)",
|
||||||
|
"Name[is]": "KDE-hópurinn í myndrænni hönnun",
|
||||||
|
"Name[it]": "KDE Visual Design Group",
|
||||||
|
"Name[ka]": "KDE-ის ვიზუალური დიზაინის ჯგუფი",
|
||||||
|
"Name[ko]": "KDE 시각 디자인 그룹",
|
||||||
|
"Name[lv]": "KDE vizuālā dizaina grupa",
|
||||||
|
"Name[nl]": "KDE Visuele ontwerpgroep",
|
||||||
|
"Name[nn]": "KDE Visual Design Group",
|
||||||
|
"Name[pa]": "KDE ਦਿੱਖ ਡਿਜ਼ਾਇਨ ਗਰੁੱਪ",
|
||||||
|
"Name[pl]": "Grupa oprawy graficznej KDE",
|
||||||
|
"Name[pt_BR]": "Grupo de design visual do KDE",
|
||||||
|
"Name[ru]": "Группа по визуальному дизайну KDE",
|
||||||
|
"Name[sa]": "KDE दृश्य डिजाइन समूह",
|
||||||
|
"Name[sk]": "KDE Visual Design Group",
|
||||||
|
"Name[sl]": "KDE Visual Design Group",
|
||||||
|
"Name[sv]": "KDE:s visuella designgrupp",
|
||||||
|
"Name[tr]": "KDE Görsel Tasarım Grubu",
|
||||||
|
"Name[uk]": "Група з візуального дизайну KDE",
|
||||||
|
"Name[zh_CN]": "KDE 视觉设计团队",
|
||||||
|
"Name[zh_TW]": "KDE VDG 視覺設計組"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Category": "",
|
"Category": "",
|
||||||
"Description": "SHIFT for mobile and convergent devices",
|
"Description": "Plasma for mobile devices",
|
||||||
"Id": "org.shift.mobile",
|
"Description[ar]": "بلازما للأجهزة الجوالة",
|
||||||
|
"Description[ca@valencia]": "Plasma per a dispositius mòbils",
|
||||||
|
"Description[ca]": "Plasma per a dispositius mòbils",
|
||||||
|
"Description[cs]": "Plasma pro mobilní telefony",
|
||||||
|
"Description[de]": "Plasma-Shell für Mobilgeräte",
|
||||||
|
"Description[en_GB]": "Plasma for mobile devices",
|
||||||
|
"Description[eo]": "Plasmo por porteblaj aparatoj",
|
||||||
|
"Description[es]": "Plasma para dispositivos móviles",
|
||||||
|
"Description[eu]": "Plasma gailu mugikorretarako",
|
||||||
|
"Description[fi]": "Plasma mobiililaitteille",
|
||||||
|
"Description[fr]": "Plasma pour périphériques mobiles",
|
||||||
|
"Description[gl]": "Plasma para dispositivos móbiles.",
|
||||||
|
"Description[he]": "פלזמה למכשירים ניידים",
|
||||||
|
"Description[hu]": "Plasma mobileszközökhöz",
|
||||||
|
"Description[ia]": "Plasma per dispositivos mobile",
|
||||||
|
"Description[is]": "Plasma fyrir farsíma",
|
||||||
|
"Description[it]": "Plasma per dispositivi mobili",
|
||||||
|
"Description[ka]": "Plasma მობილური მოწყობილობებისთვის",
|
||||||
|
"Description[ko]": "모바일 장치용 Plasma",
|
||||||
|
"Description[lv]": "„Plasma“ mobilajām ierīcēm",
|
||||||
|
"Description[nl]": "Plasma voor mobiele apparaten",
|
||||||
|
"Description[nn]": "Plasma for telefonar og nettbrett",
|
||||||
|
"Description[pa]": "ਮੋਬਾਈਲ ਡਿਵਾਈਸ ਲਈ ਪਲਾਜ਼ਮਾ",
|
||||||
|
"Description[pl]": "Plazma dla urządzeń przenośnych",
|
||||||
|
"Description[pt_BR]": "Plasma para dispositivos móveis",
|
||||||
|
"Description[ru]": "Plasma для мобильных устройств",
|
||||||
|
"Description[sa]": "चलयन्त्राणां कृते प्लाज्मा",
|
||||||
|
"Description[sk]": "Plasma pre mobilné zariadenia",
|
||||||
|
"Description[sl]": "Plasma za mobilne naprave",
|
||||||
|
"Description[sv]": "Plasma för mobilapparater",
|
||||||
|
"Description[ta]": "கைபேசிகளுக்கான பிளாஸ்மா",
|
||||||
|
"Description[tr]": "Taşınabilir aygıtlar için Plasma",
|
||||||
|
"Description[uk]": "Плазма для мобільних пристроїв",
|
||||||
|
"Description[zh_CN]": "针对手机设备设计的 Plasma 环境",
|
||||||
|
"Description[zh_TW]": "適用於行動裝置的 Plasma",
|
||||||
|
"Id": "org.kde.breeze.mobile",
|
||||||
"License": "GPLv2+",
|
"License": "GPLv2+",
|
||||||
"Name": "SHIFT Mobile"
|
"Name": "Plasma Mobile Breeze",
|
||||||
|
"Name[ar]": "نسيم بلازما الجوال",
|
||||||
|
"Name[ca@valencia]": "Plasma Mobile Brisa",
|
||||||
|
"Name[ca]": "Plasma Mobile Brisa",
|
||||||
|
"Name[cs]": "Plasma Mobile Breeze",
|
||||||
|
"Name[de]": "Plasma Mobile Breeze",
|
||||||
|
"Name[en_GB]": "Plasma Mobile Breeze",
|
||||||
|
"Name[eo]": "Plasma-Poŝtelefona Breeze",
|
||||||
|
"Name[es]": "Brisa de Plasma Mobile",
|
||||||
|
"Name[eu]": "Plasma Mugikorra Breeze",
|
||||||
|
"Name[fi]": "Plasma Mobilen Breeze",
|
||||||
|
"Name[fr]": "Thème « Breeze » pour Plasma Mobile",
|
||||||
|
"Name[gl]": "Brisa para Plasma Mobile",
|
||||||
|
"Name[he]": "בריזה לפלזמה לניידים",
|
||||||
|
"Name[hu]": "Plasma Mobile Breeze",
|
||||||
|
"Name[ia]": "Breeze de Plasma Mobile",
|
||||||
|
"Name[is]": "Plasma Mobile Breeze",
|
||||||
|
"Name[it]": "Brezza per Plasma Mobile",
|
||||||
|
"Name[ka]": "Plasma მობილური ნიავი",
|
||||||
|
"Name[ko]": "Plasma 모바일 Breeze",
|
||||||
|
"Name[lv]": "„Plasma Mobile“ „Breeze“ motīvs",
|
||||||
|
"Name[nl]": "Plasma Mobile Breeze",
|
||||||
|
"Name[nn]": "Plasma Mobile Breeze",
|
||||||
|
"Name[pa]": "ਪਲਾਜ਼ਮਾ ਮੋਬਾਈਲ ਬਰੀਜ਼",
|
||||||
|
"Name[pl]": "Bryza Przenośnej Plazmy",
|
||||||
|
"Name[pt_BR]": "Breeze do Plasma Mobile",
|
||||||
|
"Name[ru]": "Breeze для Plasma Mobile",
|
||||||
|
"Name[sa]": "प्लाज्मा चल हवा",
|
||||||
|
"Name[sk]": "Vánok Plasma Mobile",
|
||||||
|
"Name[sl]": "Plasma Mobile Sapica",
|
||||||
|
"Name[sv]": "Plasma mobil Breeze",
|
||||||
|
"Name[tr]": "Plasma Cep Esinti",
|
||||||
|
"Name[uk]": "Breeze для мобільної Плазми",
|
||||||
|
"Name[zh_CN]": "Plasma Mobile Breeze 微风",
|
||||||
|
"Name[zh_TW]": "Plasma 行動 Breeze",
|
||||||
|
"Website": "https://www.kde.org"
|
||||||
},
|
},
|
||||||
"Keywords": "Phone;Mobile;Desktop;Workspace;Appearance;Look and Feel;Logout;Lock;Suspend;Shutdown;Hibernate;",
|
"Keywords": "Phone;Mobile;Desktop;Workspace;Appearance;Look and Feel;Logout;Lock;Suspend;Shutdown;Hibernate;",
|
||||||
"X-Plasma-APIVersion": "2"
|
"X-Plasma-APIVersion": "2"
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ plasma_install_package(bluetooth org.kde.plasma.quicksetting.bluetooth quicksett
|
||||||
plasma_install_package(caffeine org.kde.plasma.quicksetting.caffeine quicksettings)
|
plasma_install_package(caffeine org.kde.plasma.quicksetting.caffeine quicksettings)
|
||||||
plasma_install_package(docked org.kde.plasma.quicksetting.docked quicksettings)
|
plasma_install_package(docked org.kde.plasma.quicksetting.docked quicksettings)
|
||||||
plasma_install_package(donotdisturb org.kde.plasma.quicksetting.donotdisturb quicksettings)
|
plasma_install_package(donotdisturb org.kde.plasma.quicksetting.donotdisturb quicksettings)
|
||||||
plasma_install_package(dynamictiling org.kde.plasma.quicksetting.dynamictiling quicksettings)
|
|
||||||
plasma_install_package(gaming org.kde.plasma.quicksetting.gaming quicksettings)
|
plasma_install_package(gaming org.kde.plasma.quicksetting.gaming quicksettings)
|
||||||
plasma_install_package(gaminghint org.kde.plasma.quicksetting.gaminghint quicksettings)
|
plasma_install_package(gaminghint org.kde.plasma.quicksetting.gaminghint quicksettings)
|
||||||
plasma_install_package(autohidepanels org.kde.plasma.quicksetting.autohidepanels quicksettings)
|
plasma_install_package(autohidepanels org.kde.plasma.quicksetting.autohidepanels quicksettings)
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2026 Marco Allegretti
|
|
||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
|
|
||||||
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
|
|
||||||
import org.kde.plasma.private.mobileshell.quicksettingsplugin as QS
|
|
||||||
|
|
||||||
QS.QuickSetting {
|
|
||||||
text: i18n("Dynamic Tiling")
|
|
||||||
icon: "view-grid-symbolic"
|
|
||||||
|
|
||||||
// Only meaningful in convergence (desktop) mode. Hidden everywhere else.
|
|
||||||
available: ShellSettings.Settings.convergenceModeEnabled
|
|
||||||
&& !ShellSettings.Settings.gamingModeEnabled
|
|
||||||
|
|
||||||
enabled: ShellSettings.Settings.dynamicTilingEnabled
|
|
||||||
status: enabled ? i18n("On") : i18n("Off")
|
|
||||||
|
|
||||||
function toggle() {
|
|
||||||
ShellSettings.Settings.dynamicTilingEnabled = !ShellSettings.Settings.dynamicTilingEnabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
{
|
|
||||||
"KPackageStructure": "KPackage/GenericQML",
|
|
||||||
"KPlugin": {
|
|
||||||
"Authors": [
|
|
||||||
{
|
|
||||||
"Email": "marco@lateuf.it",
|
|
||||||
"Name": "Marco Allegretti"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"Description": "Quick setting to toggle SHIFT dynamic (BSP) window tiling in convergence mode",
|
|
||||||
"Icon": "view-grid-symbolic",
|
|
||||||
"Id": "org.kde.plasma.quicksetting.dynamictiling",
|
|
||||||
"License": "LGPL",
|
|
||||||
"Name": "Dynamic Tiling",
|
|
||||||
"Website": "https://kde.org"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2014 Aaron J. Seigo
|
|
||||||
// SPDX-FileCopyrightText: 2014-2019 Marco Martin <mart@kde.org>
|
|
||||||
// SPDX-FileCopyrightText: 2015-2021 Bhushan Shah <bshah@kde.org>
|
|
||||||
// SPDX-FileCopyrightText: 2021 Aleix Pol <apol@kde.org>
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
// Load panel layout
|
|
||||||
loadTemplate("org.kde.plasma.mobile.defaultNavigationPanel");
|
|
||||||
loadTemplate("org.kde.plasma.mobile.defaultStatusBar");
|
|
||||||
|
|
||||||
// Set wallpaper plugin
|
|
||||||
var shiftDefaultWallpaperUrl = "@SHIFT_DEFAULT_WALLPAPER_URL@";
|
|
||||||
var desktopsArray = desktopsForActivity(currentActivity());
|
|
||||||
for (var j = 0; j < desktopsArray.length; j++) {
|
|
||||||
desktopsArray[j].wallpaperPlugin = "org.kde.image";
|
|
||||||
desktopsArray[j].currentConfigGroup = ["Wallpaper", "org.kde.image", "General"];
|
|
||||||
desktopsArray[j].writeConfig("Image", shiftDefaultWallpaperUrl);
|
|
||||||
|
|
||||||
if (j == 0) {
|
|
||||||
// Add meta shortcut
|
|
||||||
desktopsArray[0].currentConfigGroup = ["Shortcuts"]
|
|
||||||
desktopsArray[0].writeConfig("global", "Meta+F1")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,11 +2,3 @@
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
add_subdirectory(notificationtest)
|
add_subdirectory(notificationtest)
|
||||||
|
|
||||||
find_program(BASH_EXECUTABLE bash)
|
|
||||||
if(BASH_EXECUTABLE)
|
|
||||||
add_test(
|
|
||||||
NAME convergence-dock-invariant
|
|
||||||
COMMAND ${BASH_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/check-convergence-dock-invariant.sh
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,5 @@ Be sure to have the project installed on the system.
|
||||||
To test the power menu, the following command can be used to invoke `ksmserver-logout-greeter` with the mobile look and feel in a window:
|
To test the power menu, the following command can be used to invoke `ksmserver-logout-greeter` with the mobile look and feel in a window:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
~/kde/usr/lib/libexec/ksmserver-logout-greeter --windowed --lookandfeel org.shift.mobile
|
~/kde/usr/lib/libexec/ksmserver-logout-greeter --windowed --lookandfeel org.kde.breeze.mobile
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# SPDX-FileCopyrightText: 2026 Marco Allegretti <mightymarco4@gmail.com>
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
||||||
|
|
||||||
constants="$repo_root/components/mobileshell/qml/components/Constants.qml"
|
|
||||||
taskpanel="$repo_root/containments/taskpanel/qml/main.qml"
|
|
||||||
folio_main="$repo_root/containments/homescreens/folio/qml/main.qml"
|
|
||||||
folio_home="$repo_root/containments/homescreens/folio/qml/FolioHomeScreen.qml"
|
|
||||||
|
|
||||||
require_line() {
|
|
||||||
local file="$1"
|
|
||||||
local needle="$2"
|
|
||||||
|
|
||||||
if ! grep -Fq "$needle" "$file"; then
|
|
||||||
echo "Missing invariant in ${file#$repo_root/}: $needle" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
require_line "$constants" "readonly property real convergenceDockHeight: Kirigami.Units.gridUnit * 3"
|
|
||||||
require_line "$constants" "readonly property real convergenceDockRevealHeight: Kirigami.Units.gridUnit"
|
|
||||||
|
|
||||||
require_line "$taskpanel" "height: MobileShell.Constants.convergenceDockHeight"
|
|
||||||
require_line "$taskpanel" "LayerShell.Window.exclusionZone: MobileShell.Constants.convergenceDockHeight"
|
|
||||||
|
|
||||||
require_line "$folio_main" "height: 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_home" "height: ShellSettings.Settings.convergenceModeEnabled ? MobileShell.Constants.convergenceDockHeight : Kirigami.Units.gridUnit * 6"
|
|
||||||
|
|
||||||
dock_offset_transforms="$(grep -F "transform: Translate { y: dockOverlay.dockOffset }" "$folio_main" | wc -l)"
|
|
||||||
if [[ "$dock_offset_transforms" -ne 1 ]]; then
|
|
||||||
echo "Expected only dock contents to slide with dockOverlay.dockOffset; found $dock_offset_transforms transforms" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
# SPDX-FileCopyrightText: 2026 SHIFT Contributors
|
|
||||||
# SPDX-License-Identifier: EUPL-1.2
|
|
||||||
|
|
||||||
install(DIRECTORY SHIFT
|
|
||||||
DESTINATION ${KDE_INSTALL_WALLPAPERDIR}
|
|
||||||
PATTERN "*.license" EXCLUDE
|
|
||||||
)
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 246 KiB |
|
|
@ -1,2 +0,0 @@
|
||||||
SPDX-FileCopyrightText: 2026 Luiza Balaur
|
|
||||||
SPDX-License-Identifier: EUPL-1.2
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 236 KiB |
|
|
@ -1,2 +0,0 @@
|
||||||
SPDX-FileCopyrightText: 2026 Luiza Balaur
|
|
||||||
SPDX-License-Identifier: EUPL-1.2
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"KPlugin": {
|
|
||||||
"Authors": [
|
|
||||||
{
|
|
||||||
"Name": "Luiza Balaur"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"Id": "SHIFT",
|
|
||||||
"License": "EUPL-1.2",
|
|
||||||
"Name": "SHIFT Light / SHIFT Dark"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
SPDX-FileCopyrightText: 2026 Luiza Balaur
|
|
||||||
SPDX-License-Identifier: EUPL-1.2
|
|
||||||
Loading…
Reference in a new issue