Files
z-bar-qt/Helpers/Picker.qml

402 lines
9.5 KiB
QML
Executable File

pragma ComponentBehavior: Bound
import ZShell
import Quickshell
import Quickshell.Wayland
import QtQuick
import QtQuick.Effects
import qs.Components
import ZShell.Config
import qs.Helpers.Picker
import qs.Paths
import qs.Services
Item {
id: root
property list<var> clients: {
const mon = Hypr.monitorFor(screen);
if (!mon)
return [];
const special = mon.lastIpcObject.specialWorkspace;
const wsId = special.name ? special.id : mon.activeWorkspace.id;
return Hypr.toplevels.values.filter(c => c.workspace?.id === wsId).sort((a, b) => {
const ac = a.lastIpcObject;
const bc = b.lastIpcObject;
return (bc.pinned - ac.pinned) || ((bc.fullscreen !== 0) - (ac.fullscreen !== 0)) || (bc.floating - ac.floating);
});
}
readonly property int cornerRadius: Hypr.options.decoration.rounding
property real ex: screen.width
property real ey: screen.height
property color inkColor: "#ff3b30"
required property LazyLoader loader
property string mode: "select"
property bool onClient
property real realBorderWidth: 2
property real realRounding: onClient ? (Hypr.options.decoration.rounding ?? 0) : 0
property real rsx: Math.min(sx, ex)
property real rsy: Math.min(sy, ey)
readonly property real scaleRatio: Hypr.monitorFor(screen).scale
required property ShellScreen screen
property real sh: Math.abs(sy - ey)
readonly property color shadowColor: Hypr.options.decoration.shadow.color
readonly property bool shadowEnabled: Hypr.options.decoration.shadow.enabled
readonly property var shadowOffset: Hypr.options.decoration.shadow.offset
readonly property int shadowRange: Hypr.options.decoration.shadow.range
readonly property int shadowRenderPower: Hypr.options.decoration.shadow.render_power
property real ssx
property real ssy
property real sw: Math.abs(sx - ex)
property real sx: 0
property real sy: 0
property string tool: "move"
function cancel(): void {
annotations.clearAll();
closeAnim.start();
}
function checkClientRects(x: real, y: real): void {
for (const client of clients) {
if (!client)
continue;
let {
at: [cx, cy],
size: [cw, ch]
} = client.lastIpcObject;
cx -= screen.x;
cy -= screen.y;
if (cx <= x && cy <= y && cx + cw >= x && cy + ch >= y) {
onClient = true;
sx = cx;
sy = cy;
ex = cx + cw;
ey = cy + ch;
break;
}
}
}
function enterEditMode(): void {
mode = "edit";
tool = "move";
}
function save(copyToClipboard: bool): void {
const rounding = root.cornerRadius > 0;
const shadow_blur = root.shadowRange / root.shadowRenderPower;
const r = Math.floor(root.shadowColor.r * 256);
const g = Math.floor(root.shadowColor.g * 256);
const b = Math.floor(root.shadowColor.b * 256);
const a = Math.floor(root.shadowColor.a * 256);
const tmpfile = Qt.resolvedUrl(`/tmp/zshell-picker-${Quickshell.processId}-${Date.now()}.png`);
const args = Config.screenshot.mode === "auto" ? ["--rounding", `${rounding}`, "--radius", root.cornerRadius, "--shadow", root.shadowEnabled, "--shadow-blur", `${shadow_blur}`, "--shadow-color", `${r},${g},${b},${a}`, "--shadow-offset-x", root.shadowOffset[0], "--shadow-offset-y", root.shadowOffset[1]] : [];
const postCmd = Config.screenshot.enable_pp ? ["zshell-img-tools", "--scale", root.scaleRatio, ...args, "--image"] : null;
overlay.visible = border.visible = false;
handles.visible = false;
toolbar.visible = false;
ZShellIo.saveItem(captureLayer, tmpfile, Qt.rect(Math.ceil(rsx), Math.ceil(rsy), Math.floor(sw), Math.floor(sh)), path => {
const finalize = finalPath => {
if (copyToClipboard) {
Quickshell.execDetached(["sh", "-c", `wl-copy --type image/png < '${finalPath.replace(/'/g, `'\\''`)}'`]);
}
if (!copyToClipboard || Config.screenshot.saveOnCopy) {
const dest = `${Paths.screenshots}/zshell-${Date.now()}.png`;
Quickshell.execDetached(["sh", "-c", `mkdir -p "$(dirname '${dest}')" && cp '${path.replace(/'/g, `'\\''`)}' '${dest}'`]);
}
};
if (postCmd) {
Quickshell.execDetached(postCmd.concat([path]));
finalize(path);
} else {
finalize(path);
}
});
closeAnim.start();
}
anchors.fill: parent
focus: true
opacity: 0
Behavior on rsx {
enabled: !selectArea.pressed && root.mode === "select"
ExAnim {}
}
Behavior on rsy {
enabled: !selectArea.pressed && root.mode === "select"
ExAnim {}
}
Behavior on sh {
enabled: !selectArea.pressed && root.mode === "select"
ExAnim {}
}
Behavior on sw {
enabled: !selectArea.pressed && root.mode === "select"
ExAnim {}
}
Behavior on opacity {
Anim {
duration: 300
}
}
Component.onCompleted: {
Hypr.extras.refreshOptions();
screencopy.active = true;
opacity = 1;
const c = clients[0];
if (c) {
const cx = c.lastIpcObject.at[0] - screen.x;
const cy = c.lastIpcObject.at[1] - screen.y;
onClient = true;
sx = cx;
sy = cy;
ex = cx + c.lastIpcObject.size[0];
ey = cy + c.lastIpcObject.size[1];
} else {
sx = screen.width / 2 - 100;
sy = screen.height / 2 - 100;
ex = screen.width / 2 + 100;
ey = screen.height / 2 + 100;
}
}
Keys.onEscapePressed: root.mode === "edit" ? cancel() : closeAnim.start()
Keys.onPressed: e => {
if ((e.key == Qt.Key_C) && (e.modifiers & Qt.ControlModifier)) {
save(true);
e.accepted = true;
} else if ((e.key == Qt.Key_S) && (e.modifiers & Qt.ControlModifier)) {
save(false);
e.accepted = true;
}
}
onClientsChanged: {
if (root.mode === "select")
checkClientRects(selectArea.mouseX, selectArea.mouseY);
}
MouseArea {
id: selectArea
anchors.fill: parent
cursorShape: Qt.CrossCursor
enabled: root.mode === "select"
hoverEnabled: true
visible: root.mode === "select"
onPositionChanged: event => {
const x = event.x;
const y = event.y;
if (pressed) {
root.onClient = false;
root.sx = root.ssx;
root.sy = root.ssy;
root.ex = x;
root.ey = y;
} else {
root.checkClientRects(x, y);
}
}
onPressed: event => {
root.ssx = event.x;
root.ssy = event.y;
}
onReleased: root.enterEditMode()
}
SequentialAnimation {
id: closeAnim
PropertyAction {
property: "closing"
target: root.loader
value: true
}
ParallelAnimation {
Anim {
duration: 300
property: "opacity"
target: root
to: 0
}
ExAnim {
properties: "rsx,rsy"
target: root
to: 0
}
ExAnim {
property: "sw"
target: root
to: root.screen.width
}
ExAnim {
property: "sh"
target: root
to: root.screen.height
}
}
PropertyAction {
property: "activeAsync"
target: root.loader
value: false
}
}
Item {
id: captureLayer
anchors.fill: parent
Loader {
id: screencopy
active: false
anchors.fill: parent
asynchronous: true
sourceComponent: ScreencopyView {
captureSource: root.screen
paintCursor: false
}
}
AnnotationCanvas {
id: annotations
anchors.fill: parent
inkColor: root.inkColor
layer.enabled: true
tool: root.mode === "edit" && root.tool !== "move" ? root.tool : "none"
layer.effect: MultiEffect {
maskEnabled: true
maskSource: selectionWrapper
maskSpreadAtMin: 1
maskThresholdMin: 0.5
}
}
}
Rectangle {
id: overlay
anchors.fill: parent
color: "white"
layer.enabled: true
opacity: 0.3
radius: root.realRounding
visible: screencopy.item?.hasContent ?? false
layer.effect: MultiEffect {
maskEnabled: true
maskInverted: true
maskSource: selectionWrapper
maskSpreadAtMin: 1
maskThresholdMin: 0.5
}
}
Item {
id: selectionWrapper
anchors.fill: parent
layer.enabled: true
visible: false
Rectangle {
id: selectionRect
implicitHeight: root.sh
implicitWidth: root.sw
radius: root.realRounding
x: root.rsx
y: root.rsy
}
}
Rectangle {
id: border
border.color: Colors.palette.m3primary
border.width: root.realBorderWidth
color: "transparent"
implicitHeight: selectionRect.implicitHeight + root.realBorderWidth * 2
implicitWidth: selectionRect.implicitWidth + root.realBorderWidth * 2
radius: root.realRounding > 0 ? root.realRounding + root.realBorderWidth : 0
visible: screencopy.item?.hasContent ?? false
x: selectionRect.x - root.realBorderWidth
y: selectionRect.y - root.realBorderWidth
Behavior on border.color {
Anim {}
}
}
SelectionHandles {
id: handles
selH: root.sh
selW: root.sw
selX: root.rsx
selY: root.rsy
visible: root.mode === "edit" && root.tool === "move"
onBoundsChanged: (x, y, w, h) => {
root.sx = x;
root.sy = y;
root.ex = x + w;
root.ey = y + h;
}
onMoveBy: (dx, dy) => {
const w = root.sw, h = root.sh;
let nx = root.rsx + dx, ny = root.rsy + dy;
nx = Math.max(0, Math.min(nx, root.screen.width - w));
ny = Math.max(0, Math.min(ny, root.screen.height - h));
root.sx = nx;
root.sy = ny;
root.ex = nx + w;
root.ey = ny + h;
}
}
AnnotationToolbar {
id: toolbar
inkColor: root.inkColor
tool: root.tool
visible: root.mode === "edit"
x: Math.max(8, Math.min(root.rsx + root.sw / 2 - width / 2, root.screen.width - width - 8))
y: (root.rsy + root.sh + height + Tokens.padding.small <= root.screen.height) ? root.rsy + root.sh + Tokens.padding.small : (root.rsy - height - Tokens.padding.small <= 0) ? root.rsy + root.sh - height - Tokens.padding.small : root.rsy - height - Tokens.padding.small
onCancelRequested: root.cancel()
onClearRequested: annotations.clearAll()
onColorSelected: c => root.inkColor = c
onCopyRequested: root.save(true)
onSaveRequested: root.save(false)
onToolSelected: t => root.tool = t
onUndoRequested: annotations.undo()
}
component ExAnim: Anim {}
}