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. 多入口支持: 支持鼠标滚轮、命令、代码等多种触发方式
相关推荐
smchaopiao17 小时前
Python中字典与列表合并的问题与解决方法
开发语言·python
敲代码的瓦龙17 小时前
Java?面向对象三大特性!!!
java·开发语言
2501_9216494917 小时前
期货 Tick 级数据与基金净值历史数据 API 接口详解
开发语言·后端·python·websocket·金融·区块链
野犬寒鸦17 小时前
Redis复习记录day1
服务器·开发语言·数据库·redis·缓存
小菜鸡桃蛋狗17 小时前
C++——类和对象(下)
开发语言·c++
骑龙赶鸭17 小时前
java开发项目中遇到的难点,面试!
java·开发语言·面试
张人玉17 小时前
C#通讯(上位机)常用知识点
开发语言·c#·通讯·上位机开发
NGC_661117 小时前
Java线程池七大核心参数介绍
java·开发语言
crescent_悦17 小时前
C++:Highest Price in Supply Chain
开发语言·c++