added audio and apps page in settings
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 18s
Python / test (pull_request) Successful in 28s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m6s

This commit is contained in:
2026-06-23 22:32:56 +02:00
parent 2366901b24
commit 6a80961b2b
29 changed files with 2080 additions and 640 deletions
+100
View File
@@ -0,0 +1,100 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import qs.Components
import qs.Config
import qs.Helpers
import qs.Modules.SettingsNew.Common
PageBase {
id: root
isSubPage: true
title: qsTr("All apps")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
Repeater {
id: list
model: [...DesktopEntries.applications.values].sort((a, b) => a.name.localeCompare(b.name))
ConnectedRect {
id: appItem
required property int index
required property DesktopEntry modelData
Layout.fillWidth: true
first: index === 0
implicitHeight: appRow.implicitHeight + appRow.anchors.margins * 2
last: index === list.count - 1
StateLayer {
onClicked: {
root.sState.selectedApp = appItem.modelData;
root.sState.openSubPage(2);
}
}
RowLayout {
id: appRow
anchors.fill: parent
anchors.leftMargin: Appearance.padding.largeIncreased
anchors.margins: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.largeIncreased
spacing: Appearance.spacing.small
IconImage {
asynchronous: true
implicitSize: Math.round(Appearance.font.size.large * 1.8)
source: Quickshell.iconPath(appItem.modelData.icon, "image-missing")
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: appItem.modelData.name
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: (appItem.modelData.comment || appItem.modelData.genericName) ?? ""
visible: text
}
}
MaterialIcon {
color: DynamicColors.palette.m3primary
fill: 1
font.pointSize: Appearance.font.size.small
text: "favorite"
visible: Strings.testRegexList(Config.launcher.favoriteApps, appItem.modelData.id)
}
MaterialIcon {
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.medium
text: "chevron_right"
}
}
}
}
}
}
+172
View File
@@ -0,0 +1,172 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import qs.Components
import qs.Config
import qs.Helpers
import qs.Modules.SettingsNew.Common
PageBase {
id: root
readonly property DesktopEntry app: sState.selectedApp
readonly property bool favoriteByRegex: app && matchedByRegex(Config.launcher.favoriteApps, app.id)
readonly property bool hiddenByRegex: app && matchedByRegex(Config.launcher.hiddenApps, app.id)
function isRegexEntry(s: string): bool {
return /^\^.*\$$/.test(s);
}
function matchedByRegex(filterList: list<string>, id: string): bool {
return filterList.some(f => isRegexEntry(f) && new RegExp(f).test(id));
}
isSubPage: true
title: qsTr("App info")
onAppChanged: {
// Auto close when app lost
if (!app)
sState.closeSubPage();
}
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
// Header
RowLayout {
Layout.bottomMargin: Appearance.spacing.large
Layout.fillWidth: true
Layout.leftMargin: Appearance.padding.small
spacing: Appearance.spacing.large
IconImage {
asynchronous: true
implicitSize: Math.round(Appearance.font.size.large * 3)
source: Quickshell.iconPath(root.app?.icon, "image-missing")
}
ColumnLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.extraSmall / 2
CustomText {
Layout.fillWidth: true
font.pointSize: Appearance.font.size.medium
text: root.app?.name ?? ""
wrapMode: Text.WordWrap
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3outline
font.pointSize: Appearance.font.size.small
text: (root.app?.comment || root.app?.genericName) ?? ""
visible: text
wrapMode: Text.WordWrap
}
}
}
// Launcher
SectionHeader {
first: true
text: qsTr("Launcher")
}
ToggleRow {
checked: root.app && Strings.testRegexList(Config.launcher.favoriteApps, root.app.id)
enabled: !root.favoriteByRegex
first: true
subtext: root.favoriteByRegex ? qsTr("Matched by a regex in favoriteApps — edit the config file to change") : qsTr("Pin to the top of the launcher")
text: qsTr("Favorite")
onToggled: {
const apps = Config.launcher.favoriteApps;
Config.launcher.favoriteApps = checked ? [...apps, root.app.id] : apps.filter(a => a !== root.app.id);
}
}
ToggleRow {
checked: root.app && Strings.testRegexList(Config.launcher.hiddenApps, root.app.id)
enabled: !root.hiddenByRegex
last: true
subtext: root.hiddenByRegex ? qsTr("Matched by a regex in hiddenApps — edit the config file to change") : qsTr("Hide from the launcher")
text: qsTr("Hidden")
onToggled: {
const apps = Config.launcher.hiddenApps;
Config.launcher.hiddenApps = checked ? [...apps, root.app.id] : apps.filter(a => a !== root.app.id);
}
}
// Details
SectionHeader {
text: qsTr("Details")
}
WrapInfoRow {
id: appId
first: true
label: qsTr("App ID")
labelComp.Layout.preferredWidth: Math.max(labelComp.implicitWidth, command.labelComp.implicitWidth)
value: root.app?.id ?? ""
}
WrapInfoRow {
id: command
label: qsTr("Command")
labelComp.Layout.preferredWidth: Math.max(labelComp.implicitWidth, appId.labelComp.implicitWidth)
last: true
value: (root.app?.command ?? []).join(" ")
}
}
component WrapInfoRow: ConnectedRect {
id: row
property alias label: label.text
readonly property alias labelComp: label
property alias value: value.text
Layout.fillWidth: true
implicitHeight: rowLayout.implicitHeight + rowLayout.anchors.margins * 2
RowLayout {
id: rowLayout
anchors.fill: parent
anchors.leftMargin: Appearance.padding.largeIncreased
anchors.margins: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.largeIncreased
spacing: Appearance.spacing.small
CustomText {
id: label
Layout.alignment: Qt.AlignTop
font.pointSize: Appearance.font.size.small
}
Item {
Layout.fillWidth: true
}
CustomText {
id: value
Layout.fillWidth: true
Layout.maximumWidth: implicitWidth + 1 // Whyyyyyyyyy
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.small
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
}
}
}
}
+177
View File
@@ -0,0 +1,177 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import ZShell
import qs.Helpers
import qs.Components
import qs.Config
import qs.Modules.SettingsNew.Common
PageBase {
id: root
title: qsTr("Apps")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
// Default applications
SectionHeader {
first: true
text: qsTr("Default applications")
}
DefaultRow {
first: true
icon: "terminal"
label: qsTr("Terminal")
status: Config.general.apps.terminal.join(" ")
onSelected: app => Config.general.apps.terminal = app.command
}
DefaultRow {
icon: "volume_up"
label: qsTr("Audio")
status: Config.general.apps.audio.join(" ")
onSelected: app => Config.general.apps.audio = app.command
}
DefaultRow {
icon: "play_circle"
label: qsTr("Media playback")
status: Config.general.apps.playback.join(" ")
onSelected: app => Config.general.apps.playback = app.command
}
DefaultRow {
icon: "folder"
label: qsTr("File manager")
last: true
status: Config.general.apps.explorer.join(" ")
onSelected: app => Config.general.apps.explorer = app.command
}
// Library
SectionHeader {
text: qsTr("Library")
}
NavRow {
first: true
icon: "apps"
label: qsTr("All apps")
last: true
status: qsTr("Browse installed apps, set favorites and hidden")
onClicked: root.sState.openSubPage(1)
}
}
component DefaultRow: PopupRow {
id: row
readonly property int popupHeight: root.flickable.height - y + root.flickable.contentY - Appearance.padding.large - Appearance.padding.extraLarge
signal selected(app: DesktopEntry)
keepPopupAsChild: {
if (root.sState.animatingContainer || root.opacity < 1)
return true;
let p = root.parent;
while (p && p.objectName !== "PageContainer")
p = p.parent;
return p?.opacity < 1;
}
popup.topMovement: Math.max(0 - popupHeight, Appearance.padding.large)
Loader {
active: row.popup.animDriver > 0
anchors.centerIn: parent
sourceComponent: VerticalFadeListView {
id: list
fadeAmount: 0.05
implicitHeight: ZUtils.clamp(row.popupHeight, 200, 800)
implicitWidth: 300
model: {
const apps = [...DesktopEntries.applications.values];
const favorited = new Set(apps.filter(a => Strings.testRegexList(Config.launcher.favoriteApps, a.id)));
return apps.sort((a, b) => (favorited.has(b) - favorited.has(a)) || a.name.localeCompare(b.name));
}
delegate: StateLayer {
id: appItem
required property int index
required property DesktopEntry modelData
anchors.fill: undefined
anchors.left: list.contentItem.left
anchors.right: list.contentItem.right
implicitHeight: itemLayout.implicitHeight + itemLayout.anchors.margins * 2
radius: Appearance.rounding.small
onClicked: {
row.popup.open = false;
row.selected(modelData);
}
RowLayout {
id: itemLayout
anchors.fill: parent
anchors.margins: Appearance.padding.normal
spacing: Appearance.spacing.small
IconImage {
asynchronous: true
implicitSize: Math.round(Appearance.font.size.large * 1.8)
source: Quickshell.iconPath(appItem.modelData.icon, "image-missing")
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: appItem.modelData.name
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: (appItem.modelData.comment || appItem.modelData.genericName) ?? ""
visible: text
}
}
MaterialIcon {
color: DynamicColors.palette.m3primary
fill: 1
font.pointSize: Appearance.font.size.small
text: "favorite"
visible: Strings.testRegexList(Config.launcher.favoriteApps, appItem.modelData.id)
}
}
}
}
}
}
}
@@ -0,0 +1,69 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.Pipewire
import qs.Modules.SettingsNew.Common
import qs.Helpers
import qs.Components
import qs.Config
import qs.Daemons
PageBase {
id: root
isSubPage: true
title: qsTr("App volumes")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
CustomText {
Layout.bottomMargin: Appearance.spacing.small
Layout.fillWidth: true
Layout.leftMargin: Appearance.padding.small
color: DynamicColors.palette.m3outline
font.pointSize: Appearance.font.size.small
text: qsTr("Adjust the volume of individual apps currently playing audio.")
wrapMode: Text.WordWrap
}
ItemList {
id: streamList
color: list.count === 0 ? DynamicColors.tPalette.m3surfaceContainer : "transparent"
first: true
last: true
list.spacing: Appearance.spacing.extraSmall / 2
placeholderIcon: "music_off"
placeholderText: qsTr("No apps playing audio")
showList: true
delegate: SliderRow {
id: stream
required property int index
required property PwNode modelData
anchors.left: streamList.list.contentItem.left
anchors.right: streamList.list.contentItem.right
enabled: !stream.modelData?.audio?.muted
first: index === 0
icon: Icons.getVolumeIcon(stream.modelData?.audio?.volume ?? 0, stream.modelData?.audio?.muted ?? false)
label: Audio.getStreamName(stream.modelData)
last: index === streamList.list.count - 1
value: stream.modelData?.audio?.volume ?? 0
valueLabel: Math.round(value * 100) + "%"
onMoved: v => Audio.setStreamVolume(stream.modelData, v)
}
model: ScriptModel {
values: [...Audio.streams]
}
}
}
}
+136
View File
@@ -0,0 +1,136 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Daemons
import qs.Config
import qs.Components
import qs.Modules.SettingsNew.Common
import qs.Helpers
PageBase {
id: root
title: qsTr("Audio")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
// Output
SliderRow {
enabled: !Audio.muted
first: true
icon: Icons.getVolumeIcon(Audio.volume, Audio.muted)
label: qsTr("Output")
value: Audio.volume
valueLabel: Math.round(value * 100) + "%"
onMoved: v => Audio.setVolume(v)
}
ToggleRow {
checked: Audio.muted
text: qsTr("Muted")
onToggled: Audio.setStreamMuted(Audio.sink, checked)
}
AudioDeviceList {
currentId: Audio.sink?.id ?? -1
iconName: "speaker"
nodes: Audio.sinks
placeholderIcon: "speaker"
placeholderText: qsTr("No output devices")
onSelected: node => Audio.setAudioSink(node)
}
// Input
SliderRow {
Layout.topMargin: Appearance.spacing.large - parent.spacing
enabled: !Audio.sourceMuted
first: true
icon: Icons.getMicVolumeIcon(Audio.sourceVolume, Audio.sourceMuted)
label: qsTr("Input")
value: Audio.sourceVolume
valueLabel: Math.round(value * 100) + "%"
onMoved: v => Audio.setSourceVolume(v)
}
ToggleRow {
checked: Audio.sourceMuted
text: qsTr("Muted")
onToggled: Audio.setStreamMuted(Audio.source, checked)
}
AudioDeviceList {
currentId: Audio.source?.id ?? -1
iconName: "mic"
nodes: Audio.sources
placeholderIcon: "mic_off"
placeholderText: qsTr("No input devices")
onSelected: node => Audio.setAudioSource(node)
}
// Per-app volumes
ConnectedRect {
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.large - parent.spacing
first: true
implicitHeight: appLayout.implicitHeight + appLayout.anchors.margins * 2
last: true
StateLayer {
onClicked: root.sState.openSubPage(1)
}
RowLayout {
id: appLayout
anchors.fill: parent
anchors.leftMargin: Appearance.padding.largeIncreased
anchors.margins: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.largeIncreased
spacing: Appearance.spacing.small
MaterialIcon {
font.pointSize: Appearance.font.size.medium
text: "tune"
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: qsTr("App volumes")
}
CustomText {
Layout.fillWidth: true
animate: true
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: Audio.streams.length === 0 ? qsTr("No apps playing audio") : Audio.streams.length === 1 ? qsTr("1 app playing audio") : qsTr("%1 apps playing audio").arg(Audio.streams.length)
}
}
MaterialIcon {
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.medium
text: "chevron_right"
}
}
}
}
}
+2 -1
View File
@@ -68,6 +68,7 @@ PageBase {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.rounding
from: 0
last: true
stepSize: 1
text: qsTr("Corner radius")
to: 50
@@ -80,9 +81,9 @@ PageBase {
}
ToggleRow {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
checked: Config.screenshot.shadow
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual"
first: true
text: qsTr("Enable shadow")
onToggled: Config.screenshot.shadow = checked
+6 -93
View File
@@ -20,105 +20,18 @@ PageBase {
spacing: Appearance.spacing.large
width: root.cappedWidth
CustomClippingRect {
Item {
id: wallWrapper
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: {
const screen = root.sState.screen;
const cWidth = root.cappedWidth;
return Math.min(Math.round(cWidth * 0.4), cWidth / screen.width * screen.height);
}
implicitHeight: cropper.height
implicitWidth: {
const screen = root.sState.screen;
return implicitHeight / screen.height * screen.width;
}
radius: Appearance.rounding.large
Loader {
active: opacity > 0
anchors.centerIn: parent
opacity: Config.background.enabled ? 0 : 1
Behavior on opacity {
Anim {
type: Anim.SlowEffects
}
}
sourceComponent: ColumnLayout {
spacing: Appearance.spacing.extraSmall
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3onSurfaceVariant
text: "hide_image"
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3onSurfaceVariant
text: qsTr("Wallpaper disabled")
}
}
}
Item {
anchors.fill: parent
opacity: Config.background.enabled ? 1 : 0
Behavior on opacity {
Anim {
type: Anim.SlowEffects
}
}
Loader {
id: wallIndicatorLoader
active: opacity > 0
anchors.fill: parent
opacity: 0
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
sourceComponent: CustomRect {
color: DynamicColors.palette.m3primaryContainer
radius: Appearance.rounding.normal
}
}
Timer {
id: wallLoadDebounceTimer
interval: 100
onTriggered: {
if (wallImg.status !== Image.Ready)
wallIndicatorLoader.opacity = 1;
}
}
FadeImage {
id: wallImg
anchors.fill: parent
fadeInAnim: Anim.SlowEffects
fadeOutAnim: Anim.DefaultEffects
preventInit: wallIndicatorLoader.opacity > 0
source: Wallpapers.current
onSourceChanged: wallLoadDebounceTimer.restart()
onStatusChanged: {
if (status === Image.Ready) {
wallLoadDebounceTimer.stop();
wallIndicatorLoader.opacity = 0;
}
}
}
WallpaperCropper {
id: cropper
}
}
@@ -162,7 +75,7 @@ PageBase {
onToggled: DynamicColors.setMode(checked ? "dark" : "light")
}
PopupRow {
OverlayRow {
checked: Config.general.color.scheduleDark
first: true
subtext: qsTr("Dark mode will turn on at %1, and turn off at %2.").arg(Config.general.color.scheduleDarkStart).arg(Config.general.color.scheduleDarkEnd)
@@ -189,7 +102,7 @@ PageBase {
}
}
PopupRow {
OverlayRow {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
checked: Config.general.color.scheduleHyprsunset
last: true
@@ -15,118 +15,102 @@ PageBase {
isSubPage: true
title: qsTr("Wallpapers")
Item {
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
implicitHeight: childrenRect.height
spacing: Appearance.spacing.small
width: root.cappedWidth
WallpaperCropper {
id: cropper
CustomText {
Layout.topMargin: Appearance.spacing.large
font.pointSize: Appearance.font.size.large
text: qsTr("Wallpapers")
}
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.margins: Appearance.spacing.normal
anchors.top: cropper.bottom
spacing: Appearance.spacing.small
width: root.cappedWidth
GridLayout {
Layout.fillWidth: true
columnSpacing: Appearance.spacing.extraSmall
columns: 4
rowSpacing: Appearance.spacing.extraSmall
visible: localWalls.count > 0
CustomText {
Layout.topMargin: Appearance.spacing.large
font.pointSize: Appearance.font.size.large
text: qsTr("Wallpapers")
}
Repeater {
id: localWalls
GridLayout {
Layout.fillWidth: true
columnSpacing: Appearance.spacing.extraSmall
columns: 4
rowSpacing: Appearance.spacing.extraSmall
visible: localWalls.count > 0
model: {
const walls = Wallpapers.list;
var baseDir = Paths.wallsdir;
const categories = {};
const list = [];
for (const w of walls) {
var parentDir = w.parentDir;
if (!parentDir.endsWith("/"))
parentDir = parentDir + "/";
Repeater {
id: localWalls
if (!baseDir.endsWith("/"))
baseDir = baseDir + "/";
model: {
const walls = Wallpapers.list;
var baseDir = Paths.wallsdir;
const categories = {};
const list = [];
for (const w of walls) {
var parentDir = w.parentDir;
if (!parentDir.endsWith("/"))
parentDir = parentDir + "/";
if (!baseDir.endsWith("/"))
baseDir = baseDir + "/";
if (parentDir !== baseDir) {
const category = Wallpapers.getCategoryFor(w);
if (category && (!(category in categories) || categories[category].name.localeCompare(w.name) > 0))
categories[category] = w;
} else {
list.push(w);
}
if (parentDir !== baseDir) {
const category = Wallpapers.getCategoryFor(w);
if (category && (!(category in categories) || categories[category].name.localeCompare(w.name) > 0))
categories[category] = w;
} else {
list.push(w);
}
list.push(...Object.values(categories));
list.sort((a, b) => ((a.parentDir === baseDir) - (b.parentDir === baseDir)) || a.name.localeCompare(b.name));
while (list.length < 4)
list.push(null);
return list;
}
list.push(...Object.values(categories));
list.sort((a, b) => ((a.parentDir === baseDir) - (b.parentDir === baseDir)) || a.name.localeCompare(b.name));
while (list.length < 4)
list.push(null);
WallItem {
required property FileSystemEntry modelData
return list;
}
enabled: modelData
WallItem {
required property FileSystemEntry modelData
// Empty placeholders for sizing
opacity: modelData ? 1 : 0
source: String(modelData?.path ?? "")
enabled: modelData
onClicked: {
if (modelData.parentDir !== Paths.wallsdir) {
root.sState.selectedWallpaperCategory = Wallpapers.getCategoryFor(modelData);
root.sState.openSubPage(2); // Category page
} else {
Wallpapers.setWallpaper(modelData.path);
root.sState.closeSubPage();
}
}
// Empty placeholders for sizing
opacity: modelData ? 1 : 0
source: String(modelData?.path ?? "")
onClicked: {
Wallpapers.setWallpaper(modelData.path);
root.sState.closeSubPage();
}
}
}
}
Loader {
Layout.fillWidth: true
active: localWalls.count === 0
asynchronous: true
visible: active
Loader {
Layout.fillWidth: true
active: localWalls.count === 0
asynchronous: true
visible: active
sourceComponent: CustomRect {
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: noWallsLayout.implicitHeight + Appearance.padding.extraLarge * 3
radius: Appearance.rounding.large
sourceComponent: CustomRect {
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: noWallsLayout.implicitHeight + Appearance.padding.extraLarge * 3
radius: Appearance.rounding.large
ColumnLayout {
id: noWallsLayout
ColumnLayout {
id: noWallsLayout
anchors.centerIn: parent
spacing: Appearance.spacing.extraSmall
anchors.centerIn: parent
spacing: Appearance.spacing.extraSmall
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3outline
text: "hide_image"
}
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3outline
text: "hide_image"
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3outline
text: qsTr("No local wallpapers found")
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3outline
text: qsTr("No local wallpapers found")
}
}
}