feat.启动器图片自定义

This commit is contained in:
lincube
2026-06-05 23:38:32 +08:00
parent eae3e67238
commit 8df0271032
13 changed files with 806 additions and 176 deletions

View File

@@ -5,25 +5,37 @@
xmlns:views="clr-namespace:LanMountainDesktop.Launcher.Views"
xmlns:res="clr-namespace:LanMountainDesktop.Launcher.Resources"
mc:Ignorable="d"
d:DesignWidth="420"
d:DesignHeight="320"
d:DesignWidth="460"
d:DesignHeight="500"
x:Class="LanMountainDesktop.Launcher.Views.ErrorDebugWindow"
x:DataType="views:ErrorDebugWindow"
x:CompileBindings="False"
Title="{x:Static res:Strings.DebugDebug_Title}"
Width="420"
Height="320"
Width="460"
Height="500"
CanResize="False"
WindowStartupLocation="CenterOwner"
Background="{DynamicResource SolidBackgroundFillColorBaseBrush}"
TransparencyLevelHint="None"
Icon="/Assets/logo.ico">
<Window.Resources>
<CornerRadius x:Key="DesignCornerRadiusMicro">2</CornerRadius>
<CornerRadius x:Key="DesignCornerRadiusXs">4</CornerRadius>
<CornerRadius x:Key="DesignCornerRadiusSm">4</CornerRadius>
<CornerRadius x:Key="DesignCornerRadiusMd">8</CornerRadius>
<CornerRadius x:Key="DesignCornerRadiusLg">8</CornerRadius>
<CornerRadius x:Key="DesignCornerRadiusXl">12</CornerRadius>
<CornerRadius x:Key="DesignCornerRadiusIsland">16</CornerRadius>
<CornerRadius x:Key="DesignCornerRadiusComponent">8</CornerRadius>
<CornerRadius x:Key="OverlayCornerRadius">8</CornerRadius>
<CornerRadius x:Key="ControlCornerRadius">4</CornerRadius>
</Window.Resources>
<Design.DataContext>
<views:ErrorDebugWindow />
</Design.DataContext>
<Grid Margin="24" RowDefinitions="Auto,*,Auto">
<!-- 标题 -->
<TextBlock Grid.Row="0"
Text="{x:Static res:Strings.DebugDebug_SettingsTitle}"
FontSize="20"
@@ -31,65 +43,108 @@
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Margin="0,0,0,16" />
<!-- 设置内容 -->
<StackPanel Grid.Row="1" Spacing="16">
<!-- 开发模式开关 -->
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="{DynamicResource ControlCornerRadius}"
Padding="16,12">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" VerticalAlignment="Center">
<TextBlock Text="{x:Static res:Strings.DebugDebug_DevMode}"
<ScrollViewer Grid.Row="1"
VerticalScrollBarVisibility="Auto">
<StackPanel Spacing="16">
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
Padding="16,12">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" VerticalAlignment="Center">
<TextBlock Text="{x:Static res:Strings.DebugDebug_DevMode}"
FontSize="14"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="{x:Static res:Strings.DebugDebug_DevModeDesc}"
FontSize="12"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="0,2,0,0" />
</StackPanel>
<ToggleSwitch x:Name="DevModeToggle"
Grid.Column="1"
OnContent="{x:Static res:Strings.DebugDebug_On}"
OffContent="{x:Static res:Strings.DebugDebug_Off}" />
</Grid>
</Border>
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
Padding="16,12">
<Grid RowDefinitions="Auto,Auto" ColumnDefinitions="*,Auto">
<TextBlock Grid.Row="0" Grid.Column="0"
Text="{x:Static res:Strings.DebugDebug_AppPath}"
FontSize="14"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="{x:Static res:Strings.DebugDebug_DevModeDesc}"
<TextBlock x:Name="PathTextBlock"
Grid.Row="1" Grid.Column="0"
Text="{x:Static res:Strings.DebugDebug_NotSelected}"
FontSize="12"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="0,2,0,0" />
</StackPanel>
<ToggleSwitch x:Name="DevModeToggle"
TextTrimming="CharacterEllipsis"
Margin="0,4,12,0" />
<Button x:Name="BrowseButton"
Grid.Row="0" Grid.RowSpan="2" Grid.Column="1"
Content="{x:Static res:Strings.DebugDebug_Browse}"
VerticalAlignment="Center" />
</Grid>
</Border>
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
Padding="16,12">
<Grid RowDefinitions="Auto,Auto,Auto" ColumnDefinitions="*,Auto">
<StackPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Spacing="2">
<TextBlock Text="{x:Static res:Strings.DebugDebug_BackgroundImage}"
FontSize="14"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="{x:Static res:Strings.DebugDebug_BackgroundImageDesc}"
FontSize="12"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap" />
</StackPanel>
<TextBlock x:Name="BackgroundImagePathTextBlock"
Grid.Row="1" Grid.Column="0"
Text="{x:Static res:Strings.DebugDebug_BackgroundImageNotSet}"
FontSize="12"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextTrimming="CharacterEllipsis"
Margin="0,10,12,0" />
<StackPanel Grid.Row="1"
Grid.Column="1"
OnContent="{x:Static res:Strings.DebugDebug_On}"
OffContent="{x:Static res:Strings.DebugDebug_Off}" />
</Grid>
</Border>
Orientation="Horizontal"
Spacing="8"
Margin="0,6,0,0">
<Button x:Name="BrowseImageButton"
Content="{x:Static res:Strings.DebugDebug_Browse}"
VerticalAlignment="Center" />
<Button x:Name="ClearImageButton"
Content="{x:Static res:Strings.DebugDebug_Clear}"
VerticalAlignment="Center" />
</StackPanel>
<!-- 应用路径选择 -->
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="{DynamicResource ControlCornerRadius}"
Padding="16,12">
<Grid RowDefinitions="Auto,Auto" ColumnDefinitions="*,Auto">
<TextBlock Grid.Row="0" Grid.Column="0"
Text="{x:Static res:Strings.DebugDebug_AppPath}"
FontSize="14"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock x:Name="PathTextBlock"
Grid.Row="1" Grid.Column="0"
Text="{x:Static res:Strings.DebugDebug_NotSelected}"
<TextBlock x:Name="BackgroundImageStatusTextBlock"
Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
Text="{x:Static res:Strings.DebugDebug_BackgroundImageNotSet}"
FontSize="12"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
Margin="0,8,0,0" />
</Grid>
</Border>
<Border Background="{DynamicResource SystemFillColorCautionBackgroundBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
Padding="12,10"
IsVisible="True">
<TextBlock Text="{x:Static res:Strings.DebugDebug_Warning}"
FontSize="12"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextTrimming="CharacterEllipsis"
Margin="0,4,12,0" />
<Button x:Name="BrowseButton"
Grid.Row="0" Grid.RowSpan="2" Grid.Column="1"
Content="{x:Static res:Strings.DebugDebug_Browse}"
VerticalAlignment="Center" />
</Grid>
</Border>
Foreground="{DynamicResource SystemFillColorCautionBrush}"
TextWrapping="Wrap" />
</Border>
</StackPanel>
</ScrollViewer>
<!-- 提示信息 -->
<Border Background="{DynamicResource SystemFillColorCautionBackgroundBrush}"
CornerRadius="{DynamicResource ControlCornerRadius}"
Padding="12,10"
IsVisible="True">
<TextBlock Text="{x:Static res:Strings.DebugDebug_Warning}"
FontSize="12"
Foreground="{DynamicResource SystemFillColorCautionBrush}"
TextWrapping="Wrap" />
</Border>
</StackPanel>
<!-- 按钮区域 -->
<StackPanel Grid.Row="2"
Orientation="Horizontal"
HorizontalAlignment="Right"

