Compare commits
135 commits
6e009d3551
...
9792f401f7
| Author | SHA1 | Date | |
|---|---|---|---|
| 9792f401f7 | |||
|
|
6e5efc3244 | ||
|
|
271a887bbf | ||
|
|
0571ba7325 | ||
|
|
c2f6c39016 | ||
|
|
2de2908509 | ||
|
|
99d56687ef | ||
|
|
434b8273f0 | ||
|
|
663382c81c | ||
|
|
3f388bdb4b | ||
|
|
0a4317f712 | ||
|
|
b9dbbf7bdd | ||
|
|
6ed9a8c5ae | ||
|
|
73de564bb6 | ||
|
|
1f62cdedb5 | ||
|
|
7ed0e894ec | ||
|
|
d39a9a85bf | ||
|
|
d16d1c1d26 | ||
|
|
291ffac102 | ||
|
|
2b18ed3c41 | ||
|
|
3b50efc7d0 | ||
|
|
d91a635781 | ||
|
|
74fce51c2d | ||
|
|
4fbb8314eb | ||
|
|
851a5a6f58 | ||
|
|
833808152e | ||
|
|
84706cab4b | ||
|
|
e571f26583 | ||
|
|
16cea533da | ||
|
|
b1f501f3f9 | ||
|
|
afcba942c7 | ||
|
|
5e6f77f875 | ||
|
|
1f9247c429 | ||
|
|
d089966249 | ||
|
|
b2d629e6a1 | ||
|
|
032087b611 | ||
|
|
22dd2bf75c | ||
|
|
7adc7f43cc | ||
|
|
a38f49cb35 | ||
|
|
ca7684c944 | ||
|
|
955369ab13 | ||
|
|
48f6c0705b | ||
|
|
43eec0e387 | ||
|
|
b1f9609cd3 | ||
|
|
4f731b67d1 | ||
|
|
6549b0fc57 | ||
|
|
ffb972f7c6 | ||
|
|
6ed2daa386 | ||
|
|
8a4042913b | ||
|
|
fb3c2f3bb2 | ||
|
|
5dc4ba504c | ||
|
|
c31dc75c63 | ||
|
|
1f0be929d7 | ||
|
|
56ea1f92fa | ||
|
|
5042d4d747 | ||
|
|
144406ae0e | ||
|
|
3d51f758f8 | ||
|
|
107f6fdfce | ||
|
|
e4d499b550 | ||
|
|
9c3726bdb1 | ||
|
|
a4534b1611 | ||
|
|
e4cad6ed20 | ||
|
|
5f1cfb9072 | ||
|
|
a6ccc8b0da | ||
|
|
fe139c208a | ||
|
|
64c1e842f9 | ||
|
|
cfd7dec04d | ||
|
|
a00676f5db | ||
|
|
61cf7ab843 | ||
|
|
d76d1c628a | ||
|
|
f6b3f6d2ec | ||
|
|
24863c2527 | ||
|
|
ecd6141739 | ||
|
|
ee44920fa4 | ||
|
|
864cbfcfab | ||
|
|
73541eec49 | ||
|
|
87425efa88 | ||
|
|
4455074493 | ||
|
|
5e23476089 | ||
|
|
1232c0268c | ||
|
|
ed9ee65885 | ||
|
|
bc7fe21d27 | ||
|
|
f7b0a28b1e | ||
|
|
b422a419cd | ||
|
|
663f3abff5 | ||
|
|
3c9ce6f8b5 | ||
|
|
c9a128e439 | ||
|
|
e8f356f5ac | ||
|
|
94d64a91b8 | ||
|
|
fdfe9ea2e1 | ||
|
|
d4d7b06b64 | ||
|
|
56d87ecfcf | ||
|
|
76ef2469e8 | ||
|
|
16bd4b41dc | ||
|
|
0e4b79fd16 | ||
|
|
ad73f11b69 | ||
|
|
bacd65b274 | ||
|
|
1f8c55d581 | ||
|
|
ccdb4e0664 | ||
|
|
c77784b5c1 | ||
|
|
74cf71755b | ||
|
|
a4107c87c0 | ||
|
|
8da2cdf430 | ||
|
|
7e93e29f66 | ||
|
|
b2e11137d4 | ||
|
|
d086d64d5f | ||
|
|
fa970986dc | ||
|
|
4c9e89915e | ||
|
|
97c7fd8073 | ||
|
|
29167de546 | ||
|
|
d6f629d4bb | ||
|
|
b13c40e238 | ||
|
|
170fbea7a4 | ||
|
|
08d2747f1e | ||
|
|
b91112fc7a | ||
|
|
ea6b8e0c02 | ||
|
|
404a1d3e8b | ||
|
|
6f1b88e76d | ||
|
|
6169f88d90 | ||
|
|
6f4a4bb764 | ||
|
|
242ae17d0a | ||
|
|
736979c4dc | ||
|
|
4b775fc29d | ||
|
|
ed78b6b3f5 | ||
|
|
a0494d1759 | ||
|
|
1c0c4e955a | ||
|
|
b639c3632d | ||
|
|
6c93b1b768 | ||
|
|
d05255c15b | ||
|
|
59ef26af1c | ||
|
|
33c6ade8f8 | ||
|
|
8c115b8bb0 | ||
|
|
3271fa1d23 | ||
|
|
b43b065cf2 | ||
|
|
53ff6cc21a |
102 changed files with 7930 additions and 1626 deletions
|
|
@ -1,19 +1,19 @@
|
|||
{
|
||||
"dark": {
|
||||
"mPrimary": "#ebbcba",
|
||||
"mOnPrimary": "#1f1d2e",
|
||||
"mOnPrimary": "#191724",
|
||||
"mSecondary": "#9ccfd8",
|
||||
"mOnSecondary": "#1f1d2e",
|
||||
"mOnSecondary": "#191724",
|
||||
"mTertiary": "#f6c177",
|
||||
"mOnTertiary": "#1f1d2e",
|
||||
"mOnTertiary": "#191724",
|
||||
"mError": "#eb6f92",
|
||||
"mOnError": "#1f1d2e",
|
||||
"mSurface": "#1f1d2e",
|
||||
"mOnError": "#191724",
|
||||
"mSurface": "#191724",
|
||||
"mOnSurface": "#e0def4",
|
||||
"mSurfaceVariant": "#26233a",
|
||||
"mOnSurfaceVariant": "#908caa",
|
||||
"mOutline": "#403d52",
|
||||
"mShadow": "#1f1d2e"
|
||||
"mShadow": "#191724"
|
||||
},
|
||||
"light": {
|
||||
"mPrimary": "#d46e6b",
|
||||
|
|
|
|||
16
Assets/Fonts/tabler/tabler-icons-license.txt
Normal file
16
Assets/Fonts/tabler/tabler-icons-license.txt
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
Tabler Licenses - Detailed Usage Rights and Guidelines
|
||||
|
||||
This is a legal agreement between you, the Purchaser, and Tabler. Purchasing or downloading of any Tabler product (Tabler Admin Template, Tabler Icons, Tabler Emails, Tabler Illustrations), constitutes your acceptance of the terms of this license, Tabler terms of service and Tabler private policy.
|
||||
|
||||
Tabler Admin Template and Tabler Icons License*
|
||||
Tabler Admin Template and Tabler Icons are available under MIT License.
|
||||
|
||||
Copyright (c) 2018-2025 Tabler
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
See more at Tabler Admin Template MIT License See more at Tabler Icons MIT License
|
||||
BIN
Assets/Fonts/tabler/tabler-icons.woff2
Normal file
BIN
Assets/Fonts/tabler/tabler-icons.woff2
Normal file
Binary file not shown.
|
|
@ -1,270 +0,0 @@
|
|||
#!/usr/bin/env -S bash
|
||||
|
||||
# A Bash script to monitor system stats and output them in JSON format.
|
||||
|
||||
# --- Configuration ---
|
||||
# Default sleep duration in seconds. Can be overridden by the first argument.
|
||||
SLEEP_DURATION=3
|
||||
|
||||
# --- Argument Parsing ---
|
||||
# Check if a command-line argument is provided for the sleep duration.
|
||||
if [[ -n "$1" ]]; then
|
||||
# Basic validation to ensure the argument is a number (integer or float).
|
||||
if [[ "$1" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
|
||||
SLEEP_DURATION=$1
|
||||
else
|
||||
# Output to stderr if the format is invalid.
|
||||
echo "Warning: Invalid duration format '$1'. Using default of ${SLEEP_DURATION}s." >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Global Cache Variables ---
|
||||
# These variables will store the discovered CPU temperature sensor path and type
|
||||
# to avoid searching for it on every loop iteration.
|
||||
TEMP_SENSOR_PATH=""
|
||||
TEMP_SENSOR_TYPE=""
|
||||
|
||||
# Network speed monitoring variables
|
||||
PREV_RX_BYTES=0
|
||||
PREV_TX_BYTES=0
|
||||
PREV_TIME=0
|
||||
|
||||
# --- Data Collection Functions ---
|
||||
|
||||
#
|
||||
# Gets memory usage in GB, MB, and as a percentage.
|
||||
#
|
||||
get_memory_info() {
|
||||
awk '
|
||||
/MemTotal/ {total=$2}
|
||||
/MemAvailable/ {available=$2}
|
||||
END {
|
||||
if (total > 0) {
|
||||
usage_kb = total - available
|
||||
usage_gb = usage_kb / 1000000
|
||||
usage_percent = (usage_kb / total) * 100
|
||||
printf "%.1f %.0f\n", usage_gb, usage_percent
|
||||
} else {
|
||||
# Fallback if /proc/meminfo is unreadable or empty.
|
||||
print "0.0 0 0"
|
||||
}
|
||||
}
|
||||
' /proc/meminfo
|
||||
}
|
||||
|
||||
#
|
||||
# Gets the usage percentage of the root filesystem ("/").
|
||||
#
|
||||
get_disk_usage() {
|
||||
# df gets disk usage. --output=pcent shows only the percentage for the root path.
|
||||
# tail -1 gets the data line, and tr removes the '%' sign and whitespace.
|
||||
df --output=pcent / | tail -1 | tr -d ' %'
|
||||
}
|
||||
|
||||
#
|
||||
# Calculates current CPU usage over a short interval.
|
||||
#
|
||||
get_cpu_usage() {
|
||||
# Read all 10 CPU time fields to prevent errors on newer kernels.
|
||||
read -r cpu prev_user prev_nice prev_system prev_idle prev_iowait prev_irq prev_softirq prev_steal prev_guest prev_guest_nice < /proc/stat
|
||||
|
||||
# Calculate previous total and idle times.
|
||||
local prev_total_idle=$((prev_idle + prev_iowait))
|
||||
local prev_total=$((prev_user + prev_nice + prev_system + prev_idle + prev_iowait + prev_irq + prev_softirq + prev_steal + prev_guest + prev_guest_nice))
|
||||
|
||||
# Wait for a short period.
|
||||
sleep 0.05
|
||||
|
||||
# Read all 10 CPU time fields again for the second measurement.
|
||||
read -r cpu user nice system idle iowait irq softirq steal guest guest_nice < /proc/stat
|
||||
|
||||
# Calculate new total and idle times.
|
||||
local total_idle=$((idle + iowait))
|
||||
local total=$((user + nice + system + idle + iowait + irq + softirq + steal + guest + guest_nice))
|
||||
|
||||
# Add a check to prevent division by zero if total hasn't changed.
|
||||
if (( total <= prev_total )); then
|
||||
echo "0.0"
|
||||
return
|
||||
fi
|
||||
|
||||
# Calculate the difference over the interval.
|
||||
local diff_total=$((total - prev_total))
|
||||
local diff_idle=$((total_idle - prev_total_idle))
|
||||
|
||||
# Use awk for floating-point calculation and print the percentage.
|
||||
awk -v total="$diff_total" -v idle="$diff_idle" '
|
||||
BEGIN {
|
||||
if (total > 0) {
|
||||
# Formula: 100 * (Total - Idle) / Total
|
||||
usage = 100 * (total - idle) / total
|
||||
printf "%.1f\n", usage
|
||||
} else {
|
||||
print "0.0"
|
||||
}
|
||||
}'
|
||||
}
|
||||
|
||||
#
|
||||
# Finds and returns the CPU temperature in degrees Celsius.
|
||||
# Caches the sensor path for efficiency.
|
||||
#
|
||||
get_cpu_temp() {
|
||||
# If the sensor path hasn't been found yet, search for it.
|
||||
if [[ -z "$TEMP_SENSOR_PATH" ]]; then
|
||||
for dir in /sys/class/hwmon/hwmon*; do
|
||||
# Check if the 'name' file exists and read it.
|
||||
if [[ -f "$dir/name" ]]; then
|
||||
local name
|
||||
name=$(<"$dir/name")
|
||||
# Check for supported sensor types.
|
||||
if [[ "$name" == "coretemp" || "$name" == "k10temp" || "$name" == "zenpower" ]]; then
|
||||
TEMP_SENSOR_PATH=$dir
|
||||
TEMP_SENSOR_TYPE=$name
|
||||
break # Found it, no need to keep searching.
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# If after searching no sensor was found, return 0.
|
||||
if [[ -z "$TEMP_SENSOR_PATH" ]]; then
|
||||
echo 0
|
||||
return
|
||||
fi
|
||||
|
||||
# --- Get temp based on sensor type ---
|
||||
if [[ "$TEMP_SENSOR_TYPE" == "coretemp" ]]; then
|
||||
# For Intel 'coretemp', average all available temperature sensors.
|
||||
local total_temp=0
|
||||
local sensor_count=0
|
||||
|
||||
# Use a for loop with a glob to iterate over all temp input files.
|
||||
# This is more efficient than 'find' for this simple case.
|
||||
for temp_file in "$TEMP_SENSOR_PATH"/temp*_input; do
|
||||
# The glob returns the pattern itself if no files match,
|
||||
# so we must check if the file actually exists.
|
||||
if [[ -f "$temp_file" ]]; then
|
||||
total_temp=$((total_temp + $(<"$temp_file")))
|
||||
sensor_count=$((sensor_count + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
if (( sensor_count > 0 )); then
|
||||
# Use awk for the final division to handle potential floating point numbers
|
||||
# and convert from millidegrees to integer degrees Celsius.
|
||||
awk -v total="$total_temp" -v count="$sensor_count" 'BEGIN { print int(total / count / 1000) }'
|
||||
else
|
||||
# If no sensor files were found, return 0.
|
||||
echo 0
|
||||
fi
|
||||
|
||||
elif [[ "$TEMP_SENSOR_TYPE" == "k10temp" ]]; then
|
||||
# For AMD 'k10temp', find the 'Tctl' sensor, which is the control temperature.
|
||||
local tctl_input=""
|
||||
for label_file in "$TEMP_SENSOR_PATH"/temp*_label; do
|
||||
if [[ -f "$label_file" ]] && [[ $(<"$label_file") == "Tctl" ]]; then
|
||||
# The input file has the same name but with '_input' instead of '_label'.
|
||||
tctl_input="${label_file%_label}_input"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -f "$tctl_input" ]]; then
|
||||
# Read the temperature and convert from millidegrees to degrees.
|
||||
echo "$(( $(<"$tctl_input") / 1000 ))"
|
||||
else
|
||||
echo 0 # Fallback
|
||||
fi
|
||||
elif [[ "$TEMP_SENSOR_TYPE" == "zenpower" ]]; then
|
||||
# For zenpower, read the first available temp sensor
|
||||
for temp_file in "$TEMP_SENSOR_PATH"/temp*_input; do
|
||||
if [[ -f "$temp_file" ]]; then
|
||||
local temp_value
|
||||
temp_value=$(cat "$temp_file" | tr -d '\n\r') # Remove any newlines
|
||||
echo "$((temp_value / 1000))"
|
||||
return
|
||||
fi
|
||||
done
|
||||
echo 0
|
||||
|
||||
if [[ -f "$tctl_input" ]]; then
|
||||
# Read the temperature and convert from millidegrees to degrees.
|
||||
echo "$(($(<"$tctl_input") / 1000))"
|
||||
else
|
||||
echo 0 # Fallback
|
||||
fi
|
||||
else
|
||||
echo 0 # Should not happen if cache logic is correct.
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
# --- Main Loop ---
|
||||
# This loop runs indefinitely, gathering and printing stats.
|
||||
while true; do
|
||||
# Call the functions to gather all the data.
|
||||
# get_memory_info
|
||||
read -r mem_gb mem_per <<< "$(get_memory_info)"
|
||||
|
||||
# Command substitution captures the single output from the other functions.
|
||||
disk_per=$(get_disk_usage)
|
||||
cpu_usage=$(get_cpu_usage)
|
||||
cpu_temp=$(get_cpu_temp)
|
||||
|
||||
# Get network speeds
|
||||
current_time=$(date +%s.%N)
|
||||
total_rx=0
|
||||
total_tx=0
|
||||
|
||||
# Read total bytes from /proc/net/dev for all interfaces
|
||||
while IFS=: read -r interface stats; do
|
||||
# Skip only loopback interface, allow other interfaces
|
||||
if [[ "$interface" =~ ^lo[[:space:]]*$ ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Extract rx and tx bytes (fields 1 and 9 in the stats part)
|
||||
rx_bytes=$(echo "$stats" | awk '{print $1}')
|
||||
tx_bytes=$(echo "$stats" | awk '{print $9}')
|
||||
|
||||
# Add to totals if they are valid numbers
|
||||
if [[ "$rx_bytes" =~ ^[0-9]+$ ]] && [[ "$tx_bytes" =~ ^[0-9]+$ ]]; then
|
||||
total_rx=$((total_rx + rx_bytes))
|
||||
total_tx=$((total_tx + tx_bytes))
|
||||
fi
|
||||
done < <(tail -n +3 /proc/net/dev)
|
||||
|
||||
# Calculate speeds if we have previous data
|
||||
rx_speed=0
|
||||
tx_speed=0
|
||||
|
||||
if [[ "$PREV_TIME" != "0" ]]; then
|
||||
time_diff=$(awk -v current="$current_time" -v prev="$PREV_TIME" 'BEGIN { printf "%.3f", current - prev }')
|
||||
rx_diff=$((total_rx - PREV_RX_BYTES))
|
||||
tx_diff=$((total_tx - PREV_TX_BYTES))
|
||||
|
||||
# Calculate speeds in bytes per second using awk
|
||||
rx_speed=$(awk -v rx="$rx_diff" -v time="$time_diff" 'BEGIN { printf "%.0f", rx / time }')
|
||||
tx_speed=$(awk -v tx="$tx_diff" -v time="$time_diff" 'BEGIN { printf "%.0f", tx / time }')
|
||||
fi
|
||||
|
||||
# Update previous values for next iteration
|
||||
PREV_RX_BYTES=$total_rx
|
||||
PREV_TX_BYTES=$total_tx
|
||||
PREV_TIME=$current_time
|
||||
|
||||
# Use printf to format the final JSON output string, adding the mem_mb key.
|
||||
printf '{"cpu": "%s", "cputemp": "%s", "memgb":"%s", "memper": "%s", "diskper": "%s", "rx_speed": "%s", "tx_speed": "%s"}\n' \
|
||||
"$cpu_usage" \
|
||||
"$cpu_temp" \
|
||||
"$mem_gb" \
|
||||
"$mem_per" \
|
||||
"$disk_per" \
|
||||
"$rx_speed" \
|
||||
"$tx_speed"
|
||||
|
||||
# Wait for the specified duration before the next update.
|
||||
sleep "$SLEEP_DURATION"
|
||||
done
|
||||
54
Commons/AppIcons.qml
Normal file
54
Commons/AppIcons.qml
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Services
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
function iconFromName(iconName, fallbackName) {
|
||||
const fallback = fallbackName || "application-x-executable"
|
||||
try {
|
||||
if (iconName && typeof Quickshell !== 'undefined' && Quickshell.iconPath) {
|
||||
const p = Quickshell.iconPath(iconName, fallback)
|
||||
if (p && p !== "")
|
||||
return p
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
// ignore and fall back
|
||||
}
|
||||
try {
|
||||
return Quickshell.iconPath ? (Quickshell.iconPath(fallback, true) || "") : ""
|
||||
} catch (e2) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve icon path for a DesktopEntries appId - safe on missing entries
|
||||
function iconForAppId(appId, fallbackName) {
|
||||
const fallback = fallbackName || "application-x-executable"
|
||||
if (!appId)
|
||||
return iconFromName(fallback, fallback)
|
||||
try {
|
||||
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
|
||||
return iconFromName(fallback, fallback)
|
||||
const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(
|
||||
appId) : DesktopEntries.byId(appId)
|
||||
const name = entry && entry.icon ? entry.icon : ""
|
||||
return iconFromName(name || fallback, fallback)
|
||||
} catch (e) {
|
||||
return iconFromName(fallback, fallback)
|
||||
}
|
||||
}
|
||||
|
||||
// Distro logo helper (absolute path or empty string)
|
||||
function distroLogoPath() {
|
||||
try {
|
||||
return (typeof OSInfo !== 'undefined' && OSInfo.distroIconPath) ? OSInfo.distroIconPath : ""
|
||||
} catch (e) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -102,7 +102,8 @@ Singleton {
|
|||
// FileView to load custom colors data from colors.json
|
||||
FileView {
|
||||
id: customColorsFile
|
||||
path: Settings.directoriesCreated ? (Settings.configDir + "colors.json") : ""
|
||||
path: Settings.directoriesCreated ? (Settings.configDir + "colors.json") : undefined
|
||||
printErrors: false
|
||||
watchChanges: true
|
||||
onFileChanged: {
|
||||
Logger.log("Color", "Reloading colors from disk")
|
||||
|
|
@ -115,7 +116,7 @@ Singleton {
|
|||
|
||||
// Trigger initial load when path changes from empty to actual path
|
||||
onPathChanged: {
|
||||
if (path === Settings.configDir + "colors.json") {
|
||||
if (path !== undefined) {
|
||||
reload()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,54 +1,49 @@
|
|||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Services
|
||||
import qs.Commons
|
||||
import qs.Commons.IconsSets
|
||||
|
||||
Singleton {
|
||||
id: icons
|
||||
id: root
|
||||
|
||||
function iconFromName(iconName, fallbackName) {
|
||||
const fallback = fallbackName || "application-x-executable"
|
||||
try {
|
||||
if (iconName && typeof Quickshell !== 'undefined' && Quickshell.iconPath) {
|
||||
const p = Quickshell.iconPath(iconName, fallback)
|
||||
if (p && p !== "")
|
||||
return p
|
||||
// Expose the font family name for easy access
|
||||
readonly property string fontFamily: fontLoader.name
|
||||
readonly property string defaultIcon: TablerIcons.defaultIcon
|
||||
readonly property var icons: TablerIcons.icons
|
||||
readonly property var aliases: TablerIcons.aliases
|
||||
readonly property string fontPath: "/Assets/Fonts/tabler/tabler-icons.woff2"
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.log("Icons", "Service started")
|
||||
}
|
||||
|
||||
function get(iconName) {
|
||||
// Check in aliases first
|
||||
if (aliases[iconName] !== undefined) {
|
||||
iconName = aliases[iconName]
|
||||
}
|
||||
|
||||
// Find the appropriate codepoint
|
||||
return icons[iconName]
|
||||
}
|
||||
|
||||
FontLoader {
|
||||
id: fontLoader
|
||||
source: Quickshell.shellDir + fontPath
|
||||
}
|
||||
|
||||
// Monitor font loading status
|
||||
Connections {
|
||||
target: fontLoader
|
||||
function onStatusChanged() {
|
||||
if (fontLoader.status === FontLoader.Ready) {
|
||||
Logger.log("Icons", "Font loaded successfully:", fontFamily)
|
||||
} else if (fontLoader.status === FontLoader.Error) {
|
||||
Logger.error("Icons", "Font failed to load")
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
// ignore and fall back
|
||||
}
|
||||
try {
|
||||
return Quickshell.iconPath ? (Quickshell.iconPath(fallback, true) || "") : ""
|
||||
} catch (e2) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve icon path for a DesktopEntries appId - safe on missing entries
|
||||
function iconForAppId(appId, fallbackName) {
|
||||
const fallback = fallbackName || "application-x-executable"
|
||||
if (!appId)
|
||||
return iconFromName(fallback, fallback)
|
||||
try {
|
||||
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
|
||||
return iconFromName(fallback, fallback)
|
||||
const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(
|
||||
appId) : DesktopEntries.byId(appId)
|
||||
const name = entry && entry.icon ? entry.icon : ""
|
||||
return iconFromName(name || fallback, fallback)
|
||||
} catch (e) {
|
||||
return iconFromName(fallback, fallback)
|
||||
}
|
||||
}
|
||||
|
||||
// Distro logo helper (absolute path or empty string)
|
||||
function distroLogoPath() {
|
||||
try {
|
||||
return (typeof OSInfo !== 'undefined' && OSInfo.distroIconPath) ? OSInfo.distroIconPath : ""
|
||||
} catch (e) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
6125
Commons/IconsSets/TablerIcons.qml
Normal file
6125
Commons/IconsSets/TablerIcons.qml
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -23,9 +23,9 @@ Singleton {
|
|||
property string settingsFile: Quickshell.env("NOCTALIA_SETTINGS_FILE") || (configDir + "settings.json")
|
||||
|
||||
property string defaultAvatar: Quickshell.env("HOME") + "/.face"
|
||||
property string defaultWallpapersDirectory: Quickshell.env("HOME") + "/Pictures/Wallpapers"
|
||||
property string defaultVideosDirectory: Quickshell.env("HOME") + "/Videos"
|
||||
property string defaultLocation: "Tokyo"
|
||||
property string defaultWallpapersDirectory: Quickshell.env("HOME") + "/Pictures/Wallpapers"
|
||||
property string defaultWallpaper: Quickshell.shellDir + "/Assets/Wallpaper/noctalia.png"
|
||||
|
||||
// Used to access via Settings.data.xxx.yyy
|
||||
|
|
@ -93,7 +93,22 @@ Singleton {
|
|||
}
|
||||
|
||||
// -----------------
|
||||
// 2nd. migrate global settings to user settings
|
||||
// 2nd. remove any non existing widget type
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
const sectionName = sections[s]
|
||||
const widgets = adapter.bar.widgets[sectionName]
|
||||
// Iterate backward through the widgets array, so it does not break when removing a widget
|
||||
for (var i = widgets.length - 1; i >= 0; i--) {
|
||||
var widget = widgets[i]
|
||||
if (!BarWidgetRegistry.hasWidget(widget.id)) {
|
||||
widgets.splice(i, 1)
|
||||
Logger.warn(`Settings`, `Deleted invalid widget ${widget.id}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------
|
||||
// 3nd. migrate global settings to user settings
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
const sectionName = sections[s]
|
||||
for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) {
|
||||
|
|
@ -105,60 +120,63 @@ Singleton {
|
|||
continue
|
||||
}
|
||||
|
||||
// Check that the widget was not previously migrated and skip if necessary
|
||||
const keys = Object.keys(widget)
|
||||
if (keys.length > 1) {
|
||||
continue
|
||||
if (upgradeWidget(widget)) {
|
||||
Logger.log("Settings", `Upgraded ${widget.id} widget:`, JSON.stringify(widget))
|
||||
}
|
||||
|
||||
migrateWidget(widget)
|
||||
Logger.log("Settings", JSON.stringify(widget))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
function migrateWidget(widget) {
|
||||
Logger.log("Settings", `Migrating '${widget.id}' widget`)
|
||||
function upgradeWidget(widget) {
|
||||
// Backup the widget definition before altering
|
||||
const widgetBefore = JSON.stringify(widget)
|
||||
|
||||
// Migrate old bar settings to proper per widget settings
|
||||
switch (widget.id) {
|
||||
case "ActiveWindow":
|
||||
widget.showIcon = adapter.bar.showActiveWindowIcon
|
||||
widget.showIcon = widget.showIcon !== undefined ? widget.showIcon : adapter.bar.showActiveWindowIcon
|
||||
break
|
||||
case "Battery":
|
||||
widget.alwaysShowPercentage = adapter.bar.alwaysShowBatteryPercentage
|
||||
break
|
||||
case "Brightness":
|
||||
widget.alwaysShowPercentage = BarWidgetRegistry.widgetMetadata[widget.id].alwaysShowPercentage
|
||||
widget.alwaysShowPercentage = widget.alwaysShowPercentage
|
||||
!== undefined ? widget.alwaysShowPercentage : adapter.bar.alwaysShowBatteryPercentage
|
||||
break
|
||||
case "Clock":
|
||||
widget.showDate = adapter.location.showDateWithClock
|
||||
widget.use12HourClock = adapter.location.use12HourClock
|
||||
widget.reverseDayMonth = adapter.location.reverseDayMonth
|
||||
widget.showSeconds = BarWidgetRegistry.widgetMetadata[widget.id].showSeconds
|
||||
widget.showDate = widget.showDate !== undefined ? widget.showDate : adapter.location.showDateWithClock
|
||||
widget.use12HourClock = widget.use12HourClock !== undefined ? widget.use12HourClock : adapter.location.use12HourClock
|
||||
widget.reverseDayMonth = widget.reverseDayMonth !== undefined ? widget.reverseDayMonth : adapter.location.reverseDayMonth
|
||||
break
|
||||
case "MediaMini":
|
||||
widget.showAlbumArt = adapter.audio.showMiniplayerAlbumArt
|
||||
widget.showVisualizer = adapter.audio.showMiniplayerCava
|
||||
widget.visualizerType = BarWidgetRegistry.widgetMetadata[widget.id].visualizerType
|
||||
break
|
||||
case "NotificationHistory":
|
||||
widget.showUnreadBadge = BarWidgetRegistry.widgetMetadata[widget.id].showUnreadBadge
|
||||
widget.hideWhenZero = BarWidgetRegistry.widgetMetadata[widget.id].hideWhenZero
|
||||
widget.showAlbumArt = widget.showAlbumArt !== undefined ? widget.showAlbumArt : adapter.audio.showMiniplayerAlbumArt
|
||||
widget.showVisualizer = widget.showVisualizer !== undefined ? widget.showVisualizer : adapter.audio.showMiniplayerCava
|
||||
break
|
||||
case "SidePanelToggle":
|
||||
widget.useDistroLogo = adapter.bar.useDistroLogo
|
||||
widget.useDistroLogo = widget.useDistroLogo !== undefined ? widget.useDistroLogo : adapter.bar.useDistroLogo
|
||||
break
|
||||
case "SystemMonitor":
|
||||
widget.showNetworkStats = adapter.bar.showNetworkStats
|
||||
break
|
||||
case "Volume":
|
||||
widget.alwaysShowPercentage = BarWidgetRegistry.widgetMetadata[widget.id].alwaysShowPercentage
|
||||
widget.showNetworkStats = widget.showNetworkStats !== undefined ? widget.showNetworkStats : adapter.bar.showNetworkStats
|
||||
break
|
||||
case "Workspace":
|
||||
widget.labelMode = adapter.bar.showWorkspaceLabel
|
||||
widget.labelMode = widget.labelMode !== undefined ? widget.labelMode : adapter.bar.showWorkspaceLabel
|
||||
break
|
||||
}
|
||||
|
||||
// Inject missing default setting (metaData) from BarWidgetRegistry
|
||||
const keys = Object.keys(BarWidgetRegistry.widgetMetadata[widget.id])
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
const k = keys[i]
|
||||
if (k === "id" || k === "allowUserSettings") {
|
||||
continue
|
||||
}
|
||||
|
||||
if (widget[k] === undefined) {
|
||||
widget[k] = BarWidgetRegistry.widgetMetadata[widget.id][k]
|
||||
}
|
||||
}
|
||||
|
||||
// Backup the widget definition before altering
|
||||
const widgetAfter = JSON.stringify(widget)
|
||||
return (widgetAfter !== widgetBefore)
|
||||
}
|
||||
// -----------------------------------------------------
|
||||
// Kickoff essential services
|
||||
|
|
@ -175,6 +193,8 @@ Singleton {
|
|||
FontService.init()
|
||||
|
||||
HooksService.init()
|
||||
|
||||
BluetoothService.init()
|
||||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
|
|
@ -200,14 +220,15 @@ Singleton {
|
|||
|
||||
FileView {
|
||||
id: settingsFileView
|
||||
path: directoriesCreated ? settingsFile : ""
|
||||
path: directoriesCreated ? settingsFile : undefined
|
||||
printErrors: false
|
||||
watchChanges: true
|
||||
onFileChanged: reload()
|
||||
onAdapterUpdated: saveTimer.start()
|
||||
|
||||
// Trigger initial load when path changes from empty to actual path
|
||||
onPathChanged: {
|
||||
if (path === settingsFile) {
|
||||
if (path !== undefined) {
|
||||
reload()
|
||||
}
|
||||
}
|
||||
|
|
@ -215,7 +236,6 @@ Singleton {
|
|||
if (!isLoaded) {
|
||||
Logger.log("Settings", "----------------------------")
|
||||
Logger.log("Settings", "Settings loaded successfully")
|
||||
isLoaded = true
|
||||
|
||||
upgradeSettingsData()
|
||||
|
||||
|
|
@ -223,6 +243,8 @@ Singleton {
|
|||
|
||||
kickOffServices()
|
||||
|
||||
isLoaded = true
|
||||
|
||||
// Emit the signal
|
||||
root.settingsLoaded()
|
||||
}
|
||||
|
|
@ -335,7 +357,6 @@ Singleton {
|
|||
property int transitionDuration: 1500 // 1500 ms
|
||||
property string transitionType: "random"
|
||||
property real transitionEdgeSmoothness: 0.05
|
||||
property string defaultWallpaper: root.defaultWallpaper
|
||||
property list<var> monitors: []
|
||||
}
|
||||
|
||||
|
|
@ -422,6 +443,7 @@ Singleton {
|
|||
// night light
|
||||
property JsonObject nightLight: JsonObject {
|
||||
property bool enabled: false
|
||||
property bool forced: false
|
||||
property bool autoSchedule: true
|
||||
property string nightTemp: "4000"
|
||||
property string dayTemp: "6500"
|
||||
|
|
|
|||
|
|
@ -57,10 +57,10 @@ Singleton {
|
|||
property real opacityFull: 1.0
|
||||
|
||||
// Animation duration (ms)
|
||||
property int animationFast: Math.round(150 * Settings.data.general.animationSpeed)
|
||||
property int animationNormal: Math.round(300 * Settings.data.general.animationSpeed)
|
||||
property int animationSlow: Math.round(450 * Settings.data.general.animationSpeed)
|
||||
property int animationSlowest: Math.round(750 * Settings.data.general.animationSpeed)
|
||||
property int animationFast: Math.round(150 / Settings.data.general.animationSpeed)
|
||||
property int animationNormal: Math.round(300 / Settings.data.general.animationSpeed)
|
||||
property int animationSlow: Math.round(450 / Settings.data.general.animationSpeed)
|
||||
property int animationSlowest: Math.round(750 / Settings.data.general.animationSpeed)
|
||||
|
||||
// Dimensions
|
||||
property int barHeight: 36
|
||||
|
|
|
|||
|
|
@ -1,527 +0,0 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
NPanel {
|
||||
id: root
|
||||
panelWidth: 380 * scaling
|
||||
panelHeight: 500 * scaling
|
||||
panelAnchorRight: true
|
||||
|
||||
panelContent: Rectangle {
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusL * scaling
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL * scaling
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Header
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
NIcon {
|
||||
text: "system_update_alt"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "System Updates"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Reset button (only show if update failed)
|
||||
NIconButton {
|
||||
visible: ArchUpdaterService.updateFailed
|
||||
icon: "refresh"
|
||||
tooltipText: "Reset update state"
|
||||
sizeRatio: 0.8
|
||||
colorBg: Color.mError
|
||||
colorFg: Color.mOnError
|
||||
onClicked: {
|
||||
ArchUpdaterService.resetUpdateState()
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
tooltipText: "Close"
|
||||
sizeRatio: 0.8
|
||||
onClicked: root.close()
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Update summary (only show when packages are available and terminal is configured)
|
||||
NText {
|
||||
visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed
|
||||
&& !ArchUpdaterService.checkFailed && !ArchUpdaterService.aurBusy
|
||||
&& ArchUpdaterService.totalUpdates > 0 && ArchUpdaterService.terminalAvailable
|
||||
&& ArchUpdaterService.aurHelperAvailable
|
||||
text: ArchUpdaterService.totalUpdates + " package" + (ArchUpdaterService.totalUpdates !== 1 ? "s" : "") + " can be updated"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Package selection info (only show when not updating and have packages and terminal is configured)
|
||||
NText {
|
||||
visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed
|
||||
&& !ArchUpdaterService.checkFailed && !ArchUpdaterService.aurBusy
|
||||
&& ArchUpdaterService.totalUpdates > 0 && ArchUpdaterService.terminalAvailable
|
||||
&& ArchUpdaterService.aurHelperAvailable
|
||||
text: ArchUpdaterService.selectedPackagesCount + " of " + ArchUpdaterService.totalUpdates + " packages selected"
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Update in progress state
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
visible: ArchUpdaterService.updateInProgress
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
} // Spacer
|
||||
|
||||
NIcon {
|
||||
text: "hourglass_empty"
|
||||
font.pointSize: Style.fontSizeXXXL * scaling
|
||||
color: Color.mPrimary
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Update in progress"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
color: Color.mOnSurface
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Please check your terminal window for update progress and prompts."
|
||||
font.pointSize: Style.fontSizeNormal * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.Wrap
|
||||
Layout.maximumWidth: 280 * scaling
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
} // Spacer
|
||||
}
|
||||
|
||||
// Terminal not available state
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
visible: !ArchUpdaterService.terminalAvailable && !ArchUpdaterService.updateInProgress
|
||||
&& !ArchUpdaterService.updateFailed
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
NIcon {
|
||||
text: "terminal"
|
||||
font.pointSize: Style.fontSizeXXXL * scaling
|
||||
color: Color.mError
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Terminal not configured"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
color: Color.mOnSurface
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "The TERMINAL environment variable is not set. Please set it to your preferred terminal (e.g., kitty, alacritty, foot) in your shell configuration."
|
||||
font.pointSize: Style.fontSizeNormal * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.Wrap
|
||||
Layout.maximumWidth: 280 * scaling
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AUR helper not available state
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
visible: ArchUpdaterService.terminalAvailable && !ArchUpdaterService.aurHelperAvailable
|
||||
&& !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed
|
||||
&& !ArchUpdaterService.checkFailed && !ArchUpdaterService.aurBusy
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
NIcon {
|
||||
text: "package"
|
||||
font.pointSize: Style.fontSizeXXXL * scaling
|
||||
color: Color.mError
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "AUR helper not found"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
color: Color.mOnSurface
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "No AUR helper (yay or paru) is installed. Please install either yay or paru to manage AUR packages. yay is recommended."
|
||||
font.pointSize: Style.fontSizeNormal * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.Wrap
|
||||
Layout.maximumWidth: 280 * scaling
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check failed state (AUR down, network issues, etc.)
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
visible: ArchUpdaterService.checkFailed && !ArchUpdaterService.updateInProgress
|
||||
&& !ArchUpdaterService.updateFailed && ArchUpdaterService.terminalAvailable
|
||||
&& ArchUpdaterService.aurHelperAvailable
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
NIcon {
|
||||
text: "error"
|
||||
font.pointSize: Style.fontSizeXXXL * scaling
|
||||
color: Color.mError
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Cannot check for updates"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
color: Color.mOnSurface
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: ArchUpdaterService.lastCheckError
|
||||
|| "AUR helper is unavailable or network connection failed. This could be due to AUR being down, network issues, or missing AUR helper (yay/paru)."
|
||||
font.pointSize: Style.fontSizeNormal * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.Wrap
|
||||
Layout.maximumWidth: 280 * scaling
|
||||
}
|
||||
|
||||
// Prominent refresh button
|
||||
NIconButton {
|
||||
icon: "refresh"
|
||||
tooltipText: "Try checking again"
|
||||
sizeRatio: 1.2
|
||||
colorBg: Color.mPrimary
|
||||
colorFg: Color.mOnPrimary
|
||||
onClicked: {
|
||||
ArchUpdaterService.forceRefresh()
|
||||
}
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: Style.marginL * scaling
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update failed state
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
visible: ArchUpdaterService.updateFailed
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
NIcon {
|
||||
text: "error_outline"
|
||||
font.pointSize: Style.fontSizeXXXL * scaling
|
||||
color: Color.mError
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Update failed"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
color: Color.mOnSurface
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Check your terminal for error details and try again."
|
||||
font.pointSize: Style.fontSizeNormal * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.Wrap
|
||||
Layout.maximumWidth: 280 * scaling
|
||||
}
|
||||
|
||||
// Prominent refresh button
|
||||
NIconButton {
|
||||
icon: "refresh"
|
||||
tooltipText: "Refresh and try again"
|
||||
sizeRatio: 1.2
|
||||
colorBg: Color.mPrimary
|
||||
colorFg: Color.mOnPrimary
|
||||
onClicked: {
|
||||
ArchUpdaterService.resetUpdateState()
|
||||
}
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: Style.marginL * scaling
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No updates available state
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed
|
||||
&& !ArchUpdaterService.checkFailed && !ArchUpdaterService.aurBusy
|
||||
&& ArchUpdaterService.totalUpdates === 0 && ArchUpdaterService.terminalAvailable
|
||||
&& ArchUpdaterService.aurHelperAvailable
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
NIcon {
|
||||
text: "check_circle"
|
||||
font.pointSize: Style.fontSizeXXXL * scaling
|
||||
color: Color.mPrimary
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "System is up to date"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
color: Color.mOnSurface
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "All packages are current. Check back later for updates."
|
||||
font.pointSize: Style.fontSizeNormal * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.Wrap
|
||||
Layout.maximumWidth: 280 * scaling
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Checking for updates state
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
visible: ArchUpdaterService.aurBusy && !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed
|
||||
&& ArchUpdaterService.terminalAvailable && ArchUpdaterService.aurHelperAvailable
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
NBusyIndicator {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
size: Style.fontSizeXXXL * scaling
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Checking for updates"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
color: Color.mOnSurface
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Scanning package databases for available updates..."
|
||||
font.pointSize: Style.fontSizeNormal * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.Wrap
|
||||
Layout.maximumWidth: 280 * scaling
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Package list (only show when not in any special state)
|
||||
NBox {
|
||||
visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed
|
||||
&& !ArchUpdaterService.checkFailed && !ArchUpdaterService.aurBusy
|
||||
&& ArchUpdaterService.totalUpdates > 0 && ArchUpdaterService.terminalAvailable
|
||||
&& ArchUpdaterService.aurHelperAvailable
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
// Combine repo and AUR lists in order: repos first, then AUR
|
||||
property var items: (ArchUpdaterService.repoPackages || []).concat(ArchUpdaterService.aurPackages || [])
|
||||
|
||||
ListView {
|
||||
id: unifiedList
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM * scaling
|
||||
cacheBuffer: Math.round(300 * scaling)
|
||||
clip: true
|
||||
|
||||
model: parent.items
|
||||
delegate: Rectangle {
|
||||
width: unifiedList.width
|
||||
height: 44 * scaling
|
||||
color: Color.transparent
|
||||
radius: Style.radiusS * scaling
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
// Checkbox for selection
|
||||
NCheckbox {
|
||||
id: checkbox
|
||||
label: ""
|
||||
description: ""
|
||||
checked: ArchUpdaterService.isPackageSelected(modelData.name)
|
||||
baseSize: Math.max(Style.baseWidgetSize * 0.7, 14)
|
||||
onToggled: function (checked) {
|
||||
ArchUpdaterService.togglePackageSelection(modelData.name)
|
||||
// Force refresh of the checked property
|
||||
checkbox.checked = ArchUpdaterService.isPackageSelected(modelData.name)
|
||||
}
|
||||
}
|
||||
|
||||
// Package info
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginXXS * scaling
|
||||
|
||||
NText {
|
||||
text: modelData.name
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: modelData.oldVersion + " → " + modelData.newVersion
|
||||
font.pointSize: Style.fontSizeXXS * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
// Source tag (AUR vs PAC)
|
||||
Rectangle {
|
||||
visible: !!modelData.source
|
||||
radius: width * 0.5
|
||||
color: modelData.source === "aur" ? Color.mTertiary : Color.mSecondary
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
implicitHeight: Style.fontSizeS * 1.8 * scaling
|
||||
// Width based on label content + horizontal padding
|
||||
implicitWidth: badgeText.implicitWidth + Math.max(12 * scaling, Style.marginS * scaling)
|
||||
|
||||
NText {
|
||||
id: badgeText
|
||||
anchors.centerIn: parent
|
||||
text: modelData.source === "aur" ? "AUR" : "PAC"
|
||||
font.pointSize: Style.fontSizeXXS * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: modelData.source === "aur" ? Color.mOnTertiary : Color.mOnSecondary
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Action buttons (only show when not updating)
|
||||
RowLayout {
|
||||
visible: !ArchUpdaterService.updateInProgress && !ArchUpdaterService.updateFailed
|
||||
&& !ArchUpdaterService.checkFailed && ArchUpdaterService.terminalAvailable
|
||||
&& ArchUpdaterService.aurHelperAvailable
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
NIconButton {
|
||||
icon: "refresh"
|
||||
tooltipText: ArchUpdaterService.aurBusy ? "Checking for updates..." : (!ArchUpdaterService.canPoll ? "Refresh available soon" : "Refresh package lists")
|
||||
onClicked: {
|
||||
ArchUpdaterService.forceRefresh()
|
||||
}
|
||||
colorBg: Color.mSurfaceVariant
|
||||
colorFg: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
enabled: !ArchUpdaterService.aurBusy
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "system_update_alt"
|
||||
tooltipText: "Update all packages"
|
||||
enabled: ArchUpdaterService.totalUpdates > 0
|
||||
onClicked: {
|
||||
ArchUpdaterService.runUpdate()
|
||||
root.close()
|
||||
}
|
||||
colorBg: ArchUpdaterService.totalUpdates > 0 ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorFg: ArchUpdaterService.totalUpdates > 0 ? Color.mOnPrimary : Color.mOnSurfaceVariant
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "check_box"
|
||||
tooltipText: "Update selected packages"
|
||||
enabled: ArchUpdaterService.selectedPackagesCount > 0
|
||||
onClicked: {
|
||||
if (ArchUpdaterService.selectedPackagesCount > 0) {
|
||||
ArchUpdaterService.runSelectiveUpdate()
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
colorBg: ArchUpdaterService.selectedPackagesCount > 0 ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorFg: ArchUpdaterService.selectedPackagesCount > 0 ? Color.mOnPrimary : Color.mOnSurfaceVariant
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,8 +22,6 @@ Variants {
|
|||
// Internal state management
|
||||
property string transitionType: "fade"
|
||||
property real transitionProgress: 0
|
||||
// Scaling support for widgets that rely on it
|
||||
property real scaling: ScalingService.getScreenScale(screen)
|
||||
|
||||
readonly property real edgeSmoothness: Settings.data.wallpaper.transitionEdgeSmoothness
|
||||
readonly property var allTransitions: WallpaperService.allTransitions
|
||||
|
|
@ -91,15 +89,6 @@ Variants {
|
|||
left: true
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ScalingService
|
||||
function onScaleChanged(screenName, scale) {
|
||||
if ((screen !== null) && (screenName === screen.name)) {
|
||||
scaling = scale
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: debounceTimer
|
||||
interval: 333
|
||||
|
|
@ -150,7 +139,7 @@ Variants {
|
|||
property real screenWidth: width
|
||||
property real screenHeight: height
|
||||
|
||||
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_fade.frag.qsb")
|
||||
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/wp_fade.frag.qsb")
|
||||
}
|
||||
|
||||
// Wipe transition shader
|
||||
|
|
@ -175,7 +164,7 @@ Variants {
|
|||
property real screenWidth: width
|
||||
property real screenHeight: height
|
||||
|
||||
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_wipe.frag.qsb")
|
||||
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/wp_wipe.frag.qsb")
|
||||
}
|
||||
|
||||
// Disc reveal transition shader
|
||||
|
|
@ -202,7 +191,7 @@ Variants {
|
|||
property real screenWidth: width
|
||||
property real screenHeight: height
|
||||
|
||||
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_disc.frag.qsb")
|
||||
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/wp_disc.frag.qsb")
|
||||
}
|
||||
|
||||
// Diagonal stripes transition shader
|
||||
|
|
@ -229,7 +218,7 @@ Variants {
|
|||
property real screenWidth: width
|
||||
property real screenHeight: height
|
||||
|
||||
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_stripes.frag.qsb")
|
||||
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/wp_stripes.frag.qsb")
|
||||
}
|
||||
|
||||
// Animation for the transition progress
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ PopupWindow {
|
|||
}
|
||||
|
||||
NIcon {
|
||||
text: modelData?.hasChildren ? "menu" : ""
|
||||
icon: modelData?.hasChildren ? "menu" : ""
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
visible: modelData?.hasChildren ?? false
|
||||
|
|
|
|||
|
|
@ -33,28 +33,34 @@ RowLayout {
|
|||
|
||||
readonly property bool showIcon: (widgetSettings.showIcon !== undefined) ? widgetSettings.showIcon : widgetMetadata.showIcon
|
||||
|
||||
readonly property real minWidth: 160
|
||||
readonly property real maxWidth: 400
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: Style.marginS * scaling
|
||||
visible: getTitle() !== ""
|
||||
// 6% of total width
|
||||
readonly property real minWidth: Math.max(1, screen.width * 0.06)
|
||||
readonly property real maxWidth: minWidth * 2
|
||||
|
||||
function getTitle() {
|
||||
return CompositorService.focusedWindowTitle !== "(No active window)" ? CompositorService.focusedWindowTitle : ""
|
||||
}
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: Style.marginS * scaling
|
||||
visible: getTitle() !== ""
|
||||
|
||||
function getAppIcon() {
|
||||
// Try CompositorService first
|
||||
const focusedWindow = CompositorService.getFocusedWindow()
|
||||
if (focusedWindow && focusedWindow.appId) {
|
||||
return Icons.iconForAppId(focusedWindow.appId.toLowerCase())
|
||||
const idValue = focusedWindow.appId
|
||||
const normalizedId = (typeof idValue === 'string') ? idValue : String(idValue)
|
||||
return AppIcons.iconForAppId(normalizedId.toLowerCase())
|
||||
}
|
||||
|
||||
// Fallback to ToplevelManager
|
||||
if (ToplevelManager && ToplevelManager.activeToplevel) {
|
||||
const activeToplevel = ToplevelManager.activeToplevel
|
||||
if (activeToplevel.appId) {
|
||||
return Icons.iconForAppId(activeToplevel.appId.toLowerCase())
|
||||
const idValue2 = activeToplevel.appId
|
||||
const normalizedId2 = (typeof idValue2 === 'string') ? idValue2 : String(idValue2)
|
||||
return AppIcons.iconForAppId(normalizedId2.toLowerCase())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -123,7 +129,7 @@ RowLayout {
|
|||
font.weight: Style.fontWeightMedium
|
||||
elide: mouseArea.containsMouse ? Text.ElideNone : Text.ElideRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mSecondary
|
||||
color: Color.mPrimary
|
||||
clip: true
|
||||
|
||||
Behavior on Layout.preferredWidth {
|
||||
|
|
|
|||
|
|
@ -1,80 +0,0 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
NIconButton {
|
||||
id: root
|
||||
|
||||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
|
||||
sizeRatio: 0.8
|
||||
colorBg: Color.mSurfaceVariant
|
||||
colorBorder: Color.transparent
|
||||
colorBorderHover: Color.transparent
|
||||
colorFg: {
|
||||
if (!ArchUpdaterService.terminalAvailable || !ArchUpdaterService.aurHelperAvailable) {
|
||||
return Color.mError
|
||||
}
|
||||
return (ArchUpdaterService.totalUpdates === 0) ? Color.mOnSurface : Color.mPrimary
|
||||
}
|
||||
|
||||
// Icon states
|
||||
icon: {
|
||||
if (!ArchUpdaterService.terminalAvailable) {
|
||||
return "terminal"
|
||||
}
|
||||
if (!ArchUpdaterService.aurHelperAvailable) {
|
||||
return "package"
|
||||
}
|
||||
if (ArchUpdaterService.aurBusy) {
|
||||
return "sync"
|
||||
}
|
||||
if (ArchUpdaterService.totalUpdates > 0) {
|
||||
return "system_update_alt"
|
||||
}
|
||||
return "task_alt"
|
||||
}
|
||||
|
||||
// Tooltip with repo vs AUR breakdown and sample lists
|
||||
tooltipText: {
|
||||
if (!ArchUpdaterService.terminalAvailable) {
|
||||
return "Terminal not configured\nSet TERMINAL environment variable"
|
||||
}
|
||||
if (!ArchUpdaterService.aurHelperAvailable) {
|
||||
return "AUR helper not found\nInstall yay or paru"
|
||||
}
|
||||
if (ArchUpdaterService.aurBusy) {
|
||||
return "Checking for updates…"
|
||||
}
|
||||
|
||||
const total = ArchUpdaterService.totalUpdates
|
||||
if (total === 0) {
|
||||
return "System is up to date ✓"
|
||||
}
|
||||
let header = (total === 1) ? "1 package can be updated" : (total + " packages can be updated")
|
||||
|
||||
const pacCount = ArchUpdaterService.updates
|
||||
const aurCount = ArchUpdaterService.aurUpdates
|
||||
const pacmanTooltip = (pacCount > 0) ? ((pacCount === 1) ? "1 system package" : pacCount + " system packages") : ""
|
||||
const aurTooltip = (aurCount > 0) ? ((aurCount === 1) ? "1 AUR package" : aurCount + " AUR packages") : ""
|
||||
|
||||
let tooltip = header
|
||||
if (pacmanTooltip !== "") {
|
||||
tooltip += "\n" + pacmanTooltip
|
||||
}
|
||||
if (aurTooltip !== "") {
|
||||
tooltip += "\n" + aurTooltip
|
||||
}
|
||||
return tooltip
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
// Always allow panel to open, never block
|
||||
PanelService.getPanel("archUpdaterPanel").toggle(screen, this)
|
||||
}
|
||||
}
|
||||
|
|
@ -38,8 +38,8 @@ Item {
|
|||
|
||||
// Test mode
|
||||
readonly property bool testMode: false
|
||||
readonly property int testPercent: 50
|
||||
readonly property bool testCharging: true
|
||||
readonly property int testPercent: 90
|
||||
readonly property bool testCharging: false
|
||||
|
||||
// Main properties
|
||||
readonly property var battery: UPower.displayDevice
|
||||
|
|
@ -57,9 +57,7 @@ Item {
|
|||
// Only notify once we are a below threshold
|
||||
if (!charging && !root.hasNotifiedLowBattery && percent <= warningThreshold) {
|
||||
root.hasNotifiedLowBattery = true
|
||||
// Maybe go with toast ?
|
||||
Quickshell.execDetached(
|
||||
["notify-send", "-u", "critical", "-i", "battery-caution", "Low Battery", `Battery is at ${p}%. Please connect charger.`])
|
||||
ToastService.showWarning("Low Battery", `Battery is at ${Math.round(percent)}%. Please connect the charger.`)
|
||||
} else if (root.hasNotifiedLowBattery && (charging || percent > warningThreshold + 5)) {
|
||||
// Reset when charging starts or when battery recovers 5% above threshold
|
||||
root.hasNotifiedLowBattery = false
|
||||
|
|
@ -70,14 +68,20 @@ Item {
|
|||
Connections {
|
||||
target: UPower.displayDevice
|
||||
function onPercentageChanged() {
|
||||
root.maybeNotify(percent, charging)
|
||||
var currentPercent = UPower.displayDevice.percentage * 100
|
||||
var isCharging = UPower.displayDevice.state === UPowerDeviceState.Charging
|
||||
root.maybeNotify(currentPercent, isCharging)
|
||||
}
|
||||
|
||||
function onStateChanged() {
|
||||
var isCharging = UPower.displayDevice.state === UPowerDeviceState.Charging
|
||||
// Reset notification flag when charging starts
|
||||
if (charging) {
|
||||
if (isCharging) {
|
||||
root.hasNotifiedLowBattery = false
|
||||
}
|
||||
// Also re-evaluate maybeNotify, as state might have changed
|
||||
var currentPercent = UPower.displayDevice.percentage * 100
|
||||
root.maybeNotify(currentPercent, isCharging)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -87,11 +91,7 @@ Item {
|
|||
rightOpen: BarWidgetRegistry.getNPillDirection(root)
|
||||
icon: testMode ? BatteryService.getIcon(testPercent, testCharging, true) : BatteryService.getIcon(percent,
|
||||
charging, isReady)
|
||||
iconRotation: -90
|
||||
text: ((isReady && battery.isLaptopBattery) || testMode) ? Math.round(percent) + "%" : "-"
|
||||
textColor: charging ? Color.mPrimary : Color.mOnSurface
|
||||
iconCircleColor: Color.mPrimary
|
||||
collapsedIconColor: Color.mOnSurface
|
||||
text: (isReady || testMode) ? Math.round(percent) + "%" : "-"
|
||||
autoHide: false
|
||||
forceOpen: isReady && (testMode || battery.isLaptopBattery) && alwaysShowPercentage
|
||||
disableOpen: (!isReady || (!testMode && !battery.isLaptopBattery))
|
||||
|
|
|
|||
|
|
@ -13,14 +13,13 @@ NIconButton {
|
|||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
|
||||
visible: Settings.data.network.bluetoothEnabled
|
||||
sizeRatio: 0.8
|
||||
colorBg: Color.mSurfaceVariant
|
||||
colorFg: Color.mOnSurface
|
||||
colorBorder: Color.transparent
|
||||
colorBorderHover: Color.transparent
|
||||
|
||||
icon: "bluetooth"
|
||||
tooltipText: "Bluetooth"
|
||||
icon: Settings.data.network.bluetoothEnabled ? "bluetooth" : "bluetooth-off"
|
||||
tooltipText: "Bluetooth devices."
|
||||
onClicked: PanelService.getPanel("bluetoothPanel")?.toggle(screen, this)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,8 +46,7 @@ Item {
|
|||
function getIcon() {
|
||||
var monitor = getMonitor()
|
||||
var brightness = monitor ? monitor.brightness : 0
|
||||
return brightness <= 0 ? "brightness_1" : brightness < 0.33 ? "brightness_low" : brightness
|
||||
< 0.66 ? "brightness_medium" : "brightness_high"
|
||||
return brightness <= 0.5 ? "brightness-low" : "brightness-high"
|
||||
}
|
||||
|
||||
// Connection used to open the pill when brightness changes
|
||||
|
|
@ -80,8 +79,6 @@ Item {
|
|||
|
||||
rightOpen: BarWidgetRegistry.getNPillDirection(root)
|
||||
icon: getIcon()
|
||||
iconCircleColor: Color.mPrimary
|
||||
collapsedIconColor: Color.mOnSurface
|
||||
autoHide: false // Important to be false so we can hover as long as we want
|
||||
text: {
|
||||
var monitor = getMonitor()
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ Rectangle {
|
|||
anchors.centerIn: parent
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
||||
NTooltip {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,10 @@ NIconButton {
|
|||
readonly property string middleClickExec: widgetSettings.middleClickExec || widgetMetadata.middleClickExec
|
||||
readonly property bool hasExec: (leftClickExec || rightClickExec || middleClickExec)
|
||||
|
||||
enabled: hasExec
|
||||
allowClickWhenDisabled: true // we want to be able to open config with left click when its not setup properly
|
||||
colorBorder: Color.transparent
|
||||
colorBorderHover: Color.transparent
|
||||
sizeRatio: 0.8
|
||||
icon: customIcon
|
||||
tooltipText: {
|
||||
|
|
@ -57,7 +61,6 @@ NIconButton {
|
|||
return lines.join("<br/>")
|
||||
}
|
||||
}
|
||||
opacity: hasExec ? Style.opacityFull : Style.opacityMedium
|
||||
|
||||
onClicked: {
|
||||
if (leftClickExec) {
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ NIconButton {
|
|||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
|
||||
icon: "contrast"
|
||||
icon: "dark-mode"
|
||||
tooltipText: "Toggle light/dark mode"
|
||||
sizeRatio: 0.8
|
||||
|
||||
colorBg: Color.mSurfaceVariant
|
||||
colorFg: Color.mOnSurface
|
||||
colorBg: Settings.data.colorSchemes.darkMode ? Color.mSurfaceVariant : Color.mPrimary
|
||||
colorFg: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mOnPrimary
|
||||
colorBorder: Color.transparent
|
||||
colorBorderHover: Color.transparent
|
||||
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ NIconButton {
|
|||
|
||||
sizeRatio: 0.8
|
||||
|
||||
icon: "coffee"
|
||||
icon: IdleInhibitorService.isInhibited ? "keep-awake-on" : "keep-awake-off"
|
||||
tooltipText: IdleInhibitorService.isInhibited ? "Disable keep awake" : "Enable keep awake"
|
||||
colorBg: Color.mSurfaceVariant
|
||||
colorFg: IdleInhibitorService.isInhibited ? Color.mPrimary : Color.mOnSurface
|
||||
colorBg: IdleInhibitorService.isInhibited ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorFg: IdleInhibitorService.isInhibited ? Color.mOnPrimary : Color.mOnSurface
|
||||
colorBorder: Color.transparent
|
||||
onClicked: {
|
||||
IdleInhibitorService.manualToggle()
|
||||
|
|
|
|||
|
|
@ -24,9 +24,7 @@ Item {
|
|||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
rightOpen: BarWidgetRegistry.getNPillDirection(root)
|
||||
icon: "keyboard_alt"
|
||||
iconCircleColor: Color.mPrimary
|
||||
collapsedIconColor: Color.mOnSurface
|
||||
icon: "keyboard"
|
||||
autoHide: false // Important to be false so we can hover as long as we want
|
||||
text: currentLayout
|
||||
tooltipText: "Keyboard layout: " + currentLayout
|
||||
|
|
|
|||
|
|
@ -38,8 +38,9 @@ RowLayout {
|
|||
readonly property string visualizerType: (widgetSettings.visualizerType !== undefined && widgetSettings.visualizerType
|
||||
!== "") ? widgetSettings.visualizerType : widgetMetadata.visualizerType
|
||||
|
||||
readonly property real minWidth: 160
|
||||
readonly property real maxWidth: 400
|
||||
// 6% of total width
|
||||
readonly property real minWidth: Math.max(1, screen.width * 0.06)
|
||||
readonly property real maxWidth: minWidth * 2
|
||||
|
||||
function getTitle() {
|
||||
return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "")
|
||||
|
|
@ -134,7 +135,7 @@ RowLayout {
|
|||
|
||||
NIcon {
|
||||
id: windowIcon
|
||||
text: MediaService.isPlaying ? "pause" : "play_arrow"
|
||||
icon: MediaService.isPlaying ? "media-pause" : "media-play"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
|
@ -154,7 +155,8 @@ RowLayout {
|
|||
id: trackArt
|
||||
anchors.fill: parent
|
||||
imagePath: MediaService.trackArtUrl
|
||||
fallbackIcon: MediaService.isPlaying ? "pause" : "play_arrow"
|
||||
fallbackIcon: MediaService.isPlaying ? "media-pause" : "media-play"
|
||||
fallbackIconSize: 10 * scaling
|
||||
borderWidth: 0
|
||||
border.color: Color.transparent
|
||||
}
|
||||
|
|
@ -178,7 +180,7 @@ RowLayout {
|
|||
font.weight: Style.fontWeightMedium
|
||||
elide: Text.ElideRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mTertiary
|
||||
color: Color.mSecondary
|
||||
|
||||
Behavior on Layout.preferredWidth {
|
||||
NumberAnimation {
|
||||
|
|
|
|||
|
|
@ -43,9 +43,9 @@ Item {
|
|||
|
||||
function getIcon() {
|
||||
if (AudioService.inputMuted) {
|
||||
return "mic_off"
|
||||
return "microphone-mute"
|
||||
}
|
||||
return AudioService.inputVolume <= Number.EPSILON ? "mic_off" : (AudioService.inputVolume < 0.33 ? "mic" : "mic")
|
||||
return (AudioService.inputVolume <= Number.EPSILON) ? "microphone-mute" : "microphone"
|
||||
}
|
||||
|
||||
// Connection used to open the pill when input volume changes
|
||||
|
|
@ -92,8 +92,6 @@ Item {
|
|||
|
||||
rightOpen: BarWidgetRegistry.getNPillDirection(root)
|
||||
icon: getIcon()
|
||||
iconCircleColor: Color.mPrimary
|
||||
collapsedIconColor: Color.mOnSurface
|
||||
autoHide: false // Important to be false so we can hover as long as we want
|
||||
text: Math.floor(AudioService.inputVolume * 100) + "%"
|
||||
forceOpen: alwaysShowPercentage
|
||||
|
|
|
|||
|
|
@ -15,14 +15,24 @@ NIconButton {
|
|||
property real scaling: 1.0
|
||||
|
||||
sizeRatio: 0.8
|
||||
colorBg: Color.mSurfaceVariant
|
||||
colorFg: Color.mOnSurface
|
||||
colorBg: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? Color.mTertiary : Color.mPrimary) : Color.mSurfaceVariant
|
||||
colorFg: Settings.data.nightLight.enabled ? Color.mOnPrimary : Color.mOnSurface
|
||||
colorBorder: Color.transparent
|
||||
colorBorderHover: Color.transparent
|
||||
|
||||
icon: Settings.data.nightLight.enabled ? "bedtime" : "bedtime_off"
|
||||
tooltipText: `Night light: ${Settings.data.nightLight.enabled ? "enabled." : "disabled."}\nLeft click to toggle.\nRight click to access settings.`
|
||||
onClicked: Settings.data.nightLight.enabled = !Settings.data.nightLight.enabled
|
||||
icon: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? "nightlight-forced" : "nightlight-on") : "nightlight-off"
|
||||
tooltipText: `Night light: ${Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? "forced." : "enabled.") : "disabled."}\nLeft click to cycle (disabled → normal → forced).\nRight click to access settings.`
|
||||
onClicked: {
|
||||
if (!Settings.data.nightLight.enabled) {
|
||||
Settings.data.nightLight.enabled = true
|
||||
Settings.data.nightLight.forced = false
|
||||
} else if (Settings.data.nightLight.enabled && !Settings.data.nightLight.forced) {
|
||||
Settings.data.nightLight.forced = true
|
||||
} else {
|
||||
Settings.data.nightLight.enabled = false
|
||||
Settings.data.nightLight.forced = false
|
||||
}
|
||||
}
|
||||
|
||||
onRightClicked: {
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel")
|
||||
|
|
|
|||
|
|
@ -53,10 +53,10 @@ NIconButton {
|
|||
}
|
||||
|
||||
sizeRatio: 0.8
|
||||
icon: Settings.data.notifications.doNotDisturb ? "notifications_off" : "notifications"
|
||||
icon: Settings.data.notifications.doNotDisturb ? "bell-off" : "bell"
|
||||
tooltipText: Settings.data.notifications.doNotDisturb ? "Notification history.\nRight-click to disable 'Do Not Disturb'." : "Notification history.\nRight-click to enable 'Do Not Disturb'."
|
||||
colorBg: Color.mSurfaceVariant
|
||||
colorFg: Settings.data.notifications.doNotDisturb ? Color.mError : Color.mOnSurface
|
||||
colorFg: Color.mOnSurface
|
||||
colorBorder: Color.transparent
|
||||
colorBorderHover: Color.transparent
|
||||
|
||||
|
|
|
|||
|
|
@ -11,49 +11,43 @@ NIconButton {
|
|||
|
||||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
property var powerProfiles: PowerProfiles
|
||||
readonly property bool hasPP: powerProfiles.hasPerformanceProfile
|
||||
readonly property bool hasPP: PowerProfileService.available
|
||||
|
||||
sizeRatio: 0.8
|
||||
visible: hasPP
|
||||
|
||||
function profileIcon() {
|
||||
if (!hasPP)
|
||||
return "balance"
|
||||
if (powerProfiles.profile === PowerProfile.Performance)
|
||||
return "speed"
|
||||
if (powerProfiles.profile === PowerProfile.Balanced)
|
||||
return "balance"
|
||||
if (powerProfiles.profile === PowerProfile.PowerSaver)
|
||||
return "eco"
|
||||
return "balanced"
|
||||
if (PowerProfileService.profile === PowerProfile.Performance)
|
||||
return "performance"
|
||||
if (PowerProfileService.profile === PowerProfile.Balanced)
|
||||
return "balanced"
|
||||
if (PowerProfileService.profile === PowerProfile.PowerSaver)
|
||||
return "powersaver"
|
||||
}
|
||||
|
||||
function profileName() {
|
||||
if (!hasPP)
|
||||
return "Unknown"
|
||||
if (powerProfiles.profile === PowerProfile.Performance)
|
||||
if (PowerProfileService.profile === PowerProfile.Performance)
|
||||
return "Performance"
|
||||
if (powerProfiles.profile === PowerProfile.Balanced)
|
||||
if (PowerProfileService.profile === PowerProfile.Balanced)
|
||||
return "Balanced"
|
||||
if (powerProfiles.profile === PowerProfile.PowerSaver)
|
||||
if (PowerProfileService.profile === PowerProfile.PowerSaver)
|
||||
return "Power Saver"
|
||||
}
|
||||
|
||||
function changeProfile() {
|
||||
if (!hasPP)
|
||||
return
|
||||
if (powerProfiles.profile === PowerProfile.Performance)
|
||||
powerProfiles.profile = PowerProfile.PowerSaver
|
||||
else if (powerProfiles.profile === PowerProfile.Balanced)
|
||||
powerProfiles.profile = PowerProfile.Performance
|
||||
else if (powerProfiles.profile === PowerProfile.PowerSaver)
|
||||
powerProfiles.profile = PowerProfile.Balanced
|
||||
PowerProfileService.cycleProfile()
|
||||
}
|
||||
|
||||
icon: root.profileIcon()
|
||||
tooltipText: root.profileName()
|
||||
colorBg: Color.mSurfaceVariant
|
||||
colorFg: Color.mOnSurface
|
||||
colorBg: (PowerProfileService.profile === PowerProfile.Balanced) ? Color.mSurfaceVariant : Color.mPrimary
|
||||
colorFg: (PowerProfileService.profile === PowerProfile.Balanced) ? Color.mOnSurface : Color.mOnPrimary
|
||||
colorBorder: Color.transparent
|
||||
colorBorderHover: Color.transparent
|
||||
onClicked: root.changeProfile()
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ NIconButton {
|
|||
|
||||
sizeRatio: 0.8
|
||||
|
||||
icon: "power_settings_new"
|
||||
icon: "power"
|
||||
tooltipText: "Power Settings"
|
||||
colorBg: Color.mSurfaceVariant
|
||||
colorFg: Color.mError
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ NIconButton {
|
|||
property real scaling: 1.0
|
||||
|
||||
visible: ScreenRecorderService.isRecording
|
||||
icon: "videocam"
|
||||
icon: "camera-video"
|
||||
tooltipText: "Screen recording is active\nClick to stop recording"
|
||||
sizeRatio: 0.8
|
||||
colorBg: Color.mPrimary
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ NIconButton {
|
|||
readonly property bool useDistroLogo: (widgetSettings.useDistroLogo
|
||||
!== undefined) ? widgetSettings.useDistroLogo : widgetMetadata.useDistroLogo
|
||||
|
||||
icon: useDistroLogo ? "" : "widgets"
|
||||
icon: useDistroLogo ? "" : "apps"
|
||||
tooltipText: "Open side panel."
|
||||
sizeRatio: 0.8
|
||||
|
||||
|
|
|
|||
|
|
@ -38,12 +38,4 @@ Item {
|
|||
implicitHeight: Style.barHeight * scaling
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
// Optional: Add a subtle visual indicator in debug mode
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(1, 0, 0, 0.1) // Very subtle red tint
|
||||
visible: Settings.data.general.debugMode || false
|
||||
radius: Style.radiusXXS * scaling
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,10 @@ RowLayout {
|
|||
!== undefined) ? widgetSettings.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent
|
||||
readonly property bool showNetworkStats: (widgetSettings.showNetworkStats
|
||||
!== undefined) ? widgetSettings.showNetworkStats : widgetMetadata.showNetworkStats
|
||||
readonly property bool showDiskUsage: (widgetSettings.showDiskUsage
|
||||
!== undefined) ? widgetSettings.showDiskUsage : widgetMetadata.showDiskUsage
|
||||
readonly property bool showGpuTemp: (widgetSettings.showGpuTemp !== undefined) ? widgetSettings.showGpuTemp : (widgetMetadata.showGpuTemp
|
||||
|| false)
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: Style.marginS * scaling
|
||||
|
|
@ -52,126 +56,218 @@ RowLayout {
|
|||
|
||||
RowLayout {
|
||||
id: mainLayout
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Style.marginS * scaling
|
||||
anchors.rightMargin: Style.marginS * scaling
|
||||
anchors.centerIn: parent // Better centering than margins
|
||||
width: parent.width - Style.marginM * scaling * 2
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
// CPU Usage Component
|
||||
RowLayout {
|
||||
id: cpuUsageLayout
|
||||
spacing: Style.marginXS * scaling
|
||||
Item {
|
||||
Layout.preferredWidth: cpuUsageRow.implicitWidth
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showCpuUsage
|
||||
|
||||
NIcon {
|
||||
id: cpuUsageIcon
|
||||
text: "speed"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
RowLayout {
|
||||
id: cpuUsageRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXS * scaling
|
||||
|
||||
NText {
|
||||
id: cpuUsageText
|
||||
text: `${SystemStatService.cpuUsage}%`
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
NIcon {
|
||||
icon: "cpu-usage"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: `${SystemStatService.cpuUsage}%`
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CPU Temperature Component
|
||||
RowLayout {
|
||||
id: cpuTempLayout
|
||||
// spacing is thin here to compensate for the vertical thermometer icon
|
||||
spacing: Style.marginXXS * scaling
|
||||
Item {
|
||||
Layout.preferredWidth: cpuTempRow.implicitWidth
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showCpuTemp
|
||||
|
||||
NIcon {
|
||||
text: "thermometer"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
RowLayout {
|
||||
id: cpuTempRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXS * scaling
|
||||
|
||||
NText {
|
||||
text: `${SystemStatService.cpuTemp}°C`
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
NIcon {
|
||||
icon: "cpu-temperature"
|
||||
// Fire is so tall, we need to make it smaller
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: `${SystemStatService.cpuTemp}°C`
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GPU Temperature Component
|
||||
Item {
|
||||
Layout.preferredWidth: gpuTempRow.implicitWidth
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showGpuTemp
|
||||
|
||||
RowLayout {
|
||||
id: gpuTempRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXS * scaling
|
||||
|
||||
NIcon {
|
||||
icon: "gpu-temperature"
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: `${SystemStatService.gpuTemp}°C`
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Memory Usage Component
|
||||
RowLayout {
|
||||
id: memoryUsageLayout
|
||||
spacing: Style.marginXS * scaling
|
||||
Item {
|
||||
Layout.preferredWidth: memoryUsageRow.implicitWidth
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showMemoryUsage
|
||||
|
||||
NIcon {
|
||||
text: "memory"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
RowLayout {
|
||||
id: memoryUsageRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXS * scaling
|
||||
|
||||
NText {
|
||||
text: showMemoryAsPercent ? `${SystemStatService.memPercent}%` : `${SystemStatService.memGb}G`
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
NIcon {
|
||||
icon: "memory"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: showMemoryAsPercent ? `${SystemStatService.memPercent}%` : `${SystemStatService.memGb}G`
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Network Download Speed Component
|
||||
RowLayout {
|
||||
id: networkDownloadLayout
|
||||
spacing: Style.marginXS * scaling
|
||||
Item {
|
||||
Layout.preferredWidth: networkDownloadRow.implicitWidth
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showNetworkStats
|
||||
|
||||
NIcon {
|
||||
text: "download"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
RowLayout {
|
||||
id: networkDownloadRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXS * scaling
|
||||
|
||||
NText {
|
||||
text: SystemStatService.formatSpeed(SystemStatService.rxSpeed)
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
NIcon {
|
||||
icon: "download-speed"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: SystemStatService.formatSpeed(SystemStatService.rxSpeed)
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Network Upload Speed Component
|
||||
RowLayout {
|
||||
id: networkUploadLayout
|
||||
spacing: Style.marginXS * scaling
|
||||
Item {
|
||||
Layout.preferredWidth: networkUploadRow.implicitWidth
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showNetworkStats
|
||||
|
||||
NIcon {
|
||||
text: "upload"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
RowLayout {
|
||||
id: networkUploadRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXS * scaling
|
||||
|
||||
NText {
|
||||
text: SystemStatService.formatSpeed(SystemStatService.txSpeed)
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
NIcon {
|
||||
icon: "upload-speed"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: SystemStatService.formatSpeed(SystemStatService.txSpeed)
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Disk Usage Component (primary drive)
|
||||
Item {
|
||||
Layout.preferredWidth: diskUsageRow.implicitWidth
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showDiskUsage
|
||||
|
||||
RowLayout {
|
||||
id: diskUsageRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXS * scaling
|
||||
|
||||
NIcon {
|
||||
icon: "storage"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: `${SystemStatService.diskPercent}%`
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ Rectangle {
|
|||
anchors.centerIn: parent
|
||||
width: Style.marginL * root.scaling
|
||||
height: Style.marginL * root.scaling
|
||||
source: Icons.iconForAppId(taskbarItem.modelData.appId)
|
||||
source: AppIcons.iconForAppId(taskbarItem.modelData.appId)
|
||||
smooth: true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,9 +43,9 @@ Item {
|
|||
|
||||
function getIcon() {
|
||||
if (AudioService.muted) {
|
||||
return "volume_off"
|
||||
return "volume-mute"
|
||||
}
|
||||
return AudioService.volume <= Number.EPSILON ? "volume_off" : (AudioService.volume < 0.33 ? "volume_down" : "volume_up")
|
||||
return (AudioService.volume <= Number.EPSILON) ? "volume-zero" : (AudioService.volume <= 0.5) ? "volume-low" : "volume-high"
|
||||
}
|
||||
|
||||
// Connection used to open the pill when volume changes
|
||||
|
|
@ -77,8 +77,6 @@ Item {
|
|||
|
||||
rightOpen: BarWidgetRegistry.getNPillDirection(root)
|
||||
icon: getIcon()
|
||||
iconCircleColor: Color.mPrimary
|
||||
collapsedIconColor: Color.mOnSurface
|
||||
autoHide: false // Important to be false so we can hover as long as we want
|
||||
text: Math.floor(AudioService.volume * 100) + "%"
|
||||
forceOpen: alwaysShowPercentage
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ NIconButton {
|
|||
icon: {
|
||||
try {
|
||||
if (NetworkService.ethernetConnected) {
|
||||
return "lan"
|
||||
return "ethernet"
|
||||
}
|
||||
let connected = false
|
||||
let signalStrength = 0
|
||||
|
|
@ -34,7 +34,7 @@ NIconButton {
|
|||
break
|
||||
}
|
||||
}
|
||||
return connected ? NetworkService.signalIcon(signalStrength) : "wifi_find"
|
||||
return connected ? NetworkService.signalIcon(signalStrength) : "wifi-off"
|
||||
} catch (error) {
|
||||
Logger.error("Wi-Fi", "Error getting icon:", error)
|
||||
return "signal_wifi_bad"
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ ColumnLayout {
|
|||
|
||||
// One device BT icon
|
||||
NIcon {
|
||||
text: BluetoothService.getDeviceIcon(modelData)
|
||||
icon: BluetoothService.getDeviceIcon(modelData)
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
color: getContentColor(Color.mOnSurface)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
|
@ -164,7 +164,7 @@ ColumnLayout {
|
|||
}
|
||||
return "Connect"
|
||||
}
|
||||
icon: (isBusy ? "hourglass_full" : null)
|
||||
icon: (isBusy ? "hourglass-split" : null)
|
||||
onClicked: {
|
||||
if (modelData.connected) {
|
||||
BluetoothService.disconnectDevice(modelData)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ NPanel {
|
|||
spacing: Style.marginM * scaling
|
||||
|
||||
NIcon {
|
||||
text: "bluetooth"
|
||||
icon: "bluetooth"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
|
@ -41,8 +41,16 @@ NPanel {
|
|||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: wifiSwitch
|
||||
checked: Settings.data.network.bluetoothEnabled
|
||||
onToggled: checked => BluetoothService.setBluetoothEnabled(checked)
|
||||
baseSize: Style.baseWidgetSize * 0.65 * scaling
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: BluetoothService.adapter && BluetoothService.adapter.discovering ? "stop_circle" : "refresh"
|
||||
enabled: Settings.data.network.bluetoothEnabled
|
||||
icon: BluetoothService.adapter && BluetoothService.adapter.discovering ? "stop" : "refresh"
|
||||
tooltipText: "Refresh Devices"
|
||||
sizeRatio: 0.8
|
||||
onClicked: {
|
||||
|
|
@ -66,7 +74,42 @@ NPanel {
|
|||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: !Settings.data.network.bluetoothEnabled
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
color: Color.transparent
|
||||
|
||||
// Center the content within this rectangle
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
NIcon {
|
||||
icon: "bluetooth-off"
|
||||
font.pointSize: 64 * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Bluetooth is disabled"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Enable Bluetooth to see available devices."
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
|
@ -75,7 +118,6 @@ NPanel {
|
|||
contentWidth: availableWidth
|
||||
|
||||
ColumnLayout {
|
||||
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
||||
width: parent.width
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
|
|
@ -146,7 +188,7 @@ NPanel {
|
|||
spacing: Style.marginXS * scaling
|
||||
|
||||
NIcon {
|
||||
text: "sync"
|
||||
icon: "refresh"
|
||||
font.pointSize: Style.fontSizeXXL * 1.5 * scaling
|
||||
color: Color.mPrimary
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ NPanel {
|
|||
spacing: Style.marginS * scaling
|
||||
|
||||
NIconButton {
|
||||
icon: "chevron_left"
|
||||
icon: "chevron-left"
|
||||
tooltipText: "Previous month"
|
||||
onClicked: {
|
||||
let newDate = new Date(grid.year, grid.month - 1, 1)
|
||||
|
|
@ -47,7 +47,7 @@ NPanel {
|
|||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "chevron_right"
|
||||
icon: "chevron-right"
|
||||
tooltipText: "Next month"
|
||||
onClicked: {
|
||||
let newDate = new Date(grid.year, grid.month + 1, 1)
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ Variants {
|
|||
function getAppIcon(toplevel: Toplevel): string {
|
||||
if (!toplevel)
|
||||
return ""
|
||||
return Icons.iconForAppId(toplevel.appId?.toLowerCase())
|
||||
return AppIcons.iconForAppId(toplevel.appId?.toLowerCase())
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
|
|
@ -256,11 +256,10 @@ Variants {
|
|||
}
|
||||
|
||||
// Fall back if no icon
|
||||
NText {
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
visible: !appIcon.visible
|
||||
text: "question_mark"
|
||||
font.family: "Material Symbols Rounded"
|
||||
icon: "question-mark"
|
||||
font.pointSize: iconSize * 0.7
|
||||
color: appButton.isActive ? Color.mPrimary : Color.mOnSurfaceVariant
|
||||
scale: appButton.hovered ? 1.15 : 1.0
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ Item {
|
|||
IpcHandler {
|
||||
target: "screenRecorder"
|
||||
function toggle() {
|
||||
ScreenRecorderService.toggleRecording()
|
||||
if (ScreenRecorderService.isAvailable) {
|
||||
ScreenRecorderService.toggleRecording()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -404,7 +404,7 @@ NPanel {
|
|||
sourceComponent: Component {
|
||||
IconImage {
|
||||
anchors.fill: parent
|
||||
source: modelData.icon ? Icons.iconFromName(modelData.icon, "application-x-executable") : ""
|
||||
source: modelData.icon ? AppIcons.iconFromName(modelData.icon, "application-x-executable") : ""
|
||||
visible: modelData.icon && source !== ""
|
||||
asynchronous: true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ Item {
|
|||
const items = ClipboardService.items || []
|
||||
|
||||
// If no items and we haven't tried loading yet, trigger a load
|
||||
if (items.length === 0 && !ClipboardService.loading) {
|
||||
if (items.count === 0 && !ClipboardService.loading) {
|
||||
isWaitingForData = true
|
||||
ClipboardService.list(100)
|
||||
return [{
|
||||
|
|
|
|||
|
|
@ -418,7 +418,7 @@ Loader {
|
|||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
NIcon {
|
||||
text: "keyboard_alt"
|
||||
icon: "keyboard"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
|
@ -428,7 +428,7 @@ Loader {
|
|||
spacing: Style.marginS * scaling
|
||||
visible: batteryIndicator.batteryVisible
|
||||
NIcon {
|
||||
text: BatteryService.getIcon(batteryIndicator.percent, batteryIndicator.charging,
|
||||
icon: BatteryService.getIcon(batteryIndicator.percent, batteryIndicator.charging,
|
||||
batteryIndicator.isReady)
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
color: batteryIndicator.charging ? Color.mPrimary : Color.mOnSurface
|
||||
|
|
@ -718,21 +718,47 @@ Loader {
|
|||
anchors.margins: 50 * scaling
|
||||
spacing: 20 * scaling
|
||||
|
||||
// Shutdown
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 60 * scaling
|
||||
Layout.preferredHeight: 60 * scaling
|
||||
Layout.preferredWidth: iconPower.implicitWidth + Style.marginXL * scaling
|
||||
Layout.preferredHeight: Layout.preferredWidth
|
||||
radius: width * 0.5
|
||||
color: powerButtonArea.containsMouse ? Color.mError : Qt.alpha(Color.mError, 0.2)
|
||||
border.color: Color.mError
|
||||
border.width: Math.max(1, Style.borderM * scaling)
|
||||
|
||||
NIcon {
|
||||
id: iconPower
|
||||
anchors.centerIn: parent
|
||||
text: "power_settings_new"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
icon: "shutdown"
|
||||
font.pointSize: Style.fontSizeXXXL * scaling
|
||||
color: powerButtonArea.containsMouse ? Color.mOnError : Color.mError
|
||||
}
|
||||
|
||||
// Tooltip (inline rectangle to avoid separate Window during lock)
|
||||
Rectangle {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.top
|
||||
anchors.bottomMargin: 12 * scaling
|
||||
radius: Style.radiusM * scaling
|
||||
color: Color.mSurface
|
||||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
visible: powerButtonArea.containsMouse
|
||||
z: 1
|
||||
NText {
|
||||
id: shutdownTooltipText
|
||||
anchors.margins: Style.marginM * scaling
|
||||
anchors.fill: parent
|
||||
text: "Shut down the computer."
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
implicitWidth: shutdownTooltipText.implicitWidth + Style.marginM * 2 * scaling
|
||||
implicitHeight: shutdownTooltipText.implicitHeight + Style.marginM * 2 * scaling
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: powerButtonArea
|
||||
anchors.fill: parent
|
||||
|
|
@ -743,21 +769,47 @@ Loader {
|
|||
}
|
||||
}
|
||||
|
||||
// Reboot
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 60 * scaling
|
||||
Layout.preferredHeight: 60 * scaling
|
||||
Layout.preferredWidth: iconReboot.implicitWidth + Style.marginXL * scaling
|
||||
Layout.preferredHeight: Layout.preferredWidth
|
||||
radius: width * 0.5
|
||||
color: restartButtonArea.containsMouse ? Color.mPrimary : Qt.alpha(Color.mPrimary, Style.opacityLight)
|
||||
border.color: Color.mPrimary
|
||||
border.width: Math.max(1, Style.borderM * scaling)
|
||||
|
||||
NIcon {
|
||||
id: iconReboot
|
||||
anchors.centerIn: parent
|
||||
text: "restart_alt"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
icon: "reboot"
|
||||
font.pointSize: Style.fontSizeXXXL * scaling
|
||||
color: restartButtonArea.containsMouse ? Color.mOnPrimary : Color.mPrimary
|
||||
}
|
||||
|
||||
// Tooltip
|
||||
Rectangle {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.top
|
||||
anchors.bottomMargin: 12 * scaling
|
||||
radius: Style.radiusM * scaling
|
||||
color: Color.mSurface
|
||||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
visible: restartButtonArea.containsMouse
|
||||
z: 1
|
||||
NText {
|
||||
id: restartTooltipText
|
||||
anchors.margins: Style.marginM * scaling
|
||||
anchors.fill: parent
|
||||
text: "Restart the computer."
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
implicitWidth: restartTooltipText.implicitWidth + Style.marginM * 2 * scaling
|
||||
implicitHeight: restartTooltipText.implicitHeight + Style.marginM * 2 * scaling
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: restartButtonArea
|
||||
anchors.fill: parent
|
||||
|
|
@ -765,24 +817,51 @@ Loader {
|
|||
onClicked: {
|
||||
CompositorService.reboot()
|
||||
}
|
||||
// Tooltip handled via inline rectangle visibility
|
||||
}
|
||||
}
|
||||
|
||||
// Suspend
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 60 * scaling
|
||||
Layout.preferredHeight: 60 * scaling
|
||||
Layout.preferredWidth: iconSuspend.implicitWidth + Style.marginXL * scaling
|
||||
Layout.preferredHeight: Layout.preferredWidth
|
||||
radius: width * 0.5
|
||||
color: suspendButtonArea.containsMouse ? Color.mSecondary : Qt.alpha(Color.mSecondary, 0.2)
|
||||
border.color: Color.mSecondary
|
||||
border.width: Math.max(1, Style.borderM * scaling)
|
||||
|
||||
NIcon {
|
||||
id: iconSuspend
|
||||
anchors.centerIn: parent
|
||||
text: "bedtime"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
icon: "suspend"
|
||||
font.pointSize: Style.fontSizeXXXL * scaling
|
||||
color: suspendButtonArea.containsMouse ? Color.mOnSecondary : Color.mSecondary
|
||||
}
|
||||
|
||||
// Tooltip
|
||||
Rectangle {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.top
|
||||
anchors.bottomMargin: 12 * scaling
|
||||
radius: Style.radiusM * scaling
|
||||
color: Color.mSurface
|
||||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
visible: suspendButtonArea.containsMouse
|
||||
z: 1
|
||||
NText {
|
||||
id: suspendTooltipText
|
||||
anchors.margins: Style.marginM * scaling
|
||||
anchors.fill: parent
|
||||
text: "Suspend the system."
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
implicitWidth: suspendTooltipText.implicitWidth + Style.marginM * 2 * scaling
|
||||
implicitHeight: suspendTooltipText.implicitHeight + Style.marginM * 2 * scaling
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: suspendButtonArea
|
||||
anchors.fill: parent
|
||||
|
|
@ -790,6 +869,7 @@ Loader {
|
|||
onClicked: {
|
||||
CompositorService.suspend()
|
||||
}
|
||||
// Tooltip handled via inline rectangle visibility
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,14 +205,12 @@ Variants {
|
|||
Layout.fillWidth: true
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Avatar
|
||||
// Image
|
||||
NImageCircled {
|
||||
id: appAvatar
|
||||
Layout.preferredWidth: 40 * scaling
|
||||
Layout.preferredHeight: 40 * scaling
|
||||
Layout.alignment: Qt.AlignTop
|
||||
imagePath: model.image && model.image !== "" ? model.image : ""
|
||||
fallbackIcon: ""
|
||||
borderColor: Color.transparent
|
||||
borderWidth: 0
|
||||
visible: (model.image && model.image !== "")
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ NPanel {
|
|||
spacing: Style.marginM * scaling
|
||||
|
||||
NIcon {
|
||||
text: "notifications"
|
||||
icon: "bell"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
|
@ -45,14 +45,15 @@ NPanel {
|
|||
}
|
||||
|
||||
NIconButton {
|
||||
icon: Settings.data.notifications.doNotDisturb ? "notifications_off" : "notifications_active"
|
||||
icon: Settings.data.notifications.doNotDisturb ? "bell-off" : "bell"
|
||||
tooltipText: Settings.data.notifications.doNotDisturb ? "'Do Not Disturb' is enabled." : "'Do Not Disturb' is disabled."
|
||||
sizeRatio: 0.8
|
||||
onClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb
|
||||
onRightClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "delete"
|
||||
icon: "trash"
|
||||
tooltipText: "Clear history"
|
||||
sizeRatio: 0.8
|
||||
onClicked: NotificationService.clearHistory()
|
||||
|
|
@ -85,7 +86,7 @@ NPanel {
|
|||
}
|
||||
|
||||
NIcon {
|
||||
text: "notifications_off"
|
||||
icon: "bell-off"
|
||||
font.pointSize: 64 * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
|
@ -103,6 +104,9 @@ NPanel {
|
|||
font.pointSize: Style.fontSizeS * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
|
|
@ -135,10 +139,29 @@ NPanel {
|
|||
anchors.margins: Style.marginM * scaling
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// App icon (same style as popup)
|
||||
NImageCircled {
|
||||
Layout.preferredWidth: 28 * scaling
|
||||
Layout.preferredHeight: 28 * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
// Prefer stable themed icons over transient image paths
|
||||
imagePath: (appIcon
|
||||
&& appIcon !== "") ? (AppIcons.iconFromName(appIcon, "application-x-executable")
|
||||
|| appIcon) : ((AppIcons.iconForAppId(desktopEntry
|
||||
|| appName, "application-x-executable")
|
||||
|| (image && image
|
||||
!== "" ? image : AppIcons.iconFromName("application-x-executable",
|
||||
"application-x-executable"))))
|
||||
borderColor: Color.transparent
|
||||
borderWidth: 0
|
||||
visible: true
|
||||
}
|
||||
|
||||
// Notification content column
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.maximumWidth: notificationList.width - (Style.marginM * scaling * 4) // Account for margins and delete button
|
||||
spacing: Style.marginXXS * scaling
|
||||
|
||||
NText {
|
||||
|
|
@ -148,7 +171,6 @@ NPanel {
|
|||
color: notificationMouseArea.containsMouse ? Color.mSurface : Color.mPrimary
|
||||
wrapMode: Text.Wrap
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: parent.width
|
||||
maximumLineCount: 2
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
|
@ -159,7 +181,6 @@ NPanel {
|
|||
color: notificationMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface
|
||||
wrapMode: Text.Wrap
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: parent.width
|
||||
maximumLineCount: 3
|
||||
elide: Text.ElideRight
|
||||
visible: text.length > 0
|
||||
|
|
@ -175,7 +196,7 @@ NPanel {
|
|||
|
||||
// Delete button
|
||||
NIconButton {
|
||||
icon: "delete"
|
||||
icon: "trash"
|
||||
tooltipText: "Delete notification"
|
||||
sizeRatio: 0.7
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
|
|
|||
|
|
@ -29,27 +29,27 @@ NPanel {
|
|||
property int selectedIndex: 0
|
||||
readonly property var powerOptions: [{
|
||||
"action": "lock",
|
||||
"icon": "lock_outline",
|
||||
"icon": "lock",
|
||||
"title": "Lock",
|
||||
"subtitle": "Lock your session"
|
||||
}, {
|
||||
"action": "suspend",
|
||||
"icon": "bedtime",
|
||||
"icon": "suspend",
|
||||
"title": "Suspend",
|
||||
"subtitle": "Put the system to sleep"
|
||||
}, {
|
||||
"action": "reboot",
|
||||
"icon": "refresh",
|
||||
"icon": "reboot",
|
||||
"title": "Reboot",
|
||||
"subtitle": "Restart the system"
|
||||
}, {
|
||||
"action": "logout",
|
||||
"icon": "exit_to_app",
|
||||
"icon": "logout",
|
||||
"title": "Logout",
|
||||
"subtitle": "End your session"
|
||||
}, {
|
||||
"action": "shutdown",
|
||||
"icon": "power_settings_new",
|
||||
"icon": "shutdown",
|
||||
"title": "Shutdown",
|
||||
"subtitle": "Turn off the system",
|
||||
"isShutdown": true
|
||||
|
|
@ -276,7 +276,7 @@ NPanel {
|
|||
}
|
||||
|
||||
NIconButton {
|
||||
icon: timerActive ? "back_hand" : "close"
|
||||
icon: timerActive ? "stop" : "close"
|
||||
tooltipText: timerActive ? "Cancel Timer" : "Close"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
colorBg: timerActive ? Qt.alpha(Color.mError, 0.08) : Color.transparent
|
||||
|
|
@ -360,7 +360,7 @@ NPanel {
|
|||
id: iconElement
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: buttonRoot.icon
|
||||
icon: buttonRoot.icon
|
||||
color: {
|
||||
if (buttonRoot.pending)
|
||||
return Color.mPrimary
|
||||
|
|
|
|||
|
|
@ -18,7 +18,11 @@ NBox {
|
|||
signal removeWidget(string section, int index)
|
||||
signal reorderWidget(string section, int fromIndex, int toIndex)
|
||||
signal updateWidgetSettings(string section, int index, var settings)
|
||||
signal dragPotentialStarted
|
||||
// Emitted when a widget is pressed (potential drag start)
|
||||
signal dragPotentialEnded
|
||||
|
||||
// Emitted when interaction ends (drag or click)
|
||||
color: Color.mSurface
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: {
|
||||
|
|
@ -105,13 +109,11 @@ NBox {
|
|||
}
|
||||
|
||||
// Drag and Drop Widget Area
|
||||
// Replace your Flow section with this:
|
||||
|
||||
// Drag and Drop Widget Area - use Item container
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumHeight: 65 * scaling
|
||||
clip: false // Don't clip children so ghost can move freely
|
||||
|
||||
Flow {
|
||||
id: widgetFlow
|
||||
|
|
@ -139,13 +141,18 @@ NBox {
|
|||
readonly property int buttonsCount: 1 + BarWidgetRegistry.widgetHasUserSettings(modelData.id)
|
||||
|
||||
// Visual feedback during drag
|
||||
states: State {
|
||||
when: flowDragArea.draggedIndex === index
|
||||
PropertyChanges {
|
||||
target: widgetItem
|
||||
scale: 1.1
|
||||
opacity: 0.9
|
||||
z: 1000
|
||||
opacity: flowDragArea.draggedIndex === index ? 0.5 : 1.0
|
||||
scale: flowDragArea.draggedIndex === index ? 0.95 : 1.0
|
||||
z: flowDragArea.draggedIndex === index ? 1000 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
}
|
||||
}
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -227,31 +234,186 @@ NBox {
|
|||
}
|
||||
}
|
||||
|
||||
// MouseArea outside Flow, covering the same area
|
||||
// Ghost/Clone widget for dragging
|
||||
Rectangle {
|
||||
id: dragGhost
|
||||
width: 0
|
||||
height: Style.baseWidgetSize * 1.15 * scaling
|
||||
radius: Style.radiusL * scaling
|
||||
color: "transparent"
|
||||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
opacity: 0.7
|
||||
visible: flowDragArea.dragStarted
|
||||
z: 2000
|
||||
clip: false // Ensure ghost isn't clipped
|
||||
|
||||
Text {
|
||||
id: ghostText
|
||||
anchors.centerIn: parent
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
color: Color.mOnPrimary
|
||||
}
|
||||
}
|
||||
|
||||
// Drop indicator - visual feedback for where the widget will be inserted
|
||||
Rectangle {
|
||||
id: dropIndicator
|
||||
width: 3 * scaling
|
||||
height: Style.baseWidgetSize * 1.15 * scaling
|
||||
radius: width / 2
|
||||
color: Color.mPrimary
|
||||
opacity: 0
|
||||
visible: opacity > 0
|
||||
z: 1999
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
id: pulseAnimation
|
||||
running: false
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation {
|
||||
to: 1
|
||||
duration: 400
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
NumberAnimation {
|
||||
to: 0.6
|
||||
duration: 400
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: 100
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: 100
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MouseArea for drag and drop
|
||||
MouseArea {
|
||||
id: flowDragArea
|
||||
anchors.fill: parent
|
||||
z: -1 // Ensure this mouse area is below the Settings and Close buttons
|
||||
z: -1
|
||||
|
||||
// Critical properties for proper event handling
|
||||
acceptedButtons: Qt.LeftButton
|
||||
preventStealing: false // Prevent child items from stealing events
|
||||
propagateComposedEvents: draggedIndex != -1 // Don't propagate to children during drag
|
||||
hoverEnabled: draggedIndex != -1
|
||||
preventStealing: false
|
||||
propagateComposedEvents: !dragStarted
|
||||
hoverEnabled: true // Always track mouse for drag operations
|
||||
|
||||
property point startPos: Qt.point(0, 0)
|
||||
property bool dragStarted: false
|
||||
property bool potentialDrag: false // Track if we're in a potential drag interaction
|
||||
property int draggedIndex: -1
|
||||
property real dragThreshold: 15 * scaling
|
||||
property Item draggedWidget: null
|
||||
property point clickOffsetInWidget: Qt.point(0, 0)
|
||||
property point originalWidgetPos: Qt.point(0, 0) // ADD THIS: Store original position
|
||||
property int dropTargetIndex: -1
|
||||
property var draggedModelData: null
|
||||
|
||||
// Drop position calculation
|
||||
function updateDropIndicator(mouseX, mouseY) {
|
||||
if (!dragStarted || draggedIndex === -1) {
|
||||
dropIndicator.opacity = 0
|
||||
pulseAnimation.running = false
|
||||
return
|
||||
}
|
||||
|
||||
let bestIndex = -1
|
||||
let bestPosition = null
|
||||
let minDistance = Infinity
|
||||
|
||||
// Check position relative to each widget
|
||||
for (var i = 0; i < widgetModel.length; i++) {
|
||||
if (i === draggedIndex)
|
||||
continue
|
||||
|
||||
const widget = widgetFlow.children[i]
|
||||
if (!widget || widget.widgetIndex === undefined)
|
||||
continue
|
||||
|
||||
// Check distance to left edge (insert before)
|
||||
const leftDist = Math.sqrt(Math.pow(mouseX - widget.x,
|
||||
2) + Math.pow(mouseY - (widget.y + widget.height / 2), 2))
|
||||
|
||||
// Check distance to right edge (insert after)
|
||||
const rightDist = Math.sqrt(Math.pow(mouseX - (widget.x + widget.width),
|
||||
2) + Math.pow(mouseY - (widget.y + widget.height / 2), 2))
|
||||
|
||||
if (leftDist < minDistance) {
|
||||
minDistance = leftDist
|
||||
bestIndex = i
|
||||
bestPosition = Qt.point(widget.x - dropIndicator.width / 2 - Style.marginXS * scaling, widget.y)
|
||||
}
|
||||
|
||||
if (rightDist < minDistance) {
|
||||
minDistance = rightDist
|
||||
bestIndex = i + 1
|
||||
bestPosition = Qt.point(widget.x + widget.width + Style.marginXS * scaling - dropIndicator.width / 2,
|
||||
widget.y)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we should insert at position 0 (very beginning)
|
||||
if (widgetModel.length > 0 && draggedIndex !== 0) {
|
||||
const firstWidget = widgetFlow.children[0]
|
||||
if (firstWidget) {
|
||||
const dist = Math.sqrt(Math.pow(mouseX, 2) + Math.pow(mouseY - firstWidget.y, 2))
|
||||
if (dist < minDistance && mouseX < firstWidget.x + firstWidget.width / 2) {
|
||||
minDistance = dist
|
||||
bestIndex = 0
|
||||
bestPosition = Qt.point(Math.max(0, firstWidget.x - dropIndicator.width - Style.marginS * scaling),
|
||||
firstWidget.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only show indicator if we're close enough and it's a different position
|
||||
if (minDistance < 80 * scaling && bestIndex !== -1) {
|
||||
// Adjust index if we're moving forward
|
||||
let adjustedIndex = bestIndex
|
||||
if (bestIndex > draggedIndex) {
|
||||
adjustedIndex = bestIndex - 1
|
||||
}
|
||||
|
||||
// Don't show if it's the same position
|
||||
if (adjustedIndex === draggedIndex) {
|
||||
dropIndicator.opacity = 0
|
||||
pulseAnimation.running = false
|
||||
dropTargetIndex = -1
|
||||
return
|
||||
}
|
||||
|
||||
dropTargetIndex = adjustedIndex
|
||||
if (bestPosition) {
|
||||
dropIndicator.x = bestPosition.x
|
||||
dropIndicator.y = bestPosition.y
|
||||
dropIndicator.opacity = 1
|
||||
if (!pulseAnimation.running) {
|
||||
pulseAnimation.running = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dropIndicator.opacity = 0
|
||||
pulseAnimation.running = false
|
||||
dropTargetIndex = -1
|
||||
}
|
||||
}
|
||||
|
||||
onPressed: mouse => {
|
||||
startPos = Qt.point(mouse.x, mouse.y)
|
||||
dragStarted = false
|
||||
potentialDrag = false
|
||||
draggedIndex = -1
|
||||
draggedWidget = null
|
||||
dropTargetIndex = -1
|
||||
draggedModelData = null
|
||||
|
||||
// Find which widget was clicked
|
||||
for (var i = 0; i < widgetModel.length; i++) {
|
||||
|
|
@ -264,22 +426,18 @@ NBox {
|
|||
const buttonsStartX = widget.width - (widget.buttonsCount * widget.buttonsWidth)
|
||||
|
||||
if (localX < buttonsStartX) {
|
||||
// This is a draggable area - prevent panel close immediately
|
||||
draggedIndex = widget.widgetIndex
|
||||
draggedWidget = widget
|
||||
|
||||
// Calculate and store where within the widget the user clicked
|
||||
const clickOffsetX = mouse.x - widget.x
|
||||
const clickOffsetY = mouse.y - widget.y
|
||||
clickOffsetInWidget = Qt.point(clickOffsetX, clickOffsetY)
|
||||
|
||||
// STORE ORIGINAL POSITION
|
||||
originalWidgetPos = Qt.point(widget.x, widget.y)
|
||||
|
||||
// Immediately set prevent stealing to true when drag candidate is found
|
||||
draggedModelData = widget.modelData
|
||||
potentialDrag = true
|
||||
preventStealing = true
|
||||
|
||||
// Signal that interaction started (prevents panel close)
|
||||
root.dragPotentialStarted()
|
||||
break
|
||||
} else {
|
||||
// Click was on buttons - allow event propagation
|
||||
// This is a button area - let the click through
|
||||
mouse.accepted = false
|
||||
return
|
||||
}
|
||||
|
|
@ -289,154 +447,83 @@ NBox {
|
|||
}
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
if (draggedIndex !== -1) {
|
||||
if (draggedIndex !== -1 && potentialDrag) {
|
||||
const deltaX = mouse.x - startPos.x
|
||||
const deltaY = mouse.y - startPos.y
|
||||
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY)
|
||||
|
||||
if (!dragStarted && distance > dragThreshold) {
|
||||
dragStarted = true
|
||||
//Logger.log("BarSectionEditor", "Drag started")
|
||||
|
||||
// Enable visual feedback
|
||||
// Setup ghost widget
|
||||
if (draggedWidget) {
|
||||
draggedWidget.z = 1000
|
||||
dragGhost.width = draggedWidget.width
|
||||
dragGhost.color = root.getWidgetColor(draggedModelData)
|
||||
ghostText.text = draggedModelData.id
|
||||
}
|
||||
}
|
||||
|
||||
if (dragStarted && draggedWidget) {
|
||||
// Adjust position to account for where within the widget the user clicked
|
||||
draggedWidget.x = mouse.x - clickOffsetInWidget.x
|
||||
draggedWidget.y = mouse.y - clickOffsetInWidget.y
|
||||
if (dragStarted) {
|
||||
// Move ghost widget
|
||||
dragGhost.x = mouse.x - dragGhost.width / 2
|
||||
dragGhost.y = mouse.y - dragGhost.height / 2
|
||||
|
||||
// Update drop indicator
|
||||
updateDropIndicator(mouse.x, mouse.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: mouse => {
|
||||
if (dragStarted && draggedWidget) {
|
||||
// Find drop target using improved logic
|
||||
let targetIndex = -1
|
||||
let minDistance = Infinity
|
||||
const mouseX = mouse.x
|
||||
const mouseY = mouse.y
|
||||
if (dragStarted && dropTargetIndex !== -1 && dropTargetIndex !== draggedIndex) {
|
||||
// Perform the reorder
|
||||
reorderWidget(sectionId, draggedIndex, dropTargetIndex)
|
||||
}
|
||||
|
||||
// Check if we should insert at the beginning
|
||||
let insertAtBeginning = true
|
||||
let insertAtEnd = true
|
||||
|
||||
// Check if the dragged item is already the last item
|
||||
let isLastItem = true
|
||||
for (var k = 0; k < widgetModel.length; k++) {
|
||||
if (k !== draggedIndex && k > draggedIndex) {
|
||||
isLastItem = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < widgetModel.length; i++) {
|
||||
if (i !== draggedIndex) {
|
||||
const widget = widgetFlow.children[i]
|
||||
if (widget && widget.widgetIndex !== undefined) {
|
||||
const centerX = widget.x + widget.width / 2
|
||||
const centerY = widget.y + widget.height / 2
|
||||
const distance = Math.sqrt(Math.pow(mouseX - centerX, 2) + Math.pow(mouseY - centerY, 2))
|
||||
|
||||
// Check if mouse is to the right of this widget
|
||||
if (mouseX > widget.x + widget.width / 2) {
|
||||
insertAtBeginning = false
|
||||
}
|
||||
// Check if mouse is to the left of this widget
|
||||
if (mouseX < widget.x + widget.width / 2) {
|
||||
insertAtEnd = false
|
||||
}
|
||||
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance
|
||||
targetIndex = widget.widgetIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If dragging the last item to the right, don't reorder
|
||||
if (isLastItem && insertAtEnd) {
|
||||
insertAtEnd = false
|
||||
targetIndex = -1
|
||||
//Logger.log("BarSectionEditor", "Last item dropped to right - no reordering needed")
|
||||
}
|
||||
|
||||
// Determine final target index based on position
|
||||
let finalTargetIndex = targetIndex
|
||||
|
||||
if (insertAtBeginning && widgetModel.length > 1) {
|
||||
// Insert at the very beginning (position 0)
|
||||
finalTargetIndex = 0
|
||||
//Logger.log("BarSectionEditor", "Inserting at beginning")
|
||||
} else if (insertAtEnd && widgetModel.length > 1) {
|
||||
// Insert at the very end
|
||||
let maxIndex = -1
|
||||
for (var j = 0; j < widgetModel.length; j++) {
|
||||
if (j !== draggedIndex) {
|
||||
maxIndex = Math.max(maxIndex, j)
|
||||
}
|
||||
}
|
||||
finalTargetIndex = maxIndex
|
||||
//Logger.log("BarSectionEditor", "Inserting at end, target:", finalTargetIndex)
|
||||
} else if (targetIndex !== -1) {
|
||||
// Normal case - determine if we should insert before or after the target
|
||||
const targetWidget = widgetFlow.children[targetIndex]
|
||||
if (targetWidget) {
|
||||
const targetCenterX = targetWidget.x + targetWidget.width / 2
|
||||
if (mouseX > targetCenterX) {
|
||||
|
||||
// Mouse is to the right of target center, insert after
|
||||
//Logger.log("BarSectionEditor", "Inserting after widget at index:", targetIndex)
|
||||
} else {
|
||||
// Mouse is to the left of target center, insert before
|
||||
finalTargetIndex = targetIndex
|
||||
//Logger.log("BarSectionEditor", "Inserting before widget at index:", targetIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Logger.log("BarSectionEditor", "Final drop target index:", finalTargetIndex)
|
||||
|
||||
// Check if reordering is needed
|
||||
if (finalTargetIndex !== -1 && finalTargetIndex !== draggedIndex) {
|
||||
// Reordering will happen - reset position for the Flow to handle
|
||||
draggedWidget.x = 0
|
||||
draggedWidget.y = 0
|
||||
draggedWidget.z = 0
|
||||
reorderWidget(sectionId, draggedIndex, finalTargetIndex)
|
||||
} else {
|
||||
// No reordering - restore original position
|
||||
draggedWidget.x = originalWidgetPos.x
|
||||
draggedWidget.y = originalWidgetPos.y
|
||||
draggedWidget.z = 0
|
||||
//Logger.log("BarSectionEditor", "No reordering - restoring original position")
|
||||
}
|
||||
} else if (draggedIndex !== -1 && !dragStarted) {
|
||||
|
||||
// This was a click without drag - could add click handling here if needed
|
||||
// Always signal end of interaction if we started one
|
||||
if (potentialDrag) {
|
||||
root.dragPotentialEnded()
|
||||
}
|
||||
|
||||
// Reset everything
|
||||
dragStarted = false
|
||||
potentialDrag = false
|
||||
draggedIndex = -1
|
||||
draggedWidget = null
|
||||
preventStealing = false // Allow normal event propagation again
|
||||
originalWidgetPos = Qt.point(0, 0) // Reset stored position
|
||||
dropTargetIndex = -1
|
||||
draggedModelData = null
|
||||
preventStealing = false
|
||||
dropIndicator.opacity = 0
|
||||
pulseAnimation.running = false
|
||||
dragGhost.width = 0
|
||||
}
|
||||
|
||||
// Handle case where mouse leaves the area during drag
|
||||
onExited: {
|
||||
if (dragStarted && draggedWidget) {
|
||||
// Restore original position when mouse leaves area
|
||||
draggedWidget.x = originalWidgetPos.x
|
||||
draggedWidget.y = originalWidgetPos.y
|
||||
draggedWidget.z = 0
|
||||
if (dragStarted) {
|
||||
// Hide drop indicator when mouse leaves, but keep ghost visible
|
||||
dropIndicator.opacity = 0
|
||||
pulseAnimation.running = false
|
||||
}
|
||||
}
|
||||
|
||||
onCanceled: {
|
||||
// Handle cancel (e.g., ESC key pressed during drag)
|
||||
if (potentialDrag) {
|
||||
root.dragPotentialEnded()
|
||||
}
|
||||
|
||||
// Reset everything
|
||||
dragStarted = false
|
||||
potentialDrag = false
|
||||
draggedIndex = -1
|
||||
draggedWidget = null
|
||||
dropTargetIndex = -1
|
||||
draggedModelData = null
|
||||
preventStealing = false
|
||||
dropIndicator.opacity = 0
|
||||
pulseAnimation.running = false
|
||||
dragGhost.width = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ Popup {
|
|||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginM * scaling
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
|
|
|
|||
|
|
@ -16,10 +16,13 @@ ColumnLayout {
|
|||
// Local state
|
||||
property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage
|
||||
!== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
|
||||
property int valueWarningThreshold: widgetData.warningThreshold
|
||||
!== undefined ? widgetData.warningThreshold : widgetMetadata.warningThreshold
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.alwaysShowPercentage = valueAlwaysShowPercentage
|
||||
settings.warningThreshold = valueWarningThreshold
|
||||
return settings
|
||||
}
|
||||
|
||||
|
|
@ -28,4 +31,14 @@ ColumnLayout {
|
|||
checked: root.valueAlwaysShowPercentage
|
||||
onToggled: checked => root.valueAlwaysShowPercentage = checked
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
label: "Low battery warning threshold"
|
||||
description: "Show a warning when battery falls below this percentage."
|
||||
value: valueWarningThreshold
|
||||
suffix: "%"
|
||||
minimum: 5
|
||||
maximum: 50
|
||||
onValueChanged: valueWarningThreshold = value
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services
|
||||
|
|
@ -9,7 +10,6 @@ ColumnLayout {
|
|||
id: root
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
|
|
@ -22,16 +22,189 @@ ColumnLayout {
|
|||
return settings
|
||||
}
|
||||
|
||||
// Icon setting
|
||||
NTextInput {
|
||||
id: iconInput
|
||||
Layout.fillWidth: true
|
||||
label: "Icon Name"
|
||||
description: "Choose a name from the Material Icon set."
|
||||
placeholderText: "Enter icon name (e.g., favorite, home, settings)"
|
||||
description: "Select an icon from the library."
|
||||
placeholderText: "Enter icon name (e.g., cat, gear, house, ...)"
|
||||
text: widgetData?.icon || widgetMetadata.icon
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon: iconInput.text
|
||||
visible: iconInput.text !== ""
|
||||
}
|
||||
NButton {
|
||||
text: "Browse"
|
||||
onClicked: iconPicker.open()
|
||||
}
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: iconPicker
|
||||
modal: true
|
||||
property real panelWidth: {
|
||||
var w = Math.round(Math.max(Screen.width * 0.35, 900) * scaling)
|
||||
w = Math.min(w, Screen.width - Style.marginL * 2)
|
||||
return w
|
||||
}
|
||||
property real panelHeight: {
|
||||
var h = Math.round(Math.max(Screen.height * 0.65, 700) * scaling)
|
||||
h = Math.min(h, Screen.height - Style.barHeight * scaling - Style.marginL * 2)
|
||||
return h
|
||||
}
|
||||
width: panelWidth
|
||||
height: panelHeight
|
||||
anchors.centerIn: Overlay.overlay
|
||||
padding: Style.marginXL * scaling
|
||||
|
||||
property string query: ""
|
||||
property string selectedIcon: ""
|
||||
property var allIcons: Object.keys(Icons.icons)
|
||||
property var filteredIcons: allIcons.filter(function (name) {
|
||||
return query === "" || name.toLowerCase().indexOf(query.toLowerCase()) !== -1
|
||||
})
|
||||
readonly property int columns: 6
|
||||
readonly property int cellW: Math.floor(grid.width / columns)
|
||||
readonly property int cellH: Math.round(cellW * 0.7 + 36 * scaling)
|
||||
|
||||
background: Rectangle {
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusL * scaling
|
||||
border.color: Color.mPrimary
|
||||
border.width: Style.borderM * scaling
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Title row
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
NText {
|
||||
text: "Icon Picker"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
onClicked: iconPicker.close()
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS * scaling
|
||||
NTextInput {
|
||||
Layout.fillWidth: true
|
||||
label: "Search"
|
||||
placeholderText: "Search (e.g., arrow, battery, cloud)"
|
||||
text: iconPicker.query
|
||||
onTextChanged: iconPicker.query = text.trim().toLowerCase()
|
||||
}
|
||||
}
|
||||
|
||||
// Icon grid
|
||||
ScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
|
||||
GridView {
|
||||
id: grid
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM * scaling
|
||||
cellWidth: iconPicker.cellW
|
||||
cellHeight: iconPicker.cellH
|
||||
model: iconPicker.filteredIcons
|
||||
delegate: Rectangle {
|
||||
width: grid.cellWidth
|
||||
height: grid.cellHeight
|
||||
radius: Style.radiusS * scaling
|
||||
clip: true
|
||||
color: (iconPicker.selectedIcon === modelData) ? Qt.alpha(Color.mPrimary, 0.15) : "transparent"
|
||||
border.color: (iconPicker.selectedIcon === modelData) ? Color.mPrimary : Qt.rgba(0, 0, 0, 0)
|
||||
border.width: (iconPicker.selectedIcon === modelData) ? Style.borderS * scaling : 0
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: iconPicker.selectedIcon = modelData
|
||||
onDoubleClicked: {
|
||||
iconPicker.selectedIcon = modelData
|
||||
iconInput.text = iconPicker.selectedIcon
|
||||
iconPicker.close()
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginS * scaling
|
||||
spacing: Style.marginS * scaling
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredHeight: 4 * scaling
|
||||
}
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
icon: modelData
|
||||
font.pointSize: 42 * scaling
|
||||
}
|
||||
NText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXS * scaling
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
maximumLineCount: 1
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: Color.mOnSurfaceVariant
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
text: modelData
|
||||
}
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM * scaling
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
NButton {
|
||||
text: "Cancel"
|
||||
outlined: true
|
||||
onClicked: iconPicker.close()
|
||||
}
|
||||
NButton {
|
||||
text: "Apply"
|
||||
icon: "check"
|
||||
enabled: iconPicker.selectedIcon !== ""
|
||||
onClicked: {
|
||||
iconInput.text = iconPicker.selectedIcon
|
||||
iconPicker.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: leftClickExecInput
|
||||
Layout.fillWidth: true
|
||||
|
|
|
|||
|
|
@ -16,19 +16,24 @@ ColumnLayout {
|
|||
// Local, editable state for checkboxes
|
||||
property bool valueShowCpuUsage: widgetData.showCpuUsage !== undefined ? widgetData.showCpuUsage : widgetMetadata.showCpuUsage
|
||||
property bool valueShowCpuTemp: widgetData.showCpuTemp !== undefined ? widgetData.showCpuTemp : widgetMetadata.showCpuTemp
|
||||
property bool valueShowGpuTemp: widgetData.showGpuTemp !== undefined ? widgetData.showGpuTemp : (widgetMetadata.showGpuTemp
|
||||
|| false)
|
||||
property bool valueShowMemoryUsage: widgetData.showMemoryUsage !== undefined ? widgetData.showMemoryUsage : widgetMetadata.showMemoryUsage
|
||||
property bool valueShowMemoryAsPercent: widgetData.showMemoryAsPercent
|
||||
!== undefined ? widgetData.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent
|
||||
property bool valueShowNetworkStats: widgetData.showNetworkStats
|
||||
!== undefined ? widgetData.showNetworkStats : widgetMetadata.showNetworkStats
|
||||
property bool valueShowDiskUsage: widgetData.showDiskUsage !== undefined ? widgetData.showDiskUsage : widgetMetadata.showDiskUsage
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.showCpuUsage = valueShowCpuUsage
|
||||
settings.showCpuTemp = valueShowCpuTemp
|
||||
settings.showGpuTemp = valueShowGpuTemp
|
||||
settings.showMemoryUsage = valueShowMemoryUsage
|
||||
settings.showMemoryAsPercent = valueShowMemoryAsPercent
|
||||
settings.showNetworkStats = valueShowNetworkStats
|
||||
settings.showDiskUsage = valueShowDiskUsage
|
||||
return settings
|
||||
}
|
||||
|
||||
|
|
@ -48,6 +53,14 @@ ColumnLayout {
|
|||
onToggled: checked => valueShowCpuTemp = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showGpuTemp
|
||||
Layout.fillWidth: true
|
||||
label: "GPU temperature"
|
||||
checked: valueShowGpuTemp
|
||||
onToggled: checked => valueShowGpuTemp = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showMemoryUsage
|
||||
Layout.fillWidth: true
|
||||
|
|
@ -59,7 +72,7 @@ ColumnLayout {
|
|||
NToggle {
|
||||
id: showMemoryAsPercent
|
||||
Layout.fillWidth: true
|
||||
label: "Show memory as percentage"
|
||||
label: "Memory as percentage"
|
||||
checked: valueShowMemoryAsPercent
|
||||
onToggled: checked => valueShowMemoryAsPercent = checked
|
||||
}
|
||||
|
|
@ -71,4 +84,12 @@ ColumnLayout {
|
|||
checked: valueShowNetworkStats
|
||||
onToggled: checked => valueShowNetworkStats = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showDiskUsage
|
||||
Layout.fillWidth: true
|
||||
label: "Storage usage"
|
||||
checked: valueShowDiskUsage
|
||||
onToggled: checked => valueShowDiskUsage = checked
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,52 +123,52 @@ NPanel {
|
|||
let newTabs = [{
|
||||
"id": SettingsPanel.Tab.General,
|
||||
"label": "General",
|
||||
"icon": "tune",
|
||||
"icon": "settings-general",
|
||||
"source": generalTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Bar,
|
||||
"label": "Bar",
|
||||
"icon": "web_asset",
|
||||
"icon": "settings-bar",
|
||||
"source": barTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Launcher,
|
||||
"label": "Launcher",
|
||||
"icon": "apps",
|
||||
"icon": "settings-launcher",
|
||||
"source": launcherTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Audio,
|
||||
"label": "Audio",
|
||||
"icon": "volume_up",
|
||||
"icon": "settings-audio",
|
||||
"source": audioTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Display,
|
||||
"label": "Display",
|
||||
"icon": "monitor",
|
||||
"icon": "settings-display",
|
||||
"source": displayTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Network,
|
||||
"label": "Network",
|
||||
"icon": "lan",
|
||||
"icon": "settings-network",
|
||||
"source": networkTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Brightness,
|
||||
"label": "Brightness",
|
||||
"icon": "brightness_6",
|
||||
"icon": "settings-brightness",
|
||||
"source": brightnessTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Weather,
|
||||
"label": "Weather",
|
||||
"icon": "partly_cloudy_day",
|
||||
"icon": "settings-weather",
|
||||
"source": weatherTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.ColorScheme,
|
||||
"label": "Color Scheme",
|
||||
"icon": "palette",
|
||||
"icon": "settings-color-scheme",
|
||||
"source": colorSchemeTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Wallpaper,
|
||||
"label": "Wallpaper",
|
||||
"icon": "image",
|
||||
"icon": "settings-wallpaper",
|
||||
"source": wallpaperTab
|
||||
}]
|
||||
|
||||
|
|
@ -177,7 +177,7 @@ NPanel {
|
|||
newTabs.push({
|
||||
"id": SettingsPanel.Tab.WallpaperSelector,
|
||||
"label": "Wallpaper Selector",
|
||||
"icon": "wallpaper_slideshow",
|
||||
"icon": "settings-wallpaper-selector",
|
||||
"source": wallpaperSelectorTab
|
||||
})
|
||||
}
|
||||
|
|
@ -185,17 +185,17 @@ NPanel {
|
|||
newTabs.push({
|
||||
"id": SettingsPanel.Tab.ScreenRecorder,
|
||||
"label": "Screen Recorder",
|
||||
"icon": "videocam",
|
||||
"icon": "settings-screen-recorder",
|
||||
"source": screenRecorderTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Hooks,
|
||||
"label": "Hooks",
|
||||
"icon": "cable",
|
||||
"icon": "settings-hooks",
|
||||
"source": hooksTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.About,
|
||||
"label": "About",
|
||||
"icon": "info",
|
||||
"icon": "settings-about",
|
||||
"source": aboutTab
|
||||
})
|
||||
|
||||
|
|
@ -400,13 +400,13 @@ NPanel {
|
|||
anchors.fill: parent
|
||||
anchors.leftMargin: Style.marginS * scaling
|
||||
anchors.rightMargin: Style.marginS * scaling
|
||||
spacing: Style.marginS * scaling
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Tab icon
|
||||
NIcon {
|
||||
text: modelData.icon
|
||||
icon: modelData.icon
|
||||
color: tabTextColor
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
}
|
||||
|
||||
// Tab label
|
||||
|
|
@ -416,6 +416,7 @@ NPanel {
|
|||
font.pointSize: Style.fontSizeM * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -461,7 +462,14 @@ NPanel {
|
|||
Layout.fillWidth: true
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
// Tab title
|
||||
// Main icon
|
||||
NIcon {
|
||||
icon: root.tabsModel[currentTabIndex]?.icon
|
||||
color: Color.mPrimary
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
}
|
||||
|
||||
// Main title
|
||||
NText {
|
||||
text: root.tabsModel[currentTabIndex]?.label || ""
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ ColumnLayout {
|
|||
spacing: Style.marginS * scaling
|
||||
|
||||
NIcon {
|
||||
text: "download"
|
||||
icon: "download"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
color: updateArea.containsMouse ? Color.mSurface : Color.mPrimary
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,22 @@ import qs.Modules.SettingsPanel.Bar
|
|||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
// Handler for drag start - disables panel background clicks
|
||||
function handleDragStart() {
|
||||
var panel = PanelService.getPanel("settingsPanel")
|
||||
if (panel && panel.disableBackgroundClick) {
|
||||
panel.disableBackgroundClick()
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for drag end - re-enables panel background clicks
|
||||
function handleDragEnd() {
|
||||
var panel = PanelService.getPanel("settingsPanel")
|
||||
if (panel && panel.enableBackgroundClick) {
|
||||
panel.enableBackgroundClick()
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
|
|
@ -116,6 +132,8 @@ ColumnLayout {
|
|||
onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index)
|
||||
onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex)
|
||||
onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings)
|
||||
onDragPotentialStarted: root.handleDragStart()
|
||||
onDragPotentialEnded: root.handleDragEnd()
|
||||
}
|
||||
|
||||
// Center Section
|
||||
|
|
@ -128,6 +146,8 @@ ColumnLayout {
|
|||
onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index)
|
||||
onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex)
|
||||
onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings)
|
||||
onDragPotentialStarted: root.handleDragStart()
|
||||
onDragPotentialEnded: root.handleDragEnd()
|
||||
}
|
||||
|
||||
// Right Section
|
||||
|
|
@ -140,6 +160,8 @@ ColumnLayout {
|
|||
onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index)
|
||||
onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex)
|
||||
onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings)
|
||||
onDragPotentialStarted: root.handleDragStart()
|
||||
onDragPotentialEnded: root.handleDragEnd()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -194,6 +194,7 @@ ColumnLayout {
|
|||
wlsunsetCheck.running = true
|
||||
} else {
|
||||
Settings.data.nightLight.enabled = false
|
||||
Settings.data.nightLight.forced = false
|
||||
NightLightService.apply()
|
||||
ToastService.showNotice("Night Light", "Disabled")
|
||||
}
|
||||
|
|
@ -276,6 +277,7 @@ ColumnLayout {
|
|||
ColumnLayout {
|
||||
spacing: Style.marginXS * scaling
|
||||
visible: Settings.data.nightLight.enabled && !Settings.data.nightLight.autoSchedule
|
||||
&& !Settings.data.nightLight.forced
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: false
|
||||
|
|
@ -319,4 +321,21 @@ ColumnLayout {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Force activation toggle
|
||||
NToggle {
|
||||
label: "Force activation"
|
||||
description: "Immediately apply night temperature without scheduling or fade."
|
||||
checked: Settings.data.nightLight.forced
|
||||
onToggled: checked => {
|
||||
Settings.data.nightLight.forced = checked
|
||||
if (checked && !Settings.data.nightLight.enabled) {
|
||||
// Ensure enabled when forcing
|
||||
wlsunsetCheck.running = true
|
||||
} else {
|
||||
NightLightService.apply()
|
||||
}
|
||||
}
|
||||
visible: Settings.data.nightLight.enabled
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -187,7 +187,8 @@ ColumnLayout {
|
|||
color: getSchemeColor(modelData, "mSurface")
|
||||
border.width: Math.max(1, Style.borderL * scaling)
|
||||
border.color: (!Settings.data.colorSchemes.useWallpaperColors
|
||||
&& (Settings.data.colorSchemes.predefinedScheme === modelData)) ? Color.mPrimary : Color.mOutline
|
||||
&& (Settings.data.colorSchemes.predefinedScheme === modelData.split("/").pop().replace(
|
||||
".json", ""))) ? Color.mPrimary : Color.mOutline
|
||||
scale: root.cardScaleLow
|
||||
|
||||
// Mouse area for selection
|
||||
|
|
@ -198,8 +199,8 @@ ColumnLayout {
|
|||
Settings.data.colorSchemes.useWallpaperColors = false
|
||||
Logger.log("ColorSchemeTab", "Disabled matugen setting")
|
||||
|
||||
Settings.data.colorSchemes.predefinedScheme = schemePath
|
||||
ColorSchemeService.applyScheme(schemePath)
|
||||
Settings.data.colorSchemes.predefinedScheme = schemePath.split("/").pop().replace(".json", "")
|
||||
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme)
|
||||
}
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
|
@ -281,7 +282,8 @@ ColumnLayout {
|
|||
// Selection indicator (Checkmark)
|
||||
Rectangle {
|
||||
visible: !Settings.data.colorSchemes.useWallpaperColors
|
||||
&& (Settings.data.colorSchemes.predefinedScheme === schemePath)
|
||||
&& (Settings.data.colorSchemes.predefinedScheme === schemePath.split("/").pop().replace(".json",
|
||||
""))
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Style.marginS * scaling
|
||||
|
|
|
|||
|
|
@ -22,15 +22,7 @@ ColumnLayout {
|
|||
label: "Enable Bluetooth"
|
||||
description: "Enable Bluetooth connectivity."
|
||||
checked: Settings.data.network.bluetoothEnabled
|
||||
onToggled: checked => {
|
||||
Settings.data.network.bluetoothEnabled = checked
|
||||
BluetoothService.setBluetoothEnabled(checked)
|
||||
if (checked) {
|
||||
ToastService.showNotice("Bluetooth", "Enabled")
|
||||
} else {
|
||||
ToastService.showNotice("Bluetooth", "Disabled")
|
||||
}
|
||||
}
|
||||
onToggled: checked => BluetoothService.setBluetoothEnabled(checked)
|
||||
}
|
||||
|
||||
NDivider {
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ ColumnLayout {
|
|||
visible: isSelected
|
||||
|
||||
NIcon {
|
||||
text: "check"
|
||||
icon: "check"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSecondary
|
||||
|
|
@ -246,8 +246,8 @@ ColumnLayout {
|
|||
}
|
||||
|
||||
NIcon {
|
||||
text: "folder_open"
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
icon: "folder-open"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
color: Color.mOnSurface
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ NBox {
|
|||
Layout.fillHeight: true
|
||||
anchors.margins: Style.marginL * scaling
|
||||
|
||||
// Fallback
|
||||
// No media player detected
|
||||
ColumnLayout {
|
||||
id: fallback
|
||||
|
||||
|
|
@ -31,8 +31,8 @@ NBox {
|
|||
}
|
||||
|
||||
NIcon {
|
||||
text: "album"
|
||||
font.pointSize: Style.fontSizeXXXL * 2.5 * scaling
|
||||
icon: "disc"
|
||||
font.pointSize: Style.fontSizeXXXL * 3 * scaling
|
||||
color: Color.mPrimary
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
|
@ -89,7 +89,7 @@ NBox {
|
|||
indicator: NIcon {
|
||||
x: playerSelector.width - width
|
||||
y: playerSelector.topPadding + (playerSelector.availableHeight - height) / 2
|
||||
text: "arrow_drop_down"
|
||||
icon: "caret-down"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
color: Color.mOnSurface
|
||||
horizontalAlignment: Text.AlignRight
|
||||
|
|
@ -156,22 +156,22 @@ NBox {
|
|||
color: trackArt.visible ? Color.mPrimary : Color.transparent
|
||||
clip: true
|
||||
|
||||
// Can't use fallback icon here, as we have a big disc behind
|
||||
NImageCircled {
|
||||
id: trackArt
|
||||
visible: MediaService.trackArtUrl.toString() !== ""
|
||||
visible: MediaService.trackArtUrl !== ""
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginXS * scaling
|
||||
imagePath: MediaService.trackArtUrl
|
||||
fallbackIcon: "music_note"
|
||||
borderColor: Color.mOutline
|
||||
borderWidth: Math.max(1, Style.borderS * scaling)
|
||||
}
|
||||
|
||||
// Fallback icon when no album art available
|
||||
NIcon {
|
||||
text: "album"
|
||||
icon: "disc"
|
||||
color: Color.mPrimary
|
||||
font.pointSize: Style.fontSizeL * 12 * scaling
|
||||
font.pointSize: Style.fontSizeXXXL * 3 * scaling
|
||||
visible: !trackArt.visible
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
|
@ -307,7 +307,7 @@ NBox {
|
|||
|
||||
// Previous button
|
||||
NIconButton {
|
||||
icon: "skip_previous"
|
||||
icon: "media-prev"
|
||||
tooltipText: "Previous Media"
|
||||
visible: MediaService.canGoPrevious
|
||||
onClicked: MediaService.canGoPrevious ? MediaService.previous() : {}
|
||||
|
|
@ -315,7 +315,7 @@ NBox {
|
|||
|
||||
// Play/Pause button
|
||||
NIconButton {
|
||||
icon: MediaService.isPlaying ? "pause" : "play_arrow"
|
||||
icon: MediaService.isPlaying ? "media-pause" : "media-play"
|
||||
tooltipText: MediaService.isPlaying ? "Pause" : "Play"
|
||||
visible: (MediaService.canPlay || MediaService.canPause)
|
||||
onClicked: (MediaService.canPlay || MediaService.canPause) ? MediaService.playPause() : {}
|
||||
|
|
@ -323,7 +323,7 @@ NBox {
|
|||
|
||||
// Next button
|
||||
NIconButton {
|
||||
icon: "skip_next"
|
||||
icon: "media-next"
|
||||
tooltipText: "Next media"
|
||||
visible: MediaService.canGoNext
|
||||
onClicked: MediaService.canGoNext ? MediaService.next() : {}
|
||||
|
|
|
|||
|
|
@ -13,9 +13,8 @@ NBox {
|
|||
Layout.preferredWidth: 1
|
||||
implicitHeight: powerRow.implicitHeight + Style.marginM * 2 * scaling
|
||||
|
||||
// PowerProfiles service
|
||||
property var powerProfiles: PowerProfiles
|
||||
readonly property bool hasPP: powerProfiles.hasPerformanceProfile
|
||||
// Centralized service
|
||||
readonly property bool hasPP: PowerProfileService.available
|
||||
property real spacing: 0
|
||||
|
||||
RowLayout {
|
||||
|
|
@ -28,43 +27,46 @@ NBox {
|
|||
}
|
||||
// Performance
|
||||
NIconButton {
|
||||
icon: "speed"
|
||||
icon: "performance"
|
||||
tooltipText: "Set performance power profile."
|
||||
enabled: hasPP
|
||||
opacity: enabled ? Style.opacityFull : Style.opacityMedium
|
||||
colorBg: (enabled && powerProfiles.profile === PowerProfile.Performance) ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorFg: (enabled && powerProfiles.profile === PowerProfile.Performance) ? Color.mOnPrimary : Color.mPrimary
|
||||
colorBg: (enabled
|
||||
&& PowerProfileService.profile === PowerProfile.Performance) ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorFg: (enabled && PowerProfileService.profile === PowerProfile.Performance) ? Color.mOnPrimary : Color.mPrimary
|
||||
onClicked: {
|
||||
if (enabled) {
|
||||
powerProfiles.profile = PowerProfile.Performance
|
||||
PowerProfileService.setProfile(PowerProfile.Performance)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Balanced
|
||||
NIconButton {
|
||||
icon: "balance"
|
||||
icon: "balanced"
|
||||
tooltipText: "Set balanced power profile."
|
||||
enabled: hasPP
|
||||
opacity: enabled ? Style.opacityFull : Style.opacityMedium
|
||||
colorBg: (enabled && powerProfiles.profile === PowerProfile.Balanced) ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorFg: (enabled && powerProfiles.profile === PowerProfile.Balanced) ? Color.mOnPrimary : Color.mPrimary
|
||||
colorBg: (enabled
|
||||
&& PowerProfileService.profile === PowerProfile.Balanced) ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorFg: (enabled && PowerProfileService.profile === PowerProfile.Balanced) ? Color.mOnPrimary : Color.mPrimary
|
||||
onClicked: {
|
||||
if (enabled) {
|
||||
powerProfiles.profile = PowerProfile.Balanced
|
||||
PowerProfileService.setProfile(PowerProfile.Balanced)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Eco
|
||||
NIconButton {
|
||||
icon: "eco"
|
||||
icon: "powersaver"
|
||||
tooltipText: "Set eco power profile."
|
||||
enabled: hasPP
|
||||
opacity: enabled ? Style.opacityFull : Style.opacityMedium
|
||||
colorBg: (enabled && powerProfiles.profile === PowerProfile.PowerSaver) ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorFg: (enabled && powerProfiles.profile === PowerProfile.PowerSaver) ? Color.mOnPrimary : Color.mPrimary
|
||||
colorBg: (enabled
|
||||
&& PowerProfileService.profile === PowerProfile.PowerSaver) ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorFg: (enabled && PowerProfileService.profile === PowerProfile.PowerSaver) ? Color.mOnPrimary : Color.mPrimary
|
||||
onClicked: {
|
||||
if (enabled) {
|
||||
powerProfiles.profile = PowerProfile.PowerSaver
|
||||
PowerProfileService.setProfile(PowerProfile.PowerSaver)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,8 @@ NBox {
|
|||
}
|
||||
NText {
|
||||
text: `System uptime: ${uptimeText}`
|
||||
color: Color.mOnSurface
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -68,7 +69,7 @@ NBox {
|
|||
|
||||
NIconButton {
|
||||
id: powerButton
|
||||
icon: "power_settings_new"
|
||||
icon: "power"
|
||||
tooltipText: "Power menu."
|
||||
onClicked: {
|
||||
powerPanel.open(screen)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ NBox {
|
|||
|
||||
NCircleStat {
|
||||
value: SystemStatService.cpuUsage
|
||||
icon: "speed"
|
||||
icon: "cpu-usage"
|
||||
flat: true
|
||||
contentScale: 0.8
|
||||
width: 72 * scaling
|
||||
|
|
@ -33,7 +33,7 @@ NBox {
|
|||
NCircleStat {
|
||||
value: SystemStatService.cpuTemp
|
||||
suffix: "°C"
|
||||
icon: "device_thermostat"
|
||||
icon: "cpu-temperature"
|
||||
flat: true
|
||||
contentScale: 0.8
|
||||
width: 72 * scaling
|
||||
|
|
@ -49,7 +49,7 @@ NBox {
|
|||
}
|
||||
NCircleStat {
|
||||
value: SystemStatService.diskPercent
|
||||
icon: "hard_drive"
|
||||
icon: "storage"
|
||||
flat: true
|
||||
contentScale: 0.8
|
||||
width: 72 * scaling
|
||||
|
|
|
|||
|
|
@ -25,11 +25,14 @@ NBox {
|
|||
}
|
||||
// Screen Recorder
|
||||
NIconButton {
|
||||
icon: "videocam"
|
||||
tooltipText: ScreenRecorderService.isRecording ? "Stop screen recording." : "Start screen recording."
|
||||
icon: "camera-video"
|
||||
enabled: ScreenRecorderService.isAvailable
|
||||
tooltipText: ScreenRecorderService.isAvailable ? (ScreenRecorderService.isRecording ? "Stop screen recording." : "Start screen recording.") : "Screen recorder not installed."
|
||||
colorBg: ScreenRecorderService.isRecording ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorFg: ScreenRecorderService.isRecording ? Color.mOnPrimary : Color.mPrimary
|
||||
onClicked: {
|
||||
if (!ScreenRecorderService.isAvailable)
|
||||
return
|
||||
ScreenRecorderService.toggleRecording()
|
||||
// If we were not recording and we just initiated a start, close the panel
|
||||
if (!ScreenRecorderService.isRecording) {
|
||||
|
|
@ -41,7 +44,7 @@ NBox {
|
|||
|
||||
// Idle Inhibitor
|
||||
NIconButton {
|
||||
icon: "coffee"
|
||||
icon: IdleInhibitorService.isInhibited ? "keep-awake-on" : "keep-awake-off"
|
||||
tooltipText: IdleInhibitorService.isInhibited ? "Disable keep awake." : "Enable keep awake."
|
||||
colorBg: IdleInhibitorService.isInhibited ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorFg: IdleInhibitorService.isInhibited ? Color.mOnPrimary : Color.mPrimary
|
||||
|
|
@ -53,7 +56,7 @@ NBox {
|
|||
// Wallpaper
|
||||
NIconButton {
|
||||
visible: Settings.data.wallpaper.enabled
|
||||
icon: "image"
|
||||
icon: "wallpaper-selector"
|
||||
tooltipText: "Left click: Open wallpaper selector.\nRight click: Set random wallpaper."
|
||||
onClicked: {
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel")
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ NBox {
|
|||
RowLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
NIcon {
|
||||
text: weatherReady ? LocationService.weatherSymbolFromCode(
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon: weatherReady ? LocationService.weatherSymbolFromCode(
|
||||
LocationService.data.weather.current_weather.weathercode) : ""
|
||||
font.pointSize: Style.fontSizeXXXL * 1.75 * scaling
|
||||
color: Color.mPrimary
|
||||
|
|
@ -89,20 +90,23 @@ NBox {
|
|||
model: weatherReady ? LocationService.data.weather.daily.time : []
|
||||
delegate: ColumnLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: Style.marginS * scaling
|
||||
spacing: Style.marginL * scaling
|
||||
NText {
|
||||
text: {
|
||||
var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/"))
|
||||
return Qt.formatDateTime(weatherDate, "ddd")
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
NIcon {
|
||||
text: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index])
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
|
||||
icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index])
|
||||
font.pointSize: Style.fontSizeXXL * 1.6 * scaling
|
||||
color: Color.mPrimary
|
||||
}
|
||||
NText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: {
|
||||
var max = LocationService.data.weather.daily.temperature_2m_max[index]
|
||||
var min = LocationService.data.weather.daily.temperature_2m_min[index]
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ NPanel {
|
|||
spacing: Style.marginM * scaling
|
||||
|
||||
NIcon {
|
||||
text: Settings.data.network.wifiEnabled ? "wifi" : "wifi_off"
|
||||
icon: Settings.data.network.wifiEnabled ? "wifi" : "wifi-off"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
color: Settings.data.network.wifiEnabled ? Color.mPrimary : Color.mOnSurfaceVariant
|
||||
}
|
||||
|
|
@ -91,7 +91,7 @@ NPanel {
|
|||
spacing: Style.marginS * scaling
|
||||
|
||||
NIcon {
|
||||
text: "error"
|
||||
icon: "warning"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
color: Color.mError
|
||||
}
|
||||
|
|
@ -129,7 +129,7 @@ NPanel {
|
|||
}
|
||||
|
||||
NIcon {
|
||||
text: "wifi_off"
|
||||
icon: "wifi-off"
|
||||
font.pointSize: 64 * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
|
@ -245,7 +245,7 @@ NPanel {
|
|||
spacing: Style.marginS * scaling
|
||||
|
||||
NIcon {
|
||||
text: NetworkService.signalIcon(modelData.signal)
|
||||
icon: NetworkService.signalIcon(modelData.signal)
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
color: modelData.connected ? Color.mPrimary : Color.mOnSurface
|
||||
}
|
||||
|
|
@ -377,7 +377,7 @@ NPanel {
|
|||
&& NetworkService.connectingTo !== modelData.ssid
|
||||
&& NetworkService.forgettingNetwork !== modelData.ssid
|
||||
&& NetworkService.disconnectingFrom !== modelData.ssid
|
||||
icon: "delete"
|
||||
icon: "trash"
|
||||
tooltipText: "Forget network"
|
||||
sizeRatio: 0.7
|
||||
onClicked: expandedSsid = expandedSsid === modelData.ssid ? "" : modelData.ssid
|
||||
|
|
@ -521,7 +521,7 @@ NPanel {
|
|||
|
||||
RowLayout {
|
||||
NIcon {
|
||||
text: "delete_outline"
|
||||
icon: "trash"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
color: Color.mError
|
||||
}
|
||||
|
|
@ -571,7 +571,7 @@ NPanel {
|
|||
}
|
||||
|
||||
NIcon {
|
||||
text: "wifi_find"
|
||||
icon: "search"
|
||||
font.pointSize: 64 * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@
|
|||
</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
A sleek and minimal desktop shell thoughtfully crafted for Wayland, built with Quickshell.
|
||||
|
||||
Features a modern modular architecture with a status bar, notification system, control panel, comprehensive system integration, and more — all styled with a warm lavender palette, or your favorite color scheme!
|
||||
|
|
@ -66,7 +68,6 @@ Features a modern modular architecture with a status bar, notification system, c
|
|||
- `quickshell-git` - Core shell framework
|
||||
- `ttf-roboto` - The default font used for most of the UI
|
||||
- `inter-font` - The default font used for Headers (ex: clock on the LockScreen)
|
||||
- `ttf-material-symbols-variable-git` - Icon font for UI elements
|
||||
- `gpu-screen-recorder` - Screen recording functionality
|
||||
- `brightnessctl` - For internal/laptop monitor brightness
|
||||
- `ddcutil` - For desktop monitor brightness (might introduce some system instability with certain monitors)
|
||||
|
|
@ -341,7 +342,7 @@ Special thanks to the creators of [**Caelestia**](https://github.com/caelestia-d
|
|||
|
||||
While all donations are greatly appreciated, they are completely voluntary.
|
||||
|
||||
<a href="https://ko-fi.com/soramane">
|
||||
<a href="https://ko-fi.com/lysec">
|
||||
<img src="https://img.shields.io/badge/donate-ko--fi-A8AEFF?style=for-the-badge&logo=kofi&logoColor=FFFFFF&labelColor=0C0D11" alt="Ko-Fi" />
|
||||
</a>
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ Singleton {
|
|||
function onMutedChanged() {
|
||||
root._muted = (sink?.audio.muted ?? true)
|
||||
Logger.log("AudioService", "OnMuteChanged:", root._muted)
|
||||
// Toast: audio output mute toggle
|
||||
ToastService.showNotice("Audio Output", root._muted ? "Muted" : "Unmuted")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -79,6 +81,8 @@ Singleton {
|
|||
function onMutedChanged() {
|
||||
root._inputMuted = (source?.audio.muted ?? true)
|
||||
Logger.log("AudioService", "OnInputMuteChanged:", root._inputMuted)
|
||||
// Toast: microphone mute toggle
|
||||
ToastService.showNotice("Microphone", root._inputMuted ? "Muted" : "Unmuted")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ Singleton {
|
|||
// Widget registry object mapping widget names to components
|
||||
property var widgets: ({
|
||||
"ActiveWindow": activeWindowComponent,
|
||||
"ArchUpdater": archUpdaterComponent,
|
||||
"Battery": batteryComponent,
|
||||
"Bluetooth": bluetoothComponent,
|
||||
"Brightness": brightnessComponent,
|
||||
|
|
@ -60,7 +59,7 @@ Singleton {
|
|||
},
|
||||
"CustomButton": {
|
||||
"allowUserSettings": true,
|
||||
"icon": "favorite",
|
||||
"icon": "heart",
|
||||
"leftClickExec": "",
|
||||
"rightClickExec": "",
|
||||
"middleClickExec": ""
|
||||
|
|
@ -82,9 +81,11 @@ Singleton {
|
|||
"allowUserSettings": true,
|
||||
"showCpuUsage": true,
|
||||
"showCpuTemp": true,
|
||||
"showGpuTemp": false,
|
||||
"showMemoryUsage": true,
|
||||
"showMemoryAsPercent": false,
|
||||
"showNetworkStats": false
|
||||
"showNetworkStats": false,
|
||||
"showDiskUsage": false
|
||||
},
|
||||
"Workspace": {
|
||||
"allowUserSettings": true,
|
||||
|
|
@ -110,9 +111,6 @@ Singleton {
|
|||
property Component activeWindowComponent: Component {
|
||||
ActiveWindow {}
|
||||
}
|
||||
property Component archUpdaterComponent: Component {
|
||||
ArchUpdater {}
|
||||
}
|
||||
property Component batteryComponent: Component {
|
||||
Battery {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ pragma Singleton
|
|||
|
||||
import Quickshell
|
||||
import Quickshell.Services.UPower
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
|
@ -9,41 +11,21 @@ Singleton {
|
|||
// Choose icon based on charge and charging state
|
||||
function getIcon(percent, charging, isReady) {
|
||||
if (!isReady) {
|
||||
return "battery_error"
|
||||
return "battery-exclamation"
|
||||
}
|
||||
|
||||
if (charging) {
|
||||
if (percent >= 95)
|
||||
return "battery_full"
|
||||
if (percent >= 85)
|
||||
return "battery_charging_90"
|
||||
if (percent >= 65)
|
||||
return "battery_charging_80"
|
||||
if (percent >= 55)
|
||||
return "battery_charging_60"
|
||||
if (percent >= 45)
|
||||
return "battery_charging_50"
|
||||
if (percent >= 25)
|
||||
return "battery_charging_30"
|
||||
if (percent >= 0)
|
||||
return "battery_charging_20"
|
||||
return "battery-charging"
|
||||
} else {
|
||||
if (percent >= 95)
|
||||
return "battery_full"
|
||||
if (percent >= 85)
|
||||
return "battery_6_bar"
|
||||
if (percent >= 70)
|
||||
return "battery_5_bar"
|
||||
if (percent >= 55)
|
||||
return "battery_4_bar"
|
||||
if (percent >= 40)
|
||||
return "battery_3_bar"
|
||||
if (percent >= 90)
|
||||
return "battery-4"
|
||||
if (percent >= 50)
|
||||
return "battery-3"
|
||||
if (percent >= 25)
|
||||
return "battery_2_bar"
|
||||
if (percent >= 10)
|
||||
return "battery_1_bar"
|
||||
return "battery-2"
|
||||
if (percent >= 0)
|
||||
return "battery_0_bar"
|
||||
return "battery-1"
|
||||
return "battery"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,31 @@ Singleton {
|
|||
})
|
||||
}
|
||||
|
||||
function init() {
|
||||
Logger.log("Bluetooth", "Service initialized")
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: delayDiscovery
|
||||
interval: 1000
|
||||
repeat: false
|
||||
onTriggered: adapter.discovering = true
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: adapter
|
||||
function onEnabledChanged() {
|
||||
Settings.data.network.bluetoothEnabled = adapter.enabled
|
||||
if (adapter.enabled) {
|
||||
ToastService.showNotice("Bluetooth", "Enabled")
|
||||
// Using a timer to give a little time so the adapter is really enabled
|
||||
delayDiscovery.running = true
|
||||
} else {
|
||||
ToastService.showNotice("Bluetooth", "Disabled")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sortDevices(devices) {
|
||||
return devices.sort((a, b) => {
|
||||
var aName = a.name || a.deviceName || ""
|
||||
|
|
@ -51,36 +76,36 @@ Singleton {
|
|||
|
||||
function getDeviceIcon(device) {
|
||||
if (!device) {
|
||||
return "bluetooth"
|
||||
return "bt-device-generic"
|
||||
}
|
||||
|
||||
var name = (device.name || device.deviceName || "").toLowerCase()
|
||||
var icon = (device.icon || "").toLowerCase()
|
||||
if (icon.includes("headset") || icon.includes("audio") || name.includes("headphone") || name.includes("airpod")
|
||||
|| name.includes("headset") || name.includes("arctis")) {
|
||||
return "headset"
|
||||
return "bt-device-headphones"
|
||||
}
|
||||
|
||||
if (icon.includes("mouse") || name.includes("mouse")) {
|
||||
return "mouse"
|
||||
return "bt-device-mouse"
|
||||
}
|
||||
if (icon.includes("keyboard") || name.includes("keyboard")) {
|
||||
return "keyboard"
|
||||
return "bt-device-keyboard"
|
||||
}
|
||||
if (icon.includes("phone") || name.includes("phone") || name.includes("iphone") || name.includes("android")
|
||||
|| name.includes("samsung")) {
|
||||
return "smartphone"
|
||||
return "bt-device-phone"
|
||||
}
|
||||
if (icon.includes("watch") || name.includes("watch")) {
|
||||
return "watch"
|
||||
return "bt-device-watch"
|
||||
}
|
||||
if (icon.includes("speaker") || name.includes("speaker")) {
|
||||
return "speaker"
|
||||
return "bt-device-speaker"
|
||||
}
|
||||
if (icon.includes("display") || name.includes("tv")) {
|
||||
return "tv"
|
||||
return "bt-device-tv"
|
||||
}
|
||||
return "bluetooth"
|
||||
return "bt-device-generic"
|
||||
}
|
||||
|
||||
function canConnect(device) {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ Singleton {
|
|||
Process {
|
||||
id: process
|
||||
stdinEnabled: true
|
||||
running: true
|
||||
running: MediaService.isPlaying
|
||||
command: ["cava", "-p", "/dev/stdin"]
|
||||
onExited: {
|
||||
stdinEnabled = true
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@ Singleton {
|
|||
// Re-apply current scheme to pick the right variant
|
||||
applyScheme(Settings.data.colorSchemes.predefinedScheme)
|
||||
}
|
||||
// Toast: dark/light mode switched
|
||||
const enabled = !!Settings.data.colorSchemes.darkMode
|
||||
const label = enabled ? "Dark Mode" : "Light Mode"
|
||||
const description = enabled ? "Enabled" : "Enabled"
|
||||
ToastService.showNotice(label, description)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -43,8 +48,26 @@ Singleton {
|
|||
folderModel.folder = "file://" + schemesDirectory
|
||||
}
|
||||
|
||||
function applyScheme(filePath) {
|
||||
function getBasename(path) {
|
||||
if (!path)
|
||||
return ""
|
||||
var chunks = path.split("/")
|
||||
var last = chunks[chunks.length - 1]
|
||||
return last.endsWith(".json") ? last.slice(0, -5) : last
|
||||
}
|
||||
|
||||
function resolveSchemePath(nameOrPath) {
|
||||
if (!nameOrPath)
|
||||
return ""
|
||||
if (nameOrPath.indexOf("/") !== -1) {
|
||||
return nameOrPath
|
||||
}
|
||||
return schemesDirectory + "/" + nameOrPath.replace(".json", "") + ".json"
|
||||
}
|
||||
|
||||
function applyScheme(nameOrPath) {
|
||||
// Force reload by bouncing the path
|
||||
var filePath = resolveSchemePath(nameOrPath)
|
||||
schemeReader.path = ""
|
||||
schemeReader.path = filePath
|
||||
}
|
||||
|
|
@ -64,6 +87,17 @@ Singleton {
|
|||
schemes = files
|
||||
scanning = false
|
||||
Logger.log("ColorScheme", "Listed", schemes.length, "schemes")
|
||||
// Normalize stored scheme to basename and re-apply if necessary
|
||||
var stored = Settings.data.colorSchemes.predefinedScheme
|
||||
if (stored) {
|
||||
var basename = getBasename(stored)
|
||||
if (basename !== stored) {
|
||||
Settings.data.colorSchemes.predefinedScheme = basename
|
||||
}
|
||||
if (!Settings.data.colorSchemes.useWallpaperColors) {
|
||||
applyScheme(basename)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -84,7 +118,7 @@ Singleton {
|
|||
}
|
||||
}
|
||||
writeColorsToDisk(variant)
|
||||
Logger.log("ColorScheme", "Applying color scheme:", path)
|
||||
Logger.log("ColorScheme", "Applying color scheme:", getBasename(path))
|
||||
} catch (e) {
|
||||
Logger.error("ColorScheme", "Failed to parse scheme JSON:", e)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -192,9 +192,11 @@ Singleton {
|
|||
}
|
||||
|
||||
windowsList.push({
|
||||
"id": toplevel.address || "",
|
||||
"title": toplevel.title || "",
|
||||
"appId": appId,
|
||||
"id": (toplevel.address !== undefined
|
||||
&& toplevel.address !== null) ? String(toplevel.address) : "",
|
||||
"title": (toplevel.title !== undefined && toplevel.title !== null) ? String(
|
||||
toplevel.title) : "",
|
||||
"appId": (appId !== undefined && appId !== null) ? String(appId) : "",
|
||||
"workspaceId": toplevel.workspace?.id || null,
|
||||
"isFocused": toplevel.activated === true
|
||||
})
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ Singleton {
|
|||
property ListModel displayFonts: ListModel {}
|
||||
property bool fontsLoaded: false
|
||||
|
||||
// -------------------------------------------
|
||||
function init() {
|
||||
Logger.log("Font", "Service started")
|
||||
loadSystemFonts()
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ Singleton {
|
|||
FileView {
|
||||
id: locationFileView
|
||||
path: locationFile
|
||||
printErrors: false
|
||||
onAdapterUpdated: saveTimer.start()
|
||||
onLoaded: {
|
||||
Logger.log("Location", "Loaded cached data")
|
||||
|
|
@ -230,22 +231,24 @@ Singleton {
|
|||
// --------------------------------
|
||||
function weatherSymbolFromCode(code) {
|
||||
if (code === 0)
|
||||
return "sunny"
|
||||
return "weather-sun"
|
||||
if (code === 1 || code === 2)
|
||||
return "partly_cloudy_day"
|
||||
return "weather-cloud-sun"
|
||||
if (code === 3)
|
||||
return "cloud"
|
||||
return "weather-cloud"
|
||||
if (code >= 45 && code <= 48)
|
||||
return "foggy"
|
||||
return "weather-cloud-haze"
|
||||
if (code >= 51 && code <= 67)
|
||||
return "rainy"
|
||||
return "weather-cloud-rain"
|
||||
if (code >= 71 && code <= 77)
|
||||
return "weather_snowy"
|
||||
if (code >= 80 && code <= 82)
|
||||
return "rainy"
|
||||
return "weather-cloud-snow"
|
||||
if (code >= 71 && code <= 77)
|
||||
return "weather-cloud-snow"
|
||||
if (code >= 85 && code <= 86)
|
||||
return "weather-cloud-snow"
|
||||
if (code >= 95 && code <= 99)
|
||||
return "thunderstorm"
|
||||
return "cloud"
|
||||
return "weather-cloud-lightning"
|
||||
return "weather-cloud"
|
||||
}
|
||||
|
||||
// --------------------------------
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ Singleton {
|
|||
FileView {
|
||||
id: cacheFileView
|
||||
path: root.cacheFile
|
||||
printErrors: false
|
||||
|
||||
JsonAdapter {
|
||||
id: cacheAdapter
|
||||
|
|
@ -95,6 +96,7 @@ Singleton {
|
|||
|
||||
function setWifiEnabled(enabled) {
|
||||
Settings.data.network.wifiEnabled = enabled
|
||||
wifiStateEnableProcess.running = true
|
||||
}
|
||||
|
||||
function scan() {
|
||||
|
|
@ -201,14 +203,12 @@ Singleton {
|
|||
// Helper functions
|
||||
function signalIcon(signal) {
|
||||
if (signal >= 80)
|
||||
return "network_wifi"
|
||||
if (signal >= 60)
|
||||
return "network_wifi_3_bar"
|
||||
if (signal >= 40)
|
||||
return "network_wifi_2_bar"
|
||||
return "wifi"
|
||||
if (signal >= 50)
|
||||
return "wifi-2"
|
||||
if (signal >= 20)
|
||||
return "network_wifi_1_bar"
|
||||
return "signal_wifi_0_bar"
|
||||
return "wifi-1"
|
||||
return "wifi-0"
|
||||
}
|
||||
|
||||
function isSecured(security) {
|
||||
|
|
@ -235,6 +235,8 @@ Singleton {
|
|||
}
|
||||
}
|
||||
|
||||
// Only check the state of the actual interface
|
||||
// and update our setting to be in sync.
|
||||
Process {
|
||||
id: wifiStateProcess
|
||||
running: false
|
||||
|
|
@ -243,7 +245,7 @@ Singleton {
|
|||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const enabled = text.trim() === "enabled"
|
||||
Logger.log("Network", "Wi-Fi enabled:", enabled)
|
||||
Logger.log("Network", "Wi-Fi adapter was detect as enabled:", enabled)
|
||||
if (Settings.data.network.wifiEnabled !== enabled) {
|
||||
Settings.data.network.wifiEnabled = enabled
|
||||
}
|
||||
|
|
@ -251,6 +253,29 @@ Singleton {
|
|||
}
|
||||
}
|
||||
|
||||
// Process to enable/disable the Wi-Fi interface
|
||||
Process {
|
||||
id: wifiStateEnableProcess
|
||||
running: false
|
||||
command: ["nmcli", "radio", "wifi", Settings.data.network.wifiEnabled ? "on" : "off"]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
Logger.log("Network", "Wi-Fi state change command executed.")
|
||||
// Re-check the state to ensure it's in sync
|
||||
syncWifiState()
|
||||
}
|
||||
}
|
||||
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text.trim()) {
|
||||
Logger.warn("Network", "Error changing Wi-Fi state: " + text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper process to get existing profiles
|
||||
Process {
|
||||
id: profileCheckProcess
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ Singleton {
|
|||
|
||||
function apply() {
|
||||
// If using LocationService, wait for it to be ready
|
||||
if (params.autoSchedule && !LocationService.coordinatesReady) {
|
||||
if (!params.forced && params.autoSchedule && !LocationService.coordinatesReady) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -34,14 +34,25 @@ Singleton {
|
|||
|
||||
function buildCommand() {
|
||||
var cmd = ["wlsunset"]
|
||||
cmd.push("-t", `${params.nightTemp}`, "-T", `${params.dayTemp}`)
|
||||
if (params.autoSchedule) {
|
||||
cmd.push("-l", `${LocationService.stableLatitude}`, "-L", `${LocationService.stableLongitude}`)
|
||||
if (params.forced) {
|
||||
// Force immediate full night temperature regardless of time
|
||||
// Keep distinct day/night temps but set times so we're effectively always in "night"
|
||||
cmd.push("-t", `${params.nightTemp}`, "-T", `${params.dayTemp}`)
|
||||
// Night spans from sunset (00:00) to sunrise (23:59) covering almost the full day
|
||||
cmd.push("-S", "23:59") // sunrise very late
|
||||
cmd.push("-s", "00:00") // sunset at midnight
|
||||
// Near-instant transition
|
||||
cmd.push("-d", 1)
|
||||
} else {
|
||||
cmd.push("-S", params.manualSunrise)
|
||||
cmd.push("-s", params.manualSunset)
|
||||
cmd.push("-t", `${params.nightTemp}`, "-T", `${params.dayTemp}`)
|
||||
if (params.autoSchedule) {
|
||||
cmd.push("-l", `${LocationService.stableLatitude}`, "-L", `${LocationService.stableLongitude}`)
|
||||
} else {
|
||||
cmd.push("-S", params.manualSunrise)
|
||||
cmd.push("-s", params.manualSunset)
|
||||
}
|
||||
cmd.push("-d", 60 * 15) // 15min progressive fade at sunset/sunrise
|
||||
}
|
||||
cmd.push("-d", 60 * 15) // 15min progressive fade at sunset/sunrise
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
|
@ -50,6 +61,15 @@ Singleton {
|
|||
target: Settings.data.nightLight
|
||||
function onEnabledChanged() {
|
||||
apply()
|
||||
// Toast: night light toggled
|
||||
const enabled = !!Settings.data.nightLight.enabled
|
||||
ToastService.showNotice("Night Light", enabled ? "Enabled" : "Disabled")
|
||||
}
|
||||
function onForcedChanged() {
|
||||
apply()
|
||||
if (Settings.data.nightLight.enabled) {
|
||||
ToastService.showNotice("Night Light", Settings.data.nightLight.forced ? "Forced activation" : "Normal mode")
|
||||
}
|
||||
}
|
||||
function onNightTempChanged() {
|
||||
apply()
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ Singleton {
|
|||
id: historyFileView
|
||||
objectName: "notificationHistoryFileView"
|
||||
path: historyFile
|
||||
printErrors: false
|
||||
watchChanges: true
|
||||
onFileChanged: reload()
|
||||
onAdapterUpdated: writeAdapter()
|
||||
|
|
@ -116,14 +117,51 @@ Singleton {
|
|||
}
|
||||
}
|
||||
|
||||
// Function to resolve app name from notification
|
||||
function resolveAppName(notification) {
|
||||
try {
|
||||
const appName = notification.appName || ""
|
||||
|
||||
// If it's already a clean name (no dots or reverse domain notation), use it
|
||||
if (!appName.includes(".") || appName.length < 10) {
|
||||
return appName
|
||||
}
|
||||
|
||||
// Try to find a desktop entry for this app ID
|
||||
const desktopEntries = DesktopEntries.byId(appName)
|
||||
if (desktopEntries && desktopEntries.length > 0) {
|
||||
const entry = desktopEntries[0]
|
||||
// Prefer name over genericName, fallback to original appName
|
||||
return entry.name || entry.genericName || appName
|
||||
}
|
||||
|
||||
// If no desktop entry found, try to clean up the app ID
|
||||
// Convert "org.gnome.Nautilus" to "Nautilus"
|
||||
const parts = appName.split(".")
|
||||
if (parts.length > 1) {
|
||||
// Take the last part and capitalize it
|
||||
const lastPart = parts[parts.length - 1]
|
||||
return lastPart.charAt(0).toUpperCase() + lastPart.slice(1)
|
||||
}
|
||||
|
||||
return appName
|
||||
} catch (e) {
|
||||
// Fallback to original app name on any error
|
||||
return notification.appName || ""
|
||||
}
|
||||
}
|
||||
|
||||
// Function to add notification to model
|
||||
function addNotification(notification) {
|
||||
const resolvedImage = resolveNotificationImage(notification)
|
||||
const resolvedAppName = resolveAppName(notification)
|
||||
|
||||
notificationModel.insert(0, {
|
||||
"rawNotification": notification,
|
||||
"summary": notification.summary,
|
||||
"body": notification.body,
|
||||
"appName": notification.appName,
|
||||
"appName": resolvedAppName,
|
||||
"desktopEntry": notification.desktopEntry,
|
||||
"image": resolvedImage,
|
||||
"appIcon": notification.appIcon,
|
||||
"urgency": notification.urgency,
|
||||
|
|
@ -164,7 +202,7 @@ Singleton {
|
|||
|
||||
// Resolve themed icon names to absolute paths
|
||||
try {
|
||||
const p = Icons.iconFromName(icon, "")
|
||||
const p = AppIcons.iconFromName(icon, "")
|
||||
return p || ""
|
||||
} catch (e2) {
|
||||
return ""
|
||||
|
|
@ -174,12 +212,17 @@ Singleton {
|
|||
}
|
||||
}
|
||||
|
||||
// Add a simplified copy into persistent history
|
||||
function addToHistory(notification) {
|
||||
const resolvedAppName = resolveAppName(notification)
|
||||
const resolvedImage = resolveNotificationImage(notification)
|
||||
|
||||
historyModel.insert(0, {
|
||||
"summary": notification.summary,
|
||||
"body": notification.body,
|
||||
"appName": notification.appName,
|
||||
"appName": resolvedAppName,
|
||||
"desktopEntry": notification.desktopEntry || "",
|
||||
"image": resolvedImage,
|
||||
"appIcon": notification.appIcon || "",
|
||||
"urgency": notification.urgency,
|
||||
"timestamp": new Date()
|
||||
})
|
||||
|
|
@ -210,6 +253,9 @@ Singleton {
|
|||
"summary": it.summary || "",
|
||||
"body": it.body || "",
|
||||
"appName": it.appName || "",
|
||||
"desktopEntry": it.desktopEntry || "",
|
||||
"image": it.image || "",
|
||||
"appIcon": it.appIcon || "",
|
||||
"urgency": it.urgency,
|
||||
"timestamp": ts ? new Date(ts) : new Date()
|
||||
})
|
||||
|
|
@ -229,6 +275,9 @@ Singleton {
|
|||
"summary": n.summary,
|
||||
"body": n.body,
|
||||
"appName": n.appName,
|
||||
"desktopEntry": n.desktopEntry,
|
||||
"image": n.image,
|
||||
"appIcon": n.appIcon,
|
||||
"urgency": n.urgency,
|
||||
"timestamp"// Always persist in milliseconds
|
||||
: (n.timestamp instanceof Date) ? n.timestamp.getTime(
|
||||
|
|
|
|||
62
Services/PowerProfileService.qml
Normal file
62
Services/PowerProfileService.qml
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Services.UPower
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property var powerProfiles: PowerProfiles
|
||||
readonly property bool available: powerProfiles && powerProfiles.hasPerformanceProfile
|
||||
property int profile: powerProfiles ? powerProfiles.profile : PowerProfile.Balanced
|
||||
|
||||
function profileName(p) {
|
||||
const prof = (p !== undefined) ? p : profile
|
||||
if (!available)
|
||||
return "Unknown"
|
||||
if (prof === PowerProfile.Performance)
|
||||
return "Performance"
|
||||
if (prof === PowerProfile.Balanced)
|
||||
return "Balanced"
|
||||
if (prof === PowerProfile.PowerSaver)
|
||||
return "Power Saver"
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
function setProfile(p) {
|
||||
if (!available)
|
||||
return
|
||||
try {
|
||||
powerProfiles.profile = p
|
||||
} catch (e) {
|
||||
Logger.error("PowerProfileService", "Failed to set profile:", e)
|
||||
}
|
||||
}
|
||||
|
||||
function cycleProfile() {
|
||||
if (!available)
|
||||
return
|
||||
const current = powerProfiles.profile
|
||||
if (current === PowerProfile.Performance)
|
||||
setProfile(PowerProfile.PowerSaver)
|
||||
else if (current === PowerProfile.Balanced)
|
||||
setProfile(PowerProfile.Performance)
|
||||
else if (current === PowerProfile.PowerSaver)
|
||||
setProfile(PowerProfile.Balanced)
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: powerProfiles
|
||||
function onProfileChanged() {
|
||||
root.profile = powerProfiles.profile
|
||||
// Only show toast if we have a valid profile name (not "Unknown")
|
||||
const profileName = root.profileName()
|
||||
if (profileName !== "Unknown") {
|
||||
ToastService.showNotice("Power Profile", profileName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,17 @@ Singleton {
|
|||
property bool isRecording: false
|
||||
property bool isPending: false
|
||||
property string outputPath: ""
|
||||
property bool isAvailable: false
|
||||
|
||||
Component.onCompleted: {
|
||||
checkAvailability()
|
||||
}
|
||||
|
||||
function checkAvailability() {
|
||||
// Detect native or Flatpak gpu-screen-recorder
|
||||
availabilityCheckProcess.command = ["sh", "-c", "command -v gpu-screen-recorder >/dev/null 2>&1 || (command -v flatpak >/dev/null 2>&1 && flatpak list --app | grep -q 'com.dec05eba.gpu_screen_recorder')"]
|
||||
availabilityCheckProcess.running = true
|
||||
}
|
||||
|
||||
// Start or Stop recording
|
||||
function toggleRecording() {
|
||||
|
|
@ -21,6 +32,9 @@ Singleton {
|
|||
|
||||
// Start screen recording using Quickshell.execDetached
|
||||
function startRecording() {
|
||||
if (!isAvailable) {
|
||||
return
|
||||
}
|
||||
if (isRecording || isPending) {
|
||||
return
|
||||
}
|
||||
|
|
@ -88,6 +102,18 @@ Singleton {
|
|||
}
|
||||
}
|
||||
|
||||
// Availability check process
|
||||
Process {
|
||||
id: availabilityCheckProcess
|
||||
command: ["sh", "-c", "true"]
|
||||
onExited: function (exitCode, exitStatus) {
|
||||
// exitCode 0 means available, non-zero means unavailable
|
||||
root.isAvailable = (exitCode === 0)
|
||||
}
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: pendingTimer
|
||||
interval: 2000 // Wait 2 seconds to see if process stays alive
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ Singleton {
|
|||
// Public values
|
||||
property real cpuUsage: 0
|
||||
property real cpuTemp: 0
|
||||
property real gpuTemp: 0
|
||||
property real memGb: 0
|
||||
property real memPercent: 0
|
||||
property real diskPercent: 0
|
||||
|
|
@ -35,6 +36,12 @@ Singleton {
|
|||
readonly property var supportedTempCpuSensorNames: ["coretemp", "k10temp", "zenpower"]
|
||||
property string cpuTempSensorName: ""
|
||||
property string cpuTempHwmonPath: ""
|
||||
// Gpu temperature (simple hwmon read if available)
|
||||
readonly property var supportedTempGpuSensorNames: ["amdgpu", "nvidia", "radeon"]
|
||||
property string gpuTempSensorName: ""
|
||||
property string gpuTempHwmonPath: ""
|
||||
property bool gpuIsDedicated: false
|
||||
property string _gpuPendingAmdPath: ""
|
||||
// For Intel coretemp averaging of all cores/sensors
|
||||
property var intelTempValues: []
|
||||
property int intelTempFilesChecked: 0
|
||||
|
|
@ -66,6 +73,7 @@ Singleton {
|
|||
dfProcess.running = true
|
||||
|
||||
updateCpuTemperature()
|
||||
updateGpuTemperature()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -107,6 +115,7 @@ Singleton {
|
|||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------
|
||||
// --------------------------------------------
|
||||
// CPU Temperature
|
||||
// It's more complex.
|
||||
|
|
@ -115,9 +124,10 @@ Singleton {
|
|||
FileView {
|
||||
id: cpuTempNameReader
|
||||
property int currentIndex: 0
|
||||
printErrors: false
|
||||
|
||||
function checkNext() {
|
||||
if (currentIndex >= 10) {
|
||||
if (currentIndex >= 16) {
|
||||
// Check up to hwmon10
|
||||
Logger.warn("No supported temperature sensor found")
|
||||
return
|
||||
|
|
@ -182,6 +192,109 @@ Singleton {
|
|||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------
|
||||
// --------------------------------------------
|
||||
// ---- GPU temperature detection (hwmon)
|
||||
FileView {
|
||||
id: gpuTempNameReader
|
||||
property int currentIndex: 0
|
||||
printErrors: false
|
||||
|
||||
function checkNext() {
|
||||
if (currentIndex >= 16) {
|
||||
// Check up to hwmon10
|
||||
Logger.warn("SystemStat", "No supported GPU temperature sensor found")
|
||||
return
|
||||
}
|
||||
|
||||
gpuTempNameReader.path = `/sys/class/hwmon/hwmon${currentIndex}/name`
|
||||
gpuTempNameReader.reload()
|
||||
}
|
||||
|
||||
Component.onCompleted: checkNext()
|
||||
|
||||
onLoaded: {
|
||||
const name = text().trim()
|
||||
if (root.supportedTempGpuSensorNames.includes(name)) {
|
||||
const hwPath = `/sys/class/hwmon/hwmon${currentIndex}`
|
||||
if (name === "nvidia") {
|
||||
// Treat NVIDIA as dedicated by default
|
||||
root.gpuTempSensorName = name
|
||||
root.gpuTempHwmonPath = hwPath
|
||||
root.gpuIsDedicated = true
|
||||
Logger.log("SystemStat", `Selected NVIDIA GPU thermal sensor at ${root.gpuTempHwmonPath}`)
|
||||
} else if (name === "amdgpu") {
|
||||
// Probe VRAM to distinguish dGPU vs iGPU
|
||||
root._gpuPendingAmdPath = hwPath
|
||||
vramReader.requestCheck(hwPath)
|
||||
} else if (!root.gpuTempHwmonPath) {
|
||||
// Fallback to first supported sensor (e.g., radeon)
|
||||
root.gpuTempSensorName = name
|
||||
root.gpuTempHwmonPath = hwPath
|
||||
Logger.log("SystemStat", `Selected GPU thermal sensor at ${root.gpuTempHwmonPath}`)
|
||||
}
|
||||
} else {
|
||||
currentIndex++
|
||||
Qt.callLater(() => {
|
||||
checkNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onLoadFailed: function (error) {
|
||||
currentIndex++
|
||||
Qt.callLater(() => {
|
||||
checkNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Reader to detect AMD dGPU by checking VRAM presence
|
||||
FileView {
|
||||
id: vramReader
|
||||
property string targetHwmonPath: ""
|
||||
function requestCheck(hwPath) {
|
||||
targetHwmonPath = hwPath
|
||||
vramReader.path = `${hwPath}/device/mem_info_vram_total`
|
||||
vramReader.reload()
|
||||
}
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
const val = parseInt(text().trim())
|
||||
// If VRAM present (>0), prefer this as dGPU
|
||||
if (!isNaN(val) && val > 0) {
|
||||
root.gpuTempSensorName = "amdgpu"
|
||||
root.gpuTempHwmonPath = targetHwmonPath
|
||||
root.gpuIsDedicated = true
|
||||
Logger.log("SystemStat",
|
||||
`Selected AMD dGPU (VRAM=${Math.round(val / (1024 * 1024 * 1024))}GB) at ${root.gpuTempHwmonPath}`)
|
||||
} else if (!root.gpuTempHwmonPath) {
|
||||
// Use as fallback iGPU if nothing selected yet
|
||||
root.gpuTempSensorName = "amdgpu"
|
||||
root.gpuTempHwmonPath = targetHwmonPath
|
||||
root.gpuIsDedicated = false
|
||||
Logger.log("SystemStat", `Selected AMD GPU (no VRAM) at ${root.gpuTempHwmonPath}`)
|
||||
}
|
||||
// Continue scanning other hwmon entries
|
||||
gpuTempNameReader.currentIndex++
|
||||
Qt.callLater(() => {
|
||||
gpuTempNameReader.checkNext()
|
||||
})
|
||||
}
|
||||
onLoadFailed: function (error) {
|
||||
// If failed to read VRAM, consider as fallback if none selected
|
||||
if (!root.gpuTempHwmonPath) {
|
||||
root.gpuTempSensorName = "amdgpu"
|
||||
root.gpuTempHwmonPath = targetHwmonPath
|
||||
}
|
||||
gpuTempNameReader.currentIndex++
|
||||
Qt.callLater(() => {
|
||||
gpuTempNameReader.checkNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// -------------------------------------------------------
|
||||
// Parse memory info from /proc/meminfo
|
||||
function parseMemoryInfo(text) {
|
||||
|
|
@ -321,10 +434,8 @@ Singleton {
|
|||
// -------------------------------------------------------
|
||||
// Helper function to format network speeds
|
||||
function formatSpeed(bytesPerSecond) {
|
||||
if (bytesPerSecond < 1024) {
|
||||
return bytesPerSecond.toFixed(0) + "B/s"
|
||||
} else if (bytesPerSecond < 1024 * 1024) {
|
||||
return (bytesPerSecond / 1024).toFixed(0) + "KB/s"
|
||||
if (bytesPerSecond < 1024 * 1024) {
|
||||
return (bytesPerSecond / 1024).toFixed(1) + "KB/s"
|
||||
} else if (bytesPerSecond < 1024 * 1024 * 1024) {
|
||||
return (bytesPerSecond / (1024 * 1024)).toFixed(1) + "MB/s"
|
||||
} else {
|
||||
|
|
@ -348,6 +459,26 @@ Singleton {
|
|||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// Function to start/refresh the GPU temperature
|
||||
function updateGpuTemperature() {
|
||||
if (!root.gpuTempHwmonPath)
|
||||
return
|
||||
gpuTempReader.path = `${root.gpuTempHwmonPath}/temp1_input`
|
||||
gpuTempReader.reload()
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: gpuTempReader
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
const data = parseInt(text().trim())
|
||||
if (!isNaN(data)) {
|
||||
root.gpuTemp = Math.round(data / 1000.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// Function to check next Intel temperature sensor
|
||||
function checkNextIntelTemp() {
|
||||
|
|
|
|||
|
|
@ -185,6 +185,11 @@ Singleton {
|
|||
// Process the message queue
|
||||
function processQueue() {
|
||||
if (messageQueue.length === 0 || allToasts.length === 0) {
|
||||
// Added this so we don't accidentally get duplicate toasts
|
||||
// if it causes issues, remove it and we'll find a different solution
|
||||
if (allToasts.length === 0 && messageQueue.length > 0) {
|
||||
messageQueue = []
|
||||
}
|
||||
isShowingToast = false
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ Singleton {
|
|||
id: root
|
||||
|
||||
// Public properties
|
||||
property string baseVersion: "2.7.0"
|
||||
property bool isDevelopment: true
|
||||
property string baseVersion: "2.8.0"
|
||||
property bool isDevelopment: false
|
||||
|
||||
property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}`
|
||||
|
||||
|
|
|
|||
|
|
@ -216,11 +216,7 @@ Singleton {
|
|||
// -------------------------------------------------------------------
|
||||
// Get specific monitor wallpaper - now from cache
|
||||
function getWallpaper(screenName) {
|
||||
var path = currentWallpapers[screenName] || ""
|
||||
if (path === "") {
|
||||
return Settings.data.wallpaper.defaultWallpaper || ""
|
||||
}
|
||||
return path
|
||||
return currentWallpapers[screenName] || Settings.defaultWallpaper
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -82,9 +82,9 @@ Rectangle {
|
|||
// Icon (optional)
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
layoutTopMargin: 1 * scaling
|
||||
visible: root.icon !== ""
|
||||
text: root.icon
|
||||
|
||||
icon: root.icon
|
||||
font.pointSize: root.iconSize
|
||||
color: {
|
||||
if (!root.enabled)
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ RowLayout {
|
|||
NIcon {
|
||||
visible: root.checked
|
||||
anchors.centerIn: parent
|
||||
text: "check"
|
||||
icon: "check"
|
||||
color: root.activeOnColor
|
||||
font.pointSize: Math.max(Style.fontSizeS, root.baseSize * 0.7) * scaling
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,20 +88,21 @@ Rectangle {
|
|||
// Tiny circular badge for the icon, positioned using anchors within the gauge
|
||||
Rectangle {
|
||||
id: iconBadge
|
||||
width: 28 * scaling * contentScale
|
||||
width: iconText.implicitWidth + Style.marginXS * scaling
|
||||
height: width
|
||||
radius: width / 2
|
||||
color: Color.mSurface
|
||||
color: Color.mPrimary
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: -6 * scaling * contentScale
|
||||
anchors.topMargin: Style.marginXXS * scaling * contentScale
|
||||
anchors.rightMargin: -2 * scaling
|
||||
anchors.topMargin: -2 * scaling
|
||||
|
||||
NIcon {
|
||||
id: iconText
|
||||
anchors.centerIn: parent
|
||||
text: root.icon
|
||||
font.pointSize: Style.fontSizeLargeXL * scaling * contentScale
|
||||
color: Color.mOnSurface
|
||||
icon: root.icon
|
||||
color: Color.mOnPrimary
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import QtQuick
|
|||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
|
|
@ -58,7 +59,7 @@ Rectangle {
|
|||
}
|
||||
|
||||
NIcon {
|
||||
text: "palette"
|
||||
icon: "color-picker"
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import QtQuick
|
|||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Popup {
|
||||
|
|
@ -129,7 +130,7 @@ Popup {
|
|||
spacing: Style.marginS * scaling
|
||||
|
||||
NIcon {
|
||||
text: "palette"
|
||||
icon: "color-picker"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
|
@ -491,7 +492,6 @@ Popup {
|
|||
NButton {
|
||||
id: cancelButton
|
||||
text: "Cancel"
|
||||
icon: "close"
|
||||
outlined: cancelButton.hovered ? false : true
|
||||
customHeight: 36 * scaling
|
||||
customWidth: 100 * scaling
|
||||
|
|
|
|||
|
|
@ -85,8 +85,8 @@ RowLayout {
|
|||
indicator: NIcon {
|
||||
x: combo.width - width - Style.marginM * scaling
|
||||
y: combo.topPadding + (combo.availableHeight - height) / 2
|
||||
text: "arrow_drop_down"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
icon: "caret-down"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
}
|
||||
|
||||
popup: Popup {
|
||||
|
|
|
|||
|
|
@ -1,19 +1,27 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import QtQuick.Layouts
|
||||
|
||||
Text {
|
||||
// Optional layout nudge for optical alignment when used inside Layouts
|
||||
property real layoutTopMargin: 0
|
||||
text: "question_mark"
|
||||
font.family: "Material Symbols Rounded"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.variableAxes: {
|
||||
"wght"// slightly bold to ensure all lines looks good
|
||||
: (Font.Normal + Font.Bold) / 2.5
|
||||
id: root
|
||||
|
||||
property string icon: Icons.defaultIcon
|
||||
|
||||
visible: (icon !== undefined) && (icon !== "")
|
||||
text: {
|
||||
if ((icon === undefined) || (icon === "")) {
|
||||
return ""
|
||||
}
|
||||
if (Icons.get(icon) === undefined) {
|
||||
Logger.warn("Icon", `"${icon}"`, "doesn't exist in the icons font")
|
||||
Logger.callStack()
|
||||
return Icons.get(Icons.defaultIcon)
|
||||
}
|
||||
return Icons.get(icon)
|
||||
}
|
||||
font.family: Icons.fontFamily
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
color: Color.mOnSurface
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.topMargin: layoutTopMargin
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ Rectangle {
|
|||
property string icon
|
||||
property string tooltipText
|
||||
property bool enabled: true
|
||||
property bool allowClickWhenDisabled: false
|
||||
property bool hovering: false
|
||||
|
||||
property color colorBg: Color.mSurfaceVariant
|
||||
|
|
@ -35,17 +36,31 @@ Rectangle {
|
|||
opacity: root.enabled ? Style.opacityFull : Style.opacityMedium
|
||||
color: root.enabled && root.hovering ? colorBgHover : colorBg
|
||||
radius: width * 0.5
|
||||
border.color: root.hovering ? colorBorderHover : colorBorder
|
||||
border.color: root.enabled && root.hovering ? colorBorderHover : colorBorder
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
NIcon {
|
||||
text: root.icon
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
color: root.hovering ? colorFgHover : colorFg
|
||||
icon: root.icon
|
||||
font.pointSize: Math.max(1, root.width * 0.47)
|
||||
color: root.enabled && root.hovering ? colorFgHover : colorFg
|
||||
// Center horizontally
|
||||
x: (root.width - width) / 2
|
||||
// Center vertically accounting for font metrics
|
||||
y: (root.height - height) / 2 + (height - contentHeight) / 2
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NTooltip {
|
||||
|
|
@ -56,13 +71,14 @@ Rectangle {
|
|||
}
|
||||
|
||||
MouseArea {
|
||||
enabled: root.enabled
|
||||
// Always enabled to allow hover/tooltip even when the button is disabled
|
||||
enabled: true
|
||||
anchors.fill: parent
|
||||
cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
hoverEnabled: true
|
||||
onEntered: {
|
||||
hovering = true
|
||||
hovering = root.enabled ? true : false
|
||||
if (tooltipText) {
|
||||
tooltip.show()
|
||||
}
|
||||
|
|
@ -79,6 +95,9 @@ Rectangle {
|
|||
if (tooltipText) {
|
||||
tooltip.hide()
|
||||
}
|
||||
if (!root.enabled && !allowClickWhenDisabled) {
|
||||
return
|
||||
}
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
root.clicked()
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
|
|
|
|||
|
|
@ -9,9 +9,10 @@ Rectangle {
|
|||
id: root
|
||||
|
||||
property string imagePath: ""
|
||||
property string fallbackIcon: ""
|
||||
property color borderColor: Color.transparent
|
||||
property real borderWidth: 0
|
||||
property string fallbackIcon: ""
|
||||
property real fallbackIconSize: Style.fontSizeXXL * scaling
|
||||
|
||||
color: Color.transparent
|
||||
radius: parent.width * 0.5
|
||||
|
|
@ -45,18 +46,20 @@ Rectangle {
|
|||
}
|
||||
|
||||
property real imageOpacity: root.opacity
|
||||
fragmentShader: Qt.resolvedUrl("../Shaders/qsb/circled_image.frag.qsb")
|
||||
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/circled_image.frag.qsb")
|
||||
supportsAtlasTextures: false
|
||||
blending: true
|
||||
}
|
||||
|
||||
// Fallback icon
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
text: fallbackIcon
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
visible: fallbackIcon !== undefined && fallbackIcon !== "" && (imagePath === undefined || imagePath === "")
|
||||
z: 0
|
||||
Loader {
|
||||
active: fallbackIcon !== undefined && fallbackIcon !== "" && (imagePath === undefined || imagePath === "")
|
||||
sourceComponent: NIcon {
|
||||
anchors.centerIn: parent
|
||||
icon: fallbackIcon
|
||||
font.pointSize: fallbackIconSize
|
||||
z: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,10 +9,11 @@ Rectangle {
|
|||
id: root
|
||||
|
||||
property string imagePath: ""
|
||||
property string fallbackIcon: ""
|
||||
property color borderColor: Color.transparent
|
||||
property real borderWidth: 0
|
||||
property real imageRadius: width * 0.5
|
||||
property string fallbackIcon: ""
|
||||
property real fallbackIconSize: Style.fontSizeXXL * scaling
|
||||
|
||||
property real scaledRadius: imageRadius * Settings.data.general.radiusRatio
|
||||
|
||||
|
|
@ -56,7 +57,7 @@ Rectangle {
|
|||
property real itemHeight: root.height
|
||||
property real cornerRadius: root.radius
|
||||
property real imageOpacity: root.opacity
|
||||
fragmentShader: Qt.resolvedUrl("../Shaders/qsb/rounded_image.frag.qsb")
|
||||
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/rounded_image.frag.qsb")
|
||||
|
||||
// Qt6 specific properties - ensure proper blending
|
||||
supportsAtlasTextures: false
|
||||
|
|
@ -71,12 +72,14 @@ Rectangle {
|
|||
}
|
||||
|
||||
// Fallback icon
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
text: fallbackIcon
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
visible: fallbackIcon !== undefined && fallbackIcon !== "" && (imagePath === undefined || imagePath === "")
|
||||
z: 0
|
||||
Loader {
|
||||
active: fallbackIcon !== undefined && fallbackIcon !== "" && (imagePath === undefined || imagePath === "")
|
||||
sourceComponent: NIcon {
|
||||
anchors.centerIn: parent
|
||||
icon: fallbackIcon
|
||||
font.pointSize: fallbackIconSize
|
||||
z: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import QtQuick
|
|||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services
|
||||
|
||||
// Input and button row
|
||||
RowLayout {
|
||||
|
|
@ -13,7 +14,7 @@ RowLayout {
|
|||
property string placeholderText: ""
|
||||
property string text: ""
|
||||
property string actionButtonText: "Test"
|
||||
property string actionButtonIcon: "play_arrow"
|
||||
property string actionButtonIcon: "media-play"
|
||||
property bool actionButtonEnabled: text !== ""
|
||||
|
||||
// Signals
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ Loader {
|
|||
property int buttonWidth: 0
|
||||
property int buttonHeight: 0
|
||||
|
||||
// Whether this panel should accept keyboard focus
|
||||
property bool panelKeyboardFocus: false
|
||||
property bool backgroundClickEnabled: true
|
||||
|
||||
// Animation properties
|
||||
readonly property real originalScale: 0.7
|
||||
|
|
@ -62,6 +62,24 @@ Loader {
|
|||
PanelService.registerPanel(root)
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// Functions to control background click behavior
|
||||
function disableBackgroundClick() {
|
||||
backgroundClickEnabled = false
|
||||
}
|
||||
|
||||
function enableBackgroundClick() {
|
||||
// Add a small delay to prevent immediate close after drag release
|
||||
enableBackgroundClickTimer.restart()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: enableBackgroundClickTimer
|
||||
interval: 100
|
||||
repeat: false
|
||||
onTriggered: backgroundClickEnabled = true
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
function toggle(aScreen, buttonItem) {
|
||||
// Don't toggle if screen is null or invalid
|
||||
|
|
@ -110,6 +128,7 @@ Loader {
|
|||
|
||||
PanelService.willOpenPanel(root)
|
||||
|
||||
backgroundClickEnabled = true
|
||||
active = true
|
||||
root.opened()
|
||||
}
|
||||
|
|
@ -125,7 +144,8 @@ Loader {
|
|||
function closeCompleted() {
|
||||
root.closed()
|
||||
active = false
|
||||
useButtonPosition = false // Reset button position usage
|
||||
useButtonPosition = false
|
||||
backgroundClickEnabled = true
|
||||
PanelService.closedPanel(root)
|
||||
}
|
||||
|
||||
|
|
@ -179,6 +199,7 @@ Loader {
|
|||
// Clicking outside of the rectangle to close
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.backgroundClickEnabled
|
||||
onClicked: root.close()
|
||||
}
|
||||
|
||||
|
|
@ -208,7 +229,7 @@ Loader {
|
|||
|
||||
return Math.round(Math.max(minX, Math.min(targetX, maxX)))
|
||||
} else if (!panelAnchorHorizontalCenter && panelAnchorLeft) {
|
||||
return Math.round(marginS * scaling)
|
||||
return Math.round(Style.marginS * scaling)
|
||||
} else if (!panelAnchorHorizontalCenter && panelAnchorRight) {
|
||||
return Math.round(panelWindow.width - panelWidth - (Style.marginS * scaling))
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -9,21 +9,15 @@ Item {
|
|||
property string icon: ""
|
||||
property string text: ""
|
||||
property string tooltipText: ""
|
||||
property color pillColor: Color.mSurfaceVariant
|
||||
property color textColor: Color.mOnSurface
|
||||
property color iconCircleColor: Color.mPrimary
|
||||
property color iconTextColor: Color.mSurface
|
||||
property color collapsedIconColor: Color.mOnSurface
|
||||
|
||||
property real iconRotation: 0
|
||||
property real sizeRatio: 0.8
|
||||
property bool autoHide: false
|
||||
property bool forceOpen: false
|
||||
property bool disableOpen: false
|
||||
property bool rightOpen: false
|
||||
property bool hovered: false
|
||||
|
||||
// Effective shown state (true if hovered/animated open or forced)
|
||||
readonly property bool effectiveShown: forceOpen || showPill
|
||||
readonly property bool revealed: forceOpen || showPill
|
||||
|
||||
signal shown
|
||||
signal hidden
|
||||
|
|
@ -50,14 +44,14 @@ Item {
|
|||
|
||||
Rectangle {
|
||||
id: pill
|
||||
width: effectiveShown ? maxPillWidth : 1
|
||||
width: revealed ? maxPillWidth : 1
|
||||
height: pillHeight
|
||||
|
||||
x: rightOpen ? (iconCircle.x + iconCircle.width / 2) : // Opens right
|
||||
(iconCircle.x + iconCircle.width / 2) - width // Opens left
|
||||
|
||||
opacity: effectiveShown ? Style.opacityFull : Style.opacityNone
|
||||
color: pillColor
|
||||
opacity: revealed ? Style.opacityFull : Style.opacityNone
|
||||
color: Color.mSurfaceVariant
|
||||
|
||||
topLeftRadius: rightOpen ? 0 : pillHeight * 0.5
|
||||
bottomLeftRadius: rightOpen ? 0 : pillHeight * 0.5
|
||||
|
|
@ -77,8 +71,8 @@ Item {
|
|||
text: root.text
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: textColor
|
||||
visible: effectiveShown
|
||||
color: Color.mPrimary
|
||||
visible: revealed
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
|
|
@ -102,11 +96,8 @@ Item {
|
|||
width: iconSize
|
||||
height: iconSize
|
||||
radius: width * 0.5
|
||||
// When forced shown, match pill background; otherwise use accent when hovered
|
||||
color: forceOpen ? pillColor : (showPill ? iconCircleColor : Color.mSurfaceVariant)
|
||||
color: hovered && !forceOpen ? Color.mPrimary : Color.mSurfaceVariant
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
border.color: forceOpen ? Qt.alpha(Color.mOutline, 0.5) : Color.transparent
|
||||
|
||||
x: rightOpen ? 0 : (parent.width - width)
|
||||
|
||||
|
|
@ -118,11 +109,9 @@ Item {
|
|||
}
|
||||
|
||||
NIcon {
|
||||
text: root.icon
|
||||
rotation: root.iconRotation
|
||||
icon: root.icon
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
// When forced shown, use pill text color; otherwise accent color when hovered
|
||||
color: forceOpen ? textColor : (showPill ? iconTextColor : Color.mOnSurface)
|
||||
color: hovered && !forceOpen ? Color.mOnPrimary : Color.mOnSurface
|
||||
// Center horizontally
|
||||
x: (iconCircle.width - width) / 2
|
||||
// Center vertically accounting for font metrics
|
||||
|
|
@ -220,6 +209,7 @@ Item {
|
|||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onEntered: {
|
||||
hovered = true
|
||||
root.entered()
|
||||
tooltip.show()
|
||||
if (disableOpen) {
|
||||
|
|
@ -230,6 +220,7 @@ Item {
|
|||
}
|
||||
}
|
||||
onExited: {
|
||||
hovered = false
|
||||
root.exited()
|
||||
if (!forceOpen) {
|
||||
hide()
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ RowLayout {
|
|||
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
text: "remove"
|
||||
icon: "chevron-left"
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
color: decreaseArea.containsMouse ? Color.mOnPrimary : Color.mPrimary
|
||||
}
|
||||
|
|
@ -130,7 +130,7 @@ RowLayout {
|
|||
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
text: "add"
|
||||
icon: "chevron-right"
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
color: increaseArea.containsMouse ? Color.mOnPrimary : Color.mPrimary
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,23 +112,13 @@ Item {
|
|||
RowLayout {
|
||||
id: contentLayout
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM * scaling
|
||||
spacing: Style.marginS * scaling
|
||||
anchors.margins: Style.marginL * scaling
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
// Icon
|
||||
NIcon {
|
||||
id: icon
|
||||
text: {
|
||||
switch (root.type) {
|
||||
case "warning":
|
||||
return "warning"
|
||||
case "notice":
|
||||
return "info"
|
||||
default:
|
||||
return "info"
|
||||
}
|
||||
}
|
||||
|
||||
icon: (root.type == "warning") ? "toast-warning" : "toast-notice"
|
||||
color: {
|
||||
switch (root.type) {
|
||||
case "warning":
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue