forked from Shinonome/dots-hyprland
Merge branch 'end-4:main' into parallax
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
import QtQuick
|
||||
|
||||
// QtObject that allows stuff to be freely declared inside
|
||||
QtObject {
|
||||
default property list<QtObject> data
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQml
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
import qs.services
|
||||
import "../"
|
||||
|
||||
NestableObject {
|
||||
id: root
|
||||
|
||||
required property string key
|
||||
property alias fetching: fetchProc.running
|
||||
property bool set
|
||||
property var value
|
||||
|
||||
Component.onCompleted: fetch()
|
||||
|
||||
Connections {
|
||||
target: HyprlandConfig
|
||||
function onReloaded() {
|
||||
root.fetch();
|
||||
}
|
||||
}
|
||||
|
||||
function fetch() {
|
||||
fetchProc.command = fetchProc.baseCommand.concat([root.key]);
|
||||
fetchProc.running = true;
|
||||
}
|
||||
|
||||
function setValue(newValue) {
|
||||
HyprlandConfig.set(root.key, newValue)
|
||||
}
|
||||
|
||||
function reset() {
|
||||
HyprlandConfig.reset(root.key)
|
||||
}
|
||||
|
||||
Process {
|
||||
id: fetchProc
|
||||
property list<string> baseCommand: ["hyprctl", "getoption", "-j"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text == "no such option")
|
||||
return;
|
||||
try {
|
||||
const obj = JSON.parse(text);
|
||||
// Note that the value is returned as "<data type>": <value>
|
||||
// It's the only field that isn't always in the same key so we put it in an else
|
||||
for (const key in obj) {
|
||||
if (key == "option")
|
||||
continue;
|
||||
else if (key == "set")
|
||||
root.set = obj[key];
|
||||
else
|
||||
root.value = obj[key];
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`[HyprlandConfigOption] Failed to fetch option "${root.key}":\n - Output: ${text.trim()}\n - Error: ${e}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -8,10 +8,10 @@ QuickToggleModel {
|
||||
name: Translation.tr("Anti-flashbang")
|
||||
tooltipText: Translation.tr("Anti-flashbang")
|
||||
icon: "flash_off"
|
||||
toggled: Config.options.light.antiFlashbang.enable
|
||||
toggled: HyprlandAntiFlashbangShader.enabled
|
||||
|
||||
mainAction: () => {
|
||||
Config.options.light.antiFlashbang.enable = !Config.options.light.antiFlashbang.enable;
|
||||
HyprlandAntiFlashbangShader.toggle()
|
||||
}
|
||||
hasMenu: true
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
import qs.modules.common.models.hyprland
|
||||
import qs.services
|
||||
|
||||
QuickToggleModel {
|
||||
id: root
|
||||
name: Translation.tr("Game mode")
|
||||
toggled: toggled
|
||||
toggled: !confOpt.value
|
||||
icon: "gamepad"
|
||||
|
||||
mainAction: () => {
|
||||
@@ -34,13 +35,11 @@ QuickToggleModel {
|
||||
]);
|
||||
}
|
||||
}
|
||||
Process {
|
||||
id: fetchActiveState
|
||||
running: true
|
||||
command: ["bash", "-c", `test "$(hyprctl getoption animations:enabled -j | jq ".int")" -ne 0`]
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
root.toggled = exitCode !== 0; // Inverted because enabled = nonzero exit
|
||||
}
|
||||
|
||||
HyprlandConfigOption {
|
||||
id: confOpt
|
||||
key: "animations:enabled"
|
||||
}
|
||||
|
||||
tooltipText: Translation.tr("Game mode")
|
||||
}
|
||||
|
||||
@@ -29,6 +29,11 @@ Item {
|
||||
}
|
||||
|
||||
function setExtraWindowAndGrabFocus(window) {
|
||||
if (root.activeMenu && root.activeMenu !== window) {
|
||||
if (typeof root.activeMenu.close === "function")
|
||||
root.activeMenu.close();
|
||||
root.activeMenu = null;
|
||||
}
|
||||
root.activeMenu = window;
|
||||
root.grabFocus();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Services.SystemTray
|
||||
@@ -26,7 +27,11 @@ MouseArea {
|
||||
item.activate();
|
||||
break;
|
||||
case Qt.RightButton:
|
||||
if (item.hasMenu) menu.open();
|
||||
if (item.hasMenu)
|
||||
if (menu.active && menu.item && typeof menu.item.close === "function")
|
||||
menu.item.close();
|
||||
else
|
||||
menu.open();
|
||||
break;
|
||||
}
|
||||
event.accepted = true;
|
||||
@@ -44,14 +49,16 @@ MouseArea {
|
||||
sourceComponent: SysTrayMenu {
|
||||
Component.onCompleted: this.open();
|
||||
trayItemMenuHandle: root.item.menu
|
||||
trayItemId: root.item.id
|
||||
anchor {
|
||||
window: root.QsWindow.window
|
||||
rect.x: root.x + (Config.options.bar.vertical ? 0 : QsWindow.window?.width)
|
||||
rect.y: root.y + (Config.options.bar.vertical ? QsWindow.window?.height : 0)
|
||||
rect.height: root.height
|
||||
rect.width: root.width
|
||||
edges: Config.options.bar.bottom ? (Edges.Top | Edges.Left) : (Edges.Bottom | Edges.Right)
|
||||
gravity: Config.options.bar.bottom ? (Edges.Top | Edges.Left) : (Edges.Bottom | Edges.Right)
|
||||
item: root
|
||||
gravity: Config.options.bar.vertical
|
||||
? (Config.options.bar.bottom ? Edges.Left : Edges.Right)
|
||||
: (Config.options.bar.bottom ? Edges.Top : Edges.Bottom)
|
||||
edges: Config.options.bar.vertical
|
||||
? (Config.options.bar.bottom ? Edges.Left : Edges.Right)
|
||||
: (Config.options.bar.bottom ? Edges.Top : Edges.Bottom)
|
||||
}
|
||||
onMenuOpened: (window) => root.menuOpened(window);
|
||||
onMenuClosed: {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
@@ -9,6 +11,7 @@ import Quickshell
|
||||
PopupWindow {
|
||||
id: root
|
||||
required property QsMenuHandle trayItemMenuHandle
|
||||
property string trayItemId: ""
|
||||
property real popupBackgroundMargin: 0
|
||||
|
||||
signal menuClosed
|
||||
@@ -173,6 +176,48 @@ PopupWindow {
|
||||
}
|
||||
}
|
||||
}
|
||||
RippleButton {
|
||||
id: pinEntry
|
||||
buttonRadius: popupBackground.radius - popupBackground.padding
|
||||
horizontalPadding: 12
|
||||
implicitWidth: contentItem.implicitWidth + horizontalPadding * 2
|
||||
implicitHeight: 36
|
||||
Layout.topMargin: 0
|
||||
Layout.bottomMargin: 0
|
||||
Layout.fillWidth: true
|
||||
|
||||
visible: root.trayItemId !== undefined && root.trayItemId.length > 0 && stackView.depth === 1
|
||||
releaseAction: () => TrayService.togglePin(root.trayItemId);
|
||||
|
||||
contentItem: RowLayout {
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
leftMargin: pinEntry.horizontalPadding
|
||||
rightMargin: pinEntry.horizontalPadding
|
||||
}
|
||||
spacing: 8
|
||||
|
||||
MaterialSymbol {
|
||||
iconSize: 18
|
||||
text: "push_pin"
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: TrayService.isPinned(root.trayItemId) ? Translation.tr("Unpin") : Translation.tr("Pin")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: 1
|
||||
color: Appearance.colors.colSubtext
|
||||
Layout.topMargin: 4
|
||||
Layout.bottomMargin: 4
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: menuEntriesRepeater
|
||||
|
||||
+17
-23
@@ -14,11 +14,14 @@ Item {
|
||||
required property color overlayColor
|
||||
property bool showAimLines: Config.options.regionSelector.rect.showAimLines
|
||||
|
||||
property bool breathingBorderOnly: false
|
||||
|
||||
// Overlay to darken screen
|
||||
// Base dark overlay around region
|
||||
Rectangle {
|
||||
id: darkenOverlay
|
||||
z: 1
|
||||
visible: !root.breathingBorderOnly
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
@@ -32,25 +35,6 @@ Item {
|
||||
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 {
|
||||
id: selectionBorder
|
||||
z: 9
|
||||
@@ -64,13 +48,23 @@ Item {
|
||||
height: Math.round(root.regionHeight) + borderWidth * 2
|
||||
|
||||
color: root.color
|
||||
dashLength: 6
|
||||
gapLength: 3
|
||||
dashLength: 8
|
||||
gapLength: 4
|
||||
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 {
|
||||
z: 2
|
||||
visible: !root.breathingBorderOnly
|
||||
anchors {
|
||||
top: selectionBorder.bottom
|
||||
right: selectionBorder.right
|
||||
@@ -82,7 +76,7 @@ Item {
|
||||
|
||||
// Coord lines
|
||||
Rectangle { // Vertical
|
||||
visible: root.showAimLines
|
||||
visible: root.showAimLines && !root.breathingBorderOnly
|
||||
opacity: 0.2
|
||||
z: 2
|
||||
x: root.mouseX
|
||||
@@ -94,7 +88,7 @@ Item {
|
||||
color: root.color
|
||||
}
|
||||
Rectangle { // Horizontal
|
||||
visible: root.showAimLines
|
||||
visible: root.showAimLines && !root.breathingBorderOnly
|
||||
opacity: 0.2
|
||||
z: 2
|
||||
y: root.mouseY
|
||||
|
||||
@@ -27,13 +27,17 @@ PanelWindow {
|
||||
bottom: true
|
||||
}
|
||||
|
||||
// Modes
|
||||
// TODO: Ask: sidebar AI
|
||||
enum SnipAction { Copy, Edit, Search, CharRecognition, Record, RecordWithSound }
|
||||
enum SelectionMode { RectCorners, Circle }
|
||||
enum Phase { Select, Post }
|
||||
property var action: RegionSelection.SnipAction.Copy
|
||||
property var selectionMode: RegionSelection.SelectionMode.RectCorners
|
||||
property var phase: RegionSelection.Phase.Select
|
||||
signal dismiss()
|
||||
|
||||
// Styles
|
||||
property string screenshotDir: Directories.screenshotTemp
|
||||
property color overlayColor: ColorUtils.transparentize("#000000", 0.4)
|
||||
property color brightText: Appearance.m3colors.darkmode ? Appearance.colors.colOnLayer0 : Appearance.colors.colLayer0
|
||||
@@ -46,6 +50,10 @@ PanelWindow {
|
||||
property color imageBorderColor: brightTertiary
|
||||
property color imageFillColor: ColorUtils.transparentize(imageBorderColor, 0.85)
|
||||
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) => {
|
||||
// Sort floating=true windows before others
|
||||
if (a.floating === b.floating) return 0;
|
||||
@@ -54,6 +62,7 @@ PanelWindow {
|
||||
readonly property var layers: HyprlandData.layers
|
||||
readonly property real falsePositivePreventionRatio: 0.5
|
||||
|
||||
// Screen & interaction vars
|
||||
readonly property HyprlandMonitor hyprlandMonitor: Hyprland.monitorFor(screen)
|
||||
readonly property real monitorScale: hyprlandMonitor.scale
|
||||
readonly property real monitorOffsetX: hyprlandMonitor.x
|
||||
@@ -105,13 +114,13 @@ PanelWindow {
|
||||
return offsetAdjustedLayers;
|
||||
}
|
||||
|
||||
// Config
|
||||
property bool isCircleSelection: (root.selectionMode === RegionSelection.SelectionMode.Circle)
|
||||
property bool enableWindowRegions: Config.options.regionSelector.targetRegions.windows && !isCircleSelection
|
||||
property bool enableLayerRegions: Config.options.regionSelector.targetRegions.layers && !isCircleSelection
|
||||
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 targetedRegionY: -1
|
||||
property real targetedRegionWidth: 0
|
||||
@@ -175,6 +184,7 @@ PanelWindow {
|
||||
property real regionX: Math.min(dragStartX, draggingX)
|
||||
property real regionY: Math.min(dragStartY, draggingY)
|
||||
|
||||
// Screenshot stuff
|
||||
TempScreenshotProcess {
|
||||
id: screenshotProc
|
||||
running: true
|
||||
@@ -247,6 +257,7 @@ PanelWindow {
|
||||
}
|
||||
}
|
||||
|
||||
// Execution after selection
|
||||
function snip() {
|
||||
// Validity check
|
||||
if (root.regionWidth <= 0 || root.regionHeight <= 0) {
|
||||
@@ -277,21 +288,27 @@ PanelWindow {
|
||||
screenshotAction, //
|
||||
screenshotDir
|
||||
)
|
||||
snipProc.command = command;
|
||||
|
||||
// Image post-processing
|
||||
snipProc.startDetached();
|
||||
root.dismiss();
|
||||
Quickshell.execDetached(command);
|
||||
if (root.action == RegionSelection.SnipAction.Record || root.action == RegionSelection.SnipAction.RecordWithSound) {
|
||||
root.phase = RegionSelection.Phase.Post
|
||||
} else {
|
||||
root.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: snipProc
|
||||
// Only clickable in Selection phase
|
||||
mask: Region {
|
||||
item: switch(root.phase) {
|
||||
case RegionSelection.Phase.Select: return mouseArea;
|
||||
case RegionSelection.Phase.Post: return null;
|
||||
}
|
||||
}
|
||||
|
||||
ScreencopyView {
|
||||
ScreencopyView { // For freezing
|
||||
anchors.fill: parent
|
||||
live: false
|
||||
captureSource: root.screen
|
||||
visible: root.phase === RegionSelection.Phase.Select
|
||||
|
||||
focus: root.visible
|
||||
Keys.onPressed: (event) => { // Esc to close
|
||||
@@ -299,220 +316,242 @@ PanelWindow {
|
||||
root.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.CrossCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
hoverEnabled: true
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.CrossCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
hoverEnabled: true
|
||||
|
||||
// Controls
|
||||
onPressed: (mouse) => {
|
||||
root.dragStartX = mouse.x;
|
||||
root.dragStartY = mouse.y;
|
||||
root.draggingX = mouse.x;
|
||||
root.draggingY = mouse.y;
|
||||
root.dragging = true;
|
||||
root.mouseButton = mouse.button;
|
||||
}
|
||||
onReleased: (mouse) => {
|
||||
// Detect if it was a click -> Try to select targeted region
|
||||
if (root.draggingX === root.dragStartX && root.draggingY === root.dragStartY) {
|
||||
if (root.targetedRegionValid()) {
|
||||
root.setRegionToTargeted();
|
||||
}
|
||||
}
|
||||
// Circle dragging?
|
||||
else if (root.selectionMode === RegionSelection.SelectionMode.Circle) {
|
||||
const padding = Config.options.regionSelector.circle.padding + Config.options.regionSelector.circle.strokeWidth / 2;
|
||||
const dragPoints = (root.points.length > 0) ? root.points : [{ x: mouseArea.mouseX, y: mouseArea.mouseY }];
|
||||
const maxX = Math.max(...dragPoints.map(p => p.x));
|
||||
const minX = Math.min(...dragPoints.map(p => p.x));
|
||||
const maxY = Math.max(...dragPoints.map(p => p.y));
|
||||
const minY = Math.min(...dragPoints.map(p => p.y));
|
||||
root.regionX = minX - padding;
|
||||
root.regionY = minY - padding;
|
||||
root.regionWidth = maxX - minX + padding * 2;
|
||||
root.regionHeight = maxY - minY + padding * 2;
|
||||
}
|
||||
root.snip();
|
||||
}
|
||||
onPositionChanged: (mouse) => {
|
||||
root.updateTargetedRegion(mouse.x, mouse.y);
|
||||
if (!root.dragging) return;
|
||||
root.draggingX = mouse.x;
|
||||
root.draggingY = mouse.y;
|
||||
root.dragDiffX = mouse.x - root.dragStartX;
|
||||
root.dragDiffY = mouse.y - root.dragStartY;
|
||||
root.points.push({ x: mouse.x, y: mouse.y });
|
||||
}
|
||||
|
||||
Loader {
|
||||
z: 2
|
||||
anchors.fill: parent
|
||||
active: root.selectionMode === RegionSelection.SelectionMode.RectCorners
|
||||
sourceComponent: RectCornersSelectionDetails {
|
||||
regionX: root.regionX
|
||||
regionY: root.regionY
|
||||
regionWidth: root.regionWidth
|
||||
regionHeight: root.regionHeight
|
||||
mouseX: mouseArea.mouseX
|
||||
mouseY: mouseArea.mouseY
|
||||
color: root.selectionBorderColor
|
||||
overlayColor: root.overlayColor
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
z: 2
|
||||
anchors.fill: parent
|
||||
active: root.selectionMode === RegionSelection.SelectionMode.Circle
|
||||
sourceComponent: CircleSelectionDetails {
|
||||
color: root.selectionBorderColor
|
||||
overlayColor: root.overlayColor
|
||||
points: root.points
|
||||
}
|
||||
}
|
||||
|
||||
CursorGuide {
|
||||
z: 9999
|
||||
x: root.dragging ? root.regionX + root.regionWidth : mouseArea.mouseX
|
||||
y: root.dragging ? root.regionY + root.regionHeight : mouseArea.mouseY
|
||||
action: root.action
|
||||
selectionMode: root.selectionMode
|
||||
}
|
||||
|
||||
// Window regions
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: root.enableWindowRegions ? root.windowRegions : []
|
||||
}
|
||||
delegate: TargetRegion {
|
||||
z: 2
|
||||
required property var modelData
|
||||
clientDimensions: modelData
|
||||
showIcon: true
|
||||
targeted: !root.draggedAway &&
|
||||
(root.targetedRegionX === modelData.at[0]
|
||||
&& root.targetedRegionY === modelData.at[1]
|
||||
&& root.targetedRegionWidth === modelData.size[0]
|
||||
&& root.targetedRegionHeight === modelData.size[1])
|
||||
|
||||
opacity: root.draggedAway ? 0 : root.targetRegionOpacity
|
||||
borderColor: root.windowBorderColor
|
||||
fillColor: targeted ? root.windowFillColor : "transparent"
|
||||
text: `${modelData.class}`
|
||||
radius: Appearance.rounding.windowRounding
|
||||
}
|
||||
}
|
||||
|
||||
// Layer regions
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: root.enableLayerRegions ? root.layerRegions : []
|
||||
}
|
||||
delegate: TargetRegion {
|
||||
z: 3
|
||||
required property var modelData
|
||||
clientDimensions: modelData
|
||||
targeted: !root.draggedAway &&
|
||||
(root.targetedRegionX === modelData.at[0]
|
||||
&& root.targetedRegionY === modelData.at[1]
|
||||
&& root.targetedRegionWidth === modelData.size[0]
|
||||
&& root.targetedRegionHeight === modelData.size[1])
|
||||
|
||||
opacity: root.draggedAway ? 0 : root.targetRegionOpacity
|
||||
borderColor: root.windowBorderColor
|
||||
fillColor: targeted ? root.windowFillColor : "transparent"
|
||||
text: `${modelData.namespace}`
|
||||
radius: Appearance.rounding.windowRounding
|
||||
}
|
||||
}
|
||||
|
||||
// Content regions
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: root.enableContentRegions ? root.imageRegions : []
|
||||
}
|
||||
delegate: TargetRegion {
|
||||
z: 4
|
||||
required property var modelData
|
||||
clientDimensions: modelData
|
||||
targeted: !root.draggedAway &&
|
||||
(root.targetedRegionX === modelData.at[0]
|
||||
&& root.targetedRegionY === modelData.at[1]
|
||||
&& root.targetedRegionWidth === modelData.size[0]
|
||||
&& root.targetedRegionHeight === modelData.size[1])
|
||||
|
||||
opacity: root.draggedAway ? 0 : root.contentRegionOpacity
|
||||
borderColor: root.imageBorderColor
|
||||
fillColor: targeted ? root.imageFillColor : "transparent"
|
||||
text: Translation.tr("Content region")
|
||||
}
|
||||
}
|
||||
|
||||
// Controls
|
||||
Row {
|
||||
id: regionSelectionControls
|
||||
z: 10
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
bottom: parent.bottom
|
||||
bottomMargin: -height
|
||||
}
|
||||
opacity: 0
|
||||
Connections {
|
||||
target: root
|
||||
function onVisibleChanged() {
|
||||
if (!visible) return;
|
||||
regionSelectionControls.anchors.bottomMargin = 8;
|
||||
regionSelectionControls.opacity = 1;
|
||||
}
|
||||
}
|
||||
Behavior on opacity {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
Behavior on anchors.bottomMargin {
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
spacing: 6
|
||||
|
||||
OptionsToolbar {
|
||||
Synchronizer on action {
|
||||
property alias source: root.action
|
||||
}
|
||||
Synchronizer on selectionMode {
|
||||
property alias source: root.selectionMode
|
||||
}
|
||||
onDismiss: root.dismiss();
|
||||
}
|
||||
Item {
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
implicitWidth: closeFab.implicitWidth
|
||||
implicitHeight: closeFab.implicitHeight
|
||||
StyledRectangularShadow {
|
||||
target: closeFab
|
||||
radius: closeFab.buttonRadius
|
||||
}
|
||||
FloatingActionButton {
|
||||
id: closeFab
|
||||
baseSize: 48
|
||||
iconText: "close"
|
||||
onClicked: root.dismiss();
|
||||
StyledToolTip {
|
||||
text: Translation.tr("Close")
|
||||
}
|
||||
colBackground: Appearance.colors.colTertiaryContainer
|
||||
colBackgroundHover: Appearance.colors.colTertiaryContainerHover
|
||||
colRipple: Appearance.colors.colTertiaryContainerActive
|
||||
colOnBackground: Appearance.colors.colOnTertiaryContainer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Controls
|
||||
onPressed: (mouse) => {
|
||||
root.dragStartX = mouse.x;
|
||||
root.dragStartY = mouse.y;
|
||||
root.draggingX = mouse.x;
|
||||
root.draggingY = mouse.y;
|
||||
root.dragging = true;
|
||||
root.mouseButton = mouse.button;
|
||||
}
|
||||
onReleased: (mouse) => {
|
||||
// Detect if it was a click -> Try to select targeted region
|
||||
if (root.draggingX === root.dragStartX && root.draggingY === root.dragStartY) {
|
||||
if (root.targetedRegionValid()) {
|
||||
root.setRegionToTargeted();
|
||||
}
|
||||
}
|
||||
// Circle dragging?
|
||||
else if (root.selectionMode === RegionSelection.SelectionMode.Circle) {
|
||||
const padding = Config.options.regionSelector.circle.padding + Config.options.regionSelector.circle.strokeWidth / 2;
|
||||
const dragPoints = (root.points.length > 0) ? root.points : [{ x: mouseArea.mouseX, y: mouseArea.mouseY }];
|
||||
const maxX = Math.max(...dragPoints.map(p => p.x));
|
||||
const minX = Math.min(...dragPoints.map(p => p.x));
|
||||
const maxY = Math.max(...dragPoints.map(p => p.y));
|
||||
const minY = Math.min(...dragPoints.map(p => p.y));
|
||||
root.regionX = minX - padding;
|
||||
root.regionY = minY - padding;
|
||||
root.regionWidth = maxX - minX + padding * 2;
|
||||
root.regionHeight = maxY - minY + padding * 2;
|
||||
}
|
||||
root.snip();
|
||||
}
|
||||
onPositionChanged: (mouse) => {
|
||||
root.updateTargetedRegion(mouse.x, mouse.y);
|
||||
if (!root.dragging) return;
|
||||
root.draggingX = mouse.x;
|
||||
root.draggingY = mouse.y;
|
||||
root.dragDiffX = mouse.x - root.dragStartX;
|
||||
root.dragDiffY = mouse.y - root.dragStartY;
|
||||
root.points.push({ x: mouse.x, y: mouse.y });
|
||||
}
|
||||
|
||||
Loader {
|
||||
z: 2
|
||||
anchors.fill: parent
|
||||
active: root.selectionMode === RegionSelection.SelectionMode.RectCorners
|
||||
sourceComponent: RectCornersSelectionDetails {
|
||||
regionX: root.regionX
|
||||
regionY: root.regionY
|
||||
regionWidth: root.regionWidth
|
||||
regionHeight: root.regionHeight
|
||||
mouseX: mouseArea.mouseX
|
||||
mouseY: mouseArea.mouseY
|
||||
color: root.selectionBorderColor
|
||||
overlayColor: root.overlayColor
|
||||
breathingBorderOnly: root.phase === RegionSelection.Phase.Post
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
z: 2
|
||||
anchors.fill: parent
|
||||
active: root.selectionMode === RegionSelection.SelectionMode.Circle
|
||||
sourceComponent: CircleSelectionDetails {
|
||||
color: root.selectionBorderColor
|
||||
overlayColor: root.overlayColor
|
||||
points: root.points
|
||||
}
|
||||
}
|
||||
|
||||
// The thing to the bottom-right with an icon
|
||||
CursorGuide {
|
||||
z: 9999
|
||||
visible: root.phase === RegionSelection.Phase.Select
|
||||
x: root.dragging ? root.regionX + root.regionWidth : mouseArea.mouseX
|
||||
y: root.dragging ? root.regionY + root.regionHeight : mouseArea.mouseY
|
||||
action: root.action
|
||||
selectionMode: root.selectionMode
|
||||
}
|
||||
|
||||
// Window regions
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
if (root.phase === RegionSelection.Phase.Select && root.enableWindowRegions) {
|
||||
return root.windowRegions
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
delegate: TargetRegion {
|
||||
z: 2
|
||||
required property var modelData
|
||||
clientDimensions: modelData
|
||||
showIcon: true
|
||||
targeted: !root.draggedAway && //
|
||||
(root.targetedRegionX === modelData.at[0] //
|
||||
&& root.targetedRegionY === modelData.at[1] //
|
||||
&& root.targetedRegionWidth === modelData.size[0] //
|
||||
&& root.targetedRegionHeight === modelData.size[1])
|
||||
|
||||
opacity: root.draggedAway ? 0 : root.targetRegionOpacity
|
||||
borderColor: root.windowBorderColor
|
||||
fillColor: targeted ? root.windowFillColor : "transparent"
|
||||
text: `${modelData.class}`
|
||||
radius: Appearance.rounding.windowRounding
|
||||
}
|
||||
}
|
||||
|
||||
// Layer regions
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
if (root.phase === RegionSelection.Phase.Select && root.enableLayerRegions) {
|
||||
return root.layerRegions
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
delegate: TargetRegion {
|
||||
z: 3
|
||||
required property var modelData
|
||||
clientDimensions: modelData
|
||||
targeted: !root.draggedAway &&
|
||||
(root.targetedRegionX === modelData.at[0]
|
||||
&& root.targetedRegionY === modelData.at[1]
|
||||
&& root.targetedRegionWidth === modelData.size[0]
|
||||
&& root.targetedRegionHeight === modelData.size[1])
|
||||
|
||||
opacity: root.draggedAway ? 0 : root.targetRegionOpacity
|
||||
borderColor: root.windowBorderColor
|
||||
fillColor: targeted ? root.windowFillColor : "transparent"
|
||||
text: `${modelData.namespace}`
|
||||
radius: Appearance.rounding.windowRounding
|
||||
}
|
||||
}
|
||||
|
||||
// Content regions
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
if (root.phase === RegionSelection.Phase.Select && root.enableContentRegions) {
|
||||
return root.imageRegions
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
delegate: TargetRegion {
|
||||
z: 4
|
||||
required property var modelData
|
||||
clientDimensions: modelData
|
||||
targeted: !root.draggedAway &&
|
||||
(root.targetedRegionX === modelData.at[0]
|
||||
&& root.targetedRegionY === modelData.at[1]
|
||||
&& root.targetedRegionWidth === modelData.size[0]
|
||||
&& root.targetedRegionHeight === modelData.size[1])
|
||||
|
||||
opacity: root.draggedAway ? 0 : root.contentRegionOpacity
|
||||
borderColor: root.imageBorderColor
|
||||
fillColor: targeted ? root.imageFillColor : "transparent"
|
||||
text: Translation.tr("Content region")
|
||||
}
|
||||
}
|
||||
|
||||
// Controls
|
||||
Row {
|
||||
id: regionSelectionControls
|
||||
z: 10
|
||||
visible: root.phase === RegionSelection.Phase.Select
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
bottom: parent.bottom
|
||||
bottomMargin: -height
|
||||
}
|
||||
opacity: 0
|
||||
Connections {
|
||||
target: root
|
||||
function onVisibleChanged() {
|
||||
if (!visible) return;
|
||||
regionSelectionControls.anchors.bottomMargin = 8;
|
||||
regionSelectionControls.opacity = 1;
|
||||
}
|
||||
}
|
||||
Behavior on opacity {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
Behavior on anchors.bottomMargin {
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
spacing: 6
|
||||
|
||||
OptionsToolbar {
|
||||
Synchronizer on action {
|
||||
property alias source: root.action
|
||||
}
|
||||
Synchronizer on selectionMode {
|
||||
property alias source: root.selectionMode
|
||||
}
|
||||
onDismiss: root.dismiss();
|
||||
}
|
||||
Item {
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
implicitWidth: closeFab.implicitWidth
|
||||
implicitHeight: closeFab.implicitHeight
|
||||
StyledRectangularShadow {
|
||||
target: closeFab
|
||||
radius: closeFab.buttonRadius
|
||||
}
|
||||
FloatingActionButton {
|
||||
id: closeFab
|
||||
baseSize: 48
|
||||
iconText: "close"
|
||||
onClicked: root.dismiss();
|
||||
StyledToolTip {
|
||||
text: Translation.tr("Close")
|
||||
}
|
||||
colBackground: Appearance.colors.colTertiaryContainer
|
||||
colBackgroundHover: Appearance.colors.colTertiaryContainerHover
|
||||
colRipple: Appearance.colors.colTertiaryContainerActive
|
||||
colOnBackground: Appearance.colors.colOnTertiaryContainer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,12 +65,16 @@ Scope {
|
||||
function record() {
|
||||
root.action = RegionSelection.SnipAction.Record
|
||||
root.selectionMode = RegionSelection.SelectionMode.RectCorners
|
||||
// If already open then re-trigger to stop recording
|
||||
if (GlobalStates.regionSelectorOpen) GlobalStates.regionSelectorOpen = false
|
||||
GlobalStates.regionSelectorOpen = true
|
||||
}
|
||||
|
||||
function recordWithSound() {
|
||||
root.action = RegionSelection.SnipAction.RecordWithSound
|
||||
root.selectionMode = RegionSelection.SelectionMode.RectCorners
|
||||
// If already open then re-trigger to stop recording
|
||||
if (GlobalStates.regionSelectorOpen) GlobalStates.regionSelectorOpen = false
|
||||
GlobalStates.regionSelectorOpen = true
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.common.functions
|
||||
import qs.services
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Hyprland
|
||||
|
||||
import qs
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
|
||||
RippleButton {
|
||||
id: root
|
||||
|
||||
@@ -42,7 +42,7 @@ WindowDialog {
|
||||
right: parent.right
|
||||
}
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
buttonIcon: "lightbulb"
|
||||
buttonIcon: "check"
|
||||
text: Translation.tr("Enable now")
|
||||
checked: Hyprsunset.active
|
||||
onCheckedChanged: {
|
||||
@@ -102,14 +102,32 @@ WindowDialog {
|
||||
right: parent.right
|
||||
}
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
buttonIcon: "flash_off"
|
||||
text: Translation.tr("Enable")
|
||||
buttonIcon: "filter"
|
||||
text: Translation.tr("Content adjustment")
|
||||
checked: HyprlandAntiFlashbangShader.enabled
|
||||
onCheckedChanged: {
|
||||
if (checked) HyprlandAntiFlashbangShader.enable()
|
||||
else HyprlandAntiFlashbangShader.disable()
|
||||
}
|
||||
StyledToolTip {
|
||||
text: Translation.tr("<b>Dims screen content</b> as needed.<br><br>Pros: Immediately responsive<br>Cons: Expensive and can hurt color accuracy<br><br><i>Uses a Hyprland screen shader</i>")
|
||||
}
|
||||
}
|
||||
|
||||
ConfigSwitch {
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
buttonIcon: "light_mode"
|
||||
text: Translation.tr("Brightness adjustment")
|
||||
checked: Config.options.light.antiFlashbang.enable
|
||||
onCheckedChanged: {
|
||||
Config.options.light.antiFlashbang.enable = checked;
|
||||
}
|
||||
StyledToolTip {
|
||||
text: Translation.tr("Example use case: eroge on one workspace, dark Discord window on another")
|
||||
text: Translation.tr("Adapts the <b>display (physical screen) brightness</b><br><br>Pros: Less expensive, retains colors<br>Cons: Not immediately responsive<br><br><i>Adjusts display brightness after each Hyprland IPC event</i>")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user