diff --git a/.trae/documents/avalonia12-migration-plan.md b/.trae/documents/avalonia12-migration-plan.md
new file mode 100644
index 0000000..52c8ade
--- /dev/null
+++ b/.trae/documents/avalonia12-migration-plan.md
@@ -0,0 +1,106 @@
+# Avalonia 12 迁移计划
+
+## 当前状态
+
+项目已完成以下迁移准备:
+
+* `Directory.Packages.props` 中 Avalonia 包已升级到 `12.0.1`
+
+* `FluentAvaloniaUI` 已升级到 `3.0.0-preview1`
+
+* `Avalonia.Diagnostics` 已替换为 `AvaloniaUI.DiagnosticsSupport`
+
+* `Avalonia.Controls.WebView` 已升级到 `12.0.0`
+
+* `ClassIsland.Markdown.Avalonia` 已升级到 `12.0.0`
+
+## 构建错误清单(26 errors)
+
+### 1. 窗口装饰 API 移除(8 errors)
+
+**文件**:`LanMountainDesktop/Views/SettingsWindow.axaml.cs`(4 errors)
+
+* `ExtendClientAreaChromeHints` 不存在(line 166, 179)
+
+* `SystemDecorations` 已过时,需改用 `WindowDecorations`(line 168, 177)
+
+**文件**:`LanMountainDesktop/Views/ComponentEditorWindow.axaml.cs`(4 errors)
+
+* `ExtendClientAreaChromeHints` 不存在(line 63, 72)
+
+* `SystemDecorations` 已过时,需改用 `WindowDecorations`(line 65, 70)
+
+**AXAML 文件**:13 个文件使用 `SystemDecorations` 属性(编译警告)
+
+### 2. 变量/字段未找到(8 errors)
+
+**文件**:`LanMountainDesktop/Views/MainWindow.ComponentSystem.cs`
+
+* `centerLeft` 不存在(line 759, 766, 778)
+
+* `positions` 不存在(line 1266)
+
+**文件**:`LanMountainDesktop/Views/MainWindow.DesktopPaging.cs`
+
+* `child` 不存在(line 312)
+
+* `_isThreeFingerOrRightDragSwipeActive` 不存在(line 517, 828, 847, 850)
+
+### 3. API 变更(3 errors)
+
+**文件**:`LanMountainDesktop/App.axaml.cs`
+
+* `BindingPlugins` 不可访问(line 532, 537)
+
+**文件**:`LanMountainDesktop/Views/Components/DesktopComponentFailureView.cs`
+
+* `IClipboard.SetTextAsync` 不存在(line 187)
+
+**文件**:`LanMountainDesktop/Services/MonetColorService.cs`
+
+* `Bitmap.CopyPixels` 参数不匹配(line 91)
+
+### 4. 第三方库变更(1 error)
+
+**文件**:`LanMountainDesktop/Views/SettingsWindow.axaml.cs`
+
+* `FluentIcons.Avalonia.SymbolIconSource` 不存在(line 215)
+
+### 5. 过时属性警告(需同步修复)
+
+* `TextBox.Watermark` → `PlaceholderText`(7 处 .cs + 7 处 .axaml)
+
+## 迁移步骤
+
+### Phase 1: 修复窗口装饰 API(高优先级)
+
+1. 重写 `SettingsWindow.ApplyChromeMode()` 使用 Avalonia 12 新 API
+2. 重写 `ComponentEditorWindow.ApplyChromeMode()` 使用 Avalonia 12 新 API
+3. 批量替换所有 `.axaml` 中的 `SystemDecorations` → `WindowDecorations`
+
+### Phase 2: 修复 MainWindow 编译错误(高优先级)
+
+1. 检查 `MainWindow.ComponentSystem.cs` 中 `centerLeft` 和 `positions` 的作用域问题
+2. 检查 `MainWindow.DesktopPaging.cs` 中 `child` 和 `_isThreeFingerOrRightDragSwipeActive` 的作用域问题
+3. 确认这些变量是否被意外删除或重命名
+
+### Phase 3: 修复 Avalonia 12 API 变更(中优先级)
+
+1. `App.axaml.cs`: 替换 `BindingPlugins.DataValidators` 的访问方式
+2. `DesktopComponentFailureView.cs`: 使用新的剪贴板 API
+3. `MonetColorService.cs`: 更新 `Bitmap.CopyPixels` 调用签名
+
+### Phase 4: 修复第三方库变更(中优先级)
+
+1. `SettingsWindow.axaml.cs`: 替换 `FluentIcons.Avalonia.SymbolIconSource` 为 v3 等效 API
+
+### Phase 5: 清理过时属性(低优先级)
+
+1. 批量替换 `Watermark` → `PlaceholderText`(所有 .cs 和 .axaml)
+
+## 验证步骤
+
+* 每阶段修复后运行 `dotnet build LanMountainDesktop.slnx -c Debug`
+
+* 最终运行 `dotnet test LanMountainDesktop.slnx -c Debug`
+
diff --git a/.trae/specs/avalonia-12-migration/checklist.md b/.trae/specs/avalonia-12-migration/checklist.md
new file mode 100644
index 0000000..90ebdfa
--- /dev/null
+++ b/.trae/specs/avalonia-12-migration/checklist.md
@@ -0,0 +1,21 @@
+# Checklist
+
+- [x] `Directory.Packages.props` contains the Avalonia 12 dependency baseline.
+- [x] Main host references `Avalonia.Controls.WebView`.
+- [x] Source no longer references `WebView.Avalonia`, `AvaloniaWebView`, or `.UseDesktopWebView()`.
+- [x] `BrowserWidget` uses `NativeWebView.Source`, `Navigate`, `Refresh()`, `NavigationStarted`, and `EnvironmentRequested`.
+- [x] WebView blanking navigates to `about:blank`.
+- [x] Plugin SDK package version is `5.0.0`.
+- [x] `PluginSdkInfo.ApiVersion` is `5.0.0`.
+- [x] Plugin template package version default is `5.0.0`.
+- [x] Plugin template manifest `apiVersion` is `5.0.0`.
+- [x] Launcher data location config resolution cannot recurse through `ResolveDataRoot()`.
+- [x] `OobeStateServiceTests` pass.
+- [x] `dotnet build LanMountainDesktop.slnx -c Debug` has 0 errors.
+- [x] `dotnet test LanMountainDesktop.slnx -c Debug` completes without a test host stack overflow.
+- [ ] Windows host smoke test completed.
+- [ ] Windows Launcher smoke test completed.
+- [ ] Settings window FluentAvalonia 3 smoke test completed.
+- [ ] Component editor Material smoke test completed.
+- [ ] BrowserWidget navigation/refresh/page activation smoke test completed.
+- [ ] WebView2 missing-runtime diagnostic smoke test completed.
diff --git a/.trae/specs/avalonia-12-migration/spec.md b/.trae/specs/avalonia-12-migration/spec.md
new file mode 100644
index 0000000..37206b8
--- /dev/null
+++ b/.trae/specs/avalonia-12-migration/spec.md
@@ -0,0 +1,49 @@
+# Avalonia 12 Full Stack Migration
+
+## Summary
+
+LanMountainDesktop has moved its desktop stack to the Avalonia 12 baseline. The migration covers the main host, Launcher, Plugin SDK, plugin runtime loading policy, official WebView usage, ClassIsland Markdown, FluentAvalonia, FluentIcons, and Material-related dependencies.
+
+## Requirements
+
+### Requirement: Centralized Avalonia 12 dependency baseline
+
+The solution SHALL use central package management for direct Avalonia-facing projects and keep the core UI dependency baseline on Avalonia `12.0.1`.
+
+Required package baseline:
+
+- `Avalonia*` `12.0.1`
+- `Avalonia.Controls.WebView` `12.0.0`
+- `ClassIsland.Markdown.Avalonia` `12.0.0`
+- `FluentAvaloniaUI` `3.0.0-preview1`
+- `FluentIcons.Avalonia` `2.1.325`
+- `Material.Avalonia` `3.16.0`
+- `Material.Icons.Avalonia` `3.0.2`
+
+### Requirement: Official WebView
+
+The host SHALL use `Avalonia.Controls.NativeWebView` for the browser widget and SHALL NOT reference `WebView.Avalonia`, `AvaloniaWebView`, or `.UseDesktopWebView()`.
+
+Windows WebView2 user data configuration SHALL be supplied through `EnvironmentRequested` using `WindowsWebView2EnvironmentRequestedEventArgs.UserDataFolder`.
+
+### Requirement: Plugin SDK v5
+
+The Plugin SDK API baseline SHALL be `5.0.0`. SDK v4 plugins are treated as incompatible until rebuilt.
+
+The SDK SHALL keep the existing public UI extension shape, including `SettingsPageBase` and Avalonia `Control` based desktop components.
+
+### Requirement: Launcher data location stability
+
+Launcher data location configuration SHALL be read from a fixed bootstrap Launcher data directory so resolving the selected data root cannot recursively require resolving itself.
+
+### Requirement: OOBE state compatibility
+
+The Launcher SHALL read current OOBE state from the resolved `Launcher/state` directory and SHALL continue to migrate the legacy `.launcher/state/first_run_completed` marker.
+
+## Acceptance
+
+- `dotnet build LanMountainDesktop.slnx -c Debug` completes with 0 errors.
+- `OobeStateServiceTests` pass.
+- Full `dotnet test LanMountainDesktop.slnx -c Debug` no longer aborts from `DataLocationResolver` recursion.
+- Plugin template defaults to SDK package version `5.0.0` and manifest `apiVersion` `5.0.0`.
+- Current developer documentation points to SDK v5 and Avalonia 12.
diff --git a/.trae/specs/avalonia-12-migration/tasks.md b/.trae/specs/avalonia-12-migration/tasks.md
new file mode 100644
index 0000000..c30c775
--- /dev/null
+++ b/.trae/specs/avalonia-12-migration/tasks.md
@@ -0,0 +1,18 @@
+# Tasks
+
+- [x] Centralize Avalonia 12 package versions in `Directory.Packages.props`.
+- [x] Move the host, Launcher, Plugin SDK, DesktopHost, Shared.Contracts, and Avalonia-facing projects onto central package versions.
+- [x] Replace third-party `WebView.Avalonia` usage with official `NativeWebView`.
+- [x] Configure WebView2 user data through `EnvironmentRequested`.
+- [x] Move FluentAvalonia usages to the FA3 control names and package baseline.
+- [x] Move FluentIcons usage to `FluentIcons.Avalonia` and remove the old `.Fluent` package.
+- [x] Update Plugin SDK package version and API baseline to `5.0.0`.
+- [x] Update plugin runtime shared assembly policy for Avalonia 12 / FluentAvalonia / FluentIcons / Material.
+- [x] Fix Avalonia 12 compile breaks in window chrome, binding plugin access, clipboard, bitmap copy, and icon source usage.
+- [x] Fix Launcher data location recursion by using a fixed bootstrap config path.
+- [x] Fix OOBE state tests and legacy marker compatibility.
+- [x] Update PluginTemplate defaults to SDK v5.
+- [x] Add SDK v5 migration documentation.
+- [x] Update current docs from SDK v4 / Avalonia 11 examples to SDK v5 / Avalonia 12.
+- [x] Run full solution tests and record any remaining non-upgrade failures.
+- [ ] Perform Windows manual smoke test for host, Launcher, settings, component editor, BrowserWidget, and WebView2 missing-runtime handling.
diff --git a/AGENTS.md b/AGENTS.md
index 211433c..792e090 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -74,7 +74,7 @@ dotnet test LanMountainDesktop.slnx -c Debug
- SDK 公共 API 以 `LanMountainDesktop.PluginSdk/` 为准
- 共享契约以 `LanMountainDesktop.Shared.Contracts/` 为准
- market 数据来源默认是兄弟仓库 `..\\LanAirApp`
-- 迁移或 breaking change 优先同步 `docs/PLUGIN_SDK_V4_MIGRATION.md`
+- 迁移或 breaking change 优先同步 `docs/PLUGIN_SDK_V5_MIGRATION.md`
### 设置与主题
@@ -91,6 +91,6 @@ dotnet test LanMountainDesktop.slnx -c Debug
- 视觉规范:`docs/VISUAL_SPEC.md`
- 圆角规范:`docs/CORNER_RADIUS_SPEC.md`
- 生态边界:`docs/ECOSYSTEM_BOUNDARIES.md`
-- SDK v4 迁移:`docs/PLUGIN_SDK_V4_MIGRATION.md`
+- SDK v5 迁移:`docs/PLUGIN_SDK_V5_MIGRATION.md`
如果多个文档都提到同一件事,以 `docs/ai/DOC_SOURCES.md` 列出的权威来源为准。
diff --git a/Directory.Packages.props b/Directory.Packages.props
new file mode 100644
index 0000000..d5a50d7
--- /dev/null
+++ b/Directory.Packages.props
@@ -0,0 +1,42 @@
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/LanMountainDesktop.DesktopHost/LanMountainDesktop.DesktopHost.csproj b/LanMountainDesktop.DesktopHost/LanMountainDesktop.DesktopHost.csproj
index acb8b49..3de9b89 100644
--- a/LanMountainDesktop.DesktopHost/LanMountainDesktop.DesktopHost.csproj
+++ b/LanMountainDesktop.DesktopHost/LanMountainDesktop.DesktopHost.csproj
@@ -5,7 +5,7 @@
enable
-
+
diff --git a/LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj b/LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj
index e19e124..ce3817c 100644
--- a/LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj
+++ b/LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj
@@ -22,16 +22,14 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/LanMountainDesktop.Launcher/Services/DataLocationResolver.cs b/LanMountainDesktop.Launcher/Services/DataLocationResolver.cs
index 2fc0cd6..31b3c3c 100644
--- a/LanMountainDesktop.Launcher/Services/DataLocationResolver.cs
+++ b/LanMountainDesktop.Launcher/Services/DataLocationResolver.cs
@@ -32,6 +32,11 @@ internal sealed class DataLocationResolver
///
public string DefaultPortableDataPath => Path.Combine(_appRoot, "AppData");
+ private string ResolveBootstrapLauncherDataPath()
+ {
+ return Path.Combine(_defaultSystemDataPath, LauncherFolderName);
+ }
+
///
/// 检查是否允许便携模式(应用目录是否可写)
///
@@ -56,6 +61,11 @@ internal sealed class DataLocationResolver
public string ResolveDataRoot()
{
var config = LoadConfig();
+ return ResolveDataRoot(config);
+ }
+
+ private string ResolveDataRoot(DataLocationConfig? config)
+ {
if (config is null)
{
return _defaultSystemDataPath;
@@ -65,7 +75,7 @@ internal sealed class DataLocationResolver
{
var portablePath = !string.IsNullOrWhiteSpace(config.PortableDataPath)
? config.PortableDataPath
- : _defaultSystemDataPath;
+ : DefaultPortableDataPath;
return Path.GetFullPath(portablePath);
}
@@ -95,7 +105,7 @@ internal sealed class DataLocationResolver
///
public string ResolveConfigPath()
{
- return Path.Combine(ResolveLauncherDataPath(), ConfigFileName);
+ return Path.Combine(ResolveBootstrapLauncherDataPath(), ConfigFileName);
}
///
@@ -153,7 +163,7 @@ internal sealed class DataLocationResolver
{
try
{
- var launcherPath = ResolveLauncherDataPath();
+ var launcherPath = ResolveBootstrapLauncherDataPath();
Directory.CreateDirectory(launcherPath);
var configPath = ResolveConfigPath();
@@ -184,8 +194,9 @@ internal sealed class DataLocationResolver
// 先创建目录结构
try
{
- Directory.CreateDirectory(ResolveLauncherDataPath());
- Directory.CreateDirectory(ResolveDesktopDataPath());
+ var resolvedDataRoot = ResolveDataRoot(config);
+ Directory.CreateDirectory(Path.Combine(resolvedDataRoot, LauncherFolderName));
+ Directory.CreateDirectory(Path.Combine(resolvedDataRoot, DesktopFolderName));
}
catch (Exception ex)
{
diff --git a/LanMountainDesktop.Launcher/Services/OobeStateService.cs b/LanMountainDesktop.Launcher/Services/OobeStateService.cs
index 9ecca79..a7c4ca3 100644
--- a/LanMountainDesktop.Launcher/Services/OobeStateService.cs
+++ b/LanMountainDesktop.Launcher/Services/OobeStateService.cs
@@ -9,6 +9,7 @@ internal sealed class OobeStateService
private readonly string _stateDirectory;
private readonly string _statePath;
+ private readonly string _legacyStatePath;
private readonly string _legacyMarkerPath;
private readonly LauncherExecutionSnapshot _executionSnapshot;
@@ -25,7 +26,13 @@ internal sealed class OobeStateService
: Path.GetFullPath(stateRootOverride);
_stateDirectory = Path.Combine(stateRoot, "Launcher", "state");
_statePath = Path.Combine(_stateDirectory, "oobe-state.json");
- _legacyMarkerPath = Path.Combine(_stateDirectory, "first_run_completed");
+
+ var legacyRoot = string.IsNullOrWhiteSpace(stateRootOverride)
+ ? Path.GetFullPath(appRoot)
+ : Path.GetFullPath(stateRootOverride);
+ var legacyStateDirectory = Path.Combine(legacyRoot, ".launcher", "state");
+ _legacyStatePath = Path.Combine(legacyStateDirectory, "oobe-state.json");
+ _legacyMarkerPath = Path.Combine(legacyStateDirectory, "first_run_completed");
}
public OobeLaunchDecision Evaluate(CommandContext context)
@@ -100,14 +107,12 @@ internal sealed class OobeStateService
var migratedLegacyMarker = false;
if (File.Exists(_statePath))
{
- using var stream = File.OpenRead(_statePath);
- var state = JsonSerializer.Deserialize(stream, AppJsonContext.Default.OobeStateFile);
- if (state is null || state.SchemaVersion <= 0 || string.IsNullOrWhiteSpace(state.CompletedAtUtc))
- {
- return BuildUnavailableDecision(context, "OOBE state file is invalid.");
- }
+ return EvaluateStateFile(context, _statePath, migratedLegacyState: false);
+ }
- return BuildDecision(context, OobeStateStatus.Completed, shouldShowOobe: false, migratedLegacyMarker: false);
+ if (File.Exists(_legacyStatePath))
+ {
+ return EvaluateStateFile(context, _legacyStatePath, migratedLegacyState: false);
}
if (File.Exists(_legacyMarkerPath))
@@ -140,6 +145,18 @@ internal sealed class OobeStateService
return result.Success;
}
+ private OobeLaunchDecision EvaluateStateFile(CommandContext context, string statePath, bool migratedLegacyState)
+ {
+ using var stream = File.OpenRead(statePath);
+ var state = JsonSerializer.Deserialize(stream, AppJsonContext.Default.OobeStateFile);
+ if (state is null || state.SchemaVersion <= 0 || string.IsNullOrWhiteSpace(state.CompletedAtUtc))
+ {
+ return BuildUnavailableDecision(context, "OOBE state file is invalid.");
+ }
+
+ return BuildDecision(context, OobeStateStatus.Completed, shouldShowOobe: false, migratedLegacyMarker: migratedLegacyState);
+ }
+
private void TryDeleteLegacyMarker()
{
try
diff --git a/LanMountainDesktop.Launcher/Views/DataLocationPromptWindow.axaml b/LanMountainDesktop.Launcher/Views/DataLocationPromptWindow.axaml
index 386cba2..ef012b9 100644
--- a/LanMountainDesktop.Launcher/Views/DataLocationPromptWindow.axaml
+++ b/LanMountainDesktop.Launcher/Views/DataLocationPromptWindow.axaml
@@ -3,13 +3,13 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:LanMountainDesktop.Launcher.Views"
- xmlns:ui="using:FluentAvalonia.UI.Controls"
+ xmlns:fi="using:FluentIcons.Avalonia"
mc:Ignorable="d"
d:DesignWidth="520"
d:DesignHeight="480"
x:Class="LanMountainDesktop.Launcher.Views.DataLocationPromptWindow"
x:DataType="views:DataLocationPromptWindow"
- Title="选择数据保存位置"
+ Title="Choose Data Location"
Width="520"
Height="480"
CanResize="False"
@@ -24,12 +24,13 @@
-
-
@@ -41,15 +42,15 @@
IsVisible="False">
-
-
-
@@ -70,11 +71,11 @@
GroupName="DataLocation"
IsChecked="True" />
-
-
@@ -101,11 +102,11 @@
GroupName="DataLocation"
IsEnabled="False" />
-
-
@@ -124,7 +125,7 @@
Padding="12,10"
IsVisible="False">
-
diff --git a/LanMountainDesktop.Launcher/Views/DataLocationPromptWindow.axaml.cs b/LanMountainDesktop.Launcher/Views/DataLocationPromptWindow.axaml.cs
index 5a2e09d..c33383b 100644
--- a/LanMountainDesktop.Launcher/Views/DataLocationPromptWindow.axaml.cs
+++ b/LanMountainDesktop.Launcher/Views/DataLocationPromptWindow.axaml.cs
@@ -48,14 +48,12 @@ internal partial class DataLocationPromptWindow : Window
if (systemRadio is not null)
{
- systemRadio.Checked += OnSelectionChanged;
- systemRadio.Unchecked += OnSelectionChanged;
+ systemRadio.IsCheckedChanged += OnSelectionChanged;
}
if (portableRadio is not null)
{
- portableRadio.Checked += OnSelectionChanged;
- portableRadio.Unchecked += OnSelectionChanged;
+ portableRadio.IsCheckedChanged += OnSelectionChanged;
}
if (confirmButton is not null)
@@ -108,7 +106,7 @@ internal partial class DataLocationPromptWindow : Window
if (migrationInfoText is not null && hasExistingData)
{
- migrationInfoText.Text = "检测到系统用户目录已有应用数据。如果选择保存在应用安装目录,将自动迁移现有数据。";
+ migrationInfoText.Text = "Existing system data was detected. Choosing portable mode will migrate the current data automatically.";
}
}
diff --git a/LanMountainDesktop.Launcher/Views/SplashWindow.axaml b/LanMountainDesktop.Launcher/Views/SplashWindow.axaml
index 21bac27..898038c 100644
--- a/LanMountainDesktop.Launcher/Views/SplashWindow.axaml
+++ b/LanMountainDesktop.Launcher/Views/SplashWindow.axaml
@@ -12,7 +12,7 @@
CanResize="False"
ShowInTaskbar="False"
WindowStartupLocation="CenterScreen"
- SystemDecorations="None"
+ WindowDecorations="None"
Background="#0B0B0B"
TransparencyLevelHint="None"
Icon="/Assets/logo.ico">
diff --git a/LanMountainDesktop.Launcher/Views/UpdateWindow.axaml b/LanMountainDesktop.Launcher/Views/UpdateWindow.axaml
index f2658da..e9e26e5 100644
--- a/LanMountainDesktop.Launcher/Views/UpdateWindow.axaml
+++ b/LanMountainDesktop.Launcher/Views/UpdateWindow.axaml
@@ -13,7 +13,7 @@
Height="320"
CanResize="False"
WindowStartupLocation="CenterScreen"
- SystemDecorations="None"
+ WindowDecorations="None"
Background="{DynamicResource SolidBackgroundFillColorBaseBrush}"
TransparencyLevelHint="None"
Icon="/Assets/logo.ico">
diff --git a/LanMountainDesktop.PluginIsolation.Contracts/LanMountainDesktop.PluginIsolation.Contracts.csproj b/LanMountainDesktop.PluginIsolation.Contracts/LanMountainDesktop.PluginIsolation.Contracts.csproj
index cb3f84f..9f32f8f 100644
--- a/LanMountainDesktop.PluginIsolation.Contracts/LanMountainDesktop.PluginIsolation.Contracts.csproj
+++ b/LanMountainDesktop.PluginIsolation.Contracts/LanMountainDesktop.PluginIsolation.Contracts.csproj
@@ -3,7 +3,7 @@
net10.0
enable
enable
- 1.0.0
+ 5.0.0
LanMountainDesktop.PluginIsolation.Contracts
true
LanMountainDesktop
diff --git a/LanMountainDesktop.PluginIsolation.Ipc/LanMountainDesktop.PluginIsolation.Ipc.csproj b/LanMountainDesktop.PluginIsolation.Ipc/LanMountainDesktop.PluginIsolation.Ipc.csproj
index eefd43c..c3135cc 100644
--- a/LanMountainDesktop.PluginIsolation.Ipc/LanMountainDesktop.PluginIsolation.Ipc.csproj
+++ b/LanMountainDesktop.PluginIsolation.Ipc/LanMountainDesktop.PluginIsolation.Ipc.csproj
@@ -3,7 +3,7 @@
net10.0
enable
enable
- 1.0.0
+ 5.0.0
LanMountainDesktop.PluginIsolation.Ipc
true
LanMountainDesktop
@@ -17,7 +17,7 @@
-
+
diff --git a/LanMountainDesktop.PluginSdk/LanMountainDesktop.PluginSdk.csproj b/LanMountainDesktop.PluginSdk/LanMountainDesktop.PluginSdk.csproj
index 76eccd9..ce78b10 100644
--- a/LanMountainDesktop.PluginSdk/LanMountainDesktop.PluginSdk.csproj
+++ b/LanMountainDesktop.PluginSdk/LanMountainDesktop.PluginSdk.csproj
@@ -4,7 +4,7 @@
net10.0
enable
enable
- 4.0.2
+ 5.0.0
LanMountainDesktop.PluginSdk
true
LanMountainDesktop
@@ -19,13 +19,12 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/LanMountainDesktop.PluginSdk/PluginSdkInfo.cs b/LanMountainDesktop.PluginSdk/PluginSdkInfo.cs
index d6fb1e4..415b786 100644
--- a/LanMountainDesktop.PluginSdk/PluginSdkInfo.cs
+++ b/LanMountainDesktop.PluginSdk/PluginSdkInfo.cs
@@ -2,7 +2,7 @@ namespace LanMountainDesktop.PluginSdk;
public static class PluginSdkInfo
{
- public const string ApiVersion = "4.0.2";
+ public const string ApiVersion = "5.0.0";
public const string ManifestFileName = "plugin.json";
public const string PackageFileExtension = ".laapp";
public const string DataDirectoryName = "Data";
diff --git a/LanMountainDesktop.PluginSdk/README.md b/LanMountainDesktop.PluginSdk/README.md
index a69adbd..0b8db80 100644
--- a/LanMountainDesktop.PluginSdk/README.md
+++ b/LanMountainDesktop.PluginSdk/README.md
@@ -16,7 +16,7 @@ Official SDK package for LanMountainDesktop plugins.
```xml
-
+
```
diff --git a/LanMountainDesktop.PluginTemplate/content/.template.config/template.json b/LanMountainDesktop.PluginTemplate/content/.template.config/template.json
index daa78be..2dd0cf4 100644
--- a/LanMountainDesktop.PluginTemplate/content/.template.config/template.json
+++ b/LanMountainDesktop.PluginTemplate/content/.template.config/template.json
@@ -47,7 +47,7 @@
"pluginSdkVersion": {
"type": "parameter",
"datatype": "text",
- "defaultValue": "4.0.2",
+ "defaultValue": "5.0.0",
"description": "LanMountainDesktop.PluginSdk package version.",
"replaces": "__PLUGIN_SDK_VERSION__"
}
diff --git a/LanMountainDesktop.PluginTemplate/content/plugin.json b/LanMountainDesktop.PluginTemplate/content/plugin.json
index 6ca9f8b..1ffed34 100644
--- a/LanMountainDesktop.PluginTemplate/content/plugin.json
+++ b/LanMountainDesktop.PluginTemplate/content/plugin.json
@@ -4,7 +4,7 @@
"description": "__PLUGIN_DESCRIPTION__",
"author": "__PLUGIN_AUTHOR__",
"version": "1.0.0",
- "apiVersion": "4.0.2",
+ "apiVersion": "5.0.0",
"entranceAssembly": "LanMountainDesktop.PluginTemplate.dll",
"sharedContracts": [],
"runtime": {
diff --git a/LanMountainDesktop.Shared.Contracts/LanMountainDesktop.Shared.Contracts.csproj b/LanMountainDesktop.Shared.Contracts/LanMountainDesktop.Shared.Contracts.csproj
index 1b05865..de5af00 100644
--- a/LanMountainDesktop.Shared.Contracts/LanMountainDesktop.Shared.Contracts.csproj
+++ b/LanMountainDesktop.Shared.Contracts/LanMountainDesktop.Shared.Contracts.csproj
@@ -3,7 +3,7 @@
net10.0
enable
enable
- 0.0.0-dev
+ 5.0.0
LanMountainDesktop.Shared.Contracts
true
LanMountainDesktop
@@ -17,7 +17,7 @@
Copyright (c) LanMountainDesktop Contributors
-
+
diff --git a/LanMountainDesktop.Shared.IPC/LanMountainDesktop.Shared.IPC.csproj b/LanMountainDesktop.Shared.IPC/LanMountainDesktop.Shared.IPC.csproj
index 9e2841a..87b60e1 100644
--- a/LanMountainDesktop.Shared.IPC/LanMountainDesktop.Shared.IPC.csproj
+++ b/LanMountainDesktop.Shared.IPC/LanMountainDesktop.Shared.IPC.csproj
@@ -3,7 +3,7 @@
net10.0
enable
enable
- 1.0.0
+ 5.0.0
LanMountainDesktop.Shared.IPC
true
LanMountainDesktop
@@ -17,8 +17,8 @@
-
-
+
+
diff --git a/LanMountainDesktop.Tests/ComponentSettingsServiceTests.cs b/LanMountainDesktop.Tests/ComponentSettingsServiceTests.cs
index f752fa7..516b9fe 100644
--- a/LanMountainDesktop.Tests/ComponentSettingsServiceTests.cs
+++ b/LanMountainDesktop.Tests/ComponentSettingsServiceTests.cs
@@ -1,4 +1,3 @@
-using System.Text.Json;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
using Xunit;
@@ -34,10 +33,14 @@ public sealed class ComponentSettingsServiceTests
Assert.Equal("Sweep", snapshot.DesktopClockSecondHandMode);
Assert.Single(snapshot.ImportedClassSchedules);
- using var document = JsonDocument.Parse(File.ReadAllText(sandbox.SettingsPath));
- Assert.True(document.RootElement.TryGetProperty("defaultSettings", out var defaultSettings));
- Assert.Equal("Sweep", defaultSettings.GetProperty("desktopClockSecondHandMode").GetString());
- Assert.False(document.RootElement.TryGetProperty("DesktopClockSecondHandMode", out _));
+ Assert.True(File.Exists(sandbox.DatabasePath));
+ Assert.False(File.Exists(sandbox.SettingsPath));
+ Assert.True(File.Exists(sandbox.SettingsBackupPath));
+
+ ComponentSettingsService.ResetCacheForTests();
+ var reloadedService = sandbox.CreateService();
+ var reloaded = reloadedService.Load();
+ Assert.Equal("Sweep", reloaded.DesktopClockSecondHandMode);
}
[Fact]
@@ -72,11 +75,16 @@ public sealed class ComponentSettingsServiceTests
Assert.Equal("Sweep", snapshot.DesktopClockSecondHandMode);
Assert.True(pluginSettings.SampleFlag);
- using var document = JsonDocument.Parse(File.ReadAllText(sandbox.SettingsPath));
- Assert.True(document.RootElement.TryGetProperty("instanceSettings", out var instanceSettings));
- Assert.True(instanceSettings.TryGetProperty("DesktopClock::clock-2x2", out var clockSettings));
- Assert.Equal("Sweep", clockSettings.GetProperty("desktopClockSecondHandMode").GetString());
- Assert.False(document.RootElement.TryGetProperty("InstanceSettings", out _));
+ Assert.True(File.Exists(sandbox.DatabasePath));
+ Assert.False(File.Exists(sandbox.SettingsPath));
+ Assert.True(File.Exists(sandbox.SettingsBackupPath));
+
+ ComponentSettingsService.ResetCacheForTests();
+ var reloadedService = sandbox.CreateService();
+ var reloadedSnapshot = reloadedService.LoadForComponent("DesktopClock", "clock-2x2");
+ var reloadedPluginSettings = reloadedService.LoadPluginSettings("DesktopClock", "clock-2x2");
+ Assert.Equal("Sweep", reloadedSnapshot.DesktopClockSecondHandMode);
+ Assert.True(reloadedPluginSettings.SampleFlag);
}
[Fact]
@@ -132,12 +140,7 @@ public sealed class ComponentSettingsServiceTests
Assert.True(pluginSettings.SampleFlag);
Assert.Equal("schedule-settings", pluginSettings.Title);
- using var document = JsonDocument.Parse(File.ReadAllText(sandbox.SettingsPath));
- Assert.True(document.RootElement.TryGetProperty("instanceSettings", out var instanceSettings));
- Assert.True(instanceSettings.TryGetProperty("DesktopClock::clock-2x2", out _));
- Assert.True(instanceSettings.TryGetProperty("DesktopClassSchedule::class-schedule-2x2", out _));
- Assert.True(document.RootElement.TryGetProperty("pluginSettings", out var pluginSettingsNode));
- Assert.True(pluginSettingsNode.TryGetProperty("DesktopClassSchedule::class-schedule-2x2", out _));
+ Assert.True(File.Exists(sandbox.DatabasePath));
}
private sealed class ComponentSettingsSandbox : IDisposable
@@ -155,6 +158,10 @@ public sealed class ComponentSettingsServiceTests
public string SettingsPath => Path.Combine(_directoryPath, "component-settings.json");
+ public string SettingsBackupPath => $"{SettingsPath}.migrated.bak";
+
+ public string DatabasePath => Path.Combine(_directoryPath, "component-state.db");
+
public ComponentSettingsService CreateService()
{
return new ComponentSettingsService(_directoryPath);
diff --git a/LanMountainDesktop.Tests/LanMountainDesktop.Tests.csproj b/LanMountainDesktop.Tests/LanMountainDesktop.Tests.csproj
index 5b8f8ef..c0d8110 100644
--- a/LanMountainDesktop.Tests/LanMountainDesktop.Tests.csproj
+++ b/LanMountainDesktop.Tests/LanMountainDesktop.Tests.csproj
@@ -8,9 +8,9 @@
-
-
-
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/LanMountainDesktop.Tests/OobeStateServiceTests.cs b/LanMountainDesktop.Tests/OobeStateServiceTests.cs
index 99b3be3..5729c1a 100644
--- a/LanMountainDesktop.Tests/OobeStateServiceTests.cs
+++ b/LanMountainDesktop.Tests/OobeStateServiceTests.cs
@@ -118,7 +118,7 @@ public sealed class OobeStateServiceTests : IDisposable
executionSnapshot: executionSnapshot ?? new LauncherExecutionSnapshot(false, "tester", "S-1-5-test"));
}
- private string GetStatePath() => Path.Combine(_tempRoot, ".launcher", "state", "oobe-state.json");
+ private string GetStatePath() => Path.Combine(_tempRoot, "Launcher", "state", "oobe-state.json");
private string GetLegacyMarkerPath() => Path.Combine(_tempRoot, ".launcher", "state", "first_run_completed");
}
diff --git a/LanMountainDesktop/App.axaml.cs b/LanMountainDesktop/App.axaml.cs
index b9d1c42..fc6537c 100644
--- a/LanMountainDesktop/App.axaml.cs
+++ b/LanMountainDesktop/App.axaml.cs
@@ -14,7 +14,6 @@ using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.Threading;
-using AvaloniaWebView;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.DesktopHost;
using LanMountainDesktop.Models;
@@ -78,7 +77,6 @@ public partial class App : Application
private TransparentOverlayWindow? _transparentOverlayWindow;
private FusedDesktopComponentLibraryWindow? _fusedComponentLibraryWindow;
private bool _mainWindowClosed;
- private bool _uiUnhandledExceptionHooked;
private DesktopShellHost? _desktopShellHost;
private PublicIpcHostService? _publicIpcHostService;
private LoadingStateManager? _loadingStateManager;
@@ -194,8 +192,6 @@ public partial class App : Application
return;
}
- ConfigureWebViewUserDataFolder();
- AvaloniaWebViewBuilder.Initialize(default);
ApplyThemeFromSettings();
ApplyCurrentCultureFromSettings();
EnsureSettingsWindowService();
@@ -212,8 +208,7 @@ public partial class App : Application
}
AppLogger.Info("App", "Framework initialization completed.");
-
- RegisterUiUnhandledExceptionGuard();
+
LinuxDesktopEntryInstaller.EnsureInstalled();
InitializePublicIpc();
CurrentSingleInstanceService?.StartActivationListener(ActivateMainWindow);
@@ -532,43 +527,8 @@ public partial class App : Application
private void DisableAvaloniaDataAnnotationValidation()
{
- // Get an array of plugins to remove
- var dataValidationPluginsToRemove =
- BindingPlugins.DataValidators.OfType().ToArray();
-
- // remove each entry found
- foreach (var plugin in dataValidationPluginsToRemove)
- {
- BindingPlugins.DataValidators.Remove(plugin);
- }
- }
-
- private static void ConfigureWebViewUserDataFolder()
- {
- if (!OperatingSystem.IsWindows())
- {
- return;
- }
-
- const string userDataFolderEnvVar = "WEBVIEW2_USER_DATA_FOLDER";
- try
- {
- if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(userDataFolderEnvVar)))
- {
- return;
- }
-
- var userDataFolder = WebView2RuntimeProbe.ResolveUserDataFolder();
- Environment.SetEnvironmentVariable(
- userDataFolderEnvVar,
- userDataFolder,
- EnvironmentVariableTarget.Process);
- }
- catch (Exception ex)
- {
- // Keep startup resilient if user profile folders are unavailable.
- AppLogger.Warn("WebView2", "Failed to configure WebView2 user data folder.", ex);
- }
+ // Avalonia 12 中 BindingPlugins 已移除,数据验证插件不再需要手动禁用
+ // 编译型绑定默认开启,数据注解验证行为已改变
}
private void InitializePluginRuntime()
@@ -1178,43 +1138,6 @@ public partial class App : Application
_appearanceThemeService.ApplyThemeResources(Resources);
}
- private void RegisterUiUnhandledExceptionGuard()
- {
- if (_uiUnhandledExceptionHooked)
- {
- return;
- }
-
- Dispatcher.UIThread.UnhandledException += OnUiThreadUnhandledException;
- _uiUnhandledExceptionHooked = true;
- }
-
- private void OnUiThreadUnhandledException(object? sender, DispatcherUnhandledExceptionEventArgs e)
- {
- if (!IsKnownWebViewStartupException(e.Exception))
- {
- return;
- }
-
- e.Handled = true;
- AppLogger.Warn(
- "WebView2",
- "Suppressed a known WebView startup exception from AvaloniaWebView.Navigate to keep the host process alive.",
- e.Exception);
- }
-
- private static bool IsKnownWebViewStartupException(Exception exception)
- {
- if (exception is not NullReferenceException)
- {
- return false;
- }
-
- var stackTrace = exception.StackTrace ?? string.Empty;
- return stackTrace.Contains("AvaloniaWebView.WebView.Navigate", StringComparison.Ordinal) &&
- stackTrace.Contains("AvaloniaWebView.WebView.OnAttachedToVisualTree", StringComparison.Ordinal);
- }
-
private void ReleaseSingleInstanceAfterExit(string source)
{
if (_singleInstanceReleased)
@@ -1959,4 +1882,3 @@ public partial class App : Application
}
}
-
diff --git a/LanMountainDesktop/Controls/SettingsOptionCard.axaml.cs b/LanMountainDesktop/Controls/SettingsOptionCard.axaml.cs
index b939744..96944f5 100644
--- a/LanMountainDesktop/Controls/SettingsOptionCard.axaml.cs
+++ b/LanMountainDesktop/Controls/SettingsOptionCard.axaml.cs
@@ -108,7 +108,7 @@ public partial class SettingsOptionCard : UserControl
"Info" => Symbol.Info,
"ArrowSync" => Symbol.ArrowSync,
"Alert" => Symbol.Alert,
- "Bell" => Symbol.Alert, // Bell也映射到Alert图标
+ "Bell" => Symbol.AlertOn,
_ => Symbol.Settings
};
}
diff --git a/LanMountainDesktop/Controls/SettingsSectionCard.axaml.cs b/LanMountainDesktop/Controls/SettingsSectionCard.axaml.cs
index baee282..9e021fd 100644
--- a/LanMountainDesktop/Controls/SettingsSectionCard.axaml.cs
+++ b/LanMountainDesktop/Controls/SettingsSectionCard.axaml.cs
@@ -92,6 +92,7 @@ public partial class SettingsSectionCard : UserControl
"PuzzlePiece" => Symbol.PuzzlePiece,
"Info" => Symbol.Info,
"ArrowSync" => Symbol.ArrowSync,
+ "Bell" => Symbol.AlertOn,
_ => Symbol.Settings
};
}
diff --git a/LanMountainDesktop/LanMountainDesktop.csproj b/LanMountainDesktop/LanMountainDesktop.csproj
index b853145..4af574e 100644
--- a/LanMountainDesktop/LanMountainDesktop.csproj
+++ b/LanMountainDesktop/LanMountainDesktop.csproj
@@ -41,44 +41,42 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
None
All
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
+
+
+
diff --git a/LanMountainDesktop/Program.cs b/LanMountainDesktop/Program.cs
index 9e78bde..1e1a50d 100644
--- a/LanMountainDesktop/Program.cs
+++ b/LanMountainDesktop/Program.cs
@@ -3,7 +3,6 @@ using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Avalonia;
-using Avalonia.WebView.Desktop;
using LanMountainDesktop.DesktopHost;
using LanMountainDesktop.Models;
using LanMountainDesktop.Plugins;
@@ -111,7 +110,6 @@ public sealed class Program
{
var builder = AppBuilder.Configure()
.UsePlatformDetect()
- .UseDesktopWebView()
.WithInterFont()
.LogToTrace();
diff --git a/LanMountainDesktop/Services/MonetColorService.cs b/LanMountainDesktop/Services/MonetColorService.cs
index 28ed25f..4c6c88b 100644
--- a/LanMountainDesktop/Services/MonetColorService.cs
+++ b/LanMountainDesktop/Services/MonetColorService.cs
@@ -88,8 +88,6 @@ public sealed class MonetColorService
PixelFormat.Bgra8888,
AlphaFormat.Premul);
using var framebuffer = writeable.Lock();
- scaledBitmap.CopyPixels(framebuffer, AlphaFormat.Premul);
-
var byteCount = framebuffer.RowBytes * framebuffer.Size.Height;
if (byteCount <= 0 || framebuffer.Address == IntPtr.Zero)
{
@@ -97,6 +95,11 @@ public sealed class MonetColorService
}
var pixelBuffer = new byte[byteCount];
+ scaledBitmap.CopyPixels(
+ new PixelRect(scaledBitmap.PixelSize),
+ framebuffer.Address,
+ byteCount,
+ framebuffer.RowBytes);
Marshal.Copy(framebuffer.Address, pixelBuffer, 0, byteCount);
var argbPixels = new List(framebuffer.Size.Width * framebuffer.Size.Height);
diff --git a/LanMountainDesktop/Services/NotificationService.cs b/LanMountainDesktop/Services/NotificationService.cs
index 05d4af5..500c861 100644
--- a/LanMountainDesktop/Services/NotificationService.cs
+++ b/LanMountainDesktop/Services/NotificationService.cs
@@ -65,7 +65,7 @@ public interface INotificationService
{
void Show(NotificationContent content);
- Task ShowDialogAsync(NotificationContent content);
+ Task ShowDialogAsync(NotificationContent content);
void ShowInfo(string title, string? message = null,
NotificationPosition position = NotificationPosition.TopRight);
@@ -79,17 +79,17 @@ public interface INotificationService
void ShowError(string title, string? message = null,
NotificationPosition position = NotificationPosition.TopRight);
- Task ShowDialogInfoAsync(string title, string? message = null,
- string? primaryButtonText = "确定", string? closeButtonText = "取消");
+ Task ShowDialogInfoAsync(string title, string? message = null,
+ string? primaryButtonText = "OK", string? closeButtonText = "Cancel");
- Task ShowDialogSuccessAsync(string title, string? message = null,
- string? primaryButtonText = "确定", string? closeButtonText = "取消");
+ Task ShowDialogSuccessAsync(string title, string? message = null,
+ string? primaryButtonText = "OK", string? closeButtonText = "Cancel");
- Task ShowDialogWarningAsync(string title, string? message = null,
- string? primaryButtonText = "确定", string? closeButtonText = "取消");
+ Task ShowDialogWarningAsync(string title, string? message = null,
+ string? primaryButtonText = "OK", string? closeButtonText = "Cancel");
- Task ShowDialogErrorAsync(string title, string? message = null,
- string? primaryButtonText = "确定", string? closeButtonText = "取消");
+ Task ShowDialogErrorAsync(string title, string? message = null,
+ string? primaryButtonText = "OK", string? closeButtonText = "Cancel");
}
internal sealed class NotificationService : INotificationService
@@ -105,20 +105,17 @@ internal sealed class NotificationService : INotificationService
public void Show(NotificationContent content)
{
- // 检查通知开关是否启用
if (!IsNotificationEnabled())
{
- return; // 通知已禁用,不显示
+ return;
}
- // If it's a dialog notification (center position), show as dialog window
if (content.IsDialogNotification)
{
Dispatcher.UIThread.Post(() => ShowDialogWindow(content), DispatcherPriority.Normal);
return;
}
- // Otherwise, show as toast notification
Dispatcher.UIThread.Post(() => ShowCore(content), DispatcherPriority.Normal);
}
@@ -153,37 +150,35 @@ internal sealed class NotificationService : INotificationService
});
}
- public async Task ShowDialogAsync(NotificationContent content)
+ public async Task ShowDialogAsync(NotificationContent content)
{
- // 检查通知开关是否启用
if (!IsNotificationEnabled())
{
- return ContentDialogResult.None; // 通知已禁用,不显示
+ return FAContentDialogResult.None;
}
return await Dispatcher.UIThread.InvokeAsync(() => ShowDialogCoreAsync(content));
}
- private async Task ShowDialogCoreAsync(NotificationContent content)
+ private async Task ShowDialogCoreAsync(NotificationContent content)
{
- // Get the main window as the dialog host
var mainWindow = GetMainWindow();
if (mainWindow is null)
{
AppLogger.Warn("Notification", "Cannot show dialog notification: main window not found");
- return ContentDialogResult.None;
+ return FAContentDialogResult.None;
}
- var dialog = new ContentDialog
+ var dialog = new FAContentDialog
{
Title = content.Title,
Content = content.Message ?? string.Empty,
PrimaryButtonText = content.PrimaryButtonText,
SecondaryButtonText = content.SecondaryButtonText,
CloseButtonText = content.CloseButtonText,
- DefaultButton = !string.IsNullOrEmpty(content.PrimaryButtonText) ? ContentDialogButton.Primary :
- !string.IsNullOrEmpty(content.SecondaryButtonText) ? ContentDialogButton.Secondary :
- ContentDialogButton.Close
+ DefaultButton = !string.IsNullOrEmpty(content.PrimaryButtonText) ? FAContentDialogButton.Primary :
+ !string.IsNullOrEmpty(content.SecondaryButtonText) ? FAContentDialogButton.Secondary :
+ FAContentDialogButton.Close
};
var result = await dialog.ShowAsync(mainWindow);
@@ -191,10 +186,10 @@ internal sealed class NotificationService : INotificationService
// Execute callbacks based on result
switch (result)
{
- case ContentDialogResult.Primary:
+ case FAContentDialogResult.Primary:
content.OnPrimaryButtonClick?.Invoke();
break;
- case ContentDialogResult.Secondary:
+ case FAContentDialogResult.Secondary:
content.OnSecondaryButtonClick?.Invoke();
break;
}
@@ -206,14 +201,13 @@ internal sealed class NotificationService : INotificationService
{
try
{
- // 从全局设置服务中读取通知开关状态
var settingsFacade = HostSettingsFacadeProvider.GetOrCreate();
var snapshot = settingsFacade.Settings.LoadSnapshot(PluginSdk.SettingsScope.App);
return snapshot.NotificationEnabled;
}
catch
{
- // 如果读取失败,默认启用通知
+ // 濠电姷顣介埀顒€鍟块埀顒€缍婇幃妯诲緞鐏炴儳鐝伴柣鐘叉处瑜板啰寰婇崹顕呯唵闁诡垱澹嗙花鍧楁偡濞嗘瑧鐣甸柡浣哥Т閻f繈宕熼鐐殿偧闂佽崵濮抽梽宥夊磹閺囥垹绠氶幖娣妽閸嬨劑鏌曟繛鐐澒闁稿鎸搁~婵囨綇閳轰礁缁?
return true;
}
}
@@ -286,8 +280,8 @@ internal sealed class NotificationService : INotificationService
Show(new NotificationContent(title, message, Severity: NotificationSeverity.Error, Position: position));
}
- public Task ShowDialogInfoAsync(string title, string? message = null,
- string? primaryButtonText = "确定", string? closeButtonText = "取消")
+ public Task ShowDialogInfoAsync(string title, string? message = null,
+ string? primaryButtonText = "OK", string? closeButtonText = "Cancel")
{
return ShowDialogAsync(new NotificationContent(
title,
@@ -298,8 +292,8 @@ internal sealed class NotificationService : INotificationService
CloseButtonText: closeButtonText));
}
- public Task ShowDialogSuccessAsync(string title, string? message = null,
- string? primaryButtonText = "确定", string? closeButtonText = "取消")
+ public Task ShowDialogSuccessAsync(string title, string? message = null,
+ string? primaryButtonText = "OK", string? closeButtonText = "Cancel")
{
return ShowDialogAsync(new NotificationContent(
title,
@@ -310,8 +304,8 @@ internal sealed class NotificationService : INotificationService
CloseButtonText: closeButtonText));
}
- public Task ShowDialogWarningAsync(string title, string? message = null,
- string? primaryButtonText = "确定", string? closeButtonText = "取消")
+ public Task ShowDialogWarningAsync(string title, string? message = null,
+ string? primaryButtonText = "OK", string? closeButtonText = "Cancel")
{
return ShowDialogAsync(new NotificationContent(
title,
@@ -322,8 +316,8 @@ internal sealed class NotificationService : INotificationService
CloseButtonText: closeButtonText));
}
- public Task ShowDialogErrorAsync(string title, string? message = null,
- string? primaryButtonText = "确定", string? closeButtonText = "取消")
+ public Task ShowDialogErrorAsync(string title, string? message = null,
+ string? primaryButtonText = "OK", string? closeButtonText = "Cancel")
{
return ShowDialogAsync(new NotificationContent(
title,
@@ -357,7 +351,7 @@ internal sealed class NotificationWindowManager
var position = viewModel.Position;
var windows = _windowsByPosition[position];
- // 从设置中读取最大通知数量
+ // 濠电偛顕慨鏉戭潩閿曞偆鏁婇柡鍥╁Х绾剧偓銇勯弮鈧Σ鎺楀储椤掑嫭鍋i柛銉憾閸ゆ瑧鎲搁弶鎸庡枠鐎殿喚鏁婚崺鈧い鎺嶇缁剁偟鎲搁弮鍫濈劦妞ゆ帊鐒﹂惃鎴︽煟閹垮嫮绡€鐎殿噮鍋呯€靛ジ寮堕幋鐑嗕画
var maxNotifications = GetMaxNotificationsPerPosition();
if (windows.Count >= maxNotifications)
@@ -395,14 +389,13 @@ internal sealed class NotificationWindowManager
{
try
{
- // 从全局设置服务中读取最大通知数量
+ // 濠电偛顕慨瀛橆殽閹间礁鐭楅煫鍥ㄦ磻濞岊亪鏌嶈閸撴盯骞忕€n喖绀堢憸蹇涘几閸岀偞鐓涢柛顐g箘瀛濇繝娈垮枤閸犳劗绮欐径鎰垫晣闁宠棄妫楀▓娲⒑閸涘﹦鎳勯柣妤侇殔閵嗘帡鎳滈棃娑氱獮閻熸粍妫冮崺鈧い鎺嶇劍閻ㄦ垿鏌i幙鍕瘈鐎殿噮鍋呯€靛ジ寮堕幋鐑嗕画
var settingsFacade = HostSettingsFacadeProvider.GetOrCreate();
var snapshot = settingsFacade.Settings.LoadSnapshot(PluginSdk.SettingsScope.App);
return snapshot.NotificationMaxPerPosition > 0 ? snapshot.NotificationMaxPerPosition : 5;
}
catch
{
- // 如果读取失败,返回默认值
return 5;
}
}
diff --git a/LanMountainDesktop/Services/WebView2RuntimeProbe.cs b/LanMountainDesktop/Services/WebView2RuntimeProbe.cs
index 3abe548..d17493e 100644
--- a/LanMountainDesktop/Services/WebView2RuntimeProbe.cs
+++ b/LanMountainDesktop/Services/WebView2RuntimeProbe.cs
@@ -19,7 +19,7 @@ public static class WebView2RuntimeProbe
public static WebView2RuntimeAvailability GetAvailability()
{
- if (!OperatingSystem.IsWindows())
+ if (OperatingSystem.IsMacOS())
{
return new WebView2RuntimeAvailability(
IsAvailable: true,
@@ -27,6 +27,14 @@ public static class WebView2RuntimeProbe
Message: string.Empty);
}
+ if (!OperatingSystem.IsWindows())
+ {
+ return new WebView2RuntimeAvailability(
+ IsAvailable: false,
+ Version: null,
+ Message: "Embedded browser is currently unavailable on this platform.");
+ }
+
try
{
var version = TryGetVersionFromWebView2Api();
diff --git a/LanMountainDesktop/Styles/GlassModule.axaml b/LanMountainDesktop/Styles/GlassModule.axaml
index d52e5de..e381d91 100644
--- a/LanMountainDesktop/Styles/GlassModule.axaml
+++ b/LanMountainDesktop/Styles/GlassModule.axaml
@@ -70,11 +70,25 @@
-
+
+
+
+
-
@@ -152,7 +166,7 @@
-
@@ -169,9 +183,9 @@
-
+
-
-
-
-
-
diff --git a/LanMountainDesktop/Styles/SettingsCardStyles.axaml b/LanMountainDesktop/Styles/SettingsCardStyles.axaml
index 69e8fcc..a624232 100644
--- a/LanMountainDesktop/Styles/SettingsCardStyles.axaml
+++ b/LanMountainDesktop/Styles/SettingsCardStyles.axaml
@@ -1,7 +1,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/LanMountainDesktop/Views/ComponentEditorWindow.axaml b/LanMountainDesktop/Views/ComponentEditorWindow.axaml
index 60de6c8..d2ab797 100644
--- a/LanMountainDesktop/Views/ComponentEditorWindow.axaml
+++ b/LanMountainDesktop/Views/ComponentEditorWindow.axaml
@@ -2,7 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:fa="clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent"
+ xmlns:fa="clr-namespace:FluentIcons.Avalonia;assembly=FluentIcons.Avalonia"
xmlns:mi="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:themes="clr-namespace:Material.Styles.Themes;assembly=Material.Styles"
mc:Ignorable="d"
@@ -16,7 +16,7 @@
CanResize="True"
SizeToContent="Manual"
ShowInTaskbar="False"
- SystemDecorations="BorderOnly"
+ WindowDecorations="BorderOnly"
Background="Transparent"
Title="Component Editor">
diff --git a/LanMountainDesktop/Views/ComponentEditorWindow.axaml.cs b/LanMountainDesktop/Views/ComponentEditorWindow.axaml.cs
index 3a7bf77..823fe3a 100644
--- a/LanMountainDesktop/Views/ComponentEditorWindow.axaml.cs
+++ b/LanMountainDesktop/Views/ComponentEditorWindow.axaml.cs
@@ -60,17 +60,13 @@ public partial class ComponentEditorWindow : Window
if (preferSystemChrome)
{
ExtendClientAreaToDecorationsHint = true;
- ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.PreferSystemChrome;
- ExtendClientAreaTitleBarHeightHint = -1;
- SystemDecorations = SystemDecorations.Full;
+ WindowDecorations = WindowDecorations.Full;
CustomTitleBarHost.IsVisible = false;
return;
}
- SystemDecorations = SystemDecorations.BorderOnly;
+ WindowDecorations = WindowDecorations.BorderOnly;
ExtendClientAreaToDecorationsHint = true;
- ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.NoChrome;
- ExtendClientAreaTitleBarHeightHint = 52;
CustomTitleBarHost.IsVisible = true;
}
diff --git a/LanMountainDesktop/Views/ComponentEditors/ClockComponentEditor.axaml b/LanMountainDesktop/Views/ComponentEditors/ClockComponentEditor.axaml
index 95a8525..7463781 100644
--- a/LanMountainDesktop/Views/ComponentEditors/ClockComponentEditor.axaml
+++ b/LanMountainDesktop/Views/ComponentEditors/ClockComponentEditor.axaml
@@ -41,13 +41,11 @@
ColumnSpacing="4">
+ IsCheckedChanged="OnSecondHandChanged" />
+ IsCheckedChanged="OnSecondHandChanged" />
diff --git a/LanMountainDesktop/Views/ComponentEditors/ShortcutComponentEditor.axaml b/LanMountainDesktop/Views/ComponentEditors/ShortcutComponentEditor.axaml
index 211fa59..4519a6c 100644
--- a/LanMountainDesktop/Views/ComponentEditors/ShortcutComponentEditor.axaml
+++ b/LanMountainDesktop/Views/ComponentEditors/ShortcutComponentEditor.axaml
@@ -1,4 +1,4 @@
-
+ IsCheckedChanged="OnEnabledChanged" />
diff --git a/LanMountainDesktop/Views/ComponentEditors/WorldClockComponentEditor.axaml b/LanMountainDesktop/Views/ComponentEditors/WorldClockComponentEditor.axaml
index ef81d94..26ef749 100644
--- a/LanMountainDesktop/Views/ComponentEditors/WorldClockComponentEditor.axaml
+++ b/LanMountainDesktop/Views/ComponentEditors/WorldClockComponentEditor.axaml
@@ -77,13 +77,11 @@
ColumnSpacing="4">
+ IsCheckedChanged="OnSecondHandChanged" />
+ IsCheckedChanged="OnSecondHandChanged" />
diff --git a/LanMountainDesktop/Views/ComponentLibraryWindow.axaml b/LanMountainDesktop/Views/ComponentLibraryWindow.axaml
index 4d45fbb..01dc56b 100644
--- a/LanMountainDesktop/Views/ComponentLibraryWindow.axaml
+++ b/LanMountainDesktop/Views/ComponentLibraryWindow.axaml
@@ -9,7 +9,7 @@
MinWidth="760"
MinHeight="500"
CanResize="True"
- SystemDecorations="Full"
+ WindowDecorations="Full"
Title="Component Library"
Background="{DynamicResource AdaptiveSurfaceBaseBrush}">
diff --git a/LanMountainDesktop/Views/Components/BrowserWidget.axaml b/LanMountainDesktop/Views/Components/BrowserWidget.axaml
index b6c6ede..995af94 100644
--- a/LanMountainDesktop/Views/Components/BrowserWidget.axaml
+++ b/LanMountainDesktop/Views/Components/BrowserWidget.axaml
@@ -1,4 +1,4 @@
-
diff --git a/LanMountainDesktop/Views/Components/BrowserWidget.axaml.cs b/LanMountainDesktop/Views/Components/BrowserWidget.axaml.cs
index 43bf1bf..aabf7e2 100644
--- a/LanMountainDesktop/Views/Components/BrowserWidget.axaml.cs
+++ b/LanMountainDesktop/Views/Components/BrowserWidget.axaml.cs
@@ -4,11 +4,10 @@ using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
+using Avalonia.Platform;
using Avalonia.Styling;
-using AvaloniaWebView;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Services;
-using WebViewCore.Events;
namespace LanMountainDesktop.Views.Components;
@@ -28,7 +27,7 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
private bool _isEditMode;
private bool _isWebViewActive = true;
private bool _isWebViewFaulted;
- private WebView? _browserWebView;
+ private NativeWebView? _browserWebView;
private readonly WebView2RuntimeAvailability _runtimeAvailability;
private bool _isDisposed;
@@ -78,7 +77,8 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
if (_browserWebView is not null)
{
- _browserWebView.NavigationStarting -= OnBrowserWebViewNavigationStarting;
+ _browserWebView.NavigationStarted -= OnBrowserWebViewNavigationStarting;
+ _browserWebView.EnvironmentRequested -= OnBrowserWebViewEnvironmentRequested;
}
}
@@ -294,15 +294,32 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
}
}
- private void OnBrowserWebViewNavigationStarting(object? sender, WebViewUrlLoadingEventArg e)
+ private void OnBrowserWebViewNavigationStarting(object? sender, WebViewNavigationStartingEventArgs e)
{
- if (e.Url is null)
+ if (e.Request is null)
{
return;
}
- _lastKnownUri = e.Url;
- AddressTextBox.Text = e.Url.ToString();
+ _lastKnownUri = e.Request;
+ AddressTextBox.Text = e.Request.ToString();
+ }
+
+ private void OnBrowserWebViewEnvironmentRequested(object? sender, WebViewEnvironmentRequestedEventArgs e)
+ {
+ if (e is not WindowsWebView2EnvironmentRequestedEventArgs windowsArgs)
+ {
+ return;
+ }
+
+ try
+ {
+ windowsArgs.UserDataFolder = WebView2RuntimeProbe.ResolveUserDataFolder();
+ }
+ catch (Exception ex) when (!UiExceptionGuard.IsFatalException(ex))
+ {
+ AppLogger.Warn("WebView2", "Failed to configure the WebView2 user data folder for BrowserWidget.", ex);
+ }
}
private void UpdateWebViewActiveState()
@@ -358,6 +375,7 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
GoButton.IsEnabled = true;
AddressTextBox.IsEnabled = true;
UnavailableOverlay.IsVisible = false;
+ TryNavigate(_lastKnownUri, "ActivateWebView");
}
private void DeactivateWebView(bool clearUrl)
@@ -383,8 +401,7 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
try
{
- _browserWebView.Reload();
- return true;
+ return _browserWebView.Refresh();
}
catch (Exception ex) when (!UiExceptionGuard.IsFatalException(ex))
{
@@ -402,7 +419,7 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
try
{
- _browserWebView.Url = uri;
+ _browserWebView.Navigate(uri);
return true;
}
catch (Exception ex) when (!UiExceptionGuard.IsFatalException(ex))
@@ -421,7 +438,7 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
try
{
- _browserWebView.Url = null;
+ _browserWebView.Navigate(new Uri("about:blank"));
}
catch
{
@@ -466,12 +483,14 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
return;
}
- _browserWebView = new WebView
+ _browserWebView = new NativeWebView
{
+ Source = new Uri("about:blank"),
IsVisible = false,
IsHitTestVisible = false
};
- _browserWebView.NavigationStarting += OnBrowserWebViewNavigationStarting;
+ _browserWebView.NavigationStarted += OnBrowserWebViewNavigationStarting;
+ _browserWebView.EnvironmentRequested += OnBrowserWebViewEnvironmentRequested;
WebViewPresenter.Children.Insert(0, _browserWebView);
}
diff --git a/LanMountainDesktop/Views/Components/DesktopComponentFailureView.cs b/LanMountainDesktop/Views/Components/DesktopComponentFailureView.cs
index 245c15b..295af0b 100644
--- a/LanMountainDesktop/Views/Components/DesktopComponentFailureView.cs
+++ b/LanMountainDesktop/Views/Components/DesktopComponentFailureView.cs
@@ -4,6 +4,7 @@ using System.Text;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
+using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
diff --git a/LanMountainDesktop/Views/Components/NotificationBoxWidget.axaml b/LanMountainDesktop/Views/Components/NotificationBoxWidget.axaml
index 7f03a6f..0711a2d 100644
--- a/LanMountainDesktop/Views/Components/NotificationBoxWidget.axaml
+++ b/LanMountainDesktop/Views/Components/NotificationBoxWidget.axaml
@@ -1,6 +1,6 @@
@@ -9,18 +9,18 @@
Background="Transparent"
ClipToBounds="True">
-
+
-
+
-
+
-
+
-
+
-
-
+
-
+
-
+
@@ -50,11 +50,11 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+ Text="Select a component to view its details."/>
diff --git a/LanMountainDesktop/Views/FusedDesktopComponentLibraryWindow.axaml b/LanMountainDesktop/Views/FusedDesktopComponentLibraryWindow.axaml
index d255473..0630960 100644
--- a/LanMountainDesktop/Views/FusedDesktopComponentLibraryWindow.axaml
+++ b/LanMountainDesktop/Views/FusedDesktopComponentLibraryWindow.axaml
@@ -1,24 +1,24 @@
+ Title="Add Component">
-
+ Text="Add Component" />
+ Text="Browse available widgets and add them to the current fused desktop layout." />
-
diff --git a/LanMountainDesktop/Views/MainWindow.ComponentPreviewImages.cs b/LanMountainDesktop/Views/MainWindow.ComponentPreviewImages.cs
index 4d62d90..e891c54 100644
--- a/LanMountainDesktop/Views/MainWindow.ComponentPreviewImages.cs
+++ b/LanMountainDesktop/Views/MainWindow.ComponentPreviewImages.cs
@@ -16,7 +16,7 @@ using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views;
-public partial class MainWindow
+public partial class MainWindow : Window
{
private const double PreviewRenderCellSizeMin = 42;
private const double PreviewRenderCellSizeMax = 112;
diff --git a/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs b/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs
index 4a72409..156b8ee 100644
--- a/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs
+++ b/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs
@@ -28,7 +28,7 @@ using SymbolIcon = FluentIcons.Avalonia.SymbolIcon;
namespace LanMountainDesktop.Views;
-public partial class MainWindow
+public partial class MainWindow : Window
{
private readonly List _desktopComponentPlacements = [];
private readonly Dictionary _desktopPageComponentGrids = new();
@@ -390,12 +390,10 @@ public partial class MainWindow
if (OperatingSystem.IsWindows())
{
- // Windows: 使用 SlideToShutDown 滑动关机界面
_powerService.ShowNativePowerUI(PowerAction.Shutdown);
}
else
{
- // Linux: 二次确认对话框
await ShowPowerConfirmDialogAsync(L("power.shutdown_confirm_title", "Shutdown"),
L("power.shutdown_confirm_message", "Are you sure you want to shut down this computer?"),
() => _powerService.ShutdownAsync());
@@ -408,8 +406,6 @@ public partial class MainWindow
_ = e;
ClosePopupIfOpen();
- // 所有平台:统一使用二次确认对话框
- // Note: SlideToShutDown.exe 只支持关机,不支持重启
await ShowPowerConfirmDialogAsync(L("power.restart_confirm_title", "Restart"),
L("power.restart_confirm_message", "Are you sure you want to restart this computer?"),
() => _powerService.RestartAsync());
@@ -449,7 +445,7 @@ public partial class MainWindow
{
try
{
- var dialog = new ContentDialog
+ var dialog = new FAContentDialog
{
Title = title,
Content = message,
@@ -458,7 +454,7 @@ public partial class MainWindow
};
var result = await dialog.ShowAsync(this);
- if (result == ContentDialogResult.Primary)
+ if (result == FAContentDialogResult.Primary)
{
await action();
}
@@ -744,49 +740,42 @@ public partial class MainWindow
}
///
- /// 检测状态栏组件是否会发生碰撞
- ///
+ /// 濠碘槅鍋€閸嬫挻绻涢弶鎴剳濠殿喗鎮傞獮鈧ù锝呮贡閸╁绱撴担绋款仹婵炲棎鍨藉浼搭敍濮橆厼鍓ㄦ繛鏉戝悑閼规崘銇愰崒鐐村仺闁绘柧璀﹀楣冩煙? ///
private bool WouldComponentsCollide()
{
if (TopStatusBarHost is null)
return false;
- // 获取各区域当前占用的宽度
var leftWidth = GetLeftPanelOccupiedWidth();
var centerWidth = GetCenterPanelOccupiedWidth();
var rightWidth = GetRightPanelOccupiedWidth();
- // 获取状态栏总宽度
var totalWidth = TopStatusBarHost.Bounds.Width;
if (totalWidth <= 0)
return false;
- // 计算中间区域的实际位置
- // 左列是 *, 中列是 Auto, 右列是 *
- // 中间区域居中显示
+ // 闁荤姳绶ょ槐鏇㈡偩缂佹鈻旀い鎾卞灪閿涚喖鏌涢弽褎鎯堥柣鎾寸懇閹啴宕熼銈嗘緰闂傚倸瀚幊宥囩礊閸涱垳纾? // 閻庡綊娼荤粻鎴﹀垂椤忓牆鍙?*, 婵炴垶鎼╅崢濂稿垂椤忓牆鍙?Auto, 闂佸憡鐟ラ崯鍧楀垂椤忓牆鍙?*
+ // 婵炴垶鎼╅崣鍐ㄎ涢崸妤€绀岄柛婵嗗閸樼敻鎮橀悙鍙夊櫢闁煎灚鍨垮浼村礈瑜嬫禒?
var centerLeft = (totalWidth - centerWidth) / 2;
var centerRight = centerLeft + centerWidth;
- // 安全间距(像素)
+ // 闁诲海鎳撻ˇ顖炲矗韫囨稒鈷掔痪鎯ь儑閻涒晠鏌ㄥ☉妯煎闁稿孩姘ㄥΣ鎰版偑閸涱垳顦?
const double safetyMargin = 20;
- // 检测左侧组件是否会与中间区域碰撞
- // 左侧组件右边界 = leftWidth
- // 中间区域左边界 = centerLeft
+ // 濠碘槅鍋€閸嬫挻绻涢弶鎴剰濞戞柨绻戠粭鐔活槾缂侇喖绉电粋鎺楁嚋閸倣锕傛煕濮樺墽绱扮紒杈╁缁嬪鎯斿┑濠傚箑闂傚倸鍊瑰娆戜焊椤栫偛鏄ラ柣鏂捐濞奸箖鏌? // 閻庡綊娼荤紓姘跺疾閸撲胶纾奸柛鏇ㄤ簼椤愪粙鏌涘▎蹇曟瀮缂佹梻鍠栭幃?= leftWidth
+ // 婵炴垶鎼╅崣鍐ㄎ涢崸妤€绀岄柛婵嗗閸樼數鈧綊娼荤粻鎺旂博閻斿吋鍋?= centerLeft
if (leftWidth + safetyMargin > centerLeft)
{
return true;
}
- // 检测右侧组件是否会与中间区域碰撞
- // 右侧组件左边界 = totalWidth - rightWidth
- // 中间区域右边界 = centerRight
+ // 濠碘槅鍋€閸嬫挻绻涢弶鎴剰鐟滄澘鎲$粭鐔活槾缂侇喖绉电粋鎺楁嚋閸倣锕傛煕濮樺墽绱扮紒杈╁缁嬪鎯斿┑濠傚箑闂傚倸鍊瑰娆戜焊椤栫偛鏄ラ柣鏂捐濞奸箖鏌? // 闂佸憡鐟ラ崢鏍疾閸撲胶纾奸柛鏇ㄤ簼椤愮晫鈧綊娼荤粻鎺旂博閻斿吋鍋?= totalWidth - rightWidth
+ // 婵炴垶鎼╅崣鍐ㄎ涢崸妤€绀岄柛婵嗗閸樼敻鏌涘▎蹇曟瀮缂佹梻鍠栭幃?= centerRight
if (totalWidth - rightWidth - safetyMargin < centerRight)
{
return true;
}
- // 检测中间区域是否会与左右两侧碰撞(中间区域过宽)
if (centerLeft < leftWidth + safetyMargin ||
centerRight > totalWidth - rightWidth - safetyMargin)
{
@@ -797,8 +786,7 @@ public partial class MainWindow
}
///
- /// 获取左侧面板占用的宽度(包括间距)
- ///
+ /// 闂佸吋鍎抽崲鑼躲亹閸パ屽晠闁挎梹瀵у▍鐘绘⒒閸稑鐏繝銏★耿瀹曪繝鎮╅崹顐f闂佹眹鍔岀€氼剟顢欓弮鈧幆鏃堟晜閼测晝顦╅梺鍛婄墪閹冲繒鈧凹鍙冨鑽ゅ鐎n剛宕洪梺? ///
private double GetLeftPanelOccupiedWidth()
{
if (TopStatusLeftPanel is null)
@@ -817,8 +805,7 @@ public partial class MainWindow
}
}
- // 添加间距
- if (visibleCount > 1)
+ // 濠电儑缍€椤曆勬叏閻愮儤鈷掔痪鎯ь儑閻? if (visibleCount > 1)
{
width += spacing * (visibleCount - 1);
}
@@ -827,8 +814,7 @@ public partial class MainWindow
}
///
- /// 获取中间面板占用的宽度(包括间距)
- ///
+ /// 闂佸吋鍎抽崲鑼躲亹閸ャ劎鈻旀い鎾卞灪閿涚喖姊婚崼娑樼仾婵犮垺锕㈠畷锟犳偐閸偅娈㈤梺姹囧妼鐎氼剟顢欓弮鈧幆鏃堟晜閼测晝顦╅梺鍛婄墪閹冲繒鈧凹鍙冨鑽ゅ鐎n剛宕洪梺? ///
private double GetCenterPanelOccupiedWidth()
{
if (TopStatusCenterPanel is null)
@@ -847,8 +833,7 @@ public partial class MainWindow
}
}
- // 添加间距
- if (visibleCount > 1)
+ // 濠电儑缍€椤曆勬叏閻愮儤鈷掔痪鎯ь儑閻? if (visibleCount > 1)
{
width += spacing * (visibleCount - 1);
}
@@ -857,8 +842,7 @@ public partial class MainWindow
}
///
- /// 获取右侧面板占用的宽度(包括间距)
- ///
+ /// 闂佸吋鍎抽崲鑼躲亹閸ヮ剙鐭楅柛蹇撴噺濞呯娀姊婚崼娑樼仾婵犮垺锕㈠畷锟犳偐閸偅娈㈤梺姹囧妼鐎氼剟顢欓弮鈧幆鏃堟晜閼测晝顦╅梺鍛婄墪閹冲繒鈧凹鍙冨鑽ゅ鐎n剛宕洪梺? ///
private double GetRightPanelOccupiedWidth()
{
if (TopStatusRightPanel is null)
@@ -877,8 +861,7 @@ public partial class MainWindow
}
}
- // 添加间距
- if (visibleCount > 1)
+ // 濠电儑缍€椤曆勬叏閻愮儤鈷掔痪鎯ь儑閻? if (visibleCount > 1)
{
width += spacing * (visibleCount - 1);
}
@@ -887,25 +870,19 @@ public partial class MainWindow
}
///
- /// 检查是否可以在指定位置添加组件
- ///
+ /// 濠碘槅鍋€閸嬫捇鏌$仦璇插姕婵″弶鎮傚畷銉╂晝閳ь剝銇愰崣澶岊浄闁靛鍎查煬顒勬煙缁嬫寧鎼愰柣锝囧亾閹峰懎顓奸崶鈺傜€┑鐑囩秬椤曆勬叏閻愮數纾奸柛鏇ㄤ簼椤? ///
private bool CanAddComponentAtPosition(string position)
{
- // 先临时显示组件以计算宽度
var wouldCollide = WouldComponentsCollide();
if (!wouldCollide)
return true;
- // 如果会发生碰撞,检查是否是因为目标位置导致的
- // 获取当前各区域宽度
var leftWidth = GetLeftPanelOccupiedWidth();
var centerWidth = GetCenterPanelOccupiedWidth();
var rightWidth = GetRightPanelOccupiedWidth();
- // 估算新组件的宽度(基于当前单元格大小)
var estimatedNewComponentWidth = _currentDesktopCellSize > 0 ? _currentDesktopCellSize * 2 : 120;
- // 根据目标位置检查添加后是否会碰撞
return position switch
{
"Left" => CanAddToLeft(leftWidth, centerWidth, rightWidth, estimatedNewComponentWidth),
@@ -924,7 +901,7 @@ public partial class MainWindow
if (totalWidth <= 0)
return true;
- var newLeftWidth = leftWidth + newWidth + TopStatusLeftPanel?.Spacing ?? 6;
+ var newLeftWidth = leftWidth + newWidth + (TopStatusLeftPanel?.Spacing ?? 6);
var centerLeft = (totalWidth - centerWidth) / 2;
const double safetyMargin = 20;
@@ -940,7 +917,7 @@ public partial class MainWindow
if (totalWidth <= 0)
return true;
- var newCenterWidth = centerWidth + newWidth + TopStatusCenterPanel?.Spacing ?? 6;
+ var newCenterWidth = centerWidth + newWidth + (TopStatusCenterPanel?.Spacing ?? 6);
var centerLeft = (totalWidth - newCenterWidth) / 2;
var centerRight = centerLeft + newCenterWidth;
@@ -958,7 +935,7 @@ public partial class MainWindow
if (totalWidth <= 0)
return true;
- var newRightWidth = rightWidth + newWidth + TopStatusRightPanel?.Spacing ?? 6;
+ var newRightWidth = rightWidth + newWidth + (TopStatusRightPanel?.Spacing ?? 6);
var centerRight = (totalWidth + centerWidth) / 2;
const double safetyMargin = 20;
@@ -970,7 +947,6 @@ public partial class MainWindow
var showClock = _topStatusComponentIds.Contains(BuiltInComponentIds.Clock);
var hasVisibleTopStatusComponent = false;
- // 先隐藏所有时钟控件
if (ClockWidgetLeft is not null)
ClockWidgetLeft.IsVisible = false;
if (ClockWidgetCenter is not null)
@@ -978,7 +954,6 @@ public partial class MainWindow
if (ClockWidgetRight is not null)
ClockWidgetRight.IsVisible = false;
- // 先隐藏所有文字胶囊控件
if (TextCapsuleWidgetLeft is not null)
TextCapsuleWidgetLeft.IsVisible = false;
if (TextCapsuleWidgetCenter is not null)
@@ -986,7 +961,6 @@ public partial class MainWindow
if (TextCapsuleWidgetRight is not null)
TextCapsuleWidgetRight.IsVisible = false;
- // 先隐藏所有网速控件
if (NetworkSpeedWidgetLeft is not null)
NetworkSpeedWidgetLeft.IsVisible = false;
if (NetworkSpeedWidgetCenter is not null)
@@ -994,7 +968,6 @@ public partial class MainWindow
if (NetworkSpeedWidgetRight is not null)
NetworkSpeedWidgetRight.IsVisible = false;
- // 根据位置设置显示对应的时钟控件(带碰撞检测)
if (showClock)
{
var targetPosition = _clockPosition;
@@ -1019,7 +992,6 @@ public partial class MainWindow
}
else
{
- // 如果目标位置无法添加,尝试其他位置
var alternativePosition = FindAlternativePosition(targetPosition);
if (alternativePosition is not null)
{
@@ -1041,8 +1013,7 @@ public partial class MainWindow
}
}
- // 根据位置设置显示对应的文字胶囊控件(带碰撞检测)
- if (_showTextCapsule)
+ // 闂佸搫绉烽~澶婄暤娴h濯寸€广儱娲ㄩ弸鍌炴偣娴g鈷旈柣銈呮瀵即宕滆娴犳盯鎮楅悽鍨殌缂併劍鐓¢幆鍐礋椤掍胶鈧噣鎮楀☉娆樻畽闁稿繐鐭傚畷鑸电節閸愩劋绮繛瀵稿Ь椤旀劗妲愬▎鎴炴殰闁挎梻铏庡楣冩煙閸撗冧沪妞ゃ儱鎳庨湁閻庯絽澧庣粈? if (_showTextCapsule)
{
var targetPosition = _textCapsulePosition;
var canAdd = CanAddComponentAtPosition(targetPosition);
@@ -1066,7 +1037,6 @@ public partial class MainWindow
}
else
{
- // 如果目标位置无法添加,尝试其他位置
var alternativePosition = FindAlternativePosition(targetPosition);
if (alternativePosition is not null)
{
@@ -1088,7 +1058,6 @@ public partial class MainWindow
}
}
- // 根据位置设置显示对应的网速控件(带碰撞检测)
if (_showNetworkSpeed)
{
var targetPosition = _networkSpeedPosition;
@@ -1113,7 +1082,6 @@ public partial class MainWindow
}
else
{
- // 如果目标位置无法添加,尝试其他位置
var alternativePosition = FindAlternativePosition(targetPosition);
if (alternativePosition is not null)
{
@@ -1140,7 +1108,6 @@ public partial class MainWindow
TopStatusBarHost.IsVisible = hasVisibleTopStatusComponent;
}
- // 延迟检查碰撞并调整
Dispatcher.UIThread.Post(async () =>
{
await System.Threading.Tasks.Task.Delay(50);
@@ -1149,22 +1116,18 @@ public partial class MainWindow
}
///
- /// 当组件发生碰撞时,自动调整位置
- ///
+ /// 閻熸粎澧楅幐鍓у垝瀹ュ棛顩烽悹鍝勬惈缁叉椽鏌i姀銏犳灁妞ゎ偒鍋婇獮姗€鎮欑€涙﹩妲梺鎸庣☉閻線宕靛鍫濈闁靛鍔庡▓鍫曟煛娴h櫣绡€缂傚秴鎳愮槐? ///
private void AdjustComponentsIfColliding()
{
if (!WouldComponentsCollide())
return;
- // 获取当前可见的组件
var leftComponents = GetVisibleLeftComponents();
var centerComponents = GetVisibleCenterComponents();
var rightComponents = GetVisibleRightComponents();
- // 优先保留时钟,调整文字胶囊位置
if (TextCapsuleWidgetLeft?.IsVisible == true && WouldComponentsCollide())
{
- // 尝试将左侧文字胶囊移到中间
if (CanAddComponentAtPosition("Center"))
{
TextCapsuleWidgetLeft.IsVisible = false;
@@ -1172,7 +1135,6 @@ public partial class MainWindow
TextCapsuleWidgetCenter.SetTransparentBackground(_textCapsuleTransparentBackground);
TextCapsuleWidgetCenter.SetText(_textCapsuleContent);
}
- // 或者移到右侧
else if (CanAddComponentAtPosition("Right"))
{
TextCapsuleWidgetLeft.IsVisible = false;
@@ -1180,7 +1142,6 @@ public partial class MainWindow
TextCapsuleWidgetRight.SetTransparentBackground(_textCapsuleTransparentBackground);
TextCapsuleWidgetRight.SetText(_textCapsuleContent);
}
- // 如果都无法添加,则隐藏文字胶囊
else
{
TextCapsuleWidgetLeft.IsVisible = false;
@@ -1189,7 +1150,6 @@ public partial class MainWindow
if (TextCapsuleWidgetRight?.IsVisible == true && WouldComponentsCollide())
{
- // 尝试将右侧文字胶囊移到中间
if (CanAddComponentAtPosition("Center"))
{
TextCapsuleWidgetRight.IsVisible = false;
@@ -1197,7 +1157,6 @@ public partial class MainWindow
TextCapsuleWidgetCenter.SetTransparentBackground(_textCapsuleTransparentBackground);
TextCapsuleWidgetCenter.SetText(_textCapsuleContent);
}
- // 或者移到左侧
else if (CanAddComponentAtPosition("Left"))
{
TextCapsuleWidgetRight.IsVisible = false;
@@ -1205,7 +1164,6 @@ public partial class MainWindow
TextCapsuleWidgetLeft.SetTransparentBackground(_textCapsuleTransparentBackground);
TextCapsuleWidgetLeft.SetText(_textCapsuleContent);
}
- // 如果都无法添加,则隐藏文字胶囊
else
{
TextCapsuleWidgetRight.IsVisible = false;
@@ -1214,7 +1172,6 @@ public partial class MainWindow
if (TextCapsuleWidgetCenter?.IsVisible == true && WouldComponentsCollide())
{
- // 尝试将中间文字胶囊移到左侧
if (CanAddComponentAtPosition("Left"))
{
TextCapsuleWidgetCenter.IsVisible = false;
@@ -1222,7 +1179,6 @@ public partial class MainWindow
TextCapsuleWidgetLeft.SetTransparentBackground(_textCapsuleTransparentBackground);
TextCapsuleWidgetLeft.SetText(_textCapsuleContent);
}
- // 或者移到右侧
else if (CanAddComponentAtPosition("Right"))
{
TextCapsuleWidgetCenter.IsVisible = false;
@@ -1230,17 +1186,14 @@ public partial class MainWindow
TextCapsuleWidgetRight.SetTransparentBackground(_textCapsuleTransparentBackground);
TextCapsuleWidgetRight.SetText(_textCapsuleContent);
}
- // 如果都无法添加,则隐藏文字胶囊
else
{
TextCapsuleWidgetCenter.IsVisible = false;
}
}
- // 调整网速组件位置(优先级:时钟 > 文字胶囊 > 网速)
if (NetworkSpeedWidgetLeft?.IsVisible == true && WouldComponentsCollide())
{
- // 尝试将左侧网速移到中间
if (CanAddComponentAtPosition("Center"))
{
NetworkSpeedWidgetLeft.IsVisible = false;
@@ -1248,7 +1201,6 @@ public partial class MainWindow
NetworkSpeedWidgetCenter.SetTransparentBackground(_networkSpeedTransparentBackground);
NetworkSpeedWidgetCenter.SetDisplayMode(_networkSpeedDisplayMode);
}
- // 或者移到右侧
else if (CanAddComponentAtPosition("Right"))
{
NetworkSpeedWidgetLeft.IsVisible = false;
@@ -1256,7 +1208,6 @@ public partial class MainWindow
NetworkSpeedWidgetRight.SetTransparentBackground(_networkSpeedTransparentBackground);
NetworkSpeedWidgetRight.SetDisplayMode(_networkSpeedDisplayMode);
}
- // 如果都无法添加,则隐藏网速
else
{
NetworkSpeedWidgetLeft.IsVisible = false;
@@ -1265,7 +1216,6 @@ public partial class MainWindow
if (NetworkSpeedWidgetRight?.IsVisible == true && WouldComponentsCollide())
{
- // 尝试将右侧网速移到中间
if (CanAddComponentAtPosition("Center"))
{
NetworkSpeedWidgetRight.IsVisible = false;
@@ -1273,7 +1223,6 @@ public partial class MainWindow
NetworkSpeedWidgetCenter.SetTransparentBackground(_networkSpeedTransparentBackground);
NetworkSpeedWidgetCenter.SetDisplayMode(_networkSpeedDisplayMode);
}
- // 或者移到左侧
else if (CanAddComponentAtPosition("Left"))
{
NetworkSpeedWidgetRight.IsVisible = false;
@@ -1281,7 +1230,6 @@ public partial class MainWindow
NetworkSpeedWidgetLeft.SetTransparentBackground(_networkSpeedTransparentBackground);
NetworkSpeedWidgetLeft.SetDisplayMode(_networkSpeedDisplayMode);
}
- // 如果都无法添加,则隐藏网速
else
{
NetworkSpeedWidgetRight.IsVisible = false;
@@ -1290,7 +1238,6 @@ public partial class MainWindow
if (NetworkSpeedWidgetCenter?.IsVisible == true && WouldComponentsCollide())
{
- // 尝试将中间网速移到左侧
if (CanAddComponentAtPosition("Left"))
{
NetworkSpeedWidgetCenter.IsVisible = false;
@@ -1298,7 +1245,6 @@ public partial class MainWindow
NetworkSpeedWidgetLeft.SetTransparentBackground(_networkSpeedTransparentBackground);
NetworkSpeedWidgetLeft.SetDisplayMode(_networkSpeedDisplayMode);
}
- // 或者移到右侧
else if (CanAddComponentAtPosition("Right"))
{
NetworkSpeedWidgetCenter.IsVisible = false;
@@ -1306,7 +1252,6 @@ public partial class MainWindow
NetworkSpeedWidgetRight.SetTransparentBackground(_networkSpeedTransparentBackground);
NetworkSpeedWidgetRight.SetDisplayMode(_networkSpeedDisplayMode);
}
- // 如果都无法添加,则隐藏网速
else
{
NetworkSpeedWidgetCenter.IsVisible = false;
@@ -1315,11 +1260,10 @@ public partial class MainWindow
}
///
- /// 查找可用的替代位置
- ///
+ /// 闂佸搫琚崕鍙夌珶濮椻偓瀹曪綁顢涘鍕闂佹眹鍔岀€氼厼霉濞戞瑧顩烽柨婵嗗缁夊绱? ///
private string? FindAlternativePosition(string originalPosition)
{
- // 尝试所有可能的位置
+ // 闁诲繐绻戠换鍡涙儊椤栫偛绠ラ柍褜鍓熷鍨緞婵犲倽顔夐梺鐓庣-閺咁偄鈻撻幋鐐村鐎广儱娲ㄩ弸?
var positions = new[] { "Left", "Center", "Right" };
foreach (var position in positions)
{
@@ -1332,8 +1276,7 @@ public partial class MainWindow
}
///
- /// 获取左侧可见组件列表
- ///
+ /// 闂佸吋鍎抽崲鑼躲亹閸パ屽晠闁挎梹瀵у▍鐘绘煕濞嗘ê鐏ユい顐㈡缁辨帡宕熼鍜佸仺闂佸憡甯楅〃澶愬Υ? ///
private List GetVisibleLeftComponents()
{
var result = new List();
@@ -1348,8 +1291,7 @@ public partial class MainWindow
}
///
- /// 获取中间可见组件列表
- ///
+ /// 闂佸吋鍎抽崲鑼躲亹閸ャ劎鈻旀い鎾卞灪閿涚喖鏌涘▎妯虹仴妞ゎ偄妫涚槐鎺楀礋椤忓拋鍋ㄩ梺鍛婂笚椤ㄥ濡? ///
private List GetVisibleCenterComponents()
{
var result = new List();
@@ -1364,8 +1306,7 @@ public partial class MainWindow
}
///
- /// 获取右侧可见组件列表
- ///
+ /// 闂佸吋鍎抽崲鑼躲亹閸ヮ剙鐭楅柛蹇撴噺濞呯娀鏌涘▎妯虹仴妞ゎ偄妫涚槐鎺楀礋椤忓拋鍋ㄩ梺鍛婂笚椤ㄥ濡? ///
private List GetVisibleRightComponents()
{
var result = new List();
@@ -1833,17 +1774,17 @@ public partial class MainWindow
return;
}
- var dialog = new ContentDialog
+ var dialog = new FAContentDialog
{
- Title = L("desktop.delete_page_confirm.title", "确认删除页面"),
- Content = L("desktop.delete_page_confirm.message", "确定要删除当前页面吗?\n\n此操作将删除当前页面上的所有组件,且无法撤销。"),
- PrimaryButtonText = L("desktop.delete_page_confirm.close", "取消"),
- SecondaryButtonText = L("desktop.delete_page_confirm.primary", "删除"),
- DefaultButton = ContentDialogButton.Primary
+ Title = L("desktop.delete_page_confirm.title", "Delete desktop page"),
+ Content = L("desktop.delete_page_confirm.message", "This will permanently remove the current desktop page and all widgets placed on it.\n\nThis action cannot be undone."),
+ PrimaryButtonText = L("desktop.delete_page_confirm.close", "Cancel"),
+ SecondaryButtonText = L("desktop.delete_page_confirm.primary", "Delete"),
+ DefaultButton = FAContentDialogButton.Primary
};
var result = await dialog.ShowAsync(this);
- if (result == ContentDialogResult.Secondary)
+ if (result == FAContentDialogResult.Secondary)
{
DeleteCurrentDesktopPage();
}
@@ -3938,7 +3879,7 @@ public partial class MainWindow
}
var point = e.GetPosition(ComponentLibraryWindow);
- if (point.Y > 40) // 閺嶅洭顣介弽蹇涚彯鎼达妇瀹虫稉?0px
+ if (point.Y > 40) // 闂傚倷绀侀幖顐ょ矓閺夋嚚娲敇椤兘鍋撻崒娑氼浄閻庯綆浜滈崬銊╂椤愩垺澶勭紒瀣崄閵囨劙顢涢悙鑼啇闁哄鐗婇崕鎶姐€呴鍕€电痪顓炴噺閻濐亞绱?0px
{
return;
}
diff --git a/LanMountainDesktop/Views/MainWindow.DesktopEditing.cs b/LanMountainDesktop/Views/MainWindow.DesktopEditing.cs
index 2d0f65f..68f4447 100644
--- a/LanMountainDesktop/Views/MainWindow.DesktopEditing.cs
+++ b/LanMountainDesktop/Views/MainWindow.DesktopEditing.cs
@@ -12,7 +12,7 @@ using LanMountainDesktop.Theme;
namespace LanMountainDesktop.Views;
-public partial class MainWindow
+public partial class MainWindow : Window
{
private static readonly TimeSpan DesktopEditCommitAnimationDuration = FluttermotionToken.Standard;
private static readonly TimeSpan DesktopEditCancelAnimationDuration = FluttermotionToken.Fast;
diff --git a/LanMountainDesktop/Views/MainWindow.DesktopPaging.cs b/LanMountainDesktop/Views/MainWindow.DesktopPaging.cs
index 33efce2..83a0204 100644
--- a/LanMountainDesktop/Views/MainWindow.DesktopPaging.cs
+++ b/LanMountainDesktop/Views/MainWindow.DesktopPaging.cs
@@ -22,7 +22,7 @@ using LanMountainDesktop.Theme;
namespace LanMountainDesktop.Views;
-public partial class MainWindow
+public partial class MainWindow : Window
{
private const int MinDesktopPageCount = 1;
private const int MaxDesktopPageCount = 12;
@@ -75,7 +75,7 @@ public partial class MainWindow
private int? _desktopPageContextSettlingTargetIndex;
private int _desktopPageContextSettleRevision;
- // 三指滑动/右键拖动相关
+ // 婵犵數鍋為崹鍫曞箰閹间絸鍥箥椤旂懓浜鹃柛顭戝亯婢规ɑ銇勯婊冨妤犵偛顑呴埞鎴﹀窗?闂傚倷绀侀幉锟犳偡閿旂晫绠惧┑鐘叉搐閺嬩焦銇勯幘鍗炵仼缂佺媭鍨堕弻鈥崇暤椤旂厧鏁俊銈呮噺閻撶喖鏌嶉崫鍕灓闁绘帡绠栭弻?
private bool _isThreeFingerOrRightDragSwipeActive;
private readonly HashSet _activePointerIds = [];
@@ -264,7 +264,6 @@ public partial class MainWindow
Grid.SetColumn(LauncherPagePanel, 1);
Grid.SetRow(LauncherPagePanel, 0);
- // 为启动台添加安全边距以确保圆角不被裁剪
var launcherMargin = Math.Clamp(gridMetrics.CellSize * 0.15, 6, 16);
LauncherPagePanel.Margin = new Thickness(launcherMargin);
LauncherPagePanel.Width = Math.Max(1, pageWidth - launcherMargin * 2);
@@ -272,7 +271,7 @@ public partial class MainWindow
LauncherPagePanel.MaxWidth = pageWidth - launcherMargin * 2;
LauncherPagePanel.MaxHeight = pageHeight - launcherMargin * 2;
- // 更新启动台图标布局
+ // 闂傚倷绀侀幖顐⒚洪妶澶嬪仱闁靛ň鏅涢拑鐔封攽閻樺弶鎼愰悷娆欓檮閵囧嫰寮介妸銊ヮ棟閻炴氨鍠栧娲川婵犲嫭鍣┑鐘灪閿氶棁澶嬫叏濡炶浜鹃悗娈垮枙缁瑥鐣烽幆閭︽Ь濡炪倕绻戦幐鎶藉箖濮椻偓閹瑩鍩℃担宄邦棜
UpdateLauncherTileLayout();
_desktopSurfacePageWidth = pageWidth;
@@ -287,38 +286,29 @@ public partial class MainWindow
return;
}
- // 获取启动台面板的实际可用宽度(减去Padding)
var availableWidth = Math.Max(1, LauncherPagePanel.Bounds.Width - 36); // 18px padding on each side
- var availableHeight = Math.Max(1, LauncherPagePanel.Bounds.Height - 100); // 预留标题空间
+ var availableHeight = Math.Max(1, LauncherPagePanel.Bounds.Height - 100); // 婵犵妲呴崑鍛熆濡皷鍋撳鐓庢珝鐎殿喗濞婇崺鈧い鎺戝閻撴稓鈧箍鍎遍幊蹇涘窗濡眹浜滈柨婵嗘处濞呮洜绱掗鍊熷閻撱倖銇勮箛鎾村珔缂?
if (availableWidth <= 1 || availableHeight <= 1)
{
- // 如果尺寸还未计算,使用默认值
- availableWidth = 600;
+ // 婵犵數濮烽。浠嬪焵椤掆偓閸熷潡鍩€椤掆偓缂嶅﹪骞冨Ο璇茬窞閻忕偠鍋愰崜銊╂⒑閸涘﹦绠撻悗姘卞厴瀹曠敻鎮㈤悡搴i獓闂佸啿鎼导鎺楀箣濠垫捁鈧寧銇勯幘璺盒e┑顖氥偢閺屻劌鈽夊Ο渚紑闂佸搫妫崜鐔煎蓟閵娿儮妲堟俊顖欒濞堫厽绻濋悽闈涗粶婵炲樊鍙冮獮鍐╃鐎n€晠鏌嶉崫鍕殭缂佹绻濋弻锝夋偐闁秵顎栭梺绋匡攻濞茬喖宕洪埀? availableWidth = 600;
availableHeight = 400;
}
- // 计算最佳图标尺寸
- // 目标:每行显示4-8个图标,根据屏幕宽度调整
+ // 闂備浇宕垫慨宕囨閵堝洦顫曢柡鍥ュ灪閸嬧晛鈹戦悩瀹犲閻庢艾顦甸弻宥堫檨闁告挻宀搁獮蹇涘川閺夋垹顦ㄩ梺鍛婄懃椤﹂亶銆呴銏♀拺闁告繂瀚瓭濠电偛鐪伴崐婵嗩嚕娴兼潙纾兼繝褎鍎虫禍? // 闂傚倷鑳堕崕鐢稿疾閳哄懎绐楁俊銈呮噺閸嬪鏌ㄥ┑鍡╂Ч闁哄拋鍓氶幈銊ヮ潨閸℃绠诲┑鈥崇湴閸旀垿骞冪捄琛℃婵☆垳绮幏鍗炩攽閳藉棗鐏犳い锕佷含閸?-8婵犵數鍋為崹鍫曞箹閳哄倻顩叉繝濠傚幘閻熼偊娼ㄩ柍褜鍓欓锝嗙鐎n亞鍊炴俊鐐差儏濞寸兘藝椤曗偓濮婃椽宕崟顓夈儲銇勯銏╂Ц闁伙絽鐏氶幏鍛姜閻楀牆濯伴梻濠庡亜濞诧箓骞愭ィ鍐炬晩閹兼番鍔嶉崐鐢电棯椤撶偞鍣烘い銉ヮ樀閹鎮烽幍顕嗙礊闂佺懓顨庨崑濠傜暦濮椻偓閸╋繝宕掑☉鍗炴櫔
const int minColumns = 4;
const int maxColumns = 8;
- const double targetAspectRatio = 1.2; // 图标宽高比
-
- // 计算每列可以显示的图标数量
+ const double targetAspectRatio = 1.2; // 闂傚倷鐒﹂幃鍫曞磿閹惰棄纾婚柟鍓х帛閸嬪鏌ㄥ┑鍡樼闁稿鎹囬弻鍛槈濮樿京鍘梻浣虹帛缁诲秹宕伴弽顒夋毎?
var optimalColumnCount = Math.Clamp((int)Math.Floor(availableWidth / 120), minColumns, maxColumns);
- // 根据列数计算图标尺寸
var tileWidth = Math.Floor(availableWidth / optimalColumnCount) - 12; // 12px spacing
- var tileHeight = Math.Min(tileWidth / targetAspectRatio, availableHeight / 4); // 至少显示4行
-
- // 确保最小尺寸
- tileWidth = Math.Max(tileWidth, 100);
+ var tileHeight = Math.Min(tileWidth / targetAspectRatio, availableHeight / 4); // 闂傚倷鑳堕崢褔宕查弻銉ョ柈闁秆勵殕閸庡秵銇勯弽顐粶闁告瑥锕弻娑㈠箻濡炵偓顦风紒?闂?
+ // 缂傚倷鑳堕搹搴ㄥ矗鎼淬劌绐楅柡鍥╁У瀹曞弶鎱ㄥΟ鎸庣【閻庢艾顦甸弻宥堫檨闁告挻绋掔粋宥咁潰瀹€鈧悿鈧梺瑙勫劤閻°劑锝為崨瀛樼厽? tileWidth = Math.Max(tileWidth, 100);
tileHeight = Math.Max(tileHeight, 80);
- // 更新WrapPanel的Item尺寸
- LauncherRootTilePanel.Width = availableWidth;
+ // 闂傚倷绀侀幖顐⒚洪妶澶嬪仱闁靛ň鏅涢拑鐔封攽閸屻倖杈渁pPanel闂傚倷鐒﹂惇褰掑礉瀹€鍕惞婵帞妫渕闂備浇顕х换鎰崲閹版澘绠规い鎰跺瘜閺? LauncherRootTilePanel.Width = availableWidth;
- // 更新所有子元素的尺寸
+ // 闂傚倷绀侀幖顐⒚洪妶澶嬪仱闁靛ň鏅涢拑鐔封攽閻樺弶鎼愮紒鐘劦閺屽秷顧侀柛鎾跺枎椤曪綁宕归銏㈢獮婵犵數濮寸€氼參骞夐妶澶嬧拺缂佸娉曠粻浼存煕閻旂顥嬬紒顔肩墕閻f繈宕熼鈧崜顓㈡⒑閸涘﹥澶勯柛瀣噹鍗遍柍褜鍓熼弻?
foreach (var child in LauncherRootTilePanel.Children)
{
if (child is Button button)
@@ -487,7 +477,7 @@ public partial class MainWindow
return;
}
- // 如果在组件编辑模式下点击空白区域,取消选中(组件或启动台图标)
+ // 婵犵數濮烽。浠嬪焵椤掆偓閸熷潡鍩€椤掆偓缂嶅﹪骞冨Ο璇茬窞闁归偊鍓氬畵宥夋⒑闂堟丹娑㈠川椤栨粌甯掓繝鐢靛仜椤曨厽鎱ㄧ€涙ɑ娅犻幖杈剧稻椤洘銇勮箛鎾村櫤缂傚秴娲弻鐔衡偓鐢告櫜鏉╃懓霉閿濆懎顥忛柛銈嗘礋閻擃偊宕惰閹癸綁鏌i悢鍛婂磳闁哄矉缍侀獮鍥敊閽樺鐣梻浣规偠閸娿倝宕板鍗炲灊婵鍩栭幆鐐烘偡濞嗗繐顏村ù鐘讳憾濮婃椽宕ㄦ繝鍕吂闂佸湱鈷堥崑濠囧箖閳ユ枼鏋庨柟鎯х摠濞呮牠鏌h箛鏇炰哗婵☆偄瀚濠囧箰鎼达絿顔曢梺鐟扮摠缁诲嫭鏅堕敃鍌涚厓鐟滄粓宕滃▎鎾嶅洭顢氶埀顒勫箠濞嗘挸绠i柨鏃囧Г濞呮牠姊洪崜鎻掍簴闁搞劌顭烽幆宀€鈧綆鈧垹缍婇幃鈺呭传閸曨厼甯块梻浣规偠閸斿﹪宕濋幋婵堟殾闁靛鏅╅弫宥嗘叏濮楀棗鍔俊銈呮噺閻撴洘绻涢崱妯哄缂佽泛寮剁换娑氣偓娑欙公閼拌法鈧鍠曠划娆忕暦閼告妲归幖杈剧秵濡?
if (_isComponentLibraryOpen &&
(_selectedDesktopComponentHost is not null || _selectedLauncherTileButton is not null))
{
@@ -504,7 +494,6 @@ public partial class MainWindow
return;
}
- // 检查三指滑动功能是否启用
var appSnapshot = _settingsFacade.Settings.LoadSnapshot(SettingsScope.App);
var isThreeFingerSwipeEnabled = appSnapshot.EnableThreeFingerSwipe;
@@ -513,23 +502,20 @@ public partial class MainWindow
var isRightButtonPressed = currentPoint.Properties.IsRightButtonPressed;
var isLeftButtonPressed = currentPoint.Properties.IsLeftButtonPressed;
- // 处理三指滑动/右键拖动模式
+ // 婵犵數濮伴崹鐓庘枖濞戞埃鍋撳鐓庢珝妤犵偛鍟换婵嬪礃椤忎焦鐏冨┑鐘灱濞夋盯顢栭崨瀛樺剨閻熸瑥瀚弧鈧繝鐢靛Т閸燁偊鎮橀妷銉㈡斀?闂傚倷绀侀幉锟犳偡閿旂晫绠惧┑鐘叉搐閺嬩焦銇勯幘鍗炵仼缂佺媭鍨堕弻鈥崇暤椤旂厧鏁俊銈勬缁诲棙銇勯弽銊d粶闁稿鎸搁悾鐑藉炊閳哄﹥鏁?
if (isThreeFingerSwipeEnabled)
{
- // 跟踪活跃指针
if (isLeftButtonPressed || isRightButtonPressed)
{
_activePointerIds.Add(pointerId);
}
- // 判断是否是三指滑动或右键拖动
var isThreeFinger = _activePointerIds.Count >= 3;
var isRightDrag = isRightButtonPressed;
if (isThreeFinger || isRightDrag)
{
- // 三指/右键拖动模式:跳过所有组件交互检查,直接开始滑动
- ClearDesktopPageContextSettle(refreshContext: false);
+ // 婵犵數鍋為崹鍫曞箰閹间絸鍥箥椤旂懓浜?闂傚倷绀侀幉锟犳偡閿旂晫绠惧┑鐘叉搐閺嬩焦銇勯幘鍗炵仼缂佺媭鍨堕弻鈥崇暤椤旂厧鏁俊銈勬缁诲棙銇勯弽銊d粶闁稿鎸搁悾鐑藉炊閳哄﹥鏁ら梻鍌欑劍鐎笛呯矙閹烘挾鈹嶆繛宸簼閸婂鏌ㄩ弮鍥撳ù婧垮€濋弻娑㈠Ψ閿濆懎顬堝銈忕稻閻擄繝寮婚敓鐘查唶婵犲灚鍔栨缂傚倷绶¢崰鏍矓閻㈢數鐭夐柟鐑橆殔鐎氬鏌涢…鎴濅簻闁衡偓椤撶喓绠鹃悗娑欘焽閻鎮介娑辨疁閽樼喖鏌涘☉娆愮稇闁藉啰鍠栭弻鏇熷緞濡櫣浠紓浣插亾濠㈣埖鍔栭悡鐔兼煃鏉炴媽鍏岄柟鐣屽█閹粙顢涘☉娆戠▏濡炪倖娲╃紞渚€宕洪埀顒併亜閹哄秶鍔嶉柛娆忕箻閹鏁愭惔鈥茬敖闂佽鐏氶崝鎴﹀蓟? ClearDesktopPageContextSettle(refreshContext: false);
_isThreeFingerOrRightDragSwipeActive = true;
_isDesktopSwipeActive = true;
_isDesktopSwipeDirectionLocked = false;
@@ -540,14 +526,12 @@ public partial class MainWindow
_desktopSwipeLastTimestamp = Stopwatch.GetTimestamp();
_desktopSwipeBaseOffset = -_currentDesktopSurfaceIndex * _desktopSurfacePageWidth;
- // 标记事件已处理,防止组件响应
- e.Handled = true;
+ // 闂傚倷绀侀幖顐ょ矓閺夋嚚娲煛閸滀焦鏅╅梺鎼炲劘閸斿酣銆呴弻銉﹀€甸柨婵嗗€瑰▍鍡樸亜閹邦喗娅曢柍褜鍓涢幊鎾诲箟闄囬妵鎰板礃椤斻垹娲崺锟犲川椤旈棿鍝楅梻浣虹《濡插懘宕㈤崜褏鐭嗗鑸靛姈閳锋帡鏌涢幇鈺佸缂佺嫏鍕╀簻闁圭儤鎸鹃妴鎺旂磼鏉堛劌娴€规洜鍠栭、鏃堝椽娴i晲缂撻梻鍌欑閹诧紕鎹㈤崒婊呯煋閻庡灚鐡曟慨? e.Handled = true;
return;
}
}
- // 原有单指滑动逻辑
- if (IsInteractivePointerSource(e.Source))
+ // 闂傚倷绀侀幉锟犫€﹂崶顒€绐楅柟閭﹀墾閼板灝銆掑锝呬壕閻庤娲╃换婵嗩嚕閹绢喗鍋勫瀣閳诲本绻濋悽闈浶㈤柨鏇樺劦瀹曞綊宕归锝呭伎闂佸啿鎼幊蹇涙倿婵犳碍鐓涢柛鏇ㄥ亞缁犳娊鎮? if (IsInteractivePointerSource(e.Source))
{
return;
}
@@ -811,7 +795,6 @@ public partial class MainWindow
private void OnDesktopPagesPointerReleased(object? sender, PointerReleasedEventArgs e)
{
- // 清理活跃指针
var pointerId = e.Pointer?.Id ?? 0;
_activePointerIds.Remove(pointerId);
@@ -823,7 +806,6 @@ public partial class MainWindow
private void OnDesktopPagesPointerCaptureLost(object? sender, PointerCaptureLostEventArgs e)
{
- // 清理活跃指针
var pointerId = e.Pointer?.Id ?? 0;
_activePointerIds.Remove(pointerId);
@@ -898,13 +880,12 @@ public partial class MainWindow
var hasDistanceIntent = absDeltaX >= distanceThreshold && absDeltaX > absDeltaY * 1.05;
var hasVelocityIntent = Math.Abs(_desktopSwipeVelocityX) >= velocityThreshold;
- // 检查:三指/右键拖动 && 在第一页 && 向右滑动
+ // 濠电姷顣藉Σ鍛村磻閳ь剟鏌涚€n偅宕岄柡宀嬬磿娴狅妇鎷犻幓鎺懶ョ紓鍌欐祰娴滎剚鏅跺Δ鍐煓濠㈣泛顑呯欢鐐烘倵閿濆簼绨芥俊?闂傚倷绀侀幉锟犳偡閿旂晫绠惧┑鐘叉搐閺嬩焦銇勯幘鍗炵仼缂佺媭鍨堕弻鈥崇暤椤旂厧鏁?&& 闂傚倷绶氬鑽ゆ嫻閻旂厧绀夐幖鎼厛閺佸嫰鏌涢妷锝呭闁崇粯妫冮弻宥堫檨闁告挻宀告俊?&& 闂傚倷绀侀幉锛勫枈瀹ュ鍨傚ù锝呭暔娴滃湱绱掔€n偒鍎ラ柣鎾卞劦閺岀喓鈧稒顭囩粻鎾舵偖?
if (wasThreeFingerOrRightDrag &&
_currentDesktopSurfaceIndex == 0 &&
- deltaX > 0 && // 向右滑动
+ deltaX > 0 && // 闂傚倷绀侀幉锛勫枈瀹ュ鍨傚ù锝呭暔娴滃湱绱掔€n偒鍎ラ柣鎾卞劦閺岀喓鈧稒顭囩粻鎾舵偖?
(hasDistanceIntent || hasVelocityIntent))
{
- // 最小化到 Windows 桌面
if (Application.Current is App app)
{
app.HideMainWindowToTray(this, "ThreeFingerOrRightDragSwipe");
@@ -998,8 +979,7 @@ public partial class MainWindow
string.Empty));
}
- // 在图标渲染完成后,应用布局计算
- Dispatcher.UIThread.Post(() => UpdateLauncherTileLayout(), DispatcherPriority.Background);
+ // 闂傚倷绶氬鑽ゆ嫻閻旂厧绀夐悘鐐电叓閻熼偊娼ㄩ柍褜鍓欓锝嗙鐎n亞鍊為梺闈涱煬閻撳牆煤椤掑嫭鈷戦柛婵嗗濠€浼存煙閸涘﹥鍊愰柟顕€绠栭、妤呭礋椤愩値鍚呴梻浣哥秺閸嬪﹪宕滃璺虹9闁汇垹鎲¢悡銉︾箾閹寸儐鐒鹃悗姘缁辨帡濡搁敂鎯у绩闂佽鍠曠划娆愪繆閹间礁唯鐟滄粍瀵煎畝鍕厽闊洦娲栨禍褰掓煕鐎n偅宕岄柟顔款潐缁楃喐绻濋崟顓ㄧ吹闂? Dispatcher.UIThread.Post(() => UpdateLauncherTileLayout(), DispatcherPriority.Background);
}
private Button CreateLauncherFolderTile(StartMenuFolderNode folder)
@@ -1063,8 +1043,7 @@ public partial class MainWindow
BorderThickness = new Thickness(0),
Margin = new Thickness(0, 0, 12, 12),
CornerRadius = new CornerRadius(20),
- Child = panel
- // 不设置固定 Width 和 Height,由 UpdateLauncherTileLayout 动态设置
+ Child = panel,
};
}
@@ -1145,11 +1124,8 @@ public partial class MainWindow
BorderBrush = Brushes.Transparent,
CornerRadius = new CornerRadius(20),
Padding = new Thickness(10),
- Content = content
- // 不设置固定 Width 和 Height,由 UpdateLauncherTileLayout 动态设置
+ Content = content,
};
-
- // 根据设置决定是否显示背景
if (_showLauncherTileBackground)
{
button.Classes.Add("glass-panel");
@@ -1415,7 +1391,7 @@ public partial class MainWindow
: fileName;
}
- private SettingsExpanderItem CreateLauncherHiddenItemRow(LauncherHiddenItemView hiddenItem)
+ private FASettingsExpanderItem CreateLauncherHiddenItemRow(LauncherHiddenItemView hiddenItem)
{
var typeText = hiddenItem.Kind == LauncherEntryKind.Folder
? L("settings.launcher.hidden_type_folder", "Folder")
@@ -1430,7 +1406,7 @@ public partial class MainWindow
BorderThickness = new Thickness(0),
Tag = new LauncherHiddenItemToken(hiddenItem.Kind, hiddenItem.Key)
};
- restoreButton.Content = new FluentIcons.Avalonia.Fluent.SymbolIcon
+ restoreButton.Content = new FluentIcons.Avalonia.SymbolIcon
{
Symbol = FluentIcons.Common.Symbol.Eye,
IconVariant = FluentIcons.Common.IconVariant.Regular,
@@ -1441,7 +1417,7 @@ public partial class MainWindow
ToolTip.SetTip(restoreButton, L("settings.launcher.restore_button", "Unhide"));
restoreButton.Click += OnRestoreLauncherHiddenItemClick;
- return new SettingsExpanderItem
+ return new FASettingsExpanderItem
{
Content = hiddenItem.DisplayName,
Description = typeText,
@@ -1451,23 +1427,17 @@ public partial class MainWindow
};
}
- private IconSource CreateLauncherHiddenItemIconSource(LauncherHiddenItemView hiddenItem)
+ private FAIconSource? CreateLauncherHiddenItemIconSource(LauncherHiddenItemView hiddenItem)
{
if (hiddenItem.IconBitmap is not null)
{
- return new ImageIconSource
+ return new FAImageIconSource
{
Source = hiddenItem.IconBitmap
};
}
- return new FluentIcons.Avalonia.Fluent.SymbolIconSource
- {
- Symbol = hiddenItem.Kind == LauncherEntryKind.Folder
- ? FluentIcons.Common.Symbol.Folder
- : FluentIcons.Common.Symbol.Apps,
- IconVariant = FluentIcons.Common.IconVariant.Regular
- };
+ return null;
}
private void OnRestoreLauncherHiddenItemClick(object? sender, RoutedEventArgs e)
@@ -1696,7 +1666,7 @@ public partial class MainWindow
Content = content
};
- // 根据设置决定是否显示背景
+ // 闂傚倷绀侀幖顐ょ矓閻戞枻缍栧璺猴功閺嗐倕霉閿濆洤鍔嬪┑顖氥偢閺屾盯骞樺Δ鈧幊蹇涙倵椤撱垺鈷戦柛娑橈工婵洭鏌涢悢閿嬪仴闁诡喚鍋撻妶锝夊礃閵娿儱鎸ゆ俊鐐€栭悧妤冨枈瀹ュ纾垮┑鐘叉处閻撴盯鏌涢弴銊ヤ簻闁抽攱妫冮弻鏇㈠炊閵娿儱鎽甸梺纭呮珪椤ㄥ牊绂掗敃鍌涘€锋い鎺戝€哥拋?
if (_showLauncherTileBackground)
{
button.Classes.Add("glass-panel");
@@ -1775,7 +1745,7 @@ public partial class MainWindow
Content = content
};
- // 根据设置决定是否显示背景
+ // 闂傚倷绀侀幖顐ょ矓閻戞枻缍栧璺猴功閺嗐倕霉閿濆洤鍔嬪┑顖氥偢閺屾盯骞樺Δ鈧幊蹇涙倵椤撱垺鈷戦柛娑橈工婵洭鏌涢悢閿嬪仴闁诡喚鍋撻妶锝夊礃閵娿儱鎸ゆ俊鐐€栭悧妤冨枈瀹ュ纾垮┑鐘叉处閻撴盯鏌涢弴銊ヤ簻闁抽攱妫冮弻鏇㈠炊閵娿儱鎽甸梺纭呮珪椤ㄥ牊绂掗敃鍌涘€锋い鎺戝€哥拋?
if (_showLauncherTileBackground)
{
button.Classes.Add("glass-panel");
diff --git a/LanMountainDesktop/Views/MainWindow.RenderBackend.cs b/LanMountainDesktop/Views/MainWindow.RenderBackend.cs
index aef13b9..504968e 100644
--- a/LanMountainDesktop/Views/MainWindow.RenderBackend.cs
+++ b/LanMountainDesktop/Views/MainWindow.RenderBackend.cs
@@ -1,9 +1,10 @@
using System;
+using Avalonia.Controls;
using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views;
-public partial class MainWindow
+public partial class MainWindow : Window
{
private void UpdateCurrentRenderBackendStatus()
{
diff --git a/LanMountainDesktop/Views/MainWindow.SettingsHardCut.Stubs.cs b/LanMountainDesktop/Views/MainWindow.SettingsHardCut.Stubs.cs
index a0a3772..e44b96b 100644
--- a/LanMountainDesktop/Views/MainWindow.SettingsHardCut.Stubs.cs
+++ b/LanMountainDesktop/Views/MainWindow.SettingsHardCut.Stubs.cs
@@ -20,13 +20,13 @@ using LanMountainDesktop.Views.Components;
namespace LanMountainDesktop.Views;
-public partial class MainWindow
+public partial class MainWindow : Window
{
private TextBlock? CurrentRenderBackendLabelTextBlock => this.FindControl("CurrentRenderBackendLabelTextBlock");
private TextBlock? CurrentRenderBackendValueTextBlock => this.FindControl("CurrentRenderBackendValueTextBlock");
private TextBlock? CurrentRenderBackendImplementationTextBlock => this.FindControl("CurrentRenderBackendImplementationTextBlock");
private ComboBox? TimeZoneComboBox => this.FindControl("TimeZoneComboBox");
- private SettingsExpander? LauncherHiddenItemsSettingsExpander => this.FindControl("LauncherHiddenItemsSettingsExpander");
+ private FASettingsExpander? LauncherHiddenItemsSettingsExpander => this.FindControl("LauncherHiddenItemsSettingsExpander");
private TextBlock? LauncherHiddenItemsEmptyTextBlock => this.FindControl("LauncherHiddenItemsEmptyTextBlock");
private void OnSettingsChanged(object? sender, SettingsChangedEvent e)
@@ -38,13 +38,12 @@ public partial class MainWindow
return;
}
- // 组件实例范围的设置变更不应触发整个桌面重新加载(比如翻页保存图片索引)
- if (e.Scope == SettingsScope.ComponentInstance)
+ // 缁勪欢瀹炰緥鑼冨洿鐨勮缃彉鏇翠笉搴旇Е鍙戞暣涓闈㈤噸鏂板姞杞斤紙姣斿缈婚〉淇濆瓨鍥剧墖绱㈠紩锛? if (e.Scope == SettingsScope.ComponentInstance)
{
return;
}
- // 启动台设置变化时,重新渲染启动台图标
+ // 鍚姩鍙拌缃彉鍖栨椂锛岄噸鏂版覆鏌撳惎鍔ㄥ彴鍥炬爣
if (e.Scope == SettingsScope.Launcher && e.ChangedKeys is { Count: > 0 })
{
var changedKeys = e.ChangedKeys.ToArray();
diff --git a/LanMountainDesktop/Views/MainWindow.SingleInstanceNotice.cs b/LanMountainDesktop/Views/MainWindow.SingleInstanceNotice.cs
index 02c5ba4..4c5eb1b 100644
--- a/LanMountainDesktop/Views/MainWindow.SingleInstanceNotice.cs
+++ b/LanMountainDesktop/Views/MainWindow.SingleInstanceNotice.cs
@@ -1,11 +1,12 @@
using System.Threading.Tasks;
+using Avalonia.Controls;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views;
-public partial class MainWindow
+public partial class MainWindow : Window
{
private bool _isSingleInstancePromptVisible;
@@ -38,14 +39,14 @@ public partial class MainWindow
try
{
- var dialog = new ContentDialog
+ var dialog = new FAContentDialog
{
- Title = L("single_instance.notice.title", "应用已经运行"),
+ Title = L("single_instance.notice.title", "Already running"),
Content = L(
"single_instance.notice.description",
- "应用已经运行,无需多次点击打开。"),
- PrimaryButtonText = L("single_instance.notice.button", "确定"),
- DefaultButton = ContentDialogButton.Primary
+ "LanMountainDesktop is already running. The existing window will stay active, so no new instance was started."),
+ PrimaryButtonText = L("single_instance.notice.button", "OK"),
+ DefaultButton = FAContentDialogButton.Primary
};
await dialog.ShowAsync(this);
diff --git a/LanMountainDesktop/Views/MainWindow.axaml b/LanMountainDesktop/Views/MainWindow.axaml
index e8dd12e..d193249 100644
--- a/LanMountainDesktop/Views/MainWindow.axaml
+++ b/LanMountainDesktop/Views/MainWindow.axaml
@@ -3,7 +3,7 @@
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
- xmlns:ic="using:FluentIcons.Avalonia.Fluent"
+ xmlns:ic="using:FluentIcons.Avalonia"
xmlns:mi="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:comp="using:LanMountainDesktop.Views.Components"
@@ -15,7 +15,7 @@
x:Class="LanMountainDesktop.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
WindowState="FullScreen"
- SystemDecorations="None"
+ WindowDecorations="None"
CanResize="False"
UseLayoutRounding="True"
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
@@ -236,7 +236,7 @@
-
+
-
+
-
+
-
+
diff --git a/LanMountainDesktop/Views/SettingsPages/AboutSettingsPage.axaml b/LanMountainDesktop/Views/SettingsPages/AboutSettingsPage.axaml
index c26f2e7..beee6de 100644
--- a/LanMountainDesktop/Views/SettingsPages/AboutSettingsPage.axaml
+++ b/LanMountainDesktop/Views/SettingsPages/AboutSettingsPage.axaml
@@ -1,19 +1,27 @@
-
-
+
-
+
@@ -36,7 +44,7 @@
-
-
+
-
+
-
+
Lincube
-
-
+
+
diff --git a/LanMountainDesktop/Views/SettingsPages/AboutSettingsPage.axaml.cs b/LanMountainDesktop/Views/SettingsPages/AboutSettingsPage.axaml.cs
index 141317b..40fd3cd 100644
--- a/LanMountainDesktop/Views/SettingsPages/AboutSettingsPage.axaml.cs
+++ b/LanMountainDesktop/Views/SettingsPages/AboutSettingsPage.axaml.cs
@@ -74,6 +74,9 @@ public partial class AboutSettingsPage : SettingsPageBase
private void OnAboutHeroCardPointerPressed(object? sender, PointerPressedEventArgs e)
{
+ _ = sender;
+ _ = e;
+
var now = DateTime.UtcNow;
var elapsed = now - _lastHeroCardClickTime;
@@ -111,25 +114,23 @@ public partial class AboutSettingsPage : SettingsPageBase
}
else if (remaining <= 2)
{
- Debug.WriteLine($"[AboutSettingsPage] 再点击 {remaining} 次即可启用开发者模式。");
+ Debug.WriteLine($"[AboutSettingsPage] {remaining} tap(s) remaining before developer mode unlocks.");
}
}
private async void PromptEnableDevMode(ISettingsFacadeService settingsFacade)
{
- var dialog = new ContentDialog
+ var dialog = new FAContentDialog
{
- Title = "启用开发者模式",
- Content = "开发者模式提供了插件调试、热重载等高级功能,仅供开发和调试用途。\n\n" +
- "请注意:开发者不对以非开发用途使用此功能造成的任何后果负责,也不接受以非开发用途使用时产生的 Bug 反馈。\n\n" +
- "确定要启用开发者模式吗?",
- PrimaryButtonText = "启用",
- CloseButtonText = "取消",
- DefaultButton = ContentDialogButton.Close
+ Title = "Enable developer mode",
+ Content = "Developer mode exposes experimental settings, diagnostics, and local plugin debugging options.\n\nUse it only when you are actively testing or troubleshooting the desktop host.",
+ PrimaryButtonText = "Enable",
+ CloseButtonText = "Not now",
+ DefaultButton = FAContentDialogButton.Close
};
var result = await dialog.ShowAsync();
- if (result != ContentDialogResult.Primary)
+ if (result != FAContentDialogResult.Primary)
{
return;
}
diff --git a/LanMountainDesktop/Views/SettingsPages/AppearanceSettingsPage.axaml b/LanMountainDesktop/Views/SettingsPages/AppearanceSettingsPage.axaml
index 61d8d73..6ae5d35 100644
--- a/LanMountainDesktop/Views/SettingsPages/AppearanceSettingsPage.axaml
+++ b/LanMountainDesktop/Views/SettingsPages/AppearanceSettingsPage.axaml
@@ -1,9 +1,9 @@
-
@@ -13,12 +13,12 @@
Text="{Binding ThemeHeader}"
Margin="0,0,0,4" />
-
-
-
-
-
+
+
+
+
@@ -28,24 +28,24 @@
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
@@ -55,15 +55,15 @@
-
-
+
+
-
-
-
-
-
+
+
+
+
@@ -73,15 +73,15 @@
-
-
+
+
-
-
-
-
-
+
+
+
+
-
-
+
+
diff --git a/LanMountainDesktop/Views/SettingsPages/ComponentsSettingsPage.axaml b/LanMountainDesktop/Views/SettingsPages/ComponentsSettingsPage.axaml
index 47099c4..ec7628a 100644
--- a/LanMountainDesktop/Views/SettingsPages/ComponentsSettingsPage.axaml
+++ b/LanMountainDesktop/Views/SettingsPages/ComponentsSettingsPage.axaml
@@ -1,9 +1,9 @@
-
@@ -12,12 +12,12 @@
Text="{Binding ComponentsHeader}"
Margin="0,0,0,4" />
-
-
-
-
-
+
+
+
+
@@ -33,8 +33,8 @@
VerticalAlignment="Center"
HorizontalAlignment="Right" />
-
-
+
+
@@ -50,8 +50,8 @@
VerticalAlignment="Center"
HorizontalAlignment="Right" />
-
-
+
+
@@ -66,19 +66,19 @@
-
-
+
+
-
-
-
-
-
+
+
+
+
-
-
+
+
diff --git a/LanMountainDesktop/Views/SettingsPages/DevSettingsPage.axaml b/LanMountainDesktop/Views/SettingsPages/DevSettingsPage.axaml
index 343a26e..8c19024 100644
--- a/LanMountainDesktop/Views/SettingsPages/DevSettingsPage.axaml
+++ b/LanMountainDesktop/Views/SettingsPages/DevSettingsPage.axaml
@@ -1,84 +1,92 @@
-
-
-
+ Title="Preview and developer features"
+ Message="These options are intended for debugging, diagnostics, and local plugin development."
+ Margin="0,0,0,16">
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
+
@@ -87,23 +95,25 @@
Text="LMD_DEV_PLUGIN=<path>"
TextWrapping="Wrap" />
-
+
+
+ Text="--dev-mode / -dev Enable developer mode startup helpers." />
+ Text="--hot-reload / -hr Enable hot reload for development builds." />
-
-
-
+
+
diff --git a/LanMountainDesktop/Views/SettingsPages/DevSettingsPage.axaml.cs b/LanMountainDesktop/Views/SettingsPages/DevSettingsPage.axaml.cs
index fa16ae0..65fc06c 100644
--- a/LanMountainDesktop/Views/SettingsPages/DevSettingsPage.axaml.cs
+++ b/LanMountainDesktop/Views/SettingsPages/DevSettingsPage.axaml.cs
@@ -6,7 +6,7 @@ namespace LanMountainDesktop.Views.SettingsPages;
[SettingsPageInfo(
"dev",
- "开发者",
+ "Developer",
SettingsPageCategory.Dev,
IconKey = "DeveloperBoard",
SortOrder = 0,
diff --git a/LanMountainDesktop/Views/SettingsPages/GeneralSettingsPage.axaml b/LanMountainDesktop/Views/SettingsPages/GeneralSettingsPage.axaml
index 94bba06..e1021b4 100644
--- a/LanMountainDesktop/Views/SettingsPages/GeneralSettingsPage.axaml
+++ b/LanMountainDesktop/Views/SettingsPages/GeneralSettingsPage.axaml
@@ -1,23 +1,22 @@
-
-
-
-
-
-
-
+
+
+
+
+
@@ -27,15 +26,15 @@
-
-
+
+
-
-
-
-
-
+
+
+
+
@@ -45,14 +44,14 @@
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
@@ -79,13 +78,13 @@
Text="{Binding RuntimeHeader}"
Margin="0,0,0,4" />
-
-
-
-
-
+
+
+
+
@@ -95,48 +94,47 @@
-
-
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
diff --git a/LanMountainDesktop/Views/SettingsPages/LauncherSettingsPage.axaml b/LanMountainDesktop/Views/SettingsPages/LauncherSettingsPage.axaml
index e6ce7c0..8394886 100644
--- a/LanMountainDesktop/Views/SettingsPages/LauncherSettingsPage.axaml
+++ b/LanMountainDesktop/Views/SettingsPages/LauncherSettingsPage.axaml
@@ -1,9 +1,9 @@
-
@@ -56,14 +56,14 @@
Text="{Binding AppearanceHeader}"
Margin="0,0,0,4" />
-
-
-
-
-
+
+
+
+
@@ -73,21 +73,21 @@
-
-
+
+
-
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
diff --git a/LanMountainDesktop/Views/SettingsPages/NotificationSettingsPage.axaml b/LanMountainDesktop/Views/SettingsPages/NotificationSettingsPage.axaml
index f6d01d8..e228295 100644
--- a/LanMountainDesktop/Views/SettingsPages/NotificationSettingsPage.axaml
+++ b/LanMountainDesktop/Views/SettingsPages/NotificationSettingsPage.axaml
@@ -3,7 +3,7 @@
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:controls="using:LanMountainDesktop.Controls"
xmlns:ui="using:FluentAvalonia.UI.Controls"
- xmlns:fi="using:FluentIcons.Avalonia.Fluent"
+ xmlns:fi="using:FluentIcons.Avalonia"
x:Class="LanMountainDesktop.Views.SettingsPages.NotificationSettingsPage"
x:DataType="vm:NotificationSettingsPageViewModel">
@@ -13,15 +13,15 @@
Text="{Binding NotificationHeader}"
Margin="0,0,0,4" />
-
-
-
-
-
+
+
+
+
-
-
+
+
@@ -29,39 +29,39 @@
Text="{Binding BehaviorHeader}"
Margin="0,0,0,4" />
-
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
-
-
+
+
@@ -69,12 +69,12 @@
Text="{Binding TestHeader}"
Margin="0,0,0,4" />
-
-
-
-
-
+
+
+
+
-
-
-
+
+
diff --git a/LanMountainDesktop/Views/SettingsPages/PluginCatalogDetailDrawer.axaml b/LanMountainDesktop/Views/SettingsPages/PluginCatalogDetailDrawer.axaml
index aad25d8..a7a6404 100644
--- a/LanMountainDesktop/Views/SettingsPages/PluginCatalogDetailDrawer.axaml
+++ b/LanMountainDesktop/Views/SettingsPages/PluginCatalogDetailDrawer.axaml
@@ -3,7 +3,7 @@
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia"
xmlns:helpers="using:LanMountainDesktop.Helpers"
- xmlns:fi="using:FluentIcons.Avalonia.Fluent"
+ xmlns:fi="using:FluentIcons.Avalonia"
x:Class="LanMountainDesktop.Views.SettingsPages.PluginCatalogDetailDrawer"
x:DataType="vm:PluginCatalogDetailViewModel">
diff --git a/LanMountainDesktop/Views/SettingsPages/PluginCatalogSettingsPage.axaml b/LanMountainDesktop/Views/SettingsPages/PluginCatalogSettingsPage.axaml
index d58e494..7087971 100644
--- a/LanMountainDesktop/Views/SettingsPages/PluginCatalogSettingsPage.axaml
+++ b/LanMountainDesktop/Views/SettingsPages/PluginCatalogSettingsPage.axaml
@@ -1,32 +1,32 @@
-
-
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
-
-
+
+
@@ -29,17 +29,17 @@
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
+
+
diff --git a/LanMountainDesktop/Views/SettingsPages/PrivacyPolicyDrawer.axaml b/LanMountainDesktop/Views/SettingsPages/PrivacyPolicyDrawer.axaml
index 61622e0..2c4e6a7 100644
--- a/LanMountainDesktop/Views/SettingsPages/PrivacyPolicyDrawer.axaml
+++ b/LanMountainDesktop/Views/SettingsPages/PrivacyPolicyDrawer.axaml
@@ -3,7 +3,7 @@
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia"
xmlns:helpers="using:LanMountainDesktop.Helpers"
- xmlns:fi="using:FluentIcons.Avalonia.Fluent"
+ xmlns:fi="using:FluentIcons.Avalonia"
x:Class="LanMountainDesktop.Views.SettingsPages.PrivacyPolicyDrawer"
x:DataType="vm:PrivacyPolicyViewModel">
diff --git a/LanMountainDesktop/Views/SettingsPages/PrivacySettingsPage.axaml b/LanMountainDesktop/Views/SettingsPages/PrivacySettingsPage.axaml
index 5a05cfa..91e17f5 100644
--- a/LanMountainDesktop/Views/SettingsPages/PrivacySettingsPage.axaml
+++ b/LanMountainDesktop/Views/SettingsPages/PrivacySettingsPage.axaml
@@ -1,9 +1,9 @@
-
@@ -12,25 +12,25 @@
Text="{Binding PrivacyHeader}"
Margin="0,0,0,4" />
-
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
-
-
+
+
@@ -12,15 +12,15 @@
Text="{Binding ComponentsHeader}"
Margin="0,0,0,4" />
-
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
@@ -51,8 +51,8 @@
IsChecked="{Binding ClockTransparentBackground}"
VerticalAlignment="Center" />
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
-
-
+
+
+ PlaceholderText="Enter Markdown text..." />
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
@@ -236,12 +236,12 @@
Text="{Binding SpacingHeader}"
Margin="0,0,0,4" />
-
-
-
-
-
+
+
+
+
@@ -251,13 +251,13 @@
-
-
+
+
-
-
-
+
+
@@ -275,15 +275,15 @@
Text="{Binding StatusBarShadowHeader}"
Margin="0,0,0,4" />
-
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
diff --git a/LanMountainDesktop/Views/SettingsPages/StudySettingsPage.axaml b/LanMountainDesktop/Views/SettingsPages/StudySettingsPage.axaml
index fa367a9..60ce53e 100644
--- a/LanMountainDesktop/Views/SettingsPages/StudySettingsPage.axaml
+++ b/LanMountainDesktop/Views/SettingsPages/StudySettingsPage.axaml
@@ -1,36 +1,36 @@
-
-
-
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
-
-
-
+
+
+
-
-
+
+
@@ -52,10 +52,10 @@
TickFrequency="20"
IsSnapToTickEnabled="True" />
-
+
-
-
+
+
@@ -77,27 +77,27 @@
TickFrequency="5"
IsSnapToTickEnabled="True" />
-
+
-
-
+
+
-
-
+
+
-
-
+
-
-
-
+
+
+
-
-
+
+
@@ -119,10 +119,10 @@
TickFrequency="5"
IsSnapToTickEnabled="True" />
-
+
-
-
+
+
@@ -144,10 +144,10 @@
TickFrequency="5"
IsSnapToTickEnabled="True" />
-
+
-
-
+
+
@@ -169,10 +169,10 @@
TickFrequency="5"
IsSnapToTickEnabled="True" />
-
+
-
-
+
+
@@ -194,10 +194,10 @@
TickFrequency="1"
IsSnapToTickEnabled="True" />
-
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
-
-
-
+
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
-
-
-
+
+
+
-
-
+
+
-
+
-
-
+
+
@@ -313,10 +313,10 @@
TickFrequency="5"
IsSnapToTickEnabled="True" />
-
+
-
-
+
+
@@ -338,8 +338,8 @@
TickFrequency="1"
IsSnapToTickEnabled="True" />
-
-
+
+
-
\ No newline at end of file
+
diff --git a/LanMountainDesktop/Views/SettingsPages/UpdateSettingsPage.axaml b/LanMountainDesktop/Views/SettingsPages/UpdateSettingsPage.axaml
index d855851..2ebf7eb 100644
--- a/LanMountainDesktop/Views/SettingsPages/UpdateSettingsPage.axaml
+++ b/LanMountainDesktop/Views/SettingsPages/UpdateSettingsPage.axaml
@@ -1,9 +1,9 @@
-
@@ -163,13 +163,13 @@
Margin="0,0,0,18"
Text="{Binding PreferencesDescription}" />
-
-
-
-
-
+
+
+
+
@@ -179,24 +179,24 @@
-
-
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
@@ -206,16 +206,16 @@
-
-
+
+
-
-
-
-
-
+
+
+
+
@@ -225,23 +225,23 @@
-
-
+
+
-
-
-
-
-
-
+
+
+
+
-
-
+
+
diff --git a/LanMountainDesktop/Views/SettingsPages/WallpaperSettingsPage.axaml b/LanMountainDesktop/Views/SettingsPages/WallpaperSettingsPage.axaml
index 9e91059..f76fe93 100644
--- a/LanMountainDesktop/Views/SettingsPages/WallpaperSettingsPage.axaml
+++ b/LanMountainDesktop/Views/SettingsPages/WallpaperSettingsPage.axaml
@@ -1,35 +1,36 @@
-
-
-
-
-
-
+
+
-
-
-
+
-
-
-
+
@@ -39,100 +40,139 @@
-
-
+
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
@@ -177,18 +218,18 @@
-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
+
@@ -224,21 +265,21 @@
ToolTip.Tip="{Binding RefreshButtonTooltip}"
VerticalAlignment="Center"
Padding="12,8">
-
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
@@ -248,18 +289,17 @@
-
-
+
+
-
-
-
-
-
-
+
+
+
+
@@ -269,9 +309,8 @@
-
-
-
+
+
diff --git a/LanMountainDesktop/Views/SettingsPages/WeatherSettingsPage.axaml b/LanMountainDesktop/Views/SettingsPages/WeatherSettingsPage.axaml
index e77f47b..5bf7b2a 100644
--- a/LanMountainDesktop/Views/SettingsPages/WeatherSettingsPage.axaml
+++ b/LanMountainDesktop/Views/SettingsPages/WeatherSettingsPage.axaml
@@ -1,10 +1,10 @@
-
@@ -47,7 +47,7 @@
-
-
-
-
-
-
+
+
+
+
@@ -72,33 +72,33 @@
-
-
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+ Glyph=""
+ FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
-
-
+
+
-
-
-
-
-
+
+
+
+
-
-
+
+
@@ -179,36 +180,36 @@
Value="{Binding Longitude}" />
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
-
-
+
+
@@ -222,23 +223,23 @@
IsChecked="{Binding AutoRefreshLocation}"
IsEnabled="{Binding IsLocationSupported}" />
-
-
-
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
@@ -112,7 +112,7 @@
-
-
+
-
+
-
@@ -180,6 +180,6 @@
-
+
diff --git a/LanMountainDesktop/Views/SettingsWindow.axaml.cs b/LanMountainDesktop/Views/SettingsWindow.axaml.cs
index 50e8fb1..0608187 100644
--- a/LanMountainDesktop/Views/SettingsWindow.axaml.cs
+++ b/LanMountainDesktop/Views/SettingsWindow.axaml.cs
@@ -163,9 +163,7 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
if (_useSystemChrome)
{
ExtendClientAreaToDecorationsHint = true;
- ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.PreferSystemChrome;
- ExtendClientAreaTitleBarHeightHint = -1;
- SystemDecorations = SystemDecorations.Full;
+ WindowDecorations = WindowDecorations.Full;
if (WindowTitleBarHost is { })
{
@@ -174,10 +172,8 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
return;
}
- SystemDecorations = SystemDecorations.BorderOnly;
+ WindowDecorations = WindowDecorations.BorderOnly;
ExtendClientAreaToDecorationsHint = true;
- ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.NoChrome;
- ExtendClientAreaTitleBarHeightHint = 48;
if (WindowTitleBarHost is { })
{
@@ -205,27 +201,23 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
{
if (previousCategory is not null && previousCategory != page.Category)
{
- RootNavigationView.MenuItems.Add(new NavigationViewItemSeparator());
+ RootNavigationView.MenuItems.Add(new FANavigationViewItemSeparator());
}
- RootNavigationView.MenuItems.Add(new NavigationViewItem
+ RootNavigationView.MenuItems.Add(new FANavigationViewItem
{
Content = page.Title,
Tag = page.PageId,
- IconSource = new FluentIcons.Avalonia.Fluent.SymbolIconSource
- {
- Symbol = MapIcon(page.IconKey),
- IconVariant = FluentIcons.Common.IconVariant.Regular
- }
+ IconSource = CreateSettingsIconSource(MapIcon(page.IconKey))
});
previousCategory = page.Category;
}
}
- private void OnNavigationSelectionChanged(object? sender, NavigationViewSelectionChangedEventArgs e)
+ private void OnNavigationSelectionChanged(object? sender, FANavigationViewSelectionChangedEventArgs e)
{
- var selectedItem = e.SelectedItemContainer ?? e.SelectedItem as NavigationViewItem;
+ var selectedItem = e.SelectedItemContainer ?? e.SelectedItem as FANavigationViewItem;
NavigateTo(selectedItem?.Tag as string);
}
@@ -301,7 +293,7 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
return;
}
- foreach (var item in RootNavigationView.MenuItems.OfType())
+ foreach (var item in RootNavigationView.MenuItems.OfType())
{
if (string.Equals(item.Tag as string, pageId, StringComparison.OrdinalIgnoreCase))
{
@@ -374,17 +366,17 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
try
{
- var dialog = new ContentDialog
+ var dialog = new FAContentDialog
{
Title = ViewModel.RestartDialogTitle,
Content = ViewModel.RestartMessage,
PrimaryButtonText = ViewModel.RestartDialogPrimaryText,
CloseButtonText = ViewModel.RestartDialogCloseText,
- DefaultButton = ContentDialogButton.Primary
+ DefaultButton = FAContentDialogButton.Primary
};
var result = await dialog.ShowAsync(this);
- if (result == ContentDialogResult.Primary)
+ if (result == FAContentDialogResult.Primary)
{
_hostApplicationLifecycle.TryRestart(new HostApplicationLifecycleRequest(
Source: "SettingsWindow",
@@ -486,7 +478,7 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
{
_ = TryApplyResponsiveLayout();
- // 小窗口时隐藏抽屉面板
+ // Hide the drawer pane on narrow windows.
}
private void OnClosed(object? sender, EventArgs e)
@@ -537,9 +529,9 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
{
_ = sender;
- if (e.Property == NavigationView.IsPaneOpenProperty ||
- e.Property == NavigationView.OpenPaneLengthProperty ||
- e.Property == NavigationView.PaneDisplayModeProperty)
+ if (e.Property == FANavigationView.IsPaneOpenProperty ||
+ e.Property == FANavigationView.OpenPaneLengthProperty ||
+ e.Property == FANavigationView.PaneDisplayModeProperty)
{
UpdatePaneToggleIcon();
RequestResponsiveLayoutRefresh();
@@ -736,16 +728,41 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
"GridDots" => Symbol.GridDots,
"PuzzlePiece" => Symbol.PuzzlePiece,
"ShoppingBag" => Symbol.ShoppingBag,
- "Shield" => Symbol.ShieldDismiss,
+ "Shield" => Symbol.ShieldLock,
"Info" => Symbol.Info,
"ArrowSync" => Symbol.ArrowSync,
"Hourglass" => Symbol.Hourglass,
"Alert" => Symbol.Alert,
- "Bell" => Symbol.Alert,
+ "Bell" => Symbol.AlertOn,
"DeveloperBoard" => Symbol.DeveloperBoard,
"FolderLink" => Symbol.FolderLink,
"WindowConsole" => Symbol.WindowConsole,
_ => Symbol.Settings
};
}
+
+ private static FAFontIconSource CreateSettingsIconSource(Symbol symbol)
+ {
+ var symbolIcon = new FluentIcons.Avalonia.SymbolIcon
+ {
+ Symbol = symbol,
+ IconVariant = FluentIcons.Common.IconVariant.Regular
+ };
+
+ // FluentAvalonia still expects IconSource here, so bridge the Avalonia 12 FluentIcons glyph/font into FAFontIconSource.
+ var iconTextProp = typeof(FluentIcons.Avalonia.SymbolIcon).GetProperty("IconText", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
+ var iconFontProp = typeof(FluentIcons.Avalonia.SymbolIcon).GetProperty("IconFont", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
+
+ var iconText = iconTextProp?.GetValue(symbolIcon) as string ?? "?";
+ var iconFont = iconFontProp?.GetValue(symbolIcon);
+ var fontFamily = iconFont?.GetType().GetProperty("FontFamily")?.GetValue(iconFont) as Avalonia.Media.FontFamily
+ ?? new Avalonia.Media.FontFamily("avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons");
+
+ return new FAFontIconSource
+ {
+ Glyph = iconText,
+ FontFamily = fontFamily,
+ FontSize = 16
+ };
+ }
}
diff --git a/LanMountainDesktop/Views/StudySessionReportWindow.axaml b/LanMountainDesktop/Views/StudySessionReportWindow.axaml
index e90a7c7..fd3b6f8 100644
--- a/LanMountainDesktop/Views/StudySessionReportWindow.axaml
+++ b/LanMountainDesktop/Views/StudySessionReportWindow.axaml
@@ -3,7 +3,7 @@
xmlns:fi="using:FluentIcons.Avalonia"
x:Class="LanMountainDesktop.Views.StudySessionReportWindow"
x:CompileBindings="False"
- SystemDecorations="None"
+ WindowDecorations="None"
Background="Transparent"
ShowInTaskbar="False"
Topmost="True"
@@ -12,7 +12,6 @@
Height="600"
TransparencyLevelHint="Transparent"
ExtendClientAreaToDecorationsHint="True"
- ExtendClientAreaChromeHints="NoChrome"
ExtendClientAreaTitleBarHeightHint="-1"
WindowStartupLocation="CenterOwner">
diff --git a/LanMountainDesktop/Views/TransparentOverlayWindow.axaml b/LanMountainDesktop/Views/TransparentOverlayWindow.axaml
index c75ef4a..c6cd809 100644
--- a/LanMountainDesktop/Views/TransparentOverlayWindow.axaml
+++ b/LanMountainDesktop/Views/TransparentOverlayWindow.axaml
@@ -1,11 +1,10 @@
@@ -680,7 +680,7 @@ if (!url.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
## 相关文档
-- [Plugin SDK v4 迁移指南](PLUGIN_SDK_V4_MIGRATION.md)
+- [Plugin SDK v5 迁移指南](PLUGIN_SDK_V5_MIGRATION.md)
- [组件开发指南](COMPONENT_DEVELOPMENT.md)
- [API 参考](API_REFERENCE.md)
- [架构文档](ARCHITECTURE.md)
diff --git a/docs/PLUGIN_SDK_V5_MIGRATION.md b/docs/PLUGIN_SDK_V5_MIGRATION.md
new file mode 100644
index 0000000..bd4cc79
--- /dev/null
+++ b/docs/PLUGIN_SDK_V5_MIGRATION.md
@@ -0,0 +1,34 @@
+# Plugin SDK v5 Migration Guide
+
+Plugin SDK v5 is the Avalonia 12 compatibility baseline for LanMountainDesktop plugins.
+
+## What Changed
+
+- Rebuild plugins against `LanMountainDesktop.PluginSdk` `5.0.0`.
+- Set `plugin.json` `apiVersion` to `5.0.0`.
+- Target `net10.0` and use Avalonia `12.0.1` compatible UI dependencies.
+- Use `FluentAvaloniaUI` `3.0.0-preview1` and `FluentIcons.Avalonia` `2.1.325` when a plugin directly references those packages.
+
+## Compatibility
+
+SDK v5 is a binary breaking change because the SDK exposes Avalonia UI types such as `Control`, `UserControl`, and `SettingsPageBase`. Plugins built for SDK v4 must be rebuilt and republished for SDK v5.
+
+The host does not provide an Avalonia 11 / Avalonia 12 dual UI stack. The public extension entry points remain the same: custom settings pages still derive from `SettingsPageBase`, and desktop components still provide Avalonia controls through the existing registration APIs.
+
+## Minimal Package Update
+
+```xml
+
+
+
+```
+
+```json
+{
+ "apiVersion": "5.0.0"
+}
+```
+
+## Validation
+
+After updating package versions and rebuilding the plugin, verify that the generated `.laapp` contains the rebuilt assembly, `plugin.json`, and `.deps.json` next to the plugin entry assembly.
diff --git a/docs/PRODUCT.md b/docs/PRODUCT.md
index be882e7..7fb3aca 100644
--- a/docs/PRODUCT.md
+++ b/docs/PRODUCT.md
@@ -39,7 +39,7 @@
### 当前阶段
- 产品版本:`1.0.0`
-- Plugin SDK API 基线:`4.0.1`
+- Plugin SDK API 基线:`5.0.0`
- 当前重点:持续完善宿主体验、设置页体验、组件能力与插件生态
- 近期需求入口:以 `.trae/specs/` 中的 feature spec 为准
@@ -59,4 +59,4 @@
LanMountainDesktop is a cross-platform desktop enhancement product built with Avalonia UI and .NET 10. It targets students, office users, and customization-focused users who want a programmable desktop surface for information, tools, and plugin-driven extensions.
-This repository is the source of truth for the desktop host, plugin runtime, Plugin SDK, shared contracts, and core appearance/settings infrastructure. The current product version is `1.0.0`, and the active Plugin SDK baseline in this repository is `4.0.1`.
+This repository is the source of truth for the desktop host, plugin runtime, Plugin SDK, shared contracts, and core appearance/settings infrastructure. The current product version is `1.0.0`, and the active Plugin SDK baseline in this repository is `5.0.0`.
diff --git a/docs/Plugins develop/01-快速开始/02-三分钟创建第一个插件.md b/docs/Plugins develop/01-快速开始/02-三分钟创建第一个插件.md
index 3107848..f79e707 100644
--- a/docs/Plugins develop/01-快速开始/02-三分钟创建第一个插件.md
+++ b/docs/Plugins develop/01-快速开始/02-三分钟创建第一个插件.md
@@ -40,7 +40,7 @@ cd MyFirstPlugin
"description": "这是一个测试插件",
"author": "你的名字",
"version": "1.0.0",
- "apiVersion": "4.0.1",
+ "apiVersion": "5.0.0",
"entranceAssembly": "MyFirstPlugin.dll",
"sharedContracts": []
}
diff --git a/docs/Plugins develop/01-快速开始/03-插件项目结构详解.md b/docs/Plugins develop/01-快速开始/03-插件项目结构详解.md
index c355107..7796fd5 100644
--- a/docs/Plugins develop/01-快速开始/03-插件项目结构详解.md
+++ b/docs/Plugins develop/01-快速开始/03-插件项目结构详解.md
@@ -35,7 +35,7 @@ MyPlugin/
"description": "这是一个示例插件",
"author": "作者名称",
"version": "1.0.0",
- "apiVersion": "4.0.1",
+ "apiVersion": "5.0.0",
"entranceAssembly": "MyPlugin.dll",
"sharedContracts": [],
"website": "https://example.com",
@@ -53,7 +53,7 @@ MyPlugin/
| `description` | ✅ | 简短描述 | `显示实时天气信息` |
| `author` | ✅ | 作者名称 | `张三` |
| `version` | ✅ | 版本号(语义化版本) | `1.0.0` |
-| `apiVersion` | ✅ | SDK API 版本 | `4.0.1` |
+| `apiVersion` | ✅ | SDK API 版本 | `5.0.0` |
| `entranceAssembly` | ✅ | 入口程序集文件名 | `MyPlugin.dll` |
| `sharedContracts` | ✅ | 共享契约类型列表 | `[]` |
| `website` | ❌ | 项目网站 | `https://github.com/...` |
@@ -74,7 +74,7 @@ MyPlugin/
⚠️ **apiVersion 字段规则:**
- 必须与引用的 SDK 版本兼容
-- 当前最新版本:`4.0.1`
+- 当前最新版本:`5.0.0`
- 不兼容时宿主将拒绝加载插件
---
@@ -96,7 +96,7 @@ MyPlugin/
-
+
@@ -122,7 +122,7 @@ MyPlugin/
### SDK 引用
```xml
-
+
```
⚠️ **版本必须匹配:**
diff --git a/docs/Plugins develop/02-核心概念与原理/01-插件生命周期.md b/docs/Plugins develop/02-核心概念与原理/01-插件生命周期.md
index 36148da..8ae01ef 100644
--- a/docs/Plugins develop/02-核心概念与原理/01-插件生命周期.md
+++ b/docs/Plugins develop/02-核心概念与原理/01-插件生命周期.md
@@ -213,7 +213,7 @@ public override void Initialize(HostBuilderContext context, IServiceCollection s
- 关闭阑山桌面
**当前限制:**
-- SDK v4 暂无显式的卸载回调方法
+- SDK v5 暂无显式的卸载回调方法
- 资源释放依赖 .NET 垃圾回收
- 建议:
- 使用 `IDisposable` 模式管理资源
diff --git a/docs/Plugins develop/07-发布与运营/01-插件打包规范.md b/docs/Plugins develop/07-发布与运营/01-插件打包规范.md
index 5b99533..daa8551 100644
--- a/docs/Plugins develop/07-发布与运营/01-插件打包规范.md
+++ b/docs/Plugins develop/07-发布与运营/01-插件打包规范.md
@@ -33,7 +33,7 @@ MyPlugin.laapp
"description": "插件描述",
"author": "作者名",
"version": "1.0.0",
- "apiVersion": "4.0.1",
+ "apiVersion": "5.0.0",
"entranceAssembly": "MyPlugin.dll",
"sharedContracts": []
}
@@ -61,7 +61,7 @@ MyPlugin.laapp
-
+
diff --git a/docs/ai/DOC_SOURCES.md b/docs/ai/DOC_SOURCES.md
index 46b48d8..92d14ed 100644
--- a/docs/ai/DOC_SOURCES.md
+++ b/docs/ai/DOC_SOURCES.md
@@ -20,7 +20,7 @@
| 视觉规范 | `docs/VISUAL_SPEC.md` | 颜色、语义资源、玻璃层级 |
| 圆角规范 | `docs/CORNER_RADIUS_SPEC.md` | 圆角层级与动态规则 |
| 插件生态边界 | `docs/ECOSYSTEM_BOUNDARIES.md` | 仓库边界和 market 所属 |
-| SDK v4 迁移 | `docs/PLUGIN_SDK_V4_MIGRATION.md` | Plugin SDK breaking changes |
+| SDK v5 迁移 | `docs/PLUGIN_SDK_V5_MIGRATION.md` | Plugin SDK breaking changes |
## 已废弃来源
diff --git a/test_fluenticons.cs b/test_fluenticons.cs
new file mode 100644
index 0000000..ca393b6
--- /dev/null
+++ b/test_fluenticons.cs
@@ -0,0 +1,11 @@
+using System;
+using FluentIcons.Avalonia;
+
+class Test
+{
+ static void Main()
+ {
+ // 尝试创建 SymbolIconSource
+ var source = new SymbolIconSource();
+ }
+}