Merge tag 'v2.8.0'

Release v2.8.0

We've been busy squashing bugs and adding some nice improvements based on your feedback.
What's New
New Icon Set - Swapped out Material Symbols for Tabler icons. They look great and load faster since they're built right in.
Works on Any Linux Distro - Dropped the Arch-specific update checker so this works properly on whatever distro you're running. You can build your own update notifications with Custom Buttons if you want.
Icon Picker - Added a proper icon picker for custom button widgets. No more guessing icon names.
Smarter Audio Visualizer - The Cava visualizer actually pays attention now - it only kicks in when you're playing music or videos instead of running all the time.
Better Notifications - Notifications now show actual app names like "Firefox" instead of cryptic IDs like "org.mozilla.firefox".
Less Noise - Turned a bunch of those persistent notification popups into toast notifications so they don't stick around cluttering your screen.
Fixes

Active Window widget finally shows the right app icon and title consistently
Fixed a nasty crash on Hyprland
Screen recorder button disables itself if the recording software isn't installed
Added a force-enable option for Night Light so you can turn it on manually whenever
This commit is contained in:
Never Gude 2025-09-11 19:10:35 +02:00
commit 9792f401f7
102 changed files with 7930 additions and 1626 deletions

View file

@ -82,9 +82,9 @@ Rectangle {
// Icon (optional)
NIcon {
Layout.alignment: Qt.AlignVCenter
layoutTopMargin: 1 * scaling
visible: root.icon !== ""
text: root.icon
icon: root.icon
font.pointSize: root.iconSize
color: {
if (!root.enabled)

View file

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

View file

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

View file

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

View file

@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Services
import qs.Widgets
Popup {
@ -129,7 +130,7 @@ Popup {
spacing: Style.marginS * scaling
NIcon {
text: "palette"
icon: "color-picker"
font.pointSize: Style.fontSizeXXL * scaling
color: Color.mPrimary
}
@ -491,7 +492,6 @@ Popup {
NButton {
id: cancelButton
text: "Cancel"
icon: "close"
outlined: cancelButton.hovered ? false : true
customHeight: 36 * scaling
customWidth: 100 * scaling

View file

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

View file

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

View file

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

View file

@ -9,9 +9,10 @@ Rectangle {
id: root
property string imagePath: ""
property string fallbackIcon: ""
property color borderColor: Color.transparent
property real borderWidth: 0
property string fallbackIcon: ""
property real fallbackIconSize: Style.fontSizeXXL * scaling
color: Color.transparent
radius: parent.width * 0.5
@ -45,18 +46,20 @@ Rectangle {
}
property real imageOpacity: root.opacity
fragmentShader: Qt.resolvedUrl("../Shaders/qsb/circled_image.frag.qsb")
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/circled_image.frag.qsb")
supportsAtlasTextures: false
blending: true
}
// Fallback icon
NIcon {
anchors.centerIn: parent
text: fallbackIcon
font.pointSize: Style.fontSizeXXL * scaling
visible: fallbackIcon !== undefined && fallbackIcon !== "" && (imagePath === undefined || imagePath === "")
z: 0
Loader {
active: fallbackIcon !== undefined && fallbackIcon !== "" && (imagePath === undefined || imagePath === "")
sourceComponent: NIcon {
anchors.centerIn: parent
icon: fallbackIcon
font.pointSize: fallbackIconSize
z: 0
}
}
}

View file

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

View file

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

View file

@ -40,8 +40,8 @@ Loader {
property int buttonWidth: 0
property int buttonHeight: 0
// Whether this panel should accept keyboard focus
property bool panelKeyboardFocus: false
property bool backgroundClickEnabled: true
// Animation properties
readonly property real originalScale: 0.7
@ -62,6 +62,24 @@ Loader {
PanelService.registerPanel(root)
}
// -----------------------------------------
// Functions to control background click behavior
function disableBackgroundClick() {
backgroundClickEnabled = false
}
function enableBackgroundClick() {
// Add a small delay to prevent immediate close after drag release
enableBackgroundClickTimer.restart()
}
Timer {
id: enableBackgroundClickTimer
interval: 100
repeat: false
onTriggered: backgroundClickEnabled = true
}
// -----------------------------------------
function toggle(aScreen, buttonItem) {
// Don't toggle if screen is null or invalid
@ -110,6 +128,7 @@ Loader {
PanelService.willOpenPanel(root)
backgroundClickEnabled = true
active = true
root.opened()
}
@ -125,7 +144,8 @@ Loader {
function closeCompleted() {
root.closed()
active = false
useButtonPosition = false // Reset button position usage
useButtonPosition = false
backgroundClickEnabled = true
PanelService.closedPanel(root)
}
@ -179,6 +199,7 @@ Loader {
// Clicking outside of the rectangle to close
MouseArea {
anchors.fill: parent
enabled: root.backgroundClickEnabled
onClicked: root.close()
}
@ -208,7 +229,7 @@ Loader {
return Math.round(Math.max(minX, Math.min(targetX, maxX)))
} else if (!panelAnchorHorizontalCenter && panelAnchorLeft) {
return Math.round(marginS * scaling)
return Math.round(Style.marginS * scaling)
} else if (!panelAnchorHorizontalCenter && panelAnchorRight) {
return Math.round(panelWindow.width - panelWidth - (Style.marginS * scaling))
} else {

View file

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

View file

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

View file

@ -112,23 +112,13 @@ Item {
RowLayout {
id: contentLayout
anchors.fill: parent
anchors.margins: Style.marginM * scaling
spacing: Style.marginS * scaling
anchors.margins: Style.marginL * scaling
spacing: Style.marginL * scaling
// Icon
NIcon {
id: icon
text: {
switch (root.type) {
case "warning":
return "warning"
case "notice":
return "info"
default:
return "info"
}
}
icon: (root.type == "warning") ? "toast-warning" : "toast-notice"
color: {
switch (root.type) {
case "warning":