View File

@@ -3,6 +3,7 @@ using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Platform.Storage;
using LanMountainDesktop.Launcher.Resources;
using LanMountainDesktop.Launcher.Shell;
namespace LanMountainDesktop.Launcher.Views;
@@ -46,6 +47,7 @@ public partial class ErrorDebugWindow : Window
}
UpdatePathDisplay(_selectedHostPath);
RefreshBackgroundImageDisplay();
}
private void InitializeComponents()
@@ -63,6 +65,16 @@ public partial class ErrorDebugWindow : Window
browseButton.Click += OnBrowseClick;
}
if (this.FindControl<Button>("BrowseImageButton") is { } browseImageButton)
{
browseImageButton.Click += OnBrowseImageClick;
}
if (this.FindControl<Button>("ClearImageButton") is { } clearImageButton)
{
clearImageButton.Click += OnClearImageClick;
}
if (this.FindControl<Button>("OkButton") is { } okButton)
{
okButton.Click += (_, _) =>
@@ -111,6 +123,56 @@ public partial class ErrorDebugWindow : Window
UpdatePathDisplay(_selectedHostPath);
}
private async void OnBrowseImageClick(object? sender, RoutedEventArgs e)
{
var storageProvider = StorageProvider;
if (storageProvider is null)
{
return;
}
var patterns = LauncherBackgroundService
.GetSupportedExtensions()
.Select(extension => "*" + extension)
.ToArray();
var options = new FilePickerOpenOptions
{
Title = Strings.DebugDebug_SelectImageDialog,
AllowMultiple = false,
FileTypeFilter =
[
new FilePickerFileType(Strings.DebugDebug_ImageFiles)
{
Patterns = patterns
}
]
};
var result = await storageProvider.OpenFilePickerAsync(options);
if (result.Count <= 0)
{
return;
}
var saveResult = LauncherBackgroundService.SaveBackgroundImage(result[0].Path.LocalPath);
var status = saveResult.IsSuccess
? Strings.DebugDebug_BackgroundImageSaved
: string.Format(Strings.DebugDebug_BackgroundImageSaveFailedFormat, saveResult.ErrorMessage ?? string.Empty);
RefreshBackgroundImageDisplay(status);
}
private void OnClearImageClick(object? sender, RoutedEventArgs e)
{
var clearResult = LauncherBackgroundService.ClearBackgroundImage();
var status = clearResult.IsSuccess
? Strings.DebugDebug_BackgroundImageCleared
: string.Format(Strings.DebugDebug_BackgroundImageSaveFailedFormat, clearResult.ErrorMessage ?? string.Empty);
RefreshBackgroundImageDisplay(status);
}
private void UpdatePathDisplay(string? path)
{
if (this.FindControl<TextBlock>("PathTextBlock") is { } pathTextBlock)
@@ -118,4 +180,46 @@ public partial class ErrorDebugWindow : Window
pathTextBlock.Text = string.IsNullOrEmpty(path) ? Strings.DebugDebug_NotSelected : path;
}
}
private void RefreshBackgroundImageDisplay(string? statusOverride = null)
{
var imageInfo = LauncherBackgroundService.LoadBackgroundImage();
if (this.FindControl<TextBlock>("BackgroundImagePathTextBlock") is { } pathTextBlock)
{
pathTextBlock.Text = imageInfo.Exists && !string.IsNullOrWhiteSpace(imageInfo.FilePath)
? imageInfo.FilePath
: Strings.DebugDebug_BackgroundImageNotSet;
}
if (this.FindControl<TextBlock>("BackgroundImageStatusTextBlock") is { } statusTextBlock)
{
statusTextBlock.Text = statusOverride ?? ResolveBackgroundImageStatus(imageInfo);
}
if (this.FindControl<Button>("ClearImageButton") is { } clearButton)
{
clearButton.IsEnabled = imageInfo.Exists;
}
}
private static string ResolveBackgroundImageStatus(LauncherBackgroundService.BackgroundImageInfo imageInfo)
{
if (imageInfo.IsValid)
{
return string.Format(
Strings.DebugDebug_BackgroundImageReadyFormat,
imageInfo.Width,
imageInfo.Height);
}
if (imageInfo.Exists)
{
return string.Format(
Strings.DebugDebug_BackgroundImageInvalidFormat,
imageInfo.ErrorMessage ?? string.Empty);
}
return Strings.DebugDebug_BackgroundImageNotSet;
}
}

