首先我加了CI课程表json的读取,然后把天气时钟这个老问题也修了。
This commit is contained in:
lincube
2026-03-22 04:57:19 +08:00
parent 1a7dde34d0
commit c3db5af923
19 changed files with 646 additions and 67 deletions

View File

@@ -38,6 +38,27 @@
"settings.wallpaper.title": "Wallpaper",
"settings.wallpaper.description": "Pick an image or video to apply as the app window wallpaper immediately.",
"settings.wallpaper.current_label": "Current Wallpaper",
"settings.wallpaper.type_label": "Wallpaper Type",
"settings.wallpaper.type.image": "Image",
"settings.wallpaper.type.solid_color": "Solid Color",
"settings.wallpaper.type.system": "System Wallpaper",
"settings.wallpaper.system.label": "System Wallpaper",
"settings.wallpaper.system.unavailable": "Unable to read system wallpaper",
"settings.wallpaper.refresh_interval": "Refresh Interval",
"settings.wallpaper.refresh_now": "Refresh Now",
"settings.wallpaper.refresh.30s": "30 seconds",
"settings.wallpaper.refresh.1m": "1 minute",
"settings.wallpaper.refresh.5m": "5 minutes",
"settings.wallpaper.refresh.10m": "10 minutes",
"settings.wallpaper.refresh.15m": "15 minutes",
"settings.wallpaper.refresh.30m": "30 minutes",
"settings.wallpaper.refresh.1h": "1 hour",
"settings.wallpaper.refresh.2h": "2 hours",
"settings.wallpaper.refresh.4h": "4 hours",
"settings.wallpaper.refresh.8h": "8 hours",
"settings.wallpaper.refresh.12h": "12 hours",
"settings.wallpaper.refresh.24h": "24 hours",
"settings.wallpaper.color_label": "Wallpaper Color",
"settings.wallpaper.placement_label": "Placement",
"settings.wallpaper.placement_desc": "Adjust how the image fills the desktop.",
"settings.wallpaper.pick_button": "Browse Files",
@@ -217,7 +238,14 @@
"schedule.settings.unnamed": "Unnamed Schedule",
"schedule.settings.delete": "Delete",
"schedule.settings.picker_title": "Select ClassIsland schedule file",
"schedule.settings.picker_file_type": "ClassIsland CSES schedule",
"schedule.settings.picker_file_type.all": "ClassIsland Schedule Files",
"schedule.settings.picker_file_type.json": "ClassIsland Profile (JSON)",
"schedule.settings.picker_file_type.cses": "CSES Schedule (YAML)",
"schedule.settings.semester.title": "Semester Settings",
"schedule.settings.semester.start_date": "Semester Start Date",
"schedule.settings.semester.week_cycle": "Week Cycle",
"schedule.settings.semester.week_cycle_desc": "Set the week rotation cycle for multi-week schedules (e.g., 2 for odd/even weeks).",
"schedule.settings.semester.week_cycle_format": "{0}-week rotation",
"worldclock.settings.title": "World Clock Settings",
"worldclock.settings.desc": "Choose a time zone for each of the four clocks.",
"worldclock.settings.clock_1": "Clock 1",

View File

@@ -41,6 +41,23 @@
"settings.wallpaper.type_label": "壁纸类型",
"settings.wallpaper.type.image": "图片",
"settings.wallpaper.type.solid_color": "纯色",
"settings.wallpaper.type.system": "系统壁纸",
"settings.wallpaper.system.label": "系统壁纸",
"settings.wallpaper.system.unavailable": "无法读取系统壁纸",
"settings.wallpaper.refresh_interval": "刷新频率",
"settings.wallpaper.refresh_now": "立即刷新",
"settings.wallpaper.refresh.30s": "30 秒",
"settings.wallpaper.refresh.1m": "1 分钟",
"settings.wallpaper.refresh.5m": "5 分钟",
"settings.wallpaper.refresh.10m": "10 分钟",
"settings.wallpaper.refresh.15m": "15 分钟",
"settings.wallpaper.refresh.30m": "30 分钟",
"settings.wallpaper.refresh.1h": "1 小时",
"settings.wallpaper.refresh.2h": "2 小时",
"settings.wallpaper.refresh.4h": "4 小时",
"settings.wallpaper.refresh.8h": "8 小时",
"settings.wallpaper.refresh.12h": "12 小时",
"settings.wallpaper.refresh.24h": "24 小时",
"settings.wallpaper.color_label": "壁纸颜色",
"settings.wallpaper.custom_color_tooltip": "自定义颜色",
"settings.wallpaper.custom_color_apply": "应用",
@@ -216,7 +233,14 @@
"schedule.settings.unnamed": "未命名课表",
"schedule.settings.delete": "删除",
"schedule.settings.picker_title": "选择 ClassIsland 课表文件",
"schedule.settings.picker_file_type": "ClassIsland CSES 课表",
"schedule.settings.picker_file_type.all": "ClassIsland 课表文件",
"schedule.settings.picker_file_type.json": "ClassIsland 档案 (JSON)",
"schedule.settings.picker_file_type.cses": "CSES 课表 (YAML)",
"schedule.settings.semester.title": "学期设置",
"schedule.settings.semester.start_date": "学期开始日期",
"schedule.settings.semester.week_cycle": "周循环",
"schedule.settings.semester.week_cycle_desc": "设置多周课表轮换周期,用于计算当前是第几周。",
"schedule.settings.semester.week_cycle_format": "{0} 周轮换",
"worldclock.settings.title": "世界时钟设置",
"worldclock.settings.desc": "分别为四个时钟选择时区。",
"worldclock.settings.clock_1": "时钟 1",

View File

