mirror of
https://github.com/end-4/dots-hyprland.git
synced 2026-06-05 14:59:27 -05:00
Merge branch 'end-4:main' into main
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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.);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {} }
|
||||
|
||||
@@ -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}`]);
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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 |
|
||||
|:---|:---------------|
|
||||
|  |  |
|
||||
| Window management | Weeb power |
|
||||
|  |  |
|
||||
|
||||
### 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 |
|
||||
|:---|:---------------|
|
||||
|  |  |
|
||||
| Window management | Weeb power |
|
||||
|  |  |
|
||||
|
||||
#### 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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user