mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
fead.桌面组件
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
195
LanMountainDesktop/Services/FusedDesktopManagerService.cs
Normal file
195
LanMountainDesktop/Services/FusedDesktopManagerService.cs
Normal file
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 融合桌面中央管理器服务接口
|
||||
/// </summary>
|
||||
public interface IFusedDesktopManagerService
|
||||
{
|
||||
void Initialize();
|
||||
void EnterEditMode();
|
||||
void ExitEditMode();
|
||||
void ReloadWidgets();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 融合桌面中央管理器服务实现。用于管理常态下的各个小窗口实体。
|
||||
/// </summary>
|
||||
internal sealed class FusedDesktopManagerService : IFusedDesktopManagerService
|
||||
{
|
||||
private readonly IFusedDesktopLayoutService _layoutService;
|
||||
private readonly ISettingsFacadeService _settingsFacade;
|
||||
private readonly Dictionary<string, DesktopWidgetWindow> _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<string>(_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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 工厂
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
23
LanMountainDesktop/Views/DesktopWidgetWindow.axaml
Normal file
23
LanMountainDesktop/Views/DesktopWidgetWindow.axaml
Normal file
@@ -0,0 +1,23 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
x:Class="LanMountainDesktop.Views.DesktopWidgetWindow"
|
||||
Title="Desktop Component"
|
||||
ShowInTaskbar="False"
|
||||
SystemDecorations="None"
|
||||
Background="Transparent"
|
||||
Topmost="False"
|
||||
SizeToContent="WidthAndHeight"
|
||||
TransparencyLevelHint="Transparent"
|
||||
RenderOptions.BitmapInterpolationMode="HighQuality"
|
||||
CanResize="False">
|
||||
|
||||
<Border x:Name="ComponentContainer"
|
||||
Background="Transparent"
|
||||
CornerRadius="{DynamicResource DesignCornerRadiusComponent}"
|
||||
ClipToBounds="True">
|
||||
<!-- Component control will be injected here -->
|
||||
</Border>
|
||||
</Window>
|
||||
61
LanMountainDesktop/Views/DesktopWidgetWindow.axaml.cs
Normal file
61
LanMountainDesktop/Views/DesktopWidgetWindow.axaml.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using LanMountainDesktop.Services;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace LanMountainDesktop.Views;
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个独立的组件挂载窗口。它不含有任何自己的边窗,仅仅负责包裹组件并将自身植入系统最底层。
|
||||
/// </summary>
|
||||
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<Rect>
|
||||
{
|
||||
new(0, 0, Bounds.Width, Bounds.Height)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:LanMountainDesktop.ViewModels"
|
||||
xmlns:fi="using:FluentIcons.Avalonia"
|
||||
xmlns:local="using:LanMountainDesktop.Views"
|
||||
x:Class="LanMountainDesktop.Views.FusedDesktopComponentLibraryControl">
|
||||
x:Class="LanMountainDesktop.Views.FusedDesktopComponentLibraryControl"
|
||||
x:DataType="vm:ComponentLibraryWindowViewModel">
|
||||
|
||||
<Grid ColumnDefinitions="220,*">
|
||||
<Grid ColumnDefinitions="240,*"
|
||||
ColumnSpacing="12"
|
||||
Margin="0">
|
||||
<!-- 分类列表 (左侧) -->
|
||||
<Border Grid.Column="0"
|
||||
BorderBrush="{DynamicResource AdaptiveBorderBrush}"
|
||||
BorderThickness="0,0,1,0"
|
||||
Padding="12,0,12,12">
|
||||
<Border Classes="surface-translucent-panel"
|
||||
CornerRadius="{DynamicResource DesignCornerRadiusLg}"
|
||||
Padding="10">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<TextBox x:Name="SearchBox"
|
||||
Watermark="搜索组件..."
|
||||
@@ -27,87 +29,132 @@
|
||||
Grid.Row="1"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
SelectionChanged="OnCategorySelectionChanged">
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="CornerRadius" Value="14" />
|
||||
<Setter Property="Margin" Value="0,2" />
|
||||
<Setter Property="Padding" Value="12,10" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
SelectionChanged="OnCategorySelectionChanged"
|
||||
ItemsSource="{Binding Categories}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="local:LibraryCategoryItem">
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<fi:SymbolIcon Symbol="{Binding Icon}" FontSize="18" />
|
||||
<TextBlock Text="{Binding DisplayName}" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<DataTemplate x:DataType="vm:ComponentLibraryCategoryViewModel">
|
||||
<Border Padding="10"
|
||||
Margin="0,0,0,6"
|
||||
CornerRadius="{DynamicResource DesignCornerRadiusSm}"
|
||||
Background="{DynamicResource AdaptiveNavItemBackgroundBrush}">
|
||||
<Grid ColumnDefinitions="Auto,*"
|
||||
ColumnSpacing="8">
|
||||
<fi:SymbolIcon Symbol="{Binding Icon}"
|
||||
IconVariant="Regular"
|
||||
FontSize="16" />
|
||||
<TextBlock Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Text="{Binding Title}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
|
||||
<!-- 组件网格 (右侧) -->
|
||||
<ScrollViewer Grid.Column="1" Padding="20">
|
||||
<ItemsControl x:Name="ComponentItemsControl">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="local:LibraryComponentItem">
|
||||
<Button Classes="unstyled-card"
|
||||
Width="260"
|
||||
Margin="0,0,16,16"
|
||||
Padding="0"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Click="OnAddComponentClick"
|
||||
Tag="{Binding Id}">
|
||||
<Border Classes="card"
|
||||
Background="{DynamicResource AdaptiveSurfaceLowBrush}"
|
||||
BorderBrush="{DynamicResource AdaptiveBorderBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="24"
|
||||
ClipToBounds="True">
|
||||
<Grid RowDefinitions="Auto,Auto">
|
||||
<!-- 预览区域 (动态填充预览) -->
|
||||
<Border x:Name="PreviewHost"
|
||||
Height="150"
|
||||
Background="{DynamicResource AdaptiveSurfaceNeutralBrush}"
|
||||
Margin="8"
|
||||
CornerRadius="16"
|
||||
ClipToBounds="True">
|
||||
<Panel>
|
||||
<!-- 这里将显示组件的缩放预览 -->
|
||||
<ContentPresenter Content="{Binding PreviewContent}" />
|
||||
|
||||
<!-- 空状态或加载中图标 -->
|
||||
<fi:SymbolIcon Symbol="Cube"
|
||||
FontSize="32"
|
||||
Opacity="0.1"
|
||||
IsVisible="{Binding !HasPreview}" />
|
||||
</Panel>
|
||||
<Border Grid.Column="1"
|
||||
Classes="surface-translucent-strong"
|
||||
CornerRadius="{DynamicResource DesignCornerRadiusLg}"
|
||||
Padding="10">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled">
|
||||
<ItemsControl x:Name="ComponentItemsControl"
|
||||
ItemsSource="{Binding Components}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:ComponentLibraryItemViewModel">
|
||||
<Border Width="240"
|
||||
Height="220"
|
||||
Margin="6"
|
||||
CornerRadius="{DynamicResource DesignCornerRadiusComponent}"
|
||||
Padding="10"
|
||||
Background="{DynamicResource AdaptiveSurfaceRaisedBrush}"
|
||||
BorderBrush="{DynamicResource AdaptiveButtonBorderBrush}"
|
||||
BorderThickness="1">
|
||||
<Grid RowDefinitions="*,Auto,Auto"
|
||||
RowSpacing="8">
|
||||
<!-- 预览区域 -->
|
||||
<Border CornerRadius="{DynamicResource DesignCornerRadiusSm}"
|
||||
Background="{DynamicResource AdaptiveGlassPanelBackgroundBrush}"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource AdaptiveGlassPanelBorderBrush}"
|
||||
Padding="8">
|
||||
<Grid>
|
||||
<Image Source="{Binding PreviewBitmap}"
|
||||
Stretch="Uniform"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
RenderOptions.BitmapInterpolationMode="HighQuality"
|
||||
IsVisible="{Binding IsPreviewReady}" />
|
||||
|
||||
<!-- 加载中状态 -->
|
||||
<Border IsVisible="{Binding IsPreviewPending}"
|
||||
Background="{DynamicResource AdaptiveSurfaceBaseBrush}">
|
||||
<StackPanel HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="8">
|
||||
<ProgressBar Width="96"
|
||||
IsIndeterminate="True" />
|
||||
<TextBlock HorizontalAlignment="Center"
|
||||
TextAlignment="Center"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Text="{Binding PreviewStatusText}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 失败状态 -->
|
||||
<Border IsVisible="{Binding IsPreviewFailed}"
|
||||
Background="{DynamicResource AdaptiveSurfaceBaseBrush}">
|
||||
<StackPanel HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="8">
|
||||
<TextBlock HorizontalAlignment="Center"
|
||||
TextAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Text="{Binding PreviewStatusText}" />
|
||||
<TextBlock HorizontalAlignment="Center"
|
||||
TextAlignment="Center"
|
||||
FontSize="12"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Text="{Binding PreviewErrorMessage}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 文字说明 -->
|
||||
<StackPanel Grid.Row="1" Margin="16,8,16,16" Spacing="4">
|
||||
<TextBlock Text="{Binding DisplayName}"
|
||||
FontWeight="SemiBold"
|
||||
FontSize="15" />
|
||||
<TextBlock Text="{Binding Description}"
|
||||
Opacity="0.6"
|
||||
FontSize="12"
|
||||
TextWrapping="Wrap"
|
||||
MaxLines="2" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- 组件名称 -->
|
||||
<TextBlock Grid.Row="1"
|
||||
HorizontalAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Text="{Binding DisplayName}" />
|
||||
|
||||
<!-- 添加按钮 -->
|
||||
<Button Grid.Row="2"
|
||||
HorizontalAlignment="Center"
|
||||
Padding="12,6"
|
||||
Tag="{Binding ComponentId}"
|
||||
Click="OnAddComponentClick">
|
||||
<TextBlock Text="添加到桌面" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -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<string>? AddComponentRequested;
|
||||
|
||||
private readonly ObservableCollection<LibraryCategoryItem> _categories = new();
|
||||
private readonly ObservableCollection<LibraryComponentItem> _components = new();
|
||||
private readonly ComponentLibraryWindowViewModel _viewModel = new();
|
||||
private List<DesktopComponentDefinition> _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<string, (string Display, string Icon)>
|
||||
_viewModel.Categories.Clear();
|
||||
_viewModel.Components.Clear();
|
||||
|
||||
// 添加"全部组件"分类
|
||||
_viewModel.Categories.Add(new ComponentLibraryCategoryViewModel(
|
||||
"all",
|
||||
"全部组件",
|
||||
Symbol.Apps,
|
||||
Array.Empty<ComponentLibraryItemViewModel>()));
|
||||
|
||||
var categoryMap = new Dictionary<string, (string Display, Symbol Icon)>
|
||||
{
|
||||
{ "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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using LanMountainDesktop.ComponentSystem;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -189,50 +189,21 @@
|
||||
Classes="surface-solid-strong"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Margin="52"
|
||||
MaxWidth="760"
|
||||
MaxHeight="520"
|
||||
CornerRadius="36"
|
||||
Padding="14">
|
||||
<Border.RenderTransform>
|
||||
<TranslateTransform Y="42" />
|
||||
</Border.RenderTransform>
|
||||
<Grid RowDefinitions="Auto,*"
|
||||
RowSpacing="10">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto"
|
||||
ColumnSpacing="8">
|
||||
<Button x:Name="LauncherFolderBackButton"
|
||||
Grid.Column="0"
|
||||
Width="38"
|
||||
Height="34"
|
||||
Padding="0"
|
||||
Click="OnLauncherFolderBackClick">
|
||||
<fi:FluentIcon Icon="ArrowLeft"
|
||||
IconVariant="Regular" />
|
||||
</Button>
|
||||
<TextBlock x:Name="LauncherFolderTitleTextBlock"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="SemiBold" />
|
||||
<Button x:Name="LauncherFolderCloseButton"
|
||||
Grid.Column="2"
|
||||
Width="38"
|
||||
Height="34"
|
||||
Padding="0"
|
||||
Click="OnLauncherFolderCloseClick">
|
||||
<fi:FluentIcon Icon="Dismiss"
|
||||
IconVariant="Regular" />
|
||||
</Button>
|
||||
</Grid>
|
||||
Width="464"
|
||||
Height="384"
|
||||
CornerRadius="24"
|
||||
Padding="16,14,16,12">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<TextBlock x:Name="LauncherFolderTitleTextBlock"
|
||||
FontSize="15"
|
||||
FontWeight="SemiBold"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,0,0,10" />
|
||||
|
||||
<ScrollViewer x:Name="LauncherFolderScrollViewer"
|
||||
Grid.Row="1"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled">
|
||||
<WrapPanel x:Name="LauncherFolderTilePanel"
|
||||
Orientation="Horizontal" />
|
||||
</ScrollViewer>
|
||||
<Grid x:Name="LauncherFolderGridPanel"
|
||||
Grid.Row="1"
|
||||
ColumnDefinitions="*,*,*,*"
|
||||
RowDefinitions="*,*,*" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="LanMountainDesktop.Views.TransparentOverlayWindow"
|
||||
WindowState="FullScreen"
|
||||
SystemDecorations="None"
|
||||
CanResize="False"
|
||||
ShowInTaskbar="False"
|
||||
|
||||
@@ -22,9 +22,6 @@ namespace LanMountainDesktop.Views;
|
||||
/// </summary>
|
||||
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
|
||||
/// </summary>
|
||||
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);
|
||||
// 编辑模式下不再需要底层穿透功能计算,这里留空或移除
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user