View File

@@ -15,72 +15,91 @@
ShowInTaskbar="False"
WindowStartupLocation="CenterScreen"
WindowDecorations="None"
Background="#0B0B0B"
TransparencyLevelHint="None"
Background="Transparent"
TransparencyLevelHint="Transparent"
Icon="/Assets/logo.ico">
<Window.Resources>
<CornerRadius x:Key="DesignCornerRadiusMicro">2</CornerRadius>
<CornerRadius x:Key="DesignCornerRadiusXs">4</CornerRadius>
<CornerRadius x:Key="DesignCornerRadiusSm">4</CornerRadius>
<CornerRadius x:Key="DesignCornerRadiusMd">8</CornerRadius>
<CornerRadius x:Key="DesignCornerRadiusLg">8</CornerRadius>
<CornerRadius x:Key="DesignCornerRadiusXl">12</CornerRadius>
<CornerRadius x:Key="DesignCornerRadiusIsland">16</CornerRadius>
<CornerRadius x:Key="DesignCornerRadiusComponent">8</CornerRadius>
<CornerRadius x:Key="OverlayCornerRadius">8</CornerRadius>
<CornerRadius x:Key="ControlCornerRadius">4</CornerRadius>
</Window.Resources>
<Design.DataContext>
<views:SplashWindow />
</Design.DataContext>
<Grid RowDefinitions="*,Auto">
<!-- 背景图片 -->
<Image x:Name="BackgroundImage"
Grid.RowSpan="2"
Stretch="UniformToFill"
IsVisible="False"
Opacity="0"/>
<Border x:Name="RootShell"
Background="#0B0B0B"
CornerRadius="{DynamicResource DesignCornerRadiusComponent}"
ClipToBounds="True">
<Grid RowDefinitions="*,Auto">
<Image x:Name="BackgroundImage"
Grid.RowSpan="2"
Stretch="Uniform"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="False"
Opacity="0"/>
<!-- 半透明遮罩层 -->
<Border x:Name="BackgroundOverlay"
Grid.RowSpan="2"
Background="#0B0B0B"
Opacity="0.85"/>
<Border x:Name="BackgroundOverlay"
Grid.RowSpan="2"
Background="#0B0B0B"
Opacity="0.42"/>
<Grid Grid.Row="0"
Margin="24">
<TextBlock x:Name="AppNameText"
Text="LanMountain Desktop"
FontSize="24"
FontWeight="SemiBold"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Foreground="#F6F7FB" />
</Grid>
<Border Grid.Row="1"
Padding="24,18,24,24"
Background="Transparent">
<Grid RowDefinitions="Auto,Auto"
RowSpacing="10">
<Grid ColumnDefinitions="*,Auto">
<Border x:Name="VersionTextBorder"
Background="Transparent"
Cursor="Hand"
HorizontalAlignment="Left">
<TextBlock x:Name="VersionText"
FontSize="11"
Foreground="#B9C0CC"
Text="0.0.0-dev (Administrate)" />
</Border>
<TextBlock x:Name="StatusText"
Grid.Column="1"
FontSize="11"
Foreground="#B9C0CC"
HorizontalAlignment="Right"
Text="{x:Static res:Strings.Splash_StatusInitializing}" />
</Grid>
<ProgressBar x:Name="ProgressIndicator"
Grid.Row="1"
Minimum="0"
Maximum="100"
Value="0"
Height="4"
IsIndeterminate="False"
Foreground="#F6F7FB"
Background="#2C313D" />
<Grid Grid.Row="0"
Margin="24">
<TextBlock x:Name="AppNameText"
Text="{x:Static res:Strings.Splash_AppName}"
FontSize="24"
FontWeight="SemiBold"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Foreground="#F6F7FB" />
</Grid>
</Border>
</Grid>
<Border Grid.Row="1"
Padding="24,18,24,24"
Background="Transparent">
<Grid RowDefinitions="Auto,Auto"
RowSpacing="10">
<Grid ColumnDefinitions="*,Auto">
<Border x:Name="VersionTextBorder"
Background="Transparent"
Cursor="Hand"
CornerRadius="{DynamicResource DesignCornerRadiusSm}"
HorizontalAlignment="Left">
<TextBlock x:Name="VersionText"
FontSize="11"
Foreground="#D8DEE9"
Text="0.0.0-dev (Administrate)" />
</Border>
<TextBlock x:Name="StatusText"
Grid.Column="1"
FontSize="11"
Foreground="#D8DEE9"
HorizontalAlignment="Right"
Text="{x:Static res:Strings.Splash_StatusInitializing}" />
</Grid>
<ProgressBar x:Name="ProgressIndicator"
Grid.Row="1"
Minimum="0"
Maximum="100"
Value="0"
Height="4"
IsIndeterminate="False"
Foreground="#F6F7FB"
Background="#592C313D" />
</Grid>
</Border>
</Grid>
</Border>
</Window>

