summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/areapicker/AreaPicker.qml70
-rw-r--r--modules/areapicker/Picker.qml254
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
+ }
+}