using System; using System.Collections.Generic; using System.Diagnostics; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Media; using LanMountainDesktop.Models; using LanMountainDesktop.PluginSdk; using LanMountainDesktop.Services; using LanMountainDesktop.Services.Settings; using LanMountainDesktop.ComponentSystem; using LanMountainDesktop.Views.Components; namespace LanMountainDesktop.Views; /// /// 透明覆盖层窗口 - 作为"负一屏"显示在 Windows 桌面上 /// 支持在系统桌面上自由摆放组件 /// public partial class TransparentOverlayWindow : Window { private readonly IFusedDesktopLayoutService _layoutService = FusedDesktopLayoutServiceProvider.GetOrCreate(); private readonly IWindowBottomMostService _bottomMostService = WindowBottomMostServiceFactory.GetOrCreate(); private readonly IRegionPassthroughService _regionPassthroughService = RegionPassthroughServiceFactory.GetOrCreate(); // 滑动状态 private bool _isSwipeActive; private bool _isSwipeDirectionLocked; private Point _swipeStartPoint; private Point _swipeCurrentPoint; private Point _swipeLastPoint; private double _swipeVelocityX; private long _swipeLastTimestamp; // 三指/右键拖动状态 private bool _isThreeFingerOrRightDragSwipeActive; private readonly HashSet _activePointerIds = []; // 组件管理 private readonly Dictionary _componentHosts = []; private readonly List _interactiveRegions = []; private FusedDesktopLayoutSnapshot _layout = new(); private ComponentRegistry? _componentRegistry; private DesktopComponentRuntimeRegistry? _componentRuntimeRegistry; // 基础服务 private readonly IWeatherInfoService _weatherDataService; private readonly TimeZoneService _timeZoneService; private readonly IRecommendationInfoService _recommendationInfoService = new RecommendationDataService(); private readonly ICalculatorDataService _calculatorDataService = new CalculatorDataService(); // 渲染参数 private const double DefaultCellSize = 100; private double _currentDesktopCellSize; // 拖拽与缩放状态 private bool _isDragging; private bool _isResizing; private string? _interactionPlacementId; private Point _interactionStartPoint; private double _interactionOriginalX; private double _interactionOriginalY; private double _interactionOriginalWidth; private double _interactionOriginalHeight; private Border? _interactionHost; // 选中状态 private Border? _selectedHost; public event EventHandler? RestoreMainWindowRequested; public TransparentOverlayWindow() { InitializeComponent(); var facade = HostSettingsFacadeProvider.GetOrCreate(); _weatherDataService = facade.Weather.GetWeatherInfoService(); _timeZoneService = facade.Region.GetTimeZoneService(); _settingsFacade = facade; if (OperatingSystem.IsWindows()) { _bottomMostService.SetupBottomMost(this); } } private readonly ISettingsFacadeService _settingsFacade; public void SaveLayoutAndHide() { SaveLayout(); _regionPassthroughService.ClearInteractiveRegions(this); Hide(); // Remove all components so that next time we open it builds fresh from snapshot if (Content is Canvas canvas) { canvas.Children.Clear(); } _componentHosts.Clear(); } protected override void OnOpened(EventArgs e) { base.OnOpened(e); if (Screens.Primary is { } primaryScreen) { // 避开系统任务栏 var workArea = primaryScreen.WorkingArea; var scaling = primaryScreen.Scaling; Position = new PixelPoint(workArea.X, workArea.Y); Width = workArea.Width / scaling; Height = workArea.Height / scaling; // 基于设置计算单元格尺寸 var appSnapshot = _settingsFacade.Settings.LoadSnapshot(SettingsScope.App); var shortCells = Math.Clamp(appSnapshot.GridShortSideCells > 0 ? appSnapshot.GridShortSideCells : 12, 6, 96); _currentDesktopCellSize = Height / shortCells; } else { _currentDesktopCellSize = DefaultCellSize; } if (Content is Canvas canvas) { // 保证透明区域也能被抓取事件 canvas.Background = new SolidColorBrush(Color.FromArgb(1, 0, 0, 0)); } // 确保注册表已初始化 EnsureRegistries(); // 加载布局并渲染 _layout = _layoutService.Load(); RenderAllComponents(); AppLogger.Info("TransparentOverlay", $"Opened with {_layout.ComponentPlacements.Count} components."); if (OperatingSystem.IsWindows()) { _bottomMostService.SendToBottom(this); } } /// /// 确保组件运行时注册表已初始化 /// 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); } /// /// 渲染所有布局中的组件 /// private void RenderAllComponents() { if (Content is not Canvas canvas) return; canvas.Children.Clear(); _componentHosts.Clear(); _selectedHost = null; foreach (var placement in _layout.ComponentPlacements) { try { RenderComponentInternal(placement); } catch (Exception ex) { AppLogger.Warn("TransparentOverlay", $"Failed to render component {placement.ComponentId}", ex); } } UpdateInteractiveRegions(); } protected override void OnClosed(EventArgs e) { SaveLayout(); base.OnClosed(e); } /// /// 更新可交互区域 /// private void UpdateInteractiveRegions() { _interactiveRegions.Clear(); foreach (var host in _componentHosts.Values) { var left = Canvas.GetLeft(host); var top = Canvas.GetTop(host); var width = host.Width > 0 ? host.Width : host.Bounds.Width; var height = host.Height > 0 ? host.Height : host.Bounds.Height; if (width <= 0 || height <= 0) { continue; } // 稍微向外扩一圈,确保拖拽和右下角缩放手柄也能命中。 _interactiveRegions.Add(new Rect(left - 12, top - 12, width + 24, height + 24)); } _regionPassthroughService.SetInteractiveRegions(this, _interactiveRegions); } /// /// 保存布局 /// private void SaveLayout() { _layoutService.Save(_layout); } /// /// 添加组件(供外部调用) /// public void AddComponent(string componentId, double x, double y, double? width = null, double? height = null) { EnsureRegistries(); if (_componentRegistry == null || !_componentRegistry.TryGetDefinition(componentId, out var definition)) { AppLogger.Warn("TransparentOverlay", $"Cannot add unknown component: {componentId}"); return; } var finalWidth = width ?? (definition.MinWidthCells * _currentDesktopCellSize); var finalHeight = height ?? (definition.MinHeightCells * _currentDesktopCellSize); // 对齐网格 x = Math.Round(x / _currentDesktopCellSize) * _currentDesktopCellSize; y = Math.Round(y / _currentDesktopCellSize) * _currentDesktopCellSize; finalWidth = Math.Round(finalWidth / _currentDesktopCellSize) * _currentDesktopCellSize; finalHeight = Math.Round(finalHeight / _currentDesktopCellSize) * _currentDesktopCellSize; var placementId = Guid.NewGuid().ToString("N"); var placement = new FusedDesktopComponentPlacementSnapshot { PlacementId = placementId, ComponentId = componentId, X = x, Y = y, Width = finalWidth, Height = finalHeight, ZIndex = _layout.ComponentPlacements.Count }; _layout.ComponentPlacements.Add(placement); // 立即渲染 try { RenderComponentInternal(placement); UpdateInteractiveRegions(); SaveLayout(); AppLogger.Info("TransparentOverlay", $"Added component: {componentId} at ({x}, {y}) size ({finalWidth}x{finalHeight})"); } catch (Exception ex) { AppLogger.Warn("TransparentOverlay", $"Failed to add component {componentId}", ex); _layout.ComponentPlacements.Remove(placement); } } /// /// 内部渲染单个组件 /// private void RenderComponentInternal(FusedDesktopComponentPlacementSnapshot placement) { if (_componentRuntimeRegistry is null || !_componentRuntimeRegistry.TryGetDescriptor(placement.ComponentId, out var descriptor)) { AppLogger.Warn("TransparentOverlay", $"Unknown component: {placement.ComponentId}"); return; } // 【修复问题3】尝试从现有窗口中获取组件实例,避免重新创建导致状态丢失 var control = TryGetExistingControl(placement.PlacementId); if (control is null) { // 如果没有现有实例,才创建新的 control = descriptor.CreateControl( _currentDesktopCellSize, _timeZoneService, _weatherDataService, _recommendationInfoService, _calculatorDataService, _settingsFacade, placement.PlacementId); } RenderComponent(placement.PlacementId, control, placement.X, placement.Y, placement.Width, placement.Height); } /// /// 【修复问题3】尝试从现有的小窗口中获取组件控件实例 /// private Control? TryGetExistingControl(string placementId) { try { var manager = FusedDesktopManagerServiceFactory.GetOrCreate(); // 通过反射或公共 API 获取现有窗口中的控件 // 这里需要 FusedDesktopManagerService 提供获取控件的方法 // 暂时返回 null,后续需要扩展接口 return null; } catch { return null; } } /// /// 移除组件 /// public void RemoveComponent(string placementId) { if (_componentHosts.TryGetValue(placementId, out var host)) { if (Content is Canvas canvas) { canvas.Children.Remove(host); } _componentHosts.Remove(placementId); } _layout.ComponentPlacements.RemoveAll(p => p.PlacementId == placementId); UpdateInteractiveRegions(); SaveLayout(); } /// /// 渲染组件(从外部传入控件) /// public void RenderComponent(string placementId, Control component, double x, double y, double width, double height) { var grid = new Grid(); grid.Children.Add(component); var resizeHandle = new Border { Width = 24, Height = 24, Background = new Avalonia.Media.SolidColorBrush(Avalonia.Media.Color.Parse("#3B82F6")), CornerRadius = new Avalonia.CornerRadius(12), HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right, VerticalAlignment = Avalonia.Layout.VerticalAlignment.Bottom, Margin = new Avalonia.Thickness(0, 0, -12, -12), Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.BottomRightCorner), Tag = "desktop-component-resize-handle", IsVisible = false }; grid.Children.Add(resizeHandle); var host = new Border { Tag = placementId, Width = width, Height = height, Background = Avalonia.Media.Brushes.Transparent, CornerRadius = new Avalonia.CornerRadius(12), ClipToBounds = false, // 允许把手溢出 BorderBrush = Avalonia.Media.Brushes.Transparent, BorderThickness = new Avalonia.Thickness(3), Child = grid, Classes = { "desktop-component-host" } }; Canvas.SetLeft(host, x); Canvas.SetTop(host, y); host.PointerPressed += OnComponentPointerPressed; host.PointerMoved += OnInteractionPointerMoved; host.PointerReleased += OnInteractionPointerReleased; // 右键上下文菜单(删除组件) host.ContextRequested += OnComponentContextRequested; if (Content is Canvas canvas) { canvas.Children.Add(host); } _componentHosts[placementId] = host; UpdateInteractiveRegions(); } // 组件右键上下文菜单(删除) private void OnComponentContextRequested(object? sender, ContextRequestedEventArgs e) { if (sender is not Border host || host.Tag is not string placementId) return; // 构建上下文菜单 var deleteItem = new MenuItem { Header = "移除组件", Icon = new Avalonia.Controls.TextBlock { Text = "🗑" } }; deleteItem.Click += (_, _) => { RemoveComponent(placementId); AppLogger.Info("TransparentOverlay", $"Component removed via context menu: {placementId}"); }; var menu = new ContextMenu { Items = { deleteItem } }; // 显示在当前控件上 menu.Open(host); e.Handled = true; } // 取消选中 private void OnCanvasPointerPressed(object? sender, PointerPressedEventArgs e) { DeselectComponent(); } // 选中组件 private void SelectComponent(Border host) { if (_selectedHost == host) return; DeselectComponent(); _selectedHost = host; // 渲染选中边框和把手 host.BorderBrush = new Avalonia.Media.SolidColorBrush(Avalonia.Media.Color.Parse("#3B82F6")); host.Classes.Add("desktop-component-host-selected"); if (host.Child is Grid grid) { foreach (var child in grid.Children) { if (child is Control c && c.Tag is string tg && tg == "desktop-component-resize-handle") { c.IsVisible = true; break; } } } } private void DeselectComponent() { if (_selectedHost != null) { _selectedHost.BorderBrush = Avalonia.Media.Brushes.Transparent; _selectedHost.Classes.Remove("desktop-component-host-selected"); if (_selectedHost.Child is Grid grid) { foreach (var child in grid.Children) { if (child is Control c && c.Tag is string tg && tg == "desktop-component-resize-handle") { c.IsVisible = false; break; } } } } _selectedHost = null; } // 组件拖拽与缩放处理 private void OnComponentPointerPressed(object? sender, PointerPressedEventArgs e) { if (sender is not Border host || host.Tag is not string placementId) return; var point = e.GetCurrentPoint(this); if (!point.Properties.IsLeftButtonPressed) return; SelectComponent(host); _interactionPlacementId = placementId; _interactionHost = host; _interactionStartPoint = e.GetPosition(this); // 这里必须用未吸附的原始屏幕位置计算 delta _interactionOriginalX = Canvas.GetLeft(host); _interactionOriginalY = Canvas.GetTop(host); _interactionOriginalWidth = host.Width; _interactionOriginalHeight = host.Height; if (e.Source is Control sourceControl && sourceControl.Tag is string tag && tag == "desktop-component-resize-handle") { _isResizing = true; _isDragging = false; } else { _isDragging = true; _isResizing = false; } e.Pointer.Capture(host); e.Handled = true; } private void OnInteractionPointerMoved(object? sender, PointerEventArgs e) { if ((!_isDragging && !_isResizing) || _interactionHost is null) return; var currentPoint = e.GetPosition(this); var deltaX = currentPoint.X - _interactionStartPoint.X; var deltaY = currentPoint.Y - _interactionStartPoint.Y; if (_isDragging) { var rawX = _interactionOriginalX + deltaX; var rawY = _interactionOriginalY + deltaY; var snapX = Math.Round(rawX / _currentDesktopCellSize) * _currentDesktopCellSize; var snapY = Math.Round(rawY / _currentDesktopCellSize) * _currentDesktopCellSize; Canvas.SetLeft(_interactionHost, snapX); Canvas.SetTop(_interactionHost, snapY); } else if (_isResizing) { var rawWidth = _interactionOriginalWidth + deltaX; var rawHeight = _interactionOriginalHeight + deltaY; var snapWidth = Math.Round(rawWidth / _currentDesktopCellSize) * _currentDesktopCellSize; var snapHeight = Math.Round(rawHeight / _currentDesktopCellSize) * _currentDesktopCellSize; // 防溢出与极小值保护 snapWidth = Math.Max(_currentDesktopCellSize, snapWidth); snapHeight = Math.Max(_currentDesktopCellSize, snapHeight); _interactionHost.Width = snapWidth; _interactionHost.Height = snapHeight; } e.Handled = true; } private void OnInteractionPointerReleased(object? sender, PointerReleasedEventArgs e) { if ((!_isDragging && !_isResizing) || _interactionHost is null || _interactionPlacementId is null) { _isDragging = false; _isResizing = false; return; } // 更新布局中的位置与尺寸 var placement = _layout.ComponentPlacements.Find(p => p.PlacementId == _interactionPlacementId); if (placement is not null) { placement.X = Canvas.GetLeft(_interactionHost); placement.Y = Canvas.GetTop(_interactionHost); placement.Width = _interactionHost.Width; placement.Height = _interactionHost.Height; } UpdateInteractiveRegions(); SaveLayout(); _isDragging = false; _isResizing = false; _interactionPlacementId = null; _interactionHost = null; e.Pointer.Capture(null); e.Handled = true; } // 三指滑动处理 protected override void OnPointerPressed(PointerPressedEventArgs e) { var appSnapshot = _settingsFacade.Settings.LoadSnapshot(SettingsScope.App); if (!appSnapshot.EnableThreeFingerSwipe) { base.OnPointerPressed(e); return; } if (!TryGetPointerPosition(e, out var pointerPos)) { base.OnPointerPressed(e); return; } var currentPoint = e.GetCurrentPoint(this); var pointerId = e.Pointer?.Id ?? 0; var isRightButtonPressed = currentPoint.Properties.IsRightButtonPressed; var isLeftButtonPressed = currentPoint.Properties.IsLeftButtonPressed; if (isLeftButtonPressed || isRightButtonPressed) { _activePointerIds.Add(pointerId); } var isThreeFinger = _activePointerIds.Count >= 3; var isRightDrag = isRightButtonPressed; if (isThreeFinger || isRightDrag) { _isSwipeActive = true; _isThreeFingerOrRightDragSwipeActive = true; _isSwipeDirectionLocked = false; _swipeStartPoint = pointerPos; _swipeCurrentPoint = pointerPos; _swipeLastPoint = pointerPos; _swipeVelocityX = 0; _swipeLastTimestamp = Stopwatch.GetTimestamp(); e.Handled = true; } else { base.OnPointerPressed(e); } } protected override void OnPointerMoved(PointerEventArgs e) { if (!_isSwipeActive) { base.OnPointerMoved(e); return; } if (!TryGetPointerPosition(e, out var pointerPos)) { base.OnPointerMoved(e); return; } _swipeCurrentPoint = pointerPos; UpdateSwipeVelocity(pointerPos); var deltaX = _swipeCurrentPoint.X - _swipeStartPoint.X; var deltaY = _swipeCurrentPoint.Y - _swipeStartPoint.Y; if (!_isSwipeDirectionLocked) { const double activationThreshold = 14; const double horizontalBias = 1.15; var absDeltaX = Math.Abs(deltaX); var absDeltaY = Math.Abs(deltaY); if (absDeltaY >= activationThreshold && absDeltaY > absDeltaX * horizontalBias) { CancelSwipeInteraction(e.Pointer); base.OnPointerMoved(e); return; } if (absDeltaX < activationThreshold || absDeltaX <= absDeltaY * horizontalBias) { base.OnPointerMoved(e); return; } _isSwipeDirectionLocked = true; if (e.Pointer?.Captured != this) { e.Pointer?.Capture(this); } } e.Handled = true; } protected override void OnPointerReleased(PointerReleasedEventArgs e) { var pointerId = e.Pointer?.Id ?? 0; _activePointerIds.Remove(pointerId); if (_isSwipeActive) { if (EndSwipeInteraction(e.Pointer)) { e.Handled = true; return; } } base.OnPointerReleased(e); } protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e) { var pointerId = e.Pointer?.Id ?? 0; _activePointerIds.Remove(pointerId); if (_isSwipeActive) { EndSwipeInteraction(e.Pointer); } base.OnPointerCaptureLost(e); } private bool TryGetPointerPosition(PointerEventArgs e, out Point point) { try { point = e.GetPosition(this); return true; } catch { point = default; return false; } } private void UpdateSwipeVelocity(Point currentPoint) { var now = Stopwatch.GetTimestamp(); var elapsed = Stopwatch.GetElapsedTime(_swipeLastTimestamp, now).TotalSeconds; if (elapsed > 0) { var dx = currentPoint.X - _swipeLastPoint.X; _swipeVelocityX = dx / elapsed; } _swipeLastPoint = currentPoint; _swipeLastTimestamp = now; } private void CancelSwipeInteraction(IPointer? pointer) { if (!_isSwipeActive) return; if (pointer?.Captured == this) { pointer?.Capture(null); } _isSwipeActive = false; _isSwipeDirectionLocked = false; _isThreeFingerOrRightDragSwipeActive = false; _activePointerIds.Clear(); _swipeVelocityX = 0; _swipeLastTimestamp = 0; } private bool EndSwipeInteraction(IPointer? pointer) { if (!_isSwipeActive) return false; var wasDirectionLocked = _isSwipeDirectionLocked; var wasThreeFingerOrRightDrag = _isThreeFingerOrRightDragSwipeActive; _isSwipeActive = false; _isSwipeDirectionLocked = false; _isThreeFingerOrRightDragSwipeActive = false; _activePointerIds.Clear(); if (pointer?.Captured == this) { pointer?.Capture(null); } _swipeLastTimestamp = 0; if (!wasDirectionLocked) { _swipeVelocityX = 0; return false; } var deltaX = _swipeCurrentPoint.X - _swipeStartPoint.X; var deltaY = _swipeCurrentPoint.Y - _swipeStartPoint.Y; var absDeltaX = Math.Abs(deltaX); var distanceThreshold = Math.Max(48, this.Bounds.Width * 0.14); var velocityThreshold = Math.Max(860, this.Bounds.Width * 1.08); var hasDistanceIntent = absDeltaX >= distanceThreshold && absDeltaX > Math.Abs(deltaY) * 1.05; var hasVelocityIntent = Math.Abs(_swipeVelocityX) >= velocityThreshold; // 向左滑动回到第一页 if (wasThreeFingerOrRightDrag && deltaX < 0 && (hasDistanceIntent || hasVelocityIntent)) { RestoreMainWindowRequested?.Invoke(this, EventArgs.Empty); _swipeVelocityX = 0; return true; } _swipeVelocityX = 0; return hasDistanceIntent || hasVelocityIntent; } }