Files
noctalia-shell/Modules/Panels/Settings/Tabs/WallpaperTab.qml
2025-11-18 19:17:25 -05:00

456 lines
15 KiB
QML

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Services.Compositor
import qs.Services.UI
import qs.Widgets
ColumnLayout {
id: root
property string specificFolderMonitorName: ""
spacing: Style.marginL
NHeader {
label: I18n.tr("settings.wallpaper.settings.section.label")
description: I18n.tr("settings.wallpaper.settings.section.description")
}
NToggle {
label: I18n.tr("settings.wallpaper.settings.enable-management.label")
description: I18n.tr("settings.wallpaper.settings.enable-management.description")
checked: Settings.data.wallpaper.enabled
onToggled: checked => Settings.data.wallpaper.enabled = checked
Layout.bottomMargin: Style.marginL
}
NToggle {
visible: Settings.data.wallpaper.enabled && CompositorService.isNiri
label: I18n.tr("settings.wallpaper.settings.enable-overview.label")
description: I18n.tr("settings.wallpaper.settings.enable-overview.description")
checked: Settings.data.wallpaper.overviewEnabled
onToggled: checked => Settings.data.wallpaper.overviewEnabled = checked
Layout.bottomMargin: Style.marginL
}
NDivider {
visible: Settings.data.wallpaper.enabled
Layout.fillWidth: true
Layout.topMargin: Style.marginL
Layout.bottomMargin: Style.marginL
}
ColumnLayout {
visible: Settings.data.wallpaper.enabled
spacing: Style.marginL
Layout.fillWidth: true
NTextInputButton {
id: wallpaperPathInput
label: I18n.tr("settings.wallpaper.settings.folder.label")
description: I18n.tr("settings.wallpaper.settings.folder.description")
text: Settings.data.wallpaper.directory
buttonIcon: "folder-open"
buttonTooltip: I18n.tr("settings.wallpaper.settings.folder.tooltip")
Layout.fillWidth: true
onInputEditingFinished: Settings.data.wallpaper.directory = text
onButtonClicked: mainFolderPicker.open()
}
RowLayout {
NLabel {
label: I18n.tr("settings.wallpaper.settings.selector.label")
description: I18n.tr("settings.wallpaper.settings.selector.description")
Layout.alignment: Qt.AlignTop
}
NIconButton {
icon: "wallpaper-selector"
tooltipText: I18n.tr("settings.wallpaper.settings.selector.tooltip")
onClicked: PanelService.getPanel("wallpaperPanel", screen)?.toggle()
}
}
// Recursive search
NToggle {
label: I18n.tr("settings.wallpaper.settings.recursive-search.label")
description: I18n.tr("settings.wallpaper.settings.recursive-search.description")
checked: Settings.data.wallpaper.recursiveSearch
onToggled: checked => Settings.data.wallpaper.recursiveSearch = checked
}
// Monitor-specific directories
NToggle {
label: I18n.tr("settings.wallpaper.settings.monitor-specific.label")
description: I18n.tr("settings.wallpaper.settings.monitor-specific.description")
checked: Settings.data.wallpaper.enableMultiMonitorDirectories
onToggled: checked => Settings.data.wallpaper.enableMultiMonitorDirectories = checked
}
// Hide wallpaper filenames
NToggle {
label: I18n.tr("settings.wallpaper.settings.hide-wallpaper-filenames.label")
description: I18n.tr("settings.wallpaper.settings.hide-wallpaper-filenames.description")
checked: Settings.data.wallpaper.hideWallpaperFilenames
onToggled: checked => Settings.data.wallpaper.hideWallpaperFilenames = checked
}
NBox {
visible: Settings.data.wallpaper.enableMultiMonitorDirectories
Layout.fillWidth: true
radius: Style.radiusM
color: Color.mSurface
border.color: Color.mOutline
border.width: Style.borderS
implicitHeight: contentCol.implicitHeight + Style.marginL * 2
clip: true
ColumnLayout {
id: contentCol
anchors.fill: parent
anchors.margins: Style.marginL
spacing: Style.marginM
Repeater {
model: Quickshell.screens || []
delegate: ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginS
NText {
text: (modelData.name || "Unknown")
color: Color.mPrimary
font.weight: Style.fontWeightBold
pointSize: Style.fontSizeM
}
NTextInputButton {
text: WallpaperService.getMonitorDirectory(modelData.name)
buttonIcon: "folder-open"
buttonTooltip: I18n.tr("settings.wallpaper.settings.monitor-specific.tooltip")
Layout.fillWidth: true
onInputEditingFinished: WallpaperService.setMonitorDirectory(modelData.name, text)
onButtonClicked: {
specificFolderMonitorName = modelData.name;
monitorFolderPicker.open();
}
}
}
}
}
}
NComboBox {
label: I18n.tr("settings.wallpaper.settings.selector-position.label")
description: I18n.tr("settings.wallpaper.settings.selector-position.description")
Layout.fillWidth: true
model: [
{
"key": "follow_bar",
"name": I18n.tr("options.launcher.position.follow_bar")
},
{
"key": "center",
"name": I18n.tr("options.launcher.position.center")
},
{
"key": "top_center",
"name": I18n.tr("options.launcher.position.top_center")
},
{
"key": "top_left",
"name": I18n.tr("options.launcher.position.top_left")
},
{
"key": "top_right",
"name": I18n.tr("options.launcher.position.top_right")
},
{
"key": "bottom_left",
"name": I18n.tr("options.launcher.position.bottom_left")
},
{
"key": "bottom_right",
"name": I18n.tr("options.launcher.position.bottom_right")
},
{
"key": "bottom_center",
"name": I18n.tr("options.launcher.position.bottom_center")
}
]
currentKey: Settings.data.wallpaper.panelPosition
onSelected: function (key) {
Settings.data.wallpaper.panelPosition = key;
}
}
}
NDivider {
visible: Settings.data.wallpaper.enabled
Layout.fillWidth: true
Layout.topMargin: Style.marginL
Layout.bottomMargin: Style.marginL
}
ColumnLayout {
visible: Settings.data.wallpaper.enabled
spacing: Style.marginL
Layout.fillWidth: true
NHeader {
label: I18n.tr("settings.wallpaper.look-feel.section.label")
}
// Fill Mode
NComboBox {
label: I18n.tr("settings.wallpaper.look-feel.fill-mode.label")
description: I18n.tr("settings.wallpaper.look-feel.fill-mode.description")
model: WallpaperService.fillModeModel
currentKey: Settings.data.wallpaper.fillMode
onSelected: key => Settings.data.wallpaper.fillMode = key
}
RowLayout {
NLabel {
label: I18n.tr("settings.wallpaper.look-feel.fill-color.label")
description: I18n.tr("settings.wallpaper.look-feel.fill-color.description")
Layout.alignment: Qt.AlignTop
}
NColorPicker {
selectedColor: Settings.data.wallpaper.fillColor
onColorSelected: color => Settings.data.wallpaper.fillColor = color
}
}
// Transition Type
NComboBox {
label: I18n.tr("settings.wallpaper.look-feel.transition-type.label")
description: I18n.tr("settings.wallpaper.look-feel.transition-type.description")
model: WallpaperService.transitionsModel
currentKey: Settings.data.wallpaper.transitionType
onSelected: key => Settings.data.wallpaper.transitionType = key
}
// Transition Duration
ColumnLayout {
NLabel {
label: I18n.tr("settings.wallpaper.look-feel.transition-duration.label")
description: I18n.tr("settings.wallpaper.look-feel.transition-duration.description")
}
NValueSlider {
Layout.fillWidth: true
from: 500
to: 10000
stepSize: 100
value: Settings.data.wallpaper.transitionDuration
onMoved: value => Settings.data.wallpaper.transitionDuration = value
text: (Settings.data.wallpaper.transitionDuration / 1000).toFixed(1) + "s"
}
}
// Edge Smoothness
ColumnLayout {
NLabel {
label: I18n.tr("settings.wallpaper.look-feel.edge-smoothness.label")
description: I18n.tr("settings.wallpaper.look-feel.edge-smoothness.description")
}
NValueSlider {
Layout.fillWidth: true
from: 0.0
to: 1.0
value: Settings.data.wallpaper.transitionEdgeSmoothness
onMoved: value => Settings.data.wallpaper.transitionEdgeSmoothness = value
text: Math.round(Settings.data.wallpaper.transitionEdgeSmoothness * 100) + "%"
}
}
}
NDivider {
visible: Settings.data.wallpaper.enabled
Layout.fillWidth: true
Layout.topMargin: Style.marginL
Layout.bottomMargin: Style.marginL
}
ColumnLayout {
visible: Settings.data.wallpaper.enabled
spacing: Style.marginL
Layout.fillWidth: true
NHeader {
label: I18n.tr("settings.wallpaper.automation.section.label")
}
// Random Wallpaper
NToggle {
label: I18n.tr("settings.wallpaper.automation.random-wallpaper.label")
description: I18n.tr("settings.wallpaper.automation.random-wallpaper.description")
checked: Settings.data.wallpaper.randomEnabled
onToggled: checked => Settings.data.wallpaper.randomEnabled = checked
}
// Interval
ColumnLayout {
visible: Settings.data.wallpaper.randomEnabled
RowLayout {
NLabel {
label: I18n.tr("settings.wallpaper.automation.interval.label")
description: I18n.tr("settings.wallpaper.automation.interval.description")
Layout.fillWidth: true
}
NText {
// Show friendly H:MM format from current settings
text: Time.formatVagueHumanReadableDuration(Settings.data.wallpaper.randomIntervalSec)
Layout.alignment: Qt.AlignBottom | Qt.AlignRight
}
}
// Preset chips using Repeater
RowLayout {
id: presetRow
spacing: Style.marginS
// Factorized presets data
property var intervalPresets: [5 * 60, 10 * 60, 15 * 60, 30 * 60, 45 * 60, 60 * 60, 90 * 60, 120 * 60]
// Whether current interval equals one of the presets
property bool isCurrentPreset: {
return intervalPresets.some(seconds => seconds === Settings.data.wallpaper.randomIntervalSec);
}
// Allow user to force open the custom input; otherwise it's auto-open when not a preset
property bool customForcedVisible: false
function setIntervalSeconds(sec) {
Settings.data.wallpaper.randomIntervalSec = sec;
WallpaperService.restartRandomWallpaperTimer();
// Hide custom when selecting a preset
customForcedVisible = false;
}
// Helper to color selected chip
function isSelected(sec) {
return Settings.data.wallpaper.randomIntervalSec === sec;
}
// Repeater for preset chips
Repeater {
model: presetRow.intervalPresets
delegate: IntervalPresetChip {
seconds: modelData
label: Time.formatVagueHumanReadableDuration(modelData)
selected: presetRow.isSelected(modelData)
onClicked: presetRow.setIntervalSeconds(modelData)
}
}
// Custom… opens inline input
IntervalPresetChip {
label: customRow.visible ? "Custom" : "Custom…"
selected: customRow.visible
onClicked: presetRow.customForcedVisible = !presetRow.customForcedVisible
}
}
// Custom HH:MM inline input
RowLayout {
id: customRow
visible: presetRow.customForcedVisible || !presetRow.isCurrentPreset
spacing: Style.marginS
Layout.topMargin: Style.marginS
NTextInput {
label: I18n.tr("settings.wallpaper.automation.custom-interval.label")
description: I18n.tr("settings.wallpaper.automation.custom-interval.description")
text: {
const s = Settings.data.wallpaper.randomIntervalSec;
const h = Math.floor(s / 3600);
const m = Math.floor((s % 3600) / 60);
return h + ":" + (m < 10 ? ("0" + m) : m);
}
onEditingFinished: {
const m = text.trim().match(/^(\d{1,2}):(\d{2})$/);
if (m) {
let h = parseInt(m[1]);
let min = parseInt(m[2]);
if (isNaN(h) || isNaN(min))
return;
h = Math.max(0, Math.min(24, h));
min = Math.max(0, Math.min(59, min));
Settings.data.wallpaper.randomIntervalSec = (h * 3600) + (min * 60);
WallpaperService.restartRandomWallpaperTimer();
// Keep custom visible after manual entry
presetRow.customForcedVisible = true;
}
}
}
}
}
}
// Reusable component for interval preset chips
component IntervalPresetChip: Rectangle {
property int seconds: 0
property string label: ""
property bool selected: false
signal clicked
radius: height * 0.5
color: selected ? Color.mPrimary : Color.mSurfaceVariant
implicitHeight: Math.max(Style.baseWidgetSize * 0.55, 24)
implicitWidth: chipLabel.implicitWidth + Style.marginM * 1.5
border.width: Style.borderS
border.color: selected ? Color.transparent : Color.mOutline
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: parent.clicked()
}
NText {
id: chipLabel
anchors.centerIn: parent
text: parent.label
pointSize: Style.fontSizeS
color: parent.selected ? Color.mOnPrimary : Color.mOnSurface
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginL
Layout.bottomMargin: Style.marginL
}
NFilePicker {
id: mainFolderPicker
selectionMode: "folders"
title: I18n.tr("settings.wallpaper.settings.select-folder")
initialPath: Settings.data.wallpaper.directory || Quickshell.env("HOME") + "/Pictures"
onAccepted: paths => {
if (paths.length > 0) {
Settings.data.wallpaper.directory = paths[0];
}
}
}
NFilePicker {
id: monitorFolderPicker
selectionMode: "folders"
title: I18n.tr("settings.wallpaper.settings.select-monitor-folder")
initialPath: WallpaperService.getMonitorDirectory(specificFolderMonitorName) || Quickshell.env("HOME") + "/Pictures"
onAccepted: paths => {
if (paths.length > 0) {
WallpaperService.setMonitorDirectory(specificFolderMonitorName, paths[0]);
}
}
}
}