| 1 | #nullable enable |
| 2 | using CascadeIDE.Models; |
| 3 | |
| 4 | namespace CascadeIDE.Cockpit.Graph.Layout; |
| 5 | |
| 6 | /// <summary>Геометрия related-files на карте намерений (уровень file).</summary> |
| 7 | public static class GraphFileLayoutMetrics |
| 8 | { |
| 9 | public const double SideLabelMargin = 68; |
| 10 | public const double DefaultHeightFile = GraphViewportMetrics.DefaultHeightFile; |
| 11 | public const double MaxHeightFile = 380; |
| 12 | public const double MaxHeightHierarchy = 520; |
| 13 | |
| 14 | public static double EstimatePreferredHeight( |
| 15 | int satelliteCount, |
| 16 | CodeNavigationMapDetailLevel detailLevel, |
| 17 | string relatedLayout) |
| 18 | { |
| 19 | if (satelliteCount <= 0) |
| 20 | return DefaultHeightFile; |
| 21 | |
| 22 | var layout = CodeNavigationMapRelatedGraphLayoutKind.Normalize(relatedLayout); |
| 23 | if (CodeNavigationMapRelatedGraphLayoutKind.IsHierarchy(layout)) |
| 24 | return EstimateHierarchyHeight(satelliteCount, detailLevel); |
| 25 | |
| 26 | if (satelliteCount <= 8) |
| 27 | return DefaultHeightFile; |
| 28 | |
| 29 | var perSatellite = detailLevel switch |
| 30 | { |
| 31 | CodeNavigationMapDetailLevel.Glance => 3.5, |
| 32 | CodeNavigationMapDetailLevel.Inspect => 7.5, |
| 33 | _ => 5.5 |
| 34 | }; |
| 35 | var extra = (satelliteCount - 8) * perSatellite; |
| 36 | return Math.Clamp(DefaultHeightFile + extra, DefaultHeightFile, MaxHeightFile); |
| 37 | } |
| 38 | |
| 39 | public static double EstimateHierarchyHeight(int satelliteCount, CodeNavigationMapDetailLevel detailLevel) |
| 40 | { |
| 41 | var perRow = detailLevel switch |
| 42 | { |
| 43 | CodeNavigationMapDetailLevel.Glance => 34, |
| 44 | CodeNavigationMapDetailLevel.Inspect => 44, |
| 45 | _ => 38 |
| 46 | }; |
| 47 | var rows = 1 + Math.Max(0, satelliteCount); |
| 48 | return Math.Clamp(72 + rows * perRow, DefaultHeightFile, MaxHeightHierarchy); |
| 49 | } |
| 50 | |
| 51 | /// <summary>Радиусы внутренней и внешней орбит (режим <c>radial</c>).</summary> |
| 52 | public static (double Inner, double Outer) ResolveRadialOrbits( |
| 53 | int satelliteCount, |
| 54 | double innerWidth, |
| 55 | double innerHeight, |
| 56 | double satelliteRadius) |
| 57 | { |
| 58 | var minInner = Math.Min(innerWidth, innerHeight); |
| 59 | var extent = Math.Sqrt(Math.Max(1, innerWidth * innerHeight)); |
| 60 | var wantTwoRings = satelliteCount > 8; |
| 61 | var densityBoost = 1.0 + Math.Max(0, satelliteCount - 6) * 0.042; |
| 62 | var maxOuterOrbit = Math.Max(32, minInner * 0.50 - satelliteRadius); |
| 63 | var desired = extent * (wantTwoRings ? 0.40 : 0.44) * densityBoost; |
| 64 | var useTwoRings = wantTwoRings && maxOuterOrbit >= 56; |
| 65 | |
| 66 | if (!useTwoRings) |
| 67 | { |
| 68 | var orbit = ClampOrMin(desired, 26, maxOuterOrbit); |
| 69 | return (orbit, orbit); |
| 70 | } |
| 71 | |
| 72 | var maxInnerOrbit = Math.Max(26, maxOuterOrbit - satelliteRadius * 3.2 - 14); |
| 73 | var orbitInner = ClampOrMin(desired * 0.9, 26, maxInnerOrbit); |
| 74 | var orbitOuterDesired = Math.Max( |
| 75 | orbitInner + satelliteRadius * 2.6 + 14, |
| 76 | extent * 0.52 * densityBoost); |
| 77 | var orbitOuter = ClampOrMin( |
| 78 | orbitOuterDesired, |
| 79 | orbitInner + satelliteRadius + 8, |
| 80 | maxOuterOrbit); |
| 81 | return (orbitInner, orbitOuter); |
| 82 | } |
| 83 | |
| 84 | private static double ClampOrMin(double value, double min, double max) => |
| 85 | max < min ? min : Math.Clamp(value, min, max); |
| 86 | } |
| 87 | |