forked from Shinonome/dots-hyprland
background: parallax wallpaper
This commit is contained in:
@@ -11,6 +11,7 @@ import QtQuick.Layouts
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
|
import Quickshell.Hyprland
|
||||||
|
|
||||||
Scope {
|
Scope {
|
||||||
id: root
|
id: root
|
||||||
@@ -26,7 +27,19 @@ Scope {
|
|||||||
id: bgRoot
|
id: bgRoot
|
||||||
|
|
||||||
required property var modelData
|
required property var modelData
|
||||||
|
// Workspaces
|
||||||
|
property HyprlandMonitor monitor: Hyprland.monitorFor(modelData)
|
||||||
|
property list<var> relevantWindows: HyprlandData.windowList.filter(win => win.monitor == monitor.id && win.workspace.id >= 0).sort((a, b) => a.workspace.id - b.workspace.id)
|
||||||
|
property int firstWorkspaceId: relevantWindows[0]?.workspace.id || 1
|
||||||
|
property int lastWorkspaceId: relevantWindows[relevantWindows.length - 1]?.workspace.id || 10
|
||||||
|
// Wallpaper
|
||||||
property string wallpaperPath: Config.options.background.wallpaperPath
|
property string wallpaperPath: Config.options.background.wallpaperPath
|
||||||
|
property real preferredWallpaperScale: Config.options.background.parallax.workspaceZoom
|
||||||
|
property real effectiveWallpaperScale: 1 // Some reasonable init value, to be updated
|
||||||
|
property int wallpaperWidth: modelData.width // Some reasonable init value, to be updated
|
||||||
|
property int wallpaperHeight: modelData.height // Some reasonable init value, to be updated
|
||||||
|
property real movableXSpace: (effectiveWallpaperScale - 1) / 2 * screen.width
|
||||||
|
property real movableYSpace: (effectiveWallpaperScale - 1) / 2 * screen.height
|
||||||
// Position
|
// Position
|
||||||
property real clockX: (modelData.width / 2) + ((Math.random() < 0.5 ? -1 : 1) * modelData.width)
|
property real clockX: (modelData.width / 2) + ((Math.random() < 0.5 ? -1 : 1) * modelData.width)
|
||||||
property real clockY: (modelData.height / 2) + ((Math.random() < 0.5 ? -1 : 1) * modelData.height)
|
property real clockY: (modelData.height / 2) + ((Math.random() < 0.5 ? -1 : 1) * modelData.height)
|
||||||
@@ -50,29 +63,63 @@ Scope {
|
|||||||
}
|
}
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
|
onWallpaperPathChanged: {
|
||||||
|
bgRoot.updateZoomScale()
|
||||||
|
// Clock position gets updated after zoom scale is updated
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wallpaper zoom scale
|
||||||
|
function updateZoomScale() {
|
||||||
|
getWallpaperSizeProc.path = bgRoot.wallpaperPath
|
||||||
|
getWallpaperSizeProc.running = true;
|
||||||
|
}
|
||||||
|
Process {
|
||||||
|
id: getWallpaperSizeProc
|
||||||
|
property string path: bgRoot.wallpaperPath
|
||||||
|
command: [ "magick", "identify", "-format", "%w %h", path ]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
id: wallpaperSizeOutputCollector
|
||||||
|
onStreamFinished: {
|
||||||
|
const output = wallpaperSizeOutputCollector.text
|
||||||
|
const [width, height] = output.split(" ").map(Number);
|
||||||
|
bgRoot.wallpaperWidth = width
|
||||||
|
bgRoot.wallpaperHeight = height
|
||||||
|
bgRoot.effectiveWallpaperScale = Math.max(1, Math.min(
|
||||||
|
bgRoot.preferredWallpaperScale,
|
||||||
|
width / bgRoot.screen.width,
|
||||||
|
height / bgRoot.screen.height
|
||||||
|
));
|
||||||
|
|
||||||
|
bgRoot.updateClockPosition()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Clock positioning
|
// Clock positioning
|
||||||
function updateClockPosition() {
|
function updateClockPosition() {
|
||||||
leastBusyRegionProc.path = wallpaperPath // Somehow this is needed to make the proc correctly use the new path
|
// Somehow all this manual setting is needed to make the proc correctly use the new values
|
||||||
|
leastBusyRegionProc.path = bgRoot.wallpaperPath
|
||||||
leastBusyRegionProc.contentWidth = clock.implicitWidth
|
leastBusyRegionProc.contentWidth = clock.implicitWidth
|
||||||
leastBusyRegionProc.contentHeight = clock.implicitHeight
|
leastBusyRegionProc.contentHeight = clock.implicitHeight
|
||||||
|
leastBusyRegionProc.horizontalPadding = (effectiveWallpaperScale - 1) / 2 * screen.width + 100
|
||||||
|
leastBusyRegionProc.verticalPadding = (effectiveWallpaperScale - 1) / 2 * screen.height + 100
|
||||||
leastBusyRegionProc.running = false;
|
leastBusyRegionProc.running = false;
|
||||||
leastBusyRegionProc.running = true;
|
leastBusyRegionProc.running = true;
|
||||||
}
|
}
|
||||||
onWallpaperPathChanged: {
|
|
||||||
// console.log("[Background] Wallpaper path changed to:", wallpaperPath)
|
|
||||||
bgRoot.updateClockPosition()
|
|
||||||
}
|
|
||||||
Process {
|
Process {
|
||||||
id: leastBusyRegionProc
|
id: leastBusyRegionProc
|
||||||
running: true
|
|
||||||
property string path: bgRoot.wallpaperPath
|
property string path: bgRoot.wallpaperPath
|
||||||
property int contentWidth: bgRoot.screen.width
|
property int contentWidth: 300
|
||||||
property int contentHeight: bgRoot.screen.height
|
property int contentHeight: 300
|
||||||
|
property int horizontalPadding: bgRoot.movableXSpace
|
||||||
|
property int verticalPadding: bgRoot.movableYSpace
|
||||||
command: [Quickshell.configPath("scripts/images/least_busy_region.py"),
|
command: [Quickshell.configPath("scripts/images/least_busy_region.py"),
|
||||||
"--screen-width", bgRoot.screen.width,
|
"--screen-width", bgRoot.screen.width,
|
||||||
"--screen-height", bgRoot.screen.height,
|
"--screen-height", bgRoot.screen.height,
|
||||||
"--width", contentWidth,
|
"--width", contentWidth,
|
||||||
"--height", contentHeight,
|
"--height", contentHeight,
|
||||||
|
"--horizontal-padding", horizontalPadding,
|
||||||
|
"--vertical-padding", verticalPadding,
|
||||||
path
|
path
|
||||||
]
|
]
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
@@ -91,58 +138,79 @@ Scope {
|
|||||||
|
|
||||||
// Wallpaper
|
// Wallpaper
|
||||||
Image {
|
Image {
|
||||||
z: 0
|
property real value // 0 to 1, for offset
|
||||||
anchors.fill: parent
|
value: {
|
||||||
|
// Range = half-groups that workspaces span on
|
||||||
|
const chunkSize = 3;
|
||||||
|
const lower = Math.floor(bgRoot.firstWorkspaceId / chunkSize) * chunkSize;
|
||||||
|
const upper = Math.ceil(bgRoot.lastWorkspaceId / chunkSize) * chunkSize;
|
||||||
|
const range = upper - lower;
|
||||||
|
return (bgRoot.monitor.activeWorkspace.id - lower) / range;
|
||||||
|
}
|
||||||
|
property real effectiveValue: Math.max(0, Math.min(1, value))
|
||||||
|
x: -(bgRoot.movableXSpace) - (effectiveValue - 0.5) * 2 * bgRoot.movableXSpace
|
||||||
|
y: -(bgRoot.movableYSpace)
|
||||||
source: bgRoot.wallpaperPath
|
source: bgRoot.wallpaperPath
|
||||||
fillMode: Image.PreserveAspectCrop
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
Behavior on x {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 600
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
sourceSize {
|
sourceSize {
|
||||||
width: bgRoot.screen.width
|
width: bgRoot.screen.width * bgRoot.effectiveWallpaperScale
|
||||||
height: bgRoot.screen.height
|
height: bgRoot.screen.height * bgRoot.effectiveWallpaperScale
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The clock
|
|
||||||
Item {
|
|
||||||
id: clock
|
|
||||||
z: 1
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
top: parent.top
|
|
||||||
leftMargin: (root.fixedClockPosition ? root.fixedClockX : bgRoot.clockX) - implicitWidth / 2
|
|
||||||
topMargin: (root.fixedClockPosition ? root.fixedClockY : bgRoot.clockY) - implicitHeight / 2
|
|
||||||
Behavior on leftMargin {
|
|
||||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
|
||||||
}
|
|
||||||
Behavior on topMargin {
|
|
||||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitWidth: clockColumn.implicitWidth
|
// The clock
|
||||||
implicitHeight: clockColumn.implicitHeight
|
Item {
|
||||||
|
id: clock
|
||||||
ColumnLayout {
|
anchors {
|
||||||
id: clockColumn
|
left: parent.left
|
||||||
anchors.centerIn: parent
|
top: parent.top
|
||||||
spacing: -5
|
leftMargin: ((root.fixedClockPosition ? root.fixedClockX : bgRoot.clockX * bgRoot.effectiveWallpaperScale) - implicitWidth / 2)
|
||||||
|
topMargin: ((root.fixedClockPosition ? root.fixedClockY : bgRoot.clockY * bgRoot.effectiveWallpaperScale) - implicitHeight / 2)
|
||||||
StyledText {
|
Behavior on leftMargin {
|
||||||
Layout.fillWidth: true
|
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||||
horizontalAlignment: bgRoot.textHorizontalAlignment
|
}
|
||||||
font.pixelSize: 95
|
Behavior on topMargin {
|
||||||
color: bgRoot.colText
|
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||||
style: Text.Raised
|
}
|
||||||
styleColor: Appearance.colors.colShadow
|
|
||||||
text: DateTime.time
|
|
||||||
}
|
}
|
||||||
StyledText {
|
|
||||||
Layout.fillWidth: true
|
implicitWidth: clockColumn.implicitWidth
|
||||||
horizontalAlignment: bgRoot.textHorizontalAlignment
|
implicitHeight: clockColumn.implicitHeight
|
||||||
font.pixelSize: 25
|
|
||||||
color: bgRoot.colText
|
ColumnLayout {
|
||||||
style: Text.Raised
|
id: clockColumn
|
||||||
styleColor: Appearance.colors.colShadow
|
anchors.centerIn: parent
|
||||||
text: DateTime.date
|
spacing: -5
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
horizontalAlignment: bgRoot.textHorizontalAlignment
|
||||||
|
font {
|
||||||
|
pixelSize: 85
|
||||||
|
weight: Font.Medium
|
||||||
|
}
|
||||||
|
color: bgRoot.colText
|
||||||
|
style: Text.Raised
|
||||||
|
styleColor: Appearance.colors.colShadow
|
||||||
|
text: DateTime.time
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
horizontalAlignment: bgRoot.textHorizontalAlignment
|
||||||
|
font {
|
||||||
|
pixelSize: 20
|
||||||
|
weight: Font.Medium
|
||||||
|
}
|
||||||
|
color: bgRoot.colText
|
||||||
|
style: Text.Raised
|
||||||
|
styleColor: Appearance.colors.colShadow
|
||||||
|
text: DateTime.date
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,10 @@ Singleton {
|
|||||||
property real clockX: -500
|
property real clockX: -500
|
||||||
property real clockY: -500
|
property real clockY: -500
|
||||||
property string wallpaperPath: Quickshell.configPath("assets/images/default_wallpaper.png")
|
property string wallpaperPath: Quickshell.configPath("assets/images/default_wallpaper.png")
|
||||||
|
property JsonObject parallax: JsonObject {
|
||||||
|
property real workspaceZoom: 1.07 // Relative to your screen, not wallpaper size
|
||||||
|
property bool enableWorkspace: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
property JsonObject bar: JsonObject {
|
property JsonObject bar: JsonObject {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ def center_crop(img, target_w, target_h):
|
|||||||
y2 = y1 + target_h
|
y2 = y1 + target_h
|
||||||
return img[y1:y2, x1:x2]
|
return img[y1:y2, x1:x2]
|
||||||
|
|
||||||
def find_least_busy_region(image_path, region_width=300, region_height=200, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode="fill", padding=50):
|
def find_least_busy_region(image_path, region_width=300, region_height=200, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode="fill", horizontal_padding=50, vertical_padding=50):
|
||||||
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
|
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
|
||||||
if img is None:
|
if img is None:
|
||||||
raise FileNotFoundError(f"Image not found: {image_path}")
|
raise FileNotFoundError(f"Image not found: {image_path}")
|
||||||
@@ -59,10 +59,10 @@ def find_least_busy_region(image_path, region_width=300, region_height=200, scre
|
|||||||
min_var = None
|
min_var = None
|
||||||
min_coords = (0, 0)
|
min_coords = (0, 0)
|
||||||
area = region_width * region_height
|
area = region_width * region_height
|
||||||
x_start = padding
|
x_start = horizontal_padding
|
||||||
y_start = padding
|
y_start = vertical_padding
|
||||||
x_end = w - region_width - padding + 1
|
x_end = w - region_width - horizontal_padding + 1
|
||||||
y_end = h - region_height - padding + 1
|
y_end = h - region_height - vertical_padding + 1
|
||||||
for y in range(y_start, max(y_end, y_start+1), stride):
|
for y in range(y_start, max(y_end, y_start+1), stride):
|
||||||
for x in range(x_start, max(x_end, x_start+1), stride):
|
for x in range(x_start, max(x_end, x_start+1), stride):
|
||||||
x1, y1 = x, y
|
x1, y1 = x, y
|
||||||
@@ -76,7 +76,7 @@ def find_least_busy_region(image_path, region_width=300, region_height=200, scre
|
|||||||
min_coords = (x, y)
|
min_coords = (x, y)
|
||||||
return min_coords, min_var
|
return min_coords, min_var
|
||||||
|
|
||||||
def find_largest_region(image_path, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode="fill", threshold=100.0, aspect_ratio=1.0, padding=50):
|
def find_largest_region(image_path, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode="fill", threshold=100.0, aspect_ratio=1.0, horizontal_padding=50, vertical_padding=50):
|
||||||
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
|
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
|
||||||
if img is None:
|
if img is None:
|
||||||
raise FileNotFoundError(f"Image not found: {image_path}")
|
raise FileNotFoundError(f"Image not found: {image_path}")
|
||||||
@@ -130,10 +130,10 @@ def find_largest_region(image_path, screen_width=None, screen_height=None, verbo
|
|||||||
max_size = mid - 1
|
max_size = mid - 1
|
||||||
continue
|
continue
|
||||||
found = False
|
found = False
|
||||||
x_start = padding
|
x_start = horizontal_padding
|
||||||
y_start = padding
|
y_start = vertical_padding
|
||||||
x_end = w - region_w - padding + 1
|
x_end = w - region_w - horizontal_padding + 1
|
||||||
y_end = h - region_h - padding + 1
|
y_end = h - region_h - vertical_padding + 1
|
||||||
for y in range(y_start, max(y_end, y_start+1), stride):
|
for y in range(y_start, max(y_end, y_start+1), stride):
|
||||||
for x in range(x_start, max(x_end, x_start+1), stride):
|
for x in range(x_start, max(x_end, x_start+1), stride):
|
||||||
x1, y1 = x, y
|
x1, y1 = x, y
|
||||||
@@ -263,7 +263,8 @@ def main():
|
|||||||
parser.add_argument("-l", "--largest-region", action="store_true", help="Find the largest region under the variance threshold and output its center")
|
parser.add_argument("-l", "--largest-region", action="store_true", help="Find the largest region under the variance threshold and output its center")
|
||||||
parser.add_argument("-t", "--variance-threshold", type=float, default=1000.0, help="Variance threshold for largest region mode")
|
parser.add_argument("-t", "--variance-threshold", type=float, default=1000.0, help="Variance threshold for largest region mode")
|
||||||
parser.add_argument("--aspect-ratio", type=float, default=1.78, help="Aspect ratio (width/height) for largest region mode")
|
parser.add_argument("--aspect-ratio", type=float, default=1.78, help="Aspect ratio (width/height) for largest region mode")
|
||||||
parser.add_argument("--padding", type=int, default=50, help="Minimum distance from region to image edge (default: 50)")
|
parser.add_argument("--horizontal-padding", "-hp", type=int, default=50, help="Minimum horizontal distance from region to image edge")
|
||||||
|
parser.add_argument("--vertical-padding", "-vp", type=int, default=50, help="Minimum vertical distance from region to image edge")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.largest_region:
|
if args.largest_region:
|
||||||
@@ -276,7 +277,8 @@ def main():
|
|||||||
screen_mode=args.screen_mode,
|
screen_mode=args.screen_mode,
|
||||||
threshold=args.variance_threshold,
|
threshold=args.variance_threshold,
|
||||||
aspect_ratio=args.aspect_ratio,
|
aspect_ratio=args.aspect_ratio,
|
||||||
padding=args.padding
|
horizontal_padding=args.horizontal_padding,
|
||||||
|
vertical_padding=args.vertical_padding
|
||||||
)
|
)
|
||||||
if center:
|
if center:
|
||||||
if args.visual_output:
|
if args.visual_output:
|
||||||
@@ -312,7 +314,8 @@ def main():
|
|||||||
verbose=args.verbose,
|
verbose=args.verbose,
|
||||||
stride=args.stride,
|
stride=args.stride,
|
||||||
screen_mode=args.screen_mode,
|
screen_mode=args.screen_mode,
|
||||||
padding=args.padding
|
horizontal_padding=args.horizontal_padding,
|
||||||
|
vertical_padding=args.vertical_padding
|
||||||
)
|
)
|
||||||
if args.visual_output:
|
if args.visual_output:
|
||||||
draw_region(args.image_path, coords, region_width=args.width, region_height=args.height, screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode)
|
draw_region(args.image_path, coords, region_width=args.width, region_height=args.height, screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode)
|
||||||
|
|||||||
Reference in New Issue
Block a user