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. 多入口支持: 支持鼠标滚轮、命令、代码等多种触发方式
相关推荐
isyangli_blog9 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
vb20081110 小时前
FastAPI APIRouter
开发语言·python
Benszen10 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆10 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
東雪木10 小时前
多线程与并发编程 专属复习笔记
java·开发语言·笔记·java面试
杨充10 小时前
1.3 浮点型数据设计灵魂
开发语言·python·算法
噜噜噜阿鲁~10 小时前
python学习笔记 | 11.3、面向对象高级编程-多重继承
java·开发语言
basketball61610 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang
春生野草11 小时前
反射、Tomcat执行
java·开发语言
雪的季节12 小时前
企业级 Qt 全功能项目
开发语言·数据库·qt