Forge
csharpdeeb25a2
1using System.Globalization;
2
3namespace CascadeIDE.Services.Presentation;
4
5/// <summary>
6/// Сборщик кадра геометрии <c>MainGrid</c> из строки <c>presentation</c>.
7/// Инкапсулирует правила v1 для долей и fallback-веток.
8/// </summary>
9public static class PresentationMainGridLayoutFrameBuilder
10{
11 /// <summary>Дефолт без весов/валидной конфигурации в первом экране (5 колонок MainGrid: PFD, splitter, Forward, splitter, MFD).</summary>
12 public const string DefaultColumnDefinitions = "220,4,*,4,340";
13
14 /// <param name="mainWindowPresentationScreenIndex">
15 /// Индекс группы <c>(…)</c> в строке <c>presentation</c>, которой соответствует главное окно (лобовое).
16 /// Для <c>(xP+yM)(F)</c> — группа с <c>F</c> (не всегда экран 0). Иначе используйте <c>0</c>.
17 /// </param>
18 public static PresentationMainGridLayoutFrame Build(
19 PresentationParseResult parse,
20 bool dedicatedMfdSecondScreen,
21 bool mfdColumnSuppressedForHost,
22 bool tripleOneAnchorPerZone,
23 bool suppressPfdColumnForPfdHostWindow,
24 int mainWindowPresentationScreenIndex = 0)
25 {
26 if (!parse.IsSuccess || parse.Screens.Count == 0)
27 return DefaultFrame(0);
28
29 if (tripleOneAnchorPerZone
30 && PresentationLayoutAnalyzer.IsTripleOneAnchorPerZonePreset(parse.Screens))
31 {
32 return BuildTripleMainWindowFrame(mfdColumnSuppressedForHost, suppressPfdColumnForPfdHostWindow);
33 }
34
35 var clampedMain = Math.Clamp(mainWindowPresentationScreenIndex, 0, parse.Screens.Count - 1);
36 var mainScreen = parse.Screens[clampedMain];
37
38 if (IsForwardOnlyMainScreen(mainScreen))
39 return BuildForwardOnlyMainWindowFrame();
40
41 if (mainScreen.Count is < 2 or > 3)
42 return DefaultFrame(mainScreen.Count);
43
44 if (!PresentationZoneWeights.TryNormalize(mainScreen, out var normalized))
45 return DefaultFrame(mainScreen.Count);
46
47 var hasExplicitWeights = HasExplicitWeights(mainScreen);
48 if (!hasExplicitWeights)
49 {
50 var unweightedColumns = DefaultColumnDefinitions;
51 if (mainScreen.Count == 2
52 && dedicatedMfdSecondScreen
53 && mfdColumnSuppressedForHost
54 && !ContainsAnchor(mainScreen, PresentationAnchorKind.Mfd))
55 {
56 unweightedColumns = "220,4,*,4,0";
57 }
58
59 return new PresentationMainGridLayoutFrame(
60 unweightedColumns,
61 mainScreen.Count,
62 hasExplicitWeights,
63 normalized,
64 BuildZoneBounds(mainScreen, normalized));
65 }
66
67 var columns = mainScreen.Count switch
68 {
69 3 => FormatTriple(normalized[0], normalized[1], normalized[2]),
70 2 => FormatDual(normalized[0], normalized[1], dedicatedMfdSecondScreen, mfdColumnSuppressedForHost),
71 _ => DefaultColumnDefinitions
72 };
73
74 return new PresentationMainGridLayoutFrame(
75 columns,
76 mainScreen.Count,
77 hasExplicitWeights,
78 normalized,
79 BuildZoneBounds(mainScreen, normalized));
80 }
81
82 private static bool IsForwardOnlyMainScreen(IReadOnlyList<PresentationAnchorSlot> mainScreen) =>
83 mainScreen.Count == 1 && mainScreen[0].Kind == PresentationAnchorKind.Forward;
84
85 /// <summary>Только лобовое: колонки PFD/MFD нулевые, центральная <c>*</c>.</summary>
86 private static PresentationMainGridLayoutFrame BuildForwardOnlyMainWindowFrame() =>
87 new(
88 "0,4,*,4,0",
89 1,
90 false,
91 new[] { 1.0 },
92 new[] { new PresentationZoneBound(PresentationAnchorKind.Forward, 0.0, 1.0) });
93
94 private static PresentationMainGridLayoutFrame DefaultFrame(int contentZoneCount) =>
95 new(DefaultColumnDefinitions, contentZoneCount, false, Array.Empty<double>(), Array.Empty<PresentationZoneBound>());
96
97 /// <summary>
98 /// Три физических экрана под <c>P</c>/<c>F</c>/<c>M</c>: в главном окне остаётся лобовое; колонки P/M — по флагам подавления хостов.
99 /// </summary>
100 private static PresentationMainGridLayoutFrame BuildTripleMainWindowFrame(
101 bool mfdColumnSuppressedForHost,
102 bool suppressPfdColumnForPfdHostWindow)
103 {
104 const string defaultPfd = "220";
105 const string defaultMfdTail = "340";
106 var pfdCol = suppressPfdColumnForPfdHostWindow ? "0" : defaultPfd;
107 var mfdCol = mfdColumnSuppressedForHost ? "0" : defaultMfdTail;
108 var columns = $"{pfdCol},4,*,4,{mfdCol}";
109 return new PresentationMainGridLayoutFrame(
110 columns,
111 3,
112 false,
113 Array.Empty<double>(),
114 Array.Empty<PresentationZoneBound>());
115 }
116
117 private static bool HasExplicitWeights(IReadOnlyList<PresentationAnchorSlot> first)
118 {
119 for (var i = 0; i < first.Count; i++)
120 {
121 if (first[i].Weight.HasValue)
122 return true;
123 }
124
125 return false;
126 }
127
128 private static bool ContainsAnchor(IReadOnlyList<PresentationAnchorSlot> screen, PresentationAnchorKind kind)
129 {
130 for (var i = 0; i < screen.Count; i++)
131 {
132 if (screen[i].Kind == kind)
133 return true;
134 }
135
136 return false;
137 }
138
139 private static string FormatTriple(double wP, double wF, double wM) =>
140 $"{FormatWeight(wP)}*,4,{FormatWeight(wF)}*,4,{FormatWeight(wM)}*";
141
142 private static string FormatDual(double w0, double w1, bool dedicatedMfdSecondScreen, bool mfdColumnSuppressedForHost)
143 {
144 var tail = dedicatedMfdSecondScreen && mfdColumnSuppressedForHost ? "0" : "340";
145 return $"{FormatWeight(w0)}*,4,{FormatWeight(w1)}*,4,{tail}";
146 }
147
148 private static string FormatWeight(double w) => w.ToString("0.########", CultureInfo.InvariantCulture);
149
150 private static IReadOnlyList<PresentationZoneBound> BuildZoneBounds(
151 IReadOnlyList<PresentationAnchorSlot> first,
152 IReadOnlyList<double> normalized)
153 {
154 if (first.Count == 0 || first.Count != normalized.Count)
155 return Array.Empty<PresentationZoneBound>();
156
157 var bounds = new PresentationZoneBound[first.Count];
158 var cursor = 0.0;
159 for (var i = 0; i < first.Count; i++)
160 {
161 var width = normalized[i];
162 bounds[i] = new PresentationZoneBound(first[i].Kind, cursor, width);
163 cursor += width;
164 }
165
166 return bounds;
167 }
168}
169
170
View only · write via MCP/CIDE