using System.Collections.Generic; using Avalonia; using Avalonia.Platform; using Notify.Models; using Notify.ViewModels; using Notify.Views; namespace Notify.Services; /// /// 在常驻进程内管理所有 toast 窗口:创建、按角落堆叠、关闭后重新排布 /// 这是 Rust 版"每条通知一进程 + EnumWindows"的替代——进程内一个列表即可 /// public sealed class ToastManager { private const int Gap = 8; private readonly SettingsService _settings; private readonly List _active = []; private readonly Queue _pending = new(); public ToastManager(SettingsService settings) => _settings = settings; public void Show(ToastRequest request) { var settings = _settings.Current; if (_active.Count >= settings.MaxVisible) { _pending.Enqueue(request); return; } var vm = new ToastViewModel(request); var window = new ToastWindow(vm, settings, request.Sticky, request.TargetHwnd, request.WtRuntimeId, request.IconPath, request.DurationSecondsOverride); window.Closed += OnToastClosed; _active.Add(window); // 先显示(拿到尺寸/屏幕信息),再排布 window.Show(); Arrange(); } private void OnToastClosed(object? sender, System.EventArgs e) { if (sender is ToastWindow w) { w.Closed -= OnToastClosed; _active.Remove(w); } Arrange(); if (_pending.Count > 0 && _active.Count < _settings.Current.MaxVisible) { Show(_pending.Dequeue()); } } /// /// 把所有活动 toast 从指定角落沿垂直方向依次堆叠 /// private void Arrange() { if (_active.Count == 0) { return; } var anchor = _active[0]; var screen = anchor.Screens.ScreenFromWindow(anchor) ?? anchor.Screens.Primary; if (screen is null) { return; } var settings = _settings.Current; var wa = screen.WorkingArea; // 物理像素 var scale = anchor.RenderScaling; var margin = (int)(settings.Margin * scale); var gap = (int)(Gap * scale); // 垂直靠下时向上堆叠,否则从锚点向下堆叠 var stackUp = settings.Vertical == VEdge.Bottom; var cursor = settings.Vertical switch { VEdge.Top => wa.Y + margin, VEdge.Bottom => wa.Bottom - margin, _ => wa.Y + wa.Height / 2, }; foreach (var toast in _active) { var wPx = (int)(toast.Width * scale); var hPx = (int)(toast.Bounds.Height * scale); var x = settings.Horizontal switch { HEdge.Left => wa.X + margin, HEdge.Right => wa.Right - margin - wPx, _ => wa.X + (wa.Width - wPx) / 2, }; int y; if (stackUp) { cursor -= hPx; y = cursor; cursor -= gap; } else { y = cursor; cursor += hPx + gap; } toast.Position = new PixelPoint(x, y); } } }