mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-29 15:03:09 +00:00
Add pin-to-dock for running apps in convergence mode
Right-click a running task icon to pin the app to the dock favourites bar. Pinned apps show a "Remove from Dock" action. The dock overlay renders as a LayerTop window so context menus and the dock itself stay above application windows. Menus use popupType Window to avoid clipping inside the narrow dock surface.
This commit is contained in:
parent
eb33b533c6
commit
8c56409f1c
7 changed files with 101 additions and 14 deletions
|
|
@ -30,6 +30,8 @@ Key changes so far:
|
|||
- **Desktop niceties** — right-click wallpaper settings, minimize-all on
|
||||
home press, clickable page indicators, action drawer toggle on click.
|
||||
- **Thumbnail previews** on dock icon hover via PipeWire screencasting.
|
||||
- **Pin to dock** — right-click a running app to pin it to favourites;
|
||||
pinned apps get a "Remove from Dock" action.
|
||||
- **Bug fixes** — minimized windows stay in dock instead of vanishing,
|
||||
app drawer dismiss overlay no longer covers dock icons, launching an
|
||||
app in convergence mode always starts a new process instead of
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "favouritesmodel.h"
|
||||
#include "homescreenstate.h"
|
||||
|
||||
#include <KService>
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
|
|
@ -76,6 +77,35 @@ void FavouritesModel::removeEntry(int row)
|
|||
save();
|
||||
}
|
||||
|
||||
bool FavouritesModel::addApplication(const QString &storageId)
|
||||
{
|
||||
if (containsApplication(storageId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
KService::Ptr service = KService::serviceByStorageId(storageId);
|
||||
if (!service) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto app = std::make_shared<FolioApplication>(service);
|
||||
auto delegate = std::make_shared<FolioDelegate>(app);
|
||||
return addEntry(m_delegates.size(), delegate);
|
||||
}
|
||||
|
||||
bool FavouritesModel::containsApplication(const QString &storageId) const
|
||||
{
|
||||
for (const auto &entry : m_delegates) {
|
||||
if (entry.delegate && entry.delegate->type() == FolioDelegate::Application) {
|
||||
auto app = entry.delegate->application();
|
||||
if (app && app->storageId() == storageId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void FavouritesModel::moveEntry(int fromRow, int toRow)
|
||||
{
|
||||
if (fromRow < 0 || toRow < 0 || fromRow >= m_delegates.size() || toRow >= m_delegates.size() || fromRow == toRow) {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ public:
|
|||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
Q_INVOKABLE void removeEntry(int row);
|
||||
Q_INVOKABLE bool addApplication(const QString &storageId);
|
||||
Q_INVOKABLE bool containsApplication(const QString &storageId) const;
|
||||
void moveEntry(int fromRow, int toRow);
|
||||
bool canAddEntry(int row, std::shared_ptr<FolioDelegate> delegate);
|
||||
bool addEntry(int row, std::shared_ptr<FolioDelegate> delegate);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import plasma.applet.org.kde.plasma.mobile.homescreen.folio as Folio
|
|||
import org.kde.plasma.private.mobileshell as MobileShell
|
||||
import org.kde.kirigami as Kirigami
|
||||
import QtQuick.Controls as Controls
|
||||
import QtQuick.Templates as T
|
||||
|
||||
import "./private"
|
||||
import "./delegate"
|
||||
|
|
@ -345,6 +346,7 @@ MouseArea {
|
|||
|
||||
ContextMenuLoader {
|
||||
id: contextMenu
|
||||
menuPopupType: root.convergenceMode ? T.Popup.Window : T.Popup.Item
|
||||
|
||||
// close menu when drag starts
|
||||
Connections {
|
||||
|
|
@ -425,6 +427,7 @@ MouseArea {
|
|||
|
||||
ContextMenuLoader {
|
||||
id: contextMenu
|
||||
menuPopupType: root.convergenceMode ? T.Popup.Window : T.Popup.Item
|
||||
|
||||
// close menu when drag starts
|
||||
Connections {
|
||||
|
|
@ -661,7 +664,7 @@ MouseArea {
|
|||
if (mouse.button === Qt.RightButton) {
|
||||
thumbnailPopup.close()
|
||||
thumbnailShowTimer.stop()
|
||||
taskContextMenu.popup();
|
||||
taskContextMenu.open();
|
||||
} else {
|
||||
thumbnailPopup.close()
|
||||
tasksModel.requestActivate(tasksModel.makeModelIndex(taskDelegate.index));
|
||||
|
|
@ -688,23 +691,38 @@ MouseArea {
|
|||
}
|
||||
}
|
||||
|
||||
Controls.Menu {
|
||||
PC3.Menu {
|
||||
id: taskContextMenu
|
||||
Controls.MenuItem {
|
||||
text: taskDelegate.model.IsMinimized ? i18n("Restore") : i18n("Minimize")
|
||||
popupType: T.Popup.Window
|
||||
|
||||
property string taskStorageId: {
|
||||
var id = taskDelegate.model.AppId || ""
|
||||
if (id && !id.endsWith(".desktop"))
|
||||
id += ".desktop"
|
||||
return id
|
||||
}
|
||||
|
||||
PC3.MenuItem {
|
||||
icon.name: "window-pin"
|
||||
text: i18n("Pin to Dock")
|
||||
visible: taskContextMenu.taskStorageId !== "" && !folio.FavouritesModel.containsApplication(taskContextMenu.taskStorageId)
|
||||
enabled: !folio.FolioSettings.lockLayout
|
||||
onClicked: folio.FavouritesModel.addApplication(taskContextMenu.taskStorageId)
|
||||
}
|
||||
PC3.MenuItem {
|
||||
icon.name: taskDelegate.model.IsMinimized ? "window-restore" : "window-minimize"
|
||||
onTriggered: tasksModel.requestToggleMinimized(tasksModel.makeModelIndex(taskDelegate.index))
|
||||
text: taskDelegate.model.IsMinimized ? i18n("Restore") : i18n("Minimize")
|
||||
onClicked: tasksModel.requestToggleMinimized(tasksModel.makeModelIndex(taskDelegate.index))
|
||||
}
|
||||
Controls.MenuItem {
|
||||
text: taskDelegate.model.IsMaximized ? i18n("Restore") : i18n("Maximize")
|
||||
PC3.MenuItem {
|
||||
icon.name: taskDelegate.model.IsMaximized ? "window-restore" : "window-maximize"
|
||||
onTriggered: tasksModel.requestToggleMaximized(tasksModel.makeModelIndex(taskDelegate.index))
|
||||
text: taskDelegate.model.IsMaximized ? i18n("Restore") : i18n("Maximize")
|
||||
onClicked: tasksModel.requestToggleMaximized(tasksModel.makeModelIndex(taskDelegate.index))
|
||||
}
|
||||
Controls.MenuSeparator {}
|
||||
Controls.MenuItem {
|
||||
text: i18n("Close")
|
||||
PC3.MenuItem {
|
||||
icon.name: "window-close"
|
||||
onTriggered: tasksModel.requestClose(tasksModel.makeModelIndex(taskDelegate.index))
|
||||
text: i18n("Close")
|
||||
onClicked: tasksModel.requestClose(tasksModel.makeModelIndex(taskDelegate.index))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -348,7 +348,8 @@ Item {
|
|||
|
||||
// don't show in settings mode
|
||||
opacity: 1 - folio.HomeScreenState.settingsOpenProgress
|
||||
visible: opacity > 0
|
||||
// Hidden in convergence mode — the dock overlay window renders it instead
|
||||
visible: opacity > 0 && !ShellSettings.Settings.convergenceModeEnabled
|
||||
|
||||
// one is ignored as anchors are set
|
||||
height: ShellSettings.Settings.convergenceModeEnabled ? Kirigami.Units.gridUnit * 3 : Kirigami.Units.gridUnit * 6
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import org.kde.plasma.private.mobileshell.state as MobileShellState
|
|||
import org.kde.plasma.private.mobileshell.windowplugin as WindowPlugin
|
||||
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
|
||||
|
||||
import org.kde.layershell 1.0 as LayerShell
|
||||
|
||||
import plasma.applet.org.kde.plasma.mobile.homescreen.folio as Folio
|
||||
|
||||
import "./private"
|
||||
|
|
@ -156,6 +158,33 @@ ContainmentItem {
|
|||
opacity: folio.HomeScreenState.settingsOpenProgress
|
||||
}
|
||||
|
||||
// Dock overlay window — renders the favourites bar above application
|
||||
// windows in convergence mode. LayerTop sits above normal windows but
|
||||
// below LayerOverlay (notifications, volume OSD). The exclusive zone
|
||||
// that reserves screen space is handled by the dockSpaceReserver in the
|
||||
// task panel containment; this window only provides the visible dock.
|
||||
Window {
|
||||
id: dockOverlay
|
||||
visible: ShellSettings.Settings.convergenceModeEnabled
|
||||
color: "transparent"
|
||||
width: Screen.width
|
||||
height: Kirigami.Units.gridUnit * 3
|
||||
|
||||
LayerShell.Window.scope: "dock-overlay"
|
||||
LayerShell.Window.layer: LayerShell.Window.LayerTop
|
||||
LayerShell.Window.anchors: LayerShell.Window.AnchorBottom | LayerShell.Window.AnchorLeft | LayerShell.Window.AnchorRight
|
||||
LayerShell.Window.exclusionZone: -1
|
||||
LayerShell.Window.keyboardInteractivity: LayerShell.Window.KeyboardInteractivityOnDemand
|
||||
|
||||
FavouritesBar {
|
||||
id: dockOverlayBar
|
||||
anchors.fill: parent
|
||||
folio: root.folio
|
||||
maskManager: root.maskManager
|
||||
homeScreen: folioHomeScreen
|
||||
}
|
||||
}
|
||||
|
||||
MobileShell.HomeScreen {
|
||||
id: homeScreen
|
||||
anchors.fill: parent
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import QtQuick
|
||||
import QtQuick.Window
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Templates as T
|
||||
|
||||
import org.kde.plasma.components 3.0 as PC3
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
|
@ -13,6 +14,7 @@ Loader {
|
|||
active: false
|
||||
|
||||
property list<Kirigami.Action> actions
|
||||
property int menuPopupType: T.Popup.Item
|
||||
|
||||
function open() {
|
||||
root.active = true;
|
||||
|
|
@ -28,7 +30,10 @@ Loader {
|
|||
sourceComponent: PC3.Menu {
|
||||
id: menu
|
||||
title: "Context Menu"
|
||||
closePolicy: PC3.Menu.CloseOnReleaseOutside | PC3.Menu.CloseOnEscape
|
||||
popupType: root.menuPopupType
|
||||
closePolicy: root.menuPopupType === T.Popup.Window
|
||||
? (PC3.Menu.CloseOnPressOutside | PC3.Menu.CloseOnEscape)
|
||||
: (PC3.Menu.CloseOnReleaseOutside | PC3.Menu.CloseOnEscape)
|
||||
|
||||
Repeater {
|
||||
model: root.actions
|
||||
|
|
|
|||
Loading…
Reference in a new issue