You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
287 lines
10 KiB
287 lines
10 KiB
using System;
|
|
using System.Runtime.InteropServices;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Controls.Primitives;
|
|
using Avalonia.Input;
|
|
using Avalonia.Markup.Xaml;
|
|
using ReactiveUI.Avalonia;
|
|
using Microsoft.Extensions.Logging;
|
|
using AuroraDesk.Presentation.ViewModels;
|
|
using AuroraDesk.Presentation.Views.Dialogs;
|
|
using ReactiveUI;
|
|
using System.Reactive.Disposables;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace AuroraDesk.Presentation.Views;
|
|
|
|
/// <summary>
|
|
/// 主窗口
|
|
/// </summary>
|
|
public partial class MainWindow : ReactiveWindow<MainWindowViewModel>, IActivatableView
|
|
{
|
|
private readonly ILogger<MainWindow>? _logger;
|
|
private bool _isClosingConfirmed = false;
|
|
private bool _isDragging = false;
|
|
private Avalonia.PixelPoint _dragStartScreenPoint;
|
|
private Avalonia.PixelPoint _dragStartWindowPosition;
|
|
private Avalonia.Point _lastWindowPoint;
|
|
|
|
/// <summary>
|
|
/// 无参构造函数,用于 XAML 设计器
|
|
/// </summary>
|
|
public MainWindow()
|
|
{
|
|
InitializeComponent();
|
|
WindowState = WindowState.Maximized;
|
|
SetupWindowControls();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 构造函数,接受依赖注入的 ViewModel
|
|
/// </summary>
|
|
/// <param name="viewModel">主窗口的 ViewModel</param>
|
|
/// <param name="logger">日志记录器</param>
|
|
public MainWindow(MainWindowViewModel viewModel, ILogger<MainWindow>? logger = null)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(viewModel);
|
|
|
|
_logger = logger;
|
|
InitializeComponent();
|
|
ViewModel = viewModel;
|
|
WindowState = WindowState.Maximized;
|
|
SetupWindowControls();
|
|
|
|
_logger?.LogInformation("MainWindow 已创建,ViewModel 已设置");
|
|
|
|
// 处理窗口关闭事件,显示确认框
|
|
this.Closing += OnWindowClosing;
|
|
|
|
// 使用 WhenActivated 管理订阅
|
|
this.WhenActivated(disposables =>
|
|
{
|
|
// 注册关闭确认对话框 Interaction Handler
|
|
if (ViewModel != null)
|
|
{
|
|
disposables.Add(
|
|
ViewModel.CloseConfirmInteraction
|
|
.RegisterHandler(async interaction =>
|
|
{
|
|
var dialog = new CloseConfirmDialog(interaction.Input);
|
|
|
|
// 在 Linux 上,确保窗口在显示前已准备好
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
|
{
|
|
// 先准备好窗口,但不显示
|
|
dialog.ShowActivated = false;
|
|
dialog.Opacity = 0;
|
|
|
|
// 等待窗口初始化完成
|
|
await System.Threading.Tasks.Task.Delay(10);
|
|
|
|
// 触发布局,确保内容已渲染
|
|
dialog.InvalidateMeasure();
|
|
dialog.InvalidateArrange();
|
|
|
|
// 等待布局和渲染完成
|
|
await System.Threading.Tasks.Task.Delay(50);
|
|
|
|
// 设置透明度为1,准备显示
|
|
dialog.Opacity = 1;
|
|
dialog.ShowActivated = true;
|
|
}
|
|
|
|
var result = await dialog.ShowDialog<bool>(this);
|
|
interaction.SetOutput(result);
|
|
}));
|
|
}
|
|
});
|
|
}
|
|
|
|
private void InitializeComponent()
|
|
{
|
|
AvaloniaXamlLoader.Load(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置窗口控制按钮事件
|
|
/// </summary>
|
|
private void SetupWindowControls()
|
|
{
|
|
// 设置左侧导航栏拖动功能
|
|
var leftNavigationBar = this.FindControl<Border>("LeftNavigationBar");
|
|
if (leftNavigationBar != null)
|
|
{
|
|
leftNavigationBar.PointerPressed += OnLeftNavigationBarPointerPressed;
|
|
}
|
|
|
|
// Linux 平台需要在窗口级别处理鼠标移动和释放事件
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
|
{
|
|
this.PointerMoved += OnWindowPointerMoved;
|
|
this.PointerReleased += OnWindowPointerReleased;
|
|
}
|
|
|
|
// 最小化按钮
|
|
var minimizeButton = this.FindControl<Button>("MinimizeButton");
|
|
if (minimizeButton != null)
|
|
{
|
|
minimizeButton.Click += (sender, e) => WindowState = WindowState.Minimized;
|
|
}
|
|
|
|
// 最大化/还原按钮
|
|
var maximizeButton = this.FindControl<Button>("MaximizeButton");
|
|
if (maximizeButton != null)
|
|
{
|
|
maximizeButton.Click += (sender, e) =>
|
|
{
|
|
if (WindowState == WindowState.Maximized)
|
|
{
|
|
WindowState = WindowState.Normal;
|
|
ToolTip.SetTip(maximizeButton, "最大化");
|
|
}
|
|
else
|
|
{
|
|
WindowState = WindowState.Maximized;
|
|
ToolTip.SetTip(maximizeButton, "还原");
|
|
}
|
|
};
|
|
}
|
|
|
|
// 关闭按钮 - 显示确认框而不是直接关闭
|
|
var closeButton = this.FindControl<Button>("CloseButton");
|
|
if (closeButton != null)
|
|
{
|
|
if (ViewModel != null)
|
|
{
|
|
// 关闭按钮点击时显示确认框
|
|
closeButton.Click += async (sender, e) =>
|
|
{
|
|
var confirmed = await ViewModel.RequestCloseConfirmAsync();
|
|
if (confirmed)
|
|
{
|
|
// 设置标志,表示用户已确认关闭
|
|
_isClosingConfirmed = true;
|
|
Close();
|
|
}
|
|
};
|
|
}
|
|
else
|
|
{
|
|
// 设计器模式或无 ViewModel 时,直接关闭(用于设计器预览)
|
|
closeButton.Click += (sender, e) => Close();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 处理左侧导航栏鼠标按下事件,实现窗口拖动
|
|
/// </summary>
|
|
private void OnLeftNavigationBarPointerPressed(object? sender, PointerPressedEventArgs e)
|
|
{
|
|
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
|
{
|
|
// Linux 平台需要手动实现拖动
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
|
{
|
|
_isDragging = true;
|
|
// 记录按下时的窗口位置和鼠标屏幕坐标
|
|
var pointerPoint = e.GetCurrentPoint(this);
|
|
var windowPoint = pointerPoint.Position;
|
|
// 计算鼠标的屏幕坐标:窗口位置 + 鼠标在窗口内的位置
|
|
_dragStartScreenPoint = new Avalonia.PixelPoint(
|
|
Position.X + (int)Math.Round(windowPoint.X),
|
|
Position.Y + (int)Math.Round(windowPoint.Y));
|
|
_dragStartWindowPosition = Position;
|
|
_lastWindowPoint = windowPoint;
|
|
// 捕获指针到窗口,确保拖动流畅
|
|
pointerPoint.Pointer.Capture(this);
|
|
e.Handled = true;
|
|
}
|
|
else
|
|
{
|
|
// Windows 和其他平台使用系统方法
|
|
BeginMoveDrag(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 处理窗口鼠标移动事件(仅 Linux 平台)
|
|
/// </summary>
|
|
private void OnWindowPointerMoved(object? sender, PointerEventArgs e)
|
|
{
|
|
if (_isDragging && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
|
{
|
|
// 获取当前鼠标在窗口内的位置
|
|
var currentPoint = e.GetCurrentPoint(this);
|
|
var currentWindowPoint = currentPoint.Position;
|
|
|
|
// 计算鼠标在窗口内的移动量(相对于上次记录的位置)
|
|
var deltaWindowX = currentWindowPoint.X - _lastWindowPoint.X;
|
|
var deltaWindowY = currentWindowPoint.Y - _lastWindowPoint.Y;
|
|
|
|
// 只有当鼠标在窗口内实际移动了超过0.5像素时才更新
|
|
if (Math.Abs(deltaWindowX) > 0.5 || Math.Abs(deltaWindowY) > 0.5)
|
|
{
|
|
// 根据鼠标在窗口内的移动量更新窗口位置
|
|
var newX = Position.X + (int)Math.Round(deltaWindowX);
|
|
var newY = Position.Y + (int)Math.Round(deltaWindowY);
|
|
var newPosition = new Avalonia.PixelPoint(newX, newY);
|
|
|
|
// 更新窗口位置
|
|
Position = newPosition;
|
|
|
|
// 更新记录的上次鼠标位置
|
|
_lastWindowPoint = currentWindowPoint;
|
|
}
|
|
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 处理窗口鼠标释放事件(仅 Linux 平台)
|
|
/// </summary>
|
|
private void OnWindowPointerReleased(object? sender, PointerReleasedEventArgs e)
|
|
{
|
|
if (_isDragging)
|
|
{
|
|
_isDragging = false;
|
|
// 释放指针捕获
|
|
var pointerPoint = e.GetCurrentPoint(this);
|
|
pointerPoint.Pointer.Capture(null);
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 处理窗口关闭事件,显示确认框
|
|
/// </summary>
|
|
private async void OnWindowClosing(object? sender, WindowClosingEventArgs e)
|
|
{
|
|
// 如果用户已经通过关闭按钮确认关闭,直接允许关闭
|
|
if (_isClosingConfirmed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (ViewModel != null)
|
|
{
|
|
// 取消默认关闭行为
|
|
e.Cancel = true;
|
|
|
|
// 显示确认框
|
|
var confirmed = await ViewModel.RequestCloseConfirmAsync();
|
|
if (confirmed)
|
|
{
|
|
// 用户确认关闭,设置标志并允许窗口关闭
|
|
_isClosingConfirmed = true;
|
|
e.Cancel = false;
|
|
// 不需要手动调用 Close(),系统会自动关闭窗口
|
|
}
|
|
// 如果用户取消,e.Cancel = true 已设置,窗口不会关闭
|
|
}
|
|
// 如果没有 ViewModel(设计器模式),允许直接关闭
|
|
}
|
|
}
|
|
|
|
|