diff --git a/Commons/IconsSets/TablerIcons.qml b/Commons/IconsSets/TablerIcons.qml index b4f241a..4f99f24 100644 --- a/Commons/IconsSets/TablerIcons.qml +++ b/Commons/IconsSets/TablerIcons.qml @@ -106,6 +106,7 @@ Singleton { "settings-wallpaper-selector": "library-photo", "settings-screen-recorder": "video", "settings-hooks": "link", + "settings-notification": "notification", "settings-about": "info-square-rounded", "bluetooth": "bluetooth", "bt-device-generic": "bluetooth", diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 658d047..622b92a 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -399,6 +399,10 @@ Singleton { property list monitors: [] // Last time the user opened the notification history (ms since epoch) property real lastSeenTs: 0 + // Duration settings for different urgency levels (in seconds) + property int lowUrgencyDuration: 3 + property int normalUrgencyDuration: 8 + property int criticalUrgencyDuration: 15 } // audio diff --git a/Modules/SettingsPanel/SettingsPanel.qml b/Modules/SettingsPanel/SettingsPanel.qml index ecefb1c..58ea18c 100644 --- a/Modules/SettingsPanel/SettingsPanel.qml +++ b/Modules/SettingsPanel/SettingsPanel.qml @@ -29,11 +29,11 @@ NPanel { Dock, Hooks, Launcher, - Brightness, ColorScheme, Display, General, Network, + Notification, ScreenRecorder, Weather, Wallpaper, @@ -72,10 +72,6 @@ NPanel { id: audioTab Tabs.AudioTab {} } - Component { - id: brightnessTab - Tabs.BrightnessTab {} - } Component { id: displayTab Tabs.DisplayTab {} @@ -116,6 +112,10 @@ NPanel { id: dockTab Tabs.DockTab {} } + Component { + id: notificationTab + Tabs.NotificationTab {} + } // Order *DOES* matter function updateTabsModel() { @@ -149,16 +149,16 @@ NPanel { "label": "Display", "icon": "settings-display", "source": displayTab + }, { + "id": SettingsPanel.Tab.Notification, + "label": "Notification", + "icon": "settings-notification", + "source": notificationTab }, { "id": SettingsPanel.Tab.Network, "label": "Network", "icon": "settings-network", "source": networkTab - }, { - "id": SettingsPanel.Tab.Brightness, - "label": "Brightness", - "icon": "settings-brightness", - "source": brightnessTab }, { "id": SettingsPanel.Tab.Weather, "label": "Weather", diff --git a/Modules/SettingsPanel/Tabs/AudioTab.qml b/Modules/SettingsPanel/Tabs/AudioTab.qml index d60ae38..ec9f1e3 100644 --- a/Modules/SettingsPanel/Tabs/AudioTab.qml +++ b/Modules/SettingsPanel/Tabs/AudioTab.qml @@ -9,6 +9,11 @@ import qs.Services ColumnLayout { id: root + NHeader { + title: "Audio Settings" + description: "Configure system audio, devices, and media player preferences." + } + property real localVolume: AudioService.volume Connections { @@ -158,12 +163,8 @@ ColumnLayout { ColumnLayout { spacing: Style.marginS * scaling - NText { - text: "Audio Devices" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary - Layout.bottomMargin: Style.marginS * scaling + NHeader { + title: "Audio Devices" } // ------------------------------- @@ -234,12 +235,8 @@ ColumnLayout { ColumnLayout { spacing: Style.marginL * scaling - NText { - text: "Media Player" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary - Layout.bottomMargin: Style.marginS * scaling + NHeader { + title: "Media Player" } // Preferred player @@ -360,12 +357,8 @@ ColumnLayout { spacing: Style.marginS * scaling Layout.fillWidth: true - NText { - text: "Audio Visualizer" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary - Layout.bottomMargin: Style.marginS * scaling + NHeader { + title: "Audio Visualizer" } // AudioService Visualizer section diff --git a/Modules/SettingsPanel/Tabs/BarTab.qml b/Modules/SettingsPanel/Tabs/BarTab.qml index d3d82d4..88f096d 100644 --- a/Modules/SettingsPanel/Tabs/BarTab.qml +++ b/Modules/SettingsPanel/Tabs/BarTab.qml @@ -1,6 +1,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Quickshell import qs.Commons import qs.Services import qs.Widgets @@ -9,6 +10,24 @@ import qs.Modules.SettingsPanel.Bar ColumnLayout { id: root + NHeader { + title: "Bar Settings" + description: "Configure bar appearance, positioning, and monitor settings." + } + + // Helper functions to update arrays immutably + function addMonitor(list, name) { + const arr = (list || []).slice() + if (!arr.includes(name)) + arr.push(name) + return arr + } + function removeMonitor(list, name) { + return (list || []).filter(function (n) { + return n !== name + }) + } + // Handler for drag start - disables panel background clicks function handleDragStart() { var panel = PanelService.getPanel("settingsPanel") @@ -56,23 +75,19 @@ ColumnLayout { } } + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + ColumnLayout { spacing: Style.marginXXS * scaling Layout.fillWidth: true - NText { - text: "Background Opacity" - font.pointSize: Style.fontSizeL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - } - - NText { - text: "Adjust the background opacity of the bar." - font.pointSize: Style.fontSizeXS * scaling - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap - Layout.fillWidth: true + NHeader { + title: "Background Opacity" + description: "Adjust the background opacity of the bar." } RowLayout { @@ -189,25 +204,49 @@ ColumnLayout { Layout.bottomMargin: Style.marginXL * scaling } + // Monitor Configuration + ColumnLayout { + spacing: Style.marginXXS * scaling + Layout.fillWidth: true + + NHeader { + title: "Monitor Configuration" + description: "Choose which monitors should display the bar." + } + + Repeater { + model: Quickshell.screens || [] + delegate: NCheckbox { + Layout.fillWidth: true + label: `${modelData.name || "Unknown"}${modelData.model ? `: ${modelData.model}` : ""}` + description: `${modelData.width}x${modelData.height} at (${modelData.x}, ${modelData.y})` + checked: (Settings.data.bar.monitors || []).indexOf(modelData.name) !== -1 + onToggled: checked => { + if (checked) { + Settings.data.bar.monitors = addMonitor(Settings.data.bar.monitors, modelData.name) + } else { + Settings.data.bar.monitors = removeMonitor(Settings.data.bar.monitors, modelData.name) + } + } + } + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + // Widgets Management Section ColumnLayout { spacing: Style.marginXXS * scaling Layout.fillWidth: true - NText { - text: "Widgets Positioning" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary - Layout.bottomMargin: Style.marginS * scaling - } - - NText { - text: "Drag and drop widgets to reorder them within each section, or use the add/remove buttons to manage widgets." - font.pointSize: Style.fontSizeM * scaling - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap - Layout.fillWidth: true + NHeader { + title: "Widgets Positioning" + description: "Drag and drop widgets to reorder them within each section, or use the add/remove buttons to manage widgets." + bottomMargin: 0 } // Bar Sections diff --git a/Modules/SettingsPanel/Tabs/BrightnessTab.qml b/Modules/SettingsPanel/Tabs/BrightnessTab.qml deleted file mode 100644 index 0f6f167..0000000 --- a/Modules/SettingsPanel/Tabs/BrightnessTab.qml +++ /dev/null @@ -1,340 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import Quickshell -import Quickshell.Io -import qs.Commons -import qs.Services -import qs.Widgets - -ColumnLayout { - id: root - - // Time dropdown options (00:00 .. 23:30) - ListModel { - id: timeOptions - } - Component.onCompleted: { - for (var h = 0; h < 24; h++) { - for (var m = 0; m < 60; m += 30) { - var hh = ("0" + h).slice(-2) - var mm = ("0" + m).slice(-2) - var key = hh + ":" + mm - timeOptions.append({ - "key": key, - "name": key - }) - } - } - } - - // Check for wlsunset availability when enabling Night Light - Process { - id: wlsunsetCheck - command: ["which", "wlsunset"] - running: false - - onExited: function (exitCode) { - if (exitCode === 0) { - Settings.data.nightLight.enabled = true - NightLightService.apply() - ToastService.showNotice("Night Light", "Enabled") - } else { - Settings.data.nightLight.enabled = false - ToastService.showWarning("Night Light", "wlsunset not installed") - } - } - - stdout: StdioCollector {} - stderr: StdioCollector {} - } - - spacing: Style.marginL * scaling - - // Brightness Step Section - ColumnLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true - - NSpinBox { - Layout.fillWidth: true - label: "Brightness Step Size" - description: "Adjust the step size for brightness changes (scroll wheel, keyboard shortcuts)." - minimum: 1 - maximum: 50 - value: Settings.data.brightness.brightnessStep - stepSize: 1 - suffix: "%" - onValueChanged: { - Settings.data.brightness.brightnessStep = value - } - } - } - - // Monitor Overview Section - ColumnLayout { - spacing: Style.marginL * scaling - - NLabel { - label: "Monitors Brightness Control" - description: "Current brightness levels for all detected monitors." - } - - // Single monitor display using the same data source as the bar icon - Repeater { - model: BrightnessService.monitors - Rectangle { - Layout.fillWidth: true - radius: Style.radiusM * scaling - color: Color.mSurface - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling - - ColumnLayout { - id: contentCol - anchors.fill: parent - anchors.margins: Style.marginL * scaling - spacing: Style.marginM * scaling - - RowLayout { - Layout.fillWidth: true - spacing: Style.marginM * scaling - - NText { - text: `${model.modelData.name} [${model.modelData.model}]` - font.pointSize: Style.fontSizeL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary - } - - Item { - Layout.fillWidth: true - } - - NText { - text: model.method - font.pointSize: Style.fontSizeXS * scaling - color: Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignRight - } - } - - RowLayout { - Layout.fillWidth: true - spacing: Style.marginM * scaling - - NText { - text: "Brightness:" - font.pointSize: Style.fontSizeM * scaling - color: Color.mOnSurface - } - - NSlider { - Layout.fillWidth: true - from: 0 - to: 1 - value: model.brightness - stepSize: 0.05 - onPressedChanged: { - if (!pressed) { - var monitor = BrightnessService.getMonitorForScreen(model.modelData) - monitor.setBrightness(value) - } - } - } - - NText { - text: Math.round(model.brightness * 100) + "%" - font.pointSize: Style.fontSizeM * scaling - font.weight: Style.fontWeightBold - color: Color.mPrimary - Layout.alignment: Qt.AlignRight - } - } - } - } - } - } - - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginXL * scaling - Layout.bottomMargin: Style.marginXL * scaling - } - - // Night Light Section - ColumnLayout { - spacing: Style.marginXS * scaling - Layout.fillWidth: true - - NText { - text: "Night Light" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary - } - - NText { - text: "Reduce blue light emission to help you sleep better and reduce eye strain." - font.pointSize: Style.fontSizeM * scaling - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap - Layout.fillWidth: true - } - } - - NToggle { - label: "Enable Night Light" - description: "Apply a warm color filter to reduce blue light emission." - checked: Settings.data.nightLight.enabled - onToggled: checked => { - if (checked) { - // Verify wlsunset exists before enabling - wlsunsetCheck.running = true - } else { - Settings.data.nightLight.enabled = false - Settings.data.nightLight.forced = false - NightLightService.apply() - ToastService.showNotice("Night Light", "Disabled") - } - } - } - - // Temperature - ColumnLayout { - spacing: Style.marginXS * scaling - Layout.alignment: Qt.AlignVCenter - - NLabel { - label: "Color temperature" - description: "Choose two temperatures in Kelvin." - } - - RowLayout { - visible: Settings.data.nightLight.enabled - spacing: Style.marginM * scaling - Layout.fillWidth: false - Layout.fillHeight: true - Layout.alignment: Qt.AlignVCenter - - NText { - text: "Night" - font.pointSize: Style.fontSizeM * scaling - color: Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignVCenter - } - - NTextInput { - text: Settings.data.nightLight.nightTemp - inputMethodHints: Qt.ImhDigitsOnly - Layout.alignment: Qt.AlignVCenter - onEditingFinished: { - var nightTemp = parseInt(text) - var dayTemp = parseInt(Settings.data.nightLight.dayTemp) - if (!isNaN(nightTemp) && !isNaN(dayTemp)) { - // Clamp value between [1000 .. (dayTemp-500)] - var clampedValue = Math.min(dayTemp - 500, Math.max(1000, nightTemp)) - text = Settings.data.nightLight.nightTemp = clampedValue.toString() - } - } - } - - Item {} - - NText { - text: "Day" - font.pointSize: Style.fontSizeM * scaling - color: Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignVCenter - } - NTextInput { - text: Settings.data.nightLight.dayTemp - inputMethodHints: Qt.ImhDigitsOnly - Layout.alignment: Qt.AlignVCenter - onEditingFinished: { - var dayTemp = parseInt(text) - var nightTemp = parseInt(Settings.data.nightLight.nightTemp) - if (!isNaN(nightTemp) && !isNaN(dayTemp)) { - // Clamp value between [(nightTemp+500) .. 6500] - var clampedValue = Math.max(nightTemp + 500, Math.min(6500, dayTemp)) - text = Settings.data.nightLight.dayTemp = clampedValue.toString() - } - } - } - } - } - - NToggle { - label: "Automatic Scheduling" - description: `Based on the sunset and sunrise time in ${LocationService.stableName} - recommended.` - checked: Settings.data.nightLight.autoSchedule - onToggled: checked => Settings.data.nightLight.autoSchedule = checked - visible: Settings.data.nightLight.enabled - } - - // Schedule settings - ColumnLayout { - spacing: Style.marginXS * scaling - visible: Settings.data.nightLight.enabled && !Settings.data.nightLight.autoSchedule && !Settings.data.nightLight.forced - - RowLayout { - Layout.fillWidth: false - spacing: Style.marginM * scaling - - NLabel { - label: "Manual Scheduling" - } - - Item {// add a little more spacing - } - - NText { - text: "Sunrise Time" - font.pointSize: Style.fontSizeM * scaling - color: Color.mOnSurfaceVariant - } - - NComboBox { - model: timeOptions - currentKey: Settings.data.nightLight.manualSunrise - placeholder: "Select start time" - onSelected: key => Settings.data.nightLight.manualSunrise = key - minimumWidth: 120 * scaling - } - - Item {// add a little more spacing - } - - NText { - text: "Sunset Time" - font.pointSize: Style.fontSizeM * scaling - color: Color.mOnSurfaceVariant - } - NComboBox { - model: timeOptions - currentKey: Settings.data.nightLight.manualSunset - placeholder: "Select stop time" - onSelected: key => Settings.data.nightLight.manualSunset = key - minimumWidth: 120 * scaling - } - } - } - - // Force activation toggle - NToggle { - label: "Force activation" - description: "Immediately apply night temperature without scheduling or fade." - checked: Settings.data.nightLight.forced - onToggled: checked => { - Settings.data.nightLight.forced = checked - if (checked && !Settings.data.nightLight.enabled) { - // Ensure enabled when forcing - wlsunsetCheck.running = true - } else { - NightLightService.apply() - } - } - visible: Settings.data.nightLight.enabled - } -} diff --git a/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml b/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml index f358500..76c54c8 100644 --- a/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml +++ b/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml @@ -10,6 +10,11 @@ ColumnLayout { id: root spacing: 0 + NHeader { + title: "Color Schemes" + description: "Choose and customize color schemes for your interface." + } + // Cache for scheme JSON (can be flat or {dark, light}) property var schemeColorsCache: ({}) @@ -151,19 +156,9 @@ ColumnLayout { spacing: Style.marginM * scaling Layout.fillWidth: true - NText { - text: "Predefined Color Schemes" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary - } - - NText { - text: "To use these color schemes, you must turn off Matugen. With Matugen enabled, colors are automatically generated from your wallpaper." - font.pointSize: Style.fontSizeM * scaling - color: Color.mOnSurfaceVariant - Layout.fillWidth: true - wrapMode: Text.WordWrap + NHeader { + title: "Predefined Color Schemes" + description: "To use these color schemes, you must turn off Matugen. With Matugen enabled, colors are automatically generated from your wallpaper." } // Color Schemes Grid diff --git a/Modules/SettingsPanel/Tabs/DockTab.qml b/Modules/SettingsPanel/Tabs/DockTab.qml index 3ce3dd9..6019589 100644 --- a/Modules/SettingsPanel/Tabs/DockTab.qml +++ b/Modules/SettingsPanel/Tabs/DockTab.qml @@ -1,6 +1,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Quickshell import qs.Commons import qs.Services import qs.Widgets @@ -10,6 +11,24 @@ ColumnLayout { spacing: Style.marginL * scaling width: root.width + NHeader { + title: "Dock Settings" + description: "Configure dock behavior, appearance, and monitor settings." + } + + // Helper functions to update arrays immutably + function addMonitor(list, name) { + const arr = (list || []).slice() + if (!arr.includes(name)) + arr.push(name) + return arr + } + function removeMonitor(list, name) { + return (list || []).filter(function (n) { + return n !== name + }) + } + NToggle { label: "Auto-hide" description: "Automatically hide when not in use." @@ -24,12 +43,52 @@ ColumnLayout { onToggled: checked => Settings.data.dock.exclusive = checked } + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + + // Monitor Configuration ColumnLayout { spacing: Style.marginXXS * scaling Layout.fillWidth: true - NLabel { - label: "Background Opacity" + NHeader { + title: "Monitor Configuration" + description: "Choose which monitors should display the dock." + } + + Repeater { + model: Quickshell.screens || [] + delegate: NCheckbox { + Layout.fillWidth: true + label: `${modelData.name || "Unknown"}${modelData.model ? `: ${modelData.model}` : ""}` + description: `${modelData.width}x${modelData.height} at (${modelData.x}, ${modelData.y})` + checked: (Settings.data.dock.monitors || []).indexOf(modelData.name) !== -1 + onToggled: checked => { + if (checked) { + Settings.data.dock.monitors = addMonitor(Settings.data.dock.monitors, modelData.name) + } else { + Settings.data.dock.monitors = removeMonitor(Settings.data.dock.monitors, modelData.name) + } + } + } + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + + ColumnLayout { + spacing: Style.marginXXS * scaling + Layout.fillWidth: true + + NHeader { + title: "Background Opacity" description: "Adjust the background opacity." } @@ -53,12 +112,18 @@ ColumnLayout { } } + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + ColumnLayout { spacing: Style.marginXXS * scaling Layout.fillWidth: true - NLabel { - label: "Dock Floating Distance" + NHeader { + title: "Dock Floating Distance" description: "Adjust the floating distance from the screen edge." } diff --git a/Modules/SettingsPanel/Tabs/GeneralTab.qml b/Modules/SettingsPanel/Tabs/GeneralTab.qml index 8e61387..e3ef3ba 100644 --- a/Modules/SettingsPanel/Tabs/GeneralTab.qml +++ b/Modules/SettingsPanel/Tabs/GeneralTab.qml @@ -9,6 +9,11 @@ import qs.Widgets ColumnLayout { id: root + NHeader { + title: "Profile" + description: "Configure your user profile and avatar settings." + } + // Profile section RowLayout { Layout.fillWidth: true @@ -48,12 +53,8 @@ ColumnLayout { spacing: Style.marginL * scaling Layout.fillWidth: true - NText { - text: "User Interface" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary - Layout.bottomMargin: Style.marginS * scaling + NHeader { + title: "User Interface" } NToggle { @@ -133,12 +134,8 @@ ColumnLayout { ColumnLayout { spacing: Style.marginL * scaling Layout.fillWidth: true - NText { - text: "Screen Corners" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary - Layout.bottomMargin: Style.marginS * scaling + NHeader { + title: "Screen Corners" } NToggle { @@ -187,12 +184,8 @@ ColumnLayout { ColumnLayout { spacing: Style.marginL * scaling Layout.fillWidth: true - NText { - text: "Fonts" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary - Layout.bottomMargin: Style.marginS * scaling + NHeader { + title: "Fonts" } // Font configuration section diff --git a/Modules/SettingsPanel/Tabs/HooksTab.qml b/Modules/SettingsPanel/Tabs/HooksTab.qml index 461a4b8..de4309a 100644 --- a/Modules/SettingsPanel/Tabs/HooksTab.qml +++ b/Modules/SettingsPanel/Tabs/HooksTab.qml @@ -10,6 +10,11 @@ ColumnLayout { spacing: Style.marginL * scaling width: root.width + NHeader { + title: "System Hooks" + description: "Configure commands to be executed when system events occur." + } + // Enable/Disable Toggle NToggle { label: "Enable Hooks" diff --git a/Modules/SettingsPanel/Tabs/LauncherTab.qml b/Modules/SettingsPanel/Tabs/LauncherTab.qml index 28bb9f0..d1259aa 100644 --- a/Modules/SettingsPanel/Tabs/LauncherTab.qml +++ b/Modules/SettingsPanel/Tabs/LauncherTab.qml @@ -8,6 +8,11 @@ import qs.Widgets ColumnLayout { id: root + NHeader { + title: "Launcher Settings" + description: "Configure the application launcher panel behavior and appearance." + } + ColumnLayout { spacing: Style.marginL * scaling diff --git a/Modules/SettingsPanel/Tabs/NetworkTab.qml b/Modules/SettingsPanel/Tabs/NetworkTab.qml index c4ac87a..8e70525 100644 --- a/Modules/SettingsPanel/Tabs/NetworkTab.qml +++ b/Modules/SettingsPanel/Tabs/NetworkTab.qml @@ -11,6 +11,11 @@ ColumnLayout { id: root spacing: Style.marginL * scaling + NHeader { + title: "Network Settings" + description: "Configure Wi-Fi and Bluetooth connectivity options." + } + NToggle { label: "Enable Wi-Fi" description: "Enable Wi-Fi connectivity." diff --git a/Modules/SettingsPanel/Tabs/NotificationTab.qml b/Modules/SettingsPanel/Tabs/NotificationTab.qml new file mode 100644 index 0000000..b0e123a --- /dev/null +++ b/Modules/SettingsPanel/Tabs/NotificationTab.qml @@ -0,0 +1,182 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Quickshell +import qs.Commons +import qs.Services +import qs.Widgets + +ColumnLayout { + id: root + + // Helper functions to update arrays immutably + function addMonitor(list, name) { + const arr = (list || []).slice() + if (!arr.includes(name)) + arr.push(name) + return arr + } + function removeMonitor(list, name) { + return (list || []).filter(function (n) { + return n !== name + }) + } + + // General Notification Settings + ColumnLayout { + spacing: Style.marginL * scaling + Layout.fillWidth: true + + NHeader { + title: "General Settings" + } + + NToggle { + label: "Do Not Disturb" + description: "Disable all notification popups when enabled." + checked: Settings.data.notifications.doNotDisturb + onToggled: checked => Settings.data.notifications.doNotDisturb = checked + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + + // Monitor Configuration + ColumnLayout { + spacing: Style.marginXXS * scaling + Layout.fillWidth: true + + NHeader { + title: "Monitor Configuration" + description: "Choose which monitors should display notifications." + } + + Repeater { + model: Quickshell.screens || [] + delegate: NCheckbox { + Layout.fillWidth: true + label: `${modelData.name || "Unknown"}${modelData.model ? `: ${modelData.model}` : ""}` + description: `${modelData.width}x${modelData.height} at (${modelData.x}, ${modelData.y})` + checked: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1 + onToggled: checked => { + if (checked) { + Settings.data.notifications.monitors = addMonitor(Settings.data.notifications.monitors, modelData.name) + } else { + Settings.data.notifications.monitors = removeMonitor(Settings.data.notifications.monitors, modelData.name) + } + } + } + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + + // Notification Duration Settings + ColumnLayout { + spacing: Style.marginL * scaling + Layout.fillWidth: true + + NHeader { + title: "Notification Duration" + description: "Configure how long notifications stay visible based on their urgency level." + } + + // Low Urgency Duration + ColumnLayout { + spacing: Style.marginXXS * scaling + Layout.fillWidth: true + + NLabel { + label: "Low Urgency Duration" + description: "How long low priority notifications stay visible." + } + + RowLayout { + NSlider { + Layout.fillWidth: true + from: 1 + to: 30 + stepSize: 1 + value: Settings.data.notifications.lowUrgencyDuration + onMoved: Settings.data.notifications.lowUrgencyDuration = value + cutoutColor: Color.mSurface + } + + NText { + text: Settings.data.notifications.lowUrgencyDuration + "s" + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: Style.marginS * scaling + color: Color.mOnSurface + } + } + } + + // Normal Urgency Duration + ColumnLayout { + spacing: Style.marginXXS * scaling + Layout.fillWidth: true + + NLabel { + label: "Normal Urgency Duration" + description: "How long normal priority notifications stay visible." + } + + RowLayout { + NSlider { + Layout.fillWidth: true + from: 1 + to: 30 + stepSize: 1 + value: Settings.data.notifications.normalUrgencyDuration + onMoved: Settings.data.notifications.normalUrgencyDuration = value + cutoutColor: Color.mSurface + } + + NText { + text: Settings.data.notifications.normalUrgencyDuration + "s" + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: Style.marginS * scaling + color: Color.mOnSurface + } + } + } + + // Critical Urgency Duration + ColumnLayout { + spacing: Style.marginXXS * scaling + Layout.fillWidth: true + + NLabel { + label: "Critical Urgency Duration" + description: "How long critical priority notifications stay visible." + } + + RowLayout { + NSlider { + Layout.fillWidth: true + from: 1 + to: 30 + stepSize: 1 + value: Settings.data.notifications.criticalUrgencyDuration + onMoved: Settings.data.notifications.criticalUrgencyDuration = value + cutoutColor: Color.mSurface + } + + NText { + text: Settings.data.notifications.criticalUrgencyDuration + "s" + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: Style.marginS * scaling + color: Color.mOnSurface + } + } + } + } +} diff --git a/Modules/SettingsPanel/Tabs/ScreenRecorderTab.qml b/Modules/SettingsPanel/Tabs/ScreenRecorderTab.qml index 58f1e8d..7493fd1 100644 --- a/Modules/SettingsPanel/Tabs/ScreenRecorderTab.qml +++ b/Modules/SettingsPanel/Tabs/ScreenRecorderTab.qml @@ -10,6 +10,11 @@ ColumnLayout { spacing: Style.marginL * scaling + NHeader { + title: "Screen Recorder" + description: "Configure screen recording settings and output options." + } + // Output Directory ColumnLayout { spacing: Style.marginS * scaling @@ -53,12 +58,8 @@ ColumnLayout { spacing: Style.marginL * scaling Layout.fillWidth: true - NText { - text: "Video Settings" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary - Layout.bottomMargin: Style.marginS * scaling + NHeader { + title: "Video Settings" } // Source @@ -203,12 +204,8 @@ ColumnLayout { spacing: Style.marginL * scaling Layout.fillWidth: true - NText { - text: "Audio Settings" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary - Layout.bottomMargin: Style.marginS * scaling + NHeader { + title: "Audio Settings" } // Audio Source diff --git a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml index f82c4c9..01f8420 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml @@ -12,6 +12,11 @@ ColumnLayout { spacing: Style.marginL * scaling + NHeader { + title: "Wallpaper Selector" + description: "Browse and select wallpapers from your configured directory." + } + property list wallpapersList: [] property string currentWallpaper: "" @@ -42,11 +47,8 @@ ColumnLayout { } // Current wallpaper display - NText { - text: "Current Wallpaper" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary + NHeader { + title: "Current Wallpaper" } Rectangle { @@ -80,18 +82,9 @@ ColumnLayout { Layout.fillWidth: true // Wallpaper grid - NText { - text: "Wallpaper Selector" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary - } - - NText { - text: "Click on a wallpaper to set it as your current wallpaper." - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap - Layout.fillWidth: true + NHeader { + title: "Wallpaper Selector" + description: "Click on a wallpaper to set it as your current wallpaper." } } diff --git a/Modules/SettingsPanel/Tabs/WallpaperTab.qml b/Modules/SettingsPanel/Tabs/WallpaperTab.qml index 0d40d33..a0019d4 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperTab.qml @@ -10,6 +10,11 @@ import qs.Widgets ColumnLayout { id: root + NHeader { + title: "Wallpaper Management" + description: "Configure wallpaper settings, automation, and appearance options." + } + NToggle { label: "Enable Wallpaper Management" description: "Manage wallpapers with Noctalia. (Uncheck if you prefer using another application)." @@ -89,11 +94,8 @@ ColumnLayout { spacing: Style.marginL * scaling Layout.fillWidth: true - NText { - text: "Look & Feel" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary + NHeader { + title: "Look & Feel" } // Fill Mode @@ -189,11 +191,8 @@ ColumnLayout { spacing: Style.marginL * scaling Layout.fillWidth: true - NText { - text: "Automation" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary + NHeader { + title: "Automation" } // Random Wallpaper diff --git a/Modules/SettingsPanel/Tabs/WeatherTab.qml b/Modules/SettingsPanel/Tabs/WeatherTab.qml index 667aca5..9f3858a 100644 --- a/Modules/SettingsPanel/Tabs/WeatherTab.qml +++ b/Modules/SettingsPanel/Tabs/WeatherTab.qml @@ -8,6 +8,11 @@ import qs.Widgets ColumnLayout { id: root + NHeader { + title: "Weather & Location" + description: "Configure weather display and location settings." + } + // Location section RowLayout { Layout.fillWidth: true @@ -57,11 +62,8 @@ ColumnLayout { spacing: Style.marginM * scaling Layout.fillWidth: true - NText { - text: "Weather" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary + NHeader { + title: "Weather" } NToggle { diff --git a/Services/NotificationService.qml b/Services/NotificationService.qml index e31ad14..98ad57b 100644 --- a/Services/NotificationService.qml +++ b/Services/NotificationService.qml @@ -87,9 +87,26 @@ Singleton { // Maximum visible notifications property int maxVisible: 5 + // Function to get duration based on urgency + function getDurationForUrgency(urgency) { + switch (urgency) { + case 0: + // Low urgency + return (Settings.data.notifications.lowUrgencyDuration || 3) * 1000 + case 1: + // Normal urgency + return (Settings.data.notifications.normalUrgencyDuration || 8) * 1000 + case 2: + // Critical urgency + return (Settings.data.notifications.criticalUrgencyDuration || 15) * 1000 + default: + return (Settings.data.notifications.normalUrgencyDuration || 8) * 1000 + } + } + // Auto-hide timer property Timer hideTimer: Timer { - interval: 8000 // 8 seconds - longer display time + interval: 1000 // Check every second repeat: true running: notificationModel.count > 0 @@ -98,11 +115,26 @@ Singleton { return } - // Remove the oldest notification (last in the list) - let oldestNotification = notificationModel.get(notificationModel.count - 1).rawNotification - if (oldestNotification) { - // Trigger animation signal instead of direct dismiss - animateAndRemove(oldestNotification, notificationModel.count - 1) + // Check each notification for expiration + for (var i = notificationModel.count - 1; i >= 0; i--) { + let notificationData = notificationModel.get(i) + if (notificationData && notificationData.rawNotification) { + let notification = notificationData.rawNotification + let urgency = notificationData.urgency + let timestamp = notificationData.timestamp + + // Calculate if this notification should be removed + let duration = getDurationForUrgency(urgency) + let now = new Date() + let elapsed = now.getTime() - timestamp.getTime() + + if (elapsed >= duration) { + // Trigger animation signal instead of direct dismiss + animateAndRemove(notification, i) + break + // Only remove one notification per check to avoid conflicts + } + } } } } diff --git a/Widgets/NHeader.qml b/Widgets/NHeader.qml new file mode 100644 index 0000000..e077c7b --- /dev/null +++ b/Widgets/NHeader.qml @@ -0,0 +1,33 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons + +ColumnLayout { + id: root + + property string title: "" + property string description: "" + property real bottomMargin: Style.marginL * scaling + + spacing: Style.marginXXS * scaling + Layout.fillWidth: true + + NText { + text: root.title + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mSecondary + Layout.bottomMargin: Style.marginS * scaling + visible: root.title !== "" + } + + NText { + text: root.description + font.pointSize: Style.fontSizeM * scaling + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + Layout.bottomMargin: root.bottomMargin + visible: root.description !== "" + } +}