279 lines
6.6 KiB
QML
279 lines
6.6 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import "root:/widgets"
|
|
import "root:/services"
|
|
import "root:/config"
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import Quickshell.Wayland
|
|
import QtQuick
|
|
import QtQuick.Effects
|
|
|
|
MouseArea {
|
|
id: root
|
|
|
|
required property LazyLoader loader
|
|
required property ShellScreen screen
|
|
|
|
property int borderWidth
|
|
property int rounding
|
|
|
|
property bool onClient
|
|
|
|
property real realBorderWidth: onClient ? borderWidth : 2
|
|
property real realRounding: onClient ? rounding : 0
|
|
|
|
property real ssx
|
|
property real ssy
|
|
|
|
property real sx: 0
|
|
property real sy: 0
|
|
property real ex: screen.width
|
|
property real ey: screen.height
|
|
|
|
property real rsx: Math.min(sx, ex)
|
|
property real rsy: Math.min(sy, ey)
|
|
property real sw: Math.abs(sx - ex)
|
|
property real sh: Math.abs(sy - ey)
|
|
|
|
property list<var> clients: Hyprland.toplevels.values.filter(c => c.workspace.id === Hyprland.activeWsId).sort((a, b) => {
|
|
// Pinned first, then floating, then any other
|
|
if (a.lastIpcObject.pinned === b.lastIpcObject.pinned)
|
|
return a.lastIpcObject.floating === b.lastIpcObject.floating ? 0 : a.lastIpcObject.floating ? -1 : 1;
|
|
if (a.lastIpcObject.pinned)
|
|
return -1;
|
|
return 1;
|
|
})
|
|
|
|
function checkClientRects(x: real, y: real): void {
|
|
for (const client of clients) {
|
|
const {
|
|
at: [cx, cy],
|
|
size: [cw, ch]
|
|
} = client.lastIpcObject;
|
|
if (cx <= x && cy <= y && cx + cw >= x && cy + ch >= y) {
|
|
onClient = true;
|
|
sx = cx;
|
|
sy = cy;
|
|
ex = cx + cw;
|
|
ey = cy + ch;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
anchors.fill: parent
|
|
opacity: 0
|
|
hoverEnabled: true
|
|
cursorShape: Qt.CrossCursor
|
|
|
|
Component.onCompleted: {
|
|
// Break binding if frozen
|
|
if (loader.freeze)
|
|
clients = clients;
|
|
|
|
opacity = 1;
|
|
sx = screen.width / 2 - 100;
|
|
sy = screen.height / 2 - 100;
|
|
ex = screen.width / 2 + 100;
|
|
ey = screen.height / 2 + 100;
|
|
}
|
|
|
|
onPressed: event => {
|
|
ssx = event.x;
|
|
ssy = event.y;
|
|
}
|
|
|
|
onReleased: {
|
|
if (closeAnim.running)
|
|
return;
|
|
|
|
Quickshell.execDetached(["sh", "-c", `grim -l 0 -g '${screen.x + Math.ceil(rsx)},${screen.y + Math.ceil(rsy)} ${Math.floor(sw)}x${Math.floor(sh)}' - | swappy -f -`]);
|
|
closeAnim.start();
|
|
}
|
|
|
|
onPositionChanged: event => {
|
|
const x = event.x;
|
|
const y = event.y;
|
|
|
|
if (pressed) {
|
|
onClient = false;
|
|
sx = ssx;
|
|
sy = ssy;
|
|
ex = x;
|
|
ey = y;
|
|
} else {
|
|
checkClientRects(x, y);
|
|
}
|
|
}
|
|
|
|
focus: true
|
|
Keys.onEscapePressed: closeAnim.start()
|
|
|
|
SequentialAnimation {
|
|
id: closeAnim
|
|
|
|
PropertyAction {
|
|
target: root.loader
|
|
property: "closing"
|
|
value: true
|
|
}
|
|
ParallelAnimation {
|
|
Anim {
|
|
target: root
|
|
property: "opacity"
|
|
to: 0
|
|
duration: Appearance.anim.durations.large
|
|
}
|
|
Anim {
|
|
target: root
|
|
properties: "rsx,rsy"
|
|
to: 0
|
|
}
|
|
Anim {
|
|
target: root
|
|
property: "sw"
|
|
to: root.screen.width
|
|
}
|
|
Anim {
|
|
target: root
|
|
property: "sh"
|
|
to: root.screen.height
|
|
}
|
|
}
|
|
PropertyAction {
|
|
target: root.loader
|
|
property: "activeAsync"
|
|
value: false
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: Hyprland
|
|
|
|
function onActiveWsIdChanged(): void {
|
|
root.checkClientRects(root.mouseX, root.mouseY);
|
|
}
|
|
}
|
|
|
|
Process {
|
|
running: true
|
|
command: ["hyprctl", "-j", "getoption", "general:border_size"]
|
|
stdout: StdioCollector {
|
|
onStreamFinished: root.borderWidth = JSON.parse(text).int
|
|
}
|
|
}
|
|
|
|
Process {
|
|
running: true
|
|
command: ["hyprctl", "-j", "getoption", "decoration:rounding"]
|
|
stdout: StdioCollector {
|
|
onStreamFinished: root.rounding = JSON.parse(text).int
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
anchors.fill: parent
|
|
|
|
active: root.loader.freeze
|
|
asynchronous: true
|
|
|
|
sourceComponent: ScreencopyView {
|
|
captureSource: root.screen
|
|
}
|
|
}
|
|
|
|
StyledRect {
|
|
id: background
|
|
|
|
anchors.fill: parent
|
|
color: Colours.palette.m3secondaryContainer
|
|
visible: false
|
|
}
|
|
|
|
Item {
|
|
id: selectionWrapper
|
|
|
|
anchors.fill: parent
|
|
layer.enabled: true
|
|
visible: false
|
|
|
|
Rectangle {
|
|
id: selectionRect
|
|
|
|
radius: root.realRounding
|
|
x: root.rsx
|
|
y: root.rsy
|
|
implicitWidth: root.sw
|
|
implicitHeight: root.sh
|
|
}
|
|
}
|
|
|
|
MultiEffect {
|
|
anchors.fill: parent
|
|
source: background
|
|
maskSource: selectionWrapper
|
|
maskEnabled: true
|
|
maskInverted: true
|
|
maskSpreadAtMin: 1
|
|
maskThresholdMin: 0.5
|
|
opacity: 0.3
|
|
}
|
|
|
|
Rectangle {
|
|
color: "transparent"
|
|
radius: root.realRounding > 0 ? root.realRounding + root.realBorderWidth : 0
|
|
border.width: root.realBorderWidth
|
|
border.color: Colours.palette.m3primary
|
|
|
|
x: selectionRect.x - root.realBorderWidth
|
|
y: selectionRect.y - root.realBorderWidth
|
|
implicitWidth: selectionRect.implicitWidth + root.realBorderWidth * 2
|
|
implicitHeight: selectionRect.implicitHeight + root.realBorderWidth * 2
|
|
|
|
Behavior on border.color {
|
|
ColorAnimation {
|
|
duration: Appearance.anim.durations.normal
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Appearance.anim.curves.standard
|
|
}
|
|
}
|
|
}
|
|
|
|
Behavior on opacity {
|
|
Anim {
|
|
duration: Appearance.anim.durations.large
|
|
}
|
|
}
|
|
|
|
Behavior on rsx {
|
|
enabled: !root.pressed
|
|
|
|
Anim {}
|
|
}
|
|
|
|
Behavior on rsy {
|
|
enabled: !root.pressed
|
|
|
|
Anim {}
|
|
}
|
|
|
|
Behavior on sw {
|
|
enabled: !root.pressed
|
|
|
|
Anim {}
|
|
}
|
|
|
|
Behavior on sh {
|
|
enabled: !root.pressed
|
|
|
|
Anim {}
|
|
}
|
|
|
|
component Anim: NumberAnimation {
|
|
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
|
}
|
|
}
|