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)