NValueSlider: new component + pimped NSlider with a small gradient and removed rounded corners due to issues.

This commit is contained in:
LemmyCook 2025-09-14 20:52:32 -04:00
parent 5ce5659b38
commit 5aa7ff7e91
14 changed files with 266 additions and 441 deletions

View file

@ -50,6 +50,7 @@ Item {
forceOpen: root.displayMode === "forceOpen"
forceClose: root.displayMode === "alwaysHide"
onClicked: {
// You could open keyboard settings here if needed
// For now, just show the current layout
}

View file

@ -2,9 +2,9 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Services.Pipewire
import qs.Widgets
import qs.Commons
import qs.Services
import qs.Widgets
ColumnLayout {
id: root
@ -34,7 +34,6 @@ ColumnLayout {
description: "System-wide volume level."
}
RowLayout {
// Pipewire seems a bit finicky, if we spam too many volume changes it breaks easily
// Probably because they have some quick fades in and out to avoid clipping
// We use a timer to space out the updates, to avoid lock up
@ -49,24 +48,17 @@ ColumnLayout {
}
}
NSlider {
NValueSlider {
Layout.fillWidth: true
from: 0
to: Settings.data.audio.volumeOverdrive ? 2.0 : 1.0
value: localVolume
stepSize: 0.01
text: Math.floor(AudioService.volume * 100) + "%"
onMoved: {
localVolume = value
}
}
NText {
text: Math.floor(AudioService.volume * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
}
}
// Mute Toggle
@ -96,24 +88,14 @@ ColumnLayout {
description: "Microphone input volume level."
}
RowLayout {
NSlider {
NValueSlider {
Layout.fillWidth: true
from: 0
to: 1.0
value: AudioService.inputVolume
stepSize: 0.01
onMoved: {
AudioService.setInputVolume(value)
}
}
NText {
text: Math.floor(AudioService.inputVolume * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
onMoved: value => AudioService.setInputVolume(value)
}
}
@ -144,9 +126,7 @@ ColumnLayout {
value: Settings.data.audio.volumeStep
stepSize: 1
suffix: "%"
onValueChanged: {
Settings.data.audio.volumeStep = value
}
onValueChanged: value => Settings.data.audio.volumeStep = value
}
}

View file

@ -82,23 +82,14 @@ ColumnLayout {
description: "Adjust the background opacity of the bar."
}
RowLayout {
NSlider {
NValueSlider {
Layout.fillWidth: true
from: 0
to: 1
stepSize: 0.01
value: Settings.data.bar.backgroundOpacity
onMoved: Settings.data.bar.backgroundOpacity = value
cutoutColor: Color.mSurface
}
NText {
onMoved: value => Settings.data.bar.backgroundOpacity = value
text: Math.floor(Settings.data.bar.backgroundOpacity * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
}
}
NToggle {
@ -133,25 +124,14 @@ ColumnLayout {
color: Color.mOnSurfaceVariant
}
RowLayout {
NSlider {
NValueSlider {
Layout.fillWidth: true
from: 0
to: 1
stepSize: 0.01
value: Settings.data.bar.marginVertical
onMoved: Settings.data.bar.marginVertical = value
cutoutColor: Color.mSurface
}
NText {
onMoved: value => Settings.data.bar.marginVertical = value
text: Math.round(Settings.data.bar.marginVertical * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginXS * scaling
Layout.preferredWidth: 50
horizontalAlignment: Text.AlignRight
color: Color.mOnSurface
}
}
}
@ -164,25 +144,14 @@ ColumnLayout {
color: Color.mOnSurfaceVariant
}
RowLayout {
NSlider {
NValueSlider {
Layout.fillWidth: true
from: 0
to: 1
stepSize: 0.01
value: Settings.data.bar.marginHorizontal
onMoved: Settings.data.bar.marginHorizontal = value
cutoutColor: Color.mSurface
}
NText {
onMoved: value => Settings.data.bar.marginHorizontal = value
text: Math.round(Settings.data.bar.marginHorizontal * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginXS * scaling
Layout.preferredWidth: 50
horizontalAlignment: Text.AlignRight
color: Color.mOnSurface
}
}
}
}

View file

@ -116,21 +116,15 @@ ColumnLayout {
Layout.preferredWidth: 80 * scaling
}
NSlider {
NValueSlider {
id: scaleSlider
from: 0.7
to: 1.8
stepSize: 0.01
value: localScaling
onPressedChanged: ScalingService.setScreenScale(modelData, value)
Layout.fillWidth: true
Layout.minimumWidth: 200 * scaling
}
NText {
onPressedChanged: (pressed, value) => ScalingService.setScreenScale(modelData, value)
text: `${Math.round(localScaling * 100)}%`
Layout.preferredWidth: 50 * scaling
horizontalAlignment: Text.AlignRight
Layout.fillWidth: true
}
// Reset button container
@ -165,24 +159,14 @@ ColumnLayout {
Layout.preferredWidth: 80 * scaling
}
NSlider {
NValueSlider {
Layout.fillWidth: true
Layout.minimumWidth: 200 * scaling
from: 0
to: 1
value: brightnessMonitor ? brightnessMonitor.brightness : 0.5
stepSize: 0.05
onPressedChanged: {
if (!pressed && brightnessMonitor) {
brightnessMonitor.setBrightness(value)
}
}
}
NText {
stepSize: 0.01
onPressedChanged: (pressed, value) => brightnessMonitor.setBrightness(value)
text: brightnessMonitor ? Math.round(brightnessMonitor.brightness * 100) + "%" : "N/A"
Layout.preferredWidth: 50 * scaling
horizontalAlignment: Text.AlignRight
}
// Empty container to match scale row layout

View file

@ -49,24 +49,14 @@ ColumnLayout {
label: "Background Opacity"
description: "Adjust the background opacity."
}
RowLayout {
NSlider {
NValueSlider {
Layout.fillWidth: true
from: 0
to: 1
stepSize: 0.01
value: Settings.data.dock.backgroundOpacity
onMoved: Settings.data.dock.backgroundOpacity = value
cutoutColor: Color.mSurface
}
NText {
onMoved: value => Settings.data.dock.backgroundOpacity = value
text: Math.floor(Settings.data.dock.backgroundOpacity * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
}
}
@ -79,23 +69,14 @@ ColumnLayout {
description: "Adjust the floating distance from the screen edge."
}
RowLayout {
NSlider {
NValueSlider {
Layout.fillWidth: true
from: 0
to: 4
stepSize: 0.01
value: Settings.data.dock.floatingRatio
onMoved: Settings.data.dock.floatingRatio = value
cutoutColor: Color.mSurface
}
NText {
onMoved: value => Settings.data.dock.floatingRatio = value
text: Math.floor(Settings.data.dock.floatingRatio * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
}
}

View file

@ -74,23 +74,14 @@ ColumnLayout {
description: "Adjust the rounded border of all UI elements."
}
RowLayout {
NSlider {
NValueSlider {
Layout.fillWidth: true
from: 0
to: 1
stepSize: 0.01
value: Settings.data.general.radiusRatio
onMoved: Settings.data.general.radiusRatio = value
cutoutColor: Color.mSurface
}
NText {
onMoved: value => Settings.data.general.radiusRatio = value
text: Math.floor(Settings.data.general.radiusRatio * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
}
}
@ -104,23 +95,14 @@ ColumnLayout {
description: "Adjust global animation speed."
}
RowLayout {
NSlider {
NValueSlider {
Layout.fillWidth: true
from: 0.1
to: 2.0
stepSize: 0.01
value: Settings.data.general.animationSpeed
onMoved: Settings.data.general.animationSpeed = value
cutoutColor: Color.mSurface
}
NText {
onMoved: value => Settings.data.general.animationSpeed = value
text: Math.round(Settings.data.general.animationSpeed * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
}
}
}
@ -156,23 +138,14 @@ ColumnLayout {
description: "Adjust the rounded corners of the screen."
}
RowLayout {
NSlider {
NValueSlider {
Layout.fillWidth: true
from: 0
to: 2
stepSize: 0.01
value: Settings.data.general.screenRadiusRatio
onMoved: Settings.data.general.screenRadiusRatio = value
cutoutColor: Color.mSurface
}
NText {
onMoved: value => Settings.data.general.screenRadiusRatio = value
text: Math.floor(Settings.data.general.screenRadiusRatio * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
}
}
}

View file

@ -74,24 +74,15 @@ ColumnLayout {
Layout.fillWidth: true
}
RowLayout {
NSlider {
NValueSlider {
id: launcherBgOpacity
Layout.fillWidth: true
from: 0.0
to: 1.0
stepSize: 0.01
value: Settings.data.appLauncher.backgroundOpacity
onMoved: Settings.data.appLauncher.backgroundOpacity = value
cutoutColor: Color.mSurface
}
NText {
onMoved: value => Settings.data.appLauncher.backgroundOpacity = value
text: Math.floor(Settings.data.appLauncher.backgroundOpacity * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
}
}

View file

@ -100,23 +100,14 @@ ColumnLayout {
description: "How long low priority notifications stay visible."
}
RowLayout {
NSlider {
NValueSlider {
Layout.fillWidth: true
from: 1
to: 30
stepSize: 1
value: Settings.data.notifications.lowUrgencyDuration
onMoved: Settings.data.notifications.lowUrgencyDuration = value
cutoutColor: Color.mSurface
}
NText {
onMoved: value => Settings.data.notifications.lowUrgencyDuration = value
text: Settings.data.notifications.lowUrgencyDuration + "s"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
}
}
@ -130,23 +121,14 @@ ColumnLayout {
description: "How long normal priority notifications stay visible."
}
RowLayout {
NSlider {
NValueSlider {
Layout.fillWidth: true
from: 1
to: 30
stepSize: 1
value: Settings.data.notifications.normalUrgencyDuration
onMoved: Settings.data.notifications.normalUrgencyDuration = value
cutoutColor: Color.mSurface
}
NText {
onMoved: value => Settings.data.notifications.normalUrgencyDuration = value
text: Settings.data.notifications.normalUrgencyDuration + "s"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
}
}
@ -160,23 +142,14 @@ ColumnLayout {
description: "How long critical priority notifications stay visible."
}
RowLayout {
NSlider {
NValueSlider {
Layout.fillWidth: true
from: 1
to: 30
stepSize: 1
value: Settings.data.notifications.criticalUrgencyDuration
onMoved: Settings.data.notifications.criticalUrgencyDuration = value
cutoutColor: Color.mSurface
}
NText {
onMoved: value => Settings.data.notifications.criticalUrgencyDuration = value
text: Settings.data.notifications.criticalUrgencyDuration + "s"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
}
}
}

View file

@ -138,21 +138,14 @@ ColumnLayout {
description: "Duration of transition animations in seconds."
}
RowLayout {
spacing: Style.marginL * scaling
NSlider {
NValueSlider {
Layout.fillWidth: true
from: 500
to: 10000
stepSize: 100
value: Settings.data.wallpaper.transitionDuration
onMoved: Settings.data.wallpaper.transitionDuration = value
cutoutColor: Color.mSurface
}
NText {
text: (Settings.data.wallpaper.transitionDuration / 1000).toFixed(2) + "s"
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
}
onMoved: value => Settings.data.wallpaper.transitionDuration = value
text: (Settings.data.wallpaper.transitionDuration / 1000).toFixed(1) + "s"
}
}
@ -163,20 +156,13 @@ ColumnLayout {
description: "Duration of transition animations in seconds."
}
RowLayout {
spacing: Style.marginL * scaling
NSlider {
NValueSlider {
Layout.fillWidth: true
from: 0.0
to: 1.0
value: Settings.data.wallpaper.transitionEdgeSmoothness
onMoved: Settings.data.wallpaper.transitionEdgeSmoothness = value
cutoutColor: Color.mSurface
}
NText {
onMoved: value => Settings.data.wallpaper.transitionEdgeSmoothness = value
text: Math.round(Settings.data.wallpaper.transitionEdgeSmoothness * 100) + "%"
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
}
}
}
}

View file

@ -258,7 +258,6 @@ NBox {
stepSize: 0
snapAlways: false
enabled: MediaService.trackLength > 0 && MediaService.canSeek
cutoutColor: Color.mSurface
heightRatio: 0.65
onMoved: {

View file

@ -243,24 +243,19 @@ Popup {
Layout.preferredWidth: 20 * scaling
}
NSlider {
NValueSlider {
id: redSlider
Layout.fillWidth: true
from: 0
to: 255
value: Math.round(root.selectedColor.r * 255)
onMoved: {
onMoved: value => {
root.selectedColor = Qt.rgba(value / 255, root.selectedColor.g, root.selectedColor.b, 1)
var hsv = root.rgbToHsv(root.selectedColor.r * 255, root.selectedColor.g * 255, root.selectedColor.b * 255)
root.currentHue = hsv[0]
root.currentSaturation = hsv[1]
}
}
NText {
text: Math.round(redSlider.value)
font.family: Settings.data.ui.fontFixed
Layout.preferredWidth: 30 * scaling
text: Math.round(value)
}
}
@ -274,25 +269,20 @@ Popup {
Layout.preferredWidth: 20 * scaling
}
NSlider {
NValueSlider {
id: greenSlider
Layout.fillWidth: true
from: 0
to: 255
value: Math.round(root.selectedColor.g * 255)
onMoved: {
onMoved: value => {
root.selectedColor = Qt.rgba(root.selectedColor.r, value / 255, root.selectedColor.b, 1)
// Update stored hue and saturation when RGB changes
var hsv = root.rgbToHsv(root.selectedColor.r * 255, root.selectedColor.g * 255, root.selectedColor.b * 255)
root.currentHue = hsv[0]
root.currentSaturation = hsv[1]
}
}
NText {
text: Math.round(greenSlider.value)
font.family: Settings.data.ui.fontFixed
Layout.preferredWidth: 30 * scaling
text: Math.round(value)
}
}
@ -306,25 +296,20 @@ Popup {
Layout.preferredWidth: 20 * scaling
}
NSlider {
NValueSlider {
id: blueSlider
Layout.fillWidth: true
from: 0
to: 255
value: Math.round(root.selectedColor.b * 255)
onMoved: {
onMoved: value => {
root.selectedColor = Qt.rgba(root.selectedColor.r, root.selectedColor.g, value / 255, 1)
// Update stored hue and saturation when RGB changes
var hsv = root.rgbToHsv(root.selectedColor.r * 255, root.selectedColor.g * 255, root.selectedColor.b * 255)
root.currentHue = hsv[0]
root.currentSaturation = hsv[1]
}
}
NText {
text: Math.round(blueSlider.value)
font.family: Settings.data.ui.fontFixed
Layout.preferredWidth: 30 * scaling
text: Math.round(value)
}
}
@ -338,7 +323,7 @@ Popup {
Layout.preferredWidth: 80 * scaling
}
NSlider {
NValueSlider {
id: brightnessSlider
Layout.fillWidth: true
from: 0
@ -347,7 +332,7 @@ Popup {
var hsv = root.rgbToHsv(root.selectedColor.r * 255, root.selectedColor.g * 255, root.selectedColor.b * 255)
return hsv[2]
}
onMoved: {
onMoved: value => {
var hue = root.currentHue
var saturation = root.currentSaturation
@ -362,12 +347,7 @@ Popup {
var rgb = root.hsvToRgb(hue, saturation, value)
root.selectedColor = Qt.rgba(rgb[0] / 255, rgb[1] / 255, rgb[2] / 255, 1)
}
}
NText {
text: Math.round(brightnessSlider.value) + "%"
font.family: Settings.data.ui.fontFixed
Layout.preferredWidth: 40 * scaling
}
}
}

View file

@ -7,13 +7,12 @@ import qs.Services
Slider {
id: root
// Optional color to cut the track beneath the knob (should match surrounding background)
property var cutoutColor
property var cutoutColor: Color.mSurface
property bool snapAlways: true
property real heightRatio: 0.75
readonly property real knobDiameter: Math.round(Style.baseWidgetSize * heightRatio * scaling)
readonly property real trackHeight: knobDiameter * 0.5
readonly property real trackHeight: knobDiameter * 0.4
readonly property real cutoutExtra: Math.round(Style.baseWidgetSize * 0.1 * scaling)
snapMode: snapAlways ? Slider.SnapAlways : Slider.SnapOnRelease
@ -26,15 +25,38 @@ Slider {
implicitHeight: trackHeight
width: root.availableWidth
height: implicitHeight
radius: height / 2
radius: 0
color: Color.mSurface
// Animated gradient active track
Rectangle {
id: activeTrack
width: root.visualPosition * parent.width
height: parent.height
color: Color.mPrimary
radius: parent.radius
// Animated gradient fill
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop {
position: 0.0
color: Qt.darker(Color.mPrimary, 1.2)
Behavior on color { ColorAnimation { duration: 300 } }
}
GradientStop {
position: 0.5
color: Color.mPrimary
SequentialAnimation on position {
loops: Animation.Infinite
NumberAnimation { from: 0.3; to: 0.7; duration: 2000; easing.type: Easing.InOutSine }
NumberAnimation { from: 0.7; to: 0.3; duration: 2000; easing.type: Easing.InOutSine }
}
}
GradientStop {
position: 1.0
color: Qt.lighter(Color.mPrimary, 1.2)
}
}
}
// Circular cutout
@ -44,8 +66,7 @@ Slider {
height: knobDiameter + cutoutExtra
radius: width / 2
color: root.cutoutColor !== undefined ? root.cutoutColor : Color.mSurface
x: Math.max(0, Math.min(parent.width - width, Math.round(root.visualPosition * (parent.width - root.knobDiameter) - cutoutExtra / 2)))
y: (parent.height - height) / 2
x: root.leftPadding + root.visualPosition * (root.availableWidth - root.knobDiameter) - cutoutExtra / 2
anchors.verticalCenter: parent.verticalCenter
}
}

View file

@ -1,61 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Services
import qs.Widgets
RowLayout {
id: root
// Properties that mirror NSlider
property real from: 0
property real to: 1
property real value: 0
property real stepSize: 0.01
property var cutoutColor
property bool snapAlways: true
property real heightRatio: 0.75
property bool showPercentage: true
property string suffix: "%"
property int decimalPlaces: 0 // 0 for integers, 1 for one decimal place, etc.
// Signals
signal moved(real value)
signal pressedChanged(bool pressed)
spacing: Style.marginS * scaling
NSlider {
id: slider
Layout.fillWidth: true
from: root.from
to: root.to
value: root.value
stepSize: root.stepSize
cutoutColor: root.cutoutColor
snapAlways: root.snapAlways
heightRatio: root.heightRatio
stableWidth: true
minWidth: 200 * scaling
onMoved: root.moved(value)
onPressedChanged: root.pressedChanged(pressed)
}
NText {
id: percentageLabel
visible: root.showPercentage
text: {
if (root.decimalPlaces === 0) {
return Math.round(slider.value * 100) + root.suffix
} else {
return (slider.value * 100).toFixed(root.decimalPlaces) + root.suffix
}
}
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
Layout.preferredWidth: 50 * scaling
horizontalAlignment: Text.AlignRight
}
}

48
Widgets/NValueSlider.qml Normal file
View file

@ -0,0 +1,48 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Services
import qs.Widgets
RowLayout {
id: root
property real from: 0
property real to: 1
property real value: 0
property real stepSize: 0.01
property var cutoutColor: Color.mSurface
property bool snapAlways: true
property real heightRatio: 0.75
property string text: ""
// Signals
signal moved(real value)
signal pressedChanged(bool pressed, real value)
spacing: Style.marginL * scaling
NSlider {
id: slider
Layout.fillWidth: true
from: root.from
to: root.to
value: root.value
stepSize: root.stepSize
cutoutColor: root.cutoutColor
snapAlways: root.snapAlways
heightRatio: root.heightRatio
onMoved: root.moved(value)
onPressedChanged: root.pressedChanged(pressed, value)
}
NText {
visible: root.text !== ""
text: root.text
font.family: Settings.data.ui.fontFixed
Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: 40 * scaling
horizontalAlignment: Text.AlignRight
}
}