mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
0.7.4
首先我加了CI课程表json的读取,然后把天气时钟这个老问题也修了。
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
warnings.Add("ClassIsland Settings.json not found, using default cycle rule.");
|
||||
return new ClassIslandScheduleCycleRule(null, 4, new List<int> { -1, -1, 0, 0, 0 });
|
||||
}
|
||||
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))
|
||||
{
|
||||
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 (!singleWeekStartDate.HasValue)
|
||||
{
|
||||
offsetList = new List<int> { -1, -1, 0, 0, 0 };
|
||||
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.");
|
||||
}
|
||||
|
||||
if (maxCycle < 2)
|
||||
{
|
||||
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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
65
LanMountainDesktop/Services/SystemWallpaperProvider.cs
Normal file
65
LanMountainDesktop/Services/SystemWallpaperProvider.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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");
|
||||
|
||||
if (isNightMode)
|
||||
{
|
||||
TimeTextBlock.Foreground = WeatherTypographyAccessibility.CreateReadableBrush(
|
||||
isNightMode ? "#F8FBFF" : "#10131A",
|
||||
"#F8FBFF",
|
||||
backgroundSamples,
|
||||
WeatherTypographyAccessibility.WcagLargeTextContrast);
|
||||
DateTextBlock.Foreground = WeatherTypographyAccessibility.CreateReadableBrush(
|
||||
isNightMode ? "#BCC8DD" : "#7A7E87",
|
||||
"#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");
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user