重构了设置系统。解决了大量的bug,正式添加了图标。引入了遥测的同意与许可(暂无实际功能)
This commit is contained in:
lincube
2026-03-15 21:27:48 +08:00
parent f83c6ede1d
commit 557b79e8c0
19 changed files with 1143 additions and 869 deletions

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View 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

View 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>

View File

@@ -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>

View 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();
}
}
}

View File

@@ -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,
[], [],

View 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();
}
}
}

View 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;
}
}

View File

@@ -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)

View File

@@ -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);
} }

View File

@@ -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

View File

@@ -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="&#32452;&#20214;&#24211;"> <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="&#32452;&#20214;&#24211;" /> 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>

View File

@@ -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;

View File

@@ -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>

View File

@@ -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>

View File

@@ -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)

View File

@@ -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