Merge branch 'end-4:main' into main

This commit is contained in:
jwihardi
2025-09-27 00:04:30 -04:00
committed by GitHub
96 changed files with 2206 additions and 1372 deletions
+2
View File
@@ -33,6 +33,7 @@ bindd = Super, N, Toggle right sidebar, global, quickshell:sidebarRightToggle #
bindd = Super, Slash, Toggle cheatsheet, global, quickshell:cheatsheetToggle # Toggle cheatsheet
bindd = Super, K, Toggle on-screen keyboard, global, quickshell:oskToggle # Toggle on-screen keyboard
bindd = Super, M, Toggle media controls, global, quickshell:mediaControlsToggle # Toggle media controls
bind = Super, G, global, quickshell:crosshairToggle # Toggle crosshair
bindd = Ctrl+Alt, Delete, Toggle session menu, global, quickshell:sessionToggle # Toggle session menu
bindd = Super, J, Toggle bar, global, quickshell:barToggle # Toggle bar
bind = Ctrl+Alt, Delete, exec, qs -c $qsConfig ipc call TEST_ALIVE || pkill wlogout || wlogout -p layer-shell # [hidden] Session menu (fallback)
@@ -49,6 +50,7 @@ bindl = Alt ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_SOURCE@ toggle # [hidd
bindl = ,XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_SOURCE@ toggle # [hidden]
bindld = Super+Alt,M, Toggle mic, exec, wpctl set-mute @DEFAULT_SOURCE@ toggle # [hidden]
bindd = Ctrl+Super, T, Toggle wallpaper selector, global, quickshell:wallpaperSelectorToggle # Wallpaper selector
bindd = Ctrl+Super+Alt, T, Select random wallpaper, global, quickshell:wallpaperSelectorRandom # Random wallpaper
bindd = Ctrl+Super, T, Change wallpaper, exec, qs -c $qsConfig ipc call TEST_ALIVE || ~/.config/quickshell/$qsConfig/scripts/colors/switchwall.sh # [hidden] Change wallpaper (fallback)
bind = Ctrl+Super, R, exec, killall ags agsv1 gjs ydotool qs quickshell; qs -c $qsConfig & # Restart widgets
+12 -22
View File
@@ -130,32 +130,22 @@ layerrule = blurpopups, quickshell:.*
layerrule = blur, quickshell:.*
layerrule = ignorealpha 0.79, quickshell:.*
layerrule = animation slide, quickshell:bar
layerrule = animation slide, quickshell:verticalBar
layerrule = animation fade, quickshell:screenCorners
layerrule = animation slide right, quickshell:sidebarRight
layerrule = animation slide left, quickshell:sidebarLeft
layerrule = animation slide top, quickshell:wallpaperSelector
layerrule = animation slide bottom, quickshell:osk
layerrule = animation slide bottom, quickshell:dock
layerrule = animation slide bottom, quickshell:cheatsheet
layerrule = noanim, quickshell:crosshair
layerrule = animation slide bottom, quickshell:dock
layerrule = animation popin 120%, quickshell:screenCorners
layerrule = noanim, quickshell:lockWindowPusher
layerrule = animation fade, quickshell:notificationPopup
layerrule = noanim, quickshell:overview
layerrule = animation slide bottom, quickshell:osk
layerrule = noanim, quickshell:screenshot
layerrule = blur, quickshell:session
layerrule = noanim, quickshell:session
layerrule = ignorealpha 0, quickshell:session
layerrule = animation fade, quickshell:notificationPopup
layerrule = blur, quickshell:backgroundWidgets
layerrule = ignorealpha 0.05, quickshell:backgroundWidgets
layerrule = noanim, quickshell:screenshot
layerrule = animation popin 120%, quickshell:screenCorners
layerrule = noanim, quickshell:lockWindowPusher
layerrule = animation slide right, quickshell:sidebarRight
layerrule = animation slide left, quickshell:sidebarLeft
layerrule = animation slide, quickshell:verticalBar
layerrule = animation slide top, quickshell:wallpaperSelector
# Launchers need to be FAST
layerrule = noanim, quickshell:overview
layerrule = noanim, gtk4-layer-shell
## outfoxxed's stuff
layerrule = blur, shell:bar
layerrule = ignorezero, shell:bar
layerrule = blur, shell:notifications
layerrule = ignorealpha 0.1, shell:notifications
@@ -23,13 +23,15 @@ while [[ "$#" -gt 0 ]]; do
done
# Combine the system prompt with the clipboard content
content=$(wl-paste -p | tr '\n' ' ')
prompt="$SYSTEM_PROMPT $content"
content=$(wl-paste -p | tr '\n' ' ' | head -c 2000) # 2000 char limit to prevent overflow
# Properly escape content for JSON using jq
prompt_json=$(jq -n --arg system_prompt "$SYSTEM_PROMPT" --arg content "$content" '$system_prompt + " " + $content')
# Make the API call with the specified or default model
response=$(curl http://localhost:11434/api/generate -d \
"{\"model\": \"$model\",\"prompt\": \"$prompt\",\"stream\": false}" \
| jq -r '.response')
api_payload=$(jq -n --arg model "$model" --argjson prompt "$prompt_json" --argjson stream false \
'{model: $model, prompt: $prompt, stream: $stream}')
response=$(curl -s http://localhost:11434/api/generate -d "$api_payload" | jq -r '.response' 2>/dev/null)
# Check if content is a single line and no longer than 30 characters
if [[ ${#content} -le 30 && "$content" != *$'\n'* ]]; then
@@ -1,24 +0,0 @@
// vim: set ft=glsl:
precision highp float;
varying highp vec2 v_texcoord;
uniform highp sampler2D tex;
#define STRENGTH 0.0027
void main() {
vec2 center = vec2(0.5, 0.5);
vec2 offset = (v_texcoord - center) * STRENGTH;
float rSquared = dot(offset, offset);
float distortion = 1.0 + 1.0 * rSquared;
vec2 distortedOffset = offset * distortion;
vec2 redOffset = vec2(distortedOffset.x, distortedOffset.y);
vec2 blueOffset = vec2(distortedOffset.x, distortedOffset.y);
vec4 redColor = texture2D(tex, v_texcoord + redOffset);
vec4 blueColor = texture2D(tex, v_texcoord + blueOffset);
gl_FragColor = vec4(redColor.r, texture2D(tex, v_texcoord).g, blueColor.b, 1.0);
}
-511
View File
@@ -1,511 +0,0 @@
#version 100
precision highp float;
varying highp vec2 v_texcoord;
varying highp vec3 v_pos;
uniform highp sampler2D tex;
uniform lowp float time;
#define BORDER_COLOR vec4(vec3(0.0, 0.0, 0.0), 1.0) // black border
#define BORDER_RADIUS 1.0 // larger vignette radius
#define BORDER_SIZE 0.01 // small border size
#define CHROMATIC_ABERRATION_STRENGTH 0.00
#define DENOISE_INTENSITY 0.0001 //
#define DISTORTION_AMOUNT 0.00 // moderate distortion amount
#define HDR_BLOOM 0.75 // bloom intensity
#define HDR_BRIGHTNESS 0.011 // brightness
#define HDR_CONTRAST 0.011 // contrast
#define HDR_SATURATION 1.0// saturation
#define LENS_DISTORTION_AMOUNT 0.0
#define NOISE_THRESHOLD 0.0001
#define PHOSPHOR_BLUR_AMOUNT 0.77 // Amount of blur for phosphor glow
#define PHOSPHOR_GLOW_AMOUNT 0.77 // Amount of phosphor glow
#define SAMPLING_RADIUS 0.0001
#define SCANLINE_FREQUENCY 540.0
#define SCANLINE_THICKNESS 0.0507
#define SCANLINE_TIME time * 471.24
#define SHARPNESS 0.25
#define SUPERSAMPLING_SAMPLES 16.0
#define VIGNETTE_RADIUS 0.0 // larger vignette radius
#define PI 3.14159265359
#define TWOPI 6.28318530718
vec2 applyBarrelDistortion(vec2 coord, float amt) {
vec2 p = coord.xy / vec2(1.0);
vec2 v = p * 2.0 - vec2(1.0);
float r = dot(v, v);
float k = 1.0 + pow(r, 2.0) * pow(amt, 2.0);
vec2 result = v * k;
return vec2(0.5, 0.5) + 0.5 * result.xy;
}
vec4 applyColorCorrection(vec4 color) {
color.rgb *= vec3(1.0, 0.79, 0.89);
return vec4(color.rgb, 1.0);
}
vec4 applyBorder(vec2 tc, vec4 color, float borderSize, vec4 borderColor) {
float dist_x = min(tc.x, 1.0 - tc.x);
float dist_y = min(tc.y, 1.0 - tc.y);
float dist = min(dist_x, dist_y) * -1.0;
float border = smoothstep(borderSize, 0.0, dist);
border += smoothstep(borderSize, 0.0, dist);
return mix(color, borderColor, border);
}
vec4 applyFakeHDR(vec4 color, float brightness, float contrast, float saturation, float bloom) {
color.rgb = (color.rgb - vec3(0.5)) * exp2(brightness) + vec3(0.5);
vec3 crtfactor = vec3(1.05, 0.92, 1.0);
color.rgb = pow(color.rgb, crtfactor);
// // NTSC
// vec3 lumCoeff = vec3(0.2125, 0.7154, 0.0721);
// // BT.709
// vec3 lumCoeff = vec3(0.299, 0.587, 0.114);
// BT.2020
vec3 lumCoeff = vec3(0.2627, 0.6780, 0.0593);
// // Warm NTSC
// vec3 lumCoeff = vec3(0.2125, 0.7010, 0.0865);
float luminance = dot(color.rgb, lumCoeff);
luminance = pow(luminance, 2.2);
color.rgb = mix(vec3(luminance), color.rgb, saturation);
color.rgb = mix(color.rgb, vec3(1.0), pow(max(0.0, luminance - 1.0 + bloom), 4.0));
return color;
}
vec4 applyVignette(vec4 color) {
vec2 center = vec2(0.5, 0.5); // center of screen
float radius = VIGNETTE_RADIUS; // radius of vignette effect
float softness = 1.0; // softness of vignette effect
float intensity = 0.7; // intensity of vignette effect
vec2 offset = v_texcoord - center; // offset from center of screen
float distance = length(offset); // distance from center of screen
float alpha = smoothstep(radius, radius - radius * softness, distance) * intensity; // calculate alpha value for vignette effect
return mix(vec4(0.0, 0.0, 0.0, alpha), color, alpha); // mix black with color using calculated alpha value
}
vec4 applyPhosphorGlow(vec2 tc, vec4 color, sampler2D tex) {
// Calculate average color value of the texture
vec4 texelColor = color;
float averageColor = (texelColor.r + texelColor.g + texelColor.b) / 3.0;
// Determine brightness-dependent color factor
float factor = mix(
mix(0.09,
mix(0.005, 0.0075, (averageColor - 0.1) / 0.1),
step(0.01, averageColor)), 0.0005,
step(0.02, averageColor));
// Apply phosphor glow effect
vec4 sum = vec4(0.0);
vec4 pixels[9];
pixels[0] = texture2D(tex, tc - vec2(0.001, 0.001));
pixels[1] = texture2D(tex, tc - vec2(0.001, 0.0));
pixels[2] = texture2D(tex, tc - vec2(0.001, -0.001));
pixels[3] = texture2D(tex, tc - vec2(0.0, 0.001));
pixels[4] = texture2D(tex, tc);
pixels[5] = texture2D(tex, tc + vec2(0.001, 0.001));
pixels[6] = texture2D(tex, tc + vec2(0.001, 0.0));
pixels[7] = texture2D(tex, tc + vec2(0.001, -0.001));
pixels[8] = texture2D(tex, tc + vec2(0.0, 0.001));
// Perform operations on input pixels in parallel
sum = pixels[0]
+ pixels[1]
+ pixels[2]
+ pixels[3]
+ pixels[4]
+ pixels[5]
+ pixels[6]
+ pixels[7]
+ pixels[8];
sum /= 9.0;
sum += texture2D(tex, tc - vec2(0.01, 0.01)) * 0.001;
sum += texture2D(tex, tc - vec2(0.0, 0.01)) * 0.001;
sum += texture2D(tex, tc - vec2(-0.01, 0.01)) * 0.001;
sum += texture2D(tex, tc - vec2(0.01, 0.0)) * 0.001;
sum += color * PHOSPHOR_BLUR_AMOUNT;
sum += texture2D(tex, tc - vec2(-0.01, 0.0)) * 0.001;
sum += texture2D(tex, tc - vec2(0.01, -0.01)) * 0.001;
sum += texture2D(tex, tc - vec2(0.0, -0.01)) * 0.001;
sum += texture2D(tex, tc - vec2(-0.01, -0.01)) * 0.001;
sum *= PHOSPHOR_GLOW_AMOUNT;
// Initialize sum_sum_factor to zero
vec4 sum_sum_factor = vec4(0.0);
// Compute sum_j for i = -1
vec4 sum_j = vec4(0.0);
sum_j += texture2D(tex, tc + vec2(-1, -1) * 0.01);
sum_j += texture2D(tex, tc + vec2(0, -1) * 0.01);
sum_j += texture2D(tex, tc + vec2(1, -1) * 0.01);
sum_j += texture2D(tex, tc + vec2(-1, 0) * 0.01);
sum_j += texture2D(tex, tc + vec2(0, 0) * 0.01);
sum_j += texture2D(tex, tc + vec2(1, 0) * 0.01);
sum_j += texture2D(tex, tc + vec2(-1, 1) * 0.01);
sum_j += texture2D(tex, tc + vec2(0, 1) * 0.01);
sum_j += texture2D(tex, tc + vec2(1, 1) * 0.01);
sum_sum_factor += sum_j * vec4(0.011);
// Compute sum_j for i = 0
sum_j = vec4(0.0);
sum_j += texture2D(tex, tc + vec2(-1, 0) * 0.01);
sum_j += texture2D(tex, tc + vec2(0, 0) * 0.01);
sum_j += texture2D(tex, tc + vec2(1, 0) * 0.01);
sum_j += texture2D(tex, tc + vec2(-1, 1) * 0.01);
sum_j += texture2D(tex, tc + vec2(0, 1) * 0.01);
sum_j += texture2D(tex, tc + vec2(1, 1) * 0.01);
sum_sum_factor += sum_j * vec4(0.011);
// Compute sum_j for i = 1
sum_j = vec4(0.0);
sum_j += texture2D(tex, tc + vec2(-1, 0) * 0.01);
sum_j += texture2D(tex, tc + vec2(0, 1) * 0.01);
sum_j += texture2D(tex, tc + vec2(1, 0) * 0.01);
sum_j += texture2D(tex, tc + vec2(-1, 1) * 0.01);
sum_j += texture2D(tex, tc + vec2(0, 1) * 0.01);
sum_j += texture2D(tex, tc + vec2(1, 1) * 0.01);
sum_sum_factor += sum_j * vec4(0.011);
color += mix(sum_sum_factor * sum_sum_factor * vec4(factor), sum, 0.5);
return color;
}
vec4 applyAdaptiveSharpen(vec2 tc, vec4 color, sampler2D tex) {
vec4 color_tl = texture2D(tex, tc + vec2(-1.0, -1.0) * 0.5 / 2160.0);
vec4 color_tr = texture2D(tex, tc + vec2(1.0, -1.0) * 0.5 / 2160.0);
vec4 color_bl = texture2D(tex, tc + vec2(-1.0, 1.0) * 0.5 / 2160.0);
vec4 color_br = texture2D(tex, tc + vec2(1.0, 1.0) * 0.5 / 2160.0);
float sharpness = SHARPNESS;
vec3 color_no_alpha = color.rgb;
vec3 color_tl_no_alpha = color_tl.rgb;
vec3 color_tr_no_alpha = color_tr.rgb;
vec3 color_bl_no_alpha = color_bl.rgb;
vec3 color_br_no_alpha = color_br.rgb;
float delta = (dot(color_no_alpha, vec3(0.333333)) + dot(color_tl_no_alpha, vec3(0.333333)) + dot(color_tr_no_alpha, vec3(0.333333)) + dot(color_bl_no_alpha, vec3(0.333333)) + dot(color_br_no_alpha, vec3(0.333333))) * 0.2 - dot(color_no_alpha, vec3(0.333333));
vec3 sharp_color_no_alpha = color_no_alpha + min(vec3(0.0), vec3(delta * sharpness));
vec4 sharp_color = vec4(sharp_color_no_alpha, color.a);
return sharp_color;
}
vec4 applyScanlines(vec2 tc, vec4 color) {
float scanline = (cos(tc.y * SCANLINE_FREQUENCY + SCANLINE_TIME) *
sin(tc.y * SCANLINE_FREQUENCY + SCANLINE_TIME)) * SCANLINE_THICKNESS;
float alpha = clamp(1.0 - abs(scanline), 0.0, 1.0);
return vec4(color.rgb * alpha, color.a);
}
vec4 applyChromaticAberration(vec2 uv, vec4 color) {
vec2 center = vec2(0.5, 0.5); // center of the screen
vec2 offset = (uv - center) * CHROMATIC_ABERRATION_STRENGTH; // calculate the offset from the center
// apply lens distortion
float rSquared = dot(offset, offset);
float distortion = 1.0 + LENS_DISTORTION_AMOUNT * rSquared;
vec2 distortedOffset = offset * distortion;
// apply chromatic aberration
vec2 redOffset = vec2(distortedOffset.x * 1.00, distortedOffset.y * 1.00);
vec2 blueOffset = vec2(distortedOffset.x * 1.00, distortedOffset.y * 1.00);
vec4 redColor = texture2D(tex, uv + redOffset);
vec4 blueColor = texture2D(tex, uv + blueOffset);
vec4 result = vec4(redColor.r, color.g, blueColor.b, color.a);
return result;
}
vec4 reduceGlare(vec4 color) {
// Calculate the intensity of the color by taking the average of the RGB components
float intensity = (color.r + color.g + color.b) / 3.0;
// Set the maximum intensity that can be considered for glare
float maxIntensity = 0.98;
// Use smoothstep to create a smooth transition from no glare to full glare
// based on the intensity of the color and the maximum intensity
float glareIntensity = smoothstep(maxIntensity - 0.02, maxIntensity, intensity);
// Set the amount of glare to apply to the color
float glareAmount = 0.02;
// Mix the original color with the reduced color that has glare applied to it
vec3 reducedColor = mix(color.rgb, vec3(glareIntensity), glareAmount);
// Return the reduced color with the original alpha value
return vec4(reducedColor, color.a);
}
// Apply a fake HDR effect to the input color.
// Parameters:
// - inputColor: the color to apply the effect to.
// - brightness: the brightness of the image. Should be a value between 0 and 1.
// - contrast: the contrast of the image. Should be a value between 0 and 1.
// - saturation: the saturation of the image. Should be a value between 0 and 2.
// - bloom: the intensity of the bloom effect. Should be a value between 0 and 1.
vec4 applyFakeHDREffect(vec4 inputColor, float brightness, float contrast, float saturation, float bloom) {
const float minBrightness = 0.0;
const float maxBrightness = 1.0;
const float minContrast = 0.0;
const float maxContrast = 1.0;
const float minSaturation = 0.0;
const float maxSaturation = 2.0;
const float minBloom = 0.0;
const float maxBloom = 1.0;
// Check input parameters for validity
if (brightness < minBrightness || brightness > maxBrightness) {
return vec4(0.0, 0.0, 0.0, 1.0); // Return black with alpha of 1.0 to indicate error
}
if (contrast < minContrast || contrast > maxContrast) {
return vec4(0.0, 0.0, 0.0, 1.0);
}
if (saturation < minSaturation || saturation > maxSaturation) {
return vec4(0.0, 0.0, 0.0, 1.0);
}
if (bloom < minBloom || bloom > maxBloom) {
return vec4(0.0, 0.0, 0.0, 1.0);
}
// Apply brightness and contrast
vec3 color = inputColor.rgb;
color = (color - vec3(0.5)) * exp2(brightness * 10.0) + vec3(0.5);
color = mix(vec3(0.5), color, pow(contrast * 4.0 + 1.0, 2.0));
// // NTSC
// vec3 lumCoeff = vec3(0.2125, 0.7154, 0.0721);
// // BT.709
// vec3 lumCoeff = vec3(0.299, 0.587, 0.114);
// // BT.2020
// vec3 lumCoeff = vec3(0.2627, 0.6780, 0.0593);
// Warm NTSC
vec3 lumCoeff = vec3(0.2125, 0.7010, 0.0865);
// Apply saturation
float luminance = dot(color, lumCoeff);
vec3 grey = vec3(luminance);
color = mix(grey, color, saturation);
// Apply bloom effect
float threshold = 1.0 - bloom;
vec3 bloomColor = max(color - threshold, vec3(0.0));
bloomColor = pow(bloomColor, vec3(2.0));
bloomColor = mix(vec3(0.0), bloomColor, pow(min(luminance, threshold), 4.0));
color += bloomColor;
return vec4(color, inputColor.a);
}
vec4 bilateralFilter(sampler2D tex, vec2 uv, vec4 color, float sampleRadius, float noiseThreshold, float intensity) {
vec4 filteredColor = vec4(0.0);
float totalWeight = 0.0;
// Top-left pixel
vec4 sample = texture2D(tex, uv + vec2(-1.0, -1.0));
float dist = length(vec2(-1.0, -1.0));
float colorDist = length(sample - color);
float weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
filteredColor += sample * weight;
totalWeight += weight;
// Top pixel
sample = texture2D(tex, uv + vec2(0.0, -1.0));
dist = length(vec2(0.0, -1.0));
colorDist = length(sample - color);
weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
filteredColor += sample * weight;
totalWeight += weight;
// Top-right pixel
sample = texture2D(tex, uv + vec2(1.0, -1.0));
dist = length(vec2(1.0, -1.0));
colorDist = length(sample - color);
weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
filteredColor += sample * weight;
totalWeight += weight;
// Left pixel
sample = texture2D(tex, uv + vec2(-1.0, 0.0));
dist = length(vec2(-1.0, 0.0));
colorDist = length(sample - color);
weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
filteredColor += sample * weight;
totalWeight += weight;
// Center pixel
sample = texture2D(tex, uv);
dist = 0.0;
colorDist = length(sample - color);
weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
filteredColor += sample * weight;
totalWeight += weight;
// Right pixel
sample = texture2D(tex, uv + vec2(1.0, 0.0));
dist = length(vec2(1.0, 0.0));
colorDist = length(sample - color);
weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
filteredColor += sample * weight;
totalWeight += weight;
// Bottom-left pixel
sample = texture2D(tex, uv + vec2(-1.0, 1.0));
dist = length(vec2(-1.0, 1.0));
colorDist = length(sample - color);
weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
filteredColor += sample * weight;
totalWeight += weight;
// Bottom pixel
sample = texture2D(tex, uv + vec2(0.0, sampleRadius));
dist = length(vec2(0.0, sampleRadius));
colorDist = length(sample - color);
weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
filteredColor += sample * weight;
totalWeight += weight;
filteredColor /= totalWeight;
return mix(color, filteredColor, step(noiseThreshold, length(filteredColor - color)));
}
vec4 supersample(sampler2D tex, vec2 uv, float sampleRadius, float noiseThreshold, float intensity) {
float radiusSq = sampleRadius * sampleRadius;
vec2 poissonDisk;
vec4 color = vec4(0.0);
float r1_0 = sqrt(0.0 / 16.0);
float r2_0 = fract(1.0 / 3.0);
float theta_0 = TWOPI * r2_0;
poissonDisk = vec2(r1_0 * cos(theta_0), r1_0 * sin(theta_0));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_1 = sqrt(1.0 / 16.0);
float r2_1 = fract(2.0 / 3.0);
float theta_1 = TWOPI * r2_1;
poissonDisk = vec2(r1_1 * cos(theta_1), r1_1 * sin(theta_1));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_2 = sqrt(2.0 / 16.0);
float r2_2 = fract(3.0 / 3.0);
float theta_2 = TWOPI * r2_2;
poissonDisk = vec2(r1_2 * cos(theta_2), r1_2 * sin(theta_2));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_3 = sqrt(3.0 / 16.0);
float r2_3 = fract(4.0 / 3.0);
float theta_3 = TWOPI * r2_3;
poissonDisk = vec2(r1_3 * cos(theta_3), r1_3 * sin(theta_3));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_4 = sqrt(4.0 / 16.0);
float r2_4 = fract(5.0 / 3.0);
float theta_4 = TWOPI * r2_4;
poissonDisk = vec2(r1_4 * cos(theta_4), r1_4 * sin(theta_4));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_5 = sqrt(5.0 / 16.0);
float r2_5 = fract(6.0 / 3.0);
float theta_5 = TWOPI * r2_5;
poissonDisk = vec2(r1_5 * cos(theta_5), r1_5 * sin(theta_5));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_6 = sqrt(6.0 / 16.0);
float r2_6 = fract(7.0 / 3.0);
float theta_6 = TWOPI * r2_6;
poissonDisk = vec2(r1_6 * cos(theta_6), r1_6 * sin(theta_6));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_7 = sqrt(7.0 / 16.0);
float r2_7 = fract(8.0 / 3.0);
float theta_7 = TWOPI * r2_7;
poissonDisk = vec2(r1_7 * cos(theta_7), r1_7 * sin(theta_7));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_8 = sqrt(8.0 / 16.0);
float r2_8 = fract(9.0 / 3.0);
float theta_8 = TWOPI * r2_8;
poissonDisk = vec2(r1_8 * cos(theta_8), r1_8 * sin(theta_8));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_9 = sqrt(9.0 / 16.0);
float r2_9 = fract(10.0 / 3.0);
float theta_9 = TWOPI * r2_9;
poissonDisk = vec2(r1_9 * cos(theta_9), r1_9 * sin(theta_9));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_10 = sqrt(10.0 / 16.0);
float r2_10 = fract(11.0 / 3.0);
float theta_10 = TWOPI * r2_10;
poissonDisk = vec2(r1_10 * cos(theta_10), r1_10 * sin(theta_10));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_11 = sqrt(11.0 / 16.0);
float r2_11 = fract(12.0 / 3.0);
float theta_11 = TWOPI * r2_11;
poissonDisk = vec2(r1_11 * cos(theta_11), r1_11 * sin(theta_11));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_12 = sqrt(12.0 / 16.0);
float r2_12 = fract(13.0 / 3.0);
float theta_12 = TWOPI * r2_12;
poissonDisk = vec2(r1_12 * cos(theta_12), r1_12 * sin(theta_12));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_13 = sqrt(13.0 / 16.0);
float r2_13 = fract(14.0 / 3.0);
float theta_13 = TWOPI * r2_13;
poissonDisk = vec2(r1_13 * cos(theta_13), r1_13 * sin(theta_13));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_14 = sqrt(14.0 / 16.0);
float r2_14 = fract(15.0 / 3.0);
float theta_14 = TWOPI * r2_14;
poissonDisk = vec2(r1_14 * cos(theta_14), r1_14 * sin(theta_14));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_15 = sqrt(15.0 / 16.0);
float r2_15 = fract(16.0 / 3.0);
float theta_15 = TWOPI * r2_15;
poissonDisk = vec2(r1_15 * cos(theta_15), r1_15 * sin(theta_15));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
return bilateralFilter(tex, uv, color, sampleRadius, noiseThreshold, intensity);
}
void main() {
vec2 tc_no_dist = v_texcoord;
vec2 tc = applyBarrelDistortion(tc_no_dist, DISTORTION_AMOUNT);
// [-1, 1]
vec2 tc_no_dist_symmetric = tc_no_dist * 2.0 - 1.0;
// [0,1]
vec2 tc_no_dist_normalized = (tc_no_dist_symmetric + 1.0) / 2.0;
// vec4 color = texture2D(tex, tc);
vec4 color = supersample(tex, tc, SAMPLING_RADIUS, NOISE_THRESHOLD, DENOISE_INTENSITY);
color = applyAdaptiveSharpen(tc, color, tex);
color = applyPhosphorGlow(tc, color, tex);
color = reduceGlare(color);
color = mix(applyFakeHDREffect(color, HDR_BRIGHTNESS, HDR_CONTRAST, HDR_SATURATION, HDR_BLOOM), color, 0.5);
color = applyColorCorrection(color);
color /= SUPERSAMPLING_SAMPLES;
color = mix(applyChromaticAberration(tc, color), color, 0.25);
color = mix(color, applyVignette(color), 0.37);
color = applyBorder(tc_no_dist_normalized, color, 1.0 - BORDER_SIZE * BORDER_RADIUS, BORDER_COLOR);
color = mix(applyBorder(tc, color, BORDER_SIZE, BORDER_COLOR), color, 0.05);
color = applyScanlines(tc, color);
gl_FragColor = color;
gl_FragColor.a = 1.0;
}
-42
View File
@@ -1,42 +0,0 @@
precision highp float;
varying vec2 v_texcoord;
uniform sampler2D tex;
uniform float time;
void warpco(inout vec2 tc) {
tc -= 0.5;
tc *= length(tc) * 2.0;
tc += 0.5;
}
float rand1d(float seed) {
return sin(seed*1454.0);
}
float rand2d(vec2 co)
{
return fract(sin(dot(co.xy, vec2(12.9898,78.233))) * 43758.5453);
}
vec3 rgb(in vec2 tc, float freq, float amp, inout vec4 centre) {
vec2 off = vec2(1.0/800.0, 0.0) * sin(tc.t * freq + time) * amp;
vec2 off2 = vec2(1.0/800.0, 0.0) * sin(tc.t * freq - time * 1.5) * amp;
centre = texture2D(tex, tc);
return vec3(texture2D(tex, tc-off).r, centre.g, texture2D(tex, tc+off2).b);
}
void main() {
// vec2 px = 1.0 / textureSize(tex, 0).st;
vec2 tc = v_texcoord;
warpco(tc);
tc = mix(v_texcoord, tc, sin(time * 2.0)*0.07);
tc.x += rand2d(floor(tc * 20.0 + floor(time * 2.5))) * 0.01;
tc.x += rand1d(floor(tc.x * 40.0)) * 0.005 * rand1d(time * 0.001);
tc.y += sin(tc.x + time) * 0.02;
vec4 centre;
vec3 bent = rgb(tc, 100.0, 5.0, centre);
vec3 col = mix(centre.rgb, bent, sin(time));
gl_FragColor = vec4(col, centre.a);
// gl_FragColor = vec4(texture2D(tex, v_texcoord));
}
-21
View File
@@ -1,21 +0,0 @@
// vim: set ft=glsl:
// blue light filter shader
// values from https://reshade.me/forum/shader-discussion/3673-blue-light-filter-similar-to-f-lux
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D tex;
void main() {
vec4 pixColor = texture2D(tex, v_texcoord);
// red
pixColor[0] *= 0.7;
// green
pixColor[1] *= 0.6;
// blue
pixColor[2] *= 0.5;
gl_FragColor = pixColor;
}
-13
View File
@@ -1,13 +0,0 @@
// vim: set ft=glsl:
// blue light filter shader
// values from https://reshade.me/forum/shader-discussion/3673-blue-light-filter-similar-to-f-lux
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D tex;
void main() {
vec4 pixColor = texture2D(tex, v_texcoord);
pixColor.rgb = 1.0 - pixColor.rgb;
gl_FragColor = pixColor;
}
-41
View File
@@ -1,41 +0,0 @@
// -*- mode:c -*-
precision lowp float;
varying vec2 v_texcoord;
uniform sampler2D tex;
float distanceSquared(vec3 pixColor, vec3 solarizedColor) {
vec3 distanceVector = pixColor - solarizedColor;
return dot(distanceVector, distanceVector);
}
void main() {
vec3 solarized[16];
solarized[0] = vec3(0.,0.169,0.212);
solarized[1] = vec3(0.027,0.212,0.259);
solarized[2] = vec3(0.345,0.431,0.459);
solarized[3] = vec3(0.396,0.482,0.514);
solarized[4] = vec3(0.514,0.58,0.588);
solarized[5] = vec3(0.576,0.631,0.631);
solarized[6] = vec3(0.933,0.91,0.835);
solarized[7] = vec3(0.992,0.965,0.89);
solarized[8] = vec3(0.71,0.537,0.);
solarized[9] = vec3(0.796,0.294,0.086);
solarized[10] = vec3(0.863,0.196,0.184);
solarized[11] = vec3(0.827,0.212,0.51);
solarized[12] = vec3(0.424,0.443,0.769);
solarized[13] = vec3(0.149,0.545,0.824);
solarized[14] = vec3(0.165,0.631,0.596);
solarized[15] = vec3(0.522,0.6,0.);
vec3 pixColor = vec3(texture2D(tex, v_texcoord));
int closest = 0;
float closestDistanceSquared = distanceSquared(pixColor, solarized[0]);
for (int i = 1; i < 15; i++) {
float newDistanceSquared = distanceSquared(pixColor, solarized[i]);
if (newDistanceSquared < closestDistanceSquared) {
closest = i;
closestDistanceSquared = newDistanceSquared;
}
}
gl_FragColor = vec4(solarized[closest], 1.);
}
+1
View File
@@ -10,6 +10,7 @@ pragma ComponentBehavior: Bound
Singleton {
id: root
property bool barOpen: true
property bool crosshairOpen: false
property bool sidebarLeftOpen: false
property bool sidebarRightOpen: false
property bool mediaControlsOpen: false
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
import qs
import qs.services
import qs.modules.common
import qs.modules.common.models
import qs.modules.common.widgets
import qs.modules.common.functions as CF
import QtQuick
@@ -13,14 +14,14 @@ import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
Variants {
id: root
readonly property bool fixedClockPosition: Config.options.background.fixedClockPosition
readonly property real fixedClockX: Config.options.background.clockX
readonly property real fixedClockY: Config.options.background.clockY
readonly property bool fixedClockPosition: Config.options.background.clock.fixedPosition
readonly property real fixedClockX: Config.options.background.clock.x
readonly property real fixedClockY: Config.options.background.clock.y
readonly property real clockSizePadding: 20
readonly property real screenSizePadding: 50
readonly property string clockStyle: Config.options.background.clock.style
model: Quickshell.screens
PanelWindow {
@@ -29,8 +30,8 @@ Variants {
required property var modelData
// Hide when fullscreen
property list<HyprlandWorkspace> workspacesForMonitor: Hyprland.workspaces.values.filter(workspace=>workspace.monitor && workspace.monitor.name == monitor.name)
property var activeWorkspaceWithFullscreen: workspacesForMonitor.filter(workspace=>((workspace.toplevels.values.filter(window=>window.wayland?.fullscreen)[0] != undefined) && workspace.active))[0]
property list<HyprlandWorkspace> workspacesForMonitor: Hyprland.workspaces.values.filter(workspace => workspace.monitor && workspace.monitor.name == monitor.name)
property var activeWorkspaceWithFullscreen: workspacesForMonitor.filter(workspace => ((workspace.toplevels.values.filter(window => window.wayland?.fullscreen)[0] != undefined) && workspace.active))[0]
visible: GlobalStates.screenLocked || (!(activeWorkspaceWithFullscreen != undefined)) || !Config?.options.background.hideWhenFullscreen
// Workspaces
@@ -39,12 +40,14 @@ Variants {
property int firstWorkspaceId: relevantWindows[0]?.workspace.id || 1
property int lastWorkspaceId: relevantWindows[relevantWindows.length - 1]?.workspace.id || 10
// Wallpaper
property bool wallpaperIsVideo: Config.options.background.wallpaperPath.endsWith(".mp4")
|| Config.options.background.wallpaperPath.endsWith(".webm")
|| Config.options.background.wallpaperPath.endsWith(".mkv")
|| Config.options.background.wallpaperPath.endsWith(".avi")
|| Config.options.background.wallpaperPath.endsWith(".mov")
property bool wallpaperIsVideo: Config.options.background.wallpaperPath.endsWith(".mp4") || Config.options.background.wallpaperPath.endsWith(".webm") || Config.options.background.wallpaperPath.endsWith(".mkv") || Config.options.background.wallpaperPath.endsWith(".avi") || Config.options.background.wallpaperPath.endsWith(".mov")
property string wallpaperPath: wallpaperIsVideo ? Config.options.background.thumbnailPath : Config.options.background.wallpaperPath
property bool wallpaperSafetyTriggered: {
const enabled = Config.options.workSafety.enable.wallpaper
const sensitiveWallpaper = (CF.StringUtils.stringListContainsSubstring(wallpaperPath.toLowerCase(), Config.options.workSafety.triggerCondition.fileKeywords))
const sensitiveNetwork = (CF.StringUtils.stringListContainsSubstring(Network.networkName.toLowerCase(), Config.options.workSafety.triggerCondition.networkNameKeywords))
return enabled && sensitiveWallpaper && sensitiveNetwork;
}
property real wallpaperToScreenRatio: Math.min(wallpaperWidth / screen.width, wallpaperHeight / screen.height)
property real preferredWallpaperScale: Config.options.background.parallax.workspaceZoom
property real effectiveWallpaperScale: 1 // Some reasonable init value, to be updated
@@ -57,7 +60,7 @@ Variants {
property real clockX: (modelData.width / 2)
property real clockY: (modelData.height / 2)
property var textHorizontalAlignment: {
if (Config.options.background.blur.enable && Config.options.background.blur.centerClock && GlobalStates.screenLocked)
if ((Config.options.lock.centerClock && GlobalStates.screenLocked) || wallpaperSafetyTriggered)
return Text.AlignHCenter;
if (clockX < screen.width / 3)
return Text.AlignLeft;
@@ -66,10 +69,17 @@ Variants {
return Text.AlignHCenter;
}
// Colors
property color dominantColor: Appearance.colors.colPrimary
property bool shouldBlur: (GlobalStates.screenLocked && Config.options.lock.blur.enable)
property color dominantColor: Appearance.colors.colPrimary // Default, to be changed
property bool dominantColorIsDark: dominantColor.hslLightness < 0.5
property color colText: CF.ColorUtils.colorWithLightness(Appearance.colors.colPrimary, (dominantColorIsDark ? 0.8 : 0.12))
property bool shouldBlur: (GlobalStates.screenLocked && Config.options.background.blur.enable)
property color colText: {
if (wallpaperSafetyTriggered)
return CF.ColorUtils.mix(Appearance.colors.colOnLayer0, Appearance.colors.colPrimary, 0.75);
return (GlobalStates.screenLocked && shouldBlur) ? Appearance.colors.colOnLayer0 : CF.ColorUtils.colorWithLightness(Appearance.colors.colPrimary, (dominantColorIsDark ? 0.8 : 0.12));
}
Behavior on colText {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
// Layer props
screen: modelData
@@ -83,42 +93,43 @@ Variants {
left: true
right: true
}
color: "transparent"
color: CF.ColorUtils.transparentize(CF.ColorUtils.mix(Appearance.colors.colLayer0, Appearance.colors.colPrimary, 0.75), (bgRoot.wallpaperIsVideo ? 1 : 0))
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
onWallpaperPathChanged: {
bgRoot.updateZoomScale()
bgRoot.updateZoomScale();
// Clock position gets updated after zoom scale is updated
}
// Wallpaper zoom scale
function updateZoomScale() {
getWallpaperSizeProc.path = bgRoot.wallpaperPath
getWallpaperSizeProc.path = bgRoot.wallpaperPath;
getWallpaperSizeProc.running = true;
}
Process {
id: getWallpaperSizeProc
property string path: bgRoot.wallpaperPath
command: [ "magick", "identify", "-format", "%w %h", path ]
command: ["magick", "identify", "-format", "%w %h", path]
stdout: StdioCollector {
id: wallpaperSizeOutputCollector
onStreamFinished: {
const output = wallpaperSizeOutputCollector.text
const output = wallpaperSizeOutputCollector.text;
const [width, height] = output.split(" ").map(Number);
const [screenWidth, screenHeight] = [bgRoot.screen.width, bgRoot.screen.height];
bgRoot.wallpaperWidth = width
bgRoot.wallpaperHeight = height
bgRoot.wallpaperWidth = width;
bgRoot.wallpaperHeight = height;
if (width <= screenWidth || height <= screenHeight) { // Undersized/perfectly sized wallpapers
if (width <= screenWidth || height <= screenHeight) {
// Undersized/perfectly sized wallpapers
bgRoot.effectiveWallpaperScale = Math.max(screenWidth / width, screenHeight / height);
} else { // Oversized = can be zoomed for parallax, yay
bgRoot.effectiveWallpaperScale = Math.min(
bgRoot.preferredWallpaperScale,
width / screenWidth, height / screenHeight
);
} else {
// Oversized = can be zoomed for parallax, yay
bgRoot.effectiveWallpaperScale = Math.min(bgRoot.preferredWallpaperScale, width / screenWidth, height / screenHeight);
}
bgRoot.updateClockPosition()
bgRoot.updateClockPosition();
}
}
}
@@ -126,11 +137,11 @@ Variants {
// Clock positioning
function updateClockPosition() {
// Somehow all this manual setting is needed to make the proc correctly use the new values
leastBusyRegionProc.path = bgRoot.wallpaperPath
leastBusyRegionProc.contentWidth = clockLoader.implicitWidth + root.clockSizePadding * 2
leastBusyRegionProc.contentHeight = clockLoader.implicitHeight + root.clockSizePadding * 2
leastBusyRegionProc.horizontalPadding = bgRoot.movableXSpace + root.screenSizePadding * 2
leastBusyRegionProc.verticalPadding = bgRoot.movableYSpace + root.screenSizePadding * 2
leastBusyRegionProc.path = bgRoot.wallpaperPath;
leastBusyRegionProc.contentWidth = clockLoader.implicitWidth + root.clockSizePadding * 2;
leastBusyRegionProc.contentHeight = clockLoader.implicitHeight + root.clockSizePadding * 2;
leastBusyRegionProc.horizontalPadding = bgRoot.movableXSpace + root.screenSizePadding * 2;
leastBusyRegionProc.verticalPadding = bgRoot.movableYSpace + root.screenSizePadding * 2;
leastBusyRegionProc.running = false;
leastBusyRegionProc.running = true;
}
@@ -141,246 +152,317 @@ Variants {
property int contentHeight: 300
property int horizontalPadding: bgRoot.movableXSpace
property int verticalPadding: bgRoot.movableYSpace
command: [Quickshell.shellPath("scripts/images/least_busy_region.py"),
"--screen-width", Math.round(bgRoot.screen.width / bgRoot.effectiveWallpaperScale),
"--screen-height", Math.round(bgRoot.screen.height / bgRoot.effectiveWallpaperScale),
"--width", contentWidth,
"--height", contentHeight,
"--horizontal-padding", horizontalPadding,
"--vertical-padding", verticalPadding,
path,
command: [Quickshell.shellPath("scripts/images/least_busy_region.py"), "--screen-width", Math.round(bgRoot.screen.width / bgRoot.effectiveWallpaperScale), "--screen-height", Math.round(bgRoot.screen.height / bgRoot.effectiveWallpaperScale), "--width", contentWidth, "--height", contentHeight, "--horizontal-padding", horizontalPadding, "--vertical-padding", verticalPadding, path
// "--visual-output",
]
,]
stdout: StdioCollector {
id: leastBusyRegionOutputCollector
onStreamFinished: {
const output = leastBusyRegionOutputCollector.text
const output = leastBusyRegionOutputCollector.text;
// console.log("[Background] Least busy region output:", output)
if (output.length === 0) return;
const parsedContent = JSON.parse(output)
bgRoot.clockX = parsedContent.center_x * bgRoot.effectiveWallpaperScale
bgRoot.clockY = parsedContent.center_y * bgRoot.effectiveWallpaperScale
bgRoot.dominantColor = parsedContent.dominant_color || Appearance.colors.colPrimary
if (output.length === 0)
return;
const parsedContent = JSON.parse(output);
bgRoot.clockX = parsedContent.center_x * bgRoot.effectiveWallpaperScale;
bgRoot.clockY = parsedContent.center_y * bgRoot.effectiveWallpaperScale;
bgRoot.dominantColor = parsedContent.dominant_color || Appearance.colors.colPrimary;
}
}
}
// Wallpaper
Image {
id: wallpaper
visible: opacity > 0 && !blurLoader.active
opacity: (status === Image.Ready && !bgRoot.wallpaperIsVideo) ? 1 : 0
Behavior on opacity {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
cache: false
asynchronous: true
retainWhileLoading: true
smooth: false
// Range = groups that workspaces span on
property int chunkSize: Config?.options.bar.workspaces.shown ?? 10;
property int lower: Math.floor(bgRoot.firstWorkspaceId / chunkSize) * chunkSize;
property int upper: Math.ceil(bgRoot.lastWorkspaceId / chunkSize) * chunkSize;
property int range: upper - lower;
property real valueX: {
let result = 0.5;
if (Config.options.background.parallax.enableWorkspace && !bgRoot.verticalParallax) {
result = ((bgRoot.monitor.activeWorkspace?.id - lower) / range);
}
if (Config.options.background.parallax.enableSidebar) {
result += (0.15 * GlobalStates.sidebarRightOpen - 0.15 * GlobalStates.sidebarLeftOpen);
}
return result;
}
property real valueY: {
let result = 0.5;
if (Config.options.background.parallax.enableWorkspace && bgRoot.verticalParallax) {
result = ((bgRoot.monitor.activeWorkspace?.id - lower) / range);
}
return result;
}
property real effectiveValueX: Math.max(0, Math.min(1, valueX))
property real effectiveValueY: Math.max(0, Math.min(1, valueY))
x: -(bgRoot.movableXSpace) - (effectiveValueX - 0.5) * 2 * bgRoot.movableXSpace
y: -(bgRoot.movableYSpace) - (effectiveValueY - 0.5) * 2 * bgRoot.movableYSpace
source: bgRoot.wallpaperPath
fillMode: Image.PreserveAspectCrop
Behavior on x {
NumberAnimation {
duration: 600
easing.type: Easing.OutCubic
}
}
Behavior on y {
NumberAnimation {
duration: 600
easing.type: Easing.OutCubic
}
}
sourceSize {
width: bgRoot.screen.width * bgRoot.effectiveWallpaperScale * bgRoot.monitor.scale
height: bgRoot.screen.height * bgRoot.effectiveWallpaperScale * bgRoot.monitor.scale
}
width: bgRoot.wallpaperWidth / bgRoot.wallpaperToScreenRatio * bgRoot.effectiveWallpaperScale
height: bgRoot.wallpaperHeight / bgRoot.wallpaperToScreenRatio * bgRoot.effectiveWallpaperScale
}
Item {
anchors.fill: parent
clip: true
Loader {
id: blurLoader
active: Config.options.background.blur.enable && (GlobalStates.screenLocked || scaleAnim.running)
anchors.fill: wallpaper
scale: GlobalStates.screenLocked ? Config.options.background.blur.extraZoom : 1
Behavior on scale {
NumberAnimation {
id: scaleAnim
duration: 400
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveDefaultSpatial
Image {
id: wallpaper
visible: opacity > 0 && !blurLoader.active
opacity: (status === Image.Ready && !bgRoot.wallpaperIsVideo) ? 1 : 0
Behavior on opacity {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
cache: false
asynchronous: true
retainWhileLoading: true
smooth: false
// Range = groups that workspaces span on
property int chunkSize: Config?.options.bar.workspaces.shown ?? 10
property int lower: Math.floor(bgRoot.firstWorkspaceId / chunkSize) * chunkSize
property int upper: Math.ceil(bgRoot.lastWorkspaceId / chunkSize) * chunkSize
property int range: upper - lower
property real valueX: {
let result = 0.5;
if (Config.options.background.parallax.enableWorkspace && !bgRoot.verticalParallax) {
result = ((bgRoot.monitor.activeWorkspace?.id - lower) / range);
}
if (Config.options.background.parallax.enableSidebar) {
result += (0.15 * GlobalStates.sidebarRightOpen - 0.15 * GlobalStates.sidebarLeftOpen);
}
return result;
}
property real valueY: {
let result = 0.5;
if (Config.options.background.parallax.enableWorkspace && bgRoot.verticalParallax) {
result = ((bgRoot.monitor.activeWorkspace?.id - lower) / range);
}
return result;
}
property real effectiveValueX: Math.max(0, Math.min(1, valueX))
property real effectiveValueY: Math.max(0, Math.min(1, valueY))
x: -(bgRoot.movableXSpace) - (effectiveValueX - 0.5) * 2 * bgRoot.movableXSpace
y: -(bgRoot.movableYSpace) - (effectiveValueY - 0.5) * 2 * bgRoot.movableYSpace
source: bgRoot.wallpaperSafetyTriggered ? "" : bgRoot.wallpaperPath
fillMode: Image.PreserveAspectCrop
Behavior on x {
NumberAnimation {
duration: 600
easing.type: Easing.OutCubic
}
}
Behavior on y {
NumberAnimation {
duration: 600
easing.type: Easing.OutCubic
}
}
sourceSize {
width: bgRoot.screen.width * bgRoot.effectiveWallpaperScale * bgRoot.monitor.scale
height: bgRoot.screen.height * bgRoot.effectiveWallpaperScale * bgRoot.monitor.scale
}
width: bgRoot.wallpaperWidth / bgRoot.wallpaperToScreenRatio * bgRoot.effectiveWallpaperScale
height: bgRoot.wallpaperHeight / bgRoot.wallpaperToScreenRatio * bgRoot.effectiveWallpaperScale
}
sourceComponent: GaussianBlur {
source: wallpaper
radius: GlobalStates.screenLocked ? Config.options.background.blur.radius : 0
samples: radius * 2 + 1
Rectangle {
opacity: GlobalStates.screenLocked ? 1 : 0
anchors.fill: parent
color: CF.ColorUtils.transparentize(Appearance.colors.colLayer0, 0.7)
Loader {
id: blurLoader
active: Config.options.lock.blur.enable && (GlobalStates.screenLocked || scaleAnim.running)
anchors.fill: wallpaper
scale: GlobalStates.screenLocked ? Config.options.lock.blur.extraZoom : 1
Behavior on scale {
NumberAnimation {
id: scaleAnim
duration: 400
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveDefaultSpatial
}
}
}
}
sourceComponent: GaussianBlur {
source: wallpaper
radius: GlobalStates.screenLocked ? Config.options.lock.blur.radius : 0
samples: radius * 2 + 1
// The clock
Loader {
id: clockLoader
active: Config.options.background.showClock
anchors {
left: wallpaper.left
top: wallpaper.top
horizontalCenter: undefined
leftMargin: bgRoot.movableXSpace + ((root.fixedClockPosition ? root.fixedClockX : bgRoot.clockX * bgRoot.effectiveWallpaperScale) - implicitWidth / 2)
topMargin: {
if (bgRoot.shouldBlur)
return bgRoot.modelData.height / 3
return bgRoot.movableYSpace + ((root.fixedClockPosition ? root.fixedClockY : bgRoot.clockY * bgRoot.effectiveWallpaperScale) - implicitHeight / 2)
}
Behavior on leftMargin {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on topMargin {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
Rectangle {
opacity: GlobalStates.screenLocked ? 1 : 0
anchors.fill: parent
color: CF.ColorUtils.transparentize(Appearance.colors.colLayer0, 0.7)
}
}
}
states: State {
name: "centered"
when: bgRoot.shouldBlur
AnchorChanges {
target: clockLoader
// The clock
Loader {
id: clockLoader
scale: Config.options.background.clock.scale
active: Config.options.background.clock.show
anchors {
left: wallpaper.left
top: wallpaper.top
horizontalCenter: undefined
verticalCenter: undefined
leftMargin: bgRoot.movableXSpace + ((root.fixedClockPosition ? root.fixedClockX : bgRoot.clockX * bgRoot.effectiveWallpaperScale) - implicitWidth / 2)
topMargin: bgRoot.movableYSpace + ((root.fixedClockPosition ? root.fixedClockY : bgRoot.clockY * bgRoot.effectiveWallpaperScale) - implicitHeight / 2)
Behavior on leftMargin {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on topMargin {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
}
states: State {
name: "centered"
when: (GlobalStates.screenLocked && Config.options.lock.centerClock) || bgRoot.wallpaperSafetyTriggered
AnchorChanges {
target: clockLoader
anchors {
left: undefined
right: undefined
top: undefined
verticalCenter: parent.verticalCenter
horizontalCenter: parent.horizontalCenter
}
}
}
transitions: Transition {
AnchorAnimation {
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
}
sourceComponent: ColumnLayout {
spacing: 8
Loader {
id: digitalClockLoader
visible: root.clockStyle === "digital"
active: visible
sourceComponent: ColumnLayout {
id: clockColumn
spacing: 6
ClockText {
font.pixelSize: 90
text: DateTime.time
}
ClockText {
Layout.topMargin: -5
text: DateTime.date
}
StyledText {
// Somehow gets fucked up if made a ClockText???
visible: Config.options.background.quote.length > 0
Layout.fillWidth: true
horizontalAlignment: bgRoot.textHorizontalAlignment
font {
family: Appearance.font.family.main
pixelSize: Appearance.font.pixelSize.normal
weight: 350
italic: true
}
color: bgRoot.colText
style: Text.Raised
styleColor: Appearance.colors.colShadow
text: Config.options.background.quote
}
}
}
Loader {
id: cookieClockLoader
Layout.alignment: Qt.AlignHCenter
visible: root.clockStyle === "cookie"
active: visible
sourceComponent: CookieClock {}
}
}
Item {
anchors {
left: undefined
horizontalCenter: wallpaper.horizontalCenter
right: undefined
top: clockLoader.bottom
topMargin: 8
horizontalCenter: (bgRoot.textHorizontalAlignment === Text.AlignHCenter || root.clockStyle === "cookie") ? clockLoader.horizontalCenter : undefined
left: (bgRoot.textHorizontalAlignment === Text.AlignLeft) ? clockLoader.left : undefined
right: (bgRoot.textHorizontalAlignment === Text.AlignRight) ? clockLoader.right : undefined
leftMargin: -26
rightMargin: -26
}
}
}
transitions: Transition {
AnchorAnimation {
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
}
sourceComponent: Item {
id: clock
implicitWidth: clockColumn.implicitWidth
implicitHeight: clockColumn.implicitHeight
implicitWidth: statusTextBg.implicitWidth
implicitHeight: statusTextBg.implicitHeight
ColumnLayout {
id: clockColumn
anchors.centerIn: parent
spacing: 6
StyledRectangularShadow {
target: statusTextBg
visible: statusTextBg.visible && root.clockStyle === "cookie"
opacity: statusTextBg.opacity
}
StyledText {
Layout.fillWidth: true
horizontalAlignment: bgRoot.textHorizontalAlignment
font {
family: Appearance.font.family.expressive
pixelSize: 90
weight: Font.Bold
Rectangle {
id: statusTextBg
anchors.centerIn: parent
clip: true
opacity: (safetyStatusText.shown || lockStatusText.shown) ? 1 : 0
visible: opacity > 0
implicitHeight: statusTextRow.implicitHeight + 5 * 2
implicitWidth: statusTextRow.implicitWidth + 5 * 2
radius: Appearance.rounding.small
color: CF.ColorUtils.transparentize(Appearance.colors.colSecondaryContainer, root.clockStyle === "cookie" ? 0 : 1)
Behavior on implicitWidth {
animation: Appearance.animation.elementResize.numberAnimation.createObject(this)
}
color: bgRoot.colText
style: Text.Raised
styleColor: Appearance.colors.colShadow
text: DateTime.time
}
StyledText {
Layout.fillWidth: true
Layout.topMargin: -5
horizontalAlignment: bgRoot.textHorizontalAlignment
font {
family: Appearance.font.family.expressive
pixelSize: 20
weight: Font.DemiBold
Behavior on implicitHeight {
animation: Appearance.animation.elementResize.numberAnimation.createObject(this)
}
color: bgRoot.colText
style: Text.Raised
styleColor: Appearance.colors.colShadow
text: DateTime.date
animateChange: true
}
StyledText {
Layout.fillWidth: true
horizontalAlignment: bgRoot.textHorizontalAlignment
font {
family: Appearance.font.family.expressive
pixelSize: 20
weight: Font.DemiBold
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
color: bgRoot.colText
style: Text.Raised
visible: Config.options.background.quote !== ""
styleColor: Appearance.colors.colShadow
text: Config.options.background.quote
}
}
RowLayout {
anchors {
top: clockColumn.bottom
left: bgRoot.textHorizontalAlignment === Text.AlignLeft ? clockColumn.left : undefined
right: bgRoot.textHorizontalAlignment === Text.AlignRight ? clockColumn.right : undefined
horizontalCenter: bgRoot.textHorizontalAlignment === Text.AlignHCenter ? clockColumn.horizontalCenter : undefined
topMargin: 5
leftMargin: -5
rightMargin: -5
RowLayout {
id: statusTextRow
anchors.centerIn: parent
spacing: 14
Item {
Layout.fillWidth: bgRoot.textHorizontalAlignment !== Text.AlignLeft
implicitWidth: 1
}
ClockStatusText {
id: safetyStatusText
shown: bgRoot.wallpaperSafetyTriggered
statusIcon: "hide_image"
statusText: qsTr("Wallpaper safety enforced")
}
ClockStatusText {
id: lockStatusText
shown: GlobalStates.screenLocked && Config.options.lock.showLockedText
statusIcon: "lock"
statusText: qsTr("Locked")
}
Item {
Layout.fillWidth: bgRoot.textHorizontalAlignment !== Text.AlignRight
implicitWidth: 1
}
}
}
opacity: GlobalStates.screenLocked && (!Config.options.background.blur.enable || Config.options.background.blur.showLockedText) ? 1 : 0
visible: opacity > 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Item { Layout.fillWidth: bgRoot.textHorizontalAlignment !== Text.AlignLeft; implicitWidth: 1 }
MaterialSymbol {
text: "lock"
Layout.fillWidth: false
iconSize: Appearance.font.pixelSize.huge
color: bgRoot.colText
style: Text.Raised
styleColor: Appearance.colors.colShadow
}
StyledText {
Layout.fillWidth: false
text: "Locked"
color: bgRoot.colText
font.pixelSize: Appearance.font.pixelSize.larger
style: Text.Raised
styleColor: Appearance.colors.colShadow
}
Item { Layout.fillWidth: bgRoot.textHorizontalAlignment !== Text.AlignRight; implicitWidth: 1 }
}
}
}
}
// Components
component ClockText: StyledText {
Layout.fillWidth: true
horizontalAlignment: bgRoot.textHorizontalAlignment
font {
family: Appearance.font.family.expressive
pixelSize: 20
weight: Font.DemiBold
}
color: bgRoot.colText
style: Text.Raised
styleColor: Appearance.colors.colShadow
animateChange: true
}
component ClockStatusText: RowLayout {
id: statusTextRow
property alias statusIcon: statusIconWidget.text
property alias statusText: statusTextWidget.text
property bool shown: true
property color textColor: root.clockStyle === "cookie" ? Appearance.colors.colOnSecondaryContainer : bgRoot.colText
opacity: shown ? 1 : 0
visible: opacity > 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Layout.fillWidth: false
MaterialSymbol {
id: statusIconWidget
Layout.fillWidth: false
iconSize: Appearance.font.pixelSize.huge
color: statusTextRow.textColor
style: Text.Raised
styleColor: Appearance.colors.colShadow
}
ClockText {
id: statusTextWidget
Layout.fillWidth: false
color: statusTextRow.textColor
font {
family: Appearance.font.family.main
pixelSize: Appearance.font.pixelSize.large
weight: Font.Normal
}
style: Text.Raised
styleColor: Appearance.colors.colShadow
}
}
}
@@ -0,0 +1,142 @@
pragma ComponentBehavior: Bound
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import QtQuick
import QtQuick.Layouts
import Quickshell
import Qt5Compat.GraphicalEffects
Item {
id: root
property real implicitSize: 230
property real hourHandLength: 72
property real hourHandWidth: 16
property real minuteHandLength: 95
property real minuteHandWidth: 8
property real centerDotSize: 10
property real hourDotSize: minuteHandWidth
property color colShadow: Appearance.colors.colShadow
property color colBackground: Appearance.colors.colSecondaryContainer
property color colOnBackground: ColorUtils.mix(Appearance.colors.colPrimary, Appearance.colors.colSecondaryContainer, 0.5)
property color colHourHand: Appearance.colors.colPrimary
property color colMinuteHand: Appearance.colors.colSecondary
property color colOnHourHand: Appearance.colors.colOnPrimary
readonly property list<string> clockNumbers: DateTime.time.split(/[: ]/)
readonly property int clockHour: parseInt(clockNumbers[0]) % 12
readonly property int clockMinute: parseInt(clockNumbers[1])
implicitWidth: implicitSize
implicitHeight: implicitSize
DropShadow {
source: cookie
anchors.fill: source
horizontalOffset: 0
verticalOffset: 2
radius: 12
samples: radius * 2 + 1
color: root.colShadow
transparentBorder: true
}
MaterialCookie {
id: cookie
z: 0
implicitSize: root.implicitSize
amplitude: implicitSize / 70
sides: 12
color: root.colBackground
// 12 dots around the cookie
Repeater {
model: 12
Item {
required property int index
rotation: 360 / 12 * index
anchors.fill: parent
Rectangle {
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
leftMargin: 10
}
implicitWidth: root.hourDotSize
implicitHeight: implicitWidth
radius: implicitWidth / 2
color: root.colOnBackground
opacity: 0.5
}
}
}
}
Column {
id: timeIndicators
z: 1
anchors.centerIn: cookie
spacing: -16
// Numbers
Repeater {
model: root.clockNumbers
delegate: StyledText {
required property string modelData
anchors.horizontalCenter: parent?.horizontalCenter
font {
pixelSize: modelData.match(/am|pm/i) ? 26 : 68
family: Appearance.font.family.expressive
weight: Font.Bold
}
color: root.colOnBackground
text: modelData.padStart(2, "0")
}
}
}
// Hour hand
Item {
anchors.fill: parent
z: 2
rotation: -90 + (360 / 12) * (root.clockHour + root.clockMinute / 60)
Rectangle {
anchors.verticalCenter: parent.verticalCenter
x: parent.width / 2 - hourHandWidth / 2
width: hourHandLength
height: hourHandWidth
radius: hourHandWidth / 2
color: root.colHourHand
}
}
// Minute hand
Item {
anchors.fill: parent
z: 3
rotation: -90 + (360 / 60) * root.clockMinute
Rectangle {
anchors.verticalCenter: parent.verticalCenter
x: parent.width / 2 - minuteHandWidth / 2
width: minuteHandLength
height: minuteHandWidth
radius: minuteHandWidth / 2
color: root.colMinuteHand
}
}
// Center dot
Rectangle {
z: 4
color: root.colOnHourHand
anchors.centerIn: parent
implicitWidth: centerDotSize
implicitHeight: implicitWidth
radius: implicitWidth / 2
}
}
+2 -4
View File
@@ -1,11 +1,9 @@
import "./weather"
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
import Quickshell.Services.UPower
import qs
import qs.services
import qs.modules.common
@@ -93,8 +91,8 @@ Scope {
id: hoverMaskRegion
anchors {
fill: barContent
topMargin: -1
bottomMargin: -1
topMargin: -Config.options.bar.autoHide.hoverRegionWidth
bottomMargin: -Config.options.bar.autoHide.hoverRegionWidth
}
}
@@ -289,16 +289,9 @@ Item { // Bar content region
color: rightSidebarButton.colText
}
}
Loader {
active: HyprlandXkb.layoutCodes.length > 1
visible: active
HyprlandXkbIndicator {
Layout.alignment: Qt.AlignVCenter
Layout.rightMargin: indicatorsRowLayout.realSpacing
sourceComponent: StyledText {
text: HyprlandXkb.currentLayoutCode
font.pixelSize: Appearance.font.pixelSize.small
color: rightSidebarButton.colText
animateChange: true
}
}
MaterialSymbol {
Layout.rightMargin: indicatorsRowLayout.realSpacing
@@ -0,0 +1,27 @@
import QtQuick
import qs.services
import qs.modules.common
import qs.modules.common.widgets
Loader {
id: root
property bool vertical: false
active: HyprlandXkb.layoutCodes.length > 1
visible: active
sourceComponent: Item {
implicitWidth: root.vertical ? null : layoutCodeText.implicitWidth
implicitHeight: root.vertical ? layoutCodeText.implicitHeight : null
StyledText {
id: layoutCodeText
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: HyprlandXkb.currentLayoutCode.split(":").join("\n")
font.pixelSize: text.includes("\n") ? Appearance.font.pixelSize.smallie : Appearance.font.pixelSize.small
color: rightSidebarButton.colText
animateChange: true
}
}
}
+1 -1
View File
@@ -21,7 +21,7 @@ Item {
Timer {
running: activePlayer?.playbackState == MprisPlaybackState.Playing
interval: 1000
interval: Config.options.resources.updateInterval
repeat: true
onTriggered: activePlayer.positionChanged()
}
@@ -24,7 +24,7 @@ Revealer { // Scroll hint
// StyledToolTip {
// extraVisibleCondition: tooltipText.length > 0
// content: tooltipText
// text: tooltipText
// }
ColumnLayout {
@@ -1,4 +1,5 @@
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import QtQuick
import QtQuick.Effects
@@ -26,6 +27,10 @@ LazyLoader {
implicitWidth: popupBackground.implicitWidth + Appearance.sizes.hyprlandGapsOut * 2 + root.popupBackgroundMargin
implicitHeight: popupBackground.implicitHeight + Appearance.sizes.hyprlandGapsOut * 2 + root.popupBackgroundMargin
mask: Region {
item: popupBackground
}
exclusionMode: ExclusionMode.Ignore
exclusiveZone: 0
margins {
@@ -49,15 +54,8 @@ LazyLoader {
WlrLayershell.namespace: "quickshell:popup"
WlrLayershell.layer: WlrLayer.Overlay
RectangularShadow {
property var target: popupBackground
anchors.fill: target
radius: target.radius
blur: 0.9 * Appearance.sizes.hyprlandGapsOut
offset: Qt.vector2d(0.0, 1.0)
spread: 0.7
color: Appearance.colors.colShadow
cached: true
StyledRectangularShadow {
target: popupBackground
}
Rectangle {
+49 -3
View File
@@ -3,6 +3,7 @@ import qs.modules.common.widgets
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import Quickshell.Services.SystemTray
Item {
@@ -14,20 +15,58 @@ Item {
property bool trayOverflowOpen: false
property bool showSeparator: true
property bool showOverflowMenu: true
property var activeMenu: null
property list<var> itemsInUserList: SystemTray.items.values.filter(i => (Config.options.bar.tray.pinnedItems.includes(i.id) && i.status !== Status.Passive))
property list<var> itemsNotInUserList: SystemTray.items.values.filter(i => (!Config.options.bar.tray.pinnedItems.includes(i.id) && i.status !== Status.Passive))
property bool invertPins: Config.options.bar.tray.invertPinnedItems
property list<var> pinnedItems: invertPins ? itemsNotInUserList : itemsInUserList
property list<var> unpinnedItems: invertPins ? itemsInUserList : itemsNotInUserList
onUnpinnedItemsChanged: if (unpinnedItems.length == 0)
root.trayOverflowOpen = false
onUnpinnedItemsChanged: {
if (unpinnedItems.length == 0) root.closeOverflowMenu();
}
function grabFocus() {
focusGrab.active = true;
}
function setExtraWindowAndGrabFocus(window) {
root.activeMenu = window;
root.grabFocus();
}
function releaseFocus() {
focusGrab.active = false;
}
function closeOverflowMenu() {
focusGrab.active = false;
}
onTrayOverflowOpenChanged: {
if (root.trayOverflowOpen) {
root.grabFocus();
}
}
HyprlandFocusGrab {
id: focusGrab
active: false
windows: [trayOverflowLayout.QsWindow?.window, root.activeMenu]
onCleared: {
root.trayOverflowOpen = false;
if (root.activeMenu) {
root.activeMenu.close();
root.activeMenu = null;
}
}
}
GridLayout {
id: gridLayout
columns: root.vertical ? 1 : -1
anchors.fill: parent
rowSpacing: 6
rowSpacing: 8
columnSpacing: 15
RippleButton {
@@ -60,6 +99,7 @@ Item {
}
StyledPopup {
id: overflowPopup
hoverTarget: trayOverflowButton
active: root.trayOverflowOpen
popupBackgroundMargin: 300 // This should be plenty... makes sure tooltips don't get cutoff (easily)
@@ -79,6 +119,8 @@ Item {
item: modelData
Layout.fillHeight: !root.vertical
Layout.fillWidth: root.vertical
onMenuClosed: root.releaseFocus();
onMenuOpened: (qsWindow) => root.setExtraWindowAndGrabFocus(qsWindow);
}
}
}
@@ -95,6 +137,10 @@ Item {
item: modelData
Layout.fillHeight: !root.vertical
Layout.fillWidth: root.vertical
onMenuClosed: root.releaseFocus();
onMenuOpened: (qsWindow) => {
root.setExtraWindowAndGrabFocus(qsWindow);
}
}
}
@@ -9,16 +9,17 @@ import Qt5Compat.GraphicalEffects
MouseArea {
id: root
property var bar: root.QsWindow.window
required property SystemTrayItem item
property bool targetMenuOpen: false
hoverEnabled: true
signal menuOpened(qsWindow: var)
signal menuClosed()
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
implicitWidth: 20
implicitHeight: 20
onClicked: (event) => {
onPressed: (event) => {
switch (event.button) {
case Qt.LeftButton:
item.activate();
@@ -30,22 +31,36 @@ MouseArea {
event.accepted = true;
}
onEntered: {
tooltip.content = item.tooltipTitle.length > 0 ? item.tooltipTitle
tooltip.text = item.tooltipTitle.length > 0 ? item.tooltipTitle
: (item.title.length > 0 ? item.title : item.id);
if (item.tooltipDescription.length > 0) tooltip.content += " • " + item.tooltipDescription;
if (Config.options.bar.tray.showItemId) tooltip.content += "\n[" + item.id + "]";
if (item.tooltipDescription.length > 0) tooltip.text += " • " + item.tooltipDescription;
if (Config.options.bar.tray.showItemId) tooltip.text += "\n[" + item.id + "]";
}
QsMenuAnchor {
Loader {
id: menu
menu: root.item.menu
anchor.window: bar
anchor.rect.x: root.x + (Config.options.bar.vertical ? 0 : bar?.width)
anchor.rect.y: root.y + (Config.options.bar.vertical ? bar?.height : 0)
anchor.rect.height: root.height
anchor.rect.width: root.width
anchor.edges: Config.options.bar.bottom ? (Edges.Top | Edges.Left) : (Edges.Bottom | Edges.Right)
function open() {
menu.active = true;
}
active: false
sourceComponent: SysTrayMenu {
Component.onCompleted: this.open();
trayItemMenuHandle: root.item.menu
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)
}
onMenuOpened: (window) => root.menuOpened(window);
onMenuClosed: {
root.menuClosed();
menu.active = false;
}
}
}
IconImage {
@@ -76,10 +91,11 @@ MouseArea {
}
}
StyledToolTip {
PopupToolTip {
id: tooltip
extraVisibleCondition: root.containsMouse
alternativeVisibleCondition: extraVisibleCondition
anchorEdges: (!Config.options.bar.bottom && !Config.options.bar.vertical) ? Edges.Bottom : Edges.Top
}
}
@@ -0,0 +1,218 @@
import qs
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
PopupWindow {
id: root
required property QsMenuHandle trayItemMenuHandle
property real popupBackgroundMargin: 0
signal menuClosed
signal menuOpened(qsWindow: var) // Correct type is QsWindow, but QML does not like that
color: "transparent"
property real padding: Appearance.sizes.elevationMargin
implicitHeight: {
let result = 0;
for (let child of stackView.children) {
result = Math.max(child.implicitHeight, result);
}
return result + popupBackground.padding * 2 + root.padding * 2;
}
implicitWidth: {
let result = 0;
for (let child of stackView.children) {
result = Math.max(child.implicitWidth, result);
}
return result + popupBackground.padding * 2 + root.padding * 2;
}
function open() {
root.visible = true;
root.menuOpened(root);
}
function close() {
root.visible = false;
while (stackView.depth > 1)
stackView.pop();
root.menuClosed();
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.BackButton | Qt.RightButton
onClicked: event => {
if ((event.button === Qt.BackButton || event.button === Qt.RightButton) && stackView.depth > 1)
stackView.pop();
}
StyledRectangularShadow {
target: popupBackground
opacity: popupBackground.opacity
}
Rectangle {
id: popupBackground
readonly property real padding: 4
anchors {
left: parent.left
right: parent.right
verticalCenter: Config.options.bar.vertical ? parent.verticalCenter : undefined
top: Config.options.bar.vertical ? undefined : Config.options.bar.bottom ? undefined : parent.top
bottom: Config.options.bar.vertical ? undefined : Config.options.bar.bottom ? parent.bottom : undefined
margins: root.padding
}
color: Appearance.colors.colLayer0
radius: Appearance.rounding.windowRounding
border.width: 1
border.color: Appearance.colors.colLayer0Border
clip: true
opacity: 0
Component.onCompleted: opacity = 1
implicitWidth: stackView.implicitWidth + popupBackground.padding * 2
implicitHeight: stackView.implicitHeight + popupBackground.padding * 2
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on implicitHeight {
animation: Appearance.animation.elementResize.numberAnimation.createObject(this)
}
Behavior on implicitWidth {
animation: Appearance.animation.elementResize.numberAnimation.createObject(this)
}
StackView {
id: stackView
anchors {
fill: parent
margins: popupBackground.padding
}
pushEnter: NoAnim {}
pushExit: NoAnim {}
popEnter: NoAnim {}
popExit: NoAnim {}
implicitWidth: currentItem.implicitWidth
implicitHeight: currentItem.implicitHeight
initialItem: SubMenu {
handle: root.trayItemMenuHandle
}
}
}
}
component NoAnim: Transition {
NumberAnimation {
duration: 0
}
}
component SubMenu: ColumnLayout {
id: submenu
required property QsMenuHandle handle
property bool isSubMenu: false
property bool shown: false
opacity: shown ? 1 : 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Component.onCompleted: shown = true
StackView.onActivating: shown = true
StackView.onDeactivating: shown = false
StackView.onRemoved: destroy()
QsMenuOpener {
id: menuOpener
menu: submenu.handle
}
spacing: 0
Loader {
Layout.fillWidth: true
visible: submenu.isSubMenu
active: visible
sourceComponent: RippleButton {
id: backButton
buttonRadius: popupBackground.radius - popupBackground.padding
horizontalPadding: 12
implicitWidth: contentItem.implicitWidth + horizontalPadding * 2
implicitHeight: 36
onClicked: stackView.pop()
contentItem: RowLayout {
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
right: parent.right
leftMargin: backButton.horizontalPadding
rightMargin: backButton.horizontalPadding
}
spacing: 8
MaterialSymbol {
iconSize: 20
text: "chevron_left"
}
StyledText {
Layout.fillWidth: true
text: Translation.tr("Back")
}
}
}
}
Repeater {
id: menuEntriesRepeater
property bool iconColumnNeeded: {
for (let i = 0; i < menuOpener.children.values.length; i++) {
if (menuOpener.children.values[i].icon.length > 0)
return true;
}
return false;
}
property bool specialInteractionColumnNeeded: {
for (let i = 0; i < menuOpener.children.values.length; i++) {
if (menuOpener.children.values[i].buttonType !== QsMenuButtonType.None)
return true;
}
return false;
}
model: menuOpener.children
delegate: SysTrayMenuEntry {
required property QsMenuEntry modelData
forceIconColumn: menuEntriesRepeater.iconColumnNeeded
forceSpecialInteractionColumn: menuEntriesRepeater.specialInteractionColumnNeeded
menuEntry: modelData
buttonRadius: popupBackground.radius - popupBackground.padding
onDismiss: root.close()
onOpenSubmenu: handle => {
stackView.push(subMenuComponent.createObject(null, {
handle: handle,
isSubMenu: true
}));
}
}
}
}
Component {
id: subMenuComponent
SubMenu {}
}
}
@@ -0,0 +1,126 @@
pragma ComponentBehavior: Bound
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
RippleButton {
id: root
required property QsMenuEntry menuEntry
property bool forceIconColumn: false
property bool forceSpecialInteractionColumn: false
readonly property bool hasIcon: menuEntry.icon.length > 0
readonly property bool hasSpecialInteraction: menuEntry.buttonType !== QsMenuButtonType.None
signal dismiss()
signal openSubmenu(handle: QsMenuHandle)
colBackground: menuEntry.isSeparator ? Appearance.m3colors.m3outlineVariant : ColorUtils.transparentize(Appearance.colors.colLayer0)
enabled: !menuEntry.isSeparator
opacity: 1
horizontalPadding: 12
implicitWidth: contentItem.implicitWidth + horizontalPadding * 2
implicitHeight: menuEntry.isSeparator ? 1 : 36
Layout.topMargin: menuEntry.isSeparator ? 4 : 0
Layout.bottomMargin: menuEntry.isSeparator ? 4 : 0
Layout.fillWidth: true
Component.onCompleted: {
if (menuEntry.isSeparator) {
root.buttonColor = root.colBackground;
}
}
releaseAction: () => {
if (menuEntry.hasChildren) {
root.openSubmenu(root.menuEntry);
return;
}
menuEntry.triggered();
root.dismiss();
}
altAction: (event) => { // Not hog right-click
event.accepted = false;
}
contentItem: RowLayout {
id: contentItem
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
right: parent.right
leftMargin: root.horizontalPadding
rightMargin: root.horizontalPadding
}
spacing: 8
visible: !root.menuEntry.isSeparator
// Interaction: checkbox or radio button
Item {
visible: root.hasSpecialInteraction || root.forceSpecialInteractionColumn
implicitWidth: 20
implicitHeight: 20
Loader {
anchors.fill: parent
active: root.menuEntry.buttonType === QsMenuButtonType.RadioButton
sourceComponent: StyledRadioButton {
enabled: false
padding: 0
checked: root.menuEntry.checkState === Qt.Checked
}
}
Loader {
anchors.fill: parent
active: root.menuEntry.buttonType === QsMenuButtonType.CheckBox && root.menuEntry.checkState !== Qt.Unchecked
sourceComponent: MaterialSymbol {
text: root.menuEntry.checkState === Qt.PartiallyChecked ? "check_indeterminate_small" : "check"
iconSize: 20
}
}
}
// Button icon
Item {
visible: root.hasIcon || root.forceIconColumn
implicitWidth: 20
implicitHeight: 20
Loader {
anchors.centerIn: parent
active: root.menuEntry.icon.length > 0
sourceComponent: IconImage {
asynchronous: true
source: root.menuEntry.icon
implicitSize: 20
mipmap: true
}
}
}
StyledText {
id: label
text: root.menuEntry.text
font.pixelSize: Appearance.font.pixelSize.smallie
Layout.fillWidth: true
}
Loader {
active: root.menuEntry.hasChildren
sourceComponent: MaterialSymbol {
text: "chevron_right"
iconSize: 20
}
}
}
}
@@ -204,7 +204,7 @@ Item {
rowSpacing: 0
anchors.fill: parent
implicitHeight: vertical ? Appearance.sizes.barWidth : Appearance.sizes.barHeight
implicitHeight: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.barHeight
implicitWidth: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.verticalBarWidth
Repeater {
@@ -212,6 +212,7 @@ Singleton {
property QtObject pixelSize: QtObject {
property int smallest: 10
property int smaller: 12
property int smallie: 13
property int small: 15
property int normal: 16
property int large: 17
@@ -254,14 +255,8 @@ Singleton {
easing.bezierCurve: root.animation.elementMove.bezierCurve
}
}
property Component colorAnimation: Component {
ColorAnimation {
duration: root.animation.elementMove.duration
easing.type: root.animation.elementMove.type
easing.bezierCurve: root.animation.elementMove.bezierCurve
}
}
}
property QtObject elementMoveEnter: QtObject {
property int duration: 400
property int type: Easing.BezierSpline
@@ -275,6 +270,7 @@ Singleton {
}
}
}
property QtObject elementMoveExit: QtObject {
property int duration: 200
property int type: Easing.BezierSpline
@@ -288,6 +284,7 @@ Singleton {
}
}
}
property QtObject elementMoveFast: QtObject {
property int duration: animationCurves.expressiveEffectsDuration
property int type: Easing.BezierSpline
@@ -304,6 +301,21 @@ Singleton {
easing.bezierCurve: root.animation.elementMoveFast.bezierCurve
}}
}
property QtObject elementResize: QtObject {
property int duration: 400
property int type: Easing.BezierSpline
property list<real> bezierCurve: animationCurves.emphasized
property int velocity: 650
property Component numberAnimation: Component {
NumberAnimation {
duration: root.animation.elementResize.duration
easing.type: root.animation.elementResize.type
easing.bezierCurve: root.animation.elementResize.bezierCurve
}
}
}
property QtObject clickBounce: QtObject {
property int duration: 200
property int type: Easing.BezierSpline
@@ -315,11 +327,13 @@ Singleton {
easing.bezierCurve: root.animation.clickBounce.bezierCurve
}}
}
property QtObject scroll: QtObject {
property int duration: 200
property int type: Easing.BezierSpline
property list<real> bezierCurve: animationCurves.standardDecel
}
property QtObject menuDecel: QtObject {
property int duration: 350
property int type: Easing.OutExpo
+42 -13
View File
@@ -122,12 +122,18 @@ Singleton {
}
property JsonObject background: JsonObject {
property bool fixedClockPosition: false
property real clockX: -500
property real clockY: -500
property bool showClock: true
property JsonObject clock: JsonObject {
property bool fixedPosition: false
property real x: -500
property real y: -500
property bool show: true
property string style: "cookie" // Options: "cookie", "digital"
property real scale: 1
}
property string wallpaperPath: ""
property string thumbnailPath: ""
property string quote: ""
property bool hideWhenFullscreen: true
property JsonObject parallax: JsonObject {
property bool vertical: false
property bool autoVertical: false
@@ -135,20 +141,12 @@ Singleton {
property real workspaceZoom: 1.07 // Relative to your screen, not wallpaper size
property bool enableSidebar: true
}
property JsonObject blur: JsonObject {
property bool enable: true
property int radius: 100
property bool centerClock: true
property bool showLockedText: true
property real extraZoom: 1.1
}
property string quote: ""
property bool hideWhenFullscreen: true
}
property JsonObject bar: JsonObject {
property JsonObject autoHide: JsonObject {
property bool enable: false
property int hoverRegionWidth: 2
property bool pushWindows: false
property JsonObject showWhenPressingSuper: JsonObject {
property bool enable: true
@@ -214,6 +212,11 @@ Singleton {
property bool autoKillTrays: false
}
property JsonObject crosshair: JsonObject {
// Valorant crosshair format. Use https://www.vcrdb.net/builder
property string code: "0;P;d;1;0l;10;0o;2;1b;0"
}
property JsonObject dock: JsonObject {
property bool enable: false
property bool monochromeIcons: true
@@ -256,6 +259,16 @@ Singleton {
}
}
property JsonObject lock: JsonObject {
property JsonObject blur: JsonObject {
property bool enable: false
property real radius: 100
property real extraZoom: 1.1
}
property bool centerClock: true
property bool showLockedText: true
}
property JsonObject media: JsonObject {
// Attempt to remove dupes (the aggregator playerctl one and browsers' native ones when there's plasma browser integration)
property bool filterDuplicatePlayers: true
@@ -265,6 +278,10 @@ Singleton {
property string userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
}
property JsonObject notifications: JsonObject {
property int timeout: 7000
}
property JsonObject osd: JsonObject {
property int timeout: 1000
}
@@ -355,6 +372,18 @@ Singleton {
property JsonObject screenshotTool: JsonObject {
property bool showContentRegions: true
}
property JsonObject workSafety: JsonObject {
property JsonObject enable: JsonObject {
property bool wallpaper: true
property bool clipboard: true
}
property JsonObject triggerCondition: JsonObject {
property list<string> networkNameKeywords: ["airport", "cafe", "college", "company", "eduroam", "free", "guest", "public", "school", "university"]
property list<string> fileKeywords: ["anime", "ecchi", "hentai", "yande.re", "konachan", "breast", "nipples", "pussy", "nsfw", "spoiler", "girl"]
property list<string> linkKeywords: ["hentai", "porn", "sukebei", "hitomi.la", "rule34", "gelbooru", "fanbox", "dlsite"]
}
}
}
}
}
@@ -10,7 +10,9 @@ Singleton {
* @returns {string}
*/
function trimFileProtocol(str) {
return str.startsWith("file://") ? str.slice(7) : str;
let s = str;
if (typeof s !== "string") s = str.toString(); // Convert to string if it's an url or whatever
return s.startsWith("file://") ? s.slice(7) : s;
}
/**
@@ -8,7 +8,7 @@ Singleton {
* Formats a string according to the args that are passed inc
* @param { string } str
* @param {...any} args
* @returns
* @returns { string }
*/
function format(str, ...args) {
return str.replace(/{(\d+)}/g, (match, index) => typeof args[index] !== 'undefined' ? args[index] : match);
@@ -35,10 +35,10 @@ Singleton {
}
/**
* Escapes single quotes in shell commands
* @param { string } str
* @returns { string }
*/
* Escapes single quotes in shell commands
* @param { string } str
* @returns { string }
*/
function shellSingleQuoteEscape(str) {
return String(str)
// .replace(/\\/g, '\\\\')
@@ -48,6 +48,7 @@ Singleton {
/**
* Splits markdown blocks into three different types: text, think, and code.
* @param { string } markdown
* @returns {Array<{type: "text" | "think" | "code", content: string, lang?: string, completed?: boolean}>}
*/
function splitMarkdownBlocks(markdown) {
const regex = /```(\w+)?\n([\s\S]*?)```|<think>([\s\S]*?)<\/think>/g;
@@ -182,6 +183,11 @@ Singleton {
return lines.join("\n");
}
/**
* Cleans up a music title by removing bracketed and special characters.
* @param { string } title
* @returns { string }
*/
function cleanMusicTitle(title) {
if (!title)
return "";
@@ -198,6 +204,11 @@ Singleton {
return title.trim();
}
/**
* Converts seconds to a friendly time string (e.g. 1:23 or 1:02:03).
* @param { number } seconds
* @returns { string }
*/
function friendlyTimeForSeconds(seconds) {
if (isNaN(seconds) || seconds < 0)
return "0:00";
@@ -212,13 +223,38 @@ Singleton {
}
}
/**
* Escapes HTML special characters in a string.
* @param { string } str
* @returns { string }
*/
function escapeHtml(str) {
if (typeof str !== 'string')
return str;
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}
/**
* Cleans a cliphist entry by removing leading digits and tab.
* @param { string } str
* @returns { string }
*/
function cleanCliphistEntry(str: string): string {
return str.replace(/^\d+\t/, "");
}
/**
* Checks if any substring in the list is contained in the string.
* @param { string } str
* @param { string[] } substrings
* @returns { boolean }
*/
function stringListContainsSubstring(str, substrings) {
for (let i = 0; i < substrings.length; ++i) {
if (str.includes(substrings[i])) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,27 @@
import QtQuick
import qs.modules.common
import qs.modules.common.functions
/**
* Material color scheme adapted to a given color. It's incomplete but enough for what we need...
*/
QtObject {
id: root
required property color color
readonly property bool colorIsDark: color.hslLightness < 0.5
property color colLayer0: ColorUtils.mix(Appearance.colors.colLayer0, root.color, (colorIsDark && Appearance.m3colors.darkmode) ? 0.6 : 0.5)
property color colLayer1: ColorUtils.mix(Appearance.colors.colLayer1, root.color, 0.5)
property color colOnLayer0: ColorUtils.mix(Appearance.colors.colOnLayer0, root.color, 0.5)
property color colOnLayer1: ColorUtils.mix(Appearance.colors.colOnLayer1, root.color, 0.5)
property color colSubtext: ColorUtils.mix(Appearance.colors.colOnLayer1, root.color, 0.5)
property color colPrimary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimary, root.color), root.color, 0.5)
property color colPrimaryHover: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimaryHover, root.color), root.color, 0.3)
property color colPrimaryActive: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimaryActive, root.color), root.color, 0.3)
property color colSecondary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colSecondary, root.color), root.color, 0.5)
property color colSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3secondaryContainer, root.color, 0.15)
property color colSecondaryContainerHover: ColorUtils.mix(Appearance.colors.colSecondaryContainerHover, root.color, 0.3)
property color colSecondaryContainerActive: ColorUtils.mix(Appearance.colors.colSecondaryContainerActive, root.color, 0.5)
property color colOnPrimary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.m3colors.m3onPrimary, root.color), root.color, 0.5)
property color colOnSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3onSecondaryContainer, root.color, 0.5)
}
@@ -113,7 +113,7 @@ Rectangle {
}
StyledToolTip {
content: Translation.tr("Edit directory")
text: Translation.tr("Edit directory")
}
}
}
@@ -12,6 +12,8 @@ Rectangle {
property string entry
property real maxWidth
property real maxHeight
property bool blur: false
property string blurText: "Image hidden"
property string imageDecodePath: Directories.cliphistDecode
property string imageDecodeFileName: `${entryNumber}`
@@ -19,26 +21,25 @@ Rectangle {
property string source
property int entryNumber: {
if (!root.entry) return 0
const match = root.entry.match(/^(\d+)\t/)
return match ? parseInt(match[1]) : 0
if (!root.entry)
return 0;
const match = root.entry.match(/^(\d+)\t/);
return match ? parseInt(match[1]) : 0;
}
property int imageWidth: {
if (!root.entry) return 0
const match = root.entry.match(/(\d+)x(\d+)/)
return match ? parseInt(match[1]) : 0
if (!root.entry)
return 0;
const match = root.entry.match(/(\d+)x(\d+)/);
return match ? parseInt(match[1]) : 0;
}
property int imageHeight: {
if (!root.entry) return 0
const match = root.entry.match(/(\d+)x(\d+)/)
return match ? parseInt(match[2]) : 0
if (!root.entry)
return 0;
const match = root.entry.match(/(\d+)x(\d+)/);
return match ? parseInt(match[2]) : 0;
}
property real scale: {
return Math.min(
root.maxWidth / imageWidth,
root.maxHeight / imageHeight,
1
)
return Math.min(root.maxWidth / imageWidth, root.maxHeight / imageHeight, 1);
}
color: Appearance.colors.colLayer1
@@ -47,26 +48,33 @@ Rectangle {
implicitWidth: imageWidth * scale
Component.onCompleted: {
decodeImageProcess.running = true
decodeImageProcess.running = true;
}
Process {
id: decodeImageProcess
command: ["bash", "-c",
`[ -f ${imageDecodeFilePath} ] || echo '${StringUtils.shellSingleQuoteEscape(root.entry)}' | ${Cliphist.cliphistBinary} decode > '${imageDecodeFilePath}'`
]
command: ["bash", "-c", `[ -f ${imageDecodeFilePath} ] || echo '${StringUtils.shellSingleQuoteEscape(root.entry)}' | ${Cliphist.cliphistBinary} decode > '${imageDecodeFilePath}'`]
onExited: (exitCode, exitStatus) => {
if (exitCode === 0) {
root.source = imageDecodeFilePath
root.source = imageDecodeFilePath;
} else {
console.error("[CliphistImage] Failed to decode image for entry:", root.entry)
root.source = ""
console.error("[CliphistImage] Failed to decode image for entry:", root.entry);
root.source = "";
}
}
}
Component.onDestruction: {
Quickshell.execDetached(["bash", "-c", `[ -f '${imageDecodeFilePath}' ] && rm -f '${imageDecodeFilePath}'`])
Quickshell.execDetached(["bash", "-c", `[ -f '${imageDecodeFilePath}' ] && rm -f '${imageDecodeFilePath}'`]);
}
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: image.width
height: image.height
radius: root.radius
}
}
Image {
@@ -82,15 +90,42 @@ Rectangle {
height: root.imageHeight * root.scale
sourceSize.width: width
sourceSize.height: height
}
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: image.width
height: image.height
radius: root.radius
Loader {
id: blurLoader
active: root.blur
anchors.fill: image
sourceComponent: GaussianBlur {
source: image
radius: 35
samples: radius * 2 + 1
Rectangle {
anchors.fill: parent
color: ColorUtils.transparentize(Appearance.colors.colLayer0, 0.5)
Column {
anchors {
left: parent.left
right: parent.right
verticalCenter: parent.verticalCenter
}
MaterialSymbol {
visible: width <= image.width
anchors.horizontalCenter: parent.horizontalCenter
text: "visibility_off"
font.pixelSize: 28
}
StyledText {
visible: width <= image.width
anchors.horizontalCenter: parent.horizontalCenter
text: root.blurText
color: Appearance.colors.colOnSurface
font.pixelSize: Appearance.font.pixelSize.smallie
}
}
}
}
}
}
@@ -24,7 +24,6 @@ RowLayout {
id: labelWidget
Layout.fillWidth: true
text: root.text
font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.colors.colOnSecondaryContainer
}
}
@@ -7,8 +7,11 @@ import QtQuick.Controls
RippleButton {
id: root
property string buttonIcon
Layout.fillWidth: true
implicitHeight: contentItem.implicitHeight + 8 * 2
font.pixelSize: Appearance.font.pixelSize.small
onClicked: checked = !checked
contentItem: RowLayout {
@@ -21,7 +24,7 @@ RippleButton {
id: labelWidget
Layout.fillWidth: true
text: root.text
font.pixelSize: Appearance.font.pixelSize.small
font: root.font
color: Appearance.colors.colOnSecondaryContainer
}
StyledSwitch {
@@ -32,7 +32,7 @@ ColumnLayout {
StyledToolTip {
extraVisibleCondition: false
alternativeVisibleCondition: infoMouseArea.containsMouse
content: root.tooltip
text: root.tooltip
}
}
}
@@ -0,0 +1,50 @@
import QtQuick
import QtQuick.Shapes
import Quickshell
Item {
id: root
property int sides: 12
property int implicitSize: 100
property real amplitude: implicitSize / 50
property int renderPoints: 360
property color color: "#605790"
property alias strokeWidth: shapePath.strokeWidth
implicitWidth: implicitSize
implicitHeight: implicitSize
Shape {
id: shape
anchors.fill: parent
preferredRendererType: Shape.CurveRenderer
ShapePath {
id: shapePath
strokeWidth: 0
fillColor: root.color
pathHints: ShapePath.PathSolid & ShapePath.PathNonIntersecting
PathPolyline {
property var pointsList: {
var points = []
var cx = shape.width / 2 // center x
var cy = shape.height / 2 // center y
var steps = root.renderPoints
var radius = root.implicitSize / 2 - root.amplitude
for (var i = 0; i <= steps; i++) {
var angle = (i / steps) * 2 * Math.PI
var wave = Math.sin(angle * root.sides + Math.PI/2) * root.amplitude
var x = Math.cos(angle) * (radius + wave) + cx
var y = Math.sin(angle) * (radius + wave) + cy
points.push(Qt.point(x, y))
}
return points
}
path: pointsList
}
}
}
}
@@ -6,6 +6,7 @@ StyledText {
property real iconSize: Appearance?.font.pixelSize.small ?? 16
property real fill: 0
property real truncatedFill: Math.round(fill * 100) / 100 // Reduce memory consumption spikes from constant font remapping
renderType: fill !== 0 ? Text.CurveRendering : Text.NativeRendering
font {
hintingPreference: Font.PreferFullHinting
family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded"
@@ -215,7 +215,7 @@ MouseArea { // Notification group area
altAction: () => { root.toggleExpanded() }
StyledToolTip {
content: Translation.tr("Tip: right-clicking a group\nalso expands it")
text: Translation.tr("Tip: right-clicking a group\nalso expands it")
}
}
}
@@ -4,6 +4,7 @@ import qs.services
import qs.modules.common.functions
import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Hyprland
import Quickshell.Services.Notifications
@@ -15,6 +16,7 @@ Item { // Notification item area
property bool onlyNotification: false
property real fontSize: Appearance.font.pixelSize.small
property real padding: onlyNotification ? 0 : 8
property real summaryElideRatio: 0.85
property real dragConfirmThreshold: 70 // Drag further to discard notification
property real dismissOvershoot: notificationIcon.implicitWidth + 20 // Account for gaps and bouncy animations
@@ -57,6 +59,12 @@ Item { // Notification item area
destroyAnimation.running = true;
}
TextMetrics {
id: summaryTextMetrics
font.pixelSize: root.fontSize
text: root.notificationObject.summary || ""
}
SequentialAnimation { // Drag finish animation
id: destroyAnimation
running: false
@@ -163,9 +171,9 @@ Item { // Notification item area
visible: !root.onlyNotification || !root.expanded
Layout.fillWidth: true
implicitHeight: summaryText.implicitHeight
// Layout.fillWidth: true
StyledText {
id: summaryText
Layout.fillWidth: summaryTextMetrics.width >= summaryRow.width * root.summaryElideRatio
visible: !root.onlyNotification
font.pixelSize: root.fontSize
color: Appearance.colors.colOnLayer3
@@ -209,7 +217,7 @@ Item { // Notification item area
textFormat: Text.RichText
text: {
return `<style>img{max-width:${300 /* binding to notificationBodyText.width would cause a binding loop */}px;}</style>` +
`${processNotificationBody(notificationObject.body, notificationObject.appName || notificationObject.summary).replace(/\n/g, "<br/>")}`
`${processNotificationBody(notificationObject.body, notificationObject.appName || notificationObject.summary).replace(/\n/g, "<br/>")}`
}
onLinkActivated: (link) => {
@@ -220,91 +228,110 @@ Item { // Notification item area
PointingHandLinkHover {}
}
StyledFlickable { // Notification actions
id: actionsFlickable
Item {
Layout.fillWidth: true
implicitHeight: actionRowLayout.implicitHeight
contentWidth: actionRowLayout.implicitWidth
clip: !onlyNotification
implicitWidth: actionsFlickable.implicitWidth
implicitHeight: actionsFlickable.implicitHeight
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on height {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on implicitHeight {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: actionsFlickable.width
height: actionsFlickable.height
radius: Appearance.rounding.small
}
}
RowLayout {
id: actionRowLayout
Layout.alignment: Qt.AlignBottom
ScrollEdgeFade {
target: actionsFlickable
vertical: false
}
NotificationActionButton {
Layout.fillWidth: true
buttonText: Translation.tr("Close")
urgency: notificationObject.urgency
implicitWidth: (notificationObject.actions.length == 0) ? ((actionsFlickable.width - actionRowLayout.spacing) / 2) :
(contentItem.implicitWidth + leftPadding + rightPadding)
StyledFlickable { // Notification actions
id: actionsFlickable
anchors.fill: parent
implicitHeight: actionRowLayout.implicitHeight
contentWidth: actionRowLayout.implicitWidth
onClicked: {
root.destroyWithAnimation()
}
contentItem: MaterialSymbol {
iconSize: Appearance.font.pixelSize.large
horizontalAlignment: Text.AlignHCenter
color: (notificationObject.urgency == NotificationUrgency.Critical) ?
Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface
text: "close"
}
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on height {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on implicitHeight {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Repeater {
id: actionRepeater
model: notificationObject.actions
RowLayout {
id: actionRowLayout
Layout.alignment: Qt.AlignBottom
NotificationActionButton {
Layout.fillWidth: true
buttonText: modelData.text
buttonText: Translation.tr("Close")
urgency: notificationObject.urgency
implicitWidth: (notificationObject.actions.length == 0) ? ((actionsFlickable.width - actionRowLayout.spacing) / 2) :
(contentItem.implicitWidth + leftPadding + rightPadding)
onClicked: {
Notifications.attemptInvokeAction(notificationObject.notificationId, modelData.identifier);
root.destroyWithAnimation()
}
}
}
NotificationActionButton {
Layout.fillWidth: true
urgency: notificationObject.urgency
implicitWidth: (notificationObject.actions.length == 0) ? ((actionsFlickable.width - actionRowLayout.spacing) / 2) :
(contentItem.implicitWidth + leftPadding + rightPadding)
onClicked: {
Quickshell.clipboardText = notificationObject.body
copyIcon.text = "inventory"
copyIconTimer.restart()
}
Timer {
id: copyIconTimer
interval: 1500
repeat: false
onTriggered: {
copyIcon.text = "content_copy"
contentItem: MaterialSymbol {
iconSize: Appearance.font.pixelSize.large
horizontalAlignment: Text.AlignHCenter
color: (notificationObject.urgency == NotificationUrgency.Critical) ?
Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface
text: "close"
}
}
contentItem: MaterialSymbol {
id: copyIcon
iconSize: Appearance.font.pixelSize.large
horizontalAlignment: Text.AlignHCenter
color: (notificationObject.urgency == NotificationUrgency.Critical) ?
Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface
text: "content_copy"
Repeater {
id: actionRepeater
model: notificationObject.actions
NotificationActionButton {
Layout.fillWidth: true
buttonText: modelData.text
urgency: notificationObject.urgency
onClicked: {
Notifications.attemptInvokeAction(notificationObject.notificationId, modelData.identifier);
}
}
}
NotificationActionButton {
Layout.fillWidth: true
urgency: notificationObject.urgency
implicitWidth: (notificationObject.actions.length == 0) ? ((actionsFlickable.width - actionRowLayout.spacing) / 2) :
(contentItem.implicitWidth + leftPadding + rightPadding)
onClicked: {
Quickshell.clipboardText = notificationObject.body
copyIcon.text = "inventory"
copyIconTimer.restart()
}
Timer {
id: copyIconTimer
interval: 1500
repeat: false
onTriggered: {
copyIcon.text = "content_copy"
}
}
contentItem: MaterialSymbol {
id: copyIcon
iconSize: Appearance.font.pixelSize.large
horizontalAlignment: Text.AlignHCenter
color: (notificationObject.urgency == NotificationUrgency.Critical) ?
Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface
text: "content_copy"
}
}
}
}
}
}
@@ -0,0 +1,52 @@
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
Item {
id: root
property string text: ""
property bool extraVisibleCondition: true
property bool alternativeVisibleCondition: false
property real horizontalPadding: 10
property real verticalPadding: 5
property var anchorEdges: Edges.Top
property var anchorGravity: anchorEdges
readonly property bool internalVisibleCondition: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition
Loader {
id: tooltipLoader
anchors.fill: parent
active: internalVisibleCondition
sourceComponent: PopupWindow {
visible: true
anchor {
window: root.QsWindow.window
item: root.parent
edges: root.anchorEdges
gravity: root.anchorGravity
}
mask: Region {
item: null
}
color: "transparent"
implicitWidth: contentItem.implicitWidth + root.horizontalPadding * 2
implicitHeight: contentItem.implicitHeight + root.verticalPadding * 2
StyledToolTipContent {
id: contentItem
anchors.centerIn: parent
text: root.text
shown: false
Component.onCompleted: shown = true
horizontalPadding: root.horizontalPadding
verticalPadding: root.verticalPadding
}
}
}
}
@@ -63,7 +63,7 @@ Button {
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onPressed: (event) => {
if(event.button === Qt.RightButton) {
if (root.altAction) root.altAction();
if (root.altAction) root.altAction(event);
return;
}
if(event.button === Qt.MiddleButton) {
@@ -0,0 +1,59 @@
import QtQuick
import qs.modules.common
import qs.modules.common.functions
Item {
id: root
z: 99
required property Item target
property real fadeSize: Appearance.m3colors.darkmode ? 40 : 20
property color color: ColorUtils.transparentize(Appearance.colors.colShadow, Appearance.m3colors.darkmode ? 0 : 0.7)
property bool vertical: true
anchors.fill: target
EndGradient {
anchors {
top: parent.top
left: parent.left
right: vertical ? parent.right : undefined
bottom: vertical ? undefined : parent.bottom
}
shown: !(root.vertical ? root.target.atYBeginning : root.target.atXBeginning)
}
EndGradient {
anchors {
bottom: parent.bottom
right: parent.right
left: vertical ? parent.left : undefined
top: vertical ? undefined : parent.top
}
shown: !(root.vertical ? root.target.atYEnd : root.target.atXEnd)
rotation: 180
}
component EndGradient: Rectangle {
required property bool shown
height: vertical ? root.fadeSize : parent.height
width: vertical ? parent.width : root.fadeSize
opacity: shown ? 1 : 0
visible: opacity > 0
Behavior on opacity {
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
}
gradient: Gradient {
orientation: root.vertical ? Gradient.Vertical : Gradient.Horizontal
GradientStop {
position: 0.0
color: root.color
}
GradientStop {
position: 1.0
color: ColorUtils.transparentize(root.color)
}
}
}
}
@@ -50,4 +50,5 @@ Flickable {
root.scrollTargetY = root.contentY;
}
}
}
@@ -10,7 +10,8 @@ import Quickshell.Services.Pipewire
RadioButton {
id: root
implicitHeight: contentItem.implicitHeight + 4 * 2
padding: 4
implicitHeight: contentItem.implicitHeight + padding * 2
property string description
property color activeColor: Appearance?.colors.colPrimary ?? "#685496"
property color inactiveColor: Appearance?.m3colors.m3onSurfaceVariant ?? "#45464F"
@@ -5,7 +5,7 @@ import qs.modules.common
RectangularShadow {
required property var target
anchors.fill: target
radius: target.radius
radius: 20
blur: 0.9 * Appearance.sizes.elevationMargin
offset: Qt.vector2d(0.0, 1.0)
spread: 1
@@ -149,7 +149,7 @@ Slider {
StyledToolTip {
extraVisibleCondition: root.pressed
content: root.tooltipContent
text: root.tooltipContent
}
}
}
@@ -6,55 +6,20 @@ import QtQuick.Layouts
ToolTip {
id: root
property string content
property bool extraVisibleCondition: true
property bool alternativeVisibleCondition: false
property bool internalVisibleCondition: {
const ans = (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition
return ans
}
readonly property bool internalVisibleCondition: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition
verticalPadding: 5
horizontalPadding: 10
opacity: internalVisibleCondition ? 1 : 0
visible: opacity > 0
Behavior on opacity {
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
}
horizontalPadding: 10
background: null
visible: internalVisibleCondition
contentItem: Item {
id: contentItemBackground
implicitWidth: tooltipTextObject.width + 2 * root.horizontalPadding
implicitHeight: tooltipTextObject.height + 2 * root.verticalPadding
Rectangle {
id: backgroundRectangle
anchors.bottom: contentItemBackground.bottom
anchors.horizontalCenter: contentItemBackground.horizontalCenter
color: Appearance?.colors.colTooltip ?? "#3C4043"
radius: Appearance?.rounding.verysmall ?? 7
width: internalVisibleCondition ? (tooltipTextObject.width + 2 * padding) : 0
height: internalVisibleCondition ? (tooltipTextObject.height + 2 * padding) : 0
clip: true
Behavior on width {
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on height {
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
}
StyledText {
id: tooltipTextObject
anchors.centerIn: parent
text: content
font.pixelSize: Appearance?.font.pixelSize.smaller ?? 14
font.hintingPreference: Font.PreferNoHinting // Prevent shaky text
color: Appearance?.colors.colOnTooltip ?? "#FFFFFF"
wrapMode: Text.Wrap
}
}
contentItem: StyledToolTipContent {
id: contentItem
text: root.text
shown: root.internalVisibleCondition
horizontalPadding: root.horizontalPadding
verticalPadding: root.verticalPadding
}
}
}
@@ -0,0 +1,52 @@
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item {
id: root
required property string text
property bool shown: false
property real horizontalPadding: 10
property real verticalPadding: 5
implicitWidth: tooltipTextObject.implicitWidth + 2 * root.horizontalPadding
implicitHeight: tooltipTextObject.implicitHeight + 2 * root.verticalPadding
property bool isVisible: backgroundRectangle.implicitHeight > 0
Rectangle {
id: backgroundRectangle
anchors {
bottom: root.bottom
horizontalCenter: root.horizontalCenter
}
color: Appearance?.colors.colTooltip ?? "#3C4043"
radius: Appearance?.rounding.verysmall ?? 7
opacity: shown ? 1 : 0
implicitWidth: shown ? (tooltipTextObject.implicitWidth + 2 * root.horizontalPadding) : 0
implicitHeight: shown ? (tooltipTextObject.implicitHeight + 2 * root.verticalPadding) : 0
clip: true
Behavior on implicitWidth {
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on implicitHeight {
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on opacity {
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
}
StyledText {
id: tooltipTextObject
anchors.centerIn: parent
text: root.text
font.pixelSize: Appearance?.font.pixelSize.smaller ?? 14
font.hintingPreference: Font.PreferNoHinting // Prevent shaky text
color: Appearance?.colors.colOnTooltip ?? "#FFFFFF"
wrapMode: Text.Wrap
}
}
}
@@ -9,7 +9,7 @@ function findSuitableMaterialSymbol(summary = "") {
const keywordsToTypes = {
'reboot': 'restart_alt',
'recording': 'screen_record',
'record': 'screen_record',
'battery': 'power',
'power': 'power',
'screenshot': 'screenshot_monitor',
@@ -21,7 +21,7 @@ function findSuitableMaterialSymbol(summary = "") {
'update': 'update',
'ai response': 'neurology',
'control': 'settings',
'upscale': 'compare',
'upsca': 'compare',
'install': 'deployed_code_update',
'startswith:file': 'folder_copy', // Declarative startsWith check
};
@@ -0,0 +1,57 @@
import qs
import qs.modules.common
import qs.modules.common.widgets
import qs.services
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
Scope {
id: root
Loader {
id: crosshairLoader
active: GlobalStates.crosshairOpen
sourceComponent: PanelWindow {
id: crosshairWindow
exclusionMode: ExclusionMode.Ignore
WlrLayershell.namespace: "quickshell:crosshair"
WlrLayershell.layer: WlrLayer.Overlay
visible: true
color: "transparent"
mask: Region { // Crosshair should not block mouse input
item: null
}
implicitWidth: crosshairContent.implicitWidth
implicitHeight: crosshairContent.implicitHeight
CrosshairContent {
id: crosshairContent
anchors.centerIn: parent
}
}
}
IpcHandler {
target: "sidebarRight"
function toggle(): void {
GlobalStates.crosshairOpen = !GlobalStates.crosshairOpen;
}
}
GlobalShortcut {
name: "crosshairToggle"
description: "Toggles crosshair on press"
onPressed: {
GlobalStates.crosshairOpen = !GlobalStates.crosshairOpen;
}
}
}
@@ -0,0 +1,197 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.modules.common
import qs.modules.common.functions
Item {
id: root
// Keys to props
// f, 0f, 1f, m are irrelevant as they're firing error stuff
// 0 is irrelevant because it's some profile stuff
property var propertyMap: ({
"c": "color",
"u": "colorCode",
"h": "outline",
"o": "outlineOpacity",
"t": "outlineThickness",
"d": "centerDot",
"a": "centerDotOpacity",
"z": "centerDotSize",
"0a": "innerLineOpacity",
"0l": "innerLineLength",
"0v": "innerLineVerticalLength",
"0g": "innerLineUnbindAxesLengths",
"0t": "innerLineThickness",
"0o": "innerLineOffset",
"1b": "outerLines",
"1a": "outerLineOpacity",
"1l": "outerLineLength",
"1v": "outerLineVerticalLength",
"1g": "outerLineUnbindAxesLengths",
"1t": "outerLineThickness",
"1o": "outerLineOffset",
})
property var colorMap: ({
0: "#FFFFFF",
1: "#00FF00",
2: "#7FFF00",
3: "#DFFF00",
4: "#FFFF00",
5: "#00FFFF",
6: "#FF00FF",
7: "#FF0000"
})
// Raw props
property int color: 0
property string colorCode: "#FFFFFF"
property bool outline: true
property real outlineOpacity: 0.5
property int outlineThickness: 1
property bool centerDot: false
property real centerDotOpacity: 1
property int centerDotSize: 2
property bool innerLines: true
property real innerLineOpacity: 0.8
property int innerLineLength: 6
property int innerLineVerticalLength: innerLineLength
property bool innerLineUnbindAxesLengths: false
property int innerLineThickness: 2
property int innerLineOffset: 3
property bool outerLines: true
property real outerLineOpacity: 0.35
property int outerLineLength: 2
property int outerLineVerticalLength: outerLineLength
property bool outerLineUnbindAxesLengths: false
property int outerLineThickness: 2
property int outerLineOffset: 10
property string defaultCode: "c;0;u;FFFFFF;h;1;o;0.5;t;1;d;0;a;1;z;2;0a;0.8;0l;6;0v;6;0g;0;0t;2;0o;3;1b;1;1a;0.35;1l;2;1v;2;1g;0;1t;2;1o;10"
function loadFromCode(code: string): void {
let args = code.split(";");
for (let i = 0; i < args.length; i+= 2) {
let key = args[i];
let value = args[i+1];
let targetKey = root.propertyMap[key];
let targetType = typeof root[targetKey];
if (targetKey === undefined) continue;
if (targetType === "number") {
value = parseFloat(value);
} else if (targetType === "boolean") {
value = (value === "1");
}
if (targetKey === "colorCode") {
value = "#" + value.slice(0, 6);
}
root[targetKey] = value;
}
if (!root.innerLineUnbindAxesLengths) {
root.innerLineVerticalLength = root.innerLineLength;
}
if (!root.outerLineUnbindAxesLengths) {
root.outerLineVerticalLength = root.outerLineLength;
}
}
// Update values from code
property var code: Config.options.crosshair.code
Component.onCompleted: reloadFromCode();
onCodeChanged: reloadFromCode();
function reloadFromCode() {
root.loadFromCode(root.defaultCode);
root.loadFromCode(root.code);
}
// Aggregated props
property color crosshairColor: {
if (colorMap[color] !== undefined) return root.colorMap[color];
if (color === 8) return colorCode;
return "#FFFFFF";
}
property int borderWidth: outline ? outlineThickness : 0
property color borderColor: ColorUtils.transparentize("black", 1 - root.outlineOpacity)
property color innerLineColor: ColorUtils.transparentize(root.crosshairColor, 1 - root.innerLineOpacity)
property color outerLineColor: ColorUtils.transparentize(root.crosshairColor, 1 - root.outerLineOpacity)
property int innerLineTotalOffset: root.centerDotSize / 2 + 1 + root.innerLineOffset
property int outerLineTotalOffset: root.centerDotSize / 2 + 1 + root.outerLineOffset
property real centerDotTotalSize: root.centerDotSize + root.borderWidth * 2
property real innerLineTotalSize: (innerLineTotalOffset + root.innerLineLength + root.borderWidth) * 2
property real outerLineTotalSize: (outerLineTotalOffset + root.outerLineLength + root.borderWidth) * 2
implicitWidth: Math.max(centerDotTotalSize, innerLineTotalSize, outerLineTotalSize) + 2 // 2 for pixel correction
implicitHeight: implicitWidth
// width: implicitWidth
// height: implicitHeight
Rectangle {
id: centerDot
visible: root.centerDot
anchors.centerIn: parent
color: root.crosshairColor
opacity: root.centerDotOpacity
width: centerDotTotalSize
height: width
border.width: root.borderWidth
border.color: root.borderColor
}
Repeater {
id: innerLines
model: 4
Item {
id: innerHair
z: index % 2 // Vertical lines above horizontal lines
required property int index
property int pixelCorrection: (root.innerLineThickness % 2 === 1 && index > 1) ? 1 : 0
property int hairLength: (innerHair.index % 2 === 0 ? root.innerLineLength : root.innerLineVerticalLength)
visible: root.innerLines && hairLength > 0
anchors.fill: parent
rotation: index * 90
Rectangle {
x: parent.width / 2 + root.innerLineTotalOffset - root.borderWidth + innerHair.pixelCorrection
y: parent.height / 2 - height / 2
color: root.innerLineColor
width: innerHair.hairLength + root.borderWidth * 2
height: root.innerLineThickness + root.borderWidth * 2
border.width: root.borderWidth
border.color: root.borderColor
}
}
}
Repeater {
id: outerLines
model: 4
Item {
id: outerHair
z: index % 2 + 2 // Vertical lines above horizontal lines, above inner lines
required property int index
property int pixelCorrection: (root.outerLineThickness % 2 === 1 && index > 1) ? 1 : 0
property int hairLength: (outerHair.index % 2 === 0 ? root.outerLineLength : root.outerLineVerticalLength)
visible: root.outerLines && hairLength > 0
anchors.fill: parent
rotation: index * 90
Rectangle {
x: parent.width / 2 + root.outerLineTotalOffset - root.borderWidth + outerHair.pixelCorrection
y: parent.height / 2 - height / 2
color: root.outerLineColor
width: hairLength + root.borderWidth * 2
height: root.outerLineThickness + root.borderWidth * 2
border.width: root.borderWidth
border.color: root.borderColor
}
}
}
}
@@ -1,4 +1,5 @@
import qs.modules.common
import qs.modules.common.models
import qs.modules.common.widgets
import qs.services
import qs.modules.common.functions
@@ -46,9 +47,9 @@ Item { // Player instance
}
}
Timer { // Force update for prevision
Timer { // Force update for revision
running: playerController.player?.playbackState == MprisPlaybackState.Playing
interval: 1000
interval: Config.options.resources.updateInterval
repeat: true
onTriggered: {
playerController.player.positionChanged()
@@ -82,22 +83,8 @@ Item { // Player instance
rescaleSize: 1 // Rescale to 1x1 pixel for faster processing
}
property bool backgroundIsDark: artDominantColor.hslLightness < 0.5
property QtObject blendedColors: QtObject {
property color colLayer0: ColorUtils.mix(Appearance.colors.colLayer0, artDominantColor, (backgroundIsDark && Appearance.m3colors.darkmode) ? 0.6 : 0.5)
property color colLayer1: ColorUtils.mix(Appearance.colors.colLayer1, artDominantColor, 0.5)
property color colOnLayer0: ColorUtils.mix(Appearance.colors.colOnLayer0, artDominantColor, 0.5)
property color colOnLayer1: ColorUtils.mix(Appearance.colors.colOnLayer1, artDominantColor, 0.5)
property color colSubtext: ColorUtils.mix(Appearance.colors.colOnLayer1, artDominantColor, 0.5)
property color colPrimary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimary, artDominantColor), artDominantColor, 0.5)
property color colPrimaryHover: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimaryHover, artDominantColor), artDominantColor, 0.3)
property color colPrimaryActive: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimaryActive, artDominantColor), artDominantColor, 0.3)
property color colSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3secondaryContainer, artDominantColor, 0.15)
property color colSecondaryContainerHover: ColorUtils.mix(Appearance.colors.colSecondaryContainerHover, artDominantColor, 0.3)
property color colSecondaryContainerActive: ColorUtils.mix(Appearance.colors.colSecondaryContainerActive, artDominantColor, 0.5)
property color colOnPrimary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.m3colors.m3onPrimary, artDominantColor), artDominantColor, 0.5)
property color colOnSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3onSecondaryContainer, artDominantColor, 0.5)
property QtObject blendedColors: AdaptedMaterialScheme {
color: artDominantColor
}
StyledRectangularShadow {
@@ -32,7 +32,7 @@ Item {
((monitor.height - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale / monitor.scale)
property real workspaceNumberMargin: 80
property real workspaceNumberSize: Math.min(workspaceImplicitHeight, workspaceImplicitWidth) * monitor.scale
property real workspaceNumberSize: 250 * monitor.scale
property int workspaceZ: 0
property int windowZ: 1
property int windowDraggingZ: 99999
@@ -97,8 +97,11 @@ Item {
StyledText {
anchors.centerIn: parent
text: workspaceValue
font.pixelSize: root.workspaceNumberSize * root.scale
font.weight: Font.DemiBold
font {
pixelSize: root.workspaceNumberSize * root.scale
weight: Font.DemiBold
family: Appearance.font.family.expressive
}
color: ColorUtils.transparentize(Appearance.colors.colOnLayer1, 0.8)
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
@@ -146,7 +149,7 @@ Item {
values: {
// console.log(JSON.stringify(ToplevelManager.toplevels.values.map(t => t), null, 2))
return ToplevelManager.toplevels.values.filter((toplevel) => {
const address = `0x${toplevel.HyprlandToplevel.address}`
const address = `0x${toplevel.HyprlandToplevel?.address}`
var win = windowByAddress[address]
const inWorkspaceGroup = (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown)
return inWorkspaceGroup;
@@ -235,7 +238,7 @@ Item {
StyledToolTip {
extraVisibleCondition: false
alternativeVisibleCondition: dragArea.containsMouse && !window.Drag.active
content: `${windowData.title}\n[${windowData.class}] ${windowData.xwayland ? "[XWayland] " : ""}\n`
text: `${windowData.title}\n[${windowData.class}] ${windowData.xwayland ? "[XWayland] " : ""}`
}
}
}
@@ -16,14 +16,16 @@ RippleButton {
property string query
property bool entryShown: entry?.shown ?? true
property string itemType: entry?.type ?? Translation.tr("App")
property string itemName: entry?.name
property string itemName: entry?.name ?? ""
property string itemIcon: entry?.icon ?? ""
property var itemExecute: entry?.execute
property string fontType: entry?.fontType ?? "main"
property string itemClickActionName: entry?.clickActionName
property string itemClickActionName: entry?.clickActionName ?? "Open"
property string bigText: entry?.bigText ?? ""
property string materialSymbol: entry?.materialSymbol ?? ""
property string cliphistRawString: entry?.cliphistRawString ?? ""
property bool blurImage: entry?.blurImage ?? false
property string blurImageText: entry?.blurImageText ?? "Image hidden"
visible: root.entryShown
property int horizontalMargin: 10
@@ -208,6 +210,8 @@ RippleButton {
entry: root.cliphistRawString
maxWidth: contentColumn.width
maxHeight: 140
blur: root.blurImage
blurText: root.blurImageText
}
}
}
@@ -233,8 +237,8 @@ RippleButton {
delegate: RippleButton {
id: actionButton
required property var modelData
property string iconName: modelData.icon
property string materialIconName: modelData.materialIcon
property string iconName: modelData.icon ?? ""
property string materialIconName: modelData.materialIcon ?? ""
implicitHeight: 34
implicitWidth: 34
@@ -246,7 +250,7 @@ RippleButton {
anchors.centerIn: parent
Loader {
anchors.centerIn: parent
active: !(actionButton.iconName && actionButton.iconName !== "") || actionButton.materialIconName
active: !(actionButton.iconName !== "") || actionButton.materialIconName
sourceComponent: MaterialSymbol {
text: actionButton.materialIconName || "video_settings"
font.pixelSize: Appearance.font.pixelSize.hugeass
@@ -255,7 +259,7 @@ RippleButton {
}
Loader {
anchors.centerIn: parent
active: !actionButton.materialIconName && actionButton.iconName && actionButton.iconName !== ""
active: actionButton.materialIconName.length == 0 && actionButton.iconName && actionButton.iconName !== ""
sourceComponent: IconImage {
source: Quickshell.iconPath(actionButton.iconName)
implicitSize: 20
@@ -266,7 +270,7 @@ RippleButton {
onClicked: modelData.execute()
StyledToolTip {
content: modelData.name
text: modelData.name
}
}
}
@@ -20,20 +20,10 @@ Item { // Wrapper
implicitHeight: searchWidgetContent.implicitHeight + Appearance.sizes.elevationMargin * 2
property string mathResult: ""
function disableExpandAnimation() {
searchWidthBehavior.enabled = false;
}
function cancelSearch() {
searchInput.selectAll();
root.searchingText = "";
searchWidthBehavior.enabled = true;
}
function setSearchingText(text) {
searchInput.text = text;
root.searchingText = text;
property bool clipboardWorkSafetyActive: {
const enabled = Config.options.workSafety.enable.clipboard;
const sensitiveNetwork = (StringUtils.stringListContainsSubstring(Network.networkName.toLowerCase(), Config.options.workSafety.triggerCondition.networkNameKeywords))
return enabled && sensitiveNetwork;
}
property var searchActions: [
@@ -97,6 +87,27 @@ Item { // Wrapper
appResults.currentIndex = 0;
}
function disableExpandAnimation() {
searchWidthBehavior.enabled = false;
}
function cancelSearch() {
searchInput.selectAll();
root.searchingText = "";
searchWidthBehavior.enabled = true;
}
function setSearchingText(text) {
searchInput.text = text;
root.searchingText = text;
}
function containsUnsafeLink(entry) {
if (entry == undefined) return false;
const unsafeKeywords = Config.options.workSafety.triggerCondition.linkKeywords;
return StringUtils.stringListContainsSubstring(entry.toLowerCase(), unsafeKeywords);
}
Timer {
id: nonAppResultsTimer
interval: Config.options.search.nonAppResultDelay
@@ -311,7 +322,12 @@ Item { // Wrapper
if (root.searchingText.startsWith(Config.options.search.prefix.clipboard)) {
// Clipboard
const searchString = root.searchingText.slice(Config.options.search.prefix.clipboard.length);
return Cliphist.fuzzyQuery(searchString).map(entry => {
return Cliphist.fuzzyQuery(searchString).map((entry, index, array) => {
const mightBlurImage = Cliphist.entryIsImage(entry) && root.clipboardWorkSafetyActive;
let shouldBlurImage = mightBlurImage;
if (mightBlurImage) {
shouldBlurImage = shouldBlurImage && (containsUnsafeLink(array[index - 1]) || containsUnsafeLink(array[index + 1]));
}
return {
cliphistRawString: entry,
name: StringUtils.cleanCliphistEntry(entry),
@@ -335,7 +351,9 @@ Item { // Wrapper
Cliphist.deleteEntry(entry);
}
}
]
],
blurImage: shouldBlurImage,
blurImageText: Translation.tr("Work safety")
};
}).filter(Boolean);
}
@@ -52,7 +52,7 @@ RippleButton {
}
StyledToolTip {
content: buttonText
text: buttonText
}
}
@@ -27,7 +27,7 @@ ContentPage {
Config.options.appearance.wallpaperTheming.enableQtApps = checked;
}
StyledToolTip {
content: Translation.tr("Shell & utilities theming must also be enabled")
text: Translation.tr("Shell & utilities theming must also be enabled")
}
}
ConfigSwitch {
@@ -38,7 +38,7 @@ ContentPage {
Config.options.appearance.wallpaperTheming.enableTerminal = checked;
}
StyledToolTip {
content: Translation.tr("Shell & utilities theming must also be enabled")
text: Translation.tr("Shell & utilities theming must also be enabled")
}
}
ConfigRow {
@@ -51,7 +51,7 @@ ContentPage {
Config.options.appearance.wallpaperTheming.terminalGenerationProps.forceDarkMode= checked;
}
StyledToolTip {
content: Translation.tr("Ignored if terminal theming is not enabled")
text: Translation.tr("Ignored if terminal theming is not enabled")
}
}
}
@@ -21,7 +21,7 @@ ContentPage {
Config.options.audio.protection.enable = checked;
}
StyledToolTip {
content: Translation.tr("Prevents abrupt increments and restricts volume limit")
text: Translation.tr("Prevents abrupt increments and restricts volume limit")
}
}
ConfigRow {
@@ -85,7 +85,7 @@ ContentPage {
Config.options.battery.automaticSuspend = checked;
}
StyledToolTip {
content: Translation.tr("Automatically suspends the system when battery is low")
text: Translation.tr("Automatically suspends the system when battery is low")
}
}
ConfigSpinBox {
@@ -14,9 +14,42 @@ ContentPage {
ConfigSwitch {
text: Translation.tr("Show clock")
checked: Config.options.background.showClock
checked: Config.options.background.clock.show
onCheckedChanged: {
Config.options.background.showClock = checked;
Config.options.background.clock.show = checked;
}
}
ConfigSpinBox {
text: Translation.tr("Scale (%)")
value: Config.options.background.clock.scale * 100
from: 1
to: 200
stepSize: 2
onValueChanged: {
Config.options.background.clock.scale = value / 100;
}
}
ContentSubsection {
title: Translation.tr("Clock style")
ConfigSelectionArray {
currentValue: Config.options.background.clock.style
onSelected: newValue => {
Config.options.background.clock.style = newValue;
}
options: [
{
displayName: Translation.tr("Simple digital"),
icon: "timer_10",
value: "digital"
},
{
displayName: Translation.tr("Material cookie"),
icon: "cookie",
value: "cookie"
},
]
}
}
@@ -61,6 +94,37 @@ ContentPage {
}
}
ContentSection {
icon: "point_scan"
title: Translation.tr("Crosshair")
MaterialTextArea {
Layout.fillWidth: true
placeholderText: Translation.tr("Crosshair code (in Valorant's format)")
text: Config.options.crosshair.code
wrapMode: TextEdit.Wrap
onTextChanged: {
Config.options.crosshair.code = text;
}
}
RowLayout {
Item { Layout.fillWidth: true }
RippleButtonWithIcon {
id: editorButton
buttonRadius: Appearance.rounding.full
materialIcon: "open_in_new"
mainText: Translation.tr("Open editor")
onClicked: {
Qt.openUrlExternally(`https://www.vcrdb.net/builder?c=${Config.options.crosshair.code}`);
}
StyledToolTip {
text: "www.vcrdb.net"
}
}
}
}
ContentSection {
icon: "call_to_action"
title: Translation.tr("Dock")
@@ -99,6 +163,68 @@ ContentPage {
}
}
ContentSection {
icon: "lock"
title: Translation.tr("Lock screen")
ContentSubsection {
title: Translation.tr("Blurred style")
ConfigSwitch {
text: Translation.tr('Enable blur')
checked: Config.options.lock.blur.enable
onCheckedChanged: {
Config.options.lock.blur.enable = checked;
}
}
ConfigSpinBox {
text: Translation.tr("Blur: Extra zoom (%)")
value: Config.options.lock.blur.extraZoom * 100
from: 1
to: 150
stepSize: 2
onValueChanged: {
Config.options.lock.blur.extraZoom = value / 100;
}
}
ConfigSwitch {
text: Translation.tr('Center clock')
checked: Config.options.lock.centerClock
onCheckedChanged: {
Config.options.lock.centerClock = checked;
}
}
ConfigSwitch {
text: Translation.tr('Show "Locked" text')
checked: Config.options.lock.showLockedText
onCheckedChanged: {
Config.options.lock.showLockedText = checked;
}
}
}
}
ContentSection {
icon: "notifications"
title: Translation.tr("Notifications")
ConfigSpinBox {
text: Translation.tr("Timeout duration (if not defined by notification) (ms)")
value: Config.options.notifications.timeout
from: 1000
to: 60000
stepSize: 1000
onValueChanged: {
Config.options.notifications.timeout = value;
}
}
}
ContentSection {
icon: "side_navigation"
title: Translation.tr("Sidebars")
@@ -110,7 +236,7 @@ ContentPage {
Config.options.sidebar.keepRightSidebarLoaded = checked;
}
StyledToolTip {
content: Translation.tr("When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\nUsing a custom kernel like linux-cachyos might help")
text: Translation.tr("When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\nUsing a custom kernel like linux-cachyos might help")
}
}
@@ -134,7 +260,7 @@ ContentPage {
}
StyledToolTip {
content: Translation.tr("When this is off you'll have to click")
text: Translation.tr("When this is off you'll have to click")
}
}
}
@@ -148,7 +274,7 @@ ContentPage {
}
StyledToolTip {
content: Translation.tr("Place the corners to trigger at the bottom")
text: Translation.tr("Place the corners to trigger at the bottom")
}
}
ConfigSwitch {
@@ -159,7 +285,7 @@ ContentPage {
}
StyledToolTip {
content: Translation.tr("Brightness and volume")
text: Translation.tr("Brightness and volume")
}
}
}
@@ -268,7 +394,7 @@ ContentPage {
Config.options.screenshotTool.showContentRegions = checked;
}
StyledToolTip {
content: Translation.tr("Such regions could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.")
text: Translation.tr("Such regions could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.")
}
}
}
@@ -76,6 +76,7 @@ ContentPage {
sourceSize.height: parent.implicitHeight
fillMode: Image.PreserveAspectCrop
source: Config.options.background.wallpaperPath
cache: false
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
@@ -93,20 +94,20 @@ ContentPage {
visible: Config.options.policies.weeb === 1
Layout.fillWidth: true
buttonRadius: Appearance.rounding.small
materialIcon: "wallpaper"
materialIcon: "ifl"
mainText: konachanWallProc.running ? Translation.tr("Be patient...") : Translation.tr("Random: Konachan")
onClicked: {
konachanWallProc.running = true;
}
StyledToolTip {
content: Translation.tr("Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers")
text: Translation.tr("Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers")
}
}
RippleButtonWithIcon {
Layout.fillWidth: true
materialIcon: "wallpaper"
StyledToolTip {
content: Translation.tr("Pick wallpaper image on your system")
text: Translation.tr("Pick wallpaper image on your system")
}
onClicked: {
Quickshell.execDetached(`${Directories.wallpaperSwitchScriptPath}`);
@@ -161,7 +162,7 @@ ContentPage {
Config.options.appearance.transparency.enable = checked;
}
StyledToolTip {
content: Translation.tr("Might look ass. Unsupported.")
text: Translation.tr("Might look ass. Unsupported.")
}
}
}
@@ -69,7 +69,7 @@ ContentPage {
Config.options.search.sloppy = checked;
}
StyledToolTip {
content: Translation.tr("Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)")
text: Translation.tr("Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)")
}
}
@@ -209,6 +209,9 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
else {
Ai.sendUserMessage(inputText);
}
// Always scroll to bottom when user sends a message
messageListView.positionViewAtEnd()
}
Process {
@@ -257,7 +260,7 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
}
StyledToolTip {
content: statusItem.description
text: statusItem.description
extraVisibleCondition: false
alternativeVisibleCondition: statusItem.containsMouse
}
@@ -305,6 +308,20 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
Item { // Messages
Layout.fillWidth: true
Layout.fillHeight: true
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: swipeView.width
height: swipeView.height
radius: Appearance.rounding.small
}
}
ScrollEdgeFade {
target: messageListView
vertical: true
}
StyledListView { // Message list
id: messageListView
anchors.fill: parent
@@ -315,15 +332,14 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
mouseScrollFactor: Config.options.interactions.scrolling.mouseScrollFactor * 1.4
property int lastResponseLength: 0
property bool shouldAutoScroll: true
clip: true
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: swipeView.width
height: swipeView.height
radius: Appearance.rounding.small
}
onContentYChanged: shouldAutoScroll = atYEnd
onContentHeightChanged: {
if (shouldAutoScroll) positionViewAtEnd();
}
onCountChanged: { // Auto-scroll when new messages are added
if (shouldAutoScroll) positionViewAtEnd();
}
add: null // Prevent function calls from being janky
@@ -141,6 +141,21 @@ Item {
Item {
Layout.fillWidth: true
Layout.fillHeight: true
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: swipeView.width
height: swipeView.height
radius: Appearance.rounding.small
}
}
ScrollEdgeFade {
target: booruResponseListView
vertical: true
}
StyledListView { // Booru responses
id: booruResponseListView
anchors.fill: parent
@@ -151,16 +166,6 @@ Item {
property int lastResponseLength: 0
clip: true
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: swipeView.width
height: swipeView.height
radius: Appearance.rounding.small
}
}
model: ScriptModel {
values: {
if(root.responses.length > booruResponseListView.lastResponseLength) {
@@ -41,7 +41,7 @@ Item { // Model indicator
id: toolTip
extraVisibleCondition: false
alternativeVisibleCondition: mouseArea.containsMouse // Show tooltip when hovered
content: root.tooltipText
text: root.tooltipText
}
}
}
@@ -101,7 +101,7 @@ Item {
ColumnLayout {
anchors.fill: parent
Flickable {
StyledFlickable {
Layout.fillWidth: true
Layout.fillHeight: true
contentHeight: contentColumn.implicitHeight
@@ -164,7 +164,7 @@ Rectangle {
text: "visibility_off"
}
StyledToolTip {
content: Translation.tr("Not visible to model")
text: Translation.tr("Not visible to model")
}
}
@@ -191,7 +191,7 @@ Rectangle {
}
StyledToolTip {
content: Translation.tr("Copy")
text: Translation.tr("Copy")
}
}
AiMessageControlButton {
@@ -206,7 +206,7 @@ Rectangle {
}
}
StyledToolTip {
content: root.editing ? Translation.tr("Save") : Translation.tr("Edit")
text: root.editing ? Translation.tr("Save") : Translation.tr("Edit")
}
}
AiMessageControlButton {
@@ -217,7 +217,7 @@ Rectangle {
root.renderMarkdown = !root.renderMarkdown
}
StyledToolTip {
content: Translation.tr("View Markdown source")
text: Translation.tr("View Markdown source")
}
}
AiMessageControlButton {
@@ -227,7 +227,7 @@ Rectangle {
Ai.removeMessage(root.messageIndex)
}
StyledToolTip {
content: Translation.tr("Delete")
text: Translation.tr("Delete")
}
}
}
@@ -84,7 +84,7 @@ ColumnLayout {
}
}
StyledToolTip {
content: Translation.tr("Copy code")
text: Translation.tr("Copy code")
}
}
AiMessageControlButton {
@@ -114,7 +114,7 @@ ColumnLayout {
}
}
StyledToolTip {
content: Translation.tr("Save to Downloads")
text: Translation.tr("Save to Downloads")
}
}
}
@@ -41,7 +41,7 @@ Button {
}
StyledToolTip {
content: `${StringUtils.wordWrap(root.imageData.tags, root.maxTagStringLineLength)}`
text: `${StringUtils.wordWrap(root.imageData.tags, root.maxTagStringLineLength)}`
}
padding: 0
@@ -105,7 +105,6 @@ Rectangle {
return true
}
implicitHeight: tagRowLayout.implicitHeight
// height: tagRowLayout.implicitHeight
contentWidth: tagRowLayout.implicitWidth
clip: true
@@ -118,9 +117,6 @@ Rectangle {
}
}
Behavior on height {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on implicitHeight {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
@@ -1,12 +1,7 @@
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell.Io
import Quickshell
import Quickshell.Wayland
@@ -88,7 +88,7 @@ Item {
Quickshell.reload(true);
}
StyledToolTip {
content: Translation.tr("Reload Hyprland & Quickshell")
text: Translation.tr("Reload Hyprland & Quickshell")
}
}
QuickToggleButton {
@@ -99,7 +99,7 @@ Item {
Quickshell.execDetached(["qs", "-p", root.settingsQmlPath]);
}
StyledToolTip {
content: Translation.tr("Settings")
text: Translation.tr("Settings")
}
}
QuickToggleButton {
@@ -109,7 +109,7 @@ Item {
GlobalStates.sessionOpen = true;
}
StyledToolTip {
content: Translation.tr("Session")
text: Translation.tr("Session")
}
}
}
@@ -30,7 +30,7 @@ RippleButton {
}
StyledToolTip {
content: tooltipText
text: tooltipText
extraVisibleCondition: tooltipText.length > 0
}
}
@@ -21,7 +21,7 @@ QuickToggleButton {
GlobalStates.sidebarRightOpen = false
}
StyledToolTip {
content: Translation.tr("%1 | Right-click to configure").arg(
text: Translation.tr("%1 | Right-click to configure").arg(
(BluetoothStatus.firstActiveDevice?.name ?? Translation.tr("Bluetooth"))
+ (BluetoothStatus.activeDeviceCount > 1 ? ` +${BluetoothStatus.activeDeviceCount - 1}` : "")
)
@@ -87,6 +87,6 @@ QuickToggleButton {
}
}
StyledToolTip {
content: Translation.tr("Cloudflare WARP (1.1.1.1)")
text: Translation.tr("Cloudflare WARP (1.1.1.1)")
}
}
@@ -21,11 +21,11 @@ QuickToggleButton {
}
altAction: () => {
Quickshell.execDetached(["easyeffects"])
Quickshell.execDetached(["bash", "-c", "flatpak run com.github.wwmm.easyeffects || easyeffects"])
GlobalStates.sidebarRightOpen = false
}
StyledToolTip {
content: Translation.tr("EasyEffects | Right-click to configure")
text: Translation.tr("EasyEffects | Right-click to configure")
}
}
@@ -26,6 +26,6 @@ QuickToggleButton {
}
}
StyledToolTip {
content: Translation.tr("Game mode")
text: Translation.tr("Game mode")
}
}
@@ -10,7 +10,7 @@ QuickToggleButton {
Idle.toggleInhibit()
}
StyledToolTip {
content: Translation.tr("Keep system awake")
text: Translation.tr("Keep system awake")
}
}
@@ -18,6 +18,6 @@ QuickToggleButton {
GlobalStates.sidebarRightOpen = false
}
StyledToolTip {
content: Translation.tr("%1 | Right-click to configure").arg(Network.networkName)
text: Translation.tr("%1 | Right-click to configure").arg(Network.networkName)
}
}
@@ -23,6 +23,6 @@ QuickToggleButton {
}
StyledToolTip {
content: Translation.tr("Night Light | Right-click to toggle Auto mode")
text: Translation.tr("Night Light | Right-click to toggle Auto mode")
}
}
@@ -26,7 +26,7 @@ RippleButton {
}
StyledToolTip {
content: tooltipText
text: tooltipText
extraVisibleCondition: tooltipText.length > 0
}
}
@@ -82,8 +82,8 @@ Scope {
id: hoverMaskRegion
anchors {
fill: barContent
leftMargin: -1
rightMargin: -1
leftMargin: -Config.options.bar.autoHide.hoverRegionWidth
rightMargin: -Config.options.bar.autoHide.hoverRegionWidth
}
}
@@ -269,16 +269,10 @@ Item { // Bar content region
color: rightSidebarButton.colText
}
}
Loader {
active: HyprlandXkb.layoutCodes.length > 1
visible: active
Bar.HyprlandXkbIndicator {
vertical: true
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: indicatorsColumnLayout.realSpacing
sourceComponent: StyledText {
text: HyprlandXkb.currentLayoutCode
font.pixelSize: Appearance.font.pixelSize.small
color: rightSidebarButton.colText
animateChange: true
}
}
MaterialSymbol {
Layout.bottomMargin: indicatorsColumnLayout.realSpacing
@@ -22,7 +22,7 @@ MouseArea {
Timer {
running: activePlayer?.playbackState == MprisPlaybackState.Playing
interval: 1000
interval: Config.options.resources.updateInterval
repeat: true
onTriggered: activePlayer.positionChanged()
}
@@ -71,11 +71,25 @@ Scope {
function toggle(): void {
root.toggleWallpaperSelector();
}
function random(): void {
Wallpapers.randomFromCurrentFolder();
}
}
GlobalShortcut {
name: "wallpaperSelectorToggle"
description: "Toggle wallpaper selector"
onPressed: root.toggleWallpaperSelector();
onPressed: {
root.toggleWallpaperSelector();
}
}
GlobalShortcut {
name: "wallpaperSelectorRandom"
description: "Select random wallpaper in current folder"
onPressed: {
Wallpapers.randomFromCurrentFolder();
}
}
}
@@ -40,6 +40,13 @@ MouseArea {
}
}
function selectWallpaperPath(filePath) {
if (filePath && filePath.length > 0) {
Wallpapers.select(filePath, root.useDarkMode);
filterField.text = "";
}
}
acceptedButtons: Qt.BackButton | Qt.ForwardButton
onPressed: event => {
if (event.button === Qt.BackButton) {
@@ -164,7 +171,7 @@ MouseArea {
{ icon: "movie", name: "Videos", path: Directories.videos },
{ icon: "", name: "---", path: "INTENTIONALLY_INVALID_DIR" },
{ icon: "wallpaper", name: "Wallpapers", path: `${Directories.pictures}/Wallpapers` },
{ icon: "favorite", name: "Homework", path: `${Directories.pictures}/homework` },
...(Config.options.policies.weeb === 1 ? [{ icon: "favorite", name: "Homework", path: `${Directories.pictures}/homework` }] : []),
]
delegate: RippleButton {
id: quickDirButton
@@ -185,6 +192,7 @@ MouseArea {
color: quickDirButton.toggled ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnLayer1
iconSize: Appearance.font.pixelSize.larger
text: quickDirButton.modelData.icon
fill: quickDirButton.toggled ? 1 : 0
}
StyledText {
Layout.fillWidth: true
@@ -267,8 +275,7 @@ MouseArea {
function activateCurrent() {
const filePath = grid.model.get(currentIndex, "filePath")
Wallpapers.select(filePath, root.useDarkMode);
filterField.text = "";
root.selectWallpaperPath(filePath);
}
model: Wallpapers.folderModel
@@ -287,8 +294,7 @@ MouseArea {
}
onActivated: {
Wallpapers.select(fileModelData.filePath, root.useDarkMode);
filterField.text = "";
root.selectWallpaperPath(fileModelData.filePath);
}
}
@@ -322,11 +328,29 @@ MouseArea {
Config.options.wallpaperSelector.useSystemFileDialog = true
}
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: "open_in_new"
iconSize: Appearance.font.pixelSize.larger
}
StyledToolTip {
content: Translation.tr("Use the system file picker instead\nRight-click to make this the default behavior")
text: Translation.tr("Use the system file picker instead\nRight-click to make this the default behavior")
}
}
ToolbarButton {
implicitWidth: height
onClicked: {
Wallpapers.randomFromCurrentFolder();
}
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: "ifl"
iconSize: Appearance.font.pixelSize.larger
}
StyledToolTip {
text: Translation.tr("Pick random from this folder")
}
}
@@ -334,11 +358,13 @@ MouseArea {
implicitWidth: height
onClicked: root.useDarkMode = !root.useDarkMode
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: root.useDarkMode ? "dark_mode" : "light_mode"
iconSize: Appearance.font.pixelSize.larger
}
StyledToolTip {
content: Translation.tr("Click to toggle light/dark mode (applied when wallpaper is chosen)")
text: Translation.tr("Click to toggle light/dark mode\n(applied when wallpaper is chosen)")
}
}
@@ -378,11 +404,18 @@ MouseArea {
}
ToolbarButton {
implicitWidth: height
onClicked: {
GlobalStates.wallpaperSelectorOpen = false;
}
contentItem: StyledText {
text: "Cancel"
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: "cancel_presentation"
iconSize: Appearance.font.pixelSize.larger
}
StyledToolTip {
text: Translation.tr("Cancel wallpaper selection")
}
}
}
+17 -6
View File
@@ -103,16 +103,27 @@ Singleton {
}
}
function setBrightness(value: real): void {
value = Math.max(0.01, Math.min(1, value));
const rounded = Math.round(value * monitor.rawMaxBrightness);
if (Math.round(brightness * monitor.rawMaxBrightness) === rounded)
return;
brightness = value;
// We need a delay for DDC monitors because they can be quite slow and might act weird with rapid changes
property var setTimer: Timer {
id: setTimer
interval: monitor.isDdc ? 300 : 0
onTriggered: {
syncBrightness();
}
}
function syncBrightness() {
const rounded = Math.round(monitor.brightness * monitor.rawMaxBrightness);
setProc.command = isDdc ? ["ddcutil", "-b", busNum, "setvcp", "10", rounded] : ["brightnessctl", "s", rounded, "--quiet"];
setProc.startDetached();
}
function setBrightness(value: real): void {
value = Math.max(0.01, Math.min(1, value));
monitor.brightness = value;
setTimer.restart();
}
Component.onCompleted: {
initialize();
}
@@ -25,12 +25,12 @@ Singleton {
function disable() {
root.active = false
Quickshell.execDetached(["pkill", "easyeffects"])
Quickshell.execDetached(["bash", "-c", "pkill easyeffects || flatpak pkill com.github.wwmm.easyeffects"])
}
function enable() {
root.active = true
Quickshell.execDetached(["easyeffects", "--gapplication-service"])
Quickshell.execDetached(["bash", "-c", "easyeffects --gapplication-service || flatpak run com.github.wwmm.easyeffects --gapplication-service"])
}
function toggle() {
@@ -44,7 +44,7 @@ Singleton {
Process {
id: fetchAvailabilityProc
running: true
command: ["bash", "-c", "command -v easyeffects"]
command: ["bash", "-c", "command -v easyeffects || flatpak info com.github.wwmm.easyeffects > /dev/null 2>&1"]
onExited: (exitCode, exitStatus) => {
root.available = exitCode === 0
}
@@ -53,7 +53,7 @@ Singleton {
Process {
id: fetchActiveStateProc
running: true
command: ["pidof", "easyeffects"]
command: ["bash", "-c", "pidof easyeffects || flatpak ps | grep com.github.wwmm.easyeffects > /dev/null 2>&1"]
onExited: (exitCode, exitStatus) => {
root.active = exitCode === 0
}
+16 -5
View File
@@ -46,13 +46,24 @@ Singleton {
if (!line.trim() || line.trim().startsWith('!'))
return false;
// Match: key + whitespace + description
const match = line.match(/^\s*(\S+)\s+(.+)$/);
if (match && match[2] === targetDescription) {
root.cachedLayoutCodes[match[2]] = match[1];
root.currentLayoutCode = match[1];
// Match layout: (whitespace + ) key + whitespace + description
const matchLayout = line.match(/^\s*(\S+)\s+(.+)$/);
if (matchLayout && matchLayout[2] === targetDescription) {
root.cachedLayoutCodes[matchLayout[2]] = matchLayout[1];
root.currentLayoutCode = matchLayout[1];
return true;
}
// Match variant: (whitespace + ) variant + whitespace + key + whitespace + description
const matchVariant = line.match(/^\s*(\S+)\s+(\S+)\s+(.+)$/);
if (matchVariant && matchVariant[3] === targetDescription) {
const complexLayout = matchVariant[2] + matchVariant[1];
root.cachedLayoutCodes[matchVariant[3]] = complexLayout;
root.currentLayoutCode = complexLayout;
return true;
}
return false;
});
// console.log("[HyprlandXkb] Found line:", foundLine);
// console.log("[HyprlandXkb] Layout:", root.currentLayoutName, "| Code:", root.currentLayoutCode);
@@ -60,7 +60,7 @@ Singleton {
component NotifTimer: Timer {
required property int notificationId
interval: 5000
interval: 7000
running: true
onTriggered: () => {
root.timeoutNotification(notificationId);
@@ -168,7 +168,7 @@ Singleton {
if (notification.expireTimeout != 0) {
newNotifObject.timer = notifTimerComponent.createObject(root, {
"notificationId": newNotifObject.notificationId,
"interval": notification.expireTimeout < 0 ? 5000 : notification.expireTimeout,
"interval": notification.expireTimeout < 0 ? (Config?.options.notifications.timeout ?? 7000) : notification.expireTimeout,
});
}
}
+13 -2
View File
@@ -16,7 +16,7 @@ Singleton {
id: root
property string thumbgenScriptPath: `${FileUtils.trimFileProtocol(Directories.scriptPath)}/thumbnails/thumbgen.py`
property string generateThumbnailsMagicScriptPath: `${FileUtils.trimFileProtocol(Directories.scriptPath)}/thumbnails/generate-thumbnails-magick.sh`
property string generateThumbnailsMagickScriptPath: `${FileUtils.trimFileProtocol(Directories.scriptPath)}/thumbnails/generate-thumbnails-magick.sh`
property alias directory: folderModel.folder
readonly property string effectiveDirectory: FileUtils.trimFileProtocol(folderModel.folder.toString())
property url defaultFolder: Qt.resolvedUrl(`${Directories.pictures}/Wallpapers`)
@@ -33,6 +33,8 @@ Singleton {
signal thumbnailGenerated(directory: string)
signal thumbnailGeneratedFile(filePath: string)
function load () {} // For forcing initialization
// Executions
Process {
id: applyProc
@@ -77,6 +79,14 @@ Singleton {
selectProc.select(filePath, darkMode);
}
function randomFromCurrentFolder(darkMode = Appearance.m3colors.darkmode) {
if (folderModel.count === 0) return;
const randomIndex = Math.floor(Math.random() * folderModel.count);
const filePath = folderModel.get(randomIndex, "filePath");
print("Randomly selected wallpaper:", filePath);
root.select(filePath, darkMode);
}
Process {
id: validateDirProc
property string nicePath: ""
@@ -136,12 +146,13 @@ Singleton {
// Thumbnail generation
function generateThumbnail(size: string) {
// console.log("[Wallpapers] Updating thumbnails")
if (!["normal", "large", "x-large", "xx-large"].includes(size)) throw new Error("Invalid thumbnail size");
thumbgenProc.directory = root.directory
thumbgenProc.running = false
thumbgenProc.command = [
"bash", "-c",
`${thumbgenScriptPath} --size ${size} --machine_progress -d ${root.directory} || ${generateThumbnailsMagicScriptPath} --size ${size} -d ${root.directory}`,
`${thumbgenScriptPath} --size ${size} --machine_progress -d ${FileUtils.trimFileProtocol(root.directory)} || ${generateThumbnailsMagickScriptPath} --size ${size} -d ${root.directory}`,
]
root.thumbnailGenerationProgress = 0
thumbgenProc.running = true
+3 -3
View File
@@ -71,8 +71,8 @@ ApplicationWindow {
MaterialThemeLoader.reapplyTheme()
}
minimumWidth: 600
minimumHeight: 400
minimumWidth: 750
minimumHeight: 500
width: 1100
height: 750
color: Appearance.m3colors.m3background
@@ -177,7 +177,7 @@ ApplicationWindow {
}
StyledToolTip {
content: Translation.tr("Open the shell config file.\nIf the button doesn't work or doesn't open in your favorite editor,\nyou can manually open ~/.config/illogical-impulse/config.json")
text: Translation.tr("Open the shell config file.\nIf the button doesn't work or doesn't open in your favorite editor,\nyou can manually open ~/.config/illogical-impulse/config.json")
}
}
+4
View File
@@ -11,6 +11,7 @@ import "./modules/common/"
import "./modules/background/"
import "./modules/bar/"
import "./modules/cheatsheet/"
import "./modules/crosshair/"
import "./modules/dock/"
import "./modules/lock/"
import "./modules/mediaControls/"
@@ -36,6 +37,7 @@ ShellRoot {
property bool enableBar: true
property bool enableBackground: true
property bool enableCheatsheet: true
property bool enableCrosshair: true
property bool enableDock: true
property bool enableLock: true
property bool enableMediaControls: true
@@ -59,11 +61,13 @@ ShellRoot {
FirstRunExperience.load()
ConflictKiller.load()
Cliphist.refresh()
Wallpapers.load()
}
LazyLoader { active: enableBar && Config.ready && !Config.options.bar.vertical; component: Bar {} }
LazyLoader { active: enableBackground; component: Background {} }
LazyLoader { active: enableCheatsheet; component: Cheatsheet {} }
LazyLoader { active: enableCrosshair; component: Crosshair {} }
LazyLoader { active: enableDock && Config.options.dock.enable; component: Dock {} }
LazyLoader { active: enableLock; component: Lock {} }
LazyLoader { active: enableMediaControls; component: MediaControls {} }
+7 -3
View File
@@ -109,6 +109,10 @@ ApplicationWindow {
text: "close"
iconSize: 20
}
StyledToolTip {
text: Translation.tr("Tip: Close a window with Super+Q")
}
}
}
}
@@ -239,20 +243,20 @@ ApplicationWindow {
visible: Config.options.policies.weeb === 1
Layout.alignment: Qt.AlignHCenter
buttonRadius: Appearance.rounding.small
materialIcon: "wallpaper"
materialIcon: "ifl"
mainText: konachanWallProc.running ? Translation.tr("Be patient...") : Translation.tr("Random: Konachan")
onClicked: {
console.log(konachanWallProc.command.join(" "));
konachanWallProc.running = true;
}
StyledToolTip {
content: Translation.tr("Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers")
text: Translation.tr("Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers")
}
}
RippleButtonWithIcon {
materialIcon: "wallpaper"
StyledToolTip {
content: Translation.tr("Pick wallpaper image on your system")
text: Translation.tr("Pick wallpaper image on your system")
}
onClicked: {
Quickshell.execDetached([`${Directories.wallpaperSwitchScriptPath}`]);
+1 -1
View File
@@ -23,7 +23,7 @@
;; Define function to read SCSS variables
(defun material-get-color-from-scss (var-name)
"Extract color value for VAR-NAME from material_colors.scss file."
(let* ((scss-file (expand-file-name "~/.cache/ags/user/generated/material_colors.scss"))
(let* ((scss-file (expand-file-name "~/.local/state/quickshell/user/generated/material_colors.scss"))
(scss-content (with-temp-buffer
(insert-file-contents scss-file)
(buffer-string)))
+64 -59
View File
@@ -47,8 +47,8 @@
| [Hyprland](https://github.com/hyprwm/hyprland) | The compositor (manages and renders windows) |
| [Quickshell](https://quickshell.outfoxxed.me/) | A QtQuick-based widget system, used for the status bar, sidebars, etc. |
- THERE IS NO WAYBAR
- For the full list of dependencies, see the [arch-packages folder](https://github.com/end-4/dots-hyprland/tree/main/arch-packages)
- THERE IS NO WAYBAR STOP FUCKING CALLING EVERY BAR WAYBAR
</details>
<details>
@@ -62,20 +62,14 @@
<h3></h3>
</div>
<table style="border-collapse: collapse;">
<tr>
<td width="25%">
<img src=".github/assets/illogical-impulse.svg" alt="illogical-impulse logo" style="float:left; width:100%;">
</td>
<td width="75%">
<i>latest and only style that I actively use. Other past styles are still there for your viewing pleasure and not actual use, but code is still available, see below.</i>
</td>
</tr>
</table>
<div align="center">
<img src=".github/assets/illogical-impulse.svg" alt="illogical-impulse logo" style="float:left; width:400;">
</div>
### illogical-impulse<sup>Quickshell</sup>
This is the latest and only supported style. Other stuff are still there mostly for viewing pleasure and not actual use, but code is still available, see below.
Widget system: Quickshell | Support: Yes
[Showcase video](https://www.youtube.com/watch?v=RPwovTInagE)
@@ -86,52 +80,7 @@ Widget system: Quickshell | Support: Yes
| Window management | Weeb power |
| <img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/e77a7c96-1905-4126-a2a0-434f818825a2" /> | <img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/c8544e99-8881-477f-b83a-d6e35c0184a1" /> |
### illogical-impulse<sup>AGS</sup> <sub>(Deprecated)</sub>
Widget system: AGS | Support: No
| AI | Common widgets |
|:---|:---------------|
| ![image](https://github.com/user-attachments/assets/9d7af13f-89ef-470d-ba78-d2288b79cf60) | ![image](https://github.com/end-4/dots-hyprland/assets/97237370/406b72b6-fa38-4f0d-a6c4-4d7d5d5ddcb7) |
| Window management | Weeb power |
| ![image](https://github.com/user-attachments/assets/02983b9b-79ba-4c25-8717-90bef2357ae5) | ![image](https://github.com/user-attachments/assets/bbb332ec-962a-4e88-a95b-486d0bd8ce76) |
### Very old stuff
- Not likely to work, but the source is still available in the [`archive`](https://github.com/end-4/dots-hyprland/tree/archive) branch. Extremely spaghetti.
- Click image for a presentation video
#### m3ww
Widget system: EWW | Support: No, dead
<a href="https://streamable.com/85ch8x">
<img src="https://github.com/end-4/dots-hyprland/assets/97237370/09533e64-b6d7-47eb-a840-ee90c6776adf" alt="Material Eww!">
</a>
#### NovelKnock
Widget system: EWW | Support: No, dead
<a href="https://streamable.com/7vo61k">
<img src="https://github.com/end-4/dots-hyprland/assets/97237370/42903d03-bf6f-49d4-be7f-dd77e6cb389d" alt="Desktop Preview">
</a>
#### Hybrid
Widget system: EWW | Support: No, dead
<a href="https://streamable.com/4oogot">
<img src="https://github.com/end-4/dots-hyprland/assets/97237370/190deb1e-f6f5-46ce-8cf0-9b39944c079d" alt="click the circles!">
</a>
#### Windoes
Widget system: EWW | Support: No, dead
<a href="https://streamable.com/5qx614">
<img src="https://github.com/end-4/dots-hyprland/assets/97237370/b15317b1-f295-49f5-b90c-fb6328b8d886" alt="Desktop Preview">
</a>
### Other styles: Available at the end of the readme.
<div align="center">
<h2>• thank you •</h2>
@@ -162,4 +111,60 @@ Widget system: AGS | Support: No
</div>
- Inspiration: osu!lazer, Windows 11, Material Design 3, AvdanOS (concept)
- Copying: The license allows you to. Personally I have absolutely no problem with others redistributing/recreating my work. There's no "stealing" (unless you do weird stuff and violate the license). <sub>(this note is here because some people actually asked)</sub>
- Copying: The license allows you to. Personally I have absolutely no problem with others redistributing/recreating my work. There's no "stealing" (maybe unless you loudly do weird stuff and violate the license) <sub>(some people actually had to ask smh)</sub>
---
---
<div align="center">
<h2>• old, UNSUPPORTED stuff •</h2>
<h3></h3>
</div>
- Source for illogical-impulse AGS available in the `ii-ags` branch, others in the `archive` branch.
- The list goes from newest to the oldest, and the code quality is worse the older a style is
- No bug fix or official support will be provided.
### illogical-impulse<sup>AGS</sup>
Widget system: AGS | Support: No
| AI | Common widgets |
|:---|:---------------|
| ![image](https://github.com/user-attachments/assets/9d7af13f-89ef-470d-ba78-d2288b79cf60) | ![image](https://github.com/end-4/dots-hyprland/assets/97237370/406b72b6-fa38-4f0d-a6c4-4d7d5d5ddcb7) |
| Window management | Weeb power |
| ![image](https://github.com/user-attachments/assets/02983b9b-79ba-4c25-8717-90bef2357ae5) | ![image](https://github.com/user-attachments/assets/bbb332ec-962a-4e88-a95b-486d0bd8ce76) |
#### m3ww
Widget system: EWW | Support: No, dead
<a href="https://streamable.com/85ch8x">
<img src="https://github.com/end-4/dots-hyprland/assets/97237370/09533e64-b6d7-47eb-a840-ee90c6776adf" alt="Material Eww!">
</a>
#### NovelKnock
Widget system: EWW | Support: No
<a href="https://streamable.com/7vo61k">
<img src="https://github.com/end-4/dots-hyprland/assets/97237370/42903d03-bf6f-49d4-be7f-dd77e6cb389d" alt="Desktop Preview">
</a>
#### Hybrid
Widget system: EWW | Support: No
<a href="https://streamable.com/4oogot">
<img src="https://github.com/end-4/dots-hyprland/assets/97237370/190deb1e-f6f5-46ce-8cf0-9b39944c079d" alt="click the circles!">
</a>
#### Windoes
Widget system: EWW | Support: No
<a href="https://streamable.com/5qx614">
<img src="https://github.com/end-4/dots-hyprland/assets/97237370/b15317b1-f295-49f5-b90c-fb6328b8d886" alt="Desktop Preview">
</a>