@@ -33,6 +33,8 @@ public sealed class AppSettingsSnapshot
public string WallpaperPlacement { get; set; } = "Fill";
public int SystemWallpaperRefreshIntervalSeconds { get; set; } = 300;
public int SettingsTabIndex { get; set; } = 0;
public string? SettingsTabTag { get; set; }

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
namespace LanMountainDesktop.Models;
@@ -12,6 +13,10 @@ public sealed class ComponentSettingsSnapshot
public string ActiveImportedClassScheduleId { get; set; } = string.Empty;
public DateOnly? SemesterStartDate { get; set; }
public int SemesterWeekCycle { get; set; } = 1;
public bool StudyEnvironmentShowDisplayDb { get; set; } = true;
public bool StudyEnvironmentShowDbfs { get; set; }

View File

@@ -12,7 +12,7 @@ namespace LanMountainDesktop.Services;
public interface IClassIslandScheduleDataService
{
ClassIslandScheduleReadResult Load(string? inputPath = null, string? profileFileName = null);
ClassIslandScheduleReadResult Load(string? inputPath = null, string? profileFileName = null, DateOnly? semesterStartDate = null, int semesterWeekCycle = 1);
bool TryResolveClassPlanForDate(
ClassIslandScheduleSnapshot snapshot,
@@ -43,7 +43,7 @@ public sealed class ClassIslandScheduleDataService : IClassIslandScheduleDataSer
.IgnoreUnmatchedProperties()
.Build();
public ClassIslandScheduleReadResult Load(string? inputPath = null, string? profileFileName = null)
public ClassIslandScheduleReadResult Load(string? inputPath = null, string? profileFileName = null, DateOnly? semesterStartDate = null, int semesterWeekCycle = 1)
{
var warnings = new List<string>();
try
@@ -73,11 +73,11 @@ public sealed class ClassIslandScheduleDataService : IClassIslandScheduleDataSer
ClassIslandScheduleSnapshot snapshot;
if (source.SourceKind == ScheduleSourceKind.Cses)
{
snapshot = ParseCsesSnapshot(source);
snapshot = ParseCsesSnapshot(source, semesterStartDate, semesterWeekCycle);
}
else
{
var cycleRule = ParseCycleRule(source.SettingsPath, warnings);
var cycleRule = ParseCycleRule(source.SettingsPath, warnings, semesterStartDate, semesterWeekCycle);
var profileJson = ReadJson(source.ProfilePath);
snapshot = ParseProfileSnapshot(profileJson.RootElement, source, cycleRule);
}
@@ -412,22 +412,50 @@ public sealed class ClassIslandScheduleDataService : IClassIslandScheduleDataSer
return null;
}
private static ClassIslandScheduleCycleRule ParseCycleRule(string? settingsPath, List<string> warnings)
private static ClassIslandScheduleCycleRule ParseCycleRule(
string? settingsPath,
List<string> warnings,
DateOnly? semesterStartDate = null,
int semesterWeekCycle = 1)
{
if (string.IsNullOrWhiteSpace(settingsPath) || !File.Exists(settingsPath))
DateOnly? singleWeekStartDate = semesterStartDate;
int maxCycle = semesterWeekCycle > 1 ? semesterWeekCycle : 4;
var offsetList = new List<int> { -1, -1, 0, 0, 0, 0, 0, 0 };
if (!string.IsNullOrWhiteSpace(settingsPath) && File.Exists(settingsPath))
{
warnings.Add("ClassIsland Settings.json not found, using default cycle rule.");
return new ClassIslandScheduleCycleRule(null, 4, new List<int> { -1, -1, 0, 0, 0 });
using var json = ReadJson(settingsPath);
var root = json.RootElement;
if (!singleWeekStartDate.HasValue)
{
singleWeekStartDate = TryReadDateOnly(root, "SingleWeekStartTime");
}
if (semesterWeekCycle <= 1)
{
maxCycle = TryReadInt(root, "MultiWeekRotationMaxCycle", 4);
}
var settingsOffsetList = ReadIntList(root, "MultiWeekRotationOffset");
if (settingsOffsetList.Count >= 2)
{
offsetList = settingsOffsetList;
}
}
else
{
warnings.Add("ClassIsland Settings.json not found, using semester settings from component.");
}
using var json = ReadJson(settingsPath);
var root = json.RootElement;
var singleWeekStartDate = TryReadDateOnly(root, "SingleWeekStartTime");
var maxCycle = TryReadInt(root, "MultiWeekRotationMaxCycle", 4);
var offsetList = ReadIntList(root, "MultiWeekRotationOffset");
if (offsetList.Count < 2)
if (maxCycle < 2)
{
offsetList = new List<int> { -1, -1, 0, 0, 0 };
maxCycle = 2;
}
while (offsetList.Count <= maxCycle)
{
offsetList.Add(0);
}
return new ClassIslandScheduleCycleRule(
@@ -469,7 +497,10 @@ public sealed class ClassIslandScheduleDataService : IClassIslandScheduleDataSer
ClassPlanGroups: groups);
}
private static ClassIslandScheduleSnapshot ParseCsesSnapshot(ResolvedSource source)
private static ClassIslandScheduleSnapshot ParseCsesSnapshot(
ResolvedSource source,
DateOnly? semesterStartDate = null,
int semesterWeekCycle = 1)
{
var yaml = File.ReadAllText(source.ProfilePath);
var csesProfile = CsesDeserializer.Deserialize<CsesProfileDto>(yaml) ?? new CsesProfileDto();
@@ -600,12 +631,19 @@ public sealed class ClassIslandScheduleDataService : IClassIslandScheduleDataSer
[GlobalClassPlanGroupId] = new ClassIslandClassPlanGroup(GlobalClassPlanGroupId, "Global", IsGlobal: true)
};
var maxCycle = semesterWeekCycle > 1 ? semesterWeekCycle : 4;
var offsetList = new List<int> { -1, -1, 0, 0, 0, 0, 0, 0 };
while (offsetList.Count <= maxCycle)
{
offsetList.Add(0);
}
return new ClassIslandScheduleSnapshot(
SourceRootPath: source.SourceRootPath,
ProfilePath: source.ProfilePath,
ProfileFileName: source.ProfileFileName,
LoadedAt: DateTimeOffset.Now,
CycleRule: new ClassIslandScheduleCycleRule(null, 4, new List<int> { -1, -1, 0, 0, 0 }),
CycleRule: new ClassIslandScheduleCycleRule(semesterStartDate, Math.Clamp(maxCycle, 2, 32), offsetList),
SelectedClassPlanGroupId: DefaultClassPlanGroupId,
TempClassPlanGroupId: null,
IsTempClassPlanGroupEnabled: false,

View File

@@ -16,7 +16,13 @@ public enum WallpaperMediaType
}
public sealed record GridSettingsState(int ShortSideCells, string SpacingPreset, int EdgeInsetPercent);
public sealed record WallpaperSettingsState(string? WallpaperPath, string Type, string? Color, string Placement, string? CustomColor = null);
public sealed record WallpaperSettingsState(
string? WallpaperPath,
string Type,
string? Color,
string Placement,
string? CustomColor = null,
int SystemWallpaperRefreshIntervalSeconds = 300);
public sealed record ThemeAppearanceSettingsState(
bool IsNightMode,
string? ThemeColor,

View File

@@ -101,7 +101,9 @@ internal sealed class WallpaperSettingsService : IWallpaperSettingsService
: snapshot.WallpaperPath,
normalizedType,
snapshot.WallpaperColor,
snapshot.WallpaperPlacement);
snapshot.WallpaperPlacement,
CustomColor: null,
SystemWallpaperRefreshIntervalSeconds: NormalizeRefreshInterval(snapshot.SystemWallpaperRefreshIntervalSeconds));
}
public void Save(WallpaperSettingsState state)
@@ -128,6 +130,7 @@ internal sealed class WallpaperSettingsService : IWallpaperSettingsService
snapshot.WallpaperPlacement = string.IsNullOrWhiteSpace(state.Placement)
? "Fill"
: state.Placement.Trim();
snapshot.SystemWallpaperRefreshIntervalSeconds = NormalizeRefreshInterval(state.SystemWallpaperRefreshIntervalSeconds);
_settingsService.SaveSnapshot(
SettingsScope.App,
snapshot,
@@ -136,9 +139,21 @@ internal sealed class WallpaperSettingsService : IWallpaperSettingsService
nameof(AppSettingsSnapshot.WallpaperPath),
nameof(AppSettingsSnapshot.WallpaperType),
nameof(AppSettingsSnapshot.WallpaperColor),
nameof(AppSettingsSnapshot.WallpaperPlacement)
nameof(AppSettingsSnapshot.WallpaperPlacement),
nameof(AppSettingsSnapshot.SystemWallpaperRefreshIntervalSeconds)
]);
}
private static int NormalizeRefreshInterval(int seconds)
{
return seconds switch
{
<= 0 => 300,
< 30 => 30,
> 86400 => 86400,
_ => seconds
};
}
}
internal sealed class WallpaperMediaService : IWallpaperMediaService

