mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23:09 +00:00
Allow dragging running dock tasks to pin them
Running task icons in the convergence dock can now be dragged into the favourites section to pin the app at the drop position. Add indexed insertion to FavouritesModel so pinning can place the new favourite at a specific slot instead of always appending. Show a placeholder while dragging and reuse the existing addApplication path for the final pin action.
This commit is contained in:
parent
0cbd933e71
commit
220be94b63
3 changed files with 124 additions and 12 deletions
|
|
@ -78,6 +78,11 @@ void FavouritesModel::removeEntry(int row)
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FavouritesModel::addApplication(const QString &storageId)
|
bool FavouritesModel::addApplication(const QString &storageId)
|
||||||
|
{
|
||||||
|
return addApplicationAt(m_delegates.size(), storageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FavouritesModel::addApplicationAt(int row, const QString &storageId)
|
||||||
{
|
{
|
||||||
if (containsApplication(storageId)) {
|
if (containsApplication(storageId)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -90,7 +95,7 @@ bool FavouritesModel::addApplication(const QString &storageId)
|
||||||
|
|
||||||
auto app = std::make_shared<FolioApplication>(service);
|
auto app = std::make_shared<FolioApplication>(service);
|
||||||
auto delegate = std::make_shared<FolioDelegate>(app);
|
auto delegate = std::make_shared<FolioDelegate>(app);
|
||||||
return addEntry(m_delegates.size(), delegate);
|
return addEntry(row, delegate);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FavouritesModel::containsApplication(const QString &storageId) const
|
bool FavouritesModel::containsApplication(const QString &storageId) const
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ public:
|
||||||
|
|
||||||
Q_INVOKABLE void removeEntry(int row);
|
Q_INVOKABLE void removeEntry(int row);
|
||||||
Q_INVOKABLE bool addApplication(const QString &storageId);
|
Q_INVOKABLE bool addApplication(const QString &storageId);
|
||||||
|
Q_INVOKABLE bool addApplicationAt(int row, const QString &storageId);
|
||||||
Q_INVOKABLE bool containsApplication(const QString &storageId) const;
|
Q_INVOKABLE bool containsApplication(const QString &storageId) const;
|
||||||
Q_INVOKABLE void moveEntry(int fromRow, int toRow);
|
Q_INVOKABLE void moveEntry(int fromRow, int toRow);
|
||||||
bool canAddEntry(int row, std::shared_ptr<FolioDelegate> delegate);
|
bool canAddEntry(int row, std::shared_ptr<FolioDelegate> delegate);
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,65 @@ MouseArea {
|
||||||
return Math.max(0, Math.min(repeater.count - 1, dragReorderIndex + shift))
|
return Math.max(0, Math.min(repeater.count - 1, dragReorderIndex + shift))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Drag-to-pin state for running tasks in convergence mode.
|
||||||
|
property int taskPinDragIndex: -1
|
||||||
|
property real taskPinDragOffset: 0
|
||||||
|
property int taskPinTargetIndex: -1
|
||||||
|
property string taskPinStorageId: ""
|
||||||
|
readonly property bool taskPinCanDrop: taskPinTargetIndex !== -1 && taskPinStorageId !== ""
|
||||||
|
|
||||||
|
function runningTaskStorageId(taskModel) {
|
||||||
|
var id = taskModel ? taskModel.AppId || "" : ""
|
||||||
|
if (id && !id.endsWith(".desktop"))
|
||||||
|
id += ".desktop"
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
function favouriteBaseX(index) {
|
||||||
|
return index * root.dockCellWidth - (root.totalItemCount / 2) * root.dockCellWidth + root.dockCenterX - root.spacerWidth / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
function taskBaseX(index) {
|
||||||
|
return (repeater.count + index) * root.dockCellWidth - (root.totalItemCount / 2) * root.dockCellWidth + root.dockCenterX + root.spacerWidth / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearTaskPinDrag() {
|
||||||
|
root.taskPinDragIndex = -1
|
||||||
|
root.taskPinDragOffset = 0
|
||||||
|
root.taskPinTargetIndex = -1
|
||||||
|
root.taskPinStorageId = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTaskPinTarget() {
|
||||||
|
if (root.taskPinDragIndex === -1 || root.taskPinStorageId === "" || folio.FolioSettings.lockLayout || folio.FavouritesModel.containsApplication(root.taskPinStorageId)) {
|
||||||
|
root.taskPinTargetIndex = -1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var draggedCenterX = root.taskBaseX(root.taskPinDragIndex) + root.dockCellWidth / 2 + root.taskPinDragOffset
|
||||||
|
var firstTaskCenterX = root.taskBaseX(0) + root.dockCellWidth / 2
|
||||||
|
|
||||||
|
if (draggedCenterX >= firstTaskCenterX) {
|
||||||
|
root.taskPinTargetIndex = -1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repeater.count === 0) {
|
||||||
|
root.taskPinTargetIndex = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let index = 0; index < repeater.count; ++index) {
|
||||||
|
let favouriteCenterX = root.favouriteBaseX(index) + root.dockCellWidth / 2
|
||||||
|
if (draggedCenterX < favouriteCenterX) {
|
||||||
|
root.taskPinTargetIndex = index
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
root.taskPinTargetIndex = repeater.count
|
||||||
|
}
|
||||||
|
|
||||||
// Home button (convergence mode, left end)
|
// Home button (convergence mode, left end)
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: homeButton
|
id: homeButton
|
||||||
|
|
@ -226,7 +285,9 @@ MouseArea {
|
||||||
NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad }
|
NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad }
|
||||||
}
|
}
|
||||||
|
|
||||||
x: (isLocationBottom ? centerPosition + root.dockCenterX - root.spacerWidth / 2 : (parent.width - width) / 2) + dragVisualShift
|
property real taskPinVisualShift: root.taskPinCanDrop && delegate.index >= root.taskPinTargetIndex ? root.dockCellWidth : 0
|
||||||
|
|
||||||
|
x: (isLocationBottom ? root.favouriteBaseX(delegate.index) : (parent.width - width) / 2) + dragVisualShift + taskPinVisualShift
|
||||||
y: isLocationBottom ? (parent.height - height) / 2 : parent.height / 2 - centerPosition - root.dockCellHeight
|
y: isLocationBottom ? (parent.height - height) / 2 : parent.height / 2 - centerPosition - root.dockCellHeight
|
||||||
|
|
||||||
implicitWidth: root.dockCellWidth
|
implicitWidth: root.dockCellWidth
|
||||||
|
|
@ -743,6 +804,17 @@ MouseArea {
|
||||||
opacity: 0.4
|
opacity: 0.4
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PlaceholderDelegate {
|
||||||
|
id: taskPinPlaceholder
|
||||||
|
visible: root.taskPinCanDrop
|
||||||
|
folio: root.folio
|
||||||
|
width: root.dockCellWidth
|
||||||
|
height: root.dockCellHeight
|
||||||
|
x: root.favouriteBaseX(root.taskPinTargetIndex)
|
||||||
|
y: (parent.height - height) / 2
|
||||||
|
z: 1
|
||||||
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
id: taskRepeater
|
id: taskRepeater
|
||||||
model: root.convergenceMode ? tasksModel : null
|
model: root.convergenceMode ? tasksModel : null
|
||||||
|
|
@ -754,6 +826,7 @@ MouseArea {
|
||||||
required property var model
|
required property var model
|
||||||
|
|
||||||
readonly property bool isLocationBottom: folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Bottom
|
readonly property bool isLocationBottom: folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Bottom
|
||||||
|
readonly property string taskStorageId: root.runningTaskStorageId(taskDelegate.model)
|
||||||
|
|
||||||
// Position after all favourites
|
// Position after all favourites
|
||||||
property double fromCenterValue: (repeater.count + taskDelegate.index) - (root.totalItemCount / 2)
|
property double fromCenterValue: (repeater.count + taskDelegate.index) - (root.totalItemCount / 2)
|
||||||
|
|
@ -763,8 +836,9 @@ MouseArea {
|
||||||
|
|
||||||
readonly property int centerPosition: (isLocationBottom ? root.dockCellWidth : root.dockCellHeight) * fromCenterValue
|
readonly property int centerPosition: (isLocationBottom ? root.dockCellWidth : root.dockCellHeight) * fromCenterValue
|
||||||
|
|
||||||
x: isLocationBottom ? centerPosition + root.dockCenterX + root.spacerWidth / 2 : (parent.width - width) / 2
|
x: isLocationBottom ? root.taskBaseX(taskDelegate.index) + (root.taskPinDragIndex === taskDelegate.index ? root.taskPinDragOffset : 0) : (parent.width - width) / 2
|
||||||
y: isLocationBottom ? (parent.height - height) / 2 : parent.height / 2 - centerPosition - root.dockCellHeight
|
y: isLocationBottom ? (parent.height - height) / 2 : parent.height / 2 - centerPosition - root.dockCellHeight
|
||||||
|
z: root.taskPinDragIndex === taskDelegate.index ? 2 : 0
|
||||||
|
|
||||||
implicitWidth: root.dockCellWidth
|
implicitWidth: root.dockCellWidth
|
||||||
implicitHeight: root.dockCellHeight
|
implicitHeight: root.dockCellHeight
|
||||||
|
|
@ -789,6 +863,45 @@ MouseArea {
|
||||||
active: taskMouseArea.containsMouse
|
active: taskMouseArea.containsMouse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DragHandler {
|
||||||
|
id: taskDragHandler
|
||||||
|
target: null
|
||||||
|
xAxis.enabled: true
|
||||||
|
yAxis.enabled: false
|
||||||
|
enabled: root.convergenceMode && taskDelegate.isLocationBottom && !folio.FolioSettings.lockLayout && taskDelegate.taskStorageId !== "" && !folio.FavouritesModel.containsApplication(taskDelegate.taskStorageId)
|
||||||
|
|
||||||
|
onActiveChanged: {
|
||||||
|
if (active) {
|
||||||
|
thumbnailPopup.close()
|
||||||
|
thumbnailShowTimer.stop()
|
||||||
|
thumbnailHideTimer.stop()
|
||||||
|
root.hoveredTaskIndex = -1
|
||||||
|
root.taskPinDragIndex = taskDelegate.index
|
||||||
|
root.taskPinDragOffset = 0
|
||||||
|
root.taskPinTargetIndex = -1
|
||||||
|
root.taskPinStorageId = taskDelegate.taskStorageId
|
||||||
|
} else if (root.taskPinDragIndex === taskDelegate.index) {
|
||||||
|
if (root.taskPinCanDrop) {
|
||||||
|
folio.FavouritesModel.addApplicationAt(root.taskPinTargetIndex, root.taskPinStorageId)
|
||||||
|
}
|
||||||
|
root.clearTaskPinDrag()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onTranslationChanged: {
|
||||||
|
if (root.taskPinDragIndex === taskDelegate.index) {
|
||||||
|
root.taskPinDragOffset = translation.x
|
||||||
|
root.updateTaskPinTarget()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onCanceled: {
|
||||||
|
if (root.taskPinDragIndex === taskDelegate.index) {
|
||||||
|
root.clearTaskPinDrag()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Window indicator dots (one per sibling window of the same app)
|
// Window indicator dots (one per sibling window of the same app)
|
||||||
Row {
|
Row {
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
|
|
@ -873,20 +986,13 @@ MouseArea {
|
||||||
id: taskContextMenu
|
id: taskContextMenu
|
||||||
popupType: T.Popup.Window
|
popupType: T.Popup.Window
|
||||||
|
|
||||||
property string taskStorageId: {
|
|
||||||
var id = taskDelegate.model.AppId || ""
|
|
||||||
if (id && !id.endsWith(".desktop"))
|
|
||||||
id += ".desktop"
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
PC3.MenuItem {
|
PC3.MenuItem {
|
||||||
icon.name: "window-pin"
|
icon.name: "window-pin"
|
||||||
text: i18n("Pin to Dock")
|
text: i18n("Pin to Dock")
|
||||||
// repeater.count dependency forces re-evaluation when favourites change
|
// repeater.count dependency forces re-evaluation when favourites change
|
||||||
visible: taskContextMenu.taskStorageId !== "" && repeater.count >= 0 && !folio.FavouritesModel.containsApplication(taskContextMenu.taskStorageId)
|
visible: taskDelegate.taskStorageId !== "" && repeater.count >= 0 && !folio.FavouritesModel.containsApplication(taskDelegate.taskStorageId)
|
||||||
enabled: !folio.FolioSettings.lockLayout
|
enabled: !folio.FolioSettings.lockLayout
|
||||||
onClicked: folio.FavouritesModel.addApplication(taskContextMenu.taskStorageId)
|
onClicked: folio.FavouritesModel.addApplication(taskDelegate.taskStorageId)
|
||||||
}
|
}
|
||||||
PC3.MenuItem {
|
PC3.MenuItem {
|
||||||
icon.name: taskDelegate.model.IsMinimized ? "window-restore" : "window-minimize"
|
icon.name: taskDelegate.model.IsMinimized ? "window-restore" : "window-minimize"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue