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. 多入口支持: 支持鼠标滚轮、命令、代码等多种触发方式
相关推荐
xyq20241 小时前
Go 语言范围(Range)
开发语言
Anastasiozzzz1 小时前
深入理解JIT编译器:从基础到逃逸分析优化
java·开发语言·jvm
独自破碎E2 小时前
BISHI56 分解质因数
java·开发语言
FL16238631292 小时前
windows从源码安装python版本paddleocr3.4.0
开发语言·windows·python
m0_531237172 小时前
C语言-static关键词,寄存器变量,define宏定义
c语言·开发语言
CHANG_THE_WORLD3 小时前
C++ 一维、二维、三维数组完整演示
开发语言·c++
~央千澈~3 小时前
抖音弹幕游戏开发之第14集:添加更多整蛊效果·优雅草云桧·卓伊凡
开发语言·python·游戏
百锦再3 小时前
Java synchronized关键字详解:从入门到原理(两课时)
java·开发语言·struts·spring·kafka·tomcat·maven
油丶酸萝卜别吃3 小时前
什么是 Java 内存模型(JMM)?
java·开发语言