diff --git a/LanMountainDesktop/App.axaml.cs b/LanMountainDesktop/App.axaml.cs
index 84396e4..8d41fb7 100644
--- a/LanMountainDesktop/App.axaml.cs
+++ b/LanMountainDesktop/App.axaml.cs
@@ -149,6 +149,11 @@ public partial class App : Application
LinuxDesktopEntryInstaller.EnsureInstalled();
DesktopBootstrap.InitializeApplication(this, InitializeDesktopShell);
+ if (!Design.IsDesignMode && OperatingSystem.IsWindows())
+ {
+ FusedDesktopManagerServiceFactory.GetOrCreate().Initialize();
+ }
+
base.OnFrameworkInitializationCompleted();
}
@@ -226,6 +231,9 @@ public partial class App : Application
AppLogger.Warn("FusedDesktop", "Fused desktop is only supported on Windows.");
return;
}
+
+ // 切换进入编辑模式,隐藏常态零散的小部件
+ FusedDesktopManagerServiceFactory.GetOrCreate().EnterEditMode();
// 确保透明覆盖层窗口存在并显示
EnsureTransparentOverlayWindow();
@@ -248,6 +256,19 @@ public partial class App : Application
window.SetOverlayWindow(_transparentOverlayWindow);
}
+ // 当组件库关闭时,退出编辑态
+ window.Closed += (s, ev) =>
+ {
+ if (_transparentOverlayWindow is not null)
+ {
+ // 触发画布保存,并隐藏画布
+ _transparentOverlayWindow.SaveLayoutAndHide();
+ }
+
+ // 让管理器根据已存储的最新快照重建生成所有实体小组件
+ FusedDesktopManagerServiceFactory.GetOrCreate().ExitEditMode();
+ };
+
window.Show();
window.Activate();
}
diff --git a/LanMountainDesktop/Services/FusedDesktopManagerService.cs b/LanMountainDesktop/Services/FusedDesktopManagerService.cs
new file mode 100644
index 0000000..595571b
--- /dev/null
+++ b/LanMountainDesktop/Services/FusedDesktopManagerService.cs
@@ -0,0 +1,195 @@
+using System;
+using System.Collections.Generic;
+using Avalonia;
+using Avalonia.Controls;
+using LanMountainDesktop.ComponentSystem;
+using LanMountainDesktop.Models;
+using LanMountainDesktop.PluginSdk;
+using LanMountainDesktop.Services.Settings;
+using LanMountainDesktop.Views;
+using LanMountainDesktop.Views.Components;
+
+namespace LanMountainDesktop.Services;
+
+///
+/// 融合桌面中央管理器服务接口
+///
+public interface IFusedDesktopManagerService
+{
+ void Initialize();
+ void EnterEditMode();
+ void ExitEditMode();
+ void ReloadWidgets();
+}
+
+///
+/// 融合桌面中央管理器服务实现。用于管理常态下的各个小窗口实体。
+///
+internal sealed class FusedDesktopManagerService : IFusedDesktopManagerService
+{
+ private readonly IFusedDesktopLayoutService _layoutService;
+ private readonly ISettingsFacadeService _settingsFacade;
+ private readonly Dictionary _widgetWindows = [];
+
+ // 基础服务依赖
+ private readonly IWeatherInfoService _weatherDataService;
+ private readonly TimeZoneService _timeZoneService;
+ private readonly IRecommendationInfoService _recommendationInfoService = new RecommendationDataService();
+ private readonly ICalculatorDataService _calculatorDataService = new CalculatorDataService();
+
+ private ComponentRegistry? _componentRegistry;
+ private DesktopComponentRuntimeRegistry? _componentRuntimeRegistry;
+ private bool _isEditMode;
+
+ private const double DefaultCellSize = 100;
+
+ public FusedDesktopManagerService(
+ IFusedDesktopLayoutService layoutService,
+ ISettingsFacadeService settingsFacade)
+ {
+ _layoutService = layoutService;
+ _settingsFacade = settingsFacade;
+
+ _weatherDataService = _settingsFacade.Weather.GetWeatherInfoService();
+ _timeZoneService = _settingsFacade.Region.GetTimeZoneService();
+ }
+
+ public void Initialize()
+ {
+ if (!OperatingSystem.IsWindows()) return;
+
+ EnsureRegistries();
+ ReloadWidgets();
+ }
+
+ private void EnsureRegistries()
+ {
+ if (_componentRuntimeRegistry is not null) return;
+
+ var pluginRuntimeService = (Application.Current as App)?.PluginRuntimeService;
+ _componentRegistry = DesktopComponentRegistryFactory.Create(pluginRuntimeService);
+ _componentRuntimeRegistry = DesktopComponentRegistryFactory.CreateRuntimeRegistry(
+ _componentRegistry,
+ pluginRuntimeService,
+ _settingsFacade);
+ }
+
+ public void EnterEditMode()
+ {
+ if (_isEditMode) return;
+ _isEditMode = true;
+
+ // 隐藏所有底层小窗口
+ foreach (var window in _widgetWindows.Values)
+ {
+ window.Hide();
+ }
+ }
+
+ public void ExitEditMode()
+ {
+ if (!_isEditMode) return;
+ _isEditMode = false;
+
+ // 编辑完成,重新加载布局(可能已发生更改)并显示
+ ReloadWidgets();
+ }
+
+ public void ReloadWidgets()
+ {
+ if (_isEditMode) return; // 编辑模式下不渲染小窗口
+
+ var layout = _layoutService.Load();
+ var existingIds = new HashSet(_widgetWindows.Keys);
+
+ foreach (var placement in layout.ComponentPlacements)
+ {
+ existingIds.Remove(placement.PlacementId);
+
+ if (_widgetWindows.TryGetValue(placement.PlacementId, out var existingWindow))
+ {
+ // 已存在,可能只更新位置或尺寸
+ existingWindow.Position = new Avalonia.PixelPoint((int)placement.X, (int)placement.Y);
+ if (existingWindow.IsVisible == false)
+ {
+ existingWindow.Show();
+ }
+ }
+ else
+ {
+ // 新组件,生成窗口
+ try
+ {
+ var window = CreateWidgetWindow(placement);
+ if (window != null)
+ {
+ _widgetWindows[placement.PlacementId] = window;
+ window.Show();
+ window.Position = new Avalonia.PixelPoint((int)placement.X, (int)placement.Y);
+ }
+ }
+ catch (Exception ex)
+ {
+ AppLogger.Warn("FusedDesktopMgr", $"Failed to render tiny window for {placement.ComponentId}", ex);
+ }
+ }
+ }
+
+ // 移除被删除的组件
+ foreach (var id in existingIds)
+ {
+ if (_widgetWindows.Remove(id, out var windowToRemove))
+ {
+ windowToRemove.Close();
+ }
+ }
+ }
+
+ private DesktopWidgetWindow? CreateWidgetWindow(FusedDesktopComponentPlacementSnapshot placement)
+ {
+ EnsureRegistries();
+ if (_componentRuntimeRegistry is null || !_componentRuntimeRegistry.TryGetDescriptor(placement.ComponentId, out var descriptor))
+ {
+ AppLogger.Warn("FusedDesktopMgr", $"Unknown component: {placement.ComponentId}");
+ return null;
+ }
+
+ var control = descriptor.CreateControl(
+ DefaultCellSize,
+ _timeZoneService,
+ _weatherDataService,
+ _recommendationInfoService,
+ _calculatorDataService,
+ _settingsFacade,
+ placement.PlacementId);
+
+ // 将组件包装到一个具有准确宽高的容器内(如果组件自身没有设置宽度)
+ control.Width = placement.Width;
+ control.Height = placement.Height;
+
+ var window = new DesktopWidgetWindow(control);
+ return window;
+ }
+}
+
+///
+/// 工厂
+///
+public static class FusedDesktopManagerServiceFactory
+{
+ private static IFusedDesktopManagerService? _instance;
+ private static readonly object _lock = new();
+
+ public static IFusedDesktopManagerService GetOrCreate()
+ {
+ if (_instance is not null) return _instance;
+
+ lock (_lock)
+ {
+ var layoutService = FusedDesktopLayoutServiceProvider.GetOrCreate();
+ var settings = HostSettingsFacadeProvider.GetOrCreate();
+ _instance ??= new FusedDesktopManagerService(layoutService, settings);
+ return _instance;
+ }
+ }
+}
diff --git a/LanMountainDesktop/Views/ComponentLibraryWindow.axaml.cs b/LanMountainDesktop/Views/ComponentLibraryWindow.axaml.cs
index 7c6cf86..428bc29 100644
--- a/LanMountainDesktop/Views/ComponentLibraryWindow.axaml.cs
+++ b/LanMountainDesktop/Views/ComponentLibraryWindow.axaml.cs
@@ -220,7 +220,7 @@ public partial class ComponentLibraryWindow : Window
if (string.Equals(categoryId, "Info", StringComparison.OrdinalIgnoreCase))
{
- return Symbol.Apps;
+ return Symbol.Info;
}
if (string.Equals(categoryId, "Calculator", StringComparison.OrdinalIgnoreCase))
diff --git a/LanMountainDesktop/Views/DesktopWidgetWindow.axaml b/LanMountainDesktop/Views/DesktopWidgetWindow.axaml
new file mode 100644
index 0000000..47c7afc
--- /dev/null
+++ b/LanMountainDesktop/Views/DesktopWidgetWindow.axaml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
diff --git a/LanMountainDesktop/Views/DesktopWidgetWindow.axaml.cs b/LanMountainDesktop/Views/DesktopWidgetWindow.axaml.cs
new file mode 100644
index 0000000..6a3ef6c
--- /dev/null
+++ b/LanMountainDesktop/Views/DesktopWidgetWindow.axaml.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using Avalonia;
+using Avalonia.Controls;
+using LanMountainDesktop.Services;
+using Avalonia.Threading;
+
+namespace LanMountainDesktop.Views;
+
+///
+/// 表示一个独立的组件挂载窗口。它不含有任何自己的边窗,仅仅负责包裹组件并将自身植入系统最底层。
+///
+public partial class DesktopWidgetWindow : Window
+{
+ private readonly IWindowBottomMostService _bottomMostService = WindowBottomMostServiceFactory.GetOrCreate();
+ private readonly IRegionPassthroughService _regionPassthroughService = RegionPassthroughServiceFactory.GetOrCreate();
+
+ public DesktopWidgetWindow()
+ {
+ InitializeComponent();
+ }
+
+ public DesktopWidgetWindow(Control componentContent) : this()
+ {
+ ComponentContainer.Child = componentContent;
+ }
+
+ protected override void OnOpened(EventArgs e)
+ {
+ base.OnOpened(e);
+
+ if (OperatingSystem.IsWindows())
+ {
+ // 通过现有的置底服务将独立的小窗口锁定到底层
+ _bottomMostService.SetupBottomMost(this);
+ _bottomMostService.SendToBottom(this);
+
+ // 当窗口展示完毕且有了尺寸后,更新可交互区域,使得整个组件都能被点击
+ Dispatcher.UIThread.Post(UpdateInteractiveRegion, DispatcherPriority.Render);
+ }
+ }
+
+ protected override void OnSizeChanged(SizeChangedEventArgs e)
+ {
+ base.OnSizeChanged(e);
+
+ if (OperatingSystem.IsWindows() && IsVisible)
+ {
+ UpdateInteractiveRegion();
+ }
+ }
+
+ private void UpdateInteractiveRegion()
+ {
+ // 既然是一个完全紧贴在组件身上的小窗,它的全部都是可交互的
+ _regionPassthroughService.SetInteractiveRegions(this, new List
+ {
+ new(0, 0, Bounds.Width, Bounds.Height)
+ });
+ }
+}
diff --git a/LanMountainDesktop/Views/FusedDesktopComponentLibraryControl.axaml b/LanMountainDesktop/Views/FusedDesktopComponentLibraryControl.axaml
index f6ab62c..454d232 100644
--- a/LanMountainDesktop/Views/FusedDesktopComponentLibraryControl.axaml
+++ b/LanMountainDesktop/Views/FusedDesktopComponentLibraryControl.axaml
@@ -1,15 +1,17 @@
+ x:Class="LanMountainDesktop.Views.FusedDesktopComponentLibraryControl"
+ x:DataType="vm:ComponentLibraryWindowViewModel">
-
+
-
+
-
-
-
+ SelectionChanged="OnCategorySelectionChanged"
+ ItemsSource="{Binding Categories}">
-
-
-
-
-
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
diff --git a/LanMountainDesktop/Views/FusedDesktopComponentLibraryControl.axaml.cs b/LanMountainDesktop/Views/FusedDesktopComponentLibraryControl.axaml.cs
index fdaa61c..72447d4 100644
--- a/LanMountainDesktop/Views/FusedDesktopComponentLibraryControl.axaml.cs
+++ b/LanMountainDesktop/Views/FusedDesktopComponentLibraryControl.axaml.cs
@@ -1,18 +1,15 @@
using System;
using System.Collections.Generic;
-using System.Collections.ObjectModel;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
-using Avalonia.Input;
using Avalonia.Interactivity;
-using Avalonia.Layout;
-using Avalonia.Media;
+using FluentIcons.Common;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
+using LanMountainDesktop.ViewModels;
using LanMountainDesktop.Views.Components;
-using LanMountainDesktop.Models;
namespace LanMountainDesktop.Views;
@@ -20,10 +17,9 @@ public partial class FusedDesktopComponentLibraryControl : UserControl
{
public event EventHandler? AddComponentRequested;
- private readonly ObservableCollection _categories = new();
- private readonly ObservableCollection _components = new();
+ private readonly ComponentLibraryWindowViewModel _viewModel = new();
private List _allDefinitions = new();
-
+
private ComponentRegistry? _componentRegistry;
private DesktopComponentRuntimeRegistry? _componentRuntimeRegistry;
private readonly ISettingsFacadeService _settingsFacade = HostSettingsFacadeProvider.GetOrCreate();
@@ -35,18 +31,17 @@ public partial class FusedDesktopComponentLibraryControl : UserControl
public FusedDesktopComponentLibraryControl()
{
InitializeComponent();
+ DataContext = _viewModel;
+
_weatherDataService = _settingsFacade.Weather.GetWeatherInfoService();
_timeZoneService = _settingsFacade.Region.GetTimeZoneService();
-
- CategoryListBox.ItemsSource = _categories;
- ComponentItemsControl.ItemsSource = _components;
-
+
LoadRegistry();
LoadCategories();
SearchBox.KeyUp += (s, e) => FilterComponents();
-
+
// 默认选择第一个分类
- if (_categories.Count > 0)
+ if (_viewModel.Categories.Count > 0)
{
CategoryListBox.SelectedIndex = 0;
}
@@ -60,7 +55,7 @@ public partial class FusedDesktopComponentLibraryControl : UserControl
_componentRegistry,
pluginRuntimeService,
_settingsFacade);
-
+
_allDefinitions = _componentRegistry.GetAll()
.Where(d => d.AllowDesktopPlacement)
.ToList();
@@ -68,18 +63,27 @@ public partial class FusedDesktopComponentLibraryControl : UserControl
private void LoadCategories()
{
- _categories.Clear();
- _categories.Add(new LibraryCategoryItem("all", "全部组件", "Apps"));
-
- var categoryMap = new Dictionary
+ _viewModel.Categories.Clear();
+ _viewModel.Components.Clear();
+
+ // 添加"全部组件"分类
+ _viewModel.Categories.Add(new ComponentLibraryCategoryViewModel(
+ "all",
+ "全部组件",
+ Symbol.Apps,
+ Array.Empty()));
+
+ var categoryMap = new Dictionary
{
- { "clock", ("时钟", "Clock") },
- { "date", ("日历", "Calendar") },
- { "weather", ("天气", "WeatherCloudy") },
- { "info", ("资讯", "News") },
- { "calculator", ("工具", "Calculator") },
- { "study", ("学习", "Book") },
- { "file", ("文件", "Document") }
+ { "clock", ("时钟", Symbol.Clock) },
+ { "date", ("日历", Symbol.CalendarDate) },
+ { "weather", ("天气", Symbol.WeatherSunny) },
+ { "board", ("画板", Symbol.Edit) },
+ { "media", ("媒体", Symbol.Play) },
+ { "info", ("资讯", Symbol.News) },
+ { "calculator", ("工具", Symbol.Calculator) },
+ { "study", ("学习", Symbol.Hourglass) },
+ { "file", ("文件", Symbol.Folder) }
};
var usedCategories = _allDefinitions
@@ -91,15 +95,36 @@ public partial class FusedDesktopComponentLibraryControl : UserControl
{
if (categoryMap.TryGetValue(cat.ToLower(), out var info))
{
- _categories.Add(new LibraryCategoryItem(cat, info.Display, info.Icon));
- }
- else
- {
- _categories.Add(new LibraryCategoryItem(cat, cat, "Cube"));
+ var categoryComponents = _allDefinitions
+ .Where(d => string.Equals(d.Category, cat, StringComparison.OrdinalIgnoreCase))
+ .OrderBy(d => d.DisplayName)
+ .Select(d => CreateComponentItem(d))
+ .ToArray();
+
+ _viewModel.Categories.Add(new ComponentLibraryCategoryViewModel(
+ cat,
+ info.Display,
+ info.Icon,
+ categoryComponents));
}
}
}
+ private ComponentLibraryItemViewModel CreateComponentItem(DesktopComponentDefinition definition)
+ {
+ var previewKey = ComponentPreviewKey.ForComponentType(
+ definition.Id,
+ definition.MinWidthCells,
+ definition.MinHeightCells);
+
+ return new ComponentLibraryItemViewModel(
+ definition.Id,
+ definition.DisplayName,
+ previewKey,
+ "正在加载预览...",
+ "预览不可用");
+ }
+
private void OnCategorySelectionChanged(object? sender, SelectionChangedEventArgs e)
{
FilterComponents();
@@ -107,7 +132,7 @@ public partial class FusedDesktopComponentLibraryControl : UserControl
private void FilterComponents()
{
- var selectedCategory = (CategoryListBox.SelectedItem as LibraryCategoryItem)?.Id;
+ var selectedCategory = (CategoryListBox.SelectedItem as ComponentLibraryCategoryViewModel)?.Id;
var searchText = SearchBox.Text?.ToLower() ?? "";
var filtered = _allDefinitions.Where(d =>
@@ -117,57 +142,10 @@ public partial class FusedDesktopComponentLibraryControl : UserControl
return matchesCategory && matchesSearch;
});
- _components.Clear();
+ _viewModel.Components.Clear();
foreach (var def in filtered)
{
- _components.Add(new LibraryComponentItem
- {
- Id = def.Id,
- DisplayName = def.DisplayName,
- Description = GetDescription(def.Id),
- PreviewContent = CreatePreview(def.Id)
- });
- }
- }
-
- private string GetDescription(string id)
- {
- // 简单映射描述信息
- return id.Contains("clock") ? "实时显示当前时间与日期。" :
- id.Contains("weather") ? "为您提供精准的天气预报。" :
- "多功能桌面组件,提升您的操作效率。";
- }
-
- private Control? CreatePreview(string id)
- {
- if (_componentRuntimeRegistry == null || !_componentRuntimeRegistry.TryGetDescriptor(id, out var descriptor))
- {
- return null;
- }
-
- try
- {
- var control = descriptor.CreateControl(
- 100, // Previews assume 100px base
- _timeZoneService,
- _weatherDataService,
- _recommendationInfoService,
- _calculatorDataService,
- _settingsFacade,
- "preview_" + id);
-
- control.IsHitTestVisible = false;
-
- return new Viewbox
- {
- Child = control,
- Stretch = Stretch.Uniform,
- Margin = new Thickness(12)
- };
- }
- catch (Exception ex)
- {
- return new TextBlock { Text = "无法预览", VerticalAlignment = VerticalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Center };
+ _viewModel.Components.Add(CreateComponentItem(def));
}
}
@@ -179,15 +157,3 @@ public partial class FusedDesktopComponentLibraryControl : UserControl
}
}
}
-
-public record LibraryCategoryItem(string Id, string DisplayName, string Icon);
-
-public class LibraryComponentItem
-{
- public string Id { get; set; } = "";
- public string DisplayName { get; set; } = "";
- public string Description { get; set; } = "";
- public Control? PreviewContent { get; set; }
- public bool HasPreview => PreviewContent != null;
-}
-
diff --git a/LanMountainDesktop/Views/FusedDesktopComponentLibraryWindow.axaml.cs b/LanMountainDesktop/Views/FusedDesktopComponentLibraryWindow.axaml.cs
index dd88982..b6dcc9f 100644
--- a/LanMountainDesktop/Views/FusedDesktopComponentLibraryWindow.axaml.cs
+++ b/LanMountainDesktop/Views/FusedDesktopComponentLibraryWindow.axaml.cs
@@ -1,4 +1,5 @@
using System;
+using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using LanMountainDesktop.ComponentSystem;
diff --git a/LanMountainDesktop/Views/MainWindow.DesktopPaging.cs b/LanMountainDesktop/Views/MainWindow.DesktopPaging.cs
index 7a661b0..b066de4 100644
--- a/LanMountainDesktop/Views/MainWindow.DesktopPaging.cs
+++ b/LanMountainDesktop/Views/MainWindow.DesktopPaging.cs
@@ -269,12 +269,6 @@ public partial class MainWindow
LauncherPagePanel.MaxWidth = pageWidth - launcherMargin * 2;
LauncherPagePanel.MaxHeight = pageHeight - launcherMargin * 2;
- if (LauncherFolderPanel is not null)
- {
- LauncherFolderPanel.MaxWidth = Math.Max(320, pageWidth - 96);
- LauncherFolderPanel.MaxHeight = Math.Max(220, pageHeight - 96);
- }
-
// 更新启动台图标布局
UpdateLauncherTileLayout();
@@ -331,19 +325,6 @@ public partial class MainWindow
}
}
- // 同样更新文件夹视图的图标尺寸
- if (LauncherFolderTilePanel is not null)
- {
- LauncherFolderTilePanel.Width = availableWidth;
- foreach (var child in LauncherFolderTilePanel.Children)
- {
- if (child is Button button)
- {
- button.Width = tileWidth;
- button.Height = tileHeight;
- }
- }
- }
}
private void ClampSurfaceIndex()
@@ -630,8 +611,12 @@ public partial class MainWindow
foreach (var node in button.GetSelfAndVisualAncestors())
{
- if (node is WrapPanel panel &&
- (panel.Name == "LauncherRootTilePanel" || panel.Name == "LauncherFolderTilePanel"))
+ if (node is WrapPanel panel && panel.Name == "LauncherRootTilePanel")
+ {
+ return true;
+ }
+
+ if (node is Grid grid && grid.Name == "LauncherFolderGridPanel")
{
return true;
}
@@ -719,8 +704,7 @@ public partial class MainWindow
return false;
}
- return scrollViewer.Name == "LauncherRootScrollViewer" ||
- scrollViewer.Name == "LauncherFolderScrollViewer";
+ return scrollViewer.Name == "LauncherRootScrollViewer";
}
private bool TryGetPointerPositionInDesktopViewport(PointerEventArgs e, out Point point)
@@ -1561,18 +1545,17 @@ public partial class MainWindow
LauncherFolderOverlay.IsVisible = false;
}
- if (LauncherFolderTilePanel is not null)
+ if (LauncherFolderGridPanel is not null)
{
- LauncherFolderTilePanel.Children.Clear();
+ LauncherFolderGridPanel.Children.Clear();
}
}
private void RenderLauncherFolderFromStack()
{
if (LauncherFolderOverlay is null ||
- LauncherFolderTilePanel is null ||
- LauncherFolderTitleTextBlock is null ||
- LauncherFolderBackButton is null)
+ LauncherFolderGridPanel is null ||
+ LauncherFolderTitleTextBlock is null)
{
return;
}
@@ -1587,38 +1570,230 @@ public partial class MainWindow
var folder = _launcherFolderStack.Peek();
LauncherFolderOverlay.IsVisible = true;
LauncherFolderTitleTextBlock.Text = folder.Name;
- LauncherFolderBackButton.IsVisible = _launcherFolderStack.Count > 1;
- LauncherFolderTilePanel.Children.Clear();
- foreach (var subFolder in folder.Folders)
+ LauncherFolderGridPanel.Children.Clear();
+
+ const int maxCols = 4;
+ const int maxRows = 3;
+ const int maxItems = maxCols * maxRows;
+
+ var visibleFolders = folder.Folders.Where(IsLauncherFolderVisible).ToList();
+ var visibleApps = folder.Apps.Where(IsLauncherAppVisible).ToList();
+
+ if (visibleFolders.Count == 0 && visibleApps.Count == 0)
{
- if (!IsLauncherFolderVisible(subFolder))
+ LauncherFolderGridPanel.Children.Add(CreateLauncherFolderGridHintCell(
+ L("launcher.empty_folder", "This folder is empty.")));
+ return;
+ }
+
+ var allItems = new List<(StartMenuFolderNode? Folder, StartMenuAppEntry? App)>();
+ foreach (var f in visibleFolders)
+ {
+ allItems.Add((f, null));
+ }
+ foreach (var a in visibleApps)
+ {
+ allItems.Add((null, a));
+ }
+
+ var displayCount = Math.Min(allItems.Count, maxItems);
+ for (var i = 0; i < displayCount; i++)
+ {
+ var col = i % maxCols;
+ var row = i / maxCols;
+ var (itemFolder, itemApp) = allItems[i];
+
+ Control cell;
+ if (itemFolder is not null)
+ {
+ var capturedFolder = itemFolder;
+ cell = CreateLauncherFolderGridTile(itemFolder.Name, GetLauncherFolderIconBitmap(), () => OpenLauncherFolder(capturedFolder));
+ }
+ else if (itemApp is not null)
+ {
+ var capturedApp = itemApp;
+ cell = CreateLauncherFolderGridTile(capturedApp, () => LaunchStartMenuEntry(capturedApp));
+ }
+ else
{
continue;
}
- LauncherFolderTilePanel.Children.Add(CreateLauncherFolderTile(subFolder));
+ Grid.SetColumn(cell, col);
+ Grid.SetRow(cell, row);
+ LauncherFolderGridPanel.Children.Add(cell);
}
+ }
- foreach (var app in folder.Apps)
- {
- if (!IsLauncherAppVisible(app))
+ private Button CreateLauncherFolderGridTile(StartMenuAppEntry app, Action clickAction)
+ {
+ var iconBitmap = GetLauncherIconBitmap(app);
+ var monogram = BuildMonogram(app.DisplayName);
+
+ Control iconControl = iconBitmap is not null
+ ? new Image
{
- continue;
+ Source = iconBitmap,
+ Width = 32,
+ Height = 32,
+ Stretch = Stretch.Uniform
+ }
+ : new Border
+ {
+ Width = 32,
+ Height = 32,
+ CornerRadius = new CornerRadius(8),
+ Background = GetThemeBrush("AdaptiveButtonBackgroundBrush"),
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center,
+ Child = new TextBlock
+ {
+ Text = monogram,
+ FontSize = 13,
+ FontWeight = FontWeight.Bold,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center
+ }
+ };
+
+ var content = new StackPanel
+ {
+ Spacing = 6,
+ HorizontalAlignment = HorizontalAlignment.Stretch,
+ VerticalAlignment = VerticalAlignment.Center
+ };
+ content.Children.Add(iconControl);
+ content.Children.Add(new TextBlock
+ {
+ Text = app.DisplayName,
+ TextTrimming = TextTrimming.CharacterEllipsis,
+ MaxLines = 2,
+ TextAlignment = TextAlignment.Center,
+ FontSize = 11,
+ HorizontalAlignment = HorizontalAlignment.Stretch
+ });
+
+ var button = new Button
+ {
+ Classes = { "glass-panel" },
+ HorizontalAlignment = HorizontalAlignment.Stretch,
+ VerticalAlignment = VerticalAlignment.Stretch,
+ BorderThickness = new Thickness(0),
+ CornerRadius = new CornerRadius(12),
+ Padding = new Thickness(8, 8, 8, 6),
+ Content = content
+ };
+ button.Click += (_, _) =>
+ {
+ if (_isComponentLibraryOpen)
+ {
+ return;
}
- LauncherFolderTilePanel.Children.Add(CreateLauncherAppTile(app));
- }
+ clickAction();
+ };
+ return button;
+ }
- if (LauncherFolderTilePanel.Children.Count == 0)
+ private Button CreateLauncherFolderGridTile(string folderName, Bitmap? iconBitmap, Action clickAction)
+ {
+ var monogram = "DIR";
+
+ Control iconControl = iconBitmap is not null
+ ? new Image
+ {
+ Source = iconBitmap,
+ Width = 32,
+ Height = 32,
+ Stretch = Stretch.Uniform
+ }
+ : new Border
+ {
+ Width = 32,
+ Height = 32,
+ CornerRadius = new CornerRadius(8),
+ Background = GetThemeBrush("AdaptiveButtonBackgroundBrush"),
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center,
+ Child = new TextBlock
+ {
+ Text = monogram,
+ FontSize = 11,
+ FontWeight = FontWeight.Bold,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center
+ }
+ };
+
+ var content = new StackPanel
{
- LauncherFolderTilePanel.Children.Add(CreateLauncherHintTile(
- L("launcher.empty_folder", "This folder is empty."),
- string.Empty));
- }
+ Spacing = 6,
+ HorizontalAlignment = HorizontalAlignment.Stretch,
+ VerticalAlignment = VerticalAlignment.Center
+ };
+ content.Children.Add(iconControl);
+ content.Children.Add(new TextBlock
+ {
+ Text = folderName,
+ TextTrimming = TextTrimming.CharacterEllipsis,
+ MaxLines = 2,
+ TextAlignment = TextAlignment.Center,
+ FontSize = 11,
+ HorizontalAlignment = HorizontalAlignment.Stretch
+ });
- // 在图标渲染完成后,应用布局计算
- Dispatcher.UIThread.Post(() => UpdateLauncherTileLayout(), DispatcherPriority.Background);
+ var button = new Button
+ {
+ Classes = { "glass-panel" },
+ HorizontalAlignment = HorizontalAlignment.Stretch,
+ VerticalAlignment = VerticalAlignment.Stretch,
+ BorderThickness = new Thickness(0),
+ CornerRadius = new CornerRadius(12),
+ Padding = new Thickness(8, 8, 8, 6),
+ Content = content
+ };
+ button.Click += (_, _) =>
+ {
+ if (_isComponentLibraryOpen)
+ {
+ return;
+ }
+
+ clickAction();
+ };
+ return button;
+ }
+
+ private Control CreateLauncherFolderGridHintCell(string message)
+ {
+ return CreateLauncherFolderGridHintCell(message, 0, 0);
+ }
+
+ private Control CreateLauncherFolderGridHintCell(string message, int col, int row)
+ {
+ var textBlock = new TextBlock
+ {
+ Text = message,
+ FontSize = 12,
+ FontWeight = FontWeight.SemiBold,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center,
+ Opacity = 0.6
+ };
+
+ var cell = new Border
+ {
+ Classes = { "glass-panel" },
+ HorizontalAlignment = HorizontalAlignment.Stretch,
+ VerticalAlignment = VerticalAlignment.Stretch,
+ CornerRadius = new CornerRadius(12),
+ Child = textBlock
+ };
+
+ Grid.SetColumn(cell, col);
+ Grid.SetRow(cell, row);
+ return cell;
}
private static string BuildMonogram(string text)
@@ -1689,18 +1864,6 @@ public partial class MainWindow
}
}
- private void OnLauncherFolderBackClick(object? sender, RoutedEventArgs e)
- {
- if (_launcherFolderStack.Count <= 1)
- {
- CloseLauncherFolderOverlay();
- return;
- }
-
- _launcherFolderStack.Pop();
- RenderLauncherFolderFromStack();
- }
-
private void OnLauncherFolderOverlayPointerPressed(object? sender, PointerPressedEventArgs e)
{
if (LauncherFolderPanel is null)
@@ -1721,11 +1884,6 @@ public partial class MainWindow
e.Handled = true;
}
- private void OnLauncherFolderCloseClick(object? sender, RoutedEventArgs e)
- {
- CloseLauncherFolderOverlay();
- }
-
private void DisposeLauncherResources()
{
foreach (var bitmap in _launcherIconCache.Values)
diff --git a/LanMountainDesktop/Views/MainWindow.axaml b/LanMountainDesktop/Views/MainWindow.axaml
index b3f9c7e..4711688 100644
--- a/LanMountainDesktop/Views/MainWindow.axaml
+++ b/LanMountainDesktop/Views/MainWindow.axaml
@@ -189,50 +189,21 @@
Classes="surface-solid-strong"
HorizontalAlignment="Center"
VerticalAlignment="Center"
- Margin="52"
- MaxWidth="760"
- MaxHeight="520"
- CornerRadius="36"
- Padding="14">
-
-
-
-
-
-
-
-
-
-
-
-
-
+ Width="464"
+ Height="384"
+ CornerRadius="24"
+ Padding="16,14,16,12">
+
+
-
-
-
+
diff --git a/LanMountainDesktop/Views/TransparentOverlayWindow.axaml b/LanMountainDesktop/Views/TransparentOverlayWindow.axaml
index d5b8838..c75ef4a 100644
--- a/LanMountainDesktop/Views/TransparentOverlayWindow.axaml
+++ b/LanMountainDesktop/Views/TransparentOverlayWindow.axaml
@@ -1,7 +1,6 @@
public partial class TransparentOverlayWindow : Window
{
- private readonly ISettingsFacadeService _settingsFacade = HostSettingsFacadeProvider.GetOrCreate();
- private readonly IWindowBottomMostService _bottomMostService = WindowBottomMostServiceFactory.GetOrCreate();
- private readonly IRegionPassthroughService _regionPassthroughService = RegionPassthroughServiceFactory.GetOrCreate();
private readonly IFusedDesktopLayoutService _layoutService = FusedDesktopLayoutServiceProvider.GetOrCreate();
// 滑动状态
@@ -67,23 +64,45 @@ public partial class TransparentOverlayWindow : Window
public TransparentOverlayWindow()
{
InitializeComponent();
- _weatherDataService = _settingsFacade.Weather.GetWeatherInfoService();
- _timeZoneService = _settingsFacade.Region.GetTimeZoneService();
+ var facade = HostSettingsFacadeProvider.GetOrCreate();
+ _weatherDataService = facade.Weather.GetWeatherInfoService();
+ _timeZoneService = facade.Region.GetTimeZoneService();
+ _settingsFacade = facade;
+ }
+
+ private readonly ISettingsFacadeService _settingsFacade;
+
+ public void SaveLayoutAndHide()
+ {
+ SaveLayout();
+ Hide();
- // 仅在 Windows 上启用置底功能
- if (OperatingSystem.IsWindows())
+ // Remove all components so that next time we open it builds fresh from snapshot
+ if (Content is Canvas canvas)
{
- _bottomMostService.SetupBottomMost(this);
+ canvas.Children.Clear();
}
+ _componentHosts.Clear();
}
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
- if (OperatingSystem.IsWindows())
+ if (Screens.Primary is { } primaryScreen)
{
- _bottomMostService.SendToBottom(this);
+ // 避开系统任务栏
+ var workArea = primaryScreen.WorkingArea;
+ var scaling = primaryScreen.Scaling;
+ Position = new PixelPoint(workArea.X, workArea.Y);
+ Width = workArea.Width / scaling;
+ Height = workArea.Height / scaling;
+ }
+
+ if (Content is Canvas canvas)
+ {
+ // 保证透明区域也能被抓取事件
+ canvas.Background = new SolidColorBrush(Color.FromArgb(1, 0, 0, 0));
}
// 确保注册表已初始化
@@ -147,16 +166,7 @@ public partial class TransparentOverlayWindow : Window
///
private void UpdateInteractiveRegions()
{
- _interactiveRegions.Clear();
-
- foreach (var host in _componentHosts.Values)
- {
- var x = Canvas.GetLeft(host);
- var y = Canvas.GetTop(host);
- _interactiveRegions.Add(new Rect(x, y, host.Width, host.Height));
- }
-
- _regionPassthroughService.SetInteractiveRegions(this, _interactiveRegions);
+ // 编辑模式下不再需要底层穿透功能计算,这里留空或移除
}
///