Forge
csharpdeeb25a2
1#nullable enable
2using CascadeIDE.Models;
3
4namespace CascadeIDE.Cockpit.Graph.Layout;
5
6/// <summary>Геометрия и политика укладки control-flow graph-backed surface (ADR 0055).</summary>
7public static class GraphControlFlowLayoutMetrics
8{
9 public const double IntrinsicHeightBasePx = 32;
10
11 public static double VerticalSpacingForDetailLevel(CodeNavigationMapDetailLevel detailLevel) =>
12 detailLevel switch
13 {
14 CodeNavigationMapDetailLevel.Glance => 30,
15 CodeNavigationMapDetailLevel.Inspect => 40,
16 _ => 36,
17 };
18
19 public static double EstimatePreferredHeight(int estimatedLevelCount, CodeNavigationMapDetailLevel detailLevel)
20 {
21 var spacing = VerticalSpacingForDetailLevel(detailLevel);
22 var levelBands = Math.Max(1, estimatedLevelCount);
23 return IntrinsicHeightBasePx + Math.Max(0, levelBands - 1) * spacing;
24 }
25
26 public const double TopPadding = 12;
27 public const double BottomPadding = 12;
28 public const double SidePadding = 12;
29 public const double LegendGap = 2;
30 public const double BesideLegendInkSlack = 4;
31 public const double LegendBesideMinClearance = 6;
32 public const double LegendSideColumnMinTextWidth = 100;
33 public const double LegendBelowBlockGap = 6;
34 public const double MinGraphHeightForBelowLegend = 50;
35 public const double MinGraphWidth = 40;
36 public const double MaxReadableBandWidth = 400;
37 public const double LegendReserveWidthFraction = 0.30;
38 public const double LegendReserveMin = 88;
39 public const double LegendReserveHardCap = 340;
40
41 public static double ResolveLegendReserveCap(double viewportWidth) =>
42 Math.Min(LegendReserveHardCap, viewportWidth * 0.52);
43
44 public const double RefVerticalStep = 38;
45 public const double MaxReadableVerticalStep = 44;
46 public const double MaxReadableVerticalStepCap = 96;
47 public const double AnchorRadiusBase = 15.5;
48 public const double NodeRadiusBase = 13.5;
49 public const double RadiusScaleMin = 0.4;
50 public const double RadiusScaleMax = 1.12;
51 public const double HorizontalRadiusScaleMin = 0.74;
52
53 public static double MinVerticalStepForLevelCount(int levelCount) =>
54 levelCount switch
55 {
56 >= 16 => 12,
57 >= 12 => 14,
58 >= 9 => 16,
59 >= 6 => 18,
60 _ => 20,
61 };
62
63 /// <summary>Ось потока: приоритет — уместить шаги по главной оси; иначе соотношение сторон и «длина цепочки vs ширина уровня».</summary>
64 public static GraphControlFlowMainAxis ChooseMainAxis(
65 double graphWidth,
66 double heightForLayout,
67 int levelCount,
68 int maxNodesOnAnyLevel)
69 {
70 var innerH = heightForLayout - TopPadding - BottomPadding;
71 var innerW = graphWidth - 2 * SidePadding;
72 if (innerH < 1 || innerW < 1 || levelCount < 1)
73 return GraphControlFlowMainAxis.Vertical;
74
75 var slotCount = Math.Max(1, levelCount - 1);
76 var minStep = MinVerticalStepForLevelCount(levelCount);
77 var slackMainIfVertical = innerH / slotCount;
78 var slackMainIfHorizontal = innerW / slotCount;
79
80 if (slackMainIfHorizontal >= minStep && slackMainIfVertical < minStep * 0.92)
81 return GraphControlFlowMainAxis.Horizontal;
82 if (slackMainIfVertical >= minStep && slackMainIfHorizontal < minStep * 0.92)
83 return GraphControlFlowMainAxis.Vertical;
84
85 var aspect = innerW / innerH;
86
87 if (aspect >= 1.18 && slackMainIfHorizontal >= minStep * 0.85)
88 return GraphControlFlowMainAxis.Horizontal;
89 if (aspect <= 0.85 && slackMainIfVertical >= minStep * 0.85)
90 return GraphControlFlowMainAxis.Vertical;
91
92 if (Math.Abs(slackMainIfHorizontal - slackMainIfVertical) > 5)
93 return slackMainIfHorizontal > slackMainIfVertical
94 ? GraphControlFlowMainAxis.Horizontal
95 : GraphControlFlowMainAxis.Vertical;
96
97 var depthHeavy = slotCount > Math.Max(2, maxNodesOnAnyLevel);
98 if (depthHeavy && slackMainIfHorizontal >= minStep && innerW >= innerH)
99 return GraphControlFlowMainAxis.Horizontal;
100
101 return innerW > innerH * 1.02
102 ? GraphControlFlowMainAxis.Horizontal
103 : GraphControlFlowMainAxis.Vertical;
104 }
105
106 /// <summary>
107 /// Не-null — ось из <c>[code_navigation_map].control_flow_main_axis</c>; <see langword="null"/> для <c>auto</c> (см. <see cref="ChooseMainAxis"/>).
108 /// </summary>
109 public static GraphControlFlowMainAxis? TryControlFlowMainAxisOverride(string normalizedSetting) =>
110 normalizedSetting switch
111 {
112 CodeNavigationMapControlFlowMainAxisKind.Vertical => GraphControlFlowMainAxis.Vertical,
113 CodeNavigationMapControlFlowMainAxisKind.Horizontal => GraphControlFlowMainAxis.Horizontal,
114 _ => null,
115 };
116
117 public const int LabelMaxLength = 22;
118 public const int LabelTruncateLength = 19;
119 public const int LabelCharBudgetMin = 8;
120
121 public static double ResolveReadableBandWidth(double graphWidth) =>
122 Math.Clamp(graphWidth, MinGraphWidth, MaxReadableBandWidth);
123
124 public static int ResolveLabelCharBudget(double bandWidth)
125 {
126 var approx = (int)Math.Floor((bandWidth - 52) / 7.0);
127 return Math.Clamp(approx, LabelCharBudgetMin, LabelMaxLength);
128 }
129
130 public static double ResolveSideLabelFontSize(double bandWidth, double verticalStep)
131 {
132 var widthFactor = Math.Clamp(bandWidth / 220.0, 0.68, 1.06);
133 var stepFactor = Math.Clamp(verticalStep / RefVerticalStep, 0.82, 1.08);
134 var combined = GraphRenderInvariants.MinSideLabelFontSize * widthFactor * stepFactor;
135 return Math.Clamp(
136 combined,
137 GraphRenderInvariants.CompactSideLabelFontSizeFloor,
138 GraphRenderInvariants.MaxSideLabelFontSize);
139 }
140
141 public static double EstimateLegendBlockHeight(
142 int rowCount,
143 bool hasShapeKeys,
144 int edgeStyleKeyRowCount = 0,
145 double captionSize = 11)
146 {
147 var lineH = Math.Max(15, captionSize * 1.2);
148 const double keyRowHBase = 17d;
149 var keyRowH = Math.Max(keyRowHBase, captionSize + 5);
150 const double gapBeforeKeys = 6d;
151 var shapeKeyBlockH = hasShapeKeys ? (keyRowH * 3 + gapBeforeKeys + 8) : 0d;
152 var edgeStyleBlockH = edgeStyleKeyRowCount > 0
153 ? edgeStyleKeyRowCount * keyRowH + 8d
154 : 0d;
155 const double betweenEdgeAndShape = 6d;
156 var betweenBlocks = 0d;
157 if (edgeStyleKeyRowCount > 0 && hasShapeKeys)
158 betweenBlocks = betweenEdgeAndShape;
159 var keyBlockH = edgeStyleBlockH + betweenBlocks + shapeKeyBlockH;
160 var rowBlock = rowCount * lineH;
161 var hasAnyKeyBlock = hasShapeKeys || edgeStyleKeyRowCount > 0;
162 if (rowCount == 0 && hasAnyKeyBlock)
163 return keyBlockH + 12d;
164 var between = rowCount > 0 && hasAnyKeyBlock ? gapBeforeKeys : 0d;
165 return rowBlock + between + keyBlockH + 12d;
166 }
167}
168
View only · write via MCP/CIDE