mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-22 00: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);
|
||||
|
||||
Reference in New Issue
Block a user