diff --git a/Modules/ArchUpdaterPanel/ArchUpdaterPanel.qml b/Modules/ArchUpdaterPanel/ArchUpdaterPanel.qml index e45901e..3ce2656 100644 --- a/Modules/ArchUpdaterPanel/ArchUpdaterPanel.qml +++ b/Modules/ArchUpdaterPanel/ArchUpdaterPanel.qml @@ -175,7 +175,7 @@ NPanel { // Action buttons RowLayout { Layout.fillWidth: true - spacing: Style.marginS * scaling + spacing: Style.marginL * scaling NIconButton { icon: "refresh" @@ -187,7 +187,6 @@ NPanel { colorBg: Color.mSurfaceVariant colorFg: Color.mOnSurface Layout.fillWidth: true - Layout.preferredHeight: 35 * scaling } NIconButton { @@ -201,7 +200,6 @@ NPanel { colorBg: ArchUpdaterService.updateInProgress ? Color.mSurfaceVariant : Color.mPrimary colorFg: ArchUpdaterService.updateInProgress ? Color.mOnSurfaceVariant : Color.mOnPrimary Layout.fillWidth: true - Layout.preferredHeight: 35 * scaling } NIconButton { @@ -219,7 +217,6 @@ NPanel { colorFg: ArchUpdaterService.updateInProgress ? Color.mOnSurfaceVariant : (ArchUpdaterService.selectedPackagesCount > 0 ? Color.mOnSecondary : Color.mOnSurfaceVariant) Layout.fillWidth: true - Layout.preferredHeight: 35 * scaling } } } diff --git a/Modules/SettingsPanel/SettingsPanel.qml b/Modules/SettingsPanel/SettingsPanel.qml index 32214cf..a1992f9 100644 --- a/Modules/SettingsPanel/SettingsPanel.qml +++ b/Modules/SettingsPanel/SettingsPanel.qml @@ -267,7 +267,7 @@ NPanel { // Tab label on the main right side NText { text: root.tabsModel[currentTabIndex].label - font.pointSize: Style.fontSizeL * scaling + font.pointSize: Style.fontSizeXL * scaling font.weight: Style.fontWeightBold color: Color.mPrimary Layout.fillWidth: true @@ -287,21 +287,28 @@ NPanel { Item { Layout.fillWidth: true Layout.fillHeight: true - clip: true Repeater { model: root.tabsModel - - onItemAdded: function (index, item) { - item.sourceComponent = root.tabsModel[index].source - } - delegate: Loader { - // All loaders will occupy the same space, stacked on top of each other. anchors.fill: parent - visible: index === root.currentTabIndex - // The loader is only active (and uses memory) when its page is visible. - active: visible + active: index === root.currentTabIndex + sourceComponent: ColumnLayout { + ScrollView { + id: scrollView + Layout.fillWidth: true + Layout.fillHeight: true + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ScrollBar.vertical.policy: ScrollBar.AsNeeded + padding: Style.marginL * scaling + + Loader { + active: true + sourceComponent: root.tabsModel[index].source + width: scrollView.availableWidth + } + } + } } } } diff --git a/Modules/SettingsPanel/Tabs/AboutTab.qml b/Modules/SettingsPanel/Tabs/AboutTab.qml index b76c3d9..bc02641 100644 --- a/Modules/SettingsPanel/Tabs/AboutTab.qml +++ b/Modules/SettingsPanel/Tabs/AboutTab.qml @@ -9,16 +9,12 @@ import qs.Services import qs.Widgets ColumnLayout { - id: root + id: root property string latestVersion: GitHubService.latestVersion property string currentVersion: "Unknown" // Fallback version property var contributors: GitHubService.contributors - spacing: 0 - Layout.fillWidth: true - Layout.fillHeight: true - Process { id: currentVersionProcess @@ -41,23 +37,9 @@ ColumnLayout { } } - ScrollView { - id: scrollView - - Layout.fillWidth: true - Layout.fillHeight: true - padding: Style.marginL * scaling - rightPadding: Style.marginM * scaling - clip: true - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - ScrollBar.vertical.policy: ScrollBar.AsNeeded - - ColumnLayout { - width: scrollView.availableWidth - spacing: 0 NText { - text: "Noctalia: quiet by design" + text: "Noctalia Shell" font.pointSize: Style.fontSizeXXXL * scaling font.weight: Style.fontWeightBold color: Color.mOnSurface @@ -65,14 +47,6 @@ ColumnLayout { Layout.bottomMargin: Style.marginS * scaling } - NText { - text: "It may just be another quickshell setup but it won't get in your way." - font.pointSize: Style.fontSizeM * scaling - color: Color.mOnSurface - Layout.alignment: Qt.AlignCenter - Layout.bottomMargin: Style.marginL * scaling - } - GridLayout { Layout.alignment: Qt.AlignCenter columns: 2 @@ -163,8 +137,8 @@ ColumnLayout { NDivider { Layout.fillWidth: true - Layout.topMargin: Style.marginL * 2 * scaling - Layout.bottomMargin: Style.marginL * scaling + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginxL * scaling } NText { @@ -258,5 +232,4 @@ ColumnLayout { } } } - } -} + diff --git a/Modules/SettingsPanel/Tabs/AudioTab.qml b/Modules/SettingsPanel/Tabs/AudioTab.qml index 92785e7..7ee5755 100644 --- a/Modules/SettingsPanel/Tabs/AudioTab.qml +++ b/Modules/SettingsPanel/Tabs/AudioTab.qml @@ -7,11 +7,8 @@ import qs.Commons import qs.Services ColumnLayout { - id: root - property real localVolume: AudioService.volume - // Connection used to open the pill when volume changes Connections { target: AudioService.sink?.audio ? AudioService.sink?.audio : null function onVolumeChanged() { @@ -19,463 +16,404 @@ ColumnLayout { } } - spacing: 0 - - ScrollView { - id: scrollView - + // Master Volume + ColumnLayout { + spacing: Style.marginS * scaling Layout.fillWidth: true - Layout.fillHeight: true - padding: Style.marginM * scaling - clip: true - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - ScrollBar.vertical.policy: ScrollBar.AsNeeded + + NLabel { + label: "Output Volume" + description: "System-wide volume level." + } + + RowLayout { + // Pipewire seems a bit finicky, if we spam too many volume changes it breaks easily + // Probably because they have some quick fades in and out to avoid clipping + // We use a timer to space out the updates, to avoid lock up + Timer { + interval: Style.animationFast + running: true + repeat: true + onTriggered: { + if (Math.abs(localVolume - AudioService.volume) >= 0.01) { + AudioService.setVolume(localVolume) + } + } + } + + NSlider { + Layout.fillWidth: true + from: 0 + to: Settings.data.audio.volumeOverdrive ? 2.0 : 1.0 + value: localVolume + stepSize: 0.01 + onMoved: { + localVolume = value + } + } + + NText { + text: Math.floor(AudioService.volume * 100) + "%" + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: Style.marginS * scaling + color: Color.mOnSurface + } + } + } + + // Mute Toggle + ColumnLayout { + spacing: Style.marginS * scaling + Layout.fillWidth: true + Layout.topMargin: Style.marginM * scaling + + NToggle { + label: "Mute Audio Output" + description: "Mute or unmute the default audio output." + checked: AudioService.muted + onToggled: checked => { + if (AudioService.sink && AudioService.sink.audio) { + AudioService.sink.audio.muted = checked + } + } + } + } + + // Volume Step Size + ColumnLayout { + spacing: Style.marginS * scaling + Layout.fillWidth: true + Layout.topMargin: Style.marginM * scaling + + NSpinBox { + Layout.fillWidth: true + label: "Volume Step Size" + description: "Adjust the step size for volume changes (scroll wheel, keyboard shortcuts)." + minimum: 1 + maximum: 25 + value: Settings.data.audio.volumeStep + stepSize: 1 + suffix: "%" + onValueChanged: { + Settings.data.audio.volumeStep = value + } + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + + // AudioService Devices + ColumnLayout { + spacing: Style.marginS * scaling + + NText { + text: "Audio Devices" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mSecondary + Layout.bottomMargin: Style.marginS * scaling + } + + // ------------------------------- + // Output Devices + ButtonGroup { + id: sinks + } ColumnLayout { - width: scrollView.availableWidth - spacing: 0 + spacing: Style.marginXS * scaling + Layout.fillWidth: true + Layout.bottomMargin: Style.marginL * scaling - Item { - Layout.fillWidth: true - Layout.preferredHeight: 0 + NLabel { + label: "Output Device" + description: "Select the desired audio output device." } - ColumnLayout { - spacing: Style.marginXS * scaling + Repeater { + model: AudioService.sinks + NRadioButton { + required property PwNode modelData + ButtonGroup.group: sinks + checked: AudioService.sink?.id === modelData.id + onClicked: AudioService.setAudioSink(modelData) + text: modelData.description + } + } + } + + // ------------------------------- + // Input Devices + ButtonGroup { + id: sources + } + + ColumnLayout { + spacing: Style.marginXS * scaling + Layout.fillWidth: true + Layout.bottomMargin: Style.marginL * scaling + + NLabel { + label: "Input Device" + description: "Select the desired audio input device." + } + + Repeater { + model: AudioService.sources + NRadioButton { + required property PwNode modelData + ButtonGroup.group: sources + checked: AudioService.source?.id === modelData.id + onClicked: AudioService.setAudioSource(modelData) + text: modelData.description + } + } + } + } + + // Divider + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + + // Media Player Preferences + ColumnLayout { + spacing: Style.marginL * scaling + + NText { + text: "Media Player" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mSecondary + Layout.bottomMargin: Style.marginS * scaling + } + + // Miniplayer section + NToggle { + label: "Show Album Art In Bar Media Player" + description: "Show the album art of the currently playing song next to the title." + checked: Settings.data.audio.showMiniplayerAlbumArt + onToggled: checked => { + Settings.data.audio.showMiniplayerAlbumArt = checked + } + } + + NToggle { + label: "Show Audio Visualizer In Bar Media Player" + description: "Shows an audio visualizer in the background of the miniplayer." + checked: Settings.data.audio.showMiniplayerCava + onToggled: checked => { + Settings.data.audio.showMiniplayerCava = checked + } + } + // Preferred player (persistent) + NTextInput { + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + label: "Preferred Player" + description: "Substring to match MPRIS player (identity/bus/desktop)." + placeholderText: "e.g. spotify, vlc, mpv" + text: Settings.data.audio.preferredPlayer + onTextChanged: { + Settings.data.audio.preferredPlayer = text + MediaService.updateCurrentPlayer() + } + } + + // Blacklist editor + ColumnLayout { + spacing: Style.marginS * scaling + Layout.fillWidth: true + + RowLayout { + spacing: Style.marginS * scaling Layout.fillWidth: true - NText { - text: "Audio Output Volume" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - Layout.bottomMargin: Style.marginS * scaling + NTextInput { + id: blacklistInput + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + label: "Blacklist player" + description: "Substring, e.g. plex, shim, mpv." + placeholderText: "type substring and press +" } - // Volume Controls - ColumnLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true - Layout.topMargin: Style.marginS * scaling - - // Master Volume - ColumnLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true - - NLabel { - label: "Master Volume" - description: "System-wide volume level." + // Button aligned to the center of the actual input field + NIconButton { + icon: "add" + Layout.alignment: Qt.AlignBottom + Layout.bottomMargin: blacklistInput.description ? Style.marginS * scaling : 0 + onClicked: { + 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 * scaling + spacing: Style.marginS * scaling + + Repeater { + model: Settings.data.audio.mprisBlacklist + delegate: Rectangle { + required property string modelData + // Padding around the inner row + property real pad: Style.marginS * scaling + // Visuals + color: Color.applyOpacity(Color.mOnSurface, "20") + border.color: Color.applyOpacity(Color.mOnSurface, "50") + border.width: Math.max(1, Style.borderS * scaling) + + // Content RowLayout { - // Pipewire seems a bit finicky, if we spam too many volume changes it breaks easily - // Probably because they have some quick fades in and out to avoid clipping - // We use a timer to space out the updates, to avoid lock up - Timer { - interval: Style.animationFast - running: true - repeat: true - onTriggered: { - if (Math.abs(localVolume - AudioService.volume) >= 0.01) { - AudioService.setVolume(localVolume) - } - } - } - - NSlider { - Layout.fillWidth: true - from: 0 - to: Settings.data.audio.volumeOverdrive ? 2.0 : 1.0 - value: localVolume - stepSize: 0.01 - onMoved: { - localVolume = value - } - } + id: chipRow + spacing: Style.marginXS * scaling + anchors.fill: parent + anchors.margins: pad NText { - text: Math.floor(AudioService.volume * 100) + "%" + text: modelData + color: Color.mOnSurface + font.pointSize: Style.fontSizeS * scaling Layout.alignment: Qt.AlignVCenter Layout.leftMargin: Style.marginS * scaling - color: Color.mOnSurface } - } - } - // Mute Toggle - ColumnLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true - Layout.topMargin: Style.marginM * scaling - - NToggle { - label: "Mute Audio Output" - description: "Mute or unmute the default audio output." - checked: AudioService.muted - onToggled: checked => { - if (AudioService.sink && AudioService.sink.audio) { - AudioService.sink.audio.muted = checked - } - } - } - } - - // Volume Step Size - ColumnLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true - Layout.topMargin: Style.marginM * scaling - - NSpinBox { - Layout.fillWidth: true - label: "Volume Step Size" - description: "Adjust the step size for volume changes (scroll wheel, keyboard shortcuts)." - minimum: 1 - maximum: 25 - value: Settings.data.audio.volumeStep - stepSize: 1 - suffix: "%" - onValueChanged: { - Settings.data.audio.volumeStep = value - } - } - } - } - - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginL * 2 * scaling - Layout.bottomMargin: Style.marginL * scaling - } - - // AudioService Devices - ColumnLayout { - spacing: Style.marginL * scaling - Layout.fillWidth: true - - NText { - text: "Audio Devices" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - Layout.bottomMargin: Style.marginS * scaling - } - - // ------------------------------- - // Output Devices - ButtonGroup { - id: sinks - } - - ColumnLayout { - spacing: Style.marginXS * scaling - Layout.fillWidth: true - Layout.bottomMargin: Style.marginL * scaling - - NLabel { - label: "Output Device" - description: "Select the desired audio output device." - } - - Repeater { - model: AudioService.sinks - NRadioButton { - required property PwNode modelData - ButtonGroup.group: sinks - checked: AudioService.sink?.id === modelData.id - onClicked: AudioService.setAudioSink(modelData) - text: modelData.description - } - } - } - } - - // ------------------------------- - // Input Devices - ButtonGroup { - id: sources - } - - ColumnLayout { - spacing: Style.marginXS * scaling - Layout.fillWidth: true - Layout.bottomMargin: Style.marginL * scaling - - NLabel { - label: "Input Device" - description: "Select the desired audio input device." - } - - Repeater { - model: AudioService.sources - NRadioButton { - required property PwNode modelData - ButtonGroup.group: sources - checked: AudioService.source?.id === modelData.id - onClicked: AudioService.setAudioSource(modelData) - text: modelData.description - } - } - } - } - - // Divider - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginL * scaling - Layout.bottomMargin: Style.marginXL * scaling - } - - // MPRIS Player Preferences - ColumnLayout { - spacing: Style.marginL * scaling - Layout.fillWidth: true - - NText { - text: "MPRIS Player Preferences" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - Layout.bottomMargin: Style.marginS * scaling - } - - // Preferred player (persistent) - NTextInput { - label: "Preferred Player" - description: "Substring to match MPRIS player (identity/bus/desktop)." - placeholderText: "e.g. spotify, vlc, mpv" - text: Settings.data.audio.preferredPlayer - onTextChanged: { - Settings.data.audio.preferredPlayer = text - MediaService.updateCurrentPlayer() - } - } - - // Blacklist editor - ColumnLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true - - RowLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true - - NTextInput { - id: blacklistInput - Layout.fillWidth: true - Layout.alignment: Qt.AlignTop - label: "Blacklist player" - description: "Substring, e.g. plex, shim, mpv" - placeholderText: "type substring and press +" - } - - // Button aligned to the center of the actual input field - NIconButton { - icon: "add" - Layout.alignment: Qt.AlignBottom - Layout.bottomMargin: blacklistInput.description ? Style.marginS * scaling : 0 - onClicked: { - const val = (blacklistInput.text || "").trim() - if (val !== "") { + NIconButton { + icon: "close" + sizeRatio: 0.8 + Layout.alignment: Qt.AlignVCenter + Layout.rightMargin: Style.marginXS * scaling + onClicked: { const arr = (Settings.data.audio.mprisBlacklist || []) - if (!arr.find(x => String(x).toLowerCase() === val.toLowerCase())) { - Settings.data.audio.mprisBlacklist = [...arr, val] - blacklistInput.text = "" + 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 * scaling) + radius: Style.radiusM * scaling } - - // Current blacklist entries - Flow { - Layout.fillWidth: true - Layout.leftMargin: Style.marginS * scaling - spacing: Style.marginS * scaling - - Repeater { - model: Settings.data.audio.mprisBlacklist - delegate: Rectangle { - required property string modelData - // Padding around the inner row - property real pad: Style.marginS * scaling - // Visuals - color: Color.applyOpacity(Color.mOnSurface, "20") - border.color: Color.applyOpacity(Color.mOnSurface, "50") - border.width: Math.max(1, Style.borderS * scaling) - - // Content - RowLayout { - id: chipRow - spacing: Style.marginXS * scaling - anchors.fill: parent - anchors.margins: pad - - NText { - text: modelData - color: Color.mOnSurface - font.pointSize: Style.fontSizeS * scaling - Layout.alignment: Qt.AlignVCenter - Layout.leftMargin: Style.marginS * scaling - } - - NIconButton { - icon: "close" - sizeRatio: 0.8 - Layout.alignment: Qt.AlignVCenter - Layout.rightMargin: Style.marginXS * scaling - 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 * scaling) - radius: Style.radiusM * scaling - } - } - } - } - } - - // Divider - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginXL * scaling - Layout.bottomMargin: Style.marginXL * scaling - } - - // Bar Mini Media player - ColumnLayout { - spacing: Style.marginL * scaling - Layout.fillWidth: true - - NText { - text: "Bar Media Player" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - Layout.bottomMargin: Style.marginS * scaling - } - - // Miniplayer section - NToggle { - label: "Show Album Art In Bar Media Player" - description: "Show the album art of the currently playing song next to the title." - checked: Settings.data.audio.showMiniplayerAlbumArt - onToggled: checked => { - Settings.data.audio.showMiniplayerAlbumArt = checked - } - } - - NToggle { - label: "Show Audio Visualizer In Bar Media Player" - description: "Shows an audio visualizer in the background of the miniplayer." - checked: Settings.data.audio.showMiniplayerCava - onToggled: checked => { - Settings.data.audio.showMiniplayerCava = checked - } - } - } - - // Divider - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginXL * scaling - Layout.bottomMargin: Style.marginXL * scaling - } - - // AudioService Visualizer Category - ColumnLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true - - NText { - text: "Audio Visualizer" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - Layout.bottomMargin: Style.marginS * scaling - } - - // AudioService Visualizer section - NComboBox { - id: audioVisualizerCombo - label: "Visualization Type" - description: "Choose a visualization type for media playback" - model: ListModel { - ListElement { - key: "none" - name: "None" - } - ListElement { - key: "linear" - name: "Linear" - } - ListElement { - key: "mirrored" - name: "Mirrored" - } - ListElement { - key: "wave" - name: "Wave" - } - } - currentKey: Settings.data.audio.visualizerType - onSelected: key => { - Settings.data.audio.visualizerType = key - } - } - - NComboBox { - label: "Frame Rate" - description: "Target frame rate for audio visualizer. (default: 60)" - model: ListModel { - ListElement { - key: "30" - name: "30 FPS" - } - ListElement { - key: "60" - name: "60 FPS" - } - ListElement { - key: "100" - name: "100 FPS" - } - ListElement { - key: "120" - name: "120 FPS" - } - ListElement { - key: "144" - name: "144 FPS" - } - ListElement { - key: "165" - name: "165 FPS" - } - ListElement { - key: "240" - name: "240 FPS" - } - } - currentKey: Settings.data.audio.cavaFrameRate - onSelected: key => { - Settings.data.audio.cavaFrameRate = key - } } } } } + + // Divider + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + + // AudioService Visualizer Category + ColumnLayout { + spacing: Style.marginS * scaling + Layout.fillWidth: true + + NText { + text: "Audio Visualizer" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mSecondary + Layout.bottomMargin: Style.marginS * scaling + } + + // AudioService Visualizer section + NComboBox { + id: audioVisualizerCombo + label: "Visualization Type" + description: "Choose a visualization type for media playback" + model: ListModel { + ListElement { + key: "none" + name: "None" + } + ListElement { + key: "linear" + name: "Linear" + } + ListElement { + key: "mirrored" + name: "Mirrored" + } + ListElement { + key: "wave" + name: "Wave" + } + } + currentKey: Settings.data.audio.visualizerType + onSelected: key => { + Settings.data.audio.visualizerType = key + } + } + + NComboBox { + label: "Frame Rate" + description: "Target frame rate for audio visualizer." + model: ListModel { + ListElement { + key: "30" + name: "30 FPS" + } + ListElement { + key: "60" + name: "60 FPS" + } + ListElement { + key: "100" + name: "100 FPS" + } + ListElement { + key: "120" + name: "120 FPS" + } + ListElement { + key: "144" + name: "144 FPS" + } + ListElement { + key: "165" + name: "165 FPS" + } + ListElement { + key: "240" + name: "240 FPS" + } + } + currentKey: Settings.data.audio.cavaFrameRate + onSelected: key => { + Settings.data.audio.cavaFrameRate = key + } + } + } + // Divider + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } } diff --git a/Modules/SettingsPanel/Tabs/BarTab.qml b/Modules/SettingsPanel/Tabs/BarTab.qml index 8f327b6..c86868a 100644 --- a/Modules/SettingsPanel/Tabs/BarTab.qml +++ b/Modules/SettingsPanel/Tabs/BarTab.qml @@ -6,221 +6,181 @@ import qs.Services import qs.Widgets ColumnLayout { - id: root - spacing: 0 + ColumnLayout { + spacing: Style.marginL * scaling - ScrollView { - id: scrollView - - Layout.fillWidth: true - Layout.fillHeight: true - padding: Style.marginM * scaling - clip: true - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - ScrollBar.vertical.policy: ScrollBar.AsNeeded + RowLayout { + NComboBox { + Layout.fillWidth: true + label: "Bar Position" + description: "Choose where to place the bar on the screen." + model: ListModel { + ListElement { + key: "top" + name: "Top" + } + ListElement { + key: "bottom" + name: "Bottom" + } + } + currentKey: Settings.data.bar.position + onSelected: key => Settings.data.bar.position = key + } + } ColumnLayout { - width: scrollView.availableWidth - spacing: 0 + spacing: Style.marginXXS * scaling + Layout.fillWidth: true - Item { - Layout.fillWidth: true - Layout.preferredHeight: 0 + NText { + text: "Background Opacity" + font.pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface } - ColumnLayout { - spacing: Style.marginL * scaling + NText { + text: "Adjust the background opacity of the bar" + font.pointSize: Style.fontSizeXS * scaling + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap Layout.fillWidth: true + } - ColumnLayout { - spacing: Style.marginXXS * scaling + RowLayout { + NSlider { Layout.fillWidth: true - - NText { - text: "Bar Position" - font.pointSize: Style.fontSizeL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - } - - NText { - text: "Choose where to place the bar on the screen" - font.pointSize: Style.fontSizeXS * scaling - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap - Layout.fillWidth: true - } - - NComboBox { - Layout.fillWidth: true - model: ListModel { - ListElement { - key: "top" - name: "Top" - } - ListElement { - key: "bottom" - name: "Bottom" - } - } - currentKey: Settings.data.bar.position - onSelected: key => { - Settings.data.bar.position = key - } - } + from: 0 + to: 1 + stepSize: 0.01 + value: Settings.data.bar.backgroundOpacity + onMoved: Settings.data.bar.backgroundOpacity = value + cutoutColor: Color.mSurface } - ColumnLayout { - spacing: Style.marginXXS * scaling - Layout.fillWidth: true - - NText { - text: "Background Opacity" - font.pointSize: Style.fontSizeL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - } - - NText { - text: "Adjust the background opacity of the bar" - font.pointSize: Style.fontSizeXS * scaling - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap - Layout.fillWidth: true - } - - RowLayout { - NSlider { - Layout.fillWidth: true - from: 0 - to: 1 - stepSize: 0.01 - value: Settings.data.bar.backgroundOpacity - onMoved: Settings.data.bar.backgroundOpacity = value - cutoutColor: Color.mSurface - } - - NText { - text: Math.floor(Settings.data.bar.backgroundOpacity * 100) + "%" - Layout.alignment: Qt.AlignVCenter - Layout.leftMargin: Style.marginS * scaling - color: Color.mOnSurface - } - } - } - - NToggle { - label: "Show Active Window's Icon" - description: "Display the app icon next to the title of the currently focused window." - checked: Settings.data.bar.showActiveWindowIcon - onToggled: checked => { - Settings.data.bar.showActiveWindowIcon = checked - } - } - - NToggle { - label: "Show Battery Percentage" - description: "Display battery percentage at all times." - checked: Settings.data.bar.alwaysShowBatteryPercentage - onToggled: checked => { - Settings.data.bar.alwaysShowBatteryPercentage = checked - } - } - - NComboBox { - label: "Show Workspaces Labels" - description: "Display the workspace name or index in the workspace indicator" - model: ListModel { - ListElement { - key: "none" - name: "None" - } - ListElement { - key: "index" - name: "Index" - } - ListElement { - key: "name" - name: "Name" - } - } - currentKey: Settings.data.bar.showWorkspaceLabel - onSelected: key => { - Settings.data.bar.showWorkspaceLabel = key - } - } - - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginL * scaling - Layout.bottomMargin: Style.marginL * scaling - } - - // Widgets Management Section - ColumnLayout { - spacing: Style.marginXXS * scaling - Layout.fillWidth: true - - NText { - text: "Widgets Positioning" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - Layout.bottomMargin: Style.marginS * scaling - } - - NText { - text: "Drag and drop widgets to reorder them within each section, or use the add/remove buttons to manage widgets." - font.pointSize: Style.fontSizeXS * scaling - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap - Layout.fillWidth: true - } - - // Bar Sections - ColumnLayout { - Layout.fillWidth: true - Layout.fillHeight: true - Layout.topMargin: Style.marginM * scaling - spacing: Style.marginM * scaling - - // Left Section - NSectionEditor { - sectionName: "Left" - widgetModel: Settings.data.bar.widgets.left - availableWidgets: availableWidgets - scrollView: scrollView - onAddWidget: (widgetName, section) => addWidgetToSection(widgetName, section) - onRemoveWidget: (section, index) => removeWidgetFromSection(section, index) - onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex) - } - - // Center Section - NSectionEditor { - sectionName: "Center" - widgetModel: Settings.data.bar.widgets.center - availableWidgets: availableWidgets - scrollView: scrollView - onAddWidget: (widgetName, section) => addWidgetToSection(widgetName, section) - onRemoveWidget: (section, index) => removeWidgetFromSection(section, index) - onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex) - } - - // Right Section - NSectionEditor { - sectionName: "Right" - widgetModel: Settings.data.bar.widgets.right - availableWidgets: availableWidgets - scrollView: scrollView - onAddWidget: (widgetName, section) => addWidgetToSection(widgetName, section) - onRemoveWidget: (section, index) => removeWidgetFromSection(section, index) - onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex) - } - } + NText { + text: Math.floor(Settings.data.bar.backgroundOpacity * 100) + "%" + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: Style.marginS * scaling + color: Color.mOnSurface } } } + + NToggle { + label: "Show Active Window's Icon" + description: "Display the app icon next to the title of the currently focused window." + checked: Settings.data.bar.showActiveWindowIcon + onToggled: checked => { + Settings.data.bar.showActiveWindowIcon = checked + } + } + + NToggle { + label: "Show Battery Percentage" + description: "Display battery percentage at all times." + checked: Settings.data.bar.alwaysShowBatteryPercentage + onToggled: checked => { + Settings.data.bar.alwaysShowBatteryPercentage = checked + } + } + + NComboBox { + label: "Show Workspaces Labels" + description: "Display the workspace name or index in the workspace indicator" + model: ListModel { + ListElement { + key: "none" + name: "None" + } + ListElement { + key: "index" + name: "Index" + } + ListElement { + key: "name" + name: "Name" + } + } + currentKey: Settings.data.bar.showWorkspaceLabel + onSelected: key => { + Settings.data.bar.showWorkspaceLabel = key + } + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + + // Widgets Management Section + ColumnLayout { + spacing: Style.marginXXS * scaling + Layout.fillWidth: true + + NText { + text: "Widgets Positioning" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.bottomMargin: Style.marginS * scaling + } + + NText { + text: "Drag and drop widgets to reorder them within each section, or use the add/remove buttons to manage widgets." + font.pointSize: Style.fontSizeXS * scaling + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + // Bar Sections + ColumnLayout { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.topMargin: Style.marginM * scaling + spacing: Style.marginM * scaling + + // Left Section + NSectionEditor { + sectionName: "Left" + widgetModel: Settings.data.bar.widgets.left + availableWidgets: availableWidgets + onAddWidget: (widgetName, section) => addWidgetToSection(widgetName, section) + onRemoveWidget: (section, index) => removeWidgetFromSection(section, index) + onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex) + } + + // Center Section + NSectionEditor { + sectionName: "Center" + widgetModel: Settings.data.bar.widgets.center + availableWidgets: availableWidgets + onAddWidget: (widgetName, section) => addWidgetToSection(widgetName, section) + onRemoveWidget: (section, index) => removeWidgetFromSection(section, index) + onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex) + } + + // Right Section + NSectionEditor { + sectionName: "Right" + widgetModel: Settings.data.bar.widgets.right + availableWidgets: availableWidgets + onAddWidget: (widgetName, section) => addWidgetToSection(widgetName, section) + onRemoveWidget: (section, index) => removeWidgetFromSection(section, index) + onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex) + } + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling } // Helper functions diff --git a/Modules/SettingsPanel/Tabs/BrightnessTab.qml b/Modules/SettingsPanel/Tabs/BrightnessTab.qml index 72f7774..27861de 100644 --- a/Modules/SettingsPanel/Tabs/BrightnessTab.qml +++ b/Modules/SettingsPanel/Tabs/BrightnessTab.qml @@ -6,151 +6,121 @@ import qs.Commons import qs.Services import qs.Widgets -Item { +ColumnLayout { readonly property real scaling: ScalingService.scale(screen) readonly property string tabIcon: "brightness_6" readonly property string tabLabel: "Brightness" - Layout.fillWidth: true - Layout.fillHeight: true + spacing: Style.marginL * scaling - ScrollView { - anchors.fill: parent - clip: true - ScrollBar.vertical.policy: ScrollBar.AsNeeded - ScrollBar.horizontal.policy: ScrollBar.AsNeeded - contentWidth: parent.width + // Brightness Step Section + ColumnLayout { + spacing: Style.marginS * scaling + Layout.fillWidth: true - ColumnLayout { - width: parent.width - ColumnLayout { - spacing: Style.marginL * scaling - Layout.margins: Style.marginL * scaling + NSpinBox { + Layout.fillWidth: true + label: "Brightness Step Size" + description: "Adjust the step size for brightness changes (scroll wheel, keyboard shortcuts)." + minimum: 1 + maximum: 50 + value: Settings.data.brightness.brightnessStep + stepSize: 1 + suffix: "%" + onValueChanged: { + Settings.data.brightness.brightnessStep = value + } + } + } + + // Monitor Overview Section + ColumnLayout { + spacing: Style.marginL * scaling + + NLabel { + label: "Monitors Brightness Control" + description: "Current brightness levels for all detected monitors." + } + + // Single monitor display using the same data source as the bar icon + Repeater { + model: BrightnessService.monitors + Rectangle { Layout.fillWidth: true + radius: Style.radiusM * scaling + color: Color.mSurface + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling - NText { - text: "Brightness Settings" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - } - - // Brightness Step Section ColumnLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true + id: contentCol + anchors.fill: parent + anchors.margins: Style.marginL * scaling + spacing: Style.marginM * scaling - NSpinBox { + RowLayout { Layout.fillWidth: true - label: "Brightness Step Size" - description: "Adjust the step size for brightness changes (scroll wheel, keyboard shortcuts)." - minimum: 1 - maximum: 50 - value: Settings.data.brightness.brightnessStep - stepSize: 1 - suffix: "%" - onValueChanged: { - Settings.data.brightness.brightnessStep = value + spacing: Style.marginM * scaling + + NText { + text: `${model.modelData.name} [${model.modelData.model}]` + font.pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold + color: Color.mSecondary + } + + Item { + Layout.fillWidth: true + } + + NText { + text: model.method + font.pointSize: Style.fontSizeXS * scaling + color: Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignRight } } - } - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginL * scaling - Layout.bottomMargin: Style.marginL * scaling - } + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM * scaling - // Monitor Overview Section - ColumnLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true + NText { + text: "Brightness:" + font.pointSize: Style.fontSizeM * scaling + color: Color.mOnSurface + } - NLabel { - label: "Monitors Brightness Control" - description: "Current brightness levels for all detected monitors." - } - - // Single monitor display using the same data source as the bar icon - Repeater { - model: BrightnessService.monitors - Rectangle { + NSlider { Layout.fillWidth: true - radius: Style.radiusM * scaling - color: Color.mSurface - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling - - ColumnLayout { - id: contentCol - anchors.fill: parent - anchors.margins: Style.marginL * scaling - spacing: Style.marginM * scaling - - RowLayout { - Layout.fillWidth: true - spacing: Style.marginM * scaling - - NText { - text: `${model.modelData.name} [${model.modelData.model}]` - font.pointSize: Style.fontSizeL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary - } - - Item { - Layout.fillWidth: true - } - - NText { - text: model.method - font.pointSize: Style.fontSizeXS * scaling - color: Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignRight - } - } - - RowLayout { - Layout.fillWidth: true - spacing: Style.marginM * scaling - - NText { - text: "Brightness:" - font.pointSize: Style.fontSizeM * scaling - color: Color.mOnSurface - } - - NSlider { - Layout.fillWidth: true - from: 0 - to: 1 - value: model.brightness - stepSize: 0.05 - onPressedChanged: { - if (!pressed) { - var monitor = BrightnessService.getMonitorForScreen(model.modelData) - monitor.setBrightness(value) - } - } - } - - NText { - text: Math.round(model.brightness * 100) + "%" - font.pointSize: Style.fontSizeM * scaling - font.weight: Style.fontWeightBold - color: Color.mPrimary - Layout.alignment: Qt.AlignRight - } + from: 0 + to: 1 + value: model.brightness + stepSize: 0.05 + onPressedChanged: { + if (!pressed) { + var monitor = BrightnessService.getMonitorForScreen(model.modelData) + monitor.setBrightness(value) } } } - } - } - Item { - Layout.fillHeight: true + NText { + text: Math.round(model.brightness * 100) + "%" + font.pointSize: Style.fontSizeM * scaling + font.weight: Style.fontWeightBold + color: Color.mPrimary + Layout.alignment: Qt.AlignRight + } + } } } } } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } } diff --git a/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml b/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml index 61b7159..795d03c 100644 --- a/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml +++ b/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml @@ -9,8 +9,6 @@ import qs.Widgets ColumnLayout { id: root - spacing: 0 - // Helper function to get color from scheme file (supports dark/light variants) function getSchemeColor(schemePath, colorKey) { // Extract scheme name from path @@ -55,6 +53,28 @@ ColumnLayout { } } + // Simple process to check if matugen exists + Process { + id: matugenCheck + command: ["which", "matugen"] + running: false + + onExited: function (exitCode) { + if (exitCode === 0) { + // Matugen exists, enable it + Settings.data.colorSchemes.useWallpaperColors = true + ColorSchemeService.changedWallpaper() + ToastService.showNotice("Matugen", "Enabled!") + } else { + // Matugen not found + ToastService.showWarning("Matugen", "Not installed!") + } + } + + stdout: StdioCollector {} + stderr: StdioCollector {} + } + // A non-visual Item to host the Repeater that loads the color scheme files. Item { visible: false @@ -83,285 +103,250 @@ ColumnLayout { } } - // UI Code - ScrollView { - id: scrollView + ColumnLayout { + width: scrollView.availableWidth + spacing: 0 - Layout.fillWidth: true - Layout.fillHeight: true - padding: Style.marginM * scaling - clip: true - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - ScrollBar.vertical.policy: ScrollBar.AsNeeded + Item { + Layout.fillWidth: true + Layout.preferredHeight: 0 + } ColumnLayout { - width: scrollView.availableWidth - spacing: 0 + spacing: Style.marginL * scaling + Layout.fillWidth: true - Item { + // Dark Mode Toggle (affects both Matugen and predefined schemes that provide variants) + NToggle { + label: "Dark Mode" + description: Settings.data.colorSchemes.useWallpaperColors ? "Generate dark theme colors when using Matugen." : "Use a dark variant if available." + checked: Settings.data.colorSchemes.darkMode + enabled: true + onToggled: checked => { + Settings.data.colorSchemes.darkMode = checked + if (Settings.data.colorSchemes.useWallpaperColors) { + ColorSchemeService.changedWallpaper() + } else if (Settings.data.colorSchemes.predefinedScheme) { + // Re-apply current scheme to pick the right variant + ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme) + // Force refresh of previews + var tmp = schemeColorsCache + schemeColorsCache = {} + schemeColorsCache = tmp + } + } + } + + // App theming + NToggle { + label: "Theme external apps (GTK, Qt & kitty)" + description: "Writes GTK (gtk.css), Qt5/6 (noctalia.conf) and Kitty (noctalia.conf) themes based on your colors." + checked: Settings.data.colorSchemes.themeApps + onToggled: checked => { + Settings.data.colorSchemes.themeApps = checked + if (Settings.data.colorSchemes.useWallpaperColors) { + ColorSchemeService.changedWallpaper() + } + } + } + + // Use Matugen + NToggle { + label: "Enable Matugen" + description: "Automatically generate colors based on your active wallpaper." + checked: Settings.data.colorSchemes.useWallpaperColors + onToggled: checked => { + if (checked) { + // Check if matugen is installed + matugenCheck.running = true + } else { + Settings.data.colorSchemes.useWallpaperColors = false + ToastService.showNotice("Matugen", "Disabled") + } + } + } + + NDivider { Layout.fillWidth: true - Layout.preferredHeight: 0 + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling } ColumnLayout { - spacing: Style.marginL * scaling + spacing: Style.marginS * scaling Layout.fillWidth: true - // Dark Mode Toggle (affects both Matugen and predefined schemes that provide variants) - NToggle { - label: "Dark Mode" - description: Settings.data.colorSchemes.useWallpaperColors ? "Generate dark theme colors when using Matugen." : "Use a dark variant if available." - checked: Settings.data.colorSchemes.darkMode - enabled: true - onToggled: checked => { - Settings.data.colorSchemes.darkMode = checked - if (Settings.data.colorSchemes.useWallpaperColors) { - ColorSchemeService.changedWallpaper() - } else if (Settings.data.colorSchemes.predefinedScheme) { - // Re-apply current scheme to pick the right variant - ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme) - // Force refresh of previews - var tmp = schemeColorsCache - schemeColorsCache = {} - schemeColorsCache = tmp - } - } + NText { + text: "Predefined Color Schemes" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mSecondary } - // App theming - NToggle { - label: "Theme external apps (GTK, Qt & kitty)" - description: "Writes GTK (gtk.css), Qt5/6 (noctalia.conf) and Kitty (noctalia.conf) themes based on your colors." - checked: Settings.data.colorSchemes.themeApps - onToggled: checked => { - Settings.data.colorSchemes.themeApps = checked - if (Settings.data.colorSchemes.useWallpaperColors) { - ColorSchemeService.changedWallpaper() - } - } - } - - // Use Matugen - NToggle { - label: "Enable Matugen" - description: "Automatically generate colors based on your active wallpaper." - checked: Settings.data.colorSchemes.useWallpaperColors - onToggled: checked => { - if (checked) { - // Check if matugen is installed - matugenCheck.running = true - } else { - Settings.data.colorSchemes.useWallpaperColors = false - ToastService.showNotice("Matugen", "Disabled") - } - } - } - - NDivider { + NText { + text: "These color schemes are only active when 'Use Matugen' is turned off. With Matugen enabled, colors will be automatically generated from your wallpaper. You can still switch between light and dark themes while using Matugen." + font.pointSize: Style.fontSizeM * scaling + color: Color.mOnSurfaceVariant Layout.fillWidth: true - Layout.topMargin: Style.marginL * scaling - Layout.bottomMargin: Style.marginL * scaling + wrapMode: Text.WordWrap } + } - ColumnLayout { - spacing: Style.marginXXS * scaling - Layout.fillWidth: true + // Color Schemes Grid + GridLayout { + columns: 4 + rowSpacing: Style.marginL * scaling + columnSpacing: Style.marginL * scaling + Layout.fillWidth: true + + Repeater { + model: ColorSchemeService.schemes + + Rectangle { + id: schemeCard + + property string schemePath: modelData - NText { - text: "Predefined Color Schemes" - font.pointSize: Style.fontSizeL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface Layout.fillWidth: true - } + Layout.preferredHeight: 120 * scaling + radius: Style.radiusM * scaling + color: getSchemeColor(modelData, "mSurface") + border.width: Math.max(1, Style.borderL * scaling) + border.color: Settings.data.colorSchemes.predefinedScheme === modelData ? Color.mPrimary : Color.mOutline + scale: root.cardScaleLow - NText { - text: "These color schemes only apply when 'Use Matugen' is disabled. When enabled, Matugen will generate colors based on your wallpaper instead. You can toggle between light and dark themes when using Matugen." - font.pointSize: Style.fontSizeXS * scaling - color: Color.mOnSurface - Layout.fillWidth: true - wrapMode: Text.WordWrap - } - } + // Mouse area for selection + MouseArea { + anchors.fill: parent + onClicked: { + // Disable useWallpaperColors when picking a predefined color scheme + Settings.data.colorSchemes.useWallpaperColors = false + Logger.log("ColorSchemeTab", "Disabled matugen setting") - ColumnLayout { - spacing: Style.marginXS * scaling - Layout.fillWidth: true - Layout.topMargin: Style.marginL * scaling + Settings.data.colorSchemes.predefinedScheme = schemePath + ColorSchemeService.applyScheme(schemePath) + } + hoverEnabled: true + cursorShape: Qt.PointingHandCursor - // Color Schemes Grid - GridLayout { - columns: 4 - rowSpacing: Style.marginL * scaling - columnSpacing: Style.marginL * scaling - Layout.fillWidth: true + onEntered: { + schemeCard.scale = root.cardScaleHigh + } - Repeater { - model: ColorSchemeService.schemes + onExited: { + schemeCard.scale = root.cardScaleLow + } + } - Rectangle { - id: schemeCard - - property string schemePath: modelData + // Card content + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginXL * scaling + spacing: Style.marginS * scaling + // Scheme name + NText { + text: { + // Remove json and the full path + var chunks = schemePath.replace(".json", "").split("/") + return chunks[chunks.length - 1] + } + font.pointSize: Style.fontSizeM * scaling + font.weight: Style.fontWeightBold + color: getSchemeColor(modelData, "mOnSurface") Layout.fillWidth: true - Layout.preferredHeight: 120 * scaling - radius: Style.radiusM * scaling - color: getSchemeColor(modelData, "mSurface") - border.width: Math.max(1, Style.borderL * scaling) - border.color: Settings.data.colorSchemes.predefinedScheme === modelData ? Color.mPrimary : Color.mOutline - scale: root.cardScaleLow + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + } - // Mouse area for selection - MouseArea { - anchors.fill: parent - onClicked: { - // Disable useWallpaperColors when picking a predefined color scheme - Settings.data.colorSchemes.useWallpaperColors = false - Logger.log("ColorSchemeTab", "Disabled matugen setting") + // Color swatches + RowLayout { + spacing: Style.marginS * scaling + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter - Settings.data.colorSchemes.predefinedScheme = schemePath - ColorSchemeService.applyScheme(schemePath) - } - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - - onEntered: { - schemeCard.scale = root.cardScaleHigh - } - - onExited: { - schemeCard.scale = root.cardScaleLow - } - } - - // Card content - ColumnLayout { - anchors.fill: parent - anchors.margins: Style.marginXL * scaling - spacing: Style.marginS * scaling - - // Scheme name - NText { - text: { - // Remove json and the full path - var chunks = schemePath.replace(".json", "").split("/") - return chunks[chunks.length - 1] - } - font.pointSize: Style.fontSizeM * scaling - font.weight: Style.fontWeightBold - color: getSchemeColor(modelData, "mOnSurface") - Layout.fillWidth: true - elide: Text.ElideRight - horizontalAlignment: Text.AlignHCenter - } - - // Color swatches - RowLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter - - // Primary color swatch - Rectangle { - width: 28 * scaling - height: 28 * scaling - radius: width * 0.5 - color: getSchemeColor(modelData, "mPrimary") - } - - // Secondary color swatch - Rectangle { - width: 28 * scaling - height: 28 * scaling - radius: width * 0.5 - color: getSchemeColor(modelData, "mSecondary") - } - - // Tertiary color swatch - Rectangle { - width: 28 * scaling - height: 28 * scaling - radius: width * 0.5 - color: getSchemeColor(modelData, "mTertiary") - } - - // Error color swatch - Rectangle { - width: 28 * scaling - height: 28 * scaling - radius: width * 0.5 - color: getSchemeColor(modelData, "mError") - } - } - } - - // Selection indicator + // Primary color swatch Rectangle { - visible: Settings.data.colorSchemes.predefinedScheme === schemePath - anchors.right: parent.right - anchors.top: parent.top - anchors.margins: Style.marginS * scaling - width: 24 * scaling - height: 24 * scaling + width: 28 * scaling + height: 28 * scaling radius: width * 0.5 - color: Color.mPrimary - - NText { - anchors.centerIn: parent - text: "✓" - font.pointSize: Style.fontSizeXS * scaling - font.weight: Style.fontWeightBold - color: Color.mOnPrimary - } + color: getSchemeColor(modelData, "mPrimary") } - // Smooth animations - Behavior on scale { - NumberAnimation { - duration: Style.animationNormal - easing.type: Easing.OutCubic - } + // Secondary color swatch + Rectangle { + width: 28 * scaling + height: 28 * scaling + radius: width * 0.5 + color: getSchemeColor(modelData, "mSecondary") } - Behavior on border.color { - ColorAnimation { - duration: Style.animationNormal - } + // Tertiary color swatch + Rectangle { + width: 28 * scaling + height: 28 * scaling + radius: width * 0.5 + color: getSchemeColor(modelData, "mTertiary") } - Behavior on border.width { - NumberAnimation { - duration: Style.animationFast - } + // Error color swatch + Rectangle { + width: 28 * scaling + height: 28 * scaling + radius: width * 0.5 + color: getSchemeColor(modelData, "mError") } } } + + // Selection indicator + Rectangle { + visible: Settings.data.colorSchemes.predefinedScheme === schemePath + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Style.marginS * scaling + width: 24 * scaling + height: 24 * scaling + radius: width * 0.5 + color: Color.mPrimary + + NText { + anchors.centerIn: parent + text: "✓" + font.pointSize: Style.fontSizeXS * scaling + font.weight: Style.fontWeightBold + color: Color.mOnPrimary + } + } + + // Smooth animations + Behavior on scale { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + + Behavior on border.color { + ColorAnimation { + duration: Style.animationNormal + } + } + + Behavior on border.width { + NumberAnimation { + duration: Style.animationFast + } + } } } } } } - // Simple process to check if matugen exists - Process { - id: matugenCheck - command: ["which", "matugen"] - running: false - - onExited: function (exitCode) { - if (exitCode === 0) { - // Matugen exists, enable it - Settings.data.colorSchemes.useWallpaperColors = true - ColorSchemeService.changedWallpaper() - ToastService.showNotice("Matugen", "Enabled!") - } else { - // Matugen not found - ToastService.showWarning("Matugen", "Not installed!") - } - } - - stdout: StdioCollector {} - stderr: StdioCollector {} + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling } } diff --git a/Modules/SettingsPanel/Tabs/DisplayTab.qml b/Modules/SettingsPanel/Tabs/DisplayTab.qml index ff780aa..4eecb3b 100644 --- a/Modules/SettingsPanel/Tabs/DisplayTab.qml +++ b/Modules/SettingsPanel/Tabs/DisplayTab.qml @@ -6,13 +6,11 @@ import qs.Commons import qs.Services import qs.Widgets -Item { +ColumnLayout { readonly property real scaling: ScalingService.scale(screen) readonly property string tabIcon: "monitor" readonly property string tabLabel: "Display" readonly property int tabIndex: 5 - Layout.fillWidth: true - Layout.fillHeight: true // Time dropdown options (00:00 .. 23:30) ListModel { @@ -45,181 +43,167 @@ Item { }) } - ScrollView { - anchors.fill: parent - clip: true - ScrollBar.vertical.policy: ScrollBar.AsNeeded - ScrollBar.horizontal.policy: ScrollBar.AsNeeded - contentWidth: Math.max(parent.width, 600 * scaling) + + NText { + text: "Monitor-specific configuration" + font.pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold + } + + NText { + text: "Bars and notifications appear on all displays by default. Choose specific displays below to limit where they're shown." + font.pointSize: Style.fontSizeM * scaling + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + Layout.preferredWidth: parent.width - (Style.marginL * 2 * scaling) + } ColumnLayout { - id: contentColumn - width: Math.max(parent.width, 600 * scaling) + spacing: Style.marginL * scaling + Layout.topMargin: Style.marginL * scaling - ColumnLayout { - spacing: Style.marginL * scaling - Layout.margins: Style.marginL * scaling - Layout.fillWidth: true - - NText { - text: "Per‑monitor configuration" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - } - - NText { - text: "By default, bars and notifications are shown on all displays. Select one or more below to narrow your view." - font.pointSize: Style.fontSize * scaling - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap + Repeater { + model: Quickshell.screens || [] + delegate: Rectangle { Layout.fillWidth: true - Layout.preferredWidth: parent.width - (Style.marginL * 2 * scaling) - } + Layout.minimumWidth: 550 * scaling + radius: Style.radiusM * scaling + color: Color.mSurface + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling - Repeater { - model: Quickshell.screens || [] - delegate: Rectangle { - Layout.fillWidth: true - Layout.minimumWidth: 550 * scaling - radius: Style.radiusM * scaling - color: Color.mSurface - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling + ColumnLayout { + id: contentCol + anchors.fill: parent + anchors.margins: Style.marginL * scaling + spacing: Style.marginXXS * scaling + + NText { + text: (modelData.name || "Unknown") + font.pointSize: Style.fontSizeXL * scaling + font.weight: Style.fontWeightBold + color: Color.mSecondary + } + + NText { + text: `Resolution: ${modelData.width}x${modelData.height} - Position: (${modelData.x}, ${modelData.y})` + font.pointSize: Style.fontSizeXS * scaling + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + } ColumnLayout { - id: contentCol - anchors.fill: parent - anchors.margins: Style.marginL * scaling - spacing: Style.marginXXS * scaling + spacing: Style.marginL * scaling + Layout.fillWidth: true - NText { - text: (modelData.name || "Unknown") - font.pointSize: Style.fontSizeXL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary + NToggle { + Layout.fillWidth: true + label: "Bar" + description: "Enable the bar on this monitor." + 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) + } + } } - NText { - text: `Resolution: ${modelData.width}x${modelData.height} - Position: (${modelData.x}, ${modelData.y})` - font.pointSize: Style.fontSizeXS * scaling - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap + NToggle { Layout.fillWidth: true + label: "Notifications" + description: "Enable notifications on this monitor." + 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) + } + } + } + + NToggle { + Layout.fillWidth: true + label: "Dock" + description: "Enable the dock on this monitor." + 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) + } + } } ColumnLayout { - spacing: Style.marginL * scaling + spacing: Style.marginS * scaling Layout.fillWidth: true - NToggle { + RowLayout { Layout.fillWidth: true - label: "Bar" - description: "Enable the bar on this monitor." - 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) - } - } - } + spacing: Style.marginL * scaling - NToggle { - Layout.fillWidth: true - label: "Notifications" - description: "Enable notifications on this monitor." - 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) - } - } - } - - NToggle { - Layout.fillWidth: true - label: "Dock" - description: "Enable the dock on this monitor." - 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) - } - } - } - - ColumnLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true - - RowLayout { + ColumnLayout { + spacing: Style.marginXXS * scaling Layout.fillWidth: true - spacing: Style.marginL * scaling - - ColumnLayout { - spacing: Style.marginXXS * scaling - Layout.fillWidth: true - - NText { - text: "Scale" - font.pointSize: Style.fontSizeM * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - } - NText { - text: "Scale the user interface on this monitor." - font.pointSize: Style.fontSizeS * scaling - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap - Layout.fillWidth: true - } - } NText { - text: `${Math.round(ScalingService.scaleByName(modelData.name) * 100)}%` - Layout.alignment: Qt.AlignVCenter - Layout.minimumWidth: 50 * scaling - horizontalAlignment: Text.AlignRight + text: "Scale" + font.pointSize: Style.fontSizeM * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + NText { + text: "Scale the user interface on this monitor." + font.pointSize: Style.fontSizeS * scaling + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true } } - RowLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true + NText { + text: `${Math.round(ScalingService.scaleByName(modelData.name) * 100)}%` + Layout.alignment: Qt.AlignVCenter + Layout.minimumWidth: 50 * scaling + horizontalAlignment: Text.AlignRight + } + } - NSlider { - id: scaleSlider - from: 0.7 - to: 1.8 - stepSize: 0.01 - value: ScalingService.scaleByName(modelData.name) - onPressedChanged: { - var data = Settings.data.monitorsScaling || {} - data[modelData.name] = value - Settings.data.monitorsScaling = data - } - Layout.fillWidth: true - Layout.minimumWidth: 150 * scaling + RowLayout { + spacing: Style.marginS * scaling + Layout.fillWidth: true + + NSlider { + id: scaleSlider + from: 0.7 + to: 1.8 + stepSize: 0.01 + value: ScalingService.scaleByName(modelData.name) + onPressedChanged: { + var data = Settings.data.monitorsScaling || {} + data[modelData.name] = value + Settings.data.monitorsScaling = data } + Layout.fillWidth: true + Layout.minimumWidth: 150 * scaling + } - NIconButton { - icon: "refresh" - tooltipText: "Reset Scaling" - onClicked: { - var data = Settings.data.monitorsScaling || {} - data[modelData.name] = 1.0 - Settings.data.monitorsScaling = data - } + NIconButton { + icon: "refresh" + tooltipText: "Reset Scaling" + onClicked: { + var data = Settings.data.monitorsScaling || {} + data[modelData.name] = 1.0 + Settings.data.monitorsScaling = data } } } @@ -227,68 +211,56 @@ Item { } } } + } - // Night Light Section + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + + // Night Light Section + ColumnLayout { + spacing: Style.marginXS * scaling NText { text: "Night Light" font.pointSize: Style.fontSizeXXL * scaling font.weight: Style.fontWeightBold - color: Color.mOnSurface - Layout.topMargin: Style.marginXL * scaling + color: Color.mSecondary } NText { text: "Reduce blue light emission to help you sleep better and reduce eye strain." - font.pointSize: Style.fontSize * scaling + font.pointSize: Style.fontSizeM * scaling color: Color.mOnSurfaceVariant wrapMode: Text.WordWrap Layout.fillWidth: true Layout.preferredWidth: parent.width - (Style.marginL * 2 * scaling) } + } - NToggle { - label: "Enable Night Light" - description: "Apply a warm color filter to reduce blue light emission." - checked: Settings.data.nightLight.enabled - onToggled: checked => { - Settings.data.nightLight.enabled = checked - } + NToggle { + label: "Enable Night Light" + description: "Apply a warm color filter to reduce blue light emission." + checked: Settings.data.nightLight.enabled + onToggled: checked => Settings.data.nightLight.enabled = checked + } + + NToggle { + label: "Auto Schedule" + description: "Automatically enable night light based on time schedule." + checked: Settings.data.nightLight.autoSchedule + onToggled: checked => Settings.data.nightLight.autoSchedule = checked + } + + // Intensity settings + ColumnLayout { + NLabel { + label: "Intensity" + description: "Higher values create warmer light." } - - NToggle { - label: "Auto Schedule" - description: "Automatically enable night light based on time schedule." - checked: Settings.data.nightLight.autoSchedule - enabled: Settings.data.nightLight.enabled - onToggled: checked => Settings.data.nightLight.autoSchedule = checked - } - - // Intensity settings - ColumnLayout { - spacing: Style.marginXS * scaling - - NText { - text: "Intensity" - font.pointSize: Style.fontSizeM * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - enabled: Settings.data.nightLight.enabled - } - - NText { - text: "Higher values create warmer (more orange) light, lower values create cooler (more blue) light." - font.pointSize: Style.fontSizeS * scaling - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap - Layout.fillWidth: true - enabled: Settings.data.nightLight.enabled - } - } - RowLayout { spacing: Style.marginS * scaling - Layout.fillWidth: true - enabled: Settings.data.nightLight.enabled NSlider { from: 0 @@ -307,66 +279,58 @@ Item { horizontalAlignment: Text.AlignRight } } + } - // Schedule settings - ColumnLayout { - spacing: Style.marginXS * scaling + // Schedule settings + ColumnLayout { + spacing: Style.marginXS * scaling - NText { - text: "Schedule" - font.pointSize: Style.fontSizeM * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - enabled: Settings.data.nightLight.enabled - } - - RowLayout { - spacing: Style.marginL * scaling - Layout.fillWidth: true - enabled: Settings.data.nightLight.enabled - - ColumnLayout { - spacing: Style.marginXXS * scaling - Layout.fillWidth: true - - NText { - text: "Start Time" - font.pointSize: Style.fontSizeS * scaling - color: Color.mOnSurfaceVariant - } - - NComboBox { - model: timeOptions - currentKey: Settings.data.nightLight.startTime - placeholder: "Select start time" - onSelected: key => Settings.data.nightLight.startTime = key - } - } - - ColumnLayout { - spacing: Style.marginXXS * scaling - Layout.fillWidth: true - - NText { - text: "Stop Time" - font.pointSize: Style.fontSizeS * scaling - color: Color.mOnSurfaceVariant - } - - NComboBox { - model: timeOptions - currentKey: Settings.data.nightLight.stopTime - placeholder: "Select stop time" - onSelected: key => Settings.data.nightLight.stopTime = key - } - } - } + NLabel { + label: "Schedule" + description: "Set a start and end time for automatic schedule." } - Item { - Layout.fillHeight: true + RowLayout { + Layout.fillWidth: false + spacing: Style.marginM * scaling + + NText { + text: "Start Time" + font.pointSize: Style.fontSizeM * scaling + color: Color.mOnSurfaceVariant + } + + NComboBox { + model: timeOptions + currentKey: Settings.data.nightLight.startTime + placeholder: "Select start time" + onSelected: key => Settings.data.nightLight.startTime = key + preferredWidth: 120 * scaling + } + + Item {// add a little more spacing + } + + NText { + text: "Stop Time" + font.pointSize: Style.fontSizeM * scaling + color: Color.mOnSurfaceVariant + } + NComboBox { + model: timeOptions + currentKey: Settings.data.nightLight.stopTime + placeholder: "Select stop time" + onSelected: key => Settings.data.nightLight.stopTime = key + preferredWidth: 120 * scaling + } } } } + + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling } } diff --git a/Modules/SettingsPanel/Tabs/GeneralTab.qml b/Modules/SettingsPanel/Tabs/GeneralTab.qml index c05adc8..9112d74 100644 --- a/Modules/SettingsPanel/Tabs/GeneralTab.qml +++ b/Modules/SettingsPanel/Tabs/GeneralTab.qml @@ -6,251 +6,201 @@ import qs.Services import qs.Widgets ColumnLayout { - id: root - - spacing: 0 - - ScrollView { - id: scrollView + // Profile section + RowLayout { Layout.fillWidth: true - Layout.fillHeight: true - padding: Style.marginM * scaling - clip: true - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - ScrollBar.vertical.policy: ScrollBar.AsNeeded + spacing: Style.marginL * scaling + + // Avatar preview + NImageCircled { + width: 64 * scaling + height: 64 * scaling + imagePath: Settings.data.general.avatarImage + fallbackIcon: "person" + borderColor: Color.mPrimary + borderWidth: Math.max(1, Style.borderM * scaling) + } + + NTextInput { + label: "Profile Picture" + description: "Your profile picture displayed in various places throughout the shell." + text: Settings.data.general.avatarImage + placeholderText: "/home/user/.face" + Layout.fillWidth: true + onEditingFinished: { + Settings.data.general.avatarImage = text + } + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + + // User Interface + ColumnLayout { + spacing: Style.marginL * scaling + Layout.fillWidth: true + + NText { + text: "User Interface" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mSecondary + Layout.bottomMargin: Style.marginS * scaling + } + + NToggle { + label: "Show Corners" + description: "Display rounded corners on the edge of the screen." + checked: Settings.data.general.showScreenCorners + onToggled: checked => { + Settings.data.general.showScreenCorners = checked + } + } + + NToggle { + label: "Dim Desktop" + description: "Dim the desktop when panels or menus are open." + checked: Settings.data.general.dimDesktop + onToggled: checked => { + Settings.data.general.dimDesktop = checked + } + } + + NToggle { + label: "Auto-hide Dock" + description: "Automatically hide the dock when not in use." + checked: Settings.data.dock.autoHide + onToggled: checked => { + Settings.data.dock.autoHide = checked + } + } ColumnLayout { - width: scrollView.availableWidth - spacing: 0 + spacing: Style.marginXXS * scaling + Layout.fillWidth: true - Item { - Layout.fillWidth: true - Layout.preferredHeight: 0 + NLabel { + label: "Border radius" + description: "Adjust the rounded border of all UI elements." } - ColumnLayout { - spacing: Style.marginL * scaling - Layout.fillWidth: true - - NText { - text: "General Settings" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - } - - // Profile section - ColumnLayout { - spacing: Style.marginS * scaling + RowLayout { + NSlider { Layout.fillWidth: true - Layout.topMargin: Style.marginS * scaling - - RowLayout { - Layout.fillWidth: true - spacing: Style.marginL * scaling - - // Avatar preview - NImageCircled { - width: 64 * scaling - height: 64 * scaling - imagePath: Settings.data.general.avatarImage - fallbackIcon: "person" - borderColor: Color.mPrimary - borderWidth: Math.max(1, Style.borderM * scaling) - } - - NTextInput { - label: "Profile Picture" - description: "Your profile picture displayed in various places throughout the shell." - text: Settings.data.general.avatarImage - placeholderText: "/home/user/.face" - Layout.fillWidth: true - onEditingFinished: { - Settings.data.general.avatarImage = text - } - } - } - } - } - - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginXL * scaling - Layout.bottomMargin: Style.marginXL * scaling - } - - ColumnLayout { - spacing: Style.marginL * scaling - Layout.fillWidth: true - - NText { - text: "User Interface" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - Layout.bottomMargin: Style.marginS * scaling - } - - NToggle { - label: "Show Corners" - description: "Display rounded corners on the edge of the screen." - checked: Settings.data.general.showScreenCorners - onToggled: checked => { - Settings.data.general.showScreenCorners = checked - } - } - - NToggle { - label: "Dim Desktop" - description: "Dim the desktop when panels or menus are open." - checked: Settings.data.general.dimDesktop - onToggled: checked => { - Settings.data.general.dimDesktop = checked - } - } - - NToggle { - label: "Auto-hide Dock" - description: "Automatically hide the dock when not in use." - checked: Settings.data.dock.autoHide - onToggled: checked => { - Settings.data.dock.autoHide = checked - } - } - - ColumnLayout { - spacing: Style.marginXXS * scaling - Layout.fillWidth: true - - NText { - text: "Border radius" - font.pointSize: Style.fontSizeL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - } - - NText { - text: "Adjust the rounded border of all UI elements" - font.pointSize: Style.fontSizeXS * scaling - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap - Layout.fillWidth: true - } - - RowLayout { - NSlider { - Layout.fillWidth: true - from: 0 - to: 1 - stepSize: 0.01 - value: Settings.data.general.radiusRatio - onMoved: Settings.data.general.radiusRatio = value - cutoutColor: Color.mSurface - } - - NText { - text: Math.floor(Settings.data.general.radiusRatio * 100) + "%" - Layout.alignment: Qt.AlignVCenter - Layout.leftMargin: Style.marginS * scaling - color: Color.mOnSurface - } - } - } - - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginXL * scaling - Layout.bottomMargin: Style.marginL * scaling - } - - // Animation Speed - ColumnLayout { - spacing: Style.marginXXS * scaling - Layout.fillWidth: true - - NText { - text: "Animation Speed" - font.pointSize: Style.fontSizeL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - } - - NText { - text: "Adjust global animation speed (0.1× to 2.0×)" - font.pointSize: Style.fontSizeXS * scaling - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap - Layout.fillWidth: true - } - - RowLayout { - NSlider { - Layout.fillWidth: true - from: 0.1 - to: 2.0 - stepSize: 0.01 - value: Settings.data.general.animationSpeed - onMoved: Settings.data.general.animationSpeed = value - cutoutColor: Color.mSurface - } - - NText { - text: Math.round(Settings.data.general.animationSpeed * 100) + "%" - Layout.alignment: Qt.AlignVCenter - Layout.leftMargin: Style.marginS * scaling - color: Color.mOnSurface - } - } + from: 0 + to: 1 + stepSize: 0.01 + value: Settings.data.general.radiusRatio + onMoved: Settings.data.general.radiusRatio = value + cutoutColor: Color.mSurface } NText { - text: "Fonts" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold + text: Math.floor(Settings.data.general.radiusRatio * 100) + "%" + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: Style.marginS * scaling color: Color.mOnSurface - Layout.bottomMargin: Style.marginS * scaling + } + } + } + + // Animation Speed + ColumnLayout { + spacing: Style.marginXXS * scaling + Layout.fillWidth: true + + NLabel { + label: "Animation Speed" + description: "Adjust global animation speed." + } + + RowLayout { + NSlider { + Layout.fillWidth: true + from: 0.1 + to: 2.0 + stepSize: 0.01 + value: Settings.data.general.animationSpeed + onMoved: Settings.data.general.animationSpeed = value + cutoutColor: Color.mSurface } - // Font configuration section - ColumnLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true - - NTextInput { - label: "Default Font" - description: "Main font used throughout the interface." - text: Settings.data.ui.fontDefault - placeholderText: "Roboto" - Layout.fillWidth: true - onEditingFinished: { - Settings.data.ui.fontDefault = text - } - } - - NTextInput { - label: "Fixed Width Font" - description: "Monospace font used for terminal and code display." - text: Settings.data.ui.fontFixed - placeholderText: "DejaVu Sans Mono" - Layout.fillWidth: true - onEditingFinished: { - Settings.data.ui.fontFixed = text - } - } - - NTextInput { - label: "Billboard Font" - description: "Large font used for clocks and prominent displays." - text: Settings.data.ui.fontBillboard - placeholderText: "Inter" - Layout.fillWidth: true - onEditingFinished: { - Settings.data.ui.fontBillboard = text - } - } + NText { + text: Math.round(Settings.data.general.animationSpeed * 100) + "%" + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: Style.marginS * scaling + color: Color.mOnSurface } } } } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + + // Fonts + ColumnLayout { + spacing: Style.marginL * scaling + Layout.fillWidth: true + NText { + text: "Fonts" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mSecondary + Layout.bottomMargin: Style.marginS * scaling + } + + // Font configuration section + ColumnLayout { + spacing: Style.marginL * scaling + Layout.fillWidth: true + + NTextInput { + label: "Default Font" + description: "Main font used throughout the interface." + text: Settings.data.ui.fontDefault + placeholderText: "Roboto" + Layout.fillWidth: true + onEditingFinished: { + Settings.data.ui.fontDefault = text + } + } + + NTextInput { + label: "Fixed Width Font" + description: "Monospace font used for terminal and code display." + text: Settings.data.ui.fontFixed + placeholderText: "DejaVu Sans Mono" + Layout.fillWidth: true + onEditingFinished: { + Settings.data.ui.fontFixed = text + } + } + + NTextInput { + label: "Billboard Font" + description: "Large font used for clocks and prominent displays." + text: Settings.data.ui.fontBillboard + placeholderText: "Inter" + Layout.fillWidth: true + onEditingFinished: { + Settings.data.ui.fontBillboard = text + } + } + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } } diff --git a/Modules/SettingsPanel/Tabs/LauncherTab.qml b/Modules/SettingsPanel/Tabs/LauncherTab.qml index d2e6f36..c003e2c 100644 --- a/Modules/SettingsPanel/Tabs/LauncherTab.qml +++ b/Modules/SettingsPanel/Tabs/LauncherTab.qml @@ -6,138 +6,104 @@ import qs.Services import qs.Widgets ColumnLayout { - id: root - spacing: 0 + ColumnLayout { + spacing: Style.marginL * scaling - ScrollView { - id: scrollView + NComboBox { + id: launcherPosition + label: "Position" + description: "Choose where the Launcher panel appears." + Layout.fillWidth: true + model: ListModel { + ListElement { + key: "center" + name: "Center (default)" + } + ListElement { + key: "top_left" + name: "Top Left" + } + ListElement { + key: "top_right" + name: "Top Right" + } + ListElement { + key: "bottom_left" + name: "Bottom Left" + } + ListElement { + key: "bottom_right" + name: "Bottom Right" + } + ListElement { + key: "bottom_center" + name: "Bottom Center" + } + ListElement { + key: "top_center" + name: "Top Center" + } + } + currentKey: Settings.data.appLauncher.position + onSelected: function (key) { + Settings.data.appLauncher.position = key + } + } - Layout.fillWidth: true - Layout.fillHeight: true - padding: Style.marginM * scaling - clip: true - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - ScrollBar.vertical.policy: ScrollBar.AsNeeded + NToggle { + label: "Enable Clipboard History" + description: "Show clipboard history in the launcher." + checked: Settings.data.appLauncher.enableClipboardHistory + onToggled: checked => { + Settings.data.appLauncher.enableClipboardHistory = checked + } + } ColumnLayout { - width: scrollView.availableWidth - spacing: 0 + spacing: Style.marginXXS * scaling + Layout.fillWidth: true - Item { - Layout.fillWidth: true - Layout.preferredHeight: 0 + NText { + text: "Background Opacity" + font.pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface } - ColumnLayout { - spacing: Style.marginL * scaling + NText { + text: "Adjust the background opacity of the launcher." + font.pointSize: Style.fontSizeXS * scaling + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap Layout.fillWidth: true + } - NText { - text: "Launcher" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - } - - NToggle { - label: "Enable Clipboard History" - description: "Show clipboard history in the Launcher (command >clip)." - checked: Settings.data.appLauncher.enableClipboardHistory - onToggled: checked => { - Settings.data.appLauncher.enableClipboardHistory = checked - } - } - - NDivider { + RowLayout { + NSlider { + id: launcherBgOpacity Layout.fillWidth: true - Layout.topMargin: Style.marginL * scaling - Layout.bottomMargin: Style.marginS * scaling + from: 0.0 + to: 1.0 + stepSize: 0.01 + value: Settings.data.appLauncher.backgroundOpacity + onMoved: Settings.data.appLauncher.backgroundOpacity = value + cutoutColor: Color.mSurface } NText { - text: "Launcher Position" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold + text: Math.floor(Settings.data.appLauncher.backgroundOpacity * 100) + "%" + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: Style.marginS * scaling color: Color.mOnSurface - Layout.bottomMargin: Style.marginS * scaling - } - - NComboBox { - id: launcherPosition - label: "Position" - description: "Choose where the Launcher panel appears." - Layout.fillWidth: true - model: ListModel { - ListElement { - key: "center" - name: "Center (default)" - } - ListElement { - key: "top_left" - name: "Top Left" - } - ListElement { - key: "top_right" - name: "Top Right" - } - ListElement { - key: "bottom_left" - name: "Bottom Left" - } - ListElement { - key: "bottom_right" - name: "Bottom Right" - } - ListElement { - key: "bottom_center" - name: "Bottom Center" - } - ListElement { - key: "top_center" - name: "Top Center" - } - } - currentKey: Settings.data.appLauncher.position - onSelected: function (key) { - Settings.data.appLauncher.position = key - } - } - - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginL * scaling - Layout.bottomMargin: Style.marginS * scaling - } - - NText { - text: "Launcher Background" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - Layout.bottomMargin: Style.marginS * scaling - } - - RowLayout { - NSlider { - id: launcherBgOpacity - Layout.fillWidth: true - from: 0.0 - to: 1.0 - stepSize: 0.01 - value: Settings.data.appLauncher.backgroundOpacity - onMoved: Settings.data.appLauncher.backgroundOpacity = value - cutoutColor: Color.mSurface - } - - NText { - text: Math.floor(Settings.data.appLauncher.backgroundOpacity * 100) + "%" - Layout.alignment: Qt.AlignVCenter - Layout.leftMargin: Style.marginS * scaling - color: Color.mOnSurface - } } } } } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } } diff --git a/Modules/SettingsPanel/Tabs/NetworkTab.qml b/Modules/SettingsPanel/Tabs/NetworkTab.qml index 7029ecb..24d35c9 100644 --- a/Modules/SettingsPanel/Tabs/NetworkTab.qml +++ b/Modules/SettingsPanel/Tabs/NetworkTab.qml @@ -8,69 +8,41 @@ import qs.Services import qs.Widgets ColumnLayout { - id: root - spacing: 0 + spacing: Style.marginL * scaling - ScrollView { - id: scrollView + NToggle { + label: "WiFi Enabled" + description: "Enable WiFi connectivity." + checked: Settings.data.network.wifiEnabled + onToggled: checked => { + Settings.data.network.wifiEnabled = checked + NetworkService.setWifiEnabled(checked) + if (checked) { + ToastService.showNotice("WiFi", "Enabled") + } else { + ToastService.showNotice("WiFi", "Disabled") + } + } + } + + NToggle { + label: "Bluetooth Enabled" + description: "Enable Bluetooth connectivity." + checked: Settings.data.network.bluetoothEnabled + onToggled: checked => { + Settings.data.network.bluetoothEnabled = checked + BluetoothService.setBluetoothEnabled(checked) + if (checked) { + ToastService.showNotice("Bluetooth", "Enabled") + } else { + ToastService.showNotice("Bluetooth", "Disabled") + } + } + } + + NDivider { Layout.fillWidth: true - Layout.fillHeight: true - padding: Style.marginM * scaling - clip: true - - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - ScrollBar.vertical.policy: ScrollBar.AsNeeded - - ColumnLayout { - width: scrollView.availableWidth - spacing: 0 - - Item { - Layout.fillWidth: true - Layout.preferredHeight: 0 - } - - ColumnLayout { - spacing: Style.marginL * scaling - Layout.fillWidth: true - - NText { - text: "Interfaces" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - } - - NToggle { - label: "WiFi Enabled" - description: "Enable WiFi connectivity." - checked: Settings.data.network.wifiEnabled - onToggled: checked => { - Settings.data.network.wifiEnabled = checked - NetworkService.setWifiEnabled(checked) - if (checked) { - ToastService.showNotice("WiFi", "Enabled") - } else { - ToastService.showNotice("WiFi", "Disabled") - } - } - } - - NToggle { - label: "Bluetooth Enabled" - description: "Enable Bluetooth connectivity." - checked: Settings.data.network.bluetoothEnabled - onToggled: checked => { - Settings.data.network.bluetoothEnabled = checked - BluetoothService.setBluetoothEnabled(checked) - if (checked) { - ToastService.showNotice("Bluetooth", "Enabled") - } else { - ToastService.showNotice("Bluetooth", "Disabled") - } - } - } - } - } + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling } } diff --git a/Modules/SettingsPanel/Tabs/ScreenRecorderTab.qml b/Modules/SettingsPanel/Tabs/ScreenRecorderTab.qml index de91242..0b439f1 100644 --- a/Modules/SettingsPanel/Tabs/ScreenRecorderTab.qml +++ b/Modules/SettingsPanel/Tabs/ScreenRecorderTab.qml @@ -6,297 +6,268 @@ import qs.Services import qs.Widgets ColumnLayout { - id: root - - spacing: 0 - - ScrollView { - id: scrollView + spacing: Style.marginL * scaling + // Output Directory + ColumnLayout { + spacing: Style.marginS * scaling Layout.fillWidth: true - Layout.fillHeight: true - padding: Style.marginM * scaling - clip: true - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - ScrollBar.vertical.policy: ScrollBar.AsNeeded + Layout.topMargin: Style.marginS * scaling + + NTextInput { + label: "Output Directory" + description: "Directory where screen recordings will be saved." + placeholderText: "/home/xxx/Videos" + text: Settings.data.screenRecorder.directory + onEditingFinished: { + Settings.data.screenRecorder.directory = text + } + Layout.fillWidth: true + } ColumnLayout { - width: scrollView.availableWidth - spacing: 0 - - Item { - Layout.fillWidth: true - Layout.preferredHeight: 0 - } - - ColumnLayout { - spacing: Style.marginXS * scaling - Layout.fillWidth: true - - NText { - text: "Recordings" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - Layout.bottomMargin: Style.marginS * scaling - } - - // Output Directory - ColumnLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true - Layout.topMargin: Style.marginS * scaling - - NTextInput { - label: "Output Directory" - description: "Directory where screen recordings will be saved." - placeholderText: "/home/xxx/Videos" - text: Settings.data.screenRecorder.directory - onEditingFinished: { - Settings.data.screenRecorder.directory = text - } - } - - ColumnLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true - Layout.topMargin: Style.marginM * scaling - // Show Cursor - NToggle { - label: "Show Cursor" - description: "Record mouse cursor in the video." - checked: Settings.data.screenRecorder.showCursor - onToggled: checked => { - Settings.data.screenRecorder.showCursor = checked - } - } - } - } - - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginL * 2 * scaling - Layout.bottomMargin: Style.marginL * scaling - } - - // Video Settings - ColumnLayout { - spacing: Style.marginL * scaling - Layout.fillWidth: true - - NText { - text: "Video Settings" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - Layout.bottomMargin: Style.marginS * scaling - } - - // Source - NComboBox { - label: "Video Source" - description: "We recommend using portal, if you get artifacts try screen." - model: ListModel { - ListElement { - key: "portal" - name: "Portal" - } - ListElement { - key: "screen" - name: "Screen" - } - } - currentKey: Settings.data.screenRecorder.videoSource - onSelected: key => { - Settings.data.screenRecorder.videoSource = key - } - } - - // Frame Rate - NComboBox { - label: "Frame Rate" - description: "Target frame rate for screen recordings. (default: 60)" - model: ListModel { - ListElement { - key: "30" - name: "30 FPS" - } - ListElement { - key: "60" - name: "60 FPS" - } - ListElement { - key: "100" - name: "100 FPS" - } - ListElement { - key: "120" - name: "120 FPS" - } - ListElement { - key: "144" - name: "144 FPS" - } - ListElement { - key: "165" - name: "165 FPS" - } - ListElement { - key: "240" - name: "240 FPS" - } - } - currentKey: Settings.data.screenRecorder.frameRate - onSelected: key => { - Settings.data.screenRecorder.frameRate = key - } - } - - // Video Quality - NComboBox { - label: "Video Quality" - description: "Higher quality results in larger file sizes." - model: ListModel { - ListElement { - key: "medium" - name: "Medium" - } - ListElement { - key: "high" - name: "High" - } - ListElement { - key: "very_high" - name: "Very High" - } - ListElement { - key: "ultra" - name: "Ultra" - } - } - currentKey: Settings.data.screenRecorder.quality - onSelected: key => { - Settings.data.screenRecorder.quality = key - } - } - - // Video Codec - NComboBox { - label: "Video Codec" - description: "Different codecs offer different compression and compatibility." - model: ListModel { - ListElement { - key: "h264" - name: "H264" - } - ListElement { - key: "hevc" - name: "HEVC" - } - ListElement { - key: "av1" - name: "AV1" - } - ListElement { - key: "vp8" - name: "VP8" - } - ListElement { - key: "vp9" - name: "VP9" - } - } - currentKey: Settings.data.screenRecorder.videoCodec - onSelected: key => { - Settings.data.screenRecorder.videoCodec = key - } - } - - // Color Range - NComboBox { - label: "Color Range" - description: "Limited is recommended for better compatibility." - model: ListModel { - ListElement { - key: "limited" - name: "Limited" - } - ListElement { - key: "full" - name: "Full" - } - } - currentKey: Settings.data.screenRecorder.colorRange - onSelected: key => { - Settings.data.screenRecorder.colorRange = key - } - } - } - - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginL * 2 * scaling - Layout.bottomMargin: Style.marginL * scaling - } - - // Audio Settings - ColumnLayout { - spacing: Style.marginL * scaling - Layout.fillWidth: true - - NText { - text: "Audio Settings" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - Layout.bottomMargin: Style.marginS * scaling - } - - // Audio Source - NComboBox { - label: "Audio Source" - description: "Audio source to capture during recording." - model: ListModel { - ListElement { - key: "default_output" - name: "System Output" - } - ListElement { - key: "default_input" - name: "Microphone Input" - } - ListElement { - key: "both" - name: "System Output + Microphone Input" - } - } - currentKey: Settings.data.screenRecorder.audioSource - onSelected: key => { - Settings.data.screenRecorder.audioSource = key - } - } - - // Audio Codec - NComboBox { - label: "Audio Codec" - description: "Opus is recommended for best performance and smallest audio size." - model: ListModel { - ListElement { - key: "opus" - name: "Opus" - } - ListElement { - key: "aac" - name: "AAC" - } - } - currentKey: Settings.data.screenRecorder.audioCodec - onSelected: key => { - Settings.data.screenRecorder.audioCodec = key - } - } - } + spacing: Style.marginS * scaling + Layout.fillWidth: true + Layout.topMargin: Style.marginM * scaling + // Show Cursor + NToggle { + label: "Show Cursor" + description: "Record mouse cursor in the video." + checked: Settings.data.screenRecorder.showCursor + onToggled: checked => { + Settings.data.screenRecorder.showCursor = checked + } } } } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + + // Video Settings + ColumnLayout { + spacing: Style.marginL * scaling + Layout.fillWidth: true + + NText { + text: "Video Settings" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mSecondary + Layout.bottomMargin: Style.marginS * scaling + } + + // Source + NComboBox { + label: "Video Source" + description: "We recommend using portal, if you get artifacts try screen." + model: ListModel { + ListElement { + key: "portal" + name: "Portal" + } + ListElement { + key: "screen" + name: "Screen" + } + } + currentKey: Settings.data.screenRecorder.videoSource + onSelected: key => { + Settings.data.screenRecorder.videoSource = key + } + } + + // Frame Rate + NComboBox { + label: "Frame Rate" + description: "Target frame rate for screen recordings." + model: ListModel { + ListElement { + key: "30" + name: "30 FPS" + } + ListElement { + key: "60" + name: "60 FPS" + } + ListElement { + key: "100" + name: "100 FPS" + } + ListElement { + key: "120" + name: "120 FPS" + } + ListElement { + key: "144" + name: "144 FPS" + } + ListElement { + key: "165" + name: "165 FPS" + } + ListElement { + key: "240" + name: "240 FPS" + } + } + currentKey: Settings.data.screenRecorder.frameRate + onSelected: key => { + Settings.data.screenRecorder.frameRate = key + } + } + + // Video Quality + NComboBox { + label: "Video Quality" + description: "Higher quality results in larger file sizes." + model: ListModel { + ListElement { + key: "medium" + name: "Medium" + } + ListElement { + key: "high" + name: "High" + } + ListElement { + key: "very_high" + name: "Very High" + } + ListElement { + key: "ultra" + name: "Ultra" + } + } + currentKey: Settings.data.screenRecorder.quality + onSelected: key => { + Settings.data.screenRecorder.quality = key + } + } + + // Video Codec + NComboBox { + label: "Video Codec" + description: "Different codecs offer different compression and compatibility." + model: ListModel { + ListElement { + key: "h264" + name: "H264" + } + ListElement { + key: "hevc" + name: "HEVC" + } + ListElement { + key: "av1" + name: "AV1" + } + ListElement { + key: "vp8" + name: "VP8" + } + ListElement { + key: "vp9" + name: "VP9" + } + } + currentKey: Settings.data.screenRecorder.videoCodec + onSelected: key => { + Settings.data.screenRecorder.videoCodec = key + } + } + + // Color Range + NComboBox { + label: "Color Range" + description: "Limited is recommended for better compatibility." + model: ListModel { + ListElement { + key: "limited" + name: "Limited" + } + ListElement { + key: "full" + name: "Full" + } + } + currentKey: Settings.data.screenRecorder.colorRange + onSelected: key => { + Settings.data.screenRecorder.colorRange = key + } + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginL * 2 * scaling + Layout.bottomMargin: Style.marginL * scaling + } + + // Audio Settings + ColumnLayout { + spacing: Style.marginL * scaling + Layout.fillWidth: true + + NText { + text: "Audio Settings" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mSecondary + Layout.bottomMargin: Style.marginS * scaling + } + + // Audio Source + NComboBox { + label: "Audio Source" + description: "Audio source to capture during recording." + model: ListModel { + ListElement { + key: "default_output" + name: "System Output" + } + ListElement { + key: "default_input" + name: "Microphone Input" + } + ListElement { + key: "both" + name: "System Output + Microphone Input" + } + } + currentKey: Settings.data.screenRecorder.audioSource + onSelected: key => { + Settings.data.screenRecorder.audioSource = key + } + } + + // Audio Codec + NComboBox { + label: "Audio Codec" + description: "Opus is recommended for best performance and smallest audio size." + model: ListModel { + ListElement { + key: "opus" + name: "Opus" + } + ListElement { + key: "aac" + name: "AAC" + } + } + currentKey: Settings.data.screenRecorder.audioCodec + onSelected: key => { + Settings.data.screenRecorder.audioCodec = key + } + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } } diff --git a/Modules/SettingsPanel/Tabs/TimeWeatherTab.qml b/Modules/SettingsPanel/Tabs/TimeWeatherTab.qml index 4e7cd52..d02ed79 100644 --- a/Modules/SettingsPanel/Tabs/TimeWeatherTab.qml +++ b/Modules/SettingsPanel/Tabs/TimeWeatherTab.qml @@ -6,136 +6,97 @@ import qs.Services import qs.Widgets ColumnLayout { - id: root - - spacing: 0 - - ScrollView { - id: scrollView + // Location section + NTextInput { + label: "Location name" + description: "Choose a known location near you." + text: Settings.data.location.name + placeholderText: "Enter the location name" Layout.fillWidth: true - Layout.fillHeight: true - padding: Style.marginM * scaling - clip: true - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - ScrollBar.vertical.policy: ScrollBar.AsNeeded - - ColumnLayout { - width: scrollView.availableWidth - spacing: 0 - - Item { - Layout.fillWidth: true - Layout.preferredHeight: 0 - } - - ColumnLayout { - spacing: Style.marginXS * scaling - Layout.fillWidth: true - - NText { - text: "Location" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - Layout.bottomMargin: Style.marginS * scaling - } - - // Location section - ColumnLayout { - spacing: Style.marginM * scaling - Layout.fillWidth: true - Layout.topMargin: Style.marginS * scaling - - NTextInput { - label: "Location name" - description: "Choose a known location near you." - text: Settings.data.location.name - placeholderText: "Enter the location name" - Layout.fillWidth: true - onEditingFinished: { - Settings.data.location.name = text.trim() - LocationService.resetWeather() - } - } - } - - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginL * 2 * scaling - Layout.bottomMargin: Style.marginL * scaling - } - - // Time section - ColumnLayout { - spacing: Style.marginL * scaling - Layout.fillWidth: true - - NText { - text: "Time Format" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - Layout.bottomMargin: Style.marginS * scaling - } - - NToggle { - label: "Use 12-Hour Clock" - description: "Display time in 12-hour format (AM/PM) instead of 24-hour." - checked: Settings.data.location.use12HourClock - onToggled: checked => { - Settings.data.location.use12HourClock = checked - } - } - - NToggle { - label: "Reverse Day/Month" - description: "Display date as DD/MM instead of MM/DD." - checked: Settings.data.location.reverseDayMonth - onToggled: checked => { - Settings.data.location.reverseDayMonth = checked - } - } - - NToggle { - label: "Show Date with Clock" - description: "Display date alongside time (e.g., 18:12 - Sat, 23 Aug)." - checked: Settings.data.location.showDateWithClock - onToggled: checked => { - Settings.data.location.showDateWithClock = checked - } - } - } - - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginL * 2 * scaling - Layout.bottomMargin: Style.marginL * scaling - } - - // Weather section - ColumnLayout { - spacing: Style.marginM * scaling - Layout.fillWidth: true - - NText { - text: "Weather" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - Layout.bottomMargin: Style.marginS * scaling - } - - NToggle { - label: "Use Fahrenheit" - description: "Display temperature in Fahrenheit instead of Celsius." - checked: Settings.data.location.useFahrenheit - onToggled: checked => { - Settings.data.location.useFahrenheit = checked - } - } - } - } + onEditingFinished: { + Settings.data.location.name = text.trim() + LocationService.resetWeather() } } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + + // Time section + ColumnLayout { + spacing: Style.marginL * scaling + Layout.fillWidth: true + + NText { + text: "Time Format" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mSecondary + } + + NToggle { + label: "Use 12-Hour Clock" + description: "Display time in 12-hour format (AM/PM) instead of 24-hour." + checked: Settings.data.location.use12HourClock + onToggled: checked => { + Settings.data.location.use12HourClock = checked + } + } + + NToggle { + label: "Reverse Day/Month" + description: "Display date as DD/MM instead of MM/DD." + checked: Settings.data.location.reverseDayMonth + onToggled: checked => { + Settings.data.location.reverseDayMonth = checked + } + } + + NToggle { + label: "Show Date with Clock" + description: "Display date alongside time (e.g., 18:12 - Sat, 23 Aug)." + checked: Settings.data.location.showDateWithClock + onToggled: checked => { + Settings.data.location.showDateWithClock = checked + } + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + + // Weather section + ColumnLayout { + spacing: Style.marginM * scaling + Layout.fillWidth: true + + NText { + text: "Weather" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mSecondary + } + + NToggle { + label: "Use Fahrenheit" + description: "Display temperature in Fahrenheit instead of Celsius." + checked: Settings.data.location.useFahrenheit + onToggled: checked => { + Settings.data.location.useFahrenheit = checked + } + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } } diff --git a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml index 4768109..0dd49ba 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml @@ -6,241 +6,229 @@ import qs.Commons import qs.Services import qs.Widgets -Item { +ColumnLayout { readonly property real scaling: ScalingService.scale(screen) readonly property string tabIcon: "photo_library" readonly property string tabLabel: "Wallpaper Selector" readonly property int tabIndex: 7 - Layout.fillWidth: true - Layout.fillHeight: true - ScrollView { - anchors.fill: parent - clip: true - ScrollBar.vertical.policy: ScrollBar.AsNeeded - ScrollBar.horizontal.policy: ScrollBar.AsNeeded - contentWidth: parent.width + spacing: Style.marginL * scaling + + // Current wallpaper display + NText { + text: "Current Wallpaper" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mSecondary + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 140 * scaling + radius: Style.radiusM * scaling + color: Color.mPrimary + + NImageRounded { + id: currentWallpaperImage + anchors.fill: parent + anchors.margins: Style.marginXS * scaling + imagePath: WallpaperService.currentWallpaper + fallbackIcon: "image" + imageRadius: Style.radiusM * scaling + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + + // Wallpaper selector + RowLayout { + Layout.fillWidth: true ColumnLayout { - width: parent.width - ColumnLayout { - spacing: Style.marginL * scaling - Layout.margins: Style.marginL * scaling + Layout.fillWidth: true + + // Wallpaper grid + NText { + text: "Wallpaper Selector" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mSecondary + } + + NText { + text: "Click on a wallpaper to set it as your current wallpaper." + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap Layout.fillWidth: true + } - // Current wallpaper display - NText { - text: "Current Wallpaper" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface + NText { + text: Settings.data.wallpaper.swww.enabled ? "Wallpapers will change with " + Settings.data.wallpaper.swww.transitionType + + " transition." : "Wallpapers will change instantly." + color: Color.mOnSurface + font.pointSize: Style.fontSizeXS * scaling + visible: Settings.data.wallpaper.swww.enabled + } + } + + NIconButton { + icon: "refresh" + tooltipText: "Refresh wallpaper list" + onClicked: { + WallpaperService.listWallpapers() + } + Layout.alignment: Qt.AlignTop | Qt.AlignRight + } + } + + // Wallpaper grid container + Item { + Layout.fillWidth: true + Layout.preferredHeight: { + return Math.ceil(WallpaperService.wallpaperList.length / wallpaperGridView.columns) * wallpaperGridView.cellHeight + } + + GridView { + id: wallpaperGridView + anchors.fill: parent + clip: true + model: WallpaperService.wallpaperList + + boundsBehavior: Flickable.StopAtBounds + flickableDirection: Flickable.AutoFlickDirection + interactive: false + + property int columns: 5 + property int itemSize: Math.floor((width - leftMargin - rightMargin - (4 * Style.marginS * scaling)) / columns) + + cellWidth: Math.floor((width - leftMargin - rightMargin) / columns) + cellHeight: Math.floor(itemSize * 0.67) + Style.marginS * scaling + + leftMargin: Style.marginS * scaling + rightMargin: Style.marginS * scaling + topMargin: Style.marginS * scaling + bottomMargin: Style.marginS * scaling + + delegate: Rectangle { + id: wallpaperItem + + property string wallpaperPath: modelData + property bool isSelected: wallpaperPath === WallpaperService.currentWallpaper + + width: wallpaperGridView.itemSize + height: Math.floor(wallpaperGridView.itemSize * 0.67) + color: Color.transparent + + // NImageCached relies on the image being visible to work properly. + // MultiEffect relies on the image being invisible to apply effects. + // That's why we don't have rounded corners here, as we don't want to bring back qt5compat. + NImageCached { + id: img + imagePath: wallpaperPath + anchors.fill: parent } + // Borders on top Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 140 * scaling - radius: Style.radiusM * scaling - color: Color.mPrimary + anchors.fill: parent + color: Color.transparent + border.color: isSelected ? Color.mSecondary : Color.mSurface + border.width: Math.max(1, Style.borderL * 1.5 * scaling) + } - NImageRounded { - id: currentWallpaperImage - anchors.fill: parent - anchors.margins: Style.marginXS * scaling - imagePath: WallpaperService.currentWallpaper - fallbackIcon: "image" - imageRadius: Style.radiusM * scaling + // Selection tick-mark + Rectangle { + anchors.top: parent.top + anchors.right: parent.right + anchors.margins: Style.marginS * scaling + width: 28 * scaling + height: 28 * scaling + radius: width / 2 + color: Color.mSecondary + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + visible: isSelected + + NIcon { + text: "check" + font.pointSize: Style.fontSizeM * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSecondary + anchors.centerIn: parent } } - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginL * scaling - Layout.bottomMargin: Style.marginL * scaling - } + // Hover effect + Rectangle { + anchors.fill: parent + color: Color.mSurface + opacity: (mouseArea.containsMouse || isSelected) ? 0 : 0.4 + radius: parent.radius - RowLayout { - Layout.fillWidth: true - - ColumnLayout { - Layout.fillWidth: true - - // Wallpaper grid - NText { - text: "Wallpaper Selector" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast } - - NText { - text: "Click on a wallpaper to set it as your current wallpaper." - color: Color.mOnSurface - wrapMode: Text.WordWrap - Layout.fillWidth: true - } - - NText { - text: Settings.data.wallpaper.swww.enabled ? "Wallpapers will change with " + Settings.data.wallpaper.swww.transitionType - + " transition." : "Wallpapers will change instantly." - color: Color.mOnSurface - font.pointSize: Style.fontSizeXS * scaling - visible: Settings.data.wallpaper.swww.enabled - } - } - - NIconButton { - icon: "refresh" - tooltipText: "Refresh wallpaper list" - onClicked: { - WallpaperService.listWallpapers() - } - Layout.alignment: Qt.AlignTop | Qt.AlignRight } } - // Wallpaper grid container - Item { - Layout.fillWidth: true - Layout.preferredHeight: { - return Math.ceil( - WallpaperService.wallpaperList.length / wallpaperGridView.columns) * wallpaperGridView.cellHeight - } - - GridView { - id: wallpaperGridView - anchors.fill: parent - clip: true - model: WallpaperService.wallpaperList - - boundsBehavior: Flickable.StopAtBounds - flickableDirection: Flickable.AutoFlickDirection - interactive: false - - property int columns: 5 - property int itemSize: Math.floor( - (width - leftMargin - rightMargin - (4 * Style.marginS * scaling)) / columns) - - cellWidth: Math.floor((width - leftMargin - rightMargin) / columns) - cellHeight: Math.floor(itemSize * 0.67) + Style.marginS * scaling - - leftMargin: Style.marginS * scaling - rightMargin: Style.marginS * scaling - topMargin: Style.marginS * scaling - bottomMargin: Style.marginS * scaling - - delegate: Rectangle { - id: wallpaperItem - - property string wallpaperPath: modelData - property bool isSelected: wallpaperPath === WallpaperService.currentWallpaper - - width: wallpaperGridView.itemSize - height: Math.floor(wallpaperGridView.itemSize * 0.67) - color: Color.transparent - - // NImageCached relies on the image being visible to work properly. - // MultiEffect relies on the image being invisible to apply effects. - // That's why we don't have rounded corners here, as we don't want to bring back qt5compat. - NImageCached { - id: img - imagePath: wallpaperPath - anchors.fill: parent - } - - // Borders on top - Rectangle { - anchors.fill: parent - color: Color.transparent - border.color: isSelected ? Color.mPrimary : Color.mSurface - border.width: Math.max(1, Style.borderL * scaling) - } - - // Selection tick-mark - Rectangle { - anchors.top: parent.top - anchors.right: parent.right - anchors.margins: Style.marginXS * scaling - width: 28 * scaling - height: 28 * scaling - radius: width / 2 - color: Color.mPrimary - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - visible: isSelected - - NIcon { - text: "check" - font.pointSize: Style.fontSizeM * scaling - font.weight: Style.fontWeightBold - color: Color.mOnPrimary - anchors.centerIn: parent - } - } - - // Hover effect - Rectangle { - anchors.fill: parent - color: Color.mOnSurface - opacity: mouseArea.containsMouse ? 0.1 : 0 - radius: parent.radius - - Behavior on opacity { - NumberAnimation { - duration: Style.animationFast - } - } - } - - MouseArea { - id: mouseArea - anchors.fill: parent - acceptedButtons: Qt.LeftButton - hoverEnabled: true - onClicked: { - WallpaperService.changeWallpaper(wallpaperPath) - } - } - } - } - - // Empty state - Rectangle { - anchors.fill: parent - color: Color.mSurface - radius: Style.radiusM * scaling - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - visible: WallpaperService.wallpaperList.length === 0 && !WallpaperService.scanning - - ColumnLayout { - anchors.centerIn: parent - spacing: Style.marginM * scaling - - NIcon { - text: "folder_open" - font.pointSize: Style.fontSizeL * scaling - color: Color.mOnSurface - Layout.alignment: Qt.AlignHCenter - } - - NText { - text: "No wallpapers found" - color: Color.mOnSurface - font.weight: Style.fontWeightBold - Layout.alignment: Qt.AlignHCenter - } - - NText { - text: "Make sure your wallpaper directory is configured and contains image files." - color: Color.mOnSurface - wrapMode: Text.WordWrap - horizontalAlignment: Text.AlignHCenter - Layout.preferredWidth: Style.sliderWidth * 1.5 * scaling - } - } + MouseArea { + id: mouseArea + anchors.fill: parent + acceptedButtons: Qt.LeftButton + hoverEnabled: true + onClicked: { + WallpaperService.changeWallpaper(wallpaperPath) } } } } + + // Empty state + Rectangle { + anchors.fill: parent + color: Color.mSurface + radius: Style.radiusM * scaling + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + visible: WallpaperService.wallpaperList.length === 0 && !WallpaperService.scanning + + ColumnLayout { + anchors.centerIn: parent + spacing: Style.marginM * scaling + + NIcon { + text: "folder_open" + font.pointSize: Style.fontSizeL * scaling + color: Color.mOnSurface + Layout.alignment: Qt.AlignHCenter + } + + NText { + text: "No wallpapers found" + color: Color.mOnSurface + font.weight: Style.fontWeightBold + Layout.alignment: Qt.AlignHCenter + } + + NText { + text: "Make sure your wallpaper directory is configured and contains image files." + color: Color.mOnSurface + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter + Layout.preferredWidth: Style.sliderWidth * 1.5 * scaling + } + } + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling } } diff --git a/Modules/SettingsPanel/Tabs/WallpaperTab.qml b/Modules/SettingsPanel/Tabs/WallpaperTab.qml index 2b92718..e6140de 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperTab.qml @@ -7,554 +7,6 @@ import qs.Services import qs.Widgets ColumnLayout { - id: root - - spacing: 0 - - ScrollView { - id: scrollView - - Layout.fillWidth: true - Layout.fillHeight: true - padding: Style.marginM * scaling - clip: true - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - ScrollBar.vertical.policy: ScrollBar.AsNeeded - - ColumnLayout { - width: scrollView.availableWidth - spacing: 0 - - Item { - Layout.fillWidth: true - Layout.preferredHeight: 0 - } - - ColumnLayout { - spacing: Style.marginL * scaling - Layout.fillWidth: true - - NText { - text: "Directory" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - Layout.bottomMargin: Style.marginS * scaling - } - - // Wallpaper Settings Category - ColumnLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true - Layout.topMargin: Style.marginS * scaling - - // Wallpaper Folder - ColumnLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true - - NTextInput { - label: "Wallpaper Directory" - description: "Path to your wallpaper directory." - text: Settings.data.wallpaper.directory - Layout.fillWidth: true - onEditingFinished: { - Settings.data.wallpaper.directory = text - } - } - } - } - } - - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginL * 2 * scaling - Layout.bottomMargin: Style.marginL * scaling - } - - ColumnLayout { - spacing: Style.marginL * scaling - Layout.fillWidth: true - - NText { - text: "Automation" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - Layout.bottomMargin: Style.marginS * scaling - } - - // Random Wallpaper - NToggle { - label: "Random Wallpaper" - description: "Automatically select random wallpapers from the folder." - checked: Settings.data.wallpaper.isRandom - onToggled: checked => { - Settings.data.wallpaper.isRandom = checked - } - } - - // Interval (slider + H:M inputs) - ColumnLayout { - RowLayout { - NLabel { - label: "Wallpaper Interval" - description: "How often to change wallpapers automatically." - Layout.fillWidth: true - } - - NText { - // Show friendly H:MM format from current settings - text: { - const s = Settings.data.wallpaper.randomInterval - const h = Math.floor(s / 3600) - const m = Math.floor((s % 3600) / 60) - return (h > 0 ? (h + "h ") : "") + (m > 0 ? (m + "m") : (h === 0 ? "0m" : "")) - } - Layout.alignment: Qt.AlignBottom | Qt.AlignRight - } - } - - // Preset chips - RowLayout { - id: presetRow - spacing: Style.marginS * scaling - - // Preset seconds list - property var presets: [15 * 60, 30 * 60, 45 * 60, 60 * 60, 90 * 60, 120 * 60] - // Whether current interval equals one of the presets - property bool isCurrentPreset: presets.indexOf(Settings.data.wallpaper.randomInterval) !== -1 - // 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.randomInterval = sec - WallpaperService.restartRandomWallpaperTimer() - // Hide custom when selecting a preset - customForcedVisible = false - } - - // Helper to color selected chip - function isSelected(sec) { - return Settings.data.wallpaper.randomInterval === sec - } - - // 15m - Rectangle { - radius: height * 0.5 - color: presetRow.isSelected(15 * 60) ? Color.mPrimary : Color.mSurfaceVariant - implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling) - implicitWidth: label15.implicitWidth + Style.marginM * 1.5 * scaling - border.width: 1 - border.color: presetRow.isSelected(15 * 60) ? Color.transparent : Color.mOutline - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: presetRow.setIntervalSeconds(15 * 60) - } - NText { - id: label15 - anchors.centerIn: parent - text: "15m" - font.pointSize: Style.fontSizeS * scaling - font.weight: Style.fontWeightMedium - color: presetRow.isSelected(15 * 60) ? Color.mOnPrimary : Color.mOnSurface - } - } - - // 30m - Rectangle { - radius: height * 0.5 - color: presetRow.isSelected(30 * 60) ? Color.mPrimary : Color.mSurfaceVariant - implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling) - implicitWidth: label30.implicitWidth + Style.marginM * 1.5 * scaling - border.width: 1 - border.color: presetRow.isSelected(30 * 60) ? Color.transparent : Color.mOutline - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: presetRow.setIntervalSeconds(30 * 60) - } - NText { - id: label30 - anchors.centerIn: parent - text: "30m" - font.pointSize: Style.fontSizeS * scaling - font.weight: Style.fontWeightMedium - color: presetRow.isSelected(30 * 60) ? Color.mOnPrimary : Color.mOnSurface - } - } - - // 45m - Rectangle { - radius: height * 0.5 - color: presetRow.isSelected(45 * 60) ? Color.mPrimary : Color.mSurfaceVariant - implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling) - implicitWidth: label45.implicitWidth + Style.marginM * 1.5 * scaling - border.width: 1 - border.color: presetRow.isSelected(45 * 60) ? Color.transparent : Color.mOutline - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: presetRow.setIntervalSeconds(45 * 60) - } - NText { - id: label45 - anchors.centerIn: parent - text: "45m" - font.pointSize: Style.fontSizeS * scaling - font.weight: Style.fontWeightMedium - color: presetRow.isSelected(45 * 60) ? Color.mOnPrimary : Color.mOnSurface - } - } - - // 1h - Rectangle { - radius: height * 0.5 - color: presetRow.isSelected(60 * 60) ? Color.mPrimary : Color.mSurfaceVariant - implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling) - implicitWidth: label1h.implicitWidth + Style.marginM * 1.5 * scaling - border.width: 1 - border.color: presetRow.isSelected(60 * 60) ? Color.transparent : Color.mOutline - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: presetRow.setIntervalSeconds(60 * 60) - } - NText { - id: label1h - anchors.centerIn: parent - text: "1h" - font.pointSize: Style.fontSizeS * scaling - font.weight: Style.fontWeightMedium - color: presetRow.isSelected(60 * 60) ? Color.mOnPrimary : Color.mOnSurface - } - } - - // 1h 30m - Rectangle { - radius: height * 0.5 - color: presetRow.isSelected(90 * 60) ? Color.mPrimary : Color.mSurfaceVariant - implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling) - implicitWidth: label90.implicitWidth + Style.marginM * 1.5 * scaling - border.width: 1 - border.color: presetRow.isSelected(90 * 60) ? Color.transparent : Color.mOutline - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: presetRow.setIntervalSeconds(90 * 60) - } - NText { - id: label90 - anchors.centerIn: parent - text: "1h 30m" - font.pointSize: Style.fontSizeS * scaling - font.weight: Style.fontWeightMedium - color: presetRow.isSelected(90 * 60) ? Color.mOnPrimary : Color.mOnSurface - } - } - - // 2h - Rectangle { - radius: height * 0.5 - color: presetRow.isSelected(120 * 60) ? Color.mPrimary : Color.mSurfaceVariant - implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling) - implicitWidth: label2h.implicitWidth + Style.marginM * 1.5 * scaling - border.width: 1 - border.color: presetRow.isSelected(120 * 60) ? Color.transparent : Color.mOutline - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: presetRow.setIntervalSeconds(120 * 60) - } - NText { - id: label2h - anchors.centerIn: parent - text: "2h" - font.pointSize: Style.fontSizeS * scaling - font.weight: Style.fontWeightMedium - color: presetRow.isSelected(120 * 60) ? Color.mOnPrimary : Color.mOnSurface - } - } - - // Custom… opens inline input - Rectangle { - radius: height * 0.5 - color: customRow.visible ? Color.mPrimary : Color.mSurfaceVariant - implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling) - implicitWidth: labelCustom.implicitWidth + Style.marginM * 1.5 * scaling - border.width: 1 - border.color: customRow.visible ? Color.transparent : Color.mOutline - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: presetRow.customForcedVisible = !presetRow.customForcedVisible - } - NText { - id: labelCustom - anchors.centerIn: parent - text: customRow.visible ? "Custom" : "Custom…" - font.pointSize: Style.fontSizeS * scaling - font.weight: Style.fontWeightMedium - color: customRow.visible ? Color.mOnPrimary : Color.mOnSurface - } - } - } - - // Custom HH:MM inline input - RowLayout { - id: customRow - visible: presetRow.customForcedVisible || !presetRow.isCurrentPreset - spacing: Style.marginS * scaling - - NTextInput { - label: "Custom Interval" - description: "Enter time as HH:MM (e.g., 1:30)" - text: { - const s = Settings.data.wallpaper.randomInterval - const h = Math.floor(s / 3600) - const m = Math.floor((s % 3600) / 60) - return h + ":" + (m < 10 ? ("0" + m) : m) - } - Layout.fillWidth: true - 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.randomInterval = (h * 3600) + (min * 60) - WallpaperService.restartRandomWallpaperTimer() - // Keep custom visible after manual entry - presetRow.customForcedVisible = true - } - } - } - } - } - } - - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginL * 2 * scaling - Layout.bottomMargin: Style.marginL * scaling - } - - // ------------------------------- - // SWWW - ColumnLayout { - spacing: Style.marginL * scaling - Layout.fillWidth: true - - NText { - text: "SWWW" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - Layout.bottomMargin: Style.marginS * scaling - } - - // Use SWWW - NToggle { - label: "Use SWWW" - description: "Use SWWW daemon for advanced wallpaper management." - checked: Settings.data.wallpaper.swww.enabled - onToggled: checked => { - if (checked) { - // Check if swww is installed - swwwCheck.running = true - } else { - Settings.data.wallpaper.swww.enabled = false - ToastService.showNotice("SWWW", "Disabled") - } - } - } - - // SWWW Settings (only visible when useSWWW is enabled) - ColumnLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true - Layout.topMargin: Style.marginS * scaling - visible: Settings.data.wallpaper.swww.enabled - - // Resize Mode - NComboBox { - label: "Resize Mode" - description: "How SWWW should resize wallpapers to fit the screen." - model: ListModel { - ListElement { - key: "no" - name: "No" - } - ListElement { - key: "crop" - name: "Crop" - } - ListElement { - key: "fit" - name: "Fit" - } - ListElement { - key: "stretch" - name: "Stretch" - } - } - currentKey: Settings.data.wallpaper.swww.resizeMethod - onSelected: key => { - Settings.data.wallpaper.swww.resizeMethod = key - } - } - - // Transition Type - NComboBox { - label: "Transition Type" - description: "Animation type when switching between wallpapers." - model: ListModel { - ListElement { - key: "none" - name: "None" - } - ListElement { - key: "simple" - name: "Simple" - } - ListElement { - key: "fade" - name: "Fade" - } - ListElement { - key: "left" - name: "Left" - } - ListElement { - key: "right" - name: "Right" - } - ListElement { - key: "top" - name: "Top" - } - ListElement { - key: "bottom" - name: "Bottom" - } - ListElement { - key: "wipe" - name: "Wipe" - } - ListElement { - key: "wave" - name: "Wave" - } - ListElement { - key: "grow" - name: "Grow" - } - ListElement { - key: "center" - name: "Center" - } - ListElement { - key: "any" - name: "Any" - } - ListElement { - key: "outer" - name: "Outer" - } - ListElement { - key: "random" - name: "Random" - } - } - currentKey: Settings.data.wallpaper.swww.transitionType - onSelected: key => { - Settings.data.wallpaper.swww.transitionType = key - } - } - - // Transition FPS - ColumnLayout { - RowLayout { - Layout.fillWidth: true - - ColumnLayout { - NText { - text: "Transition FPS" - font.weight: Style.fontWeightBold - color: Color.mOnSurface - } - NText { - text: "Frames per second for transition animations." - font.pointSize: Style.fontSizeXS * scaling - color: Color.mOnSurface - wrapMode: Text.WordWrap - Layout.fillWidth: true - } - } - - NText { - text: sliderWpTransitionFps.value + " FPS" - Layout.alignment: Qt.AlignBottom | Qt.AlignRight - } - } - - NSlider { - id: sliderWpTransitionFps - Layout.fillWidth: true - from: 30 - to: 500 - stepSize: 5 - value: Settings.data.wallpaper.swww.transitionFps - onMoved: Settings.data.wallpaper.swww.transitionFps = Math.round(value) - cutoutColor: Color.mSurface - } - } - - // Transition Duration - ColumnLayout { - RowLayout { - Layout.fillWidth: true - - ColumnLayout { - NText { - text: "Transition Duration" - font.weight: Style.fontWeightBold - color: Color.mOnSurface - } - NText { - text: "Duration of transition animations in seconds." - font.pointSize: Style.fontSizeXS * scaling - color: Color.mOnSurface - wrapMode: Text.WordWrap - Layout.fillWidth: true - } - } - - NText { - text: sliderWpTransitionDuration.value.toFixed(2) + "s" - Layout.alignment: Qt.AlignBottom | Qt.AlignRight - } - } - - NSlider { - id: sliderWpTransitionDuration - Layout.fillWidth: true - from: 0.25 - to: 10 - stepSize: 0.05 - value: Settings.data.wallpaper.swww.transitionDuration - onMoved: Settings.data.wallpaper.swww.transitionDuration = value - cutoutColor: Color.mSurface - } - } - } - } - } - } // Process to check if swww is installed Process { @@ -577,4 +29,481 @@ ColumnLayout { stdout: StdioCollector {} stderr: StdioCollector {} } + + NTextInput { + label: "Wallpaper Directory" + description: "Path to your wallpaper directory." + text: Settings.data.wallpaper.directory + Layout.fillWidth: true + onEditingFinished: { + Settings.data.wallpaper.directory = text + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + + ColumnLayout { + spacing: Style.marginL * scaling + Layout.fillWidth: true + + NText { + text: "Automation" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mSecondary + } + + // Random Wallpaper + NToggle { + label: "Random Wallpaper" + description: "Automatically select random wallpapers from the folder." + checked: Settings.data.wallpaper.isRandom + onToggled: checked => { + Settings.data.wallpaper.isRandom = checked + } + } + + // Interval (slider + H:M inputs) + ColumnLayout { + RowLayout { + NLabel { + label: "Wallpaper Interval" + description: "How often to change wallpapers automatically." + Layout.fillWidth: true + } + + NText { + // Show friendly H:MM format from current settings + text: { + const s = Settings.data.wallpaper.randomInterval + const h = Math.floor(s / 3600) + const m = Math.floor((s % 3600) / 60) + return (h > 0 ? (h + "h ") : "") + (m > 0 ? (m + "m") : (h === 0 ? "0m" : "")) + } + Layout.alignment: Qt.AlignBottom | Qt.AlignRight + } + } + + // Preset chips + RowLayout { + id: presetRow + spacing: Style.marginS * scaling + + // Preset seconds list + property var presets: [15 * 60, 30 * 60, 45 * 60, 60 * 60, 90 * 60, 120 * 60] + // Whether current interval equals one of the presets + property bool isCurrentPreset: presets.indexOf(Settings.data.wallpaper.randomInterval) !== -1 + // 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.randomInterval = sec + WallpaperService.restartRandomWallpaperTimer() + // Hide custom when selecting a preset + customForcedVisible = false + } + + // Helper to color selected chip + function isSelected(sec) { + return Settings.data.wallpaper.randomInterval === sec + } + + // 15m + Rectangle { + radius: height * 0.5 + color: presetRow.isSelected(15 * 60) ? Color.mPrimary : Color.mSurfaceVariant + implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling) + implicitWidth: label15.implicitWidth + Style.marginM * 1.5 * scaling + border.width: 1 + border.color: presetRow.isSelected(15 * 60) ? Color.transparent : Color.mOutline + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: presetRow.setIntervalSeconds(15 * 60) + } + NText { + id: label15 + anchors.centerIn: parent + text: "15m" + font.pointSize: Style.fontSizeS * scaling + font.weight: Style.fontWeightMedium + color: presetRow.isSelected(15 * 60) ? Color.mOnPrimary : Color.mOnSurface + } + } + + // 30m + Rectangle { + radius: height * 0.5 + color: presetRow.isSelected(30 * 60) ? Color.mPrimary : Color.mSurfaceVariant + implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling) + implicitWidth: label30.implicitWidth + Style.marginM * 1.5 * scaling + border.width: 1 + border.color: presetRow.isSelected(30 * 60) ? Color.transparent : Color.mOutline + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: presetRow.setIntervalSeconds(30 * 60) + } + NText { + id: label30 + anchors.centerIn: parent + text: "30m" + font.pointSize: Style.fontSizeS * scaling + font.weight: Style.fontWeightMedium + color: presetRow.isSelected(30 * 60) ? Color.mOnPrimary : Color.mOnSurface + } + } + + // 45m + Rectangle { + radius: height * 0.5 + color: presetRow.isSelected(45 * 60) ? Color.mPrimary : Color.mSurfaceVariant + implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling) + implicitWidth: label45.implicitWidth + Style.marginM * 1.5 * scaling + border.width: 1 + border.color: presetRow.isSelected(45 * 60) ? Color.transparent : Color.mOutline + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: presetRow.setIntervalSeconds(45 * 60) + } + NText { + id: label45 + anchors.centerIn: parent + text: "45m" + font.pointSize: Style.fontSizeS * scaling + font.weight: Style.fontWeightMedium + color: presetRow.isSelected(45 * 60) ? Color.mOnPrimary : Color.mOnSurface + } + } + + // 1h + Rectangle { + radius: height * 0.5 + color: presetRow.isSelected(60 * 60) ? Color.mPrimary : Color.mSurfaceVariant + implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling) + implicitWidth: label1h.implicitWidth + Style.marginM * 1.5 * scaling + border.width: 1 + border.color: presetRow.isSelected(60 * 60) ? Color.transparent : Color.mOutline + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: presetRow.setIntervalSeconds(60 * 60) + } + NText { + id: label1h + anchors.centerIn: parent + text: "1h" + font.pointSize: Style.fontSizeS * scaling + font.weight: Style.fontWeightMedium + color: presetRow.isSelected(60 * 60) ? Color.mOnPrimary : Color.mOnSurface + } + } + + // 1h 30m + Rectangle { + radius: height * 0.5 + color: presetRow.isSelected(90 * 60) ? Color.mPrimary : Color.mSurfaceVariant + implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling) + implicitWidth: label90.implicitWidth + Style.marginM * 1.5 * scaling + border.width: 1 + border.color: presetRow.isSelected(90 * 60) ? Color.transparent : Color.mOutline + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: presetRow.setIntervalSeconds(90 * 60) + } + NText { + id: label90 + anchors.centerIn: parent + text: "1h 30m" + font.pointSize: Style.fontSizeS * scaling + font.weight: Style.fontWeightMedium + color: presetRow.isSelected(90 * 60) ? Color.mOnPrimary : Color.mOnSurface + } + } + + // 2h + Rectangle { + radius: height * 0.5 + color: presetRow.isSelected(120 * 60) ? Color.mPrimary : Color.mSurfaceVariant + implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling) + implicitWidth: label2h.implicitWidth + Style.marginM * 1.5 * scaling + border.width: 1 + border.color: presetRow.isSelected(120 * 60) ? Color.transparent : Color.mOutline + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: presetRow.setIntervalSeconds(120 * 60) + } + NText { + id: label2h + anchors.centerIn: parent + text: "2h" + font.pointSize: Style.fontSizeS * scaling + font.weight: Style.fontWeightMedium + color: presetRow.isSelected(120 * 60) ? Color.mOnPrimary : Color.mOnSurface + } + } + + // Custom… opens inline input + Rectangle { + radius: height * 0.5 + color: customRow.visible ? Color.mPrimary : Color.mSurfaceVariant + implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling) + implicitWidth: labelCustom.implicitWidth + Style.marginM * 1.5 * scaling + border.width: 1 + border.color: customRow.visible ? Color.transparent : Color.mOutline + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: presetRow.customForcedVisible = !presetRow.customForcedVisible + } + NText { + id: labelCustom + anchors.centerIn: parent + text: customRow.visible ? "Custom" : "Custom…" + font.pointSize: Style.fontSizeS * scaling + font.weight: Style.fontWeightMedium + color: customRow.visible ? Color.mOnPrimary : Color.mOnSurface + } + } + } + + // Custom HH:MM inline input + RowLayout { + id: customRow + visible: presetRow.customForcedVisible || !presetRow.isCurrentPreset + spacing: Style.marginS * scaling + Layout.topMargin: Style.marginS * scaling + + NTextInput { + label: "Custom Interval" + description: "Enter time as HH:MM (e.g., 1:30)." + text: { + const s = Settings.data.wallpaper.randomInterval + const h = Math.floor(s / 3600) + const m = Math.floor((s % 3600) / 60) + return h + ":" + (m < 10 ? ("0" + m) : m) + } + Layout.fillWidth: true + 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.randomInterval = (h * 3600) + (min * 60) + WallpaperService.restartRandomWallpaperTimer() + // Keep custom visible after manual entry + presetRow.customForcedVisible = true + } + } + } + } + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + + // ------------------------------- + // Swww + ColumnLayout { + spacing: Style.marginL * scaling + Layout.fillWidth: true + + NText { + text: "Swww" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mSecondary + } + + // Use SWWW + NToggle { + label: "Use Swww" + description: "Use Swww daemon for advanced wallpaper management." + checked: Settings.data.wallpaper.swww.enabled + onToggled: checked => { + if (checked) { + // Check if swww is installed + swwwCheck.running = true + } else { + Settings.data.wallpaper.swww.enabled = false + ToastService.showNotice("Swww", "Disabled") + } + } + } + + // SWWW Settings (only visible when useSWWW is enabled) + ColumnLayout { + spacing: Style.marginS * scaling + Layout.fillWidth: true + Layout.topMargin: Style.marginS * scaling + visible: Settings.data.wallpaper.swww.enabled + + // Resize Mode + NComboBox { + label: "Resize Mode" + description: "How Swww should resize wallpapers to fit the screen." + model: ListModel { + ListElement { + key: "no" + name: "No" + } + ListElement { + key: "crop" + name: "Crop" + } + ListElement { + key: "fit" + name: "Fit" + } + ListElement { + key: "stretch" + name: "Stretch" + } + } + currentKey: Settings.data.wallpaper.swww.resizeMethod + onSelected: key => { + Settings.data.wallpaper.swww.resizeMethod = key + } + } + + // Transition Type + NComboBox { + label: "Transition Type" + description: "Animation type when switching between wallpapers." + model: ListModel { + ListElement { + key: "none" + name: "None" + } + ListElement { + key: "simple" + name: "Simple" + } + ListElement { + key: "fade" + name: "Fade" + } + ListElement { + key: "left" + name: "Left" + } + ListElement { + key: "right" + name: "Right" + } + ListElement { + key: "top" + name: "Top" + } + ListElement { + key: "bottom" + name: "Bottom" + } + ListElement { + key: "wipe" + name: "Wipe" + } + ListElement { + key: "wave" + name: "Wave" + } + ListElement { + key: "grow" + name: "Grow" + } + ListElement { + key: "center" + name: "Center" + } + ListElement { + key: "any" + name: "Any" + } + ListElement { + key: "outer" + name: "Outer" + } + ListElement { + key: "random" + name: "Random" + } + } + currentKey: Settings.data.wallpaper.swww.transitionType + onSelected: key => { + Settings.data.wallpaper.swww.transitionType = key + } + } + + // Transition FPS + ColumnLayout { + NLabel { + label: "Transition FPS" + description: "Frames per second for transition animations." + } + + RowLayout { + spacing: Style.marginL * scaling + NSlider { + Layout.fillWidth: true + from: 30 + to: 500 + stepSize: 5 + value: Settings.data.wallpaper.swww.transitionFps + onMoved: Settings.data.wallpaper.swww.transitionFps = Math.round(value) + cutoutColor: Color.mSurface + } + NText { + text: Settings.data.wallpaper.swww.transitionFps + " FPS" + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + } + } + } + + // Transition Duration + ColumnLayout { + NLabel { + label: "Transition Duration" + description: "Duration of transition animations in seconds." + } + + RowLayout { + spacing: Style.marginL * scaling + NSlider { + Layout.fillWidth: true + from: 0.25 + to: 10 + stepSize: 0.05 + value: Settings.data.wallpaper.swww.transitionDuration + onMoved: Settings.data.wallpaper.swww.transitionDuration = value + cutoutColor: Color.mSurface + } + NText { + text: Settings.data.wallpaper.swww.transitionDuration.toFixed(2) + "s" + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + } + } + } + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } } diff --git a/Widgets/NComboBox.qml b/Widgets/NComboBox.qml index d2fdb8d..a51f690 100644 --- a/Widgets/NComboBox.qml +++ b/Widgets/NComboBox.qml @@ -5,10 +5,11 @@ import qs.Commons import qs.Services import qs.Widgets -ColumnLayout { +RowLayout { id: root - readonly property real preferredHeight: Style.baseWidgetSize * 1.35 * scaling + readonly property real preferredHeight: Style.baseWidgetSize * 1.1 * scaling + property real preferredWidth: 320 * scaling property string label: "" property string description: "" @@ -23,11 +24,6 @@ ColumnLayout { spacing: Style.marginS * scaling Layout.fillWidth: true - NLabel { - label: root.label - description: root.description - } - function findIndexByKey(key) { for (var i = 0; i < root.model.count; i++) { if (root.model.get(i).key === key) { @@ -37,10 +33,15 @@ ColumnLayout { return -1 } + NLabel { + label: root.label + description: root.description + } + ComboBox { id: combo - Layout.preferredWidth: 320 * scaling + Layout.preferredWidth: root.preferredWidth Layout.preferredHeight: height model: model currentIndex: findIndexByKey(currentKey) diff --git a/Widgets/NLabel.qml b/Widgets/NLabel.qml index 78990a1..cf05b8f 100644 --- a/Widgets/NLabel.qml +++ b/Widgets/NLabel.qml @@ -11,7 +11,7 @@ ColumnLayout { NText { text: label - font.pointSize: Style.fontSizeM * scaling + font.pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold color: Color.mOnSurface visible: label !== "" diff --git a/Widgets/NSectionEditor.qml b/Widgets/NSectionEditor.qml index 2af1a23..d64a270 100644 --- a/Widgets/NSectionEditor.qml +++ b/Widgets/NSectionEditor.qml @@ -10,7 +10,6 @@ NBox { property string sectionName: "" property var widgetModel: [] property var availableWidgets: [] - property var scrollView: null signal addWidget(string widgetName, string section) signal removeWidget(string section, int index) @@ -23,7 +22,7 @@ NBox { if (widgetCount === 0) return 140 * scaling - var availableWidth = scrollView ? scrollView.availableWidth - (Style.marginM * scaling * 2) : 400 * scaling + var availableWidth = parent.width var avgWidgetWidth = 150 * scaling var widgetsPerRow = Math.max(1, Math.floor(availableWidth / avgWidgetWidth)) var rows = Math.ceil(widgetCount / widgetsPerRow) @@ -52,7 +51,7 @@ NBox { ColumnLayout { anchors.fill: parent - anchors.margins: Style.marginM * scaling + anchors.margins: Style.marginL * scaling spacing: Style.marginM * scaling RowLayout { @@ -189,13 +188,13 @@ NBox { return } - Logger.log("NSectionEditor", `Started dragging widget: ${modelData} at index ${index}`) + //Logger.log("NSectionEditor", `Started dragging widget: ${modelData} at index ${index}`) // Bring to front when starting drag widgetItem.z = 1000 } onReleased: { - Logger.log("NSectionEditor", `Released widget: ${modelData} at index ${index}`) + //Logger.log("NSectionEditor", `Released widget: ${modelData} at index ${index}`) // Reset z-index when drag ends widgetItem.z = 0 @@ -232,13 +231,13 @@ NBox { if (targetIndex !== -1 && targetIndex !== index) { const fromIndex = index const toIndex = targetIndex - Logger.log( - "NSectionEditor", - `Dropped widget from index ${fromIndex} to position ${toIndex} (distance: ${minDistance.toFixed( - 2)})`) + // Logger.log( + // "NSectionEditor", + // `Dropped widget from index ${fromIndex} to position ${toIndex} (distance: ${minDistance.toFixed( + // 2)})`) reorderWidget(sectionName.toLowerCase(), fromIndex, toIndex) } else { - Logger.log("NSectionEditor", `No valid drop target found for widget at index ${index}`) + Logger.warn("NSectionEditor", `No valid drop target found for widget at index ${index}`) } } } @@ -264,17 +263,16 @@ NBox { radius: Style.radiusS * scaling } - onEntered: function (drag) { - Logger.log("NSectionEditor", "Entered start drop zone") + onEntered: function (drag) {//Logger.log("NSectionEditor", "Entered start drop zone") } onDropped: function (drop) { - Logger.log("NSectionEditor", "Dropped on start zone") + //Logger.log("NSectionEditor", "Dropped on start zone") if (drop.source && drop.source.widgetIndex !== undefined) { const fromIndex = drop.source.widgetIndex const toIndex = 0 // Insert at the beginning if (fromIndex !== toIndex) { - Logger.log("NSectionEditor", `Dropped widget from index ${fromIndex} to beginning`) + //Logger.log("NSectionEditor", `Dropped widget from index ${fromIndex} to beginning`) reorderWidget(sectionName.toLowerCase(), fromIndex, toIndex) } } @@ -299,17 +297,16 @@ NBox { radius: Style.radiusS * scaling } - onEntered: function (drag) { - Logger.log("NSectionEditor", "Entered end drop zone") + onEntered: function (drag) {//Logger.log("NSectionEditor", "Entered end drop zone") } onDropped: function (drop) { - Logger.log("NSectionEditor", "Dropped on end zone") + //Logger.log("NSectionEditor", "Dropped on end zone") if (drop.source && drop.source.widgetIndex !== undefined) { const fromIndex = drop.source.widgetIndex const toIndex = widgetModel.length // Insert at the end if (fromIndex !== toIndex) { - Logger.log("NSectionEditor", `Dropped widget from index ${fromIndex} to end`) + //Logger.log("NSectionEditor", `Dropped widget from index ${fromIndex} to end`) reorderWidget(sectionName.toLowerCase(), fromIndex, toIndex) } } diff --git a/Widgets/NSpinBox.qml b/Widgets/NSpinBox.qml index f8e1007..79debec 100644 --- a/Widgets/NSpinBox.qml +++ b/Widgets/NSpinBox.qml @@ -30,26 +30,9 @@ RowLayout { Layout.fillWidth: true - ColumnLayout { - spacing: Style.marginXXS * scaling - Layout.fillWidth: true - - NText { - text: label - font.pointSize: Style.fontSizeM * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - visible: label !== "" - } - - NText { - text: description - font.pointSize: Style.fontSizeS * scaling - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap - Layout.fillWidth: true - visible: description !== "" - } + NLabel { + label: root.label + description: root.description } // Value diff --git a/Widgets/NToast.qml b/Widgets/NToast.qml index ff38263..c5fbee1 100644 --- a/Widgets/NToast.qml +++ b/Widgets/NToast.qml @@ -150,7 +150,7 @@ Item { id: labelText text: root.label color: Color.mOnSurface - font.pointSize: Style.fontSize * scaling + font.pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightBold wrapMode: Text.WordWrap width: parent.width @@ -161,7 +161,7 @@ Item { id: descriptionText text: root.description color: Color.mOnSurface - font.pointSize: Style.fontSize * scaling + font.pointSize: Style.fontSizeM * scaling wrapMode: Text.WordWrap width: parent.width visible: text.length > 0 @@ -176,7 +176,7 @@ Item { color: Color.mOnSurface - fontPointSize: Style.fontSize * scaling + fontPointSize: Style.fontSizeM * scaling sizeRatio: 0.8 Layout.alignment: Qt.AlignTop diff --git a/Widgets/NToggle.qml b/Widgets/NToggle.qml index 1eb150f..ddf1e80 100644 --- a/Widgets/NToggle.qml +++ b/Widgets/NToggle.qml @@ -19,24 +19,9 @@ RowLayout { Layout.fillWidth: true - ColumnLayout { - spacing: Style.marginXXS * scaling - Layout.fillWidth: true - - NText { - text: label - font.pointSize: Style.fontSizeM * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - } - - NText { - text: description - font.pointSize: Style.fontSizeS * scaling - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap - Layout.fillWidth: true - } + NLabel { + label: root.label + description: root.description } Rectangle {