mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23:09 +00:00
Group dock icons by application in convergence mode
Switch TasksModel from GroupDisabled to GroupApplications so multiple windows of the same app share a single dock icon. Replace the per-task windowCountForTask() helper with the built-in WinIdList role. The thumbnail popup now shows a horizontal row of per-window previews. Clicking a grouped icon toggles the popup instead of activating a single window. "Close All" replaces the close action for multi-window groups.
This commit is contained in:
parent
6fd5a59a43
commit
86b34878c7
1 changed files with 125 additions and 71 deletions
|
|
@ -129,19 +129,7 @@ MouseArea {
|
||||||
filterHidden: false
|
filterHidden: false
|
||||||
virtualDesktop: virtualDesktopInfo.currentDesktop
|
virtualDesktop: virtualDesktopInfo.currentDesktop
|
||||||
activity: activityInfo.currentActivity
|
activity: activityInfo.currentActivity
|
||||||
groupMode: TaskManager.TasksModel.GroupDisabled
|
groupMode: TaskManager.TasksModel.GroupApplications
|
||||||
}
|
|
||||||
|
|
||||||
// Count how many windows share the same AppId as the task at the given index
|
|
||||||
function windowCountForTask(taskIndex) {
|
|
||||||
var appId = tasksModel.data(tasksModel.makeModelIndex(taskIndex), TaskManager.AbstractTasksModel.AppId)
|
|
||||||
if (!appId) return 1
|
|
||||||
var count = 0
|
|
||||||
for (var i = 0; i < tasksModel.rowCount(); i++) {
|
|
||||||
if (tasksModel.data(tasksModel.makeModelIndex(i), TaskManager.AbstractTasksModel.AppId) === appId)
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
return count
|
|
||||||
}
|
}
|
||||||
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
|
@ -480,8 +468,9 @@ MouseArea {
|
||||||
id: thumbnailPopup
|
id: thumbnailPopup
|
||||||
|
|
||||||
property var targetDelegate: null
|
property var targetDelegate: null
|
||||||
property string windowTitle: ""
|
property int taskIndex: -1
|
||||||
property string windowUuid: ""
|
property var windowIds: []
|
||||||
|
property bool isGroup: false
|
||||||
property bool popupHovered: false
|
property bool popupHovered: false
|
||||||
|
|
||||||
function open() { visible = true }
|
function open() { visible = true }
|
||||||
|
|
@ -490,7 +479,15 @@ MouseArea {
|
||||||
|
|
||||||
flags: Qt.ToolTip | Qt.FramelessWindowHint | Qt.WindowDoesNotAcceptFocus
|
flags: Qt.ToolTip | Qt.FramelessWindowHint | Qt.WindowDoesNotAcceptFocus
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
width: Kirigami.Units.gridUnit * 16
|
|
||||||
|
readonly property real thumbWidth: windowIds.length <= 1
|
||||||
|
? Kirigami.Units.gridUnit * 16
|
||||||
|
: Kirigami.Units.gridUnit * 12
|
||||||
|
|
||||||
|
width: Math.max(Kirigami.Units.gridUnit * 8,
|
||||||
|
windowIds.length * thumbWidth
|
||||||
|
+ Math.max(0, windowIds.length - 1) * Kirigami.Units.smallSpacing
|
||||||
|
+ 2 * Kirigami.Units.smallSpacing)
|
||||||
height: popupContent.implicitHeight + 2 * Kirigami.Units.smallSpacing
|
height: popupContent.implicitHeight + 2 * Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
// Position above the hovered dock icon, in global coordinates
|
// Position above the hovered dock icon, in global coordinates
|
||||||
|
|
@ -507,8 +504,10 @@ MouseArea {
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
windowUuid = ""
|
windowIds = []
|
||||||
targetDelegate = null
|
targetDelegate = null
|
||||||
|
taskIndex = -1
|
||||||
|
isGroup = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -522,66 +521,99 @@ MouseArea {
|
||||||
border.width: 1
|
border.width: 1
|
||||||
radius: Kirigami.Units.cornerRadius
|
radius: Kirigami.Units.cornerRadius
|
||||||
|
|
||||||
MouseArea {
|
// HoverHandler for popup-level hover tracking (does not
|
||||||
id: popupHoverArea
|
// consume mouse events, so clicks still reach delegates).
|
||||||
anchors.fill: parent
|
HoverHandler {
|
||||||
hoverEnabled: true
|
id: popupHoverHandler
|
||||||
|
onHoveredChanged: {
|
||||||
onContainsMouseChanged: {
|
thumbnailPopup.popupHovered = hovered
|
||||||
thumbnailPopup.popupHovered = containsMouse
|
if (hovered) {
|
||||||
if (containsMouse) {
|
|
||||||
thumbnailHideTimer.stop()
|
thumbnailHideTimer.stop()
|
||||||
} else if (root.hoveredTaskIndex < 0) {
|
} else if (root.hoveredTaskIndex < 0) {
|
||||||
thumbnailHideTimer.restart()
|
thumbnailHideTimer.restart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onClicked: {
|
Row {
|
||||||
if (thumbnailPopup.targetDelegate) {
|
id: popupContent
|
||||||
tasksModel.requestActivate(
|
anchors.fill: parent
|
||||||
tasksModel.makeModelIndex(thumbnailPopup.targetDelegate.index))
|
anchors.margins: Kirigami.Units.smallSpacing
|
||||||
thumbnailPopup.close()
|
spacing: Kirigami.Units.smallSpacing
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
Repeater {
|
||||||
id: popupContent
|
model: thumbnailPopup.windowIds.length
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Kirigami.Units.smallSpacing
|
|
||||||
spacing: Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
Item {
|
delegate: MouseArea {
|
||||||
width: parent.width
|
id: thumbEntry
|
||||||
height: width * 9 / 16
|
width: thumbnailPopup.thumbWidth
|
||||||
|
height: thumbColumn.implicitHeight
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
Loader {
|
readonly property string childUuid: thumbnailPopup.windowIds[index] || ""
|
||||||
id: pipeWireLoader
|
readonly property string childTitle: {
|
||||||
active: thumbnailPopup.visible
|
if (!thumbnailPopup.isGroup)
|
||||||
&& thumbnailPopup.windowUuid !== ""
|
return tasksModel.data(tasksModel.makeModelIndex(thumbnailPopup.taskIndex), 0) || ""
|
||||||
|
return tasksModel.data(tasksModel.makeModelIndex(thumbnailPopup.taskIndex, index), 0) || ""
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
var idx = thumbnailPopup.isGroup
|
||||||
|
? tasksModel.makeModelIndex(thumbnailPopup.taskIndex, index)
|
||||||
|
: tasksModel.makeModelIndex(thumbnailPopup.taskIndex)
|
||||||
|
tasksModel.requestActivate(idx)
|
||||||
|
thumbnailPopup.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
sourceComponent: PipeWireThumbnail {
|
radius: Kirigami.Units.cornerRadius
|
||||||
windowUuid: thumbnailPopup.windowUuid
|
color: thumbEntry.containsMouse
|
||||||
|
? Qt.rgba(Kirigami.Theme.highlightColor.r,
|
||||||
|
Kirigami.Theme.highlightColor.g,
|
||||||
|
Kirigami.Theme.highlightColor.b, 0.15)
|
||||||
|
: "transparent"
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: thumbColumn
|
||||||
|
width: parent.width
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: width * 9 / 16
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: thumbPipeWireLoader
|
||||||
|
active: thumbnailPopup.visible
|
||||||
|
&& thumbEntry.childUuid !== ""
|
||||||
|
anchors.fill: parent
|
||||||
|
sourceComponent: PipeWireThumbnail {
|
||||||
|
windowUuid: thumbEntry.childUuid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Icon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: Kirigami.Units.iconSizes.huge
|
||||||
|
height: width
|
||||||
|
source: thumbnailPopup.targetDelegate
|
||||||
|
? thumbnailPopup.targetDelegate.model.decoration
|
||||||
|
: ""
|
||||||
|
visible: !thumbPipeWireLoader.item
|
||||||
|
|| !thumbPipeWireLoader.item.hasThumbnail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PC3.Label {
|
||||||
|
width: parent.width
|
||||||
|
text: thumbEntry.childTitle
|
||||||
|
elide: Text.ElideRight
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
maximumLineCount: 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Kirigami.Icon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: Kirigami.Units.iconSizes.huge
|
|
||||||
height: width
|
|
||||||
source: thumbnailPopup.targetDelegate
|
|
||||||
? thumbnailPopup.targetDelegate.model.decoration
|
|
||||||
: ""
|
|
||||||
visible: !pipeWireLoader.item
|
|
||||||
|| !pipeWireLoader.item.hasThumbnail
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PC3.Label {
|
|
||||||
width: parent.width
|
|
||||||
text: thumbnailPopup.windowTitle
|
|
||||||
elide: Text.ElideRight
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
maximumLineCount: 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -642,7 +674,10 @@ MouseArea {
|
||||||
spacing: Kirigami.Units.smallSpacing / 2
|
spacing: Kirigami.Units.smallSpacing / 2
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: root.windowCountForTask(taskDelegate.index)
|
model: {
|
||||||
|
var ids = taskDelegate.model.WinIdList
|
||||||
|
return ids ? ids.length : 1
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: Kirigami.Units.smallSpacing * 1.5
|
width: Kirigami.Units.smallSpacing * 1.5
|
||||||
|
|
@ -666,17 +701,32 @@ MouseArea {
|
||||||
thumbnailShowTimer.stop()
|
thumbnailShowTimer.stop()
|
||||||
taskContextMenu.open();
|
taskContextMenu.open();
|
||||||
} else {
|
} else {
|
||||||
thumbnailPopup.close()
|
var winIds = taskDelegate.model.WinIdList
|
||||||
tasksModel.requestActivate(tasksModel.makeModelIndex(taskDelegate.index));
|
if (winIds && winIds.length > 1) {
|
||||||
|
// Multiple windows: toggle thumbnail popup
|
||||||
|
if (thumbnailPopup.opened && thumbnailPopup.taskIndex === taskDelegate.index) {
|
||||||
|
thumbnailPopup.close()
|
||||||
|
} else {
|
||||||
|
thumbnailPopup.targetDelegate = taskDelegate
|
||||||
|
thumbnailPopup.taskIndex = taskDelegate.index
|
||||||
|
thumbnailPopup.windowIds = winIds
|
||||||
|
thumbnailPopup.isGroup = taskDelegate.model.IsGroupParent === true
|
||||||
|
thumbnailPopup.open()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thumbnailPopup.close()
|
||||||
|
tasksModel.requestActivate(tasksModel.makeModelIndex(taskDelegate.index));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onContainsMouseChanged: {
|
onContainsMouseChanged: {
|
||||||
if (containsMouse) {
|
if (containsMouse) {
|
||||||
thumbnailHideTimer.stop()
|
thumbnailHideTimer.stop()
|
||||||
thumbnailPopup.targetDelegate = taskDelegate
|
thumbnailPopup.targetDelegate = taskDelegate
|
||||||
thumbnailPopup.windowTitle = taskDelegate.model.display || ""
|
thumbnailPopup.taskIndex = taskDelegate.index
|
||||||
var winIds = taskDelegate.model.WinIdList
|
var winIds = taskDelegate.model.WinIdList
|
||||||
thumbnailPopup.windowUuid = (winIds && winIds.length > 0) ? winIds[0] : ""
|
thumbnailPopup.windowIds = winIds ? winIds : []
|
||||||
|
thumbnailPopup.isGroup = taskDelegate.model.IsGroupParent === true
|
||||||
root.hoveredTaskIndex = taskDelegate.index
|
root.hoveredTaskIndex = taskDelegate.index
|
||||||
if (!thumbnailPopup.opened) {
|
if (!thumbnailPopup.opened) {
|
||||||
thumbnailShowTimer.restart()
|
thumbnailShowTimer.restart()
|
||||||
|
|
@ -717,11 +767,15 @@ MouseArea {
|
||||||
PC3.MenuItem {
|
PC3.MenuItem {
|
||||||
icon.name: taskDelegate.model.IsMaximized ? "window-restore" : "window-maximize"
|
icon.name: taskDelegate.model.IsMaximized ? "window-restore" : "window-maximize"
|
||||||
text: taskDelegate.model.IsMaximized ? i18n("Restore") : i18n("Maximize")
|
text: taskDelegate.model.IsMaximized ? i18n("Restore") : i18n("Maximize")
|
||||||
|
visible: taskDelegate.model.IsGroupParent !== true
|
||||||
onClicked: tasksModel.requestToggleMaximized(tasksModel.makeModelIndex(taskDelegate.index))
|
onClicked: tasksModel.requestToggleMaximized(tasksModel.makeModelIndex(taskDelegate.index))
|
||||||
}
|
}
|
||||||
PC3.MenuItem {
|
PC3.MenuItem {
|
||||||
icon.name: "window-close"
|
icon.name: "window-close"
|
||||||
text: i18n("Close")
|
text: {
|
||||||
|
var ids = taskDelegate.model.WinIdList
|
||||||
|
return (ids && ids.length > 1) ? i18n("Close All") : i18n("Close")
|
||||||
|
}
|
||||||
onClicked: tasksModel.requestClose(tasksModel.makeModelIndex(taskDelegate.index))
|
onClicked: tasksModel.requestClose(tasksModel.makeModelIndex(taskDelegate.index))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue