mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-27 21:04:27 +08:00
Add OOBE redesign, theme & data location support
Introduce a redesigned OOBE flow and data-location/theme support across the launcher. Adds a new ThemeService for applying light/dark and accent colors; integrates FluentIcons.Avalonia package for icons. Overhauls OobeWindow (UX animations, typing effect, multi-step theme and data-location pages, Monet options, and final welcome step) and its code-behind to handle step navigation, accent selection, and data-location resolution. Adds DataLocation UI and handlers (DataLocationPromptWindow changes, DataLocation resolver usage) and wires a DevDebug UI for toggling/opening the data-location page. UpdateEngineService now resolves the launcher root via DataLocationResolver. Misc: update various view models, localization entries and remove TrimmerRoots.xml.
This commit is contained in:
@@ -25,6 +25,7 @@
|
|||||||
<PackageReference Include="Avalonia" Version="11.3.12" />
|
<PackageReference Include="Avalonia" Version="11.3.12" />
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.12" />
|
<PackageReference Include="Avalonia.Desktop" Version="11.3.12" />
|
||||||
<PackageReference Include="FluentAvaloniaUI" Version="2.5.0" />
|
<PackageReference Include="FluentAvaloniaUI" Version="2.5.0" />
|
||||||
|
<PackageReference Include="FluentIcons.Avalonia" Version="1.1.250403001" />
|
||||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.12" />
|
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.12" />
|
||||||
<PackageReference Include="Tmds.DBus.Protocol" Version="0.92.0" />
|
<PackageReference Include="Tmds.DBus.Protocol" Version="0.92.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
68
LanMountainDesktop.Launcher/Services/ThemeService.cs
Normal file
68
LanMountainDesktop.Launcher/Services/ThemeService.cs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Styling;
|
||||||
|
using FluentAvalonia.Styling;
|
||||||
|
|
||||||
|
namespace LanMountainDesktop.Launcher.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 主题服务,管理启动器的主题设置
|
||||||
|
/// </summary>
|
||||||
|
public static class ThemeService
|
||||||
|
{
|
||||||
|
private static ThemeVariant _currentTheme = ThemeVariant.Light;
|
||||||
|
private static string _accentColor = "#0078D4";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前主题
|
||||||
|
/// </summary>
|
||||||
|
public static ThemeVariant CurrentTheme => _currentTheme;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前主题色
|
||||||
|
/// </summary>
|
||||||
|
public static string AccentColor => _accentColor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用主题设置
|
||||||
|
/// </summary>
|
||||||
|
public static void ApplyTheme(ThemeMode mode, string accentColor)
|
||||||
|
{
|
||||||
|
_currentTheme = mode switch
|
||||||
|
{
|
||||||
|
ThemeMode.Dark => ThemeVariant.Dark,
|
||||||
|
_ => ThemeVariant.Light
|
||||||
|
};
|
||||||
|
_accentColor = accentColor;
|
||||||
|
|
||||||
|
// 应用到当前应用程序
|
||||||
|
if (Application.Current is { } app)
|
||||||
|
{
|
||||||
|
app.RequestedThemeVariant = _currentTheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用浅色主题
|
||||||
|
/// </summary>
|
||||||
|
public static void ApplyLightTheme(string accentColor)
|
||||||
|
{
|
||||||
|
ApplyTheme(ThemeMode.Light, accentColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用深色主题
|
||||||
|
/// </summary>
|
||||||
|
public static void ApplyDarkTheme(string accentColor)
|
||||||
|
{
|
||||||
|
ApplyTheme(ThemeMode.Dark, accentColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 主题模式
|
||||||
|
/// </summary>
|
||||||
|
public enum ThemeMode
|
||||||
|
{
|
||||||
|
Light,
|
||||||
|
Dark
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ internal sealed class UpdateEngineService
|
|||||||
|
|
||||||
private readonly DeploymentLocator _deploymentLocator;
|
private readonly DeploymentLocator _deploymentLocator;
|
||||||
private readonly string _appRoot;
|
private readonly string _appRoot;
|
||||||
|
private readonly string _dataRoot;
|
||||||
private readonly string _launcherRoot;
|
private readonly string _launcherRoot;
|
||||||
private readonly string _incomingRoot;
|
private readonly string _incomingRoot;
|
||||||
private readonly string _snapshotsRoot;
|
private readonly string _snapshotsRoot;
|
||||||
@@ -30,7 +31,8 @@ internal sealed class UpdateEngineService
|
|||||||
{
|
{
|
||||||
_deploymentLocator = deploymentLocator;
|
_deploymentLocator = deploymentLocator;
|
||||||
_appRoot = deploymentLocator.GetAppRoot();
|
_appRoot = deploymentLocator.GetAppRoot();
|
||||||
_launcherRoot = Path.Combine(_appRoot, LauncherDirectoryName);
|
_dataRoot = new DataLocationResolver(_appRoot).ResolveDataRoot();
|
||||||
|
_launcherRoot = Path.Combine(_dataRoot, LauncherDirectoryName);
|
||||||
_incomingRoot = Path.Combine(_launcherRoot, UpdateDirectoryName, IncomingDirectoryName);
|
_incomingRoot = Path.Combine(_launcherRoot, UpdateDirectoryName, IncomingDirectoryName);
|
||||||
_snapshotsRoot = Path.Combine(_launcherRoot, SnapshotsDirectoryName);
|
_snapshotsRoot = Path.Combine(_launcherRoot, SnapshotsDirectoryName);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ public sealed class DevDebugWindowViewModel : INotifyPropertyChanged
|
|||||||
private bool _isErrorEnabled = true;
|
private bool _isErrorEnabled = true;
|
||||||
private bool _isUpdateEnabled = true;
|
private bool _isUpdateEnabled = true;
|
||||||
private bool _isOobeEnabled = true;
|
private bool _isOobeEnabled = true;
|
||||||
|
private bool _isDataLocationEnabled = true;
|
||||||
private string _statusMessage = "就绪";
|
private string _statusMessage = "就绪";
|
||||||
|
|
||||||
public event PropertyChangedEventHandler? PropertyChanged;
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
@@ -87,6 +88,23 @@ public sealed class DevDebugWindowViewModel : INotifyPropertyChanged
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 数据位置选择页面是否启用实际功能
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDataLocationEnabled
|
||||||
|
{
|
||||||
|
get => _isDataLocationEnabled;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_isDataLocationEnabled != value)
|
||||||
|
{
|
||||||
|
_isDataLocationEnabled = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
UpdateStatus($"数据位置选择: {(value ? "功能模式" : "仅查看")}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 状态信息
|
#region 状态信息
|
||||||
@@ -131,6 +149,11 @@ public sealed class DevDebugWindowViewModel : INotifyPropertyChanged
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ICommand OpenOobeCommand { get; }
|
public ICommand OpenOobeCommand { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 打开数据位置选择页面命令
|
||||||
|
/// </summary>
|
||||||
|
public ICommand OpenDataLocationCommand { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 全部切换到查看模式命令
|
/// 全部切换到查看模式命令
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -170,6 +193,11 @@ public sealed class DevDebugWindowViewModel : INotifyPropertyChanged
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<OobeOpenEventArgs>? OpenOobeRequested;
|
public event EventHandler<OobeOpenEventArgs>? OpenOobeRequested;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 请求打开数据位置选择页面
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<DataLocationOpenEventArgs>? OpenDataLocationRequested;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 请求关闭窗口
|
/// 请求关闭窗口
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -199,12 +227,18 @@ public sealed class DevDebugWindowViewModel : INotifyPropertyChanged
|
|||||||
OpenOobeRequested?.Invoke(this, new OobeOpenEventArgs(IsOobeEnabled));
|
OpenOobeRequested?.Invoke(this, new OobeOpenEventArgs(IsOobeEnabled));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
OpenDataLocationCommand = new RelayCommand(() =>
|
||||||
|
{
|
||||||
|
OpenDataLocationRequested?.Invoke(this, new DataLocationOpenEventArgs(IsDataLocationEnabled));
|
||||||
|
});
|
||||||
|
|
||||||
SetAllViewOnlyCommand = new RelayCommand(() =>
|
SetAllViewOnlyCommand = new RelayCommand(() =>
|
||||||
{
|
{
|
||||||
IsSplashEnabled = false;
|
IsSplashEnabled = false;
|
||||||
IsErrorEnabled = false;
|
IsErrorEnabled = false;
|
||||||
IsUpdateEnabled = false;
|
IsUpdateEnabled = false;
|
||||||
IsOobeEnabled = false;
|
IsOobeEnabled = false;
|
||||||
|
IsDataLocationEnabled = false;
|
||||||
UpdateStatus("全部页面已切换到查看模式");
|
UpdateStatus("全部页面已切换到查看模式");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -214,6 +248,7 @@ public sealed class DevDebugWindowViewModel : INotifyPropertyChanged
|
|||||||
IsErrorEnabled = true;
|
IsErrorEnabled = true;
|
||||||
IsUpdateEnabled = true;
|
IsUpdateEnabled = true;
|
||||||
IsOobeEnabled = true;
|
IsOobeEnabled = true;
|
||||||
|
IsDataLocationEnabled = true;
|
||||||
UpdateStatus("全部页面已切换到功能模式");
|
UpdateStatus("全部页面已切换到功能模式");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -260,4 +295,10 @@ public class OobeOpenEventArgs : EventArgs
|
|||||||
public OobeOpenEventArgs(bool isFunctional) => IsFunctional = isFunctional;
|
public OobeOpenEventArgs(bool isFunctional) => IsFunctional = isFunctional;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class DataLocationOpenEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public bool IsFunctional { get; }
|
||||||
|
public DataLocationOpenEventArgs(bool isFunctional) => IsFunctional = isFunctional;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -221,6 +221,7 @@ internal partial class DataLocationPromptWindow : Window
|
|||||||
{
|
{
|
||||||
Duration = TimeSpan.FromMilliseconds(500),
|
Duration = TimeSpan.FromMilliseconds(500),
|
||||||
Easing = new CubicEaseOut(),
|
Easing = new CubicEaseOut(),
|
||||||
|
FillMode = FillMode.Forward,
|
||||||
Children =
|
Children =
|
||||||
{
|
{
|
||||||
new KeyFrame
|
new KeyFrame
|
||||||
@@ -240,6 +241,7 @@ internal partial class DataLocationPromptWindow : Window
|
|||||||
{
|
{
|
||||||
Duration = TimeSpan.FromMilliseconds(500),
|
Duration = TimeSpan.FromMilliseconds(500),
|
||||||
Easing = new CubicEaseOut(),
|
Easing = new CubicEaseOut(),
|
||||||
|
FillMode = FillMode.Forward,
|
||||||
Children =
|
Children =
|
||||||
{
|
{
|
||||||
new KeyFrame
|
new KeyFrame
|
||||||
@@ -280,6 +282,7 @@ internal partial class DataLocationPromptWindow : Window
|
|||||||
{
|
{
|
||||||
Duration = TimeSpan.FromMilliseconds(200),
|
Duration = TimeSpan.FromMilliseconds(200),
|
||||||
Easing = new CubicEaseIn(),
|
Easing = new CubicEaseIn(),
|
||||||
|
FillMode = FillMode.Forward,
|
||||||
Children =
|
Children =
|
||||||
{
|
{
|
||||||
new KeyFrame
|
new KeyFrame
|
||||||
|
|||||||
@@ -141,6 +141,32 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
|
<!-- 数据位置选择页面 -->
|
||||||
|
<Border Background="{DynamicResource SystemControlBackgroundAltMediumBrush}"
|
||||||
|
CornerRadius="8"
|
||||||
|
Padding="15">
|
||||||
|
<Grid ColumnDefinitions="*,Auto">
|
||||||
|
<StackPanel Grid.Column="0">
|
||||||
|
<TextBlock Text="📁 数据位置选择 (DataLocationPromptWindow)"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
FontSize="14" />
|
||||||
|
<TextBlock Text="选择数据保存位置"
|
||||||
|
FontSize="11"
|
||||||
|
Opacity="0.6"
|
||||||
|
Margin="0,3,0,0" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Column="1" Spacing="8">
|
||||||
|
<ToggleSwitch Content="启用功能"
|
||||||
|
IsChecked="{Binding IsDataLocationEnabled}"
|
||||||
|
OnContent="功能"
|
||||||
|
OffContent="查看" />
|
||||||
|
<Button Content="打开"
|
||||||
|
Command="{Binding OpenDataLocationCommand}"
|
||||||
|
HorizontalAlignment="Right" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ public partial class DevDebugWindow : Window
|
|||||||
_viewModel.OpenErrorRequested += OnOpenErrorRequested;
|
_viewModel.OpenErrorRequested += OnOpenErrorRequested;
|
||||||
_viewModel.OpenUpdateRequested += OnOpenUpdateRequested;
|
_viewModel.OpenUpdateRequested += OnOpenUpdateRequested;
|
||||||
_viewModel.OpenOobeRequested += OnOpenOobeRequested;
|
_viewModel.OpenOobeRequested += OnOpenOobeRequested;
|
||||||
|
_viewModel.OpenDataLocationRequested += OnOpenDataLocationRequested;
|
||||||
_viewModel.CloseRequested += OnCloseRequested;
|
_viewModel.CloseRequested += OnCloseRequested;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,6 +136,17 @@ public partial class DevDebugWindow : Window
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 打开数据位置选择页面
|
||||||
|
/// </summary>
|
||||||
|
private void OnOpenDataLocationRequested(object? sender, DataLocationOpenEventArgs e)
|
||||||
|
{
|
||||||
|
var appRoot = AppDomain.CurrentDomain.BaseDirectory;
|
||||||
|
var resolver = new DataLocationResolver(appRoot);
|
||||||
|
var window = new DataLocationPromptWindow(resolver);
|
||||||
|
window.Show();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 关闭窗口
|
/// 关闭窗口
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -4,13 +4,14 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:views="clr-namespace:LanMountainDesktop.Launcher.Views"
|
xmlns:views="clr-namespace:LanMountainDesktop.Launcher.Views"
|
||||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||||
|
xmlns:fi="using:FluentIcons.Avalonia"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignWidth="600"
|
d:DesignWidth="700"
|
||||||
d:DesignHeight="500"
|
d:DesignHeight="500"
|
||||||
x:Class="LanMountainDesktop.Launcher.Views.OobeWindow"
|
x:Class="LanMountainDesktop.Launcher.Views.OobeWindow"
|
||||||
x:DataType="views:OobeWindow"
|
x:DataType="views:OobeWindow"
|
||||||
Title="欢迎使用阑山桌面"
|
Title="欢迎使用阑山桌面"
|
||||||
Width="600"
|
Width="700"
|
||||||
Height="500"
|
Height="500"
|
||||||
CanResize="False"
|
CanResize="False"
|
||||||
WindowStartupLocation="CenterScreen"
|
WindowStartupLocation="CenterScreen"
|
||||||
@@ -21,59 +22,600 @@
|
|||||||
<views:OobeWindow />
|
<views:OobeWindow />
|
||||||
</Design.DataContext>
|
</Design.DataContext>
|
||||||
|
|
||||||
<Grid x:Name="ContentGrid"
|
<Grid x:Name="ContentGrid">
|
||||||
Opacity="0">
|
|
||||||
<Grid.RenderTransform>
|
<!-- 步骤 1: 打字机动画开场 -->
|
||||||
<TranslateTransform Y="24" />
|
<Grid x:Name="TypingStep" Margin="60,80,60,60">
|
||||||
</Grid.RenderTransform>
|
<!-- 主标题区域(左上角) -->
|
||||||
<!-- 主内容区域 -->
|
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Spacing="16">
|
||||||
<Grid Margin="48" RowDefinitions="*,Auto">
|
<!-- 打字机文本区域 -->
|
||||||
<!-- 中央内容区域 -->
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock x:Name="TypingTextBlock"
|
||||||
|
FontSize="28"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||||
|
FontFamily="Consolas, Monaco, 'Courier New', monospace" />
|
||||||
|
<Border x:Name="CursorBorder"
|
||||||
|
Width="3"
|
||||||
|
Height="28"
|
||||||
|
Background="{DynamicResource TextFillColorPrimaryBrush}"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Margin="4,0,0,4">
|
||||||
|
<Border.Styles>
|
||||||
|
<Style Selector="Border">
|
||||||
|
<Style.Animations>
|
||||||
|
<Animation Duration="0:0:0.8" IterationCount="INFINITE">
|
||||||
|
<KeyFrame Cue="0%">
|
||||||
|
<Setter Property="Opacity" Value="1" />
|
||||||
|
</KeyFrame>
|
||||||
|
<KeyFrame Cue="50%">
|
||||||
|
<Setter Property="Opacity" Value="1" />
|
||||||
|
</KeyFrame>
|
||||||
|
<KeyFrame Cue="51%">
|
||||||
|
<Setter Property="Opacity" Value="0" />
|
||||||
|
</KeyFrame>
|
||||||
|
<KeyFrame Cue="100%">
|
||||||
|
<Setter Property="Opacity" Value="0" />
|
||||||
|
</KeyFrame>
|
||||||
|
</Animation>
|
||||||
|
</Style.Animations>
|
||||||
|
</Style>
|
||||||
|
</Border.Styles>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- 副标题区域(流光渐变动画 + 打字机效果) -->
|
||||||
|
<StackPanel x:Name="SubtitlePanel" Opacity="0" IsVisible="False" Spacing="4">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock x:Name="NextGenTextBlock"
|
||||||
|
FontSize="48"
|
||||||
|
FontWeight="Bold"
|
||||||
|
FontFamily="Consolas, Monaco, 'Courier New', monospace">
|
||||||
|
<TextBlock.Foreground>
|
||||||
|
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
|
||||||
|
<GradientStop Offset="0.0" Color="#0078D4" />
|
||||||
|
<GradientStop Offset="0.33" Color="#7B68EE" />
|
||||||
|
<GradientStop Offset="0.66" Color="#FF8C00" />
|
||||||
|
<GradientStop Offset="1.0" Color="#107C10" />
|
||||||
|
</LinearGradientBrush>
|
||||||
|
</TextBlock.Foreground>
|
||||||
|
</TextBlock> <Border x:Name="SubtitleCursorBorder"
|
||||||
|
Width="4"
|
||||||
|
Height="48"
|
||||||
|
Background="{DynamicResource TextFillColorPrimaryBrush}"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Margin="4,0,0,4"
|
||||||
|
IsVisible="False">
|
||||||
|
<Border.Styles>
|
||||||
|
<Style Selector="Border">
|
||||||
|
<Style.Animations>
|
||||||
|
<Animation Duration="0:0:0.8" IterationCount="INFINITE">
|
||||||
|
<KeyFrame Cue="0%">
|
||||||
|
<Setter Property="Opacity" Value="1" />
|
||||||
|
</KeyFrame>
|
||||||
|
<KeyFrame Cue="50%">
|
||||||
|
<Setter Property="Opacity" Value="1" />
|
||||||
|
</KeyFrame>
|
||||||
|
<KeyFrame Cue="51%">
|
||||||
|
<Setter Property="Opacity" Value="0" />
|
||||||
|
</KeyFrame>
|
||||||
|
<KeyFrame Cue="100%">
|
||||||
|
<Setter Property="Opacity" Value="0" />
|
||||||
|
</KeyFrame>
|
||||||
|
</Animation>
|
||||||
|
</Style.Animations>
|
||||||
|
</Style>
|
||||||
|
</Border.Styles>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
<TextBlock x:Name="DashboardTextBlock"
|
||||||
|
FontSize="48"
|
||||||
|
FontWeight="Bold"
|
||||||
|
FontFamily="Consolas, Monaco, 'Courier New', monospace"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- 按钮动画区域(左下角) -->
|
||||||
|
<Grid x:Name="ButtonAnimationArea"
|
||||||
|
Width="280"
|
||||||
|
Height="80"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Margin="0,0,0,40"
|
||||||
|
IsVisible="False">
|
||||||
|
|
||||||
|
<!-- 方框边框(由鼠标画出) -->
|
||||||
|
<Border x:Name="DrawnBorder"
|
||||||
|
Width="160"
|
||||||
|
Height="56"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Background="Transparent"
|
||||||
|
BorderBrush="{DynamicResource AccentFillColorDefaultBrush}"
|
||||||
|
BorderThickness="0"
|
||||||
|
CornerRadius="{DynamicResource DesignCornerRadiusMd}">
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 开始按钮(从方框中弹出) -->
|
||||||
|
<Button x:Name="StartButton"
|
||||||
|
Width="160"
|
||||||
|
Height="56"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Theme="{DynamicResource AccentButtonTheme}"
|
||||||
|
Opacity="0"
|
||||||
|
IsVisible="False"
|
||||||
|
RenderTransformOrigin="0.5,0.5">
|
||||||
|
<Button.RenderTransform>
|
||||||
|
<ScaleTransform ScaleX="0.1" ScaleY="0.1" />
|
||||||
|
</Button.RenderTransform>
|
||||||
|
<TextBlock Text="开始使用"
|
||||||
|
FontSize="16"
|
||||||
|
FontWeight="SemiBold" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- 鼠标光标 -->
|
||||||
|
<Canvas x:Name="MouseCursor"
|
||||||
|
Width="24"
|
||||||
|
Height="24"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Margin="-50,-50,0,0"
|
||||||
|
IsVisible="False">
|
||||||
|
<Path Data="M0,0 L0,18 L4,14 L7,20 L10,19 L7,13 L12,13 Z"
|
||||||
|
Fill="{DynamicResource TextFillColorPrimaryBrush}"
|
||||||
|
Stroke="{DynamicResource SolidBackgroundFillColorBaseBrush}"
|
||||||
|
StrokeThickness="1" />
|
||||||
|
</Canvas>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- 步骤 2: 主题选择页面 -->
|
||||||
|
<Grid x:Name="ThemeStep" Margin="48" RowDefinitions="Auto,*,Auto" IsVisible="False">
|
||||||
|
<StackPanel Grid.Row="0" Spacing="8" Margin="0,0,0,24">
|
||||||
|
<TextBlock Text="个性化你的桌面"
|
||||||
|
FontSize="24"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||||
|
<TextBlock Text="选择你喜欢的主题样式,可随时在设置中更改"
|
||||||
|
FontSize="13"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
|
||||||
|
<StackPanel Spacing="20">
|
||||||
|
<!-- 浅色/深色模式选择 -->
|
||||||
|
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
|
||||||
|
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
|
||||||
|
Padding="16">
|
||||||
|
<StackPanel Spacing="12">
|
||||||
|
<TextBlock Text="外观模式"
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||||
|
<Grid ColumnDefinitions="*,*" ColumnSpacing="12">
|
||||||
|
<Border x:Name="LightModeOption"
|
||||||
|
Grid.Column="0"
|
||||||
|
Background="{DynamicResource SolidBackgroundFillColorQuarternaryBrush}"
|
||||||
|
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
|
||||||
|
BorderThickness="2"
|
||||||
|
BorderBrush="{DynamicResource AccentFillColorDefaultBrush}"
|
||||||
|
Padding="16"
|
||||||
|
Cursor="Hand">
|
||||||
|
<StackPanel Spacing="8" HorizontalAlignment="Center">
|
||||||
|
<Border Width="48"
|
||||||
|
Height="48"
|
||||||
|
Background="#F3F3F3"
|
||||||
|
CornerRadius="8"
|
||||||
|
BorderBrush="#E0E0E0"
|
||||||
|
BorderThickness="1">
|
||||||
|
<fi:SymbolIcon Symbol="WeatherSunny"
|
||||||
|
FontSize="24"
|
||||||
|
Foreground="#5F5F5F"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
<TextBlock Text="浅色模式"
|
||||||
|
FontSize="13"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||||
|
<RadioButton x:Name="LightModeRadio"
|
||||||
|
GroupName="ThemeMode"
|
||||||
|
IsChecked="True"
|
||||||
|
HorizontalAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Border x:Name="DarkModeOption"
|
||||||
|
Grid.Column="1"
|
||||||
|
Background="{DynamicResource SolidBackgroundFillColorQuarternaryBrush}"
|
||||||
|
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
|
||||||
|
BorderThickness="1"
|
||||||
|
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
|
||||||
|
Padding="16"
|
||||||
|
Cursor="Hand">
|
||||||
|
<StackPanel Spacing="8" HorizontalAlignment="Center">
|
||||||
|
<Border Width="48"
|
||||||
|
Height="48"
|
||||||
|
Background="#1E1E1E"
|
||||||
|
CornerRadius="8"
|
||||||
|
BorderBrush="#333333"
|
||||||
|
BorderThickness="1">
|
||||||
|
<fi:SymbolIcon Symbol="WeatherMoon"
|
||||||
|
FontSize="24"
|
||||||
|
Foreground="#E0E0E0"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
<TextBlock Text="深色模式"
|
||||||
|
FontSize="13"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||||
|
<RadioButton x:Name="DarkModeRadio"
|
||||||
|
GroupName="ThemeMode"
|
||||||
|
HorizontalAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 主题色选择 -->
|
||||||
|
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
|
||||||
|
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
|
||||||
|
Padding="16">
|
||||||
|
<StackPanel Spacing="12">
|
||||||
|
<TextBlock Text="主题色"
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||||
|
<WrapPanel x:Name="AccentColorPanel" HorizontalAlignment="Left">
|
||||||
|
<!-- 预设颜色 -->
|
||||||
|
<Border x:Name="BlueColor"
|
||||||
|
Width="40"
|
||||||
|
Height="40"
|
||||||
|
Background="#0078D4"
|
||||||
|
CornerRadius="20"
|
||||||
|
BorderThickness="3"
|
||||||
|
BorderBrush="{DynamicResource AccentFillColorDefaultBrush}"
|
||||||
|
Margin="0,0,12,12"
|
||||||
|
Cursor="Hand">
|
||||||
|
<Border.Styles>
|
||||||
|
<Style Selector="Border:pointerover">
|
||||||
|
<Setter Property="RenderTransform" Value="scale(1.1)" />
|
||||||
|
</Style>
|
||||||
|
</Border.Styles>
|
||||||
|
</Border>
|
||||||
|
<Border x:Name="PurpleColor"
|
||||||
|
Width="40"
|
||||||
|
Height="40"
|
||||||
|
Background="#7B68EE"
|
||||||
|
CornerRadius="20"
|
||||||
|
BorderThickness="0"
|
||||||
|
Margin="0,0,12,12"
|
||||||
|
Cursor="Hand" />
|
||||||
|
<Border x:Name="GreenColor"
|
||||||
|
Width="40"
|
||||||
|
Height="40"
|
||||||
|
Background="#107C10"
|
||||||
|
CornerRadius="20"
|
||||||
|
BorderThickness="0"
|
||||||
|
Margin="0,0,12,12"
|
||||||
|
Cursor="Hand" />
|
||||||
|
<Border x:Name="OrangeColor"
|
||||||
|
Width="40"
|
||||||
|
Height="40"
|
||||||
|
Background="#D83B01"
|
||||||
|
CornerRadius="20"
|
||||||
|
BorderThickness="0"
|
||||||
|
Margin="0,0,12,12"
|
||||||
|
Cursor="Hand" />
|
||||||
|
<Border x:Name="PinkColor"
|
||||||
|
Width="40"
|
||||||
|
Height="40"
|
||||||
|
Background="#E3008C"
|
||||||
|
CornerRadius="20"
|
||||||
|
BorderThickness="0"
|
||||||
|
Margin="0,0,12,12"
|
||||||
|
Cursor="Hand" />
|
||||||
|
<Border x:Name="TealColor"
|
||||||
|
Width="40"
|
||||||
|
Height="40"
|
||||||
|
Background="#008080"
|
||||||
|
CornerRadius="20"
|
||||||
|
BorderThickness="0"
|
||||||
|
Margin="0,0,12,12"
|
||||||
|
Cursor="Hand" />
|
||||||
|
</WrapPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 莫奈取色来源 -->
|
||||||
|
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
|
||||||
|
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
|
||||||
|
Padding="16">
|
||||||
|
<StackPanel Spacing="12">
|
||||||
|
<TextBlock Text="莫奈取色来源"
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||||
|
<TextBlock Text="从壁纸自动提取主题色,让界面与桌面完美融合"
|
||||||
|
FontSize="12"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||||
|
|
||||||
|
<StackPanel Spacing="8">
|
||||||
|
<Border x:Name="MonetFromWallpaperOption"
|
||||||
|
Background="{DynamicResource SolidBackgroundFillColorQuarternaryBrush}"
|
||||||
|
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
|
||||||
|
BorderThickness="2"
|
||||||
|
BorderBrush="{DynamicResource AccentFillColorDefaultBrush}"
|
||||||
|
Padding="12"
|
||||||
|
Cursor="Hand">
|
||||||
|
<Grid ColumnDefinitions="Auto,*">
|
||||||
|
<RadioButton x:Name="MonetFromWallpaperRadio"
|
||||||
|
Grid.Column="0"
|
||||||
|
GroupName="MonetSource"
|
||||||
|
IsChecked="True"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="0,0,12,0" />
|
||||||
|
<StackPanel Grid.Column="1" Spacing="4">
|
||||||
|
<TextBlock Text="从桌面壁纸取色"
|
||||||
|
FontSize="13"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||||
|
<TextBlock Text="自动分析当前壁纸颜色生成主题"
|
||||||
|
FontSize="11"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Border x:Name="MonetFromCustomOption"
|
||||||
|
Background="{DynamicResource SolidBackgroundFillColorQuarternaryBrush}"
|
||||||
|
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
|
||||||
|
BorderThickness="1"
|
||||||
|
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
|
||||||
|
Padding="12"
|
||||||
|
Cursor="Hand">
|
||||||
|
<Grid ColumnDefinitions="Auto,*">
|
||||||
|
<RadioButton x:Name="MonetFromCustomRadio"
|
||||||
|
Grid.Column="0"
|
||||||
|
GroupName="MonetSource"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="0,0,12,0" />
|
||||||
|
<StackPanel Grid.Column="1" Spacing="4">
|
||||||
|
<TextBlock Text="自定义图片取色"
|
||||||
|
FontSize="13"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||||
|
<TextBlock Text="选择一张图片作为取色来源"
|
||||||
|
FontSize="11"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Border x:Name="MonetDisabledOption"
|
||||||
|
Background="{DynamicResource SolidBackgroundFillColorQuarternaryBrush}"
|
||||||
|
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
|
||||||
|
BorderThickness="1"
|
||||||
|
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
|
||||||
|
Padding="12"
|
||||||
|
Cursor="Hand">
|
||||||
|
<Grid ColumnDefinitions="Auto,*">
|
||||||
|
<RadioButton x:Name="MonetDisabledRadio"
|
||||||
|
Grid.Column="0"
|
||||||
|
GroupName="MonetSource"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="0,0,12,0" />
|
||||||
|
<StackPanel Grid.Column="1" Spacing="4">
|
||||||
|
<TextBlock Text="不使用莫奈取色"
|
||||||
|
FontSize="13"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||||
|
<TextBlock Text="使用固定的预设主题色"
|
||||||
|
FontSize="11"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="2"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Spacing="12"
|
||||||
|
Margin="0,24,0,0">
|
||||||
|
<Button x:Name="ThemeBackButton"
|
||||||
|
Content="返回"
|
||||||
|
Theme="{DynamicResource ButtonTheme}" />
|
||||||
|
<Button x:Name="ThemeNextButton"
|
||||||
|
Content="下一步"
|
||||||
|
Theme="{DynamicResource AccentButtonTheme}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- 步骤 3: 数据位置选择页面 -->
|
||||||
|
<Grid x:Name="DataLocationStep" Margin="48" RowDefinitions="Auto,*,Auto" IsVisible="False">
|
||||||
|
<StackPanel Grid.Row="0" Spacing="8" Margin="0,0,0,24">
|
||||||
|
<TextBlock Text="选择数据保存位置"
|
||||||
|
FontSize="24"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||||
|
<TextBlock Text="决定将应用数据保存在哪里,可随时在设置中更改"
|
||||||
|
FontSize="13"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="1" Spacing="16">
|
||||||
|
<Border x:Name="AdminWarningBanner"
|
||||||
|
Background="{DynamicResource SystemFillColorCriticalBackgroundBrush}"
|
||||||
|
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
|
||||||
|
Padding="12,10"
|
||||||
|
IsVisible="False">
|
||||||
|
<StackPanel Spacing="4">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||||
|
<PathIcon Data="M12,2 L1,21 L23,21 Z M11,9 L13,9 L13,15 L11,15 Z M11,17 L13,17 L13,19 L11,19 Z"
|
||||||
|
Width="16"
|
||||||
|
Height="16"
|
||||||
|
Foreground="{DynamicResource SystemFillColorCriticalBrush}" />
|
||||||
|
<TextBlock Text="无法保存到应用目录"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
FontSize="13"
|
||||||
|
Foreground="{DynamicResource SystemFillColorCriticalBrush}" />
|
||||||
|
</StackPanel>
|
||||||
|
<TextBlock Text="当前安装目录需要管理员权限才能写入,数据将自动保存到系统用户目录。"
|
||||||
|
FontSize="12"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Foreground="{DynamicResource SystemFillColorCriticalBrush}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Border x:Name="SystemOptionBorder"
|
||||||
|
Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
|
||||||
|
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
|
||||||
|
BorderThickness="2"
|
||||||
|
BorderBrush="{DynamicResource AccentFillColorDefaultBrush}"
|
||||||
|
Padding="16,14"
|
||||||
|
Cursor="Hand">
|
||||||
|
<Grid ColumnDefinitions="Auto,*">
|
||||||
|
<RadioButton x:Name="SystemRadio"
|
||||||
|
Grid.Column="0"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Margin="0,2,12,0"
|
||||||
|
GroupName="DataLocation"
|
||||||
|
IsChecked="True" />
|
||||||
|
<StackPanel Grid.Column="1" Spacing="4">
|
||||||
|
<TextBlock Text="保存在系统用户目录(推荐)"
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||||
|
<TextBlock Text="数据与当前 Windows 用户绑定,重装应用或更新后数据不会丢失"
|
||||||
|
FontSize="12"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||||
|
<TextBlock x:Name="SystemPathText"
|
||||||
|
FontSize="11"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
|
||||||
|
Margin="0,4,0,0" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Border x:Name="PortableOptionBorder"
|
||||||
|
Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
|
||||||
|
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
|
||||||
|
BorderThickness="1"
|
||||||
|
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
|
||||||
|
Padding="16,14"
|
||||||
|
Cursor="Hand">
|
||||||
|
<Grid ColumnDefinitions="Auto,*">
|
||||||
|
<RadioButton x:Name="PortableRadio"
|
||||||
|
Grid.Column="0"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Margin="0,2,12,0"
|
||||||
|
GroupName="DataLocation"
|
||||||
|
IsEnabled="False" />
|
||||||
|
<StackPanel Grid.Column="1" Spacing="4">
|
||||||
|
<TextBlock Text="保存在应用安装目录"
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||||
|
<TextBlock Text="便于携带,可随应用文件夹整体移动到其他电脑"
|
||||||
|
FontSize="12"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||||
|
<TextBlock x:Name="PortablePathText"
|
||||||
|
FontSize="11"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
|
||||||
|
Margin="0,4,0,0" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Border x:Name="MigrationInfoBorder"
|
||||||
|
Background="{DynamicResource SystemFillColorSuccessBackgroundBrush}"
|
||||||
|
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
|
||||||
|
Padding="12,10"
|
||||||
|
IsVisible="False">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||||
|
<PathIcon Data="M9,16.17 L4.83,12 L3.41,13.41 L9,19 L21,7 L19.59,5.59 Z"
|
||||||
|
Width="16"
|
||||||
|
Height="16"
|
||||||
|
Foreground="{DynamicResource SystemFillColorSuccessBrush}" />
|
||||||
|
<TextBlock x:Name="MigrationInfoText"
|
||||||
|
FontSize="12"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Foreground="{DynamicResource SystemFillColorSuccessBrush}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="2"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Spacing="12"
|
||||||
|
Margin="0,24,0,0">
|
||||||
|
<Button x:Name="DataLocationBackButton"
|
||||||
|
Content="返回"
|
||||||
|
Theme="{DynamicResource ButtonTheme}" />
|
||||||
|
<Button x:Name="DataLocationNextButton"
|
||||||
|
Content="下一步"
|
||||||
|
Theme="{DynamicResource AccentButtonTheme}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- 步骤 4: 欢迎完成页面 -->
|
||||||
|
<Grid x:Name="WelcomeStep" Margin="48" RowDefinitions="*,Auto" IsVisible="False">
|
||||||
<StackPanel Grid.Row="0"
|
<StackPanel Grid.Row="0"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
Spacing="24">
|
Spacing="32">
|
||||||
|
|
||||||
<!-- 顶部:完成状态勾号图标 -->
|
<Border Width="96"
|
||||||
<Border Width="80"
|
Height="96"
|
||||||
Height="80"
|
|
||||||
Background="{DynamicResource SystemFillColorSuccessBackgroundBrush}"
|
Background="{DynamicResource SystemFillColorSuccessBackgroundBrush}"
|
||||||
CornerRadius="40"
|
CornerRadius="48"
|
||||||
HorizontalAlignment="Center">
|
HorizontalAlignment="Center">
|
||||||
<ui:SymbolIcon Symbol="Accept"
|
<PathIcon Data="M9,16.17 L4.83,12 L3.41,13.41 L9,19 L21,7 L19.59,5.59 Z"
|
||||||
FontSize="40"
|
Width="48"
|
||||||
|
Height="48"
|
||||||
Foreground="{DynamicResource SystemFillColorSuccessBrush}"
|
Foreground="{DynamicResource SystemFillColorSuccessBrush}"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- 中央:欢迎文字 -->
|
<StackPanel Spacing="12" HorizontalAlignment="Center">
|
||||||
<StackPanel Spacing="8" HorizontalAlignment="Center">
|
|
||||||
<TextBlock Text="欢迎使用阑山桌面"
|
<TextBlock Text="欢迎使用阑山桌面"
|
||||||
FontSize="28"
|
FontSize="32"
|
||||||
FontWeight="SemiBold"
|
FontWeight="SemiBold"
|
||||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||||
HorizontalAlignment="Center" />
|
HorizontalAlignment="Center" />
|
||||||
<TextBlock Text="你的桌面,不止一面"
|
<TextBlock Text="你的桌面,不止一面"
|
||||||
FontSize="14"
|
FontSize="16"
|
||||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||||
HorizontalAlignment="Center" />
|
HorizontalAlignment="Center" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- 底部:圆形开始按钮 -->
|
|
||||||
<Button Grid.Row="1"
|
<Button Grid.Row="1"
|
||||||
x:Name="EnterButton"
|
x:Name="EnterButton"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
Width="56"
|
Width="56"
|
||||||
Height="56"
|
Height="56"
|
||||||
Margin="0,0,0,16"
|
Margin="0,0,0,24"
|
||||||
Theme="{DynamicResource AccentButtonTheme}"
|
Theme="{DynamicResource AccentButtonTheme}"
|
||||||
CornerRadius="28">
|
CornerRadius="28">
|
||||||
<ui:SymbolIcon Symbol="Forward"
|
<fi:SymbolIcon Symbol="ArrowRight"
|
||||||
FontSize="24"
|
FontSize="24"
|
||||||
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"/>
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}"/>
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -1,182 +1,709 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Animation;
|
using Avalonia.Animation;
|
||||||
using Avalonia.Animation.Easings;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.Styling;
|
using Avalonia.Threading;
|
||||||
|
using LanMountainDesktop.Launcher.Models;
|
||||||
|
using LanMountainDesktop.Launcher.Services;
|
||||||
|
|
||||||
namespace LanMountainDesktop.Launcher.Views;
|
namespace LanMountainDesktop.Launcher.Views;
|
||||||
|
|
||||||
public partial class OobeWindow : Window
|
public partial class OobeWindow : Window
|
||||||
{
|
{
|
||||||
|
private const int AnimationDurationMs = 300;
|
||||||
|
private const int TypingDelayMs = 100;
|
||||||
|
|
||||||
private readonly TaskCompletionSource<bool> _completionSource = new();
|
private readonly TaskCompletionSource<bool> _completionSource = new();
|
||||||
|
private readonly DataLocationResolver _resolver;
|
||||||
private bool _isTransitioning;
|
private bool _isTransitioning;
|
||||||
|
private bool _isDebugMode;
|
||||||
|
private int _currentStep = 1;
|
||||||
|
|
||||||
|
// 数据位置选择
|
||||||
|
private DataLocationMode _selectedDataLocationMode = DataLocationMode.System;
|
||||||
|
private bool _migrateExistingData;
|
||||||
|
|
||||||
|
// 主题选择
|
||||||
|
private Services.ThemeMode _selectedThemeMode = Services.ThemeMode.Light;
|
||||||
|
private string _selectedAccentColor = "#0078D4";
|
||||||
|
private MonetSource _selectedMonetSource = MonetSource.Wallpaper;
|
||||||
|
|
||||||
public OobeWindow()
|
public OobeWindow()
|
||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
Loaded += OnWindowLoaded;
|
Loaded += OnWindowLoaded;
|
||||||
Opened += OnWindowOpened;
|
Opened += OnWindowOpened;
|
||||||
|
|
||||||
|
var appRoot = AppDomain.CurrentDomain.BaseDirectory;
|
||||||
|
_resolver = new DataLocationResolver(appRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetDebugMode(bool isDebugMode)
|
||||||
|
{
|
||||||
|
_isDebugMode = isDebugMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task WaitForEnterAsync() => _completionSource.Task;
|
||||||
|
|
||||||
private void OnWindowLoaded(object? sender, RoutedEventArgs e)
|
private void OnWindowLoaded(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
Console.WriteLine("[OobeWindow] Window loaded, initializing components...");
|
InitializeDataLocationStep();
|
||||||
|
SetupEventHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
var enterButton = this.FindControl<Button>("EnterButton");
|
private void SetupEventHandlers()
|
||||||
if (enterButton is not null)
|
{
|
||||||
|
// 步骤 1: 开始按钮
|
||||||
|
if (this.FindControl<Button>("StartButton") is { } startButton)
|
||||||
|
{
|
||||||
|
startButton.Click += OnStartButtonClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 步骤 2: 主题选择页面
|
||||||
|
if (this.FindControl<Button>("ThemeBackButton") is { } themeBackButton)
|
||||||
|
{
|
||||||
|
themeBackButton.Click += OnThemeBackClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.FindControl<Button>("ThemeNextButton") is { } themeNextButton)
|
||||||
|
{
|
||||||
|
themeNextButton.Click += OnThemeNextClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 浅色/深色模式选择
|
||||||
|
if (this.FindControl<Border>("LightModeOption") is { } lightModeOption)
|
||||||
|
{
|
||||||
|
lightModeOption.PointerPressed += (s, e) => SelectThemeMode(Services.ThemeMode.Light);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.FindControl<Border>("DarkModeOption") is { } darkModeOption)
|
||||||
|
{
|
||||||
|
darkModeOption.PointerPressed += (s, e) => SelectThemeMode(Services.ThemeMode.Dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.FindControl<RadioButton>("LightModeRadio") is { } lightModeRadio)
|
||||||
|
{
|
||||||
|
lightModeRadio.IsCheckedChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
if (lightModeRadio.IsChecked == true) SelectThemeMode(Services.ThemeMode.Light);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.FindControl<RadioButton>("DarkModeRadio") is { } darkModeRadio)
|
||||||
|
{
|
||||||
|
darkModeRadio.IsCheckedChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
if (darkModeRadio.IsChecked == true) SelectThemeMode(Services.ThemeMode.Dark);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主题色选择
|
||||||
|
SetupAccentColorHandlers();
|
||||||
|
|
||||||
|
// 莫奈取色来源选择
|
||||||
|
if (this.FindControl<Border>("MonetFromWallpaperOption") is { } monetWallpaperOption)
|
||||||
|
{
|
||||||
|
monetWallpaperOption.PointerPressed += (s, e) => SelectMonetSource(MonetSource.Wallpaper);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.FindControl<Border>("MonetFromCustomOption") is { } monetCustomOption)
|
||||||
|
{
|
||||||
|
monetCustomOption.PointerPressed += (s, e) => SelectMonetSource(MonetSource.Custom);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.FindControl<Border>("MonetDisabledOption") is { } monetDisabledOption)
|
||||||
|
{
|
||||||
|
monetDisabledOption.PointerPressed += (s, e) => SelectMonetSource(MonetSource.Disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.FindControl<RadioButton>("MonetFromWallpaperRadio") is { } monetWallpaperRadio)
|
||||||
|
{
|
||||||
|
monetWallpaperRadio.IsCheckedChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
if (monetWallpaperRadio.IsChecked == true) SelectMonetSource(MonetSource.Wallpaper);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.FindControl<RadioButton>("MonetFromCustomRadio") is { } monetCustomRadio)
|
||||||
|
{
|
||||||
|
monetCustomRadio.IsCheckedChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
if (monetCustomRadio.IsChecked == true) SelectMonetSource(MonetSource.Custom);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.FindControl<RadioButton>("MonetDisabledRadio") is { } monetDisabledRadio)
|
||||||
|
{
|
||||||
|
monetDisabledRadio.IsCheckedChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
if (monetDisabledRadio.IsChecked == true) SelectMonetSource(MonetSource.Disabled);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 步骤 3: 数据位置选择页面
|
||||||
|
if (this.FindControl<Button>("DataLocationBackButton") is { } dataLocationBackButton)
|
||||||
|
{
|
||||||
|
dataLocationBackButton.Click += OnDataLocationBackClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.FindControl<Button>("DataLocationNextButton") is { } dataLocationNextButton)
|
||||||
|
{
|
||||||
|
dataLocationNextButton.Click += OnDataLocationNextClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.FindControl<Border>("SystemOptionBorder") is { } systemOption)
|
||||||
|
{
|
||||||
|
systemOption.PointerPressed += (s, e) => SelectDataLocationMode(DataLocationMode.System);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.FindControl<Border>("PortableOptionBorder") is { } portableOption)
|
||||||
|
{
|
||||||
|
portableOption.PointerPressed += (s, e) => SelectDataLocationMode(DataLocationMode.Portable);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.FindControl<RadioButton>("SystemRadio") is { } systemRadio)
|
||||||
|
{
|
||||||
|
systemRadio.IsCheckedChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
if (systemRadio.IsChecked == true) SelectDataLocationMode(DataLocationMode.System);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.FindControl<RadioButton>("PortableRadio") is { } portableRadio)
|
||||||
|
{
|
||||||
|
portableRadio.IsCheckedChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
if (portableRadio.IsChecked == true) SelectDataLocationMode(DataLocationMode.Portable);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 步骤 4: 欢迎完成页面
|
||||||
|
if (this.FindControl<Button>("EnterButton") is { } enterButton)
|
||||||
{
|
{
|
||||||
enterButton.Click += OnEnterClick;
|
enterButton.Click += OnEnterClick;
|
||||||
Console.WriteLine("[OobeWindow] EnterButton event bound successfully");
|
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
|
|
||||||
|
private void SetupAccentColorHandlers()
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine("[OobeWindow] Failed to find EnterButton!");
|
var colorMap = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "BlueColor", "#0078D4" },
|
||||||
|
{ "PurpleColor", "#7B68EE" },
|
||||||
|
{ "GreenColor", "#107C10" },
|
||||||
|
{ "OrangeColor", "#D83B01" },
|
||||||
|
{ "PinkColor", "#E3008C" },
|
||||||
|
{ "TealColor", "#008080" }
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var (name, color) in colorMap)
|
||||||
|
{
|
||||||
|
if (this.FindControl<Border>(name) is { } colorBorder)
|
||||||
|
{
|
||||||
|
colorBorder.PointerPressed += (s, e) => SelectAccentColor(name, color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnWindowOpened(object? sender, EventArgs e)
|
private async void OnWindowOpened(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
Console.WriteLine("[OobeWindow] Window opened, playing entrance animation...");
|
await PlayTypingAnimationAsync();
|
||||||
await PlayEntranceAnimationAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PlayEntranceAnimationAsync()
|
private async Task PlayTypingAnimationAsync()
|
||||||
{
|
{
|
||||||
try
|
var typingTextBlock = this.FindControl<TextBlock>("TypingTextBlock");
|
||||||
|
var cursorBorder = this.FindControl<Border>("CursorBorder");
|
||||||
|
var subtitlePanel = this.FindControl<StackPanel>("SubtitlePanel");
|
||||||
|
var buttonAnimationArea = this.FindControl<Grid>("ButtonAnimationArea");
|
||||||
|
var startButton = this.FindControl<Button>("StartButton");
|
||||||
|
var mouseCursor = this.FindControl<Canvas>("MouseCursor");
|
||||||
|
|
||||||
|
if (typingTextBlock == null || cursorBorder == null) return;
|
||||||
|
|
||||||
|
// 打字机效果:阑山桌面 LanMountain Desktop(在同一行)
|
||||||
|
var fullText = "阑山桌面 LanMountain Desktop";
|
||||||
|
for (int i = 0; i <= fullText.Length; i++)
|
||||||
{
|
{
|
||||||
var contentGrid = this.FindControl<Grid>("ContentGrid");
|
typingTextBlock.Text = fullText.Substring(0, i);
|
||||||
if (contentGrid is null)
|
await Task.Delay(TypingDelayMs);
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var translateTransform = contentGrid.RenderTransform as TranslateTransform ?? new TranslateTransform();
|
// 停顿一下
|
||||||
contentGrid.RenderTransform = translateTransform;
|
await Task.Delay(500);
|
||||||
|
|
||||||
var offset = ResolveEntranceOffset();
|
// 隐藏光标
|
||||||
contentGrid.Opacity = 0;
|
cursorBorder.IsVisible = false;
|
||||||
translateTransform.Y = offset;
|
|
||||||
|
|
||||||
var fadeInAnimation = new Animation
|
// 显示副标题(打字机效果:下一代 互动信息看板)
|
||||||
|
if (subtitlePanel != null)
|
||||||
{
|
{
|
||||||
Duration = TimeSpan.FromMilliseconds(600),
|
subtitlePanel.IsVisible = true;
|
||||||
Easing = new CubicEaseOut(),
|
subtitlePanel.Opacity = 1;
|
||||||
Children =
|
await PlaySubtitleTypingAnimationAsync();
|
||||||
{
|
|
||||||
new KeyFrame
|
|
||||||
{
|
|
||||||
Setters = { new Setter(OpacityProperty, 0.0) },
|
|
||||||
KeyTime = TimeSpan.FromMilliseconds(0)
|
|
||||||
},
|
|
||||||
new KeyFrame
|
|
||||||
{
|
|
||||||
Setters = { new Setter(OpacityProperty, 1.0) },
|
|
||||||
KeyTime = TimeSpan.FromMilliseconds(600)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var slideUpAnimation = new Animation
|
// 停顿一下再显示按钮
|
||||||
{
|
await Task.Delay(400);
|
||||||
Duration = TimeSpan.FromMilliseconds(600),
|
|
||||||
Easing = new CubicEaseOut(),
|
|
||||||
Children =
|
|
||||||
{
|
|
||||||
new KeyFrame
|
|
||||||
{
|
|
||||||
Setters = { new Setter(TranslateTransform.YProperty, offset) },
|
|
||||||
KeyTime = TimeSpan.FromMilliseconds(0)
|
|
||||||
},
|
|
||||||
new KeyFrame
|
|
||||||
{
|
|
||||||
Setters = { new Setter(TranslateTransform.YProperty, 0.0) },
|
|
||||||
KeyTime = TimeSpan.FromMilliseconds(600)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
await Task.WhenAll(
|
// 显示按钮动画区域
|
||||||
fadeInAnimation.RunAsync(contentGrid),
|
if (buttonAnimationArea != null)
|
||||||
slideUpAnimation.RunAsync(translateTransform));
|
|
||||||
|
|
||||||
Console.WriteLine("[OobeWindow] Entrance animation completed");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine($"[OobeWindow] Error playing entrance animation: {ex.Message}");
|
buttonAnimationArea.IsVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 鼠标拖拽按钮入场
|
||||||
|
if (mouseCursor != null && startButton != null)
|
||||||
|
{
|
||||||
|
await AnimateMouseDragButtonAsync(mouseCursor, startButton);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task WaitForEnterAsync() => _completionSource.Task;
|
private async Task AnimateMouseDragButtonAsync(Canvas mouseCursor, Button button)
|
||||||
|
{
|
||||||
|
// 初始处于画面外部的 X 坐标
|
||||||
|
var startX = -400.0;
|
||||||
|
var endX = 0.0;
|
||||||
|
|
||||||
|
button.IsVisible = true;
|
||||||
|
button.Opacity = 1;
|
||||||
|
button.RenderTransform = new TranslateTransform(startX, 0);
|
||||||
|
|
||||||
|
// 鼠标位于按钮上,比如偏移 (100, 30) 的位置
|
||||||
|
var mouseOffsetX = 100.0;
|
||||||
|
var mouseOffsetY = 30.0;
|
||||||
|
mouseCursor.Margin = new Thickness(startX + mouseOffsetX, mouseOffsetY, 0, 0);
|
||||||
|
mouseCursor.IsVisible = true;
|
||||||
|
|
||||||
|
await Task.Delay(300);
|
||||||
|
|
||||||
|
var duration = 800;
|
||||||
|
var steps = 40;
|
||||||
|
var delay = duration / steps;
|
||||||
|
|
||||||
|
for (int i = 0; i <= steps; i++)
|
||||||
|
{
|
||||||
|
var progress = (double)i / steps;
|
||||||
|
var eased = EaseOutBack(progress); // 使用 EaseOutBack 营造“拖拽到位”的清脆回弹感
|
||||||
|
|
||||||
|
var currentX = startX + (endX - startX) * eased;
|
||||||
|
|
||||||
|
button.RenderTransform = new TranslateTransform(currentX, 0);
|
||||||
|
mouseCursor.Margin = new Thickness(currentX + mouseOffsetX, mouseOffsetY, 0, 0);
|
||||||
|
|
||||||
|
await Task.Delay(delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(200);
|
||||||
|
|
||||||
|
// 隐藏鼠标光标
|
||||||
|
await AnimateOpacityAsync(mouseCursor, 1, 0, 200);
|
||||||
|
mouseCursor.IsVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PlaySubtitleTypingAnimationAsync()
|
||||||
|
{
|
||||||
|
var nextGenTextBlock = this.FindControl<TextBlock>("NextGenTextBlock");
|
||||||
|
var dashboardTextBlock = this.FindControl<TextBlock>("DashboardTextBlock");
|
||||||
|
var subtitleCursorBorder = this.FindControl<Border>("SubtitleCursorBorder");
|
||||||
|
|
||||||
|
if (nextGenTextBlock == null || dashboardTextBlock == null) return;
|
||||||
|
|
||||||
|
// 获取渐变画刷
|
||||||
|
var gradientBrush = nextGenTextBlock.Foreground as LinearGradientBrush;
|
||||||
|
|
||||||
|
// 启动渐变色流动动画
|
||||||
|
if (gradientBrush != null)
|
||||||
|
{
|
||||||
|
_ = AnimateGradientFlowAsync(gradientBrush);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示光标
|
||||||
|
if (subtitleCursorBorder != null)
|
||||||
|
{
|
||||||
|
subtitleCursorBorder.IsVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打字机效果:下一代
|
||||||
|
var nextGenText = "下一代";
|
||||||
|
for (int i = 0; i <= nextGenText.Length; i++)
|
||||||
|
{
|
||||||
|
nextGenTextBlock.Text = nextGenText.Substring(0, i);
|
||||||
|
await Task.Delay(TypingDelayMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停顿一下
|
||||||
|
await Task.Delay(200);
|
||||||
|
|
||||||
|
// 换行,光标移到第二行
|
||||||
|
if (subtitleCursorBorder != null)
|
||||||
|
{
|
||||||
|
subtitleCursorBorder.IsVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打字机效果:互动信息看板
|
||||||
|
var dashboardText = "互动信息看板";
|
||||||
|
for (int i = 0; i <= dashboardText.Length; i++)
|
||||||
|
{
|
||||||
|
dashboardTextBlock.Text = dashboardText.Substring(0, i);
|
||||||
|
await Task.Delay(TypingDelayMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停顿一下后隐藏光标
|
||||||
|
await Task.Delay(300);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AnimateGradientFlowAsync(LinearGradientBrush? gradientBrush)
|
||||||
|
{
|
||||||
|
if (gradientBrush == null) return;
|
||||||
|
|
||||||
|
var stops = gradientBrush.GradientStops;
|
||||||
|
if (stops.Count < 2) return;
|
||||||
|
|
||||||
|
// 获取原有的所有颜色
|
||||||
|
var colors = new System.Collections.Generic.List<Color>();
|
||||||
|
foreach (var stop in stops)
|
||||||
|
{
|
||||||
|
colors.Add(stop.Color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为了实现无缝循环流动,把第一个颜色追加到最后
|
||||||
|
colors.Add(colors[0]);
|
||||||
|
|
||||||
|
// 重新分配 GradientStops
|
||||||
|
stops.Clear();
|
||||||
|
for (int i = 0; i < colors.Count; i++)
|
||||||
|
{
|
||||||
|
stops.Add(new GradientStop(colors[i], (double)i / (colors.Count - 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置铺展模式,超出范围时重复
|
||||||
|
gradientBrush.SpreadMethod = GradientSpreadMethod.Repeat;
|
||||||
|
|
||||||
|
double offset = 0;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
offset -= 0.005; // 每次流动一小步,负数表示向右流动
|
||||||
|
if (offset <= -1.0) offset = 0;
|
||||||
|
|
||||||
|
// 让渐变保持水平方向,但位置不断偏移,形成河流般的流动效果
|
||||||
|
gradientBrush.StartPoint = new RelativePoint(offset, 0, RelativeUnit.Relative);
|
||||||
|
gradientBrush.EndPoint = new RelativePoint(offset + 1, 0, RelativeUnit.Relative);
|
||||||
|
|
||||||
|
await Task.Delay(16); // 约60帧
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnStartButtonClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_isTransitioning) return;
|
||||||
|
await NavigateToStep(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主题选择页面按钮
|
||||||
|
private async void OnThemeBackClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_isTransitioning) return;
|
||||||
|
await NavigateToStep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnThemeNextClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_isTransitioning) return;
|
||||||
|
await NavigateToStep(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据位置选择页面按钮
|
||||||
|
private async void OnDataLocationBackClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_isTransitioning) return;
|
||||||
|
await NavigateToStep(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnDataLocationNextClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_isTransitioning) return;
|
||||||
|
|
||||||
|
// 应用数据位置选择
|
||||||
|
if (!_isDebugMode)
|
||||||
|
{
|
||||||
|
_resolver.ApplyLocationChoice(_selectedDataLocationMode, _migrateExistingData);
|
||||||
|
}
|
||||||
|
|
||||||
|
await NavigateToStep(4);
|
||||||
|
}
|
||||||
|
|
||||||
private async void OnEnterClick(object? sender, RoutedEventArgs e)
|
private async void OnEnterClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (_isTransitioning)
|
if (_isTransitioning) return;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_isTransitioning = true;
|
_isTransitioning = true;
|
||||||
Console.WriteLine("[OobeWindow] Enter button clicked, starting transition...");
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await PlayExitAnimationAsync();
|
await PlayExitAnimationAsync();
|
||||||
_completionSource.TrySetResult(true);
|
_completionSource.TrySetResult(true);
|
||||||
|
Close();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine($"[OobeWindow] Error during transition: {ex.Message}");
|
Console.Error.WriteLine($"[OobeWindow] Error: {ex.Message}");
|
||||||
_completionSource.TrySetResult(true);
|
_completionSource.TrySetResult(true);
|
||||||
|
Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void InitializeDataLocationStep()
|
||||||
|
{
|
||||||
|
if (this.FindControl<TextBlock>("SystemPathText") is { } systemPathText)
|
||||||
|
{
|
||||||
|
systemPathText.Text = _resolver.DefaultSystemDataPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.FindControl<TextBlock>("PortablePathText") is { } portablePathText)
|
||||||
|
{
|
||||||
|
portablePathText.Text = _resolver.DefaultPortableDataPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
var canWriteToAppRoot = _resolver.IsPortableModeAllowed();
|
||||||
|
if (this.FindControl<RadioButton>("PortableRadio") is { } portableRadio)
|
||||||
|
{
|
||||||
|
portableRadio.IsEnabled = canWriteToAppRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!canWriteToAppRoot)
|
||||||
|
{
|
||||||
|
if (this.FindControl<Border>("AdminWarningBanner") is { } warningBanner)
|
||||||
|
{
|
||||||
|
warningBanner.IsVisible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_resolver.HasExistingSystemData())
|
||||||
|
{
|
||||||
|
_migrateExistingData = true;
|
||||||
|
if (this.FindControl<Border>("MigrationInfoBorder") is { } migrationInfo)
|
||||||
|
{
|
||||||
|
migrationInfo.IsVisible = true;
|
||||||
|
}
|
||||||
|
if (this.FindControl<TextBlock>("MigrationInfoText") is { } migrationText)
|
||||||
|
{
|
||||||
|
migrationText.Text = "检测到现有数据,选择便携模式时将自动迁移。";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectDataLocationMode(DataLocationMode mode)
|
||||||
|
{
|
||||||
|
_selectedDataLocationMode = mode;
|
||||||
|
|
||||||
|
if (this.FindControl<RadioButton>("SystemRadio") is { } systemRadio)
|
||||||
|
{
|
||||||
|
systemRadio.IsChecked = mode == DataLocationMode.System;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.FindControl<RadioButton>("PortableRadio") is { } portableRadio)
|
||||||
|
{
|
||||||
|
portableRadio.IsChecked = mode == DataLocationMode.Portable;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.FindControl<Border>("SystemOptionBorder") is { } systemBorder)
|
||||||
|
{
|
||||||
|
systemBorder.BorderBrush = mode == DataLocationMode.System
|
||||||
|
? Application.Current?.Resources["AccentFillColorDefaultBrush"] as IBrush
|
||||||
|
: Application.Current?.Resources["CardStrokeColorDefaultBrush"] as IBrush;
|
||||||
|
systemBorder.BorderThickness = mode == DataLocationMode.System
|
||||||
|
? new Thickness(2)
|
||||||
|
: new Thickness(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.FindControl<Border>("PortableOptionBorder") is { } portableBorder)
|
||||||
|
{
|
||||||
|
portableBorder.BorderBrush = mode == DataLocationMode.Portable
|
||||||
|
? Application.Current?.Resources["AccentFillColorDefaultBrush"] as IBrush
|
||||||
|
: Application.Current?.Resources["CardStrokeColorDefaultBrush"] as IBrush;
|
||||||
|
portableBorder.BorderThickness = mode == DataLocationMode.Portable
|
||||||
|
? new Thickness(2)
|
||||||
|
: new Thickness(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主题选择方法
|
||||||
|
private void SelectThemeMode(Services.ThemeMode mode)
|
||||||
|
{
|
||||||
|
_selectedThemeMode = mode;
|
||||||
|
|
||||||
|
// 立即应用主题到启动器
|
||||||
|
ThemeService.ApplyTheme(mode, _selectedAccentColor);
|
||||||
|
|
||||||
|
if (this.FindControl<RadioButton>("LightModeRadio") is { } lightModeRadio)
|
||||||
|
{
|
||||||
|
lightModeRadio.IsChecked = mode == Services.ThemeMode.Light;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.FindControl<RadioButton>("DarkModeRadio") is { } darkModeRadio)
|
||||||
|
{
|
||||||
|
darkModeRadio.IsChecked = mode == Services.ThemeMode.Dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.FindControl<Border>("LightModeOption") is { } lightModeOption)
|
||||||
|
{
|
||||||
|
lightModeOption.BorderBrush = mode == Services.ThemeMode.Light
|
||||||
|
? Application.Current?.Resources["AccentFillColorDefaultBrush"] as IBrush
|
||||||
|
: Application.Current?.Resources["CardStrokeColorDefaultBrush"] as IBrush;
|
||||||
|
lightModeOption.BorderThickness = mode == Services.ThemeMode.Light
|
||||||
|
? new Thickness(2)
|
||||||
|
: new Thickness(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.FindControl<Border>("DarkModeOption") is { } darkModeOption)
|
||||||
|
{
|
||||||
|
darkModeOption.BorderBrush = mode == Services.ThemeMode.Dark
|
||||||
|
? Application.Current?.Resources["AccentFillColorDefaultBrush"] as IBrush
|
||||||
|
: Application.Current?.Resources["CardStrokeColorDefaultBrush"] as IBrush;
|
||||||
|
darkModeOption.BorderThickness = mode == Services.ThemeMode.Dark
|
||||||
|
? new Thickness(2)
|
||||||
|
: new Thickness(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectAccentColor(string colorName, string colorValue)
|
||||||
|
{
|
||||||
|
_selectedAccentColor = colorValue;
|
||||||
|
|
||||||
|
// 更新所有颜色圆圈边框
|
||||||
|
var colorBorders = new[] { "BlueColor", "PurpleColor", "GreenColor", "OrangeColor", "PinkColor", "TealColor" };
|
||||||
|
foreach (var name in colorBorders)
|
||||||
|
{
|
||||||
|
if (this.FindControl<Border>(name) is { } border)
|
||||||
|
{
|
||||||
|
var isSelected = name == colorName;
|
||||||
|
border.BorderBrush = isSelected
|
||||||
|
? Application.Current?.Resources["TextFillColorPrimaryBrush"] as IBrush
|
||||||
|
: null;
|
||||||
|
border.BorderThickness = isSelected ? new Thickness(3) : new Thickness(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectMonetSource(MonetSource source)
|
||||||
|
{
|
||||||
|
_selectedMonetSource = source;
|
||||||
|
|
||||||
|
if (this.FindControl<RadioButton>("MonetFromWallpaperRadio") is { } wallpaperRadio)
|
||||||
|
{
|
||||||
|
wallpaperRadio.IsChecked = source == MonetSource.Wallpaper;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.FindControl<RadioButton>("MonetFromCustomRadio") is { } customRadio)
|
||||||
|
{
|
||||||
|
customRadio.IsChecked = source == MonetSource.Custom;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.FindControl<RadioButton>("MonetDisabledRadio") is { } disabledRadio)
|
||||||
|
{
|
||||||
|
disabledRadio.IsChecked = source == MonetSource.Disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateMonetOptionBorder("MonetFromWallpaperOption", source == MonetSource.Wallpaper);
|
||||||
|
UpdateMonetOptionBorder("MonetFromCustomOption", source == MonetSource.Custom);
|
||||||
|
UpdateMonetOptionBorder("MonetDisabledOption", source == MonetSource.Disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateMonetOptionBorder(string borderName, bool isSelected)
|
||||||
|
{
|
||||||
|
if (this.FindControl<Border>(borderName) is { } border)
|
||||||
|
{
|
||||||
|
border.BorderBrush = isSelected
|
||||||
|
? Application.Current?.Resources["AccentFillColorDefaultBrush"] as IBrush
|
||||||
|
: Application.Current?.Resources["CardStrokeColorDefaultBrush"] as IBrush;
|
||||||
|
border.BorderThickness = isSelected ? new Thickness(2) : new Thickness(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task NavigateToStep(int step)
|
||||||
|
{
|
||||||
|
if (_isTransitioning || step == _currentStep) return;
|
||||||
|
_isTransitioning = true;
|
||||||
|
|
||||||
|
// 获取当前步骤的控件
|
||||||
|
Grid? currentStepControl = _currentStep switch
|
||||||
|
{
|
||||||
|
1 => this.FindControl<Grid>("TypingStep"),
|
||||||
|
2 => this.FindControl<Grid>("ThemeStep"),
|
||||||
|
3 => this.FindControl<Grid>("DataLocationStep"),
|
||||||
|
4 => this.FindControl<Grid>("WelcomeStep"),
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取目标步骤的控件
|
||||||
|
Grid? nextStepControl = step switch
|
||||||
|
{
|
||||||
|
1 => this.FindControl<Grid>("TypingStep"),
|
||||||
|
2 => this.FindControl<Grid>("ThemeStep"),
|
||||||
|
3 => this.FindControl<Grid>("DataLocationStep"),
|
||||||
|
4 => this.FindControl<Grid>("WelcomeStep"),
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (currentStepControl == null || nextStepControl == null)
|
||||||
|
{
|
||||||
|
_isTransitioning = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await AnimateOpacityAsync(currentStepControl, 1, 0, AnimationDurationMs);
|
||||||
|
currentStepControl.IsVisible = false;
|
||||||
|
|
||||||
|
nextStepControl.IsVisible = true;
|
||||||
|
nextStepControl.Opacity = 0;
|
||||||
|
await AnimateOpacityAsync(nextStepControl, 0, 1, AnimationDurationMs);
|
||||||
|
|
||||||
|
_currentStep = step;
|
||||||
|
_isTransitioning = false;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task PlayExitAnimationAsync()
|
private async Task PlayExitAnimationAsync()
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var contentGrid = this.FindControl<Grid>("ContentGrid");
|
var contentGrid = this.FindControl<Grid>("ContentGrid");
|
||||||
if (contentGrid is null)
|
if (contentGrid != null)
|
||||||
{
|
{
|
||||||
await Task.Delay(200);
|
await AnimateOpacityAsync(contentGrid, 1, 0, AnimationDurationMs);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var fadeOutAnimation = new Animation
|
|
||||||
{
|
|
||||||
Duration = TimeSpan.FromMilliseconds(200),
|
|
||||||
Easing = new CubicEaseIn(),
|
|
||||||
Children =
|
|
||||||
{
|
|
||||||
new KeyFrame
|
|
||||||
{
|
|
||||||
Setters = { new Setter(OpacityProperty, 1.0) },
|
|
||||||
KeyTime = TimeSpan.FromMilliseconds(0)
|
|
||||||
},
|
|
||||||
new KeyFrame
|
|
||||||
{
|
|
||||||
Setters = { new Setter(OpacityProperty, 0.0) },
|
|
||||||
KeyTime = TimeSpan.FromMilliseconds(200)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
await fadeOutAnimation.RunAsync(contentGrid);
|
|
||||||
Console.WriteLine("[OobeWindow] Exit animation completed");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine($"[OobeWindow] Error playing exit animation: {ex.Message}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private double ResolveEntranceOffset()
|
private static async Task AnimateOpacityAsync(Control element, double from, double to, int durationMs)
|
||||||
{
|
{
|
||||||
var boundsHeight = Bounds.Height > 0 ? Bounds.Height : Height;
|
var steps = 20;
|
||||||
var scaledOffset = boundsHeight * 0.05;
|
var delay = durationMs / steps;
|
||||||
return Math.Clamp(scaledOffset, 20, 48);
|
|
||||||
|
for (int i = 0; i <= steps; i++)
|
||||||
|
{
|
||||||
|
var progress = (double)i / steps;
|
||||||
|
var eased = EaseOutCubic(progress);
|
||||||
|
element.Opacity = from + (to - from) * eased;
|
||||||
|
await Task.Delay(delay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static double EaseOutCubic(double t) => 1 - Math.Pow(1 - t, 3);
|
||||||
|
private static double EaseOutQuad(double t) => 1 - Math.Pow(1 - t, 2);
|
||||||
|
private static double EaseOutBack(double t)
|
||||||
|
{
|
||||||
|
const double c1 = 1.70158;
|
||||||
|
const double c3 = c1 + 1;
|
||||||
|
var t1 = t - 1;
|
||||||
|
return 1 + c3 * Math.Pow(t1, 3) + c1 * Math.Pow(t1, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 枚举定义(使用 Services 命名空间中的 ThemeMode)
|
||||||
|
public enum MonetSource
|
||||||
|
{
|
||||||
|
Wallpaper,
|
||||||
|
Custom,
|
||||||
|
Disabled
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -150,6 +150,37 @@ public partial class App : Application
|
|||||||
|
|
||||||
_settingsFacade.Settings.Changed += OnSettingsChanged;
|
_settingsFacade.Settings.Changed += OnSettingsChanged;
|
||||||
_appearanceThemeService.Changed += OnAppearanceThemeChanged;
|
_appearanceThemeService.Changed += OnAppearanceThemeChanged;
|
||||||
|
|
||||||
|
// 监听系统主题变化
|
||||||
|
PropertyChanged += OnAppPropertyChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAppPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Property == ActualThemeVariantProperty)
|
||||||
|
{
|
||||||
|
// 系统主题变化时,检查是否需要更新
|
||||||
|
var themeMode = _settingsFacade.Settings.LoadSnapshot<AppSettingsSnapshot>(SettingsScope.App).ThemeMode;
|
||||||
|
if (string.Equals(themeMode, ThemeAppearanceValues.ThemeModeFollowSystem, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var newThemeVariant = (ThemeVariant?)e.NewValue;
|
||||||
|
var isDark = newThemeVariant == ThemeVariant.Dark;
|
||||||
|
|
||||||
|
// 同步到设置
|
||||||
|
var currentThemeState = _settingsFacade.Theme.Get();
|
||||||
|
if (currentThemeState.IsNightMode != isDark)
|
||||||
|
{
|
||||||
|
_settingsFacade.Theme.Save(currentThemeState with { IsNightMode = isDark });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用主题
|
||||||
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
ApplyThemeFromSettings();
|
||||||
|
RefreshTrayIconContent();
|
||||||
|
}, DispatcherPriority.Background);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
@@ -762,9 +793,30 @@ public partial class App : Application
|
|||||||
private void ApplyThemeFromSettings()
|
private void ApplyThemeFromSettings()
|
||||||
{
|
{
|
||||||
var snapshot = _appearanceThemeService.GetCurrent();
|
var snapshot = _appearanceThemeService.GetCurrent();
|
||||||
|
var themeMode = _settingsFacade.Settings.LoadSnapshot<AppSettingsSnapshot>(SettingsScope.App).ThemeMode;
|
||||||
|
|
||||||
|
// 处理跟随系统主题模式
|
||||||
|
if (string.Equals(themeMode, ThemeAppearanceValues.ThemeModeFollowSystem, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// 使用 Avalonia 的系统主题检测
|
||||||
|
var systemTheme = ActualThemeVariant;
|
||||||
|
RequestedThemeVariant = systemTheme;
|
||||||
|
|
||||||
|
// 同步 IsNightMode 到设置
|
||||||
|
var isSystemDark = systemTheme == ThemeVariant.Dark;
|
||||||
|
var currentThemeState = _settingsFacade.Theme.Get();
|
||||||
|
if (currentThemeState.IsNightMode != isSystemDark)
|
||||||
|
{
|
||||||
|
_settingsFacade.Theme.Save(currentThemeState with { IsNightMode = isSystemDark });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
RequestedThemeVariant = snapshot.IsNightMode
|
RequestedThemeVariant = snapshot.IsNightMode
|
||||||
? ThemeVariant.Dark
|
? ThemeVariant.Dark
|
||||||
: ThemeVariant.Light;
|
: ThemeVariant.Light;
|
||||||
|
}
|
||||||
|
|
||||||
ApplyAdaptiveThemeResources();
|
ApplyAdaptiveThemeResources();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1054,6 +1106,7 @@ public partial class App : Application
|
|||||||
var themeChanged =
|
var themeChanged =
|
||||||
refreshAll ||
|
refreshAll ||
|
||||||
changedKeys.Contains(nameof(AppSettingsSnapshot.IsNightMode), StringComparer.OrdinalIgnoreCase) ||
|
changedKeys.Contains(nameof(AppSettingsSnapshot.IsNightMode), StringComparer.OrdinalIgnoreCase) ||
|
||||||
|
changedKeys.Contains(nameof(AppSettingsSnapshot.ThemeMode), StringComparer.OrdinalIgnoreCase) ||
|
||||||
changedKeys.Contains(nameof(AppSettingsSnapshot.UseSystemChrome), StringComparer.OrdinalIgnoreCase) ||
|
changedKeys.Contains(nameof(AppSettingsSnapshot.UseSystemChrome), StringComparer.OrdinalIgnoreCase) ||
|
||||||
changedKeys.Contains(nameof(AppSettingsSnapshot.CornerRadiusStyle), StringComparer.OrdinalIgnoreCase) ||
|
changedKeys.Contains(nameof(AppSettingsSnapshot.CornerRadiusStyle), StringComparer.OrdinalIgnoreCase) ||
|
||||||
(string.Equals(liveAppearance.ThemeColorMode, ThemeAppearanceValues.ColorModeSeedMonet, StringComparison.OrdinalIgnoreCase) &&
|
(string.Equals(liveAppearance.ThemeColorMode, ThemeAppearanceValues.ColorModeSeedMonet, StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
|||||||
@@ -349,6 +349,11 @@
|
|||||||
"settings.appearance.title": "Appearance",
|
"settings.appearance.title": "Appearance",
|
||||||
"settings.appearance.description": "Adjust theme source, system material, and window chrome.",
|
"settings.appearance.description": "Adjust theme source, system material, and window chrome.",
|
||||||
"settings.appearance.theme_header": "Theme",
|
"settings.appearance.theme_header": "Theme",
|
||||||
|
"settings.appearance.theme_mode_label": "Theme mode",
|
||||||
|
"settings.appearance.theme_mode_desc": "Choose light, dark, or follow system theme.",
|
||||||
|
"settings.appearance.theme_mode.light": "Light",
|
||||||
|
"settings.appearance.theme_mode.dark": "Dark",
|
||||||
|
"settings.appearance.theme_mode.follow_system": "Follow system",
|
||||||
"settings.color.enable_night_mode_toggle": "Enable night mode",
|
"settings.color.enable_night_mode_toggle": "Enable night mode",
|
||||||
"settings.color.use_system_chrome_toggle": "Use system window chrome",
|
"settings.color.use_system_chrome_toggle": "Use system window chrome",
|
||||||
"settings.color.theme_color_label": "Theme accent color",
|
"settings.color.theme_color_label": "Theme accent color",
|
||||||
|
|||||||
@@ -292,6 +292,11 @@
|
|||||||
"settings.appearance.title": "外観",
|
"settings.appearance.title": "外観",
|
||||||
"settings.appearance.description": "テーマソース、システムマテリアル、ウィンドウクロームを調整します。",
|
"settings.appearance.description": "テーマソース、システムマテリアル、ウィンドウクロームを調整します。",
|
||||||
"settings.appearance.theme_header": "テーマ",
|
"settings.appearance.theme_header": "テーマ",
|
||||||
|
"settings.appearance.theme_mode_label": "テーマモード",
|
||||||
|
"settings.appearance.theme_mode_desc": "ライト、ダーク、またはシステムに従うを選択してください。",
|
||||||
|
"settings.appearance.theme_mode.light": "ライト",
|
||||||
|
"settings.appearance.theme_mode.dark": "ダーク",
|
||||||
|
"settings.appearance.theme_mode.follow_system": "システムに従う",
|
||||||
"settings.color.enable_night_mode_toggle": "夜モードを有効にする",
|
"settings.color.enable_night_mode_toggle": "夜モードを有効にする",
|
||||||
"settings.color.use_system_chrome_toggle": "システムのウィンドウクロームを使用",
|
"settings.color.use_system_chrome_toggle": "システムのウィンドウクロームを使用",
|
||||||
"settings.color.theme_color_label": "テーマのアクセントカラー",
|
"settings.color.theme_color_label": "テーマのアクセントカラー",
|
||||||
|
|||||||
@@ -338,6 +338,11 @@
|
|||||||
"settings.appearance.title": "외관",
|
"settings.appearance.title": "외관",
|
||||||
"settings.appearance.description": "테마 소스, 시스템 소재 및 창 외관을 조정합니다.",
|
"settings.appearance.description": "테마 소스, 시스템 소재 및 창 외관을 조정합니다.",
|
||||||
"settings.appearance.theme_header": "테마",
|
"settings.appearance.theme_header": "테마",
|
||||||
|
"settings.appearance.theme_mode_label": "테마 모드",
|
||||||
|
"settings.appearance.theme_mode_desc": "라이트, 다크 또는 시스템 설정 따르기를 선택하세요.",
|
||||||
|
"settings.appearance.theme_mode.light": "라이트",
|
||||||
|
"settings.appearance.theme_mode.dark": "다크",
|
||||||
|
"settings.appearance.theme_mode.follow_system": "시스템 설정 따르기",
|
||||||
"settings.color.enable_night_mode_toggle": "야간 모드 활성화",
|
"settings.color.enable_night_mode_toggle": "야간 모드 활성화",
|
||||||
"settings.color.use_system_chrome_toggle": "시스템 창 제목 표시줄 사용",
|
"settings.color.use_system_chrome_toggle": "시스템 창 제목 표시줄 사용",
|
||||||
"settings.color.theme_color_label": "테마 강조 색상",
|
"settings.color.theme_color_label": "테마 강조 색상",
|
||||||
|
|||||||
@@ -344,6 +344,11 @@
|
|||||||
"settings.appearance.title": "外观",
|
"settings.appearance.title": "外观",
|
||||||
"settings.appearance.description": "调整主题来源、系统材质与窗口外观。",
|
"settings.appearance.description": "调整主题来源、系统材质与窗口外观。",
|
||||||
"settings.appearance.theme_header": "主题",
|
"settings.appearance.theme_header": "主题",
|
||||||
|
"settings.appearance.theme_mode_label": "主题模式",
|
||||||
|
"settings.appearance.theme_mode_desc": "选择日间、夜间或跟随系统主题。",
|
||||||
|
"settings.appearance.theme_mode.light": "日间",
|
||||||
|
"settings.appearance.theme_mode.dark": "夜间",
|
||||||
|
"settings.appearance.theme_mode.follow_system": "跟随系统",
|
||||||
"settings.color.enable_night_mode_toggle": "启用夜间模式",
|
"settings.color.enable_night_mode_toggle": "启用夜间模式",
|
||||||
"settings.color.use_system_chrome_toggle": "使用系统窗口标题栏",
|
"settings.color.use_system_chrome_toggle": "使用系统窗口标题栏",
|
||||||
"settings.color.theme_color_label": "主题强调色",
|
"settings.color.theme_color_label": "主题强调色",
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ public sealed class AppSettingsSnapshot
|
|||||||
|
|
||||||
public string? SelectedWallpaperSeed { get; set; }
|
public string? SelectedWallpaperSeed { get; set; }
|
||||||
|
|
||||||
|
public string ThemeMode { get; set; } = "light";
|
||||||
|
|
||||||
public string? WallpaperPath { get; set; }
|
public string? WallpaperPath { get; set; }
|
||||||
|
|
||||||
public string WallpaperType { get; set; } = "Image";
|
public string WallpaperType { get; set; } = "Image";
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ public sealed record ThemeAppearanceSettingsState(
|
|||||||
string CornerRadiusStyle = GlobalAppearanceSettings.DefaultCornerRadiusStyle,
|
string CornerRadiusStyle = GlobalAppearanceSettings.DefaultCornerRadiusStyle,
|
||||||
string ThemeColorMode = ThemeAppearanceValues.ColorModeDefaultNeutral,
|
string ThemeColorMode = ThemeAppearanceValues.ColorModeDefaultNeutral,
|
||||||
string SystemMaterialMode = ThemeAppearanceValues.MaterialNone,
|
string SystemMaterialMode = ThemeAppearanceValues.MaterialNone,
|
||||||
string? SelectedWallpaperSeed = null);
|
string? SelectedWallpaperSeed = null,
|
||||||
|
string ThemeMode = ThemeAppearanceValues.ThemeModeLight);
|
||||||
public sealed record StatusBarSettingsState(
|
public sealed record StatusBarSettingsState(
|
||||||
IReadOnlyList<string> TopStatusComponentIds,
|
IReadOnlyList<string> TopStatusComponentIds,
|
||||||
IReadOnlyList<string> PinnedTaskbarActions,
|
IReadOnlyList<string> PinnedTaskbarActions,
|
||||||
|
|||||||
@@ -266,7 +266,21 @@ internal sealed class ThemeAppearanceService : IThemeAppearanceService
|
|||||||
cornerRadiusStyle,
|
cornerRadiusStyle,
|
||||||
ThemeAppearanceValues.NormalizeThemeColorMode(snapshot.ThemeColorMode, snapshot.ThemeColor),
|
ThemeAppearanceValues.NormalizeThemeColorMode(snapshot.ThemeColorMode, snapshot.ThemeColor),
|
||||||
ThemeAppearanceValues.NormalizeSystemMaterialMode(snapshot.SystemMaterialMode),
|
ThemeAppearanceValues.NormalizeSystemMaterialMode(snapshot.SystemMaterialMode),
|
||||||
snapshot.SelectedWallpaperSeed);
|
snapshot.SelectedWallpaperSeed,
|
||||||
|
NormalizeThemeMode(snapshot.ThemeMode));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeThemeMode(string? value)
|
||||||
|
{
|
||||||
|
if (string.Equals(value, ThemeAppearanceValues.ThemeModeDark, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return ThemeAppearanceValues.ThemeModeDark;
|
||||||
|
}
|
||||||
|
if (string.Equals(value, ThemeAppearanceValues.ThemeModeFollowSystem, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return ThemeAppearanceValues.ThemeModeFollowSystem;
|
||||||
|
}
|
||||||
|
return ThemeAppearanceValues.ThemeModeLight;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save(ThemeAppearanceSettingsState state)
|
public void Save(ThemeAppearanceSettingsState state)
|
||||||
@@ -323,6 +337,13 @@ internal sealed class ThemeAppearanceService : IThemeAppearanceService
|
|||||||
changedKeys.Add(nameof(AppSettingsSnapshot.SelectedWallpaperSeed));
|
changedKeys.Add(nameof(AppSettingsSnapshot.SelectedWallpaperSeed));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var normalizedThemeMode = NormalizeThemeMode(state.ThemeMode);
|
||||||
|
if (!string.Equals(snapshot.ThemeMode, normalizedThemeMode, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
snapshot.ThemeMode = normalizedThemeMode;
|
||||||
|
changedKeys.Add(nameof(AppSettingsSnapshot.ThemeMode));
|
||||||
|
}
|
||||||
|
|
||||||
if (changedKeys.Count == 0)
|
if (changedKeys.Count == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ public static class ThemeAppearanceValues
|
|||||||
public const string ColorSchemeFollowSystem = "follow_system";
|
public const string ColorSchemeFollowSystem = "follow_system";
|
||||||
public const string ColorSchemeNative = "native";
|
public const string ColorSchemeNative = "native";
|
||||||
|
|
||||||
|
public const string ThemeModeLight = "light";
|
||||||
|
public const string ThemeModeDark = "dark";
|
||||||
|
public const string ThemeModeFollowSystem = "follow_system";
|
||||||
|
|
||||||
public const string MaterialNone = "none";
|
public const string MaterialNone = "none";
|
||||||
public const string MaterialMica = "mica";
|
public const string MaterialMica = "mica";
|
||||||
public const string MaterialAcrylic = "acrylic";
|
public const string MaterialAcrylic = "acrylic";
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
<linker>
|
|
||||||
<!-- Avalonia and UI framework assemblies that should not be trimmed -->
|
|
||||||
<assembly fullname="Avalonia" preserve="all" />
|
|
||||||
<assembly fullname="Avalonia.Controls" preserve="all" />
|
|
||||||
<assembly fullname="Avalonia.Core" preserve="all" />
|
|
||||||
<assembly fullname="Avalonia.Dialogs" preserve="all" />
|
|
||||||
<assembly fullname="Avalonia.Desktop" preserve="all" />
|
|
||||||
<assembly fullname="Avalonia.Themes.Fluent" preserve="all" />
|
|
||||||
<assembly fullname="Avalonia.Fonts.Inter" preserve="all" />
|
|
||||||
|
|
||||||
<!-- FluentUI packages -->
|
|
||||||
<assembly fullname="FluentAvaloniaUI" preserve="all" />
|
|
||||||
<assembly fullname="FluentIcons.Avalonia" preserve="all" />
|
|
||||||
<assembly fullname="FluentIcons.Avalonia.Fluent" preserve="all" />
|
|
||||||
|
|
||||||
<!-- Media and rendering -->
|
|
||||||
<assembly fullname="LibVLCSharp" preserve="all" />
|
|
||||||
<assembly fullname="LibVLCSharp.Avalonia" preserve="all" />
|
|
||||||
<assembly fullname="WebView.Avalonia" preserve="all" />
|
|
||||||
<assembly fullname="WebView.Avalonia.Desktop" preserve="all" />
|
|
||||||
|
|
||||||
<!-- MVVM and utilities -->
|
|
||||||
<assembly fullname="CommunityToolkit.Mvvm" preserve="all" />
|
|
||||||
<assembly fullname="YamlDotNet" preserve="all" />
|
|
||||||
<assembly fullname="DotNetCampus.AvaloniaInkCanvas" preserve="all" />
|
|
||||||
<assembly fullname="PortAudioSharp2" preserve="all" />
|
|
||||||
|
|
||||||
<!-- System assemblies with reflection usage -->
|
|
||||||
<assembly fullname="System.Drawing.Common" preserve="all" />
|
|
||||||
<assembly fullname="System.Runtime.WindowsRuntime" preserve="all" />
|
|
||||||
<assembly fullname="System.ComponentModel.TypeConverter" preserve="all" />
|
|
||||||
<assembly fullname="System.Reflection" preserve="all" />
|
|
||||||
<assembly fullname="System.Reflection.Emit" preserve="all" />
|
|
||||||
<assembly fullname="System.Reflection.Emit.Lightweight" preserve="all" />
|
|
||||||
</linker>
|
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Avalonia;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
|
using Avalonia.Styling;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using LanMountainDesktop.ComponentSystem;
|
using LanMountainDesktop.ComponentSystem;
|
||||||
@@ -576,10 +579,36 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase
|
|||||||
_languageCode = _localizationService.NormalizeLanguageCode(_settingsFacade.Region.Get().LanguageCode);
|
_languageCode = _localizationService.NormalizeLanguageCode(_settingsFacade.Region.Get().LanguageCode);
|
||||||
RefreshLocalizedText();
|
RefreshLocalizedText();
|
||||||
ThemeColorModes = CreateThemeColorModes();
|
ThemeColorModes = CreateThemeColorModes();
|
||||||
|
ThemeModeOptions = CreateThemeModeOptions();
|
||||||
|
|
||||||
_isInitializing = true;
|
_isInitializing = true;
|
||||||
Load();
|
Load();
|
||||||
_isInitializing = false;
|
_isInitializing = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnSelectedThemeModeChanged(SelectionOption value)
|
||||||
|
{
|
||||||
|
if (_isInitializing || value is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据选择的主题模式更新夜间模式状态
|
||||||
|
var newIsNightMode = value.Value switch
|
||||||
|
{
|
||||||
|
ThemeAppearanceValues.ThemeModeDark => true,
|
||||||
|
ThemeAppearanceValues.ThemeModeLight => false,
|
||||||
|
ThemeAppearanceValues.ThemeModeFollowSystem => Application.Current?.ActualThemeVariant == ThemeVariant.Dark,
|
||||||
|
_ => IsNightMode
|
||||||
|
};
|
||||||
|
|
||||||
|
if (IsNightMode != newIsNightMode)
|
||||||
|
{
|
||||||
|
IsNightMode = newIsNightMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
PersistCurrentState(restartRequired: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public event Action<string>? RestartRequested;
|
public event Action<string>? RestartRequested;
|
||||||
@@ -595,6 +624,27 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string _themeColor = string.Empty;
|
private string _themeColor = string.Empty;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private IReadOnlyList<SelectionOption> _themeModeOptions = [];
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private SelectionOption _selectedThemeMode = new(ThemeAppearanceValues.ThemeModeLight, "Light");
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _themeModeLabel = string.Empty;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _themeModeDescription = string.Empty;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _themeModeLightText = string.Empty;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _themeModeDarkText = string.Empty;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _themeModeFollowSystemText = string.Empty;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private Color _customSeedPickerValue = DefaultSeedColor;
|
private Color _customSeedPickerValue = DefaultSeedColor;
|
||||||
|
|
||||||
@@ -797,16 +847,6 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase
|
|||||||
UpdatePreview(theme);
|
UpdatePreview(theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnIsNightModeChanged(bool value)
|
|
||||||
{
|
|
||||||
if (_isInitializing)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PersistCurrentState(restartRequired: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
partial void OnUseSystemChromeChanged(bool value)
|
partial void OnUseSystemChromeChanged(bool value)
|
||||||
{
|
{
|
||||||
if (_isInitializing)
|
if (_isInitializing)
|
||||||
@@ -887,7 +927,11 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase
|
|||||||
PageTitle = L("settings.appearance.title", "Appearance");
|
PageTitle = L("settings.appearance.title", "Appearance");
|
||||||
PageDescription = L("settings.appearance.description", "Adjust theme source, material background, and window chrome.");
|
PageDescription = L("settings.appearance.description", "Adjust theme source, material background, and window chrome.");
|
||||||
ThemeHeader = L("settings.appearance.theme_header", "Theme");
|
ThemeHeader = L("settings.appearance.theme_header", "Theme");
|
||||||
NightModeLabel = L("settings.color.enable_night_mode_toggle", "Enable night mode");
|
ThemeModeLabel = L("settings.appearance.theme_mode_label", "Theme mode");
|
||||||
|
ThemeModeDescription = L("settings.appearance.theme_mode_desc", "Choose light, dark, or follow system preference.");
|
||||||
|
ThemeModeLightText = L("settings.appearance.theme_mode.light", "Light");
|
||||||
|
ThemeModeDarkText = L("settings.appearance.theme_mode.dark", "Dark");
|
||||||
|
ThemeModeFollowSystemText = L("settings.appearance.theme_mode.follow_system", "Follow system");
|
||||||
UseSystemChromeLabel = L("settings.color.use_system_chrome_toggle", "Use system window chrome");
|
UseSystemChromeLabel = L("settings.color.use_system_chrome_toggle", "Use system window chrome");
|
||||||
ThemeColorLabel = L("settings.color.theme_color_label", "Theme Accent Color");
|
ThemeColorLabel = L("settings.color.theme_color_label", "Theme Accent Color");
|
||||||
ThemeColorModeLabel = L("settings.appearance.theme_color_mode_label", "Theme color source");
|
ThemeColorModeLabel = L("settings.appearance.theme_color_mode_label", "Theme color source");
|
||||||
@@ -957,6 +1001,26 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase
|
|||||||
SelectedSystemMaterialMode = SystemMaterialModes.FirstOrDefault(option =>
|
SelectedSystemMaterialMode = SystemMaterialModes.FirstOrDefault(option =>
|
||||||
string.Equals(option.Value, savedSystemMaterialMode, StringComparison.OrdinalIgnoreCase))
|
string.Equals(option.Value, savedSystemMaterialMode, StringComparison.OrdinalIgnoreCase))
|
||||||
?? SystemMaterialModes[0];
|
?? SystemMaterialModes[0];
|
||||||
|
|
||||||
|
// 应用主题模式设置
|
||||||
|
var savedThemeMode = NormalizeThemeMode(theme.ThemeMode);
|
||||||
|
SelectedThemeMode = ThemeModeOptions.FirstOrDefault(option =>
|
||||||
|
string.Equals(option.Value, savedThemeMode, StringComparison.OrdinalIgnoreCase))
|
||||||
|
?? ThemeModeOptions.FirstOrDefault(o => o.Value == ThemeAppearanceValues.ThemeModeLight)
|
||||||
|
?? new SelectionOption(ThemeAppearanceValues.ThemeModeLight, ThemeModeLightText);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeThemeMode(string? value)
|
||||||
|
{
|
||||||
|
if (string.Equals(value, ThemeAppearanceValues.ThemeModeDark, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return ThemeAppearanceValues.ThemeModeDark;
|
||||||
|
}
|
||||||
|
if (string.Equals(value, ThemeAppearanceValues.ThemeModeFollowSystem, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return ThemeAppearanceValues.ThemeModeFollowSystem;
|
||||||
|
}
|
||||||
|
return ThemeAppearanceValues.ThemeModeLight;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PersistCurrentState(bool restartRequired)
|
private void PersistCurrentState(bool restartRequired)
|
||||||
@@ -984,6 +1048,16 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IReadOnlyList<SelectionOption> CreateThemeModeOptions()
|
||||||
|
{
|
||||||
|
return
|
||||||
|
[
|
||||||
|
new SelectionOption(ThemeAppearanceValues.ThemeModeLight, ThemeModeLightText),
|
||||||
|
new SelectionOption(ThemeAppearanceValues.ThemeModeDark, ThemeModeDarkText),
|
||||||
|
new SelectionOption(ThemeAppearanceValues.ThemeModeFollowSystem, ThemeModeFollowSystemText)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
private ThemeAppearanceSettingsState BuildPendingState(bool usePickerSeed)
|
private ThemeAppearanceSettingsState BuildPendingState(bool usePickerSeed)
|
||||||
{
|
{
|
||||||
var themeColorMode = ThemeAppearanceValues.NormalizeThemeColorMode(SelectedThemeColorMode?.Value, ThemeColor);
|
var themeColorMode = ThemeAppearanceValues.NormalizeThemeColorMode(SelectedThemeColorMode?.Value, ThemeColor);
|
||||||
@@ -998,7 +1072,8 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase
|
|||||||
GlobalAppearanceSettings.NormalizeCornerRadiusStyle(CornerRadiusStyle),
|
GlobalAppearanceSettings.NormalizeCornerRadiusStyle(CornerRadiusStyle),
|
||||||
themeColorMode,
|
themeColorMode,
|
||||||
ThemeAppearanceValues.NormalizeSystemMaterialMode(SelectedSystemMaterialMode?.Value),
|
ThemeAppearanceValues.NormalizeSystemMaterialMode(SelectedSystemMaterialMode?.Value),
|
||||||
_selectedWallpaperSeed);
|
_selectedWallpaperSeed,
|
||||||
|
SelectedThemeMode?.Value ?? ThemeAppearanceValues.ThemeModeLight);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdatePreview(ThemeAppearanceSettingsState pendingState)
|
private void UpdatePreview(ThemeAppearanceSettingsState pendingState)
|
||||||
|
|||||||
@@ -13,12 +13,21 @@
|
|||||||
Text="{Binding ThemeHeader}"
|
Text="{Binding ThemeHeader}"
|
||||||
Margin="0,0,0,4" />
|
Margin="0,0,0,4" />
|
||||||
|
|
||||||
<ui:SettingsExpander Header="{Binding NightModeLabel}">
|
<ui:SettingsExpander Header="{Binding ThemeModeLabel}"
|
||||||
|
Description="{Binding ThemeModeDescription}">
|
||||||
<ui:SettingsExpander.IconSource>
|
<ui:SettingsExpander.IconSource>
|
||||||
<fi:SymbolIconSource Symbol="WeatherMoon" />
|
<fi:SymbolIconSource Symbol="WeatherMoon" />
|
||||||
</ui:SettingsExpander.IconSource>
|
</ui:SettingsExpander.IconSource>
|
||||||
<ui:SettingsExpander.Footer>
|
<ui:SettingsExpander.Footer>
|
||||||
<ToggleSwitch IsChecked="{Binding IsNightMode}" />
|
<ComboBox Width="200"
|
||||||
|
ItemsSource="{Binding ThemeModeOptions}"
|
||||||
|
SelectedItem="{Binding SelectedThemeMode}">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="vm:SelectionOption">
|
||||||
|
<TextBlock Text="{Binding Label}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
</ui:SettingsExpander.Footer>
|
</ui:SettingsExpander.Footer>
|
||||||
</ui:SettingsExpander>
|
</ui:SettingsExpander>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user