Compare commits

..

8 Commits

Author SHA1 Message Date
lincube
05ffadd1a0 Refactor data location paths and add background service
Refactor DataLocationResolver to centralize data path resolution (ResolveLauncherDataPath, ResolveDesktopDataPath, ResolveConfigPath, ResolveLauncherLogsPath, ResolveLauncherStatePath) and replace usages of the previous ".launcher" layout with a "Launcher" folder. Update API: LoadConfig/SaveConfig reorganized and ApplyLocationChoice now accepts an optional custom path and migration flag; migration logic updated accordingly. Update dependent services and views (Logger, DeploymentLocator, UpdateEngineService, OobeStateService, StartupAttemptRegistry, LauncherDebugSettingsStore, OobeWindow) to use the new resolver APIs and paths. Add LauncherBackgroundService to load/validate/cache a custom splash background image and wire it into SplashWindow (AXAML/Axaml.cs) with UI placeholders and overlay. Misc: minor cleanup of Oobe/Splash XAML and related code adjustments and logging improvements.
2026-04-25 18:14:29 +08:00
lincube
5b4b9f32b5 Add OOBE redesign, theme & data location support
Introduce a redesigned OOBE flow and data-location/theme support across the launcher. Adds a new ThemeService for applying light/dark and accent colors; integrates FluentIcons.Avalonia package for icons. Overhauls OobeWindow (UX animations, typing effect, multi-step theme and data-location pages, Monet options, and final welcome step) and its code-behind to handle step navigation, accent selection, and data-location resolution. Adds DataLocation UI and handlers (DataLocationPromptWindow changes, DataLocation resolver usage) and wires a DevDebug UI for toggling/opening the data-location page. UpdateEngineService now resolves the launcher root via DataLocationResolver. Misc: update various view models, localization entries and remove TrimmerRoots.xml.
2026-04-25 17:29:25 +08:00
lincube
8b8c7d1e7f Use AppJsonContext for startup state serialization
Switch serialization to the source-generated System.Text.Json context: add JsonSerializable(typeof(StartupAttemptRecord)) to AppJsonContext and replace the previous JsonSerializerOptions-based Serialize/Deserialize calls with AppJsonContext.Default.StartupAttemptRecord. Also remove the now-unused SerializerOptions field. Additionally, update .gitignore to exclude /test-aot-publish.
2026-04-24 20:55:33 +08:00
lincube
43c0ee6c06 Simplify splash to fade; add themed about banners
Simplify splash startup visuals by removing the multi-mode/slide behavior and always using a fade animation. Update App to create SplashWindow without a StartupVisualMode parameter and remove related fields, layout configuration, slide animation, and easing helpers from SplashWindow. Clean up unused using. Replace the single about_banner asset with theme-aware variants (about_banner_dark.png and about_banner_light.png), delete the old about_banner.png, and update AboutSettingsPage to use a DynamicResource ImageBrush (AboutBannerBrush) that selects the appropriate banner per theme.
2026-04-24 18:22:14 +08:00
lincube
403cf280bb Add dev/debug startup flow and launch profiles
Handle design-time initialization and add a developer debug startup path: App now skips normal startup when in design mode and shows a DevDebugWindow when running in debug (unless a preview or apply-update command). CommandContext.IsDebugMode is extended to include DOTNET_ENVIRONMENT=Development via a new IsDevelopmentEnvironment helper. Program.Main and BuildAvaloniaApp are made public to aid tooling. Added multiple launchSettings profiles for debug and preview commands that set DOTNET_ENVIRONMENT=Development to simplify IDE debugging and UI previewing.
2026-04-24 17:25:53 +08:00
lincube
ad3648a0b8 Add configurable data location (portable/system)
Introduce support for choosing and resolving the application's data root (system user dir vs. portable app folder). Adds DataLocationConfig model, DataLocationResolver (load/save/resolve/migrate), a UI prompt (DataLocationPromptWindow) and an OOBE step (DataLocationOobeStep) to let users pick and optionally migrate existing data. Wire the chosen data root into the launcher flow and host launch plan (forwarded via --data-root and LMD_DATA_ROOT), and add AppDataPathProvider to let runtime services read the effective data root (initialized in Program.Main). Update various services (logging, settings, DB, plugin/market, startup registry, etc.) to use the new provider/resolver and register the config type in the JSON context. This enables portable installs, safe migration, and runtime overrides via CLI or environment variable.
2026-04-24 12:30:05 +08:00
lincube
28f41cd27c Resolve dev paths and fix splash UI thread
Compute a solutionRoot and expand development search paths (LanMountainDesktop and dev-test) in DeploymentLocator, add logging when scanning/finding hosts, and return distinct full paths. Ensure backward-compatible path checks. Fix cross-thread UI calls: invoke splashWindow.DismissAsync on the UI thread in LauncherFlowCoordinator, and make SplashWindow.DismissAsync ensure it runs on the UI thread before closing (simplified Close call). These changes improve development host discovery and prevent UI-thread access issues during shutdown.
2026-04-24 10:05:30 +08:00
lincube
9de93d2a4d fix.hy3试图修复中 2026-04-24 08:24:13 +08:00
109 changed files with 1304 additions and 2397 deletions

View File

@@ -1,106 +0,0 @@
# 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`

View File

@@ -1,21 +0,0 @@
# 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.

View File

@@ -1,49 +0,0 @@
# 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.

View File

@@ -1,18 +0,0 @@
# 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.

View File

@@ -74,7 +74,7 @@ dotnet test LanMountainDesktop.slnx -c Debug
- SDK 公共 API 以 `LanMountainDesktop.PluginSdk/` 为准
- 共享契约以 `LanMountainDesktop.Shared.Contracts/` 为准
- market 数据来源默认是兄弟仓库 `..\\LanAirApp`
- 迁移或 breaking change 优先同步 `docs/PLUGIN_SDK_V5_MIGRATION.md`
- 迁移或 breaking change 优先同步 `docs/PLUGIN_SDK_V4_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 v5 迁移:`docs/PLUGIN_SDK_V5_MIGRATION.md`
- SDK v4 迁移:`docs/PLUGIN_SDK_V4_MIGRATION.md`
如果多个文档都提到同一件事,以 `docs/ai/DOC_SOURCES.md` 列出的权威来源为准。

View File

@@ -1,42 +0,0 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="12.0.1" />
<PackageVersion Include="Avalonia.Controls.WebView" Version="12.0.0" />
<PackageVersion Include="Avalonia.Desktop" Version="12.0.1" />
<PackageVersion Include="Avalonia.Fonts.Inter" Version="12.0.1" />
<PackageVersion Include="Avalonia.Themes.Fluent" Version="12.0.1" />
<PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.1" />
<PackageVersion Include="ClassIsland.Markdown.Avalonia" Version="12.0.0" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.2.1" />
<PackageVersion Include="dotnetCampus.Ipc" Version="2.0.0-alpha434" />
<PackageVersion Include="DotNetCampus.AvaloniaInkCanvas" Version="1.0.1" />
<PackageVersion Include="Downloader" Version="4.1.1" />
<PackageVersion Include="FluentAvaloniaUI" Version="3.0.0-preview1" />
<PackageVersion Include="FluentIcons.Avalonia" Version="2.1.325" />
<PackageVersion Include="Material.Avalonia" Version="3.16.0" />
<PackageVersion Include="MaterialColorUtilities" Version="0.3.0" />
<PackageVersion Include="Material.Icons.Avalonia" Version="3.0.2" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageVersion Include="MudTools.OfficeInterop" Version="2.0.8" />
<PackageVersion Include="MudTools.OfficeInterop.Excel" Version="2.0.8" />
<PackageVersion Include="MudTools.OfficeInterop.PowerPoint" Version="2.0.8" />
<PackageVersion Include="MudTools.OfficeInterop.Word" Version="2.0.8" />
<PackageVersion Include="PortAudioSharp2" Version="1.0.6" />
<PackageVersion Include="PostHog" Version="2.4.0" />
<PackageVersion Include="Sentry" Version="4.0.0" />
<PackageVersion Include="System.Drawing.Common" Version="10.0.0" />
<PackageVersion Include="System.Runtime.WindowsRuntime" Version="4.7.0" />
<PackageVersion Include="Tmds.DBus.Protocol" Version="0.92.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageVersion Include="YamlDotNet" Version="16.3.0" />
<PackageVersion Include="log4net" Version="3.3.0" />
</ItemGroup>
</Project>

View File

@@ -5,7 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" />
<PackageReference Include="Avalonia" Version="11.3.12" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LanMountainDesktop.PluginSdk\LanMountainDesktop.PluginSdk.csproj" />

View File

@@ -39,6 +39,4 @@ namespace LanMountainDesktop.Launcher;
[JsonSerializable(typeof(GitHubAsset))]
[JsonSerializable(typeof(List<GitHubRelease>))]
[JsonSerializable(typeof(StartupAttemptRecord))]
[JsonSerializable(typeof(PrivacyConfig))]
[JsonSerializable(typeof(PrivacyAgreementState))]
internal sealed partial class AppJsonContext : JsonSerializerContext;

View File

@@ -22,14 +22,12 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" />
<PackageReference Include="Avalonia.Desktop" />
<PackageReference Include="FluentAvaloniaUI" />
<PackageReference Include="FluentIcons.Avalonia" />
<PackageReference Include="Avalonia.Fonts.Inter" />
<PackageReference Include="ClassIsland.Markdown.Avalonia" />
<PackageReference Include="CommunityToolkit.Mvvm" />
<PackageReference Include="Tmds.DBus.Protocol" />
<PackageReference Include="Avalonia" Version="11.3.12" />
<PackageReference Include="Avalonia.Desktop" Version="11.3.12" />
<PackageReference Include="FluentAvaloniaUI" Version="2.5.0" />
<PackageReference Include="FluentIcons.Avalonia" Version="1.1.250403001" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.12" />
<PackageReference Include="Tmds.DBus.Protocol" Version="0.92.0" />
</ItemGroup>
<!-- 资源文件 -->

View File

@@ -1,42 +0,0 @@
namespace LanMountainDesktop.Launcher.Models;
/// <summary>
/// 隐私协议同意状态模型(带防篡改保护)
/// </summary>
public class PrivacyAgreementState
{
/// <summary>
/// 用户是否同意隐私协议
/// </summary>
public bool IsAgreed { get; set; } = false;
/// <summary>
/// 同意时间UTC
/// </summary>
public DateTime AgreedAtUtc { get; set; }
/// <summary>
/// 同意的协议版本
/// </summary>
public string AgreementVersion { get; set; } = "1.0";
/// <summary>
/// 用户标识(匿名)
/// </summary>
public string UserId { get; set; } = string.Empty;
/// <summary>
/// 设备标识
/// </summary>
public string DeviceId { get; set; } = string.Empty;
/// <summary>
/// 数据完整性校验哈希HMAC-SHA256
/// </summary>
public string IntegrityHash { get; set; } = string.Empty;
/// <summary>
/// 用于生成哈希的随机盐值
/// </summary>
public string Salt { get; set; } = string.Empty;
}

View File

@@ -1,22 +0,0 @@
namespace LanMountainDesktop.Launcher.Models;
/// <summary>
/// 隐私配置模型
/// </summary>
public class PrivacyConfig
{
/// <summary>
/// 是否启用崩溃报告遥测
/// </summary>
public bool CrashTelemetryEnabled { get; set; } = true;
/// <summary>
/// 是否启用使用统计遥测
/// </summary>
public bool UsageTelemetryEnabled { get; set; } = true;
/// <summary>
/// 隐私追踪 ID
/// </summary>
public string TelemetryId { get; set; } = string.Empty;
}

View File

@@ -32,11 +32,6 @@ internal sealed class DataLocationResolver
/// </summary>
public string DefaultPortableDataPath => Path.Combine(_appRoot, "AppData");
private string ResolveBootstrapLauncherDataPath()
{
return Path.Combine(_defaultSystemDataPath, LauncherFolderName);
}
/// <summary>
/// 检查是否允许便携模式(应用目录是否可写)
/// </summary>
@@ -61,11 +56,6 @@ internal sealed class DataLocationResolver
public string ResolveDataRoot()
{
var config = LoadConfig();
return ResolveDataRoot(config);
}
private string ResolveDataRoot(DataLocationConfig? config)
{
if (config is null)
{
return _defaultSystemDataPath;
@@ -75,7 +65,7 @@ internal sealed class DataLocationResolver
{
var portablePath = !string.IsNullOrWhiteSpace(config.PortableDataPath)
? config.PortableDataPath
: DefaultPortableDataPath;
: _defaultSystemDataPath;
return Path.GetFullPath(portablePath);
}
@@ -105,7 +95,7 @@ internal sealed class DataLocationResolver
/// </summary>
public string ResolveConfigPath()
{
return Path.Combine(ResolveBootstrapLauncherDataPath(), ConfigFileName);
return Path.Combine(ResolveLauncherDataPath(), ConfigFileName);
}
/// <summary>
@@ -141,9 +131,7 @@ internal sealed class DataLocationResolver
{
try
{
// 配置文件必须位于默认系统数据路径下的 Launcher 目录中
// 避免循环依赖:不能调用 ResolveConfigPath() -> ResolveLauncherDataPath() -> ResolveDataRoot() -> LoadConfig()
var configPath = Path.Combine(_defaultSystemDataPath, LauncherFolderName, ConfigFileName);
var configPath = ResolveConfigPath();
if (!File.Exists(configPath))
{
return null;
@@ -163,7 +151,7 @@ internal sealed class DataLocationResolver
{
try
{
var launcherPath = ResolveBootstrapLauncherDataPath();
var launcherPath = ResolveLauncherDataPath();
Directory.CreateDirectory(launcherPath);
var configPath = ResolveConfigPath();
@@ -194,9 +182,8 @@ internal sealed class DataLocationResolver
// 先创建目录结构
try
{
var resolvedDataRoot = ResolveDataRoot(config);
Directory.CreateDirectory(Path.Combine(resolvedDataRoot, LauncherFolderName));
Directory.CreateDirectory(Path.Combine(resolvedDataRoot, DesktopFolderName));
Directory.CreateDirectory(ResolveLauncherDataPath());
Directory.CreateDirectory(ResolveDesktopDataPath());
}
catch (Exception ex)
{

View File

@@ -9,7 +9,6 @@ internal sealed class OobeStateService
private readonly string _stateDirectory;
private readonly string _statePath;
private readonly string _legacyStatePath;
private readonly string _legacyMarkerPath;
private readonly LauncherExecutionSnapshot _executionSnapshot;
@@ -26,13 +25,7 @@ internal sealed class OobeStateService
: Path.GetFullPath(stateRootOverride);
_stateDirectory = Path.Combine(stateRoot, "Launcher", "state");
_statePath = Path.Combine(_stateDirectory, "oobe-state.json");
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");
_legacyMarkerPath = Path.Combine(_stateDirectory, "first_run_completed");
}
public OobeLaunchDecision Evaluate(CommandContext context)
@@ -107,12 +100,14 @@ internal sealed class OobeStateService
var migratedLegacyMarker = false;
if (File.Exists(_statePath))
{
return EvaluateStateFile(context, _statePath, migratedLegacyState: false);
}
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.");
}
if (File.Exists(_legacyStatePath))
{
return EvaluateStateFile(context, _legacyStatePath, migratedLegacyState: false);
return BuildDecision(context, OobeStateStatus.Completed, shouldShowOobe: false, migratedLegacyMarker: false);
}
if (File.Exists(_legacyMarkerPath))
@@ -145,18 +140,6 @@ 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

View File

@@ -1,245 +0,0 @@
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using LanMountainDesktop.Launcher.Models;
namespace LanMountainDesktop.Launcher.Services;
/// <summary>
/// 隐私协议同意状态管理服务(带防篡改保护)
/// </summary>
internal sealed class PrivacyAgreementService
{
private readonly string _storagePath;
private readonly string _secretKey;
private const string ConfigFileName = "privacy-agreement.state.json";
private const string CurrentAgreementVersion = "1.0";
public PrivacyAgreementService(string launcherDataPath)
{
_storagePath = Path.Combine(launcherDataPath, ConfigFileName);
// 使用机器特定信息生成密钥,增加篡改难度
_secretKey = GenerateMachineSpecificKey();
}
/// <summary>
/// 检查用户是否已同意隐私协议
/// </summary>
public bool HasUserAgreed()
{
try
{
if (!File.Exists(_storagePath))
{
Logger.Info("[PrivacyAgreementService] 未找到隐私协议同意状态文件");
return false;
}
var json = File.ReadAllText(_storagePath);
var state = JsonSerializer.Deserialize(json, AppJsonContext.Default.PrivacyAgreementState);
if (state == null)
{
Logger.Warn("[PrivacyAgreementService] 无法解析隐私协议状态文件");
return false;
}
// 验证数据完整性
if (!VerifyIntegrity(state))
{
Logger.Warn("[PrivacyAgreementService] 隐私协议状态文件已被篡改!");
// 删除被篡改的文件
try
{
File.Delete(_storagePath);
}
catch (Exception ex)
{
Logger.Warn($"[PrivacyAgreementService] 删除被篡改文件失败: {ex.Message}");
}
return false;
}
// 检查协议版本是否匹配
if (state.AgreementVersion != CurrentAgreementVersion)
{
Logger.Info($"[PrivacyAgreementService] 隐私协议版本已更新: {state.AgreementVersion} -> {CurrentAgreementVersion}");
return false;
}
Logger.Info($"[PrivacyAgreementService] 用户已于 {state.AgreedAtUtc:yyyy-MM-dd HH:mm:ss} UTC 同意隐私协议");
return state.IsAgreed;
}
catch (Exception ex)
{
Logger.Warn($"[PrivacyAgreementService] 检查同意状态时出错: {ex.Message}");
return false;
}
}
/// <summary>
/// 保存用户同意隐私协议的状态
/// </summary>
public bool SaveAgreement(bool isAgreed, string userId, string deviceId)
{
try
{
// 确保目录存在
var directory = Path.GetDirectoryName(_storagePath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
// 生成随机盐值
var salt = GenerateRandomSalt();
var state = new PrivacyAgreementState
{
IsAgreed = isAgreed,
AgreedAtUtc = DateTime.UtcNow,
AgreementVersion = CurrentAgreementVersion,
UserId = userId,
DeviceId = deviceId,
Salt = salt
};
// 计算完整性哈希
state.IntegrityHash = CalculateIntegrityHash(state);
// 保存到文件
var json = JsonSerializer.Serialize(state, AppJsonContext.Default.PrivacyAgreementState);
File.WriteAllText(_storagePath, json);
Logger.Info($"[PrivacyAgreementService] 隐私协议同意状态已保存: IsAgreed={isAgreed}");
return true;
}
catch (Exception ex)
{
Logger.Warn($"[PrivacyAgreementService] 保存同意状态失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 获取当前的协议版本
/// </summary>
public string GetCurrentAgreementVersion() => CurrentAgreementVersion;
/// <summary>
/// 清除同意状态(用于测试或重置)
/// </summary>
public bool ClearAgreement()
{
try
{
if (File.Exists(_storagePath))
{
File.Delete(_storagePath);
Logger.Info("[PrivacyAgreementService] 隐私协议同意状态已清除");
}
return true;
}
catch (Exception ex)
{
Logger.Warn($"[PrivacyAgreementService] 清除同意状态失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 生成机器特定的密钥
/// </summary>
private string GenerateMachineSpecificKey()
{
try
{
// 组合多个机器特定信息生成密钥
var machineName = Environment.MachineName;
var userName = Environment.UserName;
var osVersion = Environment.OSVersion.Version.ToString();
var processorCount = Environment.ProcessorCount.ToString();
// 使用硬件信息(如果可用)
var hardwareId = GetHardwareIdentifier();
var keyData = $"{machineName}:{userName}:{osVersion}:{processorCount}:{hardwareId}:LanMountainDesktop";
// 使用 SHA-256 生成固定长度的密钥
using var sha256 = SHA256.Create();
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(keyData));
return Convert.ToHexString(hash);
}
catch
{
// 如果无法获取机器信息,使用备用密钥
return "LanMountainDesktop-Privacy-Agreement-Fallback-Key-2026";
}
}
/// <summary>
/// 获取硬件标识符
/// </summary>
private string GetHardwareIdentifier()
{
try
{
// 尝试使用系统目录创建时间作为硬件标识的一部分
var systemDir = Environment.SystemDirectory;
var dirInfo = new DirectoryInfo(systemDir);
return dirInfo.CreationTimeUtc.ToString("yyyyMMddHHmmss");
}
catch
{
return "Unknown";
}
}
/// <summary>
/// 生成随机盐值
/// </summary>
private string GenerateRandomSalt()
{
var saltBytes = new byte[32];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(saltBytes);
return Convert.ToHexString(saltBytes);
}
/// <summary>
/// 计算完整性哈希HMAC-SHA256
/// </summary>
private string CalculateIntegrityHash(PrivacyAgreementState state)
{
// 构建需要哈希的数据字符串
var dataToHash = $"{state.IsAgreed}:{state.AgreedAtUtc:o}:{state.AgreementVersion}:{state.UserId}:{state.DeviceId}:{state.Salt}";
// 使用 HMAC-SHA256 计算哈希
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(_secretKey));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(dataToHash));
return Convert.ToHexString(hash);
}
/// <summary>
/// 验证数据完整性
/// </summary>
private bool VerifyIntegrity(PrivacyAgreementState state)
{
try
{
if (string.IsNullOrEmpty(state.IntegrityHash) || string.IsNullOrEmpty(state.Salt))
{
return false;
}
var expectedHash = CalculateIntegrityHash(state);
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(state.IntegrityHash),
Encoding.UTF8.GetBytes(expectedHash));
}
catch
{
return false;
}
}
}

View File

@@ -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:fi="using:FluentIcons.Avalonia"
xmlns:ui="using:FluentAvalonia.UI.Controls"
mc:Ignorable="d"
d:DesignWidth="520"
d:DesignHeight="480"
x:Class="LanMountainDesktop.Launcher.Views.DataLocationPromptWindow"
x:DataType="views:DataLocationPromptWindow"
Title="Choose Data Location"
Title="选择数据保存位置"
Width="520"
Height="480"
CanResize="False"
@@ -24,13 +24,12 @@
</Grid.RenderTransform>
<Grid Margin="36" RowDefinitions="Auto,*,Auto">
<StackPanel Grid.Row="0" Spacing="8" Margin="0,0,0,20">
<TextBlock Text="Choose Data Location"
<TextBlock Text="选择数据保存位置"
FontSize="22"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="Choose where launcher and desktop data should be stored. You can change this later in settings."
<TextBlock Text="决定将应用数据保存在哪里,可随时在设置中更改"
FontSize="13"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
@@ -42,15 +41,15 @@
IsVisible="False">
<StackPanel Spacing="4">
<StackPanel Orientation="Horizontal" Spacing="6">
<fi:SymbolIcon Symbol="Important"
<ui:SymbolIcon Symbol="Important"
FontSize="16"
Foreground="{DynamicResource SystemFillColorCriticalBrush}" />
<TextBlock Text="App folder is not writable"
<TextBlock Text="无法保存到应用目录"
FontWeight="SemiBold"
FontSize="13"
Foreground="{DynamicResource SystemFillColorCriticalBrush}" />
</StackPanel>
<TextBlock Text="The current install directory requires elevated permissions. Data will be stored in the system user profile instead."
<TextBlock Text="当前安装目录需要管理员权限才能写入,数据将自动保存到系统用户目录。"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource SystemFillColorCriticalBrush}" />
@@ -71,11 +70,11 @@
GroupName="DataLocation"
IsChecked="True" />
<StackPanel Grid.Column="1" Spacing="4">
<TextBlock Text="Store in the system user profile (Recommended)"
<TextBlock Text="保存在系统用户目录(推荐)"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="Data stays tied to the current Windows user and remains intact across app reinstalls and updates."
<TextBlock Text="数据与当前 Windows 用户绑定,重装应用或更新后数据不会丢失"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
@@ -102,11 +101,11 @@
GroupName="DataLocation"
IsEnabled="False" />
<StackPanel Grid.Column="1" Spacing="4">
<TextBlock Text="Store next to the app"
<TextBlock Text="保存在应用安装目录"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="Useful for portable installs. The whole app folder can be moved to another machine together with its data."
<TextBlock Text="便于携带,可随应用文件夹整体移动到其他电脑"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
@@ -125,7 +124,7 @@
Padding="12,10"
IsVisible="False">
<StackPanel Orientation="Horizontal" Spacing="6">
<fi:SymbolIcon Symbol="Info"
<ui:SymbolIcon Symbol="Message"
FontSize="16"
Foreground="{DynamicResource SystemFillColorSuccessBrush}" />
<TextBlock x:Name="MigrationInfoText"
@@ -142,11 +141,11 @@
Spacing="10"
Margin="0,20,0,0">
<Button x:Name="CancelButton"
Content="Cancel"
Content="取消"
Theme="{DynamicResource ButtonTheme}"
IsVisible="False" />
<Button x:Name="ConfirmButton"
Content="Confirm"
Content="确认"
Theme="{DynamicResource AccentButtonTheme}" />
</StackPanel>
</Grid>

View File

@@ -48,12 +48,14 @@ internal partial class DataLocationPromptWindow : Window
if (systemRadio is not null)
{
systemRadio.IsCheckedChanged += OnSelectionChanged;
systemRadio.Checked += OnSelectionChanged;
systemRadio.Unchecked += OnSelectionChanged;
}
if (portableRadio is not null)
{
portableRadio.IsCheckedChanged += OnSelectionChanged;
portableRadio.Checked += OnSelectionChanged;
portableRadio.Unchecked += OnSelectionChanged;
}
if (confirmButton is not null)
@@ -106,7 +108,7 @@ internal partial class DataLocationPromptWindow : Window
if (migrationInfoText is not null && hasExistingData)
{
migrationInfoText.Text = "Existing system data was detected. Choosing portable mode will migrate the current data automatically.";
migrationInfoText.Text = "检测到系统用户目录已有应用数据。如果选择保存在应用安装目录,将自动迁移现有数据。";
}
}

View File

@@ -6,13 +6,13 @@
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
mc:Ignorable="d"
d:DesignWidth="850"
d:DesignHeight="650"
d:DesignWidth="700"
d:DesignHeight="500"
x:Class="LanMountainDesktop.Launcher.Views.OobeWindow"
x:DataType="views:OobeWindow"
Title="欢迎使用阑山桌面"
Width="850"
Height="650"
Width="700"
Height="500"
CanResize="False"
WindowStartupLocation="CenterScreen"
Background="{DynamicResource SolidBackgroundFillColorBaseBrush}"
@@ -441,137 +441,116 @@
<!-- 步骤 3: 数据位置选择页面 -->
<Grid x:Name="DataLocationStep" Margin="48" RowDefinitions="Auto,*,Auto" IsVisible="False">
<StackPanel Grid.Row="0" Spacing="8" Margin="0,0,0,32">
<StackPanel Grid.Row="0" Spacing="8" Margin="0,0,0,24">
<TextBlock Text="选择数据保存位置"
FontSize="28"
FontSize="24"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="决定将应用数据保存在哪里,可随时在设置中更改"
FontSize="14"
FontSize="13"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
<StackPanel Grid.Row="1" Spacing="20">
<StackPanel Grid.Row="1" Spacing="16">
<Border x:Name="AdminWarningBanner"
Background="{DynamicResource SystemFillColorCriticalBackgroundBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
Padding="16,12"
Padding="12,10"
IsVisible="False">
<StackPanel Spacing="6">
<StackPanel Orientation="Horizontal" Spacing="8">
<fi:SymbolIcon Symbol="ShieldError"
FontSize="20"
Foreground="{DynamicResource SystemFillColorCriticalBrush}" />
<StackPanel Spacing="4">
<StackPanel Orientation="Horizontal" Spacing="6">
<PathIcon Data="M12,2 L1,21 L23,21 Z M11,9 L13,9 L13,15 L11,15 Z M11,17 L13,17 L13,19 L11,19 Z"
Width="16"
Height="16"
Foreground="{DynamicResource SystemFillColorCriticalBrush}" />
<TextBlock Text="无法保存到应用目录"
FontWeight="SemiBold"
FontSize="14"
FontSize="13"
Foreground="{DynamicResource SystemFillColorCriticalBrush}" />
</StackPanel>
<TextBlock Text="当前安装目录需要管理员权限才能写入,数据将自动保存到系统用户目录。"
FontSize="13"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource SystemFillColorCriticalBrush}" />
</StackPanel>
</Border>
<!-- 系统用户目录选项 -->
<Border x:Name="SystemOptionBorder"
Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
BorderThickness="2"
BorderBrush="{DynamicResource AccentFillColorDefaultBrush}"
Padding="20,18"
Padding="16,14"
Cursor="Hand">
<Grid ColumnDefinitions="Auto,*">
<RadioButton x:Name="SystemRadio"
Grid.Column="0"
VerticalAlignment="Center"
Margin="0,0,16,0"
VerticalAlignment="Top"
Margin="0,2,12,0"
GroupName="DataLocation"
IsChecked="True" />
<StackPanel Grid.Column="1" Spacing="8">
<StackPanel Orientation="Horizontal" Spacing="8">
<fi:SymbolIcon Symbol="Folder"
FontSize="24"
Foreground="{DynamicResource AccentFillColorDefaultBrush}" />
<TextBlock Text="保存在系统用户目录(推荐)"
FontSize="16"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
</StackPanel>
<TextBlock Text="数据与当前 Windows 用户绑定,重装应用或更新后数据不会丢失。适合大多数用户。"
FontSize="13"
<StackPanel Grid.Column="1" Spacing="4">
<TextBlock Text="保存在系统用户目录(推荐)"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="数据与当前 Windows 用户绑定,重装应用或更新后数据不会丢失"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<Border Background="{DynamicResource SolidBackgroundFillColorQuarternaryBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusSm}"
Padding="12,8"
Margin="0,4,0,0">
<TextBlock x:Name="SystemPathText"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
FontFamily="Consolas, Monaco, monospace" />
</Border>
<TextBlock x:Name="SystemPathText"
FontSize="11"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
Margin="0,4,0,0" />
</StackPanel>
</Grid>
</Border>
<!-- 便携模式选项 -->
<Border x:Name="PortableOptionBorder"
Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
BorderThickness="1"
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
Padding="20,18"
Padding="16,14"
Cursor="Hand">
<Grid ColumnDefinitions="Auto,*">
<RadioButton x:Name="PortableRadio"
Grid.Column="0"
VerticalAlignment="Center"
Margin="0,0,16,0"
VerticalAlignment="Top"
Margin="0,2,12,0"
GroupName="DataLocation"
IsEnabled="False" />
<StackPanel Grid.Column="1" Spacing="8">
<StackPanel Orientation="Horizontal" Spacing="8">
<fi:SymbolIcon Symbol="Save"
FontSize="24"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<TextBlock Text="保存在应用安装目录(便携模式)"
FontSize="16"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
</StackPanel>
<TextBlock Text="便于携带,可随应用文件夹整体移动到其他电脑。适合在多台电脑间使用或需要便携运行的场景。"
FontSize="13"
<StackPanel Grid.Column="1" Spacing="4">
<TextBlock Text="保存在应用安装目录"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="便于携带,可随应用文件夹整体移动到其他电脑"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<Border Background="{DynamicResource SolidBackgroundFillColorQuarternaryBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusSm}"
Padding="12,8"
Margin="0,4,0,0">
<TextBlock x:Name="PortablePathText"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
FontFamily="Consolas, Monaco, monospace" />
</Border>
<TextBlock x:Name="PortablePathText"
FontSize="11"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
Margin="0,4,0,0" />
</StackPanel>
</Grid>
</Border>
<!-- 数据迁移提示 -->
<Border x:Name="MigrationInfoBorder"
Background="{DynamicResource SystemFillColorSuccessBackgroundBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
Padding="16,12"
Padding="12,10"
IsVisible="False">
<StackPanel Orientation="Horizontal" Spacing="8">
<fi:SymbolIcon Symbol="Checkmark"
FontSize="20"
Foreground="{DynamicResource SystemFillColorSuccessBrush}" />
<StackPanel Orientation="Horizontal" Spacing="6">
<PathIcon Data="M9,16.17 L4.83,12 L3.41,13.41 L9,19 L21,7 L19.59,5.59 Z"
Width="16"
Height="16"
Foreground="{DynamicResource SystemFillColorSuccessBrush}" />
<TextBlock x:Name="MigrationInfoText"
FontSize="13"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource SystemFillColorSuccessBrush}" />
</StackPanel>
@@ -582,156 +561,17 @@
Orientation="Horizontal"
HorizontalAlignment="Right"
Spacing="12"
Margin="0,32,0,0">
Margin="0,24,0,0">
<Button x:Name="DataLocationBackButton"
Content="返回"
Theme="{DynamicResource ButtonTheme}"
Width="100"
Height="36" />
<Button x:Name="DataLocationNextButton"
Content="下一步"
Theme="{DynamicResource AccentButtonTheme}"
Width="100"
Height="36" />
</StackPanel>
</Grid>
<!-- 步骤 4: 信息与隐私页面 -->
<Grid x:Name="PrivacyStep" Margin="48" RowDefinitions="Auto,*,Auto" IsVisible="False">
<StackPanel Grid.Row="0" Spacing="8" Margin="0,0,0,24">
<TextBlock Text="信息与隐私"
FontSize="24"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="选择是否参与遥测计划,查看隐私政策"
FontSize="13"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
<StackPanel Grid.Row="1" Spacing="16">
<!-- 崩溃报告 -->
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
Padding="16">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" Spacing="4">
<TextBlock Text="发送匿名崩溃报告"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="帮助改进应用稳定性,不包含个人身份信息"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
<ToggleSwitch x:Name="CrashTelemetryToggle"
Grid.Column="1"
IsChecked="False"
IsEnabled="False"
VerticalAlignment="Center" />
</Grid>
</Border>
<!-- 使用统计 -->
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
Padding="16">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" Spacing="4">
<TextBlock Text="发送匿名使用统计"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="帮助了解功能使用情况,优化产品体验"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
<ToggleSwitch x:Name="UsageTelemetryToggle"
Grid.Column="1"
IsChecked="False"
IsEnabled="False"
VerticalAlignment="Center" />
</Grid>
</Border>
<!-- 隐私追踪 ID -->
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
Padding="16">
<StackPanel Spacing="8">
<TextBlock Text="隐私追踪 ID"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="此 ID 用于匿名标识您的设备,不包含任何个人信息"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<TextBox x:Name="TelemetryIdTextBox"
Text=""
IsReadOnly="True"
FontFamily="Consolas, Monaco, monospace"
FontSize="12"
HorizontalAlignment="Stretch" />
</StackPanel>
</Border>
<!-- 隐私协议同意区域 -->
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
Padding="16"
Margin="0,8,0,0">
<StackPanel Spacing="12">
<!-- 复选框和协议文本 -->
<StackPanel Orientation="Horizontal" Spacing="8">
<CheckBox x:Name="PrivacyAgreementCheckBox"
VerticalAlignment="Center" />
<StackPanel Orientation="Horizontal" Spacing="4" VerticalAlignment="Center">
<TextBlock Text="同意"
FontSize="13"
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<Button x:Name="ViewPrivacyPolicyButton"
Content="《阑山桌面遥测隐私数据收集协议》"
Background="Transparent"
BorderThickness="0"
Padding="0"
FontSize="13"
Foreground="{DynamicResource SystemAccentColor}">
<Button.Styles>
<Style Selector="Button:pointerover /template/ ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemAccentColorDark1}" />
</Style>
</Button.Styles>
</Button>
</StackPanel>
</StackPanel>
<!-- 提示文本 -->
<TextBlock Text="您必须阅读并同意隐私协议后,才能开启遥测功能。遥测数据仅用于改进应用稳定性和优化产品体验,不包含任何个人身份信息。"
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
</Border>
</StackPanel>
<StackPanel Grid.Row="2"
Orientation="Horizontal"
HorizontalAlignment="Right"
Spacing="12"
Margin="0,24,0,0">
<Button x:Name="PrivacyBackButton"
Content="返回"
Theme="{DynamicResource ButtonTheme}" />
<Button x:Name="PrivacyNextButton"
<Button x:Name="DataLocationNextButton"
Content="下一步"
Theme="{DynamicResource AccentButtonTheme}" />
</StackPanel>
</Grid>
<!-- 步骤 5: 欢迎完成页面 -->
<!-- 步骤 4: 欢迎完成页面 -->
<Grid x:Name="WelcomeStep" Margin="48" RowDefinitions="*,Auto" IsVisible="False">
<StackPanel Grid.Row="0"
VerticalAlignment="Center"

View File

@@ -51,7 +51,6 @@ public partial class OobeWindow : Window
private void OnWindowLoaded(object? sender, RoutedEventArgs e)
{
InitializeDataLocationStep();
InitializePrivacySettings();
SetupEventHandlers();
}
@@ -181,29 +180,7 @@ public partial class OobeWindow : Window
};
}
// 步骤 4: 隐私设置页面
if (this.FindControl<Button>("PrivacyBackButton") is { } privacyBackButton)
{
privacyBackButton.Click += OnPrivacyBackClick;
}
if (this.FindControl<Button>("PrivacyNextButton") is { } privacyNextButton)
{
privacyNextButton.Click += OnPrivacyNextClick;
}
if (this.FindControl<Button>("ViewPrivacyPolicyButton") is { } viewPrivacyPolicyButton)
{
viewPrivacyPolicyButton.Click += OnViewPrivacyPolicyClick;
}
// 隐私协议复选框 - 控制遥测开关
if (this.FindControl<CheckBox>("PrivacyAgreementCheckBox") is { } privacyCheckBox)
{
privacyCheckBox.IsCheckedChanged += OnPrivacyAgreementChanged;
}
// 步骤 5: 欢迎完成页面
// 步骤 4: 欢迎完成页面
if (this.FindControl<Button>("EnterButton") is { } enterButton)
{
enterButton.Click += OnEnterClick;
@@ -460,60 +437,6 @@ public partial class OobeWindow : Window
await NavigateToStep(4);
}
// 隐私设置页面按钮
private async void OnPrivacyBackClick(object? sender, RoutedEventArgs e)
{
if (_isTransitioning) return;
await NavigateToStep(3);
}
private async void OnPrivacyNextClick(object? sender, RoutedEventArgs e)
{
if (_isTransitioning) return;
// 保存隐私设置
SavePrivacySettings();
await NavigateToStep(5);
}
private void OnViewPrivacyPolicyClick(object? sender, RoutedEventArgs e)
{
// 打开隐私政策窗口
var privacyWindow = new PrivacyPolicyWindow
{
WindowStartupLocation = WindowStartupLocation.CenterOwner
};
privacyWindow.ShowDialog(this);
}
private void OnPrivacyAgreementChanged(object? sender, RoutedEventArgs e)
{
// 根据复选框状态控制遥测开关
if (this.FindControl<CheckBox>("PrivacyAgreementCheckBox") is { } checkBox &&
this.FindControl<ToggleSwitch>("CrashTelemetryToggle") is { } crashToggle &&
this.FindControl<ToggleSwitch>("UsageTelemetryToggle") is { } usageToggle)
{
var isAgreed = checkBox.IsChecked == true;
// 如果用户不同意协议,禁用遥测开关并关闭它们
crashToggle.IsEnabled = isAgreed;
usageToggle.IsEnabled = isAgreed;
if (!isAgreed)
{
crashToggle.IsChecked = false;
usageToggle.IsChecked = false;
}
else
{
// 用户同意协议后,默认开启遥测(用户可以在开关中手动关闭)
crashToggle.IsChecked = true;
usageToggle.IsChecked = true;
}
}
}
private async void OnEnterClick(object? sender, RoutedEventArgs e)
{
if (_isTransitioning) return;
@@ -712,8 +635,7 @@ public partial class OobeWindow : Window
1 => this.FindControl<Grid>("TypingStep"),
2 => this.FindControl<Grid>("ThemeStep"),
3 => this.FindControl<Grid>("DataLocationStep"),
4 => this.FindControl<Grid>("PrivacyStep"),
5 => this.FindControl<Grid>("WelcomeStep"),
4 => this.FindControl<Grid>("WelcomeStep"),
_ => null
};
@@ -723,8 +645,7 @@ public partial class OobeWindow : Window
1 => this.FindControl<Grid>("TypingStep"),
2 => this.FindControl<Grid>("ThemeStep"),
3 => this.FindControl<Grid>("DataLocationStep"),
4 => this.FindControl<Grid>("PrivacyStep"),
5 => this.FindControl<Grid>("WelcomeStep"),
4 => this.FindControl<Grid>("WelcomeStep"),
_ => null
};
@@ -777,76 +698,6 @@ public partial class OobeWindow : Window
var t1 = t - 1;
return 1 + c3 * Math.Pow(t1, 3) + c1 * Math.Pow(t1, 2);
}
private void InitializePrivacySettings()
{
// 生成隐私追踪 ID
var telemetryId = Guid.NewGuid().ToString("N");
if (this.FindControl<TextBox>("TelemetryIdTextBox") is { } telemetryIdTextBox)
{
telemetryIdTextBox.Text = telemetryId;
}
}
private void SavePrivacySettings()
{
try
{
var crashTelemetryEnabled = this.FindControl<ToggleSwitch>("CrashTelemetryToggle")?.IsChecked ?? true;
var usageTelemetryEnabled = this.FindControl<ToggleSwitch>("UsageTelemetryToggle")?.IsChecked ?? true;
var telemetryId = this.FindControl<TextBox>("TelemetryIdTextBox")?.Text ?? Guid.NewGuid().ToString("N");
// 保存到启动器配置
var privacyConfig = new PrivacyConfig
{
CrashTelemetryEnabled = crashTelemetryEnabled,
UsageTelemetryEnabled = usageTelemetryEnabled,
TelemetryId = telemetryId
};
var configPath = Path.Combine(_resolver.ResolveLauncherDataPath(), "privacy-config.json");
var json = System.Text.Json.JsonSerializer.Serialize(privacyConfig, AppJsonContext.Default.PrivacyConfig);
File.WriteAllText(configPath, json);
// 保存隐私协议同意状态(带防篡改保护)
var agreementService = new PrivacyAgreementService(_resolver.ResolveLauncherDataPath());
var isAgreed = this.FindControl<CheckBox>("PrivacyAgreementCheckBox")?.IsChecked ?? false;
// 生成用户ID和设备ID
var userId = telemetryId;
var deviceId = GetDeviceIdentifier();
agreementService.SaveAgreement(isAgreed, userId, deviceId);
Logger.Info($"[OobeWindow] 隐私设置已保存: Crash={crashTelemetryEnabled}, Usage={usageTelemetryEnabled}, Agreement={isAgreed}");
}
catch (Exception ex)
{
Logger.Warn($"[OobeWindow] 保存隐私设置失败: {ex.Message}");
}
}
/// <summary>
/// 获取设备标识符
/// </summary>
private string GetDeviceIdentifier()
{
try
{
// 使用机器名和用户名的组合作为设备标识
var machineName = Environment.MachineName;
var userName = Environment.UserName;
using var sha256 = System.Security.Cryptography.SHA256.Create();
var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes($"{machineName}:{userName}"));
return Convert.ToHexString(hash).Substring(0, 16);
}
catch
{
return "UnknownDevice";
}
}
}
// 枚举定义(使用 Services 命名空间中的 ThemeMode

View File

@@ -1,63 +0,0 @@
<Window xmlns="https://github.com/avaloniaui"
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:md="clr-namespace:Markdown.Avalonia;assembly=Markdown.Avalonia"
xmlns:views="clr-namespace:LanMountainDesktop.Launcher.Views"
mc:Ignorable="d"
x:Class="LanMountainDesktop.Launcher.Views.PrivacyPolicyWindow"
x:DataType="views:PrivacyPolicyViewModel"
Title="阑山桌面遥测隐私数据收集协议"
Width="800"
Height="600"
MinWidth="600"
MinHeight="400"
WindowStartupLocation="CenterOwner"
Background="{DynamicResource SolidBackgroundFillColorBaseBrush}"
TransparencyLevelHint="None"
Icon="/Assets/logo.ico">
<Grid RowDefinitions="Auto,*,Auto">
<!-- 标题栏 -->
<Border Grid.Row="0"
Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
BorderThickness="0,0,0,1"
Padding="24,16">
<StackPanel Spacing="4">
<TextBlock Text="阑山桌面遥测隐私数据收集协议"
FontSize="20"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="请仔细阅读以下协议内容,了解我们如何收集、使用和保护您的数据"
FontSize="13"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
</Border>
<!-- Markdown 内容区域 -->
<Border Grid.Row="1"
Margin="24,16"
Background="{DynamicResource SolidBackgroundFillColorBaseBrush}">
<md:MarkdownScrollViewer x:Name="MarkdownViewer"
Markdown="{Binding PrivacyPolicyMarkdown}"
HorizontalAlignment="Stretch" />
</Border>
<!-- 底部按钮 -->
<Border Grid.Row="2"
Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
BorderThickness="0,1,0,0"
Padding="24,16">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Right"
Spacing="12">
<Button x:Name="CloseButton"
Content="关闭"
Theme="{DynamicResource AccentButtonTheme}"
Width="100" />
</StackPanel>
</Border>
</Grid>
</Window>

View File

@@ -1,121 +0,0 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using CommunityToolkit.Mvvm.ComponentModel;
namespace LanMountainDesktop.Launcher.Views;
public partial class PrivacyPolicyWindow : Window
{
private readonly PrivacyPolicyViewModel _viewModel;
public PrivacyPolicyWindow()
{
InitializeComponent();
_viewModel = new PrivacyPolicyViewModel();
DataContext = _viewModel;
// 加载隐私政策内容
LoadPrivacyPolicy();
// 绑定关闭按钮事件
if (this.FindControl<Button>("CloseButton") is { } closeButton)
{
closeButton.Click += OnCloseClick;
}
}
private void OnCloseClick(object? sender, RoutedEventArgs e)
{
Close();
}
private void LoadPrivacyPolicy()
{
// 默认隐私政策内容Markdown 格式)
_viewModel.PrivacyPolicyMarkdown = @"# 阑山桌面遥测隐私数据收集协议
## 1. 概述
欢迎使用阑山桌面!本协议旨在向您说明我们在应用运行过程中收集哪些数据、如何使用这些数据以及如何保护您的隐私。
## 2. 我们收集的数据
### 2.1 崩溃报告(可选)
当应用发生崩溃时,我们可能会收集以下信息:
- **崩溃类型**:应用程序崩溃、无响应等异常情况的类型
- **错误堆栈**:导致崩溃的代码路径(不包含文件内容或个人数据)
- **设备信息**:操作系统版本、应用版本、.NET 运行时版本
- **匿名设备标识符**:一个随机生成的唯一标识符,用于统计崩溃频率
**注意**:崩溃报告不包含您的个人文件、桌面组件内容、浏览历史或任何可识别个人身份的信息。
### 2.2 使用统计(可选)
如果您启用了使用统计,我们可能会收集:
- **功能使用频率**:各功能模块的使用次数(如设置打开次数、组件添加次数)
- **性能指标**:应用启动时间、内存占用范围等性能数据
- **匿名设备标识符**:用于统计独立用户数量
**注意**:使用统计不包含您的组件配置、个人设置或任何敏感信息。
## 3. 我们不收集的数据
我们明确**不会**收集以下信息:
- ❌ 您的姓名、邮箱、电话号码等个人身份信息
- ❌ 您的桌面截图或壁纸内容
- ❌ 您添加的组件的具体内容或配置详情
- ❌ 您的文件系统浏览记录
- ❌ 您的网络活动或浏览历史
- ❌ 您的精确地理位置信息
## 4. 数据用途
我们收集的数据仅用于以下目的:
1. **改进应用稳定性**:通过分析崩溃报告,修复程序缺陷
2. **优化产品体验**:了解功能使用情况,优先改进常用功能
3. **性能优化**:识别性能瓶颈,提升应用运行效率
## 5. 数据存储与保护
- 所有数据通过**加密传输**HTTPS发送到我们的服务器
- 数据存储在安全的服务器环境中,访问受到严格控制
- 匿名设备标识符仅用于统计目的,无法关联到您的真实身份
- 我们**不会**将数据出售或共享给任何第三方用于商业目的
## 6. 您的控制权
您拥有以下权利:
- **随时开启或关闭**:您可以在 OOBE 向导或设置中随时更改遥测选项
- **数据删除**:如果您希望删除已收集的数据,请联系我们的支持团队
- **知情权**:您有权了解我们收集了哪些数据(通过本协议)
## 7. 协议更新
我们可能会不时更新本协议。重大变更时,我们会在应用内通知您。继续使用本应用即表示您同意修订后的协议。
## 8. 联系我们
如果您对本协议有任何疑问,请通过以下方式联系我们:
- 项目主页https://github.com/LanMountain/LanMountainDesktop
- 问题反馈:在 GitHub 仓库提交 Issue
---
**最后更新日期**2026年4月26日
感谢您信任并使用阑山桌面!";
}
}
public partial class PrivacyPolicyViewModel : ObservableObject
{
[ObservableProperty]
private string _privacyPolicyMarkdown = string.Empty;
}

View File

@@ -12,7 +12,7 @@
CanResize="False"
ShowInTaskbar="False"
WindowStartupLocation="CenterScreen"
WindowDecorations="None"
SystemDecorations="None"
Background="#0B0B0B"
TransparencyLevelHint="None"
Icon="/Assets/logo.ico">

View File

@@ -13,7 +13,7 @@
Height="320"
CanResize="False"
WindowStartupLocation="CenterScreen"
WindowDecorations="None"
SystemDecorations="None"
Background="{DynamicResource SolidBackgroundFillColorBaseBrush}"
TransparencyLevelHint="None"
Icon="/Assets/logo.ico">

View File

@@ -3,7 +3,7 @@
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Version>5.0.0</Version>
<Version>1.0.0</Version>
<PackageId>LanMountainDesktop.PluginIsolation.Contracts</PackageId>
<IsPackable>true</IsPackable>
<Authors>LanMountainDesktop</Authors>

View File

@@ -3,7 +3,7 @@
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Version>5.0.0</Version>
<Version>1.0.0</Version>
<PackageId>LanMountainDesktop.PluginIsolation.Ipc</PackageId>
<IsPackable>true</IsPackable>
<Authors>LanMountainDesktop</Authors>
@@ -17,7 +17,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="dotnetCampus.Ipc" />
<PackageReference Include="dotnetCampus.Ipc" Version="2.0.0-alpha434" />
<ProjectReference Include="..\LanMountainDesktop.PluginIsolation.Contracts\LanMountainDesktop.PluginIsolation.Contracts.csproj" />
</ItemGroup>

View File

@@ -4,7 +4,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>5.0.0</Version>
<Version>4.0.2</Version>
<PackageId>LanMountainDesktop.PluginSdk</PackageId>
<IsPackable>true</IsPackable>
<Authors>LanMountainDesktop</Authors>
@@ -19,12 +19,13 @@
<ItemGroup>
<Compile Remove="_build_verify_*\**\*.cs" />
<PackageReference Include="Avalonia" />
<PackageReference Include="FluentAvaloniaUI" ExcludeAssets="runtime" />
<PackageReference Include="FluentIcons.Avalonia" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
<PackageReference Include="dotnetCampus.Ipc" />
<PackageReference Include="Avalonia" Version="11.3.12" />
<PackageReference Include="FluentAvaloniaUI" Version="2.5.0" ExcludeAssets="runtime" />
<PackageReference Include="FluentIcons.Avalonia" Version="2.0.320" ExcludeAssets="runtime" />
<PackageReference Include="FluentIcons.Avalonia.Fluent" Version="2.0.320" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
<PackageReference Include="dotnetCampus.Ipc" Version="2.0.0-alpha434" />
<ProjectReference Include="..\LanMountainDesktop.PluginIsolation.Contracts\LanMountainDesktop.PluginIsolation.Contracts.csproj" />
<ProjectReference Include="..\LanMountainDesktop.Shared.Contracts\LanMountainDesktop.Shared.Contracts.csproj" />
<ProjectReference Include="..\LanMountainDesktop.Shared.IPC\LanMountainDesktop.Shared.IPC.csproj" />

View File

@@ -2,7 +2,7 @@ namespace LanMountainDesktop.PluginSdk;
public static class PluginSdkInfo
{
public const string ApiVersion = "5.0.0";
public const string ApiVersion = "4.0.2";
public const string ManifestFileName = "plugin.json";
public const string PackageFileExtension = ".laapp";
public const string DataDirectoryName = "Data";

View File

@@ -16,7 +16,7 @@ Official SDK package for LanMountainDesktop plugins.
```xml
<ItemGroup>
<PackageReference Include="LanMountainDesktop.PluginSdk" Version="5.0.0" />
<PackageReference Include="LanMountainDesktop.PluginSdk" Version="4.0.1" />
</ItemGroup>
```

View File

@@ -47,7 +47,7 @@
"pluginSdkVersion": {
"type": "parameter",
"datatype": "text",
"defaultValue": "5.0.0",
"defaultValue": "4.0.2",
"description": "LanMountainDesktop.PluginSdk package version.",
"replaces": "__PLUGIN_SDK_VERSION__"
}

View File

@@ -4,7 +4,7 @@
"description": "__PLUGIN_DESCRIPTION__",
"author": "__PLUGIN_AUTHOR__",
"version": "1.0.0",
"apiVersion": "5.0.0",
"apiVersion": "4.0.2",
"entranceAssembly": "LanMountainDesktop.PluginTemplate.dll",
"sharedContracts": [],
"runtime": {

View File

@@ -3,7 +3,7 @@
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Version>5.0.0</Version>
<Version>0.0.0-dev</Version>
<PackageId>LanMountainDesktop.Shared.Contracts</PackageId>
<IsPackable>true</IsPackable>
<Authors>LanMountainDesktop</Authors>
@@ -17,7 +17,7 @@
<Copyright>Copyright (c) LanMountainDesktop Contributors</Copyright>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" />
<PackageReference Include="Avalonia" Version="11.3.12" />
</ItemGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="\" />

View File

@@ -3,7 +3,7 @@
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Version>5.0.0</Version>
<Version>1.0.0</Version>
<PackageId>LanMountainDesktop.Shared.IPC</PackageId>
<IsPackable>true</IsPackable>
<Authors>LanMountainDesktop</Authors>
@@ -17,8 +17,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="dotnetCampus.Ipc" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<PackageReference Include="dotnetCampus.Ipc" Version="2.0.0-alpha434" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
<ProjectReference Include="..\LanMountainDesktop.Shared.Contracts\LanMountainDesktop.Shared.Contracts.csproj" />
</ItemGroup>

View File

@@ -1,3 +1,4 @@
using System.Text.Json;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
using Xunit;
@@ -33,14 +34,10 @@ public sealed class ComponentSettingsServiceTests
Assert.Equal("Sweep", snapshot.DesktopClockSecondHandMode);
Assert.Single(snapshot.ImportedClassSchedules);
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);
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 _));
}
[Fact]
@@ -75,16 +72,11 @@ public sealed class ComponentSettingsServiceTests
Assert.Equal("Sweep", snapshot.DesktopClockSecondHandMode);
Assert.True(pluginSettings.SampleFlag);
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<SamplePluginSettings>("DesktopClock", "clock-2x2");
Assert.Equal("Sweep", reloadedSnapshot.DesktopClockSecondHandMode);
Assert.True(reloadedPluginSettings.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 _));
}
[Fact]
@@ -140,7 +132,12 @@ public sealed class ComponentSettingsServiceTests
Assert.True(pluginSettings.SampleFlag);
Assert.Equal("schedule-settings", pluginSettings.Title);
Assert.True(File.Exists(sandbox.DatabasePath));
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 _));
}
private sealed class ComponentSettingsSandbox : IDisposable
@@ -158,10 +155,6 @@ 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);

View File

@@ -8,9 +8,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

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

View File

@@ -14,6 +14,7 @@ using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.Threading;
using AvaloniaWebView;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.DesktopHost;
using LanMountainDesktop.Models;
@@ -77,6 +78,7 @@ 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;
@@ -192,6 +194,8 @@ public partial class App : Application
return;
}
ConfigureWebViewUserDataFolder();
AvaloniaWebViewBuilder.Initialize(default);
ApplyThemeFromSettings();
ApplyCurrentCultureFromSettings();
EnsureSettingsWindowService();
@@ -208,7 +212,8 @@ public partial class App : Application
}
AppLogger.Info("App", "Framework initialization completed.");
RegisterUiUnhandledExceptionGuard();
LinuxDesktopEntryInstaller.EnsureInstalled();
InitializePublicIpc();
CurrentSingleInstanceService?.StartActivationListener(ActivateMainWindow);
@@ -527,8 +532,43 @@ public partial class App : Application
private void DisableAvaloniaDataAnnotationValidation()
{
// Avalonia 12 中 BindingPlugins 已移除,数据验证插件不再需要手动禁用
// 编译型绑定默认开启,数据注解验证行为已改变
// Get an array of plugins to remove
var dataValidationPluginsToRemove =
BindingPlugins.DataValidators.OfType<DataAnnotationsValidationPlugin>().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);
}
}
private void InitializePluginRuntime()
@@ -1138,6 +1178,43 @@ 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)
@@ -1882,3 +1959,4 @@ public partial class App : Application
}
}

View File

@@ -108,7 +108,7 @@ public partial class SettingsOptionCard : UserControl
"Info" => Symbol.Info,
"ArrowSync" => Symbol.ArrowSync,
"Alert" => Symbol.Alert,
"Bell" => Symbol.AlertOn,
"Bell" => Symbol.Alert, // Bell也映射到Alert图标
_ => Symbol.Settings
};
}

View File

@@ -92,7 +92,6 @@ public partial class SettingsSectionCard : UserControl
"PuzzlePiece" => Symbol.PuzzlePiece,
"Info" => Symbol.Info,
"ArrowSync" => Symbol.ArrowSync,
"Bell" => Symbol.AlertOn,
_ => Symbol.Settings
};
}

View File

@@ -41,42 +41,44 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" />
<PackageReference Include="Avalonia.Controls.WebView" />
<PackageReference Include="Avalonia.Desktop" />
<PackageReference Include="Avalonia.Themes.Fluent" />
<PackageReference Include="Avalonia.Fonts.Inter" />
<!--Condition below is needed to remove developer tools support from build output in Release configuration.-->
<PackageReference Include="AvaloniaUI.DiagnosticsSupport">
<PackageReference Include="Avalonia" Version="11.3.12" />
<PackageReference Include="Avalonia.Desktop" Version="11.3.12" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.12" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.12" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.12">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="CommunityToolkit.Mvvm" />
<PackageReference Include="DotNetCampus.AvaloniaInkCanvas" />
<PackageReference Include="Downloader" />
<PackageReference Include="FluentAvaloniaUI" />
<PackageReference Include="FluentIcons.Avalonia" />
<PackageReference Include="Material.Avalonia" />
<PackageReference Include="Material.Icons.Avalonia" />
<PackageReference Include="ClassIsland.Markdown.Avalonia" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
<PackageReference Include="Microsoft.Data.Sqlite" />
<PackageReference Include="MudTools.OfficeInterop" />
<PackageReference Include="MudTools.OfficeInterop.Word" />
<PackageReference Include="MudTools.OfficeInterop.Excel" />
<PackageReference Include="MudTools.OfficeInterop.PowerPoint" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
<PackageReference Include="DotNetCampus.AvaloniaInkCanvas" Version="1.0.1" />
<PackageReference Include="Downloader" Version="4.1.1" />
<PackageReference Include="FluentAvaloniaUI" Version="2.5.0" />
<PackageReference Include="FluentIcons.Avalonia" Version="2.0.320" />
<PackageReference Include="FluentIcons.Avalonia.Fluent" Version="2.0.320" />
<PackageReference Include="Material.Avalonia" Version="3.13.4" />
<PackageReference Include="Material.Icons.Avalonia" Version="2.4.1" />
<PackageReference Include="ClassIsland.Markdown.Avalonia" Version="11.0.3.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.0" />
<PackageReference Include="MudTools.OfficeInterop" Version="2.0.8" />
<PackageReference Include="MudTools.OfficeInterop.Word" Version="2.0.8" />
<PackageReference Include="MudTools.OfficeInterop.Excel" Version="2.0.8" />
<PackageReference Include="MudTools.OfficeInterop.PowerPoint" Version="2.0.8" />
<PackageReference Include="PortAudioSharp2" />
<PackageReference Include="MaterialColorUtilities" />
<PackageReference Include="PostHog" />
<PackageReference Include="Sentry" />
<PackageReference Include="System.Runtime.WindowsRuntime" />
<PackageReference Include="System.Drawing.Common" />
<PackageReference Include="PortAudioSharp2" Version="1.0.6" />
<PackageReference Include="MaterialColorUtilities" Version="0.3.0" />
<PackageReference Include="PostHog" Version="2.4.0" />
<PackageReference Include="Sentry" Version="4.0.0" />
<PackageReference Include="System.Runtime.WindowsRuntime" Version="4.7.0" />
<PackageReference Include="System.Drawing.Common" Version="10.0.0" />
<PackageReference Include="YamlDotNet" />
<PackageReference Include="Tmds.DBus.Protocol" />
<PackageReference Include="log4net" />
<PackageReference Include="WebView.Avalonia" Version="11.0.0.1" />
<PackageReference Include="WebView.Avalonia.Desktop" Version="11.0.0.1" />
<PackageReference Include="YamlDotNet" Version="16.3.0" />
<PackageReference Include="Tmds.DBus.Protocol" Version="0.92.0" />
<PackageReference Include="log4net" Version="3.3.0" />
</ItemGroup>
<!-- Launcher 构建目标已移除 - Launcher 现在是独立应用,由 CI/CD 单独构建 -->

View File

@@ -3,6 +3,7 @@ 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;
@@ -110,6 +111,7 @@ public sealed class Program
{
var builder = AppBuilder.Configure<App>()
.UsePlatformDetect()
.UseDesktopWebView()
.WithInterFont()
.LogToTrace();

View File

@@ -88,6 +88,8 @@ 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)
{
@@ -95,11 +97,6 @@ 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<uint>(framebuffer.Size.Width * framebuffer.Size.Height);

View File

@@ -65,7 +65,7 @@ public interface INotificationService
{
void Show(NotificationContent content);
Task<FAContentDialogResult> ShowDialogAsync(NotificationContent content);
Task<ContentDialogResult> 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<FAContentDialogResult> ShowDialogInfoAsync(string title, string? message = null,
string? primaryButtonText = "OK", string? closeButtonText = "Cancel");
Task<ContentDialogResult> ShowDialogInfoAsync(string title, string? message = null,
string? primaryButtonText = "确定", string? closeButtonText = "取消");
Task<FAContentDialogResult> ShowDialogSuccessAsync(string title, string? message = null,
string? primaryButtonText = "OK", string? closeButtonText = "Cancel");
Task<ContentDialogResult> ShowDialogSuccessAsync(string title, string? message = null,
string? primaryButtonText = "确定", string? closeButtonText = "取消");
Task<FAContentDialogResult> ShowDialogWarningAsync(string title, string? message = null,
string? primaryButtonText = "OK", string? closeButtonText = "Cancel");
Task<ContentDialogResult> ShowDialogWarningAsync(string title, string? message = null,
string? primaryButtonText = "确定", string? closeButtonText = "取消");
Task<FAContentDialogResult> ShowDialogErrorAsync(string title, string? message = null,
string? primaryButtonText = "OK", string? closeButtonText = "Cancel");
Task<ContentDialogResult> ShowDialogErrorAsync(string title, string? message = null,
string? primaryButtonText = "确定", string? closeButtonText = "取消");
}
internal sealed class NotificationService : INotificationService
@@ -105,17 +105,20 @@ 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);
}
@@ -150,35 +153,37 @@ internal sealed class NotificationService : INotificationService
});
}
public async Task<FAContentDialogResult> ShowDialogAsync(NotificationContent content)
public async Task<ContentDialogResult> ShowDialogAsync(NotificationContent content)
{
// 检查通知开关是否启用
if (!IsNotificationEnabled())
{
return FAContentDialogResult.None;
return ContentDialogResult.None; // 通知已禁用,不显示
}
return await Dispatcher.UIThread.InvokeAsync(() => ShowDialogCoreAsync(content));
}
private async Task<FAContentDialogResult> ShowDialogCoreAsync(NotificationContent content)
private async Task<ContentDialogResult> 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 FAContentDialogResult.None;
return ContentDialogResult.None;
}
var dialog = new FAContentDialog
var dialog = new ContentDialog
{
Title = content.Title,
Content = content.Message ?? string.Empty,
PrimaryButtonText = content.PrimaryButtonText,
SecondaryButtonText = content.SecondaryButtonText,
CloseButtonText = content.CloseButtonText,
DefaultButton = !string.IsNullOrEmpty(content.PrimaryButtonText) ? FAContentDialogButton.Primary :
!string.IsNullOrEmpty(content.SecondaryButtonText) ? FAContentDialogButton.Secondary :
FAContentDialogButton.Close
DefaultButton = !string.IsNullOrEmpty(content.PrimaryButtonText) ? ContentDialogButton.Primary :
!string.IsNullOrEmpty(content.SecondaryButtonText) ? ContentDialogButton.Secondary :
ContentDialogButton.Close
};
var result = await dialog.ShowAsync(mainWindow);
@@ -186,10 +191,10 @@ internal sealed class NotificationService : INotificationService
// Execute callbacks based on result
switch (result)
{
case FAContentDialogResult.Primary:
case ContentDialogResult.Primary:
content.OnPrimaryButtonClick?.Invoke();
break;
case FAContentDialogResult.Secondary:
case ContentDialogResult.Secondary:
content.OnSecondaryButtonClick?.Invoke();
break;
}
@@ -201,13 +206,14 @@ internal sealed class NotificationService : INotificationService
{
try
{
// 从全局设置服务中读取通知开关状态
var settingsFacade = HostSettingsFacadeProvider.GetOrCreate();
var snapshot = settingsFacade.Settings.LoadSnapshot<AppSettingsSnapshot>(PluginSdk.SettingsScope.App);
return snapshot.NotificationEnabled;
}
catch
{
// 濠电姷顣介埀顒€鍟块埀顒€缍婇幃妯诲緞鐏炴儳鐝伴柣鐘叉处瑜板啰寰婇崹顕呯唵闁诡垱澹嗙花鍧楁偡濞嗘瑧鐣甸柡浣哥Т閻f繈宕熼鐐殿偧闂佽崵濮抽梽宥夊磹閺囥垹绠氶幖娣妽閸嬨劑鏌曟繛鐐澒闁稿鎸搁~婵囨綇閳轰礁缁?
// 如果读取失败,默认启用通知
return true;
}
}
@@ -280,8 +286,8 @@ internal sealed class NotificationService : INotificationService
Show(new NotificationContent(title, message, Severity: NotificationSeverity.Error, Position: position));
}
public Task<FAContentDialogResult> ShowDialogInfoAsync(string title, string? message = null,
string? primaryButtonText = "OK", string? closeButtonText = "Cancel")
public Task<ContentDialogResult> ShowDialogInfoAsync(string title, string? message = null,
string? primaryButtonText = "确定", string? closeButtonText = "取消")
{
return ShowDialogAsync(new NotificationContent(
title,
@@ -292,8 +298,8 @@ internal sealed class NotificationService : INotificationService
CloseButtonText: closeButtonText));
}
public Task<FAContentDialogResult> ShowDialogSuccessAsync(string title, string? message = null,
string? primaryButtonText = "OK", string? closeButtonText = "Cancel")
public Task<ContentDialogResult> ShowDialogSuccessAsync(string title, string? message = null,
string? primaryButtonText = "确定", string? closeButtonText = "取消")
{
return ShowDialogAsync(new NotificationContent(
title,
@@ -304,8 +310,8 @@ internal sealed class NotificationService : INotificationService
CloseButtonText: closeButtonText));
}
public Task<FAContentDialogResult> ShowDialogWarningAsync(string title, string? message = null,
string? primaryButtonText = "OK", string? closeButtonText = "Cancel")
public Task<ContentDialogResult> ShowDialogWarningAsync(string title, string? message = null,
string? primaryButtonText = "确定", string? closeButtonText = "取消")
{
return ShowDialogAsync(new NotificationContent(
title,
@@ -316,8 +322,8 @@ internal sealed class NotificationService : INotificationService
CloseButtonText: closeButtonText));
}
public Task<FAContentDialogResult> ShowDialogErrorAsync(string title, string? message = null,
string? primaryButtonText = "OK", string? closeButtonText = "Cancel")
public Task<ContentDialogResult> ShowDialogErrorAsync(string title, string? message = null,
string? primaryButtonText = "确定", string? closeButtonText = "取消")
{
return ShowDialogAsync(new NotificationContent(
title,
@@ -351,7 +357,7 @@ internal sealed class NotificationWindowManager
var position = viewModel.Position;
var windows = _windowsByPosition[position];
// 濠电偛顕慨鏉戭潩閿曞偆鏁婇柡鍥╁Х绾剧偓銇勯弮鈧Σ鎺楀储椤掑嫭鍋i柛銉憾閸ゆ瑧鎲搁弶鎸庡枠鐎殿喚鏁婚崺鈧い鎺嶇缁剁偟鎲搁弮鍫濈劦妞ゆ帊鐒﹂惃鎴︽煟閹垮嫮绡€鐎殿噮鍋呯€靛ジ寮堕幋鐑嗕画
// 从设置中读取最大通知数量
var maxNotifications = GetMaxNotificationsPerPosition();
if (windows.Count >= maxNotifications)
@@ -389,13 +395,14 @@ internal sealed class NotificationWindowManager
{
try
{
// 濠电偛顕慨瀛橆殽閹间礁鐭楅煫鍥ㄦ磻濞岊亪鏌嶈閸撴盯骞忕€n喖绀堢憸蹇涘几閸岀偞鐓涢柛顐g箘瀛濇繝娈垮枤閸犳劗绮欐径鎰垫晣闁宠棄妫楀▓娲⒑閸涘﹦鎳勯柣妤侇殔閵嗘帡鎳滈棃娑氱獮閻熸粍妫冮崺鈧い鎺嶇劍閻ㄦ垿鏌i幙鍕瘈鐎殿噮鍋呯€靛ジ寮堕幋鐑嗕画
// 从全局设置服务中读取最大通知数量
var settingsFacade = HostSettingsFacadeProvider.GetOrCreate();
var snapshot = settingsFacade.Settings.LoadSnapshot<AppSettingsSnapshot>(PluginSdk.SettingsScope.App);
return snapshot.NotificationMaxPerPosition > 0 ? snapshot.NotificationMaxPerPosition : 5;
}
catch
{
// 如果读取失败,返回默认值
return 5;
}
}

View File

@@ -19,7 +19,7 @@ public static class WebView2RuntimeProbe
public static WebView2RuntimeAvailability GetAvailability()
{
if (OperatingSystem.IsMacOS())
if (!OperatingSystem.IsWindows())
{
return new WebView2RuntimeAvailability(
IsAvailable: true,
@@ -27,14 +27,6 @@ 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();

View File

@@ -70,25 +70,11 @@
<Setter Property="Foreground" Value="{DynamicResource AdaptiveTextPrimaryBrush}" />
</Style>
<Style Selector="ui|FANumberBox">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{DynamicResource ControlStrokeColorDefaultBrush}" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusMd}" />
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}" />
</Style>
<Style Selector="ui|FANumberBox /template/ Button#PART_SpinUp">
<Setter Property="Margin" Value="0" />
<Setter Property="CornerRadius" Value="4,4,0,0" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}" />
</Style>
<Style Selector="ui|FANumberBox /template/ Button#PART_SpinDown">
<Setter Property="Margin" Value="0" />
<Setter Property="CornerRadius" Value="0,0,4,4" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}" />
<Style Selector="ui|NumberBox">
<Setter Property="Background" Value="{DynamicResource AdaptiveButtonBackgroundBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="{DynamicResource AdaptiveButtonBorderBrush}" />
<Setter Property="Foreground" Value="{DynamicResource AdaptiveTextPrimaryBrush}" />
</Style>
<Style Selector="CheckBox">
@@ -139,7 +125,7 @@
<Setter Property="Background" Value="{DynamicResource AdaptiveNavItemSelectedBackgroundBrush}" />
</Style>
<Style Selector=".settings-scope ui|FANumberBox">
<Style Selector=".settings-scope ui|NumberBox">
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusSm}" />
<Setter Property="MinHeight" Value="34" />
</Style>
@@ -166,7 +152,7 @@
<Setter Property="Background" Value="{DynamicResource AdaptiveNavItemSelectedBackgroundBrush}" />
</Style>
<Style Selector=".settings-scope ui|FANavigationView, .settings-scope ui|FANavigationViewItem, .settings-scope ui|FASettingsExpander, .settings-scope ui|FAInfoBar, .settings-scope ListBoxItem">
<Style Selector=".settings-scope ui|NavigationView, .settings-scope ui|NavigationViewItem, .settings-scope ui|SettingsExpander, .settings-scope ui|InfoBar, .settings-scope ListBoxItem">
<Setter Property="Foreground" Value="{DynamicResource AdaptiveTextPrimaryBrush}" />
</Style>
@@ -183,9 +169,9 @@
</Style>
<!--
鍗婇€忔槑琛ㄩ潰鏍峰紡绫?
娉ㄦ剰锛氳繖浜涙牱寮忎娇鐢ㄧ函鑹插崐閫忔槑鐢诲埛妯℃嫙鐜荤拑鏁堟灉锛屽苟闈炵湡姝g殑 Mica/Acrylic 妯$硦鏉愯川銆?
鐪熸鐨?Mica/Acrylic 鏁堟灉浠呴€氳繃 WindowTransparencyLevel 鍦ㄧ嫭绔嬬獥鍙d笂搴旂敤銆?
半透明表面样式类
注意:这些样式使用纯色半透明画刷模拟玻璃效果,并非真正的 Mica/Acrylic 模糊材质。
真正的 Mica/Acrylic 效果仅通过 WindowTransparencyLevel 在独立窗口上应用。
-->
<Style Selector="Border.surface-translucent-panel">
@@ -235,7 +221,7 @@
<Setter Property="Opacity" Value="{DynamicResource AdaptiveGlassOverlayOpacity}" />
</Style>
<!-- 鍚戝悗鍏煎鐨勬棫鏍峰紡绫伙紙宸插純鐢級 -->
<!-- 向后兼容的旧样式类(已弃用) -->
<Style Selector="Border.glass-panel">
<Setter Property="Background" Value="{DynamicResource AdaptiveGlassPanelBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource AdaptiveGlassPanelBorderBrush}" />

View File

@@ -1,7 +1,7 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia">
xmlns:fi="using:FluentIcons.Avalonia.Fluent">
<Styles.Resources>
<x:Double x:Key="PaneToggleButtonWidth">40</x:Double>
@@ -115,7 +115,7 @@
<Setter Property="Background" Value="{DynamicResource SubtleFillColorTertiaryBrush}" />
</Style>
<Style Selector="ui|FANavigationView.settings-navigation-view">
<Style Selector="ui|NavigationView.settings-navigation-view">
<Setter Property="Transitions">
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.2" Easing="0.05,0.75,0.10,1.00" />
@@ -123,7 +123,7 @@
</Setter>
</Style>
<Style Selector="ui|FANavigationView.settings-navigation-view /template/ Border#NavigationViewBorder">
<Style Selector="ui|NavigationView.settings-navigation-view /template/ Border#NavigationViewBorder">
<Setter Property="Transitions">
<Transitions>
<BrushTransition Property="Background" Duration="0:0:0.167" Easing="0.05,0.75,0.10,1.00" />
@@ -131,7 +131,7 @@
</Setter>
</Style>
<Style Selector="ui|FANavigationViewItem.settings-nav-item">
<Style Selector="ui|NavigationViewItem.settings-nav-item">
<Setter Property="Transitions">
<Transitions>
<BrushTransition Property="Background" Duration="0:0:0.083" Easing="0.05,0.75,0.10,1.00" />
@@ -140,11 +140,11 @@
</Setter>
</Style>
<Style Selector="ui|FANavigationViewItem.settings-nav-item:pointerover">
<Style Selector="ui|NavigationViewItem.settings-nav-item:pointerover">
<Setter Property="RenderTransform" Value="scale(1.01)" />
</Style>
<Style Selector="ui|FANavigationViewItem.settings-nav-item:pressed">
<Style Selector="ui|NavigationViewItem.settings-nav-item:pressed">
<Setter Property="RenderTransform" Value="scale(0.99)" />
</Style>

View File

@@ -1,7 +1,7 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
xmlns:behaviors="using:LanMountainDesktop.Behaviors">
<Style Selector="StackPanel.settings-page-container">
@@ -162,59 +162,59 @@
<Setter Property="ColumnSpacing" Value="12" />
</Style>
<Style Selector="ui|FASettingsExpander">
<Style Selector="ui|SettingsExpander">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="MinWidth" Value="0" />
</Style>
<Style Selector="ui|FASettingsExpander ComboBox, ui|FASettingsExpander TextBox, ui|FASettingsExpander NumericUpDown">
<Style Selector="ui|SettingsExpander ComboBox, ui|SettingsExpander TextBox, ui|SettingsExpander NumericUpDown">
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="MinWidth" Value="0" />
</Style>
<Style Selector="ui|FASettingsExpanderItem ComboBox, ui|FASettingsExpanderItem TextBox, ui|FASettingsExpanderItem NumericUpDown">
<Style Selector="ui|SettingsExpanderItem ComboBox, ui|SettingsExpanderItem TextBox, ui|SettingsExpanderItem NumericUpDown">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="MinWidth" Value="0" />
</Style>
<Style Selector="ui|FASettingsExpander ToggleSwitch, ui|FASettingsExpanderItem ToggleSwitch">
<Style Selector="ui|SettingsExpander ToggleSwitch, ui|SettingsExpanderItem ToggleSwitch">
<Setter Property="HorizontalAlignment" Value="Left" />
</Style>
<Style Selector="ui|FASettingsExpander.settings-expander-card">
<Style Selector="ui|SettingsExpander.settings-expander-card">
<Setter Property="Margin" Value="0,0,0,14" />
</Style>
<Style Selector="ui|FASettingsExpander.settings-expander-card /template/ ContentPresenter#FooterContentPresenter">
<Style Selector="ui|SettingsExpander.settings-expander-card /template/ ContentPresenter#FooterContentPresenter">
<Setter Property="Margin" Value="0,6,0,2" />
</Style>
<Style Selector="ui|FASettingsExpander.settings-expander-card /template/ ContentPresenter#ContentPresenter">
<Style Selector="ui|SettingsExpander.settings-expander-card /template/ ContentPresenter#ContentPresenter">
<Setter Property="Margin" Value="0,14,0,0" />
</Style>
<Style Selector="ui|FASettingsExpander.settings-expander-card ComboBox, .settings-section-card ComboBox, .settings-option-card ComboBox">
<Style Selector="ui|SettingsExpander.settings-expander-card ComboBox, .settings-section-card ComboBox, .settings-option-card ComboBox">
<Setter Property="MinWidth" Value="220" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style>
<Style Selector="ui|FASettingsExpander.settings-expander-card TextBox, .settings-section-card TextBox, .settings-option-card TextBox">
<Style Selector="ui|SettingsExpander.settings-expander-card TextBox, .settings-section-card TextBox, .settings-option-card TextBox">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="MinHeight" Value="38" />
</Style>
<Style Selector="ui|FASettingsExpander.settings-expander-card NumericUpDown, .settings-section-card NumericUpDown, .settings-option-card NumericUpDown">
<Style Selector="ui|SettingsExpander.settings-expander-card NumericUpDown, .settings-section-card NumericUpDown, .settings-option-card NumericUpDown">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="MinHeight" Value="38" />
</Style>
<Style Selector="ui|FASettingsExpander.settings-expander-card ToggleSwitch, .settings-option-card ToggleSwitch, .settings-list-item ToggleSwitch">
<Style Selector="ui|SettingsExpander.settings-expander-card ToggleSwitch, .settings-option-card ToggleSwitch, .settings-list-item ToggleSwitch">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="OnContent" Value="{x:Null}" />
<Setter Property="OffContent" Value="{x:Null}" />
</Style>
<Style Selector=".settings-section-card Button, .settings-option-card Button, .settings-list-item Button, ui|FASettingsExpander.settings-expander-card Button">
<Style Selector=".settings-section-card Button, .settings-option-card Button, .settings-list-item Button, ui|SettingsExpander.settings-expander-card Button">
<Setter Property="MinHeight" Value="36" />
<Setter Property="Padding" Value="14,8" />
</Style>

View File

@@ -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;assembly=FluentIcons.Avalonia"
xmlns:fa="clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent"
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"
WindowDecorations="BorderOnly"
SystemDecorations="BorderOnly"
Background="Transparent"
Title="Component Editor">
<Window.Resources>

View File

@@ -60,13 +60,17 @@ public partial class ComponentEditorWindow : Window
if (preferSystemChrome)
{
ExtendClientAreaToDecorationsHint = true;
WindowDecorations = WindowDecorations.Full;
ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.PreferSystemChrome;
ExtendClientAreaTitleBarHeightHint = -1;
SystemDecorations = SystemDecorations.Full;
CustomTitleBarHost.IsVisible = false;
return;
}
WindowDecorations = WindowDecorations.BorderOnly;
SystemDecorations = SystemDecorations.BorderOnly;
ExtendClientAreaToDecorationsHint = true;
ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.NoChrome;
ExtendClientAreaTitleBarHeightHint = 52;
CustomTitleBarHost.IsVisible = true;
}

View File

@@ -41,11 +41,13 @@
ColumnSpacing="4">
<ToggleButton x:Name="TickRadioButton"
Classes="component-editor-segmented-choice"
IsCheckedChanged="OnSecondHandChanged" />
Checked="OnSecondHandChanged"
Unchecked="OnSecondHandChanged" />
<ToggleButton x:Name="SweepRadioButton"
Grid.Column="1"
Classes="component-editor-segmented-choice"
IsCheckedChanged="OnSecondHandChanged" />
Checked="OnSecondHandChanged"
Unchecked="OnSecondHandChanged" />
</Grid>
</Border>
</StackPanel>

View File

@@ -1,4 +1,4 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
x:Class="LanMountainDesktop.Views.ComponentEditors.ShortcutComponentEditor"
@@ -20,7 +20,7 @@
<Grid ColumnDefinitions="*,Auto">
<TextBox Text="{Binding TargetPath}"
IsReadOnly="True"
PlaceholderText="{Binding TargetPathPlaceholder}"
Watermark="{Binding TargetPathPlaceholder}"
Grid.Column="0" />
<Button Content="{Binding BrowseButtonText}"
Click="OnBrowseClick"

View File

@@ -21,7 +21,8 @@
<ToggleSwitch x:Name="EnabledToggleSwitch"
Grid.Column="1"
VerticalAlignment="Center"
IsCheckedChanged="OnEnabledChanged" />
Checked="OnEnabledChanged"
Unchecked="OnEnabledChanged" />
</Grid>
</Border>

View File

@@ -77,11 +77,13 @@
ColumnSpacing="4">
<ToggleButton x:Name="TickRadioButton"
Classes="component-editor-segmented-choice"
IsCheckedChanged="OnSecondHandChanged" />
Checked="OnSecondHandChanged"
Unchecked="OnSecondHandChanged" />
<ToggleButton x:Name="SweepRadioButton"
Grid.Column="1"
Classes="component-editor-segmented-choice"
IsCheckedChanged="OnSecondHandChanged" />
Checked="OnSecondHandChanged"
Unchecked="OnSecondHandChanged" />
</Grid>
</Border>
</StackPanel>

View File

@@ -9,7 +9,7 @@
MinWidth="760"
MinHeight="500"
CanResize="True"
WindowDecorations="Full"
SystemDecorations="Full"
Title="Component Library"
Background="{DynamicResource AdaptiveSurfaceBaseBrush}">

View File

@@ -1,4 +1,4 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
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"
@@ -75,7 +75,7 @@
Grid.Column="1"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Left"
PlaceholderText="https://example.com"
Watermark="https://example.com"
Text="https://www.bing.com"
KeyDown="OnAddressTextBoxKeyDown" />

View File

@@ -4,10 +4,11 @@ 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;
@@ -27,7 +28,7 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
private bool _isEditMode;
private bool _isWebViewActive = true;
private bool _isWebViewFaulted;
private NativeWebView? _browserWebView;
private WebView? _browserWebView;
private readonly WebView2RuntimeAvailability _runtimeAvailability;
private bool _isDisposed;
@@ -77,8 +78,7 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
if (_browserWebView is not null)
{
_browserWebView.NavigationStarted -= OnBrowserWebViewNavigationStarting;
_browserWebView.EnvironmentRequested -= OnBrowserWebViewEnvironmentRequested;
_browserWebView.NavigationStarting -= OnBrowserWebViewNavigationStarting;
}
}
@@ -294,32 +294,15 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
}
}
private void OnBrowserWebViewNavigationStarting(object? sender, WebViewNavigationStartingEventArgs e)
private void OnBrowserWebViewNavigationStarting(object? sender, WebViewUrlLoadingEventArg e)
{
if (e.Request is null)
if (e.Url is null)
{
return;
}
_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);
}
_lastKnownUri = e.Url;
AddressTextBox.Text = e.Url.ToString();
}
private void UpdateWebViewActiveState()
@@ -375,7 +358,6 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
GoButton.IsEnabled = true;
AddressTextBox.IsEnabled = true;
UnavailableOverlay.IsVisible = false;
TryNavigate(_lastKnownUri, "ActivateWebView");
}
private void DeactivateWebView(bool clearUrl)
@@ -401,7 +383,8 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
try
{
return _browserWebView.Refresh();
_browserWebView.Reload();
return true;
}
catch (Exception ex) when (!UiExceptionGuard.IsFatalException(ex))
{
@@ -419,7 +402,7 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
try
{
_browserWebView.Navigate(uri);
_browserWebView.Url = uri;
return true;
}
catch (Exception ex) when (!UiExceptionGuard.IsFatalException(ex))
@@ -438,7 +421,7 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
try
{
_browserWebView.Navigate(new Uri("about:blank"));
_browserWebView.Url = null;
}
catch
{
@@ -483,14 +466,12 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
return;
}
_browserWebView = new NativeWebView
_browserWebView = new WebView
{
Source = new Uri("about:blank"),
IsVisible = false,
IsHitTestVisible = false
};
_browserWebView.NavigationStarted += OnBrowserWebViewNavigationStarting;
_browserWebView.EnvironmentRequested += OnBrowserWebViewEnvironmentRequested;
_browserWebView.NavigationStarting += OnBrowserWebViewNavigationStarting;
WebViewPresenter.Children.Insert(0, _browserWebView);
}

View File

@@ -4,7 +4,6 @@ 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;

View File

@@ -1,6 +1,6 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
xmlns:symbol="using:FluentIcons.Common"
x:Class="LanMountainDesktop.Views.Components.NotificationBoxWidget">
@@ -9,18 +9,18 @@
Background="Transparent"
ClipToBounds="True">
<Grid>
<!-- 涓诲崱鐗?-->
<!-- 主卡片 -->
<Border x:Name="CardBorder"
Background="#FCFCFD"
CornerRadius="{DynamicResource DesignCornerRadiusComponent}"
Padding="12,10">
<Grid RowDefinitions="Auto,*,Auto">
<!-- 澶撮儴 -->
<!-- 头部 -->
<Grid Grid.Row="0" ColumnDefinitions="*,Auto">
<StackPanel Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
<fi:SymbolIcon x:Name="HeaderIcon" Symbol="{x:Static symbol:Symbol.MailInbox}" FontSize="16" />
<TextBlock x:Name="HeaderTextBlock"
Text="娑堟伅鐩掑瓙"
Text="消息盒子"
FontSize="15"
FontWeight="SemiBold" />
<Border x:Name="UnreadBadge"
@@ -46,24 +46,24 @@
</Button>
</Grid>
<!-- 閫氱煡鍒楄〃 -->
<!-- 通知列表 -->
<ScrollViewer Grid.Row="1"
Margin="0,8,0,0"
VerticalScrollBarVisibility="Auto">
<StackPanel x:Name="NotificationListPanel" Spacing="6" />
</ScrollViewer>
<!-- 绌虹姸鎬?-->
<!-- 空状态 -->
<TextBlock x:Name="EmptyStateText"
Grid.Row="1"
Text="鏆傛棤閫氱煡"
Text="暂无通知"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="#8B95A5"
FontSize="13"
IsVisible="False" />
<!-- 闅愮妯″紡閬僵 -->
<!-- 隐私模式遮罩 -->
<Border x:Name="PrivacyOverlay"
Grid.Row="1"
Background="{DynamicResource SolidBackgroundFillColorBaseBrush}"
@@ -73,13 +73,13 @@
VerticalAlignment="Center"
Spacing="6">
<fi:SymbolIcon Symbol="{x:Static symbol:Symbol.EyeOff}" FontSize="24" Foreground="#8B95A5" />
<TextBlock Text="鎮ㄦ湁鏂扮殑閫氱煡"
<TextBlock Text="您有新的通知"
Foreground="#8B95A5"
FontSize="12" />
</StackPanel>
</Border>
<!-- 搴曢儴鐘舵€?-->
<!-- 底部状态 -->
<TextBlock x:Name="StatusTextBlock"
Grid.Row="2"
FontSize="11"

View File

@@ -1,4 +1,4 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
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"
@@ -72,7 +72,7 @@
TextWrapping="Wrap" />
<TextBox x:Name="DialogRenameTextBox"
IsVisible="False"
PlaceholderText="Enter session name"
Watermark="Enter session name"
MinWidth="120"
VerticalContentAlignment="Center" />
<Grid ColumnDefinitions="*,*"

View File

@@ -577,7 +577,7 @@ public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentW
CultureInfo.InvariantCulture,
L("study.session_history.dialog.rename_message", "Set a new name for \"{0}\"."),
label);
DialogRenameTextBox.PlaceholderText = L("study.session_history.rename_placeholder", "Enter session name");
DialogRenameTextBox.Watermark = L("study.session_history.rename_placeholder", "Enter session name");
if (string.IsNullOrWhiteSpace(DialogRenameTextBox.Text))
{
DialogRenameTextBox.Text = label;

View File

@@ -6,7 +6,7 @@
x:Class="LanMountainDesktop.Views.DesktopWidgetWindow"
Title="Desktop Component"
ShowInTaskbar="False"
WindowDecorations="None"
SystemDecorations="None"
Background="Transparent"
Topmost="False"
SizeToContent="WidthAndHeight"

View File

@@ -2,13 +2,13 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
xmlns:converters="using:Avalonia.Data.Converters"
x:Class="LanMountainDesktop.Views.FusedDesktopComponentLibraryControl"
x:DataType="vm:ComponentLibraryWindowViewModel">
<UserControl.Styles>
<!-- 闂備礁鎲$敮鎺懳涘☉姘仏妞ゆ劧绠戠粈鍡樹繆閵堝懎顏ラ柍褜鍓欓崯顖炲Φ閸曨厽鍠嗛柛鏇ㄥ幖椤ュ酣鎮?- 闂傚倷绶¢崜鐔奉焽瑜旈獮?Fluent NavigationView 濠碉紕鍋涢鍛偓娑掓櫊閹?-->
<!-- 分类列表项样式 - 遵循 Fluent NavigationView 风格 -->
<Style Selector="ListBoxItem.category-item">
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0,2"/>
@@ -30,7 +30,7 @@
<Setter Property="Background" Value="{DynamicResource SubtleFillColorTertiaryBrush}"/>
</Style>
<!-- 闂備礁鎲$敮鎺懳涘☉姘仏妞ゆ劧绲绘禍婊堟煟閻斿搫顣肩紒鍌氱墦閺屸€愁吋閸涱喗鎮欓梺纭呮腹閸楀啿顕i鍕倞鐟滃繘骞?-->
<!-- 分类项图标和文字 -->
<Style Selector="ListBoxItem.category-item fi|FluentIcon.category-icon">
<Setter Property="Foreground" Value="{DynamicResource AdaptiveTextPrimaryBrush}"/>
</Style>
@@ -50,11 +50,11 @@
<Grid ColumnDefinitions="Auto,*"
ColumnSpacing="0"
Margin="0">
<!-- 闁诲骸缍婂鑽ょ磽濮樿泛鐤鹃柛鎾茶閸嬫挻鎷呴崘顭戞闂佺硶鏅涢幊妯虹暦?- 闂備礁鎲$敮鎺懳涘☉姘仏妞ゆ劧绠戠粈鍡樹繆閵堝懎顏ラ柍?+ 闂佸湱鍘ч悺銊ッ洪悢鐓庣??闂備礁鎼悮顐﹀磿閸欏鐝舵慨妞诲亾鐎殿喗鎸冲鍫曞箣椤撶啿鏌ょ紓鍌氬€风粈浣衡偓姘间簻閳? -->
<!-- 左侧导航列 - 分类列表 + 底部"查找更多组件" -->
<Border Width="280"
Background="Transparent">
<Grid RowDefinitions="*,Auto">
<!-- 闂備礁鎲$敮鎺懳涘☉姘仏妞ゆ劧绠戠粈鍡樹繆閵堝懎顏ラ柍?-->
<!-- 分类列表 -->
<ListBox x:Name="CategoryListBox"
Grid.Row="0"
Background="Transparent"
@@ -81,7 +81,7 @@
</ListBox.ItemTemplate>
</ListBox>
<!-- 闂佸湱鍘ч悺銊ッ洪悢鐓庣??闂備礁鎼悮顐﹀磿閸欏鐝舵慨妞诲亾鐎殿喗鎸冲鍫曞箣椤撶啿鏌ょ紓鍌氬€风粈浣衡偓姘间簻閳? - 闂備線娼荤拹鐔煎礉鐏炲墽鈻曢煫鍥ㄦ⒒閻熷湱鎲稿澶樻晪闂侇剙绉甸崵瀣亜韫囨挸顏╅柣蹇旂懇楠炴牜鈧稒蓱缁€瀣煕?-->
<!-- 底部"查找更多组件" - 在左侧导航列底部 -->
<StackPanel Grid.Row="1"
Margin="12,8,8,12">
<Border Height="1"
@@ -93,42 +93,42 @@
Click="OnFindMoreComponentsClick">
<StackPanel Orientation="Horizontal" Spacing="6">
<fi:FluentIcon Icon="Globe" IconVariant="Regular" FontSize="14"/>
<TextBlock Text="Find More Components"/>
<TextBlock Text="查找更多组件"/>
</StackPanel>
</Button>
</StackPanel>
</Grid>
</Border>
<!-- 闂備礁鎲¢悷銉╁储閺嶎厼鐤鹃柛顐f礀缁€鍐煕濞戝崬寮鹃柛鐔锋喘閺屾盯寮介浣碘偓鍐磼濡も偓閼活垶顢欒箛娑欐櫆闁圭瀛╅悵鐑芥⒑濮瑰洤濡奸悗姘煎墴瀹曡鎯旈妸锔规寗闂佸搫鍟崐绋库枔?-->
<!-- 右侧内容区与左侧的分隔线 -->
<Border Grid.Column="1"
Width="1"
HorizontalAlignment="Left"
Background="{DynamicResource AdaptiveGlassPanelBorderBrush}"
Opacity="0.5"/>
<!-- 缂傚倸鍊风粈浣衡偓姘间簻閳诲酣濮€閳藉懐鐭楅梺鍛婃处閸n喖顭囬弮鍫熺厱?(闂備礁鎲¢悷銉╁储閺嶎厼鐤? -->
<!-- 组件预览区 (右侧) -->
<ScrollViewer Grid.Column="1"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<StackPanel Margin="16,8,12,8"
Spacing="0">
<!-- 闂備礁鎼悧鍡浰囬悽绋跨劦妞ゆ巻鍋撴い锔诲櫍閹虫瑩骞嬮悩鐢碉紲闂佸憡娲︽禍婵嬵敃娴犲鐓涢柛鎰╁妼椤h櫕绻涢崼鐔风伌鐎殿喕鍗冲畷婊嗩槹濞?-->
<!-- 有选中组件时的显示 -->
<Panel IsVisible="{Binding SelectedComponent, Converter={x:Static converters:ObjectConverters.IsNotNull}}">
<!-- 缂傚倸鍊风粈浣衡偓姘间簻閳诲酣濮€椤厽鍕冮梺鍝勬川婵増绂掑☉銏♀拻闁割偅绋戦悘顏呯節?- 闂備礁鎼悧鍡浰囨潏鈹惧亾濮樼厧骞樼紒顔规櫇閳ь剨缍嗛崢濂稿礈瑜版帗鐓涢柛婊€绀侀悘銉ヮ熆閻熷府韬柡浣哥Ф娴狅箓鎳栭埡鍐╁枦缂傚倷鐒﹂崝鏍€冮崨鑸汗婵炴垯鍨洪崵鍕倶閻愰潧浜鹃柣婵愬灣閹叉悂鎳滈鈧悘顏堟煕閵婏附鐨戝ù鐙呯畵瀹曟帒顭ㄩ崼銏犵闂備礁鎲$敮鎺懳涘☉銏犵柧?-->
<!-- 组件展示面板 - 有独立背景色,与窗口背景形成层级分界 -->
<Border Classes="surface-translucent-panel"
CornerRadius="{DynamicResource DesignCornerRadiusLg}"
Padding="20">
<StackPanel Spacing="16">
<!-- 缂傚倸鍊风粈浣衡偓姘间簻閳诲酣濮€閵堝懎鍞ㄩ梺鎼炲労閸擄箓寮?-->
<!-- 组件标题 -->
<TextBlock FontSize="28"
FontWeight="SemiBold"
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
Text="{Binding SelectedComponent.DisplayName}"/>
<!-- 闂備焦鎮堕崕閬嶅箹椤愶附鍋╅柣鎰靛墮缁剁偟鎲稿澶嬪剭妞ゆ帒瀚崕宥夋煕閺囥劌鐏遍柡鍡樻礋閹嘲鈻庤箛鏇烆暫閻庤娲熸禍鍫曞箖?-->
<!-- 固定大小的预览卡片 -->
<Border CornerRadius="{DynamicResource DesignCornerRadiusSm}"
Background="{DynamicResource AdaptiveSurfaceBaseBrush}"
BorderBrush="{DynamicResource AdaptiveGlassPanelBorderBrush}"
@@ -137,7 +137,7 @@
Height="300"
HorizontalAlignment="Center">
<Grid Margin="16">
<!-- 濠碘槅鍋呭妯尖偓姘煎灦閿濈偛顓兼径濠勫€為梺鍛婃寙閸愮偓姣?-->
<!-- 预览图片 -->
<Image Source="{Binding SelectedComponent.PreviewBitmap}"
Stretch="Uniform"
HorizontalAlignment="Center"
@@ -145,7 +145,7 @@
RenderOptions.BitmapInterpolationMode="HighQuality"
IsVisible="{Binding SelectedComponent.IsPreviewReady}"/>
<!-- 闂備礁鎲″缁樻叏閹灐褰掑炊閵娧€鏋栧銈嗘尵婵鐟ч梻?-->
<!-- 加载中状态 -->
<Border IsVisible="{Binding SelectedComponent.IsPreviewPending}"
Background="{DynamicResource AdaptiveSurfaceBaseBrush}">
<StackPanel HorizontalAlignment="Center"
@@ -161,7 +161,7 @@
</StackPanel>
</Border>
<!-- 濠电姰鍨洪崕鑲╁垝閸撗勫枂闁挎洖鍊归崑鎰版煠閸濄儺鏆柛?-->
<!-- 失败状态 -->
<Border IsVisible="{Binding SelectedComponent.IsPreviewFailed}"
Background="{DynamicResource AdaptiveSurfaceBaseBrush}">
<StackPanel HorizontalAlignment="Center"
@@ -188,7 +188,7 @@
</Grid>
</Border>
<!-- "婵犵數鍎戠紞鈧い鏇嗗嫭鍙忛柣鎰悁閻掑﹪鐓崶銊︾闁活厼顑呴湁?闂備礁婀遍…鍫ニ囬悽绋跨?- 闂備線娼荤拹鐔煎礉閹存繍鐒藉ù鍏兼綑缁狙囨煕椤垵鏋涢柡浣哥埣閹﹢鎮欓崣澶婃闂佺厧鐏氶崹鍧楀极瀹ュ洣娌柣鎾崇岸閺嬪繘姊哄ú缁樺▏闁告柨顑囬埀顒勬涧閺堫剟鏁嶉幇顑╃喖宕崟顓犵暢闂佽崵濮撮鍛村疮閾忣偆鐝?-->
<!-- "添加小组件"按钮 - 在面板内居中,使用主题强调色 -->
<Button HorizontalAlignment="Center"
Classes="accent"
Padding="24,10"
@@ -196,14 +196,14 @@
Click="OnAddComponentClick">
<StackPanel Orientation="Horizontal" Spacing="8">
<fi:FluentIcon Icon="Add" IconVariant="Regular" FontSize="16"/>
<TextBlock Text="Add Component" FontWeight="SemiBold"/>
<TextBlock Text="添加小组件" FontWeight="SemiBold"/>
</StackPanel>
</Button>
</StackPanel>
</Border>
</Panel>
<!-- 缂傚倷绀侀惌浣糕枍閿濆棙鍙忛柟闂寸缁?-->
<!-- 空状态 -->
<Grid IsVisible="{Binding SelectedComponent, Converter={x:Static converters:ObjectConverters.IsNull}}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
@@ -218,7 +218,7 @@
<TextBlock HorizontalAlignment="Center"
FontSize="16"
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
Text="Select a component to view its details."/>
Text="请从左侧选择一个组件"/>
</StackPanel>
</Grid>
</StackPanel>

View File

@@ -1,24 +1,24 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:LanMountainDesktop.Views"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
x:Class="LanMountainDesktop.Views.FusedDesktopComponentLibraryWindow"
Width="860"
Height="620"
MinWidth="600"
MinHeight="500"
Width="860" Height="620"
MinWidth="600" MinHeight="500"
CanResize="True"
WindowStartupLocation="CenterScreen"
WindowDecorations="BorderOnly"
SystemDecorations="BorderOnly"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
ExtendClientAreaTitleBarHeightHint="48"
Background="Transparent"
Title="Add Component">
Title="添加小组件">
<Grid x:Name="RootGrid"
Classes="settings-scope"
Background="{DynamicResource AdaptiveSettingsWindowBackgroundBrush}"
RowDefinitions="Auto,*">
<!-- 自定义标题栏 - 与 SettingsWindow 风格一致 -->
<Border x:Name="WindowTitleBarHost"
Height="48"
Padding="12,0,12,0"
@@ -41,14 +41,14 @@
FontSize="12"
FontWeight="SemiBold"
IsHitTestVisible="False"
Text="Add Component" />
Text="添加小组件" />
<TextBlock Grid.Column="2"
FontSize="12"
Opacity="0.6"
IsHitTestVisible="False"
VerticalAlignment="Center"
Text="Browse available widgets and add them to the current fused desktop layout." />
Text="将精美组件放置在您的系统桌面上(负一屏)" />
<Button x:Name="CloseWindowButton"
Grid.Column="3"
@@ -65,6 +65,7 @@
</Grid>
</Border>
<!-- 组件库控件 -->
<controls:FusedDesktopComponentLibraryControl x:Name="LibraryControl"
Grid.Row="1"
Margin="12,8,16,8" />

View File

@@ -16,7 +16,7 @@ using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views;
public partial class MainWindow : Window
public partial class MainWindow
{
private const double PreviewRenderCellSizeMin = 42;
private const double PreviewRenderCellSizeMax = 112;

View File

@@ -28,7 +28,7 @@ using SymbolIcon = FluentIcons.Avalonia.SymbolIcon;
namespace LanMountainDesktop.Views;
public partial class MainWindow : Window
public partial class MainWindow
{
private readonly List<DesktopComponentPlacementSnapshot> _desktopComponentPlacements = [];
private readonly Dictionary<int, Grid> _desktopPageComponentGrids = new();
@@ -390,10 +390,12 @@ public partial class MainWindow : Window
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());
@@ -406,6 +408,8 @@ public partial class MainWindow : Window
_ = 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());
@@ -445,7 +449,7 @@ public partial class MainWindow : Window
{
try
{
var dialog = new FAContentDialog
var dialog = new ContentDialog
{
Title = title,
Content = message,
@@ -454,7 +458,7 @@ public partial class MainWindow : Window
};
var result = await dialog.ShowAsync(this);
if (result == FAContentDialogResult.Primary)
if (result == ContentDialogResult.Primary)
{
await action();
}
@@ -740,42 +744,49 @@ public partial class MainWindow : Window
}
/// <summary>
/// 濠碘槅鍋€閸嬫挻绻涢弶鎴剳濠殿喗鎮傞獮鈧ù锝呮贡閸╁绱撴担绋款仹婵炲棎鍨藉浼搭敍濮橆厼鍓ㄦ繛鏉戝悑閼规崘銇愰崒鐐村仺闁绘柧璀﹀楣冩煙? /// </summary>
/// 检测状态栏组件是否会发生碰撞
/// </summary>
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)
{
@@ -786,7 +797,8 @@ public partial class MainWindow : Window
}
/// <summary>
/// 闂佸吋鍎抽崲鑼躲亹閸パ屽晠闁挎梹瀵у▍鐘绘⒒閸稑鐏繝銏★耿瀹曪繝鎮╅崹顐f闂佹眹鍔岀€氼剟顢欓弮鈧幆鏃堟晜閼测晝顦╅梺鍛婄墪閹冲繒鈧凹鍙冨鑽ゅ鐎n剛宕洪梺? /// </summary>
/// 获取左侧面板占用的宽度(包括间距)
/// </summary>
private double GetLeftPanelOccupiedWidth()
{
if (TopStatusLeftPanel is null)
@@ -805,7 +817,8 @@ public partial class MainWindow : Window
}
}
// 濠电儑缍€椤曆勬叏閻愮儤鈷掔痪鎯ь儑閻? if (visibleCount > 1)
// 添加间距
if (visibleCount > 1)
{
width += spacing * (visibleCount - 1);
}
@@ -814,7 +827,8 @@ public partial class MainWindow : Window
}
/// <summary>
/// 闂佸吋鍎抽崲鑼躲亹閸ャ劎鈻旀い鎾卞灪閿涚喖姊婚崼娑樼仾婵犮垺锕㈠畷锟犳偐閸偅娈㈤梺姹囧妼鐎氼剟顢欓弮鈧幆鏃堟晜閼测晝顦╅梺鍛婄墪閹冲繒鈧凹鍙冨鑽ゅ鐎n剛宕洪梺? /// </summary>
/// 获取中间面板占用的宽度(包括间距)
/// </summary>
private double GetCenterPanelOccupiedWidth()
{
if (TopStatusCenterPanel is null)
@@ -833,7 +847,8 @@ public partial class MainWindow : Window
}
}
// 濠电儑缍€椤曆勬叏閻愮儤鈷掔痪鎯ь儑閻? if (visibleCount > 1)
// 添加间距
if (visibleCount > 1)
{
width += spacing * (visibleCount - 1);
}
@@ -842,7 +857,8 @@ public partial class MainWindow : Window
}
/// <summary>
/// 闂佸吋鍎抽崲鑼躲亹閸ヮ剙鐭楅柛蹇撴噺濞呯娀姊婚崼娑樼仾婵犮垺锕㈠畷锟犳偐閸偅娈㈤梺姹囧妼鐎氼剟顢欓弮鈧幆鏃堟晜閼测晝顦╅梺鍛婄墪閹冲繒鈧凹鍙冨鑽ゅ鐎n剛宕洪梺? /// </summary>
/// 获取右侧面板占用的宽度(包括间距)
/// </summary>
private double GetRightPanelOccupiedWidth()
{
if (TopStatusRightPanel is null)
@@ -861,7 +877,8 @@ public partial class MainWindow : Window
}
}
// 濠电儑缍€椤曆勬叏閻愮儤鈷掔痪鎯ь儑閻? if (visibleCount > 1)
// 添加间距
if (visibleCount > 1)
{
width += spacing * (visibleCount - 1);
}
@@ -870,19 +887,25 @@ public partial class MainWindow : Window
}
/// <summary>
/// 濠碘槅鍋€閸嬫捇鏌$仦璇插姕婵″弶鎮傚畷銉╂晝閳ь剝銇愰崣澶岊浄闁靛鍎查煬顒勬煙缁嬫寧鎼愰柣锝囧亾閹峰懎顓奸崶鈺傜€┑鐑囩秬椤曆勬叏閻愮數纾奸柛鏇ㄤ簼椤? /// </summary>
/// 检查是否可以在指定位置添加组件
/// </summary>
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),
@@ -901,7 +924,7 @@ public partial class MainWindow : Window
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;
@@ -917,7 +940,7 @@ public partial class MainWindow : Window
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;
@@ -935,7 +958,7 @@ public partial class MainWindow : Window
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;
@@ -947,6 +970,7 @@ public partial class MainWindow : Window
var showClock = _topStatusComponentIds.Contains(BuiltInComponentIds.Clock);
var hasVisibleTopStatusComponent = false;
// 先隐藏所有时钟控件
if (ClockWidgetLeft is not null)
ClockWidgetLeft.IsVisible = false;
if (ClockWidgetCenter is not null)
@@ -954,6 +978,7 @@ public partial class MainWindow : Window
if (ClockWidgetRight is not null)
ClockWidgetRight.IsVisible = false;
// 先隐藏所有文字胶囊控件
if (TextCapsuleWidgetLeft is not null)
TextCapsuleWidgetLeft.IsVisible = false;
if (TextCapsuleWidgetCenter is not null)
@@ -961,6 +986,7 @@ public partial class MainWindow : Window
if (TextCapsuleWidgetRight is not null)
TextCapsuleWidgetRight.IsVisible = false;
// 先隐藏所有网速控件
if (NetworkSpeedWidgetLeft is not null)
NetworkSpeedWidgetLeft.IsVisible = false;
if (NetworkSpeedWidgetCenter is not null)
@@ -968,6 +994,7 @@ public partial class MainWindow : Window
if (NetworkSpeedWidgetRight is not null)
NetworkSpeedWidgetRight.IsVisible = false;
// 根据位置设置显示对应的时钟控件(带碰撞检测)
if (showClock)
{
var targetPosition = _clockPosition;
@@ -992,6 +1019,7 @@ public partial class MainWindow : Window
}
else
{
// 如果目标位置无法添加,尝试其他位置
var alternativePosition = FindAlternativePosition(targetPosition);
if (alternativePosition is not null)
{
@@ -1013,7 +1041,8 @@ public partial class MainWindow : Window
}
}
// 闂佸搫绉烽~澶婄暤娴h濯寸€广儱娲ㄩ弸鍌炴偣娴g鈷旈柣銈呮瀵即宕滆娴犳盯鎮楅悽鍨殌缂併劍鐓¢幆鍐礋椤掍胶鈧噣鎮楀☉娆樻畽闁稿繐鐭傚畷鑸电節閸愩劋绮繛瀵稿Ь椤旀劗妲愬▎鎴炴殰闁挎梻铏庡楣冩煙閸撗冧沪妞ゃ儱鎳庨湁閻庯絽澧庣粈? if (_showTextCapsule)
// 根据位置设置显示对应的文字胶囊控件(带碰撞检测)
if (_showTextCapsule)
{
var targetPosition = _textCapsulePosition;
var canAdd = CanAddComponentAtPosition(targetPosition);
@@ -1037,6 +1066,7 @@ public partial class MainWindow : Window
}
else
{
// 如果目标位置无法添加,尝试其他位置
var alternativePosition = FindAlternativePosition(targetPosition);
if (alternativePosition is not null)
{
@@ -1058,6 +1088,7 @@ public partial class MainWindow : Window
}
}
// 根据位置设置显示对应的网速控件(带碰撞检测)
if (_showNetworkSpeed)
{
var targetPosition = _networkSpeedPosition;
@@ -1082,6 +1113,7 @@ public partial class MainWindow : Window
}
else
{
// 如果目标位置无法添加,尝试其他位置
var alternativePosition = FindAlternativePosition(targetPosition);
if (alternativePosition is not null)
{
@@ -1108,6 +1140,7 @@ public partial class MainWindow : Window
TopStatusBarHost.IsVisible = hasVisibleTopStatusComponent;
}
// 延迟检查碰撞并调整
Dispatcher.UIThread.Post(async () =>
{
await System.Threading.Tasks.Task.Delay(50);
@@ -1116,18 +1149,22 @@ public partial class MainWindow : Window
}
/// <summary>
/// 閻熸粎澧楅幐鍓у垝瀹ュ棛顩烽悹鍝勬惈缁叉椽鏌i姀銏犳灁妞ゎ偒鍋婇獮姗€鎮欑€涙﹩妲梺鎸庣☉閻線宕靛鍫濈闁靛鍔庡▓鍫曟煛娴h櫣绡€缂傚秴鎳愮槐? /// </summary>
/// 当组件发生碰撞时,自动调整位置
/// </summary>
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;
@@ -1135,6 +1172,7 @@ public partial class MainWindow : Window
TextCapsuleWidgetCenter.SetTransparentBackground(_textCapsuleTransparentBackground);
TextCapsuleWidgetCenter.SetText(_textCapsuleContent);
}
// 或者移到右侧
else if (CanAddComponentAtPosition("Right"))
{
TextCapsuleWidgetLeft.IsVisible = false;
@@ -1142,6 +1180,7 @@ public partial class MainWindow : Window
TextCapsuleWidgetRight.SetTransparentBackground(_textCapsuleTransparentBackground);
TextCapsuleWidgetRight.SetText(_textCapsuleContent);
}
// 如果都无法添加,则隐藏文字胶囊
else
{
TextCapsuleWidgetLeft.IsVisible = false;
@@ -1150,6 +1189,7 @@ public partial class MainWindow : Window
if (TextCapsuleWidgetRight?.IsVisible == true && WouldComponentsCollide())
{
// 尝试将右侧文字胶囊移到中间
if (CanAddComponentAtPosition("Center"))
{
TextCapsuleWidgetRight.IsVisible = false;
@@ -1157,6 +1197,7 @@ public partial class MainWindow : Window
TextCapsuleWidgetCenter.SetTransparentBackground(_textCapsuleTransparentBackground);
TextCapsuleWidgetCenter.SetText(_textCapsuleContent);
}
// 或者移到左侧
else if (CanAddComponentAtPosition("Left"))
{
TextCapsuleWidgetRight.IsVisible = false;
@@ -1164,6 +1205,7 @@ public partial class MainWindow : Window
TextCapsuleWidgetLeft.SetTransparentBackground(_textCapsuleTransparentBackground);
TextCapsuleWidgetLeft.SetText(_textCapsuleContent);
}
// 如果都无法添加,则隐藏文字胶囊
else
{
TextCapsuleWidgetRight.IsVisible = false;
@@ -1172,6 +1214,7 @@ public partial class MainWindow : Window
if (TextCapsuleWidgetCenter?.IsVisible == true && WouldComponentsCollide())
{
// 尝试将中间文字胶囊移到左侧
if (CanAddComponentAtPosition("Left"))
{
TextCapsuleWidgetCenter.IsVisible = false;
@@ -1179,6 +1222,7 @@ public partial class MainWindow : Window
TextCapsuleWidgetLeft.SetTransparentBackground(_textCapsuleTransparentBackground);
TextCapsuleWidgetLeft.SetText(_textCapsuleContent);
}
// 或者移到右侧
else if (CanAddComponentAtPosition("Right"))
{
TextCapsuleWidgetCenter.IsVisible = false;
@@ -1186,14 +1230,17 @@ public partial class MainWindow : Window
TextCapsuleWidgetRight.SetTransparentBackground(_textCapsuleTransparentBackground);
TextCapsuleWidgetRight.SetText(_textCapsuleContent);
}
// 如果都无法添加,则隐藏文字胶囊
else
{
TextCapsuleWidgetCenter.IsVisible = false;
}
}
// 调整网速组件位置(优先级:时钟 > 文字胶囊 > 网速)
if (NetworkSpeedWidgetLeft?.IsVisible == true && WouldComponentsCollide())
{
// 尝试将左侧网速移到中间
if (CanAddComponentAtPosition("Center"))
{
NetworkSpeedWidgetLeft.IsVisible = false;
@@ -1201,6 +1248,7 @@ public partial class MainWindow : Window
NetworkSpeedWidgetCenter.SetTransparentBackground(_networkSpeedTransparentBackground);
NetworkSpeedWidgetCenter.SetDisplayMode(_networkSpeedDisplayMode);
}
// 或者移到右侧
else if (CanAddComponentAtPosition("Right"))
{
NetworkSpeedWidgetLeft.IsVisible = false;
@@ -1208,6 +1256,7 @@ public partial class MainWindow : Window
NetworkSpeedWidgetRight.SetTransparentBackground(_networkSpeedTransparentBackground);
NetworkSpeedWidgetRight.SetDisplayMode(_networkSpeedDisplayMode);
}
// 如果都无法添加,则隐藏网速
else
{
NetworkSpeedWidgetLeft.IsVisible = false;
@@ -1216,6 +1265,7 @@ public partial class MainWindow : Window
if (NetworkSpeedWidgetRight?.IsVisible == true && WouldComponentsCollide())
{
// 尝试将右侧网速移到中间
if (CanAddComponentAtPosition("Center"))
{
NetworkSpeedWidgetRight.IsVisible = false;
@@ -1223,6 +1273,7 @@ public partial class MainWindow : Window
NetworkSpeedWidgetCenter.SetTransparentBackground(_networkSpeedTransparentBackground);
NetworkSpeedWidgetCenter.SetDisplayMode(_networkSpeedDisplayMode);
}
// 或者移到左侧
else if (CanAddComponentAtPosition("Left"))
{
NetworkSpeedWidgetRight.IsVisible = false;
@@ -1230,6 +1281,7 @@ public partial class MainWindow : Window
NetworkSpeedWidgetLeft.SetTransparentBackground(_networkSpeedTransparentBackground);
NetworkSpeedWidgetLeft.SetDisplayMode(_networkSpeedDisplayMode);
}
// 如果都无法添加,则隐藏网速
else
{
NetworkSpeedWidgetRight.IsVisible = false;
@@ -1238,6 +1290,7 @@ public partial class MainWindow : Window
if (NetworkSpeedWidgetCenter?.IsVisible == true && WouldComponentsCollide())
{
// 尝试将中间网速移到左侧
if (CanAddComponentAtPosition("Left"))
{
NetworkSpeedWidgetCenter.IsVisible = false;
@@ -1245,6 +1298,7 @@ public partial class MainWindow : Window
NetworkSpeedWidgetLeft.SetTransparentBackground(_networkSpeedTransparentBackground);
NetworkSpeedWidgetLeft.SetDisplayMode(_networkSpeedDisplayMode);
}
// 或者移到右侧
else if (CanAddComponentAtPosition("Right"))
{
NetworkSpeedWidgetCenter.IsVisible = false;
@@ -1252,6 +1306,7 @@ public partial class MainWindow : Window
NetworkSpeedWidgetRight.SetTransparentBackground(_networkSpeedTransparentBackground);
NetworkSpeedWidgetRight.SetDisplayMode(_networkSpeedDisplayMode);
}
// 如果都无法添加,则隐藏网速
else
{
NetworkSpeedWidgetCenter.IsVisible = false;
@@ -1260,10 +1315,11 @@ public partial class MainWindow : Window
}
/// <summary>
/// 闂佸搫琚崕鍙夌珶濮椻偓瀹曪綁顢涘鍕闂佹眹鍔岀€氼厼霉濞戞瑧顩烽柨婵嗗缁夊绱? /// </summary>
/// 查找可用的替代位置
/// </summary>
private string? FindAlternativePosition(string originalPosition)
{
// 闁诲繐绻戠换鍡涙儊椤栫偛绠ラ柍褜鍓熷鍨緞婵犲倽顔夐梺鐓庣-閺咁偄鈻撻幋鐐村鐎广儱娲ㄩ弸?
// 尝试所有可能的位置
var positions = new[] { "Left", "Center", "Right" };
foreach (var position in positions)
{
@@ -1276,7 +1332,8 @@ public partial class MainWindow : Window
}
/// <summary>
/// 闂佸吋鍎抽崲鑼躲亹閸パ屽晠闁挎梹瀵у▍鐘绘煕濞嗘ê鐏ユい顐㈡缁辨帡宕熼鍜佸仺闂佸憡甯楅〃澶愬Υ? /// </summary>
/// 获取左侧可见组件列表
/// </summary>
private List<Control> GetVisibleLeftComponents()
{
var result = new List<Control>();
@@ -1291,7 +1348,8 @@ public partial class MainWindow : Window
}
/// <summary>
/// 闂佸吋鍎抽崲鑼躲亹閸ャ劎鈻旀い鎾卞灪閿涚喖鏌涘▎妯虹仴妞ゎ偄妫涚槐鎺楀礋椤忓拋鍋ㄩ梺鍛婂笚椤ㄥ濡? /// </summary>
/// 获取中间可见组件列表
/// </summary>
private List<Control> GetVisibleCenterComponents()
{
var result = new List<Control>();
@@ -1306,7 +1364,8 @@ public partial class MainWindow : Window
}
/// <summary>
/// 闂佸吋鍎抽崲鑼躲亹閸ヮ剙鐭楅柛蹇撴噺濞呯娀鏌涘▎妯虹仴妞ゎ偄妫涚槐鎺楀礋椤忓拋鍋ㄩ梺鍛婂笚椤ㄥ濡? /// </summary>
/// 获取右侧可见组件列表
/// </summary>
private List<Control> GetVisibleRightComponents()
{
var result = new List<Control>();
@@ -1774,17 +1833,17 @@ public partial class MainWindow : Window
return;
}
var dialog = new FAContentDialog
var dialog = new ContentDialog
{
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
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
};
var result = await dialog.ShowAsync(this);
if (result == FAContentDialogResult.Secondary)
if (result == ContentDialogResult.Secondary)
{
DeleteCurrentDesktopPage();
}
@@ -3879,7 +3938,7 @@ public partial class MainWindow : Window
}
var point = e.GetPosition(ComponentLibraryWindow);
if (point.Y > 40) // 闂傚倷绀侀幖顐ょ矓閺夋嚚娲敇椤兘鍋撻崒娑氼浄閻庯綆浜滈崬銊╂椤愩垺澶勭紒瀣崄閵囨劙顢涢悙鑼啇闁哄鐗婇崕鎶姐€呴鍕€电痪顓炴噺閻濐亞绱?0px
if (point.Y > 40) // 閺嶅洭顣介弽蹇涚彯鎼达妇瀹虫稉?0px
{
return;
}

View File

@@ -12,7 +12,7 @@ using LanMountainDesktop.Theme;
namespace LanMountainDesktop.Views;
public partial class MainWindow : Window
public partial class MainWindow
{
private static readonly TimeSpan DesktopEditCommitAnimationDuration = FluttermotionToken.Standard;
private static readonly TimeSpan DesktopEditCancelAnimationDuration = FluttermotionToken.Fast;

View File

@@ -22,7 +22,7 @@ using LanMountainDesktop.Theme;
namespace LanMountainDesktop.Views;
public partial class MainWindow : Window
public partial class MainWindow
{
private const int MinDesktopPageCount = 1;
private const int MaxDesktopPageCount = 12;
@@ -75,7 +75,7 @@ public partial class MainWindow : Window
private int? _desktopPageContextSettlingTargetIndex;
private int _desktopPageContextSettleRevision;
// 婵犵數鍋為崹鍫曞箰閹间絸鍥箥椤旂懓浜鹃柛顭戝亯婢规ɑ銇勯婊冨妤犵偛顑呴埞鎴﹀窗?闂傚倷绀侀幉锟犳偡閿旂晫绠惧┑鐘叉搐閺嬩焦銇勯幘鍗炵仼缂佺媭鍨堕弻鈥崇暤椤旂厧鏁俊銈呮噺閻撶喖鏌嶉崫鍕灓闁绘帡绠栭弻?
// 三指滑动/右键拖动相关
private bool _isThreeFingerOrRightDragSwipeActive;
private readonly HashSet<int> _activePointerIds = [];
@@ -264,6 +264,7 @@ public partial class MainWindow : Window
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);
@@ -271,7 +272,7 @@ public partial class MainWindow : Window
LauncherPagePanel.MaxWidth = pageWidth - launcherMargin * 2;
LauncherPagePanel.MaxHeight = pageHeight - launcherMargin * 2;
// 闂傚倷绀侀幖顐⒚洪妶澶嬪仱闁靛ň鏅涢拑鐔封攽閻樺弶鎼愰悷娆欓檮閵囧嫰寮介妸銊ヮ棟閻炴氨鍠栧娲川婵犲嫭鍣┑鐘灪閿氶棁澶嬫叏濡炶浜鹃悗娈垮枙缁瑥鐣烽幆閭︽Ь濡炪倕绻戦幐鎶藉箖濮椻偓閹瑩鍩℃担宄邦棜
// 更新启动台图标布局
UpdateLauncherTileLayout();
_desktopSurfacePageWidth = pageWidth;
@@ -286,29 +287,38 @@ public partial class MainWindow : Window
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)
{
// 婵犵數濮烽。浠嬪焵椤掆偓閸熷潡鍩€椤掆偓缂嶅﹪骞冨Ο璇茬窞閻忕偠鍋愰崜銊╂⒑閸涘﹦绠撻悗姘卞厴瀹曠敻鎮㈤悡搴i獓闂佸啿鎼导鎺楀箣濠垫捁鈧寧銇勯幘璺盒e┑顖氥偢閺屻劌鈽夊Ο渚紑闂佸搫妫崜鐔煎蓟閵娿儮妲堟俊顖欒濞堫厽绻濋悽闈涗粶婵炲樊鍙冮獮鍐╃鐎n€晠鏌嶉崫鍕殭缂佹绻濋弻锝夋偐闁秵顎栭梺绋匡攻濞茬喖宕洪埀? availableWidth = 600;
// 如果尺寸还未计算,使用默认值
availableWidth = 600;
availableHeight = 400;
}
// 闂備浇宕垫慨宕囨閵堝洦顫曢柡鍥ュ灪閸嬧晛鈹戦悩瀹犲閻庢艾顦甸弻宥堫檨闁告挻宀搁獮蹇涘川閺夋垹顦ㄩ梺鍛婄懃椤﹂亶銆呴銏♀拺闁告繂瀚瓭濠电偛鐪伴崐婵嗩嚕娴兼潙纾兼繝褎鍎虫禍? // 闂傚倷鑳堕崕鐢稿疾閳哄懎绐楁俊銈呮噺閸嬪鏌ㄥ┑鍡╂Ч闁哄拋鍓氶幈銊ヮ潨閸℃绠诲┑鈥崇湴閸旀垿骞冪捄琛℃婵☆垳绮幏鍗炩攽閳藉棗鐏犳い锕佷含閸?-8婵犵數鍋為崹鍫曞箹閳哄倻顩叉繝濠傚幘閻熼偊娼ㄩ柍褜鍓欓锝嗙鐎亞鍊炴俊鐐差儏濞寸兘藝椤曗偓濮婃椽宕崟顓夈儲銇勯銏╂Ц闁伙絽鐏氶幏鍛姜閻楀牆濯伴梻濠庡亜濞诧箓骞愭ィ鍐炬晩閹兼番鍔嶉崐鐢电棯椤撶偞鍣烘い銉ヮ樀閹鎮烽幍顕嗙礊闂佺懓顨庨崑濠傜暦濮椻偓閸╋繝宕掑☉鍗炴櫔
// 计算最佳图标尺寸
// 目标每行显示4-8个图标根据屏幕宽度调整
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); // 闂傚倷鑳堕崢褔宕查弻銉ョ柈闁秆勵殕閸庡秵銇勯弽顐粶闁告瑥锕弻娑㈠箻濡炵偓顦风紒?闂?
// 缂傚倷鑳堕搹搴ㄥ矗鎼淬劌绐楅柡鍥╁У瀹曞弶鎱ㄥΟ鎸庣【閻庢艾顦甸弻宥堫檨闁告挻绋掔粋宥咁潰瀹€鈧悿鈧梺瑙勫劤閻°劑锝為崨瀛樼厽? tileWidth = Math.Max(tileWidth, 100);
var tileHeight = Math.Min(tileWidth / targetAspectRatio, availableHeight / 4); // 至少显示4行
// 确保最小尺寸
tileWidth = Math.Max(tileWidth, 100);
tileHeight = Math.Max(tileHeight, 80);
// 闂傚倷绀侀幖顐⒚洪妶澶嬪仱闁靛ň鏅涢拑鐔封攽閸屻倖杈渁pPanel闂傚倷鐒﹂惇褰掑礉瀹€鍕惞婵帞妫渕闂備浇顕х换鎰崲閹版澘绠规い鎰跺瘜閺? LauncherRootTilePanel.Width = availableWidth;
// 更新WrapPanel的Item尺寸
LauncherRootTilePanel.Width = availableWidth;
// 闂傚倷绀侀幖顐⒚洪妶澶嬪仱闁靛ň鏅涢拑鐔封攽閻樺弶鎼愮紒鐘劦閺屽秷顧侀柛鎾跺枎椤曪綁宕归銏㈢獮婵犵數濮寸€氼參骞夐妶澶嬧拺缂佸娉曠粻浼存煕閻旂顥嬬紒顔肩墕閻f繈宕熼鈧崜顓㈡⒑閸涘﹥澶勯柛瀣噹鍗遍柍褜鍓熼弻?
// 更新所有子元素的尺寸
foreach (var child in LauncherRootTilePanel.Children)
{
if (child is Button button)
@@ -477,7 +487,7 @@ public partial class MainWindow : Window
return;
}
// 婵犵數濮烽。浠嬪焵椤掆偓閸熷潡鍩€椤掆偓缂嶅﹪骞冨Ο璇茬窞闁归偊鍓氬畵宥夋⒑闂堟丹娑㈠川椤栨粌甯掓繝鐢靛仜椤曨厽鎱ㄧ€涙ɑ娅犻幖杈剧稻椤洘銇勮箛鎾村櫤缂傚秴娲弻鐔衡偓鐢告櫜鏉╃懓霉閿濆懎顥忛柛銈嗘礋閻擃偊宕惰閹癸綁鏌i悢鍛婂磳闁哄矉缍侀獮鍥敊閽樺鐣梻浣规偠閸娿倝宕板鍗炲灊婵鍩栭幆鐐烘偡濞嗗繐顏村ù鐘讳憾濮婃椽宕ㄦ繝鍕吂闂佸湱鈷堥崑濠囧箖閳ユ枼鏋庨柟鎯х摠濞呮牠鏌h箛鏇炰哗婵☆偄瀚濠囧箰鎼达絿顔曢梺鐟扮摠缁诲嫭鏅堕敃鍌涚厓鐟滄粓宕滃▎鎾嶅洭顢氶埀顒勫箠濞嗘挸绠i柨鏃囧Г濞呮牠姊洪崜鎻掍簴闁搞劌顭烽幆宀€鈧綆鈧垹缍婇幃鈺呭传閸曨厼甯块梻浣规偠閸斿﹪宕濋幋婵堟殾闁靛鏅╅弫宥嗘叏濮楀棗鍔俊銈呮噺閻撴洘绻涢崱妯哄缂佽泛寮剁换娑氣偓娑欙公閼拌法鈧鍠曠划娆忕暦閼告妲归幖杈剧秵濡?
// 如果在组件编辑模式下点击空白区域,取消选中(组件或启动台图标)
if (_isComponentLibraryOpen &&
(_selectedDesktopComponentHost is not null || _selectedLauncherTileButton is not null))
{
@@ -494,6 +504,7 @@ public partial class MainWindow : Window
return;
}
// 检查三指滑动功能是否启用
var appSnapshot = _settingsFacade.Settings.LoadSnapshot<AppSettingsSnapshot>(SettingsScope.App);
var isThreeFingerSwipeEnabled = appSnapshot.EnableThreeFingerSwipe;
@@ -502,20 +513,23 @@ public partial class MainWindow : Window
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)
{
// 婵犵數鍋為崹鍫曞箰閹间絸鍥箥椤旂懓浜?闂傚倷绀侀幉锟犳偡閿旂晫绠惧┑鐘叉搐閺嬩焦銇勯幘鍗炵仼缂佺媭鍨堕弻鈥崇暤椤旂厧鏁俊銈勬缁诲棙銇勯弽銊d粶闁稿鎸搁悾鐑藉炊閳哄﹥鏁ら梻鍌欑劍鐎笛呯矙閹烘挾鈹嶆繛宸簼閸婂鏌ㄩ弮鍥撳ù婧垮€濋弻娑㈠Ψ閿濆懎顬堝銈忕稻閻擄繝寮婚敓鐘查唶婵犲灚鍔栨缂傚倷绶¢崰鏍矓閻㈢數鐭夐柟鐑橆殔鐎氬鏌涢…鎴濅簻闁衡偓椤撶喓绠鹃悗娑欘焽閻鎮介娑辨疁閽樼喖鏌涘☉娆愮稇闁藉啰鍠栭弻鏇熷緞濡櫣浠紓浣插亾濠㈣埖鍔栭悡鐔兼煃鏉炴媽鍏岄柟鐣屽█閹粙顢涘☉娆戠▏濡炪倖娲╃紞渚€宕洪埀顒併亜閹哄秶鍔嶉柛娆忕箻閹鏁愭惔鈥茬敖闂佽鐏氶崝鎴﹀蓟? ClearDesktopPageContextSettle(refreshContext: false);
// 三指/右键拖动模式:跳过所有组件交互检查,直接开始滑动
ClearDesktopPageContextSettle(refreshContext: false);
_isThreeFingerOrRightDragSwipeActive = true;
_isDesktopSwipeActive = true;
_isDesktopSwipeDirectionLocked = false;
@@ -526,12 +540,14 @@ public partial class MainWindow : Window
_desktopSwipeLastTimestamp = Stopwatch.GetTimestamp();
_desktopSwipeBaseOffset = -_currentDesktopSurfaceIndex * _desktopSurfacePageWidth;
// 闂傚倷绀侀幖顐ょ矓閺夋嚚娲煛閸滀焦鏅╅梺鎼炲劘閸斿酣銆呴弻銉﹀€甸柨婵嗗€瑰▍鍡樸亜閹邦喗娅曢柍褜鍓涢幊鎾诲箟闄囬妵鎰板礃椤斻垹娲崺锟犲川椤旈棿鍝楅梻浣虹《濡插懘宕㈤崜褏鐭嗗鑸靛姈閳锋帡鏌涢幇鈺佸缂佺嫏鍕╀簻闁圭儤鎸鹃妴鎺旂磼鏉堛劌娴€规洜鍠栭、鏃堝椽娴i晲缂撻梻鍌欑閹诧紕鎹㈤崒婊呯煋閻庡灚鐡曟慨? e.Handled = true;
// 标记事件已处理,防止组件响应
e.Handled = true;
return;
}
}
// 闂傚倷绀侀幉锟犫€﹂崶顒€绐楅柟閭﹀墾閼板灝銆掑锝呬壕閻庤娲╃换婵嗩嚕閹绢喗鍋勫瀣閳诲本绻濋悽闈浶㈤柨鏇樺劦瀹曞綊宕归锝呭伎闂佸啿鎼幊蹇涙倿婵犳碍鐓涢柛鏇ㄥ亞缁犳娊鎮? if (IsInteractivePointerSource(e.Source))
// 原有单指滑动逻辑
if (IsInteractivePointerSource(e.Source))
{
return;
}
@@ -795,6 +811,7 @@ public partial class MainWindow : Window
private void OnDesktopPagesPointerReleased(object? sender, PointerReleasedEventArgs e)
{
// 清理活跃指针
var pointerId = e.Pointer?.Id ?? 0;
_activePointerIds.Remove(pointerId);
@@ -806,6 +823,7 @@ public partial class MainWindow : Window
private void OnDesktopPagesPointerCaptureLost(object? sender, PointerCaptureLostEventArgs e)
{
// 清理活跃指针
var pointerId = e.Pointer?.Id ?? 0;
_activePointerIds.Remove(pointerId);
@@ -880,12 +898,13 @@ public partial class MainWindow : Window
var hasDistanceIntent = absDeltaX >= distanceThreshold && absDeltaX > absDeltaY * 1.05;
var hasVelocityIntent = Math.Abs(_desktopSwipeVelocityX) >= velocityThreshold;
// 濠电姷顣藉Σ鍛村磻閳ь剟鏌涚€n偅宕岄柡宀嬬磿娴狅妇鎷犻幓鎺懶ョ紓鍌欐祰娴滎剚鏅跺Δ鍐煓濠㈣泛顑呯欢鐐烘倵閿濆簼绨芥俊?闂傚倷绀侀幉锟犳偡閿旂晫绠惧┑鐘叉搐閺嬩焦銇勯幘鍗炵仼缂佺媭鍨堕弻鈥崇暤椤旂厧鏁?&& 闂傚倷绶氬鑽ゆ嫻閻旂厧绀夐幖鎼厛閺佸嫰鏌涢妷锝呭闁崇粯妫冮弻宥堫檨闁告挻宀告俊?&& 闂傚倷绀侀幉锛勫枈瀹ュ鍨傚ù锝呭暔娴滃湱绱掔€n偒鍎ラ柣鎾卞劦閺岀喓鈧稒顭囩粻鎾舵偖?
// 检查:三指/右键拖动 && 在第一页 && 向右滑动
if (wasThreeFingerOrRightDrag &&
_currentDesktopSurfaceIndex == 0 &&
deltaX > 0 && // 闂傚倷绀侀幉锛勫枈瀹ュ鍨傚ù锝呭暔娴滃湱绱掔€n偒鍎ラ柣鎾卞劦閺岀喓鈧稒顭囩粻鎾舵偖?
deltaX > 0 && // 向右滑动
(hasDistanceIntent || hasVelocityIntent))
{
// 最小化到 Windows 桌面
if (Application.Current is App app)
{
app.HideMainWindowToTray(this, "ThreeFingerOrRightDragSwipe");
@@ -979,7 +998,8 @@ public partial class MainWindow : Window
string.Empty));
}
// 闂傚倷绶氬鑽ゆ嫻閻旂厧绀夐悘鐐电叓閻熼偊娼ㄩ柍褜鍓欓锝嗙鐎n亞鍊為梺闈涱煬閻撳牆煤椤掑嫭鈷戦柛婵嗗濠€浼存煙閸涘﹥鍊愰柟顕€绠栭、妤呭礋椤愩値鍚呴梻浣哥秺閸嬪﹪宕滃璺虹9闁汇垹鎲¢悡銉︾箾閹寸儐鐒鹃悗姘缁辨帡濡搁敂鎯у绩闂佽鍠曠划娆愪繆閹间礁唯鐟滄粍瀵煎畝鍕厽闊洦娲栨禍褰掓煕鐎n偅宕岄柟顔款潐缁楃喐绻濋崟顓ㄧ吹闂? Dispatcher.UIThread.Post(() => UpdateLauncherTileLayout(), DispatcherPriority.Background);
// 在图标渲染完成后,应用布局计算
Dispatcher.UIThread.Post(() => UpdateLauncherTileLayout(), DispatcherPriority.Background);
}
private Button CreateLauncherFolderTile(StartMenuFolderNode folder)
@@ -1043,7 +1063,8 @@ public partial class MainWindow : Window
BorderThickness = new Thickness(0),
Margin = new Thickness(0, 0, 12, 12),
CornerRadius = new CornerRadius(20),
Child = panel,
Child = panel
// 不设置固定 Width 和 Height由 UpdateLauncherTileLayout 动态设置
};
}
@@ -1124,8 +1145,11 @@ public partial class MainWindow : Window
BorderBrush = Brushes.Transparent,
CornerRadius = new CornerRadius(20),
Padding = new Thickness(10),
Content = content,
Content = content
// 不设置固定 Width 和 Height由 UpdateLauncherTileLayout 动态设置
};
// 根据设置决定是否显示背景
if (_showLauncherTileBackground)
{
button.Classes.Add("glass-panel");
@@ -1391,7 +1415,7 @@ public partial class MainWindow : Window
: fileName;
}
private FASettingsExpanderItem CreateLauncherHiddenItemRow(LauncherHiddenItemView hiddenItem)
private SettingsExpanderItem CreateLauncherHiddenItemRow(LauncherHiddenItemView hiddenItem)
{
var typeText = hiddenItem.Kind == LauncherEntryKind.Folder
? L("settings.launcher.hidden_type_folder", "Folder")
@@ -1406,7 +1430,7 @@ public partial class MainWindow : Window
BorderThickness = new Thickness(0),
Tag = new LauncherHiddenItemToken(hiddenItem.Kind, hiddenItem.Key)
};
restoreButton.Content = new FluentIcons.Avalonia.SymbolIcon
restoreButton.Content = new FluentIcons.Avalonia.Fluent.SymbolIcon
{
Symbol = FluentIcons.Common.Symbol.Eye,
IconVariant = FluentIcons.Common.IconVariant.Regular,
@@ -1417,7 +1441,7 @@ public partial class MainWindow : Window
ToolTip.SetTip(restoreButton, L("settings.launcher.restore_button", "Unhide"));
restoreButton.Click += OnRestoreLauncherHiddenItemClick;
return new FASettingsExpanderItem
return new SettingsExpanderItem
{
Content = hiddenItem.DisplayName,
Description = typeText,
@@ -1427,17 +1451,23 @@ public partial class MainWindow : Window
};
}
private FAIconSource? CreateLauncherHiddenItemIconSource(LauncherHiddenItemView hiddenItem)
private IconSource CreateLauncherHiddenItemIconSource(LauncherHiddenItemView hiddenItem)
{
if (hiddenItem.IconBitmap is not null)
{
return new FAImageIconSource
return new ImageIconSource
{
Source = hiddenItem.IconBitmap
};
}
return null;
return new FluentIcons.Avalonia.Fluent.SymbolIconSource
{
Symbol = hiddenItem.Kind == LauncherEntryKind.Folder
? FluentIcons.Common.Symbol.Folder
: FluentIcons.Common.Symbol.Apps,
IconVariant = FluentIcons.Common.IconVariant.Regular
};
}
private void OnRestoreLauncherHiddenItemClick(object? sender, RoutedEventArgs e)
@@ -1666,7 +1696,7 @@ public partial class MainWindow : Window
Content = content
};
// 闂傚倷绀侀幖顐ょ矓閻戞枻缍栧璺猴功閺嗐倕霉閿濆洤鍔嬪┑顖氥偢閺屾盯骞樺Δ鈧幊蹇涙倵椤撱垺鈷戦柛娑橈工婵洭鏌涢悢閿嬪仴闁诡喚鍋撻妶锝夊礃閵娿儱鎸ゆ俊鐐€栭悧妤冨枈瀹ュ纾垮┑鐘叉处閻撴盯鏌涢弴銊ヤ簻闁抽攱妫冮弻鏇㈠炊閵娿儱鎽甸梺纭呮珪椤ㄥ牊绂掗敃鍌涘€锋い鎺戝€哥拋?
// 根据设置决定是否显示背景
if (_showLauncherTileBackground)
{
button.Classes.Add("glass-panel");
@@ -1745,7 +1775,7 @@ public partial class MainWindow : Window
Content = content
};
// 闂傚倷绀侀幖顐ょ矓閻戞枻缍栧璺猴功閺嗐倕霉閿濆洤鍔嬪┑顖氥偢閺屾盯骞樺Δ鈧幊蹇涙倵椤撱垺鈷戦柛娑橈工婵洭鏌涢悢閿嬪仴闁诡喚鍋撻妶锝夊礃閵娿儱鎸ゆ俊鐐€栭悧妤冨枈瀹ュ纾垮┑鐘叉处閻撴盯鏌涢弴銊ヤ簻闁抽攱妫冮弻鏇㈠炊閵娿儱鎽甸梺纭呮珪椤ㄥ牊绂掗敃鍌涘€锋い鎺戝€哥拋?
// 根据设置决定是否显示背景
if (_showLauncherTileBackground)
{
button.Classes.Add("glass-panel");

View File

@@ -1,10 +1,9 @@
using System;
using Avalonia.Controls;
using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views;
public partial class MainWindow : Window
public partial class MainWindow
{
private void UpdateCurrentRenderBackendStatus()
{

View File

@@ -20,13 +20,13 @@ using LanMountainDesktop.Views.Components;
namespace LanMountainDesktop.Views;
public partial class MainWindow : Window
public partial class MainWindow
{
private TextBlock? CurrentRenderBackendLabelTextBlock => this.FindControl<TextBlock>("CurrentRenderBackendLabelTextBlock");
private TextBlock? CurrentRenderBackendValueTextBlock => this.FindControl<TextBlock>("CurrentRenderBackendValueTextBlock");
private TextBlock? CurrentRenderBackendImplementationTextBlock => this.FindControl<TextBlock>("CurrentRenderBackendImplementationTextBlock");
private ComboBox? TimeZoneComboBox => this.FindControl<ComboBox>("TimeZoneComboBox");
private FASettingsExpander? LauncherHiddenItemsSettingsExpander => this.FindControl<FASettingsExpander>("LauncherHiddenItemsSettingsExpander");
private SettingsExpander? LauncherHiddenItemsSettingsExpander => this.FindControl<SettingsExpander>("LauncherHiddenItemsSettingsExpander");
private TextBlock? LauncherHiddenItemsEmptyTextBlock => this.FindControl<TextBlock>("LauncherHiddenItemsEmptyTextBlock");
private void OnSettingsChanged(object? sender, SettingsChangedEvent e)
@@ -38,12 +38,13 @@ public partial class MainWindow : Window
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();

View File

@@ -1,12 +1,11 @@
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views;
public partial class MainWindow : Window
public partial class MainWindow
{
private bool _isSingleInstancePromptVisible;
@@ -39,14 +38,14 @@ public partial class MainWindow : Window
try
{
var dialog = new FAContentDialog
var dialog = new ContentDialog
{
Title = L("single_instance.notice.title", "Already running"),
Title = L("single_instance.notice.title", "应用已经运行"),
Content = L(
"single_instance.notice.description",
"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
"应用已经运行,无需多次点击打开。"),
PrimaryButtonText = L("single_instance.notice.button", "确定"),
DefaultButton = ContentDialogButton.Primary
};
await dialog.ShowAsync(this);

View File

@@ -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"
xmlns:ic="using:FluentIcons.Avalonia.Fluent"
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"
WindowDecorations="None"
SystemDecorations="None"
CanResize="False"
UseLayoutRounding="True"
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
@@ -236,7 +236,7 @@
</Grid>
</Border>
<!-- 鐘舵€佹爮闃村奖灞?- macOS 椋庢牸鐨勫畬鏁撮槾褰卞甫 -->
<!-- 状态栏阴影层 - macOS 风格的完整阴影带 -->
<Border x:Name="StatusBarOverlay"
Grid.Row="0"
Grid.Column="0"
@@ -265,7 +265,7 @@
Padding="4"
ZIndex="2">
<Grid ColumnDefinitions="*,Auto,*">
<!-- 宸︿晶鐘舵€佹爮缁勪欢 -->
<!-- 左侧状态栏组件 -->
<StackPanel x:Name="TopStatusLeftPanel"
Grid.Column="0"
Orientation="Horizontal"
@@ -281,7 +281,7 @@
IsVisible="False"
Margin="0" />
</StackPanel>
<!-- 涓棿鐘舵€佹爮缁勪欢 -->
<!-- 中间状态栏组件 -->
<StackPanel x:Name="TopStatusCenterPanel"
Grid.Column="1"
Orientation="Horizontal"
@@ -297,7 +297,7 @@
IsVisible="False"
Margin="0" />
</StackPanel>
<!-- 鍙充晶鐘舵€佹爮缁勪欢 -->
<!-- 右侧状态栏组件 -->
<StackPanel x:Name="TopStatusRightPanel"
Grid.Column="2"
Orientation="Horizontal"

View File

@@ -4,7 +4,7 @@
xmlns:vm="using:LanMountainDesktop.Views"
x:Class="LanMountainDesktop.Views.NotificationDialogWindow"
x:DataType="vm:NotificationDialogViewModel"
WindowDecorations="None"
SystemDecorations="None"
Background="Transparent"
ShowInTaskbar="False"
Topmost="True"
@@ -12,6 +12,7 @@
SizeToContent="WidthAndHeight"
TransparencyLevelHint="Transparent"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
ExtendClientAreaTitleBarHeightHint="-1">
<Border x:Name="DialogCard"

View File

@@ -9,6 +9,7 @@ using Avalonia.Styling;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using FluentIcons.Avalonia;
using FluentIcons.Avalonia.Fluent;
using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views;

View File

@@ -5,7 +5,7 @@
xmlns:controls="using:LanMountainDesktop.Controls"
x:Class="LanMountainDesktop.Views.NotificationWindow"
x:DataType="vm:NotificationViewModel"
WindowDecorations="None"
SystemDecorations="None"
Background="Transparent"
ShowInTaskbar="False"
Topmost="True"
@@ -13,6 +13,7 @@
SizeToContent="WidthAndHeight"
TransparencyLevelHint="Transparent"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
ExtendClientAreaTitleBarHeightHint="-1">
<Window.Styles>

View File

@@ -1,27 +1,19 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:controls="using:LanMountainDesktop.Controls"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
x:Class="LanMountainDesktop.Views.SettingsPages.AboutSettingsPage"
x:DataType="vm:AboutSettingsPageViewModel">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<ImageBrush x:Key="AboutBannerBrush"
Source="/Assets/about_banner_light.png"
Stretch="Uniform"
AlignmentX="Center"
AlignmentY="Center" />
<ImageBrush x:Key="AboutBannerBrush" Source="/Assets/about_banner_light.png" Stretch="Uniform" AlignmentX="Center" AlignmentY="Center" />
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<ImageBrush x:Key="AboutBannerBrush"
Source="/Assets/about_banner_dark.png"
Stretch="Uniform"
AlignmentX="Center"
AlignmentY="Center" />
<ImageBrush x:Key="AboutBannerBrush" Source="/Assets/about_banner_dark.png" Stretch="Uniform" AlignmentX="Center" AlignmentY="Center" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
@@ -44,7 +36,7 @@
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style>
<Style Selector="ui|FAInfoBar.about-static-info">
<Style Selector="ui|InfoBar.about-static-info">
<Setter Property="IsOpen" Value="True" />
<Setter Property="IsClosable" Value="False" />
<Setter Property="Severity" Value="Informational" />
@@ -65,29 +57,30 @@
<TextBlock Classes="settings-subsection-title"
Text="{Binding AppInfoHeader}" />
<ui:FAInfoBar Classes="about-static-info"
Title="{Binding VersionLabel}"
Message="{Binding VersionText}">
<ui:FAInfoBar.IconSource>
<ui:FAFontIconSource Glyph="&#xF0288;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FAInfoBar.IconSource>
</ui:FAInfoBar>
<ui:InfoBar Classes="about-static-info"
Title="{Binding VersionLabel}"
Message="{Binding VersionText}">
<ui:InfoBar.IconSource>
<fi:SymbolIconSource Symbol="Info" />
</ui:InfoBar.IconSource>
</ui:InfoBar>
<ui:FAInfoBar Classes="about-static-info"
Title="{Binding CodenameLabel}"
Message="{Binding CodenameText}">
<ui:FAInfoBar.IconSource>
<ui:FAFontIconSource Glyph="&#xF13D0;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FAInfoBar.IconSource>
</ui:FAInfoBar>
<ui:InfoBar Classes="about-static-info"
Title="{Binding CodenameLabel}"
Message="{Binding CodenameText}">
<ui:InfoBar.IconSource>
<fi:SymbolIconSource Symbol="Bookmark" />
</ui:InfoBar.IconSource>
</ui:InfoBar>
<ui:FASettingsExpander Header="Project resources"
IsExpanded="True">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF02AC;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpanderItem>
<ui:FASettingsExpanderItem.Footer>
<!-- 版权声明 - 放在渲染显示前面 -->
<ui:SettingsExpander Header="版权声明"
IsExpanded="True">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Document" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpanderItem>
<ui:SettingsExpanderItem.Footer>
<WrapPanel>
<WrapPanel.Styles>
<Style Selector="HyperlinkButton">
@@ -96,18 +89,18 @@
</Style>
</WrapPanel.Styles>
<HyperlinkButton NavigateUri="https://github.com/wwiinnddyy/LanMountainDesktop">
<TextBlock Text="GitHub Repository" />
<TextBlock Text="GitHub 仓库" />
</HyperlinkButton>
<HyperlinkButton NavigateUri="https://github.com/wwiinnddyy/LanMountainDesktop/issues">
<TextBlock Text="Issue Tracker" />
<TextBlock Text="问题反馈" />
</HyperlinkButton>
</WrapPanel>
</ui:FASettingsExpanderItem.Footer>
</ui:SettingsExpanderItem.Footer>
<TextBlock>
<Run Text="Copyright (c) 2024-" /><Run Text="2025" /> Lincube
</TextBlock>
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
</StackPanel>
</ScrollViewer>
</UserControl>

View File

@@ -74,9 +74,6 @@ public partial class AboutSettingsPage : SettingsPageBase
private void OnAboutHeroCardPointerPressed(object? sender, PointerPressedEventArgs e)
{
_ = sender;
_ = e;
var now = DateTime.UtcNow;
var elapsed = now - _lastHeroCardClickTime;
@@ -114,23 +111,25 @@ public partial class AboutSettingsPage : SettingsPageBase
}
else if (remaining <= 2)
{
Debug.WriteLine($"[AboutSettingsPage] {remaining} tap(s) remaining before developer mode unlocks.");
Debug.WriteLine($"[AboutSettingsPage] 再点击 {remaining} 次即可启用开发者模式。");
}
}
private async void PromptEnableDevMode(ISettingsFacadeService settingsFacade)
{
var dialog = new FAContentDialog
var dialog = new ContentDialog
{
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
Title = "启用开发者模式",
Content = "开发者模式提供了插件调试、热重载等高级功能,仅供开发和调试用途。\n\n" +
"请注意:开发者不对以非开发用途使用此功能造成的任何后果负责,也不接受以非开发用途使用时产生的 Bug 反馈。\n\n" +
"确定要启用开发者模式吗?",
PrimaryButtonText = "启用",
CloseButtonText = "取消",
DefaultButton = ContentDialogButton.Close
};
var result = await dialog.ShowAsync();
if (result != FAContentDialogResult.Primary)
if (result != ContentDialogResult.Primary)
{
return;
}

View File

@@ -1,9 +1,9 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:controls="using:LanMountainDesktop.Controls"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
x:Class="LanMountainDesktop.Views.SettingsPages.AppearanceSettingsPage"
x:DataType="vm:AppearanceSettingsPageViewModel">
<ScrollViewer VerticalScrollBarVisibility="Auto">
@@ -13,12 +13,12 @@
Text="{Binding ThemeHeader}"
Margin="0,0,0,4" />
<ui:FASettingsExpander Header="{Binding ThemeModeLabel}"
<ui:SettingsExpander Header="{Binding ThemeModeLabel}"
Description="{Binding ThemeModeDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0504;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="WeatherMoon" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ComboBox Width="200"
ItemsSource="{Binding ThemeModeOptions}"
SelectedItem="{Binding SelectedThemeMode}">
@@ -28,24 +28,24 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:FASettingsExpander Header="{Binding UseSystemChromeLabel}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0908;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander Header="{Binding UseSystemChromeLabel}">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Window" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding UseSystemChrome}" />
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:FASettingsExpander Header="{Binding ThemeColorModeLabel}"
<ui:SettingsExpander Header="{Binding ThemeColorModeLabel}"
Description="{Binding ThemeColorSourceDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF017C;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="PaintBrush" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ComboBox Width="240"
ItemsSource="{Binding ThemeColorModes}"
SelectedItem="{Binding SelectedThemeColorMode}">
@@ -55,15 +55,15 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:FASettingsExpander Header="{Binding SystemMaterialLabel}"
<ui:SettingsExpander Header="{Binding SystemMaterialLabel}"
Description="{Binding SystemMaterialDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF1A48;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="WindowDevTools" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ComboBox Width="220"
ItemsSource="{Binding SystemMaterialModes}"
SelectedItem="{Binding SelectedSystemMaterialMode}">
@@ -73,15 +73,15 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:FASettingsExpander Header="{Binding ThemeColorLabel}"
<ui:SettingsExpander Header="{Binding ThemeColorLabel}"
Description="{Binding ThemeColorSourceDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0888;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpanderItem>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Color" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpanderItem>
<StackPanel Spacing="12">
<StackPanel Orientation="Horizontal"
Spacing="12"
@@ -257,8 +257,8 @@
</Button>
</StackPanel>
</StackPanel>
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
</StackPanel>
</ScrollViewer>
</UserControl>

View File

@@ -1,9 +1,9 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:controls="using:LanMountainDesktop.Controls"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
x:Class="LanMountainDesktop.Views.SettingsPages.ComponentsSettingsPage"
x:DataType="vm:ComponentsSettingsPageViewModel">
<ScrollViewer VerticalScrollBarVisibility="Auto">
@@ -12,12 +12,12 @@
Text="{Binding ComponentsHeader}"
Margin="0,0,0,4" />
<ui:FASettingsExpander Header="{Binding ComponentsHeader}"
<ui:SettingsExpander Header="{Binding ComponentsHeader}"
IsExpanded="True">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0024;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpanderItem>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Apps" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="Auto,*,Auto" ColumnSpacing="16">
<TextBlock Text="{Binding ShortSideCellsLabel}"
VerticalAlignment="Center" />
@@ -33,8 +33,8 @@
VerticalAlignment="Center"
HorizontalAlignment="Right" />
</Grid>
</ui:FASettingsExpanderItem>
<ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="Auto,*,Auto" ColumnSpacing="16">
<TextBlock Text="{Binding EdgeInsetPercentLabel}"
VerticalAlignment="Center" />
@@ -50,8 +50,8 @@
VerticalAlignment="Center"
HorizontalAlignment="Right" />
</Grid>
</ui:FASettingsExpanderItem>
<ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="Auto,*">
<TextBlock Text="{Binding SpacingPresetLabel}"
VerticalAlignment="Center" />
@@ -66,19 +66,19 @@
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<controls:IconText Icon="ShapeOrganic"
Text="{Binding ComponentRadiusHeader}"
Margin="0,12,0,4" />
<ui:FASettingsExpander Header="{Binding CornerRadiusStyleLabel}"
<ui:SettingsExpander Header="{Binding CornerRadiusStyleLabel}"
Description="{Binding CornerRadiusStyleDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0FE8;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="ShapeOrganic" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<StackPanel Orientation="Horizontal" Spacing="8">
<ComboBox Width="200"
ItemsSource="{Binding CornerRadiusStyleOptions}"
@@ -93,8 +93,8 @@
<fi:SymbolIcon Symbol="QuestionCircle" />
</Button>
</StackPanel>
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</StackPanel>
</ScrollViewer>
</UserControl>

View File

@@ -1,92 +1,84 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
x:Class="LanMountainDesktop.Views.SettingsPages.DevSettingsPage"
x:DataType="vm:DevSettingsPageViewModel">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Classes="settings-page-container settings-page-animated">
<ui:FAInfoBar IsOpen="True"
<ui:InfoBar IsOpen="True"
IsClosable="False"
Severity="Warning"
Title="Preview and developer features"
Message="These options are intended for debugging, diagnostics, and local plugin development."
Margin="0,0,0,16">
<ui:FAInfoBar.IconSource>
<ui:FAFontIconSource Glyph="&#xF0288;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FAInfoBar.IconSource>
</ui:FAInfoBar>
Title="开发者模式"
Message="开发者模式仅供开发和调试用途。开发者不对以非开发用途使用此功能造成的任何后果负责。"
Margin="0,0,0,16" />
<ui:FASettingsExpander Header="Developer mode"
Description="Enable developer-focused startup helpers and diagnostics.">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF01AC;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander Header="启用开发者模式"
Description="启用后可使用插件调试、开发者插件路径等高级功能">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="DeveloperBoard" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding IsDevModeEnabled}" />
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:FASettingsExpander Header="Three-finger desktop swipe"
Description="Enable desktop page switching gestures when the current platform supports them.">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF02FC;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander Header="启用三指滑动"
Description="使用三根手指或鼠标右键拖动自由滑动页面,在第一页向右滑动可回到 Windows 桌面(实验性功能)">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Gesture" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding EnableThreeFingerSwipe}" />
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:FASettingsExpander Header="Fused desktop experience"
Description="Enable the fused desktop shell and its related experimental entry points.">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF01A8;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander Header="启用融合桌面"
Description="允许将组件放置在 Windows 系统桌面上(实验性功能,重启后生效)">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Apps" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding EnableFusedDesktop}" />
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<Separator Classes="settings-separator" />
<ui:FASettingsExpander Header="Development plugin path"
Description="Load a local plugin output directory for iterative debugging without packaging.">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF023C;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpanderItem>
<ui:SettingsExpander Header="开发者插件路径"
Description="指定开发中的插件目录路径,无需打包即可直接加载。多个路径用分号分隔。">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="FolderLink" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpanderItem>
<TextBox Text="{Binding DevPluginPath}"
PlaceholderText="C:\path\to\plugin\bin\Debug\net10.0"
Watermark="C:\path\to\plugin\bin\Debug\net10.0"
Width="360"
MinWidth="200" />
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<Separator Classes="settings-separator" />
<ui:FASettingsExpander Header="Developer startup arguments"
Description="Use these launch arguments or environment variables to start the app in development scenarios.">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF1930;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpanderItem>
<StackPanel Margin="0,8,0,0"
Spacing="8">
<TextBlock Text="Command-line arguments:"
FontWeight="SemiBold" />
<ui:SettingsExpander Header="命令行参数"
Description="也可以通过命令行参数或环境变量指定开发者插件路径">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="WindowConsole" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpanderItem>
<StackPanel Margin="0,8,0,0" Spacing="8">
<TextBlock Text="命令行参数:" FontWeight="SemiBold" />
<Border Background="{DynamicResource ControlFillColorDefaultBrush}"
CornerRadius="8"
Padding="12,8">
<TextBlock FontFamily="Cascadia Code, Consolas, monospace"
FontSize="12"
Text="--dev-plugin &lt;path&gt; or -dp &lt;path&gt;"
Text="--dev-plugin &lt;path&gt; -dp &lt;path&gt;"
TextWrapping="Wrap" />
</Border>
<TextBlock Text="Environment variables:"
FontWeight="SemiBold"
Margin="0,8,0,0" />
<TextBlock Text="环境变量:" FontWeight="SemiBold" Margin="0,8,0,0" />
<Border Background="{DynamicResource ControlFillColorDefaultBrush}"
CornerRadius="8"
Padding="12,8">
@@ -95,25 +87,23 @@
Text="LMD_DEV_PLUGIN=&lt;path&gt;"
TextWrapping="Wrap" />
</Border>
<TextBlock Text="Other arguments:"
FontWeight="SemiBold"
Margin="0,8,0,0" />
<TextBlock Text="其他参数:" FontWeight="SemiBold" Margin="0,8,0,0" />
<Border Background="{DynamicResource ControlFillColorDefaultBrush}"
CornerRadius="8"
Padding="12,8">
<StackPanel Spacing="4">
<TextBlock FontFamily="Cascadia Code, Consolas, monospace"
FontSize="12"
Text="--dev-mode / -dev Enable developer mode startup helpers." />
Text="--dev-mode / -dev 启用开发者模式" />
<TextBlock FontFamily="Cascadia Code, Consolas, monospace"
FontSize="12"
Text="--hot-reload / -hr Enable hot reload for development builds." />
Text="--hot-reload / -hr 启用热重载(预留)" />
</StackPanel>
</Border>
</StackPanel>
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
</StackPanel>
</ScrollViewer>
</UserControl>

View File

@@ -6,7 +6,7 @@ namespace LanMountainDesktop.Views.SettingsPages;
[SettingsPageInfo(
"dev",
"Developer",
"开发者",
SettingsPageCategory.Dev,
IconKey = "DeveloperBoard",
SortOrder = 0,

View File

@@ -1,22 +1,23 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:controls="using:LanMountainDesktop.Controls"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
x:Class="LanMountainDesktop.Views.SettingsPages.GeneralSettingsPage"
x:DataType="vm:GeneralSettingsPageViewModel">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Classes="settings-page-container settings-page-animated">
<controls:IconText Icon="Globe"
Text="{Binding BasicHeader}"
Margin="0,0,0,4" />
<ui:FASettingsExpander Header="{Binding LanguageHeader}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0404;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander Header="{Binding LanguageHeader}">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Settings" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ComboBox Width="220"
ItemsSource="{Binding Languages}"
SelectedItem="{Binding SelectedLanguage}">
@@ -26,15 +27,15 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:FASettingsExpander Header="{Binding TimeZoneHeader}"
<ui:SettingsExpander Header="{Binding TimeZoneHeader}"
Description="{Binding TimeZoneDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0168;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Clock" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ComboBox Width="240"
ItemsSource="{Binding TimeZones}"
SelectedItem="{Binding SelectedTimeZone}">
@@ -44,14 +45,14 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:FASettingsExpander Header="{Binding PreviewHeader}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF1C80;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpanderItem>
<ui:SettingsExpander Header="{Binding PreviewHeader}">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Calendar" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="Auto,*"
ColumnSpacing="16"
RowDefinitions="Auto,Auto"
@@ -69,8 +70,8 @@
Opacity="0.82"
Text="{Binding PreviewDateText}" />
</Grid>
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<Separator Classes="settings-separator" />
@@ -78,13 +79,13 @@
Text="{Binding RuntimeHeader}"
Margin="0,0,0,4" />
<ui:FASettingsExpander Header="{Binding RenderModeHeader}"
<ui:SettingsExpander Header="{Binding RenderModeHeader}"
Description="{Binding RuntimeDescription}"
IsExpanded="True">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0908;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="DeveloperBoard" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ComboBox Width="220"
ItemsSource="{Binding RenderModes}"
SelectedItem="{Binding SelectedRenderMode}">
@@ -94,47 +95,48 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:FASettingsExpander.Footer>
<ui:FASettingsExpanderItem>
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem>
<TextBlock Text="{Binding RenderModeRestartMessage}"
Opacity="0.7"
FontSize="12"
TextWrapping="Wrap" />
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<ui:FASettingsExpander Header="Fade startup transition"
<ui:SettingsExpander Header="淡入淡出效果"
Description="{Binding FadeTransitionDescription}"
IsVisible="{Binding IsSlideTransitionAvailable}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF1A48;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="ArrowUpload" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding EnableFadeTransition}"
IsEnabled="{Binding IsFadeTransitionToggleEnabled}" />
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:FASettingsExpander Header="Slide startup transition"
Description="Use a slide-in startup transition on supported Windows builds. This option disables the fade transition."
<ui:SettingsExpander Header="启动滑入滑出效果"
Description="启用后,启动和恢复时从屏幕右侧边缘滑入或滑出,仅 Windows 可用。"
IsVisible="{Binding IsSlideTransitionAvailable}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF08E8;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="ArrowRight" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding EnableSlideTransition}" />
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:FASettingsExpander Header="Show in taskbar"
Description="Keep the main window visible in the taskbar while the desktop host is running.">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF1BE0;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander Header="桌面主窗口在任务栏显示图标"
Description="仅控制桌面主窗口在系统任务栏中的图标显示,不影响设置窗口。">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Window" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding ShowInTaskbar}" />
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</StackPanel>
</ScrollViewer>
</UserControl>

View File

@@ -1,9 +1,9 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:controls="using:LanMountainDesktop.Controls"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
xmlns:symbol="using:FluentIcons.Common"
x:Class="LanMountainDesktop.Views.SettingsPages.LauncherSettingsPage"
x:DataType="vm:LauncherSettingsPageViewModel">
@@ -56,14 +56,14 @@
Text="{Binding AppearanceHeader}"
Margin="0,0,0,4" />
<ui:FASettingsExpander Classes="settings-expander-card"
<ui:SettingsExpander Classes="settings-expander-card"
Header="{Binding AppearanceHeader}"
Description="{Binding AppearanceDescription}"
IsExpanded="True">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0888;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpanderItem>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="{x:Static symbol:Symbol.Apps}" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="*,Auto">
<StackPanel Spacing="2">
<TextBlock Text="{Binding ShowTileBackgroundHeader}" />
@@ -73,21 +73,21 @@
<ToggleSwitch Grid.Column="1"
IsChecked="{Binding ShowTileBackground}" />
</Grid>
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<controls:IconText Icon="Apps"
Text="{Binding HiddenHeader}"
Margin="0,24,0,4" />
<ui:FASettingsExpander Classes="settings-expander-card"
<ui:SettingsExpander Classes="settings-expander-card"
Header="{Binding HiddenHeader}"
Description="{Binding HiddenDescription}"
IsExpanded="True">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF1C80;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpanderItem>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Apps" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpanderItem>
<StackPanel Spacing="8">
<TextBlock Classes="settings-item-description"
IsVisible="{Binding IsHiddenItemsEmpty}"
@@ -99,24 +99,24 @@
IsVisible="{Binding HasHiddenItems}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:LauncherHiddenItemViewModel">
<ui:FASettingsExpanderItem Content="{Binding DisplayName}"
<ui:SettingsExpanderItem Content="{Binding DisplayName}"
Description="{Binding TypeLabel}"
IsClickEnabled="False">
<ui:FASettingsExpanderItem.IconSource>
<ui:FAFontIconSource Glyph="&#xF19FC;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpanderItem.IconSource>
<ui:FASettingsExpanderItem.Footer>
<ui:SettingsExpanderItem.IconSource>
<fi:SymbolIconSource Symbol="{Binding IconSymbol}" />
</ui:SettingsExpanderItem.IconSource>
<ui:SettingsExpanderItem.Footer>
<Button Command="{Binding RestoreCommand}"
Content="{Binding RestoreButtonText}"
VerticalAlignment="Center" />
</ui:FASettingsExpanderItem.Footer>
</ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
</StackPanel>
</ScrollViewer>
</UserControl>

View File

@@ -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"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
x:Class="LanMountainDesktop.Views.SettingsPages.NotificationSettingsPage"
x:DataType="vm:NotificationSettingsPageViewModel">
<ScrollViewer VerticalScrollBarVisibility="Auto">
@@ -13,15 +13,15 @@
Text="{Binding NotificationHeader}"
Margin="0,0,0,4" />
<ui:FASettingsExpander Header="{Binding EnableNotificationHeader}"
<ui:SettingsExpander Header="{Binding EnableNotificationHeader}"
Description="{Binding EnableNotificationDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0018;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Alert" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding IsNotificationEnabled}" />
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<Separator Classes="settings-separator" />
@@ -29,39 +29,39 @@
Text="{Binding BehaviorHeader}"
Margin="0,0,0,4" />
<ui:FASettingsExpander Header="{Binding HoverPauseHeader}"
<ui:SettingsExpander Header="{Binding HoverPauseHeader}"
Description="{Binding HoverPauseDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF066C;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="CursorHover" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding IsHoverPauseEnabled}" />
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:FASettingsExpander Header="{Binding ClickCloseHeader}"
<ui:SettingsExpander Header="{Binding ClickCloseHeader}"
Description="{Binding ClickCloseDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0148;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="CursorClick" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding IsClickCloseEnabled}" />
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:FASettingsExpander Header="{Binding MaxNotificationsHeader}"
<ui:SettingsExpander Header="{Binding MaxNotificationsHeader}"
Description="{Binding MaxNotificationsDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0010;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:FANumberBox Value="{Binding MaxNotificationsPerPosition}"
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="NumberSymbol" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ui:NumberBox Value="{Binding MaxNotificationsPerPosition}"
Minimum="1"
Maximum="10"
Width="100"
SpinButtonPlacementMode="Inline" />
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<Separator Classes="settings-separator" />
@@ -69,12 +69,12 @@
Text="{Binding TestHeader}"
Margin="0,0,0,4" />
<ui:FASettingsExpander Header="{Binding TestNotificationHeader}"
<ui:SettingsExpander Header="{Binding TestNotificationHeader}"
Description="{Binding TestNotificationDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF03FC;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Beaker" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<StackPanel Orientation="Horizontal" Spacing="8">
<ComboBox Width="120"
ItemsSource="{Binding TestPositions}"
@@ -94,7 +94,7 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ui:FANumberBox Width="100"
<ui:NumberBox Width="100"
Minimum="1"
Maximum="30"
SpinButtonPlacementMode="Inline"
@@ -104,8 +104,8 @@
<fi:SymbolIcon Symbol="Send" FontSize="16" />
</Button>
</StackPanel>
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</StackPanel>
</ScrollViewer>

View File

@@ -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"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
x:Class="LanMountainDesktop.Views.SettingsPages.PluginCatalogDetailDrawer"
x:DataType="vm:PluginCatalogDetailViewModel">
<ScrollViewer VerticalScrollBarVisibility="Auto">

View File

@@ -1,32 +1,32 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
x:Class="LanMountainDesktop.Views.SettingsPages.PluginCatalogSettingsPage"
x:Name="Root"
x:DataType="vm:PluginCatalogSettingsPageViewModel">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Classes="settings-page-container settings-page-animated">
<ui:FASettingsExpander Header="{Binding RefreshButtonText}"
<ui:SettingsExpander Header="{Binding RefreshButtonText}"
Description="{Binding StatusMessage}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF15E8;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="ShoppingBag" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<Grid ColumnDefinitions="*,Auto"
ColumnSpacing="12">
<TextBox x:Name="SearchTextBox"
Text="{Binding SearchText}"
PlaceholderText="{Binding SearchPlaceholder}"
Watermark="{Binding SearchPlaceholder}"
Focusable="True"
IsTabStop="True" />
<Button Grid.Column="1"
Command="{Binding RefreshCommand}"
Content="{Binding RefreshButtonText}" />
</Grid>
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<TextBlock Classes="settings-item-description"
IsVisible="{Binding ShowEmptyState}"

View File

@@ -1,24 +1,24 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:controls="using:LanMountainDesktop.Controls"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
x:Class="LanMountainDesktop.Views.SettingsPages.PluginsSettingsPage"
x:Name="Root"
x:DataType="vm:PluginsSettingsPageViewModel">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Classes="settings-page-container settings-page-animated">
<ui:FASettingsExpander Header="{Binding RefreshButtonText}"
<ui:SettingsExpander Header="{Binding RefreshButtonText}"
Description="{Binding StatusMessage}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF06FC;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="ArrowSync" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<Button Command="{Binding RefreshCommand}"
Content="{Binding RefreshButtonText}" />
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<Separator Classes="settings-separator" />
@@ -29,17 +29,17 @@
<ItemsControl ItemsSource="{Binding InstalledPlugins}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:InstalledPluginItemViewModel">
<ui:FASettingsExpander>
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF28D4;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Header>
<ui:SettingsExpander>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="PuzzleCube" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Header>
<StackPanel>
<TextBlock FontWeight="SemiBold" Text="{Binding Name}" />
<TextBlock Opacity="0.76" FontSize="12" Text="{Binding Description}" />
</StackPanel>
</ui:FASettingsExpander.Header>
<ui:FASettingsExpander.Footer>
</ui:SettingsExpander.Header>
<ui:SettingsExpander.Footer>
<StackPanel Orientation="Horizontal" Spacing="8">
<ToggleSwitch IsChecked="{Binding IsEnabled}"
Command="{Binding #Root.DataContext.TogglePluginCommand}"
@@ -48,8 +48,8 @@
CommandParameter="{Binding}"
Content="{Binding #Root.DataContext.DeleteButtonText}" />
</StackPanel>
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

View File

@@ -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"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
x:Class="LanMountainDesktop.Views.SettingsPages.PrivacyPolicyDrawer"
x:DataType="vm:PrivacyPolicyViewModel">
<ScrollViewer VerticalScrollBarVisibility="Auto">

View File

@@ -1,9 +1,9 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:controls="using:LanMountainDesktop.Controls"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
x:Class="LanMountainDesktop.Views.SettingsPages.PrivacySettingsPage"
x:DataType="vm:PrivacySettingsPageViewModel">
<ScrollViewer VerticalScrollBarVisibility="Auto">
@@ -12,25 +12,25 @@
Text="{Binding PrivacyHeader}"
Margin="0,0,0,4" />
<ui:FASettingsExpander Header="{Binding CrashUploadHeader}"
<ui:SettingsExpander Header="{Binding CrashUploadHeader}"
Description="{Binding CrashUploadDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0D9C;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="ShieldDismiss" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding UploadAnonymousCrashData}" />
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:FASettingsExpander Header="{Binding UsageUploadHeader}"
<ui:SettingsExpander Header="{Binding UsageUploadHeader}"
Description="{Binding UsageUploadDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF07A4;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Info" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding UploadAnonymousUsageData}" />
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="8"

View File

@@ -1,9 +1,9 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:controls="using:LanMountainDesktop.Controls"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
x:Class="LanMountainDesktop.Views.SettingsPages.StatusBarSettingsPage"
x:DataType="vm:StatusBarSettingsPageViewModel">
<ScrollViewer VerticalScrollBarVisibility="Auto">
@@ -12,15 +12,15 @@
Text="{Binding ComponentsHeader}"
Margin="0,0,0,4" />
<ui:FASettingsExpander Header="{Binding ClockHeader}"
<ui:SettingsExpander Header="{Binding ClockHeader}"
Description="{Binding ClockDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0168;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Clock" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding ShowClock}" />
</ui:FASettingsExpander.Footer>
<ui:FASettingsExpanderItem>
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="Auto,*"
ColumnSpacing="16">
<TextBlock Text="{Binding ClockFormatLabel}"
@@ -37,8 +37,8 @@
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</ui:FASettingsExpanderItem>
<ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="*,Auto"
ColumnSpacing="16">
<StackPanel Spacing="2">
@@ -51,8 +51,8 @@
IsChecked="{Binding ClockTransparentBackground}"
VerticalAlignment="Center" />
</Grid>
</ui:FASettingsExpanderItem>
<ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="Auto,*"
ColumnSpacing="16">
<TextBlock Text="{Binding ClockPositionLabel}"
@@ -69,8 +69,8 @@
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</ui:FASettingsExpanderItem>
<ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="Auto,*"
ColumnSpacing="16">
<TextBlock Text="{Binding ClockFontSizeLabel}"
@@ -87,18 +87,18 @@
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<ui:FASettingsExpander Header="{Binding TextCapsuleHeader}"
<ui:SettingsExpander Header="{Binding TextCapsuleHeader}"
Description="{Binding TextCapsuleDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF07F0;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="TextQuote" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding ShowTextCapsule}" />
</ui:FASettingsExpander.Footer>
<ui:FASettingsExpanderItem>
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="Auto,*"
ColumnSpacing="16">
<TextBlock Text="{Binding TextCapsuleContentLabel}"
@@ -110,10 +110,10 @@
Height="100"
IsEnabled="{Binding ShowTextCapsule}"
Text="{Binding TextCapsuleContent}"
PlaceholderText="Enter Markdown text..." />
Watermark="Enter Markdown text..." />
</Grid>
</ui:FASettingsExpanderItem>
<ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="Auto,*"
ColumnSpacing="16">
<TextBlock Text="{Binding TextCapsulePositionLabel}"
@@ -130,8 +130,8 @@
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</ui:FASettingsExpanderItem>
<ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="*,Auto"
ColumnSpacing="16">
<TextBlock Text="{Binding TextCapsuleTransparentBackgroundLabel}"
@@ -141,18 +141,18 @@
IsEnabled="{Binding ShowTextCapsule}"
VerticalAlignment="Center" />
</Grid>
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<ui:FASettingsExpander Header="{Binding NetworkSpeedHeader}"
<ui:SettingsExpander Header="{Binding NetworkSpeedHeader}"
Description="{Binding NetworkSpeedDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF01A0;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="ArrowBidirectionalUpDown" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding ShowNetworkSpeed}" />
</ui:FASettingsExpander.Footer>
<ui:FASettingsExpanderItem>
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="Auto,*"
ColumnSpacing="16">
<TextBlock Text="{Binding NetworkSpeedPositionLabel}"
@@ -169,8 +169,8 @@
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</ui:FASettingsExpanderItem>
<ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="Auto,*"
ColumnSpacing="16">
<TextBlock Text="{Binding NetworkSpeedDisplayModeLabel}"
@@ -187,8 +187,8 @@
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</ui:FASettingsExpanderItem>
<ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="*,Auto"
ColumnSpacing="16">
<TextBlock Text="{Binding NetworkSpeedTransparentBackgroundLabel}"
@@ -198,8 +198,8 @@
IsEnabled="{Binding ShowNetworkSpeed}"
VerticalAlignment="Center" />
</Grid>
</ui:FASettingsExpanderItem>
<ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="*,Auto"
ColumnSpacing="16">
<TextBlock Text="{Binding ShowNetworkTypeIconLabel}"
@@ -209,8 +209,8 @@
IsEnabled="{Binding ShowNetworkSpeed}"
VerticalAlignment="Center" />
</Grid>
</ui:FASettingsExpanderItem>
<ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="Auto,*"
ColumnSpacing="16">
<TextBlock Text="{Binding NetworkSpeedFontSizeLabel}"
@@ -227,8 +227,8 @@
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<Separator Classes="settings-separator" />
@@ -236,12 +236,12 @@
Text="{Binding SpacingHeader}"
Margin="0,0,0,4" />
<ui:FASettingsExpander Header="{Binding SpacingHeader}"
<ui:SettingsExpander Header="{Binding SpacingHeader}"
Description="{Binding SpacingDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF02D8;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Apps" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ComboBox Width="180"
ItemsSource="{Binding SpacingModes}"
SelectedItem="{Binding SelectedSpacingMode}">
@@ -251,13 +251,13 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:FASettingsExpander.Footer>
<ui:FASettingsExpanderItem IsVisible="{Binding IsCustomSpacingVisible}">
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem IsVisible="{Binding IsCustomSpacingVisible}">
<Grid ColumnDefinitions="Auto,*"
ColumnSpacing="16">
<TextBlock Text="{Binding CustomSpacingLabel}"
VerticalAlignment="Center" />
<ui:FANumberBox Grid.Column="1"
<ui:NumberBox Grid.Column="1"
Width="160"
Minimum="0"
Maximum="30"
@@ -266,8 +266,8 @@
SpinButtonPlacementMode="Inline"
Value="{Binding CustomSpacingPercent}" />
</Grid>
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<Separator Classes="settings-separator" />
@@ -275,15 +275,15 @@
Text="{Binding StatusBarShadowHeader}"
Margin="0,0,0,4" />
<ui:FASettingsExpander Header="{Binding StatusBarShadowHeader}"
<ui:SettingsExpander Header="{Binding StatusBarShadowHeader}"
Description="{Binding StatusBarShadowDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF1A48;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Square" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding StatusBarShadowEnabled}" />
</ui:FASettingsExpander.Footer>
<ui:FASettingsExpanderItem>
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="Auto,*"
ColumnSpacing="16">
<TextBlock Text="{Binding StatusBarShadowColorLabel}"
@@ -302,8 +302,8 @@
</Button.Flyout>
</Button>
</Grid>
</ui:FASettingsExpanderItem>
<ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="Auto,*"
ColumnSpacing="16">
<TextBlock Text="{Binding StatusBarShadowOpacityLabel}"
@@ -315,8 +315,8 @@
IsEnabled="{Binding StatusBarShadowEnabled}"
Value="{Binding StatusBarShadowOpacity}" />
</Grid>
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
</StackPanel>
</ScrollViewer>
</UserControl>

View File

@@ -1,36 +1,36 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
x:Class="LanMountainDesktop.Views.SettingsPages.StudySettingsPage"
x:DataType="vm:StudySettingsPageViewModel">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Classes="settings-page-container settings-page-animated">
<!-- 閹绱戦崗?-->
<ui:FASettingsExpander Classes="settings-expander-card"
<!-- 总开关 -->
<ui:SettingsExpander Classes="settings-expander-card"
Header="{Binding MasterSwitchHeader}"
Description="{Binding MasterSwitchDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF2014;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="DataHistogram" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding StudyEnabled}" />
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- 閸n亪鐓堕惄鎴炵ゴ鐠佸墽鐤?-->
<ui:FASettingsExpander Classes="settings-expander-card"
<!-- 噪音监测设置 -->
<ui:SettingsExpander Classes="settings-expander-card"
Header="{Binding NoiseMonitoringHeader}"
Description="{Binding NoiseMonitoringDescription}"
IsEnabled="{Binding StudyEnabled}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0018;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Mic" />
</ui:SettingsExpander.IconSource>
<!-- 闁插洭娉︽0鎴犲芳 -->
<ui:FASettingsExpanderItem>
<!-- 采集频率 -->
<ui:SettingsExpanderItem>
<StackPanel Spacing="8">
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="16">
<StackPanel Classes="settings-item">
@@ -52,10 +52,10 @@
TickFrequency="20"
IsSnapToTickEnabled="True" />
</StackPanel>
</ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<!-- 閸n亪鐓堕弫蹇斿妳鎼?-->
<ui:FASettingsExpanderItem>
<!-- 噪音敏感度 -->
<ui:SettingsExpanderItem>
<StackPanel Spacing="8">
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="16">
<StackPanel Classes="settings-item">
@@ -77,27 +77,27 @@
TickFrequency="5"
IsSnapToTickEnabled="True" />
</StackPanel>
</ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<!-- 鐠囧嫬鍨庨梼鍫濃偓鍏兼▔缁€?-->
<ui:FASettingsExpanderItem>
<!-- 评分阈值显示 -->
<ui:SettingsExpanderItem>
<TextBlock Classes="settings-item-description"
Text="{Binding CurrentThresholdText}"
TextWrapping="Wrap" />
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<!-- 娑撴挻鏁炵拋鈩冩鐠佸墽鐤?-->
<ui:FASettingsExpander Classes="settings-expander-card"
<!-- 专注计时设置 -->
<ui:SettingsExpander Classes="settings-expander-card"
Header="{Binding FocusTimerHeader}"
Description="{Binding FocusTimerDescription}"
IsEnabled="{Binding StudyEnabled}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF066C;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Timer" />
</ui:SettingsExpander.IconSource>
<!-- 娑撴挻鏁為弮鍫曟毐 -->
<ui:FASettingsExpanderItem>
<!-- 专注时长 -->
<ui:SettingsExpanderItem>
<StackPanel Spacing="8">
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="16">
<StackPanel Classes="settings-item">
@@ -119,10 +119,10 @@
TickFrequency="5"
IsSnapToTickEnabled="True" />
</StackPanel>
</ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<!-- 娴兼垶浼呴弮鍫曟毐 -->
<ui:FASettingsExpanderItem>
<!-- 休息时长 -->
<ui:SettingsExpanderItem>
<StackPanel Spacing="8">
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="16">
<StackPanel Classes="settings-item">
@@ -144,10 +144,10 @@
TickFrequency="5"
IsSnapToTickEnabled="True" />
</StackPanel>
</ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<!-- 闂€澶哥搐閹垱妞傞梹?-->
<ui:FASettingsExpanderItem>
<!-- 长休息时长 -->
<ui:SettingsExpanderItem>
<StackPanel Spacing="8">
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="16">
<StackPanel Classes="settings-item">
@@ -169,10 +169,10 @@
TickFrequency="5"
IsSnapToTickEnabled="True" />
</StackPanel>
</ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<!-- 闂€澶哥搐閹垶妫块梾?-->
<ui:FASettingsExpanderItem>
<!-- 长休息间隔 -->
<ui:SettingsExpanderItem>
<StackPanel Spacing="8">
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="16">
<StackPanel Classes="settings-item">
@@ -194,10 +194,10 @@
TickFrequency="1"
IsSnapToTickEnabled="True" />
</StackPanel>
</ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<!-- 閼奉亜濮╁鈧慨瀣╃搐閹?-->
<ui:FASettingsExpanderItem>
<!-- 自动开始休息 -->
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="16">
<StackPanel Classes="settings-item">
<TextBlock Classes="settings-item-label"
@@ -208,10 +208,10 @@
<ToggleSwitch Grid.Column="1"
IsChecked="{Binding AutoStartBreak}" />
</Grid>
</ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<!-- 閼奉亜濮╁鈧慨瀣╃瑩濞?-->
<ui:FASettingsExpanderItem>
<!-- 自动开始专注 -->
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="16">
<StackPanel Classes="settings-item">
<TextBlock Classes="settings-item-label"
@@ -222,20 +222,20 @@
<ToggleSwitch Grid.Column="1"
IsChecked="{Binding AutoStartFocus}" />
</Grid>
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<!-- 閹绘劙鍟嬬拋鍓х枂 -->
<ui:FASettingsExpander Classes="settings-expander-card"
<!-- 提醒设置 -->
<ui:SettingsExpander Classes="settings-expander-card"
Header="{Binding AlertHeader}"
Description="{Binding AlertDescription}"
IsEnabled="{Binding StudyEnabled}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0010;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Alert" />
</ui:SettingsExpander.IconSource>
<!-- 閸n亪鐓堕幍鎾存焽閹绘劙鍟?-->
<ui:FASettingsExpanderItem>
<!-- 噪音打断提醒 -->
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="16">
<StackPanel Classes="settings-item">
<TextBlock Classes="settings-item-label"
@@ -246,10 +246,10 @@
<ToggleSwitch Grid.Column="1"
IsChecked="{Binding NoiseAlertEnabled}" />
</Grid>
</ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<!-- 濮e繐鍨庨柦鐔告付婢堆冾啇韫囧秵澧﹂弬顓燁偧閺?-->
<ui:FASettingsExpanderItem IsVisible="{Binding NoiseAlertEnabled}">
<!-- 每分钟最大容忍打断次数 -->
<ui:SettingsExpanderItem IsVisible="{Binding NoiseAlertEnabled}">
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="16">
<StackPanel Classes="settings-item">
<TextBlock Classes="settings-item-label"
@@ -264,20 +264,20 @@
Increment="1"
Value="{Binding MaxInterruptsPerMinute}" />
</Grid>
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<!-- 閺勫墽銇氱拋鍓х枂 -->
<ui:FASettingsExpander Classes="settings-expander-card"
<!-- 显示设置 -->
<ui:SettingsExpander Classes="settings-expander-card"
Header="{Binding DisplayHeader}"
Description="{Binding DisplayDescription}"
IsEnabled="{Binding StudyEnabled}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0908;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Eye" />
</ui:SettingsExpander.IconSource>
<!-- 閺勫墽銇氱€圭偞妞傞崚鍡氱 -->
<ui:FASettingsExpanderItem>
<!-- 显示实时分贝 -->
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="16">
<StackPanel Classes="settings-item">
<TextBlock Classes="settings-item-label"
@@ -288,10 +288,10 @@
<ToggleSwitch Grid.Column="1"
IsChecked="{Binding ShowRealtimeDb}" />
</Grid>
</ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<!-- 閸╁搫鍣弰鍓с仛閸掑棜绀?-->
<ui:FASettingsExpanderItem>
<!-- 基准显示分贝 -->
<ui:SettingsExpanderItem>
<StackPanel Spacing="8">
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="16">
<StackPanel Classes="settings-item">
@@ -313,10 +313,10 @@
TickFrequency="5"
IsSnapToTickEnabled="True" />
</StackPanel>
</ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<!-- 楠炲啿娼庨弮鍫曟?缁?-->
<ui:FASettingsExpanderItem>
<!-- 平均时间窗 -->
<ui:SettingsExpanderItem>
<StackPanel Spacing="8">
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="16">
<StackPanel Classes="settings-item">
@@ -338,8 +338,8 @@
TickFrequency="1"
IsSnapToTickEnabled="True" />
</StackPanel>
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<TextBlock Classes="settings-item-description"
Margin="0,8,0,0"
@@ -347,4 +347,4 @@
TextWrapping="Wrap" />
</StackPanel>
</ScrollViewer>
</UserControl>
</UserControl>

View File

@@ -1,9 +1,9 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:controls="using:LanMountainDesktop.Controls"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
x:Class="LanMountainDesktop.Views.SettingsPages.UpdateSettingsPage"
x:DataType="vm:UpdateSettingsPageViewModel">
<UserControl.Styles>
@@ -163,13 +163,13 @@
Margin="0,0,0,18"
Text="{Binding PreferencesDescription}" />
<ui:FASettingsExpander Classes="settings-expander-card"
<ui:SettingsExpander Classes="settings-expander-card"
Header="{Binding UpdateChannelLabel}"
Description="{Binding SelectedUpdateChannelDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF06C8;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="BranchFork" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ComboBox Width="220"
ItemsSource="{Binding UpdateChannelOptions}"
SelectedItem="{Binding SelectedUpdateChannelOption}">
@@ -179,24 +179,24 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:FASettingsExpander.Footer>
<ui:FASettingsExpanderItem Content="{Binding ForceCheckUpdateLabel}"
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem Content="{Binding ForceCheckUpdateLabel}"
Description="{Binding ForceCheckUpdateDescription}"
IsClickEnabled="True"
Command="{Binding ForceCheckUpdateCommand}">
<ui:FASettingsExpanderItem.IconSource>
<ui:FAFontIconSource Glyph="&#xF0FD4;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpanderItem.IconSource>
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
<ui:SettingsExpanderItem.IconSource>
<fi:SymbolIconSource Symbol="ArrowSync" />
</ui:SettingsExpanderItem.IconSource>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<ui:FASettingsExpander Classes="settings-expander-card"
<ui:SettingsExpander Classes="settings-expander-card"
Header="{Binding UpdateSourceLabel}"
Description="{Binding SelectedUpdateSourceDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF182C;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="GlobeArrowForward" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ComboBox Width="220"
ItemsSource="{Binding UpdateSourceOptions}"
SelectedItem="{Binding SelectedUpdateSourceOption}">
@@ -206,16 +206,16 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:FASettingsExpander Classes="settings-expander-card"
<ui:SettingsExpander Classes="settings-expander-card"
Header="{Binding UpdateModeLabel}"
Description="{Binding SelectedUpdateModeDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF06FC;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Options" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ComboBox Width="260"
ItemsSource="{Binding UpdateModeOptions}"
SelectedItem="{Binding SelectedUpdateModeOption}">
@@ -225,23 +225,23 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:FASettingsExpander Classes="settings-expander-card"
<ui:SettingsExpander Classes="settings-expander-card"
Header="{Binding DownloadThreadsLabel}"
Description="{Binding DownloadThreadsDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF06C0;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:FANumberBox Width="160"
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="ArrowDownload" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ui:NumberBox Width="160"
Minimum="1"
Maximum="128"
SpinButtonPlacementMode="Inline"
Value="{Binding DownloadThreadsSliderValue}" />
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</StackPanel>

View File

@@ -1,36 +1,35 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:controls="using:LanMountainDesktop.Controls"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
x:Class="LanMountainDesktop.Views.SettingsPages.WallpaperSettingsPage"
x:DataType="vm:WallpaperSettingsPageViewModel">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Classes="settings-page-container settings-page-animated">
<Grid ColumnDefinitions="*,*"
ColumnSpacing="32"
Margin="0,0,0,32">
<Border Grid.Column="0"
VerticalAlignment="Top">
<!-- 预览与颜色选择区域 -->
<Grid ColumnDefinitions="*,*" ColumnSpacing="32" Margin="0,0,0,32">
<!-- 左侧:模拟屏幕预览 (占一半宽度) -->
<Border Grid.Column="0" VerticalAlignment="Top">
<Viewbox Stretch="Uniform">
<Border Width="1600"
Height="900"
Background="#080808"
BorderBrush="#1A1A1A"
BorderThickness="24"
<Border Width="1600" Height="900"
Background="#080808"
BorderBrush="#1A1A1A"
BorderThickness="24"
CornerRadius="48"
BoxShadow="0 12 32 #50000000">
<Panel Background="{DynamicResource AdaptiveSurfaceBaseBrush}"
Margin="2">
<Panel Background="{DynamicResource AdaptiveSurfaceBaseBrush}" Margin="2">
<!-- 图片预览 -->
<Border Background="#FFF6F7F9"
IsVisible="{Binding IsImage}">
<Border Background="{Binding PreviewBrush}" />
</Border>
<Border Background="{Binding SelectedColor}"
<!-- 纯色预览 -->
<Border Background="{Binding SelectedColor}"
IsVisible="{Binding IsSolidColor}" />
<!-- 系统壁纸预览 -->
<Border Background="#FFF6F7F9"
IsVisible="{Binding IsSystemWallpaper}">
<Border Background="{Binding PreviewBrush}" />
@@ -40,139 +39,100 @@
</Viewbox>
</Border>
<StackPanel Grid.Column="1"
VerticalAlignment="Center"
Spacing="12"
IsVisible="{Binding IsSolidColor}">
<!-- 右侧:颜色选择网格 -->
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="12" IsVisible="{Binding IsSolidColor}">
<TextBlock Text="{Binding WallpaperColorLabel}"
FontSize="14"
FontWeight="SemiBold"
Opacity="0.8" />
<UniformGrid Columns="4"
Rows="3">
<Button Width="48"
Height="48"
Margin="4"
Padding="0"
<UniformGrid Columns="4" Rows="3">
<!-- 预设颜色 1-11 -->
<Button Width="48" Height="48" Margin="4" Padding="0"
Background="#D8A7B1"
BorderThickness="0"
CornerRadius="6"
Command="{Binding SelectColorCommand}"
CommandParameter="#D8A7B1"
ToolTip.Tip="#D8A7B1" />
<Button Width="48"
Height="48"
Margin="4"
Padding="0"
<Button Width="48" Height="48" Margin="4" Padding="0"
Background="#B6C9BB"
BorderThickness="0"
CornerRadius="6"
Command="{Binding SelectColorCommand}"
CommandParameter="#B6C9BB"
ToolTip.Tip="#B6C9BB" />
<Button Width="48"
Height="48"
Margin="4"
Padding="0"
<Button Width="48" Height="48" Margin="4" Padding="0"
Background="#A2B5BB"
BorderThickness="0"
CornerRadius="6"
Command="{Binding SelectColorCommand}"
CommandParameter="#A2B5BB"
ToolTip.Tip="#A2B5BB" />
<Button Width="48"
Height="48"
Margin="4"
Padding="0"
<Button Width="48" Height="48" Margin="4" Padding="0"
Background="#E6E2D3"
BorderThickness="0"
CornerRadius="6"
Command="{Binding SelectColorCommand}"
CommandParameter="#E6E2D3"
ToolTip.Tip="#E6E2D3" />
<Button Width="48"
Height="48"
Margin="4"
Padding="0"
<Button Width="48" Height="48" Margin="4" Padding="0"
Background="#B5A397"
BorderThickness="0"
CornerRadius="6"
Command="{Binding SelectColorCommand}"
CommandParameter="#B5A397"
ToolTip.Tip="#B5A397" />
<Button Width="48"
Height="48"
Margin="4"
Padding="0"
<Button Width="48" Height="48" Margin="4" Padding="0"
Background="#C5C1C0"
BorderThickness="0"
CornerRadius="6"
Command="{Binding SelectColorCommand}"
CommandParameter="#C5C1C0"
ToolTip.Tip="#C5C1C0" />
<Button Width="48"
Height="48"
Margin="4"
Padding="0"
<Button Width="48" Height="48" Margin="4" Padding="0"
Background="#D4BE8D"
BorderThickness="0"
CornerRadius="6"
Command="{Binding SelectColorCommand}"
CommandParameter="#D4BE8D"
ToolTip.Tip="#D4BE8D" />
<Button Width="48"
Height="48"
Margin="4"
Padding="0"
<Button Width="48" Height="48" Margin="4" Padding="0"
Background="#C08261"
BorderThickness="0"
CornerRadius="6"
Command="{Binding SelectColorCommand}"
CommandParameter="#C08261"
ToolTip.Tip="#C08261" />
<Button Width="48"
Height="48"
Margin="4"
Padding="0"
<Button Width="48" Height="48" Margin="4" Padding="0"
Background="#8E9775"
BorderThickness="0"
CornerRadius="6"
Command="{Binding SelectColorCommand}"
CommandParameter="#8E9775"
ToolTip.Tip="#8E9775" />
<Button Width="48"
Height="48"
Margin="4"
Padding="0"
<Button Width="48" Height="48" Margin="4" Padding="0"
Background="#9FBAD3"
BorderThickness="0"
CornerRadius="6"
Command="{Binding SelectColorCommand}"
CommandParameter="#9FBAD3"
ToolTip.Tip="#9FBAD3" />
<Button Width="48"
Height="48"
Margin="4"
Padding="0"
<Button Width="48" Height="48" Margin="4" Padding="0"
Background="#E5BAA2"
BorderThickness="0"
CornerRadius="6"
Command="{Binding SelectColorCommand}"
CommandParameter="#E5BAA2"
ToolTip.Tip="#E5BAA2" />
<Button Width="48"
Height="48"
Margin="4"
Padding="0"
<!-- 第12个位置自定义颜色选择器 -->
<Button Width="48" Height="48" Margin="4" Padding="0"
Background="{Binding CustomColorBrush}"
BorderThickness="0"
CornerRadius="6"
ToolTip.Tip="Custom color">
ToolTip.Tip="自定义颜色">
<Button.Flyout>
<Flyout Placement="BottomEdgeAlignedLeft">
<StackPanel Width="260"
Spacing="12">
<StackPanel Width="260" Spacing="12">
<ColorPicker Color="{Binding CustomColor}" />
</StackPanel>
</Flyout>
@@ -181,10 +141,8 @@
</UniformGrid>
</StackPanel>
<StackPanel Grid.Column="1"
VerticalAlignment="Center"
Spacing="12"
IsVisible="{Binding IsSystemWallpaper}">
<!-- 右侧:系统壁纸状态 -->
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="12" IsVisible="{Binding IsSystemWallpaper}">
<TextBlock Text="{Binding SystemWallpaperLabel}"
FontSize="14"
FontWeight="SemiBold"
@@ -197,18 +155,19 @@
</StackPanel>
</Grid>
<Separator Classes="settings-separator"
Margin="0,0,0,24" />
<Separator Classes="settings-separator" Margin="0,0,0,24" />
<!-- 基本设置项 -->
<controls:IconText Icon="Image"
Text="{Binding WallpaperHeader}"
Margin="0,0,0,8" />
<ui:FASettingsExpander Header="{Binding WallpaperTypeLabel}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF067C;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<!-- 壁纸类型 -->
<ui:SettingsExpander Header="{Binding WallpaperTypeLabel}">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Layer" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ComboBox Width="200"
ItemsSource="{Binding WallpaperTypes}"
SelectedItem="{Binding SelectedWallpaperType}">
@@ -218,18 +177,18 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:FASettingsExpander Header="{Binding WallpaperPathLabel}"
<!-- 图片文件选择 -->
<ui:SettingsExpander Header="{Binding WallpaperPathLabel}"
IsVisible="{Binding IsImage}"
Margin="0,4,0,0">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF058C;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<StackPanel Orientation="Horizontal"
Spacing="8">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="FolderOpen" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBox IsReadOnly="True"
Width="300"
Text="{Binding WallpaperPath}"
@@ -239,18 +198,18 @@
Content="{Binding ImportWallpaperButtonText}"
VerticalAlignment="Center" />
</StackPanel>
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:FASettingsExpander Header="{Binding RefreshIntervalLabel}"
<!-- 系统壁纸刷新设置 -->
<ui:SettingsExpander Header="{Binding RefreshIntervalLabel}"
IsVisible="{Binding IsSystemWallpaper}"
Margin="0,4,0,0">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0168;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<StackPanel Orientation="Horizontal"
Spacing="8">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Clock" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<StackPanel Orientation="Horizontal" Spacing="8">
<ComboBox Width="140"
ItemsSource="{Binding RefreshIntervals}"
SelectedItem="{Binding SelectedRefreshInterval}">
@@ -265,21 +224,21 @@
ToolTip.Tip="{Binding RefreshButtonTooltip}"
VerticalAlignment="Center"
Padding="12,8">
<fi:SymbolIcon Symbol="ArrowSync"
IconVariant="Regular" />
<fi:SymbolIcon Symbol="ArrowSync" IconVariant="Regular" />
</Button>
</StackPanel>
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:FASettingsExpander Header="{Binding WallpaperPlacementLabel}"
<!-- 填充方式(图片和系统壁纸都显示) -->
<ui:SettingsExpander Header="{Binding WallpaperPlacementLabel}"
Description="{Binding WallpaperPlacementDescription}"
IsVisible="{Binding IsImage}"
IsVisible="{Binding IsImageOrVideo}"
Margin="0,4,0,0">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF01A8;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Maximize" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ComboBox Width="200"
ItemsSource="{Binding WallpaperPlacements}"
SelectedItem="{Binding SelectedWallpaperPlacement}">
@@ -289,17 +248,18 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:FASettingsExpander Header="{Binding WallpaperPlacementLabel}"
<!-- 系统壁纸填充方式 -->
<ui:SettingsExpander Header="{Binding WallpaperPlacementLabel}"
Description="{Binding WallpaperPlacementDescription}"
IsVisible="{Binding IsSystemWallpaper}"
Margin="0,4,0,0">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF01A8;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Maximize" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ComboBox Width="200"
ItemsSource="{Binding WallpaperPlacements}"
SelectedItem="{Binding SelectedWallpaperPlacement}">
@@ -309,8 +269,9 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</StackPanel>
</ScrollViewer>
</UserControl>

View File

@@ -1,10 +1,10 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:models="using:LanMountainDesktop.Models"
xmlns:controls="using:LanMountainDesktop.Controls"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
x:Class="LanMountainDesktop.Views.SettingsPages.WeatherSettingsPage"
x:DataType="vm:WeatherSettingsPageViewModel">
<ScrollViewer VerticalScrollBarVisibility="Auto">
@@ -47,7 +47,7 @@
<Button Classes="settings-accent-button"
Command="{Binding RefreshPreviewCommand}"
Content="{Binding RefreshButtonText}" />
<ui:FAProgressRing IsIndeterminate="True"
<ui:ProgressRing IsIndeterminate="True"
IsVisible="{Binding IsRefreshingPreview}"
Width="28"
Height="28"
@@ -56,13 +56,13 @@
</Grid>
</Border>
<ui:FASettingsExpander Classes="settings-expander-card"
<ui:SettingsExpander Classes="settings-expander-card"
Header="{Binding LocationSourceHeader}"
Description="{Binding LocationSourceDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0504;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="WeatherMoon" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ComboBox Width="220"
ItemsSource="{Binding LocationModes}"
SelectedItem="{Binding SelectedLocationMode}">
@@ -72,33 +72,33 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:FASettingsExpander.Footer>
<ui:FASettingsExpanderItem>
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem>
<TextBlock Classes="settings-item-description"
Text="{Binding CurrentLocationSummary}"
TextWrapping="Wrap" />
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<ui:FASettingsExpander Classes="settings-expander-card"
<ui:SettingsExpander Classes="settings-expander-card"
Header="{Binding CitySearchHeader}"
Description="{Binding CitySearchDescription}"
IsVisible="{Binding IsCitySearchMode}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF03F4;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Search" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<Button Classes="settings-accent-button"
Command="{Binding ApplyCitySelectionCommand}"
Content="{Binding ApplyCityButtonText}" />
</ui:FASettingsExpander.Footer>
<ui:FASettingsExpanderItem>
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem>
<StackPanel Spacing="14">
<Grid ColumnDefinitions="*,Auto"
ColumnSpacing="12">
<TextBox x:Name="SearchKeywordTextBox"
Text="{Binding SearchKeyword}"
PlaceholderText="{Binding SearchPlaceholder}"
Watermark="{Binding SearchPlaceholder}"
Focusable="True"
IsTabStop="True" />
<Button Grid.Column="1"
@@ -106,7 +106,7 @@
Content="{Binding SearchButtonText}" />
</Grid>
<ui:FAProgressRing IsIndeterminate="True"
<ui:ProgressRing IsIndeterminate="True"
IsVisible="{Binding IsSearching}"
Width="24"
Height="24"
@@ -123,10 +123,9 @@
<DataTemplate x:DataType="models:WeatherLocation">
<Grid ColumnDefinitions="Auto,*"
ColumnSpacing="12">
<ui:FAFontIcon Classes="icon-s"
<fi:SymbolIcon Classes="icon-s"
Margin="0,2,0,0"
Glyph="&#xF02BC;"
FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
Symbol="City" />
<StackPanel Grid.Column="1"
Spacing="4">
<TextBlock Classes="settings-item-label"
@@ -141,22 +140,22 @@
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<ui:FASettingsExpander Classes="settings-expander-card"
<ui:SettingsExpander Classes="settings-expander-card"
Header="{Binding CoordinatesHeader}"
Description="{Binding CoordinatesDescription}"
IsVisible="{Binding IsCoordinatesMode}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF02BC;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Location" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<Button Classes="settings-accent-button"
Command="{Binding ApplyCoordinatesCommand}"
Content="{Binding ApplyCoordinatesButtonText}" />
</ui:FASettingsExpander.Footer>
<ui:FASettingsExpanderItem>
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="*,*"
Classes="settings-inline-pair">
<StackPanel Classes="settings-item">
@@ -180,36 +179,36 @@
Value="{Binding Longitude}" />
</StackPanel>
</Grid>
</ui:FASettingsExpanderItem>
<ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem>
<TextBox x:Name="LocationKeyTextBox"
Text="{Binding LocationKey}"
PlaceholderText="{Binding LocationKeyPlaceholder}"
Watermark="{Binding LocationKeyPlaceholder}"
Focusable="True"
IsTabStop="True" />
</ui:FASettingsExpanderItem>
<ui:FASettingsExpanderItem>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem>
<TextBox x:Name="LocationNameTextBox"
Text="{Binding LocationName}"
PlaceholderText="{Binding LocationNamePlaceholder}"
Watermark="{Binding LocationNamePlaceholder}"
Focusable="True"
IsTabStop="True" />
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<ui:FASettingsExpander Classes="settings-expander-card"
<ui:SettingsExpander Classes="settings-expander-card"
Header="{Binding LocationServicesHeader}"
Description="{Binding LocationServicesDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF02BC;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Location" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<Button Classes="settings-accent-button"
Command="{Binding UseCurrentLocationCommand}"
Content="{Binding UseCurrentLocationButtonText}"
IsVisible="{Binding IsLocationSupported}" />
</ui:FASettingsExpander.Footer>
<ui:FASettingsExpanderItem>
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="*,Auto"
ColumnSpacing="16">
<StackPanel Classes="settings-item">
@@ -223,23 +222,23 @@
IsChecked="{Binding AutoRefreshLocation}"
IsEnabled="{Binding IsLocationSupported}" />
</Grid>
</ui:FASettingsExpanderItem>
<ui:FASettingsExpanderItem IsVisible="{Binding IsRefreshingLocation}">
<ui:FAProgressRing IsIndeterminate="True"
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem IsVisible="{Binding IsRefreshingLocation}">
<ui:ProgressRing IsIndeterminate="True"
IsVisible="{Binding IsRefreshingLocation}"
Width="28"
Height="28"
HorizontalAlignment="Left" />
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<ui:FASettingsExpander Classes="settings-expander-card"
<ui:SettingsExpander Classes="settings-expander-card"
Header="{Binding AlertFilterHeader}"
Description="{Binding AlertFilterDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF04E8;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Warning" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<TextBox x:Name="ExcludedAlertsTextBox"
Width="360"
MinHeight="120"
@@ -248,24 +247,24 @@
Text="{Binding ExcludedAlerts}"
Focusable="True"
IsTabStop="True" />
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:FASettingsExpander Classes="settings-expander-card"
<ui:SettingsExpander Classes="settings-expander-card"
Header="{Binding RequestHeader}"
Description="{Binding RequestDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0BC4;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="ShieldDismiss" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ToggleSwitch IsChecked="{Binding NoTlsRequests}" />
</ui:FASettingsExpander.Footer>
<ui:FASettingsExpanderItem>
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem>
<TextBlock Classes="settings-item-description"
Text="{Binding NoTlsToggleText}"
TextWrapping="Wrap" />
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<TextBlock Classes="settings-item-description"
Margin="0,8,0,0"

View File

@@ -2,7 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
x:Class="LanMountainDesktop.Views.SettingsWindow"
x:DataType="vm:SettingsWindowViewModel"
Width="1120"
@@ -11,7 +11,7 @@
MinHeight="480"
CanResize="True"
WindowStartupLocation="Manual"
WindowDecorations="BorderOnly"
SystemDecorations="BorderOnly"
FontFamily="{DynamicResource AppFontFamily}"
Background="Transparent"
Title="{Binding Title}">
@@ -112,7 +112,7 @@
</Grid>
</Border>
<ui:FANavigationView x:Name="RootNavigationView"
<ui:NavigationView x:Name="RootNavigationView"
Grid.Row="1"
Margin="0,8,0,0"
Background="Transparent"
@@ -122,10 +122,10 @@
IsPaneToggleButtonVisible="False"
IsBackButtonVisible="False"
SelectionChanged="OnNavigationSelectionChanged">
<ui:FANavigationView.Resources>
<ui:NavigationView.Resources>
<SolidColorBrush x:Key="NavigationViewContentBackground" Color="Transparent" />
<SolidColorBrush x:Key="NavigationViewContentGridBorderBrush" Color="Transparent" />
</ui:FANavigationView.Resources>
</ui:NavigationView.Resources>
<Grid x:Name="SettingsContentGrid"
ColumnDefinitions="*,Auto"
@@ -142,7 +142,7 @@
Text="{Binding CurrentPageTitle}" />
</Grid>
<ui:FAFrame x:Name="ContentFrame"
<ui:Frame x:Name="ContentFrame"
Grid.Row="1" />
</Grid>
@@ -180,6 +180,6 @@
</Grid>
</Border>
</Grid>
</ui:FANavigationView>
</ui:NavigationView>
</Grid>
</Window>

View File

@@ -163,7 +163,9 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
if (_useSystemChrome)
{
ExtendClientAreaToDecorationsHint = true;
WindowDecorations = WindowDecorations.Full;
ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.PreferSystemChrome;
ExtendClientAreaTitleBarHeightHint = -1;
SystemDecorations = SystemDecorations.Full;
if (WindowTitleBarHost is { })
{
@@ -172,8 +174,10 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
return;
}
WindowDecorations = WindowDecorations.BorderOnly;
SystemDecorations = SystemDecorations.BorderOnly;
ExtendClientAreaToDecorationsHint = true;
ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.NoChrome;
ExtendClientAreaTitleBarHeightHint = 48;
if (WindowTitleBarHost is { })
{
@@ -201,23 +205,27 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
{
if (previousCategory is not null && previousCategory != page.Category)
{
RootNavigationView.MenuItems.Add(new FANavigationViewItemSeparator());
RootNavigationView.MenuItems.Add(new NavigationViewItemSeparator());
}
RootNavigationView.MenuItems.Add(new FANavigationViewItem
RootNavigationView.MenuItems.Add(new NavigationViewItem
{
Content = page.Title,
Tag = page.PageId,
IconSource = CreateSettingsIconSource(MapIcon(page.IconKey))
IconSource = new FluentIcons.Avalonia.Fluent.SymbolIconSource
{
Symbol = MapIcon(page.IconKey),
IconVariant = FluentIcons.Common.IconVariant.Regular
}
});
previousCategory = page.Category;
}
}
private void OnNavigationSelectionChanged(object? sender, FANavigationViewSelectionChangedEventArgs e)
private void OnNavigationSelectionChanged(object? sender, NavigationViewSelectionChangedEventArgs e)
{
var selectedItem = e.SelectedItemContainer ?? e.SelectedItem as FANavigationViewItem;
var selectedItem = e.SelectedItemContainer ?? e.SelectedItem as NavigationViewItem;
NavigateTo(selectedItem?.Tag as string);
}
@@ -293,7 +301,7 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
return;
}
foreach (var item in RootNavigationView.MenuItems.OfType<FANavigationViewItem>())
foreach (var item in RootNavigationView.MenuItems.OfType<NavigationViewItem>())
{
if (string.Equals(item.Tag as string, pageId, StringComparison.OrdinalIgnoreCase))
{
@@ -366,17 +374,17 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
try
{
var dialog = new FAContentDialog
var dialog = new ContentDialog
{
Title = ViewModel.RestartDialogTitle,
Content = ViewModel.RestartMessage,
PrimaryButtonText = ViewModel.RestartDialogPrimaryText,
CloseButtonText = ViewModel.RestartDialogCloseText,
DefaultButton = FAContentDialogButton.Primary
DefaultButton = ContentDialogButton.Primary
};
var result = await dialog.ShowAsync(this);
if (result == FAContentDialogResult.Primary)
if (result == ContentDialogResult.Primary)
{
_hostApplicationLifecycle.TryRestart(new HostApplicationLifecycleRequest(
Source: "SettingsWindow",
@@ -478,7 +486,7 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
{
_ = TryApplyResponsiveLayout();
// Hide the drawer pane on narrow windows.
// 小窗口时隐藏抽屉面板
}
private void OnClosed(object? sender, EventArgs e)
@@ -529,9 +537,9 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
{
_ = sender;
if (e.Property == FANavigationView.IsPaneOpenProperty ||
e.Property == FANavigationView.OpenPaneLengthProperty ||
e.Property == FANavigationView.PaneDisplayModeProperty)
if (e.Property == NavigationView.IsPaneOpenProperty ||
e.Property == NavigationView.OpenPaneLengthProperty ||
e.Property == NavigationView.PaneDisplayModeProperty)
{
UpdatePaneToggleIcon();
RequestResponsiveLayoutRefresh();
@@ -728,41 +736,16 @@ public partial class SettingsWindow : Window, ISettingsPageHostContext
"GridDots" => Symbol.GridDots,
"PuzzlePiece" => Symbol.PuzzlePiece,
"ShoppingBag" => Symbol.ShoppingBag,
"Shield" => Symbol.ShieldLock,
"Shield" => Symbol.ShieldDismiss,
"Info" => Symbol.Info,
"ArrowSync" => Symbol.ArrowSync,
"Hourglass" => Symbol.Hourglass,
"Alert" => Symbol.Alert,
"Bell" => Symbol.AlertOn,
"Bell" => Symbol.Alert,
"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
};
}
}

View File

@@ -3,7 +3,7 @@
xmlns:fi="using:FluentIcons.Avalonia"
x:Class="LanMountainDesktop.Views.StudySessionReportWindow"
x:CompileBindings="False"
WindowDecorations="None"
SystemDecorations="None"
Background="Transparent"
ShowInTaskbar="False"
Topmost="True"
@@ -12,6 +12,7 @@
Height="600"
TransparencyLevelHint="Transparent"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
ExtendClientAreaTitleBarHeightHint="-1"
WindowStartupLocation="CenterOwner">

View File

@@ -1,10 +1,11 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="LanMountainDesktop.Views.TransparentOverlayWindow"
WindowDecorations="None"
SystemDecorations="None"
CanResize="False"
ShowInTaskbar="False"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
Background="Transparent"
Title="LanMountainDesktop Fused Desktop">
<!--

View File

@@ -695,7 +695,7 @@ public sealed class PluginRuntimeService : IDisposable
if (assemblyName.StartsWith("Avalonia", StringComparison.OrdinalIgnoreCase) ||
string.Equals(assemblyName, "FluentAvaloniaUI", StringComparison.OrdinalIgnoreCase) ||
string.Equals(assemblyName, "FluentIcons.Avalonia", StringComparison.OrdinalIgnoreCase) ||
string.Equals(assemblyName, "Material.Avalonia", StringComparison.OrdinalIgnoreCase) ||
string.Equals(assemblyName, "FluentIcons.Avalonia.Fluent", StringComparison.OrdinalIgnoreCase) ||
string.Equals(assemblyName, "Material.Icons.Avalonia", StringComparison.OrdinalIgnoreCase) ||
string.Equals(assemblyName, "MicroCom.Runtime", StringComparison.OrdinalIgnoreCase))
{

View File

@@ -96,9 +96,9 @@ dotnet new install LanMountainDesktop.PluginTemplate
dotnet new lmd-plugin -n MyPlugin
```
- **Plugin SDK**: `LanMountainDesktop.PluginSdk` (API 5.0.0)
- **Plugin SDK**: `LanMountainDesktop.PluginSdk` (API 4.0.1)
- **共享契约**: `LanMountainDesktop.Shared.Contracts`
- **迁移指南**: [PLUGIN_SDK_V5_MIGRATION.md](docs/PLUGIN_SDK_V5_MIGRATION.md)
- **迁移指南**: [PLUGIN_SDK_V4_MIGRATION.md](docs/PLUGIN_SDK_V4_MIGRATION.md)
## 项目结构

