diff options
Diffstat (limited to 'modules')
| -rw-r--r-- | modules/areapicker/AreaPicker.qml | 70 | ||||
| -rw-r--r-- | modules/areapicker/Picker.qml | 254 |
2 files changed, 324 insertions, 0 deletions
diff --git a/modules/areapicker/AreaPicker.qml b/modules/areapicker/AreaPicker.qml new file mode 100644 index 0000000..547810b --- /dev/null +++ b/modules/areapicker/AreaPicker.qml @@ -0,0 +1,70 @@ +import "root:/widgets" +import Quickshell +import Quickshell.Wayland +import Quickshell.Io + +Scope { + LazyLoader { + id: root + + property bool freeze + + Variants { + model: Quickshell.screens + + StyledWindow { + id: win + + required property ShellScreen modelData + + screen: modelData + name: "area-picker" + WlrLayershell.exclusionMode: ExclusionMode.Ignore + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + + anchors.top: true + anchors.bottom: true + anchors.left: true + anchors.right: true + + Picker { + loader: root + screen: win.modelData + } + } + } + } + + IpcHandler { + target: "picker" + + function open(): void { + root.freeze = false; + root.activeAsync = true; + } + + function openFreeze(): void { + root.freeze = true; + root.activeAsync = true; + } + } + + CustomShortcut { + name: "screenshot" + description: "Open screenshot tool" + onPressed: { + root.freeze = false; + root.activeAsync = true; + } + } + + CustomShortcut { + name: "screenshotFreeze" + description: "Open screenshot tool (freeze mode)" + onPressed: { + root.freeze = true; + root.activeAsync = true; + } + } +} diff --git a/modules/areapicker/Picker.qml b/modules/areapicker/Picker.qml new file mode 100644 index 0000000..2125e23 --- /dev/null +++ b/modules/areapicker/Picker.qml @@ -0,0 +1,254 @@ +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 real rounding + + 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.clients.filter(c => c.workspace.id === Hyprland.activeWsId) + + function checkClientRects(x: real, y: real): void { + for (const c of clients) { + if (c.x <= x && c.y <= y && c.x + c.width >= x && c.y + c.height >= y) { + sx = c.x; + sy = c.y; + ex = c.x + c.width; + ey = c.y + c.height; + break; + } + } + } + + anchors.fill: parent + opacity: 0 + hoverEnabled: true + + 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 '${Math.ceil(rsx)},${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) { + sx = ssx; + sy = ssy; + ex = x; + ey = y; + } else { + checkClientRects(x, y); + } + } + + focus: true + Keys.onEscapePressed: closeAnim.start() + + SequentialAnimation { + id: closeAnim + + 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.rounding + 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.rounding > 0 ? root.rounding + root.borderWidth : 0 + border.width: root.borderWidth + border.color: Colours.palette.m3primary + + x: selectionRect.x - root.borderWidth + y: selectionRect.y - root.borderWidth + implicitWidth: selectionRect.implicitWidth + root.borderWidth * 2 + implicitHeight: selectionRect.implicitHeight + root.borderWidth * 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 + } +} |