mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
,0.6.0
重构了设置系统。解决了大量的bug,正式添加了图标。引入了遥测的同意与许可(暂无实际功能)
This commit is contained in:
@@ -44,6 +44,7 @@ public partial class App : Application
|
|||||||
|
|
||||||
private readonly ISettingsFacadeService _settingsFacade = HostSettingsFacadeProvider.GetOrCreate();
|
private readonly ISettingsFacadeService _settingsFacade = HostSettingsFacadeProvider.GetOrCreate();
|
||||||
private readonly IAppearanceThemeService _appearanceThemeService = HostAppearanceThemeProvider.GetOrCreate();
|
private readonly IAppearanceThemeService _appearanceThemeService = HostAppearanceThemeProvider.GetOrCreate();
|
||||||
|
private readonly IAppLogoService _appLogoService = HostAppLogoProvider.GetOrCreate();
|
||||||
private readonly LocalizationService _localizationService = new();
|
private readonly LocalizationService _localizationService = new();
|
||||||
private readonly IHostApplicationLifecycle _hostApplicationLifecycle = new HostApplicationLifecycleService();
|
private readonly IHostApplicationLifecycle _hostApplicationLifecycle = new HostApplicationLifecycleService();
|
||||||
private readonly IDetachedComponentLibraryWindowService _detachedComponentLibraryWindowService = new DetachedComponentLibraryWindowService();
|
private readonly IDetachedComponentLibraryWindowService _detachedComponentLibraryWindowService = new DetachedComponentLibraryWindowService();
|
||||||
@@ -229,10 +230,9 @@ public partial class App : Application
|
|||||||
{
|
{
|
||||||
DisposeTrayIcon();
|
DisposeTrayIcon();
|
||||||
|
|
||||||
using var iconStream = AssetLoader.Open(new Uri("avares://LanMountainDesktop/Assets/avalonia-logo.ico"));
|
|
||||||
var trayIcon = new TrayIcon
|
var trayIcon = new TrayIcon
|
||||||
{
|
{
|
||||||
Icon = new WindowIcon(iconStream),
|
Icon = _appLogoService.CreateTrayIcon(),
|
||||||
ToolTipText = L("tray.tooltip", "LanMountainDesktop"),
|
ToolTipText = L("tray.tooltip", "LanMountainDesktop"),
|
||||||
Menu = BuildTrayMenu(),
|
Menu = BuildTrayMenu(),
|
||||||
IsVisible = true
|
IsVisible = true
|
||||||
|
|||||||
BIN
LanMountainDesktop/Assets/logo_nightly.ico
Normal file
BIN
LanMountainDesktop/Assets/logo_nightly.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
LanMountainDesktop/Assets/logo_nightly.png
Normal file
BIN
LanMountainDesktop/Assets/logo_nightly.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
38
LanMountainDesktop/Assets/logo_nightly.svg
Normal file
38
LanMountainDesktop/Assets/logo_nightly.svg
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="375" height="374.999991" viewBox="0 0 375 374.999991">
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip-0">
|
||||||
|
<path clip-rule="nonzero" d="M 196.875 178.398438 L 285.058594 178.398438 L 285.058594 266.582031 L 196.875 266.582031 Z M 196.875 178.398438 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-1">
|
||||||
|
<path clip-rule="nonzero" d="M 240.96875 178.398438 C 216.617188 178.398438 196.875 198.140625 196.875 222.492188 C 196.875 246.839844 216.617188 266.582031 240.96875 266.582031 C 265.320312 266.582031 285.058594 246.839844 285.058594 222.492188 C 285.058594 198.140625 265.320312 178.398438 240.96875 178.398438 Z M 240.96875 178.398438 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-2">
|
||||||
|
<path clip-rule="nonzero" d="M 0.875 0.398438 L 89.058594 0.398438 L 89.058594 88.582031 L 0.875 88.582031 Z M 0.875 0.398438 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-3">
|
||||||
|
<path clip-rule="nonzero" d="M 44.96875 0.398438 C 20.617188 0.398438 0.875 20.140625 0.875 44.492188 C 0.875 68.839844 20.617188 88.582031 44.96875 88.582031 C 69.320312 88.582031 89.058594 68.839844 89.058594 44.492188 C 89.058594 20.140625 69.320312 0.398438 44.96875 0.398438 Z M 44.96875 0.398438 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-4">
|
||||||
|
<rect x="0" y="0" width="90" height="89"/>
|
||||||
|
</clipPath>
|
||||||
|
<g id="source-5" clip-path="url(#clip-4)">
|
||||||
|
<g clip-path="url(#clip-2)">
|
||||||
|
<g clip-path="url(#clip-3)">
|
||||||
|
<path fill-rule="nonzero" fill="rgb(100%, 100%, 100%)" fill-opacity="1" d="M 0.875 0.398438 L 89.058594 0.398438 L 89.058594 88.582031 L 0.875 88.582031 Z M 0.875 0.398438 "/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</defs>
|
||||||
|
<rect x="-37.5" y="-37.499999" width="450" height="449.999989" fill="rgb(100%, 100%, 100%)" fill-opacity="1"/>
|
||||||
|
<rect x="-37.5" y="-37.499999" width="450" height="449.999989" fill="rgb(0%, 0%, 0%)" fill-opacity="1"/>
|
||||||
|
<path fill="none" stroke-width="21" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(100%, 100%, 100%)" stroke-opacity="1" stroke-miterlimit="4" d="M 0.00219613 10.500482 L 127.627201 10.500482 " transform="matrix(0.75, 0, 0, 0.75, 93.623353, 248.98792)"/>
|
||||||
|
<path fill="none" stroke-width="21" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(100%, 100%, 100%)" stroke-opacity="1" stroke-miterlimit="4" d="M 0.00244625 10.500708 L 127.622243 10.500708 " transform="matrix(0.75, 0, 0, 0.75, 189.341915, 110.327595)"/>
|
||||||
|
<path fill="none" stroke-width="21" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(100%, 100%, 100%)" stroke-opacity="1" stroke-miterlimit="4" d="M 10.492181 33.974247 C 11.537452 2.677252 36.02023 2.673354 83.94005 33.972962 " transform="matrix(-0.0333864, -0.749256, 0.749256, -0.0333864, 70.972996, 265.851083)"/>
|
||||||
|
<path fill="none" stroke-width="21" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(100%, 100%, 100%)" stroke-opacity="1" stroke-miterlimit="4" d="M 10.498903 37.532093 C 10.670763 1.489257 37.447477 1.488034 90.82363 37.533419 " transform="matrix(0.0300175, 0.749399, -0.749399, 0.0300175, 310.455894, 109.212543)"/>
|
||||||
|
<g clip-path="url(#clip-0)">
|
||||||
|
<g clip-path="url(#clip-1)">
|
||||||
|
<use xlink:href="#source-5" transform="matrix(1, 0, 0, 1, 196, 178)"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.1 KiB |
31
LanMountainDesktop/Assets/logo_nightly_render.html
Normal file
31
LanMountainDesktop/Assets/logo_nightly_render.html
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
.stage {
|
||||||
|
width: 512px;
|
||||||
|
height: 512px;
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
.stage img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="stage">
|
||||||
|
<img src="./logo_nightly.svg" alt="logo" />
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<Version>1.0.0</Version>
|
<Version>1.0.0</Version>
|
||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
|
<ApplicationIcon>Assets\logo_nightly.ico</ApplicationIcon>
|
||||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||||
<SelfContained Condition="'$(RuntimeIdentifier)' != ''">true</SelfContained>
|
<SelfContained Condition="'$(RuntimeIdentifier)' != ''">true</SelfContained>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
72
LanMountainDesktop/Services/AppLogoService.cs
Normal file
72
LanMountainDesktop/Services/AppLogoService.cs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Platform;
|
||||||
|
|
||||||
|
namespace LanMountainDesktop.Services;
|
||||||
|
|
||||||
|
public enum AppLogoVariant
|
||||||
|
{
|
||||||
|
Auto = 0,
|
||||||
|
Day = 1,
|
||||||
|
Night = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IAppLogoService
|
||||||
|
{
|
||||||
|
WindowIcon CreateWindowIcon(AppLogoVariant variant = AppLogoVariant.Auto);
|
||||||
|
WindowIcon CreateTrayIcon(AppLogoVariant variant = AppLogoVariant.Auto);
|
||||||
|
Uri GetVectorLogoUri(AppLogoVariant variant = AppLogoVariant.Auto);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class AppLogoService : IAppLogoService
|
||||||
|
{
|
||||||
|
private static readonly Uri NightVectorLogoUri = new("avares://LanMountainDesktop/Assets/logo_nightly.svg");
|
||||||
|
private static readonly Uri DayVectorLogoUri = new("avares://LanMountainDesktop/Assets/logo_nightly.svg");
|
||||||
|
private static readonly Uri NightIconUri = new("avares://LanMountainDesktop/Assets/logo_nightly.ico");
|
||||||
|
private static readonly Uri DayIconUri = new("avares://LanMountainDesktop/Assets/logo_nightly.ico");
|
||||||
|
|
||||||
|
public WindowIcon CreateWindowIcon(AppLogoVariant variant = AppLogoVariant.Auto) => CreateIcon(ResolveIconUri(variant));
|
||||||
|
|
||||||
|
public WindowIcon CreateTrayIcon(AppLogoVariant variant = AppLogoVariant.Auto) => CreateIcon(ResolveIconUri(variant));
|
||||||
|
|
||||||
|
public Uri GetVectorLogoUri(AppLogoVariant variant = AppLogoVariant.Auto) => ResolveVectorLogoUri(variant);
|
||||||
|
|
||||||
|
private static WindowIcon CreateIcon(Uri assetUri)
|
||||||
|
{
|
||||||
|
using var stream = AssetLoader.Open(assetUri);
|
||||||
|
return new WindowIcon(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Uri ResolveIconUri(AppLogoVariant variant) => ResolveVariant(variant) switch
|
||||||
|
{
|
||||||
|
AppLogoVariant.Day => DayIconUri,
|
||||||
|
_ => NightIconUri
|
||||||
|
};
|
||||||
|
|
||||||
|
private static Uri ResolveVectorLogoUri(AppLogoVariant variant) => ResolveVariant(variant) switch
|
||||||
|
{
|
||||||
|
AppLogoVariant.Day => DayVectorLogoUri,
|
||||||
|
_ => NightVectorLogoUri
|
||||||
|
};
|
||||||
|
|
||||||
|
private static AppLogoVariant ResolveVariant(AppLogoVariant variant) => variant switch
|
||||||
|
{
|
||||||
|
AppLogoVariant.Day => AppLogoVariant.Day,
|
||||||
|
AppLogoVariant.Night => AppLogoVariant.Night,
|
||||||
|
_ => AppLogoVariant.Night
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class HostAppLogoProvider
|
||||||
|
{
|
||||||
|
private static readonly object Gate = new();
|
||||||
|
private static IAppLogoService? _instance;
|
||||||
|
|
||||||
|
public static IAppLogoService GetOrCreate()
|
||||||
|
{
|
||||||
|
lock (Gate)
|
||||||
|
{
|
||||||
|
return _instance ??= new AppLogoService();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -464,6 +464,7 @@ internal sealed class MaterialSurfaceService : IMaterialSurfaceService
|
|||||||
internal sealed class AppearanceThemeService : IAppearanceThemeService, IDisposable
|
internal sealed class AppearanceThemeService : IAppearanceThemeService, IDisposable
|
||||||
{
|
{
|
||||||
private static readonly Color DefaultAccentColor = Color.Parse("#FF3B82F6");
|
private static readonly Color DefaultAccentColor = Color.Parse("#FF3B82F6");
|
||||||
|
private static readonly Color NeutralFallbackSeedColor = Color.Parse("#FF8A8A8A");
|
||||||
private readonly ISettingsFacadeService _settingsFacade;
|
private readonly ISettingsFacadeService _settingsFacade;
|
||||||
private readonly ISystemWallpaperService _systemWallpaperService;
|
private readonly ISystemWallpaperService _systemWallpaperService;
|
||||||
private readonly IWindowMaterialService _windowMaterialService;
|
private readonly IWindowMaterialService _windowMaterialService;
|
||||||
@@ -811,7 +812,7 @@ internal sealed class AppearanceThemeService : IAppearanceThemeService, IDisposa
|
|||||||
|
|
||||||
private WallpaperPaletteResolution BuildFallbackWallpaperPaletteResolution(bool nightMode, string? resolvedWallpaperPath)
|
private WallpaperPaletteResolution BuildFallbackWallpaperPaletteResolution(bool nightMode, string? resolvedWallpaperPath)
|
||||||
{
|
{
|
||||||
var palette = _settingsFacade.Theme.BuildPalette(nightMode, null, null);
|
var palette = _monetColorService.BuildPaletteFromSeedCandidates([], nightMode, NeutralFallbackSeedColor);
|
||||||
return new WallpaperPaletteResolution(
|
return new WallpaperPaletteResolution(
|
||||||
palette,
|
palette,
|
||||||
[],
|
[],
|
||||||
|
|||||||
196
LanMountainDesktop/Services/CurrentUserProfileService.cs
Normal file
196
LanMountainDesktop/Services/CurrentUserProfileService.cs
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Avalonia.Media.Imaging;
|
||||||
|
|
||||||
|
namespace LanMountainDesktop.Services;
|
||||||
|
|
||||||
|
public sealed record CurrentUserProfileSnapshot(
|
||||||
|
string DisplayName,
|
||||||
|
Bitmap? AvatarBitmap,
|
||||||
|
string FallbackMonogram,
|
||||||
|
bool IsPlaceholder);
|
||||||
|
|
||||||
|
public interface ICurrentUserProfileService
|
||||||
|
{
|
||||||
|
CurrentUserProfileSnapshot GetCurrentProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class CurrentUserProfileService : ICurrentUserProfileService, IDisposable
|
||||||
|
{
|
||||||
|
private readonly object _gate = new();
|
||||||
|
private CurrentUserProfileSnapshot? _cachedSnapshot;
|
||||||
|
private Bitmap? _cachedAvatarBitmap;
|
||||||
|
|
||||||
|
public CurrentUserProfileSnapshot GetCurrentProfile()
|
||||||
|
{
|
||||||
|
lock (_gate)
|
||||||
|
{
|
||||||
|
if (_cachedSnapshot is not null)
|
||||||
|
{
|
||||||
|
return _cachedSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
var displayName = ResolveDisplayName();
|
||||||
|
_cachedAvatarBitmap = TryLoadSystemAvatarBitmap();
|
||||||
|
_cachedSnapshot = new CurrentUserProfileSnapshot(
|
||||||
|
displayName,
|
||||||
|
_cachedAvatarBitmap,
|
||||||
|
BuildMonogram(displayName),
|
||||||
|
_cachedAvatarBitmap is null);
|
||||||
|
return _cachedSnapshot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
lock (_gate)
|
||||||
|
{
|
||||||
|
_cachedSnapshot = null;
|
||||||
|
_cachedAvatarBitmap?.Dispose();
|
||||||
|
_cachedAvatarBitmap = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ResolveDisplayName()
|
||||||
|
{
|
||||||
|
var userName = Environment.UserName?.Trim();
|
||||||
|
return string.IsNullOrWhiteSpace(userName) ? "User" : userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Bitmap? TryLoadSystemAvatarBitmap()
|
||||||
|
{
|
||||||
|
foreach (var path in EnumerateAvatarCandidates())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var stream = File.OpenRead(path);
|
||||||
|
return new Bitmap(stream);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore unreadable avatar files and continue with the next candidate.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> EnumerateAvatarCandidates()
|
||||||
|
{
|
||||||
|
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
foreach (var path in EnumerateDirectoryCandidates(
|
||||||
|
Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||||
|
"Microsoft",
|
||||||
|
"Windows",
|
||||||
|
"AccountPictures")))
|
||||||
|
{
|
||||||
|
if (seen.Add(path))
|
||||||
|
{
|
||||||
|
yield return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var path in EnumerateDirectoryCandidates(
|
||||||
|
Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||||
|
"Microsoft",
|
||||||
|
"Windows",
|
||||||
|
"AccountPictures")))
|
||||||
|
{
|
||||||
|
if (seen.Add(path))
|
||||||
|
{
|
||||||
|
yield return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var commonPicturesDirectory = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
|
||||||
|
"Microsoft",
|
||||||
|
"User Account Pictures");
|
||||||
|
|
||||||
|
foreach (var fileName in new[]
|
||||||
|
{
|
||||||
|
"user-448.png",
|
||||||
|
"user-240.png",
|
||||||
|
"user-192.png",
|
||||||
|
"user-96.png",
|
||||||
|
"user-64.png",
|
||||||
|
"user-48.png",
|
||||||
|
"user.png"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
var path = Path.Combine(commonPicturesDirectory, fileName);
|
||||||
|
if (File.Exists(path) && seen.Add(path))
|
||||||
|
{
|
||||||
|
yield return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> EnumerateDirectoryCandidates(string directoryPath)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(directoryPath))
|
||||||
|
{
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var files = Directory.EnumerateFiles(directoryPath)
|
||||||
|
.Where(path =>
|
||||||
|
{
|
||||||
|
var extension = Path.GetExtension(path);
|
||||||
|
return extension.Equals(".png", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
extension.Equals(".jpg", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
extension.Equals(".jpeg", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
extension.Equals(".bmp", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
extension.Equals(".webp", StringComparison.OrdinalIgnoreCase);
|
||||||
|
})
|
||||||
|
.Select(path => new FileInfo(path))
|
||||||
|
.OrderByDescending(file => file.LastWriteTimeUtc)
|
||||||
|
.ThenByDescending(file => file.Length);
|
||||||
|
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
yield return file.FullName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildMonogram(string text)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(text))
|
||||||
|
{
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
|
||||||
|
var letters = text
|
||||||
|
.Trim()
|
||||||
|
.Split(' ', StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(part => part[0])
|
||||||
|
.Take(2)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (letters.Length == 0)
|
||||||
|
{
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
|
||||||
|
return new string(letters).ToUpperInvariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class HostCurrentUserProfileProvider
|
||||||
|
{
|
||||||
|
private static readonly object Gate = new();
|
||||||
|
private static ICurrentUserProfileService? _instance;
|
||||||
|
|
||||||
|
public static ICurrentUserProfileService GetOrCreate()
|
||||||
|
{
|
||||||
|
lock (Gate)
|
||||||
|
{
|
||||||
|
return _instance ??= new CurrentUserProfileService();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
69
LanMountainDesktop/Services/WallpaperImageBrushFactory.cs
Normal file
69
LanMountainDesktop/Services/WallpaperImageBrushFactory.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
using System;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using Avalonia.Media.Imaging;
|
||||||
|
|
||||||
|
namespace LanMountainDesktop.Services;
|
||||||
|
|
||||||
|
internal 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 static string NormalizePlacement(string? placement)
|
||||||
|
{
|
||||||
|
return placement switch
|
||||||
|
{
|
||||||
|
_ when string.Equals(placement, Fit, StringComparison.OrdinalIgnoreCase) => Fit,
|
||||||
|
_ when string.Equals(placement, StretchMode, StringComparison.OrdinalIgnoreCase) => StretchMode,
|
||||||
|
_ when string.Equals(placement, Center, StringComparison.OrdinalIgnoreCase) => Center,
|
||||||
|
_ when string.Equals(placement, Tile, StringComparison.OrdinalIgnoreCase) => Tile,
|
||||||
|
_ => Fill
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ImageBrush Create(Bitmap bitmap, string? placement)
|
||||||
|
{
|
||||||
|
var normalizedPlacement = NormalizePlacement(placement);
|
||||||
|
var brush = new ImageBrush(bitmap)
|
||||||
|
{
|
||||||
|
AlignmentX = AlignmentX.Center,
|
||||||
|
AlignmentY = AlignmentY.Center,
|
||||||
|
Stretch = Stretch.UniformToFill,
|
||||||
|
TileMode = TileMode.None
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (normalizedPlacement)
|
||||||
|
{
|
||||||
|
case Fit:
|
||||||
|
brush.Stretch = Stretch.Uniform;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StretchMode:
|
||||||
|
brush.Stretch = Stretch.Fill;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Center:
|
||||||
|
brush.Stretch = Stretch.None;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Tile:
|
||||||
|
brush.AlignmentX = AlignmentX.Left;
|
||||||
|
brush.AlignmentY = AlignmentY.Top;
|
||||||
|
brush.Stretch = Stretch.None;
|
||||||
|
brush.TileMode = TileMode.Tile;
|
||||||
|
brush.DestinationRect = new RelativeRect(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
Math.Max(1, bitmap.Size.Width),
|
||||||
|
Math.Max(1, bitmap.Size.Height),
|
||||||
|
RelativeUnit.Absolute);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return brush;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -79,9 +79,21 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _isSolidColor;
|
private bool _isSolidColor;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isImage;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isVideo;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private Bitmap? _previewImage;
|
private Bitmap? _previewImage;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private IBrush? _previewBrush;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _videoModeHintText = string.Empty;
|
||||||
|
|
||||||
public void Load()
|
public void Load()
|
||||||
{
|
{
|
||||||
var wallpaper = _settingsFacade.Wallpaper.Get();
|
var wallpaper = _settingsFacade.Wallpaper.Get();
|
||||||
@@ -98,18 +110,21 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
|
|||||||
?? WallpaperPlacements[0];
|
?? WallpaperPlacements[0];
|
||||||
|
|
||||||
UpdateVisibility();
|
UpdateVisibility();
|
||||||
UpdatePreviewImage(WallpaperPath);
|
UpdatePreviewFromCurrentSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnSelectedWallpaperTypeChanged(SelectionOption value)
|
partial void OnSelectedWallpaperTypeChanged(SelectionOption value)
|
||||||
{
|
{
|
||||||
UpdateVisibility();
|
UpdateVisibility();
|
||||||
|
UpdatePreviewFromCurrentSelection();
|
||||||
if (_isInitializing) return;
|
if (_isInitializing) return;
|
||||||
SaveWallpaper();
|
SaveWallpaper();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateVisibility()
|
private void UpdateVisibility()
|
||||||
{
|
{
|
||||||
|
IsImage = SelectedWallpaperType?.Value == "Image";
|
||||||
|
IsVideo = SelectedWallpaperType?.Value == "Video";
|
||||||
IsImageOrVideo = SelectedWallpaperType?.Value is "Image" or "Video";
|
IsImageOrVideo = SelectedWallpaperType?.Value is "Image" or "Video";
|
||||||
IsSolidColor = SelectedWallpaperType?.Value == "SolidColor";
|
IsSolidColor = SelectedWallpaperType?.Value == "SolidColor";
|
||||||
}
|
}
|
||||||
@@ -131,32 +146,65 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
|
|||||||
|
|
||||||
partial void OnWallpaperPathChanged(string value)
|
partial void OnWallpaperPathChanged(string value)
|
||||||
{
|
{
|
||||||
UpdatePreviewImage(value);
|
UpdatePreviewFromCurrentSelection();
|
||||||
if (_isInitializing) return;
|
if (_isInitializing) return;
|
||||||
SaveWallpaper();
|
SaveWallpaper();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdatePreviewFromCurrentSelection()
|
||||||
|
{
|
||||||
|
if (!IsImage)
|
||||||
|
{
|
||||||
|
ClearPreviewImage();
|
||||||
|
PreviewBrush = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdatePreviewImage(WallpaperPath);
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdatePreviewImage(string path)
|
private void UpdatePreviewImage(string path)
|
||||||
{
|
{
|
||||||
|
var previousPreview = PreviewImage;
|
||||||
if (string.IsNullOrWhiteSpace(path) || !System.IO.File.Exists(path))
|
if (string.IsNullOrWhiteSpace(path) || !System.IO.File.Exists(path))
|
||||||
{
|
{
|
||||||
|
previousPreview?.Dispose();
|
||||||
PreviewImage = null;
|
PreviewImage = null;
|
||||||
|
PreviewBrush = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var stream = System.IO.File.OpenRead(path);
|
using var stream = System.IO.File.OpenRead(path);
|
||||||
PreviewImage = new Bitmap(stream);
|
var bitmap = new Bitmap(stream);
|
||||||
|
PreviewImage = bitmap;
|
||||||
|
PreviewBrush = WallpaperImageBrushFactory.Create(bitmap, SelectedWallpaperPlacement?.Value);
|
||||||
|
previousPreview?.Dispose();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
previousPreview?.Dispose();
|
||||||
PreviewImage = null;
|
PreviewImage = null;
|
||||||
|
PreviewBrush = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ClearPreviewImage()
|
||||||
|
{
|
||||||
|
var previousPreview = PreviewImage;
|
||||||
|
PreviewImage = null;
|
||||||
|
PreviewBrush = null;
|
||||||
|
previousPreview?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
partial void OnSelectedWallpaperPlacementChanged(SelectionOption value)
|
partial void OnSelectedWallpaperPlacementChanged(SelectionOption value)
|
||||||
{
|
{
|
||||||
|
if (IsImage && PreviewImage is not null)
|
||||||
|
{
|
||||||
|
PreviewBrush = WallpaperImageBrushFactory.Create(PreviewImage, value?.Value);
|
||||||
|
}
|
||||||
|
|
||||||
if (_isInitializing || value is null) return;
|
if (_isInitializing || value is null) return;
|
||||||
SaveWallpaper();
|
SaveWallpaper();
|
||||||
}
|
}
|
||||||
@@ -169,14 +217,16 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
|
|||||||
|
|
||||||
private void SaveWallpaper()
|
private void SaveWallpaper()
|
||||||
{
|
{
|
||||||
|
var selectedType = SelectedWallpaperType?.Value ?? "Image";
|
||||||
|
var selectedPlacement = SelectedWallpaperPlacement?.Value ?? WallpaperImageBrushFactory.Fill;
|
||||||
var normalizedPath = SelectedWallpaperType?.Value == "SolidColor" || string.IsNullOrWhiteSpace(WallpaperPath)
|
var normalizedPath = SelectedWallpaperType?.Value == "SolidColor" || string.IsNullOrWhiteSpace(WallpaperPath)
|
||||||
? null
|
? null
|
||||||
: WallpaperPath;
|
: WallpaperPath;
|
||||||
_settingsFacade.Wallpaper.Save(new WallpaperSettingsState(
|
_settingsFacade.Wallpaper.Save(new WallpaperSettingsState(
|
||||||
normalizedPath,
|
normalizedPath,
|
||||||
SelectedWallpaperType.Value,
|
selectedType,
|
||||||
SelectedColor,
|
SelectedColor,
|
||||||
SelectedWallpaperPlacement.Value));
|
selectedPlacement));
|
||||||
}
|
}
|
||||||
|
|
||||||
private IReadOnlyList<SelectionOption> CreateWallpaperPlacements()
|
private IReadOnlyList<SelectionOption> CreateWallpaperPlacements()
|
||||||
@@ -221,6 +271,7 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
|
|||||||
WallpaperPlacementDescription = L("settings.wallpaper.placement_desc", "Adjust how the image fills the desktop.");
|
WallpaperPlacementDescription = L("settings.wallpaper.placement_desc", "Adjust how the image fills the desktop.");
|
||||||
ImportWallpaperButtonText = L("settings.wallpaper.pick_button", "Import Wallpaper");
|
ImportWallpaperButtonText = L("settings.wallpaper.pick_button", "Import Wallpaper");
|
||||||
FilePickerTitle = L("filepicker.title", "Select wallpaper");
|
FilePickerTitle = L("filepicker.title", "Select wallpaper");
|
||||||
|
VideoModeHintText = L("settings.wallpaper.video_mode", "Video wallpaper uses automatic fill mode.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private string L(string key, string fallback)
|
private string L(string key, string fallback)
|
||||||
|
|||||||
@@ -99,12 +99,186 @@ public partial class MainWindow
|
|||||||
IReadOnlyList<ComponentLibraryComponentEntry> Components);
|
IReadOnlyList<ComponentLibraryComponentEntry> Components);
|
||||||
|
|
||||||
private readonly record struct ComponentScaleRule(int WidthUnit, int HeightUnit, int MinScale);
|
private readonly record struct ComponentScaleRule(int WidthUnit, int HeightUnit, int MinScale);
|
||||||
|
private readonly record struct TaskbarProfilePopupMaterialPalette(
|
||||||
|
Color SurfaceColor,
|
||||||
|
Color OutlineColor,
|
||||||
|
Color AvatarSurfaceColor,
|
||||||
|
Color PrimaryTextColor,
|
||||||
|
Color AccentColor,
|
||||||
|
Color HoverColor,
|
||||||
|
Color PressedColor,
|
||||||
|
Color DividerColor);
|
||||||
|
|
||||||
|
private void InitializeTaskbarProfileFlyout()
|
||||||
|
{
|
||||||
|
if (TaskbarProfileButton is null || TaskbarProfilePopup is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskbarProfilePopup.PlacementTarget = TaskbarProfileButton;
|
||||||
|
RefreshTaskbarProfilePresentation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshTaskbarProfilePresentation()
|
||||||
|
{
|
||||||
|
if (TaskbarProfileButton is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var profile = _currentUserProfileService.GetCurrentProfile();
|
||||||
|
ApplyProfileAvatarVisual(TaskbarProfileAvatarImage, TaskbarProfileAvatarFallbackText, profile);
|
||||||
|
ApplyProfileAvatarVisual(TaskbarProfileHeaderAvatarImage, TaskbarProfileHeaderAvatarFallbackText, profile);
|
||||||
|
TaskbarProfileDisplayNameTextBlock.Text = profile.DisplayName;
|
||||||
|
TaskbarProfileSettingsActionTextBlock.Text = L("tooltip.open_settings", "Settings");
|
||||||
|
TaskbarProfileDesktopEditActionTextBlock.Text = L("button.component_library", "Edit Desktop");
|
||||||
|
ApplyTaskbarProfilePopupTheme(_appearanceThemeService.GetCurrent());
|
||||||
|
|
||||||
|
ToolTip.SetTip(TaskbarProfileButton, profile.DisplayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyProfileAvatarVisual(Image? image, TextBlock? fallbackText, CurrentUserProfileSnapshot profile)
|
||||||
|
{
|
||||||
|
if (image is not null)
|
||||||
|
{
|
||||||
|
image.Source = profile.AvatarBitmap;
|
||||||
|
image.IsVisible = profile.AvatarBitmap is not null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fallbackText is not null)
|
||||||
|
{
|
||||||
|
fallbackText.Text = profile.FallbackMonogram;
|
||||||
|
fallbackText.IsVisible = profile.AvatarBitmap is null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyTaskbarProfilePopupTheme(AppearanceThemeSnapshot snapshot)
|
||||||
|
{
|
||||||
|
if (TaskbarProfilePopupPanel is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var palette = BuildTaskbarProfilePopupMaterialPalette(snapshot);
|
||||||
|
SetTaskbarProfilePopupBrush("TaskbarProfilePopupSurfaceBrush", palette.SurfaceColor);
|
||||||
|
SetTaskbarProfilePopupBrush("TaskbarProfilePopupOutlineBrush", palette.OutlineColor);
|
||||||
|
SetTaskbarProfilePopupBrush("TaskbarProfilePopupAvatarSurfaceBrush", palette.AvatarSurfaceColor);
|
||||||
|
SetTaskbarProfilePopupBrush("TaskbarProfilePopupTextBrush", palette.PrimaryTextColor);
|
||||||
|
SetTaskbarProfilePopupBrush("TaskbarProfilePopupAccentBrush", palette.AccentColor);
|
||||||
|
SetTaskbarProfilePopupBrush("TaskbarProfilePopupActionHoverBrush", palette.HoverColor);
|
||||||
|
SetTaskbarProfilePopupBrush("TaskbarProfilePopupActionPressedBrush", palette.PressedColor);
|
||||||
|
SetTaskbarProfilePopupBrush("TaskbarProfilePopupDividerBrush", palette.DividerColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetTaskbarProfilePopupBrush(string resourceKey, Color color)
|
||||||
|
{
|
||||||
|
TaskbarProfilePopupPanel.Resources[resourceKey] = new SolidColorBrush(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TaskbarProfilePopupMaterialPalette BuildTaskbarProfilePopupMaterialPalette(AppearanceThemeSnapshot snapshot)
|
||||||
|
{
|
||||||
|
var primary = snapshot.MonetPalette.Primary.A > 0
|
||||||
|
? snapshot.MonetPalette.Primary
|
||||||
|
: snapshot.AccentColor;
|
||||||
|
if (primary == default)
|
||||||
|
{
|
||||||
|
primary = Color.Parse("#FF6750A4");
|
||||||
|
}
|
||||||
|
|
||||||
|
var neutral = snapshot.MonetPalette.Neutral.A > 0
|
||||||
|
? snapshot.MonetPalette.Neutral
|
||||||
|
: snapshot.IsNightMode
|
||||||
|
? Color.Parse("#FF1A1F27")
|
||||||
|
: Color.Parse("#FFF7F9FD");
|
||||||
|
var neutralVariant = snapshot.MonetPalette.NeutralVariant.A > 0
|
||||||
|
? snapshot.MonetPalette.NeutralVariant
|
||||||
|
: ColorMath.Blend(neutral, primary, snapshot.IsNightMode ? 0.20 : 0.10);
|
||||||
|
|
||||||
|
var surfaceBase = snapshot.IsNightMode
|
||||||
|
? Color.Parse("#FF141A22")
|
||||||
|
: Color.Parse("#FFFCFCFF");
|
||||||
|
var surface = ColorMath.Blend(surfaceBase, neutral, snapshot.IsNightMode ? 0.52 : 0.46);
|
||||||
|
surface = ColorMath.Blend(surface, primary, snapshot.IsNightMode ? 0.12 : 0.05);
|
||||||
|
|
||||||
|
var outlineSeed = snapshot.IsNightMode
|
||||||
|
? ColorMath.Blend(neutralVariant, Color.Parse("#FFFFFFFF"), 0.28)
|
||||||
|
: ColorMath.Blend(neutralVariant, Color.Parse("#FF111827"), 0.12);
|
||||||
|
var outline = Color.FromArgb(
|
||||||
|
snapshot.IsNightMode ? (byte)0x82 : (byte)0x38,
|
||||||
|
outlineSeed.R,
|
||||||
|
outlineSeed.G,
|
||||||
|
outlineSeed.B);
|
||||||
|
|
||||||
|
var primaryTextPreferred = snapshot.IsNightMode
|
||||||
|
? Color.Parse("#FFF4F7FB")
|
||||||
|
: Color.Parse("#FF14171B");
|
||||||
|
var primaryText = ColorMath.EnsureContrast(primaryTextPreferred, surface, 7.0);
|
||||||
|
var accent = ColorMath.EnsureContrast(primary, surface, 3.0);
|
||||||
|
var avatarSurface = ColorMath.Blend(surface, primary, snapshot.IsNightMode ? 0.26 : 0.16);
|
||||||
|
var hover = ColorMath.Blend(surface, primary, snapshot.IsNightMode ? 0.20 : 0.10);
|
||||||
|
var pressed = ColorMath.Blend(surface, primary, snapshot.IsNightMode ? 0.30 : 0.18);
|
||||||
|
var divider = Color.FromArgb(
|
||||||
|
snapshot.IsNightMode ? (byte)0x44 : (byte)0x20,
|
||||||
|
outlineSeed.R,
|
||||||
|
outlineSeed.G,
|
||||||
|
outlineSeed.B);
|
||||||
|
|
||||||
|
return new TaskbarProfilePopupMaterialPalette(
|
||||||
|
surface,
|
||||||
|
outline,
|
||||||
|
avatarSurface,
|
||||||
|
primaryText,
|
||||||
|
accent,
|
||||||
|
hover,
|
||||||
|
pressed,
|
||||||
|
divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTaskbarProfileButtonClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_ = sender;
|
||||||
|
_ = e;
|
||||||
|
|
||||||
|
if (TaskbarProfileButton is null || TaskbarProfilePopup is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TaskbarProfilePopup.IsOpen)
|
||||||
|
{
|
||||||
|
TaskbarProfilePopup.IsOpen = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshTaskbarProfilePresentation();
|
||||||
|
TaskbarProfilePopup.IsOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
private void OnOpenComponentLibraryClick(object? sender, RoutedEventArgs e)
|
private void OnOpenComponentLibraryClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_ = sender;
|
_ = sender;
|
||||||
_ = e;
|
_ = e;
|
||||||
|
if (TaskbarProfilePopup is not null)
|
||||||
|
{
|
||||||
|
TaskbarProfilePopup.IsOpen = false;
|
||||||
|
}
|
||||||
|
ExecuteTaskbarDesktopEditAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnOpenSettingsClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_ = sender;
|
||||||
|
_ = e;
|
||||||
|
if (TaskbarProfilePopup is not null)
|
||||||
|
{
|
||||||
|
TaskbarProfilePopup.IsOpen = false;
|
||||||
|
}
|
||||||
|
ExecuteTaskbarSettingsAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteTaskbarDesktopEditAction()
|
||||||
|
{
|
||||||
if (_isComponentLibraryOpen)
|
if (_isComponentLibraryOpen)
|
||||||
{
|
{
|
||||||
CloseComponentLibraryWindow(reopenSettings: false);
|
CloseComponentLibraryWindow(reopenSettings: false);
|
||||||
@@ -121,11 +295,8 @@ public partial class MainWindow
|
|||||||
OpenComponentLibraryWindow();
|
OpenComponentLibraryWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnOpenSettingsClick(object? sender, RoutedEventArgs e)
|
private void ExecuteTaskbarSettingsAction()
|
||||||
{
|
{
|
||||||
_ = sender;
|
|
||||||
_ = e;
|
|
||||||
|
|
||||||
if (_isComponentLibraryOpen)
|
if (_isComponentLibraryOpen)
|
||||||
{
|
{
|
||||||
CloseComponentLibraryWindow(reopenSettings: false);
|
CloseComponentLibraryWindow(reopenSettings: false);
|
||||||
@@ -163,7 +334,6 @@ public partial class MainWindow
|
|||||||
|
|
||||||
_topStatusComponentIds.Add(BuiltInComponentIds.Clock);
|
_topStatusComponentIds.Add(BuiltInComponentIds.Clock);
|
||||||
ApplyTopStatusComponentVisibility();
|
ApplyTopStatusComponentVisibility();
|
||||||
UpdateWallpaperPreviewLayout();
|
|
||||||
PersistSettings();
|
PersistSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +346,6 @@ public partial class MainWindow
|
|||||||
|
|
||||||
_topStatusComponentIds.Remove(BuiltInComponentIds.Clock);
|
_topStatusComponentIds.Remove(BuiltInComponentIds.Clock);
|
||||||
ApplyTopStatusComponentVisibility();
|
ApplyTopStatusComponentVisibility();
|
||||||
UpdateWallpaperPreviewLayout();
|
|
||||||
PersistSettings();
|
PersistSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,25 +426,6 @@ public partial class MainWindow
|
|||||||
{
|
{
|
||||||
TopStatusBarHost.IsVisible = hasVisibleTopStatusComponent;
|
TopStatusBarHost.IsVisible = hasVisibleTopStatusComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WallpaperPreviewClockWidget is not null)
|
|
||||||
{
|
|
||||||
WallpaperPreviewClockWidget.IsVisible = showClock;
|
|
||||||
if (showClock)
|
|
||||||
{
|
|
||||||
WallpaperPreviewClockWidget.SetDisplayFormat(_clockDisplayFormat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (WallpaperPreviewTopStatusBarHost is not null)
|
|
||||||
{
|
|
||||||
WallpaperPreviewTopStatusBarHost.IsVisible = hasVisibleTopStatusComponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GridPreviewTopStatusBarHost is not null)
|
|
||||||
{
|
|
||||||
GridPreviewTopStatusBarHost.IsVisible = hasVisibleTopStatusComponent;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TaskbarContext GetCurrentTaskbarContext()
|
private TaskbarContext GetCurrentTaskbarContext()
|
||||||
@@ -286,19 +436,15 @@ public partial class MainWindow
|
|||||||
private void ApplyTaskbarActionVisibility(TaskbarContext context)
|
private void ApplyTaskbarActionVisibility(TaskbarContext context)
|
||||||
{
|
{
|
||||||
if (BackToWindowsButton is null ||
|
if (BackToWindowsButton is null ||
|
||||||
OpenComponentLibraryButton is null ||
|
TaskbarProfileButton is null)
|
||||||
OpenSettingsButton is null)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var showMinimize = _pinnedTaskbarActions.Contains(TaskbarActionId.MinimizeToWindows);
|
var showMinimize = _pinnedTaskbarActions.Contains(TaskbarActionId.MinimizeToWindows);
|
||||||
var showSettings = true;
|
|
||||||
var showDesktopEdit = _isSettingsOpen;
|
|
||||||
|
|
||||||
BackToWindowsButton.IsVisible = showMinimize;
|
BackToWindowsButton.IsVisible = showMinimize;
|
||||||
OpenSettingsButton.IsVisible = showSettings;
|
TaskbarProfileButton.IsVisible = true;
|
||||||
OpenComponentLibraryButton.IsVisible = showDesktopEdit;
|
|
||||||
|
|
||||||
if (TaskbarFixedActionsHost is not null)
|
if (TaskbarFixedActionsHost is not null)
|
||||||
{
|
{
|
||||||
@@ -307,7 +453,7 @@ public partial class MainWindow
|
|||||||
|
|
||||||
if (TaskbarSettingsActionHost is not null)
|
if (TaskbarSettingsActionHost is not null)
|
||||||
{
|
{
|
||||||
TaskbarSettingsActionHost.IsVisible = showSettings || showDesktopEdit;
|
TaskbarSettingsActionHost.IsVisible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateOpenSettingsActionVisualState();
|
UpdateOpenSettingsActionVisualState();
|
||||||
@@ -326,24 +472,10 @@ public partial class MainWindow
|
|||||||
|
|
||||||
private void UpdateOpenSettingsActionVisualState()
|
private void UpdateOpenSettingsActionVisualState()
|
||||||
{
|
{
|
||||||
if (OpenSettingsButtonTextBlock is null || OpenSettingsButton is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var showBackToDesktop = _isSettingsOpen;
|
|
||||||
var buttonText = L("settings.back_to_desktop", "Back to Desktop");
|
|
||||||
OpenSettingsButtonTextBlock.IsVisible = showBackToDesktop;
|
|
||||||
OpenSettingsButtonTextBlock.Text = buttonText;
|
|
||||||
ToolTip.SetTip(
|
|
||||||
OpenSettingsButton,
|
|
||||||
showBackToDesktop
|
|
||||||
? buttonText
|
|
||||||
: L("tooltip.open_settings", "Settings"));
|
|
||||||
|
|
||||||
var effectiveCellSize = _currentDesktopCellSize > 0
|
var effectiveCellSize = _currentDesktopCellSize > 0
|
||||||
? _currentDesktopCellSize
|
? _currentDesktopCellSize
|
||||||
: Math.Max(32, Math.Min(Bounds.Width, Bounds.Height) / Math.Max(1, _targetShortSideCells));
|
: Math.Max(32, Math.Min(Bounds.Width, Bounds.Height) / Math.Max(1, _targetShortSideCells));
|
||||||
|
RefreshTaskbarProfilePresentation();
|
||||||
ApplyWidgetSizing(effectiveCellSize);
|
ApplyWidgetSizing(effectiveCellSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
@@ -25,45 +26,6 @@ public partial class MainWindow
|
|||||||
private TextBlock? CurrentRenderBackendLabelTextBlock => this.FindControl<TextBlock>("CurrentRenderBackendLabelTextBlock");
|
private TextBlock? CurrentRenderBackendLabelTextBlock => this.FindControl<TextBlock>("CurrentRenderBackendLabelTextBlock");
|
||||||
private TextBlock? CurrentRenderBackendValueTextBlock => this.FindControl<TextBlock>("CurrentRenderBackendValueTextBlock");
|
private TextBlock? CurrentRenderBackendValueTextBlock => this.FindControl<TextBlock>("CurrentRenderBackendValueTextBlock");
|
||||||
private TextBlock? CurrentRenderBackendImplementationTextBlock => this.FindControl<TextBlock>("CurrentRenderBackendImplementationTextBlock");
|
private TextBlock? CurrentRenderBackendImplementationTextBlock => this.FindControl<TextBlock>("CurrentRenderBackendImplementationTextBlock");
|
||||||
private Slider? GridSizeSlider => this.FindControl<Slider>("GridSizeSlider");
|
|
||||||
private NumberBox? GridSizeNumberBox => this.FindControl<NumberBox>("GridSizeNumberBox");
|
|
||||||
private Slider? GridEdgeInsetSlider => this.FindControl<Slider>("GridEdgeInsetSlider");
|
|
||||||
private NumberBox? GridEdgeInsetNumberBox => this.FindControl<NumberBox>("GridEdgeInsetNumberBox");
|
|
||||||
private TextBlock? GridEdgeInsetComputedPxTextBlock => this.FindControl<TextBlock>("GridEdgeInsetComputedPxTextBlock");
|
|
||||||
private TextBlock? GridInfoTextBlock => this.FindControl<TextBlock>("GridInfoTextBlock");
|
|
||||||
private ComboBox? GridSpacingPresetComboBox => this.FindControl<ComboBox>("GridSpacingPresetComboBox");
|
|
||||||
private Border? GridPreviewHost => this.FindControl<Border>("GridPreviewHost");
|
|
||||||
private Border? GridPreviewFrame => this.FindControl<Border>("GridPreviewFrame");
|
|
||||||
private Border? GridPreviewViewport => this.FindControl<Border>("GridPreviewViewport");
|
|
||||||
private Grid? GridPreviewGrid => this.FindControl<Grid>("GridPreviewGrid");
|
|
||||||
private Canvas? GridPreviewLinesCanvas => this.FindControl<Canvas>("GridPreviewLinesCanvas");
|
|
||||||
private Border? GridPreviewTopStatusBarHost => this.FindControl<Border>("GridPreviewTopStatusBarHost");
|
|
||||||
private StackPanel? GridPreviewTopStatusComponentsPanel => this.FindControl<StackPanel>("GridPreviewTopStatusComponentsPanel");
|
|
||||||
private Border? GridPreviewBottomTaskbarContainer => this.FindControl<Border>("GridPreviewBottomTaskbarContainer");
|
|
||||||
private StackPanel? GridPreviewBackButtonVisual => this.FindControl<StackPanel>("GridPreviewBackButtonVisual");
|
|
||||||
private TextBlock? GridPreviewBackButtonTextBlock => this.FindControl<TextBlock>("GridPreviewBackButtonTextBlock");
|
|
||||||
private StackPanel? GridPreviewComponentLibraryVisual => this.FindControl<StackPanel>("GridPreviewComponentLibraryVisual");
|
|
||||||
private FluentIcons.Avalonia.FluentIcon? GridPreviewComponentLibraryIcon => this.FindControl<FluentIcons.Avalonia.FluentIcon>("GridPreviewComponentLibraryIcon");
|
|
||||||
private TextBlock? GridPreviewComponentLibraryTextBlock => this.FindControl<TextBlock>("GridPreviewComponentLibraryTextBlock");
|
|
||||||
private FluentIcons.Avalonia.SymbolIcon? GridPreviewSettingsButtonIcon => this.FindControl<FluentIcons.Avalonia.SymbolIcon>("GridPreviewSettingsButtonIcon");
|
|
||||||
private Border? WallpaperPreviewHost => this.FindControl<Border>("WallpaperPreviewHost");
|
|
||||||
private Border? WallpaperPreviewFrame => this.FindControl<Border>("WallpaperPreviewFrame");
|
|
||||||
private Border? WallpaperPreviewViewport => this.FindControl<Border>("WallpaperPreviewViewport");
|
|
||||||
private Grid? WallpaperPreviewGrid => this.FindControl<Grid>("WallpaperPreviewGrid");
|
|
||||||
private Border? WallpaperPreviewTopStatusBarHost => this.FindControl<Border>("WallpaperPreviewTopStatusBarHost");
|
|
||||||
private StackPanel? WallpaperPreviewTopStatusComponentsPanel => this.FindControl<StackPanel>("WallpaperPreviewTopStatusComponentsPanel");
|
|
||||||
private Border? WallpaperPreviewBottomTaskbarContainer => this.FindControl<Border>("WallpaperPreviewBottomTaskbarContainer");
|
|
||||||
private ClockWidget? WallpaperPreviewClockWidget => this.FindControl<ClockWidget>("WallpaperPreviewClockWidget");
|
|
||||||
private StackPanel? WallpaperPreviewBackButtonVisual => this.FindControl<StackPanel>("WallpaperPreviewBackButtonVisual");
|
|
||||||
private TextBlock? WallpaperPreviewBackButtonTextBlock => this.FindControl<TextBlock>("WallpaperPreviewBackButtonTextBlock");
|
|
||||||
private StackPanel? WallpaperPreviewComponentLibraryVisual => this.FindControl<StackPanel>("WallpaperPreviewComponentLibraryVisual");
|
|
||||||
private TextBlock? WallpaperPreviewComponentLibraryTextBlock => this.FindControl<TextBlock>("WallpaperPreviewComponentLibraryTextBlock");
|
|
||||||
private FluentIcons.Avalonia.SymbolIcon? WallpaperPreviewSettingsButtonIcon => this.FindControl<FluentIcons.Avalonia.SymbolIcon>("WallpaperPreviewSettingsButtonIcon");
|
|
||||||
private ComboBox? StatusBarSpacingModeComboBox => this.FindControl<ComboBox>("StatusBarSpacingModeComboBox");
|
|
||||||
private SettingsExpanderItem? StatusBarSpacingCustomPanel => this.FindControl<SettingsExpanderItem>("StatusBarSpacingCustomPanel");
|
|
||||||
private Slider? StatusBarSpacingSlider => this.FindControl<Slider>("StatusBarSpacingSlider");
|
|
||||||
private NumberBox? StatusBarSpacingNumberBox => this.FindControl<NumberBox>("StatusBarSpacingNumberBox");
|
|
||||||
private TextBlock? StatusBarSpacingComputedPxTextBlock => this.FindControl<TextBlock>("StatusBarSpacingComputedPxTextBlock");
|
|
||||||
private ComboBox? TimeZoneComboBox => this.FindControl<ComboBox>("TimeZoneComboBox");
|
private ComboBox? TimeZoneComboBox => this.FindControl<ComboBox>("TimeZoneComboBox");
|
||||||
private SettingsExpander? LauncherHiddenItemsSettingsExpander => this.FindControl<SettingsExpander>("LauncherHiddenItemsSettingsExpander");
|
private SettingsExpander? LauncherHiddenItemsSettingsExpander => this.FindControl<SettingsExpander>("LauncherHiddenItemsSettingsExpander");
|
||||||
private TextBlock? LauncherHiddenItemsEmptyTextBlock => this.FindControl<TextBlock>("LauncherHiddenItemsEmptyTextBlock");
|
private TextBlock? LauncherHiddenItemsEmptyTextBlock => this.FindControl<TextBlock>("LauncherHiddenItemsEmptyTextBlock");
|
||||||
@@ -138,12 +100,12 @@ public partial class MainWindow
|
|||||||
{
|
{
|
||||||
Title = L("app.title", "LanMountainDesktop");
|
Title = L("app.title", "LanMountainDesktop");
|
||||||
BackToWindowsTextBlock.Text = L("button.back_to_windows", "Back to Windows");
|
BackToWindowsTextBlock.Text = L("button.back_to_windows", "Back to Windows");
|
||||||
OpenComponentLibraryTextBlock.Text = L("button.component_library", "Edit Desktop");
|
|
||||||
ComponentLibraryTitleTextBlock.Text = L("component_library.title", "Widgets");
|
ComponentLibraryTitleTextBlock.Text = L("component_library.title", "Widgets");
|
||||||
LauncherTitleTextBlock.Text = L("launcher.title", "App Launcher");
|
LauncherTitleTextBlock.Text = L("launcher.title", "App Launcher");
|
||||||
LauncherSubtitleTextBlock.Text = OperatingSystem.IsLinux()
|
LauncherSubtitleTextBlock.Text = OperatingSystem.IsLinux()
|
||||||
? L("launcher.subtitle_linux", "Displays installed apps discovered from Linux desktop entries.")
|
? L("launcher.subtitle_linux", "Displays installed apps discovered from Linux desktop entries.")
|
||||||
: L("launcher.subtitle", "Displays all apps and folders based on the Windows Start menu structure.");
|
: L("launcher.subtitle", "Displays all apps and folders based on the Windows Start menu structure.");
|
||||||
|
RefreshTaskbarProfilePresentation();
|
||||||
|
|
||||||
UpdateCurrentRenderBackendStatus();
|
UpdateCurrentRenderBackendStatus();
|
||||||
RenderLauncherHiddenItemsList();
|
RenderLauncherHiddenItemsList();
|
||||||
@@ -238,46 +200,63 @@ public partial class MainWindow
|
|||||||
GlassEffectService.ApplyGlassResources(applicationResources, context);
|
GlassEffectService.ApplyGlassResources(applicationResources, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
_defaultDesktopBackground = GetThemeBrush("AdaptiveWindowBackgroundBrush")
|
_defaultDesktopBackground = CreateNeutralWallpaperFallbackBrush();
|
||||||
?? GetThemeBrush("AdaptiveSurfaceBaseBrush");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TryRestoreWallpaper(string? savedWallpaperPath, string? type = null, string? color = null)
|
private void TryRestoreWallpaper(
|
||||||
|
string? savedWallpaperPath,
|
||||||
|
string? type = null,
|
||||||
|
string? color = null,
|
||||||
|
string? placement = null)
|
||||||
{
|
{
|
||||||
_wallpaperPath = string.IsNullOrWhiteSpace(savedWallpaperPath) ? null : savedWallpaperPath;
|
_wallpaperPath = string.IsNullOrWhiteSpace(savedWallpaperPath) ? null : savedWallpaperPath;
|
||||||
_wallpaperType = type ?? "Image";
|
_wallpaperType = string.IsNullOrWhiteSpace(type) ? "Image" : type.Trim();
|
||||||
if (TryParseColor(color, out var parsedColor))
|
_wallpaperPlacement = WallpaperImageBrushFactory.NormalizePlacement(placement);
|
||||||
{
|
_wallpaperSolidColor = TryParseColor(color, out var parsedColor) ? parsedColor : null;
|
||||||
_wallpaperSolidColor = parsedColor;
|
_wallpaperVideoPath = null;
|
||||||
}
|
_wallpaperDisplayState = WallpaperDisplayState.NoWallpaperConfigured;
|
||||||
|
|
||||||
_wallpaperBitmap?.Dispose();
|
_wallpaperBitmap?.Dispose();
|
||||||
_wallpaperBitmap = null;
|
_wallpaperBitmap = null;
|
||||||
|
|
||||||
if (_wallpaperType == "SolidColor")
|
if (string.Equals(_wallpaperType, "SolidColor", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
_wallpaperMediaType = WallpaperMediaType.SolidColor;
|
_wallpaperMediaType = WallpaperMediaType.SolidColor;
|
||||||
|
_wallpaperDisplayState = _wallpaperSolidColor.HasValue
|
||||||
|
? WallpaperDisplayState.CurrentValidWallpaper
|
||||||
|
: WallpaperDisplayState.NoWallpaperConfigured;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(_wallpaperPath) || !File.Exists(_wallpaperPath))
|
if (string.IsNullOrWhiteSpace(_wallpaperPath))
|
||||||
{
|
{
|
||||||
_wallpaperMediaType = WallpaperMediaType.None;
|
_wallpaperMediaType = WallpaperMediaType.None;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var extension = Path.GetExtension(_wallpaperPath);
|
var extension = Path.GetExtension(_wallpaperPath);
|
||||||
if (SupportedVideoExtensions.Contains(extension) || _wallpaperType == "Video")
|
var requestedTypeIsVideo = string.Equals(_wallpaperType, "Video", StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (SupportedVideoExtensions.Contains(extension) || requestedTypeIsVideo)
|
||||||
{
|
{
|
||||||
_wallpaperMediaType = WallpaperMediaType.Video;
|
_wallpaperMediaType = WallpaperMediaType.Video;
|
||||||
_wallpaperVideoPath = _wallpaperPath;
|
_wallpaperVideoPath = _wallpaperPath;
|
||||||
|
_wallpaperDisplayState = File.Exists(_wallpaperPath)
|
||||||
|
? WallpaperDisplayState.CurrentValidWallpaper
|
||||||
|
: WallpaperDisplayState.TemporarilyUnavailable;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!SupportedImageExtensions.Contains(extension))
|
if (!SupportedImageExtensions.Contains(extension))
|
||||||
{
|
{
|
||||||
_wallpaperMediaType = WallpaperMediaType.None;
|
_wallpaperMediaType = WallpaperMediaType.Image;
|
||||||
_wallpaperPath = null;
|
_wallpaperDisplayState = WallpaperDisplayState.TemporarilyUnavailable;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(_wallpaperPath))
|
||||||
|
{
|
||||||
|
_wallpaperMediaType = WallpaperMediaType.Image;
|
||||||
|
_wallpaperDisplayState = WallpaperDisplayState.TemporarilyUnavailable;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,11 +265,13 @@ public partial class MainWindow
|
|||||||
using var stream = File.OpenRead(_wallpaperPath);
|
using var stream = File.OpenRead(_wallpaperPath);
|
||||||
_wallpaperBitmap = new Bitmap(stream);
|
_wallpaperBitmap = new Bitmap(stream);
|
||||||
_wallpaperMediaType = WallpaperMediaType.Image;
|
_wallpaperMediaType = WallpaperMediaType.Image;
|
||||||
|
_wallpaperDisplayState = WallpaperDisplayState.CurrentValidWallpaper;
|
||||||
|
CacheLastValidWallpaperBitmap(_wallpaperPath);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
_wallpaperMediaType = WallpaperMediaType.None;
|
_wallpaperMediaType = WallpaperMediaType.Image;
|
||||||
_wallpaperPath = null;
|
_wallpaperDisplayState = WallpaperDisplayState.TemporarilyUnavailable;
|
||||||
_wallpaperBitmap?.Dispose();
|
_wallpaperBitmap?.Dispose();
|
||||||
_wallpaperBitmap = null;
|
_wallpaperBitmap = null;
|
||||||
}
|
}
|
||||||
@@ -298,22 +279,42 @@ public partial class MainWindow
|
|||||||
|
|
||||||
private void ApplyWallpaperBrush()
|
private void ApplyWallpaperBrush()
|
||||||
{
|
{
|
||||||
|
DesktopWallpaperImageLayer.Background = null;
|
||||||
|
DesktopWallpaperImageLayer.IsVisible = false;
|
||||||
|
|
||||||
if (_wallpaperMediaType == WallpaperMediaType.SolidColor && _wallpaperSolidColor.HasValue)
|
if (_wallpaperMediaType == WallpaperMediaType.SolidColor && _wallpaperSolidColor.HasValue)
|
||||||
{
|
{
|
||||||
DesktopWallpaperLayer.Background = new SolidColorBrush(_wallpaperSolidColor.Value);
|
DesktopWallpaperLayer.Background = new SolidColorBrush(_wallpaperSolidColor.Value);
|
||||||
|
ApplyVideoWallpaperPosterVisibility(showPoster: false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_wallpaperMediaType == WallpaperMediaType.Image && _wallpaperBitmap is not null)
|
if (_wallpaperDisplayState == WallpaperDisplayState.CurrentValidWallpaper &&
|
||||||
|
_wallpaperMediaType == WallpaperMediaType.Image &&
|
||||||
|
_wallpaperBitmap is not null)
|
||||||
{
|
{
|
||||||
DesktopWallpaperLayer.Background = new ImageBrush(_wallpaperBitmap)
|
DesktopWallpaperLayer.Background = _defaultDesktopBackground ?? CreateNeutralWallpaperFallbackBrush();
|
||||||
{
|
DesktopWallpaperImageLayer.Background = WallpaperImageBrushFactory.Create(_wallpaperBitmap, _wallpaperPlacement);
|
||||||
Stretch = Stretch.UniformToFill
|
DesktopWallpaperImageLayer.IsVisible = true;
|
||||||
};
|
ApplyVideoWallpaperPosterVisibility(showPoster: false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DesktopWallpaperLayer.Background = _defaultDesktopBackground ?? Brushes.Transparent;
|
if (_wallpaperDisplayState == WallpaperDisplayState.TemporarilyUnavailable &&
|
||||||
|
_lastValidWallpaperBitmap is not null &&
|
||||||
|
!string.IsNullOrWhiteSpace(_wallpaperPath) &&
|
||||||
|
string.Equals(_lastValidWallpaperPath, _wallpaperPath, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
DesktopWallpaperLayer.Background = _defaultDesktopBackground ?? CreateNeutralWallpaperFallbackBrush();
|
||||||
|
DesktopWallpaperImageLayer.Background = WallpaperImageBrushFactory.Create(_lastValidWallpaperBitmap, _wallpaperPlacement);
|
||||||
|
DesktopWallpaperImageLayer.IsVisible = true;
|
||||||
|
ApplyVideoWallpaperPosterVisibility(showPoster: false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DesktopWallpaperLayer.Background = _defaultDesktopBackground ?? CreateNeutralWallpaperFallbackBrush();
|
||||||
|
ApplyVideoWallpaperPosterVisibility(
|
||||||
|
showPoster: _wallpaperMediaType == WallpaperMediaType.Video && _videoWallpaperPosterBitmap is not null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateWallpaperDisplay()
|
private void UpdateWallpaperDisplay()
|
||||||
@@ -337,6 +338,7 @@ public partial class MainWindow
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(videoPath) || !File.Exists(videoPath))
|
if (string.IsNullOrWhiteSpace(videoPath) || !File.Exists(videoPath))
|
||||||
{
|
{
|
||||||
|
ApplyVideoWallpaperPosterVisibility(showPoster: _videoWallpaperPosterBitmap is not null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,13 +360,25 @@ public partial class MainWindow
|
|||||||
videoView.IsVisible = true;
|
videoView.IsVisible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!string.Equals(_videoWallpaperPosterPath, videoPath, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
ApplyVideoWallpaperPosterVisibility(showPoster: false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ApplyVideoWallpaperPosterVisibility(showPoster: _videoWallpaperPosterBitmap is not null);
|
||||||
|
}
|
||||||
|
|
||||||
if (!_videoWallpaperPlayer.IsPlaying)
|
if (!_videoWallpaperPlayer.IsPlaying)
|
||||||
{
|
{
|
||||||
_videoWallpaperPlayer.Play();
|
_videoWallpaperPlayer.Play();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TryCaptureVideoWallpaperPosterFrame(videoPath);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
ApplyVideoWallpaperPosterVisibility(showPoster: _videoWallpaperPosterBitmap is not null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,6 +391,7 @@ public partial class MainWindow
|
|||||||
|
|
||||||
_videoWallpaperPlayer?.Stop();
|
_videoWallpaperPlayer?.Stop();
|
||||||
_wallpaperVideoPath = null;
|
_wallpaperVideoPath = null;
|
||||||
|
ApplyVideoWallpaperPosterVisibility(showPoster: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double CalculateCurrentBackgroundLuminance()
|
private double CalculateCurrentBackgroundLuminance()
|
||||||
@@ -514,13 +529,22 @@ public partial class MainWindow
|
|||||||
InitializeDesktopSurfaceState(layoutSnapshot);
|
InitializeDesktopSurfaceState(layoutSnapshot);
|
||||||
InitializeLauncherVisibilitySettings(launcherSnapshot);
|
InitializeLauncherVisibilitySettings(launcherSnapshot);
|
||||||
InitializeDesktopComponentPlacements(layoutSnapshot);
|
InitializeDesktopComponentPlacements(layoutSnapshot);
|
||||||
TryRestoreWallpaper(snapshot.WallpaperPath, snapshot.WallpaperType, snapshot.WallpaperColor);
|
|
||||||
if (TryParseColor(snapshot.ThemeColor, out var savedThemeColor))
|
if (TryParseColor(snapshot.ThemeColor, out var savedThemeColor))
|
||||||
{
|
{
|
||||||
_selectedThemeColor = savedThemeColor;
|
_selectedThemeColor = savedThemeColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
_isNightMode = snapshot.IsNightMode ?? (CalculateCurrentBackgroundLuminance() < LightBackgroundLuminanceThreshold);
|
_isNightMode = snapshot.IsNightMode ?? _isNightMode;
|
||||||
|
_defaultDesktopBackground = CreateNeutralWallpaperFallbackBrush();
|
||||||
|
TryRestoreWallpaper(
|
||||||
|
snapshot.WallpaperPath,
|
||||||
|
snapshot.WallpaperType,
|
||||||
|
snapshot.WallpaperColor,
|
||||||
|
snapshot.WallpaperPlacement);
|
||||||
|
if (!snapshot.IsNightMode.HasValue)
|
||||||
|
{
|
||||||
|
_isNightMode = CalculateCurrentBackgroundLuminance() < LightBackgroundLuminanceThreshold;
|
||||||
|
}
|
||||||
ApplyNightModeState(_isNightMode, refreshPalettes: true);
|
ApplyNightModeState(_isNightMode, refreshPalettes: true);
|
||||||
ApplyWallpaperBrush();
|
ApplyWallpaperBrush();
|
||||||
UpdateWallpaperDisplay();
|
UpdateWallpaperDisplay();
|
||||||
@@ -536,6 +560,7 @@ public partial class MainWindow
|
|||||||
|
|
||||||
private AppSettingsSnapshot BuildAppSettingsSnapshot()
|
private AppSettingsSnapshot BuildAppSettingsSnapshot()
|
||||||
{
|
{
|
||||||
|
var latestWallpaperState = _settingsFacade.Wallpaper.Get();
|
||||||
var latestWeatherState = _weatherSettingsService.Get();
|
var latestWeatherState = _weatherSettingsService.Get();
|
||||||
var latestUpdateState = _updateSettingsService.Get();
|
var latestUpdateState = _updateSettingsService.Get();
|
||||||
var latestThemeState = _themeSettingsService.Get();
|
var latestThemeState = _themeSettingsService.Get();
|
||||||
@@ -550,9 +575,12 @@ public partial class MainWindow
|
|||||||
SystemMaterialMode = latestThemeState.SystemMaterialMode,
|
SystemMaterialMode = latestThemeState.SystemMaterialMode,
|
||||||
SelectedWallpaperSeed = latestThemeState.SelectedWallpaperSeed,
|
SelectedWallpaperSeed = latestThemeState.SelectedWallpaperSeed,
|
||||||
UseSystemChrome = latestThemeState.UseSystemChrome,
|
UseSystemChrome = latestThemeState.UseSystemChrome,
|
||||||
WallpaperPath = _wallpaperPath,
|
WallpaperPath = latestWallpaperState.WallpaperPath,
|
||||||
WallpaperType = _wallpaperType,
|
WallpaperType = latestWallpaperState.Type,
|
||||||
WallpaperColor = _wallpaperSolidColor?.ToString(),
|
WallpaperColor = string.Equals(latestWallpaperState.Type, "SolidColor", StringComparison.OrdinalIgnoreCase)
|
||||||
|
? latestWallpaperState.Color
|
||||||
|
: null,
|
||||||
|
WallpaperPlacement = latestWallpaperState.Placement,
|
||||||
LanguageCode = _languageCode,
|
LanguageCode = _languageCode,
|
||||||
TimeZoneId = _timeZoneService.CurrentTimeZone.Id,
|
TimeZoneId = _timeZoneService.CurrentTimeZone.Id,
|
||||||
WeatherLocationMode = latestWeatherState.LocationMode,
|
WeatherLocationMode = latestWeatherState.LocationMode,
|
||||||
@@ -587,6 +615,141 @@ public partial class MainWindow
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IBrush CreateNeutralWallpaperFallbackBrush()
|
||||||
|
{
|
||||||
|
var neutralColor = _isNightMode
|
||||||
|
? Color.Parse("#FF0B0F14")
|
||||||
|
: Color.Parse("#FFF6F7F9");
|
||||||
|
return new SolidColorBrush(neutralColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CacheLastValidWallpaperBitmap(string wallpaperPath)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(wallpaperPath) || !File.Exists(wallpaperPath))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var stream = File.OpenRead(wallpaperPath);
|
||||||
|
var cachedBitmap = new Bitmap(stream);
|
||||||
|
_lastValidWallpaperBitmap?.Dispose();
|
||||||
|
_lastValidWallpaperBitmap = cachedBitmap;
|
||||||
|
_lastValidWallpaperPath = wallpaperPath;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Best effort cache only.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyVideoWallpaperPosterVisibility(bool showPoster)
|
||||||
|
{
|
||||||
|
if (DesktopVideoWallpaperImage is not { } posterImage)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!showPoster ||
|
||||||
|
_videoWallpaperPosterBitmap is null ||
|
||||||
|
!string.Equals(_videoWallpaperPosterPath, _wallpaperVideoPath, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
posterImage.IsVisible = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
posterImage.Source = _videoWallpaperPosterBitmap;
|
||||||
|
posterImage.IsVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryCaptureVideoWallpaperPosterFrame(string videoPath)
|
||||||
|
{
|
||||||
|
if (_videoWallpaperPlayer is null || string.IsNullOrWhiteSpace(videoPath))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
var snapshotPath = Path.Combine(
|
||||||
|
Path.GetTempPath(),
|
||||||
|
$"lanmountaindesktop-wallpaper-poster-{Guid.NewGuid():N}.png");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (var attempt = 0; attempt < 12; attempt++)
|
||||||
|
{
|
||||||
|
await Task.Delay(250).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (_wallpaperMediaType != WallpaperMediaType.Video ||
|
||||||
|
!string.Equals(_wallpaperVideoPath, videoPath, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
_videoWallpaperPlayer is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_videoWallpaperPlayer.TakeSnapshot(0, snapshotPath, 640, 360))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(snapshotPath))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileInfo = new FileInfo(snapshotPath);
|
||||||
|
if (fileInfo.Length <= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bitmap posterBitmap;
|
||||||
|
await using (var stream = File.OpenRead(snapshotPath))
|
||||||
|
{
|
||||||
|
posterBitmap = new Bitmap(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
if (_wallpaperMediaType != WallpaperMediaType.Video ||
|
||||||
|
!string.Equals(_wallpaperVideoPath, videoPath, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
posterBitmap.Dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_videoWallpaperPosterBitmap?.Dispose();
|
||||||
|
_videoWallpaperPosterBitmap = posterBitmap;
|
||||||
|
_videoWallpaperPosterPath = videoPath;
|
||||||
|
ApplyVideoWallpaperPosterVisibility(showPoster: true);
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Best effort poster capture only.
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(snapshotPath))
|
||||||
|
{
|
||||||
|
File.Delete(snapshotPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Best effort cleanup only.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private DesktopLayoutSettingsSnapshot BuildDesktopLayoutSettingsSnapshot()
|
private DesktopLayoutSettingsSnapshot BuildDesktopLayoutSettingsSnapshot()
|
||||||
{
|
{
|
||||||
return new DesktopLayoutSettingsSnapshot
|
return new DesktopLayoutSettingsSnapshot
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||||
xmlns:fi="using:FluentIcons.Avalonia"
|
xmlns:fi="using:FluentIcons.Avalonia"
|
||||||
xmlns:ic="using:FluentIcons.Avalonia.Fluent"
|
xmlns:ic="using:FluentIcons.Avalonia.Fluent"
|
||||||
|
xmlns:mi="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:comp="using:LanMountainDesktop.Views.Components"
|
xmlns:comp="using:LanMountainDesktop.Views.Components"
|
||||||
xmlns:vlc="clr-namespace:LibVLCSharp.Avalonia;assembly=LibVLCSharp.Avalonia"
|
xmlns:vlc="clr-namespace:LibVLCSharp.Avalonia;assembly=LibVLCSharp.Avalonia"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
@@ -13,7 +14,6 @@
|
|||||||
d:DesignHeight="720"
|
d:DesignHeight="720"
|
||||||
x:Class="LanMountainDesktop.Views.MainWindow"
|
x:Class="LanMountainDesktop.Views.MainWindow"
|
||||||
x:DataType="vm:MainWindowViewModel"
|
x:DataType="vm:MainWindowViewModel"
|
||||||
Icon="/Assets/avalonia-logo.ico"
|
|
||||||
WindowState="FullScreen"
|
WindowState="FullScreen"
|
||||||
SystemDecorations="None"
|
SystemDecorations="None"
|
||||||
CanResize="False"
|
CanResize="False"
|
||||||
@@ -25,6 +25,75 @@
|
|||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<vm:MainWindowViewModel />
|
<vm:MainWindowViewModel />
|
||||||
</Design.DataContext>
|
</Design.DataContext>
|
||||||
|
|
||||||
|
<Window.Styles>
|
||||||
|
<Style Selector="Border.taskbar-profile-popup-panel">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource TaskbarProfilePopupSurfaceBrush}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource TaskbarProfilePopupOutlineBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="CornerRadius" Value="26" />
|
||||||
|
<Setter Property="Padding" Value="16" />
|
||||||
|
<Setter Property="BoxShadow" Value="0 14 36 #2A000000" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Border.taskbar-profile-popup-avatar">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource TaskbarProfilePopupAvatarSurfaceBrush}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource TaskbarProfilePopupOutlineBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="CornerRadius" Value="22" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBlock.taskbar-profile-popup-primary">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TaskbarProfilePopupTextBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBlock.taskbar-profile-popup-title">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TaskbarProfilePopupTextBrush}" />
|
||||||
|
<Setter Property="FontSize" Value="16" />
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBlock.taskbar-profile-popup-action-text">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TaskbarProfilePopupTextBrush}" />
|
||||||
|
<Setter Property="FontSize" Value="14" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="mi|MaterialIcon.taskbar-profile-popup-action-icon">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TaskbarProfilePopupAccentBrush}" />
|
||||||
|
<Setter Property="Width" Value="20" />
|
||||||
|
<Setter Property="Height" Value="20" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.taskbar-profile-popup-action">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Setter Property="CornerRadius" Value="18" />
|
||||||
|
<Setter Property="Padding" Value="14,12" />
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||||
|
<Setter Property="MinHeight" Value="48" />
|
||||||
|
<Setter Property="Transitions">
|
||||||
|
<Transitions>
|
||||||
|
<BrushTransition Property="Background" Duration="{StaticResource FluttermotionToken.Duration.Fast}" />
|
||||||
|
<TransformOperationsTransition Property="RenderTransform" Duration="{StaticResource FluttermotionToken.Duration.Fast}" />
|
||||||
|
</Transitions>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.taskbar-profile-popup-action:pointerover">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource TaskbarProfilePopupActionHoverBrush}" />
|
||||||
|
<Setter Property="RenderTransform" Value="scale(1.01)" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.taskbar-profile-popup-action:pressed">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource TaskbarProfilePopupActionPressedBrush}" />
|
||||||
|
<Setter Property="RenderTransform" Value="scale(0.985)" />
|
||||||
|
</Style>
|
||||||
|
</Window.Styles>
|
||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid x:Name="DesktopPage"
|
<Grid x:Name="DesktopPage"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
@@ -45,13 +114,14 @@
|
|||||||
IsHitTestVisible="False"
|
IsHitTestVisible="False"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
Background="{DynamicResource AdaptiveSurfaceBaseBrush}" />
|
Background="#FFF6F7F9" />
|
||||||
|
|
||||||
<vlc:VideoView x:Name="DesktopVideoWallpaperView"
|
<Border x:Name="DesktopWallpaperImageLayer"
|
||||||
IsVisible="False"
|
IsVisible="False"
|
||||||
IsHitTestVisible="False"
|
IsHitTestVisible="False"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch" />
|
VerticalAlignment="Stretch"
|
||||||
|
Background="Transparent" />
|
||||||
|
|
||||||
<Image x:Name="DesktopVideoWallpaperImage"
|
<Image x:Name="DesktopVideoWallpaperImage"
|
||||||
IsVisible="False"
|
IsVisible="False"
|
||||||
@@ -60,6 +130,12 @@
|
|||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch" />
|
VerticalAlignment="Stretch" />
|
||||||
|
|
||||||
|
<vlc:VideoView x:Name="DesktopVideoWallpaperView"
|
||||||
|
IsVisible="False"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch" />
|
||||||
|
|
||||||
<Grid x:Name="DesktopGrid"
|
<Grid x:Name="DesktopGrid"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
@@ -254,57 +330,109 @@
|
|||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
BorderThickness="0">
|
BorderThickness="0">
|
||||||
<Grid ColumnDefinitions="Auto,Auto,Auto"
|
<Grid>
|
||||||
ColumnSpacing="8">
|
<Button x:Name="TaskbarProfileButton"
|
||||||
<Button x:Name="OpenSettingsButton"
|
Padding="0"
|
||||||
Grid.Column="0"
|
HorizontalAlignment="Right"
|
||||||
IsVisible="False"
|
VerticalAlignment="Center"
|
||||||
Padding="8"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
VerticalAlignment="Stretch"
|
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
BorderThickness="0"
|
BorderThickness="0"
|
||||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||||
Click="OnOpenSettingsClick"
|
Click="OnTaskbarProfileButtonClick">
|
||||||
ToolTip.Tip="Settings">
|
<Grid HorizontalAlignment="Center"
|
||||||
<StackPanel Orientation="Horizontal"
|
VerticalAlignment="Center">
|
||||||
HorizontalAlignment="Center"
|
<Border x:Name="TaskbarProfileAvatarBorder"
|
||||||
VerticalAlignment="Center"
|
Background="{DynamicResource AdaptiveButtonHoverBackgroundBrush}"
|
||||||
Spacing="8">
|
BorderBrush="{DynamicResource AdaptiveDockGlassBorderBrush}"
|
||||||
<fi:FluentIcon x:Name="OpenSettingsIcon"
|
BorderThickness="1"
|
||||||
Icon="Settings"
|
CornerRadius="999"
|
||||||
IconVariant="Regular" />
|
ClipToBounds="True">
|
||||||
<TextBlock x:Name="OpenSettingsButtonTextBlock"
|
<Grid>
|
||||||
IsVisible="False"
|
<Image x:Name="TaskbarProfileAvatarImage"
|
||||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
Stretch="UniformToFill"
|
||||||
Text="Settings" />
|
IsVisible="False" />
|
||||||
</StackPanel>
|
<TextBlock x:Name="TaskbarProfileAvatarFallbackText"
|
||||||
|
Classes="taskbar-profile-popup-primary"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Text="U" />
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button x:Name="OpenComponentLibraryButton"
|
<Popup x:Name="TaskbarProfilePopup"
|
||||||
Grid.Column="1"
|
Placement="TopEdgeAlignedRight"
|
||||||
IsVisible="False"
|
IsLightDismissEnabled="True">
|
||||||
Padding="8"
|
<Border x:Name="TaskbarProfilePopupPanel"
|
||||||
HorizontalAlignment="Stretch"
|
Classes="taskbar-profile-popup-panel"
|
||||||
VerticalAlignment="Stretch"
|
Margin="0,0,0,10">
|
||||||
Background="Transparent"
|
<StackPanel Width="280"
|
||||||
BorderThickness="0"
|
Spacing="12">
|
||||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
<Grid ColumnDefinitions="Auto,*"
|
||||||
Click="OnOpenComponentLibraryClick"
|
ColumnSpacing="12">
|
||||||
ToolTip.Tip="组件库">
|
<Border x:Name="TaskbarProfileHeaderAvatarBorder"
|
||||||
<StackPanel Orientation="Horizontal"
|
Classes="taskbar-profile-popup-avatar"
|
||||||
HorizontalAlignment="Center"
|
Width="44"
|
||||||
VerticalAlignment="Center"
|
Height="44"
|
||||||
Spacing="8">
|
ClipToBounds="True">
|
||||||
<fi:FluentIcon x:Name="OpenComponentLibraryIcon"
|
<Grid>
|
||||||
Icon="Apps"
|
<Image x:Name="TaskbarProfileHeaderAvatarImage"
|
||||||
IconVariant="Regular" />
|
Stretch="UniformToFill"
|
||||||
<TextBlock x:Name="OpenComponentLibraryTextBlock"
|
IsVisible="False" />
|
||||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
<TextBlock x:Name="TaskbarProfileHeaderAvatarFallbackText"
|
||||||
Text="组件库" />
|
Classes="taskbar-profile-popup-primary"
|
||||||
</StackPanel>
|
HorizontalAlignment="Center"
|
||||||
</Button>
|
VerticalAlignment="Center"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Text="U" />
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="1"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Spacing="2">
|
||||||
|
<TextBlock x:Name="TaskbarProfileDisplayNameTextBlock"
|
||||||
|
Classes="taskbar-profile-popup-title"
|
||||||
|
Text="User" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Border x:Name="TaskbarProfilePopupDivider"
|
||||||
|
Height="1"
|
||||||
|
Background="{DynamicResource TaskbarProfilePopupDividerBrush}" />
|
||||||
|
|
||||||
|
<Button x:Name="TaskbarProfileSettingsActionButton"
|
||||||
|
Classes="taskbar-profile-popup-action"
|
||||||
|
Click="OnOpenSettingsClick">
|
||||||
|
<Grid ColumnDefinitions="Auto,*"
|
||||||
|
ColumnSpacing="12">
|
||||||
|
<mi:MaterialIcon Classes="taskbar-profile-popup-action-icon"
|
||||||
|
Kind="Settings" />
|
||||||
|
<TextBlock x:Name="TaskbarProfileSettingsActionTextBlock"
|
||||||
|
Grid.Column="1"
|
||||||
|
Classes="taskbar-profile-popup-action-text"
|
||||||
|
Text="Settings" />
|
||||||
|
</Grid>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button x:Name="TaskbarProfileDesktopEditActionButton"
|
||||||
|
Classes="taskbar-profile-popup-action"
|
||||||
|
Click="OnOpenComponentLibraryClick">
|
||||||
|
<Grid ColumnDefinitions="Auto,*"
|
||||||
|
ColumnSpacing="12">
|
||||||
|
<mi:MaterialIcon Classes="taskbar-profile-popup-action-icon"
|
||||||
|
Kind="Pencil" />
|
||||||
|
<TextBlock x:Name="TaskbarProfileDesktopEditActionTextBlock"
|
||||||
|
Grid.Column="1"
|
||||||
|
Classes="taskbar-profile-popup-action-text"
|
||||||
|
Text="Edit Desktop" />
|
||||||
|
</Grid>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Popup>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ using FluentAvalonia.UI.Controls;
|
|||||||
using Avalonia.Layout;
|
using Avalonia.Layout;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
using Line = Avalonia.Controls.Shapes.Line;
|
|
||||||
using Avalonia.Platform;
|
using Avalonia.Platform;
|
||||||
using Avalonia.Platform.Storage;
|
using Avalonia.Platform.Storage;
|
||||||
using Avalonia.Styling;
|
using Avalonia.Styling;
|
||||||
@@ -32,15 +31,6 @@ namespace LanMountainDesktop.Views;
|
|||||||
|
|
||||||
public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
||||||
{
|
{
|
||||||
private enum WallpaperPlacement
|
|
||||||
{
|
|
||||||
Fill,
|
|
||||||
Fit,
|
|
||||||
Stretch,
|
|
||||||
Center,
|
|
||||||
Tile
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum WallpaperMediaType
|
private enum WallpaperMediaType
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
@@ -49,6 +39,13 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
SolidColor
|
SolidColor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum WallpaperDisplayState
|
||||||
|
{
|
||||||
|
NoWallpaperConfigured,
|
||||||
|
TemporarilyUnavailable,
|
||||||
|
CurrentValidWallpaper
|
||||||
|
}
|
||||||
|
|
||||||
private enum WeatherLocationMode
|
private enum WeatherLocationMode
|
||||||
{
|
{
|
||||||
CitySearch,
|
CitySearch,
|
||||||
@@ -62,7 +59,6 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
private const int MaxEdgeInsetPercent = 30;
|
private const int MaxEdgeInsetPercent = 30;
|
||||||
private const int DefaultEdgeInsetPercent = 18;
|
private const int DefaultEdgeInsetPercent = 18;
|
||||||
private static readonly int SettingsTransitionDurationMs = (int)FluttermotionToken.Page.TotalMilliseconds;
|
private static readonly int SettingsTransitionDurationMs = (int)FluttermotionToken.Page.TotalMilliseconds;
|
||||||
private const double WallpaperPreviewMaxWidth = 520;
|
|
||||||
private const double LightBackgroundLuminanceThreshold = 0.57;
|
private const double LightBackgroundLuminanceThreshold = 0.57;
|
||||||
private const string TaskbarLayoutBottomFullRowMacStyle = "BottomFullRowMacStyle";
|
private const string TaskbarLayoutBottomFullRowMacStyle = "BottomFullRowMacStyle";
|
||||||
private static readonly HashSet<string> SupportedImageExtensions = new(StringComparer.OrdinalIgnoreCase)
|
private static readonly HashSet<string> SupportedImageExtensions = new(StringComparer.OrdinalIgnoreCase)
|
||||||
@@ -79,6 +75,8 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
];
|
];
|
||||||
private readonly ISettingsFacadeService _settingsFacade = HostSettingsFacadeProvider.GetOrCreate();
|
private readonly ISettingsFacadeService _settingsFacade = HostSettingsFacadeProvider.GetOrCreate();
|
||||||
private readonly IAppearanceThemeService _appearanceThemeService = HostAppearanceThemeProvider.GetOrCreate();
|
private readonly IAppearanceThemeService _appearanceThemeService = HostAppearanceThemeProvider.GetOrCreate();
|
||||||
|
private readonly IAppLogoService _appLogoService = HostAppLogoProvider.GetOrCreate();
|
||||||
|
private readonly ICurrentUserProfileService _currentUserProfileService = HostCurrentUserProfileProvider.GetOrCreate();
|
||||||
private readonly IGridSettingsService _gridSettingsService;
|
private readonly IGridSettingsService _gridSettingsService;
|
||||||
private readonly IThemeAppearanceService _themeSettingsService;
|
private readonly IThemeAppearanceService _themeSettingsService;
|
||||||
private readonly IWeatherSettingsService _weatherSettingsService;
|
private readonly IWeatherSettingsService _weatherSettingsService;
|
||||||
@@ -114,14 +112,19 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
private bool _suppressTimeZoneSelectionEvents;
|
private bool _suppressTimeZoneSelectionEvents;
|
||||||
private bool _suppressWeatherLocationEvents;
|
private bool _suppressWeatherLocationEvents;
|
||||||
private bool _suppressSettingsPersistence;
|
private bool _suppressSettingsPersistence;
|
||||||
private bool _isUpdatingWallpaperPreviewLayout;
|
|
||||||
private bool _isComponentLibraryOpen;
|
private bool _isComponentLibraryOpen;
|
||||||
private Border? _selectedDesktopComponentHost;
|
private Border? _selectedDesktopComponentHost;
|
||||||
private bool _reopenSettingsAfterComponentLibraryClose;
|
private bool _reopenSettingsAfterComponentLibraryClose;
|
||||||
private TranslateTransform? _settingsContentPanelTransform;
|
private TranslateTransform? _settingsContentPanelTransform;
|
||||||
private IBrush? _defaultDesktopBackground;
|
private IBrush? _defaultDesktopBackground;
|
||||||
private Bitmap? _wallpaperBitmap;
|
private Bitmap? _wallpaperBitmap;
|
||||||
|
private Bitmap? _lastValidWallpaperBitmap;
|
||||||
|
private string? _lastValidWallpaperPath;
|
||||||
|
private Bitmap? _videoWallpaperPosterBitmap;
|
||||||
|
private string? _videoWallpaperPosterPath;
|
||||||
private WallpaperMediaType _wallpaperMediaType;
|
private WallpaperMediaType _wallpaperMediaType;
|
||||||
|
private WallpaperDisplayState _wallpaperDisplayState = WallpaperDisplayState.NoWallpaperConfigured;
|
||||||
|
private string _wallpaperPlacement = WallpaperImageBrushFactory.Fill;
|
||||||
private string? _wallpaperVideoPath;
|
private string? _wallpaperVideoPath;
|
||||||
private string _wallpaperType = "Image";
|
private string _wallpaperType = "Image";
|
||||||
private Color? _wallpaperSolidColor;
|
private Color? _wallpaperSolidColor;
|
||||||
@@ -136,13 +139,11 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
private IntPtr _desktopVideoFrameBufferPtr;
|
private IntPtr _desktopVideoFrameBufferPtr;
|
||||||
private byte[]? _desktopVideoStagingBuffer;
|
private byte[]? _desktopVideoStagingBuffer;
|
||||||
private WriteableBitmap? _desktopVideoBitmap;
|
private WriteableBitmap? _desktopVideoBitmap;
|
||||||
private WriteableBitmap? _wallpaperPreviewSnapshotBitmap;
|
|
||||||
private int _desktopVideoFrameWidth;
|
private int _desktopVideoFrameWidth;
|
||||||
private int _desktopVideoFrameHeight;
|
private int _desktopVideoFrameHeight;
|
||||||
private int _desktopVideoFramePitch;
|
private int _desktopVideoFramePitch;
|
||||||
private int _desktopVideoFrameBufferSize;
|
private int _desktopVideoFrameBufferSize;
|
||||||
private int _desktopVideoFrameDirtyFlag;
|
private int _desktopVideoFrameDirtyFlag;
|
||||||
private bool _wallpaperPreviewSnapshotPending;
|
|
||||||
private string? _wallpaperPath;
|
private string? _wallpaperPath;
|
||||||
private string _wallpaperStatus = "Current background uses solid color.";
|
private string _wallpaperStatus = "Current background uses solid color.";
|
||||||
private IReadOnlyList<Color> _recommendedColors = Array.Empty<Color>();
|
private IReadOnlyList<Color> _recommendedColors = Array.Empty<Color>();
|
||||||
@@ -154,9 +155,6 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
private string _gridSpacingPreset = "Relaxed";
|
private string _gridSpacingPreset = "Relaxed";
|
||||||
private string _statusBarSpacingMode = "Relaxed";
|
private string _statusBarSpacingMode = "Relaxed";
|
||||||
private int _statusBarCustomSpacingPercent = 12;
|
private int _statusBarCustomSpacingPercent = 12;
|
||||||
private bool _suppressGridSpacingEvents;
|
|
||||||
private bool _suppressGridInsetEvents;
|
|
||||||
private bool _suppressStatusBarSpacingEvents;
|
|
||||||
private int _desktopEdgeInsetPercent = DefaultEdgeInsetPercent;
|
private int _desktopEdgeInsetPercent = DefaultEdgeInsetPercent;
|
||||||
private string _taskbarLayoutMode = TaskbarLayoutBottomFullRowMacStyle;
|
private string _taskbarLayoutMode = TaskbarLayoutBottomFullRowMacStyle;
|
||||||
private string _languageCode = "zh-CN";
|
private string _languageCode = "zh-CN";
|
||||||
@@ -179,7 +177,6 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
private bool _isWeatherPreviewInProgress;
|
private bool _isWeatherPreviewInProgress;
|
||||||
private ClockDisplayFormat _clockDisplayFormat = ClockDisplayFormat.HourMinuteSecond;
|
private ClockDisplayFormat _clockDisplayFormat = ClockDisplayFormat.HourMinuteSecond;
|
||||||
private bool _externalSettingsReloadPending;
|
private bool _externalSettingsReloadPending;
|
||||||
|
|
||||||
private double CurrentDesktopPitch => _currentDesktopCellSize + _currentDesktopCellGap;
|
private double CurrentDesktopPitch => _currentDesktopCellSize + _currentDesktopCellGap;
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
@@ -196,6 +193,8 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
_weatherDataService = _weatherSettingsService.GetWeatherInfoService();
|
_weatherDataService = _weatherSettingsService.GetWeatherInfoService();
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
Icon = _appLogoService.CreateWindowIcon();
|
||||||
|
InitializeTaskbarProfileFlyout();
|
||||||
_componentRuntimeRegistry = DesktopComponentRegistryFactory.CreateRuntimeRegistry(
|
_componentRuntimeRegistry = DesktopComponentRegistryFactory.CreateRuntimeRegistry(
|
||||||
_componentRegistry,
|
_componentRegistry,
|
||||||
pluginRuntimeService,
|
pluginRuntimeService,
|
||||||
@@ -276,7 +275,6 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
|
|
||||||
_statusBarSpacingMode = NormalizeStatusBarSpacingMode(snapshot.StatusBarSpacingMode);
|
_statusBarSpacingMode = NormalizeStatusBarSpacingMode(snapshot.StatusBarSpacingMode);
|
||||||
_statusBarCustomSpacingPercent = Math.Clamp(snapshot.StatusBarCustomSpacingPercent, 0, 30);
|
_statusBarCustomSpacingPercent = Math.Clamp(snapshot.StatusBarCustomSpacingPercent, 0, 30);
|
||||||
_defaultDesktopBackground = DesktopWallpaperLayer.Background;
|
|
||||||
ApplyTaskbarSettings(snapshot);
|
ApplyTaskbarSettings(snapshot);
|
||||||
InitializeLocalization(snapshot.LanguageCode);
|
InitializeLocalization(snapshot.LanguageCode);
|
||||||
InitializeWeatherSettings(snapshot);
|
InitializeWeatherSettings(snapshot);
|
||||||
@@ -288,16 +286,28 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
InitializeDesktopComponentPlacements(desktopLayoutSnapshot);
|
InitializeDesktopComponentPlacements(desktopLayoutSnapshot);
|
||||||
InitializeSettingsIcons();
|
InitializeSettingsIcons();
|
||||||
|
|
||||||
TryRestoreWallpaper(snapshot.WallpaperPath, snapshot.WallpaperType, snapshot.WallpaperColor);
|
|
||||||
ApplyWallpaperBrush();
|
|
||||||
UpdateWallpaperDisplay();
|
|
||||||
|
|
||||||
if (TryParseColor(snapshot.ThemeColor, out var savedThemeColor))
|
if (TryParseColor(snapshot.ThemeColor, out var savedThemeColor))
|
||||||
{
|
{
|
||||||
_selectedThemeColor = savedThemeColor;
|
_selectedThemeColor = savedThemeColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
_isNightMode = snapshot.IsNightMode ?? (CalculateCurrentBackgroundLuminance() < LightBackgroundLuminanceThreshold);
|
_isNightMode = snapshot.IsNightMode
|
||||||
|
?? (Application.Current?.ActualThemeVariant == ThemeVariant.Dark);
|
||||||
|
_defaultDesktopBackground = CreateNeutralWallpaperFallbackBrush();
|
||||||
|
|
||||||
|
TryRestoreWallpaper(
|
||||||
|
snapshot.WallpaperPath,
|
||||||
|
snapshot.WallpaperType,
|
||||||
|
snapshot.WallpaperColor,
|
||||||
|
snapshot.WallpaperPlacement);
|
||||||
|
ApplyWallpaperBrush();
|
||||||
|
UpdateWallpaperDisplay();
|
||||||
|
|
||||||
|
if (!snapshot.IsNightMode.HasValue)
|
||||||
|
{
|
||||||
|
_isNightMode = CalculateCurrentBackgroundLuminance() < LightBackgroundLuminanceThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
ApplyNightModeState(_isNightMode, refreshPalettes: true);
|
ApplyNightModeState(_isNightMode, refreshPalettes: true);
|
||||||
ApplyLocalization();
|
ApplyLocalization();
|
||||||
DesktopHost.SizeChanged += OnDesktopHostSizeChanged;
|
DesktopHost.SizeChanged += OnDesktopHostSizeChanged;
|
||||||
@@ -331,8 +341,11 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
_videoWallpaperPlayer = null;
|
_videoWallpaperPlayer = null;
|
||||||
_desktopVideoFrameRefreshTimer?.Stop();
|
_desktopVideoFrameRefreshTimer?.Stop();
|
||||||
_desktopVideoFrameRefreshTimer = null;
|
_desktopVideoFrameRefreshTimer = null;
|
||||||
_wallpaperPreviewSnapshotBitmap?.Dispose();
|
_videoWallpaperPosterBitmap?.Dispose();
|
||||||
_wallpaperPreviewSnapshotBitmap = null;
|
_videoWallpaperPosterBitmap = null;
|
||||||
|
_videoWallpaperPosterPath = null;
|
||||||
|
_lastValidWallpaperBitmap?.Dispose();
|
||||||
|
_lastValidWallpaperBitmap = null;
|
||||||
_libVlc?.Dispose();
|
_libVlc?.Dispose();
|
||||||
_libVlc = null;
|
_libVlc = null;
|
||||||
if (_recommendationInfoService is IDisposable recommendationServiceDisposable)
|
if (_recommendationInfoService is IDisposable recommendationServiceDisposable)
|
||||||
@@ -382,449 +395,6 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
PersistSettings();
|
PersistSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnWallpaperPreviewHostSizeChanged(object? sender, SizeChangedEventArgs e)
|
|
||||||
{
|
|
||||||
UpdateWallpaperPreviewLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnGridPreviewHostSizeChanged(object? sender, SizeChangedEventArgs e)
|
|
||||||
{
|
|
||||||
UpdateGridPreviewLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnGridSizeSliderChanged(object? sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (GridSizeSlider is null || GridSizeNumberBox is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var sliderValue = (int)Math.Round(GridSizeSlider.Value);
|
|
||||||
if (Math.Abs(GridSizeNumberBox.Value - sliderValue) > double.Epsilon)
|
|
||||||
{
|
|
||||||
GridSizeNumberBox.Value = sliderValue;
|
|
||||||
}
|
|
||||||
UpdateGridPreviewLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnGridSizeNumberBoxChanged(object? sender, NumberBoxValueChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (GridSizeSlider is null || GridSizeNumberBox is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var numberBoxValue = (int)Math.Round(GridSizeNumberBox.Value);
|
|
||||||
if (Math.Abs(GridSizeSlider.Value - numberBoxValue) > double.Epsilon)
|
|
||||||
{
|
|
||||||
GridSizeSlider.Value = numberBoxValue;
|
|
||||||
}
|
|
||||||
UpdateGridPreviewLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnGridEdgeInsetSliderChanged(object? sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (GridEdgeInsetSlider is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_suppressGridInsetEvents)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var value = (int)Math.Round(GridEdgeInsetSlider.Value);
|
|
||||||
SetPendingGridEdgeInsetPercent(value, updateSlider: false, updateNumberBox: true);
|
|
||||||
UpdateGridPreviewLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnGridEdgeInsetNumberBoxChanged(object? sender, NumberBoxValueChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (GridEdgeInsetNumberBox is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_suppressGridInsetEvents)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var value = (int)Math.Round(GridEdgeInsetNumberBox.Value);
|
|
||||||
SetPendingGridEdgeInsetPercent(value, updateSlider: true, updateNumberBox: false);
|
|
||||||
UpdateGridPreviewLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetPendingGridEdgeInsetPercent(int percent, bool updateSlider, bool updateNumberBox)
|
|
||||||
{
|
|
||||||
var clamped = Math.Clamp(percent, MinEdgeInsetPercent, MaxEdgeInsetPercent);
|
|
||||||
|
|
||||||
_suppressGridInsetEvents = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (updateSlider && Math.Abs(GridEdgeInsetSlider.Value - clamped) > double.Epsilon)
|
|
||||||
{
|
|
||||||
GridEdgeInsetSlider.Value = clamped;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updateNumberBox && Math.Abs(GridEdgeInsetNumberBox.Value - clamped) > double.Epsilon)
|
|
||||||
{
|
|
||||||
GridEdgeInsetNumberBox.Value = clamped;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_suppressGridInsetEvents = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnGridSpacingPresetSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (_suppressGridSpacingEvents)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateGridPreviewLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnStatusBarSpacingModeChanged(object? sender, SelectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (StatusBarSpacingModeComboBox is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_suppressStatusBarSpacingEvents)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_statusBarSpacingMode = NormalizeStatusBarSpacingMode(
|
|
||||||
TryGetSelectedComboBoxTag(StatusBarSpacingModeComboBox) ?? _statusBarSpacingMode);
|
|
||||||
|
|
||||||
StatusBarSpacingCustomPanel.IsVisible = string.Equals(_statusBarSpacingMode, "Custom", StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
ApplyDesktopStatusBarComponentSpacing();
|
|
||||||
UpdateWallpaperPreviewLayout();
|
|
||||||
UpdateGridPreviewLayout();
|
|
||||||
SchedulePersistSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnStatusBarSpacingSliderChanged(object? sender, RangeBaseValueChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (StatusBarSpacingSlider is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_suppressStatusBarSpacingEvents)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var percent = (int)Math.Round(StatusBarSpacingSlider.Value);
|
|
||||||
SetStatusBarCustomSpacingPercent(percent, updateSlider: false, updateNumberBox: true);
|
|
||||||
|
|
||||||
if (string.Equals(_statusBarSpacingMode, "Custom", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
ApplyDesktopStatusBarComponentSpacing();
|
|
||||||
UpdateWallpaperPreviewLayout();
|
|
||||||
UpdateGridPreviewLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
SchedulePersistSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnStatusBarSpacingNumberBoxChanged(object? sender, NumberBoxValueChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (StatusBarSpacingNumberBox is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_suppressStatusBarSpacingEvents)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var percent = (int)Math.Round(StatusBarSpacingNumberBox.Value);
|
|
||||||
SetStatusBarCustomSpacingPercent(percent, updateSlider: true, updateNumberBox: false);
|
|
||||||
|
|
||||||
if (string.Equals(_statusBarSpacingMode, "Custom", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
ApplyDesktopStatusBarComponentSpacing();
|
|
||||||
UpdateWallpaperPreviewLayout();
|
|
||||||
UpdateGridPreviewLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
SchedulePersistSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetStatusBarCustomSpacingPercent(int percent, bool updateSlider, bool updateNumberBox)
|
|
||||||
{
|
|
||||||
percent = Math.Clamp(percent, 0, 30);
|
|
||||||
_statusBarCustomSpacingPercent = percent;
|
|
||||||
|
|
||||||
_suppressStatusBarSpacingEvents = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (updateSlider && Math.Abs(StatusBarSpacingSlider.Value - percent) > double.Epsilon)
|
|
||||||
{
|
|
||||||
StatusBarSpacingSlider.Value = percent;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updateNumberBox && Math.Abs(StatusBarSpacingNumberBox.Value - percent) > double.Epsilon)
|
|
||||||
{
|
|
||||||
StatusBarSpacingNumberBox.Value = percent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_suppressStatusBarSpacingEvents = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateGridPreviewLayout()
|
|
||||||
{
|
|
||||||
if (GridPreviewFrame is null ||
|
|
||||||
GridPreviewHost is null ||
|
|
||||||
GridPreviewViewport is null ||
|
|
||||||
GridPreviewGrid is null ||
|
|
||||||
GridPreviewLinesCanvas is null ||
|
|
||||||
GridSizeSlider is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var previewShortSideCells = (int)Math.Round(GridSizeSlider.Value);
|
|
||||||
if (previewShortSideCells < MinShortSideCells || previewShortSideCells > MaxShortSideCells)
|
|
||||||
{
|
|
||||||
previewShortSideCells = _targetShortSideCells;
|
|
||||||
}
|
|
||||||
|
|
||||||
var desktopWidth = Math.Max(1, DesktopHost.Bounds.Width);
|
|
||||||
var desktopHeight = Math.Max(1, DesktopHost.Bounds.Height);
|
|
||||||
var aspectRatio = desktopWidth / desktopHeight;
|
|
||||||
|
|
||||||
var availableWidth = Math.Max(100, GridPreviewHost.Bounds.Width);
|
|
||||||
|
|
||||||
var framePadding = GridPreviewFrame.Padding;
|
|
||||||
var horizontalPadding = framePadding.Left + framePadding.Right;
|
|
||||||
var verticalPadding = framePadding.Top + framePadding.Bottom;
|
|
||||||
|
|
||||||
var gridPreviewWidth = availableWidth;
|
|
||||||
var gridPreviewHeight = gridPreviewWidth / aspectRatio;
|
|
||||||
|
|
||||||
GridPreviewFrame.Width = gridPreviewWidth;
|
|
||||||
GridPreviewFrame.Height = gridPreviewHeight;
|
|
||||||
|
|
||||||
var innerWidth = Math.Max(1, gridPreviewWidth - horizontalPadding);
|
|
||||||
var innerHeight = Math.Max(1, gridPreviewHeight - verticalPadding);
|
|
||||||
var preset = _gridSettingsService.NormalizeSpacingPreset(TryGetSelectedComboBoxTag(GridSpacingPresetComboBox) ?? _gridSpacingPreset);
|
|
||||||
var gapRatio = _gridSettingsService.ResolveGapRatio(preset);
|
|
||||||
var pendingEdgeInsetPercent = ResolvePendingGridEdgeInsetPercent();
|
|
||||||
var edgeInset = _gridSettingsService.CalculateEdgeInset(innerWidth, innerHeight, previewShortSideCells, pendingEdgeInsetPercent);
|
|
||||||
var gridMetrics = _gridSettingsService.CalculateGridMetrics(innerWidth, innerHeight, previewShortSideCells, gapRatio, edgeInset);
|
|
||||||
if (gridMetrics.CellSize <= 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var inset = new Thickness(gridMetrics.EdgeInsetPx);
|
|
||||||
GridPreviewGrid.Margin = inset;
|
|
||||||
GridPreviewGrid.RowSpacing = gridMetrics.GapPx;
|
|
||||||
GridPreviewGrid.ColumnSpacing = gridMetrics.GapPx;
|
|
||||||
GridPreviewGrid.Width = gridMetrics.GridWidthPx;
|
|
||||||
GridPreviewGrid.Height = gridMetrics.GridHeightPx;
|
|
||||||
|
|
||||||
GridPreviewLinesCanvas.Margin = inset;
|
|
||||||
|
|
||||||
GridPreviewGrid.RowDefinitions.Clear();
|
|
||||||
GridPreviewGrid.ColumnDefinitions.Clear();
|
|
||||||
|
|
||||||
for (var row = 0; row < gridMetrics.RowCount; row++)
|
|
||||||
{
|
|
||||||
GridPreviewGrid.RowDefinitions.Add(
|
|
||||||
new RowDefinition(new GridLength(gridMetrics.CellSize, GridUnitType.Pixel)));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var col = 0; col < gridMetrics.ColumnCount; col++)
|
|
||||||
{
|
|
||||||
GridPreviewGrid.ColumnDefinitions.Add(
|
|
||||||
new ColumnDefinition(new GridLength(gridMetrics.CellSize, GridUnitType.Pixel)));
|
|
||||||
}
|
|
||||||
|
|
||||||
PlaceStatusBarComponent(
|
|
||||||
GridPreviewTopStatusBarHost,
|
|
||||||
column: 0,
|
|
||||||
requestedColumnSpan: gridMetrics.ColumnCount,
|
|
||||||
totalColumns: gridMetrics.ColumnCount);
|
|
||||||
|
|
||||||
var taskbarRow = gridMetrics.RowCount - 1;
|
|
||||||
Grid.SetRow(GridPreviewBottomTaskbarContainer, taskbarRow);
|
|
||||||
Grid.SetColumn(GridPreviewBottomTaskbarContainer, 0);
|
|
||||||
Grid.SetRowSpan(GridPreviewBottomTaskbarContainer, 1);
|
|
||||||
Grid.SetColumnSpan(GridPreviewBottomTaskbarContainer, gridMetrics.ColumnCount);
|
|
||||||
|
|
||||||
ApplyGridPreviewWidgetSizing(gridMetrics.CellSize);
|
|
||||||
ApplyStatusBarComponentSpacingForPanel(GridPreviewTopStatusComponentsPanel, gridMetrics.CellSize);
|
|
||||||
UpdateGridEdgeInsetComputedPxText(gridMetrics.CellSize);
|
|
||||||
|
|
||||||
if (GridInfoTextBlock is not null)
|
|
||||||
{
|
|
||||||
GridInfoTextBlock.Text = Lf(
|
|
||||||
"settings.grid.info_format",
|
|
||||||
"Grid: {0} cols x {1} rows | cell {2:F1}px (1:1)",
|
|
||||||
gridMetrics.ColumnCount,
|
|
||||||
gridMetrics.RowCount,
|
|
||||||
gridMetrics.CellSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawGridPreviewLines(gridMetrics);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawGridPreviewLines(DesktopGridMetrics gridMetrics)
|
|
||||||
{
|
|
||||||
if (GridPreviewLinesCanvas is null || GridPreviewViewport is null || GridPreviewGrid is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var viewportBackground = GridPreviewViewport.Background as SolidColorBrush;
|
|
||||||
var backgroundColor = viewportBackground?.Color ?? Color.Parse("#30111827");
|
|
||||||
var luminance = CalculateRelativeLuminance(backgroundColor);
|
|
||||||
var lineColor = luminance >= LightBackgroundLuminanceThreshold
|
|
||||||
? Color.Parse("#80000000")
|
|
||||||
: Color.Parse("#80FFFFFF");
|
|
||||||
|
|
||||||
GridPreviewLinesCanvas.Children.Clear();
|
|
||||||
|
|
||||||
var cellSize = gridMetrics.CellSize;
|
|
||||||
var pitch = gridMetrics.Pitch;
|
|
||||||
var gridWidth = gridMetrics.GridWidthPx;
|
|
||||||
var gridHeight = gridMetrics.GridHeightPx;
|
|
||||||
|
|
||||||
GridPreviewLinesCanvas.Width = gridWidth;
|
|
||||||
GridPreviewLinesCanvas.Height = gridHeight;
|
|
||||||
|
|
||||||
var dashLength = cellSize * 0.3;
|
|
||||||
var gapLength = cellSize * 0.2;
|
|
||||||
|
|
||||||
for (var row = 0; row <= gridMetrics.RowCount; row++)
|
|
||||||
{
|
|
||||||
var y = row == gridMetrics.RowCount ? gridHeight : row * pitch;
|
|
||||||
var line = new Line
|
|
||||||
{
|
|
||||||
StartPoint = new Point(0, y),
|
|
||||||
EndPoint = new Point(gridWidth, y),
|
|
||||||
Stroke = new SolidColorBrush(lineColor),
|
|
||||||
StrokeThickness = 1,
|
|
||||||
StrokeDashArray = new Avalonia.Collections.AvaloniaList<double> { dashLength, gapLength },
|
|
||||||
IsHitTestVisible = false
|
|
||||||
};
|
|
||||||
GridPreviewLinesCanvas.Children.Add(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var col = 0; col <= gridMetrics.ColumnCount; col++)
|
|
||||||
{
|
|
||||||
var x = col == gridMetrics.ColumnCount ? gridWidth : col * pitch;
|
|
||||||
var line = new Line
|
|
||||||
{
|
|
||||||
StartPoint = new Point(x, 0),
|
|
||||||
EndPoint = new Point(x, gridHeight),
|
|
||||||
Stroke = new SolidColorBrush(lineColor),
|
|
||||||
StrokeThickness = 1,
|
|
||||||
StrokeDashArray = new Avalonia.Collections.AvaloniaList<double> { dashLength, gapLength },
|
|
||||||
IsHitTestVisible = false
|
|
||||||
};
|
|
||||||
GridPreviewLinesCanvas.Children.Add(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApplyGridPreviewWidgetSizing(double cellSize)
|
|
||||||
{
|
|
||||||
var previewTaskbarCell = Math.Clamp(cellSize * 0.74, 10, 30);
|
|
||||||
var iconSize = Math.Clamp(cellSize * 0.35, 8, 16);
|
|
||||||
|
|
||||||
GridPreviewTopStatusBarHost.Padding = new Thickness(0);
|
|
||||||
GridPreviewBottomTaskbarContainer.Margin = new Thickness(0);
|
|
||||||
GridPreviewBottomTaskbarContainer.CornerRadius = new CornerRadius(Math.Clamp(cellSize * 0.45, 16, 32));
|
|
||||||
GridPreviewBottomTaskbarContainer.Padding = new Thickness(Math.Clamp(cellSize * 0.06, 1, 4));
|
|
||||||
|
|
||||||
GridPreviewBackButtonTextBlock.FontSize = Math.Clamp(cellSize * 0.19, 5, 13);
|
|
||||||
GridPreviewComponentLibraryTextBlock.FontSize = Math.Clamp(cellSize * 0.18, 5, 12);
|
|
||||||
GridPreviewComponentLibraryIcon.FontSize = iconSize;
|
|
||||||
GridPreviewBackButtonVisual.MinHeight = previewTaskbarCell;
|
|
||||||
GridPreviewBackButtonVisual.MinWidth = Math.Clamp(cellSize * 2.1, 30, 120);
|
|
||||||
GridPreviewComponentLibraryVisual.MinHeight = previewTaskbarCell;
|
|
||||||
GridPreviewComponentLibraryVisual.MinWidth = Math.Clamp(cellSize * 2.0, 28, 110);
|
|
||||||
GridPreviewSettingsButtonIcon.Width = Math.Clamp(previewTaskbarCell * 0.42, 6, 14);
|
|
||||||
GridPreviewSettingsButtonIcon.Height = Math.Clamp(previewTaskbarCell * 0.42, 6, 14);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnApplyGridSizeClick(object? sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (GridSizeNumberBox is null || GridSizeSlider is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_gridSpacingPreset = _gridSettingsService.NormalizeSpacingPreset(
|
|
||||||
TryGetSelectedComboBoxTag(GridSpacingPresetComboBox) ?? _gridSpacingPreset);
|
|
||||||
_desktopEdgeInsetPercent = ResolvePendingGridEdgeInsetPercent();
|
|
||||||
|
|
||||||
var requested = (int)Math.Round(GridSizeNumberBox.Value);
|
|
||||||
if (requested <= 0)
|
|
||||||
{
|
|
||||||
requested = _targetShortSideCells;
|
|
||||||
}
|
|
||||||
|
|
||||||
_targetShortSideCells = Math.Clamp(requested, MinShortSideCells, MaxShortSideCells);
|
|
||||||
|
|
||||||
if (Math.Abs(GridSizeNumberBox.Value - _targetShortSideCells) > double.Epsilon)
|
|
||||||
{
|
|
||||||
GridSizeNumberBox.Value = _targetShortSideCells;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Math.Abs(GridSizeSlider.Value - _targetShortSideCells) > double.Epsilon)
|
|
||||||
{
|
|
||||||
GridSizeSlider.Value = _targetShortSideCells;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetPendingGridEdgeInsetPercent(_desktopEdgeInsetPercent, updateSlider: true, updateNumberBox: true);
|
|
||||||
|
|
||||||
RebuildDesktopGrid();
|
|
||||||
PersistSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnClockFormatChanged(object? sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is not RadioButton radioButton || radioButton.Tag is not string formatTag)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (radioButton.IsChecked != true)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_clockDisplayFormat = formatTag == "Hm"
|
|
||||||
? ClockDisplayFormat.HourMinute
|
|
||||||
: ClockDisplayFormat.HourMinuteSecond;
|
|
||||||
|
|
||||||
if (ClockWidget is ClockWidget clock)
|
|
||||||
{
|
|
||||||
clock.SetDisplayFormat(_clockDisplayFormat);
|
|
||||||
}
|
|
||||||
|
|
||||||
ApplyTopStatusComponentVisibility();
|
|
||||||
UpdateWallpaperPreviewLayout();
|
|
||||||
PersistSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RebuildDesktopGrid()
|
private void RebuildDesktopGrid()
|
||||||
{
|
{
|
||||||
var hostWidth = DesktopHost.Bounds.Width;
|
var hostWidth = DesktopHost.Bounds.Width;
|
||||||
@@ -839,7 +409,6 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
_currentDesktopCellSize = gridMetrics.CellSize;
|
_currentDesktopCellSize = gridMetrics.CellSize;
|
||||||
_currentDesktopCellGap = gridMetrics.GapPx;
|
_currentDesktopCellGap = gridMetrics.GapPx;
|
||||||
_currentDesktopEdgeInset = gridMetrics.EdgeInsetPx;
|
_currentDesktopEdgeInset = gridMetrics.EdgeInsetPx;
|
||||||
UpdateGridEdgeInsetComputedPxText(gridMetrics.CellSize);
|
|
||||||
|
|
||||||
DesktopGrid.RowDefinitions.Clear();
|
DesktopGrid.RowDefinitions.Clear();
|
||||||
DesktopGrid.ColumnDefinitions.Clear();
|
DesktopGrid.ColumnDefinitions.Clear();
|
||||||
@@ -878,24 +447,11 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
ApplyDesktopStatusBarComponentSpacing();
|
ApplyDesktopStatusBarComponentSpacing();
|
||||||
UpdateDesktopSurfaceLayout(gridMetrics);
|
UpdateDesktopSurfaceLayout(gridMetrics);
|
||||||
UpdateSettingsViewportInsets(gridMetrics.CellSize);
|
UpdateSettingsViewportInsets(gridMetrics.CellSize);
|
||||||
|
|
||||||
if (GridInfoTextBlock is not null)
|
|
||||||
{
|
|
||||||
GridInfoTextBlock.Text = Lf(
|
|
||||||
"settings.grid.info_format",
|
|
||||||
"Grid: {0} cols x {1} rows | cell {2:F1}px (1:1)",
|
|
||||||
gridMetrics.ColumnCount,
|
|
||||||
gridMetrics.RowCount,
|
|
||||||
gridMetrics.CellSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateWallpaperPreviewLayout();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplyDesktopStatusBarComponentSpacing()
|
private void ApplyDesktopStatusBarComponentSpacing()
|
||||||
{
|
{
|
||||||
ApplyStatusBarComponentSpacingForPanel(TopStatusComponentsPanel, _currentDesktopCellSize);
|
ApplyStatusBarComponentSpacingForPanel(TopStatusComponentsPanel, _currentDesktopCellSize);
|
||||||
UpdateStatusBarSpacingComputedPxText(_currentDesktopCellSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int ResolveStatusBarSpacingPercent()
|
private int ResolveStatusBarSpacingPercent()
|
||||||
@@ -920,47 +476,6 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
panel.Spacing = spacingPx;
|
panel.Spacing = spacingPx;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateStatusBarSpacingComputedPxText(double cellSize)
|
|
||||||
{
|
|
||||||
if (StatusBarSpacingComputedPxTextBlock is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var percent = ResolveStatusBarSpacingPercent();
|
|
||||||
var spacingPx = Math.Max(0, cellSize) * (percent / 100d);
|
|
||||||
StatusBarSpacingComputedPxTextBlock.Text = Lf(
|
|
||||||
"settings.status_bar.spacing_custom_px_format",
|
|
||||||
">= {0:F1}px",
|
|
||||||
spacingPx);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int ResolvePendingGridEdgeInsetPercent()
|
|
||||||
{
|
|
||||||
if (GridEdgeInsetNumberBox is null)
|
|
||||||
{
|
|
||||||
return _desktopEdgeInsetPercent;
|
|
||||||
}
|
|
||||||
|
|
||||||
var pending = (int)Math.Round(GridEdgeInsetNumberBox.Value);
|
|
||||||
return Math.Clamp(pending, MinEdgeInsetPercent, MaxEdgeInsetPercent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateGridEdgeInsetComputedPxText(double cellSize)
|
|
||||||
{
|
|
||||||
if (GridEdgeInsetComputedPxTextBlock is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var percent = ResolvePendingGridEdgeInsetPercent();
|
|
||||||
var insetPx = Math.Clamp(Math.Max(0, cellSize) * (percent / 100d), 0, 80);
|
|
||||||
GridEdgeInsetComputedPxTextBlock.Text = Lf(
|
|
||||||
"settings.grid.edge_inset_px_format",
|
|
||||||
"{0:F1}px",
|
|
||||||
insetPx);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string NormalizeStatusBarSpacingMode(string? value)
|
private static string NormalizeStatusBarSpacingMode(string? value)
|
||||||
{
|
{
|
||||||
return value switch
|
return value switch
|
||||||
@@ -971,16 +486,6 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string? TryGetSelectedComboBoxTag(ComboBox? comboBox)
|
|
||||||
{
|
|
||||||
if (comboBox?.SelectedItem is ComboBoxItem item)
|
|
||||||
{
|
|
||||||
return item.Tag?.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return comboBox?.SelectedItem?.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int ClampComponentSpan(int requestedSpan, int axisCellCount)
|
private static int ClampComponentSpan(int requestedSpan, int axisCellCount)
|
||||||
{
|
{
|
||||||
return Math.Clamp(requestedSpan, 1, Math.Max(1, axisCellCount));
|
return Math.Clamp(requestedSpan, 1, Math.Max(1, axisCellCount));
|
||||||
@@ -1036,25 +541,21 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
BackToWindowsTextBlock.FontSize = taskbarTextSize;
|
BackToWindowsTextBlock.FontSize = taskbarTextSize;
|
||||||
SetButtonContentSpacing(BackToWindowsButton, buttonContentSpacing);
|
SetButtonContentSpacing(BackToWindowsButton, buttonContentSpacing);
|
||||||
|
|
||||||
OpenSettingsButton.Margin = new Thickness(0);
|
TaskbarProfileButton.Margin = new Thickness(0);
|
||||||
OpenSettingsButton.Padding = taskbarButtonPadding;
|
TaskbarProfileButton.Padding = new Thickness(0);
|
||||||
OpenSettingsButton.FontSize = taskbarTextSize;
|
TaskbarProfileButton.MinHeight = taskbarCellHeight;
|
||||||
OpenSettingsButton.MinHeight = taskbarCellHeight;
|
TaskbarProfileButton.MinWidth = taskbarCellHeight;
|
||||||
OpenSettingsButton.MinWidth = OpenSettingsButtonTextBlock.IsVisible
|
TaskbarProfileButton.Width = taskbarCellHeight;
|
||||||
? Math.Clamp(taskbarCellHeight * 2.35, 100, 340)
|
TaskbarProfileButton.Height = taskbarCellHeight;
|
||||||
: Math.Clamp(taskbarCellHeight * 1.10, 48, 88);
|
|
||||||
OpenSettingsIcon.FontSize = taskbarIconSize;
|
var avatarSize = Math.Clamp(taskbarCellHeight * 0.82, 28, 60);
|
||||||
OpenSettingsButtonTextBlock.FontSize = taskbarTextSize;
|
var avatarRadius = avatarSize / 2d;
|
||||||
SetButtonContentSpacing(OpenSettingsButton, buttonContentSpacing);
|
TaskbarProfileAvatarBorder.Width = avatarSize;
|
||||||
|
TaskbarProfileAvatarBorder.Height = avatarSize;
|
||||||
OpenComponentLibraryButton.Margin = new Thickness(0);
|
TaskbarProfileAvatarBorder.CornerRadius = new CornerRadius(avatarRadius);
|
||||||
OpenComponentLibraryButton.Padding = taskbarButtonPadding;
|
TaskbarProfileAvatarImage.Width = avatarSize;
|
||||||
OpenComponentLibraryButton.FontSize = taskbarTextSize;
|
TaskbarProfileAvatarImage.Height = avatarSize;
|
||||||
OpenComponentLibraryButton.MinHeight = taskbarCellHeight;
|
TaskbarProfileAvatarFallbackText.FontSize = Math.Clamp(avatarSize * 0.34, 10, 22);
|
||||||
OpenComponentLibraryButton.MinWidth = Math.Clamp(taskbarCellHeight * 2.15, 92, 320);
|
|
||||||
OpenComponentLibraryIcon.FontSize = taskbarIconSize;
|
|
||||||
OpenComponentLibraryTextBlock.FontSize = taskbarTextSize;
|
|
||||||
SetButtonContentSpacing(OpenComponentLibraryButton, buttonContentSpacing);
|
|
||||||
|
|
||||||
UpdateComponentLibraryLayout(cellSize);
|
UpdateComponentLibraryLayout(cellSize);
|
||||||
}
|
}
|
||||||
@@ -1093,141 +594,6 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
_ = cellSize;
|
_ = cellSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateWallpaperPreviewLayout()
|
|
||||||
{
|
|
||||||
if (WallpaperPreviewFrame is null ||
|
|
||||||
WallpaperPreviewHost is null ||
|
|
||||||
WallpaperPreviewViewport is null ||
|
|
||||||
WallpaperPreviewGrid is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_isUpdatingWallpaperPreviewLayout)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_isUpdatingWallpaperPreviewLayout = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var desktopWidth = Math.Max(1, DesktopHost.Bounds.Width);
|
|
||||||
var desktopHeight = Math.Max(1, DesktopHost.Bounds.Height);
|
|
||||||
var aspectRatio = desktopWidth / desktopHeight;
|
|
||||||
|
|
||||||
var availableWidth = Math.Max(100, WallpaperPreviewHost.Bounds.Width);
|
|
||||||
var availableHeight = WallpaperPreviewHost.Bounds.Height;
|
|
||||||
// During initial measure, host height can be too small and cause the preview to collapse.
|
|
||||||
// Ignore tiny heights so width-driven sizing can stabilize first.
|
|
||||||
if (availableHeight < 120)
|
|
||||||
{
|
|
||||||
availableHeight = double.PositiveInfinity;
|
|
||||||
}
|
|
||||||
|
|
||||||
var framePadding = WallpaperPreviewFrame.Padding;
|
|
||||||
var horizontalPadding = framePadding.Left + framePadding.Right;
|
|
||||||
var verticalPadding = framePadding.Top + framePadding.Bottom;
|
|
||||||
|
|
||||||
var previewWidth = Math.Min(availableWidth, WallpaperPreviewMaxWidth);
|
|
||||||
var previewHeight = previewWidth / aspectRatio;
|
|
||||||
if (double.IsFinite(availableHeight) && previewHeight > availableHeight)
|
|
||||||
{
|
|
||||||
previewHeight = availableHeight;
|
|
||||||
previewWidth = previewHeight * aspectRatio;
|
|
||||||
}
|
|
||||||
|
|
||||||
WallpaperPreviewFrame.Width = previewWidth;
|
|
||||||
WallpaperPreviewFrame.Height = previewHeight;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var innerWidth = Math.Max(1, previewWidth - horizontalPadding);
|
|
||||||
var innerHeight = Math.Max(1, previewHeight - verticalPadding);
|
|
||||||
var gapRatio = _gridSettingsService.ResolveGapRatio(_gridSpacingPreset);
|
|
||||||
var edgeInset = _gridSettingsService.CalculateEdgeInset(innerWidth, innerHeight, _targetShortSideCells, _desktopEdgeInsetPercent);
|
|
||||||
var gridMetrics = _gridSettingsService.CalculateGridMetrics(innerWidth, innerHeight, _targetShortSideCells, gapRatio, edgeInset);
|
|
||||||
if (gridMetrics.CellSize <= 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
WallpaperPreviewGrid.Margin = new Thickness(gridMetrics.EdgeInsetPx);
|
|
||||||
WallpaperPreviewGrid.RowSpacing = gridMetrics.GapPx;
|
|
||||||
WallpaperPreviewGrid.ColumnSpacing = gridMetrics.GapPx;
|
|
||||||
WallpaperPreviewGrid.Width = gridMetrics.GridWidthPx;
|
|
||||||
WallpaperPreviewGrid.Height = gridMetrics.GridHeightPx;
|
|
||||||
|
|
||||||
// This can be triggered by layout changes; always rebuild the preview grid definitions
|
|
||||||
// to avoid definitions accumulating and shifting overlay components out of place.
|
|
||||||
WallpaperPreviewGrid.RowDefinitions.Clear();
|
|
||||||
WallpaperPreviewGrid.ColumnDefinitions.Clear();
|
|
||||||
|
|
||||||
for (var row = 0; row < gridMetrics.RowCount; row++)
|
|
||||||
{
|
|
||||||
WallpaperPreviewGrid.RowDefinitions.Add(
|
|
||||||
new RowDefinition(new GridLength(gridMetrics.CellSize, GridUnitType.Pixel)));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var col = 0; col < gridMetrics.ColumnCount; col++)
|
|
||||||
{
|
|
||||||
WallpaperPreviewGrid.ColumnDefinitions.Add(
|
|
||||||
new ColumnDefinition(new GridLength(gridMetrics.CellSize, GridUnitType.Pixel)));
|
|
||||||
}
|
|
||||||
|
|
||||||
PlaceStatusBarComponent(
|
|
||||||
WallpaperPreviewTopStatusBarHost,
|
|
||||||
column: 0,
|
|
||||||
requestedColumnSpan: gridMetrics.ColumnCount,
|
|
||||||
totalColumns: gridMetrics.ColumnCount);
|
|
||||||
|
|
||||||
var taskbarRow = gridMetrics.RowCount - 1;
|
|
||||||
Grid.SetRow(WallpaperPreviewBottomTaskbarContainer, taskbarRow);
|
|
||||||
Grid.SetColumn(WallpaperPreviewBottomTaskbarContainer, 0);
|
|
||||||
Grid.SetRowSpan(WallpaperPreviewBottomTaskbarContainer, 1);
|
|
||||||
Grid.SetColumnSpan(WallpaperPreviewBottomTaskbarContainer, gridMetrics.ColumnCount);
|
|
||||||
|
|
||||||
ApplyTopStatusComponentVisibility();
|
|
||||||
ApplyTaskbarActionVisibility(GetCurrentTaskbarContext());
|
|
||||||
ApplyPreviewWidgetSizing(gridMetrics.CellSize);
|
|
||||||
ApplyStatusBarComponentSpacingForPanel(WallpaperPreviewTopStatusComponentsPanel, gridMetrics.CellSize);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_isUpdatingWallpaperPreviewLayout = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApplyPreviewWidgetSizing(double cellSize)
|
|
||||||
{
|
|
||||||
var previewTaskbarCell = Math.Clamp(cellSize * 0.74, 10, 28);
|
|
||||||
var previewTextSize = Math.Clamp(previewTaskbarCell * 0.38, 7, 14);
|
|
||||||
var previewIconSize = Math.Clamp(previewTaskbarCell * 0.46, 8, 16);
|
|
||||||
var previewInset = Math.Clamp(previewTaskbarCell * 0.20, 2, 6);
|
|
||||||
var previewContentSpacing = Math.Clamp(previewTaskbarCell * 0.20, 2, 6);
|
|
||||||
|
|
||||||
// Match desktop behavior: special bars fill their preview row.
|
|
||||||
WallpaperPreviewTopStatusBarHost.Margin = new Thickness(0);
|
|
||||||
WallpaperPreviewTopStatusBarHost.Padding = new Thickness(0);
|
|
||||||
|
|
||||||
WallpaperPreviewBottomTaskbarContainer.Margin = new Thickness(0);
|
|
||||||
WallpaperPreviewBottomTaskbarContainer.CornerRadius = new CornerRadius(Math.Clamp(cellSize * 0.45, 6, 14));
|
|
||||||
WallpaperPreviewBottomTaskbarContainer.Padding = new Thickness(previewInset);
|
|
||||||
|
|
||||||
WallpaperPreviewClockWidget.ApplyCellSize(cellSize);
|
|
||||||
WallpaperPreviewBackButtonTextBlock.FontSize = previewTextSize;
|
|
||||||
WallpaperPreviewComponentLibraryTextBlock.FontSize = previewTextSize;
|
|
||||||
WallpaperPreviewBackButtonVisual.Spacing = previewContentSpacing;
|
|
||||||
WallpaperPreviewComponentLibraryVisual.Spacing = previewContentSpacing;
|
|
||||||
|
|
||||||
WallpaperPreviewBackButtonVisual.MinHeight = previewTaskbarCell;
|
|
||||||
WallpaperPreviewBackButtonVisual.MinWidth = Math.Clamp(cellSize * 2.1, 30, 120);
|
|
||||||
WallpaperPreviewComponentLibraryVisual.MinHeight = previewTaskbarCell;
|
|
||||||
WallpaperPreviewComponentLibraryVisual.MinWidth = Math.Clamp(cellSize * 2.0, 28, 110);
|
|
||||||
|
|
||||||
WallpaperPreviewSettingsButtonIcon.Width = previewIconSize;
|
|
||||||
WallpaperPreviewSettingsButtonIcon.Height = previewIconSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnMinimizeClick(object? sender, RoutedEventArgs e)
|
private void OnMinimizeClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
WindowState = WindowState.Minimized;
|
WindowState = WindowState.Minimized;
|
||||||
|
|||||||
@@ -22,9 +22,26 @@
|
|||||||
BoxShadow="0 12 32 #50000000">
|
BoxShadow="0 12 32 #50000000">
|
||||||
<Panel Background="{DynamicResource AdaptiveSurfaceBaseBrush}" Margin="2">
|
<Panel Background="{DynamicResource AdaptiveSurfaceBaseBrush}" Margin="2">
|
||||||
<!-- 图片/视频预览 -->
|
<!-- 图片/视频预览 -->
|
||||||
<Image Source="{Binding PreviewImage}"
|
<Border Background="#FFF6F7F9"
|
||||||
IsVisible="{Binding IsImageOrVideo}"
|
IsVisible="{Binding IsImage}">
|
||||||
Stretch="UniformToFill" />
|
<Border Background="{Binding PreviewBrush}" />
|
||||||
|
</Border>
|
||||||
|
<Border Background="#FFF6F7F9"
|
||||||
|
IsVisible="{Binding IsVideo}">
|
||||||
|
<StackPanel HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Spacing="12">
|
||||||
|
<fi:FluentIcon Icon="Video"
|
||||||
|
Width="72"
|
||||||
|
Height="72"
|
||||||
|
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}" />
|
||||||
|
<TextBlock Text="{Binding VideoModeHintText}"
|
||||||
|
Width="300"
|
||||||
|
TextAlignment="Center"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
<!-- 纯色预览 -->
|
<!-- 纯色预览 -->
|
||||||
<Border Background="{Binding SelectedColor}"
|
<Border Background="{Binding SelectedColor}"
|
||||||
IsVisible="{Binding IsSolidColor}" />
|
IsVisible="{Binding IsSolidColor}" />
|
||||||
@@ -90,7 +107,7 @@
|
|||||||
|
|
||||||
<!-- 图片/视频文件选择 -->
|
<!-- 图片/视频文件选择 -->
|
||||||
<ui:SettingsExpander Header="{Binding WallpaperPathLabel}"
|
<ui:SettingsExpander Header="{Binding WallpaperPathLabel}"
|
||||||
IsVisible="{Binding IsImageOrVideo}"
|
IsVisible="{Binding IsImage}"
|
||||||
Margin="0,4,0,0">
|
Margin="0,4,0,0">
|
||||||
<ui:SettingsExpander.IconSource>
|
<ui:SettingsExpander.IconSource>
|
||||||
<fi:SymbolIconSource Symbol="FolderOpen" />
|
<fi:SymbolIconSource Symbol="FolderOpen" />
|
||||||
@@ -130,6 +147,12 @@
|
|||||||
</ui:SettingsExpander.Footer>
|
</ui:SettingsExpander.Footer>
|
||||||
</ui:SettingsExpander>
|
</ui:SettingsExpander>
|
||||||
|
|
||||||
|
<TextBlock Margin="0,8,0,0"
|
||||||
|
IsVisible="{Binding IsVideo}"
|
||||||
|
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||||
|
Text="{Binding VideoModeHintText}"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
SystemDecorations="BorderOnly"
|
SystemDecorations="BorderOnly"
|
||||||
FontFamily="{DynamicResource AppFontFamily}"
|
FontFamily="{DynamicResource AppFontFamily}"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
Icon="/Assets/avalonia-logo.ico"
|
|
||||||
Title="{Binding Title}">
|
Title="{Binding Title}">
|
||||||
|
|
||||||
<Window.Resources>
|
<Window.Resources>
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
|
|||||||
|
|
||||||
private readonly ISettingsPageRegistry _pageRegistry;
|
private readonly ISettingsPageRegistry _pageRegistry;
|
||||||
private readonly IHostApplicationLifecycle _hostApplicationLifecycle;
|
private readonly IHostApplicationLifecycle _hostApplicationLifecycle;
|
||||||
|
private readonly IAppLogoService _appLogoService = HostAppLogoProvider.GetOrCreate();
|
||||||
private readonly Dictionary<string, Control> _cachedPages = new(StringComparer.OrdinalIgnoreCase);
|
private readonly Dictionary<string, Control> _cachedPages = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private readonly bool _useSystemChrome;
|
private readonly bool _useSystemChrome;
|
||||||
private bool _isResponsiveRefreshPending;
|
private bool _isResponsiveRefreshPending;
|
||||||
@@ -55,6 +56,7 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
|
|||||||
_hostApplicationLifecycle = hostApplicationLifecycle;
|
_hostApplicationLifecycle = hostApplicationLifecycle;
|
||||||
DataContext = ViewModel;
|
DataContext = ViewModel;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
Icon = _appLogoService.CreateWindowIcon();
|
||||||
ApplyChromeMode(useSystemChrome);
|
ApplyChromeMode(useSystemChrome);
|
||||||
|
|
||||||
if (RootNavigationView is not null)
|
if (RootNavigationView is not null)
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ UsePreviousAppDir=no
|
|||||||
ShowLanguageDialog=yes
|
ShowLanguageDialog=yes
|
||||||
UsePreviousLanguage=no
|
UsePreviousLanguage=no
|
||||||
LanguageDetectionMethod=uilanguage
|
LanguageDetectionMethod=uilanguage
|
||||||
DefaultGroupName={#MyAppName}
|
DefaultGroupName={cm:AppShortcutName}
|
||||||
UninstallDisplayIcon={app}\{#MyAppExeName}
|
UninstallDisplayIcon={app}\{#MyAppExeName}
|
||||||
OutputDir={#MyOutputDir}
|
OutputDir={#MyOutputDir}
|
||||||
OutputBaseFilename={#MyAppName}-Setup-{#MyAppVersion}-{#MyAppArch}
|
OutputBaseFilename={#MyAppName}-Setup-{#MyAppVersion}-{#MyAppArch}
|
||||||
@@ -62,6 +62,8 @@ Name: "chinesesimplified"; MessagesFile: "{#SourcePath}\ChineseSimplified.isl"
|
|||||||
[CustomMessages]
|
[CustomMessages]
|
||||||
english.StartupTaskDescription=Launch LanMountainDesktop when you sign in to Windows
|
english.StartupTaskDescription=Launch LanMountainDesktop when you sign in to Windows
|
||||||
chinesesimplified.StartupTaskDescription=登录 Windows 时启动 LanMountainDesktop
|
chinesesimplified.StartupTaskDescription=登录 Windows 时启动 LanMountainDesktop
|
||||||
|
english.AppShortcutName=LanMountainDesktop
|
||||||
|
chinesesimplified.AppShortcutName=阑山桌面
|
||||||
english.WebView2MissingMessage=Microsoft Edge WebView2 Runtime is required for the browser component.
|
english.WebView2MissingMessage=Microsoft Edge WebView2 Runtime is required for the browser component.
|
||||||
chinesesimplified.WebView2MissingMessage=浏览器组件需要 Microsoft Edge WebView2 Runtime。
|
chinesesimplified.WebView2MissingMessage=浏览器组件需要 Microsoft Edge WebView2 Runtime。
|
||||||
english.WebView2MissingAction=Click "Yes" to open the official download page. Install it first, then run this installer again.
|
english.WebView2MissingAction=Click "Yes" to open the official download page. Install it first, then run this installer again.
|
||||||
@@ -111,8 +113,8 @@ Type: files; Name: "{app}\LanMontainDesktop.pdb"
|
|||||||
Source: "{#PublishDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
Source: "{#PublishDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
|
|
||||||
[Icons]
|
[Icons]
|
||||||
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
Name: "{autoprograms}\{cm:AppShortcutName}"; Filename: "{app}\{#MyAppExeName}"
|
||||||
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
Name: "{autodesktop}\{cm:AppShortcutName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
||||||
|
|
||||||
[Registry]
|
[Registry]
|
||||||
Root: HKA; Subkey: "Software\Microsoft\Windows\CurrentVersion\Run"; ValueType: string; ValueName: "{#MyAppName}"; ValueData: """{app}\{#MyAppExeName}"""; Tasks: startup; Flags: uninsdeletevalue
|
Root: HKA; Subkey: "Software\Microsoft\Windows\CurrentVersion\Run"; ValueType: string; ValueName: "{#MyAppName}"; ValueData: """{app}\{#MyAppExeName}"""; Tasks: startup; Flags: uninsdeletevalue
|
||||||
|
|||||||
Reference in New Issue
Block a user