View File

@@ -1,29 +0,0 @@
using System;
using System.Linq;
using FluentAvalonia.UI.Controls;
class Test
{
static void Main()
{
var faSymbols = new System.Collections.Generic.HashSet<string>(Enum.GetNames(typeof(FASymbol)));
// 从错误信息中提取的图标名称
var usedIcons = new[]
{
"Info", "Color", "Apps", "Code", "Home", "Settings",
"WeatherMoon", "Search", "Location", "City", "Warning",
"ShieldDismiss", "Shield", "Announcements", "Package",
"StatusCircle", "Book", "BranchFork", "ArrowSync",
"GlobeArrowForward", "Options", "Store", "Layer",
"FolderOpen", "Clock", "Maximize"
};
Console.WriteLine("Checking icon availability in FASymbol:");
foreach (var icon in usedIcons.Distinct().OrderBy(i => i))
{
bool exists = faSymbols.Contains(icon);
Console.WriteLine($" {icon}: {(exists ? "OK" : "MISSING")}");
}
}
}

View File

@@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAvaloniaUI" />
<PackageReference Include="FluentIcons.Avalonia" />
</ItemGroup>
</Project>

View File

@@ -63,11 +63,11 @@ MyAwesomePlugin/
### 插件 SDK 版本
当前 SDK 版本: **5.0.0**
当前 SDK 版本: **4.0.1**
```xml
<PackageReference Include="LanMountainDesktop.PluginSdk" Version="5.0.0" />
<PackageReference Include="LanMountainDesktop.Shared.Contracts" Version="5.0.0" />
<PackageReference Include="LanMountainDesktop.PluginSdk" Version="4.0.1" />
<PackageReference Include="LanMountainDesktop.Shared.Contracts" Version="4.0.1" />
```
### 插件清单 (plugin.json)
@@ -175,9 +175,9 @@ public class Plugin : IPlugin
</PropertyGroup>
<ItemGroup>
<PackageReference Include="LanMountainDesktop.PluginSdk" Version="5.0.0" />
<PackageReference Include="LanMountainDesktop.Shared.Contracts" Version="5.0.0" />
<PackageReference Include="Avalonia" Version="12.0.1" />
<PackageReference Include="LanMountainDesktop.PluginSdk" Version="4.0.1" />
<PackageReference Include="LanMountainDesktop.Shared.Contracts" Version="4.0.1" />
<PackageReference Include="Avalonia" Version="11.3.12" />
</ItemGroup>
<!-- 复制 plugin.json 到输出目录 -->
@@ -680,7 +680,7 @@ if (!url.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
## 相关文档
- [Plugin SDK v5 迁移指南](PLUGIN_SDK_V5_MIGRATION.md)
- [Plugin SDK v4 迁移指南](PLUGIN_SDK_V4_MIGRATION.md)
- [组件开发指南](COMPONENT_DEVELOPMENT.md)
- [API 参考](API_REFERENCE.md)
- [架构文档](ARCHITECTURE.md)

Some files were not shown because too many files have changed in this diff Show More