nosignal-shell/modules/controlcenter/appearance/AppearancePane.qml

2071 lines
80 KiB
QML

pragma ComponentBehavior: Bound
import ".."
import "../../launcher/services"
import qs.components
import qs.components.controls
import qs.components.effects
import qs.components.containers
import qs.components.images
import qs.services
import qs.config
import qs.utils
import Caelestia.Models
import Quickshell
import Quickshell.Io
import QtQuick
import QtQuick.Layouts
RowLayout {
id: root
required property Session session
// Appearance settings
property real animDurationsScale: 1
property string fontFamilyMaterial: "Material Symbols Rounded"
property string fontFamilyMono: "CaskaydiaCove NF"
property string fontFamilySans: "Rubik"
property real fontSizeScale: 1
property real paddingScale: 1
property real roundingScale: 1
property real spacingScale: 1
property bool transparencyEnabled: false
property real transparencyBase: 0.85
property real transparencyLayers: 0.4
property real borderRounding: 1
property real borderThickness: 1
// Background settings
property bool desktopClockEnabled: true
property bool backgroundEnabled: true
property bool visualiserEnabled: true
property bool visualiserAutoHide: true
property real visualiserRounding: 1
property real visualiserSpacing: 1
anchors.fill: parent
spacing: 0
FileView {
id: configFile
path: `${Paths.config}/shell.json`
watchChanges: true
onLoaded: {
try {
const config = JSON.parse(text());
updateFromConfig(config);
} catch (e) {
console.error("Failed to parse config:", e);
}
}
onSaveFailed: err => {
console.error("Failed to save config file:", err);
}
}
function updateFromConfig(config) {
// Update appearance settings
if (config.appearance) {
if (config.appearance.anim && config.appearance.anim.durations) {
root.animDurationsScale = config.appearance.anim.durations.scale ?? 1;
}
if (config.appearance.font) {
if (config.appearance.font.family) {
root.fontFamilyMaterial = config.appearance.font.family.material ?? "Material Symbols Rounded";
root.fontFamilyMono = config.appearance.font.family.mono ?? "CaskaydiaCove NF";
root.fontFamilySans = config.appearance.font.family.sans ?? "Rubik";
}
if (config.appearance.font.size) {
root.fontSizeScale = config.appearance.font.size.scale ?? 1;
}
}
if (config.appearance.padding) {
root.paddingScale = config.appearance.padding.scale ?? 1;
}
if (config.appearance.rounding) {
root.roundingScale = config.appearance.rounding.scale ?? 1;
}
if (config.appearance.spacing) {
root.spacingScale = config.appearance.spacing.scale ?? 1;
}
if (config.appearance.transparency) {
root.transparencyEnabled = config.appearance.transparency.enabled ?? false;
root.transparencyBase = config.appearance.transparency.base ?? 0.85;
root.transparencyLayers = config.appearance.transparency.layers ?? 0.4;
}
}
// Update border settings
if (config.border) {
root.borderRounding = config.border.rounding ?? 1;
root.borderThickness = config.border.thickness ?? 1;
}
// Update background settings
if (config.background) {
root.desktopClockEnabled = config.background.desktopClock?.enabled !== undefined ? config.background.desktopClock.enabled : false;
root.backgroundEnabled = config.background.enabled !== undefined ? config.background.enabled : true;
if (config.background.visualiser) {
root.visualiserEnabled = config.background.visualiser.enabled !== undefined ? config.background.visualiser.enabled : false;
root.visualiserAutoHide = config.background.visualiser.autoHide !== undefined ? config.background.visualiser.autoHide : true;
root.visualiserRounding = config.background.visualiser.rounding !== undefined ? config.background.visualiser.rounding : 1;
root.visualiserSpacing = config.background.visualiser.spacing !== undefined ? config.background.visualiser.spacing : 1;
} else {
// Set defaults if visualiser object doesn't exist (matching BackgroundConfig defaults)
root.visualiserEnabled = false;
root.visualiserAutoHide = true;
root.visualiserRounding = 1;
root.visualiserSpacing = 1;
}
} else {
// Set defaults if background object doesn't exist (matching BackgroundConfig defaults)
root.desktopClockEnabled = false;
root.backgroundEnabled = true;
root.visualiserEnabled = false;
root.visualiserAutoHide = true;
root.visualiserRounding = 1;
root.visualiserSpacing = 1;
}
}
function collapseAllSections(exceptSection) {
if (exceptSection !== themeModeSection) themeModeSection.expanded = false;
if (exceptSection !== colorVariantSection) colorVariantSection.expanded = false;
if (exceptSection !== colorSchemeSection) colorSchemeSection.expanded = false;
if (exceptSection !== animationsSection) animationsSection.expanded = false;
if (exceptSection !== fontsSection) fontsSection.expanded = false;
if (exceptSection !== scalesSection) scalesSection.expanded = false;
if (exceptSection !== transparencySection) transparencySection.expanded = false;
if (exceptSection !== borderSection) borderSection.expanded = false;
if (exceptSection !== backgroundSection) backgroundSection.expanded = false;
}
function saveConfig() {
if (!configFile.loaded) {
console.error("Config file not loaded yet");
return;
}
try {
const config = JSON.parse(configFile.text());
// Ensure appearance object exists
if (!config.appearance) config.appearance = {};
// Update animations
if (!config.appearance.anim) config.appearance.anim = {};
if (!config.appearance.anim.durations) config.appearance.anim.durations = {};
config.appearance.anim.durations.scale = root.animDurationsScale;
// Update fonts
if (!config.appearance.font) config.appearance.font = {};
if (!config.appearance.font.family) config.appearance.font.family = {};
config.appearance.font.family.material = root.fontFamilyMaterial;
config.appearance.font.family.mono = root.fontFamilyMono;
config.appearance.font.family.sans = root.fontFamilySans;
if (!config.appearance.font.size) config.appearance.font.size = {};
config.appearance.font.size.scale = root.fontSizeScale;
// Update scales
if (!config.appearance.padding) config.appearance.padding = {};
config.appearance.padding.scale = root.paddingScale;
if (!config.appearance.rounding) config.appearance.rounding = {};
config.appearance.rounding.scale = root.roundingScale;
if (!config.appearance.spacing) config.appearance.spacing = {};
config.appearance.spacing.scale = root.spacingScale;
// Update transparency
if (!config.appearance.transparency) config.appearance.transparency = {};
config.appearance.transparency.enabled = root.transparencyEnabled;
config.appearance.transparency.base = root.transparencyBase;
config.appearance.transparency.layers = root.transparencyLayers;
// Ensure background object exists
if (!config.background) config.background = {};
// Update desktop clock
if (!config.background.desktopClock) config.background.desktopClock = {};
config.background.desktopClock.enabled = root.desktopClockEnabled;
// Update background enabled
config.background.enabled = root.backgroundEnabled;
// Update visualiser
if (!config.background.visualiser) config.background.visualiser = {};
config.background.visualiser.enabled = root.visualiserEnabled;
config.background.visualiser.autoHide = root.visualiserAutoHide;
config.background.visualiser.rounding = root.visualiserRounding;
config.background.visualiser.spacing = root.visualiserSpacing;
// Update border
if (!config.border) config.border = {};
config.border.rounding = root.borderRounding;
config.border.thickness = root.borderThickness;
// Write back to file using setText (same simple approach that worked for taskbar)
const jsonString = JSON.stringify(config, null, 4);
configFile.setText(jsonString);
} catch (e) {
console.error("Failed to save config:", e);
}
}
Item {
Layout.preferredWidth: Math.floor(parent.width * 0.4)
Layout.minimumWidth: 420
Layout.fillHeight: true
StyledFlickable {
id: sidebarFlickable
anchors.fill: parent
flickableDirection: Flickable.VerticalFlick
contentHeight: sidebarLayout.implicitHeight + Appearance.padding.large * 2
StyledScrollBar.vertical: StyledScrollBar {
flickable: sidebarFlickable
}
ColumnLayout {
id: sidebarLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Appearance.padding.large + Appearance.padding.normal
anchors.leftMargin: Appearance.padding.large
anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2
spacing: Appearance.spacing.small
RowLayout {
spacing: Appearance.spacing.smaller
StyledText {
text: qsTr("Settings")
font.pointSize: Appearance.font.size.large
font.weight: 500
}
Item {
Layout.fillWidth: true
}
}
Item {
id: themeModeSection
Layout.fillWidth: true
Layout.preferredHeight: themeModeSectionHeader.implicitHeight
property bool expanded: false
ColumnLayout {
id: themeModeSectionHeader
anchors.left: parent.left
anchors.right: parent.right
spacing: Appearance.spacing.small
RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.small
StyledText {
Layout.topMargin: Appearance.spacing.large
text: qsTr("Theme mode")
font.pointSize: Appearance.font.size.larger
font.weight: 500
}
Item {
Layout.fillWidth: true
}
MaterialIcon {
text: "expand_more"
rotation: themeModeSection.expanded ? 180 : 0
color: Colours.palette.m3onSurface
Behavior on rotation {
Anim {}
}
}
}
StateLayer {
anchors.fill: parent
anchors.leftMargin: -Appearance.padding.normal
anchors.rightMargin: -Appearance.padding.normal
function onClicked(): void {
const wasExpanded = themeModeSection.expanded;
root.collapseAllSections(themeModeSection);
themeModeSection.expanded = !wasExpanded;
}
}
StyledText {
visible: themeModeSection.expanded
text: qsTr("Light or dark theme")
color: Colours.palette.m3outline
Layout.fillWidth: true
}
}
}
StyledRect {
visible: themeModeSection.expanded
Layout.fillWidth: true
implicitHeight: themeModeSection.expanded ? modeToggle.implicitHeight + Appearance.padding.large * 2 : 0
radius: Appearance.rounding.normal
color: Colours.tPalette.m3surfaceContainer
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: modeToggle
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.normal
StyledText {
Layout.fillWidth: true
text: qsTr("Dark mode")
}
StyledSwitch {
checked: !Colours.currentLight
onToggled: {
Colours.setMode(checked ? "dark" : "light");
}
}
}
}
Item {
id: colorVariantSection
Layout.fillWidth: true
Layout.preferredHeight: colorVariantSectionHeader.implicitHeight
property bool expanded: false
ColumnLayout {
id: colorVariantSectionHeader
anchors.left: parent.left
anchors.right: parent.right
spacing: Appearance.spacing.small
RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.small
StyledText {
Layout.topMargin: Appearance.spacing.large
text: qsTr("Color variant")
font.pointSize: Appearance.font.size.larger
font.weight: 500
}
Item {
Layout.fillWidth: true
}
MaterialIcon {
text: "expand_more"
rotation: colorVariantSection.expanded ? 180 : 0
color: Colours.palette.m3onSurface
Behavior on rotation {
Anim {}
}
}
}
StateLayer {
anchors.fill: parent
anchors.leftMargin: -Appearance.padding.normal
anchors.rightMargin: -Appearance.padding.normal
function onClicked(): void {
const wasExpanded = colorVariantSection.expanded;
root.collapseAllSections(colorVariantSection);
colorVariantSection.expanded = !wasExpanded;
}
}
StyledText {
visible: colorVariantSection.expanded
text: qsTr("Material theme variant")
color: Colours.palette.m3outline
Layout.fillWidth: true
}
}
}
StyledListView {
visible: colorVariantSection.expanded
Layout.fillWidth: true
implicitHeight: colorVariantSection.expanded ? Math.min(400, M3Variants.list.length * 60) : 0
Layout.topMargin: 0
Behavior on implicitHeight {
Anim {}
}
model: M3Variants.list
spacing: Appearance.spacing.small / 2
clip: true
StyledScrollBar.vertical: StyledScrollBar {
flickable: parent
}
delegate: StyledRect {
required property var modelData
anchors.left: parent.left
anchors.right: parent.right
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, modelData.variant === Schemes.currentVariant ? Colours.tPalette.m3surfaceContainer.a : 0)
radius: Appearance.rounding.normal
border.width: modelData.variant === Schemes.currentVariant ? 1 : 0
border.color: Colours.palette.m3primary
StateLayer {
function onClicked(): void {
const variant = modelData.variant;
// Optimistic update - set immediately
Schemes.currentVariant = variant;
// Execute the command
Quickshell.execDetached(["caelestia", "scheme", "set", "-v", variant]);
// Reload after a delay to confirm
Qt.callLater(() => {
reloadTimer.restart();
});
}
}
Timer {
id: reloadTimer
interval: 300
onTriggered: {
Schemes.reload();
}
}
RowLayout {
id: variantRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.normal
spacing: Appearance.spacing.normal
MaterialIcon {
text: modelData.icon
font.pointSize: Appearance.font.size.large
fill: modelData.variant === Schemes.currentVariant ? 1 : 0
}
StyledText {
Layout.fillWidth: true
text: modelData.name
font.weight: modelData.variant === Schemes.currentVariant ? 500 : 400
}
MaterialIcon {
visible: modelData.variant === Schemes.currentVariant
text: "check"
color: Colours.palette.m3primary
font.pointSize: Appearance.font.size.large
}
}
implicitHeight: variantRow.implicitHeight + Appearance.padding.normal * 2
}
}
Item {
id: colorSchemeSection
Layout.fillWidth: true
Layout.preferredHeight: colorSchemeSectionHeader.implicitHeight
property bool expanded: false
ColumnLayout {
id: colorSchemeSectionHeader
anchors.left: parent.left
anchors.right: parent.right
spacing: Appearance.spacing.small
RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.small
StyledText {
Layout.topMargin: Appearance.spacing.large
text: qsTr("Color scheme")
font.pointSize: Appearance.font.size.larger
font.weight: 500
}
Item {
Layout.fillWidth: true
}
MaterialIcon {
text: "expand_more"
rotation: colorSchemeSection.expanded ? 180 : 0
color: Colours.palette.m3onSurface
Behavior on rotation {
Anim {}
}
}
}
StateLayer {
anchors.fill: parent
anchors.leftMargin: -Appearance.padding.normal
anchors.rightMargin: -Appearance.padding.normal
function onClicked(): void {
const wasExpanded = colorSchemeSection.expanded;
root.collapseAllSections(colorSchemeSection);
colorSchemeSection.expanded = !wasExpanded;
}
}
StyledText {
visible: colorSchemeSection.expanded
text: qsTr("Available color schemes")
color: Colours.palette.m3outline
Layout.fillWidth: true
}
}
}
StyledListView {
visible: colorSchemeSection.expanded
Layout.fillWidth: true
implicitHeight: colorSchemeSection.expanded ? Math.min(400, Schemes.list.length * 80) : 0
Layout.topMargin: 0
Behavior on implicitHeight {
Anim {}
}
model: Schemes.list
spacing: Appearance.spacing.small / 2
clip: true
StyledScrollBar.vertical: StyledScrollBar {
flickable: parent
}
delegate: StyledRect {
required property var modelData
anchors.left: parent.left
anchors.right: parent.right
readonly property string schemeKey: `${modelData.name} ${modelData.flavour}`
readonly property bool isCurrent: schemeKey === Schemes.currentScheme
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0)
radius: Appearance.rounding.normal
border.width: isCurrent ? 1 : 0
border.color: Colours.palette.m3primary
StateLayer {
function onClicked(): void {
const name = modelData.name;
const flavour = modelData.flavour;
const schemeKey = `${name} ${flavour}`;
// Optimistic update - set immediately
Schemes.currentScheme = schemeKey;
// Execute the command
Quickshell.execDetached(["caelestia", "scheme", "set", "-n", name, "-f", flavour]);
// Reload after a delay to confirm
Qt.callLater(() => {
reloadTimer.restart();
});
}
}
Timer {
id: reloadTimer
interval: 300
onTriggered: {
Schemes.reload();
}
}
RowLayout {
id: schemeRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.normal
spacing: Appearance.spacing.normal
StyledRect {
id: preview
anchors.verticalCenter: parent.verticalCenter
border.width: 1
border.color: Qt.alpha(`#${modelData.colours?.outline}`, 0.5)
color: `#${modelData.colours?.surface}`
radius: Appearance.rounding.full
implicitWidth: iconPlaceholder.implicitWidth
implicitHeight: iconPlaceholder.implicitWidth
MaterialIcon {
id: iconPlaceholder
visible: false
text: "circle"
font.pointSize: Appearance.font.size.large
}
Item {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
implicitWidth: parent.implicitWidth / 2
clip: true
StyledRect {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
implicitWidth: preview.implicitWidth
color: `#${modelData.colours?.primary}`
radius: Appearance.rounding.full
}
}
}
Column {
Layout.fillWidth: true
spacing: 0
StyledText {
text: modelData.flavour ?? ""
font.pointSize: Appearance.font.size.normal
}
StyledText {
text: modelData.name ?? ""
font.pointSize: Appearance.font.size.small
color: Colours.palette.m3outline
elide: Text.ElideRight
anchors.left: parent.left
anchors.right: parent.right
}
}
Loader {
active: isCurrent
asynchronous: true
sourceComponent: MaterialIcon {
text: "check"
color: Colours.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.large
}
}
}
implicitHeight: schemeRow.implicitHeight + Appearance.padding.normal * 2
}
}
Item {
id: animationsSection
Layout.fillWidth: true
Layout.preferredHeight: animationsSectionHeader.implicitHeight
property bool expanded: false
ColumnLayout {
id: animationsSectionHeader
anchors.left: parent.left
anchors.right: parent.right
spacing: Appearance.spacing.small
RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.small
StyledText {
Layout.topMargin: Appearance.spacing.large
text: qsTr("Animations")
font.pointSize: Appearance.font.size.larger
font.weight: 500
}
Item {
Layout.fillWidth: true
}
MaterialIcon {
text: "expand_more"
rotation: animationsSection.expanded ? 180 : 0
color: Colours.palette.m3onSurface
Behavior on rotation {
Anim {}
}
}
}
StateLayer {
anchors.fill: parent
anchors.leftMargin: -Appearance.padding.normal
anchors.rightMargin: -Appearance.padding.normal
function onClicked(): void {
const wasExpanded = animationsSection.expanded;
root.collapseAllSections(animationsSection);
animationsSection.expanded = !wasExpanded;
}
}
}
}
StyledRect {
visible: animationsSection.expanded
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.small / 2
implicitHeight: animationsSection.expanded ? animDurationsScaleRow.implicitHeight + Appearance.padding.large * 2 : 0
radius: Appearance.rounding.normal
color: Colours.tPalette.m3surfaceContainer
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: animDurationsScaleRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.normal
StyledText {
Layout.fillWidth: true
text: qsTr("Animation duration scale")
}
CustomSpinBox {
id: animDurationsScaleSpinBox
min: 0.1
max: 5
value: root.animDurationsScale
onValueModified: value => {
root.animDurationsScale = value;
root.saveConfig();
}
}
}
}
Item {
id: fontsSection
Layout.fillWidth: true
Layout.preferredHeight: fontsSectionHeader.implicitHeight
property bool expanded: false
ColumnLayout {
id: fontsSectionHeader
anchors.left: parent.left
anchors.right: parent.right
spacing: Appearance.spacing.small
RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.small
StyledText {
Layout.topMargin: Appearance.spacing.large
text: qsTr("Fonts")
font.pointSize: Appearance.font.size.larger
font.weight: 500
}
Item {
Layout.fillWidth: true
}
MaterialIcon {
text: "expand_more"
rotation: fontsSection.expanded ? 180 : 0
color: Colours.palette.m3onSurface
Behavior on rotation {
Anim {}
}
}
}
StateLayer {
anchors.fill: parent
anchors.leftMargin: -Appearance.padding.normal
anchors.rightMargin: -Appearance.padding.normal
function onClicked(): void {
const wasExpanded = fontsSection.expanded;
root.collapseAllSections(fontsSection);
fontsSection.expanded = !wasExpanded;
}
}
}
}
StyledText {
visible: fontsSection.expanded
Layout.topMargin: Appearance.spacing.normal
text: qsTr("Material font family")
font.pointSize: Appearance.font.size.larger
font.weight: 500
}
StyledListView {
visible: fontsSection.expanded
Layout.fillWidth: true
implicitHeight: fontsSection.expanded ? Math.min(300, Qt.fontFamilies().length * 50) : 0
Layout.topMargin: 0
Behavior on implicitHeight {
Anim {}
}
model: Qt.fontFamilies()
spacing: Appearance.spacing.small / 2
clip: true
StyledScrollBar.vertical: StyledScrollBar {
flickable: parent
}
delegate: StyledRect {
required property string modelData
anchors.left: parent.left
anchors.right: parent.right
readonly property bool isCurrent: modelData === root.fontFamilyMaterial
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0)
radius: Appearance.rounding.normal
border.width: isCurrent ? 1 : 0
border.color: Colours.palette.m3primary
StateLayer {
function onClicked(): void {
root.fontFamilyMaterial = modelData;
root.saveConfig();
}
}
RowLayout {
id: fontFamilyMaterialRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.normal
spacing: Appearance.spacing.normal
StyledText {
text: modelData
font.pointSize: Appearance.font.size.normal
}
Item {
Layout.fillWidth: true
}
Loader {
active: isCurrent
asynchronous: true
sourceComponent: MaterialIcon {
text: "check"
color: Colours.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.large
}
}
}
implicitHeight: fontFamilyMaterialRow.implicitHeight + Appearance.padding.normal * 2
}
}
StyledText {
visible: fontsSection.expanded
Layout.topMargin: Appearance.spacing.normal
text: qsTr("Monospace font family")
font.pointSize: Appearance.font.size.larger
font.weight: 500
}
StyledListView {
visible: fontsSection.expanded
Layout.fillWidth: true
implicitHeight: fontsSection.expanded ? Math.min(300, Qt.fontFamilies().length * 50) : 0
Layout.topMargin: 0
Behavior on implicitHeight {
Anim {}
}
model: Qt.fontFamilies()
spacing: Appearance.spacing.small / 2
clip: true
StyledScrollBar.vertical: StyledScrollBar {
flickable: parent
}
delegate: StyledRect {
required property string modelData
anchors.left: parent.left
anchors.right: parent.right
readonly property bool isCurrent: modelData === root.fontFamilyMono
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0)
radius: Appearance.rounding.normal
border.width: isCurrent ? 1 : 0
border.color: Colours.palette.m3primary
StateLayer {
function onClicked(): void {
root.fontFamilyMono = modelData;
root.saveConfig();
}
}
RowLayout {
id: fontFamilyMonoRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.normal
spacing: Appearance.spacing.normal
StyledText {
text: modelData
font.pointSize: Appearance.font.size.normal
}
Item {
Layout.fillWidth: true
}
Loader {
active: isCurrent
asynchronous: true
sourceComponent: MaterialIcon {
text: "check"
color: Colours.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.large
}
}
}
implicitHeight: fontFamilyMonoRow.implicitHeight + Appearance.padding.normal * 2
}
}
StyledText {
visible: fontsSection.expanded
Layout.topMargin: Appearance.spacing.normal
text: qsTr("Sans-serif font family")
font.pointSize: Appearance.font.size.larger
font.weight: 500
}
StyledListView {
visible: fontsSection.expanded
Layout.fillWidth: true
implicitHeight: fontsSection.expanded ? Math.min(300, Qt.fontFamilies().length * 50) : 0
Layout.topMargin: 0
Behavior on implicitHeight {
Anim {}
}
model: Qt.fontFamilies()
spacing: Appearance.spacing.small / 2
clip: true
StyledScrollBar.vertical: StyledScrollBar {
flickable: parent
}
delegate: StyledRect {
required property string modelData
anchors.left: parent.left
anchors.right: parent.right
readonly property bool isCurrent: modelData === root.fontFamilySans
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0)
radius: Appearance.rounding.normal
border.width: isCurrent ? 1 : 0
border.color: Colours.palette.m3primary
StateLayer {
function onClicked(): void {
root.fontFamilySans = modelData;
root.saveConfig();
}
}
RowLayout {
id: fontFamilySansRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.normal
spacing: Appearance.spacing.normal
StyledText {
text: modelData
font.pointSize: Appearance.font.size.normal
}
Item {
Layout.fillWidth: true
}
Loader {
active: isCurrent
asynchronous: true
sourceComponent: MaterialIcon {
text: "check"
color: Colours.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.large
}
}
}
implicitHeight: fontFamilySansRow.implicitHeight + Appearance.padding.normal * 2
}
}
StyledRect {
visible: fontsSection.expanded
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.small / 2
implicitHeight: fontsSection.expanded ? fontSizeScaleRow.implicitHeight + Appearance.padding.large * 2 : 0
radius: Appearance.rounding.normal
color: Colours.tPalette.m3surfaceContainer
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: fontSizeScaleRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.normal
StyledText {
Layout.fillWidth: true
text: qsTr("Font size scale")
}
CustomSpinBox {
id: fontSizeScaleSpinBox
min: 0.1
max: 5
value: root.fontSizeScale
onValueModified: value => {
root.fontSizeScale = value;
root.saveConfig();
}
}
}
}
Item {
id: scalesSection
Layout.fillWidth: true
Layout.preferredHeight: scalesSectionHeader.implicitHeight
property bool expanded: false
ColumnLayout {
id: scalesSectionHeader
anchors.left: parent.left
anchors.right: parent.right
spacing: Appearance.spacing.small
RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.small
StyledText {
Layout.topMargin: Appearance.spacing.large
text: qsTr("Scales")
font.pointSize: Appearance.font.size.larger
font.weight: 500
}
Item {
Layout.fillWidth: true
}
MaterialIcon {
text: "expand_more"
rotation: scalesSection.expanded ? 180 : 0
color: Colours.palette.m3onSurface
Behavior on rotation {
Anim {}
}
}
}
StateLayer {
anchors.fill: parent
anchors.leftMargin: -Appearance.padding.normal
anchors.rightMargin: -Appearance.padding.normal
function onClicked(): void {
const wasExpanded = scalesSection.expanded;
root.collapseAllSections(scalesSection);
scalesSection.expanded = !wasExpanded;
}
}
}
}
StyledRect {
visible: scalesSection.expanded
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.small / 2
implicitHeight: scalesSection.expanded ? paddingScaleRow.implicitHeight + Appearance.padding.large * 2 : 0
radius: Appearance.rounding.normal
color: Colours.tPalette.m3surfaceContainer
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: paddingScaleRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.normal
StyledText {
Layout.fillWidth: true
text: qsTr("Padding scale")
}
CustomSpinBox {
id: paddingScaleSpinBox
min: 0.1
max: 5
value: root.paddingScale
onValueModified: value => {
root.paddingScale = value;
root.saveConfig();
}
}
}
}
StyledRect {
visible: scalesSection.expanded
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.small / 2
implicitHeight: scalesSection.expanded ? roundingScaleRow.implicitHeight + Appearance.padding.large * 2 : 0
radius: Appearance.rounding.normal
color: Colours.tPalette.m3surfaceContainer
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: roundingScaleRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.normal
StyledText {
Layout.fillWidth: true
text: qsTr("Rounding scale")
}
CustomSpinBox {
id: roundingScaleSpinBox
min: 0.1
max: 5
value: root.roundingScale
onValueModified: value => {
root.roundingScale = value;
root.saveConfig();
}
}
}
}
StyledRect {
visible: scalesSection.expanded
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.small / 2
implicitHeight: scalesSection.expanded ? spacingScaleRow.implicitHeight + Appearance.padding.large * 2 : 0
radius: Appearance.rounding.normal
color: Colours.tPalette.m3surfaceContainer
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: spacingScaleRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.normal
StyledText {
Layout.fillWidth: true
text: qsTr("Spacing scale")
}
CustomSpinBox {
id: spacingScaleSpinBox
min: 0.1
max: 5
value: root.spacingScale
onValueModified: value => {
root.spacingScale = value;
root.saveConfig();
}
}
}
}
Item {
id: transparencySection
Layout.fillWidth: true
Layout.preferredHeight: transparencySectionHeader.implicitHeight
property bool expanded: false
ColumnLayout {
id: transparencySectionHeader
anchors.left: parent.left
anchors.right: parent.right
spacing: Appearance.spacing.small
RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.small
StyledText {
Layout.topMargin: Appearance.spacing.large
text: qsTr("Transparency")
font.pointSize: Appearance.font.size.larger
font.weight: 500
}
Item {
Layout.fillWidth: true
}
MaterialIcon {
text: "expand_more"
rotation: transparencySection.expanded ? 180 : 0
color: Colours.palette.m3onSurface
Behavior on rotation {
Anim {}
}
}
}
StateLayer {
anchors.fill: parent
anchors.leftMargin: -Appearance.padding.normal
anchors.rightMargin: -Appearance.padding.normal
function onClicked(): void {
const wasExpanded = transparencySection.expanded;
root.collapseAllSections(transparencySection);
transparencySection.expanded = !wasExpanded;
}
}
}
}
StyledRect {
visible: transparencySection.expanded
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.small / 2
implicitHeight: transparencySection.expanded ? transparencyEnabledRow.implicitHeight + Appearance.padding.large * 2 : 0
radius: Appearance.rounding.normal
color: Colours.tPalette.m3surfaceContainer
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: transparencyEnabledRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.normal
StyledText {
Layout.fillWidth: true
text: qsTr("Transparency enabled")
}
StyledSwitch {
id: transparencyEnabledSwitch
checked: root.transparencyEnabled
onToggled: {
root.transparencyEnabled = checked;
root.saveConfig();
}
}
}
}
StyledRect {
visible: transparencySection.expanded
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.small / 2
implicitHeight: transparencySection.expanded ? transparencyBaseRow.implicitHeight + Appearance.padding.large * 2 : 0
radius: Appearance.rounding.normal
color: Colours.tPalette.m3surfaceContainer
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: transparencyBaseRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.normal
StyledText {
Layout.fillWidth: true
text: qsTr("Transparency base")
}
CustomSpinBox {
id: transparencyBaseSpinBox
min: 0
max: 1
value: root.transparencyBase
onValueModified: value => {
root.transparencyBase = value;
root.saveConfig();
}
}
}
}
StyledRect {
visible: transparencySection.expanded
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.small / 2
implicitHeight: transparencySection.expanded ? transparencyLayersRow.implicitHeight + Appearance.padding.large * 2 : 0
radius: Appearance.rounding.normal
color: Colours.tPalette.m3surfaceContainer
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: transparencyLayersRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.normal
StyledText {
Layout.fillWidth: true
text: qsTr("Transparency layers")
}
CustomSpinBox {
id: transparencyLayersSpinBox
min: 0
max: 1
value: root.transparencyLayers
onValueModified: value => {
root.transparencyLayers = value;
root.saveConfig();
}
}
}
}
Item {
id: borderSection
Layout.fillWidth: true
Layout.preferredHeight: borderSectionHeader.implicitHeight
property bool expanded: false
ColumnLayout {
id: borderSectionHeader
anchors.left: parent.left
anchors.right: parent.right
spacing: Appearance.spacing.small
RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.small
StyledText {
Layout.topMargin: Appearance.spacing.large
text: qsTr("Border")
font.pointSize: Appearance.font.size.larger
font.weight: 500
}
Item {
Layout.fillWidth: true
}
MaterialIcon {
text: "expand_more"
rotation: borderSection.expanded ? 180 : 0
color: Colours.palette.m3onSurface
Behavior on rotation {
Anim {}
}
}
}
StateLayer {
anchors.fill: parent
anchors.leftMargin: -Appearance.padding.normal
anchors.rightMargin: -Appearance.padding.normal
function onClicked(): void {
const wasExpanded = borderSection.expanded;
root.collapseAllSections(borderSection);
borderSection.expanded = !wasExpanded;
}
}
}
}
StyledRect {
visible: borderSection.expanded
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.small / 2
implicitHeight: borderSection.expanded ? borderRoundingRow.implicitHeight + Appearance.padding.large * 2 : 0
radius: Appearance.rounding.normal
color: Colours.tPalette.m3surfaceContainer
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: borderRoundingRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.normal
StyledText {
Layout.fillWidth: true
text: qsTr("Border rounding")
}
CustomSpinBox {
id: borderRoundingSpinBox
min: 0.1
max: 5
value: root.borderRounding
onValueModified: value => {
root.borderRounding = value;
root.saveConfig();
}
}
}
}
StyledRect {
visible: borderSection.expanded
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.small / 2
implicitHeight: borderSection.expanded ? borderThicknessRow.implicitHeight + Appearance.padding.large * 2 : 0
radius: Appearance.rounding.normal
color: Colours.tPalette.m3surfaceContainer
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: borderThicknessRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.normal
StyledText {
Layout.fillWidth: true
text: qsTr("Border thickness")
}
CustomSpinBox {
id: borderThicknessSpinBox
min: 0.1
max: 5
value: root.borderThickness
onValueModified: value => {
root.borderThickness = value;
root.saveConfig();
}
}
}
}
Item {
id: backgroundSection
Layout.fillWidth: true
Layout.preferredHeight: backgroundSectionHeader.implicitHeight
property bool expanded: false
ColumnLayout {
id: backgroundSectionHeader
anchors.left: parent.left
anchors.right: parent.right
spacing: Appearance.spacing.small
RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.small
StyledText {
Layout.topMargin: Appearance.spacing.large
text: qsTr("Background")
font.pointSize: Appearance.font.size.larger
font.weight: 500
}
Item {
Layout.fillWidth: true
}
MaterialIcon {
text: "expand_more"
rotation: backgroundSection.expanded ? 180 : 0
color: Colours.palette.m3onSurface
Behavior on rotation {
Anim {}
}
}
}
StateLayer {
anchors.fill: parent
anchors.leftMargin: -Appearance.padding.normal
anchors.rightMargin: -Appearance.padding.normal
function onClicked(): void {
const wasExpanded = backgroundSection.expanded;
root.collapseAllSections(backgroundSection);
backgroundSection.expanded = !wasExpanded;
}
}
}
}
StyledRect {
visible: backgroundSection.expanded
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.small / 2
implicitHeight: backgroundSection.expanded ? desktopClockRow.implicitHeight + Appearance.padding.large * 2 : 0
radius: Appearance.rounding.normal
color: Colours.tPalette.m3surfaceContainer
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: desktopClockRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.normal
StyledText {
Layout.fillWidth: true
text: qsTr("Desktop clock")
}
StyledSwitch {
id: desktopClockSwitch
checked: root.desktopClockEnabled
onToggled: {
root.desktopClockEnabled = checked;
root.saveConfig();
}
}
}
}
StyledRect {
visible: backgroundSection.expanded
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.small / 2
implicitHeight: backgroundSection.expanded ? backgroundEnabledRow.implicitHeight + Appearance.padding.large * 2 : 0
radius: Appearance.rounding.normal
color: Colours.tPalette.m3surfaceContainer
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: backgroundEnabledRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.normal
StyledText {
Layout.fillWidth: true
text: qsTr("Background enabled")
}
StyledSwitch {
id: backgroundEnabledSwitch
checked: root.backgroundEnabled
onToggled: {
root.backgroundEnabled = checked;
root.saveConfig();
}
}
}
}
StyledText {
visible: backgroundSection.expanded
Layout.topMargin: Appearance.spacing.normal
text: qsTr("Visualiser")
font.pointSize: Appearance.font.size.larger
font.weight: 500
}
StyledRect {
visible: backgroundSection.expanded
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.small / 2
implicitHeight: backgroundSection.expanded ? visualiserEnabledRow.implicitHeight + Appearance.padding.large * 2 : 0
radius: Appearance.rounding.normal
color: Colours.tPalette.m3surfaceContainer
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: visualiserEnabledRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.normal
StyledText {
Layout.fillWidth: true
text: qsTr("Visualiser enabled")
}
StyledSwitch {
id: visualiserEnabledSwitch
checked: root.visualiserEnabled
onToggled: {
root.visualiserEnabled = checked;
root.saveConfig();
}
}
}
}
StyledRect {
visible: backgroundSection.expanded
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.small / 2
implicitHeight: backgroundSection.expanded ? visualiserAutoHideRow.implicitHeight + Appearance.padding.large * 2 : 0
radius: Appearance.rounding.normal
color: Colours.tPalette.m3surfaceContainer
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: visualiserAutoHideRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.normal
StyledText {
Layout.fillWidth: true
text: qsTr("Visualiser auto hide")
}
StyledSwitch {
id: visualiserAutoHideSwitch
checked: root.visualiserAutoHide
onToggled: {
root.visualiserAutoHide = checked;
root.saveConfig();
}
}
}
}
StyledRect {
visible: backgroundSection.expanded
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.small / 2
implicitHeight: backgroundSection.expanded ? visualiserRoundingRow.implicitHeight + Appearance.padding.large * 2 : 0
radius: Appearance.rounding.normal
color: Colours.tPalette.m3surfaceContainer
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: visualiserRoundingRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.normal
StyledText {
Layout.fillWidth: true
text: qsTr("Visualiser rounding")
}
CustomSpinBox {
id: visualiserRoundingSpinBox
min: 0
max: 10
value: Math.round(root.visualiserRounding)
onValueModified: value => {
root.visualiserRounding = value;
root.saveConfig();
}
}
}
}
StyledRect {
visible: backgroundSection.expanded
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.small / 2
implicitHeight: backgroundSection.expanded ? visualiserSpacingRow.implicitHeight + Appearance.padding.large * 2 : 0
radius: Appearance.rounding.normal
color: Colours.tPalette.m3surfaceContainer
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: visualiserSpacingRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.normal
StyledText {
Layout.fillWidth: true
text: qsTr("Visualiser spacing")
}
CustomSpinBox {
id: visualiserSpacingSpinBox
min: 0
max: 10
value: Math.round(root.visualiserSpacing)
onValueModified: value => {
root.visualiserSpacing = value;
root.saveConfig();
}
}
}
}
}
}
InnerBorder {
leftThickness: 0
rightThickness: Appearance.padding.normal / 2
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
StyledFlickable {
anchors.fill: parent
anchors.margins: Appearance.padding.large * 2
flickableDirection: Flickable.VerticalFlick
contentHeight: contentLayout.implicitHeight
StyledScrollBar.vertical: StyledScrollBar {
flickable: parent
}
ColumnLayout {
id: contentLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
spacing: Appearance.spacing.normal
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
text: "palette"
font.pointSize: Appearance.font.size.extraLarge * 3
font.bold: true
}
StyledText {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Appearance settings")
font.pointSize: Appearance.font.size.large
font.bold: true
}
StyledText {
Layout.topMargin: Appearance.spacing.large
Layout.alignment: Qt.AlignHCenter
text: qsTr("Theme mode")
font.pointSize: Appearance.font.size.larger
font.weight: 500
}
StyledText {
Layout.alignment: Qt.AlignHCenter
text: Colours.currentLight ? qsTr("Light mode") : qsTr("Dark mode")
color: Colours.palette.m3outline
}
StyledText {
Layout.topMargin: Appearance.spacing.large
Layout.alignment: Qt.AlignHCenter
text: qsTr("Wallpaper")
font.pointSize: Appearance.font.size.larger
font.weight: 500
}
StyledText {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Select a wallpaper")
color: Colours.palette.m3outline
}
GridLayout {
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.normal
Layout.alignment: Qt.AlignHCenter
columns: Math.max(1, Math.floor(parent.width / 200))
rowSpacing: Appearance.spacing.normal
columnSpacing: Appearance.spacing.normal
// Center the grid content
Layout.maximumWidth: {
const cols = columns;
const itemWidth = 180;
const spacing = columnSpacing;
return cols * itemWidth + (cols - 1) * spacing;
}
Repeater {
model: Wallpapers.list
delegate: Item {
required property var modelData
Layout.preferredWidth: 180
Layout.preferredHeight: 120
Layout.minimumWidth: 180
Layout.minimumHeight: 120
readonly property bool isCurrent: modelData.path === Wallpapers.actualCurrent
readonly property real imageWidth: Math.max(1, width)
readonly property real imageHeight: Math.max(1, height)
StateLayer {
radius: Appearance.rounding.normal
function onClicked(): void {
Wallpapers.setWallpaper(modelData.path);
}
}
StyledClippingRect {
id: image
anchors.fill: parent
color: Colours.tPalette.m3surfaceContainer
radius: Appearance.rounding.normal
border.width: isCurrent ? 2 : 0
border.color: Colours.palette.m3primary
CachingImage {
id: cachingImage
path: modelData.path
anchors.fill: parent
// Ensure sourceSize is always set to valid dimensions
sourceSize: Qt.size(
Math.max(1, Math.floor(parent.width)),
Math.max(1, Math.floor(parent.height))
)
// Show when ready, hide if fallback is showing
opacity: status === Image.Ready && !fallbackImage.visible ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: 200
}
}
}
// Fallback: Direct image load if caching fails or is slow
Image {
id: fallbackImage
anchors.fill: parent
source: modelData.path
asynchronous: true
fillMode: Image.PreserveAspectCrop
sourceSize: Qt.size(
Math.max(1, Math.floor(parent.width)),
Math.max(1, Math.floor(parent.height))
)
// Show if caching image hasn't loaded after a delay
visible: opacity > 0
opacity: 0
Timer {
id: fallbackTimer
interval: 500
running: cachingImage.status === Image.Loading || (cachingImage.status !== Image.Ready && cachingImage.status !== Image.Null)
onTriggered: {
if (cachingImage.status !== Image.Ready && fallbackImage.status === Image.Ready) {
fallbackImage.opacity = 1;
}
}
}
// Also check status changes
onStatusChanged: {
if (status === Image.Ready && cachingImage.status !== Image.Ready) {
Qt.callLater(() => {
if (cachingImage.status !== Image.Ready) {
fallbackImage.opacity = 1;
}
});
}
}
Behavior on opacity {
NumberAnimation {
duration: 200
}
}
}
MaterialIcon {
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Appearance.padding.small
visible: isCurrent
text: "check_circle"
color: Colours.palette.m3primary
font.pointSize: Appearance.font.size.large
}
}
StyledText {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: Appearance.padding.small
text: modelData.relativePath
font.pointSize: Appearance.font.size.small
color: isCurrent ? Colours.palette.m3primary : Colours.palette.m3onSurface
elide: Text.ElideRight
}
}
}
}
}
}
InnerBorder {
leftThickness: Appearance.padding.normal / 2
}
}
}