QuickSettings: customization!?

This commit is contained in:
lysec
2025-10-09 13:56:35 +02:00
parent a90bca23aa
commit f77efc409b
15 changed files with 507 additions and 33 deletions
+22
View File
@@ -95,6 +95,24 @@
"label": "Position",
"description": "Choose where the Control Center panel appears when opened."
}
},
"controlCenter": {
"quickSettingsStyle": {
"section": {
"label": "Quick Settings Style",
"description": "Choose the visual style for quick settings buttons."
},
"style": {
"label": "Display Style",
"description": "Select between modern card-style buttons or classic icon buttons."
}
},
"widgets": {
"section": {
"label": "Widgets",
"description": "Manage and configure Control Center widgets."
}
}
}
},
"audio": {
@@ -1241,6 +1259,10 @@
"bottom_right": "Bottom right",
"bottom_center": "Bottom center",
"top_center": "Top center"
},
"quickSettingsStyle": {
"modern": "Modern Cards",
"classic": "Classic Icons"
}
},
"osd": {
+30 -1
View File
@@ -112,7 +112,36 @@
"terminalCommand": "xterm -e"
},
"controlCenter": {
"position": "close_to_bar_button"
"position": "close_to_bar_button",
"quickSettingsStyle": "modern",
"widgets": {
"quickSettings": [
{
"id": "WiFi"
},
{
"id": "Bluetooth"
},
{
"id": "DoNotDisturb"
},
{
"id": "NightLight"
},
{
"id": "KeepAwake"
},
{
"id": "PowerProfile"
},
{
"id": "ScreenRecorder"
},
{
"id": "WallpaperSelector"
}
]
}
},
"dock": {
"displayMode": "always_visible",
+1
View File
@@ -246,6 +246,7 @@ Singleton {
property JsonObject controlCenter: JsonObject {
// Position: close_to_bar_button, center, top_left, top_right, bottom_left, bottom_right, bottom_center, top_center
property string position: "close_to_bar_button"
property string quickSettingsStyle: "modern" // "modern" or "classic"
property JsonObject widgets
widgets: JsonObject {
property list<var> quickSettings: [{
+2 -2
View File
@@ -106,8 +106,8 @@ NBox {
id: grid
Layout.fillWidth: true
columns: 3
columnSpacing: Style.marginL * scaling
rowSpacing: Style.marginM * scaling
columnSpacing: Style.marginM * scaling
rowSpacing: Style.marginS * scaling
Repeater {
model: Settings.data.controlCenter.widgets.quickSettings
+6 -3
View File
@@ -4,14 +4,17 @@ import qs.Commons
import qs.Services
import qs.Widgets
NButton {
NQuickSetting {
property ShellScreen screen
property real scaling: 1.0
outlined: true
text: "Bluetooth"
fontSize: Style.fontSizeS * scaling
fontWeight: Style.fontWeightRegular
fontWeight: Style.fontWeightMedium
icon: BluetoothService.enabled ? "bluetooth" : "bluetooth-off"
active: BluetoothService.enabled
tooltipText: BluetoothService.enabled ? "Bluetooth enabled" : "Bluetooth disabled"
style: Settings.data.controlCenter.quickSettingsStyle || "modern"
onClicked: PanelService.getPanel("bluetoothPanel")?.toggle(this)
}
@@ -4,14 +4,17 @@ import qs.Commons
import qs.Services
import qs.Widgets
NButton {
NQuickSetting {
property ShellScreen screen
property real scaling: 1.0
outlined: true
text: "Do not Disturb"
fontSize: Style.fontSizeS * scaling
fontWeight: Style.fontWeightRegular
icon: Settings.data.notifications.doNotDisturb ? "bell-off" : "bell"
fontWeight: Style.fontWeightMedium
icon: Settings.data.notifications.doNotDisturb ? "bell-off" : "bell"
active: Settings.data.notifications.doNotDisturb
tooltipText: Settings.data.notifications.doNotDisturb ? "Turn off Do Not Disturb" : "Turn on Do Not Disturb"
style: Settings.data.controlCenter.quickSettingsStyle || "modern"
onClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb
}
+7 -4
View File
@@ -4,14 +4,17 @@ import qs.Commons
import qs.Services
import qs.Widgets
NButton {
NQuickSetting {
property ShellScreen screen
property real scaling: 1.0
outlined: true
text: IdleInhibitorService.isInhibited ? "Keep-awake" : "Keep-awake"
text: "Keep-awake"
fontSize: Style.fontSizeS * scaling
fontWeight: Style.fontWeightRegular
fontWeight: Style.fontWeightMedium
icon: IdleInhibitorService.isInhibited ? "keep-awake-on" : "keep-awake-off"
active: IdleInhibitorService.isInhibited
tooltipText: IdleInhibitorService.isInhibited ? "Disable keep-awake" : "Enable keep-awake"
style: Settings.data.controlCenter.quickSettingsStyle || "modern"
onClicked: IdleInhibitorService.manualToggle()
}
+15 -3
View File
@@ -4,16 +4,27 @@ import qs.Commons
import qs.Services
import qs.Widgets
NButton {
NQuickSetting {
property ShellScreen screen
property real scaling: 1.0
outlined: true
enabled: ProgramCheckerService.wlsunsetAvailable
text: "Night Light"
fontSize: Style.fontSizeS * scaling
fontWeight: Style.fontWeightRegular
fontWeight: Style.fontWeightMedium
icon: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? "nightlight-forced" : "nightlight-on") : "nightlight-off"
active: Settings.data.nightLight.enabled
style: Settings.data.controlCenter.quickSettingsStyle || "modern"
tooltipText: {
if (!Settings.data.nightLight.enabled) {
return "Turn on Night Light"
} else if (Settings.data.nightLight.forced) {
return "Night Light forced on"
} else {
return "Turn off Night Light"
}
}
onClicked: {
if (!Settings.data.nightLight.enabled) {
Settings.data.nightLight.enabled = true
@@ -25,6 +36,7 @@ NButton {
Settings.data.nightLight.forced = false
}
}
onRightClicked: {
var settingsPanel = PanelService.getPanel("settingsPanel")
settingsPanel.requestedTab = SettingsPanel.Tab.Display
@@ -6,17 +6,20 @@ import qs.Services
import qs.Widgets
// Performance
NButton {
NQuickSetting {
property ShellScreen screen
property real scaling: 1.0
readonly property bool hasPP: PowerProfileService.available
enabled: hasPP
outlined: true
text: PowerProfileService.getName()
fontSize: Style.fontSizeS * scaling
fontWeight: Style.fontWeightRegular
fontWeight: Style.fontWeightMedium
icon: PowerProfileService.getIcon()
active: hasPP
tooltipText: hasPP ? "Current: " + PowerProfileService.getName() : "Power profiles not available"
style: Settings.data.controlCenter.quickSettingsStyle || "modern"
onClicked: {
PowerProfileService.cycleProfile()
}
@@ -4,17 +4,19 @@ import qs.Commons
import qs.Services
import qs.Widgets
NButton {
NQuickSetting {
property ShellScreen screen
property real scaling: 1.0
enabled: ProgramCheckerService.gpuScreenRecorderAvailable
outlined: true
icon: "camera-video"
text: "Screen Rec."
fontSize: Style.fontSizeS * scaling
fontWeight: Style.fontWeightRegular
fontWeight: Style.fontWeightMedium
active: ScreenRecorderService.isRecording
tooltipText: ScreenRecorderService.isRecording ? "Stop recording" : "Start screen recording"
style: Settings.data.controlCenter.quickSettingsStyle || "modern"
onClicked: {
ScreenRecorderService.toggleRecording()
if (!ScreenRecorderService.isRecording) {
@@ -4,17 +4,19 @@ import qs.Commons
import qs.Services
import qs.Widgets
NButton {
NQuickSetting {
property ShellScreen screen
property real scaling: 1.0
enabled: Settings.data.wallpaper.enabled
outlined: true
icon: "wallpaper-selector"
text: "Wallpaper"
fontSize: Style.fontSizeS * scaling
fontWeight: Style.fontWeightRegular
fontWeight: Style.fontWeightMedium
active: Settings.data.wallpaper.enabled
tooltipText: "Open wallpaper selector"
style: Settings.data.controlCenter.quickSettingsStyle || "modern"
onClicked: PanelService.getPanel("wallpaperPanel")?.toggle(this)
onRightClicked: WallpaperService.setRandomWallpaper()
}
+36 -4
View File
@@ -4,12 +4,10 @@ import qs.Commons
import qs.Services
import qs.Widgets
NButton {
NQuickSetting {
property ShellScreen screen
property real scaling: 1.0
outlined: true
icon: {
try {
if (NetworkService.ethernetConnected) {
@@ -30,13 +28,47 @@ NButton {
return "signal_wifi_bad"
}
}
text: {
if (NetworkService.ethernetConnected) {
return "Network"
}
return "Wi-Fi"
}
fontSize: Style.fontSizeS * scaling
fontWeight: Style.fontWeightRegular
fontWeight: Style.fontWeightMedium
style: Settings.data.controlCenter.quickSettingsStyle || "modern"
active: {
if (NetworkService.ethernetConnected) {
return true
}
try {
for (const net in NetworkService.networks) {
if (NetworkService.networks[net].connected) {
return true
}
}
return false
} catch (error) {
return false
}
}
tooltipText: {
if (NetworkService.ethernetConnected) {
return "Ethernet connected"
}
let connected = false
for (const net in NetworkService.networks) {
if (NetworkService.networks[net].connected) {
connected = true
break
}
}
return connected ? "Wi-Fi connected" : "Wi-Fi disconnected"
}
onClicked: PanelService.getPanel("wifiPanel")?.toggle(this)
}
@@ -27,6 +27,41 @@ ColumnLayout {
}
}
// Quick Settings Style Section
ColumnLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true
NHeader {
label: I18n.tr("settings.controlCenter.quickSettingsStyle.section.label")
description: I18n.tr("settings.controlCenter.quickSettingsStyle.section.description")
}
NComboBox {
id: quickSettingsStyle
label: I18n.tr("settings.controlCenter.quickSettingsStyle.style.label")
description: I18n.tr("settings.controlCenter.quickSettingsStyle.style.description")
Layout.fillWidth: true
model: [{
"key": "modern",
"name": I18n.tr("options.controlCenter.quickSettingsStyle.modern")
}, {
"key": "classic",
"name": I18n.tr("options.controlCenter.quickSettingsStyle.classic")
}]
currentKey: Settings.data.controlCenter.quickSettingsStyle || "modern"
onSelected: function (key) {
Settings.data.controlCenter.quickSettingsStyle = key
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// Widgets Management Section
ColumnLayout {
spacing: Style.marginXXS * scaling
+1 -1
View File
@@ -19,7 +19,7 @@ Rectangle {
property int fontWeight: Style.fontWeightBold
property real iconSize: Style.fontSizeL * scaling
property bool outlined: false
property int horizontalAlignment: Qt.AlignHCenter
property int horizontalAlignment: Qt.AlignHCenter
// Signals
signal clicked
+327
View File
@@ -0,0 +1,327 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Effects
import qs.Commons
import qs.Services
Rectangle {
id: root
// Public properties
property string text: ""
property string icon: ""
property string tooltipText: ""
property bool enabled: true
property bool active: false
property bool compact: false
property string style: "modern" // "modern" or "classic"
// Styling properties
property real fontSize: Style.fontSizeS * scaling
property int fontWeight: Style.fontWeightMedium
property real iconSize: Style.fontSizeL * scaling
property real cornerRadius: Style.radiusM * scaling
// Colors - Style-dependent colors
property color backgroundColor: style === "classic" ? Color.mSurfaceVariant : Color.mSurface
property color textColor: Color.mOnSurface
property color iconColor: style === "classic" ? Color.mPrimary : (active ? Color.mPrimary : Color.mOnSurface)
property color borderColor: Color.mOutline
property color hoverColor: style === "classic" ? Color.mTertiary : Color.mPrimary
property color pressedColor: style === "classic" ? Color.mTertiary : Qt.darker(Color.mPrimary, 1.1)
property color hoverTextColor: Color.mOnPrimary
property color hoverIconColor: style === "classic" ? Color.mOnTertiary : Color.mOnPrimary
// Signals
signal clicked
signal rightClicked
signal middleClicked
// Internal properties
property bool hovered: false
property bool pressed: false
property real scaling: 1.0
// Dimensions - Style-dependent sizing
implicitWidth: {
if (style === "classic") {
return Style.baseWidgetSize * scaling
}
return compact ? Math.max(100 * scaling, contentRow.implicitWidth + (Style.marginL * scaling)) : Math.max(120 * scaling, contentRow.implicitWidth + (Style.marginL * scaling))
}
implicitHeight: {
if (style === "classic") {
return Style.baseWidgetSize * scaling
}
return compact ? Math.max(48 * scaling, contentRow.implicitHeight + (Style.marginM * scaling)) : Math.max(56 * scaling, contentRow.implicitHeight + (Style.marginL * scaling))
}
// Appearance - Style-dependent styling
radius: style === "classic" ? width * 0.5 : cornerRadius
color: {
if (!enabled)
return Qt.lighter(Color.mSurface, 1.1)
if (pressed)
return pressedColor
if (hovered)
return hoverColor
return backgroundColor
}
border.width: style === "classic" ? Math.max(1, Style.borderS * scaling) : 0
border.color: style === "classic" ? borderColor : "transparent"
opacity: enabled ? (style === "classic" ? Style.opacityFull : 1.0) : (style === "classic" ? Style.opacityMedium : 0.6)
// Smooth animations
Behavior on color {
ColorAnimation {
duration: style === "classic" ? Style.animationNormal : Style.animationFast
easing.type: style === "classic" ? Easing.InOutQuad : Easing.OutCubic
}
}
Behavior on border.color {
ColorAnimation {
duration: style === "classic" ? Style.animationNormal : Style.animationFast
easing.type: style === "classic" ? Easing.InOutQuad : Easing.OutCubic
}
}
Behavior on scale {
NumberAnimation {
duration: Style.animationFast
easing.type: Easing.OutCubic
}
}
// Hover scale effect
scale: hovered ? 1.02 : 1.0
// Subtle shadow/elevation effect
Rectangle {
anchors.fill: parent
radius: parent.radius
color: Qt.rgba(0, 0, 0, 0.1)
visible: active
z: -1
Behavior on color {
ColorAnimation {
duration: Style.animationFast
easing.type: Easing.OutCubic
}
}
}
// Modern style - icon above text
ColumnLayout {
id: contentRow
anchors.centerIn: parent
spacing: Style.marginXXS * scaling
visible: root.style !== "classic"
// Icon
NIcon {
Layout.alignment: Qt.AlignHCenter
visible: root.icon !== ""
icon: root.icon
pointSize: root.iconSize
color: {
if (!root.enabled)
return Color.mOnSurfaceVariant
if (root.hovered)
return root.hoverIconColor
return root.iconColor
}
Behavior on color {
ColorAnimation {
duration: Style.animationFast
easing.type: Easing.OutCubic
}
}
}
// Text content
NText {
Layout.alignment: Qt.AlignHCenter
visible: root.text !== "" && !compact
text: root.text
pointSize: root.fontSize
font.weight: root.fontWeight
color: {
if (!root.enabled)
return Color.mOnSurfaceVariant
if (root.hovered)
return root.hoverTextColor
return root.textColor
}
elide: Text.ElideRight
Behavior on color {
ColorAnimation {
duration: Style.animationFast
easing.type: Easing.OutCubic
}
}
}
}
// Classic style - EXACTLY like NIconButton (icon + text)
RowLayout {
anchors.centerIn: parent
visible: root.style === "classic"
spacing: Style.marginXS * scaling
NIcon {
visible: root.icon !== ""
icon: root.icon
pointSize: Style.fontSizeM * scaling
color: {
if (!root.enabled)
return Color.mOnSurfaceVariant
if (root.hovered)
return root.hoverIconColor
return root.iconColor
}
Behavior on color {
ColorAnimation {
duration: Style.animationFast
easing.type: Easing.OutCubic
}
}
}
NText {
visible: root.text !== ""
text: root.text
pointSize: root.fontSize
font.weight: root.fontWeight
color: {
if (!root.enabled)
return Color.mOnSurfaceVariant
if (root.hovered)
return root.hoverTextColor
return root.textColor
}
Behavior on color {
ColorAnimation {
duration: Style.animationFast
easing.type: Easing.OutCubic
}
}
}
}
// Mouse interaction with enhanced feedback
MouseArea {
id: mouseArea
anchors.fill: parent
enabled: root.enabled
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onEntered: {
root.hovered = true
if (tooltipText) {
TooltipService.show(Screen, root, root.tooltipText)
}
}
onExited: {
root.hovered = false
if (tooltipText) {
TooltipService.hide()
}
}
onPressed: mouse => {
root.pressed = true
root.scale = 0.95
if (tooltipText) {
TooltipService.hide()
}
}
onReleased: mouse => {
root.pressed = false
root.scale = 1.0
if (mouse.button === Qt.LeftButton) {
root.clicked()
} else if (mouse.button === Qt.RightButton) {
root.rightClicked()
} else if (mouse.button === Qt.MiddleButton) {
root.middleClicked()
}
}
onCanceled: {
root.hovered = false
root.pressed = false
root.scale = 1.0
if (tooltipText) {
TooltipService.hide()
}
}
}
// Ripple effect for M3-style interaction feedback
Rectangle {
id: ripple
anchors.fill: parent
radius: parent.radius
color: Qt.rgba(1, 1, 1, 0.2)
scale: 0
opacity: 0
visible: false
SequentialAnimation {
id: rippleAnimation
running: false
ParallelAnimation {
NumberAnimation {
target: ripple
property: "scale"
from: 0
to: 1.2
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
NumberAnimation {
target: ripple
property: "opacity"
from: 0.6
to: 0
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
}
}
}
// Trigger ripple effect on click
Connections {
target: root
function onClicked() {
ripple.visible = true
rippleAnimation.start()
}
}
// Clean up ripple after animation
Connections {
target: rippleAnimation
function onFinished() {
ripple.visible = false
ripple.scale = 0
ripple.opacity = 0
}
}
}