using System;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.Threading;
using Notify.Models;
using Notify.ViewModels;
namespace Notify.Views;
public partial class ToastWindow : Window
{
private static readonly Color BorderNormal = Color.Parse("#FF4B64B2");
private static readonly Color BorderInput = Color.Parse("#FF00CFCF");
private readonly ToastSettings _settings;
private readonly DispatcherTimer _dismissTimer;
private readonly bool _sticky;
private readonly long _targetHwnd;
private readonly string? _wtRuntimeId;
private Avalonia.Media.Imaging.Bitmap? _appIcon;
private bool _closing;
// 设计器需要的无参构造
public ToastWindow() : this(new ToastViewModel(new ToastRequest { Title = "Title", Message = "Message" }), new ToastSettings(), false, 0, null, null, null)
{
}
public ToastWindow(ToastViewModel vm, ToastSettings settings, bool sticky, long targetHwnd, string? wtRuntimeId, string? iconPath, int? durationOverride)
{
_settings = settings;
_targetHwnd = targetHwnd;
_wtRuntimeId = wtRuntimeId;
var duration = durationOverride ?? settings.DurationSeconds;
// 常驻:请求显式 Sticky,或停留时长 <= 0
_sticky = sticky || duration <= 0;
InitializeComponent();
DataContext = vm;
Width = settings.Width;
Opacity = 0;
// Opacity 过渡用于淡入/淡出
Transitions =
[
new DoubleTransition
{
Property = OpacityProperty,
Duration = TimeSpan.FromMilliseconds(settings.FadeMilliseconds),
Easing = new Avalonia.Animation.Easings.CubicEaseOut(),
},
];
Root.BorderBrush = new SolidColorBrush(vm.InputMode ? BorderInput : BorderNormal);
// 调用方 App 图标,取不到则保留默认 Claude 图标
if (!string.IsNullOrEmpty(iconPath))
{
_appIcon = Notify.Interop.AppIcon.Extract(iconPath);
if (_appIcon is not null)
{
IconImage.Source = _appIcon;
}
}
_dismissTimer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(Math.Max(1, duration)),
};
_dismissTimer.Tick += (_, _) => BeginClose();
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
_appIcon?.Dispose();
_appIcon = null;
}
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
Opacity = _settings.Opacity; // 触发淡入
if (TryGetPlatformHandle()?.Handle is { } hwnd)
{
// 工具窗口:从任务栏与 Alt+Tab 中隐藏
Notify.Interop.Win32.MakeToolWindow(hwnd);
// 跨虚拟桌面:把窗口钉到所有桌面(失败自动忽略)
if (_settings.ShowOnAllDesktops)
{
TryPinWithRetry(hwnd);
}
}
if (!_sticky)
{
_dismissTimer.Start();
}
}
///
/// 窗口刚打开时 shell 可能还没给它登记 ApplicationView(GetViewForHwnd 报
/// TYPE_E_ELEMENTNOTFOUND),故短间隔重试若干次直到成功
///
private void TryPinWithRetry(IntPtr hwnd)
{
if (Notify.Interop.VirtualDesktopPinner.TryPin(hwnd))
{
return;
}
var attempts = 0;
var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(60) };
timer.Tick += (_, _) =>
{
attempts++;
if (_closing || Notify.Interop.VirtualDesktopPinner.TryPin(hwnd) || attempts >= 15)
{
timer.Stop();
}
};
timer.Start();
}
private void OnPointerEntered(object? sender, PointerEventArgs e)
{
// 悬停时暂停自动消失
_dismissTimer.Stop();
if (!_closing)
{
Opacity = _settings.Opacity;
}
}
private void OnPointerExited(object? sender, PointerEventArgs e)
{
if (!_closing && !_sticky)
{
_dismissTimer.Start();
}
}
private void OnBodyPressed(object? sender, PointerPressedEventArgs e)
{
// 左键点击主体:激活原窗口后关闭
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
if (_targetHwnd != 0)
{
var target = new IntPtr(_targetHwnd);
Notify.Interop.WindowActivator.Activate(target);
// 目标是 Windows Terminal 则切回原标签
if (!string.IsNullOrEmpty(_wtRuntimeId))
{
Notify.Interop.WinTerminalTabs.SelectTab(target, _wtRuntimeId);
}
}
BeginClose();
}
}
private void OnCloseClick(object? sender, RoutedEventArgs e) => BeginClose();
///
/// 淡出后再真正关闭
///
private void BeginClose()
{
if (_closing)
{
return;
}
_closing = true;
_dismissTimer.Stop();
Opacity = 0;
var closeTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(_settings.FadeMilliseconds),
};
closeTimer.Tick += (_, _) =>
{
closeTimer.Stop();
Close();
};
closeTimer.Start();
}
}