diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/calendar-add-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/calendar-add-filled.svg new file mode 100644 index 000000000..9b12e58e6 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/calendar-add-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/calendar-add.svg b/dots/.config/quickshell/ii/assets/icons/fluent/calendar-add.svg new file mode 100644 index 000000000..fe0ff7263 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/calendar-add.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/camera-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/camera-filled.svg new file mode 100644 index 000000000..643964740 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/camera-filled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/camera.svg b/dots/.config/quickshell/ii/assets/icons/fluent/camera.svg new file mode 100644 index 000000000..40fa6d1f2 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/camera.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/crop-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/crop-filled.svg new file mode 100644 index 000000000..f86a4e42c --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/crop-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/crop.svg b/dots/.config/quickshell/ii/assets/icons/fluent/crop.svg new file mode 100644 index 000000000..1447e7c1f --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/crop.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/image-edit-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/image-edit-filled.svg new file mode 100644 index 000000000..661466195 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/image-edit-filled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/image-edit.svg b/dots/.config/quickshell/ii/assets/icons/fluent/image-edit.svg new file mode 100644 index 000000000..6f751b3ab --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/image-edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/scan-text-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/scan-text-filled.svg new file mode 100644 index 000000000..3a8c786b8 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/scan-text-filled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/scan-text.svg b/dots/.config/quickshell/ii/assets/icons/fluent/scan-text.svg new file mode 100644 index 000000000..beaf70538 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/scan-text.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/search-visual-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/search-visual-filled.svg new file mode 100644 index 000000000..1ab1d25b0 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/search-visual-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/search-visual.svg b/dots/.config/quickshell/ii/assets/icons/fluent/search-visual.svg new file mode 100644 index 000000000..167f228d0 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/search-visual.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/video-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/video-filled.svg new file mode 100644 index 000000000..b3d6843ea --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/video-filled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/video.svg b/dots/.config/quickshell/ii/assets/icons/fluent/video.svg new file mode 100644 index 000000000..021cbd863 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/video.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/modules/common/utils/ScreenshotAction.qml b/dots/.config/quickshell/ii/modules/common/utils/ScreenshotAction.qml new file mode 100644 index 000000000..831834bd5 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/utils/ScreenshotAction.qml @@ -0,0 +1,81 @@ +pragma ComponentBehavior: Bound +pragma Singleton +import qs.modules.common +import qs.modules.common.utils +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.services +import QtQuick +import QtQuick.Controls +import Qt.labs.synchronizer +import Quickshell + +Singleton { + id: root + + enum Action { + Copy, + Edit, + Search, + CharRecognition, + Record, + RecordWithSound + } + + property string imageSearchEngineBaseUrl: Config.options.search.imageSearch.imageSearchEngineBaseUrl + property string fileUploadApiEndpoint: "https://uguu.se/upload" + + function getCommand(x, y, width, height, screenshotPath, action, saveDir = "") { + // Set command for action + const rx = Math.round(x); + const ry = Math.round(y); + const rw = Math.round(width); + const rh = Math.round(height); + const cropBase = `magick ${StringUtils.shellSingleQuoteEscape(screenshotPath)} ` + + `-crop ${rw}x${rh}+${rx}+${ry}` + const cropToStdout = `${cropBase} -` + const cropInPlace = `${cropBase} '${StringUtils.shellSingleQuoteEscape(screenshotPath)}'` + const cleanup = `rm '${StringUtils.shellSingleQuoteEscape(screenshotPath)}'` + const slurpRegion = `${rx},${ry} ${rw}x${rh}` + const uploadAndGetUrl = (filePath) => { + return `curl -sF files[]=@'${StringUtils.shellSingleQuoteEscape(filePath)}' ${root.fileUploadApiEndpoint} | jq -r '.files[0].url'` + } + const annotationCommand = `${Config.options.regionSelector.annotation.useSatty ? "satty" : "swappy"} -f -`; + switch (action) { + case ScreenshotAction.Action.Copy: + if (saveDir === "") { + // not saving the screenshot, just copy to clipboard + return ["bash", "-c", `${cropToStdout} | wl-copy && ${cleanup}`] + break; + } + return [ + "bash", "-c", + `mkdir -p '${StringUtils.shellSingleQuoteEscape(saveDir)}' && \ + saveFileName="screenshot-$(date '+%Y-%m-%d_%H.%M.%S').png" && \ + savePath="${saveDir}/$saveFileName" && \ + ${cropToStdout} | tee >(wl-copy) > "$savePath" && \ + ${cleanup}` + ] + + break; + case ScreenshotAction.Action.Edit: + return ["bash", "-c", `${cropToStdout} | ${annotationCommand} && ${cleanup}`] + break; + case ScreenshotAction.Action.Search: + return ["bash", "-c", `${cropInPlace} && xdg-open "${root.imageSearchEngineBaseUrl}$(${uploadAndGetUrl(screenshotPath)})" && ${cleanup}`] + break; + case ScreenshotAction.Action.CharRecognition: + return ["bash", "-c", `${cropInPlace} && tesseract '${StringUtils.shellSingleQuoteEscape(screenshotPath)}' stdout -l $(tesseract --list-langs | awk 'NR>1{print $1}' | tr '\\n' '+' | sed 's/\\+$/\\n/') | wl-copy && ${cleanup}`] + break; + case ScreenshotAction.Action.Record: + return ["bash", "-c", `${Directories.recordScriptPath} --region '${slurpRegion}'`] + break; + case ScreenshotAction.Action.RecordWithSound: + return ["bash", "-c", `${Directories.recordScriptPath} --region '${slurpRegion}' --sound`] + break; + default: + console.warn("[Region Selector] Unknown snip action, skipping snip."); + return; + } + } +} diff --git a/dots/.config/quickshell/ii/modules/common/utils/TempScreenshotProcess.qml b/dots/.config/quickshell/ii/modules/common/utils/TempScreenshotProcess.qml new file mode 100644 index 000000000..40c40fb34 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/utils/TempScreenshotProcess.qml @@ -0,0 +1,14 @@ +import QtQuick +import Quickshell +import Quickshell.Io +import qs.modules.common +import qs.modules.common.functions + +Process { + id: screenshotProc + running: true + property string screenshotDir: Directories.screenshotTemp + required property ShellScreen screen + property string screenshotPath: `${screenshotDir}/image-${screen.name}` + command: ["bash", "-c", `mkdir -p '${StringUtils.shellSingleQuoteEscape(screenshotDir)}' && grim -o '${StringUtils.shellSingleQuoteEscape(screen.name)}' '${StringUtils.shellSingleQuoteEscape(screenshotPath)}'`] +} diff --git a/dots/.config/quickshell/ii/modules/common/widgets/DashedBorder.qml b/dots/.config/quickshell/ii/modules/common/widgets/DashedBorder.qml new file mode 100644 index 000000000..1d992ffab --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/DashedBorder.qml @@ -0,0 +1,28 @@ +import QtQuick +import qs.modules.common +import qs.modules.common.functions + +Canvas { + id: root + property color color: "#ffffff" + property int dashLength: 6 + property int gapLength: 4 + property int borderWidth: 1 + + onDashLengthChanged: requestPaint() + onGapLengthChanged: requestPaint() + onWidthChanged: requestPaint() + onHeightChanged: requestPaint() + onPaint: { + var ctx = getContext("2d"); + ctx.clearRect(0, 0, width, height); + ctx.save(); + ctx.strokeStyle = root.color; + ctx.lineWidth = root.borderWidth; + if (root.gapLength > 0) { + ctx.setLineDash([root.dashLength, root.gapLength]); // Set dash pattern + } + ctx.strokeRect(root.borderWidth / 2, root.borderWidth / 2, width - root.borderWidth, height - root.borderWidth); // Draw it + ctx.restore(); + } +} diff --git a/dots/.config/quickshell/ii/modules/common/widgets/DragManager.qml b/dots/.config/quickshell/ii/modules/common/widgets/DragManager.qml index 9a430d93b..4d6225400 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/DragManager.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/DragManager.qml @@ -14,12 +14,16 @@ MouseArea { property bool automaticallyReset: true readonly property real dragDiffX: _dragDiffX readonly property real dragDiffY: _dragDiffY + property real startX: 0 + property real startY: 0 + property real regionTopLeftX: Math.min(startX, startX + _dragDiffX) + property real regionTopLeftY: Math.min(startY, startY + _dragDiffY) + property real regionWidth: Math.abs(_dragDiffX) + property real regionHeight: Math.abs(_dragDiffY) signal dragPressed(diffX: real, diffY: real) signal dragReleased(diffX: real, diffY: real) - property real startX: 0 - property real startY: 0 property bool dragging: false property real _dragDiffX: 0 property real _dragDiffY: 0 diff --git a/dots/.config/quickshell/ii/modules/common/widgets/ToolbarTabBar.qml b/dots/.config/quickshell/ii/modules/common/widgets/ToolbarTabBar.qml index 1bca18463..708b30bd7 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/ToolbarTabBar.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/ToolbarTabBar.qml @@ -12,19 +12,30 @@ Item { required property var tabButtonList function incrementCurrentIndex() { - tabBar.incrementCurrentIndex() + tabBar.incrementCurrentIndex(); } function decrementCurrentIndex() { - tabBar.decrementCurrentIndex() + tabBar.decrementCurrentIndex(); } function setCurrentIndex(index) { - tabBar.setCurrentIndex(index) + tabBar.setCurrentIndex(index); } Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter implicitWidth: contentItem.implicitWidth implicitHeight: 40 + property Component delegate: ToolbarTabButton { + required property int index + required property var modelData + current: index == root.currentIndex + text: modelData.name + materialSymbol: modelData.icon + onClicked: { + root.setCurrentIndex(index); + } + } + Row { id: contentItem z: 1 @@ -33,16 +44,7 @@ Item { Repeater { model: root.tabButtonList - delegate: ToolbarTabButton { - required property int index - required property var modelData - current: index == root.currentIndex - text: modelData.name - materialSymbol: modelData.icon - onClicked: { - root.setCurrentIndex(index) - } - } + delegate: root.delegate } } @@ -76,23 +78,23 @@ Item { z: 2 acceptedButtons: Qt.NoButton cursorShape: Qt.PointingHandCursor - onWheel: (event) => { + onWheel: event => { if (event.angleDelta.y < 0) { root.incrementCurrentIndex(); - } - else { + } else { root.decrementCurrentIndex(); } } } - // TabBar doesn't allow tabs to be of different sizes. Literally unusable. + // TabBar doesn't allow tabs to be of different sizes. That's what I thought... // We use it only for the logic and draw stuff manually TabBar { id: tabBar z: -1 background: null - Repeater { // This is to fool the TabBar that it has tabs so it does the indices properly + Repeater { + // This is to fool the TabBar that it has tabs so it does the indices properly model: root.tabButtonList.length delegate: TabButton { background: null diff --git a/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelection.qml b/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelection.qml index 59dc186e7..920e69b56 100644 --- a/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelection.qml +++ b/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelection.qml @@ -1,5 +1,6 @@ pragma ComponentBehavior: Bound import qs.modules.common +import qs.modules.common.utils import qs.modules.common.functions import qs.modules.common.widgets import qs.services @@ -32,14 +33,8 @@ PanelWindow { property var action: RegionSelection.SnipAction.Copy property var selectionMode: RegionSelection.SelectionMode.RectCorners signal dismiss() - - property string saveScreenshotDir: Config.options.screenSnip.savePath !== "" - ? Config.options.screenSnip.savePath - : "" property string screenshotDir: Directories.screenshotTemp - property string imageSearchEngineBaseUrl: Config.options.search.imageSearch.imageSearchEngineBaseUrl - property string fileUploadApiEndpoint: "https://uguu.se/upload" property color overlayColor: "#88111111" property color brightText: Appearance.m3colors.darkmode ? Appearance.colors.colOnLayer0 : Appearance.colors.colLayer0 property color brightSecondary: Appearance.m3colors.darkmode ? Appearance.colors.colSecondary : Appearance.colors.colOnSecondary @@ -180,10 +175,12 @@ PanelWindow { property real regionX: Math.min(dragStartX, draggingX) property real regionY: Math.min(dragStartY, draggingY) - Process { + TempScreenshotProcess { id: screenshotProc running: true - command: ["bash", "-c", `mkdir -p '${StringUtils.shellSingleQuoteEscape(root.screenshotDir)}' && grim -o '${StringUtils.shellSingleQuoteEscape(root.screen.name)}' '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'`] + screen: root.screen + screenshotDir: root.screenshotDir + screenshotPath: root.screenshotPath onExited: (exitCode, exitStatus) => { if (root.enableContentRegions) imageDetectionProcess.running = true; root.preparationDone = !checkRecordingProc.running; @@ -229,6 +226,27 @@ PanelWindow { } } + function getScreenshotAction() { + switch(root.action) { + case RegionSelection.SnipAction.Copy: + return ScreenshotAction.Action.Copy; + case RegionSelection.SnipAction.Edit: + return ScreenshotAction.Action.Edit; + case RegionSelection.SnipAction.Search: + return ScreenshotAction.Action.Search; + case RegionSelection.SnipAction.CharRecognition: + return ScreenshotAction.Action.CharRecognition; + case RegionSelection.SnipAction.Record: + return ScreenshotAction.Action.Record; + case RegionSelection.SnipAction.RecordWithSound: + return ScreenshotAction.Action.RecordWithSound; + default: + console.warn("[Region Selector] Unknown snip action, skipping snip."); + root.dismiss(); + return; + } + } + function snip() { // Validity check if (root.regionWidth <= 0 || root.regionHeight <= 0) { @@ -246,62 +264,20 @@ PanelWindow { if (root.action === RegionSelection.SnipAction.Copy || root.action === RegionSelection.SnipAction.Edit) { root.action = root.mouseButton === Qt.RightButton ? RegionSelection.SnipAction.Edit : RegionSelection.SnipAction.Copy; } - - // Set command for action - const rx = Math.round(root.regionX * root.monitorScale); - const ry = Math.round(root.regionY * root.monitorScale); - const rw = Math.round(root.regionWidth * root.monitorScale); - const rh = Math.round(root.regionHeight * root.monitorScale); - const cropBase = `magick ${StringUtils.shellSingleQuoteEscape(root.screenshotPath)} ` - + `-crop ${rw}x${rh}+${rx}+${ry}` - const cropToStdout = `${cropBase} -` - const cropInPlace = `${cropBase} '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'` - const cleanup = `rm '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'` - const slurpRegion = `${rx},${ry} ${rw}x${rh}` - const uploadAndGetUrl = (filePath) => { - return `curl -sF files[]=@'${StringUtils.shellSingleQuoteEscape(filePath)}' ${root.fileUploadApiEndpoint} | jq -r '.files[0].url'` - } - const annotationCommand = `${Config.options.regionSelector.annotation.useSatty ? "satty" : "swappy"} -f -`; - switch (root.action) { - case RegionSelection.SnipAction.Copy: - if (saveScreenshotDir === "") { - // not saving the screenshot, just copy to clipboard - snipProc.command = ["bash", "-c", `${cropToStdout} | wl-copy && ${cleanup}`] - break; - } - - const savePathBase = root.saveScreenshotDir - - snipProc.command = [ - "bash", "-c", - `mkdir -p '${StringUtils.shellSingleQuoteEscape(savePathBase)}' && \ - saveFileName="screenshot-$(date '+%Y-%m-%d_%H.%M.%S').png" && \ - savePath="${savePathBase}/$saveFileName" && \ - ${cropToStdout} | tee >(wl-copy) > "$savePath" && \ - ${cleanup}` - ] - - break; - case RegionSelection.SnipAction.Edit: - snipProc.command = ["bash", "-c", `${cropToStdout} | ${annotationCommand} && ${cleanup}`] - break; - case RegionSelection.SnipAction.Search: - snipProc.command = ["bash", "-c", `${cropInPlace} && xdg-open "${root.imageSearchEngineBaseUrl}$(${uploadAndGetUrl(root.screenshotPath)})" && ${cleanup}`] - break; - case RegionSelection.SnipAction.CharRecognition: - snipProc.command = ["bash", "-c", `${cropInPlace} && tesseract '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}' stdout -l $(tesseract --list-langs | awk 'NR>1{print $1}' | tr '\\n' '+' | sed 's/\\+$/\\n/') | wl-copy && ${cleanup}`] - break; - case RegionSelection.SnipAction.Record: - snipProc.command = ["bash", "-c", `${Directories.recordScriptPath} --region '${slurpRegion}'`] - break; - case RegionSelection.SnipAction.RecordWithSound: - snipProc.command = ["bash", "-c", `${Directories.recordScriptPath} --region '${slurpRegion}' --sound`] - break; - default: - console.warn("[Region Selector] Unknown snip action, skipping snip."); - root.dismiss(); - return; - } + + const screenshotDir = Config.options.screenSnip.savePath !== "" ? // + Config.options.screenSnip.savePath : ""; + var screenshotAction = root.getScreenshotAction(); + const command = ScreenshotAction.getCommand( + root.regionX * root.monitorScale, // + root.regionY * root.monitorScale, // + root.regionWidth * root.monitorScale,// + root.regionHeight * root.monitorScale, // + root.screenshotPath, // + screenshotAction, // + screenshotDir + ) + snipProc.command = command; // Image post-processing snipProc.startDetached(); diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml index 8120aa85e..44075570f 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml @@ -93,8 +93,8 @@ Singleton { property color bgPanelFooter: ColorUtils.transparentize(root.dark ? root.darkColors.bgPanelFooter : root.lightColors.bgPanelFooter, root.panelLayerTransparency) property color bgPanelBody: ColorUtils.transparentize(root.dark ? root.darkColors.bgPanelBody : root.lightColors.bgPanelBody, root.panelLayerTransparency) property color bgPanelSeparator: ColorUtils.transparentize(root.dark ? root.darkColors.bgPanelSeparator : root.lightColors.bgPanelSeparator, root.backgroundTransparency) - property color bg0Opaque: root.dark ? root.darkColors.bg0 : root.lightColors.bg0 - property color bg0: ColorUtils.transparentize(bg0Opaque, root.backgroundTransparency) + property color bg0Base: root.dark ? root.darkColors.bg0 : root.lightColors.bg0 + property color bg0: ColorUtils.transparentize(bg0Base, root.backgroundTransparency) property color bg0Border: ColorUtils.transparentize(root.dark ? root.darkColors.bg0Border : root.lightColors.bg0Border, root.backgroundTransparency) property color bg1Base: root.dark ? root.darkColors.bg1Base : root.lightColors.bg1Base property color bg1: ColorUtils.transparentize(root.dark ? root.darkColors.bg1 : root.lightColors.bg1, root.contentTransparency) diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WButton.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WButton.qml index ceed470ba..d2cef0634 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WButton.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WButton.qml @@ -39,9 +39,10 @@ Button { } } property color fgColor: { + if (!root.enabled) return root.colForegroundDisabled if (root.checked) return root.colForegroundToggled if (root.enabled) return root.colForeground - return root.colForegroundDisabled + return root.colForeground } property alias horizontalAlignment: buttonText.horizontalAlignment font { diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WMenu.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WMenu.qml index 9f4f7f340..1d30576ec 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WMenu.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WMenu.qml @@ -76,8 +76,9 @@ Menu { contentItem: Item { implicitWidth: menuListView.implicitWidth implicitHeight: menuListView.implicitHeight - ListView { + WListView { id: menuListView + interactive: contentHeight > height anchors { left: parent.left right: parent.right @@ -87,6 +88,7 @@ Menu { topMargin: root.downDirection ? root.sourceEdgeMargin : root.margins bottomMargin: root.downDirection ? root.margins : root.sourceEdgeMargin } + clip: true implicitHeight: contentHeight implicitWidth: Array.from({ length: count diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WMenuItem.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WMenuItem.qml index 731b0d704..0a27c6cb3 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WMenuItem.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WMenuItem.qml @@ -6,6 +6,7 @@ import Quickshell import Quickshell.Hyprland import qs.modules.common import qs.modules.common.functions +import qs.modules.common.widgets import qs.modules.waffle.looks MenuItem { @@ -14,11 +15,11 @@ MenuItem { property color colBackground: ColorUtils.transparentize(Looks.colors.bg1) property color colBackgroundHover: Looks.colors.bg2Hover property color colBackgroundActive: Looks.colors.bg2Active - property color colBackgroundToggled: Looks.colors.accent - property color colBackgroundToggledHover: Looks.colors.accentHover - property color colBackgroundToggledActive: Looks.colors.accentActive + property color colBackgroundToggled: Looks.colors.bg2Hover + property color colBackgroundToggledHover: Looks.colors.bg2Active + property color colBackgroundToggledActive: Looks.colors.bg2Hover property color colForeground: Looks.colors.fg - property color colForegroundToggled: Looks.colors.accentFg + property color colForegroundToggled: Looks.colors.fg property color colForegroundDisabled: ColorUtils.transparentize(Looks.colors.subfg, 0.4) property color color: { if (!root.enabled) @@ -70,27 +71,57 @@ MenuItem { implicitHeight: Math.max(28, contentItem.implicitHeight) + topInset + bottomInset implicitWidth: contentItem.implicitWidth + leftInset + rightInset + leftPadding + rightPadding - contentItem: RowLayout { - id: contentLayout - spacing: 12 - FluentIcon { - id: buttonIcon - monochrome: true - implicitSize: 20 - Layout.fillWidth: false - Layout.alignment: Qt.AlignVCenter - color: root.fgColor - visible: root.icon.name !== ""; - icon: root.icon.name + contentItem: Item { + implicitWidth: contentLayout.implicitWidth + implicitHeight: contentLayout.implicitHeight + + RowLayout { + id: contentLayout + anchors.fill: parent + spacing: 12 + FluentIcon { + id: buttonIcon + monochrome: true + implicitSize: 20 + Layout.fillWidth: false + Layout.alignment: Qt.AlignVCenter + color: root.fgColor + visible: root.icon.name !== "" + icon: root.icon.name + } + WText { + id: buttonText + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + text: root.text + horizontalAlignment: Text.AlignLeft + font.pixelSize: Looks.font.pixelSize.large + color: root.fgColor + } } - WText { - id: buttonText - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft - text: root.text - horizontalAlignment: Text.AlignLeft - font.pixelSize: Looks.font.pixelSize.large - color: root.fgColor + + WFadeLoader { + anchors { + verticalCenter: parent.verticalCenter + left: parent.left + leftMargin: -root.leftPadding + width + } + shown: root.checked + sourceComponent: Rectangle { + implicitWidth: 3 + implicitHeight: 3 + radius: width / 2 + color: Looks.colors.accent + property bool forceZeroHeight: true + height: forceZeroHeight ? 0 : Math.max(root.down ? 10 : 16, root.background.height - 18 * 2) + Component.onCompleted: { + forceZeroHeight = false; + } + + Behavior on height { + animation: Looks.transition.resize.createObject(this) + } + } } } } diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WToolbar.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WToolbar.qml new file mode 100644 index 000000000..6d58e8f7c --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WToolbar.qml @@ -0,0 +1,38 @@ +import QtQuick +import QtQuick.Layouts +import qs.modules.common +import qs.modules.common.widgets + +Item { + id: root + + property real padding: 9 + property alias colBackground: background.color + property alias spacing: toolbarLayout.spacing + property alias radius: background.radius + default property alias data: toolbarLayout.data + + implicitWidth: background.implicitWidth + implicitHeight: background.implicitHeight + + Rectangle { + id: background + anchors.fill: parent + implicitHeight: 50 + implicitWidth: toolbarLayout.implicitWidth + root.padding * 2 + radius: Looks.radius.large + color: Looks.colors.bg0Base + + border.width: 1 + border.color: Looks.colors.bg1Border + + RowLayout { + id: toolbarLayout + spacing: 4 + anchors { + fill: parent + margins: root.padding + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WToolbarButton.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WToolbarButton.qml new file mode 100644 index 000000000..59dc5ee99 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WToolbarButton.qml @@ -0,0 +1,8 @@ +import QtQuick +import QtQuick.Layouts +import qs.modules.common + +WButton { + implicitHeight: 32 + radius: Looks.radius.medium +} diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WToolbarIconButton.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WToolbarIconButton.qml new file mode 100644 index 000000000..4a3644735 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WToolbarIconButton.qml @@ -0,0 +1,16 @@ +import QtQuick +import QtQuick.Layouts +import qs.modules.common + +WToolbarButton { + id: root + implicitWidth: height + contentItem: Item { + FluentIcon { + anchors.centerIn: parent + icon: root.icon.name + implicitSize: 18 + color: root.fgColor + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WToolbarIconTabButton.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WToolbarIconTabButton.qml new file mode 100644 index 000000000..0dc6ae208 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WToolbarIconTabButton.qml @@ -0,0 +1,21 @@ +import QtQuick +import QtQuick.Controls +import qs.modules.common + +TabButton { + id: root + + implicitWidth: 38 + implicitHeight: 32 + padding: 0 + + background: null + contentItem: Item { + FluentIcon { + anchors.centerIn: parent + icon: root.icon.name + color: root.icon.color + implicitSize: 18 + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WToolbarSeparator.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WToolbarSeparator.qml new file mode 100644 index 000000000..d67c4b767 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WToolbarSeparator.qml @@ -0,0 +1,11 @@ +import QtQuick +import QtQuick.Layouts +import qs.modules.common + +Rectangle { + Layout.leftMargin: 4 + Layout.rightMargin: 4 + implicitHeight: 24 + implicitWidth: 1 + color: Looks.colors.bg0Border +} diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WToolbarTabBar.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WToolbarTabBar.qml new file mode 100644 index 000000000..025ee53a4 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WToolbarTabBar.qml @@ -0,0 +1,53 @@ +import QtQuick +import QtQuick.Controls +import qs.modules.common +import qs.modules.common.functions + +TabBar { + id: root + implicitHeight: 32 + + background: Rectangle { + radius: Looks.radius.medium + color: Looks.colors.bgPanelFooter + border.color: ColorUtils.transparentize(Looks.colors.bg0Border, 0.7) + border.width: 1 + + // Indicator + Rectangle { + anchors { + top: parent.top + bottom: parent.bottom + left: parent.left + leftMargin: root.currentIndex * (root.width / root.count) + Behavior on leftMargin { + animation: Looks.transition.resize.createObject(this) + } + } + radius: Looks.radius.medium + color: Looks.colors.bg2Base + border.color: Looks.colors.bg0Border + border.width: 1 + width: root.width / root.count + + Rectangle { + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + bottomMargin: 1 + } + implicitWidth: pressDetector.containsPress ? 16 : 12 + implicitHeight: 3 + radius: height / 2 + color: Looks.colors.accent + } + } + } + + MouseArea { + id: pressDetector + z: 9999 + anchors.fill: parent + acceptedButtons: Qt.LeftButton + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/screenSnip/WRectangularSelection.qml b/dots/.config/quickshell/ii/modules/waffle/screenSnip/WRectangularSelection.qml new file mode 100644 index 000000000..a0ec287b8 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/screenSnip/WRectangularSelection.qml @@ -0,0 +1,62 @@ +import QtQuick +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks + +Item { + id: root + + required property int regionX + required property int regionY + required property int regionWidth + required property int regionHeight + + property bool dashed: true + property color borderColor: "#ffffff" + property color overlayColor: ColorUtils.transparentize("#000000", 1) + Component.onCompleted: overlayColor = ColorUtils.transparentize("#000000", 0.4) + Behavior on overlayColor { + ColorAnimation { + duration: 250 + easing.type: Easing.InOutQuad + } + } + + // Overlay to darken screen + // Base dark overlay around region + Rectangle { + id: darkenOverlay + z: 1 + anchors { + left: parent.left + top: parent.top + leftMargin: root.regionX - darkenOverlay.border.width + topMargin: root.regionY - darkenOverlay.border.width + } + width: root.regionWidth + darkenOverlay.border.width * 2 + height: root.regionHeight + darkenOverlay.border.width * 2 + color: "transparent" + border.color: root.overlayColor + border.width: Math.max(root.width, root.height) + } + + // Selection border + DashedBorder { + id: border + z: 2 + visible: root.regionWidth > 0 && root.regionHeight > 0 + anchors { + left: parent.left + top: parent.top + leftMargin: Math.round(root.regionX - borderWidth) + topMargin: Math.round(root.regionY - borderWidth) + } + width: Math.round(root.regionWidth + borderWidth * 2) + height: Math.round(root.regionHeight + borderWidth * 2) + color: root.borderColor + dashLength: 4 + gapLength: root.dashed ? 3 : 0 + borderWidth: 1 + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/screenSnip/WRegionSelectionPanel.qml b/dots/.config/quickshell/ii/modules/waffle/screenSnip/WRegionSelectionPanel.qml new file mode 100644 index 000000000..318887928 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/screenSnip/WRegionSelectionPanel.qml @@ -0,0 +1,375 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt.labs.synchronizer +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Hyprland +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.utils +import qs.modules.common.widgets +import qs.modules.waffle.looks + +PanelWindow { + id: root + + enum MediaType { + Image, + Video + } + enum ImageAction { + Copy, + Menu, + CharRecognition, + Search + } + enum VideoAction { + Record, + RecordWithSound + } + enum SelectionMode { + Rect, + Window + } + + signal closed + function close() { + root.closed(); + } + + property var mediaType: WRegionSelectionPanel.MediaType.Image + property var imageAction: WRegionSelectionPanel.ImageAction.Copy + property var selectionMode: WRegionSelectionPanel.SelectionMode.Rect + + visible: false + color: "transparent" + WlrLayershell.namespace: "quickshell:regionSelector" + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand + exclusionMode: ExclusionMode.Ignore + anchors { + left: true + right: true + top: true + bottom: true + } + + // Hyprland stuff + readonly property HyprlandMonitor hyprlandMonitor: Hyprland.monitorFor(screen) + readonly property real monitorScale: hyprlandMonitor.scale + readonly property var windows: [...HyprlandData.windowList].sort((a, b) => { + // Sort floating=true windows before others + if (a.floating === b.floating) + return 0; + return a.floating ? -1 : 1; + }) + + property string screenshotDir: Directories.screenshotTemp + property string screenshotPath: `${root.screenshotDir}/image-${screen.name}` + TempScreenshotProcess { + id: screenshotProc + running: true + screen: root.screen + screenshotDir: root.screenshotDir + screenshotPath: root.screenshotPath + onExited: (exitCode, exitStatus) => { + root.preparationDone = true; + } + } + property bool preparationDone: false + onPreparationDoneChanged: { + if (!preparationDone) + return; + root.visible = true; + } + + function getScreenshotAction() { + switch (root.mediaType) { + case WRegionSelectionPanel.MediaType.Image: + switch (root.imageAction) { + case WRegionSelectionPanel.ImageAction.Copy: + return ScreenshotAction.Action.Copy; + case WRegionSelectionPanel.ImageAction.Menu: + return ScreenshotAction.Action.Edit; + case WRegionSelectionPanel.ImageAction.CharRecognition: + return ScreenshotAction.Action.CharRecognition; + case WRegionSelectionPanel.ImageAction.Search: + return ScreenshotAction.Action.Search; + default: + return ScreenshotAction.Action.Copy; + } + break; + case WRegionSelectionPanel.MediaType.Video: + switch (root.videoAction) { + case WRegionSelectionPanel.VideoAction.Record: + return ScreenshotAction.Action.Record; + case WRegionSelectionPanel.VideoAction.RecordWithSound: + return ScreenshotAction.Action.RecordWithSound; + } + } + } + + Process { + id: snipProc + } + + ScreencopyView { + id: screencopyView + anchors.fill: parent + live: false + captureSource: root.screen + + focus: root.visible + Keys.onPressed: event => { // Esc to close + if (event.key === Qt.Key_Escape) { + root.close(); + } else if (event.key === Qt.Key_E && event.modifiers & Qt.ControlModifier) { + if (root.imageAction === WRegionSelectionPanel.ImageAction.Menu) { + root.imageAction = WRegionSelectionPanel.ImageAction.Copy; + } else { + root.imageAction = WRegionSelectionPanel.ImageAction.Menu; + } + } + } + + DragManager { + id: dragArea + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton + cursorShape: Qt.CrossCursor + + property bool isWindowSelection: root.selectionMode === WRegionSelectionPanel.SelectionMode.Window + property var hoveredWindow: root.windows.find(w => { + const inCurrentWorkspace = w.workspace.id === HyprlandData.activeWorkspace.id; + const withinXRange = w.at[0] <= dragArea.mouseX && dragArea.mouseX <= w.at[0] + w.size[0]; + const withinYRange = w.at[1] <= dragArea.mouseY && dragArea.mouseY <= w.at[1] + w.size[1]; + return inCurrentWorkspace && withinXRange && withinYRange; + }) + property int winPadding: 1 + property int selectionX: isWindowSelection ? ((hoveredWindow?.at[0] ?? 0) - winPadding) : regionTopLeftX + property int selectionY: isWindowSelection ? ((hoveredWindow?.at[1] ?? 0) - winPadding) : regionTopLeftY + property int selectionWidth: isWindowSelection ? ((hoveredWindow?.size[0] ?? 0) + winPadding * 2) : regionWidth + property int selectionHeight: isWindowSelection ? ((hoveredWindow?.size[1] ?? 0) + winPadding * 2) : regionHeight + + onDragReleased: (diffX, diffY) => { + if (selectionWidth === 0 || selectionHeight === 0) { + return; + } + const screenshotDir = Config.options.screenSnip.savePath !== "" ? Config.options.screenSnip.savePath : ""; + const screenshotAction = root.getScreenshotAction(); + const command = ScreenshotAction.getCommand(dragArea.selectionX * root.monitorScale // + , dragArea.selectionY * root.monitorScale // + , dragArea.selectionWidth * root.monitorScale// + , dragArea.selectionHeight * root.monitorScale // + , root.screenshotPath // + , screenshotAction // + , screenshotDir); // yo wtf is this formatting qmlls do be funnie + snipProc.command = command; + + // Image post-processing + snipProc.startDetached(); + root.close(); + } + + WRectangularSelection { + id: rectangularSelection + anchors.fill: parent + regionX: dragArea.selectionX + regionY: dragArea.selectionY + regionWidth: dragArea.selectionWidth + regionHeight: dragArea.selectionHeight + dashed: root.selectionMode === WRegionSelectionPanel.SelectionMode.Rect + } + + RegionSelectionOptionsToolbar { + anchors { + horizontalCenter: parent.horizontalCenter + top: parent.top + topMargin: 12 + } + } + } + } + + component RegionSelectionOptionsToolbar: WToolbar { + // Image/video + WToolbarTabBar { + currentIndex: switch (root.mediaType) { + case WRegionSelectionPanel.MediaType.Image: + return 0; + case WRegionSelectionPanel.MediaType.Video: + return 1; + default: + return 0; + } + WToolbarIconTabButton { + icon.name: "camera" + icon.color: Looks.colors.fg + } + WToolbarIconTabButton { + icon.name: "video" + icon.color: Looks.colors.fg + } + onCurrentIndexChanged: { + switch (currentIndex) { + case 0: + root.mediaType = WRegionSelectionPanel.MediaType.Image; + break; + case 1: + root.mediaType = WRegionSelectionPanel.MediaType.Video; + break; + } + } + + WToolTip { + text: Translation.tr("Snip") + } + } + + // Selection type + WToolbarButton { + id: selectionTypeBtn + implicitWidth: selectionTypeBtnRow.implicitWidth + 11 * 2 + leftPadding: 11 + rightPadding: 11 + onClicked: { + selectionTypeMenu.visible = !selectionTypeMenu.visible; + } + contentItem: Row { + id: selectionTypeBtnRow + spacing: 4 + FluentIcon { + anchors.verticalCenter: parent.verticalCenter + icon: switch (root.selectionMode) { + case WRegionSelectionPanel.SelectionMode.Rect: + return "crop"; + case WRegionSelectionPanel.SelectionMode.Window: + return "calendar-add"; + default: + return "crop"; + } + implicitSize: 18 + } + FluentIcon { + anchors { + top: parent.top + topMargin: (parent.height - height) / 2 + (selectionTypeBtn.down ? 2 : 0) + Behavior on topMargin { + animation: Looks.transition.enter.createObject(this) + } + } + icon: "chevron-down" + implicitSize: 12 + } + } + + WMenu { + id: selectionTypeMenu + onClosed: screencopyView.focus = true + x: -margins + y: -margins - (selectionTypeBtn.parent.height - selectionTypeBtn.height) - 16 + topMargin: -6 + height: implicitHeight + sourceEdgeMargin + + color: Looks.colors.bg1Base + + Action { + icon.name: "crop" + text: Translation.tr("Rectangle") + checked: root.selectionMode === WRegionSelectionPanel.SelectionMode.Rect + onTriggered: { + root.selectionMode = WRegionSelectionPanel.SelectionMode.Rect; + } + } + Action { + icon.name: "calendar-add" + text: Translation.tr("Window") + checked: root.selectionMode === WRegionSelectionPanel.SelectionMode.Window + onTriggered: { + root.selectionMode = WRegionSelectionPanel.SelectionMode.Window; + } + } + } + + WToolTip { + text: Translation.tr("Snipping area") + } + } + + // Markup + WToolbarIconButton { + icon.name: "image-edit" + enabled: root.mediaType === WRegionSelectionPanel.MediaType.Image + checked: root.imageAction === WRegionSelectionPanel.ImageAction.Menu + onClicked: { + if (root.imageAction === WRegionSelectionPanel.ImageAction.Menu) { + root.imageAction = WRegionSelectionPanel.ImageAction.Copy; + } else { + root.imageAction = WRegionSelectionPanel.ImageAction.Menu; + } + } + WToolTip { + text: Translation.tr("Quick markup (Ctrl+E)") + } + } + + WToolbarSeparator {} + + // Tools + WToolbarIconButton { + icon.name: "search-visual" + checked: root.imageAction === WRegionSelectionPanel.ImageAction.Search + onClicked: { + if (root.imageAction === WRegionSelectionPanel.ImageAction.Search && root.mediaType === WRegionSelectionPanel.MediaType.Image) { + root.imageAction = WRegionSelectionPanel.ImageAction.Copy; + } else { + root.mediaType = WRegionSelectionPanel.MediaType.Image; + root.imageAction = WRegionSelectionPanel.ImageAction.Search; + } + } + WToolTip { + text: Translation.tr("Image search") + } + } + WToolbarIconButton { + icon.name: "eyedropper" + onClicked: { + Quickshell.execDetached(["bash", "-c", "sleep 0.2; hyprpicker -a"]); + root.closed(); + } + WToolTip { + text: Translation.tr("Color picker") + } + } + WToolbarIconButton { + icon.name: "scan-text" + checked: root.imageAction === WRegionSelectionPanel.ImageAction.CharRecognition + onClicked: { + if (root.imageAction === WRegionSelectionPanel.ImageAction.CharRecognition && root.mediaType === WRegionSelectionPanel.MediaType.Image) { + root.imageAction = WRegionSelectionPanel.ImageAction.Copy; + } else { + root.mediaType = WRegionSelectionPanel.MediaType.Image; + root.imageAction = WRegionSelectionPanel.ImageAction.CharRecognition; + } + } + WToolTip { + text: Translation.tr("Text extractor") + } + } + + WToolbarSeparator {} + + WToolbarIconButton { + icon.name: "dismiss" + onClicked: root.close() + WToolTip { + text: Translation.tr("Close (Esc)") + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/screenSnip/WScreenSnip.qml b/dots/.config/quickshell/ii/modules/waffle/screenSnip/WScreenSnip.qml new file mode 100644 index 000000000..c462f9e7b --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/screenSnip/WScreenSnip.qml @@ -0,0 +1,106 @@ +pragma ComponentBehavior: Bound +import qs +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.services +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Widgets +import Quickshell.Hyprland + +Scope { + id: root + + function dismiss() { + GlobalStates.regionSelectorOpen = false; + } + + Loader { + id: regionSelectorLoader + active: GlobalStates.regionSelectorOpen + + sourceComponent: WRegionSelectionPanel { + onClosed: root.dismiss() + } + } + + function screenshot() { + GlobalStates.regionSelectorOpen = true; + } + + function ocr() { + GlobalStates.regionSelectorOpen = true; + regionSelectorLoader.item.mediaType = WRegionSelectionPanel.MediaType.Image; + regionSelectorLoader.item.imageAction = WRegionSelectionPanel.ImageAction.CharRecognition; + } + + function record() { + GlobalStates.regionSelectorOpen = true; + regionSelectorLoader.item.mediaType = WRegionSelectionPanel.MediaType.Video; + regionSelectorLoader.item.videoAction = WRegionSelectionPanel.VideoAction.Record; + } + + function recordWithSound() { + GlobalStates.regionSelectorOpen = true; + regionSelectorLoader.item.mediaType = WRegionSelectionPanel.MediaType.Video; + regionSelectorLoader.item.videoAction = WRegionSelectionPanel.VideoAction.RecordWithSound; + } + + function search() { + GlobalStates.regionSelectorOpen = true; + regionSelectorLoader.item.mediaType = WRegionSelectionPanel.MediaType.Image; + regionSelectorLoader.item.imageAction = WRegionSelectionPanel.ImageAction.Search; + } + + IpcHandler { + target: "region" + + function screenshot() { + root.screenshot(); + } + function ocr() { + root.ocr(); + } + function record() { + root.record(); + } + function recordWithSound() { + root.recordWithSound(); + } + function search() { + root.search(); + } + } + + GlobalShortcut { + name: "regionScreenshot" + description: "Takes a screenshot of the selected region" + onPressed: root.screenshot() + } + GlobalShortcut { + name: "regionSearch" + description: "Searches the selected region" + onPressed: root.search() + } + GlobalShortcut { + name: "regionOcr" + description: "Recognizes text in the selected region" + onPressed: root.ocr() + } + GlobalShortcut { + name: "regionRecord" + description: "Records the selected region" + onPressed: root.record() + } + GlobalShortcut { + name: "regionRecordWithSound" + description: "Records the selected region with sound" + onPressed: root.recordWithSound() + } +} diff --git a/dots/.config/quickshell/ii/shell.qml b/dots/.config/quickshell/ii/shell.qml index feced64cd..93541c5bf 100644 --- a/dots/.config/quickshell/ii/shell.qml +++ b/dots/.config/quickshell/ii/shell.qml @@ -34,6 +34,7 @@ import qs.modules.waffle.lock import qs.modules.waffle.notificationCenter import qs.modules.waffle.onScreenDisplay import qs.modules.waffle.polkit +import qs.modules.waffle.screenSnip import qs.modules.waffle.startMenu import qs.modules.waffle.sessionScreen import qs.modules.waffle.taskView @@ -89,6 +90,7 @@ ShellRoot { PanelLoader { identifier: "wNotificationCenter"; component: WaffleNotificationCenter {} } PanelLoader { identifier: "wOnScreenDisplay"; component: WaffleOSD {} } PanelLoader { identifier: "wPolkit"; component: WafflePolkit {} } + PanelLoader { identifier: "wScreenSnip"; component: WScreenSnip {} } PanelLoader { identifier: "wStartMenu"; component: WaffleStartMenu {} } PanelLoader { identifier: "wSessionScreen"; component: WaffleSessionScreen {} } PanelLoader { identifier: "wTaskView"; component: WaffleTaskView {} } @@ -104,7 +106,7 @@ ShellRoot { property list families: ["ii", "waffle"] property var panelFamilies: ({ "ii": ["iiBar", "iiBackground", "iiCheatsheet", "iiDock", "iiLock", "iiMediaControls", "iiNotificationPopup", "iiOnScreenDisplay", "iiOnScreenKeyboard", "iiOverlay", "iiOverview", "iiPolkit", "iiRegionSelector", "iiScreenCorners", "iiSessionScreen", "iiSidebarLeft", "iiSidebarRight", "iiVerticalBar", "iiWallpaperSelector"], - "waffle": ["wActionCenter", "wBar", "wBackground", "wLock", "wNotificationCenter", "wOnScreenDisplay", "wTaskView", "wPolkit", "wSessionScreen", "wStartMenu", "iiCheatsheet", "iiNotificationPopup", "iiOnScreenKeyboard", "iiOverlay", "iiRegionSelector", "iiWallpaperSelector"], + "waffle": ["wActionCenter", "wBar", "wBackground", "wLock", "wNotificationCenter", "wOnScreenDisplay", "wTaskView", "wPolkit", "wScreenSnip", "wSessionScreen", "wStartMenu", "iiCheatsheet", "iiNotificationPopup", "iiOnScreenKeyboard", "iiOverlay", "iiWallpaperSelector"], }) function cyclePanelFamily() { const currentIndex = families.indexOf(Config.options.panelFamily)