forked from Shinonome/dots-hyprland
region selector: breathing for region recording
This commit is contained in:
+17
-23
@@ -14,11 +14,14 @@ Item {
|
|||||||
required property color overlayColor
|
required property color overlayColor
|
||||||
property bool showAimLines: Config.options.regionSelector.rect.showAimLines
|
property bool showAimLines: Config.options.regionSelector.rect.showAimLines
|
||||||
|
|
||||||
|
property bool breathingBorderOnly: false
|
||||||
|
|
||||||
// Overlay to darken screen
|
// Overlay to darken screen
|
||||||
// Base dark overlay around region
|
// Base dark overlay around region
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: darkenOverlay
|
id: darkenOverlay
|
||||||
z: 1
|
z: 1
|
||||||
|
visible: !root.breathingBorderOnly
|
||||||
anchors {
|
anchors {
|
||||||
left: parent.left
|
left: parent.left
|
||||||
top: parent.top
|
top: parent.top
|
||||||
@@ -32,25 +35,6 @@ Item {
|
|||||||
border.width: Math.max(root.width, root.height)
|
border.width: Math.max(root.width, root.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selection border
|
|
||||||
// Rectangle {
|
|
||||||
// id: selectionBorder
|
|
||||||
// z: 1
|
|
||||||
// anchors {
|
|
||||||
// left: parent.left
|
|
||||||
// top: parent.top
|
|
||||||
// leftMargin: root.regionX
|
|
||||||
// topMargin: root.regionY
|
|
||||||
// }
|
|
||||||
// width: root.regionWidth
|
|
||||||
// height: root.regionHeight
|
|
||||||
// color: "transparent"
|
|
||||||
// border.color: root.color
|
|
||||||
// border.width: 2
|
|
||||||
// // radius: root.standardRounding
|
|
||||||
// radius: 0 // TODO: figure out how to make the overlay thing work with rounding
|
|
||||||
// }
|
|
||||||
|
|
||||||
DashedBorder {
|
DashedBorder {
|
||||||
id: selectionBorder
|
id: selectionBorder
|
||||||
z: 9
|
z: 9
|
||||||
@@ -64,13 +48,23 @@ Item {
|
|||||||
height: Math.round(root.regionHeight) + borderWidth * 2
|
height: Math.round(root.regionHeight) + borderWidth * 2
|
||||||
|
|
||||||
color: root.color
|
color: root.color
|
||||||
dashLength: 6
|
dashLength: 8
|
||||||
gapLength: 3
|
gapLength: 4
|
||||||
borderWidth: 1
|
borderWidth: 1
|
||||||
|
|
||||||
|
// Breathing
|
||||||
|
opacity: 0.9
|
||||||
|
SequentialAnimation on opacity {
|
||||||
|
running: root.breathingBorderOnly
|
||||||
|
loops: Animation.Infinite
|
||||||
|
NumberAnimation { from: 0.9; to: 0.3; duration: 1200; easing.type: Easing.InOutQuad }
|
||||||
|
NumberAnimation { from: 0.3; to: 0.9; duration: 1200; easing.type: Easing.InOutQuad }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
z: 2
|
z: 2
|
||||||
|
visible: !root.breathingBorderOnly
|
||||||
anchors {
|
anchors {
|
||||||
top: selectionBorder.bottom
|
top: selectionBorder.bottom
|
||||||
right: selectionBorder.right
|
right: selectionBorder.right
|
||||||
@@ -82,7 +76,7 @@ Item {
|
|||||||
|
|
||||||
// Coord lines
|
// Coord lines
|
||||||
Rectangle { // Vertical
|
Rectangle { // Vertical
|
||||||
visible: root.showAimLines
|
visible: root.showAimLines && !root.breathingBorderOnly
|
||||||
opacity: 0.2
|
opacity: 0.2
|
||||||
z: 2
|
z: 2
|
||||||
x: root.mouseX
|
x: root.mouseX
|
||||||
@@ -94,7 +88,7 @@ Item {
|
|||||||
color: root.color
|
color: root.color
|
||||||
}
|
}
|
||||||
Rectangle { // Horizontal
|
Rectangle { // Horizontal
|
||||||
visible: root.showAimLines
|
visible: root.showAimLines && !root.breathingBorderOnly
|
||||||
opacity: 0.2
|
opacity: 0.2
|
||||||
z: 2
|
z: 2
|
||||||
y: root.mouseY
|
y: root.mouseY
|
||||||
|
|||||||
@@ -27,13 +27,17 @@ PanelWindow {
|
|||||||
bottom: true
|
bottom: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Modes
|
||||||
// TODO: Ask: sidebar AI
|
// TODO: Ask: sidebar AI
|
||||||
enum SnipAction { Copy, Edit, Search, CharRecognition, Record, RecordWithSound }
|
enum SnipAction { Copy, Edit, Search, CharRecognition, Record, RecordWithSound }
|
||||||
enum SelectionMode { RectCorners, Circle }
|
enum SelectionMode { RectCorners, Circle }
|
||||||
|
enum Phase { Select, Post }
|
||||||
property var action: RegionSelection.SnipAction.Copy
|
property var action: RegionSelection.SnipAction.Copy
|
||||||
property var selectionMode: RegionSelection.SelectionMode.RectCorners
|
property var selectionMode: RegionSelection.SelectionMode.RectCorners
|
||||||
|
property var phase: RegionSelection.Phase.Select
|
||||||
signal dismiss()
|
signal dismiss()
|
||||||
|
|
||||||
|
// Styles
|
||||||
property string screenshotDir: Directories.screenshotTemp
|
property string screenshotDir: Directories.screenshotTemp
|
||||||
property color overlayColor: ColorUtils.transparentize("#000000", 0.4)
|
property color overlayColor: ColorUtils.transparentize("#000000", 0.4)
|
||||||
property color brightText: Appearance.m3colors.darkmode ? Appearance.colors.colOnLayer0 : Appearance.colors.colLayer0
|
property color brightText: Appearance.m3colors.darkmode ? Appearance.colors.colOnLayer0 : Appearance.colors.colLayer0
|
||||||
@@ -46,6 +50,10 @@ PanelWindow {
|
|||||||
property color imageBorderColor: brightTertiary
|
property color imageBorderColor: brightTertiary
|
||||||
property color imageFillColor: ColorUtils.transparentize(imageBorderColor, 0.85)
|
property color imageFillColor: ColorUtils.transparentize(imageBorderColor, 0.85)
|
||||||
property color onBorderColor: "#ff000000"
|
property color onBorderColor: "#ff000000"
|
||||||
|
property real targetRegionOpacity: Config.options.regionSelector.targetRegions.opacity
|
||||||
|
property bool contentRegionOpacity: Config.options.regionSelector.targetRegions.contentRegionOpacity
|
||||||
|
|
||||||
|
// Vars for indicators
|
||||||
readonly property var windows: [...HyprlandData.windowList].sort((a, b) => {
|
readonly property var windows: [...HyprlandData.windowList].sort((a, b) => {
|
||||||
// Sort floating=true windows before others
|
// Sort floating=true windows before others
|
||||||
if (a.floating === b.floating) return 0;
|
if (a.floating === b.floating) return 0;
|
||||||
@@ -54,6 +62,7 @@ PanelWindow {
|
|||||||
readonly property var layers: HyprlandData.layers
|
readonly property var layers: HyprlandData.layers
|
||||||
readonly property real falsePositivePreventionRatio: 0.5
|
readonly property real falsePositivePreventionRatio: 0.5
|
||||||
|
|
||||||
|
// Screen & interaction vars
|
||||||
readonly property HyprlandMonitor hyprlandMonitor: Hyprland.monitorFor(screen)
|
readonly property HyprlandMonitor hyprlandMonitor: Hyprland.monitorFor(screen)
|
||||||
readonly property real monitorScale: hyprlandMonitor.scale
|
readonly property real monitorScale: hyprlandMonitor.scale
|
||||||
readonly property real monitorOffsetX: hyprlandMonitor.x
|
readonly property real monitorOffsetX: hyprlandMonitor.x
|
||||||
@@ -105,13 +114,13 @@ PanelWindow {
|
|||||||
return offsetAdjustedLayers;
|
return offsetAdjustedLayers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Config
|
||||||
property bool isCircleSelection: (root.selectionMode === RegionSelection.SelectionMode.Circle)
|
property bool isCircleSelection: (root.selectionMode === RegionSelection.SelectionMode.Circle)
|
||||||
property bool enableWindowRegions: Config.options.regionSelector.targetRegions.windows && !isCircleSelection
|
property bool enableWindowRegions: Config.options.regionSelector.targetRegions.windows && !isCircleSelection
|
||||||
property bool enableLayerRegions: Config.options.regionSelector.targetRegions.layers && !isCircleSelection
|
property bool enableLayerRegions: Config.options.regionSelector.targetRegions.layers && !isCircleSelection
|
||||||
property bool enableContentRegions: Config.options.regionSelector.targetRegions.content
|
property bool enableContentRegions: Config.options.regionSelector.targetRegions.content
|
||||||
property real targetRegionOpacity: Config.options.regionSelector.targetRegions.opacity
|
|
||||||
property bool contentRegionOpacity: Config.options.regionSelector.targetRegions.contentRegionOpacity
|
|
||||||
|
|
||||||
|
// Target
|
||||||
property real targetedRegionX: -1
|
property real targetedRegionX: -1
|
||||||
property real targetedRegionY: -1
|
property real targetedRegionY: -1
|
||||||
property real targetedRegionWidth: 0
|
property real targetedRegionWidth: 0
|
||||||
@@ -175,6 +184,7 @@ PanelWindow {
|
|||||||
property real regionX: Math.min(dragStartX, draggingX)
|
property real regionX: Math.min(dragStartX, draggingX)
|
||||||
property real regionY: Math.min(dragStartY, draggingY)
|
property real regionY: Math.min(dragStartY, draggingY)
|
||||||
|
|
||||||
|
// Screenshot stuff
|
||||||
TempScreenshotProcess {
|
TempScreenshotProcess {
|
||||||
id: screenshotProc
|
id: screenshotProc
|
||||||
running: true
|
running: true
|
||||||
@@ -247,6 +257,7 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Execution after selection
|
||||||
function snip() {
|
function snip() {
|
||||||
// Validity check
|
// Validity check
|
||||||
if (root.regionWidth <= 0 || root.regionHeight <= 0) {
|
if (root.regionWidth <= 0 || root.regionHeight <= 0) {
|
||||||
@@ -277,21 +288,27 @@ PanelWindow {
|
|||||||
screenshotAction, //
|
screenshotAction, //
|
||||||
screenshotDir
|
screenshotDir
|
||||||
)
|
)
|
||||||
snipProc.command = command;
|
Quickshell.execDetached(command);
|
||||||
|
if (root.action == RegionSelection.SnipAction.Record || root.action == RegionSelection.SnipAction.RecordWithSound) {
|
||||||
// Image post-processing
|
root.phase = RegionSelection.Phase.Post
|
||||||
snipProc.startDetached();
|
} else {
|
||||||
root.dismiss();
|
root.dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
|
||||||
id: snipProc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ScreencopyView {
|
// Only clickable in Selection phase
|
||||||
|
mask: Region {
|
||||||
|
item: switch(root.phase) {
|
||||||
|
case RegionSelection.Phase.Select: return mouseArea;
|
||||||
|
case RegionSelection.Phase.Post: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScreencopyView { // For freezing
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
live: false
|
live: false
|
||||||
captureSource: root.screen
|
captureSource: root.screen
|
||||||
|
visible: root.phase === RegionSelection.Phase.Select
|
||||||
|
|
||||||
focus: root.visible
|
focus: root.visible
|
||||||
Keys.onPressed: (event) => { // Esc to close
|
Keys.onPressed: (event) => { // Esc to close
|
||||||
@@ -299,6 +316,7 @@ PanelWindow {
|
|||||||
root.dismiss();
|
root.dismiss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: mouseArea
|
id: mouseArea
|
||||||
@@ -361,6 +379,7 @@ PanelWindow {
|
|||||||
mouseY: mouseArea.mouseY
|
mouseY: mouseArea.mouseY
|
||||||
color: root.selectionBorderColor
|
color: root.selectionBorderColor
|
||||||
overlayColor: root.overlayColor
|
overlayColor: root.overlayColor
|
||||||
|
breathingBorderOnly: root.phase === RegionSelection.Phase.Post
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,8 +394,10 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The thing to the bottom-right with an icon
|
||||||
CursorGuide {
|
CursorGuide {
|
||||||
z: 9999
|
z: 9999
|
||||||
|
visible: root.phase === RegionSelection.Phase.Select
|
||||||
x: root.dragging ? root.regionX + root.regionWidth : mouseArea.mouseX
|
x: root.dragging ? root.regionX + root.regionWidth : mouseArea.mouseX
|
||||||
y: root.dragging ? root.regionY + root.regionHeight : mouseArea.mouseY
|
y: root.dragging ? root.regionY + root.regionHeight : mouseArea.mouseY
|
||||||
action: root.action
|
action: root.action
|
||||||
@@ -386,17 +407,23 @@ PanelWindow {
|
|||||||
// Window regions
|
// Window regions
|
||||||
Repeater {
|
Repeater {
|
||||||
model: ScriptModel {
|
model: ScriptModel {
|
||||||
values: root.enableWindowRegions ? root.windowRegions : []
|
values: {
|
||||||
|
if (root.phase === RegionSelection.Phase.Select && root.enableWindowRegions) {
|
||||||
|
return root.windowRegions
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
delegate: TargetRegion {
|
delegate: TargetRegion {
|
||||||
z: 2
|
z: 2
|
||||||
required property var modelData
|
required property var modelData
|
||||||
clientDimensions: modelData
|
clientDimensions: modelData
|
||||||
showIcon: true
|
showIcon: true
|
||||||
targeted: !root.draggedAway &&
|
targeted: !root.draggedAway && //
|
||||||
(root.targetedRegionX === modelData.at[0]
|
(root.targetedRegionX === modelData.at[0] //
|
||||||
&& root.targetedRegionY === modelData.at[1]
|
&& root.targetedRegionY === modelData.at[1] //
|
||||||
&& root.targetedRegionWidth === modelData.size[0]
|
&& root.targetedRegionWidth === modelData.size[0] //
|
||||||
&& root.targetedRegionHeight === modelData.size[1])
|
&& root.targetedRegionHeight === modelData.size[1])
|
||||||
|
|
||||||
opacity: root.draggedAway ? 0 : root.targetRegionOpacity
|
opacity: root.draggedAway ? 0 : root.targetRegionOpacity
|
||||||
@@ -410,7 +437,13 @@ PanelWindow {
|
|||||||
// Layer regions
|
// Layer regions
|
||||||
Repeater {
|
Repeater {
|
||||||
model: ScriptModel {
|
model: ScriptModel {
|
||||||
values: root.enableLayerRegions ? root.layerRegions : []
|
values: {
|
||||||
|
if (root.phase === RegionSelection.Phase.Select && root.enableLayerRegions) {
|
||||||
|
return root.layerRegions
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
delegate: TargetRegion {
|
delegate: TargetRegion {
|
||||||
z: 3
|
z: 3
|
||||||
@@ -433,7 +466,13 @@ PanelWindow {
|
|||||||
// Content regions
|
// Content regions
|
||||||
Repeater {
|
Repeater {
|
||||||
model: ScriptModel {
|
model: ScriptModel {
|
||||||
values: root.enableContentRegions ? root.imageRegions : []
|
values: {
|
||||||
|
if (root.phase === RegionSelection.Phase.Select && root.enableContentRegions) {
|
||||||
|
return root.imageRegions
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
delegate: TargetRegion {
|
delegate: TargetRegion {
|
||||||
z: 4
|
z: 4
|
||||||
@@ -456,6 +495,7 @@ PanelWindow {
|
|||||||
Row {
|
Row {
|
||||||
id: regionSelectionControls
|
id: regionSelectionControls
|
||||||
z: 10
|
z: 10
|
||||||
|
visible: root.phase === RegionSelection.Phase.Select
|
||||||
anchors {
|
anchors {
|
||||||
horizontalCenter: parent.horizontalCenter
|
horizontalCenter: parent.horizontalCenter
|
||||||
bottom: parent.bottom
|
bottom: parent.bottom
|
||||||
@@ -515,4 +555,3 @@ PanelWindow {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -65,12 +65,16 @@ Scope {
|
|||||||
function record() {
|
function record() {
|
||||||
root.action = RegionSelection.SnipAction.Record
|
root.action = RegionSelection.SnipAction.Record
|
||||||
root.selectionMode = RegionSelection.SelectionMode.RectCorners
|
root.selectionMode = RegionSelection.SelectionMode.RectCorners
|
||||||
|
// If already open then re-trigger to stop recording
|
||||||
|
if (GlobalStates.regionSelectorOpen) GlobalStates.regionSelectorOpen = false
|
||||||
GlobalStates.regionSelectorOpen = true
|
GlobalStates.regionSelectorOpen = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function recordWithSound() {
|
function recordWithSound() {
|
||||||
root.action = RegionSelection.SnipAction.RecordWithSound
|
root.action = RegionSelection.SnipAction.RecordWithSound
|
||||||
root.selectionMode = RegionSelection.SelectionMode.RectCorners
|
root.selectionMode = RegionSelection.SelectionMode.RectCorners
|
||||||
|
// If already open then re-trigger to stop recording
|
||||||
|
if (GlobalStates.regionSelectorOpen) GlobalStates.regionSelectorOpen = false
|
||||||
GlobalStates.regionSelectorOpen = true
|
GlobalStates.regionSelectorOpen = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user