diff --git a/ShellSettings.qml b/ShellSettings.qml index 7000be5..8ad1dfe 100644 --- a/ShellSettings.qml +++ b/ShellSettings.qml @@ -11,7 +11,7 @@ Singleton { FileView { path: `${Quickshell.env("XDG_DATA_HOME")}/quickshell/settings.json` watchChanges: true - // onFileChanged: reload() + onFileChanged: reload() onAdapterUpdated: writeAdapter() blockLoading: true @@ -22,6 +22,7 @@ Singleton { property int barHeight: 25 property string wallpaperUrl: Qt.resolvedUrl("root:resources/wallpapers/pixelart0.jpg") property string colorScheme: "scheme-fruit-salad" + property string screenshotPath: "/home/koss/Pictures" } property var colors: { diff --git a/bar/Bar.qml b/bar/Bar.qml index 142afab..519f750 100644 --- a/bar/Bar.qml +++ b/bar/Bar.qml @@ -78,18 +78,18 @@ PanelWindow { // bar: root // } - Text { - text: "home" - color: "white" - font.family: "Material Symbols Rounded" - renderType: Text.NativeRendering - textFormat: Text.PlainText - font.pointSize: 14 - - font.variableAxes: { - "FILL": 0 - } - } + // Text { + // text: "home" + // color: "white" + // font.family: "Material Symbols Rounded" + // renderType: Text.NativeRendering + // textFormat: Text.PlainText + // font.pointSize: 14 + // + // font.variableAxes: { + // "FILL": 0 + // } + // } BatteryIndicator { id: batteryIndicator diff --git a/screencapture/Controller.qml b/screencapture/Controller.qml new file mode 100644 index 0000000..d57d8c5 --- /dev/null +++ b/screencapture/Controller.qml @@ -0,0 +1,101 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import QtQuick +import ".." + +Singleton { + id: root + + property bool windowOpen: false + + IpcHandler { + target: "screencapture" + + function screenshot(): void { + root.windowOpen = true; + } + } + + // Just use this window to grab screen context + LazyLoader { + activeAsync: root.windowOpen + + PanelWindow { + id: focusedScreen + color: "transparent" + exclusionMode: ExclusionMode.Ignore + WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + WlrLayershell.namespace: "shell:screencapture" + + anchors { + top: true + bottom: true + left: true + right: true + } + + Item { + anchors.fill: parent + focus: true + Keys.onEscapePressed: root.windowOpen = false + + // to get a freeze frame for now + ScreencopyView { + id: screenView + captureSource: focusedScreen.screen + anchors.fill: parent + + SelectionRectangle { + id: selection + anchors.fill: parent + + property string position + property bool running: false + + onAreaSelected: selection => { + let screen = focusedScreen.screen; + const x = Math.floor(selection.x) + screen.x; + const y = Math.floor(selection.y) + screen.y; + const width = Math.floor(selection.width); + const height = Math.floor(selection.height); + position = `${x},${y} ${width}x${height}`; + + running = true; + } + + LazyLoader { + activeAsync: selection.running + + Process { + id: grim + running: true + + property var path: `${ShellSettings.settings.screenshotPath}/screenshot.png` + + command: ["grim", "-g", selection.position, path] + + + onRunningChanged: { + if (!running) { + root.windowOpen = false; + } + } + + stderr: SplitParser { + onRead: data => console.log(`line read: ${data}`) + } + } + } + } + } + } + } + } + + function init() { + } +} diff --git a/screencapture/Screenshot.qml b/screencapture/Screenshot.qml new file mode 100644 index 0000000..34f719a --- /dev/null +++ b/screencapture/Screenshot.qml @@ -0,0 +1,3 @@ +import QtQuick + +Image {} diff --git a/screencapture/SelectionCutout.qml b/screencapture/SelectionCutout.qml new file mode 100644 index 0000000..0303a46 --- /dev/null +++ b/screencapture/SelectionCutout.qml @@ -0,0 +1,152 @@ +import QtQuick +import ".." + +Canvas { + id: root + anchors.fill: parent + + property color overlayColor: "#80000000" + property color borderColor: ShellSettings.colors["primary"] + property real borderWidth: 3 + property real handleSize: 16 + property var screen + + property real centerX: width / 2 + property real centerY: height / 2 + property real minWidth: 400 + property real minHeight: 300 + + // rect that holds positional data for the selection + property rect selectionRect: Qt.rect(centerX - minWidth / 2, centerY - minHeight / 2, minWidth, minHeight) + + // handle positions + property point topLeftHandle: Qt.point(selectionRect.x, selectionRect.y) + property point topRightHandle: Qt.point(selectionRect.x + selectionRect.width, selectionRect.y) + property point bottomLeftHandle: Qt.point(selectionRect.x, selectionRect.y + selectionRect.height) + property point bottomRightHandle: Qt.point(selectionRect.x + selectionRect.width, selectionRect.y + selectionRect.height) + + // dragging state + property int activeHandle: -1 + property point dragStart: Qt.point(0, 0) + property rect initialRect: Qt.rect(0, 0, 0, 0) + + onPaint: { + var ctx = getContext("2d"); + ctx.clearRect(0, 0, width, height); + + // grey overlay + ctx.fillStyle = overlayColor; + ctx.fillRect(0, 0, width, height); + + // cut out the selection rectangle + ctx.globalCompositeOperation = "destination-out"; + ctx.fillRect(selectionRect.x, selectionRect.y, selectionRect.width, selectionRect.height); + ctx.globalCompositeOperation = "source-over"; + + // draw border + ctx.strokeStyle = borderColor; + ctx.lineWidth = borderWidth; + ctx.beginPath(); + ctx.moveTo(topLeftHandle.x, topLeftHandle.y); + ctx.lineTo(topRightHandle.x, topRightHandle.y); + ctx.lineTo(bottomRightHandle.x, bottomRightHandle.y); + ctx.lineTo(bottomLeftHandle.x, bottomLeftHandle.y); + ctx.closePath(); + ctx.stroke(); + + // draw handles + ctx.fillStyle = borderColor; + drawHandle(ctx, topLeftHandle); + drawHandle(ctx, topRightHandle); + drawHandle(ctx, bottomLeftHandle); + drawHandle(ctx, bottomRightHandle); + } + + function drawHandle(ctx, center) { + var radius = handleSize / 2; + ctx.beginPath(); + ctx.arc(center.x, center.y, radius, 0, 2 * Math.PI); + ctx.fill(); + } + + function getHandleAt(x, y) { + var halfSize = handleSize / 2; + var handles = [topLeftHandle, topRightHandle, bottomLeftHandle, bottomRightHandle]; + + for (var i = 0; i < handles.length; i++) { + var handle = handles[i]; + if (x >= handle.x - halfSize && x <= handle.x + halfSize && y >= handle.y - halfSize && y <= handle.y + halfSize) { + return i; + } + } + return -1; + } + + function constrainRect(rect) { + // Ensure minimum size + var width = Math.max(rect.width, minWidth); + var height = Math.max(rect.height, minHeight); + + // Ensure within canvas bounds + var x = Math.max(0, Math.min(rect.x, root.width - width)); + var y = Math.max(0, Math.min(rect.y, root.height - height)); + + return Qt.rect(x, y, width, height); + } + + MouseArea { + anchors.fill: parent + + onPressed: function (mouse) { + activeHandle = root.getHandleAt(mouse.x, mouse.y); + if (root.activeHandle >= 0) { + dragStart = Qt.point(mouse.x, mouse.y); + initialRect = root.selectionRect; + } + } + + // kinda stupid, should maybe bind a mouse area to each handle I don't know + onPositionChanged: function (mouse) { + if (root.activeHandle < 0) + return; + + var dx = mouse.x - root.dragStart.x; + var dy = mouse.y - root.dragStart.y; + var newRect; + + switch (root.activeHandle) { + // top left + case 0: + var newX = Math.max(0, Math.min(root.initialRect.x + dx, root.initialRect.x + root.initialRect.width - root.minWidth)); + var newY = Math.max(0, Math.min(root.initialRect.y + dy, root.initialRect.y + root.initialRect.height - minHeight)); + newRect = Qt.rect(newX, newY, root.initialRect.width - (newX - root.initialRect.x), root.initialRect.height - (newY - root.initialRect.y)); + break; + // top right + case 1: + var newY = Math.max(0, Math.min(root.initialRect.y + dy, root.initialRect.y + root.initialRect.height - root.minHeight)); + var newWidth = Math.max(root.minWidth, Math.min(root.initialRect.width + dx, root.width - root.initialRect.x)); + newRect = Qt.rect(root.initialRect.x, newY, newWidth, root.initialRect.height - (newY - root.initialRect.y)); + break; + // bottom left + case 2: + var newX = Math.max(0, Math.min(root.initialRect.x + dx, root.initialRect.x + root.initialRect.width - minWidth)); + var newHeight = Math.max(root.minHeight, Math.min(root.initialRect.height + dy, root.height - root.initialRect.y)); + newRect = Qt.rect(newX, root.initialRect.y, root.initialRect.width - (newX - root.initialRect.x), newHeight); + break; + // bottom right + case 3: + var newWidth = Math.max(root.minWidth, Math.min(root.initialRect.width + dx, root.width - root.initialRect.x)); + var newHeight = Math.max(root.minHeight, Math.min(root.initialRect.height + dy, root.height - root.initialRect.y)); + newRect = Qt.rect(root.initialRect.x, root.initialRect.y, newWidth, newHeight); + break; + } + + selectionRect = root.constrainRect(newRect); + root.requestPaint(); + } + + onReleased: { + root.activeHandle = -1; + } + } +} diff --git a/screencapture/SelectionRectangle.qml b/screencapture/SelectionRectangle.qml new file mode 100644 index 0000000..0c0fc25 --- /dev/null +++ b/screencapture/SelectionRectangle.qml @@ -0,0 +1,54 @@ +import QtQuick +import ".." + +Canvas { + id: root + + property color overlayColor: "#80000000" + property color outlineColor: ShellSettings.colors["primary"] + property rect selectionRect + property point startPosition + signal areaSelected(rect selection) + + onPaint: { + var ctx = getContext("2d"); + ctx.clearRect(0, 0, width, height); + + // grey overlay + ctx.fillStyle = overlayColor; + ctx.fillRect(0, 0, width, height); + + // cut out the selection rectangle + ctx.globalCompositeOperation = "destination-out"; + ctx.fillRect(selectionRect.x, selectionRect.y, selectionRect.width, selectionRect.height); + ctx.globalCompositeOperation = "source-over"; + ctx.strokeStyle = outlineColor; + ctx.lineWidth = 2; + ctx.strokeRect(selectionRect.x, selectionRect.y, selectionRect.width, selectionRect.height); + } + + MouseArea { + anchors.fill: parent + + onPressed: mouse => { + root.startPosition = Qt.point(mouse.x, mouse.y); + } + + onPositionChanged: mouse => { + if (pressed) { + var x = Math.min(root.startPosition.x, mouse.x); + var y = Math.min(root.startPosition.y, mouse.y); + var width = Math.abs(mouse.x - root.startPosition.x); + var height = Math.abs(mouse.y - root.startPosition.y); + + root.selectionRect = Qt.rect(x, y, width, height); + root.requestPaint(); + } + } + + onReleased: mouse => { + root.visible = false; + root.areaSelected(root.selectionRect); + } + } +} diff --git a/shell.qml b/shell.qml index 5eb81ad..3c4981c 100644 --- a/shell.qml +++ b/shell.qml @@ -9,9 +9,15 @@ import "volume-osd" as VolumeOSD import "settings" as Settings import "launcher" as Launcher import "wallpaper" as Wallpaper +import "screencapture" as ScreenCapture ShellRoot { - Component.onCompleted: [Launcher.Controller.init(), Settings.Controller.init(), Notifications.NotificationCenter.init()] + Component.onCompleted: { + Launcher.Controller.init(); + Settings.Controller.init(); + Notifications.NotificationCenter.init(); + ScreenCapture.Controller.init(); + } Variants { model: Quickshell.screens diff --git a/wallpaper/Controller.qml b/wallpaper/Controller.qml index 129a21a..4230c95 100644 --- a/wallpaper/Controller.qml +++ b/wallpaper/Controller.qml @@ -5,7 +5,6 @@ import ".." Scope { id: root - required property var screen property string matugenConf: Qt.resolvedUrl("matugen.toml").toString().replace("file://", "") LazyLoader { @@ -54,8 +53,18 @@ Scope { id: matugen running: false - // Formatter is keeping me hostage frfr... - command: ["matugen", "image", ShellSettings.settings.wallpaperUrl.replace("file://", ""), "--type", ShellSettings.settings.colorScheme, "--json", "hex", "--config", root.matugenConf] + // Don't format this lol + command: [ + "matugen", + "image", + ShellSettings.settings.wallpaperUrl.replace("file://", ""), + "--type", + ShellSettings.settings.colorScheme, + "--json", + "hex", + "--config", + root.matugenConf + ] stdout: SplitParser { onRead: data => {