diff --git a/Ink Canvas/Helpers/InkFadeManager.cs b/Ink Canvas/Helpers/InkFadeManager.cs index eec846ac..a13d1ea6 100644 --- a/Ink Canvas/Helpers/InkFadeManager.cs +++ b/Ink Canvas/Helpers/InkFadeManager.cs @@ -6,6 +6,7 @@ using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; +using System.Windows.Media.Effects; using System.Windows.Shapes; using System.Windows.Threading; @@ -324,6 +325,13 @@ namespace Ink_Canvas.Helpers { path.StrokeThickness = Math.Max(drawingAttribs.Width * 1.5, 20); } + + // 为高亮笔添加轻微的模糊效果,使渐隐更加自然 + path.Effect = new BlurEffect + { + Radius = 0.5, // 轻微的模糊效果 + KernelType = KernelType.Gaussian + }; } // 不设置任何变换,保持墨迹原有粗细 @@ -381,8 +389,7 @@ namespace Ink_Canvas.Helpers { try { - // 对于普通墨迹也使用连续渐隐动画 - StartContinuousFadeAnimation(visual, stroke, currentOpacity, AnimationDuration); + StartProgressiveFadeAnimation(visual, stroke, currentOpacity, AnimationDuration); } catch (Exception ex) { @@ -390,6 +397,57 @@ namespace Ink_Canvas.Helpers } } + /// + /// 统一渐隐动画 - 整个墨迹作为一个整体进行渐隐,与擦除效果一致 + /// + private void StartUnifiedFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity, int duration) + { + try + { + // 创建透明度动画,模拟擦除时的效果 + var fadeAnimation = new DoubleAnimation + { + From = currentOpacity, + To = 0.0, + Duration = TimeSpan.FromMilliseconds(duration), + EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut } + }; + + // 如果是高亮笔,添加轻微的缩放效果,使渐隐更加自然 + if (stroke.DrawingAttributes.IsHighlighter) + { + // 创建轻微的缩放动画,模拟墨迹"蒸发"的效果 + var scaleAnimation = new DoubleAnimation + { + From = 1.0, + To = 0.95, // 轻微缩小,增加自然感 + Duration = TimeSpan.FromMilliseconds(duration), + EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseIn } + }; + + // 创建缩放变换 + var scaleTransform = new ScaleTransform(); + visual.RenderTransform = scaleTransform; + visual.RenderTransformOrigin = new Point(0.5, 0.5); + + // 应用缩放动画 + scaleTransform.BeginAnimation(ScaleTransform.ScaleXProperty, scaleAnimation); + scaleTransform.BeginAnimation(ScaleTransform.ScaleYProperty, scaleAnimation); + } + + // 添加动画完成事件 + fadeAnimation.Completed += (sender, e) => OnAnimationCompleted(visual, stroke); + + // 应用透明度动画 + visual.BeginAnimation(UIElement.OpacityProperty, fadeAnimation); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"统一渐隐动画失败: {ex}", LogHelper.LogType.Error); + OnAnimationCompleted(visual, stroke); + } + } + /// /// 开始高亮笔的渐隐动画 /// @@ -397,8 +455,8 @@ namespace Ink_Canvas.Helpers { try { - // 使用连续渐隐动画而不是分段动画 - StartContinuousFadeAnimation(visual, stroke, currentOpacity, (int)(AnimationDuration * 1.5)); + // 高亮笔使用统一的渐隐动画,与擦除效果一致 + StartUnifiedFadeAnimation(visual, stroke, currentOpacity, (int)(AnimationDuration * 1.2)); } catch (Exception ex) { @@ -804,98 +862,6 @@ namespace Ink_Canvas.Helpers return curve; } - /// - /// 连续渐隐动画 - 模拟橡皮擦擦除效果 - /// - private void StartContinuousFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity, int duration) - { - try - { - // 获取墨迹的边界 - var geometry = stroke.GetGeometry(); - if (geometry == null) return; - - var bounds = geometry.Bounds; - var strokeStart = stroke.StylusPoints[0].ToPoint(); - var strokeEnd = stroke.StylusPoints[stroke.StylusPoints.Count - 1].ToPoint(); - - // 计算墨迹的方向向量 - var direction = new Vector(strokeEnd.X - strokeStart.X, strokeEnd.Y - strokeStart.Y); - var length = direction.Length; - - if (length == 0) - { - // 如果墨迹没有方向(单点),使用简单渐隐 - StartSimpleFadeAnimation(visual, stroke, currentOpacity, duration); - return; - } - - // 归一化方向向量 - direction.Normalize(); - - // 创建精确的擦除动画 - 使用PathGeometry来避免变形 - CreatePreciseEraseAnimation(visual, stroke, bounds, strokeStart, strokeEnd, direction, length, duration); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"连续渐隐动画失败: {ex}", LogHelper.LogType.Error); - // 失败时回退到简单动画 - StartSimpleFadeAnimation(visual, stroke, currentOpacity, duration); - } - } - - /// - /// 创建精确的擦除动画 - 使用动态裁剪区域避免墨迹移动 - /// - private void CreatePreciseEraseAnimation(UIElement visual, Stroke stroke, Rect bounds, Point strokeStart, Point strokeEnd, Vector direction, double length, int duration) - { - try - { - // 计算擦除方向上的移动距离 - var totalDistance = Math.Sqrt(Math.Pow(strokeEnd.X - strokeStart.X, 2) + Math.Pow(strokeEnd.Y - strokeStart.Y, 2)); - - if (totalDistance == 0) - { - // 如果墨迹没有长度,使用简单渐隐 - StartSimpleFadeAnimation(visual, stroke, visual.Opacity, duration); - return; - } - - // 创建动态裁剪区域 - 从完整墨迹开始,逐渐缩小 - var clipGeometry = new RectangleGeometry - { - Rect = bounds - }; - - visual.Clip = clipGeometry; - - // 创建擦除动画 - 裁剪区域从起点向终点移动并缩小 - // 使用更自然的擦除效果:从起点开始,逐渐向终点移动 - var eraseAnimation = new RectAnimation - { - From = bounds, // 从完整边界开始 - To = new Rect(strokeEnd.X, strokeEnd.Y, 0, bounds.Height), // 到终点位置,宽度为0 - Duration = TimeSpan.FromMilliseconds(duration), - EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut } - }; - - // 添加动画完成事件 - eraseAnimation.Completed += (sender, e) => - { - OnAnimationCompleted(visual, stroke); - }; - - // 开始动画 - clipGeometry.BeginAnimation(RectangleGeometry.RectProperty, eraseAnimation); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"精确擦除动画失败: {ex}", LogHelper.LogType.Error); - // 失败时回退到简单动画 - StartSimpleFadeAnimation(visual, stroke, visual.Opacity, duration); - } - } - /// /// 动画完成后的统一处理 /// diff --git a/Ink Canvas/Helpers/WindowZOrderManager.cs b/Ink Canvas/Helpers/WindowZOrderManager.cs index 898b0c06..2ce292c8 100644 --- a/Ink Canvas/Helpers/WindowZOrderManager.cs +++ b/Ink Canvas/Helpers/WindowZOrderManager.cs @@ -148,31 +148,30 @@ namespace Ink_Canvas.Helpers { if (window == null) return; - lock (_lockObject) + try { - var windowInfo = _windowStack.FirstOrDefault(w => w.Window == window); - if (windowInfo != null) - { - // 更新创建时间,使其成为最新的窗口 - windowInfo.CreatedTime = DateTime.Now; - - // 立即将窗口置顶 - var hwnd = new WindowInteropHelper(window).Handle; - if (hwnd != IntPtr.Zero) - { - SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); - - // 确保窗口样式正确 - int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); - if ((exStyle & WS_EX_TOPMOST) == 0) - { - SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST); - } - } - - ApplyZOrder(); - } + var hwnd = new WindowInteropHelper(window).Handle; + if (hwnd == IntPtr.Zero) return; + + // 使用更直接的方法:先激活窗口,再置顶 + window.Activate(); + window.Focus(); + + // 设置窗口为置顶 + SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); + + // 确保窗口样式正确 + int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); + SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST); + + // 再次确保置顶 + SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"BringToTop失败: {ex.Message}", LogHelper.LogType.Error); } } diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs index fc40ba2b..9cb41455 100644 --- a/Ink Canvas/MainWindow.xaml.cs +++ b/Ink Canvas/MainWindow.xaml.cs @@ -1751,18 +1751,22 @@ namespace Ink_Canvas { try { + var hwnd = new WindowInteropHelper(this).Handle; if (Settings.Advanced.IsAlwaysOnTop) { - // 注册到Z-Order管理器 - WindowZOrderManager.RegisterWindow(this, true, Settings.Advanced.IsNoFocusMode); - // 先设置WPF的Topmost属性 Topmost = true; - // 立即应用置顶 - WindowZOrderManager.BringToTop(this); + // 使用更强的Win32 API调用来确保置顶 + // 1. 设置窗口样式为置顶 + int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); + SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST); - // 如果启用了无焦点模式,需要特殊处理 + // 2. 使用SetWindowPos确保窗口在最顶层 + SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); + + // 3. 如果启用了无焦点模式,需要特殊处理 if (Settings.Advanced.IsNoFocusMode) { // 启动置顶维护定时器 @@ -1777,9 +1781,15 @@ namespace Ink_Canvas else { // 取消置顶时 - WindowZOrderManager.SetWindowTopmost(this, false); - - // 停止置顶维护定时器 + // 1. 先使用Win32 API取消置顶 + SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); + + // 2. 移除置顶窗口样式 + int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); + SetWindowLong(hwnd, GWL_EXSTYLE, exStyle & ~WS_EX_TOPMOST); + + // 3. 停止置顶维护定时器 StopTopmostMaintenance(); // 注意:这里不直接设置Topmost,让其他代码根据模式决定 @@ -1848,14 +1858,30 @@ namespace Ink_Canvas return; } - // 清理无效的窗口记录 - WindowZOrderManager.CleanupInvalidWindows(); - // 检查是否有子窗口在前景 - if (!WindowZOrderManager.HasChildWindowInForeground()) + var foregroundWindow = GetForegroundWindow(); + if (foregroundWindow != hwnd) { - // 没有子窗口在前景,强制刷新所有窗口的置顶状态 - WindowZOrderManager.ForceRefreshAllWindows(); + // 检查前景窗口是否是当前应用程序的子窗口 + var foregroundWindowProcessId = GetWindowThreadProcessId(foregroundWindow, out uint processId); + var currentProcessId = GetCurrentProcessId(); + + if (processId == currentProcessId) + { + // 如果有子窗口在前景,暂停置顶维护 + return; + } + + // 如果窗口不在最顶层且没有子窗口,重新设置置顶 + SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); + + // 确保窗口样式正确 + int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); + if ((exStyle & WS_EX_TOPMOST) == 0) + { + SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST); + } } } catch (Exception ex) diff --git a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs index a2dfdaa1..3b976f54 100644 --- a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs +++ b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs @@ -948,7 +948,8 @@ namespace Ink_Canvas randWindow.Show(); // 确保窗口显示后立即置顶 randWindow.Activate(); - WindowZOrderManager.BringToTop(randWindow); + randWindow.Topmost = true; + randWindow.Focus(); } public void CheckEraserTypeTab() diff --git a/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs b/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs index 0b78d802..21a9de2b 100644 --- a/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs +++ b/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs @@ -30,11 +30,10 @@ namespace Ink_Canvas var mainWin = (MainWindow)Current.MainWindow; if (mainWin.IsLoaded) { - // 通知Z-Order管理器有系统菜单打开 - // 这会导致主窗口暂时取消置顶,让系统菜单能够正常显示 + // 在无焦点模式下,暂时取消主窗口置顶,让系统菜单能够正常显示 if (Ink_Canvas.MainWindow.Settings.Advanced.IsAlwaysOnTop && Ink_Canvas.MainWindow.Settings.Advanced.IsNoFocusMode) { - WindowZOrderManager.SetWindowTopmost(mainWin, false); + mainWin.Topmost = false; } // 判斷是否在收納模式中 @@ -72,7 +71,7 @@ namespace Ink_Canvas // 菜单关闭后,恢复主窗口的置顶状态 if (Ink_Canvas.MainWindow.Settings.Advanced.IsAlwaysOnTop && Ink_Canvas.MainWindow.Settings.Advanced.IsNoFocusMode) { - WindowZOrderManager.SetWindowTopmost(mainWin, true); + mainWin.Topmost = true; } } } diff --git a/Ink Canvas/Windows/RandWindow.xaml.cs b/Ink Canvas/Windows/RandWindow.xaml.cs index d62a36d8..66ecd8df 100644 --- a/Ink Canvas/Windows/RandWindow.xaml.cs +++ b/Ink Canvas/Windows/RandWindow.xaml.cs @@ -30,14 +30,11 @@ namespace Ink_Canvas // 加载背景 LoadBackground(settings); - // 注册到Z-Order管理器,确保窗口能够正确置顶 - WindowZOrderManager.RegisterWindow(this, true, false); + // 设置窗口为置顶 + Topmost = true; // 添加窗口关闭事件处理 Closed += RandWindow_Closed; - - // 添加窗口显示事件处理 - Loaded += RandWindow_Loaded; } private void LoadBackground(Settings settings) @@ -84,14 +81,11 @@ namespace Ink_Canvas // 加载背景 LoadBackground(settings); - // 注册到Z-Order管理器,确保窗口能够正确置顶 - WindowZOrderManager.RegisterWindow(this, true, false); + // 设置窗口为置顶 + Topmost = true; // 添加窗口关闭事件处理 Closed += RandWindow_Closed; - - // 添加窗口显示事件处理 - Loaded += RandWindow_Loaded; new Thread(() => { @@ -355,22 +349,13 @@ namespace Ink_Canvas } } - /// - /// 窗口加载事件处理 - /// - private void RandWindow_Loaded(object sender, RoutedEventArgs e) - { - // 窗口加载完成后,立即将其置顶 - WindowZOrderManager.BringToTop(this); - } - /// /// 窗口关闭事件处理 /// private void RandWindow_Closed(object sender, EventArgs e) { - // 从Z-Order管理器中移除窗口 - WindowZOrderManager.UnregisterWindow(this); + // 窗口关闭时的清理工作 + // 这里可以添加必要的清理代码 } } }