mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
0.1.8
This commit is contained in:
6
LanMontainDesktop/ComponentSystem/BuiltInComponentIds.cs
Normal file
6
LanMontainDesktop/ComponentSystem/BuiltInComponentIds.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace LanMontainDesktop.ComponentSystem;
|
||||
|
||||
public static class BuiltInComponentIds
|
||||
{
|
||||
public const string Clock = "Clock";
|
||||
}
|
||||
28
LanMontainDesktop/ComponentSystem/ComponentPlacementRules.cs
Normal file
28
LanMontainDesktop/ComponentSystem/ComponentPlacementRules.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
|
||||
namespace LanMontainDesktop.ComponentSystem;
|
||||
|
||||
public static class ComponentPlacementRules
|
||||
{
|
||||
public static (int WidthCells, int HeightCells) EnsureMinimumSize(
|
||||
DesktopComponentDefinition definition,
|
||||
int requestedWidthCells,
|
||||
int requestedHeightCells)
|
||||
{
|
||||
var width = Math.Max(definition.MinWidthCells, requestedWidthCells);
|
||||
var height = Math.Max(definition.MinHeightCells, requestedHeightCells);
|
||||
return (Math.Max(1, width), Math.Max(1, height));
|
||||
}
|
||||
|
||||
public static bool CanPlaceInStatusBar(DesktopComponentDefinition definition, int requestedHeightCells)
|
||||
{
|
||||
return definition.AllowStatusBarPlacement && requestedHeightCells == 1;
|
||||
}
|
||||
|
||||
public static (int Column, int Row) ClampToGrid(int requestedColumn, int requestedRow, int maxColumns, int maxRows)
|
||||
{
|
||||
var clampedColumn = Math.Clamp(requestedColumn, 0, Math.Max(0, maxColumns - 1));
|
||||
var clampedRow = Math.Clamp(requestedRow, 0, Math.Max(0, maxRows - 1));
|
||||
return (clampedColumn, clampedRow);
|
||||
}
|
||||
}
|
||||
74
LanMontainDesktop/ComponentSystem/ComponentRegistry.cs
Normal file
74
LanMontainDesktop/ComponentSystem/ComponentRegistry.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using LanMontainDesktop.ComponentSystem.Extensions;
|
||||
|
||||
namespace LanMontainDesktop.ComponentSystem;
|
||||
|
||||
public sealed class ComponentRegistry
|
||||
{
|
||||
private readonly Dictionary<string, DesktopComponentDefinition> _definitions;
|
||||
|
||||
public ComponentRegistry(IEnumerable<DesktopComponentDefinition> definitions)
|
||||
{
|
||||
_definitions = definitions
|
||||
.Where(d => !string.IsNullOrWhiteSpace(d.Id))
|
||||
.GroupBy(d => d.Id, StringComparer.OrdinalIgnoreCase)
|
||||
.ToDictionary(g => g.Key, g => g.Last(), StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static ComponentRegistry CreateDefault()
|
||||
{
|
||||
var builtIn = new[]
|
||||
{
|
||||
new DesktopComponentDefinition(
|
||||
BuiltInComponentIds.Clock,
|
||||
"Clock",
|
||||
"Clock",
|
||||
"Status",
|
||||
MinWidthCells: 1,
|
||||
MinHeightCells: 1,
|
||||
AllowStatusBarPlacement: true,
|
||||
AllowDesktopPlacement: true)
|
||||
};
|
||||
|
||||
return new ComponentRegistry(builtIn);
|
||||
}
|
||||
|
||||
public ComponentRegistry RegisterExtensions(IEnumerable<IComponentExtensionProvider> providers)
|
||||
{
|
||||
var merged = _definitions.Values.ToList();
|
||||
foreach (var provider in providers)
|
||||
{
|
||||
var externalDefinitions = provider.GetComponents();
|
||||
if (externalDefinitions is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
merged.AddRange(externalDefinitions);
|
||||
}
|
||||
|
||||
return new ComponentRegistry(merged);
|
||||
}
|
||||
|
||||
public bool TryGetDefinition(string componentId, out DesktopComponentDefinition definition)
|
||||
{
|
||||
return _definitions.TryGetValue(componentId, out definition!);
|
||||
}
|
||||
|
||||
public bool IsKnownComponent(string componentId)
|
||||
{
|
||||
return _definitions.ContainsKey(componentId);
|
||||
}
|
||||
|
||||
public bool AllowsStatusBarPlacement(string componentId)
|
||||
{
|
||||
return _definitions.TryGetValue(componentId, out var definition) && definition.AllowStatusBarPlacement;
|
||||
}
|
||||
|
||||
public IReadOnlyList<DesktopComponentDefinition> GetAll()
|
||||
{
|
||||
return _definitions.Values.OrderBy(d => d.Category).ThenBy(d => d.DisplayName).ToList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace LanMontainDesktop.ComponentSystem;
|
||||
|
||||
public sealed record DesktopComponentDefinition(
|
||||
string Id,
|
||||
string DisplayName,
|
||||
string IconKey,
|
||||
string Category,
|
||||
int MinWidthCells,
|
||||
int MinHeightCells,
|
||||
bool AllowStatusBarPlacement,
|
||||
bool AllowDesktopPlacement);
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LanMontainDesktop.ComponentSystem.Extensions;
|
||||
|
||||
public interface IComponentExtensionProvider
|
||||
{
|
||||
IReadOnlyList<DesktopComponentDefinition> GetComponents();
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace LanMontainDesktop.ComponentSystem.Extensions;
|
||||
|
||||
public sealed class JsonComponentExtensionProvider : IComponentExtensionProvider
|
||||
{
|
||||
private readonly IReadOnlyList<DesktopComponentDefinition> _definitions;
|
||||
|
||||
private JsonComponentExtensionProvider(IReadOnlyList<DesktopComponentDefinition> definitions)
|
||||
{
|
||||
_definitions = definitions;
|
||||
}
|
||||
|
||||
public IReadOnlyList<DesktopComponentDefinition> GetComponents()
|
||||
{
|
||||
return _definitions;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<IComponentExtensionProvider> LoadProvidersFromDirectory(string directoryPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(directoryPath) || !Directory.Exists(directoryPath))
|
||||
{
|
||||
return Array.Empty<IComponentExtensionProvider>();
|
||||
}
|
||||
|
||||
var providers = new List<IComponentExtensionProvider>();
|
||||
foreach (var filePath in Directory.GetFiles(directoryPath, "*.json", SearchOption.TopDirectoryOnly))
|
||||
{
|
||||
var provider = TryLoadFromFile(filePath);
|
||||
if (provider is not null)
|
||||
{
|
||||
providers.Add(provider);
|
||||
}
|
||||
}
|
||||
|
||||
return providers;
|
||||
}
|
||||
|
||||
private static JsonComponentExtensionProvider? TryLoadFromFile(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(filePath);
|
||||
var entries = JsonSerializer.Deserialize<List<ComponentExtensionEntry>>(json);
|
||||
if (entries is null || entries.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var definitions = new List<DesktopComponentDefinition>();
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(entry.Id) ||
|
||||
string.IsNullOrWhiteSpace(entry.DisplayName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
definitions.Add(new DesktopComponentDefinition(
|
||||
entry.Id.Trim(),
|
||||
entry.DisplayName.Trim(),
|
||||
string.IsNullOrWhiteSpace(entry.IconKey) ? "PuzzlePiece" : entry.IconKey,
|
||||
string.IsNullOrWhiteSpace(entry.Category) ? "Extensions" : entry.Category,
|
||||
MinWidthCells: Math.Max(1, entry.MinWidthCells),
|
||||
MinHeightCells: Math.Max(1, entry.MinHeightCells),
|
||||
AllowStatusBarPlacement: entry.AllowStatusBarPlacement,
|
||||
AllowDesktopPlacement: entry.AllowDesktopPlacement));
|
||||
}
|
||||
|
||||
return definitions.Count == 0 ? null : new JsonComponentExtensionProvider(definitions);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ComponentExtensionEntry
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
|
||||
public string IconKey { get; set; } = string.Empty;
|
||||
|
||||
public string Category { get; set; } = "Extensions";
|
||||
|
||||
public int MinWidthCells { get; set; } = 1;
|
||||
|
||||
public int MinHeightCells { get; set; } = 1;
|
||||
|
||||
public bool AllowStatusBarPlacement { get; set; }
|
||||
|
||||
public bool AllowDesktopPlacement { get; set; } = true;
|
||||
}
|
||||
}
|
||||
77
LanMontainDesktop/ComponentSystem/README.md
Normal file
77
LanMontainDesktop/ComponentSystem/README.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# 组件系统模块(Component System Module)
|
||||
|
||||
本目录提供组件系统的模块化基础,用于支持内置组件管理与第三方扩展接入。
|
||||
This directory provides the modular foundation for built-in component management and third-party extension integration.
|
||||
|
||||
## 核心文件职责(Core Files)
|
||||
- `BuiltInComponentIds.cs`:内置组件 ID 常量(例如 `Clock`)。
|
||||
Built-in component ID constants (for example `Clock`).
|
||||
- `DesktopComponentDefinition.cs`:组件元数据定义(名称、类别、最小尺寸、可放置区域等)。
|
||||
Component metadata model (name, category, minimum size, placement permissions).
|
||||
- `ComponentPlacementRules.cs`:组件放置规则(最小尺寸、状态栏高度限制、网格边界约束)。
|
||||
Placement rules (minimum size, status-bar height rule, grid clamping).
|
||||
- `ComponentRegistry.cs`:组件注册中心,负责内置组件与扩展组件合并。
|
||||
Registry that merges built-in and extension components.
|
||||
- `Extensions/IComponentExtensionProvider.cs`:扩展提供者接口契约。
|
||||
Extension provider interface contract.
|
||||
- `Extensions/JsonComponentExtensionProvider.cs`:基于 JSON 的扩展加载器。
|
||||
JSON-based extension loader.
|
||||
|
||||
## 第三方扩展契约(Extension Contract)
|
||||
- 第三方可通过实现 `IComponentExtensionProvider` 提供组件定义。
|
||||
Third parties can provide component definitions via `IComponentExtensionProvider`.
|
||||
- 当前内置了 JSON 提供者,运行时扫描目录:
|
||||
Built-in JSON provider scans at runtime:
|
||||
- `Extensions/Components/*.json`(相对应用输出目录)
|
||||
`Extensions/Components/*.json` (relative to app output directory)
|
||||
|
||||
## 加载流程(Load Flow)
|
||||
1. `ComponentRegistry.CreateDefault()` 先注册内置组件。
|
||||
Register built-in components first via `ComponentRegistry.CreateDefault()`.
|
||||
2. 调用 `.RegisterExtensions(...)` 合并扩展组件。
|
||||
Merge extension components via `.RegisterExtensions(...)`.
|
||||
3. 主窗口通过注册中心校验组件合法性与放置权限。
|
||||
Main window validates component identity and placement permission through the registry.
|
||||
|
||||
## JSON 清单格式(Manifest Schema)
|
||||
JSON 文件为数组,每一项代表一个组件定义。
|
||||
The JSON file is an array, where each item represents one component definition.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "Weather",
|
||||
"displayName": "Weather",
|
||||
"iconKey": "WeatherSunny",
|
||||
"category": "Status",
|
||||
"minWidthCells": 1,
|
||||
"minHeightCells": 1,
|
||||
"allowStatusBarPlacement": true,
|
||||
"allowDesktopPlacement": true
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
字段说明(Field notes):
|
||||
- `id`:组件唯一 ID(建议英文、稳定不变)。
|
||||
Unique component ID (prefer stable English key).
|
||||
- `displayName`:显示名。
|
||||
Display name.
|
||||
- `iconKey`:图标键(由上层 UI 解释)。
|
||||
Icon key resolved by UI layer.
|
||||
- `category`:组件分类。
|
||||
Component category.
|
||||
- `minWidthCells` / `minHeightCells`:最小占格,必须满足 `>= 1`。
|
||||
Minimum cell size, must satisfy `>= 1`.
|
||||
- `allowStatusBarPlacement`:是否允许放到顶部状态栏。
|
||||
Whether placing in top status bar is allowed.
|
||||
- `allowDesktopPlacement`:是否允许放到桌面区域。
|
||||
Whether placing in desktop area is allowed.
|
||||
|
||||
## 放置规则摘要(Placement Rules Summary)
|
||||
- 最小尺寸约束:`minWidthCells >= 1` 且 `minHeightCells >= 1`。
|
||||
Minimum size constraint: `minWidthCells >= 1` and `minHeightCells >= 1`.
|
||||
- 状态栏约束:状态栏组件高度必须为 `1` 格。
|
||||
Status bar constraint: component height must be exactly `1` cell.
|
||||
- 越界约束:所有组件坐标会被网格边界钳制(clamp)。
|
||||
Out-of-bounds constraint: component coordinates are clamped to grid bounds.
|
||||
16
LanMontainDesktop/Extensions/Components/README.txt
Normal file
16
LanMontainDesktop/Extensions/Components/README.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
在此目录放置第三方组件清单文件(*.json)。
|
||||
Place third-party component manifest files (*.json) in this directory.
|
||||
|
||||
运行时加载路径:
|
||||
Runtime load path:
|
||||
Extensions/Components/*.json
|
||||
|
||||
最小示例文件建议:
|
||||
Minimal example file suggestion:
|
||||
Extensions/Components/weather.json
|
||||
|
||||
命名建议:
|
||||
Naming suggestions:
|
||||
- weather.json
|
||||
- stock.json
|
||||
- calendar.json
|
||||
@@ -12,6 +12,7 @@
|
||||
<Folder Include="Models\" />
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
<None Include="Localization\*.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Include="Extensions\Components\*.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
347
LanMontainDesktop/Views/MainWindow.ComponentSystem.cs
Normal file
347
LanMontainDesktop/Views/MainWindow.ComponentSystem.cs
Normal file
@@ -0,0 +1,347 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Threading;
|
||||
using LanMontainDesktop.ComponentSystem;
|
||||
using LanMontainDesktop.Models;
|
||||
|
||||
namespace LanMontainDesktop.Views;
|
||||
|
||||
public partial class MainWindow
|
||||
{
|
||||
private void OnOpenComponentLibraryClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_isComponentLibraryOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_reopenSettingsAfterComponentLibraryClose = _isSettingsOpen;
|
||||
if (_isSettingsOpen)
|
||||
{
|
||||
CloseSettingsPage(immediate: true);
|
||||
}
|
||||
|
||||
OpenComponentLibraryWindow();
|
||||
}
|
||||
|
||||
private void OnCloseComponentLibraryClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
CloseComponentLibraryWindow(reopenSettings: true);
|
||||
}
|
||||
|
||||
private void OnStatusBarClockChecked(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_suppressStatusBarToggleEvents)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_topStatusComponentIds.Add(BuiltInComponentIds.Clock);
|
||||
ApplyTopStatusComponentVisibility();
|
||||
UpdateWallpaperPreviewLayout();
|
||||
PersistSettings();
|
||||
}
|
||||
|
||||
private void OnStatusBarClockUnchecked(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_suppressStatusBarToggleEvents)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_topStatusComponentIds.Remove(BuiltInComponentIds.Clock);
|
||||
ApplyTopStatusComponentVisibility();
|
||||
UpdateWallpaperPreviewLayout();
|
||||
PersistSettings();
|
||||
}
|
||||
|
||||
private void ApplyTaskbarSettings(AppSettingsSnapshot snapshot)
|
||||
{
|
||||
_topStatusComponentIds.Clear();
|
||||
if (snapshot.TopStatusComponentIds is not null)
|
||||
{
|
||||
foreach (var componentId in snapshot.TopStatusComponentIds)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(componentId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var normalizedId = componentId.Trim();
|
||||
if (_componentRegistry.IsKnownComponent(normalizedId) &&
|
||||
_componentRegistry.AllowsStatusBarPlacement(normalizedId))
|
||||
{
|
||||
_topStatusComponentIds.Add(normalizedId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_pinnedTaskbarActions.Clear();
|
||||
if (snapshot.PinnedTaskbarActions is not null)
|
||||
{
|
||||
foreach (var actionText in snapshot.PinnedTaskbarActions)
|
||||
{
|
||||
if (Enum.TryParse<TaskbarActionId>(actionText, ignoreCase: true, out var action))
|
||||
{
|
||||
_pinnedTaskbarActions.Add(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_pinnedTaskbarActions.Count == 0)
|
||||
{
|
||||
foreach (var action in DefaultPinnedTaskbarActions)
|
||||
{
|
||||
_pinnedTaskbarActions.Add(action);
|
||||
}
|
||||
}
|
||||
|
||||
_enableDynamicTaskbarActions = snapshot.EnableDynamicTaskbarActions;
|
||||
_taskbarLayoutMode = string.IsNullOrWhiteSpace(snapshot.TaskbarLayoutMode)
|
||||
? TaskbarLayoutBottomFullRowMacStyle
|
||||
: snapshot.TaskbarLayoutMode;
|
||||
}
|
||||
|
||||
private void ApplyTopStatusComponentVisibility()
|
||||
{
|
||||
var showClock = _topStatusComponentIds.Contains(BuiltInComponentIds.Clock);
|
||||
|
||||
if (ClockWidget is not null)
|
||||
{
|
||||
ClockWidget.IsVisible = showClock;
|
||||
}
|
||||
|
||||
if (WallpaperPreviewClockContainer is not null)
|
||||
{
|
||||
WallpaperPreviewClockContainer.IsVisible = showClock;
|
||||
}
|
||||
|
||||
if (WallpaperPreviewClockTextBlock is not null && showClock)
|
||||
{
|
||||
WallpaperPreviewClockTextBlock.Text = DateTime.Now.ToString("HH:mm");
|
||||
}
|
||||
}
|
||||
|
||||
private TaskbarContext GetCurrentTaskbarContext()
|
||||
{
|
||||
if (!_isSettingsOpen)
|
||||
{
|
||||
return TaskbarContext.Desktop;
|
||||
}
|
||||
|
||||
return SettingsNavListBox?.SelectedIndex switch
|
||||
{
|
||||
0 => TaskbarContext.SettingsWallpaper,
|
||||
1 => TaskbarContext.SettingsGrid,
|
||||
2 => TaskbarContext.SettingsColor,
|
||||
3 => TaskbarContext.SettingsStatusBar,
|
||||
4 => TaskbarContext.SettingsRegion,
|
||||
_ => TaskbarContext.Desktop
|
||||
};
|
||||
}
|
||||
|
||||
private void ApplyTaskbarActionVisibility(TaskbarContext context)
|
||||
{
|
||||
if (BackToWindowsButton is null ||
|
||||
OpenComponentLibraryButton is null ||
|
||||
OpenSettingsButton is null ||
|
||||
WallpaperPreviewBackButtonVisual is null ||
|
||||
WallpaperPreviewComponentLibraryVisual is null ||
|
||||
WallpaperPreviewSettingsButtonIcon is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var showMinimize = _pinnedTaskbarActions.Contains(TaskbarActionId.MinimizeToWindows);
|
||||
var showSettings = _pinnedTaskbarActions.Contains(TaskbarActionId.OpenSettings);
|
||||
var showComponentLibrary = _isSettingsOpen || _isComponentLibraryOpen;
|
||||
|
||||
BackToWindowsButton.IsVisible = showMinimize;
|
||||
OpenComponentLibraryButton.IsVisible = showComponentLibrary;
|
||||
OpenSettingsButton.IsVisible = showSettings;
|
||||
WallpaperPreviewBackButtonVisual.IsVisible = showMinimize;
|
||||
WallpaperPreviewComponentLibraryVisual.IsVisible = showComponentLibrary;
|
||||
WallpaperPreviewSettingsButtonIcon.IsVisible = showSettings;
|
||||
|
||||
if (TaskbarFixedActionsHost is not null)
|
||||
{
|
||||
TaskbarFixedActionsHost.IsVisible = showMinimize;
|
||||
}
|
||||
|
||||
if (TaskbarSettingsActionHost is not null)
|
||||
{
|
||||
TaskbarSettingsActionHost.IsVisible = showSettings || showComponentLibrary;
|
||||
}
|
||||
|
||||
if (WallpaperPreviewTaskbarFixedActionsHost is not null)
|
||||
{
|
||||
WallpaperPreviewTaskbarFixedActionsHost.IsVisible = showMinimize;
|
||||
}
|
||||
|
||||
if (WallpaperPreviewTaskbarSettingsActionHost is not null)
|
||||
{
|
||||
WallpaperPreviewTaskbarSettingsActionHost.IsVisible = showSettings || showComponentLibrary;
|
||||
}
|
||||
|
||||
var dynamicActions = ResolveDynamicTaskbarActions(context);
|
||||
var hasDynamicActions = dynamicActions.Count > 0;
|
||||
BuildDynamicTaskbarVisuals(dynamicActions);
|
||||
|
||||
if (TaskbarDynamicActionsHost is not null)
|
||||
{
|
||||
TaskbarDynamicActionsHost.IsVisible = hasDynamicActions;
|
||||
}
|
||||
|
||||
if (WallpaperPreviewTaskbarDynamicActionsHost is not null)
|
||||
{
|
||||
WallpaperPreviewTaskbarDynamicActionsHost.IsVisible = hasDynamicActions;
|
||||
}
|
||||
|
||||
UpdateOpenSettingsActionVisualState();
|
||||
}
|
||||
|
||||
private void UpdateOpenSettingsActionVisualState()
|
||||
{
|
||||
if (OpenSettingsButtonTextBlock is null || OpenSettingsButton is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var showBackToDesktop = _isSettingsOpen;
|
||||
OpenSettingsButtonTextBlock.IsVisible = showBackToDesktop;
|
||||
OpenSettingsButtonTextBlock.Text = L("settings.back_to_desktop", "Back to Desktop");
|
||||
ToolTip.SetTip(
|
||||
OpenSettingsButton,
|
||||
showBackToDesktop
|
||||
? L("settings.back_to_desktop", "Back to Desktop")
|
||||
: L("tooltip.open_settings", "Settings"));
|
||||
|
||||
var effectiveCellSize = _currentDesktopCellSize > 0
|
||||
? _currentDesktopCellSize
|
||||
: Math.Max(32, Math.Min(Bounds.Width, Bounds.Height) / Math.Max(1, _targetShortSideCells));
|
||||
ApplyWidgetSizing(effectiveCellSize);
|
||||
}
|
||||
|
||||
private void OpenComponentLibraryWindow()
|
||||
{
|
||||
if (ComponentLibraryWindow is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isComponentLibraryOpen = true;
|
||||
ComponentLibraryWindow.IsVisible = true;
|
||||
ComponentLibraryWindow.Opacity = 0;
|
||||
ApplyTaskbarActionVisibility(GetCurrentTaskbarContext());
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (!_isComponentLibraryOpen || ComponentLibraryWindow is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ComponentLibraryWindow.Opacity = 1;
|
||||
}, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
private void CloseComponentLibraryWindow(bool reopenSettings)
|
||||
{
|
||||
if (!_isComponentLibraryOpen || ComponentLibraryWindow is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isComponentLibraryOpen = false;
|
||||
ComponentLibraryWindow.Opacity = 0;
|
||||
ApplyTaskbarActionVisibility(GetCurrentTaskbarContext());
|
||||
|
||||
DispatcherTimer.RunOnce(() =>
|
||||
{
|
||||
if (_isComponentLibraryOpen || ComponentLibraryWindow is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ComponentLibraryWindow.IsVisible = false;
|
||||
|
||||
var shouldReopenSettings = reopenSettings && _reopenSettingsAfterComponentLibraryClose;
|
||||
_reopenSettingsAfterComponentLibraryClose = false;
|
||||
if (shouldReopenSettings)
|
||||
{
|
||||
OpenSettingsPage();
|
||||
}
|
||||
}, TimeSpan.FromMilliseconds(200));
|
||||
}
|
||||
|
||||
private IReadOnlyList<TaskbarActionItem> ResolveDynamicTaskbarActions(TaskbarContext context)
|
||||
{
|
||||
if (!_enableDynamicTaskbarActions)
|
||||
{
|
||||
return Array.Empty<TaskbarActionItem>();
|
||||
}
|
||||
|
||||
// Reserved for page-specific actions. Disabled by default in this phase.
|
||||
_ = context;
|
||||
return Array.Empty<TaskbarActionItem>();
|
||||
}
|
||||
|
||||
private void BuildDynamicTaskbarVisuals(IReadOnlyList<TaskbarActionItem> actions)
|
||||
{
|
||||
if (TaskbarDynamicActionsPanel is not null)
|
||||
{
|
||||
TaskbarDynamicActionsPanel.Children.Clear();
|
||||
}
|
||||
|
||||
if (WallpaperPreviewTaskbarDynamicActionsPanel is not null)
|
||||
{
|
||||
WallpaperPreviewTaskbarDynamicActionsPanel.Children.Clear();
|
||||
}
|
||||
|
||||
if (actions.Count == 0 ||
|
||||
TaskbarDynamicActionsPanel is null ||
|
||||
WallpaperPreviewTaskbarDynamicActionsPanel is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var action in actions)
|
||||
{
|
||||
if (!action.IsVisible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var button = new Button
|
||||
{
|
||||
Content = action.Title,
|
||||
Background = Brushes.Transparent,
|
||||
BorderThickness = new Thickness(0),
|
||||
Padding = new Thickness(12, 6),
|
||||
Foreground = Foreground
|
||||
};
|
||||
|
||||
TaskbarDynamicActionsPanel.Children.Add(button);
|
||||
|
||||
var previewText = new TextBlock
|
||||
{
|
||||
Text = action.Title,
|
||||
Foreground = Foreground,
|
||||
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
|
||||
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
|
||||
};
|
||||
var previewBorder = new Border
|
||||
{
|
||||
Background = Brushes.Transparent,
|
||||
BorderThickness = new Thickness(0),
|
||||
Child = previewText
|
||||
};
|
||||
WallpaperPreviewTaskbarDynamicActionsPanel.Children.Add(previewBorder);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,27 +38,6 @@ public partial class MainWindow
|
||||
OpenSettingsPage();
|
||||
}
|
||||
|
||||
private void OnOpenComponentLibraryClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_isComponentLibraryOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_reopenSettingsAfterComponentLibraryClose = _isSettingsOpen;
|
||||
if (_isSettingsOpen)
|
||||
{
|
||||
CloseSettingsPage(immediate: true);
|
||||
}
|
||||
|
||||
OpenComponentLibraryWindow();
|
||||
}
|
||||
|
||||
private void OnCloseComponentLibraryClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
CloseComponentLibraryWindow(reopenSettings: true);
|
||||
}
|
||||
|
||||
private void OnCloseSettingsClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
CloseSettingsPage();
|
||||
@@ -92,32 +71,6 @@ public partial class MainWindow
|
||||
ApplyTaskbarActionVisibility(GetCurrentTaskbarContext());
|
||||
}
|
||||
|
||||
private void OnStatusBarClockChecked(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_suppressStatusBarToggleEvents)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_topStatusComponentIds.Add(ClockStatusComponentId);
|
||||
ApplyTopStatusComponentVisibility();
|
||||
UpdateWallpaperPreviewLayout();
|
||||
PersistSettings();
|
||||
}
|
||||
|
||||
private void OnStatusBarClockUnchecked(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_suppressStatusBarToggleEvents)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_topStatusComponentIds.Remove(ClockStatusComponentId);
|
||||
ApplyTopStatusComponentVisibility();
|
||||
UpdateWallpaperPreviewLayout();
|
||||
PersistSettings();
|
||||
}
|
||||
|
||||
private void OnNightModeChecked(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_suppressThemeToggleEvents)
|
||||
@@ -646,284 +599,6 @@ public partial class MainWindow
|
||||
_previewVideoWallpaperMedia = null;
|
||||
}
|
||||
|
||||
private void ApplyTaskbarSettings(AppSettingsSnapshot snapshot)
|
||||
{
|
||||
_topStatusComponentIds.Clear();
|
||||
if (snapshot.TopStatusComponentIds is not null)
|
||||
{
|
||||
foreach (var componentId in snapshot.TopStatusComponentIds)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(componentId))
|
||||
{
|
||||
_topStatusComponentIds.Add(componentId.Trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_pinnedTaskbarActions.Clear();
|
||||
if (snapshot.PinnedTaskbarActions is not null)
|
||||
{
|
||||
foreach (var actionText in snapshot.PinnedTaskbarActions)
|
||||
{
|
||||
if (Enum.TryParse<TaskbarActionId>(actionText, ignoreCase: true, out var action))
|
||||
{
|
||||
_pinnedTaskbarActions.Add(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_pinnedTaskbarActions.Count == 0)
|
||||
{
|
||||
foreach (var action in DefaultPinnedTaskbarActions)
|
||||
{
|
||||
_pinnedTaskbarActions.Add(action);
|
||||
}
|
||||
}
|
||||
|
||||
_enableDynamicTaskbarActions = snapshot.EnableDynamicTaskbarActions;
|
||||
_taskbarLayoutMode = string.IsNullOrWhiteSpace(snapshot.TaskbarLayoutMode)
|
||||
? TaskbarLayoutBottomFullRowMacStyle
|
||||
: snapshot.TaskbarLayoutMode;
|
||||
}
|
||||
|
||||
private void ApplyTopStatusComponentVisibility()
|
||||
{
|
||||
var showClock = _topStatusComponentIds.Contains(ClockStatusComponentId);
|
||||
|
||||
if (ClockWidget is not null)
|
||||
{
|
||||
ClockWidget.IsVisible = showClock;
|
||||
}
|
||||
|
||||
if (WallpaperPreviewClockContainer is not null)
|
||||
{
|
||||
WallpaperPreviewClockContainer.IsVisible = showClock;
|
||||
}
|
||||
|
||||
if (WallpaperPreviewClockTextBlock is not null && showClock)
|
||||
{
|
||||
WallpaperPreviewClockTextBlock.Text = DateTime.Now.ToString("HH:mm");
|
||||
}
|
||||
}
|
||||
|
||||
private TaskbarContext GetCurrentTaskbarContext()
|
||||
{
|
||||
if (!_isSettingsOpen)
|
||||
{
|
||||
return TaskbarContext.Desktop;
|
||||
}
|
||||
|
||||
return SettingsNavListBox?.SelectedIndex switch
|
||||
{
|
||||
0 => TaskbarContext.SettingsWallpaper,
|
||||
1 => TaskbarContext.SettingsGrid,
|
||||
2 => TaskbarContext.SettingsColor,
|
||||
3 => TaskbarContext.SettingsStatusBar,
|
||||
4 => TaskbarContext.SettingsRegion,
|
||||
_ => TaskbarContext.Desktop
|
||||
};
|
||||
}
|
||||
|
||||
private void ApplyTaskbarActionVisibility(TaskbarContext context)
|
||||
{
|
||||
if (BackToWindowsButton is null ||
|
||||
OpenComponentLibraryButton is null ||
|
||||
OpenSettingsButton is null ||
|
||||
WallpaperPreviewBackButtonVisual is null ||
|
||||
WallpaperPreviewComponentLibraryVisual is null ||
|
||||
WallpaperPreviewSettingsButtonIcon is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var showMinimize = _pinnedTaskbarActions.Contains(TaskbarActionId.MinimizeToWindows);
|
||||
var showSettings = _pinnedTaskbarActions.Contains(TaskbarActionId.OpenSettings);
|
||||
var showComponentLibrary = _isSettingsOpen || _isComponentLibraryOpen;
|
||||
|
||||
BackToWindowsButton.IsVisible = showMinimize;
|
||||
OpenComponentLibraryButton.IsVisible = showComponentLibrary;
|
||||
OpenSettingsButton.IsVisible = showSettings;
|
||||
WallpaperPreviewBackButtonVisual.IsVisible = showMinimize;
|
||||
WallpaperPreviewComponentLibraryVisual.IsVisible = showComponentLibrary;
|
||||
WallpaperPreviewSettingsButtonIcon.IsVisible = showSettings;
|
||||
|
||||
if (TaskbarFixedActionsHost is not null)
|
||||
{
|
||||
TaskbarFixedActionsHost.IsVisible = showMinimize;
|
||||
}
|
||||
|
||||
if (TaskbarSettingsActionHost is not null)
|
||||
{
|
||||
TaskbarSettingsActionHost.IsVisible = showSettings || showComponentLibrary;
|
||||
}
|
||||
|
||||
if (WallpaperPreviewTaskbarFixedActionsHost is not null)
|
||||
{
|
||||
WallpaperPreviewTaskbarFixedActionsHost.IsVisible = showMinimize;
|
||||
}
|
||||
|
||||
if (WallpaperPreviewTaskbarSettingsActionHost is not null)
|
||||
{
|
||||
WallpaperPreviewTaskbarSettingsActionHost.IsVisible = showSettings || showComponentLibrary;
|
||||
}
|
||||
|
||||
var dynamicActions = ResolveDynamicTaskbarActions(context);
|
||||
var hasDynamicActions = dynamicActions.Count > 0;
|
||||
BuildDynamicTaskbarVisuals(dynamicActions);
|
||||
|
||||
if (TaskbarDynamicActionsHost is not null)
|
||||
{
|
||||
TaskbarDynamicActionsHost.IsVisible = hasDynamicActions;
|
||||
}
|
||||
|
||||
if (WallpaperPreviewTaskbarDynamicActionsHost is not null)
|
||||
{
|
||||
WallpaperPreviewTaskbarDynamicActionsHost.IsVisible = hasDynamicActions;
|
||||
}
|
||||
|
||||
UpdateOpenSettingsActionVisualState();
|
||||
}
|
||||
|
||||
private void UpdateOpenSettingsActionVisualState()
|
||||
{
|
||||
if (OpenSettingsButtonTextBlock is null || OpenSettingsButton is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var showBackToDesktop = _isSettingsOpen;
|
||||
OpenSettingsButtonTextBlock.IsVisible = showBackToDesktop;
|
||||
OpenSettingsButtonTextBlock.Text = L("settings.back_to_desktop", "Back to Desktop");
|
||||
ToolTip.SetTip(
|
||||
OpenSettingsButton,
|
||||
showBackToDesktop
|
||||
? L("settings.back_to_desktop", "Back to Desktop")
|
||||
: L("tooltip.open_settings", "Settings"));
|
||||
|
||||
var effectiveCellSize = _currentDesktopCellSize > 0
|
||||
? _currentDesktopCellSize
|
||||
: Math.Max(32, Math.Min(Bounds.Width, Bounds.Height) / Math.Max(1, _targetShortSideCells));
|
||||
ApplyWidgetSizing(effectiveCellSize);
|
||||
}
|
||||
|
||||
private void OpenComponentLibraryWindow()
|
||||
{
|
||||
if (ComponentLibraryWindow is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isComponentLibraryOpen = true;
|
||||
ComponentLibraryWindow.IsVisible = true;
|
||||
ComponentLibraryWindow.Opacity = 0;
|
||||
ApplyTaskbarActionVisibility(GetCurrentTaskbarContext());
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (!_isComponentLibraryOpen || ComponentLibraryWindow is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ComponentLibraryWindow.Opacity = 1;
|
||||
}, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
private void CloseComponentLibraryWindow(bool reopenSettings)
|
||||
{
|
||||
if (!_isComponentLibraryOpen || ComponentLibraryWindow is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isComponentLibraryOpen = false;
|
||||
ComponentLibraryWindow.Opacity = 0;
|
||||
ApplyTaskbarActionVisibility(GetCurrentTaskbarContext());
|
||||
|
||||
DispatcherTimer.RunOnce(() =>
|
||||
{
|
||||
if (_isComponentLibraryOpen || ComponentLibraryWindow is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ComponentLibraryWindow.IsVisible = false;
|
||||
|
||||
var shouldReopenSettings = reopenSettings && _reopenSettingsAfterComponentLibraryClose;
|
||||
_reopenSettingsAfterComponentLibraryClose = false;
|
||||
if (shouldReopenSettings)
|
||||
{
|
||||
OpenSettingsPage();
|
||||
}
|
||||
}, TimeSpan.FromMilliseconds(200));
|
||||
}
|
||||
|
||||
private IReadOnlyList<TaskbarActionItem> ResolveDynamicTaskbarActions(TaskbarContext context)
|
||||
{
|
||||
if (!_enableDynamicTaskbarActions)
|
||||
{
|
||||
return Array.Empty<TaskbarActionItem>();
|
||||
}
|
||||
|
||||
// Reserved for page-specific actions. Disabled by default in this phase.
|
||||
_ = context;
|
||||
return Array.Empty<TaskbarActionItem>();
|
||||
}
|
||||
|
||||
private void BuildDynamicTaskbarVisuals(IReadOnlyList<TaskbarActionItem> actions)
|
||||
{
|
||||
if (TaskbarDynamicActionsPanel is not null)
|
||||
{
|
||||
TaskbarDynamicActionsPanel.Children.Clear();
|
||||
}
|
||||
|
||||
if (WallpaperPreviewTaskbarDynamicActionsPanel is not null)
|
||||
{
|
||||
WallpaperPreviewTaskbarDynamicActionsPanel.Children.Clear();
|
||||
}
|
||||
|
||||
if (actions.Count == 0 ||
|
||||
TaskbarDynamicActionsPanel is null ||
|
||||
WallpaperPreviewTaskbarDynamicActionsPanel is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var action in actions)
|
||||
{
|
||||
if (!action.IsVisible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var button = new Button
|
||||
{
|
||||
Content = action.Title,
|
||||
Background = Brushes.Transparent,
|
||||
BorderThickness = new Thickness(0),
|
||||
Padding = new Thickness(12, 6),
|
||||
Foreground = Foreground
|
||||
};
|
||||
|
||||
TaskbarDynamicActionsPanel.Children.Add(button);
|
||||
|
||||
var previewText = new TextBlock
|
||||
{
|
||||
Text = action.Title,
|
||||
Foreground = Foreground,
|
||||
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
|
||||
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
|
||||
};
|
||||
var previewBorder = new Border
|
||||
{
|
||||
Background = Brushes.Transparent,
|
||||
BorderThickness = new Thickness(0),
|
||||
Child = previewText
|
||||
};
|
||||
WallpaperPreviewTaskbarDynamicActionsPanel.Children.Add(previewBorder);
|
||||
}
|
||||
}
|
||||
|
||||
private void PersistSettings()
|
||||
{
|
||||
if (_suppressSettingsPersistence)
|
||||
|
||||
@@ -14,6 +14,8 @@ using Avalonia.Platform.Storage;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia.Threading;
|
||||
using FluentAvalonia.Styling;
|
||||
using LanMontainDesktop.ComponentSystem;
|
||||
using LanMontainDesktop.ComponentSystem.Extensions;
|
||||
using LanMontainDesktop.Models;
|
||||
using LanMontainDesktop.Services;
|
||||
using LanMontainDesktop.Theme;
|
||||
@@ -45,7 +47,6 @@ public partial class MainWindow : Window
|
||||
private const int SettingsTransitionDurationMs = 240;
|
||||
private const double WallpaperPreviewMaxWidth = 520;
|
||||
private const double LightBackgroundLuminanceThreshold = 0.57;
|
||||
private const string ClockStatusComponentId = "Clock";
|
||||
private const string TaskbarLayoutBottomFullRowMacStyle = "BottomFullRowMacStyle";
|
||||
private static readonly HashSet<string> SupportedImageExtensions = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
@@ -64,6 +65,11 @@ public partial class MainWindow : Window
|
||||
private readonly MonetColorService _monetColorService = new();
|
||||
private readonly AppSettingsService _appSettingsService = new();
|
||||
private readonly LocalizationService _localizationService = new();
|
||||
private readonly ComponentRegistry _componentRegistry = ComponentRegistry
|
||||
.CreateDefault()
|
||||
.RegisterExtensions(
|
||||
JsonComponentExtensionProvider.LoadProvidersFromDirectory(
|
||||
Path.Combine(AppContext.BaseDirectory, "Extensions", "Components")));
|
||||
private readonly FluentAvaloniaTheme? _fluentAvaloniaTheme;
|
||||
private readonly HashSet<string> _topStatusComponentIds = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly HashSet<TaskbarActionId> _pinnedTaskbarActions = [];
|
||||
@@ -137,7 +143,7 @@ public partial class MainWindow : Window
|
||||
_isNightMode = snapshot.IsNightMode ?? (CalculateCurrentBackgroundLuminance() < LightBackgroundLuminanceThreshold);
|
||||
ApplyNightModeState(_isNightMode, refreshPalettes: true);
|
||||
_suppressStatusBarToggleEvents = true;
|
||||
StatusBarClockToggleSwitch.IsChecked = _topStatusComponentIds.Contains(ClockStatusComponentId);
|
||||
StatusBarClockToggleSwitch.IsChecked = _topStatusComponentIds.Contains(BuiltInComponentIds.Clock);
|
||||
_suppressStatusBarToggleEvents = false;
|
||||
ApplyLocalization();
|
||||
ThemeColorStatusTextBlock.Text = Lf("settings.color.theme_ready_format", "Theme color ready: {0}.", _selectedThemeColor);
|
||||
|
||||
75
README.md
Normal file
75
README.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# LanMontainDesktop
|
||||
|
||||
## 项目简介 / Project Overview
|
||||
`LanMontainDesktop` 是一个基于 Avalonia 的桌面壳层应用原型,聚焦于网格化桌面布局、毛玻璃视觉、主题色系统与可扩展组件体系。
|
||||
`LanMontainDesktop` is an Avalonia-based desktop shell prototype focused on grid layout, glass visuals, theme system, and extensible components.
|
||||
|
||||
## 主要功能 / Key Features
|
||||
- 网格化桌面:顶部状态栏 + 底部任务栏(Dock 风格容器)。
|
||||
Grid-based desktop with top status bar and bottom taskbar (dock-like container).
|
||||
- 设置中心:壁纸、网格、颜色、状态栏、地区(语言)选项。
|
||||
Settings center with wallpaper, grid, color, status bar, and region (language) tabs.
|
||||
- 壁纸系统:支持图片与视频壁纸,并提供设置页预览。
|
||||
Wallpaper system supporting image/video wallpapers with in-settings preview.
|
||||
- 主题系统:日夜模式、主题色、Monet 调色联动。
|
||||
Theme system with day/night mode, accent color, and Monet palette integration.
|
||||
- 组件系统基础:内置组件注册 + 第三方扩展入口(JSON manifest)。
|
||||
Component system foundation with built-in registry and third-party JSON extension entry.
|
||||
|
||||
## 技术栈 / Tech Stack
|
||||
- .NET 10 (`net10.0`)
|
||||
- Avalonia 11
|
||||
- FluentAvalonia + FluentIcons.Avalonia
|
||||
- LibVLCSharp + VideoLAN.LibVLC.Windows(视频壁纸)
|
||||
|
||||
## 环境要求 / Prerequisites
|
||||
- .NET SDK `10.0`
|
||||
- Windows(当前项目引用 `VideoLAN.LibVLC.Windows`,视频能力以 Windows 为主)
|
||||
Windows is the primary platform for current video capability due to `VideoLAN.LibVLC.Windows`.
|
||||
|
||||
## 快速启动 / Quick Start
|
||||
```bash
|
||||
dotnet restore
|
||||
dotnet build LanMontainDesktop/LanMontainDesktop.csproj
|
||||
dotnet run --project LanMontainDesktop/LanMontainDesktop.csproj
|
||||
```
|
||||
|
||||
## 配置与持久化 / Configuration & Persistence
|
||||
应用设置通过 `AppSettingsSnapshot` 持久化到本地:
|
||||
App settings are persisted from `AppSettingsSnapshot` to local storage:
|
||||
|
||||
- 路径 / Path: `%LOCALAPPDATA%\LanMontainDesktop\settings.json`
|
||||
|
||||
核心字段(简表)/ Key fields (summary):
|
||||
- `GridShortSideCells`: 网格短边格子数 / short-side grid cells
|
||||
- `IsNightMode`: 日夜模式 / day-night mode
|
||||
- `ThemeColor`: 主题色 / accent color
|
||||
- `WallpaperPath` + `WallpaperPlacement`: 壁纸路径与显示模式 / wallpaper path and placement
|
||||
- `SettingsTabIndex`: 设置页当前选项卡 / active settings tab index
|
||||
- `LanguageCode`: 语言代码(`zh-CN` / `en-US`)
|
||||
- `TopStatusComponentIds`: 顶部状态栏组件 ID 列表 / status bar component IDs
|
||||
- `PinnedTaskbarActions`: 任务栏固定动作 / pinned taskbar actions
|
||||
|
||||
## 组件扩展入口 / Component Extension Entry
|
||||
- 运行时会扫描:`Extensions/Components/*.json`(相对应用输出目录)
|
||||
Runtime scan target: `Extensions/Components/*.json` (relative to app output).
|
||||
- 扩展加载器:`JsonComponentExtensionProvider`
|
||||
- 详细契约与 schema 见:`LanMontainDesktop/ComponentSystem/README.md`
|
||||
|
||||
## 国际化 / Localization
|
||||
- 语言资源文件:
|
||||
Localization files:
|
||||
- `LanMontainDesktop/Localization/zh-CN.json`
|
||||
- `LanMontainDesktop/Localization/en-US.json`
|
||||
- 当前支持:简体中文、English
|
||||
|
||||
## 已知限制(快速版)/ Known Limitations
|
||||
- 视频壁纸能力当前以 Windows 运行环境为主。
|
||||
Video wallpaper support is currently Windows-first.
|
||||
- `docs/VISUAL_SPEC.md` 存在历史编码问题,本次未纳入修复范围。
|
||||
`docs/VISUAL_SPEC.md` has historical encoding issues and is not updated in this round.
|
||||
|
||||
## 许可证与贡献(占位)/ License & Contributing (Placeholder)
|
||||
- License: TBD
|
||||
- Contributing guide: TBD
|
||||
|
||||
Reference in New Issue
Block a user