View File

@@ -42,6 +42,8 @@ public partial class SplashWindow : Window, ISplashStageReporter
{
try
{
ResetBackgroundImage();
var imageInfo = LauncherBackgroundService.LoadBackgroundImage();
if (imageInfo is { IsValid: true, Bitmap: not null })
{
@@ -51,16 +53,27 @@ public partial class SplashWindow : Window, ISplashStageReporter
backgroundImage.IsVisible = true;
backgroundImage.Opacity = 1;
}
Logger.Info("[SplashWindow] 背景图片加载成功");
Logger.Info("[SplashWindow] Background image loaded.");
}
else if (imageInfo is { Exists: true, IsValid: false })
{
Logger.Warn($"[SplashWindow] 背景图片校验失败: {imageInfo.ErrorMessage}");
Logger.Warn($"[SplashWindow] Background image validation failed: {imageInfo.ErrorMessage}");
}
}
catch (Exception ex)
{
Logger.Warn($"[SplashWindow] 加载背景图片失败: {ex.Message}");
Logger.Warn($"[SplashWindow] Failed to load background image: {ex.Message}");
}
}
private void ResetBackgroundImage()
{
if (this.FindControl<Image>("BackgroundImage") is { } backgroundImage)
{
backgroundImage.Source = null;
backgroundImage.IsVisible = false;
backgroundImage.Opacity = 0;
}
}
@@ -224,6 +237,7 @@ public partial class SplashWindow : Window, ISplashStageReporter
debugWindow.SelectedHostPath));
}
InitializeBackgroundImage();
_isDebugModeOpened = false;
_versionTextClickCount = 0;
};