feat: screenshot tool
This commit is contained in:
parent
b0a0b6d322
commit
53cea2beb3
3 changed files with 326 additions and 0 deletions
70
modules/areapicker/AreaPicker.qml
Normal file
70
modules/areapicker/AreaPicker.qml
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
254
modules/areapicker/Picker.qml
Normal file
254
modules/areapicker/Picker.qml
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
import "modules"
|
||||
import "modules/drawers"
|
||||
import "modules/background"
|
||||
import "modules/areapicker"
|
||||
import Quickshell
|
||||
|
||||
ShellRoot {
|
||||
Background {}
|
||||
Drawers {}
|
||||
AreaPicker {}
|
||||
|
||||
Shortcuts {}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue