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 的变换系统实现,通过 ScaleTransform 和 TranslateTransform 的组合来实现平滑的缩放体验。


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. 多入口支持: 支持鼠标滚轮、命令、代码等多种触发方式
相关推荐
李永奉6 分钟前
C语言-指针函数与函数指针及回调函数的使用
c语言·开发语言
小oo呆6 分钟前
【学习心得】迭代器和可迭代对象
开发语言·python
AC赳赳老秦26 分钟前
采集行为合规自检:OpenClaw 自动校验 robots 协议与采集频率,规避违规采集风险
java·开发语言·c++·python·php·deepseek·openclaw
钱栈up30 分钟前
mvn compile 卡死在 javac 阶段:一次类型不匹配的排查与修复
java·开发语言
迅猛龙办公室32 分钟前
Python输出当前计算机的系统日期和时间
开发语言·前端·python
杨丰玮4181 小时前
C语言基础知识(一)————各类符号的含义与输入输出库(stdio)
c语言·开发语言
程序猿编码1 小时前
工业视觉多任务方案:可编程梯度信息视觉框架,训练评估 + C++ 部署完整实战
开发语言·c++·视觉·推理引擎
言乐62 小时前
Python实现语音审核模型
开发语言·python·django·virtualenv·pygame
朝朝辞暮i3 小时前
C++ 第 21 课:struct —— 把一组相关数据打包在一起
开发语言·c++·算法
SHARK_pssm3 小时前
【C++——类和对象(下)】
开发语言·c++·经验分享·笔记