| 1 | #nullable enable |
| 2 | using Avalonia; |
| 3 | using CascadeIDE.Cockpit.Graph.Layout; |
| 4 | |
| 5 | namespace CascadeIDE.Views.SkiaKit.Graph; |
| 6 | |
| 7 | /// <summary>Pointer hit-test для сцены графа (порядок отрисовки, масштаб viewport).</summary> |
| 8 | public static class SkiaGraphSceneHitTesting |
| 9 | { |
| 10 | /// <summary>Узел под точкой <paramref name="pointInLayoutSpace"/> или null.</summary> |
| 11 | public static GraphLayoutNode? FindNodeAt(GraphLayoutScene scene, Point pointInLayoutSpace) |
| 12 | { |
| 13 | GraphLayoutNode? best = null; |
| 14 | var bestDistance = double.MaxValue; |
| 15 | |
| 16 | for (var i = scene.Nodes.Count - 1; i >= 0; i--) |
| 17 | { |
| 18 | var n = scene.Nodes[i]; |
| 19 | if (!SkiaGraphSceneDrawing.HitTestNode(n, pointInLayoutSpace)) |
| 20 | continue; |
| 21 | |
| 22 | var dx = pointInLayoutSpace.X - n.Center.X; |
| 23 | var dy = pointInLayoutSpace.Y - n.Center.Y; |
| 24 | var distance = Math.Sqrt(dx * dx + dy * dy); |
| 25 | if (distance >= bestDistance) |
| 26 | continue; |
| 27 | |
| 28 | bestDistance = distance; |
| 29 | best = n; |
| 30 | } |
| 31 | |
| 32 | return best; |
| 33 | } |
| 34 | |
| 35 | /// <summary>Координаты клика в control → логические координаты укладки.</summary> |
| 36 | public static Point MapControlPointToLayout( |
| 37 | Point controlPoint, |
| 38 | double controlWidth, |
| 39 | double controlHeight, |
| 40 | double layoutViewportWidth, |
| 41 | double layoutViewportHeight) |
| 42 | { |
| 43 | if (layoutViewportWidth > 0 && layoutViewportHeight > 0 |
| 44 | && controlWidth > 0 && controlHeight > 0 |
| 45 | && (Math.Abs(controlWidth - layoutViewportWidth) > 0.5 |
| 46 | || Math.Abs(controlHeight - layoutViewportHeight) > 0.5)) |
| 47 | { |
| 48 | return new Point( |
| 49 | controlPoint.X * layoutViewportWidth / controlWidth, |
| 50 | controlPoint.Y * layoutViewportHeight / controlHeight); |
| 51 | } |
| 52 | |
| 53 | return controlPoint; |
| 54 | } |
| 55 | } |
| 56 | |