Merge branch 'ii-qs' into ii-qs-patch-1

This commit is contained in:
_xB
2025-06-12 09:23:30 +03:00
committed by GitHub
94 changed files with 2060 additions and 701 deletions
+3 -1
View File
@@ -17,7 +17,9 @@ if test -f ~/.local/state/quickshell/user/generated/terminal/sequences.txt
cat ~/.local/state/quickshell/user/generated/terminal/sequences.txt cat ~/.local/state/quickshell/user/generated/terminal/sequences.txt
end end
alias pamcan=pacman alias pamcan pacman
alias ls 'eza --icons'
# function fish_prompt # function fish_prompt
# set_color cyan; echo (pwd) # set_color cyan; echo (pwd)
+3 -3
View File
@@ -7,17 +7,17 @@ general {
} }
listener { listener {
timeout = 180 # 3mins timeout = 300 # 5mins
on-timeout = loginctl lock-session on-timeout = loginctl lock-session
} }
listener { listener {
timeout = 240 # 4mins timeout = 600 # 10mins
on-timeout = hyprctl dispatch dpms off on-timeout = hyprctl dispatch dpms off
on-resume = hyprctl dispatch dpms on on-resume = hyprctl dispatch dpms on
} }
listener { listener {
timeout = 540 # 9mins timeout = 900 # 15mins
on-timeout = $suspend_cmd on-timeout = $suspend_cmd
} }
+1 -1
View File
@@ -1,6 +1,6 @@
# Bar, wallpaper # Bar, wallpaper
exec-once = swww-daemon --format xrgb --no-cache 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 = /usr/lib/geoclue-2.0/demos/agent & gammastep
exec-once = qs & exec-once = qs &
+6 -4
View File
@@ -59,15 +59,17 @@ decoration {
contrast = 1 contrast = 1
popups = true popups = true
popups_ignorealpha = 0.6 popups_ignorealpha = 0.6
input_methods = true
input_methods_ignorealpha = 0.8
} }
shadow { shadow {
enabled = true enabled = true
ignore_window = true ignore_window = true
range = 70 range = 30
offset = 0 4 offset = 0 2
render_power = 2 render_power = 4
color = rgba(00000020) color = rgba(00000010)
} }
# Dim # Dim
+1 -1
View File
@@ -208,7 +208,7 @@ bind = Super, W, exec, zen-browser # [hidden]
bind = Super+Shift, W, exec, wps # WPS Office bind = Super+Shift, W, exec, wps # WPS Office
bind = Super, X, exec, kate # Kate (text editor) bind = Super, X, exec, kate # Kate (text editor)
bind = Ctrl+Super, V, exec, pavucontrol-qt # Pavucontrol Qt (volume mixer) 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 bind = Ctrl+Shift, Escape, exec, plasma-systemmonitor --page-name Processes # Plasma system monitor
# Cursed stuff # Cursed stuff
+5
View File
@@ -19,6 +19,8 @@ windowrulev2 = center, class:^(org.pulseaudio.pavucontrol)$
windowrulev2 = float, class:^(nm-connection-editor)$ windowrulev2 = float, class:^(nm-connection-editor)$
windowrulev2 = size 45%, class:^(nm-connection-editor)$ windowrulev2 = size 45%, class:^(nm-connection-editor)$
windowrulev2 = center, class:^(nm-connection-editor)$ windowrulev2 = center, class:^(nm-connection-editor)$
windowrulev2 = float, class:.*plasmawindowed.*
windowrulev2 = float, class:kcm_.*
# No appearance # 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. # 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 right, quickshell:sidebarRight
layerrule = animation slide left, quickshell:sidebarLeft layerrule = animation slide left, quickshell:sidebarLeft
layerrule = animation slide bottom, quickshell:osk layerrule = animation slide bottom, quickshell:osk
layerrule = animation slide bottom, quickshell:dock
layerrule = blur, quickshell:session layerrule = blur, quickshell:session
layerrule = noanim, quickshell:session layerrule = noanim, quickshell:session
layerrule = animation fade, quickshell:notificationPopup layerrule = animation fade, quickshell:notificationPopup
layerrule = blur, quickshell:backgroundWidgets
layerrule = ignorealpha 0.05, quickshell:backgroundWidgets
# layerrule = blurpopups, quickshell:.* # layerrule = blurpopups, quickshell:.*
# layerrule = blur, quickshell:.* # layerrule = blur, quickshell:.*
+1 -1
View File
@@ -45,4 +45,4 @@ post_hook = '~/.config/matugen/templates/kde/kde-material-you-colors-wrapper.sh'
[templates.wallpaper] [templates.wallpaper]
input_path = '~/.config/matugen/templates/wallpaper.txt' 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'
+338
View File
@@ -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
}
}
}
}
}
}
}
@@ -13,7 +13,6 @@ Item {
height: parent.height height: parent.height
width: colLayout.width width: colLayout.width
ColumnLayout { ColumnLayout {
id: colLayout id: colLayout
+15 -5
View File
@@ -19,6 +19,13 @@ Scope {
readonly property int osdHideMouseMoveThreshold: 20 readonly property int osdHideMouseMoveThreshold: 20
property bool showBarBackground: ConfigOptions.bar.showBackground 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 // Check screensList from config, If no screens are specified, show on all screens
property var filteredScreens: { property var filteredScreens: {
@@ -150,7 +157,7 @@ Scope {
colBackground: barLeftSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1) colBackground: barLeftSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)
colBackgroundHover: Appearance.colors.colLayer1Hover colBackgroundHover: Appearance.colors.colLayer1Hover
colRipple: Appearance.colors.colLayer1Active colRipple: Appearance.colors.colLayer1Active
colBackgroundToggled: Appearance.m3colors.m3secondaryContainer colBackgroundToggled: Appearance.colors.colSecondaryContainer
colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover
colRippleToggled: Appearance.colors.colSecondaryContainerActive colRippleToggled: Appearance.colors.colSecondaryContainerActive
toggled: GlobalStates.sidebarLeftOpen toggled: GlobalStates.sidebarLeftOpen
@@ -177,7 +184,7 @@ Scope {
} }
ActiveWindow { ActiveWindow {
visible: barRoot.useShortenedForm === 0 visible: barRoot.useShortenedForm === 0 && width > 0 && height > 0
Layout.rightMargin: Appearance.rounding.screenRounding Layout.rightMargin: Appearance.rounding.screenRounding
Layout.fillWidth: true Layout.fillWidth: true
bar: barRoot bar: barRoot
@@ -189,7 +196,7 @@ Scope {
RowLayout { // Middle section RowLayout { // Middle section
id: middleSection id: middleSection
anchors.centerIn: parent anchors.centerIn: parent
spacing: 8 spacing: ConfigOptions?.bar.borderless ? 4 : 8
RowLayout { RowLayout {
id: leftCenterGroup id: leftCenterGroup
@@ -210,9 +217,10 @@ Scope {
} }
VerticalBarSeparator {visible: ConfigOptions?.bar.borderless}
RowLayout { RowLayout {
id: middleCenterGroup id: middleCenterGroup
Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Workspaces { Workspaces {
@@ -231,6 +239,8 @@ Scope {
} }
VerticalBarSeparator {visible: ConfigOptions?.bar.borderless}
RowLayout { RowLayout {
id: rightCenterGroup id: rightCenterGroup
Layout.preferredWidth: leftCenterGroup.width Layout.preferredWidth: leftCenterGroup.width
@@ -344,7 +354,7 @@ Scope {
colBackground: barRightSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1) colBackground: barRightSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)
colBackgroundHover: Appearance.colors.colLayer1Hover colBackgroundHover: Appearance.colors.colLayer1Hover
colRipple: Appearance.colors.colLayer1Active colRipple: Appearance.colors.colLayer1Active
colBackgroundToggled: Appearance.m3colors.m3secondaryContainer colBackgroundToggled: Appearance.colors.colSecondaryContainer
colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover
colRippleToggled: Appearance.colors.colSecondaryContainerActive colRippleToggled: Appearance.colors.colSecondaryContainerActive
toggled: GlobalStates.sidebarRightOpen toggled: GlobalStates.sidebarRightOpen
@@ -48,7 +48,7 @@ Rectangle {
lineWidth: 2 lineWidth: 2
value: percentage value: percentage
size: 26 size: 26
secondaryColor: (isLow && !isCharging) ? batteryLowBackground : Appearance.m3colors.m3secondaryContainer secondaryColor: (isLow && !isCharging) ? batteryLowBackground : Appearance.colors.colSecondaryContainer
primaryColor: (isLow && !isCharging) ? batteryLowOnBackground : Appearance.m3colors.m3onSecondaryContainer primaryColor: (isLow && !isCharging) ? batteryLowOnBackground : Appearance.m3colors.m3onSecondaryContainer
fill: (isLow && !isCharging) fill: (isLow && !isCharging)
+2 -2
View File
@@ -62,13 +62,13 @@ Item {
lineWidth: 2 lineWidth: 2
value: activePlayer?.position / activePlayer?.length value: activePlayer?.position / activePlayer?.length
size: 26 size: 26
secondaryColor: Appearance.m3colors.m3secondaryContainer secondaryColor: Appearance.colors.colSecondaryContainer
primaryColor: Appearance.m3colors.m3onSecondaryContainer primaryColor: Appearance.m3colors.m3onSecondaryContainer
MaterialSymbol { MaterialSymbol {
anchors.centerIn: parent anchors.centerIn: parent
fill: 1 fill: 1
text: activePlayer?.isPlaying ? "pause" : "play_arrow" text: activePlayer?.isPlaying ? "pause" : "music_note"
iconSize: Appearance.font.pixelSize.normal iconSize: Appearance.font.pixelSize.normal
color: Appearance.m3colors.m3onSecondaryContainer color: Appearance.m3colors.m3onSecondaryContainer
} }
+1 -1
View File
@@ -23,7 +23,7 @@ Item {
lineWidth: 2 lineWidth: 2
value: percentage value: percentage
size: 26 size: 26
secondaryColor: Appearance.m3colors.m3secondaryContainer secondaryColor: Appearance.colors.colSecondaryContainer
primaryColor: Appearance.m3colors.m3onSecondaryContainer primaryColor: Appearance.m3colors.m3onSecondaryContainer
MaterialSymbol { MaterialSymbol {
@@ -2,6 +2,7 @@ import "root:/"
import "root:/services/" import "root:/services/"
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
@@ -98,15 +99,17 @@ Item {
implicitWidth: workspaceButtonWidth implicitWidth: workspaceButtonWidth
implicitHeight: workspaceButtonWidth implicitHeight: workspaceButtonWidth
radius: Appearance.rounding.full radius: Appearance.rounding.full
property var radiusLeft: (workspaceOccupied[index-1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index)) ? 0 : Appearance.rounding.full property var leftOccupied: (workspaceOccupied[index-1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index))
property var radiusRight: (workspaceOccupied[index+1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+2)) ? 0 : Appearance.rounding.full 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 topLeftRadius: radiusLeft
bottomLeftRadius: radiusLeft bottomLeftRadius: radiusLeft
topRightRadius: radiusRight topRightRadius: radiusRight
bottomRightRadius: 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 opacity: (workspaceOccupied[index] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+1)) ? 1 : 0
Behavior on opacity { Behavior on opacity {
@@ -144,13 +147,13 @@ Item {
Behavior on activeWorkspaceMargin { Behavior on activeWorkspaceMargin {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
} }
Behavior on idx1 { Behavior on idx1 { // Leading anim
NumberAnimation { NumberAnimation {
duration: 100 duration: 100
easing.type: Easing.OutSine easing.type: Easing.OutSine
} }
} }
Behavior on idx2 { Behavior on idx2 { // Following anim
NumberAnimation { NumberAnimation {
duration: 300 duration: 300
easing.type: Easing.OutSine easing.type: Easing.OutSine
@@ -203,7 +206,7 @@ Item {
elide: Text.ElideRight elide: Text.ElideRight
color: (monitor.activeWorkspace?.id == button.workspaceValue) ? color: (monitor.activeWorkspace?.id == button.workspaceValue) ?
Appearance.m3colors.m3onPrimary : Appearance.m3colors.m3onPrimary :
(workspaceOccupied[index] ? Appearance.colors.colOnLayer1 : (workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer :
Appearance.colors.colOnLayer1Inactive) Appearance.colors.colOnLayer1Inactive)
Behavior on opacity { Behavior on opacity {
@@ -15,11 +15,11 @@ Singleton {
property QtObject sizes property QtObject sizes
property string syntaxHighlightingTheme 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 transparency: 0
property real contentTransparency: 0 property real contentTransparency: 0
// property real transparency: 0.15 // property real transparency: m3colors.darkmode ? 0.05 : 0
// property real contentTransparency: 0.5 // property real contentTransparency: m3colors.darkmode ? 0.18 : 0
m3colors: QtObject { m3colors: QtObject {
property bool darkmode: false property bool darkmode: false
@@ -106,10 +106,10 @@ Singleton {
property color colOnLayer0: m3colors.m3onBackground property color colOnLayer0: m3colors.m3onBackground
property color colLayer0Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer0, colOnLayer0, 0.9, root.contentTransparency)) 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 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 colOnLayer1: m3colors.m3onSurfaceVariant;
property color colOnLayer1Inactive: ColorUtils.mix(colOnLayer1, colLayer1, 0.45); 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 colOnLayer2: m3colors.m3onSurface;
property color colOnLayer2Disabled: ColorUtils.mix(colOnLayer2, m3colors.m3background, 0.4); 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) 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 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 colLayer3Active: ColorUtils.transparentize(ColorUtils.mix(colLayer3, colOnLayer3, 0.80), root.contentTransparency);
property color colPrimary: m3colors.m3primary property color colPrimary: m3colors.m3primary
property color colOnPrimary: m3colors.m3onPrimary
property color colPrimaryHover: ColorUtils.mix(colors.colPrimary, colLayer1Hover, 0.87) property color colPrimaryHover: ColorUtils.mix(colors.colPrimary, colLayer1Hover, 0.87)
property color colPrimaryActive: ColorUtils.mix(colors.colPrimary, colLayer1Active, 0.7) property color colPrimaryActive: ColorUtils.mix(colors.colPrimary, colLayer1Active, 0.7)
property color colPrimaryContainer: m3colors.m3primaryContainer property color colPrimaryContainer: m3colors.m3primaryContainer
property color colPrimaryContainerHover: ColorUtils.mix(colors.colPrimaryContainer, colLayer1Hover, 0.7) property color colPrimaryContainerHover: ColorUtils.mix(colors.colPrimaryContainer, colLayer1Hover, 0.7)
property color colPrimaryContainerActive: ColorUtils.mix(colors.colPrimaryContainer, colLayer1Active, 0.6) 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 colSecondaryHover: ColorUtils.mix(m3colors.m3secondary, colLayer1Hover, 0.85)
property color colSecondaryActive: ColorUtils.mix(m3colors.m3secondary, colLayer1Active, 0.4) 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 colSecondaryContainerHover: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Hover, 0.6)
property color colSecondaryContainerActive: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Active, 0.54) 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 colSurfaceContainerHighestHover: ColorUtils.mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.95)
property color colSurfaceContainerHighestActive: ColorUtils.mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.85) 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 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 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 { rounding: QtObject {
@@ -176,6 +185,7 @@ Singleton {
animationCurves: QtObject { animationCurves: QtObject {
readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.90, 1, 1] // Default, 350ms 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> 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> 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> 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] 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 searchWidthCollapsed: 260
property real searchWidth: 450 property real searchWidth: 450
property real hyprlandGapsOut: 5 property real hyprlandGapsOut: 5
property real elevationMargin: 8 property real elevationMargin: 10
property real fabShadowRadius: 5 property real fabShadowRadius: 5
property real fabHoveredShadowRadius: 7 property real fabHoveredShadowRadius: 7
} }
@@ -9,14 +9,23 @@ Singleton {
} }
property QtObject appearance: QtObject { 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 QtObject apps: QtObject {
property string bluetooth: "better-control --bluetooth" property string bluetooth: "kcmshell6 kcm_bluetooth"
property string imageViewer: "loupe" property string imageViewer: "loupe"
property string network: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center wifi" property string network: "plasmawindowed org.kde.plasma.networkmanagement"
property string settings: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center" property string networkEthernet: "kcmshell6 kcm_networkmanagement"
property string settings: "systemsettings"
property string taskManager: "plasma-systemmonitor --page-name Processes" property string taskManager: "plasma-systemmonitor --page-name Processes"
property string terminal: "kitty -1" // This is only for shell actions property string terminal: "kitty -1" // This is only for shell actions
} }
@@ -29,7 +38,7 @@ Singleton {
property QtObject bar: QtObject { property QtObject bar: QtObject {
property bool bottom: false // Instead of top 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 string topLeftIcon: "spark" // Options: distro, spark
property bool showBackground: true property bool showBackground: true
property QtObject resources: QtObject { property QtObject resources: QtObject {
@@ -48,15 +57,24 @@ Singleton {
} }
property QtObject dock: QtObject { property QtObject dock: QtObject {
property bool enable: false
property real height: 60 property real height: 60
property real hoverRegionHeight: 3 property real hoverRegionHeight: 3
property bool pinnedOnStartup: false property bool pinnedOnStartup: false
property bool hoverToReveal: false // When false, only reveals on empty workspace
property list<string> pinnedApps: [ // IDs of pinned entries property list<string> pinnedApps: [ // IDs of pinned entries
"org.kde.dolphin", "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 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" 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 booruPreviews: FileUtils.trimFileProtocol(`${Directories.cache}/media/boorus`)
property string booruDownloads: FileUtils.trimFileProtocol(Directories.pictures + "/homework") property string booruDownloads: FileUtils.trimFileProtocol(Directories.pictures + "/homework")
property string booruDownloadsNsfw: 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 shellConfig: FileUtils.trimFileProtocol(`${Directories.config}/illogical-impulse`)
property string shellConfigName: "config.json" property string shellConfigName: "config.json"
property string shellConfigPath: `${Directories.shellConfig}/${Directories.shellConfigName}` property string shellConfigPath: `${Directories.shellConfig}/${Directories.shellConfigName}`
@@ -32,6 +32,7 @@ Singleton {
property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.config}/quickshell/scripts/switchwall.sh`) property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.config}/quickshell/scripts/switchwall.sh`)
// Cleanup on init // Cleanup on init
Component.onCompleted: { Component.onCompleted: {
Hyprland.dispatch(`exec mkdir -p '${shellConfig}'`)
Hyprland.dispatch(`exec mkdir -p '${favicons}'`) Hyprland.dispatch(`exec mkdir -p '${favicons}'`)
Hyprland.dispatch(`exec rm -rf '${coverArt}'; mkdir -p '${coverArt}'`) Hyprland.dispatch(`exec rm -rf '${coverArt}'; mkdir -p '${coverArt}'`)
Hyprland.dispatch(`exec rm -rf '${booruPreviews}'; mkdir -p '${booruPreviews}'`) Hyprland.dispatch(`exec rm -rf '${booruPreviews}'; mkdir -p '${booruPreviews}'`)
@@ -39,6 +39,30 @@ function colorWithSaturationOf(color1, color2) {
return Qt.hsva(hue, sat, val, alpha); 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. * 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. * @param {number} percentage - The mix ratio (0-1). 1 = all color1, 0 = all color2.
* @returns {Qt.rgba} The resulting mixed color. * @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 c1 = Qt.color(color1);
var c2 = Qt.color(color2); 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); 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 { Rectangle {
id: root id: root
default property alias content: rowLayout.data default property alias data: rowLayout.data
property real spacing: 5 property real spacing: 5
property real padding: 0 property real padding: 0
property int clickIndex: rowLayout.clickIndex property int clickIndex: rowLayout.clickIndex
@@ -14,8 +14,8 @@ Item {
property int lineWidth: 2 property int lineWidth: 2
property real value: 0 property real value: 0
property color primaryColor: Appearance.m3colors.m3onSecondaryContainer property color primaryColor: Appearance.m3colors.m3onSecondaryContainer
property color secondaryColor: Appearance.m3colors.m3secondaryContainer property color secondaryColor: Appearance.colors.colSecondaryContainer
property real gapAngle: Math.PI / 10 property real gapAngle: Math.PI / 9
property bool fill: false property bool fill: false
property int fillOverflow: 2 property int fillOverflow: 2
property int animationDuration: 1000 property int animationDuration: 1000
@@ -15,7 +15,7 @@ Rectangle {
property real extraBottomBorderWidth: 2 property real extraBottomBorderWidth: 2
property color borderColor: Appearance.colors.colOnLayer0 property color borderColor: Appearance.colors.colOnLayer0
property real borderRadius: 5 property real borderRadius: 5
property color keyColor: Appearance.m3colors.m3surfaceContainerLow property color keyColor: Appearance.colors.colSurfaceContainerLow
implicitWidth: keyFace.implicitWidth + borderWidth * 2 implicitWidth: keyFace.implicitWidth + borderWidth * 2
implicitHeight: keyFace.implicitHeight + borderWidth * 2 + extraBottomBorderWidth implicitHeight: keyFace.implicitHeight + borderWidth * 2 + extraBottomBorderWidth
radius: borderRadius radius: borderRadius
@@ -6,23 +6,26 @@ Text {
id: root id: root
property real iconSize: Appearance?.font.pixelSize.small ?? 16 property real iconSize: Appearance?.font.pixelSize.small ?? 16
property real fill: 0 property real fill: 0
renderType: Text.NativeRendering property real truncatedFill: Math.round(fill * 100) / 100 // Reduce memory consumption spikes from constant font remapping
font.hintingPreference: Font.PreferFullHinting renderType: Text.CurveRendering
font {
hintingPreference: Font.PreferFullHinting
family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded"
pixelSize: iconSize
}
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
font.family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded"
font.pixelSize: iconSize
color: Appearance.m3colors.m3onBackground color: Appearance.m3colors.m3onBackground
Behavior on fill { // Behavior on fill {
NumberAnimation { // NumberAnimation {
duration: Appearance?.animation.elementMoveFast.duration ?? 200 // duration: Appearance?.animation.elementMoveFast.duration ?? 200
easing.type: Appearance?.animation.elementMoveFast.type ?? Easing.BezierSpline // easing.type: Appearance?.animation.elementMoveFast.type ?? Easing.BezierSpline
easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1] // easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1]
} // }
} // }
font.variableAxes: { font.variableAxes: {
"FILL": fill, "FILL": truncatedFill,
// "wght": font.weight, // "wght": font.weight,
// "GRAD": 0, // "GRAD": 0,
"opsz": iconSize, "opsz": iconSize,
@@ -30,7 +30,7 @@ Button {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
radius: Appearance.rounding.full radius: Appearance.rounding.full
color: toggled ? 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)) (button.down ? Appearance.colors.colLayer1Active : button.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1))
Behavior on color { Behavior on color {
@@ -15,7 +15,7 @@ RippleButton {
leftPadding: 15 leftPadding: 15
rightPadding: 15 rightPadding: 15
buttonRadius: Appearance.rounding.small 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 colBackgroundHover: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSurfaceContainerHighestHover
colRipple: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colSurfaceContainerHighestActive colRipple: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colSurfaceContainerHighestActive
@@ -26,7 +26,7 @@ Rectangle { // App icon
implicitWidth: size implicitWidth: size
implicitHeight: size implicitHeight: size
radius: Appearance.rounding.full radius: Appearance.rounding.full
color: Appearance.m3colors.m3secondaryContainer color: Appearance.colors.colSecondaryContainer
Loader { Loader {
id: materialSymbolLoader id: materialSymbolLoader
active: root.appIcon == "" active: root.appIcon == ""
@@ -113,7 +113,7 @@ Item { // Notification group area
id: background id: background
anchors.left: parent.left anchors.left: parent.left
width: parent.width width: parent.width
color: Appearance.m3colors.m3surfaceContainer color: Appearance.colors.colSurfaceContainer
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
anchors.leftMargin: root.xOffset anchors.leftMargin: root.xOffset
@@ -154,42 +154,56 @@ Item { // Notification group area
ColumnLayout { // Content ColumnLayout { // Content
Layout.fillWidth: true Layout.fillWidth: true
spacing: expanded ? spacing: expanded ? (root.multipleNotifications ?
((root.multipleNotifications && (notificationGroup?.notifications[root.notificationCount - 1].image != "") ? 35 :
notificationGroup?.notifications[root.notificationCount - 1].image != "") ? 35 : 5 : 0) : 0
5) : 0 // spacing: 00
Behavior on spacing { Behavior on spacing {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) 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 id: topRow
spacing: 0 // spacing: 0
Layout.fillWidth: true
property real fontSize: Appearance.font.pixelSize.smaller property real fontSize: Appearance.font.pixelSize.smaller
property bool showAppName: root.multipleNotifications property bool showAppName: root.multipleNotifications
implicitHeight: Math.max(topTextRow.implicitHeight, expandButton.implicitHeight)
StyledText { RowLayout {
id: appName id: topTextRow
text: (topRow.showAppName ? anchors.left: parent.left
notificationGroup?.appName : anchors.right: expandButton.left
notificationGroup?.notifications[0]?.summary) || "" anchors.verticalCenter: parent.verticalCenter
font.pixelSize: topRow.showAppName ? spacing: 5
topRow.fontSize : StyledText {
Appearance.font.pixelSize.small id: appName
color: topRow.showAppName ? elide: Text.ElideRight
Appearance.colors.colSubtext : Layout.fillWidth: true
Appearance.colors.colOnLayer2 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 { NotificationGroupExpandButton {
id: expandButton
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
count: root.notificationCount count: root.notificationCount
expanded: root.expanded expanded: root.expanded
fontSize: topRow.fontSize fontSize: topRow.fontSize
@@ -129,9 +129,9 @@ Item { // Notification item area
color: (expanded && !onlyNotification) ? color: (expanded && !onlyNotification) ?
(notificationObject.urgency == NotificationUrgency.Critical) ? (notificationObject.urgency == NotificationUrgency.Critical) ?
ColorUtils.mix(Appearance.m3colors.m3secondaryContainer, Appearance.colors.colLayer2, 0.35) : ColorUtils.mix(Appearance.colors.colSecondaryContainer, Appearance.colors.colLayer2, 0.35) :
(Appearance.m3colors.m3surfaceContainerHigh) : (Appearance.colors.colSurfaceContainerHigh) :
ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainerHighest) ColorUtils.transparentize(Appearance.colors.colSurfaceContainerHighest)
implicitHeight: expanded ? (contentColumn.implicitHeight + padding * 2) : summaryRow.implicitHeight implicitHeight: expanded ? (contentColumn.implicitHeight + padding * 2) : summaryRow.implicitHeight
Behavior on implicitHeight { Behavior on implicitHeight {
@@ -110,13 +110,29 @@ TabButton {
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this) animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
} }
Rectangle { Item {
id: ripple id: ripple
width: ripple.implicitWidth
radius: Appearance?.rounding.full ?? 9999 height: ripple.implicitHeight
color: button.colRipple
opacity: 0 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 { transform: Translate {
x: -ripple.width / 2 x: -ripple.width / 2
y: -ripple.height / 2 y: -ripple.height / 2
@@ -13,6 +13,7 @@ Item {
implicitWidth: (reveal || vertical) ? childrenRect.width : 0 implicitWidth: (reveal || vertical) ? childrenRect.width : 0
implicitHeight: (reveal || !vertical) ? childrenRect.height : 0 implicitHeight: (reveal || !vertical) ? childrenRect.height : 0
visible: width > 0 && height > 0
Behavior on implicitWidth { Behavior on implicitWidth {
enabled: !vertical enabled: !vertical
@@ -150,16 +150,29 @@ Button {
} }
} }
Rectangle { Item {
id: ripple id: ripple
width: ripple.implicitWidth
radius: Appearance?.rounding.full ?? 9999 height: ripple.implicitHeight
opacity: 0 opacity: 0
color: root.rippleColor visible: width > 0 && height > 0
Behavior on color {
property real implicitWidth: 0
property real implicitHeight: 0
Behavior on opacity {
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this) 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 { transform: Translate {
x: -ripple.width / 2 x: -ripple.width / 2
y: -ripple.height / 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 real removeOvershoot: 20 // Account for gaps and bouncy animations
property int dragIndex: -1 property int dragIndex: -1
property real dragDistance: 0 property real dragDistance: 0
property bool popin: true
function resetDrag() { function resetDrag() {
root.dragIndex = -1 root.dragIndex = -1
@@ -27,7 +28,7 @@ ListView {
add: Transition { add: Transition {
animations: [ animations: [
Appearance?.animation.elementMove.numberAnimation.createObject(this, { Appearance?.animation.elementMove.numberAnimation.createObject(this, {
properties: "opacity,scale", properties: popin ? "opacity,scale" : "opacity",
from: 0, from: 0,
to: 1, to: 1,
}), }),
@@ -40,46 +41,46 @@ ListView {
property: "y", property: "y",
}), }),
Appearance?.animation.elementMove.numberAnimation.createObject(this, { Appearance?.animation.elementMove.numberAnimation.createObject(this, {
properties: "opacity,scale", properties: popin ? "opacity,scale" : "opacity",
to: 1, to: 1,
}), }),
] ]
} }
displaced: Transition { // displaced: Transition {
animations: [ // animations: [
Appearance?.animation.elementMove.numberAnimation.createObject(this, { // Appearance?.animation.elementMove.numberAnimation.createObject(this, {
property: "y", // property: "y",
}), // }),
Appearance?.animation.elementMove.numberAnimation.createObject(this, { // Appearance?.animation.elementMove.numberAnimation.createObject(this, {
properties: "opacity,scale", // properties: "opacity,scale",
to: 1, // to: 1,
}), // }),
] // ]
} // }
move: Transition { // move: Transition {
animations: [ // animations: [
Appearance?.animation.elementMove.numberAnimation.createObject(this, { // Appearance?.animation.elementMove.numberAnimation.createObject(this, {
property: "y", // property: "y",
}), // }),
Appearance?.animation.elementMove.numberAnimation.createObject(this, { // Appearance?.animation.elementMove.numberAnimation.createObject(this, {
properties: "opacity,scale", // properties: "opacity,scale",
to: 1, // to: 1,
}), // }),
] // ]
} // }
moveDisplaced: Transition { // moveDisplaced: Transition {
animations: [ // animations: [
Appearance?.animation.elementMove.numberAnimation.createObject(this, { // Appearance?.animation.elementMove.numberAnimation.createObject(this, {
property: "y", // property: "y",
}), // }),
Appearance?.animation.elementMove.numberAnimation.createObject(this, { // Appearance?.animation.elementMove.numberAnimation.createObject(this, {
properties: "opacity,scale", // properties: "opacity,scale",
to: 1, // to: 1,
}), // }),
] // ]
} // }
remove: Transition { remove: Transition {
animations: [ animations: [
@@ -19,7 +19,7 @@ Slider {
property real handleLimit: root.backgroundDotMargins property real handleLimit: root.backgroundDotMargins
property real trackHeight: 30 * scale property real trackHeight: 30 * scale
property color highlightColor: Appearance.colors.colPrimary 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 color handleColor: Appearance.m3colors.m3onSecondaryContainer
property real trackRadius: Appearance.rounding.verysmall * scale property real trackRadius: Appearance.rounding.verysmall * scale
property real unsharpenRadius: Appearance.rounding.unsharpen property real unsharpenRadius: Appearance.rounding.unsharpen
@@ -13,7 +13,7 @@ Switch {
implicitHeight: 32 * root.scale implicitHeight: 32 * root.scale
implicitWidth: 52 * root.scale implicitWidth: 52 * root.scale
property color activeColor: Appearance?.colors.colPrimary ?? "#685496" property color activeColor: Appearance?.colors.colPrimary ?? "#685496"
property color inactiveColor: Appearance?.m3colors.m3surfaceContainerHighest ?? "#45464F" property color inactiveColor: Appearance?.colors.colSurfaceContainerHighest ?? "#45464F"
PointingHandInteraction {} PointingHandInteraction {}
@@ -11,4 +11,5 @@ Text {
pixelSize: Appearance?.font.pixelSize.small ?? 15 pixelSize: Appearance?.font.pixelSize.small ?? 15
} }
color: Appearance?.m3colors.m3onBackground ?? "black" color: Appearance?.m3colors.m3onBackground ?? "black"
linkColor: Appearance?.m3colors.m3primary
} }
@@ -5,7 +5,7 @@ import QtQuick.Controls
TextArea { TextArea {
renderType: Text.NativeRendering renderType: Text.NativeRendering
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
selectionColor: Appearance.m3colors.m3secondaryContainer selectionColor: Appearance.colors.colSecondaryContainer
placeholderTextColor: Appearance.m3colors.m3outline placeholderTextColor: Appearance.m3colors.m3outline
font { font {
family: Appearance?.font.family.main ?? "sans-serif" 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
}
}
+105 -90
View File
@@ -18,115 +18,130 @@ Scope { // Scope
Variants { // For each monitor Variants { // For each monitor
model: Quickshell.screens model: Quickshell.screens
PanelWindow { // Window
Loader {
id: dockLoader
required property var modelData required property var modelData
id: dockRoot active: ConfigOptions?.dock.hoverToReveal || (!ToplevelManager.activeToplevel?.activated)
screen: modelData
property bool reveal: root.pinned || dockMouseArea.containsMouse || dockApps.requestDockShow sourceComponent: PanelWindow { // Window
id: dockRoot
screen: dockLoader.modelData
anchors { property bool reveal: root.pinned
bottom: true || (ConfigOptions?.dock.hoverToReveal && dockMouseArea.containsMouse)
left: true || dockApps.requestDockShow
right: true || (!ToplevelManager.activeToplevel?.activated)
}
function hide() { anchors {
cheatsheetLoader.active = false bottom: true
} left: true
exclusiveZone: root.pinned ? implicitHeight - Appearance.sizes.hyprlandGapsOut : 0 right: true
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)
} }
Item { exclusiveZone: root.pinned ? implicitHeight
id: dockHoverRegion - (Appearance.sizes.hyprlandGapsOut)
anchors.fill: parent - (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 { Item {
id: dockBackground id: dockHoverRegion
anchors.top: parent.top anchors.fill: parent
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
implicitWidth: dockRow.implicitWidth + 5 * 2 Item { // Wrapper for the dock background
height: parent.height - Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut id: dockBackground
anchors {
top: parent.top
bottom: parent.bottom
horizontalCenter: parent.horizontalCenter
}
StyledRectangularShadow { implicitWidth: dockRow.implicitWidth + 5 * 2
target: dockVisualBackground height: parent.height - Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut
}
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
}
RowLayout { StyledRectangularShadow {
id: dockRow target: dockVisualBackground
anchors.top: parent.top }
anchors.bottom: parent.bottom Rectangle { // The real rectangle that is visible
anchors.horizontalCenter: parent.horizontalCenter id: dockVisualBackground
spacing: 3 property real margin: Appearance.sizes.elevationMargin
property real padding: 5 anchors.fill: parent
anchors.topMargin: Appearance.sizes.elevationMargin
anchors.bottomMargin: Appearance.sizes.hyprlandGapsOut
color: Appearance.colors.colLayer0
radius: Appearance.rounding.large
}
VerticalButtonGroup { RowLayout {
GroupButton { // Pin button id: dockRow
baseWidth: 35 anchors.top: parent.top
baseHeight: 35 anchors.bottom: parent.bottom
clickedWidth: baseWidth anchors.horizontalCenter: parent.horizontalCenter
clickedHeight: baseHeight + 20 spacing: 3
buttonRadius: Appearance.rounding.normal property real padding: 5
toggled: root.pinned
onClicked: root.pinned = !root.pinned VerticalButtonGroup {
contentItem: MaterialSymbol { Layout.topMargin: Appearance.sizes.hyprlandGapsOut // why does this work
text: "keep" GroupButton { // Pin button
horizontalAlignment: Text.AlignHCenter baseWidth: 35
iconSize: Appearance.font.pixelSize.larger baseHeight: 35
color: root.pinned ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer0 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 {}
DockSeparator {} DockApps { id: dockApps; }
DockApps { id: dockApps } DockSeparator {}
DockSeparator {} DockButton {
DockButton { Layout.fillHeight: true
onClicked: Hyprland.dispatch("global quickshell:overviewToggle") onClicked: Hyprland.dispatch("global quickshell:overviewToggle")
contentItem: MaterialSymbol { contentItem: MaterialSymbol {
anchors.centerIn: parent anchors.fill: parent
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
font.pixelSize: parent.width / 2 font.pixelSize: parent.width / 2
text: "apps" text: "apps"
color: Appearance.colors.colOnLayer0 color: Appearance.colors.colOnLayer0
}
} }
} }
} }
} }
}
}
} }
} }
} }
@@ -2,6 +2,7 @@ import "root:/"
import "root:/services" import "root:/services"
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects import QtQuick.Effects
@@ -13,32 +14,102 @@ import Quickshell.Wayland
import Quickshell.Hyprland import Quickshell.Hyprland
DockButton { DockButton {
id: appButton id: root
required property var appToplevel property var appToplevel
property var appListRoot property var appListRoot
property int lastFocused: -1 property int lastFocused: -1
MouseArea { property real iconSize: 35
id: mouseArea property real countDotWidth: 10
anchors.fill: parent property real countDotHeight: 4
hoverEnabled: true property bool appIsActive: appToplevel.toplevels.find(t => (t.activated == true)) !== undefined
acceptedButtons: Qt.NoButton
onEntered: { property bool isSeparator: appToplevel.appId === "SEPARATOR"
appListRoot.lastHoveredButton = appButton property var desktopEntry: DesktopEntries.byId(appToplevel.appId)
appListRoot.buttonHovered = true enabled: !isSeparator
lastFocused = appToplevel.toplevels.length - 1 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: { sourceComponent: DockSeparator {}
if (appListRoot.lastHoveredButton === appButton) { }
appListRoot.buttonHovered = false
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: { onClicked: {
if (appToplevel.toplevels.length === 0) {
root.desktopEntry?.execute();
return;
}
lastFocused = (lastFocused + 1) % appToplevel.toplevels.length lastFocused = (lastFocused + 1) % appToplevel.toplevels.length
appToplevel.toplevels[lastFocused].activate() appToplevel.toplevels[lastFocused].activate()
} }
contentItem: IconImage {
id: iconImage middleClickAction: () => {
source: Quickshell.iconPath(AppSearch.guessIcon(appToplevel.appId), "image-missing") 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)
}
}
}
}
} }
} }
+59 -36
View File
@@ -23,40 +23,66 @@ Item {
property Item lastHoveredButton property Item lastHoveredButton
property bool buttonHovered: false property bool buttonHovered: false
property bool requestDockShow: previewPopup.show 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 Layout.fillHeight: true
implicitHeight: rowLayout.implicitHeight Layout.topMargin: Appearance.sizes.hyprlandGapsOut // why does this work
implicitWidth: listView.implicitWidth
RowLayout { StyledListView {
id: rowLayout id: listView
spacing: 2 spacing: 2
orientation: ListView.Horizontal
anchors {
top: parent.top
bottom: parent.bottom
}
implicitWidth: contentWidth
Repeater { Behavior on implicitWidth {
model: ScriptModel { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
objectProp: "appId" }
values: {
var map = new Map();
for (const toplevel of ToplevelManager.toplevels.values) { model: ScriptModel {
if (!map.has(toplevel.appId.toLowerCase())) map.set(toplevel.appId.toLowerCase(), []); objectProp: "appId"
map.get(toplevel.appId.toLowerCase()).push(toplevel); values: {
} var map = new Map();
var values = []; // Pinned apps
const pinnedApps = ConfigOptions?.dock.pinnedApps ?? [];
for (const [key, value] of map) { for (const appId of pinnedApps) {
values.push({ appId: key, toplevels: value }); if (!map.has(appId.toLowerCase())) map.set(appId.toLowerCase(), ({
} pinned: true,
toplevels: []
return values; }));
} }
// 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 delegate: DockAppButton {
appToplevel: modelData required property var modelData
appListRoot: root appToplevel: modelData
} appListRoot: root
} }
} }
@@ -118,14 +144,9 @@ Item {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
implicitWidth: popupBackground.implicitWidth + Appearance.sizes.elevationMargin * 2 implicitWidth: popupBackground.implicitWidth + Appearance.sizes.elevationMargin * 2
implicitHeight: root.maxWindowPreviewHeight + root.windowControlsHeight + Appearance.sizes.elevationMargin * 2 implicitHeight: root.maxWindowPreviewHeight + root.windowControlsHeight + Appearance.sizes.elevationMargin * 2
// anchors.horizontalCenter: parent.horizontalCenter
hoverEnabled: true hoverEnabled: true
// x: previewPopup.width / 2 + root.popupX
// Behavior on x {
// animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
// }
x: { 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 return itemCenter.x - width / 2
} }
StyledRectangularShadow { StyledRectangularShadow {
@@ -145,7 +166,7 @@ Item {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
} }
clip: true clip: true
color: Appearance.m3colors.m3surfaceContainer color: Appearance.colors.colSurfaceContainer
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: Appearance.sizes.elevationMargin anchors.bottomMargin: Appearance.sizes.elevationMargin
@@ -163,7 +184,9 @@ Item {
id: previewRowLayout id: previewRowLayout
anchors.centerIn: parent anchors.centerIn: parent
Repeater { Repeater {
model: previewPopup.appTopLevel?.toplevels ?? [] model: ScriptModel {
values: previewPopup.appTopLevel?.toplevels ?? []
}
RippleButton { RippleButton {
id: windowButton id: windowButton
required property var modelData required property var modelData
@@ -182,7 +205,7 @@ Item {
contentWidth: parent.width - anchors.margins * 2 contentWidth: parent.width - anchors.margins * 2
WrapperRectangle { WrapperRectangle {
Layout.fillWidth: true Layout.fillWidth: true
color: ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainer) color: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer)
radius: Appearance.rounding.small radius: Appearance.rounding.small
margin: 5 margin: 5
StyledText { StyledText {
@@ -195,7 +218,7 @@ Item {
} }
GroupButton { GroupButton {
id: closeButton id: closeButton
colBackground: ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainer) colBackground: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer)
baseWidth: windowControlsHeight baseWidth: windowControlsHeight
baseHeight: windowControlsHeight baseHeight: windowControlsHeight
buttonRadius: Appearance.rounding.full buttonRadius: Appearance.rounding.full
@@ -7,9 +7,10 @@ import QtQuick.Layouts
RippleButton { RippleButton {
Layout.fillHeight: true Layout.fillHeight: true
Layout.topMargin: Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut
implicitWidth: implicitHeight - topInset - bottomInset implicitWidth: implicitHeight - topInset - bottomInset
buttonRadius: Appearance.rounding.normal buttonRadius: Appearance.rounding.normal
topInset: dockVisualBackground.margin + dockRow.padding topInset: Appearance.sizes.hyprlandGapsOut + dockRow.padding
bottomInset: dockVisualBackground.margin + dockRow.padding bottomInset: Appearance.sizes.hyprlandGapsOut + dockRow.padding
} }
@@ -5,9 +5,9 @@ import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
Rectangle { Rectangle {
Layout.topMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal Layout.topMargin: Appearance.sizes.elevationMargin + dockRow.padding + Appearance.rounding.normal
Layout.bottomMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal Layout.bottomMargin: Appearance.sizes.hyprlandGapsOut + dockRow.padding + Appearance.rounding.normal
Layout.fillHeight: true Layout.fillHeight: true
implicitWidth: 1 implicitWidth: 1
color: Appearance.m3colors.m3outlineVariant color: Appearance.colors.colOutlineVariant
} }
@@ -26,6 +26,7 @@ Scope {
property real contentPadding: 13 property real contentPadding: 13
property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1 property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1
property real artRounding: Appearance.rounding.verysmall property real artRounding: Appearance.rounding.verysmall
property list<real> visualizerPoints: []
property bool hasPlasmaIntegration: false property bool hasPlasmaIntegration: false
function isRealPlayer(player) { function isRealPlayer(player) {
@@ -53,7 +54,7 @@ Scope {
for (let j = i + 1; j < players.length; ++j) { for (let j = i + 1; j < players.length; ++j) {
let p2 = players[j]; let p2 = players[j];
if (p1.trackTitle && p2.trackTitle && 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); group.push(j);
} }
} }
@@ -68,13 +69,31 @@ Scope {
return filtered; 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 { Loader {
id: mediaControlsLoader id: mediaControlsLoader
active: false active: false
sourceComponent: PanelWindow { sourceComponent: PanelWindow {
id: mediaControlsRoot id: mediaControlsRoot
visible: mediaControlsLoader.active visible: true
exclusiveZone: 0 exclusiveZone: 0
implicitWidth: ( implicitWidth: (
@@ -112,6 +131,7 @@ Scope {
delegate: PlayerControl { delegate: PlayerControl {
required property MprisPlayer modelData required property MprisPlayer modelData
player: modelData player: modelData
visualizerPoints: root.visualizerPoints
} }
} }
} }
@@ -25,6 +25,9 @@ Item { // Player instance
property string artFilePath: `${artDownloadLocation}/${artFileName}` property string artFilePath: `${artDownloadLocation}/${artFileName}`
property color artDominantColor: colorQuantizer?.colors[0] || Appearance.m3colors.m3secondaryContainer property color artDominantColor: colorQuantizer?.colors[0] || Appearance.m3colors.m3secondaryContainer
property bool downloaded: false 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 implicitWidth: widgetWidth
implicitHeight: widgetHeight 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 { RowLayout {
anchors.fill: parent anchors.fill: parent
anchors.margins: root.contentPadding anchors.margins: root.contentPadding
@@ -160,7 +173,7 @@ Item { // Player instance
Layout.fillHeight: true Layout.fillHeight: true
implicitWidth: height implicitWidth: height
radius: root.artRounding radius: root.artRounding
color: blendedColors.colLayer1 color: ColorUtils.transparentize(blendedColors.colLayer1, 0.5)
layer.enabled: true layer.enabled: true
layer.effect: OpacityMask { layer.effect: OpacityMask {
@@ -235,12 +248,18 @@ Item { // Player instance
iconName: "skip_previous" iconName: "skip_previous"
onClicked: playerController.player?.previous() onClicked: playerController.player?.previous()
} }
StyledProgressBar { Item {
id: slider id: progressBarContainer
Layout.fillWidth: true Layout.fillWidth: true
highlightColor: blendedColors.colPrimary implicitHeight: progressBar.implicitHeight
trackColor: blendedColors.colSecondaryContainer
value: playerController.player?.position / playerController.player?.length StyledProgressBar {
id: progressBar
anchors.fill: parent
highlightColor: blendedColors.colPrimary
trackColor: blendedColors.colSecondaryContainer
value: playerController.player?.position / playerController.player?.length
}
} }
TrackChangeButton { TrackChangeButton {
iconName: "skip_next" iconName: "skip_next"
@@ -15,7 +15,7 @@ Scope {
PanelWindow { PanelWindow {
id: root id: root
visible: (Notifications.popupList.length > 0) 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.namespace: "quickshell:notificationPopup"
WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.layer: WlrLayer.Overlay
@@ -73,7 +73,7 @@ Scope {
item: osdValuesWrapper item: osdValuesWrapper
} }
implicitWidth: Appearance.sizes.osdWidth implicitWidth: columnLayout.implicitWidth
implicitHeight: columnLayout.implicitHeight implicitHeight: columnLayout.implicitHeight
visible: osdLoader.active visible: osdLoader.active
@@ -12,6 +12,7 @@ import Quickshell.Hyprland
Scope { Scope {
id: root id: root
property bool showOsdValues: false property bool showOsdValues: false
property string protectionMessage: ""
property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)
function triggerOsd() { function triggerOsd() {
@@ -25,7 +26,8 @@ Scope {
repeat: false repeat: false
running: false running: false
onTriggered: { onTriggered: {
showOsdValues = false root.showOsdValues = false
root.protectionMessage = ""
} }
} }
@@ -36,7 +38,7 @@ Scope {
} }
} }
Connections { Connections { // Listen to volume changes
target: Audio.sink?.audio ?? null target: Audio.sink?.audio ?? null
function onVolumeChanged() { function onVolumeChanged() {
if (!Audio.ready) return 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 { Loader {
id: osdLoader id: osdLoader
active: showOsdValues active: showOsdValues
@@ -75,7 +85,7 @@ Scope {
item: osdValuesWrapper item: osdValuesWrapper
} }
implicitWidth: Appearance.sizes.osdWidth implicitWidth: columnLayout.implicitWidth
implicitHeight: columnLayout.implicitHeight implicitHeight: columnLayout.implicitHeight
visible: osdLoader.active visible: osdLoader.active
@@ -85,8 +95,8 @@ Scope {
Item { Item {
id: osdValuesWrapper id: osdValuesWrapper
// Extra space for shadow // Extra space for shadow
implicitHeight: osdValues.implicitHeight + Appearance.sizes.elevationMargin * 2 implicitHeight: contentColumnLayout.implicitHeight + Appearance.sizes.elevationMargin * 2
implicitWidth: osdValues.implicitWidth implicitWidth: contentColumnLayout.implicitWidth
clip: true clip: true
MouseArea { MouseArea {
@@ -95,20 +105,63 @@ Scope {
onEntered: root.showOsdValues = false onEntered: root.showOsdValues = false
} }
Behavior on implicitHeight { ColumnLayout {
NumberAnimation { id: contentColumnLayout
duration: Appearance.animation.menuDecel.duration anchors {
easing.type: Appearance.animation.menuDecel.type top: parent.top
left: parent.left
right: parent.right
leftMargin: Appearance.sizes.elevationMargin
rightMargin: Appearance.sizes.elevationMargin
} }
} spacing: 0
OsdValueIndicator { OsdValueIndicator {
id: osdValues id: osdValues
anchors.fill: parent Layout.fillWidth: true
anchors.margins: Appearance.sizes.elevationMargin value: Audio.sink?.audio.volume ?? 0
value: Audio.sink?.audio.volume ?? 0 icon: Audio.sink?.audio.muted ? "volume_off" : "volume_up"
icon: Audio.sink?.audio.muted ? "volume_off" : "volume_up" name: qsTr("Volume")
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.bottomMargin: 20
Layout.fillHeight: true Layout.fillHeight: true
implicitWidth: 1 implicitWidth: 1
color: Appearance.m3colors.m3outlineVariant color: Appearance.colors.colOutlineVariant
} }
OskContent { OskContent {
id: oskContent id: oskContent
@@ -92,8 +92,8 @@ Scope {
visible: GlobalStates.overviewOpen visible: GlobalStates.overviewOpen
anchors { anchors {
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
top: !ConfigOptions.bar.bottom ? parent.top : null top: !ConfigOptions.bar.bottom ? parent.top : undefined
bottom: ConfigOptions.bar.bottom ? parent.bottom : null bottom: ConfigOptions.bar.bottom ? parent.bottom : undefined
} }
Keys.onPressed: (event) => { Keys.onPressed: (event) => {
@@ -25,7 +25,7 @@ Item {
property var windowAddresses: HyprlandData.addresses property var windowAddresses: HyprlandData.addresses
property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor.id) property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor.id)
property real scale: ConfigOptions.overview.scale 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) ? property real workspaceImplicitWidth: (monitorData?.transform % 2 === 1) ?
((monitor.height - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale) : ((monitor.height - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale) :
@@ -144,25 +144,38 @@ Item {
implicitHeight: workspaceColumnLayout.implicitHeight implicitHeight: workspaceColumnLayout.implicitHeight
Repeater { // Window repeater Repeater { // Window repeater
model: windowAddresses.filter((address) => { model: ScriptModel {
var win = windowByAddress[address] values: {
return (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown) // 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 { delegate: OverviewWindow {
id: window id: window
windowData: windowByAddress[modelData] required property var modelData
property var address: `0x${modelData.HyprlandToplevel.address}`
windowData: windowByAddress[address]
toplevel: modelData
monitorData: root.monitorData monitorData: root.monitorData
scale: root.scale scale: root.scale
availableWorkspaceWidth: root.workspaceImplicitWidth availableWorkspaceWidth: root.workspaceImplicitWidth
availableWorkspaceHeight: root.workspaceImplicitHeight availableWorkspaceHeight: root.workspaceImplicitHeight
property int monitorId: windowData?.monitor
property var monitor: HyprlandData.monitors[monitorId]
property bool atInitPosition: (initX == x && initY == y) property bool atInitPosition: (initX == x && initY == y)
restrictToWorkspace: Drag.active || atInitPosition restrictToWorkspace: Drag.active || atInitPosition
property int workspaceColIndex: (windowData?.workspace.id - 1) % ConfigOptions.overview.numOfCols property int workspaceColIndex: (windowData?.workspace.id - 1) % ConfigOptions.overview.numOfCols
property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / ConfigOptions.overview.numOfCols) property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / ConfigOptions.overview.numOfCols)
xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex - (monitor?.x * root.scale)
yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex - (monitor?.y * root.scale)
Timer { Timer {
id: updateWindowPosition id: updateWindowPosition
@@ -170,8 +183,9 @@ Item {
repeat: false repeat: false
running: false running: false
onTriggered: { onTriggered: {
window.x = Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0) + xOffset window.x = Math.round(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.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.pressed = true
window.Drag.active = true window.Drag.active = true
window.Drag.source = window window.Drag.source = window
console.log(`[OverviewWindow] Dragging window ${windowData?.address} from position (${window.x}, ${window.y})`)
} }
onReleased: { onReleased: {
const targetWorkspace = root.draggingTargetWorkspace const targetWorkspace = root.draggingTargetWorkspace
@@ -1,17 +1,21 @@
import "root:/"
import "root:/services/" import "root:/services/"
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils import "root:/modules/common/functions/color_utils.js" as ColorUtils
import Qt5Compat.GraphicalEffects import Qt5Compat.GraphicalEffects
import QtQuick import QtQuick
import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Widgets import Quickshell.Widgets
import Quickshell.Io import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland import Quickshell.Hyprland
Rectangle { // Window Item { // Window
id: root id: root
property var toplevel
property var windowData property var windowData
property var monitorData property var monitorData
property var scale property var scale
@@ -38,14 +42,17 @@ Rectangle { // Window
x: initX x: initX
y: initY y: initY
width: Math.min(windowData?.size[0] * root.scale, (restrictToWorkspace ? windowData?.size[0] : availableWorkspaceWidth - x + xOffset)) width: Math.round(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)) height: Math.round(Math.min(windowData?.size[1] * root.scale, (restrictToWorkspace ? windowData?.size[1] : availableWorkspaceHeight - y + yOffset)))
radius: Appearance.rounding.windowRounding * root.scale layer.enabled: true
color: pressed ? Appearance.colors.colLayer2Active : hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2 layer.effect: OpacityMask {
border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.9) maskSource: Rectangle {
border.pixelAligned : false width: root.width
border.width : 1 height: root.height
radius: Appearance.rounding.windowRounding * root.scale
}
}
Behavior on x { Behavior on x {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
@@ -60,34 +67,45 @@ Rectangle { // Window
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
} }
ColumnLayout { ScreencopyView {
anchors.verticalCenter: parent.verticalCenter id: windowPreview
anchors.left: parent.left anchors.fill: parent
anchors.right: parent.right captureSource: GlobalStates.overviewOpen ? root.toplevel : null
spacing: Appearance.font.pixelSize.smaller * 0.5 live: true
IconImage { Rectangle {
id: windowIcon anchors.fill: parent
Layout.alignment: Qt.AlignHCenter radius: Appearance.rounding.windowRounding * root.scale
source: root.iconPath color: pressed ? ColorUtils.transparentize(Appearance.colors.colLayer2Active, 0.5) :
implicitSize: Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) hovered ? ColorUtils.transparentize(Appearance.colors.colLayer2Hover, 0.7) :
ColorUtils.transparentize(Appearance.colors.colLayer2)
Behavior on implicitSize { border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.7)
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) border.width : 1
}
} }
StyledText { ColumnLayout {
Layout.leftMargin: 10 anchors.verticalCenter: parent.verticalCenter
Layout.rightMargin: 10 anchors.left: parent.left
visible: !compactMode anchors.right: parent.right
Layout.fillWidth: true spacing: Appearance.font.pixelSize.smaller * 0.5
Layout.fillHeight: true
horizontalAlignment: Text.AlignHCenter Image {
font.pixelSize: Appearance.font.pixelSize.smaller id: windowIcon
font.italic: indicateXWayland ? true : false property var iconSize: Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio)
elide: Text.ElideRight // mipmap: true
text: windowData?.title ?? "" 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 buttonRadius: Appearance.rounding.normal
colBackground: (root.down || root.keyboardDown) ? Appearance.colors.colLayer1Active : colBackground: (root.down || root.keyboardDown) ? Appearance.colors.colLayer1Active :
((root.hovered || root.focus) ? Appearance.colors.colLayer1Hover : ((root.hovered || root.focus) ? Appearance.colors.colLayer1Hover :
ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainerHigh, 1)) ColorUtils.transparentize(Appearance.colors.colSurfaceContainerHigh, 1))
colBackgroundHover: Appearance.colors.colLayer1Hover colBackgroundHover: Appearance.colors.colLayer1Hover
colRipple: Appearance.colors.colLayer1Active colRipple: Appearance.colors.colLayer1Active
@@ -219,7 +219,7 @@ Item { // Wrapper
} }
color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
selectionColor: Appearance.m3colors.m3secondaryContainer selectionColor: Appearance.colors.colSecondaryContainer
placeholderText: qsTr("Search, calculate or run") placeholderText: qsTr("Search, calculate or run")
placeholderTextColor: Appearance.m3colors.m3outline placeholderTextColor: Appearance.m3colors.m3outline
implicitWidth: root.searchingText == "" ? Appearance.sizes.searchWidthCollapsed : Appearance.sizes.searchWidth implicitWidth: root.searchingText == "" ? Appearance.sizes.searchWidthCollapsed : Appearance.sizes.searchWidth
@@ -260,7 +260,7 @@ Item { // Wrapper
visible: root.showResults visible: root.showResults
Layout.fillWidth: true Layout.fillWidth: true
height: 1 height: 1
color: Appearance.m3colors.m3outlineVariant color: Appearance.colors.colOutlineVariant
} }
ListView { // App results ListView { // App results
@@ -5,6 +5,7 @@ import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Hyprland
Scope { Scope {
id: screenCorners id: screenCorners
@@ -14,7 +15,9 @@ Scope {
model: Quickshell.screens model: Quickshell.screens
PanelWindow { 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 property var modelData
@@ -23,6 +26,20 @@ Scope {
mask: Region { mask: Region {
item: null item: null
} }
HyprlandWindow.visibleMask: Region {
Region {
item: topLeftCorner
}
Region {
item: topRightCorner
}
Region {
item: bottomLeftCorner
}
Region {
item: bottomRightCorner
}
}
WlrLayershell.namespace: "quickshell:screenCorners" WlrLayershell.namespace: "quickshell:screenCorners"
WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.layer: WlrLayer.Overlay
color: "transparent" color: "transparent"
@@ -35,24 +52,28 @@ Scope {
} }
RoundCorner { RoundCorner {
id: topLeftCorner
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
size: Appearance.rounding.screenRounding size: Appearance.rounding.screenRounding
corner: cornerEnum.topLeft corner: cornerEnum.topLeft
} }
RoundCorner { RoundCorner {
id: topRightCorner
anchors.top: parent.top anchors.top: parent.top
anchors.right: parent.right anchors.right: parent.right
size: Appearance.rounding.screenRounding size: Appearance.rounding.screenRounding
corner: cornerEnum.topRight corner: cornerEnum.topRight
} }
RoundCorner { RoundCorner {
id: bottomLeftCorner
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
size: Appearance.rounding.screenRounding size: Appearance.rounding.screenRounding
corner: cornerEnum.bottomLeft corner: cornerEnum.bottomLeft
} }
RoundCorner { RoundCorner {
id: bottomRightCorner
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
size: Appearance.rounding.screenRounding size: Appearance.rounding.screenRounding
@@ -18,7 +18,7 @@ RippleButton {
buttonRadius: (button.focus || button.down) ? size / 2 : Appearance.rounding.verylarge buttonRadius: (button.focus || button.down) ? size / 2 : Appearance.rounding.verylarge
colBackground: button.keyboardDown ? Appearance.colors.colSecondaryContainerActive : colBackground: button.keyboardDown ? Appearance.colors.colSecondaryContainerActive :
button.focus ? Appearance.colors.colPrimary : button.focus ? Appearance.colors.colPrimary :
Appearance.m3colors.m3secondaryContainer Appearance.colors.colSecondaryContainer
colBackgroundHover: Appearance.colors.colPrimary colBackgroundHover: Appearance.colors.colPrimary
colRipple: Appearance.colors.colPrimaryActive colRipple: Appearance.colors.colPrimaryActive
property color colText: (button.down || button.keyboardDown || button.focus || button.hovered) ? property color colText: (button.down || button.keyboardDown || button.focus || button.hovered) ?
@@ -125,9 +125,14 @@ int main(int argc, char* argv[]) {
### LaTeX ### LaTeX
- Simple inline: $\\frac{1}{2} = \\frac{2}{4}$
- Complex inline: $$\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}$$ Inline w/ dollar signs: $\\frac{1}{2} = \\frac{2}{4}$
- Another complex inline: \\\\[\\int_0^\\infty \\frac{1}{x^2} dx = \\infty\\\\]
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); Ai.interfaceRole);
} }
@@ -162,6 +167,7 @@ int main(int argc, char* argv[]) {
id: messageListView id: messageListView
anchors.fill: parent anchors.fill: parent
spacing: 10 spacing: 10
popin: false
property int lastResponseLength: 0 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 { Behavior on contentY {
NumberAnimation { NumberAnimation {
id: scrollAnim id: scrollAnim
@@ -337,7 +345,7 @@ int main(int argc, char* argv[]) {
implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin
+ commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + columnSpacing, 45) + commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + columnSpacing, 45)
clip: true clip: true
border.color: Appearance.m3colors.m3outlineVariant border.color: Appearance.colors.colOutlineVariant
border.width: 1 border.width: 1
Behavior on implicitHeight { Behavior on implicitHeight {
@@ -359,7 +359,7 @@ Item {
implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin
+ commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + columnSpacing, 45) + commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + columnSpacing, 45)
clip: true clip: true
border.color: Appearance.m3colors.m3outlineVariant border.color: Appearance.colors.colOutlineVariant
border.width: 1 border.width: 1
Behavior on implicitHeight { Behavior on implicitHeight {
@@ -562,8 +562,10 @@ Item {
text: "•" text: "•"
} }
Rectangle { // NSFW toggle Item { // NSFW toggle
visible: width > 0
implicitWidth: switchesRow.implicitWidth implicitWidth: switchesRow.implicitWidth
Layout.fillHeight: true
RowLayout { RowLayout {
id: switchesRow id: switchesRow
@@ -97,7 +97,7 @@ Scope { // Scope
anchors.left: parent.left anchors.left: parent.left
anchors.topMargin: Appearance.sizes.hyprlandGapsOut anchors.topMargin: Appearance.sizes.hyprlandGapsOut
anchors.leftMargin: 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 height: parent.height - Appearance.sizes.hyprlandGapsOut * 2
color: Appearance.colors.colLayer0 color: Appearance.colors.colLayer0
radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1 radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1
@@ -3,6 +3,7 @@ import "root:/services"
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "root:/modules/common/functions/string_utils.js" as StringUtils import "root:/modules/common/functions/string_utils.js" as StringUtils
import "./translator/"
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
@@ -15,11 +16,24 @@ import Quickshell.Hyprland
*/ */
Item { Item {
id: root id: root
property var inputField: inputTextArea // Widgets
property var outputField: outputTextArea property var inputField: inputCanvas.inputTextArea
// Widget variables
property bool translationFor: false // Indicates if the translation is for an autocorrected text property bool translationFor: false // Indicates if the translation is for an autocorrected text
property string translatedText: "" 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) => { onFocusChanged: (focus) => {
if (focus) { if (focus) {
@@ -32,19 +46,23 @@ Item {
interval: ConfigOptions.sidebar.translator.delay interval: ConfigOptions.sidebar.translator.delay
repeat: false repeat: false
onTriggered: () => { 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.running = false;
translateProc.buffer = ""; // Clear the buffer translateProc.buffer = ""; // Clear the buffer
translateProc.running = true; // Restart the process translateProc.running = true; // Restart the process
} else { } else {
outputTextArea.text = ""; root.translatedText = "";
} }
} }
} }
Process { Process {
id: translateProc 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: "" property string buffer: ""
stdout: SplitParser { stdout: SplitParser {
onRead: data => { onRead: data => {
@@ -59,170 +77,171 @@ Item {
// 2. Extract relevant data // 2. Extract relevant data
root.translatedText = sections.length > 1 ? sections[1].trim() : ""; 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 anchors.fill: parent
contentHeight: contentColumn.implicitHeight Flickable {
Layout.fillWidth: true
Layout.fillHeight: true
contentHeight: contentColumn.implicitHeight
ColumnLayout { ColumnLayout {
id: contentColumn id: contentColumn
anchors.fill: parent anchors.fill: parent
Rectangle { // INPUT LanguageSelectorButton { // Target language button
id: inputCanvas id: targetLanguageButton
Layout.fillWidth: true displayText: root.targetLanguage
implicitHeight: Math.max(150, inputColumn.implicitHeight) onClicked: {
color: Appearance.colors.colLayer1 root.showLanguageSelectorDialog(true);
radius: Appearance.rounding.normal }
border.color: Appearance.m3colors.m3outlineVariant }
border.width: 1
ColumnLayout { TextCanvas { // Content translation
id: inputColumn id: outputCanvas
anchors.fill: parent isInput: false
spacing: 0 placeholderText: qsTr("Translation goes here...")
property bool hasTranslation: (root.translatedText.trim().length > 0)
StyledTextArea { // Input area text: hasTranslation ? root.translatedText : ""
id: inputTextArea GroupButton {
Layout.fillWidth: true id: copyButton
placeholderText: qsTr("Enter text to translate...") baseWidth: height
wrapMode: TextEdit.Wrap buttonRadius: Appearance.rounding.small
textFormat: TextEdit.PlainText enabled: outputCanvas.displayedText.trim().length > 0
font.pixelSize: Appearance.font.pixelSize.small contentItem: MaterialSymbol {
color: Appearance.colors.colOnLayer1 anchors.centerIn: parent
padding: 15 horizontalAlignment: Text.AlignHCenter
background: null iconSize: Appearance.font.pixelSize.larger
onTextChanged: { text: "content_copy"
if (inputTextArea.text.trim().length > 0) { color: copyButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
translateTimer.restart(); }
} else { onClicked: {
outputTextArea.text = ""; Quickshell.clipboardText = outputCanvas.displayedText
}
} }
} }
GroupButton {
Item { Layout.fillHeight: true } id: searchButton
baseWidth: height
RowLayout { // Status row buttonRadius: Appearance.rounding.small
Layout.fillWidth: true enabled: outputCanvas.displayedText.trim().length > 0
Layout.margins: 10 contentItem: MaterialSymbol {
spacing: 10 anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
Text { iconSize: Appearance.font.pixelSize.larger
Layout.leftMargin: 10 text: "travel_explore"
text: qsTr("%1 characters").arg(inputTextArea.text.length) color: searchButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
color: Appearance.colors.colOnLayer1
font.pixelSize: Appearance.font.pixelSize.smaller
} }
Item { Layout.fillWidth: true } onClicked: {
ButtonGroup { let url = ConfigOptions.search.engineBaseUrl + outputCanvas.displayedText;
GroupButton { for (let site of ConfigOptions.search.excludedSites) {
id: pasteButton url += ` -site:${site}`;
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 = ""
}
} }
Qt.openUrlExternally(url);
} }
} }
} }
}
}
LanguageSelectorButton { // Source language button
id: sourceLanguageButton
displayText: root.sourceLanguage
onClicked: {
root.showLanguageSelectorDialog(false);
}
}
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 {
Rectangle { // OUTPUT id: deleteButton
id: outputCanvas baseWidth: height
Layout.fillWidth: true buttonRadius: Appearance.rounding.small
implicitHeight: Math.max(150, outputColumn.implicitHeight) enabled: inputCanvas.inputTextArea.text.length > 0
color: Appearance.m3colors.m3surfaceContainer contentItem: MaterialSymbol {
radius: Appearance.rounding.normal anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
ColumnLayout { // Output column iconSize: Appearance.font.pixelSize.larger
id: outputColumn text: "close"
anchors.fill: parent color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
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);
}
}
}
}
} }
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 renderMarkdown: true
property bool editing: false property bool editing: false
property list<var> messageBlocks: StringUtils.splitMarkdownBlocks(root.messageData?.content)
anchors.left: parent?.left anchors.left: parent?.left
anchors.right: parent?.right anchors.right: parent?.right
implicitHeight: columnLayout.implicitHeight + root.messagePadding * 2 implicitHeight: columnLayout.implicitHeight + root.messagePadding * 2
@@ -89,7 +91,7 @@ Rectangle {
Rectangle { // Name Rectangle { // Name
id: nameWrapper id: nameWrapper
color: Appearance.m3colors.m3secondaryContainer color: Appearance.colors.colSecondaryContainer
// color: "transparent" // color: "transparent"
radius: Appearance.rounding.small radius: Appearance.rounding.small
implicitHeight: Math.max(nameRowLayout.implicitHeight + 5 * 2, 30) implicitHeight: Math.max(nameRowLayout.implicitHeight + 5 * 2, 30)
@@ -246,23 +248,25 @@ Rectangle {
spacing: 0 spacing: 0
Repeater { Repeater {
model: ScriptModel { model: ScriptModel {
values: StringUtils.splitMarkdownBlocks(root.messageData?.content) values: root.messageBlocks.map((block, index) => index)
} }
delegate: Loader { delegate: Loader {
required property int index
property var thisBlock: root.messageBlocks[index]
Layout.fillWidth: true Layout.fillWidth: true
// property var segment: modelData // property var segment: thisBlock
property var segmentContent: modelData.content property var segmentContent: thisBlock.content
property var segmentLang: modelData.lang property var segmentLang: thisBlock.lang
property var messageData: root.messageData property var messageData: root.messageData
property var editing: root.editing property var editing: root.editing
property var renderMarkdown: root.renderMarkdown property var renderMarkdown: root.renderMarkdown
property var enableMouseSelection: root.enableMouseSelection property var enableMouseSelection: root.enableMouseSelection
property bool thinking: root.messageData?.thinking ?? true property bool thinking: root.messageData?.thinking ?? true
property bool done: root.messageData?.done ?? false 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" : source: thisBlock.type === "code" ? "MessageCodeBlock.qml" :
modelData.type === "think" ? "MessageThinkBlock.qml" : thisBlock.type === "think" ? "MessageThinkBlock.qml" :
"MessageTextBlock.qml" "MessageTextBlock.qml"
} }
@@ -277,7 +281,9 @@ Rectangle {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Repeater { Repeater {
model: root.messageData?.annotationSources model: ScriptModel {
values: root.messageData?.annotationSources || []
}
delegate: AnnotationSourceButton { delegate: AnnotationSourceButton {
id: annotationButton id: annotationButton
displayText: modelData.text displayText: modelData.text
@@ -21,7 +21,7 @@ RippleButton {
leftPadding: (implicitHeight - faviconSize) / 2 leftPadding: (implicitHeight - faviconSize) / 2
rightPadding: 10 rightPadding: 10
buttonRadius: Appearance.rounding.full buttonRadius: Appearance.rounding.full
colBackground: Appearance.m3colors.m3surfaceContainerHighest colBackground: Appearance.colors.colSurfaceContainerHighest
colBackgroundHover: Appearance.colors.colSurfaceContainerHighestHover colBackgroundHover: Appearance.colors.colSurfaceContainerHighestHover
colRipple: Appearance.colors.colSurfaceContainerHighestActive colRipple: Appearance.colors.colSurfaceContainerHighestActive
@@ -40,7 +40,7 @@ ColumnLayout {
topRightRadius: codeBlockBackgroundRounding topRightRadius: codeBlockBackgroundRounding
bottomLeftRadius: Appearance.rounding.unsharpen bottomLeftRadius: Appearance.rounding.unsharpen
bottomRightRadius: Appearance.rounding.unsharpen bottomRightRadius: Appearance.rounding.unsharpen
color: Appearance.m3colors.m3surfaceContainerHighest color: Appearance.colors.colSurfaceContainerHighest
implicitHeight: codeBlockTitleBarRowLayout.implicitHeight + codeBlockHeaderPadding * 2 implicitHeight: codeBlockTitleBarRowLayout.implicitHeight + codeBlockHeaderPadding * 2
RowLayout { // Language and buttons RowLayout { // Language and buttons
@@ -97,7 +97,7 @@ ColumnLayout {
onClicked: { onClicked: {
const downloadPath = FileUtils.trimFileProtocol(Directories.downloads) const downloadPath = FileUtils.trimFileProtocol(Directories.downloads)
Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(segmentContent)}' > '${downloadPath}/code.${segmentLang || "txt"}'`) 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 saveCodeButton.activated = true
saveIconTimer.restart() saveIconTimer.restart()
} }
@@ -209,7 +209,7 @@ ColumnLayout {
font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text
font.pixelSize: Appearance.font.pixelSize.small font.pixelSize: Appearance.font.pixelSize.small
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
selectionColor: Appearance.m3colors.m3secondaryContainer selectionColor: Appearance.colors.colSecondaryContainer
// wrapMode: TextEdit.Wrap // wrapMode: TextEdit.Wrap
color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1 color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1
@@ -30,20 +30,32 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
Timer {
id: renderTimer
interval: 1000
repeat: false
onTriggered: {
renderLatex()
for (const hash of renderedLatexHashes) {
handleRenderedLatex(hash, true);
}
}
}
function renderLatex() { function renderLatex() {
// Regex for $...$, $$...$$, \[...\] // Regex for $...$, $$...$$, \[...\]
// Note: This is a simple approach and may need refinement for edge cases // 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; let match;
while ((match = regex.exec(segmentContent)) !== null) { 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) { if (expression) {
// Qt.callLater(() => { Qt.callLater(() => {
// });
const [renderHash, isNew] = LatexRenderer.requestRender(expression.trim()); const [renderHash, isNew] = LatexRenderer.requestRender(expression.trim());
if (!renderedLatexHashes.includes(renderHash)) { if (!renderedLatexHashes.includes(renderHash)) {
renderedLatexHashes.push(renderHash); renderedLatexHashes.push(renderHash);
} }
});
} }
} }
} }
@@ -53,16 +65,13 @@ ColumnLayout {
const imagePath = LatexRenderer.renderedImagePaths[hash]; const imagePath = LatexRenderer.renderedImagePaths[hash];
const markdownImage = `![latex](${imagePath})`; const markdownImage = `![latex](${imagePath})`;
const expression = StringUtils.escapeBackslashes(LatexRenderer.processedExpressions[hash]); const expression = LatexRenderer.processedExpressions[hash];
renderedSegmentContent = renderedSegmentContent.replace(expression, markdownImage); renderedSegmentContent = renderedSegmentContent.replace(expression, markdownImage);
} }
} }
onDoneChanged: { onDoneChanged: {
renderLatex() renderTimer.restart();
for (const hash of renderedLatexHashes) {
handleRenderedLatex(hash, true);
}
} }
onEditingChanged: { onEditingChanged: {
if (!editing) { if (!editing) {
@@ -111,7 +120,7 @@ ColumnLayout {
font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text
font.pixelSize: Appearance.font.pixelSize.small font.pixelSize: Appearance.font.pixelSize.small
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
selectionColor: Appearance.m3colors.m3secondaryContainer selectionColor: Appearance.colors.colSecondaryContainer
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1 color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1
textFormat: renderMarkdown ? TextEdit.MarkdownText : TextEdit.PlainText textFormat: renderMarkdown ? TextEdit.MarkdownText : TextEdit.PlainText
@@ -64,7 +64,7 @@ Item {
Rectangle { // Header background Rectangle { // Header background
id: header id: header
color: Appearance.m3colors.m3surfaceContainerHighest color: Appearance.colors.colSurfaceContainerHighest
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: thinkBlockTitleBarRowLayout.implicitHeight + thinkBlockHeaderPaddingVertical * 2 implicitHeight: thinkBlockTitleBarRowLayout.implicitHeight + thinkBlockHeaderPaddingVertical * 2
@@ -133,7 +133,7 @@ Button {
opacity: root.showActions ? 1 : 0 opacity: root.showActions ? 1 : 0
visible: opacity > 0 visible: opacity > 0
radius: Appearance.rounding.small radius: Appearance.rounding.small
color: Appearance.m3colors.m3surfaceContainer color: Appearance.colors.colSurfaceContainer
implicitHeight: contextMenuColumnLayout.implicitHeight + radius * 2 implicitHeight: contextMenuColumnLayout.implicitHeight + radius * 2
implicitWidth: contextMenuColumnLayout.implicitWidth implicitWidth: contextMenuColumnLayout.implicitWidth
@@ -67,7 +67,7 @@ Rectangle {
RowLayout { // Header RowLayout { // Header
Rectangle { // Provider name Rectangle { // Provider name
id: providerNameWrapper id: providerNameWrapper
color: Appearance.m3colors.m3secondaryContainer color: Appearance.colors.colSecondaryContainer
radius: Appearance.rounding.small radius: Appearance.rounding.small
implicitWidth: providerName.implicitWidth + 10 * 2 implicitWidth: providerName.implicitWidth + 10 * 2
implicitHeight: Math.max(providerName.implicitHeight + 5 * 2, 30) implicitHeight: Math.max(providerName.implicitHeight + 5 * 2, 30)
@@ -269,7 +269,7 @@ Rectangle {
} }
buttonRadius: Appearance.rounding.small buttonRadius: Appearance.rounding.small
colBackground: Appearance.m3colors.m3surfaceContainerHighest colBackground: Appearance.colors.colSurfaceContainerHighest
colBackgroundHover: Appearance.colors.colSurfaceContainerHighestHover colBackgroundHover: Appearance.colors.colSurfaceContainerHighestHover
colRipple: Appearance.colors.colSurfaceContainerHighestActive 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 { Loader {
id: sidebarContentLoader id: sidebarContentLoader
active: GlobalStates.sidebarRightOpen active: GlobalStates.sidebarRightOpen
anchors.centerIn: parent anchors {
width: sidebarWidth - Appearance.sizes.hyprlandGapsOut * 2 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 height: parent.height - Appearance.sizes.hyprlandGapsOut * 2
sourceComponent: Item { sourceComponent: Item {
@@ -118,14 +127,38 @@ Scope {
Layout.fillWidth: true Layout.fillWidth: true
} }
QuickToggleButton { ButtonGroup {
toggled: false QuickToggleButton {
buttonIcon: "power_settings_new" toggled: false
onClicked: { buttonIcon: "restart_alt"
Hyprland.dispatch("global quickshell:sessionOpen") onClicked: {
Hyprland.dispatch("reload")
Quickshell.reload(true)
}
StyledToolTip {
content: qsTr("Reload Hyprland & Quickshell")
}
} }
StyledToolTip { QuickToggleButton {
content: qsTr("Session") 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 font.weight: bold ? Font.DemiBold : Font.Normal
color: (isToday == 1) ? Appearance.m3colors.m3onPrimary : color: (isToday == 1) ? Appearance.m3colors.m3onPrimary :
(isToday == 0) ? Appearance.colors.colOnLayer1 : (isToday == 0) ? Appearance.colors.colOnLayer1 :
Appearance.m3colors.m3outlineVariant Appearance.colors.colOutlineVariant
Behavior on color { Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
@@ -3,16 +3,28 @@ import "root:/modules/common/widgets"
import "../" import "../"
import Quickshell.Io import Quickshell.Io
import Quickshell import Quickshell
import Quickshell.Hyprland
QuickToggleButton { QuickToggleButton {
toggled: idleInhibitor.running id: root
toggled: false
buttonIcon: "coffee" buttonIcon: "coffee"
onClicked: { 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 { Process {
id: idleInhibitor id: fetchActiveState
command: ["bash", "-c", "${XDG_CONFIG_HOME:-$HOME/.config}/quickshell/scripts/wayland-idle-inhibitor.py"] running: true
command: ["bash", "-c", "pidof wayland-idle-inhibitor.py"]
onExited: (exitCode, exitStatus) => {
root.toggled = exitCode === 0
}
} }
StyledToolTip { StyledToolTip {
content: qsTr("Keep system awake") content: qsTr("Keep system awake")
@@ -15,7 +15,7 @@ QuickToggleButton {
toggleNetwork.running = true toggleNetwork.running = true
} }
altAction: () => { altAction: () => {
Hyprland.dispatch(`exec ${ConfigOptions.apps.network}`) Hyprland.dispatch(`exec ${Network.ethernet ? ConfigOptions.apps.networkEthernet : ConfigOptions.apps.network}`)
Hyprland.dispatch("global quickshell:sidebarRightClose") Hyprland.dispatch("global quickshell:sidebarRightClose")
} }
Process { Process {
@@ -114,7 +114,7 @@ Item {
id: tabBarBottomBorder id: tabBarBottomBorder
Layout.fillWidth: true Layout.fillWidth: true
height: 1 height: 1
color: Appearance.m3colors.m3outlineVariant color: Appearance.colors.colOutlineVariant
} }
SwipeView { SwipeView {
@@ -228,7 +228,7 @@ Item {
anchors.margins: root.dialogMargins anchors.margins: root.dialogMargins
implicitHeight: dialogColumnLayout.implicitHeight implicitHeight: dialogColumnLayout.implicitHeight
color: Appearance.m3colors.m3surfaceContainerHigh color: Appearance.colors.colSurfaceContainerHigh
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
function addTask() { function addTask() {
@@ -264,7 +264,7 @@ Item {
color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant
renderType: Text.NativeRendering renderType: Text.NativeRendering
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
selectionColor: Appearance.m3colors.m3secondaryContainer selectionColor: Appearance.colors.colSecondaryContainer
placeholderText: qsTr("Task description") placeholderText: qsTr("Task description")
placeholderTextColor: Appearance.m3colors.m3outline placeholderTextColor: Appearance.m3colors.m3outline
focus: root.showAddDialog focus: root.showAddDialog
@@ -5,6 +5,7 @@ import Qt5Compat.GraphicalEffects
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets import Quickshell.Widgets
import Quickshell.Services.Pipewire import Quickshell.Services.Pipewire
@@ -16,7 +17,7 @@ Item {
property int dialogMargins: 16 property int dialogMargins: 16
property PwNode selectedDevice property PwNode selectedDevice
function showDeviceSelectorDialog(input) { function showDeviceSelectorDialog(input: bool) {
root.selectedDevice = null root.selectedDevice = null
root.showDeviceSelector = true root.showDeviceSelector = true
root.deviceSelectorInput = input root.deviceSelectorInput = input
@@ -160,7 +161,7 @@ Item {
Rectangle { // The dialog Rectangle { // The dialog
id: dialog id: dialog
color: Appearance.m3colors.m3surfaceContainerHigh color: Appearance.colors.colSurfaceContainerHigh
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
@@ -207,9 +208,11 @@ Item {
spacing: 0 spacing: 0
Repeater { Repeater {
model: Pipewire.nodes.values.filter(node => { model: ScriptModel {
return !node.isStream && node.isSink !== root.deviceSelectorInput && node.audio 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 // This could and should be refractored, but all data becomes null when passed wtf
delegate: StyledRadioButton { 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
+19 -3
View File
@@ -7,6 +7,7 @@ CONFIG_DIR="$XDG_CONFIG_HOME/quickshell"
CACHE_DIR="$XDG_CACHE_HOME/quickshell" CACHE_DIR="$XDG_CACHE_HOME/quickshell"
STATE_DIR="$XDG_STATE_HOME/quickshell" STATE_DIR="$XDG_STATE_HOME/quickshell"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MATUGEN_DIR="$XDG_CONFIG_HOME/matugen"
terminalscheme="$XDG_CONFIG_HOME/quickshell/scripts/terminal/scheme-base.json" terminalscheme="$XDG_CONFIG_HOME/quickshell/scripts/terminal/scheme-base.json"
pre_process() { pre_process() {
@@ -26,7 +27,19 @@ pre_process() {
} }
post_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() { check_and_prompt_upscale() {
@@ -219,7 +232,10 @@ switch() {
"$SCRIPT_DIR"/applycolor.sh "$SCRIPT_DIR"/applycolor.sh
deactivate 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() { main() {
@@ -267,7 +283,7 @@ main() {
# Only prompt for wallpaper if not using --color and not using --noswitch and no imgpath set # 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 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 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 fi
switch "$imgpath" "$mode_flag" "$type_flag" "$color_flag" "$color" switch "$imgpath" "$mode_flag" "$type_flag" "$color_flag" "$color"
+2 -3
View File
@@ -430,8 +430,7 @@ Singleton {
"parts": [{ text: root.systemPrompt }] "parts": [{ text: root.systemPrompt }]
}, },
"generationConfig": { "generationConfig": {
"temperature": root.temperature, // "temperature": root.temperature,
"responseMimeType": "text/plain",
}, },
}; };
return model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData; return model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData;
@@ -450,7 +449,7 @@ Singleton {
}), }),
], ],
"stream": true, "stream": true,
"temperature": root.temperature, // "temperature": root.temperature,
}; };
return model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData; return model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData;
} }
+32 -2
View File
@@ -1,3 +1,4 @@
import "root:/modules/common"
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Services.Pipewire import Quickshell.Services.Pipewire
@@ -11,11 +12,40 @@ Singleton {
id: root id: root
property bool ready: Pipewire.defaultAudioSink?.ready ?? false property bool ready: Pipewire.defaultAudioSink?.ready ?? false
property var sink: Pipewire.defaultAudioSink property PwNode sink: Pipewire.defaultAudioSink
property var source: Pipewire.defaultAudioSource property PwNode source: Pipewire.defaultAudioSource
signal sinkProtectionTriggered(string reason);
PwObjectTracker { PwObjectTracker {
objects: [sink, source] 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;
}
}
} }
+31 -9
View File
@@ -14,12 +14,14 @@ import Qt.labs.platform
/** /**
* Loads and manages the shell configuration file. * Loads and manages the shell configuration file.
* The config file is by default at XDG_CONFIG_HOME/illogical-impulse/config.json. * 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 { Singleton {
id: root id: root
property string filePath: Directories.shellConfigPath property string filePath: Directories.shellConfigPath
property bool firstLoad: true property bool firstLoad: true
property bool preventNextLoad: false
property var preventNextNotification: false
function loadConfig() { function loadConfig() {
configFileView.reload() configFileView.reload()
@@ -27,16 +29,21 @@ Singleton {
function applyConfig(fileContent) { function applyConfig(fileContent) {
try { try {
if (fileContent.trim() === "") {
console.warn("[ConfigLoader] Config file is empty, skipping load.");
return;
}
const json = JSON.parse(fileContent); const json = JSON.parse(fileContent);
ObjectUtils.applyToQtObject(ConfigOptions, json); ObjectUtils.applyToQtObject(ConfigOptions, json);
if (root.firstLoad) { if (root.firstLoad) {
root.firstLoad = false; root.firstLoad = false;
} else { root.preventNextLoad = true;
Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration reloaded")}" "${root.filePath}"`) root.saveConfig(); // Make sure new properties are added to the user's config file
} }
} catch (e) { } catch (e) {
console.error("[ConfigLoader] Error reading file:", 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}"`) Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration failed to load")}" "${root.filePath}"`)
return; 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; obj[keys[keys.length - 1]] = convertedValue;
} }
@@ -80,13 +85,31 @@ Singleton {
Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(JSON.stringify(plainConfig, null, 2))}' > '${root.filePath}'`) 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 { Timer {
id: delayedFileRead id: delayedFileRead
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay interval: ConfigOptions.hacks.arbitraryRaceConditionDelay
repeat: false
running: false running: false
onTriggered: { 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) path: Qt.resolvedUrl(root.filePath)
watchChanges: true watchChanges: true
onFileChanged: { onFileChanged: {
console.log("[ConfigLoader] File changed, reloading...")
this.reload() this.reload()
delayedFileRead.start() delayedFileRead.start()
} }
onLoadedChanged: { onLoadedChanged: {
const fileContent = configFileView.text() const fileContent = configFileView.text()
root.applyConfig(fileContent) delayedFileRead.start()
} }
onLoadFailed: (error) => { onLoadFailed: (error) => {
if(error == FileViewError.FileNotFound) { if(error == FileViewError.FileNotFound) {
+15 -9
View File
@@ -25,7 +25,8 @@ Singleton {
property list<string> processedHashes: [] property list<string> processedHashes: []
property var processedExpressions: ({}) property var processedExpressions: ({})
property var renderedImagePaths: ({}) 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 property string latexOutputPath: Directories.latexOutput
signal renderFinished(string hash, string imagePath) signal renderFinished(string hash, string imagePath)
@@ -51,23 +52,28 @@ Singleton {
} }
// 3. If not, render it with MicroTeX and mark as processed // 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 = ` const processQml = `
import Quickshell.Io import Quickshell.Io
Process { Process {
id: microtexProcess${hash} id: microtexProcess${hash}
running: true running: true
command: [ "${microtexBinaryPath}", "-headless", command: [ "bash", "-c",
"-input=${StringUtils.escapeBackslashes(expression)}", "cd ${root.microtexBinaryDir} && ./${root.microtexBinaryName} -headless '-input=${StringUtils.shellSingleQuoteEscape(StringUtils.escapeBackslashes(expression))}' "
"-output=${imagePath}", + "'-output=${imagePath}' "
"-textsize=${Appearance.font.pixelSize.normal}", + "'-textsize=${Appearance.font.pixelSize.normal}' "
"-padding=${renderPadding}", + "'-padding=${renderPadding}' "
"-background=${Appearance.m3colors.m3tertiary}", // + "'-background=${Appearance.m3colors.m3tertiary}' "
"-foreground=${Appearance.m3colors.m3onTertiary}", + "'-foreground=${Appearance.colors.colOnLayer1}' "
"-maxwidth=0.85" ] + "-maxwidth=0.85 "
]
// stdout: SplitParser { // stdout: SplitParser {
// onRead: data => { console.log("MicroTeX: " + data) } // onRead: data => { console.log("MicroTeX: " + data) }
// } // }
onExited: (exitCode, exitStatus) => { onExited: (exitCode, exitStatus) => {
// console.log("[LatexRenderer] MicroTeX process exited with code: " + exitCode + ", status: " + exitStatus)
renderedImagePaths["${hash}"] = "${imagePath}" renderedImagePaths["${hash}"] = "${imagePath}"
root.renderFinished("${hash}", "${imagePath}") root.renderFinished("${hash}", "${imagePath}")
microtexProcess${hash}.destroy() microtexProcess${hash}.destroy()
+17 -14
View File
@@ -3,6 +3,7 @@
//@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic //@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic
import "./modules/common/" import "./modules/common/"
import "./modules/backgroundWidgets/"
import "./modules/bar/" import "./modules/bar/"
import "./modules/cheatsheet/" import "./modules/cheatsheet/"
import "./modules/dock/" import "./modules/dock/"
@@ -26,6 +27,7 @@ ShellRoot {
// Enable/disable modules here. False = not loaded at all, so rest assured // 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. // no unnecessary stuff will take up memory if you decide to only use, say, the overview.
property bool enableBar: true property bool enableBar: true
property bool enableBackgroundWidgets: true
property bool enableCheatsheet: true property bool enableCheatsheet: true
property bool enableDock: false property bool enableDock: false
property bool enableMediaControls: true property bool enableMediaControls: true
@@ -49,19 +51,20 @@ ShellRoot {
FirstRunExperience.load() FirstRunExperience.load()
} }
Loader { active: enableBar; sourceComponent: Bar {} } LazyLoader { active: enableBar; component: Bar {} }
Loader { active: enableCheatsheet; sourceComponent: Cheatsheet {} } LazyLoader { active: enableBackgroundWidgets; component: BackgroundWidgets {} }
Loader { active: (enableDock || ConfigOptions?.dock.enable); sourceComponent: Dock {} } LazyLoader { active: enableCheatsheet; component: Cheatsheet {} }
Loader { active: enableMediaControls; sourceComponent: MediaControls {} } LazyLoader { active: enableDock; component: Dock {} }
Loader { active: enableNotificationPopup; sourceComponent: NotificationPopup {} } LazyLoader { active: enableMediaControls; component: MediaControls {} }
Loader { active: enableOnScreenDisplayBrightness; sourceComponent: OnScreenDisplayBrightness {} } LazyLoader { active: enableNotificationPopup; component: NotificationPopup {} }
Loader { active: enableOnScreenDisplayVolume; sourceComponent: OnScreenDisplayVolume {} } LazyLoader { active: enableOnScreenDisplayBrightness; component: OnScreenDisplayBrightness {} }
Loader { active: enableOnScreenKeyboard; sourceComponent: OnScreenKeyboard {} } LazyLoader { active: enableOnScreenDisplayVolume; component: OnScreenDisplayVolume {} }
Loader { active: enableOverview; sourceComponent: Overview {} } LazyLoader { active: enableOnScreenKeyboard; component: OnScreenKeyboard {} }
Loader { active: enableReloadPopup; sourceComponent: ReloadPopup {} } LazyLoader { active: enableOverview; component: Overview {} }
Loader { active: enableScreenCorners; sourceComponent: ScreenCorners {} } LazyLoader { active: enableReloadPopup; component: ReloadPopup {} }
Loader { active: enableSession; sourceComponent: Session {} } LazyLoader { active: enableScreenCorners; component: ScreenCorners {} }
Loader { active: enableSidebarLeft; sourceComponent: SidebarLeft {} } LazyLoader { active: enableSession; component: Session {} }
Loader { active: enableSidebarRight; sourceComponent: SidebarRight {} } LazyLoader { active: enableSidebarLeft; component: SidebarLeft {} }
LazyLoader { active: enableSidebarRight; component: SidebarRight {} }
} }
+11 -10
View File
@@ -10,25 +10,25 @@ add_newline = false
# $character # $character
# """ # """
format = """ format = """
$cmd_duration$directory $git_branch $cmd_duration 󰜥 $directory $git_branch
$character $character
""" """
# Replace the "" symbol in the prompt with "➜" # Replace the "" symbol in the prompt with "➜"
[character] # The name of the module we are configuring is "character" [character] # The name of the module we are configuring is "character"
success_symbol = "[• ](bold fg:green) " success_symbol = "[ 󰜥 ](bold fg:blue)"
error_symbol = "[• 󰅙](bold fg:red) " error_symbol = "[ 󰜥 ](bold fg:red)"
# Disable the package module, hiding it from the prompt completely # Disable the package module, hiding it from the prompt completely
[package] [package]
disabled = true disabled = true
[git_branch] [git_branch]
style = "bg: green" style = "bg: cyan"
symbol = "󰘬" symbol = "󰘬"
truncation_length = 4 truncation_length = 12
truncation_symbol = "" 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] [git_commit]
commit_hash_length = 4 commit_hash_length = 4
@@ -52,7 +52,7 @@ deleted = " 🗑 "
[hostname] [hostname]
ssh_only = false 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" trim_at = ".companyname.com"
disabled = false disabled = false
@@ -82,8 +82,8 @@ home_symbol = "  "
read_only = "  " read_only = "  "
style = "bg:green fg:black" style = "bg:green fg:black"
truncation_length = 6 truncation_length = 6
truncation_symbol = "••/" truncation_symbol = " ••/"
format = '[](bold fg:green)[$path ]($style)[](bold fg:green)' format = '[](bold fg:green)[󰉋 $path]($style)[](bold fg:green)'
[directory.substitutions] [directory.substitutions]
@@ -93,7 +93,8 @@ format = '[](bold fg:green)[$path ]($style)[](bold fg:green)'
"Music" = " 󰎈 " "Music" = " 󰎈 "
"Pictures" = "  " "Pictures" = "  "
"Videos" = "  " "Videos" = "  "
"GitHub" = " 󰊤 "
[cmd_duration] [cmd_duration]
min_time = 0 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)'
+15 -13
View File
@@ -81,23 +81,25 @@ It's ready if you don't need localization... so quite likely
<h3></h3> <h3></h3>
</div> </div>
## Main branch (*illogical-impulse*) ## illogical-impulse<sup>Quickshell</sup>
### AI | AI | Common widgets |
![image](https://github.com/user-attachments/assets/9d7af13f-89ef-470d-ba78-d2288b79cf60) |:---|:---------------|
_<sup>Sidebar offers online and offline chat. Text selection summary is offline only for privacy.</sup>_ | ![image](https://github.com/user-attachments/assets/08d26785-b54d-4ad1-875b-bb08cc6757f5) | ![image](https://github.com/user-attachments/assets/4fcd63d9-0943-4b21-8737-4bed97b71961) |
| Window management | Weeb power |
| ![image](https://github.com/user-attachments/assets/86cc511b-0d33-4c78-bcc0-3037d02a17da) | ![image](https://github.com/user-attachments/assets/e402f74a-6bd8-4ebe-bcf4-3a4a4846de10) |
### Notifications, music controls, system, calendar By the way...
![image](https://github.com/end-4/dots-hyprland/assets/97237370/406b72b6-fa38-4f0d-a6c4-4d7d5d5ddcb7) - The funny notification positions are mimicking Android 16's dragging behavior
_<sup>On the sidebar: flicking the notification</sup>_ - The clock on the wallpaper is automatically placed at the "least busy" region of the image
### Intuitive window management ## illogical-impulse<sup>AGS</sup>
![image](https://github.com/user-attachments/assets/02983b9b-79ba-4c25-8717-90bef2357ae5)
_<sup>You can also drag and drop windows across workspaces</sup>_
### Power to weebs | AI | Common widgets |
![image](https://github.com/user-attachments/assets/bbb332ec-962a-4e88-a95b-486d0bd8ce76) |:---|:---------------|
_<sup>Get yande.re and konachan images from sidebar</sup>_ | ![image](https://github.com/user-attachments/assets/9d7af13f-89ef-470d-ba78-d2288b79cf60) | ![image](https://github.com/end-4/dots-hyprland/assets/97237370/406b72b6-fa38-4f0d-a6c4-4d7d5d5ddcb7) |
| Window management | Weeb power |
| ![image](https://github.com/user-attachments/assets/02983b9b-79ba-4c25-8717-90bef2357ae5) | ![image](https://github.com/user-attachments/assets/bbb332ec-962a-4e88-a95b-486d0bd8ce76) |
## Unsupported stuff ## Unsupported stuff
@@ -5,6 +5,7 @@ pkgdesc='Illogical Impulse Audio Dependencies'
arch=(any) arch=(any)
license=(None) license=(None)
depends=( depends=(
cava
pavucontrol-qt pavucontrol-qt
wireplumber wireplumber
libdbusmenu-gtk3 libdbusmenu-gtk3
@@ -7,6 +7,7 @@ license=(None)
depends=( depends=(
adw-gtk-theme-git adw-gtk-theme-git
breeze-plus breeze-plus
eza
fish fish
fontconfig fontconfig
kde-material-you-colors kde-material-you-colors
+5 -3
View File
@@ -5,8 +5,10 @@ pkgdesc='Illogical Impulse KDE Dependencies'
arch=(any) arch=(any)
license=(None) license=(None)
depends=( depends=(
polkit-kde-agent bluedevil
gnome-keyring gnome-keyring
gnome-control-center networkmanager
networkmanager better-control-git plasma-nm
polkit-kde-agent
systemsettings
) )
@@ -13,4 +13,5 @@ depends=(
libportal-gtk4 libportal-gtk4
gobject-introspection gobject-introspection
sassc sassc
python-opencv
) )
@@ -5,6 +5,7 @@ pkgdesc='Illogical Impulse GTK/Qt Dependencies'
arch=(any) arch=(any)
license=(None) license=(None)
depends=( depends=(
kdialog
qt6-5compat qt6-5compat
qt6-base qt6-base
qt6-declarative qt6-declarative
@@ -21,7 +22,5 @@ depends=(
syntax-highlighting syntax-highlighting
upower upower
wtype wtype
xdg-user-dirs-gtk
yad
ydotool ydotool
) )
-11
View File
@@ -38,16 +38,6 @@ ii_check_venv() {
which python which python
deactivate 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" e "Checking git repo info"
x git remote get-url origin x git remote get-url origin
@@ -57,7 +47,6 @@ e "Checking distro"
x ii_check_distro x ii_check_distro
e "Checking variables" e "Checking variables"
x declare -p XDG_BIN_HOME # ~/.local/bin
x declare -p XDG_CACHE_HOME # ~/.cache x declare -p XDG_CACHE_HOME # ~/.cache
x declare -p XDG_CONFIG_HOME # ~/.config x declare -p XDG_CONFIG_HOME # ~/.config
x declare -p XDG_DATA_HOME # ~/.local/share x declare -p XDG_DATA_HOME # ~/.local/share
+7 -38
View File
@@ -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 # original dotfiles and new ones in the SAME DIRECTORY
# (eg. in ~/.config/hypr) won't be mixed together # (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 case $SKIP_MISCCONF in
true) sleep 0;; 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" # i=".config/$i"
echo "[$0]: Found target: .config/$i" echo "[$0]: Found target: .config/$i"
if [ -d ".config/$i" ];then v rsync -av --delete ".config/$i/" "$XDG_CONFIG_HOME/$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 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 # For Hyprland
case $SKIP_HYPRLAND in case $SKIP_HYPRLAND in
true) sleep 0;; true) sleep 0;;
@@ -202,15 +184,9 @@ case $SKIP_HYPRLAND in
t="$XDG_CONFIG_HOME/hypr/hyprland.conf" t="$XDG_CONFIG_HOME/hypr/hyprland.conf"
if [ -f $t ];then if [ -f $t ];then
echo -e "\e[34m[$0]: \"$t\" already exists.\e[0m" echo -e "\e[34m[$0]: \"$t\" already exists.\e[0m"
if [ -f "$XDG_STATE_HOME/ags/user/firstrun.txt" ] v mv $t $t.old
then v cp -f .config/hypr/hyprland.conf $t
v cp -f .config/hypr/hyprland.conf $t.new existed_hypr_conf_firstrun=y
existed_hypr_conf=y
else
v mv $t $t.old
v cp -f .config/hypr/hyprland.conf $t
existed_hypr_conf_firstrun=y
fi
else else
echo -e "\e[33m[$0]: \"$t\" does not exist yet.\e[0m" echo -e "\e[33m[$0]: \"$t\" does not exist yet.\e[0m"
v cp .config/hypr/hyprland.conf $t 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, # 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. # 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 # Prevent hyprland from not fully loaded
sleep 1 sleep 1
@@ -260,10 +236,7 @@ grep -q 'source ${XDG_CONFIG_HOME:-~/.config}/zshrc.d/dots-hyprland.zsh' ~/.zshr
warn_files=() warn_files=()
warn_files_tests=() 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/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/fonts/TTF/Rubik{,-Italic}'[wght]'.ttf)
warn_files_tests+=(/usr/local/share/licenses/ttf-rubik) 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) 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 "\e[36mPress \e[30m\e[46m Super+/ \e[0m\e[36m for a list of keybinds\e[0m\n"
printf "\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 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" 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" 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 ;;esac
if [[ -z "${ILLOGICAL_IMPULSE_VIRTUAL_ENV}" ]]; then 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 fi
if [[ ! -z "${warn_files[@]}" ]]; then if [[ ! -z "${warn_files[@]}" ]]; then
-1
View File
@@ -11,7 +11,6 @@ source ./scriptdata/installers
prevent_sudo_or_root 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 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-Rubik
v install-Gabarito v install-Gabarito
v install-OneUI v install-OneUI
-2
View File
@@ -26,8 +26,6 @@ pycparser==2.22
# via cffi # via cffi
pyproject-hooks==1.2.0 pyproject-hooks==1.2.0
# via build # via build
# pywal==3.3.0
# via -r scriptdata/requirements.in
pywayland==0.4.18 pywayland==0.4.18
# via -r scriptdata/requirements.in # via -r scriptdata/requirements.in
setproctitle==1.3.4 setproctitle==1.3.4