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

1044 lines
51 KiB
QML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

pragma ComponentBehavior: Bound
import ".."
import "../components"
import "./sections"
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.Widgets
import QtQuick
import QtQuick.Layouts
Item {
id: root
required property Session session
property real animDurationsScale: Config.appearance.anim.durations.scale ?? 1
property string fontFamilyMaterial: Config.appearance.font.family.material ?? "Material Symbols Rounded"
property string fontFamilyMono: Config.appearance.font.family.mono ?? "CaskaydiaCove NF"
property string fontFamilySans: Config.appearance.font.family.sans ?? "Rubik"
property real fontSizeScale: Config.appearance.font.size.scale ?? 1
property real paddingScale: Config.appearance.padding.scale ?? 1
property real roundingScale: Config.appearance.rounding.scale ?? 1
property real spacingScale: Config.appearance.spacing.scale ?? 1
property bool transparencyEnabled: Config.appearance.transparency.enabled ?? false
property real transparencyBase: Config.appearance.transparency.base ?? 0.85
property real transparencyLayers: Config.appearance.transparency.layers ?? 0.4
property real borderRounding: Config.border.rounding ?? 1
property real borderThickness: Config.border.thickness ?? 1
property bool desktopClockEnabled: Config.background.desktopClock.enabled ?? false
property bool backgroundEnabled: Config.background.enabled ?? true
property bool visualiserEnabled: Config.background.visualiser.enabled ?? false
property bool visualiserAutoHide: Config.background.visualiser.autoHide ?? true
property real visualiserRounding: Config.background.visualiser.rounding ?? 1
property real visualiserSpacing: Config.background.visualiser.spacing ?? 1
anchors.fill: parent
function saveConfig() {
Config.appearance.anim.durations.scale = root.animDurationsScale;
Config.appearance.font.family.material = root.fontFamilyMaterial;
Config.appearance.font.family.mono = root.fontFamilyMono;
Config.appearance.font.family.sans = root.fontFamilySans;
Config.appearance.font.size.scale = root.fontSizeScale;
Config.appearance.padding.scale = root.paddingScale;
Config.appearance.rounding.scale = root.roundingScale;
Config.appearance.spacing.scale = root.spacingScale;
Config.appearance.transparency.enabled = root.transparencyEnabled;
Config.appearance.transparency.base = root.transparencyBase;
Config.appearance.transparency.layers = root.transparencyLayers;
Config.background.desktopClock.enabled = root.desktopClockEnabled;
Config.background.enabled = root.backgroundEnabled;
Config.background.visualiser.enabled = root.visualiserEnabled;
Config.background.visualiser.autoHide = root.visualiserAutoHide;
Config.background.visualiser.rounding = root.visualiserRounding;
Config.background.visualiser.spacing = root.visualiserSpacing;
Config.border.rounding = root.borderRounding;
Config.border.thickness = root.borderThickness;
Config.save();
}
Component {
id: appearanceRightContentComponent
StyledFlickable {
id: rightAppearanceFlickable
flickableDirection: Flickable.VerticalFlick
contentHeight: contentLayout.height
StyledScrollBar.vertical: StyledScrollBar {
flickable: rightAppearanceFlickable
}
ColumnLayout {
id: contentLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
spacing: Appearance.spacing.normal
SettingsHeader {
icon: "palette"
title: qsTr("Appearance Settings")
}
StyledText {
Layout.topMargin: Appearance.spacing.large
Layout.alignment: Qt.AlignHCenter
text: qsTr("Wallpaper")
font.pointSize: Appearance.font.size.extraLarge
font.weight: 600
}
StyledText {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Select a wallpaper")
font.pointSize: Appearance.font.size.normal
color: Colours.palette.m3onSurfaceVariant
}
Item {
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.large
Layout.preferredHeight: wallpaperLoader.item ? wallpaperLoader.item.layoutPreferredHeight : 0
Loader {
id: wallpaperLoader
anchors.fill: parent
asynchronous: true
active: {
const isActive = root.session.activeIndex === 3;
const isAdjacent = Math.abs(root.session.activeIndex - 3) === 1;
const splitLayout = root.children[0];
const loader = splitLayout && splitLayout.rightLoader ? splitLayout.rightLoader : null;
const shouldActivate = loader && loader.item !== null && (isActive || isAdjacent);
return shouldActivate;
}
onStatusChanged: {
if (status === Loader.Error) {
console.error("[AppearancePane] Wallpaper loader error!");
}
}
// Stop lazy loading when loader becomes inactive
onActiveChanged: {
if (!active && wallpaperLoader.item) {
const container = wallpaperLoader.item;
if (container && container.wallpaperGrid) {
const grid = container.wallpaperGrid;
if (grid.imageUpdateTimer) {
grid.imageUpdateTimer.stop();
}
}
}
}
sourceComponent: Item {
id: wallpaperGridContainer
property alias layoutPreferredHeight: wallpaperGrid.layoutPreferredHeight
// Find and store reference to parent Flickable for scroll monitoring
property var parentFlickable: {
let item = parent;
while (item) {
if (item.flickableDirection !== undefined) {
return item;
}
item = item.parent;
}
return null;
}
// Cleanup when component is destroyed
Component.onDestruction: {
if (wallpaperGrid) {
if (wallpaperGrid.scrollCheckTimer) {
wallpaperGrid.scrollCheckTimer.stop();
}
wallpaperGrid._expansionInProgress = false;
}
}
QtObject {
id: lazyModel
property var sourceList: null
property int loadedCount: 0
property int visibleCount: 0
property int totalCount: 0
function initialize(list) {
sourceList = list;
totalCount = list ? list.length : 0;
const initialRows = 3;
const cols = wallpaperGrid.columnsCount > 0 ? wallpaperGrid.columnsCount : 3;
const initialCount = Math.min(initialRows * cols, totalCount);
loadedCount = initialCount;
visibleCount = initialCount;
}
function loadOneRow() {
if (loadedCount < totalCount) {
const cols = wallpaperGrid.columnsCount > 0 ? wallpaperGrid.columnsCount : 1;
const itemsToLoad = Math.min(cols, totalCount - loadedCount);
loadedCount += itemsToLoad;
}
}
function updateVisibleCount(neededCount) {
const cols = wallpaperGrid.columnsCount > 0 ? wallpaperGrid.columnsCount : 1;
const maxVisible = Math.min(neededCount, loadedCount);
const rows = Math.ceil(maxVisible / cols);
const newVisibleCount = Math.min(rows * cols, loadedCount);
if (newVisibleCount > visibleCount) {
visibleCount = newVisibleCount;
}
}
}
GridView {
id: wallpaperGrid
anchors.fill: parent
property int _delegateCount: 0
readonly property int minCellWidth: 200 + Appearance.spacing.normal
readonly property int columnsCount: Math.max(1, Math.floor(parent.width / minCellWidth))
readonly property int layoutPreferredHeight: {
if (!lazyModel || lazyModel.visibleCount === 0 || columnsCount === 0) {
return 0;
}
const calculated = Math.ceil(lazyModel.visibleCount / columnsCount) * cellHeight;
return calculated;
}
height: layoutPreferredHeight
cellWidth: width / columnsCount
cellHeight: 140 + Appearance.spacing.normal
leftMargin: 0
rightMargin: 0
topMargin: 0
bottomMargin: 0
ListModel {
id: wallpaperListModel
}
model: wallpaperListModel
Connections {
target: lazyModel
function onVisibleCountChanged(): void {
if (!lazyModel || !lazyModel.sourceList) return;
const newCount = lazyModel.visibleCount;
const currentCount = wallpaperListModel.count;
if (newCount > currentCount) {
const flickable = wallpaperGridContainer.parentFlickable;
const oldScrollY = flickable ? flickable.contentY : 0;
for (let i = currentCount; i < newCount; i++) {
wallpaperListModel.append({modelData: lazyModel.sourceList[i]});
}
if (flickable) {
Qt.callLater(function() {
if (Math.abs(flickable.contentY - oldScrollY) < 1) {
flickable.contentY = oldScrollY;
}
});
}
}
}
}
Component.onCompleted: {
Qt.callLater(function() {
const isActive = root.session.activeIndex === 3;
if (width > 0 && parent && parent.visible && isActive && Wallpapers.list) {
lazyModel.initialize(Wallpapers.list);
wallpaperListModel.clear();
for (let i = 0; i < lazyModel.visibleCount; i++) {
wallpaperListModel.append({modelData: lazyModel.sourceList[i]});
}
}
});
}
Connections {
target: root.session
function onActiveIndexChanged(): void {
const isActive = root.session.activeIndex === 3;
// Stop lazy loading when switching away from appearance pane
if (!isActive) {
if (scrollCheckTimer) {
scrollCheckTimer.stop();
}
if (wallpaperGrid) {
wallpaperGrid._expansionInProgress = false;
}
return;
}
// Initialize if needed when switching to appearance pane
if (isActive && width > 0 && !lazyModel.sourceList && parent && parent.visible && Wallpapers.list) {
lazyModel.initialize(Wallpapers.list);
wallpaperListModel.clear();
for (let i = 0; i < lazyModel.visibleCount; i++) {
wallpaperListModel.append({modelData: lazyModel.sourceList[i]});
}
}
}
}
onWidthChanged: {
const isActive = root.session.activeIndex === 3;
if (width > 0 && !lazyModel.sourceList && parent && parent.visible && isActive && Wallpapers.list) {
lazyModel.initialize(Wallpapers.list);
wallpaperListModel.clear();
for (let i = 0; i < lazyModel.visibleCount; i++) {
wallpaperListModel.append({modelData: lazyModel.sourceList[i]});
}
}
}
// Force true lazy loading: only create delegates for visible items
displayMarginBeginning: 0
displayMarginEnd: 0
cacheBuffer: 0
// Debounce expansion to avoid too frequent checks
property bool _expansionInProgress: false
Connections {
target: wallpaperGridContainer.parentFlickable
function onContentYChanged(): void {
// Don't process scroll events if appearance pane is not active
const isActive = root.session.activeIndex === 3;
if (!isActive) return;
if (!lazyModel || !lazyModel.sourceList || lazyModel.loadedCount >= lazyModel.totalCount || wallpaperGrid._expansionInProgress) {
return;
}
const flickable = wallpaperGridContainer.parentFlickable;
if (!flickable) return;
const gridY = wallpaperGridContainer.y;
const scrollY = flickable.contentY;
const viewportHeight = flickable.height;
const topY = scrollY - gridY;
const bottomY = scrollY + viewportHeight - gridY;
if (bottomY < 0) return;
const topRow = Math.max(0, Math.floor(topY / wallpaperGrid.cellHeight));
const bottomRow = Math.floor(bottomY / wallpaperGrid.cellHeight);
// Update visible count with 1 row buffer ahead
const bufferRows = 1;
const neededBottomRow = bottomRow + bufferRows;
const neededCount = Math.min((neededBottomRow + 1) * wallpaperGrid.columnsCount, lazyModel.loadedCount);
lazyModel.updateVisibleCount(neededCount);
const loadedRows = Math.ceil(lazyModel.loadedCount / wallpaperGrid.columnsCount);
const rowsRemaining = loadedRows - (bottomRow + 1);
if (rowsRemaining <= 1 && lazyModel.loadedCount < lazyModel.totalCount) {
if (!wallpaperGrid._expansionInProgress) {
wallpaperGrid._expansionInProgress = true;
lazyModel.loadOneRow();
Qt.callLater(function() {
wallpaperGrid._expansionInProgress = false;
});
}
}
}
}
// Fallback timer to check scroll position periodically
Timer {
id: scrollCheckTimer
interval: 100
running: {
const isActive = root.session.activeIndex === 3;
return isActive && lazyModel && lazyModel.sourceList && lazyModel.loadedCount < lazyModel.totalCount;
}
repeat: true
onTriggered: {
// Double-check that appearance pane is still active
const isActive = root.session.activeIndex === 3;
if (!isActive) {
stop();
return;
}
const flickable = wallpaperGridContainer.parentFlickable;
if (!flickable || !lazyModel || !lazyModel.sourceList) return;
const gridY = wallpaperGridContainer.y;
const scrollY = flickable.contentY;
const viewportHeight = flickable.height;
const topY = scrollY - gridY;
const bottomY = scrollY + viewportHeight - gridY;
if (bottomY < 0) return;
const topRow = Math.max(0, Math.floor(topY / wallpaperGrid.cellHeight));
const bottomRow = Math.floor(bottomY / wallpaperGrid.cellHeight);
const bufferRows = 1;
const neededBottomRow = bottomRow + bufferRows;
const neededCount = Math.min((neededBottomRow + 1) * wallpaperGrid.columnsCount, lazyModel.loadedCount);
lazyModel.updateVisibleCount(neededCount);
const loadedRows = Math.ceil(lazyModel.loadedCount / wallpaperGrid.columnsCount);
const rowsRemaining = loadedRows - (bottomRow + 1);
if (rowsRemaining <= 1 && lazyModel.loadedCount < lazyModel.totalCount) {
if (!wallpaperGrid._expansionInProgress) {
wallpaperGrid._expansionInProgress = true;
lazyModel.loadOneRow();
Qt.callLater(function() {
wallpaperGrid._expansionInProgress = false;
});
}
}
}
}
interactive: false
delegate: Item {
required property var modelData
width: wallpaperGrid.cellWidth
height: wallpaperGrid.cellHeight
readonly property bool isCurrent: modelData.path === Wallpapers.actualCurrent
readonly property real itemMargin: Appearance.spacing.normal / 2
readonly property real itemRadius: Appearance.rounding.normal
Component.onCompleted: {
wallpaperGrid._delegateCount++;
}
StateLayer {
anchors.fill: parent
anchors.leftMargin: itemMargin
anchors.rightMargin: itemMargin
anchors.topMargin: itemMargin
anchors.bottomMargin: itemMargin
radius: itemRadius
function onClicked(): void {
Wallpapers.setWallpaper(modelData.path);
}
}
StyledClippingRect {
id: image
anchors.fill: parent
anchors.leftMargin: itemMargin
anchors.rightMargin: itemMargin
anchors.topMargin: itemMargin
anchors.bottomMargin: itemMargin
color: Colours.tPalette.m3surfaceContainer
radius: itemRadius
antialiasing: true
layer.enabled: true
layer.smooth: true
CachingImage {
id: cachingImage
path: modelData.path
anchors.fill: parent
fillMode: Image.PreserveAspectCrop
cache: true
visible: opacity > 0
antialiasing: true
smooth: true
opacity: status === Image.Ready ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: 1000
easing.type: Easing.OutQuad
}
}
}
// Fallback if CachingImage fails to load
Image {
id: fallbackImage
anchors.fill: parent
source: fallbackTimer.triggered && cachingImage.status !== Image.Ready ? modelData.path : ""
asynchronous: true
fillMode: Image.PreserveAspectCrop
cache: true
visible: opacity > 0
antialiasing: true
smooth: true
opacity: status === Image.Ready && cachingImage.status !== Image.Ready ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: 1000
easing.type: Easing.OutQuad
}
}
}
Timer {
id: fallbackTimer
property bool triggered: false
interval: 800
running: cachingImage.status === Image.Loading || cachingImage.status === Image.Null
onTriggered: triggered = true
}
// Gradient overlay for filename
Rectangle {
id: filenameOverlay
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
implicitHeight: filenameText.implicitHeight + Appearance.padding.normal * 1.5
radius: 0
gradient: Gradient {
GradientStop {
position: 0.0
color: Qt.rgba(Colours.palette.m3surfaceContainer.r,
Colours.palette.m3surfaceContainer.g,
Colours.palette.m3surfaceContainer.b, 0)
}
GradientStop {
position: 0.3
color: Qt.rgba(Colours.palette.m3surfaceContainer.r,
Colours.palette.m3surfaceContainer.g,
Colours.palette.m3surfaceContainer.b, 0.7)
}
GradientStop {
position: 0.6
color: Qt.rgba(Colours.palette.m3surfaceContainer.r,
Colours.palette.m3surfaceContainer.g,
Colours.palette.m3surfaceContainer.b, 0.9)
}
GradientStop {
position: 1.0
color: Qt.rgba(Colours.palette.m3surfaceContainer.r,
Colours.palette.m3surfaceContainer.g,
Colours.palette.m3surfaceContainer.b, 0.95)
}
}
opacity: 0
Behavior on opacity {
NumberAnimation {
duration: 1000
easing.type: Easing.OutCubic
}
}
Component.onCompleted: {
opacity = 1;
}
}
}
Rectangle {
anchors.fill: parent
anchors.leftMargin: itemMargin
anchors.rightMargin: itemMargin
anchors.topMargin: itemMargin
anchors.bottomMargin: itemMargin
color: "transparent"
radius: itemRadius + border.width
border.width: isCurrent ? 2 : 0
border.color: Colours.palette.m3primary
antialiasing: true
smooth: true
Behavior on border.width {
NumberAnimation {
duration: 150
easing.type: Easing.OutQuad
}
}
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 {
id: filenameText
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.leftMargin: Appearance.padding.normal + Appearance.spacing.normal / 2
anchors.rightMargin: Appearance.padding.normal + Appearance.spacing.normal / 2
anchors.bottomMargin: Appearance.padding.normal
readonly property string fileName: {
const path = modelData.relativePath || "";
const parts = path.split("/");
return parts.length > 0 ? parts[parts.length - 1] : path;
}
text: fileName
font.pointSize: Appearance.font.size.smaller
font.weight: 500
color: isCurrent ? Colours.palette.m3primary : Colours.palette.m3onSurface
elide: Text.ElideMiddle
maximumLineCount: 1
horizontalAlignment: Text.AlignHCenter
opacity: 0
Behavior on opacity {
NumberAnimation {
duration: 1000
easing.type: Easing.OutCubic
}
}
Component.onCompleted: {
opacity = 1;
}
}
}
}
}
}
}
}
}
}
SplitPaneLayout {
anchors.fill: parent
leftContent: Component {
StyledFlickable {
id: sidebarFlickable
readonly property var rootPane: root
flickableDirection: Flickable.VerticalFlick
contentHeight: sidebarLayout.height
StyledScrollBar.vertical: StyledScrollBar {
flickable: sidebarFlickable
}
ColumnLayout {
id: sidebarLayout
anchors.left: parent.left
anchors.right: parent.right
spacing: Appearance.spacing.small
readonly property var rootPane: sidebarFlickable.rootPane
readonly property bool allSectionsExpanded:
themeModeSection.expanded &&
colorVariantSection.expanded &&
colorSchemeSection.expanded &&
animationsSection.expanded &&
fontsSection.expanded &&
scalesSection.expanded &&
transparencySection.expanded &&
borderSection.expanded &&
backgroundSection.expanded
RowLayout {
spacing: Appearance.spacing.smaller
StyledText {
text: qsTr("Appearance")
font.pointSize: Appearance.font.size.large
font.weight: 500
}
Item {
Layout.fillWidth: true
}
IconButton {
icon: sidebarLayout.allSectionsExpanded ? "unfold_less" : "unfold_more"
type: IconButton.Text
label.animate: true
onClicked: {
const shouldExpand = !sidebarLayout.allSectionsExpanded;
themeModeSection.expanded = shouldExpand;
colorVariantSection.expanded = shouldExpand;
colorSchemeSection.expanded = shouldExpand;
animationsSection.expanded = shouldExpand;
fontsSection.expanded = shouldExpand;
scalesSection.expanded = shouldExpand;
transparencySection.expanded = shouldExpand;
borderSection.expanded = shouldExpand;
backgroundSection.expanded = shouldExpand;
}
}
}
ThemeModeSection {
id: themeModeSection
}
ColorVariantSection {
id: colorVariantSection
}
ColorSchemeSection {
id: colorSchemeSection
}
AnimationsSection {
id: animationsSection
rootPane: sidebarFlickable.rootPane
}
CollapsibleSection {
id: fontsSection
title: qsTr("Fonts")
showBackground: true
CollapsibleSection {
id: materialFontSection
title: qsTr("Material font family")
expanded: true
showBackground: true
nested: true
Loader {
id: materialFontLoader
Layout.fillWidth: true
Layout.preferredHeight: item ? Math.min(item.contentHeight, 300) : 0
asynchronous: true
active: materialFontSection.expanded
sourceComponent: StyledListView {
id: materialFontList
property alias contentHeight: materialFontList.contentHeight
clip: true
spacing: Appearance.spacing.small / 2
model: Qt.fontFamilies()
StyledScrollBar.vertical: StyledScrollBar {
flickable: materialFontList
}
delegate: StyledRect {
required property string modelData
required property int index
width: ListView.view.width
readonly property bool isCurrent: modelData === rootPane.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 {
rootPane.fontFamilyMaterial = modelData;
rootPane.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
}
}
}
}
CollapsibleSection {
id: monoFontSection
title: qsTr("Monospace font family")
expanded: false
showBackground: true
nested: true
Loader {
Layout.fillWidth: true
Layout.preferredHeight: item ? Math.min(item.contentHeight, 300) : 0
asynchronous: true
active: monoFontSection.expanded
sourceComponent: StyledListView {
id: monoFontList
property alias contentHeight: monoFontList.contentHeight
clip: true
spacing: Appearance.spacing.small / 2
model: Qt.fontFamilies()
StyledScrollBar.vertical: StyledScrollBar {
flickable: monoFontList
}
delegate: StyledRect {
required property string modelData
required property int index
width: ListView.view.width
readonly property bool isCurrent: modelData === rootPane.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 {
rootPane.fontFamilyMono = modelData;
rootPane.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
}
}
}
}
CollapsibleSection {
id: sansFontSection
title: qsTr("Sans-serif font family")
expanded: false
showBackground: true
nested: true
Loader {
Layout.fillWidth: true
Layout.preferredHeight: item ? Math.min(item.contentHeight, 300) : 0
asynchronous: true
active: sansFontSection.expanded
sourceComponent: StyledListView {
id: sansFontList
property alias contentHeight: sansFontList.contentHeight
clip: true
spacing: Appearance.spacing.small / 2
model: Qt.fontFamilies()
StyledScrollBar.vertical: StyledScrollBar {
flickable: sansFontList
}
delegate: StyledRect {
required property string modelData
required property int index
width: ListView.view.width
readonly property bool isCurrent: modelData === rootPane.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 {
rootPane.fontFamilySans = modelData;
rootPane.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
}
}
}
}
SectionContainer {
contentSpacing: Appearance.spacing.normal
SliderInput {
Layout.fillWidth: true
label: qsTr("Font size scale")
value: rootPane.fontSizeScale
from: 0.7
to: 1.5
decimals: 2
suffix: "×"
validator: DoubleValidator { bottom: 0.7; top: 1.5 }
onValueModified: (newValue) => {
rootPane.fontSizeScale = newValue;
rootPane.saveConfig();
}
}
}
}
ScalesSection {
id: scalesSection
rootPane: sidebarFlickable.rootPane
}
TransparencySection {
id: transparencySection
rootPane: sidebarFlickable.rootPane
}
BorderSection {
id: borderSection
rootPane: sidebarFlickable.rootPane
}
BackgroundSection {
id: backgroundSection
rootPane: sidebarFlickable.rootPane
}
}
}
}
rightContent: appearanceRightContentComponent
}
}