View File

@@ -0,0 +1,65 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using Avalonia.Media.Imaging;
using Microsoft.Win32;
namespace LanMountainDesktop.Services;
public interface ISystemWallpaperProvider
{
bool IsSupported { get; }
string? GetWallpaperPath();
event EventHandler? WallpaperChanged;
}
internal sealed class SystemWallpaperProvider : ISystemWallpaperProvider, IDisposable
{
public bool IsSupported => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
public event EventHandler? WallpaperChanged;
public string? GetWallpaperPath()
{
if (!IsSupported)
{
return null;
}
try
{
using var key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Desktop");
var wallpaperPath = key?.GetValue("Wallpaper") as string;
if (string.IsNullOrWhiteSpace(wallpaperPath))
{
return null;
}
if (!File.Exists(wallpaperPath))
{
return null;
}
return wallpaperPath;
}
catch
{
return null;
}
}
public void Dispose()
{
}
}
public static class HostSystemWallpaperProvider
{
private static ISystemWallpaperProvider? _instance;
public static ISystemWallpaperProvider GetOrCreate()
{
return _instance ??= new SystemWallpaperProvider();
}
}

View File

@@ -5,13 +5,13 @@ using Avalonia.Media.Imaging;
namespace LanMountainDesktop.Services;
internal static class WallpaperImageBrushFactory
public static class WallpaperImageBrushFactory
{
internal const string Fill = "Fill";
internal const string Fit = "Fit";
internal const string StretchMode = "Stretch";
internal const string Center = "Center";
internal const string Tile = "Tile";
public const string Fill = "Fill";
public const string Fit = "Fit";
public const string StretchMode = "Stretch";
public const string Center = "Center";
public const string Tile = "Tile";
public static string NormalizePlacement(string? placement)
{

View File

@@ -1,7 +1,8 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Avalonia.Media;
using Avalonia.Media.Imaging;
@@ -15,6 +16,7 @@ namespace LanMountainDesktop.ViewModels;
public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
{
private readonly ISettingsFacadeService _settingsFacade;
private readonly ISystemWallpaperProvider _systemWallpaperProvider;
private readonly LocalizationService _localizationService = new();
private readonly string _languageCode;
private bool _isInitializing;
@@ -22,9 +24,11 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
public WallpaperSettingsPageViewModel(ISettingsFacadeService settingsFacade)
{
_settingsFacade = settingsFacade;
_systemWallpaperProvider = HostSystemWallpaperProvider.GetOrCreate();
_languageCode = _localizationService.NormalizeLanguageCode(_settingsFacade.Region.Get().LanguageCode);
WallpaperPlacements = CreateWallpaperPlacements();
WallpaperTypes = CreateWallpaperTypes();
RefreshIntervals = CreateRefreshIntervals();
PresetColors = CreatePresetColors();
RefreshLocalizedText();
@@ -35,8 +39,11 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
public IReadOnlyList<SelectionOption> WallpaperPlacements { get; }
public IReadOnlyList<SelectionOption> WallpaperTypes { get; }
public IReadOnlyList<SelectionOption> RefreshIntervals { get; }
public IReadOnlyList<string> PresetColors { get; }
public bool IsSystemWallpaperSupported => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
[ObservableProperty]
private string _wallpaperPath = string.Empty;
@@ -49,6 +56,9 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
[ObservableProperty]
private SelectionOption _selectedWallpaperPlacement = null!;
[ObservableProperty]
private SelectionOption _selectedRefreshInterval = null!;
[ObservableProperty]
private string _wallpaperHeader = string.Empty;
@@ -73,6 +83,18 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
[ObservableProperty]
private string _filePickerTitle = string.Empty;
[ObservableProperty]
private string _systemWallpaperLabel = string.Empty;
[ObservableProperty]
private string _refreshIntervalLabel = string.Empty;
[ObservableProperty]
private string _refreshButtonTooltip = string.Empty;
[ObservableProperty]
private string _systemWallpaperStatus = string.Empty;
[ObservableProperty]
private bool _isImageOrVideo;
@@ -82,13 +104,15 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
[ObservableProperty]
private bool _isImage;
[ObservableProperty]
private bool _isSystemWallpaper;
[ObservableProperty]
private Bitmap? _previewImage;
[ObservableProperty]
private IBrush? _previewBrush;
// 自定义颜色持久化
[ObservableProperty]
private Color _customColor = Colors.White;
@@ -110,7 +134,11 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
string.Equals(option.Value, wallpaperPlacement, StringComparison.OrdinalIgnoreCase))
?? WallpaperPlacements[0];
// 加载自定义颜色
var refreshIntervalSeconds = wallpaper.SystemWallpaperRefreshIntervalSeconds;
SelectedRefreshInterval = RefreshIntervals.FirstOrDefault(option =>
GetIntervalSeconds(option.Value) == refreshIntervalSeconds)
?? RefreshIntervals[2];
if (!string.IsNullOrWhiteSpace(wallpaper.CustomColor) && Color.TryParse(wallpaper.CustomColor, out var customColor))
{
CustomColor = customColor;
@@ -119,6 +147,7 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
UpdateVisibility();
UpdatePreviewFromCurrentSelection();
UpdateSystemWallpaperStatus();
}
partial void OnSelectedWallpaperTypeChanged(SelectionOption value)
@@ -132,8 +161,9 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
private void UpdateVisibility()
{
IsImage = SelectedWallpaperType?.Value == "Image";
IsImageOrVideo = IsImage;
IsImageOrVideo = IsImage || SelectedWallpaperType?.Value == "SystemWallpaper";
IsSolidColor = SelectedWallpaperType?.Value == "SolidColor";
IsSystemWallpaper = SelectedWallpaperType?.Value == "SystemWallpaper";
}
partial void OnSelectedColorChanged(string? value)
@@ -145,13 +175,18 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
partial void OnCustomColorChanged(Color value)
{
CustomColorBrush = new SolidColorBrush(value);
// 将自定义颜色应用到壁纸
var colorHex = $"#{value.A:X2}{value.R:X2}{value.G:X2}{value.B:X2}";
SelectedColor = colorHex;
if (_isInitializing) return;
SaveWallpaper();
}
partial void OnSelectedRefreshIntervalChanged(SelectionOption value)
{
if (_isInitializing) return;
SaveWallpaper();
}
public async Task ImportWallpaperAsync(string sourcePath)
{
var importedPath = await _settingsFacade.WallpaperMedia.ImportAssetAsync(sourcePath);
@@ -170,6 +205,12 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
private void UpdatePreviewFromCurrentSelection()
{
if (IsSystemWallpaper)
{
UpdateSystemWallpaperPreview();
return;
}
if (!IsImage)
{
ClearPreviewImage();
@@ -180,10 +221,24 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
UpdatePreviewImage(WallpaperPath);
}
private void UpdatePreviewImage(string path)
private void UpdateSystemWallpaperPreview()
{
var systemPath = _systemWallpaperProvider.GetWallpaperPath();
if (string.IsNullOrWhiteSpace(systemPath))
{
ClearPreviewImage();
SystemWallpaperStatus = L("settings.wallpaper.system.unavailable", "Unable to read system wallpaper");
return;
}
SystemWallpaperStatus = systemPath;
UpdatePreviewImage(systemPath);
}
private void UpdatePreviewImage(string? path)
{
var previousPreview = PreviewImage;
if (string.IsNullOrWhiteSpace(path) || !System.IO.File.Exists(path))
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
{
previousPreview?.Dispose();
PreviewImage = null;
@@ -193,7 +248,7 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
try
{
using var stream = System.IO.File.OpenRead(path);
using var stream = File.OpenRead(path);
var bitmap = new Bitmap(stream);
PreviewImage = bitmap;
PreviewBrush = WallpaperImageBrushFactory.Create(bitmap, SelectedWallpaperPlacement?.Value);
@@ -215,9 +270,21 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
previousPreview?.Dispose();
}
private void UpdateSystemWallpaperStatus()
{
if (!IsSystemWallpaper) return;
UpdateSystemWallpaperPreview();
}
[RelayCommand]
private void RefreshSystemWallpaper()
{
UpdateSystemWallpaperPreview();
}
partial void OnSelectedWallpaperPlacementChanged(SelectionOption value)
{
if (IsImage && PreviewImage is not null)
if ((IsImage || IsSystemWallpaper) && PreviewImage is not null)
{
PreviewBrush = WallpaperImageBrushFactory.Create(PreviewImage, value?.Value);
}
@@ -236,16 +303,46 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
{
var selectedType = SelectedWallpaperType?.Value ?? "Image";
var selectedPlacement = SelectedWallpaperPlacement?.Value ?? WallpaperImageBrushFactory.Fill;
var normalizedPath = SelectedWallpaperType?.Value == "SolidColor" || string.IsNullOrWhiteSpace(WallpaperPath)
? null
: WallpaperPath;
var refreshIntervalSeconds = GetIntervalSeconds(SelectedRefreshInterval?.Value);
string? normalizedPath;
if (selectedType == "SolidColor" || selectedType == "SystemWallpaper")
{
normalizedPath = null;
}
else
{
normalizedPath = string.IsNullOrWhiteSpace(WallpaperPath) ? null : WallpaperPath;
}
var customColorHex = $"#{CustomColor.A:X2}{CustomColor.R:X2}{CustomColor.G:X2}{CustomColor.B:X2}";
_settingsFacade.Wallpaper.Save(new WallpaperSettingsState(
normalizedPath,
selectedType,
SelectedColor,
selectedPlacement,
customColorHex));
customColorHex,
refreshIntervalSeconds));
}
private static int GetIntervalSeconds(string? value)
{
return value switch
{
"30s" => 30,
"1m" => 60,
"5m" => 300,
"10m" => 600,
"15m" => 900,
"30m" => 1800,
"1h" => 3600,
"2h" => 7200,
"4h" => 14400,
"8h" => 28800,
"12h" => 43200,
"24h" => 86400,
_ => 300
};
}
private IReadOnlyList<SelectionOption> CreateWallpaperPlacements()
@@ -262,10 +359,36 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
private IReadOnlyList<SelectionOption> CreateWallpaperTypes()
{
return
[
var types = new List<SelectionOption>
{
new SelectionOption("Image", L("settings.wallpaper.type.image", "Image")),
new SelectionOption("SolidColor", L("settings.wallpaper.type.solid_color", "Solid Color"))
};
if (IsSystemWallpaperSupported)
{
types.Add(new SelectionOption("SystemWallpaper", L("settings.wallpaper.type.system", "System Wallpaper")));
}
return types;
}
private IReadOnlyList<SelectionOption> CreateRefreshIntervals()
{
return
[
new SelectionOption("30s", L("settings.wallpaper.refresh.30s", "30 seconds")),
new SelectionOption("1m", L("settings.wallpaper.refresh.1m", "1 minute")),
new SelectionOption("5m", L("settings.wallpaper.refresh.5m", "5 minutes")),
new SelectionOption("10m", L("settings.wallpaper.refresh.10m", "10 minutes")),
new SelectionOption("15m", L("settings.wallpaper.refresh.15m", "15 minutes")),
new SelectionOption("30m", L("settings.wallpaper.refresh.30m", "30 minutes")),
new SelectionOption("1h", L("settings.wallpaper.refresh.1h", "1 hour")),
new SelectionOption("2h", L("settings.wallpaper.refresh.2h", "2 hours")),
new SelectionOption("4h", L("settings.wallpaper.refresh.4h", "4 hours")),
new SelectionOption("8h", L("settings.wallpaper.refresh.8h", "8 hours")),
new SelectionOption("12h", L("settings.wallpaper.refresh.12h", "12 hours")),
new SelectionOption("24h", L("settings.wallpaper.refresh.24h", "24 hours"))
];
}
@@ -289,6 +412,9 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
WallpaperPlacementDescription = L("settings.wallpaper.placement_desc", "Adjust how the image fills the desktop.");
ImportWallpaperButtonText = L("settings.wallpaper.pick_button", "Import Wallpaper");
FilePickerTitle = L("filepicker.title", "Select wallpaper");
SystemWallpaperLabel = L("settings.wallpaper.system.label", "System Wallpaper");
RefreshIntervalLabel = L("settings.wallpaper.refresh_interval", "Refresh Interval");
RefreshButtonTooltip = L("settings.wallpaper.refresh_now", "Refresh Now");
}
private string L(string key, string fallback)

View File

@@ -2,6 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:material="clr-namespace:Material.Styles;assembly=Material.Styles"
xmlns:materialAssists="clr-namespace:Material.Styles.Assists;assembly=Material.Styles"
mc:Ignorable="d"
x:Class="LanMountainDesktop.Views.ComponentEditors.ClassScheduleComponentEditor">
<StackPanel Spacing="16">
@@ -36,6 +38,45 @@
</StackPanel>
</Border>
<Border Classes="component-editor-card"
Padding="20">
<StackPanel Spacing="16">
<TextBlock x:Name="SemesterSettingsHeaderTextBlock"
Classes="component-editor-section-title" />
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="16">
<StackPanel Spacing="8">
<TextBlock x:Name="SemesterStartDateLabel"
Classes="component-editor-secondary-text" />
<CalendarDatePicker x:Name="SemesterStartDatePicker"
HorizontalAlignment="Stretch"
SelectedDateChanged="OnSemesterStartDateChanged" />
</StackPanel>
<StackPanel Grid.Column="1" Spacing="8" Width="160">
<TextBlock x:Name="WeekCycleLabel"
Classes="component-editor-secondary-text" />
<ComboBox x:Name="WeekCycleComboBox"
Classes="component-editor-select"
HorizontalAlignment="Stretch"
SelectionChanged="OnWeekCycleSelectionChanged">
<ComboBoxItem Content="1" Tag="1" />
<ComboBoxItem Content="2" Tag="2" />
<ComboBoxItem Content="3" Tag="3" />
<ComboBoxItem Content="4" Tag="4" />
<ComboBoxItem Content="5" Tag="5" />
<ComboBoxItem Content="6" Tag="6" />
<ComboBoxItem Content="7" Tag="7" />
</ComboBox>
</StackPanel>
</Grid>
<TextBlock x:Name="WeekCycleDescription"
Classes="component-editor-secondary-text"
TextWrapping="Wrap"
Opacity="0.7" />
</StackPanel>
</Border>
<Border Classes="component-editor-card"
Padding="20">
<StackPanel Spacing="12">

View File

@@ -76,6 +76,11 @@ public partial class ClassScheduleComponentEditor : ComponentEditorViewBase
FollowSystemColorSchemeItem.Content = L("component.color_scheme.follow_system", "Follow system color scheme");
UseNativeColorSchemeItem.Content = L("component.color_scheme.native", "Use component custom color scheme");
SemesterSettingsHeaderTextBlock.Text = L("schedule.settings.semester.title", "Semester Settings");
SemesterStartDateLabel.Text = L("schedule.settings.semester.start_date", "Semester Start Date");
WeekCycleLabel.Text = L("schedule.settings.semester.week_cycle", "Week Cycle");
WeekCycleDescription.Text = L("schedule.settings.semester.week_cycle_desc", "Set the week rotation cycle for multi-week schedules (e.g., 2 for odd/even weeks).");
AddScheduleButton.Content = L("schedule.settings.add", "Add Schedule");
EmptyStateTextBlock.Text = L("schedule.settings.empty", "No imported schedules yet.");
@@ -85,9 +90,25 @@ public partial class ClassScheduleComponentEditor : ComponentEditorViewBase
string.Equals(colorSchemeSource, ThemeAppearanceValues.ColorSchemeFollowSystem, StringComparison.OrdinalIgnoreCase)
? FollowSystemColorSchemeItem
: UseNativeColorSchemeItem;
if (snapshot.SemesterStartDate.HasValue)
{
SemesterStartDatePicker.SelectedDate = snapshot.SemesterStartDate.Value.ToDateTime(TimeOnly.MinValue);
}
var weekCycle = Math.Clamp(snapshot.SemesterWeekCycle, 1, 7);
WeekCycleComboBox.SelectedIndex = weekCycle - 1;
UpdateWeekCycleDescription(weekCycle);
_suppressEvents = false;
}
private void UpdateWeekCycleDescription(int weekCycle)
{
var format = L("schedule.settings.semester.week_cycle_format", "{0}-week rotation");
WeekCycleDescription.Text = string.Format(format, weekCycle);
}
private void OnColorSchemeSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
_ = sender;
@@ -106,6 +127,39 @@ public partial class ClassScheduleComponentEditor : ComponentEditorViewBase
SaveSnapshot(snapshot, nameof(ComponentSettingsSnapshot.ColorSchemeSource));
}
private void OnSemesterStartDateChanged(object? sender, SelectionChangedEventArgs e)
{
if (_suppressEvents)
{
return;
}
var snapshot = LoadSnapshot();
if (SemesterStartDatePicker.SelectedDate.HasValue)
{
snapshot.SemesterStartDate = DateOnly.FromDateTime(SemesterStartDatePicker.SelectedDate.Value);
}
else
{
snapshot.SemesterStartDate = null;
}
SaveSnapshot(snapshot, nameof(ComponentSettingsSnapshot.SemesterStartDate));
}
private void OnWeekCycleSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (_suppressEvents)
{
return;
}
var weekCycle = WeekCycleComboBox.SelectedIndex + 1;
var snapshot = LoadSnapshot();
snapshot.SemesterWeekCycle = weekCycle;
SaveSnapshot(snapshot, nameof(ComponentSettingsSnapshot.SemesterWeekCycle));
UpdateWeekCycleDescription(weekCycle);
}
private async void OnAddScheduleClick(object? sender, RoutedEventArgs e)
{
_ = sender;
@@ -122,7 +176,15 @@ public partial class ClassScheduleComponentEditor : ComponentEditorViewBase
AllowMultiple = false,
FileTypeFilter =
[
new FilePickerFileType(L("schedule.settings.picker_file_type", "ClassIsland CSES Schedule"))
new FilePickerFileType(L("schedule.settings.picker_file_type.all", "ClassIsland Schedule Files"))
{
Patterns = ["*.json", "*.cses", "*.yaml", "*.yml"]
},
new FilePickerFileType(L("schedule.settings.picker_file_type.json", "ClassIsland Profile (JSON)"))
{
Patterns = ["*.json"]
},
new FilePickerFileType(L("schedule.settings.picker_file_type.cses", "CSES Schedule (YAML)"))
{
Patterns = ["*.cses", "*.yaml", "*.yml"]
}

View File

@@ -253,7 +253,11 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
var today = DateOnly.FromDateTime(now);
var importedSchedulePath = ResolveImportedSchedulePath(componentSettings);
var readResult = _scheduleService.Load(importedSchedulePath);
var readResult = _scheduleService.Load(
importedSchedulePath,
profileFileName: null,
semesterStartDate: componentSettings.SemesterStartDate,
semesterWeekCycle: componentSettings.SemesterWeekCycle);
if (!readResult.Success || readResult.Snapshot is null)
{
_courseItems = Array.Empty<CourseItemViewModel>();

View File

@@ -10,7 +10,7 @@
x:Class="LanMountainDesktop.Views.Components.OfficeRecentDocumentsWidget">
<Border x:Name="RootBorder"
CornerRadius="{DynamicResource DesignCornerRadiusIsland}"
CornerRadius="{DynamicResource DesignCornerRadiusLg}"
Background="#2D5A8E"
ClipToBounds="True"
BorderThickness="0"

View File

@@ -213,10 +213,17 @@ public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget,
ContentGrid.ColumnDefinitions[1].Width = new GridLength(showDial ? dialSize : 0, GridUnitType.Pixel);
}
var leftWidthFactor = Math.Clamp(leftContentWidth / 122d, 0.48, 1.35);
TimeTextBlock.FontSize = Math.Clamp((metrics.PrimaryTemperatureFont * 0.74) * scale * compactFactor * leftWidthFactor, 10, 62);
DateTextBlock.FontSize = Math.Clamp(metrics.SecondaryTextFont * scale * compactFactor * leftWidthFactor, 8, 30);
var weatherIconSize = Math.Clamp(metrics.IconFont * scale * compactFactor * leftWidthFactor, 9, 32);
var timeTextWidth = leftContentWidth * 0.92;
var timeCharCount = 5;
var maxTimeFontSize = timeTextWidth / (timeCharCount * 0.58);
var baseTimeFontSize = Math.Clamp(maxTimeFontSize, 12, 48);
var timeFontSize = Math.Clamp(baseTimeFontSize * scale * compactFactor, 10, 48);
TimeTextBlock.FontSize = timeFontSize;
var dateFontSize = Math.Clamp(timeFontSize * 0.48, 8, 22);
DateTextBlock.FontSize = dateFontSize;
var weatherIconSize = Math.Clamp(dateFontSize * 1.1, 10, 24);
WeatherIconImage.Width = weatherIconSize;
WeatherIconImage.Height = weatherIconSize;
@@ -226,11 +233,11 @@ public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget,
LeftStack.Width = leftContentWidth;
LeftStack.MaxWidth = leftContentWidth;
DateWeatherStack.MaxWidth = leftContentWidth;
TimeTextBlock.MaxWidth = leftContentWidth;
TimeTextBlock.MaxWidth = timeTextWidth;
var showDateLine = leftContentWidth >= Math.Max(40, TimeTextBlock.FontSize * 1.72);
var showDateLine = leftContentWidth >= Math.Max(36, timeFontSize * 1.4) && contentHeight >= 38;
DateWeatherStack.IsVisible = showDateLine;
WeatherIconImage.IsVisible = showDateLine && leftContentWidth >= Math.Max(56, DateTextBlock.FontSize * 2.4);
WeatherIconImage.IsVisible = showDateLine && leftContentWidth >= Math.Max(48, dateFontSize * 3.2);
var dateReservedWidth = WeatherIconImage.IsVisible
? weatherIconSize + DateWeatherStack.Spacing
@@ -477,14 +484,22 @@ public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget,
: CreateBrush("#F8FAFF");
AnalogDialBorder.BorderBrush = CreateBrush(isNightMode ? "#34DDE7FF" : "#12000000");
TimeTextBlock.Foreground = WeatherTypographyAccessibility.CreateReadableBrush(
isNightMode ? "#F8FBFF" : "#10131A",
backgroundSamples,
WeatherTypographyAccessibility.WcagLargeTextContrast);
DateTextBlock.Foreground = WeatherTypographyAccessibility.CreateReadableBrush(
isNightMode ? "#BCC8DD" : "#7A7E87",
backgroundSamples,
WeatherTypographyAccessibility.WcagNormalTextContrast);
if (isNightMode)
{
TimeTextBlock.Foreground = WeatherTypographyAccessibility.CreateReadableBrush(
"#F8FBFF",
backgroundSamples,
WeatherTypographyAccessibility.WcagLargeTextContrast);
DateTextBlock.Foreground = WeatherTypographyAccessibility.CreateReadableBrush(
"#BCC8DD",
backgroundSamples,
WeatherTypographyAccessibility.WcagNormalTextContrast);
}
else
{
TimeTextBlock.Foreground = CreateBrush("#10131A");
DateTextBlock.Foreground = CreateBrush("#7A7E87");
}
_hourHandLine.Stroke = CreateBrush(isNightMode ? "#F1F5FF" : "#232938");
_minuteHandLine.Stroke = CreateBrush(isNightMode ? "#D6E0F2" : "#2F3749");

View File

@@ -215,17 +215,21 @@ public partial class MainWindow
string? savedWallpaperPath,
string? type = null,
string? color = null,
string? placement = null)
string? placement = null,
int systemWallpaperRefreshIntervalSeconds = 300)
{
_wallpaperPath = string.IsNullOrWhiteSpace(savedWallpaperPath) ? null : savedWallpaperPath;
_wallpaperType = string.IsNullOrWhiteSpace(type) ? "Image" : type.Trim();
_wallpaperPlacement = WallpaperImageBrushFactory.NormalizePlacement(placement);
_wallpaperSolidColor = TryParseColor(color, out var parsedColor) ? parsedColor : null;
_wallpaperDisplayState = WallpaperDisplayState.NoWallpaperConfigured;
_systemWallpaperRefreshIntervalSeconds = systemWallpaperRefreshIntervalSeconds;
_wallpaperBitmap?.Dispose();
_wallpaperBitmap = null;
StopSystemWallpaperTimer();
if (string.Equals(_wallpaperType, "SolidColor", StringComparison.OrdinalIgnoreCase))
{
_wallpaperMediaType = WallpaperMediaType.SolidColor;
@@ -235,6 +239,14 @@ public partial class MainWindow
return;
}
if (string.Equals(_wallpaperType, "SystemWallpaper", StringComparison.OrdinalIgnoreCase))
{
_wallpaperMediaType = WallpaperMediaType.Image;
LoadSystemWallpaper();
StartSystemWallpaperTimer();
return;
}
if (string.IsNullOrWhiteSpace(_wallpaperPath))
{
_wallpaperMediaType = WallpaperMediaType.None;
@@ -273,6 +285,69 @@ public partial class MainWindow
}
}
private void LoadSystemWallpaper()
{
var systemPath = _systemWallpaperProvider.GetWallpaperPath();
if (string.IsNullOrWhiteSpace(systemPath) || !File.Exists(systemPath))
{
_wallpaperDisplayState = WallpaperDisplayState.TemporarilyUnavailable;
_wallpaperBitmap?.Dispose();
_wallpaperBitmap = null;
return;
}
try
{
using var stream = File.OpenRead(systemPath);
_wallpaperBitmap?.Dispose();
_wallpaperBitmap = new Bitmap(stream);
_wallpaperPath = systemPath;
_wallpaperDisplayState = WallpaperDisplayState.CurrentValidWallpaper;
CacheLastValidWallpaperBitmap(systemPath);
}
catch
{
_wallpaperDisplayState = WallpaperDisplayState.TemporarilyUnavailable;
_wallpaperBitmap?.Dispose();
_wallpaperBitmap = null;
}
}
private void StartSystemWallpaperTimer()
{
StopSystemWallpaperTimer();
var intervalSeconds = Math.Clamp(_systemWallpaperRefreshIntervalSeconds, 30, 86400);
_systemWallpaperRefreshTimer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(intervalSeconds)
};
_systemWallpaperRefreshTimer.Tick += OnSystemWallpaperRefreshTimerTick;
_systemWallpaperRefreshTimer.Start();
}
private void StopSystemWallpaperTimer()
{
if (_systemWallpaperRefreshTimer is not null)
{
_systemWallpaperRefreshTimer.Stop();
_systemWallpaperRefreshTimer.Tick -= OnSystemWallpaperRefreshTimerTick;
_systemWallpaperRefreshTimer = null;
}
}
private void OnSystemWallpaperRefreshTimerTick(object? sender, EventArgs e)
{
if (!string.Equals(_wallpaperType, "SystemWallpaper", StringComparison.OrdinalIgnoreCase))
{
StopSystemWallpaperTimer();
return;
}
LoadSystemWallpaper();
ApplyWallpaperBrush();
}
private void ApplyWallpaperBrush()
{
DesktopWallpaperImageLayer.Background = null;
@@ -480,7 +555,8 @@ public partial class MainWindow
snapshot.WallpaperPath,
snapshot.WallpaperType,
snapshot.WallpaperColor,
snapshot.WallpaperPlacement);
snapshot.WallpaperPlacement,
snapshot.SystemWallpaperRefreshIntervalSeconds);
if (!snapshot.IsNightMode.HasValue)
{
_isNightMode = CalculateCurrentBackgroundLuminance() < LightBackgroundLuminanceThreshold;
@@ -523,6 +599,7 @@ public partial class MainWindow
? latestWallpaperState.Color
: null,
WallpaperPlacement = latestWallpaperState.Placement,
SystemWallpaperRefreshIntervalSeconds = latestWallpaperState.SystemWallpaperRefreshIntervalSeconds,
LanguageCode = _languageCode,
TimeZoneId = _timeZoneService.CurrentTimeZone.Id,
WeatherLocationMode = latestWeatherState.LocationMode,

View File

@@ -122,6 +122,9 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
private Color? _wallpaperSolidColor;
private string? _wallpaperPath;
private string _wallpaperStatus = "Current background uses solid color.";
private int _systemWallpaperRefreshIntervalSeconds = 300;
private DispatcherTimer? _systemWallpaperRefreshTimer;
private readonly ISystemWallpaperProvider _systemWallpaperProvider = HostSystemWallpaperProvider.GetOrCreate();
private IReadOnlyList<Color> _recommendedColors = Array.Empty<Color>();
private IReadOnlyList<Color> _monetColors = Array.Empty<Color>();
private Color _selectedThemeColor = Color.Parse("#FF3B82F6");

View File

@@ -77,10 +77,10 @@
<Setter Property="Margin" Value="2" />
</Style>
</WrapPanel.Styles>
<HyperlinkButton NavigateUri="https://github.com/Lincube/LanMountainDesktop">
<HyperlinkButton NavigateUri="https://github.com/wwiinnddyy/LanMountainDesktop">
<TextBlock Text="GitHub 仓库" />
</HyperlinkButton>
<HyperlinkButton NavigateUri="https://github.com/Lincube/LanMountainDesktop/issues">
<HyperlinkButton NavigateUri="https://github.com/wwiinnddyy/LanMountainDesktop/issues">
<TextBlock Text="问题反馈" />
</HyperlinkButton>
</WrapPanel>

View File

@@ -29,6 +29,11 @@
<!-- 纯色预览 -->
<Border Background="{Binding SelectedColor}"
IsVisible="{Binding IsSolidColor}" />
<!-- 系统壁纸预览 -->
<Border Background="#FFF6F7F9"
IsVisible="{Binding IsSystemWallpaper}">
<Border Background="{Binding PreviewBrush}" />
</Border>
</Panel>
</Border>
</Viewbox>
@@ -135,6 +140,19 @@
</Button>
</UniformGrid>
</StackPanel>
<!-- 右侧:系统壁纸状态 -->
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="12" IsVisible="{Binding IsSystemWallpaper}">
<TextBlock Text="{Binding SystemWallpaperLabel}"
FontSize="14"
FontWeight="SemiBold"
Opacity="0.8" />
<TextBlock Text="{Binding SystemWallpaperStatus}"
FontSize="12"
Opacity="0.7"
TextWrapping="Wrap"
MaxWidth="280" />
</StackPanel>
</Grid>
<Separator Classes="settings-separator" Margin="0,0,0,24" />
@@ -183,10 +201,60 @@
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- 填充方式 -->
<!-- 系统壁纸刷新设置 -->
<ui:SettingsExpander Header="{Binding RefreshIntervalLabel}"
IsVisible="{Binding IsSystemWallpaper}"
Margin="0,4,0,0">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Clock" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<StackPanel Orientation="Horizontal" Spacing="8">
<ComboBox Width="140"
ItemsSource="{Binding RefreshIntervals}"
SelectedItem="{Binding SelectedRefreshInterval}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:SelectionOption">
<TextBlock Text="{Binding Label}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Classes="settings-accent-button"
Command="{Binding RefreshSystemWallpaperCommand}"
ToolTip.Tip="{Binding RefreshButtonTooltip}"
VerticalAlignment="Center"
Padding="12,8">
<fi:SymbolIcon Symbol="ArrowSync" IconVariant="Regular" />
</Button>
</StackPanel>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- 填充方式(图片和系统壁纸都显示) -->
<ui:SettingsExpander Header="{Binding WallpaperPlacementLabel}"
Description="{Binding WallpaperPlacementDescription}"
IsVisible="{Binding IsImage}"
IsVisible="{Binding IsImageOrVideo}"
Margin="0,4,0,0">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Maximize" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ComboBox Width="200"
ItemsSource="{Binding WallpaperPlacements}"
SelectedItem="{Binding SelectedWallpaperPlacement}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:SelectionOption">
<TextBlock Text="{Binding Label}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- 系统壁纸填充方式 -->
<ui:SettingsExpander Header="{Binding WallpaperPlacementLabel}"
Description="{Binding WallpaperPlacementDescription}"
IsVisible="{Binding IsSystemWallpaper}"
Margin="0,4,0,0">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Maximize" />