mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
Settings: Subtabs with horizontal scrolling
This commit is contained in:
@@ -1059,6 +1059,11 @@
|
||||
"volumes": "Volumes"
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
"devices": "Devices",
|
||||
"media": "Media",
|
||||
"volumes": "Volumes"
|
||||
},
|
||||
"title": "Audio",
|
||||
"volumes": {
|
||||
"input-volume": {
|
||||
@@ -1146,6 +1151,11 @@
|
||||
"label": "Monitors display"
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
"appearance": "Appearance",
|
||||
"monitors": "Monitors",
|
||||
"widgets": "Widgets"
|
||||
},
|
||||
"title": "Bar",
|
||||
"tray": {
|
||||
"back": "Back",
|
||||
@@ -1343,6 +1353,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
"colors": "Colors",
|
||||
"templates": "Templates"
|
||||
},
|
||||
"title": "Color Scheme"
|
||||
},
|
||||
"control-center": {
|
||||
@@ -1622,6 +1636,10 @@
|
||||
"night-description": "Controls the temperature during nighttime."
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
"brightness": "Brightness",
|
||||
"night-light": "Night Light"
|
||||
},
|
||||
"title": "Display"
|
||||
},
|
||||
"dock": {
|
||||
@@ -1688,6 +1706,10 @@
|
||||
"label": "Monitor display"
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
"appearance": "Appearance",
|
||||
"monitors": "Monitors"
|
||||
},
|
||||
"title": "Dock"
|
||||
},
|
||||
"general": {
|
||||
@@ -2137,6 +2159,12 @@
|
||||
"label": "Sound volume"
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
"general": "General",
|
||||
"history": "History",
|
||||
"sounds": "Sounds",
|
||||
"toast": "Toast"
|
||||
},
|
||||
"title": "Notifications",
|
||||
"toast": {
|
||||
"keyboard": {
|
||||
@@ -2485,9 +2513,18 @@
|
||||
"warning": "Warning threshold"
|
||||
},
|
||||
"thresholds-section": {
|
||||
"description": "Adjust warning/critical thresholds and polling intervals for each system metric.",
|
||||
"description": "Adjust warning/critical thresholds for each system metric.",
|
||||
"label": "Thresholds"
|
||||
},
|
||||
"polling-section": {
|
||||
"description": "Configure how often each system metric is updated.",
|
||||
"label": "Polling intervals"
|
||||
},
|
||||
"tabs": {
|
||||
"general": "General",
|
||||
"polling": "Polling",
|
||||
"thresholds": "Thresholds"
|
||||
},
|
||||
"title": "System Monitor",
|
||||
"use-custom-highlight-colors": {
|
||||
"description": "When disabled, theme default highlight colors are used.",
|
||||
@@ -2567,10 +2604,21 @@
|
||||
},
|
||||
"label": "Drop shadows"
|
||||
},
|
||||
"tabs": {
|
||||
"appearance": "Appearance",
|
||||
"panels": "Panels",
|
||||
"screen-corners": "Screen Corners"
|
||||
},
|
||||
"title": "User Interface",
|
||||
"tooltips": {
|
||||
"description": "Enable or disable tooltips throughout the interface.",
|
||||
"label": "Show tooltips"
|
||||
},
|
||||
"appearance": {
|
||||
"section": {
|
||||
"description": "Customize visual elements like tooltips, borders, and shadows.",
|
||||
"label": "Appearance"
|
||||
}
|
||||
}
|
||||
},
|
||||
"wallpaper": {
|
||||
@@ -2669,6 +2717,11 @@
|
||||
"label": "Position"
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
"automation": "Automation",
|
||||
"look-feel": "Look & feel",
|
||||
"settings": "Settings"
|
||||
},
|
||||
"title": "Wallpaper"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -4,8 +4,16 @@ import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Modules.Panels.Settings.Tabs
|
||||
import qs.Modules.Panels.Settings.Tabs.Audio
|
||||
import qs.Modules.Panels.Settings.Tabs.Bar
|
||||
import qs.Modules.Panels.Settings.Tabs.ColorScheme
|
||||
import qs.Modules.Panels.Settings.Tabs.Display
|
||||
import qs.Modules.Panels.Settings.Tabs.Dock
|
||||
import qs.Modules.Panels.Settings.Tabs.Notifications
|
||||
import qs.Modules.Panels.Settings.Tabs.SessionMenu
|
||||
import qs.Modules.Panels.Settings.Tabs.SystemMonitor
|
||||
import qs.Modules.Panels.Settings.Tabs.UserInterface
|
||||
import qs.Modules.Panels.Settings.Tabs.Wallpaper
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.audio.tabs.volumes")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.audio.tabs.devices")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.audio.tabs.media")
|
||||
tabIndex: 2
|
||||
checked: subTabBar.currentIndex === 2
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
VolumesSubTab {}
|
||||
DevicesSubTab {}
|
||||
MediaSubTab {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Services.Pipewire
|
||||
import qs.Commons
|
||||
import qs.Services.Media
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.audio.devices.section.label")
|
||||
description: I18n.tr("settings.audio.devices.section.description")
|
||||
}
|
||||
|
||||
// Output Devices
|
||||
ButtonGroup {
|
||||
id: sinks
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXS
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginL
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.audio.devices.output-device.label")
|
||||
description: I18n.tr("settings.audio.devices.output-device.description")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: AudioService.sinks
|
||||
NRadioButton {
|
||||
ButtonGroup.group: sinks
|
||||
required property PwNode modelData
|
||||
text: modelData.description
|
||||
checked: AudioService.sink?.id === modelData.id
|
||||
onClicked: {
|
||||
AudioService.setAudioSink(modelData);
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Input Devices
|
||||
ButtonGroup {
|
||||
id: sources
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.audio.devices.input-device.label")
|
||||
description: I18n.tr("settings.audio.devices.input-device.description")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: AudioService.sources
|
||||
NRadioButton {
|
||||
ButtonGroup.group: sources
|
||||
required property PwNode modelData
|
||||
text: modelData.description
|
||||
checked: AudioService.source?.id === modelData.id
|
||||
onClicked: AudioService.setAudioSource(modelData)
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.Media
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.audio.media.section.label")
|
||||
description: I18n.tr("settings.audio.media.section.description")
|
||||
}
|
||||
|
||||
// Preferred player
|
||||
NTextInput {
|
||||
label: I18n.tr("settings.audio.media.primary-player.label")
|
||||
description: I18n.tr("settings.audio.media.primary-player.description")
|
||||
placeholderText: I18n.tr("settings.audio.media.primary-player.placeholder")
|
||||
text: Settings.data.audio.preferredPlayer
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("audio.preferredPlayer")
|
||||
onTextChanged: {
|
||||
Settings.data.audio.preferredPlayer = text;
|
||||
MediaService.updateCurrentPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
// Blacklist editor
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NTextInputButton {
|
||||
id: blacklistInput
|
||||
label: I18n.tr("settings.audio.media.excluded-player.label")
|
||||
description: I18n.tr("settings.audio.media.excluded-player.description")
|
||||
placeholderText: I18n.tr("settings.audio.media.excluded-player.placeholder")
|
||||
buttonIcon: "add"
|
||||
Layout.fillWidth: true
|
||||
onButtonClicked: {
|
||||
const val = (blacklistInput.text || "").trim();
|
||||
if (val !== "") {
|
||||
const arr = (Settings.data.audio.mprisBlacklist || []);
|
||||
if (!arr.find(x => String(x).toLowerCase() === val.toLowerCase())) {
|
||||
Settings.data.audio.mprisBlacklist = [...arr, val];
|
||||
blacklistInput.text = "";
|
||||
MediaService.updateCurrentPlayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Current blacklist entries
|
||||
Flow {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.marginS
|
||||
spacing: Style.marginS
|
||||
|
||||
Repeater {
|
||||
model: Settings.data.audio.mprisBlacklist
|
||||
delegate: Rectangle {
|
||||
required property string modelData
|
||||
property real pad: Style.marginS
|
||||
color: Qt.alpha(Color.mOnSurface, 0.125)
|
||||
border.color: Qt.alpha(Color.mOnSurface, Style.opacityLight)
|
||||
border.width: Style.borderS
|
||||
|
||||
RowLayout {
|
||||
id: chipRow
|
||||
spacing: Style.marginXS
|
||||
anchors.fill: parent
|
||||
anchors.margins: pad
|
||||
|
||||
NText {
|
||||
text: modelData
|
||||
color: Color.mOnSurface
|
||||
pointSize: Style.fontSizeS
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: Style.marginS
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.rightMargin: Style.marginXS
|
||||
onClicked: {
|
||||
const arr = (Settings.data.audio.mprisBlacklist || []);
|
||||
const idx = arr.findIndex(x => String(x) === modelData);
|
||||
if (idx >= 0) {
|
||||
arr.splice(idx, 1);
|
||||
Settings.data.audio.mprisBlacklist = arr;
|
||||
MediaService.updateCurrentPlayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
implicitWidth: chipRow.implicitWidth + pad * 2
|
||||
implicitHeight: Math.max(chipRow.implicitHeight + pad * 2, Style.baseWidgetSize * 0.8)
|
||||
radius: Style.radiusM
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Audio Visualizer section
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.audio.media.visualizer-type.label")
|
||||
description: I18n.tr("settings.audio.media.visualizer-type.description")
|
||||
model: [
|
||||
{
|
||||
"key": "none",
|
||||
"name": I18n.tr("options.visualizer-types.none")
|
||||
},
|
||||
{
|
||||
"key": "linear",
|
||||
"name": I18n.tr("options.visualizer-types.linear")
|
||||
},
|
||||
{
|
||||
"key": "mirrored",
|
||||
"name": I18n.tr("options.visualizer-types.mirrored")
|
||||
},
|
||||
{
|
||||
"key": "wave",
|
||||
"name": I18n.tr("options.visualizer-types.wave")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.audio.visualizerType
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("audio.visualizerType")
|
||||
onSelected: key => Settings.data.audio.visualizerType = key
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.audio.media.frame-rate.label")
|
||||
description: I18n.tr("settings.audio.media.frame-rate.description")
|
||||
model: [
|
||||
{
|
||||
"key": "30",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "30"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "60",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "60"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "100",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "100"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "120",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "120"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "144",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "144"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "165",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "165"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "240",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "240"
|
||||
})
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.audio.cavaFrameRate
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("audio.cavaFrameRate")
|
||||
onSelected: key => Settings.data.audio.cavaFrameRate = key
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.Media
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
property real localVolume: AudioService.volume
|
||||
|
||||
Connections {
|
||||
target: AudioService
|
||||
function onSinkChanged() {
|
||||
localVolume = AudioService.volume;
|
||||
}
|
||||
function onVolumeChanged() {
|
||||
localVolume = AudioService.volume;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: AudioService.sink?.audio ? AudioService.sink?.audio : null
|
||||
function onVolumeChanged() {
|
||||
localVolume = AudioService.volume;
|
||||
}
|
||||
}
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.audio.volumes.section.label")
|
||||
description: I18n.tr("settings.audio.volumes.section.description")
|
||||
}
|
||||
|
||||
// Master Volume
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.audio.volumes.output-volume.label")
|
||||
description: I18n.tr("settings.audio.volumes.output-volume.description")
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 100
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (!AudioService.isSwitchingSink && Math.abs(localVolume - AudioService.volume) >= 0.01) {
|
||||
AudioService.setVolume(localVolume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0
|
||||
value: localVolume
|
||||
stepSize: 0.01
|
||||
text: Math.round(AudioService.volume * 100) + "%"
|
||||
onMoved: value => localVolume = value
|
||||
}
|
||||
}
|
||||
|
||||
// Mute Toggle
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.audio.volumes.mute-output.label")
|
||||
description: I18n.tr("settings.audio.volumes.mute-output.description")
|
||||
checked: AudioService.muted
|
||||
onToggled: checked => {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Input Volume
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.audio.volumes.input-volume.label")
|
||||
description: I18n.tr("settings.audio.volumes.input-volume.description")
|
||||
from: 0
|
||||
to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0
|
||||
value: AudioService.inputVolume
|
||||
stepSize: 0.01
|
||||
text: Math.round(AudioService.inputVolume * 100) + "%"
|
||||
onMoved: value => AudioService.setInputVolume(value)
|
||||
}
|
||||
}
|
||||
|
||||
// Input Mute Toggle
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.audio.volumes.mute-input.label")
|
||||
description: I18n.tr("settings.audio.volumes.mute-input.description")
|
||||
checked: AudioService.inputMuted
|
||||
onToggled: checked => AudioService.setInputMuted(checked)
|
||||
}
|
||||
}
|
||||
|
||||
// Volume Step Size
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NSpinBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.audio.volumes.step-size.label")
|
||||
description: I18n.tr("settings.audio.volumes.step-size.description")
|
||||
minimum: 1
|
||||
maximum: 25
|
||||
value: Settings.data.audio.volumeStep
|
||||
stepSize: 1
|
||||
suffix: "%"
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("audio.volumeStep")
|
||||
onValueChanged: Settings.data.audio.volumeStep = value
|
||||
}
|
||||
}
|
||||
|
||||
// Raise maximum volume above 100%
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.audio.volumes.volume-overdrive.label")
|
||||
description: I18n.tr("settings.audio.volumes.volume-overdrive.description")
|
||||
checked: Settings.data.audio.volumeOverdrive
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("audio.volumeOverdrive")
|
||||
onToggled: checked => Settings.data.audio.volumeOverdrive = checked
|
||||
}
|
||||
}
|
||||
|
||||
// External mixer command
|
||||
NTextInput {
|
||||
label: I18n.tr("settings.audio.external-mixer.label")
|
||||
description: I18n.tr("settings.audio.external-mixer.description")
|
||||
placeholderText: I18n.tr("settings.audio.external-mixer.placeholder")
|
||||
text: Settings.data.audio.externalMixer
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("audio.externalMixer")
|
||||
onTextChanged: Settings.data.audio.externalMixer = text
|
||||
}
|
||||
}
|
||||
@@ -1,445 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Services.Pipewire
|
||||
import qs.Commons
|
||||
import qs.Services.Media
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
|
||||
property real localVolume: AudioService.volume
|
||||
|
||||
Connections {
|
||||
target: AudioService
|
||||
function onSinkChanged() {
|
||||
// Immediately update local volume when device changes to prevent old value from being applied
|
||||
localVolume = AudioService.volume;
|
||||
}
|
||||
function onVolumeChanged() {
|
||||
localVolume = AudioService.volume;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: AudioService.sink?.audio ? AudioService.sink?.audio : null
|
||||
function onVolumeChanged() {
|
||||
localVolume = AudioService.volume;
|
||||
}
|
||||
}
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.audio.volumes.section.label")
|
||||
description: I18n.tr("settings.audio.volumes.section.description")
|
||||
}
|
||||
|
||||
// Master Volume
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.audio.volumes.output-volume.label")
|
||||
description: I18n.tr("settings.audio.volumes.output-volume.description")
|
||||
}
|
||||
|
||||
// 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
|
||||
Timer {
|
||||
interval: 100
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
// Don't set volume if device is switching - wait for new device's volume to be read
|
||||
if (!AudioService.isSwitchingSink && Math.abs(localVolume - AudioService.volume) >= 0.01) {
|
||||
AudioService.setVolume(localVolume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0
|
||||
value: localVolume
|
||||
stepSize: 0.01
|
||||
text: Math.round(AudioService.volume * 100) + "%"
|
||||
onMoved: value => localVolume = value
|
||||
}
|
||||
}
|
||||
|
||||
// Mute Toggle
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.audio.volumes.mute-output.label")
|
||||
description: I18n.tr("settings.audio.volumes.mute-output.description")
|
||||
checked: AudioService.muted
|
||||
onToggled: checked => {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Input Volume
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.audio.volumes.input-volume.label")
|
||||
description: I18n.tr("settings.audio.volumes.input-volume.description")
|
||||
from: 0
|
||||
to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0
|
||||
value: AudioService.inputVolume
|
||||
stepSize: 0.01
|
||||
text: Math.round(AudioService.inputVolume * 100) + "%"
|
||||
onMoved: value => AudioService.setInputVolume(value)
|
||||
}
|
||||
}
|
||||
|
||||
// Input Mute Toggle
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.audio.volumes.mute-input.label")
|
||||
description: I18n.tr("settings.audio.volumes.mute-input.description")
|
||||
checked: AudioService.inputMuted
|
||||
onToggled: checked => AudioService.setInputMuted(checked)
|
||||
}
|
||||
}
|
||||
|
||||
// Volume Step Size
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NSpinBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.audio.volumes.step-size.label")
|
||||
description: I18n.tr("settings.audio.volumes.step-size.description")
|
||||
minimum: 1
|
||||
maximum: 25
|
||||
value: Settings.data.audio.volumeStep
|
||||
stepSize: 1
|
||||
suffix: "%"
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("audio.volumeStep")
|
||||
onValueChanged: Settings.data.audio.volumeStep = value
|
||||
}
|
||||
}
|
||||
|
||||
// Raise maximum volume above 100%
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.audio.volumes.volume-overdrive.label")
|
||||
description: I18n.tr("settings.audio.volumes.volume-overdrive.description")
|
||||
checked: Settings.data.audio.volumeOverdrive
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("audio.volumeOverdrive")
|
||||
onToggled: checked => Settings.data.audio.volumeOverdrive = checked
|
||||
}
|
||||
}
|
||||
|
||||
// External mixer command
|
||||
NTextInput {
|
||||
label: I18n.tr("settings.audio.external-mixer.label")
|
||||
description: I18n.tr("settings.audio.external-mixer.description")
|
||||
placeholderText: I18n.tr("settings.audio.external-mixer.placeholder")
|
||||
text: Settings.data.audio.externalMixer
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("audio.externalMixer")
|
||||
onTextChanged: Settings.data.audio.externalMixer = text
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// AudioService Devices
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.audio.devices.section.label")
|
||||
description: I18n.tr("settings.audio.devices.section.description")
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
// Output Devices
|
||||
ButtonGroup {
|
||||
id: sinks
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXS
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginL
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.audio.devices.output-device.label")
|
||||
description: I18n.tr("settings.audio.devices.output-device.description")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: AudioService.sinks
|
||||
NRadioButton {
|
||||
ButtonGroup.group: sinks
|
||||
required property PwNode modelData
|
||||
text: modelData.description
|
||||
checked: AudioService.sink?.id === modelData.id
|
||||
onClicked: {
|
||||
AudioService.setAudioSink(modelData);
|
||||
localVolume = AudioService.volume;
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
// Input Devices
|
||||
ButtonGroup {
|
||||
id: sources
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.audio.devices.input-device.label")
|
||||
description: I18n.tr("settings.audio.devices.input-device.description")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: AudioService.sources
|
||||
//Layout.fillWidth: true
|
||||
NRadioButton {
|
||||
ButtonGroup.group: sources
|
||||
required property PwNode modelData
|
||||
text: modelData.description
|
||||
checked: AudioService.source?.id === modelData.id
|
||||
onClicked: AudioService.setAudioSource(modelData)
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Divider
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// Media Player Preferences
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.audio.media.section.label")
|
||||
description: I18n.tr("settings.audio.media.section.description")
|
||||
}
|
||||
|
||||
// Preferred player
|
||||
NTextInput {
|
||||
label: I18n.tr("settings.audio.media.primary-player.label")
|
||||
description: I18n.tr("settings.audio.media.primary-player.description")
|
||||
placeholderText: I18n.tr("settings.audio.media.primary-player.placeholder")
|
||||
text: Settings.data.audio.preferredPlayer
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("audio.preferredPlayer")
|
||||
onTextChanged: {
|
||||
Settings.data.audio.preferredPlayer = text;
|
||||
MediaService.updateCurrentPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
// Blacklist editor
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NTextInputButton {
|
||||
id: blacklistInput
|
||||
label: I18n.tr("settings.audio.media.excluded-player.label")
|
||||
description: I18n.tr("settings.audio.media.excluded-player.description")
|
||||
placeholderText: I18n.tr("settings.audio.media.excluded-player.placeholder")
|
||||
buttonIcon: "add"
|
||||
Layout.fillWidth: true
|
||||
onButtonClicked: {
|
||||
const val = (blacklistInput.text || "").trim();
|
||||
if (val !== "") {
|
||||
const arr = (Settings.data.audio.mprisBlacklist || []);
|
||||
if (!arr.find(x => String(x).toLowerCase() === val.toLowerCase())) {
|
||||
Settings.data.audio.mprisBlacklist = [...arr, val];
|
||||
blacklistInput.text = "";
|
||||
MediaService.updateCurrentPlayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Current blacklist entries
|
||||
Flow {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.marginS
|
||||
spacing: Style.marginS
|
||||
|
||||
Repeater {
|
||||
model: Settings.data.audio.mprisBlacklist
|
||||
delegate: Rectangle {
|
||||
required property string modelData
|
||||
// Padding around the inner row
|
||||
property real pad: Style.marginS
|
||||
// Visuals
|
||||
color: Qt.alpha(Color.mOnSurface, 0.125)
|
||||
border.color: Qt.alpha(Color.mOnSurface, Style.opacityLight)
|
||||
border.width: Style.borderS
|
||||
|
||||
// Content
|
||||
RowLayout {
|
||||
id: chipRow
|
||||
spacing: Style.marginXS
|
||||
anchors.fill: parent
|
||||
anchors.margins: pad
|
||||
|
||||
NText {
|
||||
text: modelData
|
||||
color: Color.mOnSurface
|
||||
pointSize: Style.fontSizeS
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: Style.marginS
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.rightMargin: Style.marginXS
|
||||
onClicked: {
|
||||
const arr = (Settings.data.audio.mprisBlacklist || []);
|
||||
const idx = arr.findIndex(x => String(x) === modelData);
|
||||
if (idx >= 0) {
|
||||
arr.splice(idx, 1);
|
||||
Settings.data.audio.mprisBlacklist = arr;
|
||||
MediaService.updateCurrentPlayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Intrinsic size derived from inner row + padding
|
||||
implicitWidth: chipRow.implicitWidth + pad * 2
|
||||
implicitHeight: Math.max(chipRow.implicitHeight + pad * 2, Style.baseWidgetSize * 0.8)
|
||||
radius: Style.radiusM
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// AudioService Visualizer section
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.audio.media.visualizer-type.label")
|
||||
description: I18n.tr("settings.audio.media.visualizer-type.description")
|
||||
model: [
|
||||
{
|
||||
"key": "none",
|
||||
"name": I18n.tr("options.visualizer-types.none")
|
||||
},
|
||||
{
|
||||
"key": "linear",
|
||||
"name": I18n.tr("options.visualizer-types.linear")
|
||||
},
|
||||
{
|
||||
"key": "mirrored",
|
||||
"name": I18n.tr("options.visualizer-types.mirrored")
|
||||
},
|
||||
{
|
||||
"key": "wave",
|
||||
"name": I18n.tr("options.visualizer-types.wave")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.audio.visualizerType
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("audio.visualizerType")
|
||||
onSelected: key => Settings.data.audio.visualizerType = key
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.audio.media.frame-rate.label")
|
||||
description: I18n.tr("settings.audio.media.frame-rate.description")
|
||||
model: [
|
||||
{
|
||||
"key": "30",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "30"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "60",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "60"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "100",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "100"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "120",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "120"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "144",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "144"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "165",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "165"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "240",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "240"
|
||||
})
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.audio.cavaFrameRate
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("audio.cavaFrameRate")
|
||||
onSelected: key => Settings.data.audio.cavaFrameRate = key
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.bar.appearance.section.label")
|
||||
description: I18n.tr("settings.bar.appearance.section.description")
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.bar.appearance.position.label")
|
||||
description: I18n.tr("settings.bar.appearance.position.description")
|
||||
model: [
|
||||
{
|
||||
"key": "top",
|
||||
"name": I18n.tr("options.bar.position.top")
|
||||
},
|
||||
{
|
||||
"key": "bottom",
|
||||
"name": I18n.tr("options.bar.position.bottom")
|
||||
},
|
||||
{
|
||||
"key": "left",
|
||||
"name": I18n.tr("options.bar.position.left")
|
||||
},
|
||||
{
|
||||
"key": "right",
|
||||
"name": I18n.tr("options.bar.position.right")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.bar.position
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("bar.position")
|
||||
onSelected: key => Settings.data.bar.position = key
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.bar.appearance.density.label")
|
||||
description: I18n.tr("settings.bar.appearance.density.description")
|
||||
model: [
|
||||
{
|
||||
"key": "mini",
|
||||
"name": I18n.tr("options.bar.density.mini")
|
||||
},
|
||||
{
|
||||
"key": "compact",
|
||||
"name": I18n.tr("options.bar.density.compact")
|
||||
},
|
||||
{
|
||||
"key": "default",
|
||||
"name": I18n.tr("options.bar.density.default")
|
||||
},
|
||||
{
|
||||
"key": "comfortable",
|
||||
"name": I18n.tr("options.bar.density.comfortable")
|
||||
},
|
||||
{
|
||||
"key": "spacious",
|
||||
"name": I18n.tr("options.bar.density.spacious")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.bar.density
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("bar.density")
|
||||
onSelected: key => Settings.data.bar.density = key
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.bar.appearance.use-separate-opacity.label")
|
||||
description: I18n.tr("settings.bar.appearance.use-separate-opacity.description")
|
||||
checked: Settings.data.bar.useSeparateOpacity
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("bar.useSeparateOpacity")
|
||||
onToggled: checked => Settings.data.bar.useSeparateOpacity = checked
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
visible: Settings.data.bar.useSeparateOpacity
|
||||
label: I18n.tr("settings.bar.appearance.background-opacity.label")
|
||||
description: I18n.tr("settings.bar.appearance.background-opacity.description")
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.bar.backgroundOpacity
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("bar.backgroundOpacity")
|
||||
onMoved: value => Settings.data.bar.backgroundOpacity = value
|
||||
text: Math.floor(Settings.data.bar.backgroundOpacity * 100) + "%"
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.bar.appearance.show-outline.label")
|
||||
description: I18n.tr("settings.bar.appearance.show-outline.description")
|
||||
checked: Settings.data.bar.showOutline
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("bar.showOutline")
|
||||
onToggled: checked => Settings.data.bar.showOutline = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.bar.appearance.show-capsule.label")
|
||||
description: I18n.tr("settings.bar.appearance.show-capsule.description")
|
||||
checked: Settings.data.bar.showCapsule
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("bar.showCapsule")
|
||||
onToggled: checked => Settings.data.bar.showCapsule = checked
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginXXS
|
||||
visible: Settings.data.bar.showCapsule
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.bar.appearance.capsule-opacity.label")
|
||||
description: I18n.tr("settings.bar.appearance.capsule-opacity.description")
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.bar.capsuleOpacity
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("bar.capsuleOpacity")
|
||||
onMoved: value => Settings.data.bar.capsuleOpacity = value
|
||||
text: Math.floor(Settings.data.bar.capsuleOpacity * 100) + "%"
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.bar.appearance.floating.label")
|
||||
description: I18n.tr("settings.bar.appearance.floating.description")
|
||||
checked: Settings.data.bar.floating
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("bar.floating")
|
||||
onToggled: checked => {
|
||||
Settings.data.bar.floating = checked;
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.bar.appearance.outer-corners.label")
|
||||
description: I18n.tr("settings.bar.appearance.outer-corners.description")
|
||||
checked: Settings.data.bar.outerCorners
|
||||
visible: !Settings.data.bar.floating
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("bar.outerCorners")
|
||||
onToggled: checked => Settings.data.bar.outerCorners = checked
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: Settings.data.bar.floating
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.bar.appearance.margins.label")
|
||||
description: I18n.tr("settings.bar.appearance.margins.description")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginL
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.bar.appearance.margins.vertical")
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.bar.marginVertical
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("bar.marginVertical")
|
||||
onMoved: value => Settings.data.bar.marginVertical = value
|
||||
text: Math.round(Settings.data.bar.marginVertical * 100) + "%"
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.bar.appearance.margins.horizontal")
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.bar.marginHorizontal
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("bar.marginHorizontal")
|
||||
onMoved: value => Settings.data.bar.marginHorizontal = value
|
||||
text: Math.ceil(Settings.data.bar.marginHorizontal * 100) + "%"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Noctalia
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
|
||||
// Signal functions for widgets sub-tab
|
||||
function _addWidgetToSection(widgetId, section) {
|
||||
var newWidget = {
|
||||
"id": widgetId
|
||||
};
|
||||
if (BarWidgetRegistry.widgetHasUserSettings(widgetId)) {
|
||||
var metadata = BarWidgetRegistry.widgetMetadata[widgetId];
|
||||
if (metadata) {
|
||||
Object.keys(metadata).forEach(function (key) {
|
||||
if (key !== "allowUserSettings") {
|
||||
newWidget[key] = metadata[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Settings.data.bar.widgets[section].push(newWidget);
|
||||
}
|
||||
|
||||
function _removeWidgetFromSection(section, index) {
|
||||
if (index >= 0 && index < Settings.data.bar.widgets[section].length) {
|
||||
var newArray = Settings.data.bar.widgets[section].slice();
|
||||
var removedWidgets = newArray.splice(index, 1);
|
||||
Settings.data.bar.widgets[section] = newArray;
|
||||
|
||||
if (removedWidgets[0].id === "ControlCenter" && BarService.lookupWidget("ControlCenter") === undefined) {
|
||||
ToastService.showWarning(I18n.tr("toast.missing-control-center.label"), I18n.tr("toast.missing-control-center.description"), 12000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _reorderWidgetInSection(section, fromIndex, toIndex) {
|
||||
if (fromIndex >= 0 && fromIndex < Settings.data.bar.widgets[section].length && toIndex >= 0 && toIndex < Settings.data.bar.widgets[section].length) {
|
||||
var newArray = Settings.data.bar.widgets[section].slice();
|
||||
var item = newArray[fromIndex];
|
||||
newArray.splice(fromIndex, 1);
|
||||
newArray.splice(toIndex, 0, item);
|
||||
Settings.data.bar.widgets[section] = newArray;
|
||||
}
|
||||
}
|
||||
|
||||
function _updateWidgetSettingsInSection(section, index, settings) {
|
||||
Settings.data.bar.widgets[section][index] = settings;
|
||||
}
|
||||
|
||||
function _moveWidgetBetweenSections(fromSection, index, toSection) {
|
||||
if (index >= 0 && index < Settings.data.bar.widgets[fromSection].length) {
|
||||
var widget = Settings.data.bar.widgets[fromSection][index];
|
||||
var sourceArray = Settings.data.bar.widgets[fromSection].slice();
|
||||
sourceArray.splice(index, 1);
|
||||
Settings.data.bar.widgets[fromSection] = sourceArray;
|
||||
var targetArray = Settings.data.bar.widgets[toSection].slice();
|
||||
targetArray.push(widget);
|
||||
Settings.data.bar.widgets[toSection] = targetArray;
|
||||
}
|
||||
}
|
||||
|
||||
function getWidgetLocations(widgetId) {
|
||||
if (!BarService)
|
||||
return [];
|
||||
const instances = BarService.getAllRegisteredWidgets();
|
||||
const locations = {};
|
||||
for (var i = 0; i < instances.length; i++) {
|
||||
if (instances[i].widgetId === widgetId) {
|
||||
const section = instances[i].section;
|
||||
if (section === "left")
|
||||
locations["arrow-bar-to-left"] = true;
|
||||
else if (section === "center")
|
||||
locations["layout-columns"] = true;
|
||||
else if (section === "right")
|
||||
locations["arrow-bar-to-right"] = true;
|
||||
}
|
||||
}
|
||||
return Object.keys(locations);
|
||||
}
|
||||
|
||||
function createBadges(isPlugin, locations) {
|
||||
const badges = [];
|
||||
if (isPlugin) {
|
||||
badges.push({
|
||||
"icon": "plugin",
|
||||
"color": Color.mSecondary
|
||||
});
|
||||
}
|
||||
locations.forEach(function (location) {
|
||||
badges.push({
|
||||
"icon": location,
|
||||
"color": Color.mOnSurfaceVariant
|
||||
});
|
||||
});
|
||||
return badges;
|
||||
}
|
||||
|
||||
function updateAvailableWidgetsModel() {
|
||||
availableWidgets.clear();
|
||||
const widgets = BarWidgetRegistry.getAvailableWidgets();
|
||||
widgets.forEach(entry => {
|
||||
const isPlugin = BarWidgetRegistry.isPluginWidget(entry);
|
||||
let displayName = entry;
|
||||
if (isPlugin) {
|
||||
const pluginId = entry.replace("plugin:", "");
|
||||
const manifest = PluginRegistry.getPluginManifest(pluginId);
|
||||
if (manifest && manifest.name) {
|
||||
displayName = manifest.name;
|
||||
} else {
|
||||
displayName = pluginId;
|
||||
}
|
||||
}
|
||||
availableWidgets.append({
|
||||
"key": entry,
|
||||
"name": displayName,
|
||||
"badges": createBadges(isPlugin, getWidgetLocations(entry))
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: availableWidgets
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
updateAvailableWidgetsModel();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: BarService
|
||||
function onActiveWidgetsChanged() {
|
||||
updateAvailableWidgetsModel();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: BarWidgetRegistry
|
||||
function onPluginWidgetRegistryUpdated() {
|
||||
updateAvailableWidgetsModel();
|
||||
}
|
||||
}
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.bar.tabs.appearance")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.bar.tabs.widgets")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.bar.tabs.monitors")
|
||||
tabIndex: 2
|
||||
checked: subTabBar.currentIndex === 2
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
AppearanceSubTab {}
|
||||
WidgetsSubTab {
|
||||
availableWidgets: availableWidgets
|
||||
addWidgetToSection: root._addWidgetToSection
|
||||
removeWidgetFromSection: root._removeWidgetFromSection
|
||||
reorderWidgetInSection: root._reorderWidgetInSection
|
||||
updateWidgetSettingsInSection: root._updateWidgetSettingsInSection
|
||||
moveWidgetBetweenSections: root._moveWidgetBetweenSections
|
||||
onOpenPluginSettings: manifest => pluginSettingsDialog.openPluginSettings(manifest)
|
||||
}
|
||||
MonitorsSubTab {
|
||||
addMonitor: root.addMonitor
|
||||
removeMonitor: root.removeMonitor
|
||||
}
|
||||
}
|
||||
|
||||
NPluginSettingsPopup {
|
||||
id: pluginSettingsDialog
|
||||
parent: Overlay.overlay
|
||||
showToastOnSave: false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
property var addMonitor
|
||||
property var removeMonitor
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.bar.monitors.section.label")
|
||||
description: I18n.tr("settings.bar.monitors.section.description")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NCheckbox {
|
||||
Layout.fillWidth: true
|
||||
label: modelData.name || "Unknown"
|
||||
description: {
|
||||
const compositorScale = CompositorService.getDisplayScale(modelData.name);
|
||||
I18n.tr("system.monitor-description", {
|
||||
"model": modelData.model,
|
||||
"width": modelData.width * compositorScale,
|
||||
"height": modelData.height * compositorScale,
|
||||
"scale": compositorScale
|
||||
});
|
||||
}
|
||||
checked: (Settings.data.bar.monitors || []).indexOf(modelData.name) !== -1
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
Settings.data.bar.monitors = root.addMonitor(Settings.data.bar.monitors, modelData.name);
|
||||
} else {
|
||||
Settings.data.bar.monitors = root.removeMonitor(Settings.data.bar.monitors, modelData.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
property var availableWidgets
|
||||
property var addWidgetToSection
|
||||
property var removeWidgetFromSection
|
||||
property var reorderWidgetInSection
|
||||
property var updateWidgetSettingsInSection
|
||||
property var moveWidgetBetweenSections
|
||||
|
||||
signal openPluginSettings(var manifest)
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.bar.widgets.section.label")
|
||||
}
|
||||
|
||||
NLabel {
|
||||
description: I18n.tr("settings.bar.widgets.section.description")
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.topMargin: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
// Left Section
|
||||
NSectionEditor {
|
||||
sectionName: "Left"
|
||||
sectionId: "left"
|
||||
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml")
|
||||
widgetRegistry: BarWidgetRegistry
|
||||
widgetModel: Settings.data.bar.widgets.left
|
||||
availableWidgets: root.availableWidgets
|
||||
onAddWidget: (widgetId, section) => root.addWidgetToSection(widgetId, section)
|
||||
onRemoveWidget: (section, index) => root.removeWidgetFromSection(section, index)
|
||||
onReorderWidget: (section, fromIndex, toIndex) => root.reorderWidgetInSection(section, fromIndex, toIndex)
|
||||
onUpdateWidgetSettings: (section, index, settings) => root.updateWidgetSettingsInSection(section, index, settings)
|
||||
onMoveWidget: (fromSection, index, toSection) => root.moveWidgetBetweenSections(fromSection, index, toSection)
|
||||
onOpenPluginSettingsRequested: manifest => root.openPluginSettings(manifest)
|
||||
}
|
||||
|
||||
// Center Section
|
||||
NSectionEditor {
|
||||
sectionName: "Center"
|
||||
sectionId: "center"
|
||||
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml")
|
||||
widgetRegistry: BarWidgetRegistry
|
||||
widgetModel: Settings.data.bar.widgets.center
|
||||
availableWidgets: root.availableWidgets
|
||||
onAddWidget: (widgetId, section) => root.addWidgetToSection(widgetId, section)
|
||||
onRemoveWidget: (section, index) => root.removeWidgetFromSection(section, index)
|
||||
onReorderWidget: (section, fromIndex, toIndex) => root.reorderWidgetInSection(section, fromIndex, toIndex)
|
||||
onUpdateWidgetSettings: (section, index, settings) => root.updateWidgetSettingsInSection(section, index, settings)
|
||||
onMoveWidget: (fromSection, index, toSection) => root.moveWidgetBetweenSections(fromSection, index, toSection)
|
||||
onOpenPluginSettingsRequested: manifest => root.openPluginSettings(manifest)
|
||||
}
|
||||
|
||||
// Right Section
|
||||
NSectionEditor {
|
||||
sectionName: "Right"
|
||||
sectionId: "right"
|
||||
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml")
|
||||
widgetRegistry: BarWidgetRegistry
|
||||
widgetModel: Settings.data.bar.widgets.right
|
||||
availableWidgets: root.availableWidgets
|
||||
onAddWidget: (widgetId, section) => root.addWidgetToSection(widgetId, section)
|
||||
onRemoveWidget: (section, index) => root.removeWidgetFromSection(section, index)
|
||||
onReorderWidget: (section, fromIndex, toIndex) => root.reorderWidgetInSection(section, fromIndex, toIndex)
|
||||
onUpdateWidgetSettings: (section, index, settings) => root.updateWidgetSettingsInSection(section, index, settings)
|
||||
onMoveWidget: (fromSection, index, toSection) => root.moveWidgetBetweenSections(fromSection, index, toSection)
|
||||
onOpenPluginSettingsRequested: manifest => root.openPluginSettings(manifest)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,526 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.Noctalia
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.bar.appearance.section.label")
|
||||
description: I18n.tr("settings.bar.appearance.section.description")
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.bar.appearance.position.label")
|
||||
description: I18n.tr("settings.bar.appearance.position.description")
|
||||
model: [
|
||||
{
|
||||
"key": "top",
|
||||
"name": I18n.tr("options.bar.position.top")
|
||||
},
|
||||
{
|
||||
"key": "bottom",
|
||||
"name": I18n.tr("options.bar.position.bottom")
|
||||
},
|
||||
{
|
||||
"key": "left",
|
||||
"name": I18n.tr("options.bar.position.left")
|
||||
},
|
||||
{
|
||||
"key": "right",
|
||||
"name": I18n.tr("options.bar.position.right")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.bar.position
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("bar.position")
|
||||
onSelected: key => Settings.data.bar.position = key
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.bar.appearance.density.label")
|
||||
description: I18n.tr("settings.bar.appearance.density.description")
|
||||
model: [
|
||||
{
|
||||
"key": "mini",
|
||||
"name": I18n.tr("options.bar.density.mini")
|
||||
},
|
||||
{
|
||||
"key": "compact",
|
||||
"name": I18n.tr("options.bar.density.compact")
|
||||
},
|
||||
{
|
||||
"key": "default",
|
||||
"name": I18n.tr("options.bar.density.default")
|
||||
},
|
||||
{
|
||||
"key": "comfortable",
|
||||
"name": I18n.tr("options.bar.density.comfortable")
|
||||
},
|
||||
{
|
||||
"key": "spacious",
|
||||
"name": I18n.tr("options.bar.density.spacious")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.bar.density
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("bar.density")
|
||||
onSelected: key => Settings.data.bar.density = key
|
||||
}
|
||||
|
||||
// Use Separate Bar Opacity Toggle
|
||||
NToggle {
|
||||
label: I18n.tr("settings.bar.appearance.use-separate-opacity.label")
|
||||
description: I18n.tr("settings.bar.appearance.use-separate-opacity.description")
|
||||
checked: Settings.data.bar.useSeparateOpacity
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("bar.useSeparateOpacity")
|
||||
onToggled: checked => Settings.data.bar.useSeparateOpacity = checked
|
||||
}
|
||||
|
||||
// Bar Background Opacity (only visible when separate opacity is enabled)
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
visible: Settings.data.bar.useSeparateOpacity
|
||||
label: I18n.tr("settings.bar.appearance.background-opacity.label")
|
||||
description: I18n.tr("settings.bar.appearance.background-opacity.description")
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.bar.backgroundOpacity
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("bar.backgroundOpacity")
|
||||
onMoved: value => Settings.data.bar.backgroundOpacity = value
|
||||
text: Math.floor(Settings.data.bar.backgroundOpacity * 100) + "%"
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.bar.appearance.show-outline.label")
|
||||
description: I18n.tr("settings.bar.appearance.show-outline.description")
|
||||
checked: Settings.data.bar.showOutline
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("bar.showOutline")
|
||||
onToggled: checked => Settings.data.bar.showOutline = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.bar.appearance.show-capsule.label")
|
||||
description: I18n.tr("settings.bar.appearance.show-capsule.description")
|
||||
checked: Settings.data.bar.showCapsule
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("bar.showCapsule")
|
||||
onToggled: checked => Settings.data.bar.showCapsule = checked
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginXXS
|
||||
visible: Settings.data.bar.showCapsule
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.bar.appearance.capsule-opacity.label")
|
||||
description: I18n.tr("settings.bar.appearance.capsule-opacity.description")
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.bar.capsuleOpacity
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("bar.capsuleOpacity")
|
||||
onMoved: value => Settings.data.bar.capsuleOpacity = value
|
||||
text: Math.floor(Settings.data.bar.capsuleOpacity * 100) + "%"
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.bar.appearance.floating.label")
|
||||
description: I18n.tr("settings.bar.appearance.floating.description")
|
||||
checked: Settings.data.bar.floating
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("bar.floating")
|
||||
onToggled: checked => {
|
||||
Settings.data.bar.floating = checked;
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.bar.appearance.outer-corners.label")
|
||||
description: I18n.tr("settings.bar.appearance.outer-corners.description")
|
||||
checked: Settings.data.bar.outerCorners
|
||||
visible: !Settings.data.bar.floating
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("bar.outerCorners")
|
||||
onToggled: checked => Settings.data.bar.outerCorners = checked
|
||||
}
|
||||
|
||||
// Floating bar options - only show when floating is enabled
|
||||
ColumnLayout {
|
||||
visible: Settings.data.bar.floating
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.bar.appearance.margins.label")
|
||||
description: I18n.tr("settings.bar.appearance.margins.description")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginL
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.bar.appearance.margins.vertical")
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.bar.marginVertical
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("bar.marginVertical")
|
||||
onMoved: value => Settings.data.bar.marginVertical = value
|
||||
text: Math.round(Settings.data.bar.marginVertical * 100) + "%"
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.bar.appearance.margins.horizontal")
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.bar.marginHorizontal
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("bar.marginHorizontal")
|
||||
onMoved: value => Settings.data.bar.marginHorizontal = value
|
||||
text: Math.ceil(Settings.data.bar.marginHorizontal * 100) + "%"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// Widgets Management Section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.bar.widgets.section.label")
|
||||
}
|
||||
|
||||
NLabel {
|
||||
description: I18n.tr("settings.bar.widgets.section.description")
|
||||
}
|
||||
|
||||
// Bar Sections
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.topMargin: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
// Left Section
|
||||
NSectionEditor {
|
||||
sectionName: "Left"
|
||||
sectionId: "left"
|
||||
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml")
|
||||
widgetRegistry: BarWidgetRegistry
|
||||
widgetModel: Settings.data.bar.widgets.left
|
||||
availableWidgets: availableWidgets
|
||||
onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section)
|
||||
onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index)
|
||||
onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex)
|
||||
onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings)
|
||||
onMoveWidget: (fromSection, index, toSection) => _moveWidgetBetweenSections(fromSection, index, toSection)
|
||||
onOpenPluginSettingsRequested: manifest => pluginSettingsDialog.openPluginSettings(manifest)
|
||||
}
|
||||
|
||||
// Center Section
|
||||
NSectionEditor {
|
||||
sectionName: "Center"
|
||||
sectionId: "center"
|
||||
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml")
|
||||
widgetRegistry: BarWidgetRegistry
|
||||
widgetModel: Settings.data.bar.widgets.center
|
||||
availableWidgets: availableWidgets
|
||||
onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section)
|
||||
onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index)
|
||||
onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex)
|
||||
onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings)
|
||||
onMoveWidget: (fromSection, index, toSection) => _moveWidgetBetweenSections(fromSection, index, toSection)
|
||||
onOpenPluginSettingsRequested: manifest => pluginSettingsDialog.openPluginSettings(manifest)
|
||||
}
|
||||
|
||||
// Right Section
|
||||
NSectionEditor {
|
||||
sectionName: "Right"
|
||||
sectionId: "right"
|
||||
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml")
|
||||
widgetRegistry: BarWidgetRegistry
|
||||
widgetModel: Settings.data.bar.widgets.right
|
||||
availableWidgets: availableWidgets
|
||||
onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section)
|
||||
onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index)
|
||||
onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex)
|
||||
onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings)
|
||||
onMoveWidget: (fromSection, index, toSection) => _moveWidgetBetweenSections(fromSection, index, toSection)
|
||||
onOpenPluginSettingsRequested: manifest => pluginSettingsDialog.openPluginSettings(manifest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// Monitor Configuration
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.bar.monitors.section.label")
|
||||
description: I18n.tr("settings.bar.monitors.section.description")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NCheckbox {
|
||||
Layout.fillWidth: true
|
||||
label: modelData.name || "Unknown"
|
||||
description: {
|
||||
const compositorScale = CompositorService.getDisplayScale(modelData.name);
|
||||
I18n.tr("system.monitor-description", {
|
||||
"model": modelData.model,
|
||||
"width": modelData.width * compositorScale,
|
||||
"height": modelData.height * compositorScale,
|
||||
"scale": compositorScale
|
||||
});
|
||||
}
|
||||
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.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// Signal functions
|
||||
function _addWidgetToSection(widgetId, section) {
|
||||
var newWidget = {
|
||||
"id": widgetId
|
||||
};
|
||||
if (BarWidgetRegistry.widgetHasUserSettings(widgetId)) {
|
||||
var metadata = BarWidgetRegistry.widgetMetadata[widgetId];
|
||||
if (metadata) {
|
||||
Object.keys(metadata).forEach(function (key) {
|
||||
if (key !== "allowUserSettings") {
|
||||
newWidget[key] = metadata[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Settings.data.bar.widgets[section].push(newWidget);
|
||||
}
|
||||
|
||||
function _removeWidgetFromSection(section, index) {
|
||||
if (index >= 0 && index < Settings.data.bar.widgets[section].length) {
|
||||
var newArray = Settings.data.bar.widgets[section].slice();
|
||||
var removedWidgets = newArray.splice(index, 1);
|
||||
Settings.data.bar.widgets[section] = newArray;
|
||||
|
||||
// Check that we still have a control center
|
||||
if (removedWidgets[0].id === "ControlCenter" && BarService.lookupWidget("ControlCenter") === undefined) {
|
||||
ToastService.showWarning(I18n.tr("toast.missing-control-center.label"), I18n.tr("toast.missing-control-center.description"), 12000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _reorderWidgetInSection(section, fromIndex, toIndex) {
|
||||
if (fromIndex >= 0 && fromIndex < Settings.data.bar.widgets[section].length && toIndex >= 0 && toIndex < Settings.data.bar.widgets[section].length) {
|
||||
|
||||
// Create a new array to avoid modifying the original
|
||||
var newArray = Settings.data.bar.widgets[section].slice();
|
||||
var item = newArray[fromIndex];
|
||||
newArray.splice(fromIndex, 1);
|
||||
newArray.splice(toIndex, 0, item);
|
||||
|
||||
Settings.data.bar.widgets[section] = newArray;
|
||||
//Logger.i("BarTab", "Widget reordered. New array:", JSON.stringify(newArray))
|
||||
}
|
||||
}
|
||||
|
||||
function _updateWidgetSettingsInSection(section, index, settings) {
|
||||
// Update the widget settings in the Settings data
|
||||
Settings.data.bar.widgets[section][index] = settings;
|
||||
//Logger.i("BarTab", `Updated widget settings for ${settings.id} in ${section} section`)
|
||||
}
|
||||
|
||||
function _moveWidgetBetweenSections(fromSection, index, toSection) {
|
||||
// Get the widget from the source section
|
||||
if (index >= 0 && index < Settings.data.bar.widgets[fromSection].length) {
|
||||
var widget = Settings.data.bar.widgets[fromSection][index];
|
||||
|
||||
// Remove from source section
|
||||
var sourceArray = Settings.data.bar.widgets[fromSection].slice();
|
||||
sourceArray.splice(index, 1);
|
||||
Settings.data.bar.widgets[fromSection] = sourceArray;
|
||||
|
||||
// Add to target section
|
||||
var targetArray = Settings.data.bar.widgets[toSection].slice();
|
||||
targetArray.push(widget);
|
||||
Settings.data.bar.widgets[toSection] = targetArray;
|
||||
|
||||
//Logger.i("BarTab", `Moved widget ${widget.id} from ${fromSection} to ${toSection}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Data model functions
|
||||
function getWidgetLocations(widgetId) {
|
||||
if (!BarService)
|
||||
return [];
|
||||
const instances = BarService.getAllRegisteredWidgets();
|
||||
const locations = {};
|
||||
for (var i = 0; i < instances.length; i++) {
|
||||
if (instances[i].widgetId === widgetId) {
|
||||
const section = instances[i].section;
|
||||
if (section === "left")
|
||||
locations["arrow-bar-to-left"] = true;
|
||||
else if (section === "center")
|
||||
locations["layout-columns"] = true;
|
||||
else if (section === "right")
|
||||
locations["arrow-bar-to-right"] = true;
|
||||
}
|
||||
}
|
||||
return Object.keys(locations);
|
||||
}
|
||||
|
||||
function createBadges(isPlugin, locations) {
|
||||
const badges = [];
|
||||
|
||||
// Add plugin badge first (with custom color)
|
||||
if (isPlugin) {
|
||||
badges.push({
|
||||
"icon": "plugin",
|
||||
"color": Color.mSecondary
|
||||
});
|
||||
}
|
||||
|
||||
// Add location badges (with default styling)
|
||||
locations.forEach(function (location) {
|
||||
badges.push({
|
||||
"icon": location,
|
||||
"color": Color.mOnSurfaceVariant
|
||||
});
|
||||
});
|
||||
|
||||
return badges;
|
||||
}
|
||||
|
||||
function updateAvailableWidgetsModel() {
|
||||
availableWidgets.clear();
|
||||
const widgets = BarWidgetRegistry.getAvailableWidgets();
|
||||
widgets.forEach(entry => {
|
||||
const isPlugin = BarWidgetRegistry.isPluginWidget(entry);
|
||||
let displayName = entry;
|
||||
|
||||
// For plugin widgets, strip the "plugin:" prefix and try to get the actual plugin name
|
||||
if (isPlugin) {
|
||||
const pluginId = entry.replace("plugin:", "");
|
||||
const manifest = PluginRegistry.getPluginManifest(pluginId);
|
||||
if (manifest && manifest.name) {
|
||||
displayName = manifest.name;
|
||||
} else {
|
||||
// Fallback: just strip the prefix
|
||||
displayName = pluginId;
|
||||
}
|
||||
}
|
||||
|
||||
availableWidgets.append({
|
||||
"key": entry,
|
||||
"name": displayName,
|
||||
"badges": createBadges(isPlugin, getWidgetLocations(entry))
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Base list model for all combo boxes
|
||||
ListModel {
|
||||
id: availableWidgets
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
updateAvailableWidgetsModel();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: BarService
|
||||
function onActiveWidgetsChanged() {
|
||||
updateAvailableWidgetsModel();
|
||||
}
|
||||
}
|
||||
|
||||
// Update available widgets when plugin widgets are registered/unregistered
|
||||
Connections {
|
||||
target: BarWidgetRegistry
|
||||
function onPluginWidgetRegistryUpdated() {
|
||||
updateAvailableWidgetsModel();
|
||||
}
|
||||
}
|
||||
|
||||
// Shared Plugin Settings Popup
|
||||
NPluginSettingsPopup {
|
||||
id: pluginSettingsDialog
|
||||
parent: Overlay.overlay
|
||||
showToastOnSave: false
|
||||
}
|
||||
}
|
||||
@@ -12,15 +12,13 @@ import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
// Cache for scheme JSON (can be flat or {dark, light})
|
||||
property var schemeColorsCache: ({})
|
||||
property int cacheVersion: 0 // Increment to trigger UI updates
|
||||
spacing: 0
|
||||
|
||||
// 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) {
|
||||
@@ -35,82 +33,6 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
spacing: Style.marginL
|
||||
|
||||
// Helper function to extract scheme name from path
|
||||
function extractSchemeName(schemePath) {
|
||||
var pathParts = schemePath.split("/");
|
||||
var filename = pathParts[pathParts.length - 1];
|
||||
var schemeName = filename.replace(".json", "");
|
||||
|
||||
if (schemeName === "Noctalia-default") {
|
||||
schemeName = "Noctalia (default)";
|
||||
} else if (schemeName === "Noctalia-legacy") {
|
||||
schemeName = "Noctalia (legacy)";
|
||||
} else if (schemeName === "Tokyo-Night") {
|
||||
schemeName = "Tokyo Night";
|
||||
} else if (schemeName === "Rosepine") {
|
||||
schemeName = "Rose Pine";
|
||||
}
|
||||
|
||||
return schemeName;
|
||||
}
|
||||
|
||||
// Helper function to get color from scheme file (supports dark/light variants)
|
||||
function getSchemeColor(schemeName, colorKey) {
|
||||
// Access cache version to create dependency
|
||||
var _ = cacheVersion;
|
||||
|
||||
if (schemeColorsCache[schemeName]) {
|
||||
var entry = schemeColorsCache[schemeName];
|
||||
var variant = entry;
|
||||
|
||||
// Check if scheme has dark/light variants
|
||||
if (entry.dark || entry.light) {
|
||||
variant = Settings.data.colorSchemes.darkMode ? (entry.dark || entry.light) : (entry.light || entry.dark);
|
||||
}
|
||||
|
||||
if (variant && variant[colorKey]) {
|
||||
return variant[colorKey];
|
||||
}
|
||||
}
|
||||
|
||||
// Return visible defaults while loading
|
||||
if (colorKey === "mSurface")
|
||||
return Color.mSurfaceVariant;
|
||||
if (colorKey === "mPrimary")
|
||||
return Color.mPrimary;
|
||||
if (colorKey === "mSecondary")
|
||||
return Color.mSecondary;
|
||||
if (colorKey === "mTertiary")
|
||||
return Color.mTertiary;
|
||||
if (colorKey === "mError")
|
||||
return Color.mError;
|
||||
return Color.mOnSurfaceVariant;
|
||||
}
|
||||
|
||||
// This function is called by the FileView Repeater when a scheme file is loaded
|
||||
function schemeLoaded(schemeName, jsonData) {
|
||||
var value = jsonData || {};
|
||||
schemeColorsCache[schemeName] = value;
|
||||
// Force UI update by incrementing cache version
|
||||
cacheVersion++;
|
||||
}
|
||||
|
||||
// Function to open download popup
|
||||
function openDownloadPopup() {
|
||||
downloadPopupLoader.open();
|
||||
}
|
||||
|
||||
// When the list of available schemes changes, clear the cache
|
||||
Connections {
|
||||
target: ColorSchemeService
|
||||
function onSchemesChanged() {
|
||||
schemeColorsCache = {};
|
||||
cacheVersion++;
|
||||
}
|
||||
}
|
||||
|
||||
// Simple process to check if matugen exists
|
||||
Process {
|
||||
id: matugenCheck
|
||||
@@ -131,799 +53,65 @@ ColumnLayout {
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
|
||||
// A non-visual Item to host the Repeater that loads the color scheme files
|
||||
// Download popup
|
||||
Loader {
|
||||
id: downloadPopupLoader
|
||||
active: false
|
||||
sourceComponent: SchemeDownloader {
|
||||
parent: Overlay.overlay
|
||||
}
|
||||
|
||||
property bool pendingOpen: false
|
||||
|
||||
function open() {
|
||||
pendingOpen = true;
|
||||
active = true;
|
||||
if (item) {
|
||||
item.open();
|
||||
pendingOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
onItemChanged: {
|
||||
if (item && pendingOpen) {
|
||||
item.open();
|
||||
pendingOpen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.color-scheme.tabs.colors")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.color-scheme.tabs.templates")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: fileLoaders
|
||||
visible: false
|
||||
|
||||
Repeater {
|
||||
model: ColorSchemeService.schemes
|
||||
delegate: Item {
|
||||
FileView {
|
||||
path: modelData
|
||||
blockLoading: false
|
||||
onLoaded: {
|
||||
var schemeName = root.extractSchemeName(path);
|
||||
|
||||
try {
|
||||
var jsonData = JSON.parse(text());
|
||||
root.schemeLoaded(schemeName, jsonData);
|
||||
} catch (e) {
|
||||
Logger.w("ColorSchemeTab", "Failed to parse JSON for scheme:", schemeName, e);
|
||||
root.schemeLoaded(schemeName, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main Toggles - Dark Mode / Matugen
|
||||
NHeader {
|
||||
label: I18n.tr("settings.color-scheme.color-source.section.label")
|
||||
description: I18n.tr("settings.color-scheme.color-source.section.description")
|
||||
}
|
||||
|
||||
// Dark Mode Toggle
|
||||
NToggle {
|
||||
label: I18n.tr("settings.color-scheme.dark-mode.switch.label")
|
||||
description: I18n.tr("settings.color-scheme.dark-mode.switch.description")
|
||||
checked: Settings.data.colorSchemes.darkMode
|
||||
onToggled: checked => {
|
||||
Settings.data.colorSchemes.darkMode = checked;
|
||||
root.cacheVersion++; // Force UI update for dark/light variants
|
||||
}
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.color-scheme.dark-mode.mode.label")
|
||||
description: I18n.tr("settings.color-scheme.dark-mode.mode.description")
|
||||
|
||||
model: [
|
||||
{
|
||||
"name": I18n.tr("settings.color-scheme.dark-mode.mode.off"),
|
||||
"key": "off"
|
||||
},
|
||||
{
|
||||
"name": I18n.tr("settings.color-scheme.dark-mode.mode.manual"),
|
||||
"key": "manual"
|
||||
},
|
||||
{
|
||||
"name": I18n.tr("settings.color-scheme.dark-mode.mode.location"),
|
||||
"key": "location"
|
||||
}
|
||||
]
|
||||
|
||||
currentKey: Settings.data.colorSchemes.schedulingMode
|
||||
|
||||
onSelected: key => {
|
||||
Settings.data.colorSchemes.schedulingMode = key;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
// Manual scheduling
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
visible: Settings.data.colorSchemes.schedulingMode === "manual"
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.display.night-light.manual-schedule.label")
|
||||
description: I18n.tr("settings.display.night-light.manual-schedule.description")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: false
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.display.night-light.manual-schedule.sunrise")
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
model: timeOptions
|
||||
currentKey: Settings.data.colorSchemes.manualSunrise
|
||||
placeholder: I18n.tr("settings.display.night-light.manual-schedule.select-start")
|
||||
onSelected: key => Settings.data.colorSchemes.manualSunrise = key
|
||||
minimumWidth: 120
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 20
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.display.night-light.manual-schedule.sunset")
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
model: timeOptions
|
||||
currentKey: Settings.data.colorSchemes.manualSunset
|
||||
placeholder: I18n.tr("settings.display.night-light.manual-schedule.select-stop")
|
||||
onSelected: key => Settings.data.colorSchemes.manualSunset = key
|
||||
minimumWidth: 120
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use Wallpaper Colors
|
||||
NToggle {
|
||||
label: I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label")
|
||||
description: I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.description")
|
||||
enabled: ProgramCheckerService.matugenAvailable
|
||||
checked: Settings.data.colorSchemes.useWallpaperColors
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
matugenCheck.running = true;
|
||||
} else {
|
||||
Settings.data.colorSchemes.useWallpaperColors = false;
|
||||
ToastService.showNotice(I18n.tr("toast.wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.disabled"), "settings-color-scheme");
|
||||
|
||||
if (Settings.data.colorSchemes.predefinedScheme) {
|
||||
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Matugen Scheme Type Selection [Descriptions sourced from DankMaterialShell]
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.color-scheme.color-source.matugen-scheme-type.label")
|
||||
description: I18n.tr("settings.color-scheme.color-source.matugen-scheme-type.description")
|
||||
enabled: Settings.data.colorSchemes.useWallpaperColors
|
||||
visible: Settings.data.colorSchemes.useWallpaperColors
|
||||
|
||||
model: [
|
||||
{
|
||||
"key": "scheme-content",
|
||||
"name": "Content"
|
||||
},
|
||||
{
|
||||
"key": "scheme-expressive",
|
||||
"name": "Expressive"
|
||||
},
|
||||
{
|
||||
"key": "scheme-fidelity",
|
||||
"name": "Fidelity"
|
||||
},
|
||||
{
|
||||
"key": "scheme-fruit-salad",
|
||||
"name": "Fruit Salad"
|
||||
},
|
||||
{
|
||||
"key": "scheme-monochrome",
|
||||
"name": "Monochrome"
|
||||
},
|
||||
{
|
||||
"key": "scheme-neutral",
|
||||
"name": "Neutral"
|
||||
},
|
||||
{
|
||||
"key": "scheme-rainbow",
|
||||
"name": "Rainbow"
|
||||
},
|
||||
{
|
||||
"key": "scheme-tonal-spot",
|
||||
"name": "Tonal Spot"
|
||||
}
|
||||
]
|
||||
|
||||
currentKey: Settings.data.colorSchemes.matugenSchemeType
|
||||
|
||||
onSelected: key => {
|
||||
Settings.data.colorSchemes.matugenSchemeType = key;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("colorSchemes.matugenSchemeType")
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
visible: !Settings.data.colorSchemes.useWallpaperColors
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
// Predefined Color Schemes
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
visible: !Settings.data.colorSchemes.useWallpaperColors
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.color-scheme.predefined.section.label")
|
||||
description: I18n.tr("settings.color-scheme.predefined.section.description")
|
||||
Layout.fillWidth: true
|
||||
ColorsSubTab {
|
||||
timeOptions: timeOptions
|
||||
onCheckMatugen: matugenCheck.running = true
|
||||
onOpenDownloadPopup: downloadPopupLoader.open()
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("settings.color-scheme.download.button")
|
||||
icon: "download"
|
||||
onClicked: root.openDownloadPopup()
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
|
||||
// Download popup
|
||||
Loader {
|
||||
id: downloadPopupLoader
|
||||
active: false
|
||||
sourceComponent: SchemeDownloader {
|
||||
parent: Overlay.overlay
|
||||
}
|
||||
|
||||
property bool pendingOpen: false
|
||||
|
||||
function open() {
|
||||
pendingOpen = true;
|
||||
active = true;
|
||||
if (item) {
|
||||
item.open();
|
||||
pendingOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
onItemChanged: {
|
||||
if (item && pendingOpen) {
|
||||
item.open();
|
||||
pendingOpen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Color Schemes Grid
|
||||
GridLayout {
|
||||
columns: 2
|
||||
rowSpacing: Style.marginM
|
||||
columnSpacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
Repeater {
|
||||
model: ColorSchemeService.schemes
|
||||
|
||||
Rectangle {
|
||||
id: schemeItem
|
||||
|
||||
property string schemePath: modelData
|
||||
property string schemeName: root.extractSchemeName(modelData)
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
height: 50 * Style.uiScaleRatio
|
||||
radius: Style.radiusS
|
||||
color: root.getSchemeColor(schemeName, "mSurface")
|
||||
border.width: Style.borderL
|
||||
border.color: {
|
||||
if (Settings.data.colorSchemes.predefinedScheme === schemeName) {
|
||||
return Color.mSecondary;
|
||||
}
|
||||
if (itemMouseArea.containsMouse) {
|
||||
return Color.mHover;
|
||||
}
|
||||
return Color.mOutline;
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: scheme
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: schemeItem.schemeName
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
wrapMode: Text.WordWrap
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
property int diameter: 16 * Style.uiScaleRatio
|
||||
|
||||
Rectangle {
|
||||
width: scheme.diameter
|
||||
height: scheme.diameter
|
||||
radius: scheme.diameter * 0.5
|
||||
color: root.getSchemeColor(schemeItem.schemeName, "mPrimary")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: scheme.diameter
|
||||
height: scheme.diameter
|
||||
radius: scheme.diameter * 0.5
|
||||
color: root.getSchemeColor(schemeItem.schemeName, "mSecondary")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: scheme.diameter
|
||||
height: scheme.diameter
|
||||
radius: scheme.diameter * 0.5
|
||||
color: root.getSchemeColor(schemeItem.schemeName, "mTertiary")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: scheme.diameter
|
||||
height: scheme.diameter
|
||||
radius: scheme.diameter * 0.5
|
||||
color: root.getSchemeColor(schemeItem.schemeName, "mError")
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: itemMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
Settings.data.colorSchemes.useWallpaperColors = false;
|
||||
Logger.i("ColorSchemeTab", "Disabled wallpaper colors");
|
||||
|
||||
Settings.data.colorSchemes.predefinedScheme = schemeItem.schemeName;
|
||||
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme);
|
||||
}
|
||||
}
|
||||
|
||||
// Selection indicator
|
||||
Rectangle {
|
||||
visible: (Settings.data.colorSchemes.predefinedScheme === schemeItem.schemeName)
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: 0
|
||||
anchors.topMargin: -3
|
||||
width: 20
|
||||
height: 20
|
||||
radius: Math.min(Style.radiusL, width / 2)
|
||||
color: Color.mSecondary
|
||||
border.width: Style.borderS
|
||||
border.color: Color.mOnSecondary
|
||||
|
||||
NIcon {
|
||||
icon: "check"
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSecondary
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate templates for predefined schemes
|
||||
NCheckbox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.color-scheme.predefined.generate-templates.label")
|
||||
description: I18n.tr("settings.color-scheme.predefined.generate-templates.description")
|
||||
checked: Settings.data.colorSchemes.generateTemplatesForPredefined
|
||||
onToggled: checked => {
|
||||
Settings.data.colorSchemes.generateTemplatesForPredefined = checked;
|
||||
if (!Settings.data.colorSchemes.useWallpaperColors && Settings.data.colorSchemes.predefinedScheme) {
|
||||
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme);
|
||||
}
|
||||
}
|
||||
Layout.topMargin: Style.marginL
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// Template toggles organized by category
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginL
|
||||
visible: Settings.data.colorSchemes.useWallpaperColors || Settings.data.colorSchemes.generateTemplatesForPredefined
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.color-scheme.templates.section.label")
|
||||
description: I18n.tr("settings.color-scheme.templates.section.description")
|
||||
}
|
||||
|
||||
// UI Components
|
||||
NCollapsible {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.color-scheme.templates.ui.label")
|
||||
description: I18n.tr("settings.color-scheme.templates.ui.description")
|
||||
defaultExpanded: false
|
||||
|
||||
NCheckbox {
|
||||
label: "GTK"
|
||||
description: I18n.tr("settings.color-scheme.templates.ui.gtk.description", {
|
||||
"filepath": "~/.config/gtk-3.0/gtk.css & ~/.config/gtk-4.0/gtk.css"
|
||||
})
|
||||
checked: Settings.data.templates.gtk
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.gtk = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Qt"
|
||||
description: I18n.tr("settings.color-scheme.templates.ui.qt.description", {
|
||||
"filepath": "~/.config/qt5ct/colors/noctalia.conf & ~/.config/qt6ct/colors/noctalia.conf"
|
||||
})
|
||||
checked: Settings.data.templates.qt
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.qt = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "KColorScheme"
|
||||
description: I18n.tr("settings.color-scheme.templates.ui.kcolorscheme.description", {
|
||||
"filepath": "~/.local/share/color-schemes/noctalia.colors"
|
||||
})
|
||||
checked: Settings.data.templates.kcolorscheme
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.kcolorscheme = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compositors
|
||||
NCollapsible {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.color-scheme.templates.compositors.label")
|
||||
description: I18n.tr("settings.color-scheme.templates.compositors.description")
|
||||
defaultExpanded: false
|
||||
|
||||
NCheckbox {
|
||||
label: "Niri"
|
||||
description: I18n.tr("settings.color-scheme.templates.compositors.niri.description", {
|
||||
"filepath": "~/.config/niri/noctalia.kdl"
|
||||
})
|
||||
checked: Settings.data.templates.niri
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.niri = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Hyprland"
|
||||
description: I18n.tr("settings.color-scheme.templates.compositors.hyprland.description", {
|
||||
"filepath": "~/.config/hypr/noctalia/noctalia-colors.conf"
|
||||
})
|
||||
checked: Settings.data.templates.hyprland
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.hyprland = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Mango"
|
||||
description: I18n.tr("settings.color-scheme.templates.compositors.mango.description", {
|
||||
"filepath": "~/.config/mango/noctalia.conf"
|
||||
})
|
||||
checked: Settings.data.templates.mango
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.mango = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Terminal Emulators
|
||||
NCollapsible {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.color-scheme.templates.terminal.label")
|
||||
description: I18n.tr("settings.color-scheme.templates.terminal.description")
|
||||
defaultExpanded: false
|
||||
|
||||
NCheckbox {
|
||||
label: "Alacritty"
|
||||
description: I18n.tr("settings.color-scheme.templates.terminal.alacritty.description", {
|
||||
"filepath": "~/.config/alacritty/themes/noctalia"
|
||||
})
|
||||
checked: Settings.data.templates.alacritty
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.alacritty = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Kitty"
|
||||
description: I18n.tr("settings.color-scheme.templates.terminal.kitty.description", {
|
||||
"filepath": "~/.config/kitty/themes/noctalia.conf"
|
||||
})
|
||||
checked: Settings.data.templates.kitty
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.kitty = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Ghostty"
|
||||
description: I18n.tr("settings.color-scheme.templates.terminal.ghostty.description", {
|
||||
"filepath": "~/.config/ghostty/themes/noctalia"
|
||||
})
|
||||
checked: Settings.data.templates.ghostty
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.ghostty = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Foot"
|
||||
description: I18n.tr("settings.color-scheme.templates.terminal.foot.description", {
|
||||
"filepath": "~/.config/foot/themes/noctalia"
|
||||
})
|
||||
checked: Settings.data.templates.foot
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.foot = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Wezterm"
|
||||
description: I18n.tr("settings.color-scheme.templates.terminal.wezterm.description", {
|
||||
"filepath": "~/.config/wezterm/colors/Noctalia.toml"
|
||||
})
|
||||
checked: Settings.data.templates.wezterm
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.wezterm = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Applications
|
||||
NCollapsible {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.color-scheme.templates.programs.label")
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.description")
|
||||
defaultExpanded: false
|
||||
|
||||
NCheckbox {
|
||||
label: "Fuzzel"
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.fuzzel.description", {
|
||||
"filepath": "~/.config/fuzzel/themes/noctalia"
|
||||
})
|
||||
checked: Settings.data.templates.fuzzel
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.fuzzel = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
// Discord clients - single toggle with dynamic description
|
||||
NCheckbox {
|
||||
id: discordToggle
|
||||
label: "Discord"
|
||||
description: {
|
||||
if (ProgramCheckerService.availableDiscordClients.length === 0) {
|
||||
return I18n.tr("settings.color-scheme.templates.programs.discord.description-missing");
|
||||
} else {
|
||||
// Show detected clients
|
||||
var clientInfo = [];
|
||||
for (var i = 0; i < ProgramCheckerService.availableDiscordClients.length; i++) {
|
||||
var client = ProgramCheckerService.availableDiscordClients[i];
|
||||
clientInfo.push(client.name.charAt(0).toUpperCase() + client.name.slice(1));
|
||||
}
|
||||
return I18n.tr("settings.color-scheme.templates.programs.discord.description-detected", {
|
||||
"clients": clientInfo.join(", ")
|
||||
});
|
||||
}
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: -1
|
||||
checked: Settings.data.templates.discord
|
||||
enabled: ProgramCheckerService.availableDiscordClients.length > 0
|
||||
onToggled: checked => {
|
||||
// Set unified discord property
|
||||
Settings.data.templates.discord = checked;
|
||||
if (ProgramCheckerService.availableDiscordClients.length > 0) {
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Pywalfox"
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.pywalfox.description", {
|
||||
"filepath": "~/.cache/wal/colors.json"
|
||||
})
|
||||
checked: Settings.data.templates.pywalfox
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.pywalfox = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
NCheckbox {
|
||||
label: "Vicinae"
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.vicinae.description", {
|
||||
"filepath": "~/.local/share/vicinae/themes/matugen.toml"
|
||||
})
|
||||
checked: Settings.data.templates.vicinae
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.vicinae = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
NCheckbox {
|
||||
label: "Walker"
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.walker.description", {
|
||||
"filepath": "~/.config/walker/style.css"
|
||||
})
|
||||
checked: Settings.data.templates.walker
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.walker = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
// Code clients - single toggle with dynamic description
|
||||
NCheckbox {
|
||||
id: codeToggle
|
||||
label: "Code"
|
||||
description: {
|
||||
if (ProgramCheckerService.availableCodeClients.length === 0) {
|
||||
return I18n.tr("settings.color-scheme.templates.programs.code.description-missing");
|
||||
} else {
|
||||
// Show detected clients
|
||||
var clientInfo = [];
|
||||
for (var i = 0; i < ProgramCheckerService.availableCodeClients.length; i++) {
|
||||
var client = ProgramCheckerService.availableCodeClients[i];
|
||||
// Capitalize first letter and format nicely
|
||||
var clientName = client.name === "code" ? "VSCode" : "VSCodium";
|
||||
clientInfo.push(clientName);
|
||||
}
|
||||
return I18n.tr("settings.color-scheme.templates.programs.code.description-detected", {
|
||||
"clients": clientInfo.join(", ")
|
||||
});
|
||||
}
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: -1
|
||||
checked: Settings.data.templates.code
|
||||
enabled: ProgramCheckerService.availableCodeClients.length > 0
|
||||
onToggled: checked => {
|
||||
// Set unified code property
|
||||
Settings.data.templates.code = checked;
|
||||
if (ProgramCheckerService.availableCodeClients.length > 0) {
|
||||
if (!checked) {
|
||||
const homeDir = Quickshell.env("HOME");
|
||||
for (var i = 0; i < ProgramCheckerService.availableCodeClients.length; i++) {
|
||||
var client = ProgramCheckerService.availableCodeClients[i];
|
||||
}
|
||||
}
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Spicetify"
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.spicetify.description", {
|
||||
"filepath": "~/.config/spicetify/Themes/Comfy/color.ini"
|
||||
})
|
||||
checked: Settings.data.templates.spicetify
|
||||
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.spicetify = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Telegram"
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.telegram.description", {
|
||||
"filepath": "~/.config/telegram-desktop/themes/noctalia.tdesktop-theme"
|
||||
})
|
||||
checked: Settings.data.templates.telegram
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.telegram = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Cava"
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.cava.description", {
|
||||
"filepath": "~/.config/cava/themes/noctalia"
|
||||
})
|
||||
checked: Settings.data.templates.cava
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.cava = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Yazi"
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.yazi.description", {
|
||||
"filepath": "~/.config/yazi/flavors/noctalia.yazi/flavor.toml"
|
||||
})
|
||||
checked: Settings.data.templates.yazi
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.yazi = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Zed"
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.zed.description", {
|
||||
"filepath": "~/.config/zed/themes/noctalia.json"
|
||||
})
|
||||
checked: Settings.data.templates.zed
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.zed = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Zen Browser"
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.zen-browser.description", {
|
||||
"filepath": "~/.cache/noctalia/zen-browser/zen-userChrome.css"
|
||||
})
|
||||
checked: Settings.data.templates.zenBrowser
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.zenBrowser = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Emacs"
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.emacs.description")
|
||||
checked: Settings.data.templates.emacs
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.emacs = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Miscellaneous
|
||||
NCollapsible {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.color-scheme.templates.misc.label")
|
||||
description: I18n.tr("settings.color-scheme.templates.misc.description")
|
||||
defaultExpanded: false
|
||||
|
||||
NCheckbox {
|
||||
label: I18n.tr("settings.color-scheme.templates.misc.user-templates.label")
|
||||
description: I18n.tr("settings.color-scheme.templates.misc.user-templates.description")
|
||||
checked: Settings.data.templates.enableUserTemplates
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.enableUserTemplates = checked;
|
||||
if (checked) {
|
||||
TemplateRegistry.writeUserTemplatesToml();
|
||||
}
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
TemplatesSubTab {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,433 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Io
|
||||
import "."
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Services.Theming
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
property var timeOptions
|
||||
property var schemeColorsCache: ({})
|
||||
property int cacheVersion: 0
|
||||
|
||||
signal checkMatugen
|
||||
signal openDownloadPopup
|
||||
|
||||
function extractSchemeName(schemePath) {
|
||||
var pathParts = schemePath.split("/");
|
||||
var filename = pathParts[pathParts.length - 1];
|
||||
var schemeName = filename.replace(".json", "");
|
||||
|
||||
if (schemeName === "Noctalia-default") {
|
||||
schemeName = "Noctalia (default)";
|
||||
} else if (schemeName === "Noctalia-legacy") {
|
||||
schemeName = "Noctalia (legacy)";
|
||||
} else if (schemeName === "Tokyo-Night") {
|
||||
schemeName = "Tokyo Night";
|
||||
} else if (schemeName === "Rosepine") {
|
||||
schemeName = "Rose Pine";
|
||||
}
|
||||
|
||||
return schemeName;
|
||||
}
|
||||
|
||||
function getSchemeColor(schemeName, colorKey) {
|
||||
var _ = cacheVersion;
|
||||
|
||||
if (schemeColorsCache[schemeName]) {
|
||||
var entry = schemeColorsCache[schemeName];
|
||||
var variant = entry;
|
||||
|
||||
if (entry.dark || entry.light) {
|
||||
variant = Settings.data.colorSchemes.darkMode ? (entry.dark || entry.light) : (entry.light || entry.dark);
|
||||
}
|
||||
|
||||
if (variant && variant[colorKey]) {
|
||||
return variant[colorKey];
|
||||
}
|
||||
}
|
||||
|
||||
if (colorKey === "mSurface")
|
||||
return Color.mSurfaceVariant;
|
||||
if (colorKey === "mPrimary")
|
||||
return Color.mPrimary;
|
||||
if (colorKey === "mSecondary")
|
||||
return Color.mSecondary;
|
||||
if (colorKey === "mTertiary")
|
||||
return Color.mTertiary;
|
||||
if (colorKey === "mError")
|
||||
return Color.mError;
|
||||
return Color.mOnSurfaceVariant;
|
||||
}
|
||||
|
||||
function schemeLoaded(schemeName, jsonData) {
|
||||
var value = jsonData || {};
|
||||
schemeColorsCache[schemeName] = value;
|
||||
cacheVersion++;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ColorSchemeService
|
||||
function onSchemesChanged() {
|
||||
root.schemeColorsCache = {};
|
||||
root.cacheVersion++;
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: fileLoaders
|
||||
visible: false
|
||||
|
||||
Repeater {
|
||||
model: ColorSchemeService.schemes
|
||||
delegate: Item {
|
||||
FileView {
|
||||
path: modelData
|
||||
blockLoading: false
|
||||
onLoaded: {
|
||||
var schemeName = root.extractSchemeName(path);
|
||||
|
||||
try {
|
||||
var jsonData = JSON.parse(text());
|
||||
root.schemeLoaded(schemeName, jsonData);
|
||||
} catch (e) {
|
||||
Logger.w("ColorSchemeTab", "Failed to parse JSON for scheme:", schemeName, e);
|
||||
root.schemeLoaded(schemeName, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.color-scheme.color-source.section.label")
|
||||
description: I18n.tr("settings.color-scheme.color-source.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.color-scheme.dark-mode.switch.label")
|
||||
description: I18n.tr("settings.color-scheme.dark-mode.switch.description")
|
||||
checked: Settings.data.colorSchemes.darkMode
|
||||
onToggled: checked => {
|
||||
Settings.data.colorSchemes.darkMode = checked;
|
||||
root.cacheVersion++;
|
||||
}
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.color-scheme.dark-mode.mode.label")
|
||||
description: I18n.tr("settings.color-scheme.dark-mode.mode.description")
|
||||
|
||||
model: [
|
||||
{
|
||||
"name": I18n.tr("settings.color-scheme.dark-mode.mode.off"),
|
||||
"key": "off"
|
||||
},
|
||||
{
|
||||
"name": I18n.tr("settings.color-scheme.dark-mode.mode.manual"),
|
||||
"key": "manual"
|
||||
},
|
||||
{
|
||||
"name": I18n.tr("settings.color-scheme.dark-mode.mode.location"),
|
||||
"key": "location"
|
||||
}
|
||||
]
|
||||
|
||||
currentKey: Settings.data.colorSchemes.schedulingMode
|
||||
|
||||
onSelected: key => {
|
||||
Settings.data.colorSchemes.schedulingMode = key;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
visible: Settings.data.colorSchemes.schedulingMode === "manual"
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.display.night-light.manual-schedule.label")
|
||||
description: I18n.tr("settings.display.night-light.manual-schedule.description")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: false
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.display.night-light.manual-schedule.sunrise")
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
model: root.timeOptions
|
||||
currentKey: Settings.data.colorSchemes.manualSunrise
|
||||
placeholder: I18n.tr("settings.display.night-light.manual-schedule.select-start")
|
||||
onSelected: key => Settings.data.colorSchemes.manualSunrise = key
|
||||
minimumWidth: 120
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 20
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.display.night-light.manual-schedule.sunset")
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
model: root.timeOptions
|
||||
currentKey: Settings.data.colorSchemes.manualSunset
|
||||
placeholder: I18n.tr("settings.display.night-light.manual-schedule.select-stop")
|
||||
onSelected: key => Settings.data.colorSchemes.manualSunset = key
|
||||
minimumWidth: 120
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label")
|
||||
description: I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.description")
|
||||
enabled: ProgramCheckerService.matugenAvailable
|
||||
checked: Settings.data.colorSchemes.useWallpaperColors
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
root.checkMatugen();
|
||||
} else {
|
||||
Settings.data.colorSchemes.useWallpaperColors = false;
|
||||
ToastService.showNotice(I18n.tr("toast.wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.disabled"), "settings-color-scheme");
|
||||
|
||||
if (Settings.data.colorSchemes.predefinedScheme) {
|
||||
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.color-scheme.color-source.matugen-scheme-type.label")
|
||||
description: I18n.tr("settings.color-scheme.color-source.matugen-scheme-type.description")
|
||||
enabled: Settings.data.colorSchemes.useWallpaperColors
|
||||
visible: Settings.data.colorSchemes.useWallpaperColors
|
||||
|
||||
model: [
|
||||
{
|
||||
"key": "scheme-content",
|
||||
"name": "Content"
|
||||
},
|
||||
{
|
||||
"key": "scheme-expressive",
|
||||
"name": "Expressive"
|
||||
},
|
||||
{
|
||||
"key": "scheme-fidelity",
|
||||
"name": "Fidelity"
|
||||
},
|
||||
{
|
||||
"key": "scheme-fruit-salad",
|
||||
"name": "Fruit Salad"
|
||||
},
|
||||
{
|
||||
"key": "scheme-monochrome",
|
||||
"name": "Monochrome"
|
||||
},
|
||||
{
|
||||
"key": "scheme-neutral",
|
||||
"name": "Neutral"
|
||||
},
|
||||
{
|
||||
"key": "scheme-rainbow",
|
||||
"name": "Rainbow"
|
||||
},
|
||||
{
|
||||
"key": "scheme-tonal-spot",
|
||||
"name": "Tonal Spot"
|
||||
}
|
||||
]
|
||||
|
||||
currentKey: Settings.data.colorSchemes.matugenSchemeType
|
||||
|
||||
onSelected: key => {
|
||||
Settings.data.colorSchemes.matugenSchemeType = key;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("colorSchemes.matugenSchemeType")
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
visible: !Settings.data.colorSchemes.useWallpaperColors
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
visible: !Settings.data.colorSchemes.useWallpaperColors
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.color-scheme.predefined.section.label")
|
||||
description: I18n.tr("settings.color-scheme.predefined.section.description")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("settings.color-scheme.download.button")
|
||||
icon: "download"
|
||||
onClicked: root.openDownloadPopup()
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
columns: 2
|
||||
rowSpacing: Style.marginM
|
||||
columnSpacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
Repeater {
|
||||
model: ColorSchemeService.schemes
|
||||
|
||||
Rectangle {
|
||||
id: schemeItem
|
||||
|
||||
property string schemePath: modelData
|
||||
property string schemeName: root.extractSchemeName(modelData)
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
height: 50 * Style.uiScaleRatio
|
||||
radius: Style.radiusS
|
||||
color: root.getSchemeColor(schemeName, "mSurface")
|
||||
border.width: Style.borderL
|
||||
border.color: {
|
||||
if (Settings.data.colorSchemes.predefinedScheme === schemeName) {
|
||||
return Color.mSecondary;
|
||||
}
|
||||
if (itemMouseArea.containsMouse) {
|
||||
return Color.mHover;
|
||||
}
|
||||
return Color.mOutline;
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: scheme
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: schemeItem.schemeName
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
wrapMode: Text.WordWrap
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
property int diameter: 16 * Style.uiScaleRatio
|
||||
|
||||
Rectangle {
|
||||
width: scheme.diameter
|
||||
height: scheme.diameter
|
||||
radius: scheme.diameter * 0.5
|
||||
color: root.getSchemeColor(schemeItem.schemeName, "mPrimary")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: scheme.diameter
|
||||
height: scheme.diameter
|
||||
radius: scheme.diameter * 0.5
|
||||
color: root.getSchemeColor(schemeItem.schemeName, "mSecondary")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: scheme.diameter
|
||||
height: scheme.diameter
|
||||
radius: scheme.diameter * 0.5
|
||||
color: root.getSchemeColor(schemeItem.schemeName, "mTertiary")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: scheme.diameter
|
||||
height: scheme.diameter
|
||||
radius: scheme.diameter * 0.5
|
||||
color: root.getSchemeColor(schemeItem.schemeName, "mError")
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: itemMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
Settings.data.colorSchemes.useWallpaperColors = false;
|
||||
Logger.i("ColorSchemeTab", "Disabled wallpaper colors");
|
||||
|
||||
Settings.data.colorSchemes.predefinedScheme = schemeItem.schemeName;
|
||||
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme);
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: (Settings.data.colorSchemes.predefinedScheme === schemeItem.schemeName)
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: 0
|
||||
anchors.topMargin: -3
|
||||
width: 20
|
||||
height: 20
|
||||
radius: Math.min(Style.radiusL, width / 2)
|
||||
color: Color.mSecondary
|
||||
border.width: Style.borderS
|
||||
border.color: Color.mOnSecondary
|
||||
|
||||
NIcon {
|
||||
icon: "check"
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSecondary
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.color-scheme.predefined.generate-templates.label")
|
||||
description: I18n.tr("settings.color-scheme.predefined.generate-templates.description")
|
||||
checked: Settings.data.colorSchemes.generateTemplatesForPredefined
|
||||
onToggled: checked => {
|
||||
Settings.data.colorSchemes.generateTemplatesForPredefined = checked;
|
||||
if (!Settings.data.colorSchemes.useWallpaperColors && Settings.data.colorSchemes.predefinedScheme) {
|
||||
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme);
|
||||
}
|
||||
}
|
||||
Layout.topMargin: Style.marginL
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Services.Theming
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.color-scheme.templates.section.label")
|
||||
description: I18n.tr("settings.color-scheme.templates.section.description")
|
||||
}
|
||||
|
||||
NCollapsible {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.color-scheme.templates.ui.label")
|
||||
description: I18n.tr("settings.color-scheme.templates.ui.description")
|
||||
defaultExpanded: false
|
||||
|
||||
NCheckbox {
|
||||
label: "GTK"
|
||||
description: I18n.tr("settings.color-scheme.templates.ui.gtk.description", {
|
||||
"filepath": "~/.config/gtk-3.0/gtk.css & ~/.config/gtk-4.0/gtk.css"
|
||||
})
|
||||
checked: Settings.data.templates.gtk
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.gtk = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Qt"
|
||||
description: I18n.tr("settings.color-scheme.templates.ui.qt.description", {
|
||||
"filepath": "~/.config/qt5ct/colors/noctalia.conf & ~/.config/qt6ct/colors/noctalia.conf"
|
||||
})
|
||||
checked: Settings.data.templates.qt
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.qt = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "KColorScheme"
|
||||
description: I18n.tr("settings.color-scheme.templates.ui.kcolorscheme.description", {
|
||||
"filepath": "~/.local/share/color-schemes/noctalia.colors"
|
||||
})
|
||||
checked: Settings.data.templates.kcolorscheme
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.kcolorscheme = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NCollapsible {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.color-scheme.templates.compositors.label")
|
||||
description: I18n.tr("settings.color-scheme.templates.compositors.description")
|
||||
defaultExpanded: false
|
||||
|
||||
NCheckbox {
|
||||
label: "Niri"
|
||||
description: I18n.tr("settings.color-scheme.templates.compositors.niri.description", {
|
||||
"filepath": "~/.config/niri/noctalia.kdl"
|
||||
})
|
||||
checked: Settings.data.templates.niri
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.niri = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Hyprland"
|
||||
description: I18n.tr("settings.color-scheme.templates.compositors.hyprland.description", {
|
||||
"filepath": "~/.config/hypr/noctalia/noctalia-colors.conf"
|
||||
})
|
||||
checked: Settings.data.templates.hyprland
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.hyprland = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Mango"
|
||||
description: I18n.tr("settings.color-scheme.templates.compositors.mango.description", {
|
||||
"filepath": "~/.config/mango/noctalia.conf"
|
||||
})
|
||||
checked: Settings.data.templates.mango
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.mango = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NCollapsible {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.color-scheme.templates.terminal.label")
|
||||
description: I18n.tr("settings.color-scheme.templates.terminal.description")
|
||||
defaultExpanded: false
|
||||
|
||||
NCheckbox {
|
||||
label: "Alacritty"
|
||||
description: I18n.tr("settings.color-scheme.templates.terminal.alacritty.description", {
|
||||
"filepath": "~/.config/alacritty/themes/noctalia"
|
||||
})
|
||||
checked: Settings.data.templates.alacritty
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.alacritty = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Kitty"
|
||||
description: I18n.tr("settings.color-scheme.templates.terminal.kitty.description", {
|
||||
"filepath": "~/.config/kitty/themes/noctalia.conf"
|
||||
})
|
||||
checked: Settings.data.templates.kitty
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.kitty = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Ghostty"
|
||||
description: I18n.tr("settings.color-scheme.templates.terminal.ghostty.description", {
|
||||
"filepath": "~/.config/ghostty/themes/noctalia"
|
||||
})
|
||||
checked: Settings.data.templates.ghostty
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.ghostty = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Foot"
|
||||
description: I18n.tr("settings.color-scheme.templates.terminal.foot.description", {
|
||||
"filepath": "~/.config/foot/themes/noctalia"
|
||||
})
|
||||
checked: Settings.data.templates.foot
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.foot = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Wezterm"
|
||||
description: I18n.tr("settings.color-scheme.templates.terminal.wezterm.description", {
|
||||
"filepath": "~/.config/wezterm/colors/Noctalia.toml"
|
||||
})
|
||||
checked: Settings.data.templates.wezterm
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.wezterm = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NCollapsible {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.color-scheme.templates.programs.label")
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.description")
|
||||
defaultExpanded: false
|
||||
|
||||
NCheckbox {
|
||||
label: "Fuzzel"
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.fuzzel.description", {
|
||||
"filepath": "~/.config/fuzzel/themes/noctalia"
|
||||
})
|
||||
checked: Settings.data.templates.fuzzel
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.fuzzel = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
id: discordToggle
|
||||
label: "Discord"
|
||||
description: {
|
||||
if (ProgramCheckerService.availableDiscordClients.length === 0) {
|
||||
return I18n.tr("settings.color-scheme.templates.programs.discord.description-missing");
|
||||
} else {
|
||||
var clientInfo = [];
|
||||
for (var i = 0; i < ProgramCheckerService.availableDiscordClients.length; i++) {
|
||||
var client = ProgramCheckerService.availableDiscordClients[i];
|
||||
clientInfo.push(client.name.charAt(0).toUpperCase() + client.name.slice(1));
|
||||
}
|
||||
return I18n.tr("settings.color-scheme.templates.programs.discord.description-detected", {
|
||||
"clients": clientInfo.join(", ")
|
||||
});
|
||||
}
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: -1
|
||||
checked: Settings.data.templates.discord
|
||||
enabled: ProgramCheckerService.availableDiscordClients.length > 0
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.discord = checked;
|
||||
if (ProgramCheckerService.availableDiscordClients.length > 0) {
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Pywalfox"
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.pywalfox.description", {
|
||||
"filepath": "~/.cache/wal/colors.json"
|
||||
})
|
||||
checked: Settings.data.templates.pywalfox
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.pywalfox = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Vicinae"
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.vicinae.description", {
|
||||
"filepath": "~/.local/share/vicinae/themes/matugen.toml"
|
||||
})
|
||||
checked: Settings.data.templates.vicinae
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.vicinae = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Walker"
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.walker.description", {
|
||||
"filepath": "~/.config/walker/style.css"
|
||||
})
|
||||
checked: Settings.data.templates.walker
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.walker = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
id: codeToggle
|
||||
label: "Code"
|
||||
description: {
|
||||
if (ProgramCheckerService.availableCodeClients.length === 0) {
|
||||
return I18n.tr("settings.color-scheme.templates.programs.code.description-missing");
|
||||
} else {
|
||||
var clientInfo = [];
|
||||
for (var i = 0; i < ProgramCheckerService.availableCodeClients.length; i++) {
|
||||
var client = ProgramCheckerService.availableCodeClients[i];
|
||||
var clientName = client.name === "code" ? "VSCode" : "VSCodium";
|
||||
clientInfo.push(clientName);
|
||||
}
|
||||
return I18n.tr("settings.color-scheme.templates.programs.code.description-detected", {
|
||||
"clients": clientInfo.join(", ")
|
||||
});
|
||||
}
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: -1
|
||||
checked: Settings.data.templates.code
|
||||
enabled: ProgramCheckerService.availableCodeClients.length > 0
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.code = checked;
|
||||
if (ProgramCheckerService.availableCodeClients.length > 0) {
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Spicetify"
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.spicetify.description", {
|
||||
"filepath": "~/.config/spicetify/Themes/Comfy/color.ini"
|
||||
})
|
||||
checked: Settings.data.templates.spicetify
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.spicetify = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Telegram"
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.telegram.description", {
|
||||
"filepath": "~/.config/telegram-desktop/themes/noctalia.tdesktop-theme"
|
||||
})
|
||||
checked: Settings.data.templates.telegram
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.telegram = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Cava"
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.cava.description", {
|
||||
"filepath": "~/.config/cava/themes/noctalia"
|
||||
})
|
||||
checked: Settings.data.templates.cava
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.cava = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Yazi"
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.yazi.description", {
|
||||
"filepath": "~/.config/yazi/flavors/noctalia.yazi/flavor.toml"
|
||||
})
|
||||
checked: Settings.data.templates.yazi
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.yazi = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Zed"
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.zed.description", {
|
||||
"filepath": "~/.config/zed/themes/noctalia.json"
|
||||
})
|
||||
checked: Settings.data.templates.zed
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.zed = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Zen Browser"
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.zen-browser.description", {
|
||||
"filepath": "~/.cache/noctalia/zen-browser/zen-userChrome.css"
|
||||
})
|
||||
checked: Settings.data.templates.zenBrowser
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.zenBrowser = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Emacs"
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.emacs.description")
|
||||
checked: Settings.data.templates.emacs
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.emacs = checked;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NCollapsible {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.color-scheme.templates.misc.label")
|
||||
description: I18n.tr("settings.color-scheme.templates.misc.description")
|
||||
defaultExpanded: false
|
||||
|
||||
NCheckbox {
|
||||
label: I18n.tr("settings.color-scheme.templates.misc.user-templates.label")
|
||||
description: I18n.tr("settings.color-scheme.templates.misc.user-templates.description")
|
||||
checked: Settings.data.templates.enableUserTemplates
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.enableUserTemplates = checked;
|
||||
if (checked) {
|
||||
TemplateRegistry.writeUserTemplatesToml();
|
||||
}
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.Hardware
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.display.monitors.section.label")
|
||||
description: I18n.tr("settings.display.monitors.section.description")
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: contentCol.implicitHeight + Style.marginL * 2
|
||||
radius: Style.radiusM
|
||||
color: Color.mSurfaceVariant
|
||||
border.color: Color.mOutline
|
||||
border.width: Style.borderS
|
||||
|
||||
property var brightnessMonitor: BrightnessService.getMonitorForScreen(modelData)
|
||||
|
||||
ColumnLayout {
|
||||
id: contentCol
|
||||
width: parent.width - 2 * Style.marginL
|
||||
x: Style.marginL
|
||||
y: Style.marginL
|
||||
spacing: Style.marginXXS
|
||||
|
||||
NLabel {
|
||||
label: modelData.name || "Unknown"
|
||||
description: {
|
||||
const compositorScale = CompositorService.getDisplayScale(modelData.name);
|
||||
I18n.tr("system.monitor-description", {
|
||||
"model": modelData.model,
|
||||
"width": modelData.width * compositorScale,
|
||||
"height": modelData.height * compositorScale,
|
||||
"scale": compositorScale
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
visible: brightnessMonitor !== undefined && brightnessMonitor !== null
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginL
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.display.monitors.brightness")
|
||||
Layout.preferredWidth: 90
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
id: brightnessSlider
|
||||
from: 0
|
||||
to: 1
|
||||
value: brightnessMonitor ? brightnessMonitor.brightness : 0.5
|
||||
stepSize: 0.01
|
||||
enabled: brightnessMonitor ? brightnessMonitor.brightnessControlAvailable : false
|
||||
onMoved: value => {
|
||||
if (brightnessMonitor && brightnessMonitor.brightnessControlAvailable) {
|
||||
brightnessMonitor.setBrightness(value);
|
||||
}
|
||||
}
|
||||
onPressedChanged: (pressed, value) => {
|
||||
if (brightnessMonitor && brightnessMonitor.brightnessControlAvailable) {
|
||||
brightnessMonitor.setBrightness(value);
|
||||
}
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
text: brightnessMonitor ? Math.round(brightnessSlider.value * 100) + "%" : "N/A"
|
||||
Layout.preferredWidth: 55
|
||||
horizontalAlignment: Text.AlignRight
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
opacity: brightnessMonitor && !brightnessMonitor.brightnessControlAvailable ? 0.5 : 1.0
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 30
|
||||
Layout.fillHeight: true
|
||||
NIcon {
|
||||
icon: brightnessMonitor && brightnessMonitor.method == "internal" ? "device-laptop" : "device-desktop"
|
||||
anchors.centerIn: parent
|
||||
opacity: brightnessMonitor && !brightnessMonitor.brightnessControlAvailable ? 0.5 : 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: brightnessMonitor && !brightnessMonitor.brightnessControlAvailable
|
||||
text: !Settings.data.brightness.enableDdcSupport ? I18n.tr("settings.display.monitors.brightness-unavailable.ddc-disabled") : I18n.tr("settings.display.monitors.brightness-unavailable.generic")
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.display.monitors.brightness-step.label")
|
||||
description: I18n.tr("settings.display.monitors.brightness-step.description")
|
||||
minimum: 1
|
||||
maximum: 50
|
||||
value: Settings.data.brightness.brightnessStep
|
||||
stepSize: 1
|
||||
suffix: "%"
|
||||
onValueChanged: Settings.data.brightness.brightnessStep = value
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("brightness.brightnessStep")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.display.monitors.enforce-minimum.label")
|
||||
description: I18n.tr("settings.display.monitors.enforce-minimum.description")
|
||||
checked: Settings.data.brightness.enforceMinimum
|
||||
onToggled: checked => Settings.data.brightness.enforceMinimum = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("brightness.enforceMinimum")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.display.monitors.external-brightness.label")
|
||||
description: I18n.tr("settings.display.monitors.external-brightness.description")
|
||||
checked: Settings.data.brightness.enableDdcSupport
|
||||
onToggled: checked => {
|
||||
Settings.data.brightness.enableDdcSupport = checked;
|
||||
}
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("brightness.enableDdcSupport")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Services.Location
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
// 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: ["sh", "-c", "command -v wlsunset"]
|
||||
running: false
|
||||
|
||||
onExited: function (exitCode) {
|
||||
if (exitCode === 0) {
|
||||
Settings.data.nightLight.enabled = true;
|
||||
NightLightService.apply();
|
||||
ToastService.showNotice(I18n.tr("settings.display.night-light.section.label"), I18n.tr("toast.night-light.enabled"), "nightlight-on");
|
||||
} else {
|
||||
Settings.data.nightLight.enabled = false;
|
||||
ToastService.showWarning(I18n.tr("settings.display.night-light.section.label"), I18n.tr("toast.night-light.not-installed"));
|
||||
}
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.display.tabs.brightness")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.display.tabs.night-light")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
BrightnessSubTab {}
|
||||
NightLightSubTab {
|
||||
timeOptions: timeOptions
|
||||
onCheckWlsunset: wlsunsetCheck.running = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.Location
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
property var timeOptions
|
||||
|
||||
signal checkWlsunset
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.display.night-light.section.label")
|
||||
description: I18n.tr("settings.display.night-light.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.display.night-light.enable.label")
|
||||
description: I18n.tr("settings.display.night-light.enable.description")
|
||||
checked: Settings.data.nightLight.enabled
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
root.checkWlsunset();
|
||||
} else {
|
||||
Settings.data.nightLight.enabled = false;
|
||||
Settings.data.nightLight.forced = false;
|
||||
NightLightService.apply();
|
||||
ToastService.showNotice(I18n.tr("settings.display.night-light.section.label"), I18n.tr("toast.night-light.disabled"), "nightlight-off");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: Settings.data.nightLight.enabled
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.display.night-light.temperature.night")
|
||||
description: I18n.tr("settings.display.night-light.temperature.night-description")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NSlider {
|
||||
id: nightSlider
|
||||
Layout.fillWidth: true
|
||||
from: 1000
|
||||
to: 6500
|
||||
value: Settings.data.nightLight.nightTemp
|
||||
|
||||
onValueChanged: {
|
||||
var dayTemp = parseInt(Settings.data.nightLight.dayTemp);
|
||||
var v = Math.round(value);
|
||||
if (!isNaN(dayTemp)) {
|
||||
var maxNight = dayTemp - 500;
|
||||
v = Math.min(maxNight, Math.max(1000, v));
|
||||
} else {
|
||||
v = Math.max(1000, v);
|
||||
}
|
||||
if (v !== value)
|
||||
value = v;
|
||||
}
|
||||
|
||||
onPressedChanged: {
|
||||
if (!pressed) {
|
||||
var dayTemp = parseInt(Settings.data.nightLight.dayTemp);
|
||||
var v = Math.round(value);
|
||||
if (!isNaN(dayTemp)) {
|
||||
var maxNight = dayTemp - 500;
|
||||
v = Math.min(maxNight, Math.max(1000, v));
|
||||
} else {
|
||||
v = Math.max(1000, v);
|
||||
}
|
||||
Settings.data.nightLight.nightTemp = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: nightSlider.value + "K"
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.display.night-light.temperature.day")
|
||||
description: I18n.tr("settings.display.night-light.temperature.day-description")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NSlider {
|
||||
id: daySlider
|
||||
Layout.fillWidth: true
|
||||
from: 1000
|
||||
to: 6500
|
||||
value: Settings.data.nightLight.dayTemp
|
||||
|
||||
onValueChanged: {
|
||||
var nightTemp = parseInt(Settings.data.nightLight.nightTemp);
|
||||
var v = Math.round(value);
|
||||
if (!isNaN(nightTemp)) {
|
||||
var minDay = nightTemp + 500;
|
||||
v = Math.max(minDay, Math.min(6500, v));
|
||||
} else {
|
||||
v = Math.min(6500, v);
|
||||
}
|
||||
if (v !== value)
|
||||
value = v;
|
||||
}
|
||||
|
||||
onPressedChanged: {
|
||||
if (!pressed) {
|
||||
var nightTemp = parseInt(Settings.data.nightLight.nightTemp);
|
||||
var v = Math.round(value);
|
||||
if (!isNaN(nightTemp)) {
|
||||
var minDay = nightTemp + 500;
|
||||
v = Math.max(minDay, Math.min(6500, v));
|
||||
} else {
|
||||
v = Math.min(6500, v);
|
||||
}
|
||||
Settings.data.nightLight.dayTemp = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: daySlider.value + "K"
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.display.night-light.auto-schedule.label")
|
||||
description: I18n.tr("settings.display.night-light.auto-schedule.description", {
|
||||
"location": LocationService.stableName
|
||||
})
|
||||
checked: Settings.data.nightLight.autoSchedule
|
||||
onToggled: checked => Settings.data.nightLight.autoSchedule = checked
|
||||
visible: Settings.data.nightLight.enabled
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
visible: Settings.data.nightLight.enabled && !Settings.data.nightLight.autoSchedule && !Settings.data.nightLight.forced
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.display.night-light.manual-schedule.label")
|
||||
description: I18n.tr("settings.display.night-light.manual-schedule.description")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.display.night-light.manual-schedule.sunrise")
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
model: root.timeOptions
|
||||
currentKey: Settings.data.nightLight.manualSunrise
|
||||
placeholder: I18n.tr("settings.display.night-light.manual-schedule.select-start")
|
||||
onSelected: key => Settings.data.nightLight.manualSunrise = key
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.display.night-light.manual-schedule.sunset")
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
model: root.timeOptions
|
||||
currentKey: Settings.data.nightLight.manualSunset
|
||||
placeholder: I18n.tr("settings.display.night-light.manual-schedule.select-stop")
|
||||
onSelected: key => Settings.data.nightLight.manualSunset = key
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.display.night-light.force-activation.label")
|
||||
description: I18n.tr("settings.display.night-light.force-activation.description")
|
||||
checked: Settings.data.nightLight.forced
|
||||
onToggled: checked => {
|
||||
Settings.data.nightLight.forced = checked;
|
||||
if (checked && !Settings.data.nightLight.enabled) {
|
||||
root.checkWlsunset();
|
||||
} else {
|
||||
NightLightService.apply();
|
||||
}
|
||||
}
|
||||
visible: Settings.data.nightLight.enabled
|
||||
}
|
||||
}
|
||||
@@ -1,455 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.Hardware
|
||||
import qs.Services.Location
|
||||
import qs.Services.UI
|
||||
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: ["sh", "-c", "command -v wlsunset"]
|
||||
running: false
|
||||
|
||||
onExited: function (exitCode) {
|
||||
if (exitCode === 0) {
|
||||
Settings.data.nightLight.enabled = true;
|
||||
NightLightService.apply();
|
||||
ToastService.showNotice(I18n.tr("settings.display.night-light.section.label"), I18n.tr("toast.night-light.enabled"), "nightlight-on");
|
||||
} else {
|
||||
Settings.data.nightLight.enabled = false;
|
||||
ToastService.showWarning(I18n.tr("settings.display.night-light.section.label"), I18n.tr("toast.night-light.not-installed"));
|
||||
}
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
|
||||
spacing: Style.marginL
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.display.monitors.section.label")
|
||||
description: I18n.tr("settings.display.monitors.section.description")
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: contentCol.implicitHeight + Style.marginL * 2
|
||||
radius: Style.radiusM
|
||||
color: Color.mSurfaceVariant
|
||||
border.color: Color.mOutline
|
||||
border.width: Style.borderS
|
||||
|
||||
property var brightnessMonitor: BrightnessService.getMonitorForScreen(modelData)
|
||||
|
||||
ColumnLayout {
|
||||
id: contentCol
|
||||
width: parent.width - 2 * Style.marginL
|
||||
x: Style.marginL
|
||||
y: Style.marginL
|
||||
spacing: Style.marginXXS
|
||||
|
||||
NLabel {
|
||||
label: modelData.name || "Unknown"
|
||||
description: {
|
||||
const compositorScale = CompositorService.getDisplayScale(modelData.name);
|
||||
I18n.tr("system.monitor-description", {
|
||||
"model": modelData.model,
|
||||
"width": modelData.width * compositorScale,
|
||||
"height": modelData.height * compositorScale,
|
||||
"scale": compositorScale
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Brightness
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
visible: brightnessMonitor !== undefined && brightnessMonitor !== null
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginL
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.display.monitors.brightness")
|
||||
Layout.preferredWidth: 90
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
id: brightnessSlider
|
||||
from: 0
|
||||
to: 1
|
||||
value: brightnessMonitor ? brightnessMonitor.brightness : 0.5
|
||||
stepSize: 0.01
|
||||
enabled: brightnessMonitor ? brightnessMonitor.brightnessControlAvailable : false
|
||||
onMoved: value => {
|
||||
if (brightnessMonitor && brightnessMonitor.brightnessControlAvailable) {
|
||||
brightnessMonitor.setBrightness(value);
|
||||
}
|
||||
}
|
||||
onPressedChanged: (pressed, value) => {
|
||||
if (brightnessMonitor && brightnessMonitor.brightnessControlAvailable) {
|
||||
brightnessMonitor.setBrightness(value);
|
||||
}
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
text: brightnessMonitor ? Math.round(brightnessSlider.value * 100) + "%" : "N/A"
|
||||
Layout.preferredWidth: 55
|
||||
horizontalAlignment: Text.AlignRight
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
opacity: brightnessMonitor && !brightnessMonitor.brightnessControlAvailable ? 0.5 : 1.0
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 30
|
||||
Layout.fillHeight: true
|
||||
NIcon {
|
||||
icon: brightnessMonitor && brightnessMonitor.method == "internal" ? "device-laptop" : "device-desktop"
|
||||
anchors.centerIn: parent
|
||||
opacity: brightnessMonitor && !brightnessMonitor.brightnessControlAvailable ? 0.5 : 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show message when brightness control is not available
|
||||
NText {
|
||||
visible: brightnessMonitor && !brightnessMonitor.brightnessControlAvailable
|
||||
text: !Settings.data.brightness.enableDdcSupport ? I18n.tr("settings.display.monitors.brightness-unavailable.ddc-disabled") : I18n.tr("settings.display.monitors.brightness-unavailable.generic")
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Brightness Step
|
||||
NSpinBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.display.monitors.brightness-step.label")
|
||||
description: I18n.tr("settings.display.monitors.brightness-step.description")
|
||||
minimum: 1
|
||||
maximum: 50
|
||||
value: Settings.data.brightness.brightnessStep
|
||||
stepSize: 1
|
||||
suffix: "%"
|
||||
onValueChanged: Settings.data.brightness.brightnessStep = value
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("brightness.brightnessStep")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.display.monitors.enforce-minimum.label")
|
||||
description: I18n.tr("settings.display.monitors.enforce-minimum.description")
|
||||
checked: Settings.data.brightness.enforceMinimum
|
||||
onToggled: checked => Settings.data.brightness.enforceMinimum = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("brightness.enforceMinimum")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.display.monitors.external-brightness.label")
|
||||
description: I18n.tr("settings.display.monitors.external-brightness.description")
|
||||
checked: Settings.data.brightness.enableDdcSupport
|
||||
onToggled: checked => {
|
||||
Settings.data.brightness.enableDdcSupport = checked;
|
||||
// DDC detection will run on next monitor change when enabled
|
||||
// Monitors will stop using DDC immediately when disabled
|
||||
}
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("brightness.enableDdcSupport")
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// Night Light Section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.display.night-light.section.label")
|
||||
description: I18n.tr("settings.display.night-light.section.description")
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.display.night-light.enable.label")
|
||||
description: I18n.tr("settings.display.night-light.enable.description")
|
||||
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(I18n.tr("settings.display.night-light.section.label"), I18n.tr("toast.night-light.disabled"), "nightlight-off");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Temperature
|
||||
ColumnLayout {
|
||||
visible: Settings.data.nightLight.enabled
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Night temperature
|
||||
NLabel {
|
||||
label: I18n.tr("settings.display.night-light.temperature.night")
|
||||
description: I18n.tr("settings.display.night-light.temperature.night-description")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NSlider {
|
||||
id: nightSlider
|
||||
Layout.fillWidth: true
|
||||
|
||||
from: 1000
|
||||
to: 6500
|
||||
value: Settings.data.nightLight.nightTemp
|
||||
|
||||
// Clamp as the thumb moves, but do NOT change Settings here
|
||||
onValueChanged: {
|
||||
var dayTemp = parseInt(Settings.data.nightLight.dayTemp);
|
||||
var v = Math.round(value);
|
||||
|
||||
if (!isNaN(dayTemp)) {
|
||||
var maxNight = dayTemp - 500;
|
||||
v = Math.min(maxNight, Math.max(1000, v));
|
||||
} else {
|
||||
v = Math.max(1000, v);
|
||||
}
|
||||
|
||||
if (v !== value)
|
||||
value = v;
|
||||
}
|
||||
|
||||
// Only write back to Settings when the user releases the slider
|
||||
onPressedChanged: {
|
||||
if (!pressed) {
|
||||
var dayTemp = parseInt(Settings.data.nightLight.dayTemp);
|
||||
var v = Math.round(value);
|
||||
|
||||
if (!isNaN(dayTemp)) {
|
||||
var maxNight = dayTemp - 500;
|
||||
v = Math.min(maxNight, Math.max(1000, v));
|
||||
} else {
|
||||
v = Math.max(1000, v);
|
||||
}
|
||||
|
||||
Settings.data.nightLight.nightTemp = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: nightSlider.value + "K"
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
// Day temperature
|
||||
NLabel {
|
||||
label: I18n.tr("settings.display.night-light.temperature.day")
|
||||
description: I18n.tr("settings.display.night-light.temperature.day-description")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NSlider {
|
||||
id: daySlider
|
||||
Layout.fillWidth: true
|
||||
|
||||
from: 1000
|
||||
to: 6500
|
||||
value: Settings.data.nightLight.dayTemp
|
||||
|
||||
// Clamp as the thumb moves, but do NOT change Settings here
|
||||
onValueChanged: {
|
||||
var nightTemp = parseInt(Settings.data.nightLight.nightTemp);
|
||||
var v = Math.round(value);
|
||||
|
||||
if (!isNaN(nightTemp)) {
|
||||
var minDay = nightTemp + 500;
|
||||
v = Math.max(minDay, Math.min(6500, v));
|
||||
} else {
|
||||
v = Math.min(6500, v);
|
||||
}
|
||||
|
||||
if (v !== value)
|
||||
value = v;
|
||||
}
|
||||
|
||||
// Only write back to Settings when the user releases the slider
|
||||
onPressedChanged: {
|
||||
if (!pressed) {
|
||||
var nightTemp = parseInt(Settings.data.nightLight.nightTemp);
|
||||
var v = Math.round(value);
|
||||
|
||||
if (!isNaN(nightTemp)) {
|
||||
var minDay = nightTemp + 500;
|
||||
v = Math.max(minDay, Math.min(6500, v));
|
||||
} else {
|
||||
v = Math.min(6500, v);
|
||||
}
|
||||
|
||||
Settings.data.nightLight.dayTemp = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: daySlider.value + "K"
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.display.night-light.auto-schedule.label")
|
||||
description: I18n.tr("settings.display.night-light.auto-schedule.description", {
|
||||
"location": LocationService.stableName
|
||||
})
|
||||
checked: Settings.data.nightLight.autoSchedule
|
||||
onToggled: checked => Settings.data.nightLight.autoSchedule = checked
|
||||
visible: Settings.data.nightLight.enabled
|
||||
}
|
||||
|
||||
// Manual scheduling
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
visible: Settings.data.nightLight.enabled && !Settings.data.nightLight.autoSchedule && !Settings.data.nightLight.forced
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.display.night-light.manual-schedule.label")
|
||||
description: I18n.tr("settings.display.night-light.manual-schedule.description")
|
||||
}
|
||||
|
||||
// Sunrise time
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.display.night-light.manual-schedule.sunrise")
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
model: timeOptions
|
||||
currentKey: Settings.data.nightLight.manualSunrise
|
||||
placeholder: I18n.tr("settings.display.night-light.manual-schedule.select-start")
|
||||
onSelected: key => Settings.data.nightLight.manualSunrise = key
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
// Sunset time
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.display.night-light.manual-schedule.sunset")
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
model: timeOptions
|
||||
currentKey: Settings.data.nightLight.manualSunset
|
||||
placeholder: I18n.tr("settings.display.night-light.manual-schedule.select-stop")
|
||||
onSelected: key => Settings.data.nightLight.manualSunset = key
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Force activation toggle
|
||||
NToggle {
|
||||
label: I18n.tr("settings.display.night-light.force-activation.label")
|
||||
description: I18n.tr("settings.display.night-light.force-activation.description")
|
||||
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
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
}
|
||||
+1
-66
@@ -1,28 +1,13 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: Style.marginL
|
||||
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.dock.appearance.section.label")
|
||||
@@ -181,54 +166,4 @@ ColumnLayout {
|
||||
defaultValue: Settings.getDefaultValue("dock.colorizeIcons")
|
||||
onToggled: checked => Settings.data.dock.colorizeIcons = checked
|
||||
}
|
||||
|
||||
NDivider {
|
||||
visible: Settings.data.dock.enabled
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// Monitor Configuration
|
||||
ColumnLayout {
|
||||
visible: Settings.data.dock.enabled
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.dock.monitors.section.label")
|
||||
description: I18n.tr("settings.dock.monitors.section.description")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NCheckbox {
|
||||
Layout.fillWidth: true
|
||||
label: modelData.name || "Unknown"
|
||||
description: {
|
||||
const compositorScale = CompositorService.getDisplayScale(modelData.name);
|
||||
I18n.tr("system.monitor-description", {
|
||||
"model": modelData.model,
|
||||
"width": modelData.width * compositorScale,
|
||||
"height": modelData.height * compositorScale,
|
||||
"scale": compositorScale
|
||||
});
|
||||
}
|
||||
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.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.dock.tabs.appearance")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.dock.tabs.monitors")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
AppearanceSubTab {}
|
||||
MonitorsSubTab {
|
||||
addMonitor: root.addMonitor
|
||||
removeMonitor: root.removeMonitor
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
property var addMonitor
|
||||
property var removeMonitor
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.dock.monitors.section.label")
|
||||
description: I18n.tr("settings.dock.monitors.section.description")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NCheckbox {
|
||||
Layout.fillWidth: true
|
||||
label: modelData.name || "Unknown"
|
||||
description: {
|
||||
const compositorScale = CompositorService.getDisplayScale(modelData.name);
|
||||
I18n.tr("system.monitor-description", {
|
||||
"model": modelData.model,
|
||||
"width": modelData.width * compositorScale,
|
||||
"height": modelData.height * compositorScale,
|
||||
"scale": compositorScale
|
||||
});
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
property var addMonitor
|
||||
property var removeMonitor
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.notifications.settings.section.label")
|
||||
description: I18n.tr("settings.notifications.settings.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.notifications.settings.enabled.label")
|
||||
description: I18n.tr("settings.notifications.settings.enabled.description")
|
||||
checked: Settings.data.notifications.enabled !== false
|
||||
onToggled: checked => Settings.data.notifications.enabled = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.enabled")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.notifications.settings.do-not-disturb.label")
|
||||
description: I18n.tr("settings.notifications.settings.do-not-disturb.description")
|
||||
checked: NotificationService.doNotDisturb
|
||||
onToggled: checked => NotificationService.doNotDisturb = checked
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.notifications.settings.location.label")
|
||||
description: I18n.tr("settings.notifications.settings.location.description")
|
||||
model: [
|
||||
{
|
||||
"key": "top",
|
||||
"name": I18n.tr("options.launcher.position.top_center")
|
||||
},
|
||||
{
|
||||
"key": "top_left",
|
||||
"name": I18n.tr("options.launcher.position.top_left")
|
||||
},
|
||||
{
|
||||
"key": "top_right",
|
||||
"name": I18n.tr("options.launcher.position.top_right")
|
||||
},
|
||||
{
|
||||
"key": "bottom",
|
||||
"name": I18n.tr("options.launcher.position.bottom_center")
|
||||
},
|
||||
{
|
||||
"key": "bottom_left",
|
||||
"name": I18n.tr("options.launcher.position.bottom_left")
|
||||
},
|
||||
{
|
||||
"key": "bottom_right",
|
||||
"name": I18n.tr("options.launcher.position.bottom_right")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.notifications.location || "top_right"
|
||||
onSelected: key => Settings.data.notifications.location = key
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.location")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.notifications.settings.always-on-top.label")
|
||||
description: I18n.tr("settings.notifications.settings.always-on-top.description")
|
||||
checked: Settings.data.notifications.overlayLayer
|
||||
onToggled: checked => Settings.data.notifications.overlayLayer = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.overlayLayer")
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.notifications.settings.background-opacity.label")
|
||||
description: I18n.tr("settings.notifications.settings.background-opacity.description")
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.notifications.backgroundOpacity
|
||||
onMoved: value => Settings.data.notifications.backgroundOpacity = value
|
||||
text: Math.round(Settings.data.notifications.backgroundOpacity * 100) + "%"
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.backgroundOpacity")
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.notifications.monitors.section.label")
|
||||
description: I18n.tr("settings.notifications.monitors.section.description")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NCheckbox {
|
||||
Layout.fillWidth: true
|
||||
label: modelData.name || I18n.tr("system.unknown")
|
||||
description: {
|
||||
const compositorScale = CompositorService.getDisplayScale(modelData.name);
|
||||
I18n.tr("system.monitor-description", {
|
||||
"model": modelData.model,
|
||||
"width": modelData.width * compositorScale,
|
||||
"height": modelData.height * compositorScale,
|
||||
"scale": compositorScale
|
||||
});
|
||||
}
|
||||
checked: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
Settings.data.notifications.monitors = root.addMonitor(Settings.data.notifications.monitors, modelData.name);
|
||||
} else {
|
||||
Settings.data.notifications.monitors = root.removeMonitor(Settings.data.notifications.monitors, modelData.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.notifications.duration.section.label")
|
||||
description: I18n.tr("settings.notifications.duration.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.notifications.duration.respect-expire.label")
|
||||
description: I18n.tr("settings.notifications.duration.respect-expire.description")
|
||||
checked: Settings.data.notifications.respectExpireTimeout
|
||||
onToggled: checked => Settings.data.notifications.respectExpireTimeout = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.respectExpireTimeout")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.notifications.duration.low-urgency.label")
|
||||
description: I18n.tr("settings.notifications.duration.low-urgency.description")
|
||||
from: 1
|
||||
to: 30
|
||||
stepSize: 1
|
||||
value: Settings.data.notifications.lowUrgencyDuration
|
||||
onMoved: value => Settings.data.notifications.lowUrgencyDuration = value
|
||||
text: Settings.data.notifications.lowUrgencyDuration + "s"
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.lowUrgencyDuration")
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 30 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: 30 * Style.uiScaleRatio
|
||||
|
||||
NIconButton {
|
||||
icon: "restore"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
tooltipText: I18n.tr("settings.notifications.duration.reset")
|
||||
onClicked: Settings.data.notifications.lowUrgencyDuration = 3
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.notifications.duration.normal-urgency.label")
|
||||
description: I18n.tr("settings.notifications.duration.normal-urgency.description")
|
||||
from: 1
|
||||
to: 30
|
||||
stepSize: 1
|
||||
value: Settings.data.notifications.normalUrgencyDuration
|
||||
onMoved: value => Settings.data.notifications.normalUrgencyDuration = value
|
||||
text: Settings.data.notifications.normalUrgencyDuration + "s"
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.normalUrgencyDuration")
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 30 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: 30 * Style.uiScaleRatio
|
||||
|
||||
NIconButton {
|
||||
icon: "restore"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
tooltipText: I18n.tr("settings.notifications.duration.reset")
|
||||
onClicked: Settings.data.notifications.normalUrgencyDuration = 8
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.notifications.duration.critical-urgency.label")
|
||||
description: I18n.tr("settings.notifications.duration.critical-urgency.description")
|
||||
from: 1
|
||||
to: 30
|
||||
stepSize: 1
|
||||
value: Settings.data.notifications.criticalUrgencyDuration
|
||||
onMoved: value => Settings.data.notifications.criticalUrgencyDuration = value
|
||||
text: Settings.data.notifications.criticalUrgencyDuration + "s"
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.criticalUrgencyDuration")
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 30 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: 30 * Style.uiScaleRatio
|
||||
|
||||
NIconButton {
|
||||
icon: "restore"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
tooltipText: I18n.tr("settings.notifications.duration.reset")
|
||||
onClicked: Settings.data.notifications.criticalUrgencyDuration = 15
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.notifications.history.section.label")
|
||||
description: I18n.tr("settings.notifications.history.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.notifications.history.low-urgency.label")
|
||||
description: I18n.tr("settings.notifications.history.low-urgency.description")
|
||||
checked: Settings.data.notifications?.saveToHistory?.low !== false
|
||||
onToggled: checked => Settings.data.notifications.saveToHistory.low = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.saveToHistory.low")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.notifications.history.normal-urgency.label")
|
||||
description: I18n.tr("settings.notifications.history.normal-urgency.description")
|
||||
checked: Settings.data.notifications?.saveToHistory?.normal !== false
|
||||
onToggled: checked => Settings.data.notifications.saveToHistory.normal = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.saveToHistory.normal")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.notifications.history.critical-urgency.label")
|
||||
description: I18n.tr("settings.notifications.history.critical-urgency.description")
|
||||
checked: Settings.data.notifications?.saveToHistory?.critical !== false
|
||||
onToggled: checked => Settings.data.notifications.saveToHistory.critical = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.saveToHistory.critical")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
|
||||
// File pickers for sound sub-tab
|
||||
function openUnifiedSoundPicker() {
|
||||
unifiedSoundFilePicker.open();
|
||||
}
|
||||
function openLowSoundPicker() {
|
||||
lowSoundFilePicker.open();
|
||||
}
|
||||
function openNormalSoundPicker() {
|
||||
normalSoundFilePicker.open();
|
||||
}
|
||||
function openCriticalSoundPicker() {
|
||||
criticalSoundFilePicker.open();
|
||||
}
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.notifications.tabs.general")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.notifications.tabs.sounds")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.notifications.tabs.history")
|
||||
tabIndex: 2
|
||||
checked: subTabBar.currentIndex === 2
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.notifications.tabs.toast")
|
||||
tabIndex: 3
|
||||
checked: subTabBar.currentIndex === 3
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
GeneralSubTab {
|
||||
addMonitor: root.addMonitor
|
||||
removeMonitor: root.removeMonitor
|
||||
}
|
||||
SoundsSubTab {
|
||||
onOpenUnifiedPicker: root.openUnifiedSoundPicker()
|
||||
onOpenLowPicker: root.openLowSoundPicker()
|
||||
onOpenNormalPicker: root.openNormalSoundPicker()
|
||||
onOpenCriticalPicker: root.openCriticalSoundPicker()
|
||||
}
|
||||
HistorySubTab {}
|
||||
ToastSubTab {}
|
||||
}
|
||||
|
||||
// File Pickers for Sound Files
|
||||
NFilePicker {
|
||||
id: unifiedSoundFilePicker
|
||||
title: I18n.tr("settings.notifications.sounds.files.unified.select-title")
|
||||
selectionMode: "files"
|
||||
initialPath: Quickshell.env("HOME")
|
||||
nameFilters: ["*.wav", "*.mp3", "*.ogg", "*.flac", "*.m4a", "*.aac"]
|
||||
onAccepted: paths => {
|
||||
if (paths.length > 0) {
|
||||
const soundPath = paths[0];
|
||||
Settings.data.notifications.sounds.normalSoundFile = soundPath;
|
||||
Settings.data.notifications.sounds.lowSoundFile = soundPath;
|
||||
Settings.data.notifications.sounds.criticalSoundFile = soundPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NFilePicker {
|
||||
id: lowSoundFilePicker
|
||||
title: I18n.tr("settings.notifications.sounds.files.low.select-title")
|
||||
selectionMode: "files"
|
||||
initialPath: Quickshell.env("HOME")
|
||||
nameFilters: ["*.wav", "*.mp3", "*.ogg", "*.flac", "*.m4a", "*.aac"]
|
||||
onAccepted: paths => {
|
||||
if (paths.length > 0) {
|
||||
Settings.data.notifications.sounds.lowSoundFile = paths[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NFilePicker {
|
||||
id: normalSoundFilePicker
|
||||
title: I18n.tr("settings.notifications.sounds.files.normal.select-title")
|
||||
selectionMode: "files"
|
||||
initialPath: Quickshell.env("HOME")
|
||||
nameFilters: ["*.wav", "*.mp3", "*.ogg", "*.flac", "*.m4a", "*.aac"]
|
||||
onAccepted: paths => {
|
||||
if (paths.length > 0) {
|
||||
Settings.data.notifications.sounds.normalSoundFile = paths[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NFilePicker {
|
||||
id: criticalSoundFilePicker
|
||||
title: I18n.tr("settings.notifications.sounds.files.critical.select-title")
|
||||
selectionMode: "files"
|
||||
initialPath: Quickshell.env("HOME")
|
||||
nameFilters: ["*.wav", "*.mp3", "*.ogg", "*.flac", "*.m4a", "*.aac"]
|
||||
onAccepted: paths => {
|
||||
if (paths.length > 0) {
|
||||
Settings.data.notifications.sounds.criticalSoundFile = paths[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
signal openUnifiedPicker
|
||||
signal openLowPicker
|
||||
signal openNormalPicker
|
||||
signal openCriticalPicker
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.notifications.sounds.section.label")
|
||||
description: I18n.tr("settings.notifications.sounds.section.description")
|
||||
}
|
||||
|
||||
// QtMultimedia unavailable message
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
visible: !SoundService.multimediaAvailable
|
||||
implicitHeight: unavailableContent.implicitHeight + Style.marginL * 2
|
||||
|
||||
RowLayout {
|
||||
id: unavailableContent
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL
|
||||
spacing: Style.marginM
|
||||
|
||||
NIcon {
|
||||
icon: "warning"
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: Style.fontSizeXL
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NLabel {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.notifications.sounds.unavailable.label")
|
||||
description: I18n.tr("settings.notifications.sounds.unavailable.description")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.notifications.sounds.enabled.label")
|
||||
description: I18n.tr("settings.notifications.sounds.enabled.description")
|
||||
checked: Settings.data.notifications?.sounds?.enabled ?? false
|
||||
visible: SoundService.multimediaAvailable
|
||||
onToggled: checked => Settings.data.notifications.sounds.enabled = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.sounds.enabled")
|
||||
}
|
||||
|
||||
// Sound Volume
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
visible: SoundService.multimediaAvailable && (Settings.data.notifications?.sounds?.enabled ?? false)
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.notifications.sounds.volume.label")
|
||||
description: I18n.tr("settings.notifications.sounds.volume.description")
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.notifications?.sounds?.volume ?? 0.5
|
||||
onMoved: value => Settings.data.notifications.sounds.volume = value
|
||||
text: Math.round((Settings.data.notifications?.sounds?.volume ?? 0.5) * 100) + "%"
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.sounds.volume")
|
||||
}
|
||||
}
|
||||
|
||||
// Separate Sounds Toggle
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
visible: SoundService.multimediaAvailable && (Settings.data.notifications?.sounds?.enabled ?? false)
|
||||
label: I18n.tr("settings.notifications.sounds.separate.label")
|
||||
description: I18n.tr("settings.notifications.sounds.separate.description")
|
||||
checked: Settings.data.notifications?.sounds?.separateSounds ?? false
|
||||
onToggled: checked => Settings.data.notifications.sounds.separateSounds = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.sounds.separateSounds")
|
||||
}
|
||||
|
||||
// Unified Sound File (shown when separateSounds is false)
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
visible: SoundService.multimediaAvailable && (Settings.data.notifications?.sounds?.enabled ?? false) && !(Settings.data.notifications?.sounds?.separateSounds ?? false)
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.notifications.sounds.files.unified.label")
|
||||
description: I18n.tr("settings.notifications.sounds.files.unified.description")
|
||||
}
|
||||
|
||||
NTextInputButton {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: I18n.tr("settings.notifications.sounds.files.placeholder")
|
||||
text: Settings.data.notifications?.sounds?.normalSoundFile ?? ""
|
||||
buttonIcon: "folder-open"
|
||||
buttonTooltip: I18n.tr("settings.notifications.sounds.files.select-file")
|
||||
onInputEditingFinished: {
|
||||
const soundPath = text;
|
||||
Settings.data.notifications.sounds.normalSoundFile = soundPath;
|
||||
Settings.data.notifications.sounds.lowSoundFile = soundPath;
|
||||
Settings.data.notifications.sounds.criticalSoundFile = soundPath;
|
||||
}
|
||||
onButtonClicked: root.openUnifiedPicker()
|
||||
}
|
||||
}
|
||||
|
||||
// Separate Sound Files (shown when separateSounds is true)
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
visible: SoundService.multimediaAvailable && (Settings.data.notifications?.sounds?.enabled ?? false) && (Settings.data.notifications?.sounds?.separateSounds ?? false)
|
||||
|
||||
// Low Urgency Sound File
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.notifications.sounds.files.low.label")
|
||||
description: I18n.tr("settings.notifications.sounds.files.low.description")
|
||||
}
|
||||
|
||||
NTextInputButton {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: I18n.tr("settings.notifications.sounds.files.placeholder")
|
||||
text: Settings.data.notifications?.sounds?.lowSoundFile ?? ""
|
||||
buttonIcon: "folder-open"
|
||||
buttonTooltip: I18n.tr("settings.notifications.sounds.files.select-file")
|
||||
onInputEditingFinished: Settings.data.notifications.sounds.lowSoundFile = text
|
||||
onButtonClicked: root.openLowPicker()
|
||||
}
|
||||
}
|
||||
|
||||
// Normal Urgency Sound File
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.notifications.sounds.files.normal.label")
|
||||
description: I18n.tr("settings.notifications.sounds.files.normal.description")
|
||||
}
|
||||
|
||||
NTextInputButton {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: I18n.tr("settings.notifications.sounds.files.placeholder")
|
||||
text: Settings.data.notifications?.sounds?.normalSoundFile ?? ""
|
||||
buttonIcon: "folder-open"
|
||||
buttonTooltip: I18n.tr("settings.notifications.sounds.files.select-file")
|
||||
onInputEditingFinished: Settings.data.notifications.sounds.normalSoundFile = text
|
||||
onButtonClicked: root.openNormalPicker()
|
||||
}
|
||||
}
|
||||
|
||||
// Critical Urgency Sound File
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.notifications.sounds.files.critical.label")
|
||||
description: I18n.tr("settings.notifications.sounds.files.critical.description")
|
||||
}
|
||||
|
||||
NTextInputButton {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: I18n.tr("settings.notifications.sounds.files.placeholder")
|
||||
text: Settings.data.notifications?.sounds?.criticalSoundFile ?? ""
|
||||
buttonIcon: "folder-open"
|
||||
buttonTooltip: I18n.tr("settings.notifications.sounds.files.select-file")
|
||||
onInputEditingFinished: Settings.data.notifications.sounds.criticalSoundFile = text
|
||||
onButtonClicked: root.openCriticalPicker()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Excluded Apps List
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
visible: SoundService.multimediaAvailable && (Settings.data.notifications?.sounds?.enabled ?? false)
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.notifications.sounds.excluded-apps.label")
|
||||
description: I18n.tr("settings.notifications.sounds.excluded-apps.description")
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: I18n.tr("settings.notifications.sounds.excluded-apps.placeholder")
|
||||
text: Settings.data.notifications?.sounds?.excludedApps ?? ""
|
||||
onEditingFinished: Settings.data.notifications.sounds.excludedApps = text
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.notifications.toast.section.label")
|
||||
description: I18n.tr("settings.notifications.toast.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.notifications.toast.keyboard.label")
|
||||
description: I18n.tr("settings.notifications.toast.keyboard.description")
|
||||
checked: Settings.data.notifications.enableKeyboardLayoutToast
|
||||
onToggled: checked => Settings.data.notifications.enableKeyboardLayoutToast = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.enableKeyboardLayoutToast")
|
||||
}
|
||||
}
|
||||
@@ -1,605 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.System
|
||||
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
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.notifications.settings.section.label")
|
||||
description: I18n.tr("settings.notifications.settings.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.notifications.settings.enabled.label")
|
||||
description: I18n.tr("settings.notifications.settings.enabled.description")
|
||||
checked: Settings.data.notifications.enabled !== false
|
||||
onToggled: checked => Settings.data.notifications.enabled = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.enabled")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.notifications.settings.do-not-disturb.label")
|
||||
description: I18n.tr("settings.notifications.settings.do-not-disturb.description")
|
||||
checked: NotificationService.doNotDisturb
|
||||
onToggled: checked => NotificationService.doNotDisturb = checked
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.notifications.settings.location.label")
|
||||
description: I18n.tr("settings.notifications.settings.location.description")
|
||||
model: [
|
||||
{
|
||||
"key": "top",
|
||||
"name": I18n.tr("options.launcher.position.top_center")
|
||||
},
|
||||
{
|
||||
"key": "top_left",
|
||||
"name": I18n.tr("options.launcher.position.top_left")
|
||||
},
|
||||
{
|
||||
"key": "top_right",
|
||||
"name": I18n.tr("options.launcher.position.top_right")
|
||||
},
|
||||
{
|
||||
"key": "bottom",
|
||||
"name": I18n.tr("options.launcher.position.bottom_center")
|
||||
},
|
||||
{
|
||||
"key": "bottom_left",
|
||||
"name": I18n.tr("options.launcher.position.bottom_left")
|
||||
},
|
||||
{
|
||||
"key": "bottom_right",
|
||||
"name": I18n.tr("options.launcher.position.bottom_right")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.notifications.location || "top_right"
|
||||
onSelected: key => Settings.data.notifications.location = key
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.location")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.notifications.settings.always-on-top.label")
|
||||
description: I18n.tr("settings.notifications.settings.always-on-top.description")
|
||||
checked: Settings.data.notifications.overlayLayer
|
||||
onToggled: checked => Settings.data.notifications.overlayLayer = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.overlayLayer")
|
||||
}
|
||||
|
||||
// Background Opacity
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.notifications.settings.background-opacity.label")
|
||||
description: I18n.tr("settings.notifications.settings.background-opacity.description")
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.notifications.backgroundOpacity
|
||||
onMoved: value => Settings.data.notifications.backgroundOpacity = value
|
||||
text: Math.round(Settings.data.notifications.backgroundOpacity * 100) + "%"
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.backgroundOpacity")
|
||||
}
|
||||
|
||||
// OSD settings moved to the dedicated OSD tab
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// Sound Settings
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.notifications.sounds.section.label")
|
||||
description: I18n.tr("settings.notifications.sounds.section.description")
|
||||
}
|
||||
|
||||
// QtMultimedia unavailable message
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
visible: !SoundService.multimediaAvailable
|
||||
implicitHeight: unavailableContent.implicitHeight + Style.marginL * 2
|
||||
|
||||
RowLayout {
|
||||
id: unavailableContent
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL
|
||||
spacing: Style.marginM
|
||||
|
||||
NIcon {
|
||||
icon: "warning"
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: Style.fontSizeXL
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NLabel {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.notifications.sounds.unavailable.label")
|
||||
description: I18n.tr("settings.notifications.sounds.unavailable.description")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.notifications.sounds.enabled.label")
|
||||
description: I18n.tr("settings.notifications.sounds.enabled.description")
|
||||
checked: Settings.data.notifications?.sounds?.enabled ?? false
|
||||
visible: SoundService.multimediaAvailable
|
||||
onToggled: checked => Settings.data.notifications.sounds.enabled = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.sounds.enabled")
|
||||
}
|
||||
|
||||
// Sound Volume
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
visible: SoundService.multimediaAvailable && (Settings.data.notifications?.sounds?.enabled ?? false)
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.notifications.sounds.volume.label")
|
||||
description: I18n.tr("settings.notifications.sounds.volume.description")
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.notifications?.sounds?.volume ?? 0.5
|
||||
onMoved: value => Settings.data.notifications.sounds.volume = value
|
||||
text: Math.round((Settings.data.notifications?.sounds?.volume ?? 0.5) * 100) + "%"
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.sounds.volume")
|
||||
}
|
||||
}
|
||||
|
||||
// Separate Sounds Toggle
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
visible: SoundService.multimediaAvailable && (Settings.data.notifications?.sounds?.enabled ?? false)
|
||||
label: I18n.tr("settings.notifications.sounds.separate.label")
|
||||
description: I18n.tr("settings.notifications.sounds.separate.description")
|
||||
checked: Settings.data.notifications?.sounds?.separateSounds ?? false
|
||||
onToggled: checked => Settings.data.notifications.sounds.separateSounds = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.sounds.separateSounds")
|
||||
}
|
||||
|
||||
// Unified Sound File (shown when separateSounds is false)
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
visible: SoundService.multimediaAvailable && (Settings.data.notifications?.sounds?.enabled ?? false) && !(Settings.data.notifications?.sounds?.separateSounds ?? false)
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.notifications.sounds.files.unified.label")
|
||||
description: I18n.tr("settings.notifications.sounds.files.unified.description")
|
||||
}
|
||||
|
||||
NTextInputButton {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: I18n.tr("settings.notifications.sounds.files.placeholder")
|
||||
text: Settings.data.notifications?.sounds?.normalSoundFile ?? ""
|
||||
buttonIcon: "folder-open"
|
||||
buttonTooltip: I18n.tr("settings.notifications.sounds.files.select-file")
|
||||
onInputEditingFinished: {
|
||||
const soundPath = text;
|
||||
Settings.data.notifications.sounds.normalSoundFile = soundPath;
|
||||
Settings.data.notifications.sounds.lowSoundFile = soundPath;
|
||||
Settings.data.notifications.sounds.criticalSoundFile = soundPath;
|
||||
}
|
||||
onButtonClicked: unifiedSoundFilePicker.open()
|
||||
}
|
||||
}
|
||||
|
||||
// Separate Sound Files (shown when separateSounds is true)
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
visible: SoundService.multimediaAvailable && (Settings.data.notifications?.sounds?.enabled ?? false) && (Settings.data.notifications?.sounds?.separateSounds ?? false)
|
||||
|
||||
// Low Urgency Sound File
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.notifications.sounds.files.low.label")
|
||||
description: I18n.tr("settings.notifications.sounds.files.low.description")
|
||||
}
|
||||
|
||||
NTextInputButton {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: I18n.tr("settings.notifications.sounds.files.placeholder")
|
||||
text: Settings.data.notifications?.sounds?.lowSoundFile ?? ""
|
||||
buttonIcon: "folder-open"
|
||||
buttonTooltip: I18n.tr("settings.notifications.sounds.files.select-file")
|
||||
onInputEditingFinished: Settings.data.notifications.sounds.lowSoundFile = text
|
||||
onButtonClicked: lowSoundFilePicker.open()
|
||||
}
|
||||
}
|
||||
|
||||
// Normal Urgency Sound File
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.notifications.sounds.files.normal.label")
|
||||
description: I18n.tr("settings.notifications.sounds.files.normal.description")
|
||||
}
|
||||
|
||||
NTextInputButton {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: I18n.tr("settings.notifications.sounds.files.placeholder")
|
||||
text: Settings.data.notifications?.sounds?.normalSoundFile ?? ""
|
||||
buttonIcon: "folder-open"
|
||||
buttonTooltip: I18n.tr("settings.notifications.sounds.files.select-file")
|
||||
onInputEditingFinished: Settings.data.notifications.sounds.normalSoundFile = text
|
||||
onButtonClicked: normalSoundFilePicker.open()
|
||||
}
|
||||
}
|
||||
|
||||
// Critical Urgency Sound File
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.notifications.sounds.files.critical.label")
|
||||
description: I18n.tr("settings.notifications.sounds.files.critical.description")
|
||||
}
|
||||
|
||||
NTextInputButton {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: I18n.tr("settings.notifications.sounds.files.placeholder")
|
||||
text: Settings.data.notifications?.sounds?.criticalSoundFile ?? ""
|
||||
buttonIcon: "folder-open"
|
||||
buttonTooltip: I18n.tr("settings.notifications.sounds.files.select-file")
|
||||
onInputEditingFinished: Settings.data.notifications.sounds.criticalSoundFile = text
|
||||
onButtonClicked: criticalSoundFilePicker.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Excluded Apps List
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
visible: SoundService.multimediaAvailable && (Settings.data.notifications?.sounds?.enabled ?? false)
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.notifications.sounds.excluded-apps.label")
|
||||
description: I18n.tr("settings.notifications.sounds.excluded-apps.description")
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: I18n.tr("settings.notifications.sounds.excluded-apps.placeholder")
|
||||
text: Settings.data.notifications?.sounds?.excludedApps ?? ""
|
||||
onEditingFinished: Settings.data.notifications.sounds.excludedApps = text
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// Duration
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.notifications.duration.section.label")
|
||||
description: I18n.tr("settings.notifications.duration.section.description")
|
||||
}
|
||||
|
||||
// Respect Expire Timeout (eg. --expire-time flag in notify-send)
|
||||
NToggle {
|
||||
label: I18n.tr("settings.notifications.duration.respect-expire.label")
|
||||
description: I18n.tr("settings.notifications.duration.respect-expire.description")
|
||||
checked: Settings.data.notifications.respectExpireTimeout
|
||||
onToggled: checked => Settings.data.notifications.respectExpireTimeout = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.respectExpireTimeout")
|
||||
}
|
||||
|
||||
// Low Urgency Duration
|
||||
RowLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.notifications.duration.low-urgency.label")
|
||||
description: I18n.tr("settings.notifications.duration.low-urgency.description")
|
||||
from: 1
|
||||
to: 30
|
||||
stepSize: 1
|
||||
value: Settings.data.notifications.lowUrgencyDuration
|
||||
onMoved: value => Settings.data.notifications.lowUrgencyDuration = value
|
||||
text: Settings.data.notifications.lowUrgencyDuration + "s"
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.lowUrgencyDuration")
|
||||
}
|
||||
// Reset button container
|
||||
Item {
|
||||
Layout.preferredWidth: 30 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: 30 * Style.uiScaleRatio
|
||||
|
||||
NIconButton {
|
||||
icon: "restore"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
tooltipText: I18n.tr("settings.notifications.duration.reset")
|
||||
onClicked: Settings.data.notifications.lowUrgencyDuration = 3
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Normal Urgency Duration
|
||||
RowLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.notifications.duration.normal-urgency.label")
|
||||
description: I18n.tr("settings.notifications.duration.normal-urgency.description")
|
||||
from: 1
|
||||
to: 30
|
||||
stepSize: 1
|
||||
value: Settings.data.notifications.normalUrgencyDuration
|
||||
onMoved: value => Settings.data.notifications.normalUrgencyDuration = value
|
||||
text: Settings.data.notifications.normalUrgencyDuration + "s"
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.normalUrgencyDuration")
|
||||
}
|
||||
|
||||
// Reset button container
|
||||
Item {
|
||||
Layout.preferredWidth: 30 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: 30 * Style.uiScaleRatio
|
||||
|
||||
NIconButton {
|
||||
icon: "restore"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
tooltipText: I18n.tr("settings.notifications.duration.reset")
|
||||
onClicked: Settings.data.notifications.normalUrgencyDuration = 8
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Critical Urgency Duration
|
||||
RowLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.notifications.duration.critical-urgency.label")
|
||||
description: I18n.tr("settings.notifications.duration.critical-urgency.description")
|
||||
from: 1
|
||||
to: 30
|
||||
stepSize: 1
|
||||
value: Settings.data.notifications.criticalUrgencyDuration
|
||||
onMoved: value => Settings.data.notifications.criticalUrgencyDuration = value
|
||||
text: Settings.data.notifications.criticalUrgencyDuration + "s"
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.criticalUrgencyDuration")
|
||||
}
|
||||
// Reset button container
|
||||
Item {
|
||||
Layout.preferredWidth: 30 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: 30 * Style.uiScaleRatio
|
||||
|
||||
NIconButton {
|
||||
icon: "restore"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
tooltipText: I18n.tr("settings.notifications.duration.reset")
|
||||
onClicked: Settings.data.notifications.criticalUrgencyDuration = 15
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// History Configuration
|
||||
NHeader {
|
||||
label: I18n.tr("settings.notifications.history.section.label")
|
||||
description: I18n.tr("settings.notifications.history.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.notifications.history.low-urgency.label")
|
||||
description: I18n.tr("settings.notifications.history.low-urgency.description")
|
||||
checked: Settings.data.notifications?.saveToHistory?.low !== false
|
||||
onToggled: checked => Settings.data.notifications.saveToHistory.low = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.saveToHistory.low")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.notifications.history.normal-urgency.label")
|
||||
description: I18n.tr("settings.notifications.history.normal-urgency.description")
|
||||
checked: Settings.data.notifications?.saveToHistory?.normal !== false
|
||||
onToggled: checked => Settings.data.notifications.saveToHistory.normal = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.saveToHistory.normal")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.notifications.history.critical-urgency.label")
|
||||
description: I18n.tr("settings.notifications.history.critical-urgency.description")
|
||||
checked: Settings.data.notifications?.saveToHistory?.critical !== false
|
||||
onToggled: checked => Settings.data.notifications.saveToHistory.critical = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.saveToHistory.critical")
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// Monitor Configuration
|
||||
NHeader {
|
||||
label: I18n.tr("settings.notifications.monitors.section.label")
|
||||
description: I18n.tr("settings.notifications.monitors.section.description")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NCheckbox {
|
||||
Layout.fillWidth: true
|
||||
label: modelData.name || I18n.tr("system.unknown")
|
||||
description: {
|
||||
const compositorScale = CompositorService.getDisplayScale(modelData.name);
|
||||
I18n.tr("system.monitor-description", {
|
||||
"model": modelData.model,
|
||||
"width": modelData.width * compositorScale,
|
||||
"height": modelData.height * compositorScale,
|
||||
"scale": compositorScale
|
||||
});
|
||||
}
|
||||
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.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// Toast Configuration
|
||||
NHeader {
|
||||
label: I18n.tr("settings.notifications.toast.section.label")
|
||||
description: I18n.tr("settings.notifications.toast.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.notifications.toast.keyboard.label")
|
||||
description: I18n.tr("settings.notifications.toast.keyboard.description")
|
||||
checked: Settings.data.notifications.enableKeyboardLayoutToast
|
||||
onToggled: checked => Settings.data.notifications.enableKeyboardLayoutToast = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("notifications.enableKeyboardLayoutToast")
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// File Pickers for Sound Files
|
||||
NFilePicker {
|
||||
id: unifiedSoundFilePicker
|
||||
title: I18n.tr("settings.notifications.sounds.files.unified.select-title")
|
||||
selectionMode: "files"
|
||||
initialPath: Quickshell.env("HOME")
|
||||
nameFilters: ["*.wav", "*.mp3", "*.ogg", "*.flac", "*.m4a", "*.aac"]
|
||||
onAccepted: paths => {
|
||||
if (paths.length > 0) {
|
||||
const soundPath = paths[0];
|
||||
Settings.data.notifications.sounds.normalSoundFile = soundPath;
|
||||
Settings.data.notifications.sounds.lowSoundFile = soundPath;
|
||||
Settings.data.notifications.sounds.criticalSoundFile = soundPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NFilePicker {
|
||||
id: lowSoundFilePicker
|
||||
title: I18n.tr("settings.notifications.sounds.files.low.select-title")
|
||||
selectionMode: "files"
|
||||
initialPath: Quickshell.env("HOME")
|
||||
nameFilters: ["*.wav", "*.mp3", "*.ogg", "*.flac", "*.m4a", "*.aac"]
|
||||
onAccepted: paths => {
|
||||
if (paths.length > 0) {
|
||||
Settings.data.notifications.sounds.lowSoundFile = paths[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NFilePicker {
|
||||
id: normalSoundFilePicker
|
||||
title: I18n.tr("settings.notifications.sounds.files.normal.select-title")
|
||||
selectionMode: "files"
|
||||
initialPath: Quickshell.env("HOME")
|
||||
nameFilters: ["*.wav", "*.mp3", "*.ogg", "*.flac", "*.m4a", "*.aac"]
|
||||
onAccepted: paths => {
|
||||
if (paths.length > 0) {
|
||||
Settings.data.notifications.sounds.normalSoundFile = paths[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NFilePicker {
|
||||
id: criticalSoundFilePicker
|
||||
title: I18n.tr("settings.notifications.sounds.files.critical.select-title")
|
||||
selectionMode: "files"
|
||||
initialPath: Quickshell.env("HOME")
|
||||
nameFilters: ["*.wav", "*.mp3", "*.ogg", "*.flac", "*.m4a", "*.aac"]
|
||||
onAccepted: paths => {
|
||||
if (paths.length > 0) {
|
||||
Settings.data.notifications.sounds.criticalSoundFile = paths[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
property var screen
|
||||
|
||||
NHeader {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.system-monitor.general.section.label")
|
||||
description: I18n.tr("settings.system-monitor.general.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginM
|
||||
label: I18n.tr("settings.system-monitor.enable-dgpu-monitoring.label")
|
||||
description: I18n.tr("settings.system-monitor.enable-dgpu-monitoring.description")
|
||||
checked: Settings.data.systemMonitor.enableDgpuMonitoring
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("systemMonitor.enableDgpuMonitoring")
|
||||
onToggled: checked => Settings.data.systemMonitor.enableDgpuMonitoring = checked
|
||||
}
|
||||
|
||||
// Colors Section
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.system-monitor.use-custom-highlight-colors.label")
|
||||
description: I18n.tr("settings.system-monitor.use-custom-highlight-colors.description")
|
||||
checked: Settings.data.systemMonitor.useCustomColors
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("systemMonitor.useCustomColors")
|
||||
onToggled: checked => {
|
||||
// If enabling custom colors and no custom color is saved, persist current theme colors
|
||||
if (checked) {
|
||||
if (!Settings.data.systemMonitor.warningColor || Settings.data.systemMonitor.warningColor === "") {
|
||||
Settings.data.systemMonitor.warningColor = Color.mTertiary.toString();
|
||||
}
|
||||
if (!Settings.data.systemMonitor.criticalColor || Settings.data.systemMonitor.criticalColor === "") {
|
||||
Settings.data.systemMonitor.criticalColor = Color.mError.toString();
|
||||
}
|
||||
}
|
||||
Settings.data.systemMonitor.useCustomColors = checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
visible: Settings.data.systemMonitor.useCustomColors
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.system-monitor.warning-color.label")
|
||||
pointSize: Style.fontSizeS
|
||||
}
|
||||
|
||||
NColorPicker {
|
||||
screen: root.screen
|
||||
Layout.preferredWidth: Style.sliderWidth
|
||||
Layout.preferredHeight: Style.baseWidgetSize
|
||||
enabled: Settings.data.systemMonitor.useCustomColors
|
||||
selectedColor: Settings.data.systemMonitor.warningColor || Color.mTertiary
|
||||
onColorSelected: color => Settings.data.systemMonitor.warningColor = color
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.system-monitor.critical-color.label")
|
||||
pointSize: Style.fontSizeS
|
||||
}
|
||||
|
||||
NColorPicker {
|
||||
screen: root.screen
|
||||
Layout.preferredWidth: Style.sliderWidth
|
||||
Layout.preferredHeight: Style.baseWidgetSize
|
||||
enabled: Settings.data.systemMonitor.useCustomColors
|
||||
selectedColor: Settings.data.systemMonitor.criticalColor || Color.mError
|
||||
onColorSelected: color => Settings.data.systemMonitor.criticalColor = color
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.system-monitor.polling-section.label")
|
||||
description: I18n.tr("settings.system-monitor.polling-section.description")
|
||||
}
|
||||
|
||||
// CPU Polling
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NText {
|
||||
Layout.fillWidth: true
|
||||
text: I18n.tr("settings.system-monitor.cpu-section.label")
|
||||
pointSize: Style.fontSizeM
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
from: 250
|
||||
to: 10000
|
||||
stepSize: 250
|
||||
value: Settings.data.systemMonitor.cpuPollingInterval
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("systemMonitor.cpuPollingInterval")
|
||||
onValueChanged: Settings.data.systemMonitor.cpuPollingInterval = value
|
||||
suffix: " ms"
|
||||
}
|
||||
}
|
||||
|
||||
// Temperature Polling
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NText {
|
||||
Layout.fillWidth: true
|
||||
text: I18n.tr("settings.system-monitor.temperature-section.label")
|
||||
pointSize: Style.fontSizeM
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
from: 250
|
||||
to: 10000
|
||||
stepSize: 250
|
||||
value: Settings.data.systemMonitor.tempPollingInterval
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("systemMonitor.tempPollingInterval")
|
||||
onValueChanged: Settings.data.systemMonitor.tempPollingInterval = value
|
||||
suffix: " ms"
|
||||
}
|
||||
}
|
||||
|
||||
// GPU Polling
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
visible: SystemStatService.gpuAvailable
|
||||
|
||||
NText {
|
||||
Layout.fillWidth: true
|
||||
text: I18n.tr("settings.system-monitor.gpu-section.label")
|
||||
pointSize: Style.fontSizeM
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
from: 250
|
||||
to: 10000
|
||||
stepSize: 250
|
||||
value: Settings.data.systemMonitor.gpuPollingInterval
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("systemMonitor.gpuPollingInterval")
|
||||
onValueChanged: Settings.data.systemMonitor.gpuPollingInterval = value
|
||||
suffix: " ms"
|
||||
}
|
||||
}
|
||||
|
||||
// Load Average Polling
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NText {
|
||||
Layout.fillWidth: true
|
||||
text: I18n.tr("settings.system-monitor.load-average-section.label")
|
||||
pointSize: Style.fontSizeM
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
from: 250
|
||||
to: 10000
|
||||
stepSize: 250
|
||||
value: Settings.data.systemMonitor.loadAvgPollingInterval
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("systemMonitor.loadAvgPollingInterval")
|
||||
onValueChanged: Settings.data.systemMonitor.loadAvgPollingInterval = value
|
||||
suffix: " ms"
|
||||
}
|
||||
}
|
||||
|
||||
// Memory Polling
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NText {
|
||||
Layout.fillWidth: true
|
||||
text: I18n.tr("settings.system-monitor.memory-section.label")
|
||||
pointSize: Style.fontSizeM
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
from: 250
|
||||
to: 10000
|
||||
stepSize: 250
|
||||
value: Settings.data.systemMonitor.memPollingInterval
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("systemMonitor.memPollingInterval")
|
||||
onValueChanged: Settings.data.systemMonitor.memPollingInterval = value
|
||||
suffix: " ms"
|
||||
}
|
||||
}
|
||||
|
||||
// Disk Polling
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NText {
|
||||
Layout.fillWidth: true
|
||||
text: I18n.tr("settings.system-monitor.disk-section.label")
|
||||
pointSize: Style.fontSizeM
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
from: 250
|
||||
to: 10000
|
||||
stepSize: 250
|
||||
value: Settings.data.systemMonitor.diskPollingInterval
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("systemMonitor.diskPollingInterval")
|
||||
onValueChanged: Settings.data.systemMonitor.diskPollingInterval = value
|
||||
suffix: " ms"
|
||||
}
|
||||
}
|
||||
|
||||
// Network Polling
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NText {
|
||||
Layout.fillWidth: true
|
||||
text: I18n.tr("settings.system-monitor.network-section.label")
|
||||
pointSize: Style.fontSizeM
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
from: 250
|
||||
to: 10000
|
||||
stepSize: 250
|
||||
value: Settings.data.systemMonitor.networkPollingInterval
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("systemMonitor.networkPollingInterval")
|
||||
onValueChanged: Settings.data.systemMonitor.networkPollingInterval = value
|
||||
suffix: " ms"
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginM
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
label: I18n.tr("settings.system-monitor.external-monitor.label")
|
||||
description: I18n.tr("settings.system-monitor.external-monitor.description")
|
||||
placeholderText: I18n.tr("settings.system-monitor.external-monitor.placeholder")
|
||||
text: Settings.data.systemMonitor.externalMonitor
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("systemMonitor.externalMonitor")
|
||||
onTextChanged: Settings.data.systemMonitor.externalMonitor = text
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
property var screen
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.system-monitor.tabs.general")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.system-monitor.tabs.thresholds")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.system-monitor.tabs.polling")
|
||||
tabIndex: 2
|
||||
checked: subTabBar.currentIndex === 2
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
GeneralSubTab {
|
||||
screen: root.screen
|
||||
}
|
||||
ThresholdsSubTab {}
|
||||
PollingSubTab {}
|
||||
}
|
||||
}
|
||||
+1
-307
@@ -1,109 +1,14 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property var screen
|
||||
|
||||
spacing: Style.marginL
|
||||
|
||||
NHeader {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.system-monitor.general.section.label")
|
||||
description: I18n.tr("settings.system-monitor.general.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginM
|
||||
label: I18n.tr("settings.system-monitor.enable-dgpu-monitoring.label")
|
||||
description: I18n.tr("settings.system-monitor.enable-dgpu-monitoring.description")
|
||||
checked: Settings.data.systemMonitor.enableDgpuMonitoring
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("systemMonitor.enableDgpuMonitoring")
|
||||
onToggled: checked => Settings.data.systemMonitor.enableDgpuMonitoring = checked
|
||||
}
|
||||
|
||||
// Colors Section
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.system-monitor.use-custom-highlight-colors.label")
|
||||
description: I18n.tr("settings.system-monitor.use-custom-highlight-colors.description")
|
||||
checked: Settings.data.systemMonitor.useCustomColors
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("systemMonitor.useCustomColors")
|
||||
onToggled: checked => {
|
||||
// If enabling custom colors and no custom color is saved, persist current theme colors
|
||||
if (checked) {
|
||||
if (!Settings.data.systemMonitor.warningColor || Settings.data.systemMonitor.warningColor === "") {
|
||||
Settings.data.systemMonitor.warningColor = Color.mTertiary.toString();
|
||||
}
|
||||
if (!Settings.data.systemMonitor.criticalColor || Settings.data.systemMonitor.criticalColor === "") {
|
||||
Settings.data.systemMonitor.criticalColor = Color.mError.toString();
|
||||
}
|
||||
}
|
||||
Settings.data.systemMonitor.useCustomColors = checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
visible: Settings.data.systemMonitor.useCustomColors
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.system-monitor.warning-color.label")
|
||||
pointSize: Style.fontSizeS
|
||||
}
|
||||
|
||||
NColorPicker {
|
||||
screen: root.screen
|
||||
Layout.preferredWidth: Style.sliderWidth
|
||||
Layout.preferredHeight: Style.baseWidgetSize
|
||||
enabled: Settings.data.systemMonitor.useCustomColors
|
||||
selectedColor: Settings.data.systemMonitor.warningColor || Color.mTertiary
|
||||
onColorSelected: color => Settings.data.systemMonitor.warningColor = color
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.system-monitor.critical-color.label")
|
||||
pointSize: Style.fontSizeS
|
||||
}
|
||||
|
||||
NColorPicker {
|
||||
screen: root.screen
|
||||
Layout.preferredWidth: Style.sliderWidth
|
||||
Layout.preferredHeight: Style.baseWidgetSize
|
||||
enabled: Settings.data.systemMonitor.useCustomColors
|
||||
selectedColor: Settings.data.systemMonitor.criticalColor || Color.mError
|
||||
onColorSelected: color => Settings.data.systemMonitor.criticalColor = color
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
Layout.fillWidth: true
|
||||
@@ -144,7 +49,6 @@ ColumnLayout {
|
||||
defaultValue: Settings.getDefaultValue("systemMonitor.cpuWarningThreshold")
|
||||
onValueChanged: {
|
||||
Settings.data.systemMonitor.cpuWarningThreshold = value;
|
||||
// Ensure critical >= warning
|
||||
if (Settings.data.systemMonitor.cpuCriticalThreshold < value) {
|
||||
Settings.data.systemMonitor.cpuCriticalThreshold = value;
|
||||
}
|
||||
@@ -176,30 +80,6 @@ ColumnLayout {
|
||||
suffix: "%"
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: I18n.tr("settings.system-monitor.polling-interval.label")
|
||||
pointSize: Style.fontSizeS
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
from: 250
|
||||
to: 10000
|
||||
stepSize: 250
|
||||
value: Settings.data.systemMonitor.cpuPollingInterval
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("systemMonitor.cpuPollingInterval")
|
||||
onValueChanged: Settings.data.systemMonitor.cpuPollingInterval = value
|
||||
suffix: " ms"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Temperature
|
||||
@@ -266,30 +146,6 @@ ColumnLayout {
|
||||
suffix: "°C"
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: I18n.tr("settings.system-monitor.polling-interval.label")
|
||||
pointSize: Style.fontSizeS
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
from: 250
|
||||
to: 10000
|
||||
stepSize: 250
|
||||
value: Settings.data.systemMonitor.tempPollingInterval
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("systemMonitor.tempPollingInterval")
|
||||
onValueChanged: Settings.data.systemMonitor.tempPollingInterval = value
|
||||
suffix: " ms"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GPU Temperature
|
||||
@@ -358,67 +214,6 @@ ColumnLayout {
|
||||
suffix: "°C"
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: I18n.tr("settings.system-monitor.polling-interval.label")
|
||||
pointSize: Style.fontSizeS
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
from: 250
|
||||
to: 10000
|
||||
stepSize: 250
|
||||
value: Settings.data.systemMonitor.gpuPollingInterval
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("systemMonitor.gpuPollingInterval")
|
||||
onValueChanged: Settings.data.systemMonitor.gpuPollingInterval = value
|
||||
suffix: " ms"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load Average
|
||||
NText {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginM
|
||||
text: I18n.tr("settings.system-monitor.load-average-section.label")
|
||||
pointSize: Style.fontSizeM
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: I18n.tr("settings.system-monitor.polling-interval.label")
|
||||
pointSize: Style.fontSizeS
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
from: 250
|
||||
to: 10000
|
||||
stepSize: 250
|
||||
value: Settings.data.systemMonitor.loadAvgPollingInterval
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("systemMonitor.loadAvgPollingInterval")
|
||||
onValueChanged: Settings.data.systemMonitor.loadAvgPollingInterval = value
|
||||
suffix: " ms"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Memory Usage
|
||||
@@ -485,30 +280,6 @@ ColumnLayout {
|
||||
suffix: "%"
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: I18n.tr("settings.system-monitor.polling-interval.label")
|
||||
pointSize: Style.fontSizeS
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
from: 250
|
||||
to: 10000
|
||||
stepSize: 250
|
||||
value: Settings.data.systemMonitor.memPollingInterval
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("systemMonitor.memPollingInterval")
|
||||
onValueChanged: Settings.data.systemMonitor.memPollingInterval = value
|
||||
suffix: " ms"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Disk Usage
|
||||
@@ -575,82 +346,5 @@ ColumnLayout {
|
||||
suffix: "%"
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: I18n.tr("settings.system-monitor.polling-interval.label")
|
||||
pointSize: Style.fontSizeS
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
from: 250
|
||||
to: 10000
|
||||
stepSize: 250
|
||||
value: Settings.data.systemMonitor.diskPollingInterval
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("systemMonitor.diskPollingInterval")
|
||||
onValueChanged: Settings.data.systemMonitor.diskPollingInterval = value
|
||||
suffix: " ms"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Network
|
||||
NText {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginM
|
||||
text: I18n.tr("settings.system-monitor.network-section.label")
|
||||
pointSize: Style.fontSizeM
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: I18n.tr("settings.system-monitor.polling-interval.label")
|
||||
pointSize: Style.fontSizeS
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
from: 250
|
||||
to: 10000
|
||||
stepSize: 250
|
||||
value: Settings.data.systemMonitor.networkPollingInterval
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("systemMonitor.networkPollingInterval")
|
||||
onValueChanged: Settings.data.systemMonitor.networkPollingInterval = value
|
||||
suffix: " ms"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
label: I18n.tr("settings.system-monitor.external-monitor.label")
|
||||
description: I18n.tr("settings.system-monitor.external-monitor.description")
|
||||
placeholderText: I18n.tr("settings.system-monitor.external-monitor.placeholder")
|
||||
text: Settings.data.systemMonitor.externalMonitor
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("systemMonitor.externalMonitor")
|
||||
onTextChanged: Settings.data.systemMonitor.externalMonitor = text
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.user-interface.appearance.section.label")
|
||||
description: I18n.tr("settings.user-interface.appearance.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.user-interface.tooltips.label")
|
||||
description: I18n.tr("settings.user-interface.tooltips.description")
|
||||
checked: Settings.data.ui.tooltipsEnabled
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("ui.tooltipsEnabled")
|
||||
onToggled: checked => Settings.data.ui.tooltipsEnabled = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.user-interface.box-border.label")
|
||||
description: I18n.tr("settings.user-interface.box-border.description")
|
||||
checked: Settings.data.ui.boxBorderEnabled
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("ui.boxBorderEnabled")
|
||||
onToggled: checked => Settings.data.ui.boxBorderEnabled = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.user-interface.shadows.label")
|
||||
description: I18n.tr("settings.user-interface.shadows.description")
|
||||
checked: Settings.data.general.enableShadows
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.enableShadows")
|
||||
onToggled: checked => Settings.data.general.enableShadows = checked
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
visible: Settings.data.general.enableShadows
|
||||
label: I18n.tr("settings.user-interface.shadows.direction.label")
|
||||
description: I18n.tr("settings.user-interface.shadows.direction.description")
|
||||
Layout.fillWidth: true
|
||||
|
||||
readonly property var shadowOptionsMap: ({
|
||||
"top_left": {
|
||||
"name": I18n.tr("options.shadow-direction.top_left"),
|
||||
"p": Qt.point(-2, -2)
|
||||
},
|
||||
"top": {
|
||||
"name": I18n.tr("options.shadow-direction.top"),
|
||||
"p": Qt.point(0, -3)
|
||||
},
|
||||
"top_right": {
|
||||
"name": I18n.tr("options.shadow-direction.top_right"),
|
||||
"p": Qt.point(2, -2)
|
||||
},
|
||||
"left": {
|
||||
"name": I18n.tr("options.shadow-direction.left"),
|
||||
"p": Qt.point(-3, 0)
|
||||
},
|
||||
"center": {
|
||||
"name": I18n.tr("options.shadow-direction.center"),
|
||||
"p": Qt.point(0, 0)
|
||||
},
|
||||
"right": {
|
||||
"name": I18n.tr("options.shadow-direction.right"),
|
||||
"p": Qt.point(3, 0)
|
||||
},
|
||||
"bottom_left": {
|
||||
"name": I18n.tr("options.shadow-direction.bottom_left"),
|
||||
"p": Qt.point(-2, 2)
|
||||
},
|
||||
"bottom": {
|
||||
"name": I18n.tr("options.shadow-direction.bottom"),
|
||||
"p": Qt.point(0, 3)
|
||||
},
|
||||
"bottom_right": {
|
||||
"name": I18n.tr("options.shadow-direction.bottom_right"),
|
||||
"p": Qt.point(2, 3)
|
||||
}
|
||||
})
|
||||
|
||||
model: Object.keys(shadowOptionsMap).map(function (k) {
|
||||
return {
|
||||
"key": k,
|
||||
"name": shadowOptionsMap[k].name
|
||||
};
|
||||
})
|
||||
|
||||
currentKey: Settings.data.general.shadowDirection
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.shadowDirection")
|
||||
|
||||
onSelected: function (key) {
|
||||
var opt = shadowOptionsMap[key];
|
||||
if (opt) {
|
||||
Settings.data.general.shadowDirection = key;
|
||||
Settings.data.general.shadowOffsetX = opt.p.x;
|
||||
Settings.data.general.shadowOffsetY = opt.p.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.user-interface.scaling.label")
|
||||
description: I18n.tr("settings.user-interface.scaling.description")
|
||||
from: 0.8
|
||||
to: 1.2
|
||||
stepSize: 0.05
|
||||
value: Settings.data.general.scaleRatio
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.scaleRatio")
|
||||
onMoved: value => Settings.data.general.scaleRatio = value
|
||||
text: Math.floor(Settings.data.general.scaleRatio * 100) + "%"
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 30 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: 30 * Style.uiScaleRatio
|
||||
|
||||
NIconButton {
|
||||
icon: "restore"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
tooltipText: I18n.tr("settings.user-interface.scaling.reset-scaling")
|
||||
onClicked: Settings.data.general.scaleRatio = 1.0
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.user-interface.box-border-radius.label")
|
||||
description: I18n.tr("settings.user-interface.box-border-radius.description")
|
||||
from: 0
|
||||
to: 2
|
||||
stepSize: 0.01
|
||||
value: Settings.data.general.radiusRatio
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.radiusRatio")
|
||||
onMoved: value => Settings.data.general.radiusRatio = value
|
||||
text: Math.floor(Settings.data.general.radiusRatio * 100) + "%"
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 30 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: 30 * Style.uiScaleRatio
|
||||
|
||||
NIconButton {
|
||||
icon: "restore"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
tooltipText: I18n.tr("settings.user-interface.box-border-radius.reset")
|
||||
onClicked: Settings.data.general.radiusRatio = 1.0
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.user-interface.control-border-radius.label")
|
||||
description: I18n.tr("settings.user-interface.control-border-radius.description")
|
||||
from: 0
|
||||
to: 2
|
||||
stepSize: 0.01
|
||||
value: Settings.data.general.iRadiusRatio
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.iRadiusRatio")
|
||||
onMoved: value => Settings.data.general.iRadiusRatio = value
|
||||
text: Math.floor(Settings.data.general.iRadiusRatio * 100) + "%"
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 30 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: 30 * Style.uiScaleRatio
|
||||
|
||||
NIconButton {
|
||||
icon: "restore"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
tooltipText: I18n.tr("settings.user-interface.control-border-radius.reset")
|
||||
onClicked: Settings.data.general.iRadiusRatio = 1.0
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
visible: !Settings.data.general.animationDisabled
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.user-interface.animation-speed.label")
|
||||
description: I18n.tr("settings.user-interface.animation-speed.description")
|
||||
from: 0
|
||||
to: 2.0
|
||||
stepSize: 0.01
|
||||
value: Settings.data.general.animationSpeed
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.animationSpeed")
|
||||
onMoved: value => Settings.data.general.animationSpeed = Math.max(value, 0.05)
|
||||
text: Math.round(Settings.data.general.animationSpeed * 100) + "%"
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 30 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: 30 * Style.uiScaleRatio
|
||||
|
||||
NIconButton {
|
||||
icon: "restore"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
tooltipText: I18n.tr("settings.user-interface.animation-speed.reset")
|
||||
onClicked: Settings.data.general.animationSpeed = 1.0
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.user-interface.animation-disable.label")
|
||||
description: I18n.tr("settings.user-interface.animation-disable.description")
|
||||
checked: Settings.data.general.animationDisabled
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.animationDisabled")
|
||||
onToggled: checked => Settings.data.general.animationDisabled = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.user-interface.section.label")
|
||||
description: I18n.tr("settings.user-interface.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.user-interface.panels-attached-to-bar.label")
|
||||
description: I18n.tr("settings.user-interface.panels-attached-to-bar.description")
|
||||
checked: Settings.data.ui.panelsAttachedToBar
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("ui.panelsAttachedToBar")
|
||||
onToggled: checked => Settings.data.ui.panelsAttachedToBar = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
visible: (Quickshell.screens.length > 1)
|
||||
label: I18n.tr("settings.user-interface.allow-panels-without-bar.label")
|
||||
description: I18n.tr("settings.user-interface.allow-panels-without-bar.description")
|
||||
checked: Settings.data.general.allowPanelsOnScreenWithoutBar
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.allowPanelsOnScreenWithoutBar")
|
||||
onToggled: checked => Settings.data.general.allowPanelsOnScreenWithoutBar = checked
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.user-interface.settings-panel-mode.label")
|
||||
description: I18n.tr("settings.user-interface.settings-panel-mode.description")
|
||||
Layout.fillWidth: true
|
||||
model: [
|
||||
{
|
||||
"key": "attached",
|
||||
"name": I18n.tr("options.settings-panel-mode.attached")
|
||||
},
|
||||
{
|
||||
"key": "centered",
|
||||
"name": I18n.tr("options.settings-panel-mode.centered")
|
||||
},
|
||||
{
|
||||
"key": "window",
|
||||
"name": I18n.tr("options.settings-panel-mode.window")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.ui.settingsPanelMode
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("ui.settingsPanelMode")
|
||||
onSelected: key => Settings.data.ui.settingsPanelMode = key
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.user-interface.panel-background-opacity.label")
|
||||
description: I18n.tr("settings.user-interface.panel-background-opacity.description")
|
||||
from: 0.4
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.ui.panelBackgroundOpacity
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("ui.panelBackgroundOpacity")
|
||||
onMoved: value => Settings.data.ui.panelBackgroundOpacity = value
|
||||
text: Math.floor(Settings.data.ui.panelBackgroundOpacity * 100) + "%"
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.user-interface.dimmer-opacity.label")
|
||||
description: I18n.tr("settings.user-interface.dimmer-opacity.description")
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.general.dimmerOpacity
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.dimmerOpacity")
|
||||
onMoved: value => Settings.data.general.dimmerOpacity = value
|
||||
text: Math.floor(Settings.data.general.dimmerOpacity * 100) + "%"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.general.screen-corners.section.label")
|
||||
description: I18n.tr("settings.general.screen-corners.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.general.screen-corners.show-corners.label")
|
||||
description: I18n.tr("settings.general.screen-corners.show-corners.description")
|
||||
checked: Settings.data.general.showScreenCorners
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.showScreenCorners")
|
||||
onToggled: checked => Settings.data.general.showScreenCorners = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.general.screen-corners.solid-black.label")
|
||||
description: I18n.tr("settings.general.screen-corners.solid-black.description")
|
||||
checked: Settings.data.general.forceBlackScreenCorners
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.forceBlackScreenCorners")
|
||||
onToggled: checked => Settings.data.general.forceBlackScreenCorners = checked
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.general.screen-corners.radius.label")
|
||||
description: I18n.tr("settings.general.screen-corners.radius.description")
|
||||
from: 0
|
||||
to: 2
|
||||
stepSize: 0.01
|
||||
value: Settings.data.general.screenRadiusRatio
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.screenRadiusRatio")
|
||||
onMoved: value => Settings.data.general.screenRadiusRatio = value
|
||||
text: Math.floor(Settings.data.general.screenRadiusRatio * 100) + "%"
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 30 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: 30 * Style.uiScaleRatio
|
||||
|
||||
NIconButton {
|
||||
icon: "restore"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
tooltipText: I18n.tr("settings.general.screen-corners.radius.reset")
|
||||
onClicked: Settings.data.general.screenRadiusRatio = 1.0
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.user-interface.tabs.panels")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.user-interface.tabs.appearance")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.user-interface.tabs.screen-corners")
|
||||
tabIndex: 2
|
||||
checked: subTabBar.currentIndex === 2
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
PanelsSubTab {}
|
||||
AppearanceSubTab {}
|
||||
ScreenCornersSubTab {}
|
||||
}
|
||||
}
|
||||
@@ -1,447 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
// User Interface
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.user-interface.section.label")
|
||||
description: I18n.tr("settings.user-interface.section.description")
|
||||
}
|
||||
|
||||
// Panels attached to bar and screen edges
|
||||
NToggle {
|
||||
label: I18n.tr("settings.user-interface.panels-attached-to-bar.label")
|
||||
description: I18n.tr("settings.user-interface.panels-attached-to-bar.description")
|
||||
checked: Settings.data.ui.panelsAttachedToBar
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("ui.panelsAttachedToBar")
|
||||
onToggled: checked => Settings.data.ui.panelsAttachedToBar = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
visible: (Quickshell.screens.length > 1)
|
||||
label: I18n.tr("settings.user-interface.allow-panels-without-bar.label")
|
||||
description: I18n.tr("settings.user-interface.allow-panels-without-bar.description")
|
||||
checked: Settings.data.general.allowPanelsOnScreenWithoutBar
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.allowPanelsOnScreenWithoutBar")
|
||||
onToggled: checked => Settings.data.general.allowPanelsOnScreenWithoutBar = checked
|
||||
}
|
||||
|
||||
// Settings panel display mode
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.user-interface.settings-panel-mode.label")
|
||||
description: I18n.tr("settings.user-interface.settings-panel-mode.description")
|
||||
Layout.fillWidth: true
|
||||
model: [
|
||||
{
|
||||
"key": "attached",
|
||||
"name": I18n.tr("options.settings-panel-mode.attached")
|
||||
},
|
||||
{
|
||||
"key": "centered",
|
||||
"name": I18n.tr("options.settings-panel-mode.centered")
|
||||
},
|
||||
{
|
||||
"key": "window",
|
||||
"name": I18n.tr("options.settings-panel-mode.window")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.ui.settingsPanelMode
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("ui.settingsPanelMode")
|
||||
onSelected: key => Settings.data.ui.settingsPanelMode = key
|
||||
}
|
||||
|
||||
// Panel Background Opacity
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.user-interface.panel-background-opacity.label")
|
||||
description: I18n.tr("settings.user-interface.panel-background-opacity.description")
|
||||
from: 0.4
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.ui.panelBackgroundOpacity
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("ui.panelBackgroundOpacity")
|
||||
onMoved: value => Settings.data.ui.panelBackgroundOpacity = value
|
||||
text: Math.floor(Settings.data.ui.panelBackgroundOpacity * 100) + "%"
|
||||
}
|
||||
|
||||
// Dim desktop opacity
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.user-interface.dimmer-opacity.label")
|
||||
description: I18n.tr("settings.user-interface.dimmer-opacity.description")
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.general.dimmerOpacity
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.dimmerOpacity")
|
||||
onMoved: value => Settings.data.general.dimmerOpacity = value
|
||||
text: Math.floor(Settings.data.general.dimmerOpacity * 100) + "%"
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.user-interface.tooltips.label")
|
||||
description: I18n.tr("settings.user-interface.tooltips.description")
|
||||
checked: Settings.data.ui.tooltipsEnabled
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("ui.tooltipsEnabled")
|
||||
onToggled: checked => Settings.data.ui.tooltipsEnabled = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.user-interface.box-border.label")
|
||||
description: I18n.tr("settings.user-interface.box-border.description")
|
||||
checked: Settings.data.ui.boxBorderEnabled
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("ui.boxBorderEnabled")
|
||||
onToggled: checked => Settings.data.ui.boxBorderEnabled = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.user-interface.shadows.label")
|
||||
description: I18n.tr("settings.user-interface.shadows.description")
|
||||
checked: Settings.data.general.enableShadows
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.enableShadows")
|
||||
onToggled: checked => Settings.data.general.enableShadows = checked
|
||||
}
|
||||
|
||||
// Shadow direction
|
||||
NComboBox {
|
||||
visible: Settings.data.general.enableShadows
|
||||
label: I18n.tr("settings.user-interface.shadows.direction.label")
|
||||
description: I18n.tr("settings.user-interface.shadows.direction.description")
|
||||
Layout.fillWidth: true
|
||||
|
||||
readonly property var shadowOptionsMap: ({
|
||||
"top_left": {
|
||||
"name": I18n.tr("options.shadow-direction.top_left"),
|
||||
"p": Qt.point(-2, -2)
|
||||
},
|
||||
"top": {
|
||||
"name": I18n.tr("options.shadow-direction.top"),
|
||||
"p": Qt.point(0, -3)
|
||||
},
|
||||
"top_right": {
|
||||
"name": I18n.tr("options.shadow-direction.top_right"),
|
||||
"p": Qt.point(2, -2)
|
||||
},
|
||||
"left": {
|
||||
"name": I18n.tr("options.shadow-direction.left"),
|
||||
"p": Qt.point(-3, 0)
|
||||
},
|
||||
"center": {
|
||||
"name": I18n.tr("options.shadow-direction.center"),
|
||||
"p": Qt.point(0, 0)
|
||||
},
|
||||
"right": {
|
||||
"name": I18n.tr("options.shadow-direction.right"),
|
||||
"p": Qt.point(3, 0)
|
||||
},
|
||||
"bottom_left": {
|
||||
"name": I18n.tr("options.shadow-direction.bottom_left"),
|
||||
"p": Qt.point(-2, 2)
|
||||
},
|
||||
"bottom": {
|
||||
"name": I18n.tr("options.shadow-direction.bottom"),
|
||||
"p": Qt.point(0, 3)
|
||||
},
|
||||
"bottom_right": {
|
||||
"name": I18n.tr("options.shadow-direction.bottom_right"),
|
||||
"p": Qt.point(2, 3)
|
||||
}
|
||||
})
|
||||
|
||||
model: Object.keys(shadowOptionsMap).map(function (k) {
|
||||
return {
|
||||
"key": k,
|
||||
"name": shadowOptionsMap[k].name
|
||||
};
|
||||
})
|
||||
|
||||
currentKey: Settings.data.general.shadowDirection
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.shadowDirection")
|
||||
|
||||
onSelected: function (key) {
|
||||
var opt = shadowOptionsMap[key];
|
||||
if (opt) {
|
||||
Settings.data.general.shadowDirection = key;
|
||||
Settings.data.general.shadowOffsetX = opt.p.x;
|
||||
Settings.data.general.shadowOffsetY = opt.p.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// User Interface Scaling
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.user-interface.scaling.label")
|
||||
description: I18n.tr("settings.user-interface.scaling.description")
|
||||
from: 0.8
|
||||
to: 1.2
|
||||
stepSize: 0.05
|
||||
value: Settings.data.general.scaleRatio
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.scaleRatio")
|
||||
onMoved: value => Settings.data.general.scaleRatio = value
|
||||
text: Math.floor(Settings.data.general.scaleRatio * 100) + "%"
|
||||
}
|
||||
|
||||
// Reset button container
|
||||
Item {
|
||||
Layout.preferredWidth: 30 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: 30 * Style.uiScaleRatio
|
||||
|
||||
NIconButton {
|
||||
icon: "restore"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
tooltipText: I18n.tr("settings.user-interface.scaling.reset-scaling")
|
||||
onClicked: Settings.data.general.scaleRatio = 1.0
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Container Border Radius
|
||||
RowLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.user-interface.box-border-radius.label")
|
||||
description: I18n.tr("settings.user-interface.box-border-radius.description")
|
||||
from: 0
|
||||
to: 2
|
||||
stepSize: 0.01
|
||||
value: Settings.data.general.radiusRatio
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.radiusRatio")
|
||||
onMoved: value => Settings.data.general.radiusRatio = value
|
||||
text: Math.floor(Settings.data.general.radiusRatio * 100) + "%"
|
||||
}
|
||||
|
||||
// Reset button container
|
||||
Item {
|
||||
Layout.preferredWidth: 30 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: 30 * Style.uiScaleRatio
|
||||
|
||||
NIconButton {
|
||||
icon: "restore"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
tooltipText: I18n.tr("settings.user-interface.box-border-radius.reset")
|
||||
onClicked: Settings.data.general.radiusRatio = 1.0
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Control Border Radius (for UI components)
|
||||
RowLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.user-interface.control-border-radius.label")
|
||||
description: I18n.tr("settings.user-interface.control-border-radius.description")
|
||||
from: 0
|
||||
to: 2
|
||||
stepSize: 0.01
|
||||
value: Settings.data.general.iRadiusRatio
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.iRadiusRatio")
|
||||
onMoved: value => Settings.data.general.iRadiusRatio = value
|
||||
text: Math.floor(Settings.data.general.iRadiusRatio * 100) + "%"
|
||||
}
|
||||
|
||||
// Reset button container
|
||||
Item {
|
||||
Layout.preferredWidth: 30 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: 30 * Style.uiScaleRatio
|
||||
|
||||
NIconButton {
|
||||
icon: "restore"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
tooltipText: I18n.tr("settings.user-interface.control-border-radius.reset")
|
||||
onClicked: Settings.data.general.iRadiusRatio = 1.0
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Animation Speed
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
visible: !Settings.data.general.animationDisabled
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.user-interface.animation-speed.label")
|
||||
description: I18n.tr("settings.user-interface.animation-speed.description")
|
||||
from: 0
|
||||
to: 2.0
|
||||
stepSize: 0.01
|
||||
value: Settings.data.general.animationSpeed
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.animationSpeed")
|
||||
onMoved: value => Settings.data.general.animationSpeed = Math.max(value, 0.05)
|
||||
text: Math.round(Settings.data.general.animationSpeed * 100) + "%"
|
||||
}
|
||||
|
||||
// Reset button container
|
||||
Item {
|
||||
Layout.preferredWidth: 30 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: 30 * Style.uiScaleRatio
|
||||
|
||||
NIconButton {
|
||||
icon: "restore"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
tooltipText: I18n.tr("settings.user-interface.animation-speed.reset")
|
||||
onClicked: Settings.data.general.animationSpeed = 1.0
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.user-interface.animation-disable.label")
|
||||
description: I18n.tr("settings.user-interface.animation-disable.description")
|
||||
checked: Settings.data.general.animationDisabled
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.animationDisabled")
|
||||
onToggled: checked => Settings.data.general.animationDisabled = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// Dock
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.general.screen-corners.section.label")
|
||||
description: I18n.tr("settings.general.screen-corners.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.general.screen-corners.show-corners.label")
|
||||
description: I18n.tr("settings.general.screen-corners.show-corners.description")
|
||||
checked: Settings.data.general.showScreenCorners
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.showScreenCorners")
|
||||
onToggled: checked => Settings.data.general.showScreenCorners = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.general.screen-corners.solid-black.label")
|
||||
description: I18n.tr("settings.general.screen-corners.solid-black.description")
|
||||
checked: Settings.data.general.forceBlackScreenCorners
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.forceBlackScreenCorners")
|
||||
onToggled: checked => Settings.data.general.forceBlackScreenCorners = checked
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.general.screen-corners.radius.label")
|
||||
description: I18n.tr("settings.general.screen-corners.radius.description")
|
||||
from: 0
|
||||
to: 2
|
||||
stepSize: 0.01
|
||||
value: Settings.data.general.screenRadiusRatio
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("general.screenRadiusRatio")
|
||||
onMoved: value => Settings.data.general.screenRadiusRatio = value
|
||||
text: Math.floor(Settings.data.general.screenRadiusRatio * 100) + "%"
|
||||
}
|
||||
|
||||
// Reset button container
|
||||
Item {
|
||||
Layout.preferredWidth: 30 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: 30 * Style.uiScaleRatio
|
||||
|
||||
NIconButton {
|
||||
icon: "restore"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
tooltipText: I18n.tr("settings.general.screen-corners.radius.reset")
|
||||
onClicked: Settings.data.general.screenRadiusRatio = 1.0
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.wallpaper.automation.section.label")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.wallpaper.automation.scheduled-change.label")
|
||||
description: I18n.tr("settings.wallpaper.automation.scheduled-change.description")
|
||||
checked: Settings.data.wallpaper.randomEnabled
|
||||
onToggled: checked => Settings.data.wallpaper.randomEnabled = checked
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
visible: Settings.data.wallpaper.randomEnabled
|
||||
label: I18n.tr("settings.wallpaper.automation.change-mode.label")
|
||||
description: I18n.tr("settings.wallpaper.automation.change-mode.description")
|
||||
Layout.fillWidth: true
|
||||
model: [
|
||||
{
|
||||
"key": "random",
|
||||
"name": I18n.tr("settings.wallpaper.automation.change-mode.random")
|
||||
},
|
||||
{
|
||||
"key": "alphabetical",
|
||||
"name": I18n.tr("settings.wallpaper.automation.change-mode.alphabetical")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.wallpaper.wallpaperChangeMode || "random"
|
||||
onSelected: key => Settings.data.wallpaper.wallpaperChangeMode = key
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("wallpaper.transitionType")
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: Settings.data.wallpaper.randomEnabled
|
||||
RowLayout {
|
||||
NLabel {
|
||||
label: I18n.tr("settings.wallpaper.automation.interval.label")
|
||||
description: I18n.tr("settings.wallpaper.automation.interval.description")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
text: Time.formatVagueHumanReadableDuration(Settings.data.wallpaper.randomIntervalSec)
|
||||
Layout.alignment: Qt.AlignBottom | Qt.AlignRight
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: presetRow
|
||||
spacing: Style.marginS
|
||||
|
||||
property var intervalPresets: [5 * 60, 10 * 60, 15 * 60, 30 * 60, 45 * 60, 60 * 60, 90 * 60, 120 * 60]
|
||||
property bool isCurrentPreset: {
|
||||
return intervalPresets.some(seconds => seconds === Settings.data.wallpaper.randomIntervalSec);
|
||||
}
|
||||
property bool customForcedVisible: false
|
||||
|
||||
function setIntervalSeconds(sec) {
|
||||
Settings.data.wallpaper.randomIntervalSec = sec;
|
||||
WallpaperService.restartRandomWallpaperTimer();
|
||||
customForcedVisible = false;
|
||||
}
|
||||
|
||||
function isSelected(sec) {
|
||||
return Settings.data.wallpaper.randomIntervalSec === sec;
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: presetRow.intervalPresets
|
||||
delegate: IntervalPresetChip {
|
||||
seconds: modelData
|
||||
label: Time.formatVagueHumanReadableDuration(modelData)
|
||||
selected: presetRow.isSelected(modelData)
|
||||
onClicked: presetRow.setIntervalSeconds(modelData)
|
||||
}
|
||||
}
|
||||
|
||||
IntervalPresetChip {
|
||||
label: customRow.visible ? "Custom" : "Custom…"
|
||||
selected: customRow.visible
|
||||
onClicked: presetRow.customForcedVisible = !presetRow.customForcedVisible
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: customRow
|
||||
visible: presetRow.customForcedVisible || !presetRow.isCurrentPreset
|
||||
spacing: Style.marginS
|
||||
Layout.topMargin: Style.marginS
|
||||
|
||||
NTextInput {
|
||||
label: I18n.tr("settings.wallpaper.automation.custom-interval.label")
|
||||
description: I18n.tr("settings.wallpaper.automation.custom-interval.description")
|
||||
text: {
|
||||
const s = Settings.data.wallpaper.randomIntervalSec;
|
||||
const h = Math.floor(s / 3600);
|
||||
const m = Math.floor((s % 3600) / 60);
|
||||
return h + ":" + (m < 10 ? ("0" + m) : m);
|
||||
}
|
||||
onEditingFinished: {
|
||||
const m = text.trim().match(/^(\d{1,2}):(\d{2})$/);
|
||||
if (m) {
|
||||
let h = parseInt(m[1]);
|
||||
let min = parseInt(m[2]);
|
||||
if (isNaN(h) || isNaN(min))
|
||||
return;
|
||||
h = Math.max(0, Math.min(24, h));
|
||||
min = Math.max(0, Math.min(59, min));
|
||||
Settings.data.wallpaper.randomIntervalSec = (h * 3600) + (min * 60);
|
||||
WallpaperService.restartRandomWallpaperTimer();
|
||||
presetRow.customForcedVisible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component IntervalPresetChip: Rectangle {
|
||||
property int seconds: 0
|
||||
property string label: ""
|
||||
property bool selected: false
|
||||
signal clicked
|
||||
|
||||
radius: height * 0.5
|
||||
color: selected ? Color.mPrimary : Color.mSurfaceVariant
|
||||
implicitHeight: Math.max(Style.baseWidgetSize * 0.55, 24)
|
||||
implicitWidth: chipLabel.implicitWidth + Style.marginM * 1.5
|
||||
border.width: Style.borderS
|
||||
border.color: selected ? "transparent" : Color.mOutline
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: parent.clicked()
|
||||
}
|
||||
|
||||
NText {
|
||||
id: chipLabel
|
||||
anchors.centerIn: parent
|
||||
text: parent.label
|
||||
pointSize: Style.fontSizeS
|
||||
color: parent.selected ? Color.mOnPrimary : Color.mOnSurface
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
property var screen
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.wallpaper.look-feel.section.label")
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.wallpaper.look-feel.fill-mode.label")
|
||||
description: I18n.tr("settings.wallpaper.look-feel.fill-mode.description")
|
||||
model: WallpaperService.fillModeModel
|
||||
currentKey: Settings.data.wallpaper.fillMode
|
||||
onSelected: key => Settings.data.wallpaper.fillMode = key
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("wallpaper.fillMode")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
NLabel {
|
||||
label: I18n.tr("settings.wallpaper.look-feel.fill-color.label")
|
||||
description: I18n.tr("settings.wallpaper.look-feel.fill-color.description")
|
||||
Layout.alignment: Qt.AlignTop
|
||||
}
|
||||
|
||||
NColorPicker {
|
||||
screen: root.screen
|
||||
selectedColor: Settings.data.wallpaper.fillColor
|
||||
onColorSelected: color => Settings.data.wallpaper.fillColor = color
|
||||
}
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.wallpaper.look-feel.transition-type.label")
|
||||
description: I18n.tr("settings.wallpaper.look-feel.transition-type.description")
|
||||
model: WallpaperService.transitionsModel
|
||||
currentKey: Settings.data.wallpaper.transitionType
|
||||
onSelected: key => Settings.data.wallpaper.transitionType = key
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("wallpaper.transitionType")
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.wallpaper.look-feel.transition-duration.label")
|
||||
description: I18n.tr("settings.wallpaper.look-feel.transition-duration.description")
|
||||
from: 500
|
||||
to: 10000
|
||||
stepSize: 100
|
||||
value: Settings.data.wallpaper.transitionDuration
|
||||
onMoved: value => Settings.data.wallpaper.transitionDuration = value
|
||||
text: (Settings.data.wallpaper.transitionDuration / 1000).toFixed(1) + "s"
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("wallpaper.transitionDuration")
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.wallpaper.look-feel.edge-smoothness.label")
|
||||
description: I18n.tr("settings.wallpaper.look-feel.edge-smoothness.description")
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
value: Settings.data.wallpaper.transitionEdgeSmoothness
|
||||
onMoved: value => Settings.data.wallpaper.transitionEdgeSmoothness = value
|
||||
text: Math.round(Settings.data.wallpaper.transitionEdgeSmoothness * 100) + "%"
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("wallpaper.transitionEdgeSmoothness")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
property var screen
|
||||
|
||||
signal openMainFolderPicker
|
||||
signal openMonitorFolderPicker(string monitorName)
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.wallpaper.settings.section.label")
|
||||
description: I18n.tr("settings.wallpaper.settings.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.wallpaper.settings.enable-management.label")
|
||||
description: I18n.tr("settings.wallpaper.settings.enable-management.description")
|
||||
checked: Settings.data.wallpaper.enabled
|
||||
onToggled: checked => Settings.data.wallpaper.enabled = checked
|
||||
Layout.bottomMargin: Style.marginL
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("wallpaper.enabled")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
visible: Settings.data.wallpaper.enabled && CompositorService.isNiri
|
||||
label: I18n.tr("settings.wallpaper.settings.enable-overview.label")
|
||||
description: I18n.tr("settings.wallpaper.settings.enable-overview.description")
|
||||
checked: Settings.data.wallpaper.overviewEnabled
|
||||
onToggled: checked => Settings.data.wallpaper.overviewEnabled = checked
|
||||
Layout.bottomMargin: Style.marginL
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("wallpaper.overviewEnabled")
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: Settings.data.wallpaper.enabled
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NTextInputButton {
|
||||
id: wallpaperPathInput
|
||||
label: I18n.tr("settings.wallpaper.settings.folder.label")
|
||||
description: I18n.tr("settings.wallpaper.settings.folder.description")
|
||||
text: Settings.data.wallpaper.directory
|
||||
buttonIcon: "folder-open"
|
||||
buttonTooltip: I18n.tr("settings.wallpaper.settings.folder.tooltip")
|
||||
Layout.fillWidth: true
|
||||
onInputEditingFinished: Settings.data.wallpaper.directory = text
|
||||
onButtonClicked: root.openMainFolderPicker()
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
NLabel {
|
||||
label: I18n.tr("settings.wallpaper.settings.selector.label")
|
||||
description: I18n.tr("settings.wallpaper.settings.selector.description")
|
||||
Layout.alignment: Qt.AlignTop
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "wallpaper-selector"
|
||||
tooltipText: I18n.tr("settings.wallpaper.settings.selector.tooltip")
|
||||
onClicked: PanelService.getPanel("wallpaperPanel", root.screen)?.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.wallpaper.settings.recursive-search.label")
|
||||
description: I18n.tr("settings.wallpaper.settings.recursive-search.description")
|
||||
checked: Settings.data.wallpaper.recursiveSearch
|
||||
onToggled: checked => Settings.data.wallpaper.recursiveSearch = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("wallpaper.recursiveSearch")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.wallpaper.settings.monitor-specific.label")
|
||||
description: I18n.tr("settings.wallpaper.settings.monitor-specific.description")
|
||||
checked: Settings.data.wallpaper.enableMultiMonitorDirectories
|
||||
onToggled: checked => Settings.data.wallpaper.enableMultiMonitorDirectories = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("wallpaper.enableMultiMonitorDirectories")
|
||||
}
|
||||
|
||||
NBox {
|
||||
visible: Settings.data.wallpaper.enableMultiMonitorDirectories
|
||||
Layout.fillWidth: true
|
||||
radius: Style.radiusM
|
||||
color: Color.mSurface
|
||||
border.color: Color.mOutline
|
||||
border.width: Style.borderS
|
||||
implicitHeight: contentCol.implicitHeight + Style.marginL * 2
|
||||
clip: true
|
||||
|
||||
ColumnLayout {
|
||||
id: contentCol
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL
|
||||
spacing: Style.marginM
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: (modelData.name || "Unknown")
|
||||
color: Color.mPrimary
|
||||
font.weight: Style.fontWeightBold
|
||||
pointSize: Style.fontSizeM
|
||||
}
|
||||
|
||||
NTextInputButton {
|
||||
text: WallpaperService.getMonitorDirectory(modelData.name)
|
||||
buttonIcon: "folder-open"
|
||||
buttonTooltip: I18n.tr("settings.wallpaper.settings.monitor-specific.tooltip")
|
||||
Layout.fillWidth: true
|
||||
onInputEditingFinished: WallpaperService.setMonitorDirectory(modelData.name, text)
|
||||
onButtonClicked: root.openMonitorFolderPicker(modelData.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.wallpaper.settings.selector-position.label")
|
||||
description: I18n.tr("settings.wallpaper.settings.selector-position.description")
|
||||
Layout.fillWidth: true
|
||||
model: [
|
||||
{
|
||||
"key": "follow_bar",
|
||||
"name": I18n.tr("options.launcher.position.follow_bar")
|
||||
},
|
||||
{
|
||||
"key": "center",
|
||||
"name": I18n.tr("options.launcher.position.center")
|
||||
},
|
||||
{
|
||||
"key": "top_center",
|
||||
"name": I18n.tr("options.launcher.position.top_center")
|
||||
},
|
||||
{
|
||||
"key": "top_left",
|
||||
"name": I18n.tr("options.launcher.position.top_left")
|
||||
},
|
||||
{
|
||||
"key": "top_right",
|
||||
"name": I18n.tr("options.launcher.position.top_right")
|
||||
},
|
||||
{
|
||||
"key": "bottom_left",
|
||||
"name": I18n.tr("options.launcher.position.bottom_left")
|
||||
},
|
||||
{
|
||||
"key": "bottom_right",
|
||||
"name": I18n.tr("options.launcher.position.bottom_right")
|
||||
},
|
||||
{
|
||||
"key": "bottom_center",
|
||||
"name": I18n.tr("options.launcher.position.bottom_center")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.wallpaper.panelPosition
|
||||
onSelected: key => Settings.data.wallpaper.panelPosition = key
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("wallpaper.panelPosition")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
property var screen
|
||||
|
||||
function openMainFolderPicker() {
|
||||
mainFolderPicker.open();
|
||||
}
|
||||
|
||||
function openMonitorFolderPicker(monitorName) {
|
||||
specificFolderMonitorName = monitorName;
|
||||
monitorFolderPicker.open();
|
||||
}
|
||||
|
||||
property string specificFolderMonitorName: ""
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.wallpaper.tabs.settings")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.wallpaper.tabs.look-feel")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.wallpaper.tabs.automation")
|
||||
tabIndex: 2
|
||||
checked: subTabBar.currentIndex === 2
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
SettingsSubTab {
|
||||
screen: root.screen
|
||||
onOpenMainFolderPicker: root.openMainFolderPicker()
|
||||
onOpenMonitorFolderPicker: monitorName => root.openMonitorFolderPicker(monitorName)
|
||||
}
|
||||
LookAndFeelSubTab {
|
||||
screen: root.screen
|
||||
}
|
||||
AutomationSubTab {}
|
||||
}
|
||||
|
||||
NFilePicker {
|
||||
id: mainFolderPicker
|
||||
selectionMode: "folders"
|
||||
title: I18n.tr("settings.wallpaper.settings.select-folder")
|
||||
initialPath: Settings.data.wallpaper.directory || Quickshell.env("HOME") + "/Pictures"
|
||||
onAccepted: paths => {
|
||||
if (paths.length > 0) {
|
||||
Settings.data.wallpaper.directory = paths[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NFilePicker {
|
||||
id: monitorFolderPicker
|
||||
selectionMode: "folders"
|
||||
title: I18n.tr("settings.wallpaper.settings.select-monitor-folder")
|
||||
initialPath: WallpaperService.getMonitorDirectory(specificFolderMonitorName) || Quickshell.env("HOME") + "/Pictures"
|
||||
onAccepted: paths => {
|
||||
if (paths.length > 0) {
|
||||
WallpaperService.setMonitorDirectory(specificFolderMonitorName, paths[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,482 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property var screen
|
||||
property string specificFolderMonitorName: ""
|
||||
|
||||
spacing: Style.marginL
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.wallpaper.settings.section.label")
|
||||
description: I18n.tr("settings.wallpaper.settings.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.wallpaper.settings.enable-management.label")
|
||||
description: I18n.tr("settings.wallpaper.settings.enable-management.description")
|
||||
checked: Settings.data.wallpaper.enabled
|
||||
onToggled: checked => Settings.data.wallpaper.enabled = checked
|
||||
Layout.bottomMargin: Style.marginL
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("wallpaper.enabled")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
visible: Settings.data.wallpaper.enabled && CompositorService.isNiri
|
||||
label: I18n.tr("settings.wallpaper.settings.enable-overview.label")
|
||||
description: I18n.tr("settings.wallpaper.settings.enable-overview.description")
|
||||
checked: Settings.data.wallpaper.overviewEnabled
|
||||
onToggled: checked => Settings.data.wallpaper.overviewEnabled = checked
|
||||
Layout.bottomMargin: Style.marginL
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("wallpaper.overviewEnabled")
|
||||
}
|
||||
|
||||
NDivider {
|
||||
visible: Settings.data.wallpaper.enabled
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: Settings.data.wallpaper.enabled
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NTextInputButton {
|
||||
id: wallpaperPathInput
|
||||
label: I18n.tr("settings.wallpaper.settings.folder.label")
|
||||
description: I18n.tr("settings.wallpaper.settings.folder.description")
|
||||
text: Settings.data.wallpaper.directory
|
||||
buttonIcon: "folder-open"
|
||||
buttonTooltip: I18n.tr("settings.wallpaper.settings.folder.tooltip")
|
||||
Layout.fillWidth: true
|
||||
onInputEditingFinished: Settings.data.wallpaper.directory = text
|
||||
onButtonClicked: mainFolderPicker.open()
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
NLabel {
|
||||
label: I18n.tr("settings.wallpaper.settings.selector.label")
|
||||
description: I18n.tr("settings.wallpaper.settings.selector.description")
|
||||
Layout.alignment: Qt.AlignTop
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "wallpaper-selector"
|
||||
tooltipText: I18n.tr("settings.wallpaper.settings.selector.tooltip")
|
||||
onClicked: PanelService.getPanel("wallpaperPanel", screen)?.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
// Recursive search
|
||||
NToggle {
|
||||
label: I18n.tr("settings.wallpaper.settings.recursive-search.label")
|
||||
description: I18n.tr("settings.wallpaper.settings.recursive-search.description")
|
||||
checked: Settings.data.wallpaper.recursiveSearch
|
||||
onToggled: checked => Settings.data.wallpaper.recursiveSearch = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("wallpaper.recursiveSearch")
|
||||
}
|
||||
|
||||
// Monitor-specific directories
|
||||
NToggle {
|
||||
label: I18n.tr("settings.wallpaper.settings.monitor-specific.label")
|
||||
description: I18n.tr("settings.wallpaper.settings.monitor-specific.description")
|
||||
checked: Settings.data.wallpaper.enableMultiMonitorDirectories
|
||||
onToggled: checked => Settings.data.wallpaper.enableMultiMonitorDirectories = checked
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("wallpaper.enableMultiMonitorDirectories")
|
||||
}
|
||||
|
||||
NBox {
|
||||
visible: Settings.data.wallpaper.enableMultiMonitorDirectories
|
||||
|
||||
Layout.fillWidth: true
|
||||
radius: Style.radiusM
|
||||
color: Color.mSurface
|
||||
border.color: Color.mOutline
|
||||
border.width: Style.borderS
|
||||
implicitHeight: contentCol.implicitHeight + Style.marginL * 2
|
||||
clip: true
|
||||
|
||||
ColumnLayout {
|
||||
id: contentCol
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL
|
||||
spacing: Style.marginM
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: (modelData.name || "Unknown")
|
||||
color: Color.mPrimary
|
||||
font.weight: Style.fontWeightBold
|
||||
pointSize: Style.fontSizeM
|
||||
}
|
||||
|
||||
NTextInputButton {
|
||||
text: WallpaperService.getMonitorDirectory(modelData.name)
|
||||
buttonIcon: "folder-open"
|
||||
buttonTooltip: I18n.tr("settings.wallpaper.settings.monitor-specific.tooltip")
|
||||
Layout.fillWidth: true
|
||||
onInputEditingFinished: WallpaperService.setMonitorDirectory(modelData.name, text)
|
||||
onButtonClicked: {
|
||||
specificFolderMonitorName = modelData.name;
|
||||
monitorFolderPicker.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.wallpaper.settings.selector-position.label")
|
||||
description: I18n.tr("settings.wallpaper.settings.selector-position.description")
|
||||
Layout.fillWidth: true
|
||||
model: [
|
||||
{
|
||||
"key": "follow_bar",
|
||||
"name": I18n.tr("options.launcher.position.follow_bar")
|
||||
},
|
||||
{
|
||||
"key": "center",
|
||||
"name": I18n.tr("options.launcher.position.center")
|
||||
},
|
||||
{
|
||||
"key": "top_center",
|
||||
"name": I18n.tr("options.launcher.position.top_center")
|
||||
},
|
||||
{
|
||||
"key": "top_left",
|
||||
"name": I18n.tr("options.launcher.position.top_left")
|
||||
},
|
||||
{
|
||||
"key": "top_right",
|
||||
"name": I18n.tr("options.launcher.position.top_right")
|
||||
},
|
||||
{
|
||||
"key": "bottom_left",
|
||||
"name": I18n.tr("options.launcher.position.bottom_left")
|
||||
},
|
||||
{
|
||||
"key": "bottom_right",
|
||||
"name": I18n.tr("options.launcher.position.bottom_right")
|
||||
},
|
||||
{
|
||||
"key": "bottom_center",
|
||||
"name": I18n.tr("options.launcher.position.bottom_center")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.wallpaper.panelPosition
|
||||
onSelected: key => Settings.data.wallpaper.panelPosition = key
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("wallpaper.panelPosition")
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
visible: Settings.data.wallpaper.enabled
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: Settings.data.wallpaper.enabled
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.wallpaper.look-feel.section.label")
|
||||
}
|
||||
|
||||
// Fill Mode
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.wallpaper.look-feel.fill-mode.label")
|
||||
description: I18n.tr("settings.wallpaper.look-feel.fill-mode.description")
|
||||
model: WallpaperService.fillModeModel
|
||||
currentKey: Settings.data.wallpaper.fillMode
|
||||
onSelected: key => Settings.data.wallpaper.fillMode = key
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("wallpaper.fillMode")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
NLabel {
|
||||
label: I18n.tr("settings.wallpaper.look-feel.fill-color.label")
|
||||
description: I18n.tr("settings.wallpaper.look-feel.fill-color.description")
|
||||
Layout.alignment: Qt.AlignTop
|
||||
}
|
||||
|
||||
NColorPicker {
|
||||
screen: root.screen
|
||||
selectedColor: Settings.data.wallpaper.fillColor
|
||||
onColorSelected: color => Settings.data.wallpaper.fillColor = color
|
||||
}
|
||||
}
|
||||
|
||||
// Transition Type
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.wallpaper.look-feel.transition-type.label")
|
||||
description: I18n.tr("settings.wallpaper.look-feel.transition-type.description")
|
||||
model: WallpaperService.transitionsModel
|
||||
currentKey: Settings.data.wallpaper.transitionType
|
||||
onSelected: key => Settings.data.wallpaper.transitionType = key
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("wallpaper.transitionType")
|
||||
}
|
||||
|
||||
// Transition Duration
|
||||
ColumnLayout {
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.wallpaper.look-feel.transition-duration.label")
|
||||
description: I18n.tr("settings.wallpaper.look-feel.transition-duration.description")
|
||||
from: 500
|
||||
to: 10000
|
||||
stepSize: 100
|
||||
value: Settings.data.wallpaper.transitionDuration
|
||||
onMoved: value => Settings.data.wallpaper.transitionDuration = value
|
||||
text: (Settings.data.wallpaper.transitionDuration / 1000).toFixed(1) + "s"
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("wallpaper.transitionDuration")
|
||||
}
|
||||
}
|
||||
|
||||
// Edge Smoothness
|
||||
ColumnLayout {
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.wallpaper.look-feel.edge-smoothness.label")
|
||||
description: I18n.tr("settings.wallpaper.look-feel.edge-smoothness.description")
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
value: Settings.data.wallpaper.transitionEdgeSmoothness
|
||||
onMoved: value => Settings.data.wallpaper.transitionEdgeSmoothness = value
|
||||
text: Math.round(Settings.data.wallpaper.transitionEdgeSmoothness * 100) + "%"
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("wallpaper.transitionEdgeSmoothness")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
visible: Settings.data.wallpaper.enabled
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: Settings.data.wallpaper.enabled
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.wallpaper.automation.section.label")
|
||||
}
|
||||
|
||||
// Scheduled change toggle
|
||||
NToggle {
|
||||
label: I18n.tr("settings.wallpaper.automation.scheduled-change.label")
|
||||
description: I18n.tr("settings.wallpaper.automation.scheduled-change.description")
|
||||
checked: Settings.data.wallpaper.randomEnabled
|
||||
onToggled: checked => Settings.data.wallpaper.randomEnabled = checked
|
||||
}
|
||||
|
||||
// Change mode combo box
|
||||
NComboBox {
|
||||
visible: Settings.data.wallpaper.randomEnabled
|
||||
label: I18n.tr("settings.wallpaper.automation.change-mode.label")
|
||||
description: I18n.tr("settings.wallpaper.automation.change-mode.description")
|
||||
Layout.fillWidth: true
|
||||
model: [
|
||||
{
|
||||
"key": "random",
|
||||
"name": I18n.tr("settings.wallpaper.automation.change-mode.random")
|
||||
},
|
||||
{
|
||||
"key": "alphabetical",
|
||||
"name": I18n.tr("settings.wallpaper.automation.change-mode.alphabetical")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.wallpaper.wallpaperChangeMode || "random"
|
||||
onSelected: key => Settings.data.wallpaper.wallpaperChangeMode = key
|
||||
isSettings: true
|
||||
defaultValue: Settings.getDefaultValue("wallpaper.transitionType")
|
||||
}
|
||||
|
||||
// Interval
|
||||
ColumnLayout {
|
||||
visible: Settings.data.wallpaper.randomEnabled
|
||||
RowLayout {
|
||||
NLabel {
|
||||
label: I18n.tr("settings.wallpaper.automation.interval.label")
|
||||
description: I18n.tr("settings.wallpaper.automation.interval.description")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
// Show friendly H:MM format from current settings
|
||||
text: Time.formatVagueHumanReadableDuration(Settings.data.wallpaper.randomIntervalSec)
|
||||
Layout.alignment: Qt.AlignBottom | Qt.AlignRight
|
||||
}
|
||||
}
|
||||
|
||||
// Preset chips using Repeater
|
||||
RowLayout {
|
||||
id: presetRow
|
||||
spacing: Style.marginS
|
||||
|
||||
// Factorized presets data
|
||||
property var intervalPresets: [5 * 60, 10 * 60, 15 * 60, 30 * 60, 45 * 60, 60 * 60, 90 * 60, 120 * 60]
|
||||
|
||||
// Whether current interval equals one of the presets
|
||||
property bool isCurrentPreset: {
|
||||
return intervalPresets.some(seconds => seconds === Settings.data.wallpaper.randomIntervalSec);
|
||||
}
|
||||
// Allow user to force open the custom input; otherwise it's auto-open when not a preset
|
||||
property bool customForcedVisible: false
|
||||
|
||||
function setIntervalSeconds(sec) {
|
||||
Settings.data.wallpaper.randomIntervalSec = sec;
|
||||
WallpaperService.restartRandomWallpaperTimer();
|
||||
// Hide custom when selecting a preset
|
||||
customForcedVisible = false;
|
||||
}
|
||||
|
||||
// Helper to color selected chip
|
||||
function isSelected(sec) {
|
||||
return Settings.data.wallpaper.randomIntervalSec === sec;
|
||||
}
|
||||
|
||||
// Repeater for preset chips
|
||||
Repeater {
|
||||
model: presetRow.intervalPresets
|
||||
delegate: IntervalPresetChip {
|
||||
seconds: modelData
|
||||
label: Time.formatVagueHumanReadableDuration(modelData)
|
||||
selected: presetRow.isSelected(modelData)
|
||||
onClicked: presetRow.setIntervalSeconds(modelData)
|
||||
}
|
||||
}
|
||||
|
||||
// Custom… opens inline input
|
||||
IntervalPresetChip {
|
||||
label: customRow.visible ? "Custom" : "Custom…"
|
||||
selected: customRow.visible
|
||||
onClicked: presetRow.customForcedVisible = !presetRow.customForcedVisible
|
||||
}
|
||||
}
|
||||
|
||||
// Custom HH:MM inline input
|
||||
RowLayout {
|
||||
id: customRow
|
||||
visible: presetRow.customForcedVisible || !presetRow.isCurrentPreset
|
||||
spacing: Style.marginS
|
||||
Layout.topMargin: Style.marginS
|
||||
|
||||
NTextInput {
|
||||
label: I18n.tr("settings.wallpaper.automation.custom-interval.label")
|
||||
description: I18n.tr("settings.wallpaper.automation.custom-interval.description")
|
||||
text: {
|
||||
const s = Settings.data.wallpaper.randomIntervalSec;
|
||||
const h = Math.floor(s / 3600);
|
||||
const m = Math.floor((s % 3600) / 60);
|
||||
return h + ":" + (m < 10 ? ("0" + m) : m);
|
||||
}
|
||||
onEditingFinished: {
|
||||
const m = text.trim().match(/^(\d{1,2}):(\d{2})$/);
|
||||
if (m) {
|
||||
let h = parseInt(m[1]);
|
||||
let min = parseInt(m[2]);
|
||||
if (isNaN(h) || isNaN(min))
|
||||
return;
|
||||
h = Math.max(0, Math.min(24, h));
|
||||
min = Math.max(0, Math.min(59, min));
|
||||
Settings.data.wallpaper.randomIntervalSec = (h * 3600) + (min * 60);
|
||||
WallpaperService.restartRandomWallpaperTimer();
|
||||
// Keep custom visible after manual entry
|
||||
presetRow.customForcedVisible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reusable component for interval preset chips
|
||||
component IntervalPresetChip: Rectangle {
|
||||
property int seconds: 0
|
||||
property string label: ""
|
||||
property bool selected: false
|
||||
signal clicked
|
||||
|
||||
radius: height * 0.5
|
||||
color: selected ? Color.mPrimary : Color.mSurfaceVariant
|
||||
implicitHeight: Math.max(Style.baseWidgetSize * 0.55, 24)
|
||||
implicitWidth: chipLabel.implicitWidth + Style.marginM * 1.5
|
||||
border.width: Style.borderS
|
||||
border.color: selected ? "transparent" : Color.mOutline
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: parent.clicked()
|
||||
}
|
||||
|
||||
NText {
|
||||
id: chipLabel
|
||||
anchors.centerIn: parent
|
||||
text: parent.label
|
||||
pointSize: Style.fontSizeS
|
||||
color: parent.selected ? Color.mOnPrimary : Color.mOnSurface
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
NFilePicker {
|
||||
id: mainFolderPicker
|
||||
selectionMode: "folders"
|
||||
title: I18n.tr("settings.wallpaper.settings.select-folder")
|
||||
initialPath: Settings.data.wallpaper.directory || Quickshell.env("HOME") + "/Pictures"
|
||||
onAccepted: paths => {
|
||||
if (paths.length > 0) {
|
||||
Settings.data.wallpaper.directory = paths[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NFilePicker {
|
||||
id: monitorFolderPicker
|
||||
selectionMode: "folders"
|
||||
title: I18n.tr("settings.wallpaper.settings.select-monitor-folder")
|
||||
initialPath: WallpaperService.getMonitorDirectory(specificFolderMonitorName) || Quickshell.env("HOME") + "/Pictures"
|
||||
onAccepted: paths => {
|
||||
if (paths.length > 0) {
|
||||
WallpaperService.setMonitorDirectory(specificFolderMonitorName, paths[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property int currentIndex: 0
|
||||
|
||||
// Private
|
||||
property int previousIndex: 0
|
||||
property bool initialized: false
|
||||
property bool animating: false
|
||||
property real animatingHeight: 0
|
||||
property real transitionGap: Style.marginXL
|
||||
property real transitionTime: Style.animationNormal
|
||||
property list<Item> contentItems: []
|
||||
|
||||
default property alias content: container.data
|
||||
|
||||
clip: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
// During animation, use max height to prevent clipping. Otherwise use current item height.
|
||||
implicitHeight: animating ? animatingHeight : (contentItems[currentIndex] ? contentItems[currentIndex].implicitHeight : 0)
|
||||
|
||||
Item {
|
||||
id: container
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
_initializeItems();
|
||||
}
|
||||
|
||||
function _initializeItems() {
|
||||
contentItems = [];
|
||||
for (let i = 0; i < container.children.length; i++) {
|
||||
const child = container.children[i];
|
||||
contentItems.push(child);
|
||||
child.width = Qt.binding(() => root.width);
|
||||
|
||||
if (i === currentIndex) {
|
||||
child.x = 0;
|
||||
child.visible = true;
|
||||
} else {
|
||||
child.x = root.width;
|
||||
child.visible = false;
|
||||
}
|
||||
}
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
if (!initialized || contentItems.length === 0)
|
||||
return;
|
||||
if (previousIndex === currentIndex)
|
||||
return;
|
||||
|
||||
_animateTransition(previousIndex, currentIndex);
|
||||
previousIndex = currentIndex;
|
||||
}
|
||||
|
||||
function _animateTransition(fromIdx, toIdx) {
|
||||
const fromItem = contentItems[fromIdx];
|
||||
const toItem = contentItems[toIdx];
|
||||
const slideLeft = toIdx > fromIdx;
|
||||
|
||||
// Set height to max of both items during animation
|
||||
const fromHeight = fromItem ? fromItem.implicitHeight : 0;
|
||||
const toHeight = toItem ? toItem.implicitHeight : 0;
|
||||
animatingHeight = Math.max(fromHeight, toHeight);
|
||||
animating = true;
|
||||
|
||||
// Position incoming item off-screen (with gap)
|
||||
if (toItem) {
|
||||
toItem.visible = true;
|
||||
toItem.x = slideLeft ? root.width + transitionGap : -root.width - transitionGap;
|
||||
}
|
||||
|
||||
// Animate both items together (with gap)
|
||||
if (fromItem) {
|
||||
fromAnim.target = fromItem;
|
||||
fromAnim.to = slideLeft ? -root.width - transitionGap : root.width + transitionGap;
|
||||
fromAnim.start();
|
||||
}
|
||||
|
||||
if (toItem) {
|
||||
toAnim.target = toItem;
|
||||
toAnim.start();
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: fromAnim
|
||||
property: "x"
|
||||
duration: root.transitionTime
|
||||
easing.type: Easing.OutCubic
|
||||
onFinished: {
|
||||
if (target && target !== contentItems[currentIndex]) {
|
||||
target.visible = false;
|
||||
}
|
||||
animating = false;
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: toAnim
|
||||
property: "x"
|
||||
to: 0
|
||||
duration: root.transitionTime
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user