forked from Shinonome/dots-hyprland
Merge branch 'ii-qs' into ii-qs-patch-1
This commit is contained in:
@@ -17,7 +17,9 @@ if test -f ~/.local/state/quickshell/user/generated/terminal/sequences.txt
|
||||
cat ~/.local/state/quickshell/user/generated/terminal/sequences.txt
|
||||
end
|
||||
|
||||
alias pamcan=pacman
|
||||
alias pamcan pacman
|
||||
alias ls 'eza --icons'
|
||||
|
||||
|
||||
# function fish_prompt
|
||||
# set_color cyan; echo (pwd)
|
||||
|
||||
@@ -7,17 +7,17 @@ general {
|
||||
}
|
||||
|
||||
listener {
|
||||
timeout = 180 # 3mins
|
||||
timeout = 300 # 5mins
|
||||
on-timeout = loginctl lock-session
|
||||
}
|
||||
|
||||
listener {
|
||||
timeout = 240 # 4mins
|
||||
timeout = 600 # 10mins
|
||||
on-timeout = hyprctl dispatch dpms off
|
||||
on-resume = hyprctl dispatch dpms on
|
||||
}
|
||||
|
||||
listener {
|
||||
timeout = 540 # 9mins
|
||||
timeout = 900 # 15mins
|
||||
on-timeout = $suspend_cmd
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bar, wallpaper
|
||||
exec-once = swww-daemon --format xrgb --no-cache
|
||||
exec-once = sleep 0.5; swww img "$(cat ~/.local/state/quickshell/user/generated/wallpaper.txt)" --transition-step 100 --transition-fps 120 --transition-type grow --transition-angle 30 --transition-duration 1
|
||||
exec-once = sleep 0.5; swww img "$(cat ~/.local/state/quickshell/user/generated/wallpaper/path.txt)" --transition-step 100 --transition-fps 120 --transition-type grow --transition-angle 30 --transition-duration 1
|
||||
exec-once = /usr/lib/geoclue-2.0/demos/agent & gammastep
|
||||
exec-once = qs &
|
||||
|
||||
|
||||
@@ -59,15 +59,17 @@ decoration {
|
||||
contrast = 1
|
||||
popups = true
|
||||
popups_ignorealpha = 0.6
|
||||
input_methods = true
|
||||
input_methods_ignorealpha = 0.8
|
||||
}
|
||||
|
||||
shadow {
|
||||
enabled = true
|
||||
ignore_window = true
|
||||
range = 70
|
||||
offset = 0 4
|
||||
render_power = 2
|
||||
color = rgba(00000020)
|
||||
range = 30
|
||||
offset = 0 2
|
||||
render_power = 4
|
||||
color = rgba(00000010)
|
||||
}
|
||||
|
||||
# Dim
|
||||
|
||||
@@ -208,7 +208,7 @@ bind = Super, W, exec, zen-browser # [hidden]
|
||||
bind = Super+Shift, W, exec, wps # WPS Office
|
||||
bind = Super, X, exec, kate # Kate (text editor)
|
||||
bind = Ctrl+Super, V, exec, pavucontrol-qt # Pavucontrol Qt (volume mixer)
|
||||
bind = Super, I, exec, XDG_CURRENT_DESKTOP=gnome gnome-control-center # GNOME Control center (settings app)
|
||||
bind = Super, I, exec, systemsettings # Plasma system settings
|
||||
bind = Ctrl+Shift, Escape, exec, plasma-systemmonitor --page-name Processes # Plasma system monitor
|
||||
|
||||
# Cursed stuff
|
||||
|
||||
@@ -19,6 +19,8 @@ windowrulev2 = center, class:^(org.pulseaudio.pavucontrol)$
|
||||
windowrulev2 = float, class:^(nm-connection-editor)$
|
||||
windowrulev2 = size 45%, class:^(nm-connection-editor)$
|
||||
windowrulev2 = center, class:^(nm-connection-editor)$
|
||||
windowrulev2 = float, class:.*plasmawindowed.*
|
||||
windowrulev2 = float, class:kcm_.*
|
||||
|
||||
# No appearance
|
||||
# kde-material-you-colors spawns a window when changing dark/light theme. This is to make sure it doesn't interfere at all.
|
||||
@@ -116,9 +118,12 @@ layerrule = animation fade, quickshell:screenCorners
|
||||
layerrule = animation slide right, quickshell:sidebarRight
|
||||
layerrule = animation slide left, quickshell:sidebarLeft
|
||||
layerrule = animation slide bottom, quickshell:osk
|
||||
layerrule = animation slide bottom, quickshell:dock
|
||||
layerrule = blur, quickshell:session
|
||||
layerrule = noanim, quickshell:session
|
||||
layerrule = animation fade, quickshell:notificationPopup
|
||||
layerrule = blur, quickshell:backgroundWidgets
|
||||
layerrule = ignorealpha 0.05, quickshell:backgroundWidgets
|
||||
|
||||
# layerrule = blurpopups, quickshell:.*
|
||||
# layerrule = blur, quickshell:.*
|
||||
|
||||
@@ -45,4 +45,4 @@ post_hook = '~/.config/matugen/templates/kde/kde-material-you-colors-wrapper.sh'
|
||||
|
||||
[templates.wallpaper]
|
||||
input_path = '~/.config/matugen/templates/wallpaper.txt'
|
||||
output_path = '~/.local/state/quickshell/user/generated/wallpaper.txt'
|
||||
output_path = '~/.local/state/quickshell/user/generated/wallpaper/path.txt'
|
||||
|
||||
Executable
+338
@@ -0,0 +1,338 @@
|
||||
#!/usr/bin/env python3
|
||||
# Disclaimer: This script was ai-generated and went through minimal revision.
|
||||
|
||||
import os
|
||||
os.environ["OPENCV_LOG_LEVEL"] = "SILENT"
|
||||
import cv2
|
||||
import numpy as np
|
||||
import argparse
|
||||
import json
|
||||
|
||||
def center_crop(img, target_w, target_h):
|
||||
h, w = img.shape[:2]
|
||||
if w == target_w and h == target_h:
|
||||
return img
|
||||
x1 = max(0, (w - target_w) // 2)
|
||||
y1 = max(0, (h - target_h) // 2)
|
||||
x2 = x1 + target_w
|
||||
y2 = y1 + target_h
|
||||
return img[y1:y2, x1:x2]
|
||||
|
||||
def find_least_busy_region(image_path, region_width=300, region_height=200, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode="fill", padding=50):
|
||||
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
|
||||
if img is None:
|
||||
raise FileNotFoundError(f"Image not found: {image_path}")
|
||||
orig_h, orig_w = img.shape
|
||||
scale = 1.0
|
||||
if screen_width is not None and screen_height is not None:
|
||||
scale_w = screen_width / orig_w
|
||||
scale_h = screen_height / orig_h
|
||||
if screen_mode == "fill":
|
||||
scale = max(scale_w, scale_h)
|
||||
else:
|
||||
scale = min(scale_w, scale_h)
|
||||
new_w = int(orig_w * scale)
|
||||
new_h = int(orig_h * scale)
|
||||
if verbose:
|
||||
print(f"Scaling image from {orig_w}x{orig_h} to {new_w}x{new_h} (scale: {scale:.3f}, mode: {screen_mode})")
|
||||
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
|
||||
img = center_crop(img, screen_width, screen_height)
|
||||
if verbose:
|
||||
print(f"Cropped image to {screen_width}x{screen_height}")
|
||||
else:
|
||||
if verbose:
|
||||
print(f"Using original image size: {orig_w}x{orig_h}")
|
||||
arr = img.astype(np.float64)
|
||||
h, w = arr.shape
|
||||
# Use OpenCV's integral for fast computation
|
||||
integral = cv2.integral(arr, sdepth=cv2.CV_64F)[1:,1:]
|
||||
integral_sq = cv2.integral(arr**2, sdepth=cv2.CV_64F)[1:,1:]
|
||||
def region_sum(ii, x1, y1, x2, y2):
|
||||
total = ii[y2, x2]
|
||||
if x1 > 0:
|
||||
total -= ii[y2, x1-1]
|
||||
if y1 > 0:
|
||||
total -= ii[y1-1, x2]
|
||||
if x1 > 0 and y1 > 0:
|
||||
total += ii[y1-1, x1-1]
|
||||
return total
|
||||
min_var = None
|
||||
min_coords = (0, 0)
|
||||
area = region_width * region_height
|
||||
x_start = padding
|
||||
y_start = padding
|
||||
x_end = w - region_width - padding + 1
|
||||
y_end = h - region_height - padding + 1
|
||||
for y in range(y_start, max(y_end, y_start+1), stride):
|
||||
for x in range(x_start, max(x_end, x_start+1), stride):
|
||||
x1, y1 = x, y
|
||||
x2, y2 = x + region_width - 1, y + region_height - 1
|
||||
s = region_sum(integral, x1, y1, x2, y2)
|
||||
s2 = region_sum(integral_sq, x1, y1, x2, y2)
|
||||
mean = s / area
|
||||
var = (s2 / area) - (mean ** 2)
|
||||
if (min_var is None) or (var < min_var):
|
||||
min_var = var
|
||||
min_coords = (x, y)
|
||||
return min_coords, min_var
|
||||
|
||||
def find_largest_region(image_path, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode="fill", threshold=100.0, aspect_ratio=1.0, padding=50):
|
||||
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
|
||||
if img is None:
|
||||
raise FileNotFoundError(f"Image not found: {image_path}")
|
||||
orig_h, orig_w = img.shape
|
||||
scale = 1.0
|
||||
if screen_width is not None and screen_height is not None:
|
||||
scale_w = screen_width / orig_w
|
||||
scale_h = screen_height / orig_h
|
||||
if screen_mode == "fill":
|
||||
scale = max(scale_w, scale_h)
|
||||
else:
|
||||
scale = min(scale_w, scale_h)
|
||||
new_w = int(orig_w * scale)
|
||||
new_h = int(orig_h * scale)
|
||||
if verbose:
|
||||
print(f"Scaling image from {orig_w}x{orig_h} to {new_w}x{new_h} (scale: {scale:.3f}, mode: {screen_mode})")
|
||||
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
|
||||
img = center_crop(img, screen_width, screen_height)
|
||||
if verbose:
|
||||
print(f"Cropped image to {screen_width}x{screen_height}")
|
||||
else:
|
||||
if verbose:
|
||||
print(f"Using original image size: {orig_w}x{orig_h}")
|
||||
arr = img.astype(np.float64)
|
||||
h, w = arr.shape
|
||||
# Use OpenCV's integral for fast computation
|
||||
integral = cv2.integral(arr, sdepth=cv2.CV_64F)[1:,1:]
|
||||
integral_sq = cv2.integral(arr**2, sdepth=cv2.CV_64F)[1:,1:]
|
||||
def region_sum(ii, x1, y1, x2, y2):
|
||||
total = ii[y2, x2]
|
||||
if x1 > 0:
|
||||
total -= ii[y2, x1-1]
|
||||
if y1 > 0:
|
||||
total -= ii[y1-1, x2]
|
||||
if x1 > 0 and y1 > 0:
|
||||
total += ii[y1-1, x1-1]
|
||||
return total
|
||||
min_size = 10
|
||||
max_size = min(h, int(w / aspect_ratio)) if aspect_ratio >= 1.0 else min(int(h * aspect_ratio), w)
|
||||
best = None
|
||||
best_size = min_size
|
||||
while min_size <= max_size:
|
||||
mid = (min_size + max_size) // 2
|
||||
if aspect_ratio >= 1.0:
|
||||
region_h = mid
|
||||
region_w = int(mid * aspect_ratio)
|
||||
else:
|
||||
region_w = mid
|
||||
region_h = int(mid / aspect_ratio)
|
||||
if region_w > w or region_h > h:
|
||||
max_size = mid - 1
|
||||
continue
|
||||
found = False
|
||||
x_start = padding
|
||||
y_start = padding
|
||||
x_end = w - region_w - padding + 1
|
||||
y_end = h - region_h - padding + 1
|
||||
for y in range(y_start, max(y_end, y_start+1), stride):
|
||||
for x in range(x_start, max(x_end, x_start+1), stride):
|
||||
x1, y1 = x, y
|
||||
x2, y2 = x + region_w - 1, y + region_h - 1
|
||||
s = region_sum(integral, x1, y1, x2, y2)
|
||||
s2 = region_sum(integral_sq, x1, y1, x2, y2)
|
||||
area = region_w * region_h
|
||||
mean = s / area
|
||||
var = (s2 / area) - (mean ** 2)
|
||||
if var <= threshold:
|
||||
found = True
|
||||
best = (x, y, region_w, region_h, var)
|
||||
break
|
||||
if found:
|
||||
break
|
||||
if found:
|
||||
best_size = mid
|
||||
min_size = mid + 1
|
||||
else:
|
||||
max_size = mid - 1
|
||||
if best:
|
||||
x, y, region_w, region_h, var = best
|
||||
center_x = x + region_w // 2
|
||||
center_y = y + region_h // 2
|
||||
return (center_x, center_y), (region_w, region_h), var
|
||||
else:
|
||||
return None, (0, 0), None
|
||||
|
||||
def draw_region(image_path, coords, region_width=300, region_height=200, output_path='output.png', screen_width=None, screen_height=None, screen_mode="fill"):
|
||||
img = cv2.imread(image_path)
|
||||
if img is None:
|
||||
raise FileNotFoundError(f"Image not found: {image_path}")
|
||||
orig_h, orig_w = img.shape[:2]
|
||||
if screen_width is not None and screen_height is not None:
|
||||
scale_w = screen_width / orig_w
|
||||
scale_h = screen_height / orig_h
|
||||
if screen_mode == "fill":
|
||||
scale = max(scale_w, scale_h)
|
||||
else:
|
||||
scale = min(scale_w, scale_h)
|
||||
new_w = int(orig_w * scale)
|
||||
new_h = int(orig_h * scale)
|
||||
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
|
||||
img = center_crop(img, screen_width, screen_height)
|
||||
x, y = coords
|
||||
cv2.rectangle(img, (x, y), (x+region_width-1, y+region_height-1), (0,0,255), 3)
|
||||
cv2.imwrite(output_path, img)
|
||||
print(f"Saved output image with rectangle at {output_path}")
|
||||
|
||||
def draw_largest_region(image_path, center, size, output_path='output.png', screen_width=None, screen_height=None, screen_mode="fill"):
|
||||
img = cv2.imread(image_path)
|
||||
if img is None:
|
||||
raise FileNotFoundError(f"Image not found: {image_path}")
|
||||
orig_h, orig_w = img.shape[:2]
|
||||
if screen_width is not None and screen_height is not None:
|
||||
scale_w = screen_width / orig_w
|
||||
scale_h = screen_height / orig_h
|
||||
if screen_mode == "fill":
|
||||
scale = max(scale_w, scale_h)
|
||||
else:
|
||||
scale = min(scale_w, scale_h)
|
||||
new_w = int(orig_w * scale)
|
||||
new_h = int(orig_h * scale)
|
||||
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
|
||||
img = center_crop(img, screen_width, screen_height)
|
||||
cx, cy = center
|
||||
region_w, region_h = size
|
||||
x1 = cx - region_w // 2
|
||||
y1 = cy - region_h // 2
|
||||
x2 = cx + region_w // 2 - 1
|
||||
y2 = cy + region_h // 2 - 1
|
||||
cv2.rectangle(img, (x1, y1), (x2, y2), (255,0,0), 3)
|
||||
cv2.imwrite(output_path, img)
|
||||
print(f"Saved output image with largest region at {output_path}")
|
||||
|
||||
def get_dominant_color(image_path, x, y, w, h, screen_width=None, screen_height=None, screen_mode="fill"):
|
||||
img = cv2.imread(image_path)
|
||||
if img is None:
|
||||
raise FileNotFoundError(f"Image not found: {image_path}")
|
||||
orig_h, orig_w = img.shape[:2]
|
||||
if screen_width is not None and screen_height is not None:
|
||||
scale_w = screen_width / orig_w
|
||||
scale_h = screen_height / orig_h
|
||||
if screen_mode == "fill":
|
||||
scale = max(scale_w, scale_h)
|
||||
else:
|
||||
scale = min(scale_w, scale_h)
|
||||
new_w = int(orig_w * scale)
|
||||
new_h = int(orig_h * scale)
|
||||
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
|
||||
img = center_crop(img, screen_width, screen_height)
|
||||
# Ensure region is within bounds
|
||||
x = max(0, x)
|
||||
y = max(0, y)
|
||||
w = max(1, min(w, img.shape[1] - x))
|
||||
h = max(1, min(h, img.shape[0] - y))
|
||||
region = img[y:y+h, x:x+w]
|
||||
if region.size == 0 or region.shape[0] == 0 or region.shape[1] == 0:
|
||||
return [0, 0, 0]
|
||||
region = region.reshape((-1, 3))
|
||||
# Filter out black pixels (optional, improves accuracy for some images)
|
||||
non_black = region[np.any(region > 10, axis=1)]
|
||||
if non_black.shape[0] == 0:
|
||||
non_black = region
|
||||
region = np.float32(non_black)
|
||||
if region.shape[0] < 3:
|
||||
return [int(x) for x in np.mean(region, axis=0)]
|
||||
# K-means to find dominant color
|
||||
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
|
||||
K = min(3, region.shape[0])
|
||||
_, labels, centers = cv2.kmeans(region, K, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
|
||||
counts = np.bincount(labels.flatten())
|
||||
dominant = centers[np.argmax(counts)]
|
||||
return [int(x) for x in dominant]
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Find least busy region in an image and output a JSON. Made for determining a suitable position for a wallpaper widget.")
|
||||
parser.add_argument("image_path", help="Path to the input image")
|
||||
parser.add_argument("--width", type=int, default=300, help="Region width")
|
||||
parser.add_argument("--height", type=int, default=200, help="Region height")
|
||||
parser.add_argument("-v", "--visual-output", action="store_true", help="Output image with rectangle")
|
||||
parser.add_argument("--screen-width", type=int, default=1920, help="Screen width for wallpaper scaling")
|
||||
parser.add_argument("--screen-height", type=int, default=1080, help="Screen height for wallpaper scaling")
|
||||
parser.add_argument("--stride", type=int, default=4, help="Step size for sliding window (higher is faster, less precise)")
|
||||
parser.add_argument("--screen-mode", choices=["fill", "fit"], default="fill", help="Wallpaper scaling mode: 'fill' (default) or 'fit'")
|
||||
parser.add_argument("--verbose", action="store_true", help="Print verbose output")
|
||||
parser.add_argument("-l", "--largest-region", action="store_true", help="Find the largest region under the variance threshold and output its center")
|
||||
parser.add_argument("-t", "--variance-threshold", type=float, default=1000.0, help="Variance threshold for largest region mode")
|
||||
parser.add_argument("--aspect-ratio", type=float, default=1.78, help="Aspect ratio (width/height) for largest region mode")
|
||||
parser.add_argument("--padding", type=int, default=50, help="Minimum distance from region to image edge (default: 50)")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.largest_region:
|
||||
center, size, var = find_largest_region(
|
||||
args.image_path,
|
||||
screen_width=args.screen_width,
|
||||
screen_height=args.screen_height,
|
||||
verbose=args.verbose,
|
||||
stride=args.stride,
|
||||
screen_mode=args.screen_mode,
|
||||
threshold=args.variance_threshold,
|
||||
aspect_ratio=args.aspect_ratio,
|
||||
padding=args.padding
|
||||
)
|
||||
if center:
|
||||
if args.visual_output:
|
||||
draw_largest_region(args.image_path, center, size, screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode)
|
||||
# Extract dominant color
|
||||
cx, cy = center
|
||||
region_w, region_h = size
|
||||
x1 = cx - region_w // 2
|
||||
y1 = cy - region_h // 2
|
||||
dominant_color = get_dominant_color(
|
||||
args.image_path, x1, y1, region_w, region_h,
|
||||
screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode
|
||||
)
|
||||
dominant_color_hex = '#{:02x}{:02x}{:02x}'.format(*dominant_color)
|
||||
print(json.dumps({
|
||||
"center_x": center[0],
|
||||
"center_y": center[1],
|
||||
"width": size[0],
|
||||
"height": size[1],
|
||||
"variance": var,
|
||||
"dominant_color": dominant_color_hex
|
||||
}))
|
||||
else:
|
||||
print(json.dumps({"error": "No region found under the threshold."}))
|
||||
return
|
||||
|
||||
coords, variance = find_least_busy_region(
|
||||
args.image_path,
|
||||
region_width=args.width,
|
||||
region_height=args.height,
|
||||
screen_width=args.screen_width,
|
||||
screen_height=args.screen_height,
|
||||
verbose=args.verbose,
|
||||
stride=args.stride,
|
||||
screen_mode=args.screen_mode,
|
||||
padding=args.padding
|
||||
)
|
||||
if args.visual_output:
|
||||
draw_region(args.image_path, coords, region_width=args.width, region_height=args.height, screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode)
|
||||
# Output JSON with center point
|
||||
center_x = coords[0] + args.width // 2
|
||||
center_y = coords[1] + args.height // 2
|
||||
dominant_color = get_dominant_color(
|
||||
args.image_path, coords[0], coords[1], args.width, args.height,
|
||||
screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode
|
||||
)
|
||||
dominant_color_hex = '#{:02x}{:02x}{:02x}'.format(*dominant_color)
|
||||
print(json.dumps({
|
||||
"center_x": center_x,
|
||||
"center_y": center_y,
|
||||
"width": args.width,
|
||||
"height": args.height,
|
||||
"variance": variance,
|
||||
"dominant_color": dominant_color_hex
|
||||
}))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
import "root:/"
|
||||
import "root:/modules/common"
|
||||
import "root:/modules/common/widgets"
|
||||
import "root:/services"
|
||||
import "root:/modules/common/functions/color_utils.js" as ColorUtils
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Services.UPower
|
||||
|
||||
Scope {
|
||||
id: root
|
||||
property string filePath: `${Directories.state}/user/generated/wallpaper/least_busy_region.json`
|
||||
property real centerX: 0
|
||||
property real centerY: 0
|
||||
property color dominantColor: Appearance.colors.colPrimary
|
||||
property bool dominantColorIsDark: dominantColor.hslLightness < 0.5
|
||||
property color colBackground: ColorUtils.transparentize(ColorUtils.mix(Appearance.colors.colPrimary, Appearance.colors.colSecondaryContainer), 1)
|
||||
property color colText: ColorUtils.colorWithLightness(Appearance.colors.colPrimary, (root.dominantColorIsDark ? 0.8 : 0.12))
|
||||
|
||||
function updateWidgetPosition(fileContent) {
|
||||
// console.log("[BackgroundWidgets] Updating widget position with content:", fileContent)
|
||||
const parsedContent = JSON.parse(fileContent)
|
||||
root.centerX = parsedContent.center_x
|
||||
root.centerY = parsedContent.center_y
|
||||
root.dominantColor = parsedContent.dominant_color || Appearance.colors.colPrimary
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: delayedFileRead
|
||||
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay
|
||||
running: false
|
||||
onTriggered: {
|
||||
root.updateWidgetPosition(leastBusyRegionFileView.text())
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: leastBusyRegionFileView
|
||||
path: Qt.resolvedUrl(root.filePath)
|
||||
watchChanges: true
|
||||
onFileChanged: {
|
||||
this.reload()
|
||||
delayedFileRead.start()
|
||||
}
|
||||
onLoadedChanged: {
|
||||
const fileContent = leastBusyRegionFileView.text()
|
||||
root.updateWidgetPosition(fileContent)
|
||||
}
|
||||
}
|
||||
|
||||
Variants { // For each monitor
|
||||
model: Quickshell.screens
|
||||
|
||||
Loader {
|
||||
required property var modelData
|
||||
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(modelData)
|
||||
active: !ToplevelManager.activeToplevel?.activated
|
||||
sourceComponent: PanelWindow { // Window
|
||||
id: windowRoot
|
||||
screen: modelData
|
||||
property var textHorizontalAlignment: root.centerX / monitor.scale < windowRoot.width / 3 ? Text.AlignLeft :
|
||||
(root.centerX / monitor.scale > windowRoot.width * 2 / 3 ? Text.AlignRight : Text.AlignHCenter)
|
||||
|
||||
WlrLayershell.layer: WlrLayer.Bottom
|
||||
WlrLayershell.namespace: "quickshell:backgroundWidgets"
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
bottom:true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
color: "transparent"
|
||||
HyprlandWindow.visibleMask: Region {
|
||||
item: widgetBackground
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: widgetBackground
|
||||
property real verticalPadding: 20
|
||||
property real horizontalPadding: 30
|
||||
radius: 40
|
||||
color: root.colBackground
|
||||
implicitHeight: columnLayout.implicitHeight + verticalPadding * 2
|
||||
implicitWidth: columnLayout.implicitWidth + horizontalPadding * 2
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
leftMargin: (root.centerX / monitor.scale - implicitWidth / 2)
|
||||
topMargin: (root.centerY / monitor.scale - implicitHeight / 2)
|
||||
Behavior on leftMargin {
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
Behavior on topMargin {
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: columnLayout
|
||||
anchors.centerIn: parent
|
||||
spacing: -5
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: windowRoot.textHorizontalAlignment
|
||||
font.pixelSize: 95
|
||||
color: root.colText
|
||||
style: Text.Raised
|
||||
styleColor: Appearance.colors.colShadow
|
||||
text: DateTime.time
|
||||
}
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: windowRoot.textHorizontalAlignment
|
||||
font.pixelSize: 25
|
||||
color: root.colText
|
||||
style: Text.Raised
|
||||
styleColor: Appearance.colors.colShadow
|
||||
text: DateTime.date
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,7 +12,6 @@ Item {
|
||||
|
||||
height: parent.height
|
||||
width: colLayout.width
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
id: colLayout
|
||||
|
||||
@@ -19,6 +19,13 @@ Scope {
|
||||
readonly property int osdHideMouseMoveThreshold: 20
|
||||
property bool showBarBackground: ConfigOptions.bar.showBackground
|
||||
|
||||
component VerticalBarSeparator: Rectangle {
|
||||
Layout.topMargin: barHeight / 3
|
||||
Layout.bottomMargin: barHeight / 3
|
||||
Layout.fillHeight: true
|
||||
implicitWidth: 1
|
||||
color: Appearance.colors.colOutlineVariant
|
||||
|
||||
|
||||
// Check screensList from config, If no screens are specified, show on all screens
|
||||
property var filteredScreens: {
|
||||
@@ -150,7 +157,7 @@ Scope {
|
||||
colBackground: barLeftSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)
|
||||
colBackgroundHover: Appearance.colors.colLayer1Hover
|
||||
colRipple: Appearance.colors.colLayer1Active
|
||||
colBackgroundToggled: Appearance.m3colors.m3secondaryContainer
|
||||
colBackgroundToggled: Appearance.colors.colSecondaryContainer
|
||||
colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover
|
||||
colRippleToggled: Appearance.colors.colSecondaryContainerActive
|
||||
toggled: GlobalStates.sidebarLeftOpen
|
||||
@@ -177,7 +184,7 @@ Scope {
|
||||
}
|
||||
|
||||
ActiveWindow {
|
||||
visible: barRoot.useShortenedForm === 0
|
||||
visible: barRoot.useShortenedForm === 0 && width > 0 && height > 0
|
||||
Layout.rightMargin: Appearance.rounding.screenRounding
|
||||
Layout.fillWidth: true
|
||||
bar: barRoot
|
||||
@@ -189,7 +196,7 @@ Scope {
|
||||
RowLayout { // Middle section
|
||||
id: middleSection
|
||||
anchors.centerIn: parent
|
||||
spacing: 8
|
||||
spacing: ConfigOptions?.bar.borderless ? 4 : 8
|
||||
|
||||
RowLayout {
|
||||
id: leftCenterGroup
|
||||
@@ -210,9 +217,10 @@ Scope {
|
||||
|
||||
}
|
||||
|
||||
VerticalBarSeparator {visible: ConfigOptions?.bar.borderless}
|
||||
|
||||
RowLayout {
|
||||
id: middleCenterGroup
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
Workspaces {
|
||||
@@ -231,6 +239,8 @@ Scope {
|
||||
|
||||
}
|
||||
|
||||
VerticalBarSeparator {visible: ConfigOptions?.bar.borderless}
|
||||
|
||||
RowLayout {
|
||||
id: rightCenterGroup
|
||||
Layout.preferredWidth: leftCenterGroup.width
|
||||
@@ -344,7 +354,7 @@ Scope {
|
||||
colBackground: barRightSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)
|
||||
colBackgroundHover: Appearance.colors.colLayer1Hover
|
||||
colRipple: Appearance.colors.colLayer1Active
|
||||
colBackgroundToggled: Appearance.m3colors.m3secondaryContainer
|
||||
colBackgroundToggled: Appearance.colors.colSecondaryContainer
|
||||
colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover
|
||||
colRippleToggled: Appearance.colors.colSecondaryContainerActive
|
||||
toggled: GlobalStates.sidebarRightOpen
|
||||
|
||||
@@ -48,7 +48,7 @@ Rectangle {
|
||||
lineWidth: 2
|
||||
value: percentage
|
||||
size: 26
|
||||
secondaryColor: (isLow && !isCharging) ? batteryLowBackground : Appearance.m3colors.m3secondaryContainer
|
||||
secondaryColor: (isLow && !isCharging) ? batteryLowBackground : Appearance.colors.colSecondaryContainer
|
||||
primaryColor: (isLow && !isCharging) ? batteryLowOnBackground : Appearance.m3colors.m3onSecondaryContainer
|
||||
fill: (isLow && !isCharging)
|
||||
|
||||
|
||||
@@ -62,13 +62,13 @@ Item {
|
||||
lineWidth: 2
|
||||
value: activePlayer?.position / activePlayer?.length
|
||||
size: 26
|
||||
secondaryColor: Appearance.m3colors.m3secondaryContainer
|
||||
secondaryColor: Appearance.colors.colSecondaryContainer
|
||||
primaryColor: Appearance.m3colors.m3onSecondaryContainer
|
||||
|
||||
MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
fill: 1
|
||||
text: activePlayer?.isPlaying ? "pause" : "play_arrow"
|
||||
text: activePlayer?.isPlaying ? "pause" : "music_note"
|
||||
iconSize: Appearance.font.pixelSize.normal
|
||||
color: Appearance.m3colors.m3onSecondaryContainer
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ Item {
|
||||
lineWidth: 2
|
||||
value: percentage
|
||||
size: 26
|
||||
secondaryColor: Appearance.m3colors.m3secondaryContainer
|
||||
secondaryColor: Appearance.colors.colSecondaryContainer
|
||||
primaryColor: Appearance.m3colors.m3onSecondaryContainer
|
||||
|
||||
MaterialSymbol {
|
||||
|
||||
@@ -2,6 +2,7 @@ import "root:/"
|
||||
import "root:/services/"
|
||||
import "root:/modules/common"
|
||||
import "root:/modules/common/widgets"
|
||||
import "root:/modules/common/functions/color_utils.js" as ColorUtils
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
@@ -98,15 +99,17 @@ Item {
|
||||
implicitWidth: workspaceButtonWidth
|
||||
implicitHeight: workspaceButtonWidth
|
||||
radius: Appearance.rounding.full
|
||||
property var radiusLeft: (workspaceOccupied[index-1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index)) ? 0 : Appearance.rounding.full
|
||||
property var radiusRight: (workspaceOccupied[index+1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+2)) ? 0 : Appearance.rounding.full
|
||||
property var leftOccupied: (workspaceOccupied[index-1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index))
|
||||
property var rightOccupied: (workspaceOccupied[index+1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+2))
|
||||
property var radiusLeft: leftOccupied ? 0 : Appearance.rounding.full
|
||||
property var radiusRight: rightOccupied ? 0 : Appearance.rounding.full
|
||||
|
||||
topLeftRadius: radiusLeft
|
||||
bottomLeftRadius: radiusLeft
|
||||
topRightRadius: radiusRight
|
||||
bottomRightRadius: radiusRight
|
||||
|
||||
color: Appearance.colors.colLayer2
|
||||
color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4)
|
||||
opacity: (workspaceOccupied[index] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+1)) ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
@@ -144,13 +147,13 @@ Item {
|
||||
Behavior on activeWorkspaceMargin {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
Behavior on idx1 {
|
||||
Behavior on idx1 { // Leading anim
|
||||
NumberAnimation {
|
||||
duration: 100
|
||||
easing.type: Easing.OutSine
|
||||
}
|
||||
}
|
||||
Behavior on idx2 {
|
||||
Behavior on idx2 { // Following anim
|
||||
NumberAnimation {
|
||||
duration: 300
|
||||
easing.type: Easing.OutSine
|
||||
@@ -203,7 +206,7 @@ Item {
|
||||
elide: Text.ElideRight
|
||||
color: (monitor.activeWorkspace?.id == button.workspaceValue) ?
|
||||
Appearance.m3colors.m3onPrimary :
|
||||
(workspaceOccupied[index] ? Appearance.colors.colOnLayer1 :
|
||||
(workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer :
|
||||
Appearance.colors.colOnLayer1Inactive)
|
||||
|
||||
Behavior on opacity {
|
||||
|
||||
@@ -15,11 +15,11 @@ Singleton {
|
||||
property QtObject sizes
|
||||
property string syntaxHighlightingTheme
|
||||
|
||||
// [!] Enabling transparency can affect readability when using light theme.
|
||||
// Extremely conservative transparency values for consistency and readability
|
||||
property real transparency: 0
|
||||
property real contentTransparency: 0
|
||||
// property real transparency: 0.15
|
||||
// property real contentTransparency: 0.5
|
||||
// property real transparency: m3colors.darkmode ? 0.05 : 0
|
||||
// property real contentTransparency: m3colors.darkmode ? 0.18 : 0
|
||||
|
||||
m3colors: QtObject {
|
||||
property bool darkmode: false
|
||||
@@ -106,10 +106,10 @@ Singleton {
|
||||
property color colOnLayer0: m3colors.m3onBackground
|
||||
property color colLayer0Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer0, colOnLayer0, 0.9, root.contentTransparency))
|
||||
property color colLayer0Active: ColorUtils.transparentize(ColorUtils.mix(colLayer0, colOnLayer0, 0.8, root.contentTransparency))
|
||||
property color colLayer1: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerLow, m3colors.m3background, 0.7), root.contentTransparency);
|
||||
property color colLayer1: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerLow, m3colors.m3background, 0.8), root.contentTransparency);
|
||||
property color colOnLayer1: m3colors.m3onSurfaceVariant;
|
||||
property color colOnLayer1Inactive: ColorUtils.mix(colOnLayer1, colLayer1, 0.45);
|
||||
property color colLayer2: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 0.55), root.contentTransparency)
|
||||
property color colLayer2: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 0.7), root.contentTransparency)
|
||||
property color colOnLayer2: m3colors.m3onSurface;
|
||||
property color colOnLayer2Disabled: ColorUtils.mix(colOnLayer2, m3colors.m3background, 0.4);
|
||||
property color colLayer3: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerHigh, m3colors.m3onSurface, 0.96), root.contentTransparency)
|
||||
@@ -122,21 +122,30 @@ Singleton {
|
||||
property color colLayer3Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer3, colOnLayer3, 0.90), root.contentTransparency)
|
||||
property color colLayer3Active: ColorUtils.transparentize(ColorUtils.mix(colLayer3, colOnLayer3, 0.80), root.contentTransparency);
|
||||
property color colPrimary: m3colors.m3primary
|
||||
property color colOnPrimary: m3colors.m3onPrimary
|
||||
property color colPrimaryHover: ColorUtils.mix(colors.colPrimary, colLayer1Hover, 0.87)
|
||||
property color colPrimaryActive: ColorUtils.mix(colors.colPrimary, colLayer1Active, 0.7)
|
||||
property color colPrimaryContainer: m3colors.m3primaryContainer
|
||||
property color colPrimaryContainerHover: ColorUtils.mix(colors.colPrimaryContainer, colLayer1Hover, 0.7)
|
||||
property color colPrimaryContainerActive: ColorUtils.mix(colors.colPrimaryContainer, colLayer1Active, 0.6)
|
||||
property color colSecondary: m3colors.m3secondary
|
||||
property color colSecondaryHover: ColorUtils.mix(m3colors.m3secondary, colLayer1Hover, 0.85)
|
||||
property color colSecondaryActive: ColorUtils.mix(m3colors.m3secondary, colLayer1Active, 0.4)
|
||||
property color colSecondaryContainer: ColorUtils.transparentize(m3colors.m3secondaryContainer, root.contentTransparency)
|
||||
property color colSecondaryContainerHover: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Hover, 0.6)
|
||||
property color colSecondaryContainerActive: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Active, 0.54)
|
||||
property color colOnSecondaryContainer: m3colors.m3onSecondaryContainer
|
||||
property color colSurfaceContainerLow: ColorUtils.transparentize(m3colors.m3surfaceContainerLow, root.contentTransparency)
|
||||
property color colSurfaceContainer: ColorUtils.transparentize(m3colors.m3surfaceContainer, root.contentTransparency)
|
||||
property color colSurfaceContainerHigh: ColorUtils.transparentize(m3colors.m3surfaceContainerHigh, root.contentTransparency)
|
||||
property color colSurfaceContainerHighest: ColorUtils.transparentize(m3colors.m3surfaceContainerHighest, root.contentTransparency)
|
||||
property color colSurfaceContainerHighestHover: ColorUtils.mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.95)
|
||||
property color colSurfaceContainerHighestActive: ColorUtils.mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.85)
|
||||
property color colTooltip: "#3C4043" // m3colors.m3inverseSurface in the specs, but the m3 website actually uses this color
|
||||
property color colTooltip: m3colors.darkmode ? ColorUtils.mix(m3colors.m3background, "#3C4043", 0.5) : "#3C4043" // m3colors.m3inverseSurface in the specs, but the m3 website actually uses #3C4043
|
||||
property color colOnTooltip: "#F8F9FA" // m3colors.m3inverseOnSurface in the specs, but the m3 website actually uses this color
|
||||
property color colScrim: ColorUtils.transparentize(m3colors.m3scrim, 0.5)
|
||||
property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.85)
|
||||
property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.7)
|
||||
property color colOutlineVariant: m3colors.m3outlineVariant
|
||||
}
|
||||
|
||||
rounding: QtObject {
|
||||
@@ -176,6 +185,7 @@ Singleton {
|
||||
animationCurves: QtObject {
|
||||
readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.90, 1, 1] // Default, 350ms
|
||||
readonly property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1.00, 1, 1] // Default, 500ms
|
||||
readonly property list<real> expressiveSlowSpatial: [0.39, 1.29, 0.35, 0.98, 1, 1] // Default, 650ms
|
||||
readonly property list<real> expressiveEffects: [0.34, 0.80, 0.34, 1.00, 1, 1] // Default, 200ms
|
||||
readonly property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]
|
||||
readonly property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
|
||||
@@ -287,7 +297,7 @@ Singleton {
|
||||
property real searchWidthCollapsed: 260
|
||||
property real searchWidth: 450
|
||||
property real hyprlandGapsOut: 5
|
||||
property real elevationMargin: 8
|
||||
property real elevationMargin: 10
|
||||
property real fabShadowRadius: 5
|
||||
property real fabHoveredShadowRadius: 7
|
||||
}
|
||||
|
||||
@@ -9,14 +9,23 @@ Singleton {
|
||||
}
|
||||
|
||||
property QtObject appearance: QtObject {
|
||||
property int fakeScreenRounding: 1 // 0: None | 1: Always | 2: When not fullscreen
|
||||
property int fakeScreenRounding: 2 // 0: None | 1: Always | 2: When not fullscreen
|
||||
}
|
||||
|
||||
property QtObject audio: QtObject { // Values in %
|
||||
property QtObject protection: QtObject { // Prevent sudden bangs
|
||||
property bool enable: true
|
||||
property real maxAllowedIncrease: 10
|
||||
property real maxAllowed: 90 // Realistically should already provide some protection when it's 99...
|
||||
}
|
||||
}
|
||||
|
||||
property QtObject apps: QtObject {
|
||||
property string bluetooth: "better-control --bluetooth"
|
||||
property string bluetooth: "kcmshell6 kcm_bluetooth"
|
||||
property string imageViewer: "loupe"
|
||||
property string network: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center wifi"
|
||||
property string settings: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center"
|
||||
property string network: "plasmawindowed org.kde.plasma.networkmanagement"
|
||||
property string networkEthernet: "kcmshell6 kcm_networkmanagement"
|
||||
property string settings: "systemsettings"
|
||||
property string taskManager: "plasma-systemmonitor --page-name Processes"
|
||||
property string terminal: "kitty -1" // This is only for shell actions
|
||||
}
|
||||
@@ -29,7 +38,7 @@ Singleton {
|
||||
|
||||
property QtObject bar: QtObject {
|
||||
property bool bottom: false // Instead of top
|
||||
property bool borderless: true
|
||||
property bool borderless: false // true for no grouping of items
|
||||
property string topLeftIcon: "spark" // Options: distro, spark
|
||||
property bool showBackground: true
|
||||
property QtObject resources: QtObject {
|
||||
@@ -48,15 +57,24 @@ Singleton {
|
||||
}
|
||||
|
||||
property QtObject dock: QtObject {
|
||||
property bool enable: false
|
||||
property real height: 60
|
||||
property real hoverRegionHeight: 3
|
||||
property bool pinnedOnStartup: false
|
||||
property bool hoverToReveal: false // When false, only reveals on empty workspace
|
||||
property list<string> pinnedApps: [ // IDs of pinned entries
|
||||
"org.kde.dolphin",
|
||||
"kitty",
|
||||
]
|
||||
}
|
||||
|
||||
property QtObject language: QtObject {
|
||||
property QtObject translator: QtObject {
|
||||
property string engine: "auto" // Run `trans -list-engines` for available engines. auto should use google
|
||||
property string targetLanguage: "auto" // Run `trans -list-all` for available languages
|
||||
property string sourceLanguage: "auto"
|
||||
}
|
||||
}
|
||||
|
||||
property QtObject networking: QtObject {
|
||||
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"
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ Singleton {
|
||||
property string booruPreviews: FileUtils.trimFileProtocol(`${Directories.cache}/media/boorus`)
|
||||
property string booruDownloads: FileUtils.trimFileProtocol(Directories.pictures + "/homework")
|
||||
property string booruDownloadsNsfw: FileUtils.trimFileProtocol(Directories.pictures + "/homework/🌶️")
|
||||
property string latexOutput: FileUtils.trimFileProtocol(`${Directories.cache}/latex`)
|
||||
property string latexOutput: FileUtils.trimFileProtocol(`${Directories.cache}/media/latex`)
|
||||
property string shellConfig: FileUtils.trimFileProtocol(`${Directories.config}/illogical-impulse`)
|
||||
property string shellConfigName: "config.json"
|
||||
property string shellConfigPath: `${Directories.shellConfig}/${Directories.shellConfigName}`
|
||||
@@ -32,6 +32,7 @@ Singleton {
|
||||
property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.config}/quickshell/scripts/switchwall.sh`)
|
||||
// Cleanup on init
|
||||
Component.onCompleted: {
|
||||
Hyprland.dispatch(`exec mkdir -p '${shellConfig}'`)
|
||||
Hyprland.dispatch(`exec mkdir -p '${favicons}'`)
|
||||
Hyprland.dispatch(`exec rm -rf '${coverArt}'; mkdir -p '${coverArt}'`)
|
||||
Hyprland.dispatch(`exec rm -rf '${booruPreviews}'; mkdir -p '${booruPreviews}'`)
|
||||
|
||||
@@ -39,6 +39,30 @@ function colorWithSaturationOf(color1, color2) {
|
||||
return Qt.hsva(hue, sat, val, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a color with the given lightness and the hue, saturation, and alpha of the input color (using HSL).
|
||||
*
|
||||
* @param {string} color - The base color (any Qt.color-compatible string).
|
||||
* @param {number} lightness - The lightness value to use (0-1).
|
||||
* @returns {Qt.rgba} The resulting color.
|
||||
*/
|
||||
function colorWithLightness(color, lightness) {
|
||||
var c = Qt.color(color);
|
||||
return Qt.hsla(c.hslHue, c.hslSaturation, lightness, c.a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a color with the lightness of color2 and the hue, saturation, and alpha of color1 (using HSL).
|
||||
*
|
||||
* @param {string} color1 - The base color (any Qt.color-compatible string).
|
||||
* @param {string} color2 - The color to take lightness from.
|
||||
* @returns {Qt.rgba} The resulting color.
|
||||
*/
|
||||
function colorWithLightnessOf(color1, color2) {
|
||||
var c2 = Qt.color(color2);
|
||||
return colorWithLightness(color1, c2.hslLightness);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapts color1 to the accent (hue and saturation) of color2 using HSL, keeping lightness and alpha from color1.
|
||||
*
|
||||
@@ -66,7 +90,7 @@ function adaptToAccent(color1, color2) {
|
||||
* @param {number} percentage - The mix ratio (0-1). 1 = all color1, 0 = all color2.
|
||||
* @returns {Qt.rgba} The resulting mixed color.
|
||||
*/
|
||||
function mix(color1, color2, percentage) {
|
||||
function mix(color1, color2, percentage = 0.5) {
|
||||
var c1 = Qt.color(color1);
|
||||
var c2 = Qt.color(color2);
|
||||
return Qt.rgba(percentage * c1.r + (1 - percentage) * c2.r, percentage * c1.g + (1 - percentage) * c2.g, percentage * c1.b + (1 - percentage) * c2.b, percentage * c1.a + (1 - percentage) * c2.a);
|
||||
|
||||
@@ -11,7 +11,7 @@ import QtQuick.Layouts
|
||||
*/
|
||||
Rectangle {
|
||||
id: root
|
||||
default property alias content: rowLayout.data
|
||||
default property alias data: rowLayout.data
|
||||
property real spacing: 5
|
||||
property real padding: 0
|
||||
property int clickIndex: rowLayout.clickIndex
|
||||
|
||||
@@ -14,8 +14,8 @@ Item {
|
||||
property int lineWidth: 2
|
||||
property real value: 0
|
||||
property color primaryColor: Appearance.m3colors.m3onSecondaryContainer
|
||||
property color secondaryColor: Appearance.m3colors.m3secondaryContainer
|
||||
property real gapAngle: Math.PI / 10
|
||||
property color secondaryColor: Appearance.colors.colSecondaryContainer
|
||||
property real gapAngle: Math.PI / 9
|
||||
property bool fill: false
|
||||
property int fillOverflow: 2
|
||||
property int animationDuration: 1000
|
||||
|
||||
@@ -15,7 +15,7 @@ Rectangle {
|
||||
property real extraBottomBorderWidth: 2
|
||||
property color borderColor: Appearance.colors.colOnLayer0
|
||||
property real borderRadius: 5
|
||||
property color keyColor: Appearance.m3colors.m3surfaceContainerLow
|
||||
property color keyColor: Appearance.colors.colSurfaceContainerLow
|
||||
implicitWidth: keyFace.implicitWidth + borderWidth * 2
|
||||
implicitHeight: keyFace.implicitHeight + borderWidth * 2 + extraBottomBorderWidth
|
||||
radius: borderRadius
|
||||
|
||||
@@ -6,23 +6,26 @@ Text {
|
||||
id: root
|
||||
property real iconSize: Appearance?.font.pixelSize.small ?? 16
|
||||
property real fill: 0
|
||||
renderType: Text.NativeRendering
|
||||
font.hintingPreference: Font.PreferFullHinting
|
||||
property real truncatedFill: Math.round(fill * 100) / 100 // Reduce memory consumption spikes from constant font remapping
|
||||
renderType: Text.CurveRendering
|
||||
font {
|
||||
hintingPreference: Font.PreferFullHinting
|
||||
family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded"
|
||||
pixelSize: iconSize
|
||||
}
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded"
|
||||
font.pixelSize: iconSize
|
||||
color: Appearance.m3colors.m3onBackground
|
||||
|
||||
Behavior on fill {
|
||||
NumberAnimation {
|
||||
duration: Appearance?.animation.elementMoveFast.duration ?? 200
|
||||
easing.type: Appearance?.animation.elementMoveFast.type ?? Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1]
|
||||
}
|
||||
}
|
||||
// Behavior on fill {
|
||||
// NumberAnimation {
|
||||
// duration: Appearance?.animation.elementMoveFast.duration ?? 200
|
||||
// easing.type: Appearance?.animation.elementMoveFast.type ?? Easing.BezierSpline
|
||||
// easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1]
|
||||
// }
|
||||
// }
|
||||
|
||||
font.variableAxes: {
|
||||
"FILL": fill,
|
||||
"FILL": truncatedFill,
|
||||
// "wght": font.weight,
|
||||
// "GRAD": 0,
|
||||
"opsz": iconSize,
|
||||
|
||||
@@ -30,7 +30,7 @@ Button {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
radius: Appearance.rounding.full
|
||||
color: toggled ?
|
||||
(button.down ? Appearance.colors.colSecondaryContainerActive : button.hovered ? Appearance.colors.colSecondaryContainerHover : Appearance.m3colors.m3secondaryContainer) :
|
||||
(button.down ? Appearance.colors.colSecondaryContainerActive : button.hovered ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSecondaryContainer) :
|
||||
(button.down ? Appearance.colors.colLayer1Active : button.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1))
|
||||
|
||||
Behavior on color {
|
||||
|
||||
@@ -15,7 +15,7 @@ RippleButton {
|
||||
leftPadding: 15
|
||||
rightPadding: 15
|
||||
buttonRadius: Appearance.rounding.small
|
||||
colBackground: (urgency == NotificationUrgency.Critical) ? Appearance.m3colors.m3secondaryContainer : Appearance.m3colors.m3surfaceContainerHighest
|
||||
colBackground: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainer : Appearance.colors.colSurfaceContainerHighest
|
||||
colBackgroundHover: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSurfaceContainerHighestHover
|
||||
colRipple: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colSurfaceContainerHighestActive
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ Rectangle { // App icon
|
||||
implicitWidth: size
|
||||
implicitHeight: size
|
||||
radius: Appearance.rounding.full
|
||||
color: Appearance.m3colors.m3secondaryContainer
|
||||
color: Appearance.colors.colSecondaryContainer
|
||||
Loader {
|
||||
id: materialSymbolLoader
|
||||
active: root.appIcon == ""
|
||||
|
||||
@@ -113,7 +113,7 @@ Item { // Notification group area
|
||||
id: background
|
||||
anchors.left: parent.left
|
||||
width: parent.width
|
||||
color: Appearance.m3colors.m3surfaceContainer
|
||||
color: Appearance.colors.colSurfaceContainer
|
||||
radius: Appearance.rounding.normal
|
||||
anchors.leftMargin: root.xOffset
|
||||
|
||||
@@ -154,42 +154,56 @@ Item { // Notification group area
|
||||
|
||||
ColumnLayout { // Content
|
||||
Layout.fillWidth: true
|
||||
spacing: expanded ?
|
||||
((root.multipleNotifications &&
|
||||
notificationGroup?.notifications[root.notificationCount - 1].image != "") ? 35 :
|
||||
5) : 0
|
||||
spacing: expanded ? (root.multipleNotifications ?
|
||||
(notificationGroup?.notifications[root.notificationCount - 1].image != "") ? 35 :
|
||||
5 : 0) : 0
|
||||
// spacing: 00
|
||||
Behavior on spacing {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
RowLayout { // App name (or summary when there's only 1 notif) and time
|
||||
Item { // App name (or summary when there's only 1 notif) and time
|
||||
id: topRow
|
||||
spacing: 0
|
||||
// spacing: 0
|
||||
Layout.fillWidth: true
|
||||
property real fontSize: Appearance.font.pixelSize.smaller
|
||||
property bool showAppName: root.multipleNotifications
|
||||
implicitHeight: Math.max(topTextRow.implicitHeight, expandButton.implicitHeight)
|
||||
|
||||
StyledText {
|
||||
id: appName
|
||||
text: (topRow.showAppName ?
|
||||
notificationGroup?.appName :
|
||||
notificationGroup?.notifications[0]?.summary) || ""
|
||||
font.pixelSize: topRow.showAppName ?
|
||||
topRow.fontSize :
|
||||
Appearance.font.pixelSize.small
|
||||
color: topRow.showAppName ?
|
||||
Appearance.colors.colSubtext :
|
||||
Appearance.colors.colOnLayer2
|
||||
RowLayout {
|
||||
id: topTextRow
|
||||
anchors.left: parent.left
|
||||
anchors.right: expandButton.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 5
|
||||
StyledText {
|
||||
id: appName
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
text: (topRow.showAppName ?
|
||||
notificationGroup?.appName :
|
||||
notificationGroup?.notifications[0]?.summary) || ""
|
||||
font.pixelSize: topRow.showAppName ?
|
||||
topRow.fontSize :
|
||||
Appearance.font.pixelSize.small
|
||||
color: topRow.showAppName ?
|
||||
Appearance.colors.colSubtext :
|
||||
Appearance.colors.colOnLayer2
|
||||
}
|
||||
StyledText {
|
||||
id: timeText
|
||||
// Layout.fillWidth: true
|
||||
Layout.rightMargin: 10
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
text: NotificationUtils.getFriendlyNotifTimeString(notificationGroup?.time)
|
||||
font.pixelSize: topRow.fontSize
|
||||
color: Appearance.colors.colSubtext
|
||||
}
|
||||
}
|
||||
StyledText {
|
||||
id: timeText
|
||||
text: " • " + NotificationUtils.getFriendlyNotifTimeString(notificationGroup?.time)
|
||||
font.pixelSize: topRow.fontSize
|
||||
color: Appearance.colors.colSubtext
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
NotificationGroupExpandButton {
|
||||
id: expandButton
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
count: root.notificationCount
|
||||
expanded: root.expanded
|
||||
fontSize: topRow.fontSize
|
||||
|
||||
@@ -129,9 +129,9 @@ Item { // Notification item area
|
||||
|
||||
color: (expanded && !onlyNotification) ?
|
||||
(notificationObject.urgency == NotificationUrgency.Critical) ?
|
||||
ColorUtils.mix(Appearance.m3colors.m3secondaryContainer, Appearance.colors.colLayer2, 0.35) :
|
||||
(Appearance.m3colors.m3surfaceContainerHigh) :
|
||||
ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainerHighest)
|
||||
ColorUtils.mix(Appearance.colors.colSecondaryContainer, Appearance.colors.colLayer2, 0.35) :
|
||||
(Appearance.colors.colSurfaceContainerHigh) :
|
||||
ColorUtils.transparentize(Appearance.colors.colSurfaceContainerHighest)
|
||||
|
||||
implicitHeight: expanded ? (contentColumn.implicitHeight + padding * 2) : summaryRow.implicitHeight
|
||||
Behavior on implicitHeight {
|
||||
|
||||
@@ -110,13 +110,29 @@ TabButton {
|
||||
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Item {
|
||||
id: ripple
|
||||
|
||||
radius: Appearance?.rounding.full ?? 9999
|
||||
color: button.colRipple
|
||||
width: ripple.implicitWidth
|
||||
height: ripple.implicitHeight
|
||||
opacity: 0
|
||||
|
||||
property real implicitWidth: 0
|
||||
property real implicitHeight: 0
|
||||
visible: width > 0 && height > 0
|
||||
|
||||
Behavior on opacity {
|
||||
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||
}
|
||||
|
||||
RadialGradient {
|
||||
anchors.fill: parent
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: button.colRipple }
|
||||
GradientStop { position: 0.3; color: button.colRipple }
|
||||
GradientStop { position: 0.5 ; color: Qt.rgba(button.colRipple.r, button.colRipple.g, button.colRipple.b, 0) }
|
||||
}
|
||||
}
|
||||
|
||||
transform: Translate {
|
||||
x: -ripple.width / 2
|
||||
y: -ripple.height / 2
|
||||
|
||||
@@ -13,6 +13,7 @@ Item {
|
||||
|
||||
implicitWidth: (reveal || vertical) ? childrenRect.width : 0
|
||||
implicitHeight: (reveal || !vertical) ? childrenRect.height : 0
|
||||
visible: width > 0 && height > 0
|
||||
|
||||
Behavior on implicitWidth {
|
||||
enabled: !vertical
|
||||
|
||||
@@ -150,16 +150,29 @@ Button {
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Item {
|
||||
id: ripple
|
||||
|
||||
radius: Appearance?.rounding.full ?? 9999
|
||||
width: ripple.implicitWidth
|
||||
height: ripple.implicitHeight
|
||||
opacity: 0
|
||||
color: root.rippleColor
|
||||
Behavior on color {
|
||||
visible: width > 0 && height > 0
|
||||
|
||||
property real implicitWidth: 0
|
||||
property real implicitHeight: 0
|
||||
|
||||
Behavior on opacity {
|
||||
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||
}
|
||||
|
||||
RadialGradient {
|
||||
anchors.fill: parent
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: root.rippleColor }
|
||||
GradientStop { position: 0.3; color: root.rippleColor }
|
||||
GradientStop { position: 0.5; color: Qt.rgba(root.rippleColor.r, root.rippleColor.g, root.rippleColor.b, 0) }
|
||||
}
|
||||
}
|
||||
|
||||
transform: Translate {
|
||||
x: -ripple.width / 2
|
||||
y: -ripple.height / 2
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
import "root:/modules/common"
|
||||
import "root:/modules/common/widgets"
|
||||
import "root:/services"
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property real dialogPadding: 15
|
||||
property real dialogMargin: 30
|
||||
property string titleText: "Selection Dialog"
|
||||
property alias items: choiceModel.values
|
||||
property int selectedId: choiceListView.currentIndex
|
||||
property var defaultChoice
|
||||
|
||||
signal canceled();
|
||||
signal selected(var result);
|
||||
|
||||
Rectangle { // Scrim
|
||||
id: scrimOverlay
|
||||
anchors.fill: parent
|
||||
radius: Appearance.rounding.small
|
||||
color: Appearance.colors.colScrim
|
||||
MouseArea {
|
||||
hoverEnabled: true
|
||||
anchors.fill: parent
|
||||
preventStealing: true
|
||||
propagateComposedEvents: false
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { // The dialog
|
||||
id: dialog
|
||||
color: Appearance.colors.colSurfaceContainerHigh
|
||||
radius: Appearance.rounding.normal
|
||||
anchors.fill: parent
|
||||
anchors.margins: dialogMargin
|
||||
implicitHeight: dialogColumnLayout.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: dialogColumnLayout
|
||||
anchors.fill: parent
|
||||
spacing: 16
|
||||
|
||||
StyledText {
|
||||
id: dialogTitle
|
||||
Layout.topMargin: dialogPadding
|
||||
Layout.leftMargin: dialogPadding
|
||||
Layout.rightMargin: dialogPadding
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
font.pixelSize: Appearance.font.pixelSize.larger
|
||||
text: root.titleText
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: Appearance.m3colors.m3outline
|
||||
implicitHeight: 1
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: dialogPadding
|
||||
Layout.rightMargin: dialogPadding
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: choiceListView
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
currentIndex: root.defaultChoice !== undefined ? root.items.indexOf(root.defaultChoice) : -1
|
||||
|
||||
model: ScriptModel {
|
||||
id: choiceModel
|
||||
}
|
||||
|
||||
delegate: StyledRadioButton {
|
||||
id: radioButton
|
||||
required property var modelData
|
||||
required property int index
|
||||
anchors {
|
||||
left: parent?.left
|
||||
right: parent?.right
|
||||
leftMargin: root.dialogPadding
|
||||
rightMargin: root.dialogPadding
|
||||
}
|
||||
|
||||
description: modelData.toString()
|
||||
checked: index === choiceListView.currentIndex
|
||||
|
||||
onCheckedChanged: {
|
||||
if (checked) {
|
||||
choiceListView.currentIndex = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: Appearance.m3colors.m3outline
|
||||
implicitHeight: 1
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: dialogPadding
|
||||
Layout.rightMargin: dialogPadding
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: dialogButtonsRowLayout
|
||||
Layout.bottomMargin: dialogPadding
|
||||
Layout.leftMargin: dialogPadding
|
||||
Layout.rightMargin: dialogPadding
|
||||
Layout.alignment: Qt.AlignRight
|
||||
|
||||
DialogButton {
|
||||
buttonText: qsTr("Cancel")
|
||||
onClicked: root.canceled()
|
||||
}
|
||||
DialogButton {
|
||||
buttonText: qsTr("OK")
|
||||
onClicked: root.selected(
|
||||
root.selectedId === -1 ? null :
|
||||
root.items[root.selectedId]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import "root:/modules/common"
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
Label {
|
||||
renderType: Text.NativeRendering
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font {
|
||||
hintingPreference: Font.PreferFullHinting
|
||||
family: Appearance?.font.family.main ?? "sans-serif"
|
||||
pixelSize: Appearance?.font.pixelSize.small ?? 15
|
||||
}
|
||||
color: Appearance?.m3colors.m3onBackground ?? "black"
|
||||
linkColor: Appearance?.m3colors.m3primary
|
||||
}
|
||||
@@ -18,6 +18,7 @@ ListView {
|
||||
property real removeOvershoot: 20 // Account for gaps and bouncy animations
|
||||
property int dragIndex: -1
|
||||
property real dragDistance: 0
|
||||
property bool popin: true
|
||||
|
||||
function resetDrag() {
|
||||
root.dragIndex = -1
|
||||
@@ -27,7 +28,7 @@ ListView {
|
||||
add: Transition {
|
||||
animations: [
|
||||
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
properties: "opacity,scale",
|
||||
properties: popin ? "opacity,scale" : "opacity",
|
||||
from: 0,
|
||||
to: 1,
|
||||
}),
|
||||
@@ -40,46 +41,46 @@ ListView {
|
||||
property: "y",
|
||||
}),
|
||||
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
properties: "opacity,scale",
|
||||
properties: popin ? "opacity,scale" : "opacity",
|
||||
to: 1,
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
displaced: Transition {
|
||||
animations: [
|
||||
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
property: "y",
|
||||
}),
|
||||
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
properties: "opacity,scale",
|
||||
to: 1,
|
||||
}),
|
||||
]
|
||||
}
|
||||
// displaced: Transition {
|
||||
// animations: [
|
||||
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
// property: "y",
|
||||
// }),
|
||||
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
// properties: "opacity,scale",
|
||||
// to: 1,
|
||||
// }),
|
||||
// ]
|
||||
// }
|
||||
|
||||
move: Transition {
|
||||
animations: [
|
||||
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
property: "y",
|
||||
}),
|
||||
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
properties: "opacity,scale",
|
||||
to: 1,
|
||||
}),
|
||||
]
|
||||
}
|
||||
moveDisplaced: Transition {
|
||||
animations: [
|
||||
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
property: "y",
|
||||
}),
|
||||
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
properties: "opacity,scale",
|
||||
to: 1,
|
||||
}),
|
||||
]
|
||||
}
|
||||
// move: Transition {
|
||||
// animations: [
|
||||
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
// property: "y",
|
||||
// }),
|
||||
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
// properties: "opacity,scale",
|
||||
// to: 1,
|
||||
// }),
|
||||
// ]
|
||||
// }
|
||||
// moveDisplaced: Transition {
|
||||
// animations: [
|
||||
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
// property: "y",
|
||||
// }),
|
||||
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||
// properties: "opacity,scale",
|
||||
// to: 1,
|
||||
// }),
|
||||
// ]
|
||||
// }
|
||||
|
||||
remove: Transition {
|
||||
animations: [
|
||||
|
||||
@@ -19,7 +19,7 @@ Slider {
|
||||
property real handleLimit: root.backgroundDotMargins
|
||||
property real trackHeight: 30 * scale
|
||||
property color highlightColor: Appearance.colors.colPrimary
|
||||
property color trackColor: Appearance.m3colors.m3secondaryContainer
|
||||
property color trackColor: Appearance.colors.colSecondaryContainer
|
||||
property color handleColor: Appearance.m3colors.m3onSecondaryContainer
|
||||
property real trackRadius: Appearance.rounding.verysmall * scale
|
||||
property real unsharpenRadius: Appearance.rounding.unsharpen
|
||||
|
||||
@@ -13,7 +13,7 @@ Switch {
|
||||
implicitHeight: 32 * root.scale
|
||||
implicitWidth: 52 * root.scale
|
||||
property color activeColor: Appearance?.colors.colPrimary ?? "#685496"
|
||||
property color inactiveColor: Appearance?.m3colors.m3surfaceContainerHighest ?? "#45464F"
|
||||
property color inactiveColor: Appearance?.colors.colSurfaceContainerHighest ?? "#45464F"
|
||||
|
||||
PointingHandInteraction {}
|
||||
|
||||
|
||||
@@ -11,4 +11,5 @@ Text {
|
||||
pixelSize: Appearance?.font.pixelSize.small ?? 15
|
||||
}
|
||||
color: Appearance?.m3colors.m3onBackground ?? "black"
|
||||
linkColor: Appearance?.m3colors.m3primary
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import QtQuick.Controls
|
||||
TextArea {
|
||||
renderType: Text.NativeRendering
|
||||
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
|
||||
selectionColor: Appearance.m3colors.m3secondaryContainer
|
||||
selectionColor: Appearance.colors.colSecondaryContainer
|
||||
placeholderTextColor: Appearance.m3colors.m3outline
|
||||
font {
|
||||
family: Appearance?.font.family.main ?? "sans-serif"
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import "root:/modules/common"
|
||||
import "root:/modules/common/widgets"
|
||||
import "root:/services"
|
||||
import "root:/modules/common/functions/color_utils.js" as ColorUtils
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
Canvas { // Visualizer
|
||||
id: root
|
||||
property list<var> points
|
||||
property list<var> smoothPoints
|
||||
property real maxVisualizerValue: 1000
|
||||
property int smoothing: 2
|
||||
property bool live: true
|
||||
property color color: Appearance.m3colors.m3primary
|
||||
|
||||
onPointsChanged: () => {
|
||||
root.requestPaint()
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
var points = root.points;
|
||||
var maxVal = root.maxVisualizerValue || 1;
|
||||
var h = height;
|
||||
var w = width;
|
||||
var n = points.length;
|
||||
if (n < 2) return;
|
||||
|
||||
// Smoothing: simple moving average (optional)
|
||||
var smoothWindow = root.smoothing; // adjust for more/less smoothing
|
||||
root.smoothPoints = [];
|
||||
for (var i = 0; i < n; ++i) {
|
||||
var sum = 0, count = 0;
|
||||
for (var j = -smoothWindow; j <= smoothWindow; ++j) {
|
||||
var idx = Math.max(0, Math.min(n - 1, i + j));
|
||||
sum += points[idx];
|
||||
count++;
|
||||
}
|
||||
root.smoothPoints.push(sum / count);
|
||||
}
|
||||
if (!root.live) smoothedPoints.fill(0); // If not playing, show no points
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, h);
|
||||
for (var i = 0; i < n; ++i) {
|
||||
var x = i * w / (n - 1);
|
||||
var y = h - (root.smoothPoints[i] / maxVal) * h;
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
ctx.lineTo(w, h);
|
||||
ctx.closePath();
|
||||
|
||||
ctx.fillStyle = Qt.rgba(
|
||||
root.color.r,
|
||||
root.color.g,
|
||||
root.color.b,
|
||||
0.15
|
||||
);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect { // Blur a bit to obscure away the points
|
||||
source: root
|
||||
saturation: 0.2
|
||||
blurEnabled: true
|
||||
blurMax: 7
|
||||
blur: 1
|
||||
}
|
||||
}
|
||||
@@ -18,115 +18,130 @@ Scope { // Scope
|
||||
|
||||
Variants { // For each monitor
|
||||
model: Quickshell.screens
|
||||
PanelWindow { // Window
|
||||
|
||||
Loader {
|
||||
id: dockLoader
|
||||
required property var modelData
|
||||
id: dockRoot
|
||||
screen: modelData
|
||||
|
||||
property bool reveal: root.pinned || dockMouseArea.containsMouse || dockApps.requestDockShow
|
||||
active: ConfigOptions?.dock.hoverToReveal || (!ToplevelManager.activeToplevel?.activated)
|
||||
|
||||
anchors {
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
sourceComponent: PanelWindow { // Window
|
||||
id: dockRoot
|
||||
screen: dockLoader.modelData
|
||||
|
||||
property bool reveal: root.pinned
|
||||
|| (ConfigOptions?.dock.hoverToReveal && dockMouseArea.containsMouse)
|
||||
|| dockApps.requestDockShow
|
||||
|| (!ToplevelManager.activeToplevel?.activated)
|
||||
|
||||
function hide() {
|
||||
cheatsheetLoader.active = false
|
||||
}
|
||||
exclusiveZone: root.pinned ? implicitHeight - Appearance.sizes.hyprlandGapsOut : 0
|
||||
|
||||
implicitWidth: dockBackground.implicitWidth
|
||||
WlrLayershell.namespace: "quickshell:dock"
|
||||
color: "transparent"
|
||||
|
||||
implicitHeight: (ConfigOptions?.dock.height ?? 70) + Appearance.sizes.elevationMargin + Appearance.sizes.hyprlandGapsOut
|
||||
|
||||
mask: Region {
|
||||
item: dockMouseArea
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: dockMouseArea
|
||||
anchors.top: parent.top
|
||||
height: parent.height
|
||||
anchors.topMargin: dockRoot.reveal ? 0 : dockRoot.implicitHeight - ConfigOptions.dock.hoverRegionHeight
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
hoverEnabled: true
|
||||
|
||||
Behavior on anchors.topMargin {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
anchors {
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
Item {
|
||||
id: dockHoverRegion
|
||||
anchors.fill: parent
|
||||
exclusiveZone: root.pinned ? implicitHeight
|
||||
- (Appearance.sizes.hyprlandGapsOut)
|
||||
- (Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut) : 0
|
||||
|
||||
implicitWidth: dockBackground.implicitWidth
|
||||
WlrLayershell.namespace: "quickshell:dock"
|
||||
color: "transparent"
|
||||
|
||||
implicitHeight: (ConfigOptions?.dock.height ?? 70) + Appearance.sizes.elevationMargin + Appearance.sizes.hyprlandGapsOut
|
||||
|
||||
mask: Region {
|
||||
item: dockMouseArea
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: dockMouseArea
|
||||
anchors.top: parent.top
|
||||
height: parent.height
|
||||
anchors.topMargin: dockRoot.reveal ? 0 :
|
||||
ConfigOptions?.dock.hoverToReveal ? (dockRoot.implicitHeight - ConfigOptions.dock.hoverRegionHeight) :
|
||||
(dockRoot.implicitHeight + 1)
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
hoverEnabled: true
|
||||
|
||||
Behavior on anchors.topMargin {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
Item {
|
||||
id: dockBackground
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
id: dockHoverRegion
|
||||
anchors.fill: parent
|
||||
|
||||
implicitWidth: dockRow.implicitWidth + 5 * 2
|
||||
height: parent.height - Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut
|
||||
Item { // Wrapper for the dock background
|
||||
id: dockBackground
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledRectangularShadow {
|
||||
target: dockVisualBackground
|
||||
}
|
||||
Rectangle {
|
||||
id: dockVisualBackground
|
||||
property real margin: Appearance.sizes.elevationMargin
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: margin
|
||||
anchors.bottomMargin: margin
|
||||
color: Appearance.colors.colLayer0
|
||||
radius: Appearance.rounding.large
|
||||
}
|
||||
implicitWidth: dockRow.implicitWidth + 5 * 2
|
||||
height: parent.height - Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut
|
||||
|
||||
RowLayout {
|
||||
id: dockRow
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: 3
|
||||
property real padding: 5
|
||||
StyledRectangularShadow {
|
||||
target: dockVisualBackground
|
||||
}
|
||||
Rectangle { // The real rectangle that is visible
|
||||
id: dockVisualBackground
|
||||
property real margin: Appearance.sizes.elevationMargin
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: Appearance.sizes.elevationMargin
|
||||
anchors.bottomMargin: Appearance.sizes.hyprlandGapsOut
|
||||
color: Appearance.colors.colLayer0
|
||||
radius: Appearance.rounding.large
|
||||
}
|
||||
|
||||
VerticalButtonGroup {
|
||||
GroupButton { // Pin button
|
||||
baseWidth: 35
|
||||
baseHeight: 35
|
||||
clickedWidth: baseWidth
|
||||
clickedHeight: baseHeight + 20
|
||||
buttonRadius: Appearance.rounding.normal
|
||||
toggled: root.pinned
|
||||
onClicked: root.pinned = !root.pinned
|
||||
RowLayout {
|
||||
id: dockRow
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: 3
|
||||
property real padding: 5
|
||||
|
||||
VerticalButtonGroup {
|
||||
Layout.topMargin: Appearance.sizes.hyprlandGapsOut // why does this work
|
||||
GroupButton { // Pin button
|
||||
baseWidth: 35
|
||||
baseHeight: 35
|
||||
clickedWidth: baseWidth
|
||||
clickedHeight: baseHeight + 20
|
||||
buttonRadius: Appearance.rounding.normal
|
||||
toggled: root.pinned
|
||||
onClicked: root.pinned = !root.pinned
|
||||
contentItem: MaterialSymbol {
|
||||
text: "keep"
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
color: root.pinned ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer0
|
||||
}
|
||||
}
|
||||
}
|
||||
DockSeparator {}
|
||||
DockApps { id: dockApps; }
|
||||
DockSeparator {}
|
||||
DockButton {
|
||||
Layout.fillHeight: true
|
||||
onClicked: Hyprland.dispatch("global quickshell:overviewToggle")
|
||||
contentItem: MaterialSymbol {
|
||||
text: "keep"
|
||||
anchors.fill: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
color: root.pinned ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer0
|
||||
font.pixelSize: parent.width / 2
|
||||
text: "apps"
|
||||
color: Appearance.colors.colOnLayer0
|
||||
}
|
||||
}
|
||||
}
|
||||
DockSeparator {}
|
||||
DockApps { id: dockApps }
|
||||
DockSeparator {}
|
||||
DockButton {
|
||||
onClicked: Hyprland.dispatch("global quickshell:overviewToggle")
|
||||
contentItem: MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pixelSize: parent.width / 2
|
||||
text: "apps"
|
||||
color: Appearance.colors.colOnLayer0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import "root:/"
|
||||
import "root:/services"
|
||||
import "root:/modules/common"
|
||||
import "root:/modules/common/widgets"
|
||||
import "root:/modules/common/functions/color_utils.js" as ColorUtils
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
@@ -13,32 +14,102 @@ import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
|
||||
DockButton {
|
||||
id: appButton
|
||||
required property var appToplevel
|
||||
id: root
|
||||
property var appToplevel
|
||||
property var appListRoot
|
||||
property int lastFocused: -1
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
onEntered: {
|
||||
appListRoot.lastHoveredButton = appButton
|
||||
appListRoot.buttonHovered = true
|
||||
lastFocused = appToplevel.toplevels.length - 1
|
||||
property real iconSize: 35
|
||||
property real countDotWidth: 10
|
||||
property real countDotHeight: 4
|
||||
property bool appIsActive: appToplevel.toplevels.find(t => (t.activated == true)) !== undefined
|
||||
|
||||
property bool isSeparator: appToplevel.appId === "SEPARATOR"
|
||||
property var desktopEntry: DesktopEntries.byId(appToplevel.appId)
|
||||
enabled: !isSeparator
|
||||
implicitWidth: isSeparator ? 1 : implicitHeight - topInset - bottomInset
|
||||
|
||||
Loader {
|
||||
active: isSeparator
|
||||
anchors {
|
||||
fill: parent
|
||||
topMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal
|
||||
bottomMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal
|
||||
}
|
||||
onExited: {
|
||||
if (appListRoot.lastHoveredButton === appButton) {
|
||||
appListRoot.buttonHovered = false
|
||||
sourceComponent: DockSeparator {}
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
active: appToplevel.toplevels.length > 0
|
||||
sourceComponent: MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
onEntered: {
|
||||
appListRoot.lastHoveredButton = root
|
||||
appListRoot.buttonHovered = true
|
||||
lastFocused = appToplevel.toplevels.length - 1
|
||||
}
|
||||
onExited: {
|
||||
if (appListRoot.lastHoveredButton === root) {
|
||||
appListRoot.buttonHovered = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
if (appToplevel.toplevels.length === 0) {
|
||||
root.desktopEntry?.execute();
|
||||
return;
|
||||
}
|
||||
lastFocused = (lastFocused + 1) % appToplevel.toplevels.length
|
||||
appToplevel.toplevels[lastFocused].activate()
|
||||
}
|
||||
contentItem: IconImage {
|
||||
id: iconImage
|
||||
source: Quickshell.iconPath(AppSearch.guessIcon(appToplevel.appId), "image-missing")
|
||||
|
||||
middleClickAction: () => {
|
||||
root.desktopEntry?.execute();
|
||||
}
|
||||
|
||||
contentItem: Loader {
|
||||
active: !isSeparator
|
||||
sourceComponent: Item {
|
||||
anchors.centerIn: parent
|
||||
|
||||
Loader {
|
||||
id: iconImageLoader
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
active: !root.isSeparator
|
||||
sourceComponent: IconImage {
|
||||
source: Quickshell.iconPath(AppSearch.guessIcon(appToplevel.appId), "image-missing")
|
||||
implicitSize: root.iconSize
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 3
|
||||
anchors {
|
||||
top: iconImageLoader.bottom
|
||||
topMargin: 2
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
Repeater {
|
||||
model: Math.min(appToplevel.toplevels.length, 3)
|
||||
delegate: Rectangle {
|
||||
required property int index
|
||||
radius: Appearance.rounding.full
|
||||
implicitWidth: (appToplevel.toplevels.length <= 3) ?
|
||||
root.countDotWidth : root.countDotHeight // Circles when too many
|
||||
implicitHeight: root.countDotHeight
|
||||
color: appIsActive ? Appearance.colors.colPrimary : ColorUtils.transparentize(Appearance.colors.colOnLayer0, 0.4)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,40 +23,66 @@ Item {
|
||||
property Item lastHoveredButton
|
||||
property bool buttonHovered: false
|
||||
property bool requestDockShow: previewPopup.show
|
||||
property real popupX: parentWindow.mapFromItem(root.lastHoveredButton, root.lastHoveredButton.width / 2, root.lastHoveredButton.height / 2).x - implicitWidth / 2
|
||||
|
||||
implicitWidth: rowLayout.implicitWidth
|
||||
implicitHeight: rowLayout.implicitHeight
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
Layout.fillHeight: true
|
||||
Layout.topMargin: Appearance.sizes.hyprlandGapsOut // why does this work
|
||||
implicitWidth: listView.implicitWidth
|
||||
|
||||
StyledListView {
|
||||
id: listView
|
||||
spacing: 2
|
||||
orientation: ListView.Horizontal
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
implicitWidth: contentWidth
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
objectProp: "appId"
|
||||
values: {
|
||||
var map = new Map();
|
||||
Behavior on implicitWidth {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
for (const toplevel of ToplevelManager.toplevels.values) {
|
||||
if (!map.has(toplevel.appId.toLowerCase())) map.set(toplevel.appId.toLowerCase(), []);
|
||||
map.get(toplevel.appId.toLowerCase()).push(toplevel);
|
||||
}
|
||||
model: ScriptModel {
|
||||
objectProp: "appId"
|
||||
values: {
|
||||
var map = new Map();
|
||||
|
||||
var values = [];
|
||||
|
||||
for (const [key, value] of map) {
|
||||
values.push({ appId: key, toplevels: value });
|
||||
}
|
||||
|
||||
return values;
|
||||
// Pinned apps
|
||||
const pinnedApps = ConfigOptions?.dock.pinnedApps ?? [];
|
||||
for (const appId of pinnedApps) {
|
||||
if (!map.has(appId.toLowerCase())) map.set(appId.toLowerCase(), ({
|
||||
pinned: true,
|
||||
toplevels: []
|
||||
}));
|
||||
}
|
||||
|
||||
// Separator
|
||||
if (pinnedApps.length > 0) {
|
||||
map.set("SEPARATOR", { pinned: false, toplevels: [] });
|
||||
}
|
||||
|
||||
// Open windows
|
||||
for (const toplevel of ToplevelManager.toplevels.values) {
|
||||
if (!map.has(toplevel.appId.toLowerCase())) map.set(toplevel.appId.toLowerCase(), ({
|
||||
pinned: false,
|
||||
toplevels: []
|
||||
}));
|
||||
map.get(toplevel.appId.toLowerCase()).toplevels.push(toplevel);
|
||||
}
|
||||
|
||||
var values = [];
|
||||
|
||||
for (const [key, value] of map) {
|
||||
values.push({ appId: key, toplevels: value.toplevels, pinned: value.pinned });
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
delegate: DockAppButton {
|
||||
required property var modelData
|
||||
appToplevel: modelData
|
||||
appListRoot: root
|
||||
}
|
||||
}
|
||||
delegate: DockAppButton {
|
||||
required property var modelData
|
||||
appToplevel: modelData
|
||||
appListRoot: root
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,14 +144,9 @@ Item {
|
||||
anchors.bottom: parent.bottom
|
||||
implicitWidth: popupBackground.implicitWidth + Appearance.sizes.elevationMargin * 2
|
||||
implicitHeight: root.maxWindowPreviewHeight + root.windowControlsHeight + Appearance.sizes.elevationMargin * 2
|
||||
// anchors.horizontalCenter: parent.horizontalCenter
|
||||
hoverEnabled: true
|
||||
// x: previewPopup.width / 2 + root.popupX
|
||||
// Behavior on x {
|
||||
// animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
// }
|
||||
x: {
|
||||
const itemCenter = root.QsWindow.mapFromItem(root.lastHoveredButton, root.lastHoveredButton.width / 2, 0);
|
||||
const itemCenter = root.QsWindow?.mapFromItem(root.lastHoveredButton, root.lastHoveredButton?.width / 2, 0);
|
||||
return itemCenter.x - width / 2
|
||||
}
|
||||
StyledRectangularShadow {
|
||||
@@ -145,7 +166,7 @@ Item {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
clip: true
|
||||
color: Appearance.m3colors.m3surfaceContainer
|
||||
color: Appearance.colors.colSurfaceContainer
|
||||
radius: Appearance.rounding.normal
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Appearance.sizes.elevationMargin
|
||||
@@ -163,7 +184,9 @@ Item {
|
||||
id: previewRowLayout
|
||||
anchors.centerIn: parent
|
||||
Repeater {
|
||||
model: previewPopup.appTopLevel?.toplevels ?? []
|
||||
model: ScriptModel {
|
||||
values: previewPopup.appTopLevel?.toplevels ?? []
|
||||
}
|
||||
RippleButton {
|
||||
id: windowButton
|
||||
required property var modelData
|
||||
@@ -182,7 +205,7 @@ Item {
|
||||
contentWidth: parent.width - anchors.margins * 2
|
||||
WrapperRectangle {
|
||||
Layout.fillWidth: true
|
||||
color: ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainer)
|
||||
color: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer)
|
||||
radius: Appearance.rounding.small
|
||||
margin: 5
|
||||
StyledText {
|
||||
@@ -195,7 +218,7 @@ Item {
|
||||
}
|
||||
GroupButton {
|
||||
id: closeButton
|
||||
colBackground: ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainer)
|
||||
colBackground: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer)
|
||||
baseWidth: windowControlsHeight
|
||||
baseHeight: windowControlsHeight
|
||||
buttonRadius: Appearance.rounding.full
|
||||
|
||||
@@ -7,9 +7,10 @@ import QtQuick.Layouts
|
||||
|
||||
RippleButton {
|
||||
Layout.fillHeight: true
|
||||
Layout.topMargin: Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut
|
||||
implicitWidth: implicitHeight - topInset - bottomInset
|
||||
buttonRadius: Appearance.rounding.normal
|
||||
|
||||
topInset: dockVisualBackground.margin + dockRow.padding
|
||||
bottomInset: dockVisualBackground.margin + dockRow.padding
|
||||
topInset: Appearance.sizes.hyprlandGapsOut + dockRow.padding
|
||||
bottomInset: Appearance.sizes.hyprlandGapsOut + dockRow.padding
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
Rectangle {
|
||||
Layout.topMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal
|
||||
Layout.bottomMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal
|
||||
Layout.topMargin: Appearance.sizes.elevationMargin + dockRow.padding + Appearance.rounding.normal
|
||||
Layout.bottomMargin: Appearance.sizes.hyprlandGapsOut + dockRow.padding + Appearance.rounding.normal
|
||||
Layout.fillHeight: true
|
||||
implicitWidth: 1
|
||||
color: Appearance.m3colors.m3outlineVariant
|
||||
color: Appearance.colors.colOutlineVariant
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ Scope {
|
||||
property real contentPadding: 13
|
||||
property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1
|
||||
property real artRounding: Appearance.rounding.verysmall
|
||||
property list<real> visualizerPoints: []
|
||||
|
||||
property bool hasPlasmaIntegration: false
|
||||
function isRealPlayer(player) {
|
||||
@@ -53,7 +54,7 @@ Scope {
|
||||
for (let j = i + 1; j < players.length; ++j) {
|
||||
let p2 = players[j];
|
||||
if (p1.trackTitle && p2.trackTitle &&
|
||||
(p1.trackTitle.startsWith(p2.trackTitle) || p2.trackTitle.startsWith(p1.trackTitle))) {
|
||||
(p1.trackTitle.includes(p2.trackTitle) || p2.trackTitle.includes(p1.trackTitle))) {
|
||||
group.push(j);
|
||||
}
|
||||
}
|
||||
@@ -68,13 +69,31 @@ Scope {
|
||||
return filtered;
|
||||
}
|
||||
|
||||
Process {
|
||||
id: cavaProc
|
||||
running: mediaControlsLoader.active
|
||||
onRunningChanged: {
|
||||
if (!cavaProc.running) {
|
||||
root.visualizerPoints = [];
|
||||
}
|
||||
}
|
||||
command: ["cava", "-p", `${FileUtils.trimFileProtocol(Directories.config)}/quickshell/scripts/cava/raw_output_config.txt`]
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
// Parse `;`-separated values into the visualizerPoints array
|
||||
let points = data.split(";").map(p => parseFloat(p.trim())).filter(p => !isNaN(p));
|
||||
root.visualizerPoints = points;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: mediaControlsLoader
|
||||
active: false
|
||||
|
||||
sourceComponent: PanelWindow {
|
||||
id: mediaControlsRoot
|
||||
visible: mediaControlsLoader.active
|
||||
visible: true
|
||||
|
||||
exclusiveZone: 0
|
||||
implicitWidth: (
|
||||
@@ -112,6 +131,7 @@ Scope {
|
||||
delegate: PlayerControl {
|
||||
required property MprisPlayer modelData
|
||||
player: modelData
|
||||
visualizerPoints: root.visualizerPoints
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,9 @@ Item { // Player instance
|
||||
property string artFilePath: `${artDownloadLocation}/${artFileName}`
|
||||
property color artDominantColor: colorQuantizer?.colors[0] || Appearance.m3colors.m3secondaryContainer
|
||||
property bool downloaded: false
|
||||
property list<real> visualizerPoints: []
|
||||
property real maxVisualizerValue: 1000 // Max value in the data points
|
||||
property int visualizerSmoothing: 2 // Number of points to average for smoothing
|
||||
|
||||
implicitWidth: widgetWidth
|
||||
implicitHeight: widgetHeight
|
||||
@@ -150,6 +153,16 @@ Item { // Player instance
|
||||
}
|
||||
}
|
||||
|
||||
WaveVisualizer {
|
||||
id: visualizerCanvas
|
||||
anchors.fill: parent
|
||||
live: playerController.player?.isPlaying
|
||||
points: playerController.visualizerPoints
|
||||
maxVisualizerValue: playerController.maxVisualizerValue
|
||||
smoothing: playerController.visualizerSmoothing
|
||||
color: blendedColors.colPrimary
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: root.contentPadding
|
||||
@@ -160,7 +173,7 @@ Item { // Player instance
|
||||
Layout.fillHeight: true
|
||||
implicitWidth: height
|
||||
radius: root.artRounding
|
||||
color: blendedColors.colLayer1
|
||||
color: ColorUtils.transparentize(blendedColors.colLayer1, 0.5)
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
@@ -235,12 +248,18 @@ Item { // Player instance
|
||||
iconName: "skip_previous"
|
||||
onClicked: playerController.player?.previous()
|
||||
}
|
||||
StyledProgressBar {
|
||||
id: slider
|
||||
Item {
|
||||
id: progressBarContainer
|
||||
Layout.fillWidth: true
|
||||
highlightColor: blendedColors.colPrimary
|
||||
trackColor: blendedColors.colSecondaryContainer
|
||||
value: playerController.player?.position / playerController.player?.length
|
||||
implicitHeight: progressBar.implicitHeight
|
||||
|
||||
StyledProgressBar {
|
||||
id: progressBar
|
||||
anchors.fill: parent
|
||||
highlightColor: blendedColors.colPrimary
|
||||
trackColor: blendedColors.colSecondaryContainer
|
||||
value: playerController.player?.position / playerController.player?.length
|
||||
}
|
||||
}
|
||||
TrackChangeButton {
|
||||
iconName: "skip_next"
|
||||
|
||||
@@ -15,7 +15,7 @@ Scope {
|
||||
PanelWindow {
|
||||
id: root
|
||||
visible: (Notifications.popupList.length > 0)
|
||||
screen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)
|
||||
screen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) ?? null
|
||||
|
||||
WlrLayershell.namespace: "quickshell:notificationPopup"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
|
||||
@@ -73,7 +73,7 @@ Scope {
|
||||
item: osdValuesWrapper
|
||||
}
|
||||
|
||||
implicitWidth: Appearance.sizes.osdWidth
|
||||
implicitWidth: columnLayout.implicitWidth
|
||||
implicitHeight: columnLayout.implicitHeight
|
||||
visible: osdLoader.active
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import Quickshell.Hyprland
|
||||
Scope {
|
||||
id: root
|
||||
property bool showOsdValues: false
|
||||
property string protectionMessage: ""
|
||||
property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)
|
||||
|
||||
function triggerOsd() {
|
||||
@@ -25,7 +26,8 @@ Scope {
|
||||
repeat: false
|
||||
running: false
|
||||
onTriggered: {
|
||||
showOsdValues = false
|
||||
root.showOsdValues = false
|
||||
root.protectionMessage = ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +38,7 @@ Scope {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
Connections { // Listen to volume changes
|
||||
target: Audio.sink?.audio ?? null
|
||||
function onVolumeChanged() {
|
||||
if (!Audio.ready) return
|
||||
@@ -48,6 +50,14 @@ Scope {
|
||||
}
|
||||
}
|
||||
|
||||
Connections { // Listen to protection triggers
|
||||
target: Audio
|
||||
function onSinkProtectionTriggered(reason) {
|
||||
root.protectionMessage = reason;
|
||||
root.triggerOsd()
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: osdLoader
|
||||
active: showOsdValues
|
||||
@@ -75,7 +85,7 @@ Scope {
|
||||
item: osdValuesWrapper
|
||||
}
|
||||
|
||||
implicitWidth: Appearance.sizes.osdWidth
|
||||
implicitWidth: columnLayout.implicitWidth
|
||||
implicitHeight: columnLayout.implicitHeight
|
||||
visible: osdLoader.active
|
||||
|
||||
@@ -85,8 +95,8 @@ Scope {
|
||||
Item {
|
||||
id: osdValuesWrapper
|
||||
// Extra space for shadow
|
||||
implicitHeight: osdValues.implicitHeight + Appearance.sizes.elevationMargin * 2
|
||||
implicitWidth: osdValues.implicitWidth
|
||||
implicitHeight: contentColumnLayout.implicitHeight + Appearance.sizes.elevationMargin * 2
|
||||
implicitWidth: contentColumnLayout.implicitWidth
|
||||
clip: true
|
||||
|
||||
MouseArea {
|
||||
@@ -95,20 +105,63 @@ Scope {
|
||||
onEntered: root.showOsdValues = false
|
||||
}
|
||||
|
||||
Behavior on implicitHeight {
|
||||
NumberAnimation {
|
||||
duration: Appearance.animation.menuDecel.duration
|
||||
easing.type: Appearance.animation.menuDecel.type
|
||||
ColumnLayout {
|
||||
id: contentColumnLayout
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
leftMargin: Appearance.sizes.elevationMargin
|
||||
rightMargin: Appearance.sizes.elevationMargin
|
||||
}
|
||||
}
|
||||
spacing: 0
|
||||
|
||||
OsdValueIndicator {
|
||||
id: osdValues
|
||||
anchors.fill: parent
|
||||
anchors.margins: Appearance.sizes.elevationMargin
|
||||
value: Audio.sink?.audio.volume ?? 0
|
||||
icon: Audio.sink?.audio.muted ? "volume_off" : "volume_up"
|
||||
name: qsTr("Volume")
|
||||
OsdValueIndicator {
|
||||
id: osdValues
|
||||
Layout.fillWidth: true
|
||||
value: Audio.sink?.audio.volume ?? 0
|
||||
icon: Audio.sink?.audio.muted ? "volume_off" : "volume_up"
|
||||
name: qsTr("Volume")
|
||||
}
|
||||
|
||||
Item {
|
||||
id: protectionMessageWrapper
|
||||
implicitHeight: protectionMessageBackground.implicitHeight
|
||||
implicitWidth: protectionMessageBackground.implicitWidth
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
opacity: root.protectionMessage !== "" ? 1 : 0
|
||||
|
||||
StyledRectangularShadow {
|
||||
target: protectionMessageBackground
|
||||
}
|
||||
Rectangle {
|
||||
id: protectionMessageBackground
|
||||
anchors.centerIn: parent
|
||||
color: Appearance.m3colors.m3error
|
||||
property real padding: 10
|
||||
implicitHeight: protectionMessageRowLayout.implicitHeight + padding * 2
|
||||
implicitWidth: protectionMessageRowLayout.implicitWidth + padding * 2
|
||||
radius: Appearance.rounding.normal
|
||||
|
||||
RowLayout {
|
||||
id: protectionMessageRowLayout
|
||||
anchors.centerIn: parent
|
||||
MaterialSymbol {
|
||||
id: protectionMessageIcon
|
||||
text: "dangerous"
|
||||
iconSize: Appearance.font.pixelSize.hugeass
|
||||
color: Appearance.m3colors.m3onError
|
||||
}
|
||||
StyledText {
|
||||
id: protectionMessageTextWidget
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: Appearance.m3colors.m3onError
|
||||
wrapMode: Text.Wrap
|
||||
text: root.protectionMessage
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ Scope { // Scope
|
||||
Layout.bottomMargin: 20
|
||||
Layout.fillHeight: true
|
||||
implicitWidth: 1
|
||||
color: Appearance.m3colors.m3outlineVariant
|
||||
color: Appearance.colors.colOutlineVariant
|
||||
}
|
||||
OskContent {
|
||||
id: oskContent
|
||||
|
||||
@@ -92,8 +92,8 @@ Scope {
|
||||
visible: GlobalStates.overviewOpen
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
top: !ConfigOptions.bar.bottom ? parent.top : null
|
||||
bottom: ConfigOptions.bar.bottom ? parent.bottom : null
|
||||
top: !ConfigOptions.bar.bottom ? parent.top : undefined
|
||||
bottom: ConfigOptions.bar.bottom ? parent.bottom : undefined
|
||||
}
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
|
||||
@@ -25,7 +25,7 @@ Item {
|
||||
property var windowAddresses: HyprlandData.addresses
|
||||
property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor.id)
|
||||
property real scale: ConfigOptions.overview.scale
|
||||
property color activeBorderColor: Appearance.m3colors.m3secondary
|
||||
property color activeBorderColor: Appearance.colors.colSecondary
|
||||
|
||||
property real workspaceImplicitWidth: (monitorData?.transform % 2 === 1) ?
|
||||
((monitor.height - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale) :
|
||||
@@ -144,25 +144,38 @@ Item {
|
||||
implicitHeight: workspaceColumnLayout.implicitHeight
|
||||
|
||||
Repeater { // Window repeater
|
||||
model: windowAddresses.filter((address) => {
|
||||
var win = windowByAddress[address]
|
||||
return (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown)
|
||||
})
|
||||
model: ScriptModel {
|
||||
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}`
|
||||
// console.log(`Checking window with address: ${address}`)
|
||||
var win = windowByAddress[address]
|
||||
return (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown)
|
||||
})
|
||||
}
|
||||
}
|
||||
delegate: OverviewWindow {
|
||||
id: window
|
||||
windowData: windowByAddress[modelData]
|
||||
required property var modelData
|
||||
property var address: `0x${modelData.HyprlandToplevel.address}`
|
||||
windowData: windowByAddress[address]
|
||||
toplevel: modelData
|
||||
monitorData: root.monitorData
|
||||
scale: root.scale
|
||||
availableWorkspaceWidth: root.workspaceImplicitWidth
|
||||
availableWorkspaceHeight: root.workspaceImplicitHeight
|
||||
|
||||
property int monitorId: windowData?.monitor
|
||||
property var monitor: HyprlandData.monitors[monitorId]
|
||||
|
||||
property bool atInitPosition: (initX == x && initY == y)
|
||||
restrictToWorkspace: Drag.active || atInitPosition
|
||||
|
||||
property int workspaceColIndex: (windowData?.workspace.id - 1) % ConfigOptions.overview.numOfCols
|
||||
property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / ConfigOptions.overview.numOfCols)
|
||||
xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex
|
||||
yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex
|
||||
xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex - (monitor?.x * root.scale)
|
||||
yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex - (monitor?.y * root.scale)
|
||||
|
||||
Timer {
|
||||
id: updateWindowPosition
|
||||
@@ -170,8 +183,9 @@ Item {
|
||||
repeat: false
|
||||
running: false
|
||||
onTriggered: {
|
||||
window.x = Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0) + xOffset
|
||||
window.y = Math.max((windowData?.at[1] - monitorData?.reserved[1]) * root.scale, 0) + yOffset
|
||||
window.x = Math.round(Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0) + xOffset)
|
||||
window.y = Math.round(Math.max((windowData?.at[1] - monitorData?.reserved[1]) * root.scale, 0) + yOffset)
|
||||
console.log(`[OverviewWindow] Updated position for window ${windowData?.address} to (${window.x}, ${window.y})`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,6 +205,7 @@ Item {
|
||||
window.pressed = true
|
||||
window.Drag.active = true
|
||||
window.Drag.source = window
|
||||
console.log(`[OverviewWindow] Dragging window ${windowData?.address} from position (${window.x}, ${window.y})`)
|
||||
}
|
||||
onReleased: {
|
||||
const targetWorkspace = root.draggingTargetWorkspace
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import "root:/"
|
||||
import "root:/services/"
|
||||
import "root:/modules/common"
|
||||
import "root:/modules/common/widgets"
|
||||
import "root:/modules/common/functions/color_utils.js" as ColorUtils
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
|
||||
Rectangle { // Window
|
||||
Item { // Window
|
||||
id: root
|
||||
property var toplevel
|
||||
property var windowData
|
||||
property var monitorData
|
||||
property var scale
|
||||
@@ -38,14 +42,17 @@ Rectangle { // Window
|
||||
|
||||
x: initX
|
||||
y: initY
|
||||
width: Math.min(windowData?.size[0] * root.scale, (restrictToWorkspace ? windowData?.size[0] : availableWorkspaceWidth - x + xOffset))
|
||||
height: Math.min(windowData?.size[1] * root.scale, (restrictToWorkspace ? windowData?.size[1] : availableWorkspaceHeight - y + yOffset))
|
||||
width: Math.round(Math.min(windowData?.size[0] * root.scale, (restrictToWorkspace ? windowData?.size[0] : availableWorkspaceWidth - x + xOffset)))
|
||||
height: Math.round(Math.min(windowData?.size[1] * root.scale, (restrictToWorkspace ? windowData?.size[1] : availableWorkspaceHeight - y + yOffset)))
|
||||
|
||||
radius: Appearance.rounding.windowRounding * root.scale
|
||||
color: pressed ? Appearance.colors.colLayer2Active : hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2
|
||||
border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.9)
|
||||
border.pixelAligned : false
|
||||
border.width : 1
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
width: root.width
|
||||
height: root.height
|
||||
radius: Appearance.rounding.windowRounding * root.scale
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||
@@ -60,34 +67,45 @@ Rectangle { // Window
|
||||
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: Appearance.font.pixelSize.smaller * 0.5
|
||||
ScreencopyView {
|
||||
id: windowPreview
|
||||
anchors.fill: parent
|
||||
captureSource: GlobalStates.overviewOpen ? root.toplevel : null
|
||||
live: true
|
||||
|
||||
IconImage {
|
||||
id: windowIcon
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
source: root.iconPath
|
||||
implicitSize: Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio)
|
||||
|
||||
Behavior on implicitSize {
|
||||
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||
}
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Appearance.rounding.windowRounding * root.scale
|
||||
color: pressed ? ColorUtils.transparentize(Appearance.colors.colLayer2Active, 0.5) :
|
||||
hovered ? ColorUtils.transparentize(Appearance.colors.colLayer2Hover, 0.7) :
|
||||
ColorUtils.transparentize(Appearance.colors.colLayer2)
|
||||
border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.7)
|
||||
border.width : 1
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.leftMargin: 10
|
||||
Layout.rightMargin: 10
|
||||
visible: !compactMode
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pixelSize: Appearance.font.pixelSize.smaller
|
||||
font.italic: indicateXWayland ? true : false
|
||||
elide: Text.ElideRight
|
||||
text: windowData?.title ?? ""
|
||||
ColumnLayout {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: Appearance.font.pixelSize.smaller * 0.5
|
||||
|
||||
Image {
|
||||
id: windowIcon
|
||||
property var iconSize: Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio)
|
||||
// mipmap: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
source: root.iconPath
|
||||
width: iconSize
|
||||
height: iconSize
|
||||
sourceSize: Qt.size(iconSize, iconSize)
|
||||
|
||||
Behavior on width {
|
||||
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||
}
|
||||
Behavior on height {
|
||||
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ RippleButton {
|
||||
buttonRadius: Appearance.rounding.normal
|
||||
colBackground: (root.down || root.keyboardDown) ? Appearance.colors.colLayer1Active :
|
||||
((root.hovered || root.focus) ? Appearance.colors.colLayer1Hover :
|
||||
ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainerHigh, 1))
|
||||
ColorUtils.transparentize(Appearance.colors.colSurfaceContainerHigh, 1))
|
||||
colBackgroundHover: Appearance.colors.colLayer1Hover
|
||||
colRipple: Appearance.colors.colLayer1Active
|
||||
|
||||
|
||||
@@ -219,7 +219,7 @@ Item { // Wrapper
|
||||
}
|
||||
color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant
|
||||
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
|
||||
selectionColor: Appearance.m3colors.m3secondaryContainer
|
||||
selectionColor: Appearance.colors.colSecondaryContainer
|
||||
placeholderText: qsTr("Search, calculate or run")
|
||||
placeholderTextColor: Appearance.m3colors.m3outline
|
||||
implicitWidth: root.searchingText == "" ? Appearance.sizes.searchWidthCollapsed : Appearance.sizes.searchWidth
|
||||
@@ -260,7 +260,7 @@ Item { // Wrapper
|
||||
visible: root.showResults
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: Appearance.m3colors.m3outlineVariant
|
||||
color: Appearance.colors.colOutlineVariant
|
||||
}
|
||||
|
||||
ListView { // App results
|
||||
|
||||
@@ -5,6 +5,7 @@ import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
|
||||
Scope {
|
||||
id: screenCorners
|
||||
@@ -14,7 +15,9 @@ Scope {
|
||||
model: Quickshell.screens
|
||||
|
||||
PanelWindow {
|
||||
visible: (ConfigOptions.appearance.fakeScreenRounding === 1 || (ConfigOptions.appearance.fakeScreenRounding === 2 && !activeWindow?.fullscreen))
|
||||
visible: (ConfigOptions.appearance.fakeScreenRounding === 1
|
||||
|| (ConfigOptions.appearance.fakeScreenRounding === 2
|
||||
&& !activeWindow?.fullscreen))
|
||||
|
||||
property var modelData
|
||||
|
||||
@@ -23,6 +26,20 @@ Scope {
|
||||
mask: Region {
|
||||
item: null
|
||||
}
|
||||
HyprlandWindow.visibleMask: Region {
|
||||
Region {
|
||||
item: topLeftCorner
|
||||
}
|
||||
Region {
|
||||
item: topRightCorner
|
||||
}
|
||||
Region {
|
||||
item: bottomLeftCorner
|
||||
}
|
||||
Region {
|
||||
item: bottomRightCorner
|
||||
}
|
||||
}
|
||||
WlrLayershell.namespace: "quickshell:screenCorners"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
color: "transparent"
|
||||
@@ -35,24 +52,28 @@ Scope {
|
||||
}
|
||||
|
||||
RoundCorner {
|
||||
id: topLeftCorner
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
size: Appearance.rounding.screenRounding
|
||||
corner: cornerEnum.topLeft
|
||||
}
|
||||
RoundCorner {
|
||||
id: topRightCorner
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
size: Appearance.rounding.screenRounding
|
||||
corner: cornerEnum.topRight
|
||||
}
|
||||
RoundCorner {
|
||||
id: bottomLeftCorner
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
size: Appearance.rounding.screenRounding
|
||||
corner: cornerEnum.bottomLeft
|
||||
}
|
||||
RoundCorner {
|
||||
id: bottomRightCorner
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
size: Appearance.rounding.screenRounding
|
||||
|
||||
@@ -18,7 +18,7 @@ RippleButton {
|
||||
buttonRadius: (button.focus || button.down) ? size / 2 : Appearance.rounding.verylarge
|
||||
colBackground: button.keyboardDown ? Appearance.colors.colSecondaryContainerActive :
|
||||
button.focus ? Appearance.colors.colPrimary :
|
||||
Appearance.m3colors.m3secondaryContainer
|
||||
Appearance.colors.colSecondaryContainer
|
||||
colBackgroundHover: Appearance.colors.colPrimary
|
||||
colRipple: Appearance.colors.colPrimaryActive
|
||||
property color colText: (button.down || button.keyboardDown || button.focus || button.hovered) ?
|
||||
|
||||
@@ -125,9 +125,14 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
### LaTeX
|
||||
|
||||
- Simple inline: $\\frac{1}{2} = \\frac{2}{4}$
|
||||
- Complex inline: $$\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}$$
|
||||
- Another complex inline: \\\\[\\int_0^\\infty \\frac{1}{x^2} dx = \\infty\\\\]
|
||||
|
||||
Inline w/ dollar signs: $\\frac{1}{2} = \\frac{2}{4}$
|
||||
|
||||
Inline w/ double dollar signs: $$\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}$$
|
||||
|
||||
Inline w/ backslash and square brackets \\[\\int_0^\\infty \\frac{1}{x^2} dx = \\infty\\]
|
||||
|
||||
Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
|
||||
`,
|
||||
Ai.interfaceRole);
|
||||
}
|
||||
@@ -162,6 +167,7 @@ int main(int argc, char* argv[]) {
|
||||
id: messageListView
|
||||
anchors.fill: parent
|
||||
spacing: 10
|
||||
popin: false
|
||||
|
||||
property int lastResponseLength: 0
|
||||
|
||||
@@ -175,6 +181,8 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
add: null // Prevent function calls from being janky
|
||||
|
||||
Behavior on contentY {
|
||||
NumberAnimation {
|
||||
id: scrollAnim
|
||||
@@ -337,7 +345,7 @@ int main(int argc, char* argv[]) {
|
||||
implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin
|
||||
+ commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + columnSpacing, 45)
|
||||
clip: true
|
||||
border.color: Appearance.m3colors.m3outlineVariant
|
||||
border.color: Appearance.colors.colOutlineVariant
|
||||
border.width: 1
|
||||
|
||||
Behavior on implicitHeight {
|
||||
|
||||
@@ -359,7 +359,7 @@ Item {
|
||||
implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin
|
||||
+ commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + columnSpacing, 45)
|
||||
clip: true
|
||||
border.color: Appearance.m3colors.m3outlineVariant
|
||||
border.color: Appearance.colors.colOutlineVariant
|
||||
border.width: 1
|
||||
|
||||
Behavior on implicitHeight {
|
||||
@@ -562,8 +562,10 @@ Item {
|
||||
text: "•"
|
||||
}
|
||||
|
||||
Rectangle { // NSFW toggle
|
||||
Item { // NSFW toggle
|
||||
visible: width > 0
|
||||
implicitWidth: switchesRow.implicitWidth
|
||||
Layout.fillHeight: true
|
||||
|
||||
RowLayout {
|
||||
id: switchesRow
|
||||
|
||||
@@ -97,7 +97,7 @@ Scope { // Scope
|
||||
anchors.left: parent.left
|
||||
anchors.topMargin: Appearance.sizes.hyprlandGapsOut
|
||||
anchors.leftMargin: Appearance.sizes.hyprlandGapsOut
|
||||
width: sidebarRoot.sidebarWidth - Appearance.sizes.hyprlandGapsOut * 2
|
||||
width: sidebarRoot.sidebarWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin
|
||||
height: parent.height - Appearance.sizes.hyprlandGapsOut * 2
|
||||
color: Appearance.colors.colLayer0
|
||||
radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1
|
||||
|
||||
@@ -3,6 +3,7 @@ import "root:/services"
|
||||
import "root:/modules/common"
|
||||
import "root:/modules/common/widgets"
|
||||
import "root:/modules/common/functions/string_utils.js" as StringUtils
|
||||
import "./translator/"
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
@@ -15,11 +16,24 @@ import Quickshell.Hyprland
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
property var inputField: inputTextArea
|
||||
property var outputField: outputTextArea
|
||||
|
||||
// Widgets
|
||||
property var inputField: inputCanvas.inputTextArea
|
||||
// Widget variables
|
||||
property bool translationFor: false // Indicates if the translation is for an autocorrected text
|
||||
property string translatedText: ""
|
||||
property list<string> languages: []
|
||||
// Options
|
||||
property string targetLanguage: ConfigOptions.language.translator.targetLanguage
|
||||
property string sourceLanguage: ConfigOptions.language.translator.sourceLanguage
|
||||
property string hostLanguage: targetLanguage
|
||||
|
||||
property bool showLanguageSelector: false
|
||||
property bool languageSelectorTarget: false // true for target language, false for source language
|
||||
|
||||
function showLanguageSelectorDialog(isTargetLang: bool) {
|
||||
root.languageSelectorTarget = isTargetLang;
|
||||
root.showLanguageSelector = true
|
||||
}
|
||||
|
||||
onFocusChanged: (focus) => {
|
||||
if (focus) {
|
||||
@@ -32,19 +46,23 @@ Item {
|
||||
interval: ConfigOptions.sidebar.translator.delay
|
||||
repeat: false
|
||||
onTriggered: () => {
|
||||
if (inputTextArea.text.trim().length > 0) {
|
||||
if (root.inputField.text.trim().length > 0) {
|
||||
// console.log("Translating with command:", translateProc.command);
|
||||
translateProc.running = false;
|
||||
translateProc.buffer = ""; // Clear the buffer
|
||||
translateProc.running = true; // Restart the process
|
||||
} else {
|
||||
outputTextArea.text = "";
|
||||
root.translatedText = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: translateProc
|
||||
command: ["bash", "-c", `trans -no-theme -no-ansi '${StringUtils.shellSingleQuoteEscape(inputTextArea.text.trim())}'`]
|
||||
command: ["bash", "-c", `trans -no-theme`
|
||||
+ ` -source '${StringUtils.shellSingleQuoteEscape(root.sourceLanguage)}'`
|
||||
+ ` -target '${StringUtils.shellSingleQuoteEscape(root.targetLanguage)}'`
|
||||
+ ` -no-ansi '${StringUtils.shellSingleQuoteEscape(root.inputField.text.trim())}'`]
|
||||
property string buffer: ""
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
@@ -59,171 +77,172 @@ Item {
|
||||
|
||||
// 2. Extract relevant data
|
||||
root.translatedText = sections.length > 1 ? sections[1].trim() : "";
|
||||
root.outputField.text = root.translatedText;
|
||||
}
|
||||
}
|
||||
|
||||
Flickable {
|
||||
|
||||
Process {
|
||||
id: getLanguagesProc
|
||||
command: ["trans", "-list-languages"]
|
||||
property list<string> bufferList: ["auto"]
|
||||
running: true
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
getLanguagesProc.bufferList.push(data.trim());
|
||||
}
|
||||
}
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
// Ensure "auto" is always the first language
|
||||
let langs = getLanguagesProc.bufferList
|
||||
.filter(lang => lang.trim().length > 0 && lang !== "auto")
|
||||
.sort((a, b) => a.localeCompare(b));
|
||||
langs.unshift("auto");
|
||||
root.languages = langs;
|
||||
getLanguagesProc.bufferList = []; // Clear the buffer
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
contentHeight: contentColumn.implicitHeight
|
||||
Flickable {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
contentHeight: contentColumn.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: contentColumn
|
||||
anchors.fill: parent
|
||||
ColumnLayout {
|
||||
id: contentColumn
|
||||
anchors.fill: parent
|
||||
|
||||
Rectangle { // INPUT
|
||||
id: inputCanvas
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: Math.max(150, inputColumn.implicitHeight)
|
||||
color: Appearance.colors.colLayer1
|
||||
radius: Appearance.rounding.normal
|
||||
border.color: Appearance.m3colors.m3outlineVariant
|
||||
border.width: 1
|
||||
LanguageSelectorButton { // Target language button
|
||||
id: targetLanguageButton
|
||||
displayText: root.targetLanguage
|
||||
onClicked: {
|
||||
root.showLanguageSelectorDialog(true);
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: inputColumn
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
StyledTextArea { // Input area
|
||||
id: inputTextArea
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("Enter text to translate...")
|
||||
wrapMode: TextEdit.Wrap
|
||||
textFormat: TextEdit.PlainText
|
||||
font.pixelSize: Appearance.font.pixelSize.small
|
||||
color: Appearance.colors.colOnLayer1
|
||||
padding: 15
|
||||
background: null
|
||||
onTextChanged: {
|
||||
if (inputTextArea.text.trim().length > 0) {
|
||||
translateTimer.restart();
|
||||
} else {
|
||||
outputTextArea.text = "";
|
||||
}
|
||||
TextCanvas { // Content translation
|
||||
id: outputCanvas
|
||||
isInput: false
|
||||
placeholderText: qsTr("Translation goes here...")
|
||||
property bool hasTranslation: (root.translatedText.trim().length > 0)
|
||||
text: hasTranslation ? root.translatedText : ""
|
||||
GroupButton {
|
||||
id: copyButton
|
||||
baseWidth: height
|
||||
buttonRadius: Appearance.rounding.small
|
||||
enabled: outputCanvas.displayedText.trim().length > 0
|
||||
contentItem: MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
text: "content_copy"
|
||||
color: copyButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
|
||||
}
|
||||
onClicked: {
|
||||
Quickshell.clipboardText = outputCanvas.displayedText
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true }
|
||||
|
||||
RowLayout { // Status row
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 10
|
||||
spacing: 10
|
||||
|
||||
Text {
|
||||
Layout.leftMargin: 10
|
||||
text: qsTr("%1 characters").arg(inputTextArea.text.length)
|
||||
color: Appearance.colors.colOnLayer1
|
||||
font.pixelSize: Appearance.font.pixelSize.smaller
|
||||
GroupButton {
|
||||
id: searchButton
|
||||
baseWidth: height
|
||||
buttonRadius: Appearance.rounding.small
|
||||
enabled: outputCanvas.displayedText.trim().length > 0
|
||||
contentItem: MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
text: "travel_explore"
|
||||
color: searchButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
ButtonGroup {
|
||||
GroupButton {
|
||||
id: pasteButton
|
||||
baseWidth: height
|
||||
buttonRadius: Appearance.rounding.small
|
||||
contentItem: MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
text: "content_paste"
|
||||
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
|
||||
}
|
||||
onClicked: {
|
||||
root.inputField.text = Quickshell.clipboardText
|
||||
}
|
||||
}
|
||||
GroupButton {
|
||||
id: deleteButton
|
||||
baseWidth: height
|
||||
buttonRadius: Appearance.rounding.small
|
||||
enabled: inputTextArea.text.length > 0
|
||||
contentItem: MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
text: "close"
|
||||
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
|
||||
}
|
||||
onClicked: {
|
||||
root.inputField.text = ""
|
||||
}
|
||||
onClicked: {
|
||||
let url = ConfigOptions.search.engineBaseUrl + outputCanvas.displayedText;
|
||||
for (let site of ConfigOptions.search.excludedSites) {
|
||||
url += ` -site:${site}`;
|
||||
}
|
||||
Qt.openUrlExternally(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
LanguageSelectorButton { // Source language button
|
||||
id: sourceLanguageButton
|
||||
displayText: root.sourceLanguage
|
||||
onClicked: {
|
||||
root.showLanguageSelectorDialog(false);
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { // OUTPUT
|
||||
id: outputCanvas
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: Math.max(150, outputColumn.implicitHeight)
|
||||
color: Appearance.m3colors.m3surfaceContainer
|
||||
radius: Appearance.rounding.normal
|
||||
|
||||
ColumnLayout { // Output column
|
||||
id: outputColumn
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
StyledText { // Output area
|
||||
id: outputTextArea
|
||||
Layout.fillWidth: true
|
||||
property bool hasTranslation: (root.translatedText.trim().length > 0)
|
||||
wrapMode: TextEdit.Wrap
|
||||
font.pixelSize: Appearance.font.pixelSize.small
|
||||
color: hasTranslation ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
|
||||
padding: 15
|
||||
text: hasTranslation ? root.translatedText : ""
|
||||
}
|
||||
Item { Layout.fillHeight: true }
|
||||
RowLayout { // Status row
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 10
|
||||
spacing: 10
|
||||
Item { Layout.fillWidth: true }
|
||||
ButtonGroup {
|
||||
GroupButton {
|
||||
id: copyButton
|
||||
baseWidth: height
|
||||
buttonRadius: Appearance.rounding.small
|
||||
enabled: root.outputField.text.trim().length > 0
|
||||
contentItem: MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
text: "content_copy"
|
||||
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
|
||||
}
|
||||
onClicked: {
|
||||
Quickshell.clipboardText = root.outputField.text
|
||||
}
|
||||
}
|
||||
GroupButton {
|
||||
id: searchButton
|
||||
baseWidth: height
|
||||
buttonRadius: Appearance.rounding.small
|
||||
enabled: root.outputField.text.trim().length > 0
|
||||
contentItem: MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
text: "travel_explore"
|
||||
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
|
||||
}
|
||||
onClicked: {
|
||||
let url = ConfigOptions.search.engineBaseUrl + root.outputField.text;
|
||||
for (let site of ConfigOptions.search.excludedSites) {
|
||||
url += ` -site:${site}`;
|
||||
}
|
||||
Qt.openUrlExternally(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
TextCanvas { // Content input
|
||||
id: inputCanvas
|
||||
isInput: true
|
||||
placeholderText: qsTr("Enter text to translate...")
|
||||
onInputTextChanged: {
|
||||
translateTimer.restart();
|
||||
}
|
||||
GroupButton {
|
||||
id: pasteButton
|
||||
baseWidth: height
|
||||
buttonRadius: Appearance.rounding.small
|
||||
contentItem: MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
text: "content_paste"
|
||||
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
|
||||
}
|
||||
onClicked: {
|
||||
root.inputField.text = Quickshell.clipboardText
|
||||
}
|
||||
}
|
||||
}
|
||||
GroupButton {
|
||||
id: deleteButton
|
||||
baseWidth: height
|
||||
buttonRadius: Appearance.rounding.small
|
||||
enabled: inputCanvas.inputTextArea.text.length > 0
|
||||
contentItem: MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
iconSize: Appearance.font.pixelSize.larger
|
||||
text: "close"
|
||||
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
|
||||
}
|
||||
onClicked: {
|
||||
root.inputField.text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
active: root.showLanguageSelector
|
||||
visible: root.showLanguageSelector
|
||||
z: 9999
|
||||
sourceComponent: SelectionDialog {
|
||||
id: languageSelectorDialog
|
||||
titleText: qsTr("Select Language")
|
||||
items: root.languages
|
||||
defaultChoice: root.languageSelectorTarget ? root.targetLanguage : root.sourceLanguage
|
||||
onCanceled: () => {
|
||||
root.showLanguageSelector = false;
|
||||
}
|
||||
onSelected: (result) => {
|
||||
root.showLanguageSelector = false;
|
||||
if (!result || result.length === 0) return; // No selection made
|
||||
|
||||
if (root.languageSelectorTarget) {
|
||||
root.targetLanguage = result;
|
||||
ConfigLoader.setConfigValueAndSave("language.translator.targetLanguage", result); // Save to config
|
||||
} else {
|
||||
root.sourceLanguage = result;
|
||||
ConfigLoader.setConfigValueAndSave("language.translator.sourceLanguage", result); // Save to config
|
||||
}
|
||||
|
||||
translateTimer.restart(); // Restart translation after language change
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ Rectangle {
|
||||
property bool renderMarkdown: true
|
||||
property bool editing: false
|
||||
|
||||
property list<var> messageBlocks: StringUtils.splitMarkdownBlocks(root.messageData?.content)
|
||||
|
||||
anchors.left: parent?.left
|
||||
anchors.right: parent?.right
|
||||
implicitHeight: columnLayout.implicitHeight + root.messagePadding * 2
|
||||
@@ -89,7 +91,7 @@ Rectangle {
|
||||
|
||||
Rectangle { // Name
|
||||
id: nameWrapper
|
||||
color: Appearance.m3colors.m3secondaryContainer
|
||||
color: Appearance.colors.colSecondaryContainer
|
||||
// color: "transparent"
|
||||
radius: Appearance.rounding.small
|
||||
implicitHeight: Math.max(nameRowLayout.implicitHeight + 5 * 2, 30)
|
||||
@@ -246,23 +248,25 @@ Rectangle {
|
||||
spacing: 0
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: StringUtils.splitMarkdownBlocks(root.messageData?.content)
|
||||
values: root.messageBlocks.map((block, index) => index)
|
||||
}
|
||||
delegate: Loader {
|
||||
required property int index
|
||||
property var thisBlock: root.messageBlocks[index]
|
||||
Layout.fillWidth: true
|
||||
// property var segment: modelData
|
||||
property var segmentContent: modelData.content
|
||||
property var segmentLang: modelData.lang
|
||||
// property var segment: thisBlock
|
||||
property var segmentContent: thisBlock.content
|
||||
property var segmentLang: thisBlock.lang
|
||||
property var messageData: root.messageData
|
||||
property var editing: root.editing
|
||||
property var renderMarkdown: root.renderMarkdown
|
||||
property var enableMouseSelection: root.enableMouseSelection
|
||||
property bool thinking: root.messageData?.thinking ?? true
|
||||
property bool done: root.messageData?.done ?? false
|
||||
property bool completed: modelData.completed ?? false
|
||||
property bool completed: thisBlock.completed ?? false
|
||||
|
||||
source: modelData.type === "code" ? "MessageCodeBlock.qml" :
|
||||
modelData.type === "think" ? "MessageThinkBlock.qml" :
|
||||
source: thisBlock.type === "code" ? "MessageCodeBlock.qml" :
|
||||
thisBlock.type === "think" ? "MessageThinkBlock.qml" :
|
||||
"MessageTextBlock.qml"
|
||||
|
||||
}
|
||||
@@ -277,7 +281,9 @@ Rectangle {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
|
||||
Repeater {
|
||||
model: root.messageData?.annotationSources
|
||||
model: ScriptModel {
|
||||
values: root.messageData?.annotationSources || []
|
||||
}
|
||||
delegate: AnnotationSourceButton {
|
||||
id: annotationButton
|
||||
displayText: modelData.text
|
||||
|
||||
@@ -21,7 +21,7 @@ RippleButton {
|
||||
leftPadding: (implicitHeight - faviconSize) / 2
|
||||
rightPadding: 10
|
||||
buttonRadius: Appearance.rounding.full
|
||||
colBackground: Appearance.m3colors.m3surfaceContainerHighest
|
||||
colBackground: Appearance.colors.colSurfaceContainerHighest
|
||||
colBackgroundHover: Appearance.colors.colSurfaceContainerHighestHover
|
||||
colRipple: Appearance.colors.colSurfaceContainerHighestActive
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ ColumnLayout {
|
||||
topRightRadius: codeBlockBackgroundRounding
|
||||
bottomLeftRadius: Appearance.rounding.unsharpen
|
||||
bottomRightRadius: Appearance.rounding.unsharpen
|
||||
color: Appearance.m3colors.m3surfaceContainerHighest
|
||||
color: Appearance.colors.colSurfaceContainerHighest
|
||||
implicitHeight: codeBlockTitleBarRowLayout.implicitHeight + codeBlockHeaderPadding * 2
|
||||
|
||||
RowLayout { // Language and buttons
|
||||
@@ -97,7 +97,7 @@ ColumnLayout {
|
||||
onClicked: {
|
||||
const downloadPath = FileUtils.trimFileProtocol(Directories.downloads)
|
||||
Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(segmentContent)}' > '${downloadPath}/code.${segmentLang || "txt"}'`)
|
||||
Hyprland.dispatch(`exec notify-send 'Code saved to file' '${downloadPath}/code.${segmentLang || "txt"}'`)
|
||||
Hyprland.dispatch(`exec notify-send 'Code saved to file' '${downloadPath}/code.${segmentLang || "txt"}' -a Shell`)
|
||||
saveCodeButton.activated = true
|
||||
saveIconTimer.restart()
|
||||
}
|
||||
@@ -209,7 +209,7 @@ ColumnLayout {
|
||||
font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text
|
||||
font.pixelSize: Appearance.font.pixelSize.small
|
||||
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
|
||||
selectionColor: Appearance.m3colors.m3secondaryContainer
|
||||
selectionColor: Appearance.colors.colSecondaryContainer
|
||||
// wrapMode: TextEdit.Wrap
|
||||
color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1
|
||||
|
||||
|
||||
@@ -30,20 +30,32 @@ ColumnLayout {
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
Timer {
|
||||
id: renderTimer
|
||||
interval: 1000
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
renderLatex()
|
||||
for (const hash of renderedLatexHashes) {
|
||||
handleRenderedLatex(hash, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderLatex() {
|
||||
// Regex for $...$, $$...$$, \[...\]
|
||||
// Note: This is a simple approach and may need refinement for edge cases
|
||||
let regex = /(\$\$([\s\S]+?)\$\$)|(\$([^\$]+?)\$)|(\\\[((?:.|\n)+?)\\\])/g;
|
||||
let regex = /(\$\$([\s\S]+?)\$\$)|(\$([^\$]+?)\$)|(\\\[((?:.|\n)+?)\\\])|(\\\(([\s\S]+?)\\\))/g;
|
||||
let match;
|
||||
while ((match = regex.exec(segmentContent)) !== null) {
|
||||
let expression = match[1] || match[2] || match[3];
|
||||
let expression = match[1] || match[2] || match[3] || match[4] || match[5] || match[6] || match[7] || match[8];
|
||||
if (expression) {
|
||||
// Qt.callLater(() => {
|
||||
// });
|
||||
Qt.callLater(() => {
|
||||
const [renderHash, isNew] = LatexRenderer.requestRender(expression.trim());
|
||||
if (!renderedLatexHashes.includes(renderHash)) {
|
||||
renderedLatexHashes.push(renderHash);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,16 +65,13 @@ ColumnLayout {
|
||||
const imagePath = LatexRenderer.renderedImagePaths[hash];
|
||||
const markdownImage = ``;
|
||||
|
||||
const expression = StringUtils.escapeBackslashes(LatexRenderer.processedExpressions[hash]);
|
||||
const expression = LatexRenderer.processedExpressions[hash];
|
||||
renderedSegmentContent = renderedSegmentContent.replace(expression, markdownImage);
|
||||
}
|
||||
}
|
||||
|
||||
onDoneChanged: {
|
||||
renderLatex()
|
||||
for (const hash of renderedLatexHashes) {
|
||||
handleRenderedLatex(hash, true);
|
||||
}
|
||||
renderTimer.restart();
|
||||
}
|
||||
onEditingChanged: {
|
||||
if (!editing) {
|
||||
@@ -111,7 +120,7 @@ ColumnLayout {
|
||||
font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text
|
||||
font.pixelSize: Appearance.font.pixelSize.small
|
||||
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
|
||||
selectionColor: Appearance.m3colors.m3secondaryContainer
|
||||
selectionColor: Appearance.colors.colSecondaryContainer
|
||||
wrapMode: TextEdit.Wrap
|
||||
color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1
|
||||
textFormat: renderMarkdown ? TextEdit.MarkdownText : TextEdit.PlainText
|
||||
|
||||
@@ -64,7 +64,7 @@ Item {
|
||||
|
||||
Rectangle { // Header background
|
||||
id: header
|
||||
color: Appearance.m3colors.m3surfaceContainerHighest
|
||||
color: Appearance.colors.colSurfaceContainerHighest
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: thinkBlockTitleBarRowLayout.implicitHeight + thinkBlockHeaderPaddingVertical * 2
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ Button {
|
||||
opacity: root.showActions ? 1 : 0
|
||||
visible: opacity > 0
|
||||
radius: Appearance.rounding.small
|
||||
color: Appearance.m3colors.m3surfaceContainer
|
||||
color: Appearance.colors.colSurfaceContainer
|
||||
implicitHeight: contextMenuColumnLayout.implicitHeight + radius * 2
|
||||
implicitWidth: contextMenuColumnLayout.implicitWidth
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ Rectangle {
|
||||
RowLayout { // Header
|
||||
Rectangle { // Provider name
|
||||
id: providerNameWrapper
|
||||
color: Appearance.m3colors.m3secondaryContainer
|
||||
color: Appearance.colors.colSecondaryContainer
|
||||
radius: Appearance.rounding.small
|
||||
implicitWidth: providerName.implicitWidth + 10 * 2
|
||||
implicitHeight: Math.max(providerName.implicitHeight + 5 * 2, 30)
|
||||
@@ -269,7 +269,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
buttonRadius: Appearance.rounding.small
|
||||
colBackground: Appearance.m3colors.m3surfaceContainerHighest
|
||||
colBackground: Appearance.colors.colSurfaceContainerHighest
|
||||
colBackgroundHover: Appearance.colors.colSurfaceContainerHighestHover
|
||||
colRipple: Appearance.colors.colSurfaceContainerHighestActive
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import "root:/"
|
||||
import "root:/services"
|
||||
import "root:/modules/common"
|
||||
import "root:/modules/common/widgets"
|
||||
import "root:/modules/common/functions/string_utils.js" as StringUtils
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Hyprland
|
||||
|
||||
RippleButton {
|
||||
id: root
|
||||
property string displayText: ""
|
||||
colBackground: Appearance.colors.colLayer2
|
||||
|
||||
implicitWidth: contentItem.implicitWidth + horizontalPadding * 2
|
||||
implicitHeight: contentItem.implicitHeight + verticalPadding * 2
|
||||
|
||||
contentItem: Item {
|
||||
anchors.centerIn: parent
|
||||
implicitWidth: languageRow.implicitWidth
|
||||
implicitHeight: languageText.implicitHeight
|
||||
RowLayout {
|
||||
id: languageRow
|
||||
anchors.centerIn: parent
|
||||
spacing: 0
|
||||
StyledText {
|
||||
id: languageText
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: 5
|
||||
text: root.displayText
|
||||
color: Appearance.colors.colOnLayer2
|
||||
font.pixelSize: Appearance.font.pixelSize.small
|
||||
}
|
||||
MaterialSymbol {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
iconSize: Appearance.font.pixelSize.hugeass
|
||||
text: "arrow_drop_down"
|
||||
color: Appearance.colors.colOnLayer2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import "root:/"
|
||||
import "root:/services"
|
||||
import "root:/modules/common"
|
||||
import "root:/modules/common/widgets"
|
||||
import "root:/modules/common/functions/string_utils.js" as StringUtils
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Hyprland
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
property bool isInput: true // true for input, false for output
|
||||
property string placeholderText
|
||||
property string text: ""
|
||||
property var inputTextArea: isInput ? inputLoader.item : undefined
|
||||
readonly property string displayedText: isInput ? inputLoader.item.text :
|
||||
root.text.length > 0 ? outputLoader.item.text : ""
|
||||
default property alias actionButtons: actions.data
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: Math.max(150, inputColumn.implicitHeight)
|
||||
color: isInput ? Appearance.colors.colLayer1 : Appearance.colors.colSurfaceContainer
|
||||
radius: Appearance.rounding.normal
|
||||
border.color: isInput ? Appearance.colors.colOutlineVariant : "transparent"
|
||||
border.width: isInput ? 1 : 0
|
||||
|
||||
signal inputTextChanged(); // Signal emitted when text changes
|
||||
|
||||
ColumnLayout {
|
||||
id: inputColumn
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
Loader {
|
||||
id: inputLoader
|
||||
active: root.isInput
|
||||
visible: root.isInput
|
||||
Layout.fillWidth: true
|
||||
sourceComponent: StyledTextArea { // Input area
|
||||
id: inputTextArea
|
||||
placeholderText: root.placeholderText
|
||||
wrapMode: TextEdit.Wrap
|
||||
textFormat: TextEdit.PlainText
|
||||
font.pixelSize: Appearance.font.pixelSize.small
|
||||
color: Appearance.colors.colOnLayer1
|
||||
padding: 15
|
||||
background: null
|
||||
onTextChanged: root.inputTextChanged()
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: outputLoader
|
||||
active: !root.isInput
|
||||
visible: !root.isInput
|
||||
Layout.fillWidth: true
|
||||
sourceComponent: StyledText { // Output area
|
||||
id: outputTextArea
|
||||
padding: 15
|
||||
wrapMode: Text.Wrap
|
||||
font.pixelSize: Appearance.font.pixelSize.small
|
||||
color: root.text.length > 0 ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
|
||||
text: root.text.length > 0 ? root.text : root.placeholderText
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true }
|
||||
|
||||
RowLayout { // Status row
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 10
|
||||
spacing: 10
|
||||
|
||||
Loader {
|
||||
active: root.isInput
|
||||
visible: root.isInput
|
||||
Layout.leftMargin: 10
|
||||
sourceComponent: Text {
|
||||
text: qsTr("%1 characters").arg(inputLoader.item.text.length)
|
||||
color: Appearance.colors.colOnLayer1
|
||||
font.pixelSize: Appearance.font.pixelSize.smaller
|
||||
}
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
ButtonGroup {
|
||||
id: actions
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,8 +52,17 @@ Scope {
|
||||
Loader {
|
||||
id: sidebarContentLoader
|
||||
active: GlobalStates.sidebarRightOpen
|
||||
anchors.centerIn: parent
|
||||
width: sidebarWidth - Appearance.sizes.hyprlandGapsOut * 2
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
right: parent.right
|
||||
left: parent.left
|
||||
topMargin: Appearance.sizes.hyprlandGapsOut
|
||||
rightMargin: Appearance.sizes.hyprlandGapsOut
|
||||
bottomMargin: Appearance.sizes.hyprlandGapsOut
|
||||
leftMargin: Appearance.sizes.elevationMargin
|
||||
}
|
||||
width: sidebarWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin
|
||||
height: parent.height - Appearance.sizes.hyprlandGapsOut * 2
|
||||
|
||||
sourceComponent: Item {
|
||||
@@ -118,14 +127,38 @@ Scope {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
QuickToggleButton {
|
||||
toggled: false
|
||||
buttonIcon: "power_settings_new"
|
||||
onClicked: {
|
||||
Hyprland.dispatch("global quickshell:sessionOpen")
|
||||
ButtonGroup {
|
||||
QuickToggleButton {
|
||||
toggled: false
|
||||
buttonIcon: "restart_alt"
|
||||
onClicked: {
|
||||
Hyprland.dispatch("reload")
|
||||
Quickshell.reload(true)
|
||||
}
|
||||
StyledToolTip {
|
||||
content: qsTr("Reload Hyprland & Quickshell")
|
||||
}
|
||||
}
|
||||
StyledToolTip {
|
||||
content: qsTr("Session")
|
||||
QuickToggleButton {
|
||||
toggled: false
|
||||
buttonIcon: "settings"
|
||||
onClicked: {
|
||||
Hyprland.dispatch(`exec ${ConfigOptions.apps.settings}`)
|
||||
Hyprland.dispatch(`global quickshell:sidebarRightClose`)
|
||||
}
|
||||
StyledToolTip {
|
||||
content: qsTr("Plasma Settings")
|
||||
}
|
||||
}
|
||||
QuickToggleButton {
|
||||
toggled: false
|
||||
buttonIcon: "power_settings_new"
|
||||
onClicked: {
|
||||
Hyprland.dispatch("global quickshell:sessionOpen")
|
||||
}
|
||||
StyledToolTip {
|
||||
content: qsTr("Session")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ RippleButton {
|
||||
font.weight: bold ? Font.DemiBold : Font.Normal
|
||||
color: (isToday == 1) ? Appearance.m3colors.m3onPrimary :
|
||||
(isToday == 0) ? Appearance.colors.colOnLayer1 :
|
||||
Appearance.m3colors.m3outlineVariant
|
||||
Appearance.colors.colOutlineVariant
|
||||
|
||||
Behavior on color {
|
||||
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||
|
||||
@@ -3,16 +3,28 @@ import "root:/modules/common/widgets"
|
||||
import "../"
|
||||
import Quickshell.Io
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
|
||||
QuickToggleButton {
|
||||
toggled: idleInhibitor.running
|
||||
id: root
|
||||
toggled: false
|
||||
buttonIcon: "coffee"
|
||||
onClicked: {
|
||||
idleInhibitor.running = !idleInhibitor.running
|
||||
if (toggled) {
|
||||
root.toggled = false
|
||||
Hyprland.dispatch("exec pkill wayland-idle") // pkill doesn't accept too long names
|
||||
} else {
|
||||
root.toggled = true
|
||||
Hyprland.dispatch('exec ${XDG_CONFIG_HOME:-$HOME/.config}/quickshell/scripts/wayland-idle-inhibitor.py')
|
||||
}
|
||||
}
|
||||
Process {
|
||||
id: idleInhibitor
|
||||
command: ["bash", "-c", "${XDG_CONFIG_HOME:-$HOME/.config}/quickshell/scripts/wayland-idle-inhibitor.py"]
|
||||
id: fetchActiveState
|
||||
running: true
|
||||
command: ["bash", "-c", "pidof wayland-idle-inhibitor.py"]
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
root.toggled = exitCode === 0
|
||||
}
|
||||
}
|
||||
StyledToolTip {
|
||||
content: qsTr("Keep system awake")
|
||||
|
||||
@@ -15,7 +15,7 @@ QuickToggleButton {
|
||||
toggleNetwork.running = true
|
||||
}
|
||||
altAction: () => {
|
||||
Hyprland.dispatch(`exec ${ConfigOptions.apps.network}`)
|
||||
Hyprland.dispatch(`exec ${Network.ethernet ? ConfigOptions.apps.networkEthernet : ConfigOptions.apps.network}`)
|
||||
Hyprland.dispatch("global quickshell:sidebarRightClose")
|
||||
}
|
||||
Process {
|
||||
|
||||
@@ -114,7 +114,7 @@ Item {
|
||||
id: tabBarBottomBorder
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: Appearance.m3colors.m3outlineVariant
|
||||
color: Appearance.colors.colOutlineVariant
|
||||
}
|
||||
|
||||
SwipeView {
|
||||
@@ -228,7 +228,7 @@ Item {
|
||||
anchors.margins: root.dialogMargins
|
||||
implicitHeight: dialogColumnLayout.implicitHeight
|
||||
|
||||
color: Appearance.m3colors.m3surfaceContainerHigh
|
||||
color: Appearance.colors.colSurfaceContainerHigh
|
||||
radius: Appearance.rounding.normal
|
||||
|
||||
function addTask() {
|
||||
@@ -264,7 +264,7 @@ Item {
|
||||
color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant
|
||||
renderType: Text.NativeRendering
|
||||
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
|
||||
selectionColor: Appearance.m3colors.m3secondaryContainer
|
||||
selectionColor: Appearance.colors.colSecondaryContainer
|
||||
placeholderText: qsTr("Task description")
|
||||
placeholderTextColor: Appearance.m3colors.m3outline
|
||||
focus: root.showAddDialog
|
||||
|
||||
@@ -5,6 +5,7 @@ import Qt5Compat.GraphicalEffects
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Services.Pipewire
|
||||
|
||||
@@ -16,7 +17,7 @@ Item {
|
||||
property int dialogMargins: 16
|
||||
property PwNode selectedDevice
|
||||
|
||||
function showDeviceSelectorDialog(input) {
|
||||
function showDeviceSelectorDialog(input: bool) {
|
||||
root.selectedDevice = null
|
||||
root.showDeviceSelector = true
|
||||
root.deviceSelectorInput = input
|
||||
@@ -160,7 +161,7 @@ Item {
|
||||
|
||||
Rectangle { // The dialog
|
||||
id: dialog
|
||||
color: Appearance.m3colors.m3surfaceContainerHigh
|
||||
color: Appearance.colors.colSurfaceContainerHigh
|
||||
radius: Appearance.rounding.normal
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@@ -207,9 +208,11 @@ Item {
|
||||
spacing: 0
|
||||
|
||||
Repeater {
|
||||
model: Pipewire.nodes.values.filter(node => {
|
||||
return !node.isStream && node.isSink !== root.deviceSelectorInput && node.audio
|
||||
})
|
||||
model: ScriptModel {
|
||||
values: Pipewire.nodes.values.filter(node => {
|
||||
return !node.isStream && node.isSink !== root.deviceSelectorInput && node.audio
|
||||
})
|
||||
}
|
||||
|
||||
// This could and should be refractored, but all data becomes null when passed wtf
|
||||
delegate: StyledRadioButton {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
[general]
|
||||
mode = waves
|
||||
framerate = 60
|
||||
autosens = 1
|
||||
bars = 50
|
||||
|
||||
[output]
|
||||
method = raw
|
||||
raw_target = /dev/stdout
|
||||
data_format = ascii
|
||||
channels = mono
|
||||
mono_option = average
|
||||
|
||||
[smoothing]
|
||||
noise_reduction = 20
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ CONFIG_DIR="$XDG_CONFIG_HOME/quickshell"
|
||||
CACHE_DIR="$XDG_CACHE_HOME/quickshell"
|
||||
STATE_DIR="$XDG_STATE_HOME/quickshell"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
MATUGEN_DIR="$XDG_CONFIG_HOME/matugen"
|
||||
terminalscheme="$XDG_CONFIG_HOME/quickshell/scripts/terminal/scheme-base.json"
|
||||
|
||||
pre_process() {
|
||||
@@ -26,7 +27,19 @@ pre_process() {
|
||||
}
|
||||
|
||||
post_process() {
|
||||
true
|
||||
local screen_width="$1"
|
||||
local screen_height="$2"
|
||||
local wallpaper_path="$3"
|
||||
|
||||
# Determine the largest region on the wallpaper that's sufficiently un-busy to put widgets in
|
||||
if [ ! -f "$MATUGEN_DIR/scripts/least_busy_region.py" ]; then
|
||||
echo "Error: least_busy_region.py script not found in $MATUGEN_DIR/scripts/"
|
||||
else
|
||||
"$MATUGEN_DIR/scripts/least_busy_region.py" \
|
||||
--screen-width "$screen_width" --screen-height "$screen_height" \
|
||||
--width 300 --height 200 \
|
||||
"$wallpaper_path" > "$STATE_DIR"/user/generated/wallpaper/least_busy_region.json
|
||||
fi
|
||||
}
|
||||
|
||||
check_and_prompt_upscale() {
|
||||
@@ -219,7 +232,10 @@ switch() {
|
||||
"$SCRIPT_DIR"/applycolor.sh
|
||||
deactivate
|
||||
|
||||
post_process
|
||||
# Pass screen width, height, and wallpaper path to post_process
|
||||
max_width_desired="$(hyprctl monitors -j | jq '([.[].width] | min)' | xargs)"
|
||||
max_height_desired="$(hyprctl monitors -j | jq '([.[].height] | min)' | xargs)"
|
||||
post_process "$max_width_desired" "$max_height_desired" "$imgpath"
|
||||
}
|
||||
|
||||
main() {
|
||||
@@ -267,10 +283,10 @@ main() {
|
||||
# Only prompt for wallpaper if not using --color and not using --noswitch and no imgpath set
|
||||
if [[ -z "$imgpath" && -z "$color_flag" && -z "$noswitch_flag" ]]; then
|
||||
cd "$(xdg-user-dir PICTURES)/Wallpapers" 2>/dev/null || cd "$(xdg-user-dir PICTURES)" || return 1
|
||||
imgpath="$(yad --width 1200 --height 800 --file --add-preview --large-preview --title='Choose wallpaper')"
|
||||
imgpath="$(kdialog --getopenfilename . --title 'Choose wallpaper')"
|
||||
fi
|
||||
|
||||
switch "$imgpath" "$mode_flag" "$type_flag" "$color_flag" "$color"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
main "$@"
|
||||
|
||||
@@ -430,8 +430,7 @@ Singleton {
|
||||
"parts": [{ text: root.systemPrompt }]
|
||||
},
|
||||
"generationConfig": {
|
||||
"temperature": root.temperature,
|
||||
"responseMimeType": "text/plain",
|
||||
// "temperature": root.temperature,
|
||||
},
|
||||
};
|
||||
return model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData;
|
||||
@@ -450,7 +449,7 @@ Singleton {
|
||||
}),
|
||||
],
|
||||
"stream": true,
|
||||
"temperature": root.temperature,
|
||||
// "temperature": root.temperature,
|
||||
};
|
||||
return model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "root:/modules/common"
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Services.Pipewire
|
||||
@@ -11,11 +12,40 @@ Singleton {
|
||||
id: root
|
||||
|
||||
property bool ready: Pipewire.defaultAudioSink?.ready ?? false
|
||||
property var sink: Pipewire.defaultAudioSink
|
||||
property var source: Pipewire.defaultAudioSource
|
||||
property PwNode sink: Pipewire.defaultAudioSink
|
||||
property PwNode source: Pipewire.defaultAudioSource
|
||||
|
||||
signal sinkProtectionTriggered(string reason);
|
||||
|
||||
PwObjectTracker {
|
||||
objects: [sink, source]
|
||||
}
|
||||
|
||||
Connections { // Protection against sudden volume changes
|
||||
target: sink?.audio ?? null
|
||||
property bool lastReady: false
|
||||
property real lastVolume: 0
|
||||
function onVolumeChanged() {
|
||||
if (!ConfigOptions.audio.protection.enable) return;
|
||||
if (!lastReady) {
|
||||
lastVolume = sink.audio.volume;
|
||||
lastReady = true;
|
||||
return;
|
||||
}
|
||||
const newVolume = sink.audio.volume;
|
||||
const maxAllowedIncrease = ConfigOptions.audio.protection.maxAllowedIncrease / 100;
|
||||
const maxAllowed = ConfigOptions.audio.protection.maxAllowed / 100;
|
||||
|
||||
if (newVolume - lastVolume > maxAllowedIncrease) {
|
||||
sink.audio.volume = lastVolume;
|
||||
root.sinkProtectionTriggered("Illegal increment");
|
||||
} else if (newVolume > maxAllowed) {
|
||||
sink.audio.volume = lastVolume;
|
||||
root.sinkProtectionTriggered("Exceeded max allowed");
|
||||
}
|
||||
lastVolume = sink.audio.volume;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,12 +14,14 @@ import Qt.labs.platform
|
||||
/**
|
||||
* Loads and manages the shell configuration file.
|
||||
* The config file is by default at XDG_CONFIG_HOME/illogical-impulse/config.json.
|
||||
* Automatically reloaded when the file changes, but does not provide a way to save changes.
|
||||
* Automatically reloaded when the file changes.
|
||||
*/
|
||||
Singleton {
|
||||
id: root
|
||||
property string filePath: Directories.shellConfigPath
|
||||
property bool firstLoad: true
|
||||
property bool preventNextLoad: false
|
||||
property var preventNextNotification: false
|
||||
|
||||
function loadConfig() {
|
||||
configFileView.reload()
|
||||
@@ -27,16 +29,21 @@ Singleton {
|
||||
|
||||
function applyConfig(fileContent) {
|
||||
try {
|
||||
if (fileContent.trim() === "") {
|
||||
console.warn("[ConfigLoader] Config file is empty, skipping load.");
|
||||
return;
|
||||
}
|
||||
const json = JSON.parse(fileContent);
|
||||
|
||||
ObjectUtils.applyToQtObject(ConfigOptions, json);
|
||||
if (root.firstLoad) {
|
||||
root.firstLoad = false;
|
||||
} else {
|
||||
Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration reloaded")}" "${root.filePath}"`)
|
||||
root.preventNextLoad = true;
|
||||
root.saveConfig(); // Make sure new properties are added to the user's config file
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[ConfigLoader] Error reading file:", e);
|
||||
console.log("[ConfigLoader] File content was:", fileContent);
|
||||
Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration failed to load")}" "${root.filePath}"`)
|
||||
return;
|
||||
|
||||
@@ -70,8 +77,6 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(parents.join("."));
|
||||
console.log(`[ConfigLoader] Setting live config value: ${nestedKey} = ${convertedValue}`);
|
||||
obj[keys[keys.length - 1]] = convertedValue;
|
||||
}
|
||||
|
||||
@@ -80,13 +85,31 @@ Singleton {
|
||||
Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(JSON.stringify(plainConfig, null, 2))}' > '${root.filePath}'`)
|
||||
}
|
||||
|
||||
function setConfigValueAndSave(nestedKey, value, preventNextNotification = true) {
|
||||
setLiveConfigValue(nestedKey, value);
|
||||
root.preventNextNotification = preventNextNotification;
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: delayedFileRead
|
||||
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay
|
||||
repeat: false
|
||||
running: false
|
||||
onTriggered: {
|
||||
root.applyConfig(configFileView.text())
|
||||
if (root.preventNextLoad) {
|
||||
root.preventNextLoad = false;
|
||||
return;
|
||||
}
|
||||
if (root.firstLoad) {
|
||||
root.applyConfig(configFileView.text())
|
||||
} else {
|
||||
root.applyConfig(configFileView.text())
|
||||
if (!root.preventNextNotification) {
|
||||
// Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration reloaded")}" "${root.filePath}"`)
|
||||
} else {
|
||||
root.preventNextNotification = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,13 +118,12 @@ Singleton {
|
||||
path: Qt.resolvedUrl(root.filePath)
|
||||
watchChanges: true
|
||||
onFileChanged: {
|
||||
console.log("[ConfigLoader] File changed, reloading...")
|
||||
this.reload()
|
||||
delayedFileRead.start()
|
||||
}
|
||||
onLoadedChanged: {
|
||||
const fileContent = configFileView.text()
|
||||
root.applyConfig(fileContent)
|
||||
delayedFileRead.start()
|
||||
}
|
||||
onLoadFailed: (error) => {
|
||||
if(error == FileViewError.FileNotFound) {
|
||||
|
||||
@@ -25,7 +25,8 @@ Singleton {
|
||||
property list<string> processedHashes: []
|
||||
property var processedExpressions: ({})
|
||||
property var renderedImagePaths: ({})
|
||||
property string microtexBinaryPath: Qt.resolvedUrl("/opt/MicroTeX/LaTeX")
|
||||
property string microtexBinaryDir: "/opt/MicroTeX"
|
||||
property string microtexBinaryName: "LaTeX"
|
||||
property string latexOutputPath: Directories.latexOutput
|
||||
|
||||
signal renderFinished(string hash, string imagePath)
|
||||
@@ -51,23 +52,28 @@ Singleton {
|
||||
}
|
||||
|
||||
// 3. If not, render it with MicroTeX and mark as processed
|
||||
// console.log(`[LatexRenderer] Rendering expression: ${expression} with hash: ${hash}`)
|
||||
// console.log(` to file: ${imagePath}`)
|
||||
// console.log(` with command: cd ${microtexBinaryDir} && ./${microtexBinaryName} -headless -input=${StringUtils.shellSingleQuoteEscape(expression)} -output=${imagePath} -textsize=${Appearance.font.pixelSize.normal} -padding=${renderPadding} -background=${Appearance.m3colors.m3tertiary} -foreground=${Appearance.m3colors.m3onTertiary} -maxwidth=0.85`)
|
||||
const processQml = `
|
||||
import Quickshell.Io
|
||||
Process {
|
||||
id: microtexProcess${hash}
|
||||
running: true
|
||||
command: [ "${microtexBinaryPath}", "-headless",
|
||||
"-input=${StringUtils.escapeBackslashes(expression)}",
|
||||
"-output=${imagePath}",
|
||||
"-textsize=${Appearance.font.pixelSize.normal}",
|
||||
"-padding=${renderPadding}",
|
||||
"-background=${Appearance.m3colors.m3tertiary}",
|
||||
"-foreground=${Appearance.m3colors.m3onTertiary}",
|
||||
"-maxwidth=0.85" ]
|
||||
command: [ "bash", "-c",
|
||||
"cd ${root.microtexBinaryDir} && ./${root.microtexBinaryName} -headless '-input=${StringUtils.shellSingleQuoteEscape(StringUtils.escapeBackslashes(expression))}' "
|
||||
+ "'-output=${imagePath}' "
|
||||
+ "'-textsize=${Appearance.font.pixelSize.normal}' "
|
||||
+ "'-padding=${renderPadding}' "
|
||||
// + "'-background=${Appearance.m3colors.m3tertiary}' "
|
||||
+ "'-foreground=${Appearance.colors.colOnLayer1}' "
|
||||
+ "-maxwidth=0.85 "
|
||||
]
|
||||
// stdout: SplitParser {
|
||||
// onRead: data => { console.log("MicroTeX: " + data) }
|
||||
// }
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
// console.log("[LatexRenderer] MicroTeX process exited with code: " + exitCode + ", status: " + exitStatus)
|
||||
renderedImagePaths["${hash}"] = "${imagePath}"
|
||||
root.renderFinished("${hash}", "${imagePath}")
|
||||
microtexProcess${hash}.destroy()
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
//@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic
|
||||
|
||||
import "./modules/common/"
|
||||
import "./modules/backgroundWidgets/"
|
||||
import "./modules/bar/"
|
||||
import "./modules/cheatsheet/"
|
||||
import "./modules/dock/"
|
||||
@@ -26,6 +27,7 @@ ShellRoot {
|
||||
// Enable/disable modules here. False = not loaded at all, so rest assured
|
||||
// no unnecessary stuff will take up memory if you decide to only use, say, the overview.
|
||||
property bool enableBar: true
|
||||
property bool enableBackgroundWidgets: true
|
||||
property bool enableCheatsheet: true
|
||||
property bool enableDock: false
|
||||
property bool enableMediaControls: true
|
||||
@@ -49,19 +51,20 @@ ShellRoot {
|
||||
FirstRunExperience.load()
|
||||
}
|
||||
|
||||
Loader { active: enableBar; sourceComponent: Bar {} }
|
||||
Loader { active: enableCheatsheet; sourceComponent: Cheatsheet {} }
|
||||
Loader { active: (enableDock || ConfigOptions?.dock.enable); sourceComponent: Dock {} }
|
||||
Loader { active: enableMediaControls; sourceComponent: MediaControls {} }
|
||||
Loader { active: enableNotificationPopup; sourceComponent: NotificationPopup {} }
|
||||
Loader { active: enableOnScreenDisplayBrightness; sourceComponent: OnScreenDisplayBrightness {} }
|
||||
Loader { active: enableOnScreenDisplayVolume; sourceComponent: OnScreenDisplayVolume {} }
|
||||
Loader { active: enableOnScreenKeyboard; sourceComponent: OnScreenKeyboard {} }
|
||||
Loader { active: enableOverview; sourceComponent: Overview {} }
|
||||
Loader { active: enableReloadPopup; sourceComponent: ReloadPopup {} }
|
||||
Loader { active: enableScreenCorners; sourceComponent: ScreenCorners {} }
|
||||
Loader { active: enableSession; sourceComponent: Session {} }
|
||||
Loader { active: enableSidebarLeft; sourceComponent: SidebarLeft {} }
|
||||
Loader { active: enableSidebarRight; sourceComponent: SidebarRight {} }
|
||||
LazyLoader { active: enableBar; component: Bar {} }
|
||||
LazyLoader { active: enableBackgroundWidgets; component: BackgroundWidgets {} }
|
||||
LazyLoader { active: enableCheatsheet; component: Cheatsheet {} }
|
||||
LazyLoader { active: enableDock; component: Dock {} }
|
||||
LazyLoader { active: enableMediaControls; component: MediaControls {} }
|
||||
LazyLoader { active: enableNotificationPopup; component: NotificationPopup {} }
|
||||
LazyLoader { active: enableOnScreenDisplayBrightness; component: OnScreenDisplayBrightness {} }
|
||||
LazyLoader { active: enableOnScreenDisplayVolume; component: OnScreenDisplayVolume {} }
|
||||
LazyLoader { active: enableOnScreenKeyboard; component: OnScreenKeyboard {} }
|
||||
LazyLoader { active: enableOverview; component: Overview {} }
|
||||
LazyLoader { active: enableReloadPopup; component: ReloadPopup {} }
|
||||
LazyLoader { active: enableScreenCorners; component: ScreenCorners {} }
|
||||
LazyLoader { active: enableSession; component: Session {} }
|
||||
LazyLoader { active: enableSidebarLeft; component: SidebarLeft {} }
|
||||
LazyLoader { active: enableSidebarRight; component: SidebarRight {} }
|
||||
}
|
||||
|
||||
|
||||
+11
-10
@@ -10,25 +10,25 @@ add_newline = false
|
||||
# $character
|
||||
# """
|
||||
format = """
|
||||
$cmd_duration$directory $git_branch
|
||||
$cmd_duration $directory $git_branch
|
||||
$character
|
||||
"""
|
||||
|
||||
# Replace the "❯" symbol in the prompt with "➜"
|
||||
[character] # The name of the module we are configuring is "character"
|
||||
success_symbol = "[• ](bold fg:green) "
|
||||
error_symbol = "[• ](bold fg:red) "
|
||||
success_symbol = "[ ](bold fg:blue)"
|
||||
error_symbol = "[ ](bold fg:red)"
|
||||
|
||||
# Disable the package module, hiding it from the prompt completely
|
||||
[package]
|
||||
disabled = true
|
||||
|
||||
[git_branch]
|
||||
style = "bg: green"
|
||||
style = "bg: cyan"
|
||||
symbol = ""
|
||||
truncation_length = 4
|
||||
truncation_length = 12
|
||||
truncation_symbol = ""
|
||||
format = "• [](bold fg:green)[$symbol $branch(:$remote_branch)](fg:black bg:green)[ ](bold fg:green)"
|
||||
format = " [](bold fg:cyan)[$symbol $branch(:$remote_branch)](fg:black bg:cyan)[ ](bold fg:cyan)"
|
||||
|
||||
[git_commit]
|
||||
commit_hash_length = 4
|
||||
@@ -52,7 +52,7 @@ deleted = " 🗑 "
|
||||
|
||||
[hostname]
|
||||
ssh_only = false
|
||||
format = "[•$hostname](bg:cyan bold fg:black)[](bold fg:cyan )"
|
||||
format = "[•$hostname](bg:cyan bold fg:black)[](bold fg:cyan)"
|
||||
trim_at = ".companyname.com"
|
||||
disabled = false
|
||||
|
||||
@@ -82,8 +82,8 @@ home_symbol = " "
|
||||
read_only = " "
|
||||
style = "bg:green fg:black"
|
||||
truncation_length = 6
|
||||
truncation_symbol = "••/"
|
||||
format = '[](bold fg:green)[$path ]($style)[](bold fg:green)'
|
||||
truncation_symbol = " ••/"
|
||||
format = '[](bold fg:green)[ $path]($style)[](bold fg:green)'
|
||||
|
||||
|
||||
[directory.substitutions]
|
||||
@@ -93,7 +93,8 @@ format = '[](bold fg:green)[$path ]($style)[](bold fg:green)'
|
||||
"Music" = " "
|
||||
"Pictures" = " "
|
||||
"Videos" = " "
|
||||
"GitHub" = " "
|
||||
|
||||
[cmd_duration]
|
||||
min_time = 0
|
||||
format = '[](bold fg:yellow)[ $duration](bold bg:yellow fg:black)[](bold fg:yellow) •• '
|
||||
format = '[](bold fg:yellow)[ $duration](bold bg:yellow fg:black)[](bold fg:yellow)'
|
||||
|
||||
@@ -81,23 +81,25 @@ It's ready if you don't need localization... so quite likely
|
||||
<h3></h3>
|
||||
</div>
|
||||
|
||||
## Main branch (*illogical-impulse*)
|
||||
## illogical-impulse<sup>Quickshell</sup>
|
||||
|
||||
### AI
|
||||

|
||||
_<sup>Sidebar offers online and offline chat. Text selection summary is offline only for privacy.</sup>_
|
||||
| AI | Common widgets |
|
||||
|:---|:---------------|
|
||||
|  |  |
|
||||
| Window management | Weeb power |
|
||||
|  |  |
|
||||
|
||||
### Notifications, music controls, system, calendar
|
||||

|
||||
_<sup>On the sidebar: flicking the notification</sup>_
|
||||
By the way...
|
||||
- The funny notification positions are mimicking Android 16's dragging behavior
|
||||
- The clock on the wallpaper is automatically placed at the "least busy" region of the image
|
||||
|
||||
### Intuitive window management
|
||||

|
||||
_<sup>You can also drag and drop windows across workspaces</sup>_
|
||||
## illogical-impulse<sup>AGS</sup>
|
||||
|
||||
### Power to weebs
|
||||

|
||||
_<sup>Get yande.re and konachan images from sidebar</sup>_
|
||||
| AI | Common widgets |
|
||||
|:---|:---------------|
|
||||
|  |  |
|
||||
| Window management | Weeb power |
|
||||
|  |  |
|
||||
|
||||
## Unsupported stuff
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ pkgdesc='Illogical Impulse Audio Dependencies'
|
||||
arch=(any)
|
||||
license=(None)
|
||||
depends=(
|
||||
cava
|
||||
pavucontrol-qt
|
||||
wireplumber
|
||||
libdbusmenu-gtk3
|
||||
|
||||
@@ -7,6 +7,7 @@ license=(None)
|
||||
depends=(
|
||||
adw-gtk-theme-git
|
||||
breeze-plus
|
||||
eza
|
||||
fish
|
||||
fontconfig
|
||||
kde-material-you-colors
|
||||
|
||||
@@ -5,8 +5,10 @@ pkgdesc='Illogical Impulse KDE Dependencies'
|
||||
arch=(any)
|
||||
license=(None)
|
||||
depends=(
|
||||
polkit-kde-agent
|
||||
bluedevil
|
||||
gnome-keyring
|
||||
gnome-control-center
|
||||
networkmanager better-control-git
|
||||
networkmanager
|
||||
plasma-nm
|
||||
polkit-kde-agent
|
||||
systemsettings
|
||||
)
|
||||
|
||||
@@ -13,4 +13,5 @@ depends=(
|
||||
libportal-gtk4
|
||||
gobject-introspection
|
||||
sassc
|
||||
python-opencv
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ pkgdesc='Illogical Impulse GTK/Qt Dependencies'
|
||||
arch=(any)
|
||||
license=(None)
|
||||
depends=(
|
||||
kdialog
|
||||
qt6-5compat
|
||||
qt6-base
|
||||
qt6-declarative
|
||||
@@ -21,7 +22,5 @@ depends=(
|
||||
syntax-highlighting
|
||||
upower
|
||||
wtype
|
||||
xdg-user-dirs-gtk
|
||||
yad
|
||||
ydotool
|
||||
)
|
||||
|
||||
@@ -38,16 +38,6 @@ ii_check_venv() {
|
||||
which python
|
||||
deactivate
|
||||
}
|
||||
ii_check_ags() {
|
||||
pkill ags
|
||||
pkill agsv1
|
||||
agsv1 > ii_ags.log 2>&1 &
|
||||
GUI_PID=$!
|
||||
sleep 10
|
||||
kill $GUI_PID
|
||||
echo "AGS log saved to \"ii_ags.log\"."
|
||||
}
|
||||
#x ii_check_ags
|
||||
|
||||
e "Checking git repo info"
|
||||
x git remote get-url origin
|
||||
@@ -57,7 +47,6 @@ e "Checking distro"
|
||||
x ii_check_distro
|
||||
|
||||
e "Checking variables"
|
||||
x declare -p XDG_BIN_HOME # ~/.local/bin
|
||||
x declare -p XDG_CACHE_HOME # ~/.cache
|
||||
x declare -p XDG_CONFIG_HOME # ~/.config
|
||||
x declare -p XDG_DATA_HOME # ~/.local/share
|
||||
|
||||
+7
-38
@@ -155,11 +155,11 @@ v mkdir -p $XDG_BIN_HOME $XDG_CACHE_HOME $XDG_CONFIG_HOME $XDG_DATA_HOME
|
||||
# original dotfiles and new ones in the SAME DIRECTORY
|
||||
# (eg. in ~/.config/hypr) won't be mixed together
|
||||
|
||||
# MISC (For .config/* but not AGS, not Fish, not Hyprland)
|
||||
# MISC (For .config/* but not fish, not Hyprland)
|
||||
case $SKIP_MISCCONF in
|
||||
true) sleep 0;;
|
||||
*)
|
||||
for i in $(find .config/ -mindepth 1 -maxdepth 1 ! -name 'ags' ! -name 'fish' ! -name 'hypr' -exec basename {} \;); do
|
||||
for i in $(find .config/ -mindepth 1 -maxdepth 1 ! -name 'fish' ! -name 'hypr' -exec basename {} \;); do
|
||||
# i=".config/$i"
|
||||
echo "[$0]: Found target: .config/$i"
|
||||
if [ -d ".config/$i" ];then v rsync -av --delete ".config/$i/" "$XDG_CONFIG_HOME/$i/"
|
||||
@@ -176,24 +176,6 @@ case $SKIP_FISH in
|
||||
;;
|
||||
esac
|
||||
|
||||
# For AGS
|
||||
case $SKIP_AGS in
|
||||
true) sleep 0;;
|
||||
*)
|
||||
v rsync -av --delete --exclude '/user_options.jsonc' .config/ags/ "$XDG_CONFIG_HOME"/ags/
|
||||
t="$XDG_CONFIG_HOME/ags/user_options.jsonc"
|
||||
if [ -f $t ];then
|
||||
echo -e "\e[34m[$0]: \"$t\" already exists.\e[0m"
|
||||
# v cp -f .config/ags/user_options.jsonc $t.new
|
||||
existed_ags_opt=y
|
||||
else
|
||||
echo -e "\e[33m[$0]: \"$t\" does not exist yet.\e[0m"
|
||||
v cp .config/ags/user_options.jsonc $t
|
||||
existed_ags_opt=n
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Hyprland
|
||||
case $SKIP_HYPRLAND in
|
||||
true) sleep 0;;
|
||||
@@ -202,15 +184,9 @@ case $SKIP_HYPRLAND in
|
||||
t="$XDG_CONFIG_HOME/hypr/hyprland.conf"
|
||||
if [ -f $t ];then
|
||||
echo -e "\e[34m[$0]: \"$t\" already exists.\e[0m"
|
||||
if [ -f "$XDG_STATE_HOME/ags/user/firstrun.txt" ]
|
||||
then
|
||||
v cp -f .config/hypr/hyprland.conf $t.new
|
||||
existed_hypr_conf=y
|
||||
else
|
||||
v mv $t $t.old
|
||||
v cp -f .config/hypr/hyprland.conf $t
|
||||
existed_hypr_conf_firstrun=y
|
||||
fi
|
||||
v mv $t $t.old
|
||||
v cp -f .config/hypr/hyprland.conf $t
|
||||
existed_hypr_conf_firstrun=y
|
||||
else
|
||||
echo -e "\e[33m[$0]: \"$t\" does not exist yet.\e[0m"
|
||||
v cp .config/hypr/hyprland.conf $t
|
||||
@@ -249,7 +225,7 @@ esac
|
||||
|
||||
# some foldes (eg. .local/bin) should be processed separately to avoid `--delete' for rsync,
|
||||
# since the files here come from different places, not only about one program.
|
||||
v rsync -av ".local/bin/" "$XDG_BIN_HOME"
|
||||
# v rsync -av ".local/bin/" "$XDG_BIN_HOME" # No longer needed since scripts are no longer in ~/.local/bin
|
||||
|
||||
# Prevent hyprland from not fully loaded
|
||||
sleep 1
|
||||
@@ -260,10 +236,7 @@ grep -q 'source ${XDG_CONFIG_HOME:-~/.config}/zshrc.d/dots-hyprland.zsh' ~/.zshr
|
||||
|
||||
warn_files=()
|
||||
warn_files_tests=()
|
||||
warn_files_tests+=(/usr/local/bin/ags)
|
||||
warn_files_tests+=(/usr/local/etc/pam.d/ags)
|
||||
warn_files_tests+=(/usr/local/lib/{GUtils-1.0.typelib,Gvc-1.0.typelib,libgutils.so,libgvc.so})
|
||||
warn_files_tests+=(/usr/local/share/com.github.Aylur.ags)
|
||||
warn_files_tests+=(/usr/local/share/fonts/TTF/Rubik{,-Italic}'[wght]'.ttf)
|
||||
warn_files_tests+=(/usr/local/share/licenses/ttf-rubik)
|
||||
warn_files_tests+=(/usr/local/share/fonts/TTF/Gabarito-{Black,Bold,ExtraBold,Medium,Regular,SemiBold}.ttf)
|
||||
@@ -289,10 +262,6 @@ printf "\e[36mPress \e[30m\e[46m Ctrl+Super+T \e[0m\e[36m to select a wallpaper\
|
||||
printf "\e[36mPress \e[30m\e[46m Super+/ \e[0m\e[36m for a list of keybinds\e[0m\n"
|
||||
printf "\n"
|
||||
|
||||
case $existed_ags_opt in
|
||||
y) printf "\n\e[33m[$0]: Warning: \"$XDG_CONFIG_HOME/ags/user_options.jsonc\" already existed before and we didn't overwrite it. \e[0m\n"
|
||||
# printf "\e[33mPlease use \"$XDG_CONFIG_HOME/ags/user_options.jsonc.new\" as a reference for a proper format.\e[0m\n"
|
||||
;;esac
|
||||
case $existed_hypr_conf_firstrun in
|
||||
y) printf "\n\e[33m[$0]: Warning: \"$XDG_CONFIG_HOME/hypr/hyprland.conf\" already existed before. As it seems it is your first run, we replaced it with a new one. \e[0m\n"
|
||||
printf "\e[33mAs it seems it is your first run, we replaced it with a new one. The old one has been renamed to \"$XDG_CONFIG_HOME/hypr/hyprland.conf.old\".\e[0m\n"
|
||||
@@ -311,7 +280,7 @@ case $existed_hyprlock_conf in
|
||||
;;esac
|
||||
|
||||
if [[ -z "${ILLOGICAL_IMPULSE_VIRTUAL_ENV}" ]]; then
|
||||
printf "\n\e[31m[$0]: \!! Important \!! : Please ensure environment variable \e[0m \$ILLOGICAL_IMPULSE_VIRTUAL_ENV \e[31m is set to proper value (by default \"~/.local/state/ags/.venv\"), or AGS config will not work. We have already provided this configuration in ~/.config/hypr/hyprland/env.conf, but you need to ensure it is included in hyprland.conf, and also a restart is needed for applying it.\e[0m\n"
|
||||
printf "\n\e[31m[$0]: \!! Important \!! : Please ensure environment variable \e[0m \$ILLOGICAL_IMPULSE_VIRTUAL_ENV \e[31m is set to proper value (by default \"~/.local/state/quickshell/.venv\"), or Quickshell config will not work. We have already provided this configuration in ~/.config/hypr/hyprland/env.conf, but you need to ensure it is included in hyprland.conf, and also a restart is needed for applying it.\e[0m\n"
|
||||
fi
|
||||
|
||||
if [[ ! -z "${warn_files[@]}" ]]; then
|
||||
|
||||
@@ -11,7 +11,6 @@ source ./scriptdata/installers
|
||||
prevent_sudo_or_root
|
||||
|
||||
if command -v pacman >/dev/null 2>&1;then printf "\e[31m[$0]: pacman found, it seems that the system is ArchLinux or Arch-based distro. Aborting...\e[0m\n";exit 1;fi
|
||||
v install-agsv1
|
||||
v install-Rubik
|
||||
v install-Gabarito
|
||||
v install-OneUI
|
||||
|
||||
@@ -26,8 +26,6 @@ pycparser==2.22
|
||||
# via cffi
|
||||
pyproject-hooks==1.2.0
|
||||
# via build
|
||||
# pywal==3.3.0
|
||||
# via -r scriptdata/requirements.in
|
||||
pywayland==0.4.18
|
||||
# via -r scriptdata/requirements.in
|
||||
setproctitle==1.3.4
|
||||
|
||||
Reference in New Issue
Block a user