forked from Shinonome/dots-hyprland
overview: drag and drop to move windows
This commit is contained in:
@@ -24,8 +24,22 @@ Item {
|
|||||||
property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor.id)
|
property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor.id)
|
||||||
property real scale: ConfigOptions.overview.scale
|
property real scale: ConfigOptions.overview.scale
|
||||||
|
|
||||||
|
property real workspaceImplicitWidth: (monitor.width - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale
|
||||||
|
property real workspaceImplicitHeight: (monitor.height - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale
|
||||||
|
|
||||||
property real workspaceNumberMargin: 80
|
property real workspaceNumberMargin: 80
|
||||||
property real workspaceNumberSize: 80
|
property real workspaceNumberSize: 80
|
||||||
|
property int workspaceZ: 0
|
||||||
|
property int windowZ: 1
|
||||||
|
property int windowDraggingZ: 99999
|
||||||
|
property real workspaceSpacing: 5
|
||||||
|
|
||||||
|
property int draggingFromWorkspace: -1
|
||||||
|
property int draggingTargetWorkspace: -1
|
||||||
|
|
||||||
|
onDraggingFromWorkspaceChanged: {
|
||||||
|
console.log("draggingTargetWorkspace", draggingFromWorkspace)
|
||||||
|
}
|
||||||
|
|
||||||
implicitWidth: overviewBackground.implicitWidth + Appearance.sizes.elevationMargin * 2
|
implicitWidth: overviewBackground.implicitWidth + Appearance.sizes.elevationMargin * 2
|
||||||
implicitHeight: overviewBackground.implicitHeight + Appearance.sizes.elevationMargin * 2
|
implicitHeight: overviewBackground.implicitHeight + Appearance.sizes.elevationMargin * 2
|
||||||
@@ -43,21 +57,23 @@ Item {
|
|||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
implicitWidth: columnLayout.implicitWidth + 5 * 2
|
implicitWidth: workspaceColumnLayout.implicitWidth + 5 * 2
|
||||||
implicitHeight: columnLayout.implicitHeight + 5 * 2
|
implicitHeight: workspaceColumnLayout.implicitHeight + 5 * 2
|
||||||
color: Appearance.colors.colLayer0
|
color: Appearance.colors.colLayer0
|
||||||
radius: Appearance.rounding.screenRounding * root.scale + 5 * 2
|
radius: Appearance.rounding.screenRounding * root.scale + 5 * 2
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: columnLayout
|
id: workspaceColumnLayout
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: 5
|
|
||||||
|
|
||||||
|
z: root.workspaceZ
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: workspaceSpacing
|
||||||
Repeater {
|
Repeater {
|
||||||
model: ConfigOptions.overview.numOfRows
|
model: ConfigOptions.overview.numOfRows
|
||||||
delegate: RowLayout {
|
delegate: RowLayout {
|
||||||
id: row
|
id: row
|
||||||
property int rowIndex: index
|
property int rowIndex: index
|
||||||
|
spacing: workspaceSpacing
|
||||||
|
|
||||||
Repeater { // Workspace repeater
|
Repeater { // Workspace repeater
|
||||||
model: ConfigOptions.overview.numOfCols
|
model: ConfigOptions.overview.numOfCols
|
||||||
@@ -65,44 +81,127 @@ Item {
|
|||||||
id: workspace
|
id: workspace
|
||||||
property int colIndex: index
|
property int colIndex: index
|
||||||
property int workspaceValue: root.workspaceGroup * workspacesShown + rowIndex * ConfigOptions.overview.numOfCols + colIndex + 1
|
property int workspaceValue: root.workspaceGroup * workspacesShown + rowIndex * ConfigOptions.overview.numOfCols + colIndex + 1
|
||||||
|
property color defaultColor: Appearance.colors.colLayer1 // TODO: reconsider this color for a cleaner look
|
||||||
|
|
||||||
implicitWidth: (monitor.width - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale
|
implicitWidth: root.workspaceImplicitWidth
|
||||||
implicitHeight: (monitor.height - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale
|
implicitHeight: root.workspaceImplicitHeight
|
||||||
color: Appearance.colors.colLayer1 // TODO: reconsider this color for a cleaner look
|
color: defaultColor
|
||||||
radius: Appearance.rounding.screenRounding * root.scale
|
radius: Appearance.rounding.screenRounding * root.scale
|
||||||
|
border.width: 2
|
||||||
|
border.color: "transparent"
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: mouseArea
|
id: workspaceArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onClicked: (event) => {
|
acceptedButtons: Qt.LeftButton
|
||||||
closeOverview.running = true
|
onClicked: {
|
||||||
Hyprland.dispatch(`workspace ${workspace.workspaceValue}`)
|
if (root.draggingTargetWorkspace === -1) {
|
||||||
|
closeOverview.running = true
|
||||||
|
Hyprland.dispatch(`workspace ${workspaceValue}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
DropArea {
|
||||||
z: 9999
|
anchors.fill: parent
|
||||||
anchors.left: parent.left
|
onEntered: {
|
||||||
anchors.top: parent.top
|
root.draggingTargetWorkspace = workspaceValue
|
||||||
anchors.leftMargin: root.workspaceNumberMargin * root.scale
|
if (root.draggingFromWorkspace == root.draggingTargetWorkspace) return;
|
||||||
anchors.topMargin: root.workspaceNumberMargin * root.scale
|
border.color = Appearance.colors.colLayer2Hover
|
||||||
font.pixelSize: root.workspaceNumberSize * root.scale
|
workspace.color = Appearance.mix(defaultColor, Appearance.colors.colLayer1Hover, 0.1)
|
||||||
color: Appearance.colors.colSubtext
|
}
|
||||||
text: workspaceValue
|
onExited: {
|
||||||
|
border.color = "transparent"
|
||||||
|
workspace.color = defaultColor
|
||||||
|
if (root.draggingTargetWorkspace == workspaceValue) root.draggingTargetWorkspace = -1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Repeater { // Window repeater
|
}
|
||||||
model: windowAddresses.filter((address) => {
|
}
|
||||||
var win = windowByAddress[address]
|
}
|
||||||
return (win?.workspace?.id === workspace.workspaceValue)
|
}
|
||||||
})
|
}
|
||||||
delegate: OverviewWindow {
|
|
||||||
windowData: windowByAddress[modelData]
|
Item {
|
||||||
monitorData: root.monitorData
|
id: windowSpace
|
||||||
scale: root.scale
|
anchors.centerIn: parent
|
||||||
availableWorkspaceWidth: workspace.implicitWidth
|
implicitWidth: workspaceColumnLayout.implicitWidth
|
||||||
availableWorkspaceHeight: workspace.implicitHeight
|
implicitHeight: workspaceColumnLayout.implicitHeight
|
||||||
}
|
|
||||||
|
Repeater { // Window repeater
|
||||||
|
model: windowAddresses.filter((address) => {
|
||||||
|
var win = windowByAddress[address]
|
||||||
|
return (root.workspaceGroup * root.workspacesShown < win.workspace.id && win.workspace.id <= (root.workspaceGroup + 1) * root.workspacesShown)
|
||||||
|
})
|
||||||
|
delegate: OverviewWindow {
|
||||||
|
id: window
|
||||||
|
windowData: windowByAddress[modelData]
|
||||||
|
monitorData: root.monitorData
|
||||||
|
scale: root.scale
|
||||||
|
availableWorkspaceWidth: root.workspaceImplicitWidth
|
||||||
|
availableWorkspaceHeight: root.workspaceImplicitHeight
|
||||||
|
|
||||||
|
property bool atInitPosition: (initX == x && initY == y)
|
||||||
|
restrictToWorkspace: Drag.active || atInitPosition
|
||||||
|
|
||||||
|
property int workspaceColIndex: (windowData?.workspace.id - 1) % ConfigOptions.overview.numOfCols
|
||||||
|
property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / ConfigOptions.overview.numOfCols)
|
||||||
|
xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex
|
||||||
|
yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: updateWindowPosition
|
||||||
|
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay
|
||||||
|
repeat: false
|
||||||
|
running: false
|
||||||
|
onTriggered: {
|
||||||
|
window.x = Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0) + xOffset
|
||||||
|
window.y = Math.max((windowData?.at[1] - monitorData?.reserved[1]) * root.scale, 0) + yOffset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
z: atInitPosition ? root.windowZ : root.windowDraggingZ
|
||||||
|
Drag.hotSpot.x: targetWindowWidth / 2
|
||||||
|
Drag.hotSpot.y: targetWindowHeight / 2
|
||||||
|
MouseArea {
|
||||||
|
id: dragArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
onEntered: hovered = true
|
||||||
|
onExited: hovered = false
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.MiddleButton
|
||||||
|
drag.target: parent
|
||||||
|
onPressed: {
|
||||||
|
root.draggingFromWorkspace = windowData?.workspace.id
|
||||||
|
window.pressed = true
|
||||||
|
window.Drag.active = true
|
||||||
|
window.Drag.source = window
|
||||||
|
}
|
||||||
|
onReleased: {
|
||||||
|
const targetWorkspace = root.draggingTargetWorkspace
|
||||||
|
window.pressed = false
|
||||||
|
window.Drag.active = false
|
||||||
|
root.draggingFromWorkspace = -1
|
||||||
|
if (targetWorkspace !== -1 && targetWorkspace !== windowData?.workspace.id) {
|
||||||
|
Hyprland.dispatch(`movetoworkspacesilent ${targetWorkspace}, address:${window.windowData?.address}`)
|
||||||
|
updateWindowPosition.restart()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
window.x = window.initX
|
||||||
|
window.y = window.initY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onClicked: (event) => {
|
||||||
|
if (!windowData) return;
|
||||||
|
|
||||||
|
if (event.button === Qt.LeftButton) {
|
||||||
|
closeOverview.running = true
|
||||||
|
Hyprland.dispatch(`workspace ${windowData.workspace.id}`)
|
||||||
|
event.accepted = true
|
||||||
|
} else if (event.button === Qt.MiddleButton) {
|
||||||
|
Hyprland.dispatch(`closewindow address:${windowData.address}`)
|
||||||
|
event.accepted = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,6 +211,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DropShadow {
|
DropShadow {
|
||||||
|
z: -9999
|
||||||
anchors.fill: overviewBackground
|
anchors.fill: overviewBackground
|
||||||
horizontalOffset: 0
|
horizontalOffset: 0
|
||||||
verticalOffset: 2
|
verticalOffset: 2
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ Rectangle { // Window
|
|||||||
property var scale
|
property var scale
|
||||||
property var availableWorkspaceWidth
|
property var availableWorkspaceWidth
|
||||||
property var availableWorkspaceHeight
|
property var availableWorkspaceHeight
|
||||||
|
property bool restrictToWorkspace: true
|
||||||
|
property real initX: Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0) + xOffset
|
||||||
|
property real initY: Math.max((windowData?.at[1] - monitorData?.reserved[1]) * root.scale, 0) + yOffset
|
||||||
|
property real xOffset: 0
|
||||||
|
property real yOffset: 0
|
||||||
|
|
||||||
property var targetWindowWidth: windowData?.size[0] * scale
|
property var targetWindowWidth: windowData?.size[0] * scale
|
||||||
property var targetWindowHeight: windowData?.size[1] * scale
|
property var targetWindowHeight: windowData?.size[1] * scale
|
||||||
@@ -29,12 +34,11 @@ Rectangle { // Window
|
|||||||
property var iconToWindowRatioCompact: 0.6
|
property var iconToWindowRatioCompact: 0.6
|
||||||
property var iconPath: Quickshell.iconPath(Icons.noKnowledgeIconGuess(windowData?.class))
|
property var iconPath: Quickshell.iconPath(Icons.noKnowledgeIconGuess(windowData?.class))
|
||||||
property bool compactMode: Appearance.font.pixelSize.smaller * 4 > targetWindowHeight || Appearance.font.pixelSize.smaller * 4 > targetWindowWidth
|
property bool compactMode: Appearance.font.pixelSize.smaller * 4 > targetWindowHeight || Appearance.font.pixelSize.smaller * 4 > targetWindowWidth
|
||||||
|
|
||||||
z: 1
|
x: initX
|
||||||
x: Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0)
|
y: initY
|
||||||
y: Math.max((windowData?.at[1] - monitorData?.reserved[1]) * root.scale, 0)
|
width: Math.min(windowData?.size[0] * root.scale, (restrictToWorkspace ? windowData?.size[0] : availableWorkspaceWidth - x + xOffset))
|
||||||
width: Math.min(windowData?.size[0] * root.scale, availableWorkspaceWidth - x)
|
height: Math.min(windowData?.size[1] * root.scale, (restrictToWorkspace ? windowData?.size[1] : availableWorkspaceHeight - y + yOffset))
|
||||||
height: Math.min(windowData?.size[1] * root.scale, availableWorkspaceHeight - y)
|
|
||||||
|
|
||||||
radius: Appearance.rounding.windowRounding * root.scale
|
radius: Appearance.rounding.windowRounding * root.scale
|
||||||
color: pressed ? Appearance.colors.colLayer2Active : hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2
|
color: pressed ? Appearance.colors.colLayer2Active : hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2
|
||||||
@@ -72,29 +76,6 @@ Rectangle { // Window
|
|||||||
command: ["bash", "-c", "qs ipc call overview close &"] // Somehow has to by async to work?
|
command: ["bash", "-c", "qs ipc call overview close &"] // Somehow has to by async to work?
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: mouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onEntered: root.hovered = true
|
|
||||||
onExited: root.hovered = false
|
|
||||||
onPressed: root.pressed = true
|
|
||||||
onReleased: root.pressed = false
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton
|
|
||||||
onClicked: (event) => {
|
|
||||||
if (!windowData) return;
|
|
||||||
|
|
||||||
if (event.button === Qt.LeftButton) {
|
|
||||||
closeOverview.running = true
|
|
||||||
Hyprland.dispatch(`workspace ${windowData.workspace.id}`)
|
|
||||||
event.accepted = true
|
|
||||||
} else if (event.button === Qt.MiddleButton) {
|
|
||||||
Hyprland.dispatch(`closewindow address:${windowData.address}`)
|
|
||||||
event.accepted = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
@@ -116,7 +97,7 @@ Rectangle { // Window
|
|||||||
|
|
||||||
IconImage {
|
IconImage {
|
||||||
id: xwaylandIndicator
|
id: xwaylandIndicator
|
||||||
visible: ConfigOptions.overview.showXwaylandIndicator && windowData?.xwayland
|
visible: (ConfigOptions.overview.showXwaylandIndicator && windowData?.xwayland) ?? false
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
source: Quickshell.iconPath("xorg")
|
source: Quickshell.iconPath("xorg")
|
||||||
|
|||||||
@@ -215,6 +215,7 @@ Item { // Wrapper
|
|||||||
focus: root.panelWindow.visible || GlobalStates.overviewOpen
|
focus: root.panelWindow.visible || GlobalStates.overviewOpen
|
||||||
Layout.rightMargin: 15
|
Layout.rightMargin: 15
|
||||||
padding: 15
|
padding: 15
|
||||||
|
renderType: Text.NativeRendering
|
||||||
color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant
|
color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant
|
||||||
selectedTextColor: Appearance.m3colors.m3onPrimary
|
selectedTextColor: Appearance.m3colors.m3onPrimary
|
||||||
selectionColor: Appearance.m3colors.m3primary
|
selectionColor: Appearance.m3colors.m3primary
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ Scope {
|
|||||||
}
|
}
|
||||||
Process {
|
Process {
|
||||||
id: logout
|
id: logout
|
||||||
command: ["bash", "-c", "loginctl terminate-session $XDG_SESSION_ID"]
|
command: ["bash", "-c", "pkill Hyprland"] // loginctl terminate-session hangs SDDM
|
||||||
}
|
}
|
||||||
Process {
|
Process {
|
||||||
id: hibernate
|
id: hibernate
|
||||||
|
|||||||
@@ -273,6 +273,7 @@ Item {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
padding: 10
|
padding: 10
|
||||||
color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant
|
color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant
|
||||||
|
renderType: Text.NativeRendering
|
||||||
selectedTextColor: Appearance.m3colors.m3onPrimary
|
selectedTextColor: Appearance.m3colors.m3onPrimary
|
||||||
selectionColor: Appearance.m3colors.m3primary
|
selectionColor: Appearance.m3colors.m3primary
|
||||||
placeholderText: qsTr("Task description")
|
placeholderText: qsTr("Task description")
|
||||||
|
|||||||
@@ -43,11 +43,12 @@ Singleton {
|
|||||||
stdout: SplitParser {
|
stdout: SplitParser {
|
||||||
onRead: (data) => {
|
onRead: (data) => {
|
||||||
root.windowList = JSON.parse(data)
|
root.windowList = JSON.parse(data)
|
||||||
root.windowByAddress = {}
|
let tempWinByAddress = {}
|
||||||
for (var i = 0; i < root.windowList.length; ++i) {
|
for (var i = 0; i < root.windowList.length; ++i) {
|
||||||
var win = root.windowList[i]
|
var win = root.windowList[i]
|
||||||
root.windowByAddress[win.address] = win
|
tempWinByAddress[win.address] = win
|
||||||
}
|
}
|
||||||
|
root.windowByAddress = tempWinByAddress
|
||||||
root.addresses = root.windowList.map((win) => win.address)
|
root.addresses = root.windowList.map((win) => win.address)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user