Merge branch 'bootstrap-icons'

This commit is contained in:
Ly-sec 2025-09-09 16:14:18 +02:00
commit a4534b1611
77 changed files with 2939 additions and 720 deletions

View file

@ -1,19 +1,19 @@
{ {
"dark": { "dark": {
"mPrimary": "#ebbcba", "mPrimary": "#ebbcba",
"mOnPrimary": "#1f1d2e", "mOnPrimary": "#191724",
"mSecondary": "#9ccfd8", "mSecondary": "#9ccfd8",
"mOnSecondary": "#1f1d2e", "mOnSecondary": "#191724",
"mTertiary": "#f6c177", "mTertiary": "#f6c177",
"mOnTertiary": "#1f1d2e", "mOnTertiary": "#191724",
"mError": "#eb6f92", "mError": "#eb6f92",
"mOnError": "#1f1d2e", "mOnError": "#191724",
"mSurface": "#1f1d2e", "mSurface": "#191724",
"mOnSurface": "#e0def4", "mOnSurface": "#e0def4",
"mSurfaceVariant": "#26233a", "mSurfaceVariant": "#26233a",
"mOnSurfaceVariant": "#908caa", "mOnSurfaceVariant": "#908caa",
"mOutline": "#403d52", "mOutline": "#403d52",
"mShadow": "#1f1d2e" "mShadow": "#191724"
}, },
"light": { "light": {
"mPrimary": "#d46e6b", "mPrimary": "#d46e6b",

View file

@ -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

2090
Commons/Bootstrap.qml Normal file

File diff suppressed because it is too large Load diff

View file

@ -28,7 +28,7 @@ NPanel {
spacing: Style.marginM * scaling spacing: Style.marginM * scaling
NIcon { NIcon {
text: "system_update_alt" icon: "box"
font.pointSize: Style.fontSizeXXL * scaling font.pointSize: Style.fontSizeXXL * scaling
color: Color.mPrimary color: Color.mPrimary
} }
@ -44,7 +44,7 @@ NPanel {
// Reset button (only show if update failed) // Reset button (only show if update failed)
NIconButton { NIconButton {
visible: ArchUpdaterService.updateFailed visible: ArchUpdaterService.updateFailed
icon: "refresh" icon: "arrow-repeat"
tooltipText: "Reset update state" tooltipText: "Reset update state"
sizeRatio: 0.8 sizeRatio: 0.8
colorBg: Color.mError colorBg: Color.mError
@ -55,7 +55,7 @@ NPanel {
} }
NIconButton { NIconButton {
icon: "close" icon: "x-lg"
tooltipText: "Close" tooltipText: "Close"
sizeRatio: 0.8 sizeRatio: 0.8
onClicked: root.close() onClicked: root.close()
@ -103,7 +103,7 @@ NPanel {
} // Spacer } // Spacer
NIcon { NIcon {
text: "hourglass_empty" icon: "hourglass"
font.pointSize: Style.fontSizeXXXL * scaling font.pointSize: Style.fontSizeXXXL * scaling
color: Color.mPrimary color: Color.mPrimary
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -143,7 +143,7 @@ NPanel {
spacing: Style.marginM * scaling spacing: Style.marginM * scaling
NIcon { NIcon {
text: "terminal" icon: "terminal"
font.pointSize: Style.fontSizeXXXL * scaling font.pointSize: Style.fontSizeXXXL * scaling
color: Color.mError color: Color.mError
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -181,7 +181,7 @@ NPanel {
spacing: Style.marginM * scaling spacing: Style.marginM * scaling
NIcon { NIcon {
text: "package" icon: "box"
font.pointSize: Style.fontSizeXXXL * scaling font.pointSize: Style.fontSizeXXXL * scaling
color: Color.mError color: Color.mError
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -219,7 +219,7 @@ NPanel {
spacing: Style.marginM * scaling spacing: Style.marginM * scaling
NIcon { NIcon {
text: "error" icon: "exclamation-triangle"
font.pointSize: Style.fontSizeXXXL * scaling font.pointSize: Style.fontSizeXXXL * scaling
color: Color.mError color: Color.mError
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -245,7 +245,7 @@ NPanel {
// Prominent refresh button // Prominent refresh button
NIconButton { NIconButton {
icon: "refresh" icon: "arrow-repeat"
tooltipText: "Try checking again" tooltipText: "Try checking again"
sizeRatio: 1.2 sizeRatio: 1.2
colorBg: Color.mPrimary colorBg: Color.mPrimary
@ -270,7 +270,7 @@ NPanel {
spacing: Style.marginM * scaling spacing: Style.marginM * scaling
NIcon { NIcon {
text: "error_outline" icon: "exclamation-triangle"
font.pointSize: Style.fontSizeXXXL * scaling font.pointSize: Style.fontSizeXXXL * scaling
color: Color.mError color: Color.mError
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -295,7 +295,7 @@ NPanel {
// Prominent refresh button // Prominent refresh button
NIconButton { NIconButton {
icon: "refresh" icon: "arrow-repeat"
tooltipText: "Refresh and try again" tooltipText: "Refresh and try again"
sizeRatio: 1.2 sizeRatio: 1.2
colorBg: Color.mPrimary colorBg: Color.mPrimary
@ -323,7 +323,7 @@ NPanel {
spacing: Style.marginM * scaling spacing: Style.marginM * scaling
NIcon { NIcon {
text: "check_circle" icon: "check-lg"
font.pointSize: Style.fontSizeXXXL * scaling font.pointSize: Style.fontSizeXXXL * scaling
color: Color.mPrimary color: Color.mPrimary
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -483,7 +483,7 @@ NPanel {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
NIconButton { NIconButton {
icon: "refresh" icon: "arrow-repeat"
tooltipText: ArchUpdaterService.aurBusy ? "Checking for updates..." : (!ArchUpdaterService.canPoll ? "Refresh available soon" : "Refresh package lists") tooltipText: ArchUpdaterService.aurBusy ? "Checking for updates..." : (!ArchUpdaterService.canPoll ? "Refresh available soon" : "Refresh package lists")
onClicked: { onClicked: {
ArchUpdaterService.forceRefresh() ArchUpdaterService.forceRefresh()
@ -495,7 +495,7 @@ NPanel {
} }
NIconButton { NIconButton {
icon: "system_update_alt" icon: "box-fill"
tooltipText: "Update all packages" tooltipText: "Update all packages"
enabled: ArchUpdaterService.totalUpdates > 0 enabled: ArchUpdaterService.totalUpdates > 0
onClicked: { onClicked: {
@ -508,7 +508,7 @@ NPanel {
} }
NIconButton { NIconButton {
icon: "check_box" icon: "box"
tooltipText: "Update selected packages" tooltipText: "Update selected packages"
enabled: ArchUpdaterService.selectedPackagesCount > 0 enabled: ArchUpdaterService.selectedPackagesCount > 0
onClicked: { onClicked: {

View file

@ -139,7 +139,7 @@ Variants {
property real screenWidth: width property real screenWidth: width
property real screenHeight: height 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 // Wipe transition shader
@ -164,7 +164,7 @@ Variants {
property real screenWidth: width property real screenWidth: width
property real screenHeight: height 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 // Disc reveal transition shader
@ -191,7 +191,7 @@ Variants {
property real screenWidth: width property real screenWidth: width
property real screenHeight: height 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 // Diagonal stripes transition shader
@ -218,7 +218,7 @@ Variants {
property real screenWidth: width property real screenWidth: width
property real screenHeight: height 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 // Animation for the transition progress

View file

@ -33,28 +33,34 @@ RowLayout {
readonly property bool showIcon: (widgetSettings.showIcon !== undefined) ? widgetSettings.showIcon : widgetMetadata.showIcon readonly property bool showIcon: (widgetSettings.showIcon !== undefined) ? widgetSettings.showIcon : widgetMetadata.showIcon
readonly property real minWidth: 160 // 6% of total width
readonly property real maxWidth: 400 readonly property real minWidth: Math.max(1, screen.width * 0.06)
Layout.alignment: Qt.AlignVCenter readonly property real maxWidth: minWidth * 2
spacing: Style.marginS * scaling
visible: getTitle() !== ""
function getTitle() { function getTitle() {
return CompositorService.focusedWindowTitle !== "(No active window)" ? CompositorService.focusedWindowTitle : "" return CompositorService.focusedWindowTitle !== "(No active window)" ? CompositorService.focusedWindowTitle : ""
} }
Layout.alignment: Qt.AlignVCenter
spacing: Style.marginS * scaling
visible: getTitle() !== ""
function getAppIcon() { function getAppIcon() {
// Try CompositorService first // Try CompositorService first
const focusedWindow = CompositorService.getFocusedWindow() const focusedWindow = CompositorService.getFocusedWindow()
if (focusedWindow && focusedWindow.appId) { if (focusedWindow && focusedWindow.appId) {
return Icons.iconForAppId(focusedWindow.appId.toLowerCase()) const idValue = focusedWindow.appId
const normalizedId = (typeof idValue === 'string') ? idValue : String(idValue)
return Icons.iconForAppId(normalizedId.toLowerCase())
} }
// Fallback to ToplevelManager // Fallback to ToplevelManager
if (ToplevelManager && ToplevelManager.activeToplevel) { if (ToplevelManager && ToplevelManager.activeToplevel) {
const activeToplevel = ToplevelManager.activeToplevel const activeToplevel = ToplevelManager.activeToplevel
if (activeToplevel.appId) { if (activeToplevel.appId) {
return Icons.iconForAppId(activeToplevel.appId.toLowerCase()) const idValue2 = activeToplevel.appId
const normalizedId2 = (typeof idValue2 === 'string') ? idValue2 : String(idValue2)
return Icons.iconForAppId(normalizedId2.toLowerCase())
} }
} }
@ -123,7 +129,7 @@ RowLayout {
font.weight: Style.fontWeightMedium font.weight: Style.fontWeightMedium
elide: mouseArea.containsMouse ? Text.ElideNone : Text.ElideRight elide: mouseArea.containsMouse ? Text.ElideNone : Text.ElideRight
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
color: Color.mSecondary color: Color.mPrimary
clip: true clip: true
Behavior on Layout.preferredWidth { Behavior on Layout.preferredWidth {

View file

@ -29,15 +29,15 @@ NIconButton {
return "terminal" return "terminal"
} }
if (!ArchUpdaterService.aurHelperAvailable) { if (!ArchUpdaterService.aurHelperAvailable) {
return "package" return "box"
} }
if (ArchUpdaterService.aurBusy) { if (ArchUpdaterService.aurBusy) {
return "sync" return "arrow-repeat"
} }
if (ArchUpdaterService.totalUpdates > 0) { if (ArchUpdaterService.totalUpdates > 0) {
return "system_update_alt" return "box-fill"
} }
return "task_alt" return "box"
} }
// Tooltip with repo vs AUR breakdown and sample lists // Tooltip with repo vs AUR breakdown and sample lists

View file

@ -39,7 +39,7 @@ Item {
// Test mode // Test mode
readonly property bool testMode: false readonly property bool testMode: false
readonly property int testPercent: 50 readonly property int testPercent: 50
readonly property bool testCharging: true readonly property bool testCharging: false
// Main properties // Main properties
readonly property var battery: UPower.displayDevice readonly property var battery: UPower.displayDevice
@ -57,9 +57,7 @@ Item {
// Only notify once we are a below threshold // Only notify once we are a below threshold
if (!charging && !root.hasNotifiedLowBattery && percent <= warningThreshold) { if (!charging && !root.hasNotifiedLowBattery && percent <= warningThreshold) {
root.hasNotifiedLowBattery = true root.hasNotifiedLowBattery = true
// Maybe go with toast ? ToastService.showWarning("Low Battery", `Battery is at ${Math.round(percent)}%. Please connect the charger.`)
Quickshell.execDetached(
["notify-send", "-u", "critical", "-i", "battery-caution", "Low Battery", `Battery is at ${p}%. Please connect charger.`])
} else if (root.hasNotifiedLowBattery && (charging || percent > warningThreshold + 5)) { } else if (root.hasNotifiedLowBattery && (charging || percent > warningThreshold + 5)) {
// Reset when charging starts or when battery recovers 5% above threshold // Reset when charging starts or when battery recovers 5% above threshold
root.hasNotifiedLowBattery = false root.hasNotifiedLowBattery = false
@ -87,11 +85,7 @@ Item {
rightOpen: BarWidgetRegistry.getNPillDirection(root) rightOpen: BarWidgetRegistry.getNPillDirection(root)
icon: testMode ? BatteryService.getIcon(testPercent, testCharging, true) : BatteryService.getIcon(percent, icon: testMode ? BatteryService.getIcon(testPercent, testCharging, true) : BatteryService.getIcon(percent,
charging, isReady) charging, isReady)
iconRotation: -90 text: (isReady || testMode) ? Math.round(percent) + "%" : "-"
text: ((isReady && battery.isLaptopBattery) || testMode) ? Math.round(percent) + "%" : "-"
textColor: charging ? Color.mPrimary : Color.mOnSurface
iconCircleColor: Color.mPrimary
collapsedIconColor: Color.mOnSurface
autoHide: false autoHide: false
forceOpen: isReady && (testMode || battery.isLaptopBattery) && alwaysShowPercentage forceOpen: isReady && (testMode || battery.isLaptopBattery) && alwaysShowPercentage
disableOpen: (!isReady || (!testMode && !battery.isLaptopBattery)) disableOpen: (!isReady || (!testMode && !battery.isLaptopBattery))

View file

@ -46,8 +46,7 @@ Item {
function getIcon() { function getIcon() {
var monitor = getMonitor() var monitor = getMonitor()
var brightness = monitor ? monitor.brightness : 0 var brightness = monitor ? monitor.brightness : 0
return brightness <= 0 ? "brightness_1" : brightness < 0.33 ? "brightness_low" : brightness return brightness <= 0.5 ? "brightness-low" : "brightness-high"
< 0.66 ? "brightness_medium" : "brightness_high"
} }
// Connection used to open the pill when brightness changes // Connection used to open the pill when brightness changes
@ -80,8 +79,6 @@ Item {
rightOpen: BarWidgetRegistry.getNPillDirection(root) rightOpen: BarWidgetRegistry.getNPillDirection(root)
icon: getIcon() icon: getIcon()
iconCircleColor: Color.mPrimary
collapsedIconColor: Color.mOnSurface
autoHide: false // Important to be false so we can hover as long as we want autoHide: false // Important to be false so we can hover as long as we want
text: { text: {
var monitor = getMonitor() var monitor = getMonitor()

View file

@ -60,6 +60,7 @@ Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
font.pointSize: Style.fontSizeS * scaling font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
color: Color.mPrimary
} }
NTooltip { NTooltip {

View file

@ -9,12 +9,12 @@ NIconButton {
property ShellScreen screen property ShellScreen screen
property real scaling: 1.0 property real scaling: 1.0
icon: "contrast" icon: "transparency"
tooltipText: "Toggle light/dark mode" tooltipText: "Toggle light/dark mode"
sizeRatio: 0.8 sizeRatio: 0.8
colorBg: Color.mSurfaceVariant colorBg: Settings.data.colorSchemes.darkMode ? Color.mSurfaceVariant : Color.mPrimary
colorFg: Color.mOnSurface colorFg: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mOnPrimary
colorBorder: Color.transparent colorBorder: Color.transparent
colorBorderHover: Color.transparent colorBorderHover: Color.transparent

View file

@ -13,10 +13,10 @@ NIconButton {
sizeRatio: 0.8 sizeRatio: 0.8
icon: "coffee" icon: "cup"
tooltipText: IdleInhibitorService.isInhibited ? "Disable keep awake" : "Enable keep awake" tooltipText: IdleInhibitorService.isInhibited ? "Disable keep awake" : "Enable keep awake"
colorBg: Color.mSurfaceVariant colorBg: IdleInhibitorService.isInhibited ? Color.mPrimary : Color.mSurfaceVariant
colorFg: IdleInhibitorService.isInhibited ? Color.mPrimary : Color.mOnSurface colorFg: IdleInhibitorService.isInhibited ? Color.mOnPrimary : Color.mOnSurface
colorBorder: Color.transparent colorBorder: Color.transparent
onClicked: { onClicked: {
IdleInhibitorService.manualToggle() IdleInhibitorService.manualToggle()

View file

@ -24,9 +24,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
rightOpen: BarWidgetRegistry.getNPillDirection(root) rightOpen: BarWidgetRegistry.getNPillDirection(root)
icon: "keyboard_alt" icon: "keyboard"
iconCircleColor: Color.mPrimary
collapsedIconColor: Color.mOnSurface
autoHide: false // Important to be false so we can hover as long as we want autoHide: false // Important to be false so we can hover as long as we want
text: currentLayout text: currentLayout
tooltipText: "Keyboard layout: " + currentLayout tooltipText: "Keyboard layout: " + currentLayout

View file

@ -38,8 +38,9 @@ RowLayout {
readonly property string visualizerType: (widgetSettings.visualizerType !== undefined && widgetSettings.visualizerType readonly property string visualizerType: (widgetSettings.visualizerType !== undefined && widgetSettings.visualizerType
!== "") ? widgetSettings.visualizerType : widgetMetadata.visualizerType !== "") ? widgetSettings.visualizerType : widgetMetadata.visualizerType
readonly property real minWidth: 160 // 6% of total width
readonly property real maxWidth: 400 readonly property real minWidth: Math.max(1, screen.width * 0.06)
readonly property real maxWidth: minWidth * 2
function getTitle() { function getTitle() {
return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "") return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "")
@ -134,7 +135,7 @@ RowLayout {
NIcon { NIcon {
id: windowIcon id: windowIcon
text: MediaService.isPlaying ? "pause" : "play_arrow" text: MediaService.isPlaying ? "pause" : "play"
font.pointSize: Style.fontSizeL * scaling font.pointSize: Style.fontSizeL * scaling
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
@ -154,7 +155,8 @@ RowLayout {
id: trackArt id: trackArt
anchors.fill: parent anchors.fill: parent
imagePath: MediaService.trackArtUrl imagePath: MediaService.trackArtUrl
fallbackIcon: MediaService.isPlaying ? "pause" : "play_arrow" fallbackIcon: MediaService.isPlaying ? "pause" : "play"
fallbackIconSize: 10 * scaling
borderWidth: 0 borderWidth: 0
border.color: Color.transparent border.color: Color.transparent
} }
@ -178,7 +180,7 @@ RowLayout {
font.weight: Style.fontWeightMedium font.weight: Style.fontWeightMedium
elide: Text.ElideRight elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
color: Color.mTertiary color: Color.mSecondary
Behavior on Layout.preferredWidth { Behavior on Layout.preferredWidth {
NumberAnimation { NumberAnimation {

View file

@ -43,9 +43,9 @@ Item {
function getIcon() { function getIcon() {
if (AudioService.inputMuted) { if (AudioService.inputMuted) {
return "mic_off" return "mic-mute"
} }
return AudioService.inputVolume <= Number.EPSILON ? "mic_off" : (AudioService.inputVolume < 0.33 ? "mic" : "mic") return AudioService.inputVolume <= Number.EPSILON ? "mic-mute" : (AudioService.inputVolume < 0.33 ? "mic" : "mic")
} }
// Connection used to open the pill when input volume changes // Connection used to open the pill when input volume changes
@ -92,8 +92,6 @@ Item {
rightOpen: BarWidgetRegistry.getNPillDirection(root) rightOpen: BarWidgetRegistry.getNPillDirection(root)
icon: getIcon() icon: getIcon()
iconCircleColor: Color.mPrimary
collapsedIconColor: Color.mOnSurface
autoHide: false // Important to be false so we can hover as long as we want autoHide: false // Important to be false so we can hover as long as we want
text: Math.floor(AudioService.inputVolume * 100) + "%" text: Math.floor(AudioService.inputVolume * 100) + "%"
forceOpen: alwaysShowPercentage forceOpen: alwaysShowPercentage

View file

@ -15,12 +15,12 @@ NIconButton {
property real scaling: 1.0 property real scaling: 1.0
sizeRatio: 0.8 sizeRatio: 0.8
colorBg: Color.mSurfaceVariant colorBg: Settings.data.nightLight.enabled ? Color.mPrimary : Color.mSurfaceVariant
colorFg: Color.mOnSurface colorFg: Settings.data.nightLight.enabled ? Color.mOnPrimary : Color.mOnSurface
colorBorder: Color.transparent colorBorder: Color.transparent
colorBorderHover: Color.transparent colorBorderHover: Color.transparent
icon: Settings.data.nightLight.enabled ? "bedtime" : "bedtime_off" icon: "moon-stars"
tooltipText: `Night light: ${Settings.data.nightLight.enabled ? "enabled." : "disabled."}\nLeft click to toggle.\nRight click to access settings.` 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 onClicked: Settings.data.nightLight.enabled = !Settings.data.nightLight.enabled

View file

@ -53,10 +53,10 @@ NIconButton {
} }
sizeRatio: 0.8 sizeRatio: 0.8
icon: Settings.data.notifications.doNotDisturb ? "notifications_off" : "notifications" icon: Settings.data.notifications.doNotDisturb ? "bell-slash" : "bell"
tooltipText: Settings.data.notifications.doNotDisturb ? "Notification history.\nRight-click to disable 'Do Not Disturb'." : "Notification history.\nRight-click to enable 'Do Not Disturb'." 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 colorBg: Color.mSurfaceVariant
colorFg: Settings.data.notifications.doNotDisturb ? Color.mError : Color.mOnSurface colorFg: Color.mOnSurface
colorBorder: Color.transparent colorBorder: Color.transparent
colorBorderHover: Color.transparent colorBorderHover: Color.transparent

View file

@ -19,13 +19,13 @@ NIconButton {
function profileIcon() { function profileIcon() {
if (!hasPP) if (!hasPP)
return "balance" return "yin-yang"
if (powerProfiles.profile === PowerProfile.Performance) if (powerProfiles.profile === PowerProfile.Performance)
return "speed" return "speedometer2"
if (powerProfiles.profile === PowerProfile.Balanced) if (powerProfiles.profile === PowerProfile.Balanced)
return "balance" return "yin-yang"
if (powerProfiles.profile === PowerProfile.PowerSaver) if (powerProfiles.profile === PowerProfile.PowerSaver)
return "eco" return "leaf"
} }
function profileName() { function profileName() {

View file

@ -13,7 +13,7 @@ NIconButton {
sizeRatio: 0.8 sizeRatio: 0.8
icon: "power_settings_new" icon: "power"
tooltipText: "Power Settings" tooltipText: "Power Settings"
colorBg: Color.mSurfaceVariant colorBg: Color.mSurfaceVariant
colorFg: Color.mError colorFg: Color.mError

View file

@ -11,7 +11,7 @@ NIconButton {
property real scaling: 1.0 property real scaling: 1.0
visible: ScreenRecorderService.isRecording visible: ScreenRecorderService.isRecording
icon: "videocam" icon: "camera-video"
tooltipText: "Screen recording is active\nClick to stop recording" tooltipText: "Screen recording is active\nClick to stop recording"
sizeRatio: 0.8 sizeRatio: 0.8
colorBg: Color.mPrimary colorBg: Color.mPrimary

View file

@ -33,7 +33,7 @@ NIconButton {
readonly property bool useDistroLogo: (widgetSettings.useDistroLogo readonly property bool useDistroLogo: (widgetSettings.useDistroLogo
!== undefined) ? widgetSettings.useDistroLogo : widgetMetadata.useDistroLogo !== undefined) ? widgetSettings.useDistroLogo : widgetMetadata.useDistroLogo
icon: useDistroLogo ? "" : "widgets" icon: useDistroLogo ? "" : "layout-sidebar-inset-reverse"
tooltipText: "Open side panel." tooltipText: "Open side panel."
sizeRatio: 0.8 sizeRatio: 0.8

View file

@ -38,12 +38,4 @@ Item {
implicitHeight: Style.barHeight * scaling implicitHeight: Style.barHeight * scaling
width: implicitWidth width: implicitWidth
height: implicitHeight 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
}
} }

View file

@ -38,6 +38,8 @@ RowLayout {
!== undefined) ? widgetSettings.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent !== undefined) ? widgetSettings.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent
readonly property bool showNetworkStats: (widgetSettings.showNetworkStats readonly property bool showNetworkStats: (widgetSettings.showNetworkStats
!== undefined) ? widgetSettings.showNetworkStats : widgetMetadata.showNetworkStats !== undefined) ? widgetSettings.showNetworkStats : widgetMetadata.showNetworkStats
readonly property bool showDiskUsage: (widgetSettings.showDiskUsage
!== undefined) ? widgetSettings.showDiskUsage : widgetMetadata.showDiskUsage
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
@ -52,26 +54,29 @@ RowLayout {
RowLayout { RowLayout {
id: mainLayout id: mainLayout
anchors.fill: parent anchors.centerIn: parent // Better centering than margins
anchors.leftMargin: Style.marginS * scaling width: parent.width - Style.marginM * scaling * 2
anchors.rightMargin: Style.marginS * scaling
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
// CPU Usage Component // CPU Usage Component
RowLayout { Item {
id: cpuUsageLayout Layout.preferredWidth: cpuUsageRow.implicitWidth
spacing: Style.marginXS * scaling Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: showCpuUsage visible: showCpuUsage
RowLayout {
id: cpuUsageRow
anchors.centerIn: parent
spacing: Style.marginXS * scaling
NIcon { NIcon {
id: cpuUsageIcon icon: "speedometer2"
text: "speed" font.pointSize: Style.fontSizeM * scaling
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
NText { NText {
id: cpuUsageText
text: `${SystemStatService.cpuUsage}%` text: `${SystemStatService.cpuUsage}%`
font.family: Settings.data.ui.fontFixed font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeS * scaling font.pointSize: Style.fontSizeS * scaling
@ -81,17 +86,24 @@ RowLayout {
color: Color.mPrimary color: Color.mPrimary
} }
} }
}
// CPU Temperature Component // CPU Temperature Component
RowLayout { Item {
id: cpuTempLayout Layout.preferredWidth: cpuTempRow.implicitWidth
// spacing is thin here to compensate for the vertical thermometer icon Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
spacing: Style.marginXXS * scaling
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: showCpuTemp visible: showCpuTemp
RowLayout {
id: cpuTempRow
anchors.centerIn: parent
spacing: Style.marginXS * scaling
NIcon { NIcon {
text: "thermometer" icon: "fire"
// Fire is so tall, we need to make it smaller
font.pointSize: Style.fontSizeS * scaling
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
@ -105,16 +117,23 @@ RowLayout {
color: Color.mPrimary color: Color.mPrimary
} }
} }
}
// Memory Usage Component // Memory Usage Component
RowLayout { Item {
id: memoryUsageLayout Layout.preferredWidth: memoryUsageRow.implicitWidth
spacing: Style.marginXS * scaling Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: showMemoryUsage visible: showMemoryUsage
RowLayout {
id: memoryUsageRow
anchors.centerIn: parent
spacing: Style.marginXS * scaling
NIcon { NIcon {
text: "memory" icon: "cpu"
font.pointSize: Style.fontSizeM * scaling
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
@ -128,16 +147,23 @@ RowLayout {
color: Color.mPrimary color: Color.mPrimary
} }
} }
}
// Network Download Speed Component // Network Download Speed Component
RowLayout { Item {
id: networkDownloadLayout Layout.preferredWidth: networkDownloadRow.implicitWidth
spacing: Style.marginXS * scaling Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: showNetworkStats visible: showNetworkStats
RowLayout {
id: networkDownloadRow
anchors.centerIn: parent
spacing: Style.marginXS * scaling
NIcon { NIcon {
text: "download" icon: "cloud-arrow-down"
font.pointSize: Style.fontSizeM * scaling
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
@ -151,16 +177,23 @@ RowLayout {
color: Color.mPrimary color: Color.mPrimary
} }
} }
}
// Network Upload Speed Component // Network Upload Speed Component
RowLayout { Item {
id: networkUploadLayout Layout.preferredWidth: networkUploadRow.implicitWidth
spacing: Style.marginXS * scaling Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: showNetworkStats visible: showNetworkStats
RowLayout {
id: networkUploadRow
anchors.centerIn: parent
spacing: Style.marginXS * scaling
NIcon { NIcon {
text: "upload" icon: "cloud-arrow-up"
font.pointSize: Style.fontSizeM * scaling
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
@ -175,5 +208,36 @@ RowLayout {
} }
} }
} }
// 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: "hdd"
font.pointSize: Style.fontSizeM * scaling
Layout.alignment: Qt.AlignVCenter
}
NText {
text: `${SystemStatService.diskPercent}%`
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
}
}
}
}
} }
} }

View file

@ -43,9 +43,9 @@ Item {
function getIcon() { function getIcon() {
if (AudioService.muted) { 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 <= 0.2 ? "volume-off" : (AudioService.volume < 0.6 ? "volume-down" : "volume-up")
} }
// Connection used to open the pill when volume changes // Connection used to open the pill when volume changes
@ -77,8 +77,6 @@ Item {
rightOpen: BarWidgetRegistry.getNPillDirection(root) rightOpen: BarWidgetRegistry.getNPillDirection(root)
icon: getIcon() icon: getIcon()
iconCircleColor: Color.mPrimary
collapsedIconColor: Color.mOnSurface
autoHide: false // Important to be false so we can hover as long as we want autoHide: false // Important to be false so we can hover as long as we want
text: Math.floor(AudioService.volume * 100) + "%" text: Math.floor(AudioService.volume * 100) + "%"
forceOpen: alwaysShowPercentage forceOpen: alwaysShowPercentage

View file

@ -23,7 +23,7 @@ NIconButton {
icon: { icon: {
try { try {
if (NetworkService.ethernetConnected) { if (NetworkService.ethernetConnected) {
return "lan" return "ethernet"
} }
let connected = false let connected = false
let signalStrength = 0 let signalStrength = 0
@ -34,7 +34,7 @@ NIconButton {
break break
} }
} }
return connected ? NetworkService.signalIcon(signalStrength) : "wifi_find" return connected ? NetworkService.signalIcon(signalStrength) : "wifi-off"
} catch (error) { } catch (error) {
Logger.error("Wi-Fi", "Error getting icon:", error) Logger.error("Wi-Fi", "Error getting icon:", error)
return "signal_wifi_bad" return "signal_wifi_bad"

View file

@ -66,7 +66,7 @@ ColumnLayout {
// One device BT icon // One device BT icon
NIcon { NIcon {
text: BluetoothService.getDeviceIcon(modelData) icon: BluetoothService.getDeviceIcon(modelData)
font.pointSize: Style.fontSizeXXL * scaling font.pointSize: Style.fontSizeXXL * scaling
color: getContentColor(Color.mOnSurface) color: getContentColor(Color.mOnSurface)
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
@ -164,7 +164,7 @@ ColumnLayout {
} }
return "Connect" return "Connect"
} }
icon: (isBusy ? "hourglass_full" : null) icon: (isBusy ? "hourglass-split" : null)
onClicked: { onClicked: {
if (modelData.connected) { if (modelData.connected) {
BluetoothService.disconnectDevice(modelData) BluetoothService.disconnectDevice(modelData)

View file

@ -28,7 +28,7 @@ NPanel {
spacing: Style.marginM * scaling spacing: Style.marginM * scaling
NIcon { NIcon {
text: "bluetooth" icon: "bluetooth"
font.pointSize: Style.fontSizeXXL * scaling font.pointSize: Style.fontSizeXXL * scaling
color: Color.mPrimary color: Color.mPrimary
} }
@ -42,7 +42,7 @@ NPanel {
} }
NIconButton { NIconButton {
icon: BluetoothService.adapter && BluetoothService.adapter.discovering ? "stop_circle" : "refresh" icon: BluetoothService.adapter && BluetoothService.adapter.discovering ? "stop" : "arrow-repeat"
tooltipText: "Refresh Devices" tooltipText: "Refresh Devices"
sizeRatio: 0.8 sizeRatio: 0.8
onClicked: { onClicked: {
@ -53,7 +53,7 @@ NPanel {
} }
NIconButton { NIconButton {
icon: "close" icon: "x-lg"
tooltipText: "Close" tooltipText: "Close"
sizeRatio: 0.8 sizeRatio: 0.8
onClicked: { onClicked: {

View file

@ -22,9 +22,11 @@ Item {
IpcHandler { IpcHandler {
target: "screenRecorder" target: "screenRecorder"
function toggle() { function toggle() {
if (ScreenRecorderService.isAvailable) {
ScreenRecorderService.toggleRecording() ScreenRecorderService.toggleRecording()
} }
} }
}
IpcHandler { IpcHandler {
target: "settings" target: "settings"

View file

@ -418,7 +418,7 @@ Loader {
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
} }
NIcon { NIcon {
text: "keyboard_alt" icon: "keyboard"
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurface color: Color.mOnSurface
} }
@ -428,7 +428,7 @@ Loader {
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
visible: batteryIndicator.batteryVisible visible: batteryIndicator.batteryVisible
NIcon { NIcon {
text: BatteryService.getIcon(batteryIndicator.percent, batteryIndicator.charging, icon: BatteryService.getIcon(batteryIndicator.percent, batteryIndicator.charging,
batteryIndicator.isReady) batteryIndicator.isReady)
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeM * scaling
color: batteryIndicator.charging ? Color.mPrimary : Color.mOnSurface color: batteryIndicator.charging ? Color.mPrimary : Color.mOnSurface
@ -718,21 +718,47 @@ Loader {
anchors.margins: 50 * scaling anchors.margins: 50 * scaling
spacing: 20 * scaling spacing: 20 * scaling
// Shutdown
Rectangle { Rectangle {
Layout.preferredWidth: 60 * scaling Layout.preferredWidth: iconPower.implicitWidth + Style.marginXL * scaling
Layout.preferredHeight: 60 * scaling Layout.preferredHeight: Layout.preferredWidth
radius: width * 0.5 radius: width * 0.5
color: powerButtonArea.containsMouse ? Color.mError : Qt.alpha(Color.mError, 0.2) color: powerButtonArea.containsMouse ? Color.mError : Qt.alpha(Color.mError, 0.2)
border.color: Color.mError border.color: Color.mError
border.width: Math.max(1, Style.borderM * scaling) border.width: Math.max(1, Style.borderM * scaling)
NIcon { NIcon {
id: iconPower
anchors.centerIn: parent anchors.centerIn: parent
text: "power_settings_new" icon: "power"
font.pointSize: Style.fontSizeXL * scaling font.pointSize: Style.fontSizeXXXL * scaling
color: powerButtonArea.containsMouse ? Color.mOnError : Color.mError 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 { MouseArea {
id: powerButtonArea id: powerButtonArea
anchors.fill: parent anchors.fill: parent
@ -743,21 +769,47 @@ Loader {
} }
} }
// Reboot
Rectangle { Rectangle {
Layout.preferredWidth: 60 * scaling Layout.preferredWidth: iconReboot.implicitWidth + Style.marginXL * scaling
Layout.preferredHeight: 60 * scaling Layout.preferredHeight: Layout.preferredWidth
radius: width * 0.5 radius: width * 0.5
color: restartButtonArea.containsMouse ? Color.mPrimary : Qt.alpha(Color.mPrimary, Style.opacityLight) color: restartButtonArea.containsMouse ? Color.mPrimary : Qt.alpha(Color.mPrimary, Style.opacityLight)
border.color: Color.mPrimary border.color: Color.mPrimary
border.width: Math.max(1, Style.borderM * scaling) border.width: Math.max(1, Style.borderM * scaling)
NIcon { NIcon {
id: iconReboot
anchors.centerIn: parent anchors.centerIn: parent
text: "restart_alt" icon: "arrow-repeat"
font.pointSize: Style.fontSizeXL * scaling font.pointSize: Style.fontSizeXXXL * scaling
color: restartButtonArea.containsMouse ? Color.mOnPrimary : Color.mPrimary 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 { MouseArea {
id: restartButtonArea id: restartButtonArea
anchors.fill: parent anchors.fill: parent
@ -765,24 +817,51 @@ Loader {
onClicked: { onClicked: {
CompositorService.reboot() CompositorService.reboot()
} }
// Tooltip handled via inline rectangle visibility
} }
} }
// Suspend
Rectangle { Rectangle {
Layout.preferredWidth: 60 * scaling Layout.preferredWidth: iconSuspend.implicitWidth + Style.marginXL * scaling
Layout.preferredHeight: 60 * scaling Layout.preferredHeight: Layout.preferredWidth
radius: width * 0.5 radius: width * 0.5
color: suspendButtonArea.containsMouse ? Color.mSecondary : Qt.alpha(Color.mSecondary, 0.2) color: suspendButtonArea.containsMouse ? Color.mSecondary : Qt.alpha(Color.mSecondary, 0.2)
border.color: Color.mSecondary border.color: Color.mSecondary
border.width: Math.max(1, Style.borderM * scaling) border.width: Math.max(1, Style.borderM * scaling)
NIcon { NIcon {
id: iconSuspend
anchors.centerIn: parent anchors.centerIn: parent
text: "bedtime" icon: "pause-fill"
font.pointSize: Style.fontSizeXL * scaling font.pointSize: Style.fontSizeXXXL * scaling
color: suspendButtonArea.containsMouse ? Color.mOnSecondary : Color.mSecondary 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 { MouseArea {
id: suspendButtonArea id: suspendButtonArea
anchors.fill: parent anchors.fill: parent
@ -790,6 +869,7 @@ Loader {
onClicked: { onClicked: {
CompositorService.suspend() CompositorService.suspend()
} }
// Tooltip handled via inline rectangle visibility
} }
} }
} }

View file

@ -205,14 +205,12 @@ Variants {
Layout.fillWidth: true Layout.fillWidth: true
spacing: Style.marginM * scaling spacing: Style.marginM * scaling
// Avatar // Image
NImageCircled { NImageCircled {
id: appAvatar
Layout.preferredWidth: 40 * scaling Layout.preferredWidth: 40 * scaling
Layout.preferredHeight: 40 * scaling Layout.preferredHeight: 40 * scaling
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
imagePath: model.image && model.image !== "" ? model.image : "" imagePath: model.image && model.image !== "" ? model.image : ""
fallbackIcon: ""
borderColor: Color.transparent borderColor: Color.transparent
borderWidth: 0 borderWidth: 0
visible: (model.image && model.image !== "") visible: (model.image && model.image !== "")
@ -294,7 +292,7 @@ Variants {
// Close button positioned absolutely // Close button positioned absolutely
NIconButton { NIconButton {
icon: "close" icon: "x-lg"
tooltipText: "Close" tooltipText: "Close"
sizeRatio: 0.6 sizeRatio: 0.6
anchors.top: parent.top anchors.top: parent.top

View file

@ -31,7 +31,7 @@ NPanel {
spacing: Style.marginM * scaling spacing: Style.marginM * scaling
NIcon { NIcon {
text: "notifications" icon: "bell"
font.pointSize: Style.fontSizeXXL * scaling font.pointSize: Style.fontSizeXXL * scaling
color: Color.mPrimary color: Color.mPrimary
} }
@ -45,21 +45,21 @@ NPanel {
} }
NIconButton { NIconButton {
icon: Settings.data.notifications.doNotDisturb ? "notifications_off" : "notifications_active" icon: Settings.data.notifications.doNotDisturb ? "bell-slash" : "bell"
tooltipText: Settings.data.notifications.doNotDisturb ? "'Do Not Disturb' is enabled." : "'Do Not Disturb' is disabled." tooltipText: Settings.data.notifications.doNotDisturb ? "'Do Not Disturb' is enabled." : "'Do Not Disturb' is disabled."
sizeRatio: 0.8 sizeRatio: 0.8
onClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb onClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb
} }
NIconButton { NIconButton {
icon: "delete" icon: "trash"
tooltipText: "Clear history" tooltipText: "Clear history"
sizeRatio: 0.8 sizeRatio: 0.8
onClicked: NotificationService.clearHistory() onClicked: NotificationService.clearHistory()
} }
NIconButton { NIconButton {
icon: "close" icon: "x-lg"
tooltipText: "Close" tooltipText: "Close"
sizeRatio: 0.8 sizeRatio: 0.8
onClicked: { onClicked: {
@ -85,7 +85,7 @@ NPanel {
} }
NIcon { NIcon {
text: "notifications_off" icon: "bell-slash"
font.pointSize: 64 * scaling font.pointSize: 64 * scaling
color: Color.mOnSurfaceVariant color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -175,7 +175,7 @@ NPanel {
// Delete button // Delete button
NIconButton { NIconButton {
icon: "delete" icon: "trash"
tooltipText: "Delete notification" tooltipText: "Delete notification"
sizeRatio: 0.7 sizeRatio: 0.7
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop

View file

@ -29,27 +29,27 @@ NPanel {
property int selectedIndex: 0 property int selectedIndex: 0
readonly property var powerOptions: [{ readonly property var powerOptions: [{
"action": "lock", "action": "lock",
"icon": "lock_outline", "icon": "lock",
"title": "Lock", "title": "Lock",
"subtitle": "Lock your session" "subtitle": "Lock your session"
}, { }, {
"action": "suspend", "action": "suspend",
"icon": "bedtime", "icon": "pause-circle",
"title": "Suspend", "title": "Suspend",
"subtitle": "Put the system to sleep" "subtitle": "Put the system to sleep"
}, { }, {
"action": "reboot", "action": "reboot",
"icon": "refresh", "icon": "arrow-repeat",
"title": "Reboot", "title": "Reboot",
"subtitle": "Restart the system" "subtitle": "Restart the system"
}, { }, {
"action": "logout", "action": "logout",
"icon": "exit_to_app", "icon": "escape",
"title": "Logout", "title": "Logout",
"subtitle": "End your session" "subtitle": "End your session"
}, { }, {
"action": "shutdown", "action": "shutdown",
"icon": "power_settings_new", "icon": "power",
"title": "Shutdown", "title": "Shutdown",
"subtitle": "Turn off the system", "subtitle": "Turn off the system",
"isShutdown": true "isShutdown": true
@ -276,7 +276,7 @@ NPanel {
} }
NIconButton { NIconButton {
icon: timerActive ? "back_hand" : "close" icon: timerActive ? "x-square" : "x-lg"
tooltipText: timerActive ? "Cancel Timer" : "Close" tooltipText: timerActive ? "Cancel Timer" : "Close"
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
colorBg: timerActive ? Qt.alpha(Color.mError, 0.08) : Color.transparent colorBg: timerActive ? Qt.alpha(Color.mError, 0.08) : Color.transparent
@ -360,7 +360,7 @@ NPanel {
id: iconElement id: iconElement
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: buttonRoot.icon icon: buttonRoot.icon
color: { color: {
if (buttonRoot.pending) if (buttonRoot.pending)
return Color.mPrimary return Color.mPrimary

View file

@ -85,7 +85,7 @@ NBox {
} }
NIconButton { NIconButton {
icon: "add" icon: "plus-lg"
colorBg: Color.mPrimary colorBg: Color.mPrimary
colorFg: Color.mOnPrimary colorFg: Color.mOnPrimary
@ -170,7 +170,7 @@ NBox {
Loader { Loader {
active: BarWidgetRegistry.widgetHasUserSettings(modelData.id) active: BarWidgetRegistry.widgetHasUserSettings(modelData.id)
sourceComponent: NIconButton { sourceComponent: NIconButton {
icon: "settings" icon: "gear"
sizeRatio: 0.6 sizeRatio: 0.6
colorBorder: Qt.alpha(Color.mOutline, Style.opacityLight) colorBorder: Qt.alpha(Color.mOutline, Style.opacityLight)
colorBg: Color.mOnSurface colorBg: Color.mOnSurface
@ -210,7 +210,7 @@ NBox {
} }
NIconButton { NIconButton {
icon: "close" icon: "x-lg"
sizeRatio: 0.6 sizeRatio: 0.6
colorBorder: Qt.alpha(Color.mOutline, Style.opacityLight) colorBorder: Qt.alpha(Color.mOutline, Style.opacityLight)
colorBg: Color.mOnSurface colorBg: Color.mOnSurface

View file

@ -84,7 +84,7 @@ Popup {
} }
NIconButton { NIconButton {
icon: "close" icon: "x-lg"
onClicked: settingsPopup.close() onClicked: settingsPopup.close()
} }
} }
@ -107,6 +107,7 @@ Popup {
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Style.marginM * scaling Layout.topMargin: Style.marginM * scaling
spacing: Style.marginM * scaling
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
@ -120,7 +121,7 @@ Popup {
NButton { NButton {
text: "Apply" text: "Apply"
icon: "check" icon: "check-lg"
onClicked: { onClicked: {
if (settingsLoader.item && settingsLoader.item.saveSettings) { if (settingsLoader.item && settingsLoader.item.saveSettings) {
var newSettings = settingsLoader.item.saveSettings() var newSettings = settingsLoader.item.saveSettings()

View file

@ -16,10 +16,13 @@ ColumnLayout {
// Local state // Local state
property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage
!== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage !== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
property int valueWarningThreshold: widgetData.warningThreshold
!== undefined ? widgetData.warningThreshold : widgetMetadata.warningThreshold
function saveSettings() { function saveSettings() {
var settings = Object.assign({}, widgetData || {}) var settings = Object.assign({}, widgetData || {})
settings.alwaysShowPercentage = valueAlwaysShowPercentage settings.alwaysShowPercentage = valueAlwaysShowPercentage
settings.warningThreshold = valueWarningThreshold
return settings return settings
} }
@ -28,4 +31,14 @@ ColumnLayout {
checked: root.valueAlwaysShowPercentage checked: root.valueAlwaysShowPercentage
onToggled: checked => root.valueAlwaysShowPercentage = checked 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
}
} }

View file

@ -1,6 +1,7 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Window
import qs.Commons import qs.Commons
import qs.Widgets import qs.Widgets
import qs.Services import qs.Services
@ -9,7 +10,6 @@ ColumnLayout {
id: root id: root
spacing: Style.marginM * scaling spacing: Style.marginM * scaling
// Properties to receive data from parent
property var widgetData: null property var widgetData: null
property var widgetMetadata: null property var widgetMetadata: null
@ -22,16 +22,190 @@ ColumnLayout {
return settings return settings
} }
// Icon setting
NTextInput { NTextInput {
id: iconInput id: iconInput
Layout.fillWidth: true Layout.fillWidth: true
label: "Icon Name" label: "Icon Name"
description: "Choose a name from the Material Icon set." description: "Pick from Bootstrap Icons or type a name."
placeholderText: "Enter icon name (e.g., favorite, home, settings)" placeholderText: "Enter icon name (e.g., speedometer2, gear, house)"
text: widgetData?.icon || widgetMetadata.icon 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(Bootstrap.icons)
property var filteredIcons: allIcons.filter(function (name) {
return query === "" || name.toLowerCase().indexOf(query.toLowerCase()) !== -1
})
readonly property int tileBase: Math.round(112 * scaling)
readonly property int columns: Math.max(3, Math.floor(grid.width / (tileBase + Style.marginS * 2)))
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: "x-lg"
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: Style.fontSizeXXXL * 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-lg"
enabled: iconPicker.selectedIcon !== ""
onClicked: {
iconInput.text = iconPicker.selectedIcon
iconPicker.close()
}
}
}
}
}
NTextInput { NTextInput {
id: leftClickExecInput id: leftClickExecInput
Layout.fillWidth: true Layout.fillWidth: true

View file

@ -21,6 +21,7 @@ ColumnLayout {
!== undefined ? widgetData.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent !== undefined ? widgetData.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent
property bool valueShowNetworkStats: widgetData.showNetworkStats property bool valueShowNetworkStats: widgetData.showNetworkStats
!== undefined ? widgetData.showNetworkStats : widgetMetadata.showNetworkStats !== undefined ? widgetData.showNetworkStats : widgetMetadata.showNetworkStats
property bool valueShowDiskUsage: widgetData.showDiskUsage !== undefined ? widgetData.showDiskUsage : widgetMetadata.showDiskUsage
function saveSettings() { function saveSettings() {
var settings = Object.assign({}, widgetData || {}) var settings = Object.assign({}, widgetData || {})
@ -29,6 +30,7 @@ ColumnLayout {
settings.showMemoryUsage = valueShowMemoryUsage settings.showMemoryUsage = valueShowMemoryUsage
settings.showMemoryAsPercent = valueShowMemoryAsPercent settings.showMemoryAsPercent = valueShowMemoryAsPercent
settings.showNetworkStats = valueShowNetworkStats settings.showNetworkStats = valueShowNetworkStats
settings.showDiskUsage = valueShowDiskUsage
return settings return settings
} }
@ -59,7 +61,7 @@ ColumnLayout {
NToggle { NToggle {
id: showMemoryAsPercent id: showMemoryAsPercent
Layout.fillWidth: true Layout.fillWidth: true
label: "Show memory as percentage" label: "Memory as percentage"
checked: valueShowMemoryAsPercent checked: valueShowMemoryAsPercent
onToggled: checked => valueShowMemoryAsPercent = checked onToggled: checked => valueShowMemoryAsPercent = checked
} }
@ -71,4 +73,12 @@ ColumnLayout {
checked: valueShowNetworkStats checked: valueShowNetworkStats
onToggled: checked => valueShowNetworkStats = checked onToggled: checked => valueShowNetworkStats = checked
} }
NToggle {
id: showDiskUsage
Layout.fillWidth: true
label: "Storage usage"
checked: valueShowDiskUsage
onToggled: checked => valueShowDiskUsage = checked
}
} }

View file

@ -123,42 +123,42 @@ NPanel {
let newTabs = [{ let newTabs = [{
"id": SettingsPanel.Tab.General, "id": SettingsPanel.Tab.General,
"label": "General", "label": "General",
"icon": "tune", "icon": "box",
"source": generalTab "source": generalTab
}, { }, {
"id": SettingsPanel.Tab.Bar, "id": SettingsPanel.Tab.Bar,
"label": "Bar", "label": "Bar",
"icon": "web_asset", "icon": "segmented-nav",
"source": barTab "source": barTab
}, { }, {
"id": SettingsPanel.Tab.Launcher, "id": SettingsPanel.Tab.Launcher,
"label": "Launcher", "label": "Launcher",
"icon": "apps", "icon": "rocket",
"source": launcherTab "source": launcherTab
}, { }, {
"id": SettingsPanel.Tab.Audio, "id": SettingsPanel.Tab.Audio,
"label": "Audio", "label": "Audio",
"icon": "volume_up", "icon": "speaker",
"source": audioTab "source": audioTab
}, { }, {
"id": SettingsPanel.Tab.Display, "id": SettingsPanel.Tab.Display,
"label": "Display", "label": "Display",
"icon": "monitor", "icon": "display",
"source": displayTab "source": displayTab
}, { }, {
"id": SettingsPanel.Tab.Network, "id": SettingsPanel.Tab.Network,
"label": "Network", "label": "Network",
"icon": "lan", "icon": "ethernet",
"source": networkTab "source": networkTab
}, { }, {
"id": SettingsPanel.Tab.Brightness, "id": SettingsPanel.Tab.Brightness,
"label": "Brightness", "label": "Brightness",
"icon": "brightness_6", "icon": "brightness-high",
"source": brightnessTab "source": brightnessTab
}, { }, {
"id": SettingsPanel.Tab.Weather, "id": SettingsPanel.Tab.Weather,
"label": "Weather", "label": "Weather",
"icon": "partly_cloudy_day", "icon": "cloud-sun",
"source": weatherTab "source": weatherTab
}, { }, {
"id": SettingsPanel.Tab.ColorScheme, "id": SettingsPanel.Tab.ColorScheme,
@ -168,7 +168,7 @@ NPanel {
}, { }, {
"id": SettingsPanel.Tab.Wallpaper, "id": SettingsPanel.Tab.Wallpaper,
"label": "Wallpaper", "label": "Wallpaper",
"icon": "image", "icon": "easel",
"source": wallpaperTab "source": wallpaperTab
}] }]
@ -177,7 +177,7 @@ NPanel {
newTabs.push({ newTabs.push({
"id": SettingsPanel.Tab.WallpaperSelector, "id": SettingsPanel.Tab.WallpaperSelector,
"label": "Wallpaper Selector", "label": "Wallpaper Selector",
"icon": "wallpaper_slideshow", "icon": "image",
"source": wallpaperSelectorTab "source": wallpaperSelectorTab
}) })
} }
@ -185,17 +185,17 @@ NPanel {
newTabs.push({ newTabs.push({
"id": SettingsPanel.Tab.ScreenRecorder, "id": SettingsPanel.Tab.ScreenRecorder,
"label": "Screen Recorder", "label": "Screen Recorder",
"icon": "videocam", "icon": "camera-video",
"source": screenRecorderTab "source": screenRecorderTab
}, { }, {
"id": SettingsPanel.Tab.Hooks, "id": SettingsPanel.Tab.Hooks,
"label": "Hooks", "label": "Hooks",
"icon": "cable", "icon": "link-45deg",
"source": hooksTab "source": hooksTab
}, { }, {
"id": SettingsPanel.Tab.About, "id": SettingsPanel.Tab.About,
"label": "About", "label": "About",
"icon": "info", "icon": "info-circle",
"source": aboutTab "source": aboutTab
}) })
@ -400,15 +400,13 @@ NPanel {
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: Style.marginS * scaling anchors.leftMargin: Style.marginS * scaling
anchors.rightMargin: Style.marginS * scaling anchors.rightMargin: Style.marginS * scaling
spacing: Style.marginS * scaling spacing: Style.marginM * scaling
// Tab icon
NIcon { NIcon {
text: modelData.icon text: Bootstrap.icons[modelData.icon]
color: tabTextColor color: tabTextColor
font.pointSize: Style.fontSizeL * scaling font.pointSize: Style.fontSizeL * scaling
} }
// Tab label // Tab label
NText { NText {
text: modelData.label text: modelData.label
@ -416,6 +414,7 @@ NPanel {
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
} }
} }
@ -473,7 +472,7 @@ NPanel {
// Close button // Close button
NIconButton { NIconButton {
icon: "close" icon: "x-lg"
tooltipText: "Close" tooltipText: "Close"
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
onClicked: root.close() onClicked: root.close()

View file

@ -272,7 +272,7 @@ ColumnLayout {
// Button aligned to the center of the actual input field // Button aligned to the center of the actual input field
NIconButton { NIconButton {
icon: "add" icon: "plus-lg"
Layout.alignment: Qt.AlignBottom Layout.alignment: Qt.AlignBottom
Layout.bottomMargin: blacklistInput.description ? Style.marginS * scaling : 0 Layout.bottomMargin: blacklistInput.description ? Style.marginS * scaling : 0
onClicked: { onClicked: {
@ -322,7 +322,7 @@ ColumnLayout {
} }
NIconButton { NIconButton {
icon: "close" icon: "x-lg"
sizeRatio: 0.8 sizeRatio: 0.8
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.rightMargin: Style.marginXS * scaling Layout.rightMargin: Style.marginXS * scaling

View file

@ -181,7 +181,7 @@ ColumnLayout {
} }
NIconButton { NIconButton {
icon: "refresh" icon: "arrow-repeat"
tooltipText: "Reset scaling" tooltipText: "Reset scaling"
onClicked: ScalingService.setScreenScale(modelData, 1.0) onClicked: ScalingService.setScreenScale(modelData, 1.0)
} }

View file

@ -96,7 +96,7 @@ ColumnLayout {
} }
NIconButton { NIconButton {
icon: "refresh" icon: "arrow-repeat"
tooltipText: "Refresh wallpaper list" tooltipText: "Refresh wallpaper list"
onClicked: { onClicked: {
WallpaperService.refreshWallpapersList() WallpaperService.refreshWallpapersList()
@ -181,7 +181,7 @@ ColumnLayout {
visible: isSelected visible: isSelected
NIcon { NIcon {
text: "check" icon: "check-lg"
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
color: Color.mOnSecondary color: Color.mOnSecondary

View file

@ -31,7 +31,7 @@ NBox {
} }
NIcon { NIcon {
text: "album" icon: "disc"
font.pointSize: Style.fontSizeXXXL * 2.5 * scaling font.pointSize: Style.fontSizeXXXL * 2.5 * scaling
color: Color.mPrimary color: Color.mPrimary
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -162,14 +162,14 @@ NBox {
anchors.fill: parent anchors.fill: parent
anchors.margins: Style.marginXS * scaling anchors.margins: Style.marginXS * scaling
imagePath: MediaService.trackArtUrl imagePath: MediaService.trackArtUrl
fallbackIcon: "music_note" fallbackIcon: "disc"
borderColor: Color.mOutline borderColor: Color.mOutline
borderWidth: Math.max(1, Style.borderS * scaling) borderWidth: Math.max(1, Style.borderS * scaling)
} }
// Fallback icon when no album art available // Fallback icon when no album art available
NIcon { NIcon {
text: "album" icon: "disc"
color: Color.mPrimary color: Color.mPrimary
font.pointSize: Style.fontSizeL * 12 * scaling font.pointSize: Style.fontSizeL * 12 * scaling
visible: !trackArt.visible visible: !trackArt.visible
@ -307,7 +307,7 @@ NBox {
// Previous button // Previous button
NIconButton { NIconButton {
icon: "skip_previous" icon: "skip-start"
tooltipText: "Previous Media" tooltipText: "Previous Media"
visible: MediaService.canGoPrevious visible: MediaService.canGoPrevious
onClicked: MediaService.canGoPrevious ? MediaService.previous() : {} onClicked: MediaService.canGoPrevious ? MediaService.previous() : {}
@ -315,7 +315,7 @@ NBox {
// Play/Pause button // Play/Pause button
NIconButton { NIconButton {
icon: MediaService.isPlaying ? "pause" : "play_arrow" icon: MediaService.isPlaying ? "pause" : "play"
tooltipText: MediaService.isPlaying ? "Pause" : "Play" tooltipText: MediaService.isPlaying ? "Pause" : "Play"
visible: (MediaService.canPlay || MediaService.canPause) visible: (MediaService.canPlay || MediaService.canPause)
onClicked: (MediaService.canPlay || MediaService.canPause) ? MediaService.playPause() : {} onClicked: (MediaService.canPlay || MediaService.canPause) ? MediaService.playPause() : {}
@ -323,7 +323,7 @@ NBox {
// Next button // Next button
NIconButton { NIconButton {
icon: "skip_next" icon: "skip-end"
tooltipText: "Next media" tooltipText: "Next media"
visible: MediaService.canGoNext visible: MediaService.canGoNext
onClicked: MediaService.canGoNext ? MediaService.next() : {} onClicked: MediaService.canGoNext ? MediaService.next() : {}

View file

@ -28,7 +28,7 @@ NBox {
} }
// Performance // Performance
NIconButton { NIconButton {
icon: "speed" icon: "speedometer2"
tooltipText: "Set performance power profile." tooltipText: "Set performance power profile."
enabled: hasPP enabled: hasPP
opacity: enabled ? Style.opacityFull : Style.opacityMedium opacity: enabled ? Style.opacityFull : Style.opacityMedium
@ -37,12 +37,13 @@ NBox {
onClicked: { onClicked: {
if (enabled) { if (enabled) {
powerProfiles.profile = PowerProfile.Performance powerProfiles.profile = PowerProfile.Performance
ToastService.showNotice("Power Profile", "Performance")
} }
} }
} }
// Balanced // Balanced
NIconButton { NIconButton {
icon: "balance" icon: "yin-yang"
tooltipText: "Set balanced power profile." tooltipText: "Set balanced power profile."
enabled: hasPP enabled: hasPP
opacity: enabled ? Style.opacityFull : Style.opacityMedium opacity: enabled ? Style.opacityFull : Style.opacityMedium
@ -51,12 +52,13 @@ NBox {
onClicked: { onClicked: {
if (enabled) { if (enabled) {
powerProfiles.profile = PowerProfile.Balanced powerProfiles.profile = PowerProfile.Balanced
ToastService.showNotice("Power Profile", "Balanced")
} }
} }
} }
// Eco // Eco
NIconButton { NIconButton {
icon: "eco" icon: "leaf"
tooltipText: "Set eco power profile." tooltipText: "Set eco power profile."
enabled: hasPP enabled: hasPP
opacity: enabled ? Style.opacityFull : Style.opacityMedium opacity: enabled ? Style.opacityFull : Style.opacityMedium
@ -65,6 +67,7 @@ NBox {
onClicked: { onClicked: {
if (enabled) { if (enabled) {
powerProfiles.profile = PowerProfile.PowerSaver powerProfiles.profile = PowerProfile.PowerSaver
ToastService.showNotice("Power Profile", "Power Saver")
} }
} }
} }

View file

@ -47,7 +47,8 @@ NBox {
} }
NText { NText {
text: `System uptime: ${uptimeText}` text: `System uptime: ${uptimeText}`
color: Color.mOnSurface font.pointSize: Style.fontSizeS * scaling
color: Color.mOnSurfaceVariant
} }
} }
@ -58,7 +59,7 @@ NBox {
Layout.fillWidth: true Layout.fillWidth: true
} }
NIconButton { NIconButton {
icon: "settings" icon: "gear"
tooltipText: "Open settings." tooltipText: "Open settings."
onClicked: { onClicked: {
settingsPanel.requestedTab = SettingsPanel.Tab.General settingsPanel.requestedTab = SettingsPanel.Tab.General
@ -68,7 +69,7 @@ NBox {
NIconButton { NIconButton {
id: powerButton id: powerButton
icon: "power_settings_new" icon: "power"
tooltipText: "Power menu." tooltipText: "Power menu."
onClicked: { onClicked: {
powerPanel.open(screen) powerPanel.open(screen)
@ -78,7 +79,7 @@ NBox {
NIconButton { NIconButton {
id: closeButton id: closeButton
icon: "close" icon: "x-lg"
tooltipText: "Close side panel." tooltipText: "Close side panel."
onClicked: { onClicked: {
sidePanel.close() sidePanel.close()

View file

@ -24,7 +24,7 @@ NBox {
NCircleStat { NCircleStat {
value: SystemStatService.cpuUsage value: SystemStatService.cpuUsage
icon: "speed" icon: "speedometer2"
flat: true flat: true
contentScale: 0.8 contentScale: 0.8
width: 72 * scaling width: 72 * scaling
@ -33,7 +33,7 @@ NBox {
NCircleStat { NCircleStat {
value: SystemStatService.cpuTemp value: SystemStatService.cpuTemp
suffix: "°C" suffix: "°C"
icon: "device_thermostat" icon: "fire"
flat: true flat: true
contentScale: 0.8 contentScale: 0.8
width: 72 * scaling width: 72 * scaling
@ -41,7 +41,7 @@ NBox {
} }
NCircleStat { NCircleStat {
value: SystemStatService.memPercent value: SystemStatService.memPercent
icon: "memory" icon: "cpu"
flat: true flat: true
contentScale: 0.8 contentScale: 0.8
width: 72 * scaling width: 72 * scaling
@ -49,7 +49,7 @@ NBox {
} }
NCircleStat { NCircleStat {
value: SystemStatService.diskPercent value: SystemStatService.diskPercent
icon: "hard_drive" icon: "hdd"
flat: true flat: true
contentScale: 0.8 contentScale: 0.8
width: 72 * scaling width: 72 * scaling

View file

@ -25,11 +25,14 @@ NBox {
} }
// Screen Recorder // Screen Recorder
NIconButton { NIconButton {
icon: "videocam" icon: "camera-video"
tooltipText: ScreenRecorderService.isRecording ? "Stop screen recording." : "Start screen recording." 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 colorBg: ScreenRecorderService.isRecording ? Color.mPrimary : Color.mSurfaceVariant
colorFg: ScreenRecorderService.isRecording ? Color.mOnPrimary : Color.mPrimary colorFg: ScreenRecorderService.isRecording ? Color.mOnPrimary : Color.mPrimary
onClicked: { onClicked: {
if (!ScreenRecorderService.isAvailable)
return
ScreenRecorderService.toggleRecording() ScreenRecorderService.toggleRecording()
// If we were not recording and we just initiated a start, close the panel // If we were not recording and we just initiated a start, close the panel
if (!ScreenRecorderService.isRecording) { if (!ScreenRecorderService.isRecording) {
@ -41,7 +44,7 @@ NBox {
// Idle Inhibitor // Idle Inhibitor
NIconButton { NIconButton {
icon: "coffee" icon: "cup"
tooltipText: IdleInhibitorService.isInhibited ? "Disable keep awake." : "Enable keep awake." tooltipText: IdleInhibitorService.isInhibited ? "Disable keep awake." : "Enable keep awake."
colorBg: IdleInhibitorService.isInhibited ? Color.mPrimary : Color.mSurfaceVariant colorBg: IdleInhibitorService.isInhibited ? Color.mPrimary : Color.mSurfaceVariant
colorFg: IdleInhibitorService.isInhibited ? Color.mOnPrimary : Color.mPrimary colorFg: IdleInhibitorService.isInhibited ? Color.mOnPrimary : Color.mPrimary

View file

@ -27,7 +27,7 @@ NBox {
RowLayout { RowLayout {
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
NIcon { NIcon {
text: weatherReady ? LocationService.weatherSymbolFromCode( icon: weatherReady ? LocationService.weatherSymbolFromCode(
LocationService.data.weather.current_weather.weathercode) : "" LocationService.data.weather.current_weather.weathercode) : ""
font.pointSize: Style.fontSizeXXXL * 1.75 * scaling font.pointSize: Style.fontSizeXXXL * 1.75 * scaling
color: Color.mPrimary color: Color.mPrimary
@ -98,7 +98,7 @@ NBox {
color: Color.mOnSurface color: Color.mOnSurface
} }
NIcon { NIcon {
text: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index]) icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index])
font.pointSize: Style.fontSizeXXL * scaling font.pointSize: Style.fontSizeXXL * scaling
color: Color.mPrimary color: Color.mPrimary
} }

View file

@ -34,7 +34,7 @@ NPanel {
spacing: Style.marginM * scaling spacing: Style.marginM * scaling
NIcon { NIcon {
text: Settings.data.network.wifiEnabled ? "wifi" : "wifi_off" icon: Settings.data.network.wifiEnabled ? "wifi" : "wifi-off"
font.pointSize: Style.fontSizeXXL * scaling font.pointSize: Style.fontSizeXXL * scaling
color: Settings.data.network.wifiEnabled ? Color.mPrimary : Color.mOnSurfaceVariant color: Settings.data.network.wifiEnabled ? Color.mPrimary : Color.mOnSurfaceVariant
} }
@ -55,7 +55,7 @@ NPanel {
} }
NIconButton { NIconButton {
icon: "refresh" icon: "arrow-repeat"
tooltipText: "Refresh" tooltipText: "Refresh"
sizeRatio: 0.8 sizeRatio: 0.8
enabled: Settings.data.network.wifiEnabled && !NetworkService.scanning enabled: Settings.data.network.wifiEnabled && !NetworkService.scanning
@ -63,7 +63,7 @@ NPanel {
} }
NIconButton { NIconButton {
icon: "close" icon: "x-lg"
tooltipText: "Close" tooltipText: "Close"
sizeRatio: 0.8 sizeRatio: 0.8
onClicked: root.close() onClicked: root.close()
@ -91,7 +91,7 @@ NPanel {
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
NIcon { NIcon {
text: "error" icon: "exclamation-triangle"
font.pointSize: Style.fontSizeL * scaling font.pointSize: Style.fontSizeL * scaling
color: Color.mError color: Color.mError
} }
@ -105,7 +105,7 @@ NPanel {
} }
NIconButton { NIconButton {
icon: "close" icon: "x-lg"
sizeRatio: 0.6 sizeRatio: 0.6
onClicked: NetworkService.lastError = "" onClicked: NetworkService.lastError = ""
} }
@ -129,7 +129,7 @@ NPanel {
} }
NIcon { NIcon {
text: "wifi_off" icon: "wifi-off"
font.pointSize: 64 * scaling font.pointSize: 64 * scaling
color: Color.mOnSurfaceVariant color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -245,7 +245,7 @@ NPanel {
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
NIcon { NIcon {
text: NetworkService.signalIcon(modelData.signal) icon: NetworkService.signalIcon(modelData.signal)
font.pointSize: Style.fontSizeXXL * scaling font.pointSize: Style.fontSizeXXL * scaling
color: modelData.connected ? Color.mPrimary : Color.mOnSurface color: modelData.connected ? Color.mPrimary : Color.mOnSurface
} }
@ -377,7 +377,7 @@ NPanel {
&& NetworkService.connectingTo !== modelData.ssid && NetworkService.connectingTo !== modelData.ssid
&& NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid
&& NetworkService.disconnectingFrom !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
icon: "delete" icon: "trash"
tooltipText: "Forget network" tooltipText: "Forget network"
sizeRatio: 0.7 sizeRatio: 0.7
onClicked: expandedSsid = expandedSsid === modelData.ssid ? "" : modelData.ssid onClicked: expandedSsid = expandedSsid === modelData.ssid ? "" : modelData.ssid
@ -492,7 +492,7 @@ NPanel {
} }
NIconButton { NIconButton {
icon: "close" icon: "x-lg"
sizeRatio: 0.8 sizeRatio: 0.8
onClicked: { onClicked: {
passwordSsid = "" passwordSsid = ""
@ -547,7 +547,7 @@ NPanel {
} }
NIconButton { NIconButton {
icon: "close" icon: "x-lg"
sizeRatio: 0.8 sizeRatio: 0.8
onClicked: expandedSsid = "" onClicked: expandedSsid = ""
} }
@ -571,7 +571,7 @@ NPanel {
} }
NIcon { NIcon {
text: "wifi_find" icon: "search"
font.pointSize: 64 * scaling font.pointSize: 64 * scaling
color: Color.mOnSurfaceVariant color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -586,7 +586,7 @@ NPanel {
NButton { NButton {
text: "Scan again" text: "Scan again"
icon: "refresh" icon: "arrow-repeat"
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
onClicked: NetworkService.scan() onClicked: NetworkService.scan()
} }

View file

@ -21,6 +21,11 @@
</a> </a>
</p> </p>
> ⚠️ **BREAKING CHANGE:**
> We transitioned from using Material Symbols to Bootstrap Icons, that means you will have to install the new font.
---
A sleek and minimal desktop shell thoughtfully crafted for Wayland, built with Quickshell. 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! 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 +71,7 @@ Features a modern modular architecture with a status bar, notification system, c
- `quickshell-git` - Core shell framework - `quickshell-git` - Core shell framework
- `ttf-roboto` - The default font used for most of the UI - `ttf-roboto` - The default font used for most of the UI
- `inter-font` - The default font used for Headers (ex: clock on the LockScreen) - `inter-font` - The default font used for Headers (ex: clock on the LockScreen)
- `ttf-material-symbols-variable-git` - Icon font for UI elements - `ttf-bootstrap-icons` - Icon font for UI elements
- `gpu-screen-recorder` - Screen recording functionality - `gpu-screen-recorder` - Screen recording functionality
- `brightnessctl` - For internal/laptop monitor brightness - `brightnessctl` - For internal/laptop monitor brightness
- `ddcutil` - For desktop monitor brightness (might introduce some system instability with certain monitors) - `ddcutil` - For desktop monitor brightness (might introduce some system instability with certain monitors)

View file

@ -62,6 +62,8 @@ Singleton {
function onMutedChanged() { function onMutedChanged() {
root._muted = (sink?.audio.muted ?? true) root._muted = (sink?.audio.muted ?? true)
Logger.log("AudioService", "OnMuteChanged:", root._muted) 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() { function onMutedChanged() {
root._inputMuted = (source?.audio.muted ?? true) root._inputMuted = (source?.audio.muted ?? true)
Logger.log("AudioService", "OnInputMuteChanged:", root._inputMuted) Logger.log("AudioService", "OnInputMuteChanged:", root._inputMuted)
// Toast: microphone mute toggle
ToastService.showNotice("Microphone", root._inputMuted ? "Muted" : "Unmuted")
} }
} }

View file

@ -84,7 +84,8 @@ Singleton {
"showCpuTemp": true, "showCpuTemp": true,
"showMemoryUsage": true, "showMemoryUsage": true,
"showMemoryAsPercent": false, "showMemoryAsPercent": false,
"showNetworkStats": false "showNetworkStats": false,
"showDiskUsage": false
}, },
"Workspace": { "Workspace": {
"allowUserSettings": true, "allowUserSettings": true,

View file

@ -2,6 +2,8 @@ pragma Singleton
import Quickshell import Quickshell
import Quickshell.Services.UPower import Quickshell.Services.UPower
import qs.Commons
import qs.Services
Singleton { Singleton {
id: root id: root
@ -9,41 +11,20 @@ Singleton {
// Choose icon based on charge and charging state // Choose icon based on charge and charging state
function getIcon(percent, charging, isReady) { function getIcon(percent, charging, isReady) {
if (!isReady) { if (!isReady) {
return "battery_error" return "exclamation-triangle"
} }
if (charging) { if (charging) {
if (percent >= 95) return "battery-charging"
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"
} else { } else {
if (percent >= 95)
return "battery_full"
if (percent >= 85) if (percent >= 85)
return "battery_6_bar" return "battery-full"
if (percent >= 70) if (percent >= 45)
return "battery_5_bar" return "battery-half"
if (percent >= 55)
return "battery_4_bar"
if (percent >= 40)
return "battery_3_bar"
if (percent >= 25) if (percent >= 25)
return "battery_2_bar" return "battery-low"
if (percent >= 10)
return "battery_1_bar"
if (percent >= 0) if (percent >= 0)
return "battery_0_bar" return "battery"
} }
} }
} }

View file

@ -62,17 +62,17 @@ Singleton {
} }
if (icon.includes("mouse") || name.includes("mouse")) { if (icon.includes("mouse") || name.includes("mouse")) {
return "mouse" return "mouse-2"
} }
if (icon.includes("keyboard") || name.includes("keyboard")) { if (icon.includes("keyboard") || name.includes("keyboard")) {
return "keyboard" return "keyboard"
} }
if (icon.includes("phone") || name.includes("phone") || name.includes("iphone") || name.includes("android") if (icon.includes("phone") || name.includes("phone") || name.includes("iphone") || name.includes("android")
|| name.includes("samsung")) { || name.includes("samsung")) {
return "smartphone" return "phone"
} }
if (icon.includes("watch") || name.includes("watch")) { if (icon.includes("watch") || name.includes("watch")) {
return "watch" return "smartwatch"
} }
if (icon.includes("speaker") || name.includes("speaker")) { if (icon.includes("speaker") || name.includes("speaker")) {
return "speaker" return "speaker"

View file

@ -23,6 +23,11 @@ Singleton {
// Re-apply current scheme to pick the right variant // Re-apply current scheme to pick the right variant
applyScheme(Settings.data.colorSchemes.predefinedScheme) 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)
} }
} }

View file

@ -192,9 +192,11 @@ Singleton {
} }
windowsList.push({ windowsList.push({
"id": toplevel.address || "", "id": (toplevel.address !== undefined
"title": toplevel.title || "", && toplevel.address !== null) ? String(toplevel.address) : "",
"appId": appId, "title": (toplevel.title !== undefined && toplevel.title !== null) ? String(
toplevel.title) : "",
"appId": (appId !== undefined && appId !== null) ? String(appId) : "",
"workspaceId": toplevel.workspace?.id || null, "workspaceId": toplevel.workspace?.id || null,
"isFocused": toplevel.activated === true "isFocused": toplevel.activated === true
}) })

View file

@ -13,6 +13,7 @@ Singleton {
property ListModel displayFonts: ListModel {} property ListModel displayFonts: ListModel {}
property bool fontsLoaded: false property bool fontsLoaded: false
// -------------------------------------------
function init() { function init() {
Logger.log("Font", "Service started") Logger.log("Font", "Service started")
loadSystemFonts() loadSystemFonts()

View file

@ -231,21 +231,23 @@ Singleton {
// -------------------------------- // --------------------------------
function weatherSymbolFromCode(code) { function weatherSymbolFromCode(code) {
if (code === 0) if (code === 0)
return "sunny" return "sun"
if (code === 1 || code === 2) if (code === 1 || code === 2)
return "partly_cloudy_day" return "cloud-sun"
if (code === 3) if (code === 3)
return "cloud" return "cloud"
if (code >= 45 && code <= 48) if (code >= 45 && code <= 48)
return "foggy" return "cloud-haze"
if (code >= 51 && code <= 67) if (code >= 51 && code <= 67)
return "rainy" return "cloud-rain"
if (code >= 71 && code <= 77) if (code >= 71 && code <= 77)
return "weather_snowy" return "cloud-snow"
if (code >= 80 && code <= 82) if (code >= 71 && code <= 77)
return "rainy" return "cloud-snow"
if (code >= 85 && code <= 86)
return "cloud-snow"
if (code >= 95 && code <= 99) if (code >= 95 && code <= 99)
return "thunderstorm" return "cloud-lightning"
return "cloud" return "cloud"
} }

View file

@ -202,14 +202,12 @@ Singleton {
// Helper functions // Helper functions
function signalIcon(signal) { function signalIcon(signal) {
if (signal >= 80) if (signal >= 80)
return "network_wifi" return "wifi"
if (signal >= 60) if (signal >= 50)
return "network_wifi_3_bar" return "wifi-2"
if (signal >= 40)
return "network_wifi_2_bar"
if (signal >= 20) if (signal >= 20)
return "network_wifi_1_bar" return "wifi-1"
return "signal_wifi_0_bar" return "dot"
} }
function isSecured(security) { function isSecured(security) {

View file

@ -50,6 +50,9 @@ Singleton {
target: Settings.data.nightLight target: Settings.data.nightLight
function onEnabledChanged() { function onEnabledChanged() {
apply() apply()
// Toast: night light toggled
const enabled = !!Settings.data.nightLight.enabled
ToastService.showNotice("Night Light", enabled ? "Enabled" : "Disabled")
} }
function onNightTempChanged() { function onNightTempChanged() {
apply() apply()

View file

@ -13,6 +13,17 @@ Singleton {
property bool isRecording: false property bool isRecording: false
property bool isPending: false property bool isPending: false
property string outputPath: "" 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 // Start or Stop recording
function toggleRecording() { function toggleRecording() {
@ -21,6 +32,9 @@ Singleton {
// Start screen recording using Quickshell.execDetached // Start screen recording using Quickshell.execDetached
function startRecording() { function startRecording() {
if (!isAvailable) {
return
}
if (isRecording || isPending) { if (isRecording || isPending) {
return 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 { Timer {
id: pendingTimer id: pendingTimer
interval: 2000 // Wait 2 seconds to see if process stays alive interval: 2000 // Wait 2 seconds to see if process stays alive

View file

@ -321,10 +321,8 @@ Singleton {
// ------------------------------------------------------- // -------------------------------------------------------
// Helper function to format network speeds // Helper function to format network speeds
function formatSpeed(bytesPerSecond) { function formatSpeed(bytesPerSecond) {
if (bytesPerSecond < 1024) { if (bytesPerSecond < 1024 * 1024) {
return bytesPerSecond.toFixed(0) + "B/s" return (bytesPerSecond / 1024).toFixed(1) + "KB/s"
} else if (bytesPerSecond < 1024 * 1024) {
return (bytesPerSecond / 1024).toFixed(0) + "KB/s"
} else if (bytesPerSecond < 1024 * 1024 * 1024) { } else if (bytesPerSecond < 1024 * 1024 * 1024) {
return (bytesPerSecond / (1024 * 1024)).toFixed(1) + "MB/s" return (bytesPerSecond / (1024 * 1024)).toFixed(1) + "MB/s"
} else { } else {

View file

@ -185,6 +185,11 @@ Singleton {
// Process the message queue // Process the message queue
function processQueue() { function processQueue() {
if (messageQueue.length === 0 || allToasts.length === 0) { 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 isShowingToast = false
return return
} }

View file

@ -80,11 +80,12 @@ Rectangle {
spacing: Style.marginXS * scaling spacing: Style.marginXS * scaling
// Icon (optional) // Icon (optional)
NIcon { Loader {
active: root.icon !== ""
sourceComponent: NIcon {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
layoutTopMargin: 1 * scaling
visible: root.icon !== "" icon: root.icon
text: root.icon
font.pointSize: root.iconSize font.pointSize: root.iconSize
color: { color: {
if (!root.enabled) if (!root.enabled)
@ -104,6 +105,7 @@ Rectangle {
} }
} }
} }
}
// Text // Text
NText { NText {

View file

@ -57,7 +57,7 @@ RowLayout {
NIcon { NIcon {
visible: root.checked visible: root.checked
anchors.centerIn: parent anchors.centerIn: parent
text: "check" icon: "check-lg"
color: root.activeOnColor color: root.activeOnColor
font.pointSize: Math.max(Style.fontSizeS, root.baseSize * 0.7) * scaling font.pointSize: Math.max(Style.fontSizeS, root.baseSize * 0.7) * scaling
} }

View file

@ -88,20 +88,21 @@ Rectangle {
// Tiny circular badge for the icon, positioned using anchors within the gauge // Tiny circular badge for the icon, positioned using anchors within the gauge
Rectangle { Rectangle {
id: iconBadge id: iconBadge
width: 28 * scaling * contentScale width: iconText.implicitWidth + Style.marginXS * scaling
height: width height: width
radius: width / 2 radius: width / 2
color: Color.mSurface color: Color.mPrimary
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.rightMargin: -6 * scaling * contentScale anchors.rightMargin: Style.marginXXS * scaling * contentScale
anchors.topMargin: Style.marginXXS * scaling * contentScale anchors.topMargin: Style.marginXS * scaling * contentScale
NIcon { NIcon {
id: iconText
anchors.centerIn: parent anchors.centerIn: parent
text: root.icon icon: root.icon
font.pointSize: Style.fontSizeLargeXL * scaling * contentScale font.pointSize: Style.fontSizeS * scaling * contentScale
color: Color.mOnSurface color: Color.mOnPrimary
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }

View file

@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import qs.Commons import qs.Commons
import qs.Services
import qs.Widgets import qs.Widgets
Rectangle { Rectangle {
@ -58,7 +59,7 @@ Rectangle {
} }
NIcon { NIcon {
text: "palette" icon: "paint-bucket"
color: Color.mOnSurfaceVariant color: Color.mOnSurfaceVariant
} }
} }

View file

@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import qs.Commons import qs.Commons
import qs.Services
import qs.Widgets import qs.Widgets
Popup { Popup {
@ -129,7 +130,7 @@ Popup {
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
NIcon { NIcon {
text: "palette" icon: "eyedropper"
font.pointSize: Style.fontSizeXXL * scaling font.pointSize: Style.fontSizeXXL * scaling
color: Color.mPrimary color: Color.mPrimary
} }
@ -147,7 +148,7 @@ Popup {
} }
NIconButton { NIconButton {
icon: "close" icon: "x-lg"
onClicked: root.close() onClicked: root.close()
} }
} }
@ -491,7 +492,7 @@ Popup {
NButton { NButton {
id: cancelButton id: cancelButton
text: "Cancel" text: "Cancel"
icon: "close" icon: "x-lg"
outlined: cancelButton.hovered ? false : true outlined: cancelButton.hovered ? false : true
customHeight: 36 * scaling customHeight: 36 * scaling
customWidth: 100 * scaling customWidth: 100 * scaling
@ -502,7 +503,7 @@ Popup {
NButton { NButton {
text: "Apply" text: "Apply"
icon: "check" icon: "check-lg"
customHeight: 36 * scaling customHeight: 36 * scaling
customWidth: 100 * scaling customWidth: 100 * scaling
onClicked: { onClicked: {

View file

@ -85,8 +85,8 @@ RowLayout {
indicator: NIcon { indicator: NIcon {
x: combo.width - width - Style.marginM * scaling x: combo.width - width - Style.marginM * scaling
y: combo.topPadding + (combo.availableHeight - height) / 2 y: combo.topPadding + (combo.availableHeight - height) / 2
text: "arrow_drop_down" icon: "chevron-down"
font.pointSize: Style.fontSizeXXL * scaling font.pointSize: Style.fontSizeL * scaling
} }
popup: Popup { popup: Popup {

View file

@ -1,19 +1,22 @@
import QtQuick import QtQuick
import QtQuick.Layouts
import qs.Commons import qs.Commons
import qs.Widgets import qs.Widgets
import QtQuick.Layouts
Text { Text {
// Optional layout nudge for optical alignment when used inside Layouts readonly property string defaultIcon: "balloon"
property real layoutTopMargin: 0 property string icon: defaultIcon
text: "question_mark"
font.family: "Material Symbols Rounded" text: {
font.pointSize: Style.fontSizeL * scaling if (icon === undefined || Bootstrap.icons[icon] === undefined) {
font.variableAxes: { Logger.warn("Icon", `"${icon}"`, "doesn't exist in the bootstrap font")
"wght"// slightly bold to ensure all lines looks good Logger.callStack()
: (Font.Normal + Font.Bold) / 2.5 return Bootstrap.icons[defaultIcon]
} }
return Bootstrap.icons[icon]
}
font.family: "bootstrap-icons"
font.pointSize: Style.fontSizeL * scaling
color: Color.mOnSurface color: Color.mOnSurface
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
Layout.topMargin: layoutTopMargin
} }

View file

@ -35,17 +35,31 @@ Rectangle {
opacity: root.enabled ? Style.opacityFull : Style.opacityMedium opacity: root.enabled ? Style.opacityFull : Style.opacityMedium
color: root.enabled && root.hovering ? colorBgHover : colorBg color: root.enabled && root.hovering ? colorBgHover : colorBg
radius: width * 0.5 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) border.width: Math.max(1, Style.borderS * scaling)
Behavior on color {
ColorAnimation {
duration: Style.animationNormal
easing.type: Easing.InOutQuad
}
}
NIcon { NIcon {
text: root.icon icon: root.icon
font.pointSize: Style.fontSizeM * scaling font.pointSize: Math.max(1, root.width * 0.4)
color: root.hovering ? colorFgHover : colorFg color: root.enabled && root.hovering ? colorFgHover : colorFg
// Center horizontally // Center horizontally
x: (root.width - width) / 2 x: (root.width - width) / 2
// Center vertically accounting for font metrics // Center vertically accounting for font metrics
y: (root.height - height) / 2 + (height - contentHeight) / 2 y: (root.height - height) / 2 + (height - contentHeight) / 2
Behavior on color {
ColorAnimation {
duration: Style.animationFast
easing.type: Easing.InOutQuad
}
}
} }
NTooltip { NTooltip {
@ -56,13 +70,14 @@ Rectangle {
} }
MouseArea { MouseArea {
enabled: root.enabled // Always enabled to allow hover/tooltip even when the button is disabled
enabled: true
anchors.fill: parent anchors.fill: parent
cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
hoverEnabled: true hoverEnabled: true
onEntered: { onEntered: {
hovering = true hovering = root.enabled ? true : false
if (tooltipText) { if (tooltipText) {
tooltip.show() tooltip.show()
} }
@ -79,6 +94,9 @@ Rectangle {
if (tooltipText) { if (tooltipText) {
tooltip.hide() tooltip.hide()
} }
if (!root.enabled) {
return
}
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
root.clicked() root.clicked()
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {

View file

@ -9,9 +9,10 @@ Rectangle {
id: root id: root
property string imagePath: "" property string imagePath: ""
property string fallbackIcon: ""
property color borderColor: Color.transparent property color borderColor: Color.transparent
property real borderWidth: 0 property real borderWidth: 0
property string fallbackIcon: ""
property real fallbackIconSize: Style.fontSizeXXL * scaling
color: Color.transparent color: Color.transparent
radius: parent.width * 0.5 radius: parent.width * 0.5
@ -45,20 +46,22 @@ Rectangle {
} }
property real imageOpacity: root.opacity 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 supportsAtlasTextures: false
blending: true blending: true
} }
// Fallback icon // Fallback icon
NIcon { Loader {
active: fallbackIcon !== undefined && fallbackIcon !== "" && (imagePath === undefined || imagePath === "")
sourceComponent: NIcon {
anchors.centerIn: parent anchors.centerIn: parent
text: fallbackIcon icon: fallbackIcon
font.pointSize: Style.fontSizeXXL * scaling font.pointSize: fallbackIconSize
visible: fallbackIcon !== undefined && fallbackIcon !== "" && (imagePath === undefined || imagePath === "")
z: 0 z: 0
} }
} }
}
//Border //Border
Rectangle { Rectangle {

View file

@ -9,10 +9,11 @@ Rectangle {
id: root id: root
property string imagePath: "" property string imagePath: ""
property string fallbackIcon: ""
property color borderColor: Color.transparent property color borderColor: Color.transparent
property real borderWidth: 0 property real borderWidth: 0
property real imageRadius: width * 0.5 property real imageRadius: width * 0.5
property string fallbackIcon: ""
property real fallbackIconSize: Style.fontSizeXXL * scaling
property real scaledRadius: imageRadius * Settings.data.general.radiusRatio property real scaledRadius: imageRadius * Settings.data.general.radiusRatio
@ -56,7 +57,7 @@ Rectangle {
property real itemHeight: root.height property real itemHeight: root.height
property real cornerRadius: root.radius property real cornerRadius: root.radius
property real imageOpacity: root.opacity 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 // Qt6 specific properties - ensure proper blending
supportsAtlasTextures: false supportsAtlasTextures: false
@ -71,14 +72,16 @@ Rectangle {
} }
// Fallback icon // Fallback icon
NIcon { Loader {
active: fallbackIcon !== undefined && fallbackIcon !== "" && (imagePath === undefined || imagePath === "")
sourceComponent: NIcon {
anchors.centerIn: parent anchors.centerIn: parent
text: fallbackIcon icon: fallbackIcon
font.pointSize: Style.fontSizeXXL * scaling font.pointSize: fallbackIconSize
visible: fallbackIcon !== undefined && fallbackIcon !== "" && (imagePath === undefined || imagePath === "")
z: 0 z: 0
} }
} }
}
// Border // Border
Rectangle { Rectangle {

View file

@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs.Commons import qs.Commons
import qs.Widgets import qs.Widgets
import qs.Services
// Input and button row // Input and button row
RowLayout { RowLayout {
@ -13,7 +14,7 @@ RowLayout {
property string placeholderText: "" property string placeholderText: ""
property string text: "" property string text: ""
property string actionButtonText: "Test" property string actionButtonText: "Test"
property string actionButtonIcon: "play_arrow" property string actionButtonIcon: "play"
property bool actionButtonEnabled: text !== "" property bool actionButtonEnabled: text !== ""
// Signals // Signals

View file

@ -9,21 +9,15 @@ Item {
property string icon: "" property string icon: ""
property string text: "" property string text: ""
property string tooltipText: "" 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 real sizeRatio: 0.8
property bool autoHide: false property bool autoHide: false
property bool forceOpen: false property bool forceOpen: false
property bool disableOpen: false property bool disableOpen: false
property bool rightOpen: false property bool rightOpen: false
property bool hovered: false
// Effective shown state (true if hovered/animated open or forced) // 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 shown
signal hidden signal hidden
@ -50,14 +44,14 @@ Item {
Rectangle { Rectangle {
id: pill id: pill
width: effectiveShown ? maxPillWidth : 1 width: revealed ? maxPillWidth : 1
height: pillHeight height: pillHeight
x: rightOpen ? (iconCircle.x + iconCircle.width / 2) : // Opens right x: rightOpen ? (iconCircle.x + iconCircle.width / 2) : // Opens right
(iconCircle.x + iconCircle.width / 2) - width // Opens left (iconCircle.x + iconCircle.width / 2) - width // Opens left
opacity: effectiveShown ? Style.opacityFull : Style.opacityNone opacity: revealed ? Style.opacityFull : Style.opacityNone
color: pillColor color: Color.mSurfaceVariant
topLeftRadius: rightOpen ? 0 : pillHeight * 0.5 topLeftRadius: rightOpen ? 0 : pillHeight * 0.5
bottomLeftRadius: rightOpen ? 0 : pillHeight * 0.5 bottomLeftRadius: rightOpen ? 0 : pillHeight * 0.5
@ -77,8 +71,8 @@ Item {
text: root.text text: root.text
font.pointSize: Style.fontSizeXS * scaling font.pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
color: textColor color: Color.mPrimary
visible: effectiveShown visible: revealed
} }
Behavior on width { Behavior on width {
@ -102,11 +96,8 @@ Item {
width: iconSize width: iconSize
height: iconSize height: iconSize
radius: width * 0.5 radius: width * 0.5
// When forced shown, match pill background; otherwise use accent when hovered color: hovered && !forceOpen ? Color.mPrimary : Color.mSurfaceVariant
color: forceOpen ? pillColor : (showPill ? iconCircleColor : Color.mSurfaceVariant)
anchors.verticalCenter: parent.verticalCenter 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) x: rightOpen ? 0 : (parent.width - width)
@ -118,11 +109,9 @@ Item {
} }
NIcon { NIcon {
text: root.icon icon: root.icon
rotation: root.iconRotation
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeM * scaling
// When forced shown, use pill text color; otherwise accent color when hovered color: hovered && !forceOpen ? Color.mOnPrimary : Color.mOnSurface
color: forceOpen ? textColor : (showPill ? iconTextColor : Color.mOnSurface)
// Center horizontally // Center horizontally
x: (iconCircle.width - width) / 2 x: (iconCircle.width - width) / 2
// Center vertically accounting for font metrics // Center vertically accounting for font metrics
@ -220,6 +209,7 @@ Item {
hoverEnabled: true hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onEntered: { onEntered: {
hovered = true
root.entered() root.entered()
tooltip.show() tooltip.show()
if (disableOpen) { if (disableOpen) {
@ -230,6 +220,7 @@ Item {
} }
} }
onExited: { onExited: {
hovered = false
root.exited() root.exited()
if (!forceOpen) { if (!forceOpen) {
hide() hide()

View file

@ -95,7 +95,7 @@ RowLayout {
NIcon { NIcon {
anchors.centerIn: parent anchors.centerIn: parent
text: "remove" icon: "chevron-left"
font.pointSize: Style.fontSizeS * scaling font.pointSize: Style.fontSizeS * scaling
color: decreaseArea.containsMouse ? Color.mOnPrimary : Color.mPrimary color: decreaseArea.containsMouse ? Color.mOnPrimary : Color.mPrimary
} }
@ -130,7 +130,7 @@ RowLayout {
NIcon { NIcon {
anchors.centerIn: parent anchors.centerIn: parent
text: "add" icon: "chevron-right"
font.pointSize: Style.fontSizeS * scaling font.pointSize: Style.fontSizeS * scaling
color: increaseArea.containsMouse ? Color.mOnPrimary : Color.mPrimary color: increaseArea.containsMouse ? Color.mOnPrimary : Color.mPrimary
} }

View file

@ -112,23 +112,13 @@ Item {
RowLayout { RowLayout {
id: contentLayout id: contentLayout
anchors.fill: parent anchors.fill: parent
anchors.margins: Style.marginM * scaling anchors.margins: Style.marginL * scaling
spacing: Style.marginS * scaling spacing: Style.marginL * scaling
// Icon // Icon
NIcon { NIcon {
id: icon id: icon
text: { icon: (root.type == "warning") ? "exclamation-triangle" : "check-circle"
switch (root.type) {
case "warning":
return "warning"
case "notice":
return "info"
default:
return "info"
}
}
color: { color: {
switch (root.type) { switch (root.type) {
case "warning": case "warning":
@ -172,7 +162,7 @@ Item {
// Close button (only if persistent or manual dismiss needed) // Close button (only if persistent or manual dismiss needed)
NIconButton { NIconButton {
icon: "close" icon: "x-lg"
visible: root.persistent || root.duration === 0 visible: root.persistent || root.duration === 0
colorBg: Color.mSurfaceVariant colorBg: Color.mSurfaceVariant

View file

@ -1,16 +1,13 @@
{ {
description = "Noctalia shell - a Wayland desktop shell built with Quickshell"; description = "Noctalia shell - a Wayland desktop shell built with Quickshell";
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
systems.url = "github:nix-systems/default"; systems.url = "github:nix-systems/default";
quickshell = { quickshell = {
url = "git+https://git.outfoxxed.me/outfoxxed/quickshell"; url = "git+https://git.outfoxxed.me/outfoxxed/quickshell";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
}; };
outputs = { outputs = {
self, self,
nixpkgs, nixpkgs,
@ -24,7 +21,6 @@
system: system:
nixpkgs.legacyPackages.${system}.alejandra nixpkgs.legacyPackages.${system}.alejandra
); );
packages = eachSystem ( packages = eachSystem (
system: let system: let
pkgs = nixpkgs.legacyPackages.${system}; pkgs = nixpkgs.legacyPackages.${system};
@ -33,6 +29,32 @@
withI3 = false; withI3 = false;
}; };
# Custom ttf-bootstrap-icons package
ttf-bootstrap-icons = pkgs.stdenvNoCC.mkDerivation rec {
pname = "ttf-bootstrap-icons";
version = "1.13.1";
src = pkgs.fetchzip {
url = "https://github.com/twbs/icons/releases/download/v${version}/bootstrap-icons-${version}.zip";
sha256 = "999021e12fab5c9ede5e4e7072eb176122be798b2f99195acf5dda47aef8fc93";
stripRoot = false;
};
installPhase = ''
runHook preInstall
install -Dm644 fonts/bootstrap-icons.ttf $out/share/fonts/truetype/bootstrap-icons.ttf
runHook postInstall
'';
meta = with pkgs.lib; {
description = "Official open source SVG icon library for Bootstrap";
homepage = "https://icons.getbootstrap.com/";
license = licenses.mit;
platforms = platforms.all;
maintainers = [];
};
};
runtimeDeps = with pkgs; [ runtimeDeps = with pkgs; [
bash bash
bluez bluez
@ -49,12 +71,12 @@
networkmanager networkmanager
wl-clipboard wl-clipboard
]; ];
fontconfig = pkgs.makeFontsConf { fontconfig = pkgs.makeFontsConf {
fontDirectories = [ fontDirectories = [
pkgs.material-symbols pkgs.material-symbols
pkgs.roboto pkgs.roboto
pkgs.inter-nerdfont pkgs.inter-nerdfont
ttf-bootstrap-icons # Add the custom font package here
]; ];
}; };
in { in {
@ -62,21 +84,17 @@
pname = "noctalia-shell"; pname = "noctalia-shell";
version = self.rev or self.dirtyRev or "dirty"; version = self.rev or self.dirtyRev or "dirty";
src = ./.; src = ./.;
nativeBuildInputs = [pkgs.gcc pkgs.makeWrapper pkgs.qt6.wrapQtAppsHook]; nativeBuildInputs = [pkgs.gcc pkgs.makeWrapper pkgs.qt6.wrapQtAppsHook];
buildInputs = [qs pkgs.xkeyboard-config pkgs.qt6.qtbase]; buildInputs = [qs pkgs.xkeyboard-config pkgs.qt6.qtbase];
propagatedBuildInputs = runtimeDeps; propagatedBuildInputs = runtimeDeps;
installPhase = '' installPhase = ''
mkdir -p $out/share/noctalia-shell mkdir -p $out/share/noctalia-shell
cp -r ./* $out/share/noctalia-shell cp -r ./* $out/share/noctalia-shell
makeWrapper ${qs}/bin/qs $out/bin/noctalia-shell \ makeWrapper ${qs}/bin/qs $out/bin/noctalia-shell \
--prefix PATH : "${pkgs.lib.makeBinPath runtimeDeps}" \ --prefix PATH : "${pkgs.lib.makeBinPath runtimeDeps}" \
--set FONTCONFIG_FILE "${fontconfig}" \ --set FONTCONFIG_FILE "${fontconfig}" \
--add-flags "-p $out/share/noctalia-shell" --add-flags "-p $out/share/noctalia-shell"
''; '';
meta = { meta = {
description = "A sleek and minimal desktop shell thoughtfully crafted for Wayland, built with Quickshell."; description = "A sleek and minimal desktop shell thoughtfully crafted for Wayland, built with Quickshell.";
homepage = "https://github.com/noctalia-dev/noctalia-shell"; homepage = "https://github.com/noctalia-dev/noctalia-shell";
@ -84,9 +102,11 @@
mainProgram = "noctalia-shell"; mainProgram = "noctalia-shell";
}; };
}; };
# Expose the custom font as a separate package (optional)
ttf-bootstrap-icons = ttf-bootstrap-icons;
} }
); );
defaultPackage = eachSystem (system: self.packages.${system}.default); defaultPackage = eachSystem (system: self.packages.${system}.default);
}; };
} }