NodifyEditor Zoom 机制分析

NodifyEditor Zoom 机制分析

  • [NodifyEditor Zoom 机制分析](#NodifyEditor Zoom 机制分析)
    • 概述
    • [1. 核心属性](#1. 核心属性)
    • [2. Zoom 触发入口](#2. Zoom 触发入口)
      • [2.1 入口点概览](#2.1 入口点概览)
      • [2.2 鼠标滚轮缩放](#2.2 鼠标滚轮缩放)
      • [2.3 ZoomIn / ZoomOut 命令](#2.3 ZoomIn / ZoomOut 命令)
      • [2.4 FitToScreen 命令](#2.4 FitToScreen 命令)
    • [3. Zoom 执行流程](#3. Zoom 执行流程)
      • [3.1 核心方法: ZoomAtPosition](#3.1 核心方法: ZoomAtPosition)
      • [3.2 流程图](#3.2 流程图)
      • [3.3 焦点保持算法](#3.3 焦点保持算法)
    • [4. 变换机制](#4. 变换机制)
      • [4.1 变换组合](#4.1 变换组合)
      • [4.2 变换属性](#4.2 变换属性)
      • [4.3 OnViewportZoomChanged 回调](#4.3 OnViewportZoomChanged 回调)
    • [5. 约束机制](#5. 约束机制)
      • [5.1 缩放值约束](#5.1 缩放值约束)
      • [5.2 最小/最大值约束](#5.2 最小/最大值约束)
      • [5.3 约束链](#5.3 约束链)
    • [6. 视口大小计算](#6. 视口大小计算)
      • [6.1 计算公式](#6.1 计算公式)
      • [6.2 关系说明](#6.2 关系说明)
    • [7. 缩放因子计算](#7. 缩放因子计算)
      • [7.1 鼠标滚轮](#7.1 鼠标滚轮)
      • [7.2 ZoomIn / ZoomOut](#7.2 ZoomIn / ZoomOut)
      • [7.3 FitToScreen](#7.3 FitToScreen)
    • [8. 相关命令绑定](#8. 相关命令绑定)
    • [9. 架构图](#9. 架构图)
    • [10. 总结](#10. 总结)

NodifyEditor Zoom 机制分析

概述

NodifyEditor 的缩放(Zoom)机制基于 Avalonia 的变换系统实现,通过 ScaleTransformTranslateTransform 的组合来实现平滑的缩放体验。


1. 核心属性

属性 类型 默认值 说明
ViewportZoom double 1.0 当前缩放级别
MinViewportZoom double 0.1 最小缩放级别
MaxViewportZoom double 2.0 最大缩放级别

属性定义位置

csharp 复制代码
// NodifyEditor.cs 第 35-37 行
public static readonly StyledProperty<double> ViewportZoomProperty = 
    AvaloniaProperty.Register<NodifyEditor, double>(nameof(ViewportZoom), 1.0, 
        defaultBindingMode: BindingMode.TwoWay, 
        coerce: ConstrainViewportZoomToRange);

public static readonly StyledProperty<double> MinViewportZoomProperty = 
    AvaloniaProperty.Register<NodifyEditor, double>(nameof(MinViewportZoom), 0.1d, 
        coerce: CoerceMinViewportZoom);

public static readonly StyledProperty<double> MaxViewportZoomProperty = 
    AvaloniaProperty.Register<NodifyEditor, double>(nameof(MaxViewportZoom), 2.0d, 
        coerce: CoerceMaxViewportZoom);

2. Zoom 触发入口

2.1 入口点概览

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                        用户触发缩放                              │
├─────────────────────────────────────────────────────────────────┤
│  ① 鼠标滚轮 + 修饰键 (默认 Ctrl)                                 │
│     → OnMouseWheel() → ZoomAtPosition(zoom, mousePos)          │
│                                                                 │
│  ② ZoomIn() / ZoomOut() 命令                                    │
│     → 在视口中心放大/缩小                                        │
│                                                                 │
│  ③ FitToScreen() 命令                                           │
│     → 计算合适的 zoom 值使所有节点适应视口                        │
│                                                                 │
│  ④ 直接设置 ViewportZoom 属性                                    │
│     → 绑定或代码设置                                             │
└─────────────────────────────────────────────────────────────────┘

2.2 鼠标滚轮缩放

文件位置 : NodifyEditor.Avalonia.cs

csharp 复制代码
// 第 27-37 行
if (!e.Handled && EditorGestures.Mappings.Editor.ZoomModifierKey == e.KeyModifiers)
{
    double zoom = Math.Pow(2.0, e.Delta.Y / 3.0 / MouseWheelDeltaForOneLine 
                          * MouseWheelAvaloniaToWpfScale 
                          * PointerTouchGestureMagnifyScale);
    ZoomAtPosition(zoom, e.GetPosition(ItemsHost));
}

2.3 ZoomIn / ZoomOut 命令

文件位置 : NodifyEditor.cs 第 984-992 行

csharp 复制代码
/// <summary>
/// Zoom in at the viewports center
/// </summary>
public void ZoomIn() => ZoomAtPosition(
    Math.Pow(2.0, 120.0 / 3.0 / MouseWheelDeltaForOneLine), 
    (Point)((Vector)ViewportLocation + ViewportSize.ToVector() / 2));

/// <summary>
/// Zoom out at the viewports center
/// </summary>
public void ZoomOut() => ZoomAtPosition(
    Math.Pow(2.0, -120.0 / 3.0 / MouseWheelDeltaForOneLine), 
    (Point)((Vector)ViewportLocation + ViewportSize.ToVector() / 2));

2.4 FitToScreen 命令

文件位置 : NodifyEditor.cs 第 1071-1087 行

csharp 复制代码
public void FitToScreen(Rect? area = null)
{
    Rect extent = area ?? ItemsExtent;
    extent = extent.Inflate(FitToScreenExtentMargin);

    if (extent.Width > 0 && extent.Height > 0)
    {
        double widthRatio = ViewportSize.Width / extent.Width;
        double heightRatio = ViewportSize.Height / extent.Height;

        double zoom = Math.Min(widthRatio, heightRatio);
        var center = new Point(extent.X + extent.Width / 2, extent.Y + extent.Height / 2);

        ZoomAtPosition(zoom, center);
        BringIntoView(center, animated: false);
    }
}

3. Zoom 执行流程

3.1 核心方法: ZoomAtPosition

文件位置 : NodifyEditor.cs 第 999-1018 行

csharp 复制代码
public void ZoomAtPosition(double zoom, Point location)
{
    if (!DisableZooming)
    {
        double prevZoom = ViewportZoom;
        ViewportZoom *= zoom;

        if (Math.Abs(prevZoom - ViewportZoom) > 0.0000001)
        {
            // 获取实际缩放值(因为 Zoom 可能被 Coerce)
            zoom = ViewportZoom / prevZoom;
            Vector position = (Vector)location;

            var dist = position - (Vector)ViewportLocation;
            var zoomedDist = dist * zoom;
            var diff = zoomedDist - dist;
            ViewportLocation += diff / zoom;
        }
    }
}

3.2 流程图

复制代码
ZoomAtPosition(zoom, location)
         │
         ▼
┌─────────────────────────────────────┐
│ 1. 检查 DisableZooming              │
│    若禁用则直接返回                  │
└─────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────┐
│ 2. 计算新缩放值                      │
│    ViewportZoom *= zoom             │
│    (自动被 Coerce 到 [Min, Max] 范围) │
└─────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────┐
│ 3. 调整视口位置 (保持焦点不变)        │
│    • 计算 location 到视口的距离      │
│    • 根据缩放比例调整视口位置        │
└─────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────┐
│ 4. OnViewportZoomChanged 触发        │
│    • 创建新 ScaleTransform           │
│    • 更新 ViewportSize              │
│    • 更新 ViewportTransform         │
│    • 触发 OnViewportUpdated         │
└─────────────────────────────────────┘

3.3 焦点保持算法

缩放时保持焦点位置不变的数学原理:

复制代码
设:
- location: 缩放焦点位置(图空间坐标)
- ViewportLocation: 视口左上角位置
- zoom: 缩放因子

则:
dist = location - ViewportLocation          // 焦点到视口左上角的距离
zoomedDist = dist * zoom                     // 缩放后的距离
diff = zoomedDist - dist                     // 距离变化量

新 ViewportLocation = 旧 ViewportLocation + diff / zoom

4. 变换机制

4.1 变换组合

文件位置 : NodifyEditor.cs 第 50-63 行

csharp 复制代码
private static void UpdateViewportTransform(NodifyEditor editor)
{
    var transform = new TransformGroup();
    transform.Children.Add(editor.ScaleTransform);      // 先缩放
    transform.Children.Add(editor.TranslateTransform);  // 后平移

    editor.SetCurrentValue(ViewportTransformProperty, transform);
    
    // DPI 缩放版本
    var dpiScaledTransform = new TransformGroup();
    dpiScaledTransform.Children.Add(editor.ScaleTransform);
    dpiScaledTransform.Children.Add(editor.DpiScaledTranslateTransform);
    
    editor.SetCurrentValue(DpiScaledViewportTransformProperty, dpiScaledTransform);
}

4.2 变换属性

属性 类型 用途
ScaleTransform ScaleTransform 应用缩放变换
TranslateTransform TranslateTransform 应用平移变换
ViewportTransform Transform 组合变换(应用到子元素)
DpiScaledViewportTransform Transform DPI 缩放版本(高 DPI 屏幕)

4.3 OnViewportZoomChanged 回调

文件位置 : NodifyEditor.cs 第 91-107 行

csharp 复制代码
private static void OnViewportZoomChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var editor = (NodifyEditor)d;
    double zoom = (double)e.NewValue;

    // 创建新的 ScaleTransform(Avalonia 的 workaround)
    editor.ScaleTransform = new();
    
    editor.ScaleTransform.ScaleX = zoom;
    editor.ScaleTransform.ScaleY = zoom;

    // 更新视口大小
    editor.ViewportSize = new Size(editor.Bounds.Width / zoom, editor.Bounds.Height / zoom);
    
    // 更新变换
    UpdateViewportTransform(editor);
    
    // 触发视口更新事件
    editor.OnViewportUpdated();
}

5. 约束机制

5.1 缩放值约束

文件位置 : NodifyEditor.cs 第 133-146 行

csharp 复制代码
private static double ConstrainViewportZoomToRange(DependencyObject d, double value)
{
    var editor = (NodifyEditor)d;
    var num = (double)value;
    double minimum = editor.MinViewportZoom;
    
    if (num < minimum)
    {
        return minimum;
    }

    double maximum = editor.MaxViewportZoom;
    return num > maximum ? maximum : value;
}

5.2 最小/最大值约束

csharp 复制代码
// MinViewportZoom 最小值为 0.1
private static double CoerceMinViewportZoom(DependencyObject d, double value)
    => (double)value > 0.1d ? value : 0.1d;

// MaxViewportZoom 不能小于 MinViewportZoom
private static double CoerceMaxViewportZoom(DependencyObject d, double value)
{
    var editor = (NodifyEditor)d;
    double min = editor.MinViewportZoom;
    return (double)value < min ? min : value;
}

5.3 约束链

复制代码
设置 MinViewportZoom
        │
        ▼
┌─────────────────────────────────┐
│ CoerceMaxViewportZoom           │
│ 确保 Max >= Min                 │
└─────────────────────────────────┘
        │
        ▼
┌─────────────────────────────────┐
│ CoerceValue(ViewportZoom)       │
│ 确保 ViewportZoom 在有效范围内   │
└─────────────────────────────────┘

6. 视口大小计算

6.1 计算公式

csharp 复制代码
ViewportSize = new Size(Bounds.Width / zoom, Bounds.Height / zoom);

6.2 关系说明

Zoom 值 效果 ViewportSize
> 1.0 放大 变小(看到的图空间范围小)
= 1.0 正常 等于控件大小
< 1.0 缩小 变大(看到的图空间范围大)

7. 缩放因子计算

7.1 鼠标滚轮

csharp 复制代码
double zoom = Math.Pow(2.0, e.Delta.Y / 3.0 / 120);
// 滚轮滚动 120 单位 ≈ 1.26x 缩放

7.2 ZoomIn / ZoomOut

csharp 复制代码
double zoom = Math.Pow(2.0, ±120.0 / 3.0 / 120);  // ≈ 1.26x

7.3 FitToScreen

csharp 复制代码
double zoom = Math.Min(
    ViewportSize.Width / extent.Width, 
    ViewportSize.Height / extent.Height
);

8. 相关命令绑定

文件位置 : EditorCommands.cs

命令 触发方法 可执行条件
ZoomIn editor.ZoomIn() ViewportZoom < MaxViewportZoom
ZoomOut editor.ZoomOut() ViewportZoom > MinViewportZoom
FitToScreen editor.FitToScreen() 始终可执行

9. 架构图

复制代码
┌─────────────────────────────────────────────────────────────┐
│                       NodifyEditor                          │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────────┐    ┌─────────────────────────────────┐│
│  │   Zoom 入口      │    │         核心属性                ││
│  │                 │    │  • ViewportZoom                ││
│  │  • MouseWheel   │    │  • MinViewportZoom            ││
│  │  • ZoomIn()     │    │  • MaxViewportZoom            ││
│  │  • ZoomOut()    │    │  • ViewportLocation           ││
│  │  • FitToScreen()│    │  • ViewportSize               ││
│  └────────┬────────┘    └─────────────────────────────────┘│
│           │                                                  │
│           ▼                                                  │
│  ┌─────────────────────────────────────────────────────────┐│
│  │              ZoomAtPosition(zoom, location)             ││
│  │  • 应用缩放因子                                          ││
│  │  • 调整视口位置保持焦点                                   ││
│  └─────────────────────────────────────────────────────────┘│
│           │                                                  │
│           ▼                                                  │
│  ┌─────────────────────────────────────────────────────────┐│
│  │              OnViewportZoomChanged                      ││
│  │  • 更新 ScaleTransform                                  ││
│  │  • 更新 ViewportSize                                    ││
│  │  • 更新 ViewportTransform                               ││
│  └─────────────────────────────────────────────────────────┘│
│           │                                                  │
│           ▼                                                  │
│  ┌─────────────────────────────────────────────────────────┐│
│  │                 ViewportTransform                       ││
│  │         = ScaleTransform + TranslateTransform           ││
│  │                                                         ││
│  │         应用到 ItemsHost (所有子元素)                    ││
│  └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘

10. 总结

NodifyEditor 的 Zoom 机制具有以下特点:

  1. 属性驱动 : 通过 ViewportZoom 属性的变化触发整个更新流程
  2. 焦点保持: 缩放时通过数学计算保持鼠标焦点位置不变
  3. 值约束 : 自动将缩放值约束在 [MinViewportZoom, MaxViewportZoom] 范围内
  4. 变换组合 : 使用 ScaleTransform + TranslateTransform 组合实现视口变换
  5. 多入口支持: 支持鼠标滚轮、命令、代码等多种触发方式
相关推荐
程序设计实验室11 小时前
C# 扩展方法只会写 this 吗?C# 14 新语法直接把扩展方法玩出了花
c#
唐青枫14 小时前
C#.NET SignalR 深入解析:实时通信、Hub 与连接管理实战
c#·.net
唐宋元明清218820 小时前
.NET Win32磁盘动态卷/跨区卷触发“函数不正确”问题排查
windows·c#·存储
hez201020 小时前
Satori GC:同时做到高吞吐、低延时和低内存占用
c#·.net·.net core·gc·clr
唐青枫2 天前
C#.NET Channel 深入解析:高性能异步生产者消费者模型实战
c#·.net
小峥降临2 天前
Rokid UXR 的手势追踪虚拟中更真实的手实战开发【含 工程源码 和 最终完成APK】
c#
晨星shine6 天前
GC、Dispose、Unmanaged Resource 和 Managed Resource
后端·c#
用户298698530146 天前
.NET 文档自动化:Spire.Doc 设置奇偶页页眉/页脚的最佳实践
后端·c#·.net
用户3667462526746 天前
接口文档汇总 - 2.设备状态管理
c#
用户3667462526746 天前
接口文档汇总 - 3.PLC通信管理
c#