mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-21 16:14:28 +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\" />
|
<Folder Include="Models\" />
|
||||||
<AvaloniaResource Include="Assets\**" />
|
<AvaloniaResource Include="Assets\**" />
|
||||||
<None Include="Localization\*.json" CopyToOutputDirectory="PreserveNewest" />
|
<None Include="Localization\*.json" CopyToOutputDirectory="PreserveNewest" />
|
||||||
|
<None Include="Extensions\Components\*.json" CopyToOutputDirectory="PreserveNewest" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<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();
|
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)
|
private void OnCloseSettingsClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
CloseSettingsPage();
|
CloseSettingsPage();
|
||||||
@@ -92,32 +71,6 @@ public partial class MainWindow
|
|||||||
ApplyTaskbarActionVisibility(GetCurrentTaskbarContext());
|
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)
|
private void OnNightModeChecked(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (_suppressThemeToggleEvents)
|
if (_suppressThemeToggleEvents)
|
||||||
@@ -646,284 +599,6 @@ public partial class MainWindow
|
|||||||
_previewVideoWallpaperMedia = null;
|
_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()
|
private void PersistSettings()
|
||||||
{
|
{
|
||||||
if (_suppressSettingsPersistence)
|
if (_suppressSettingsPersistence)
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ using Avalonia.Platform.Storage;
|
|||||||
using Avalonia.Styling;
|
using Avalonia.Styling;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using FluentAvalonia.Styling;
|
using FluentAvalonia.Styling;
|
||||||
|
using LanMontainDesktop.ComponentSystem;
|
||||||
|
using LanMontainDesktop.ComponentSystem.Extensions;
|
||||||
using LanMontainDesktop.Models;
|
using LanMontainDesktop.Models;
|
||||||
using LanMontainDesktop.Services;
|
using LanMontainDesktop.Services;
|
||||||
using LanMontainDesktop.Theme;
|
using LanMontainDesktop.Theme;
|
||||||
@@ -45,7 +47,6 @@ public partial class MainWindow : Window
|
|||||||
private const int SettingsTransitionDurationMs = 240;
|
private const int SettingsTransitionDurationMs = 240;
|
||||||
private const double WallpaperPreviewMaxWidth = 520;
|
private const double WallpaperPreviewMaxWidth = 520;
|
||||||
private const double LightBackgroundLuminanceThreshold = 0.57;
|
private const double LightBackgroundLuminanceThreshold = 0.57;
|
||||||
private const string ClockStatusComponentId = "Clock";
|
|
||||||
private const string TaskbarLayoutBottomFullRowMacStyle = "BottomFullRowMacStyle";
|
private const string TaskbarLayoutBottomFullRowMacStyle = "BottomFullRowMacStyle";
|
||||||
private static readonly HashSet<string> SupportedImageExtensions = new(StringComparer.OrdinalIgnoreCase)
|
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 MonetColorService _monetColorService = new();
|
||||||
private readonly AppSettingsService _appSettingsService = new();
|
private readonly AppSettingsService _appSettingsService = new();
|
||||||
private readonly LocalizationService _localizationService = 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 FluentAvaloniaTheme? _fluentAvaloniaTheme;
|
||||||
private readonly HashSet<string> _topStatusComponentIds = new(StringComparer.OrdinalIgnoreCase);
|
private readonly HashSet<string> _topStatusComponentIds = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private readonly HashSet<TaskbarActionId> _pinnedTaskbarActions = [];
|
private readonly HashSet<TaskbarActionId> _pinnedTaskbarActions = [];
|
||||||
@@ -137,7 +143,7 @@ public partial class MainWindow : Window
|
|||||||
_isNightMode = snapshot.IsNightMode ?? (CalculateCurrentBackgroundLuminance() < LightBackgroundLuminanceThreshold);
|
_isNightMode = snapshot.IsNightMode ?? (CalculateCurrentBackgroundLuminance() < LightBackgroundLuminanceThreshold);
|
||||||
ApplyNightModeState(_isNightMode, refreshPalettes: true);
|
ApplyNightModeState(_isNightMode, refreshPalettes: true);
|
||||||
_suppressStatusBarToggleEvents = true;
|
_suppressStatusBarToggleEvents = true;
|
||||||
StatusBarClockToggleSwitch.IsChecked = _topStatusComponentIds.Contains(ClockStatusComponentId);
|
StatusBarClockToggleSwitch.IsChecked = _topStatusComponentIds.Contains(BuiltInComponentIds.Clock);
|
||||||
_suppressStatusBarToggleEvents = false;
|
_suppressStatusBarToggleEvents = false;
|
||||||
ApplyLocalization();
|
ApplyLocalization();
|
||||||
ThemeColorStatusTextBlock.Text = Lf("settings.color.theme_ready_format", "Theme color ready: {0}.", _selectedThemeColor);
|
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