Forge
csharpdeeb25a2
1namespace CascadeIDE.Services.Presentation;
2
3/// <summary>Правила v1 ADR 0017: когда колонку Mfd в главном окне можно перенести на отдельный <c>TopLevel</c>.</summary>
4public static class PresentationLayoutAnalyzer
5{
6 /// <summary>
7 /// Два дисплея: на первом — только PFD и forward (без Mfd), на втором — только Mfd.
8 /// Типичный пресет <c>(PFD+Forward) (MFD)</c>, <c>(P+F) (M)</c>.
9 /// </summary>
10 public static bool IsDedicatedMfdSecondScreenPreset(IReadOnlyList<IReadOnlyList<PresentationAnchorSlot>> screens)
11 {
12 if (screens.Count < 2)
13 return false;
14
15 var first = screens[0];
16 var second = screens[1];
17 if (second.Count != 1 || second[0].Kind != PresentationAnchorKind.Mfd)
18 return false;
19
20 var hasPfd = ContainsAnchor(first, PresentationAnchorKind.Pfd);
21 var hasFwd = ContainsAnchor(first, PresentationAnchorKind.Forward);
22 var hasMfd = ContainsAnchor(first, PresentationAnchorKind.Mfd);
23 return hasPfd && hasFwd && !hasMfd;
24 }
25
26 /// <summary>
27 /// Первый экран в строке объединяет PFD и Forward как <c>(xP+yF)</c> (веса или равные доли) — главное окно
28 /// должно занимать рабочую область дисплея (максимизация при старте), а не дефолт 1000×600.
29 /// </summary>
30 public static bool IsPfdForwardCombinedOnFirstScreen(IReadOnlyList<IReadOnlyList<PresentationAnchorSlot>> screens)
31 {
32 if (screens.Count == 0)
33 return false;
34
35 var first = screens[0];
36 return ContainsAnchor(first, PresentationAnchorKind.Pfd)
37 && ContainsAnchor(first, PresentationAnchorKind.Forward);
38 }
39
40 /// <summary>
41 /// Главное окно при старте разворачиваем на рабочую область дисплея (не дефолт 1000×600):
42 /// на первом экране есть и PFD, и Forward — <c>(xP+yF)</c>, <c>(xP+yF+zM)</c> в одной группе и т.п.;
43 /// либо три дисплея <c>(P)(F)(M)</c> — первое окно только под PFD на первом мониторе (ADR 0017);
44 /// либо два экрана <c>(xP+yM)(F)</c> / <c>(F)(xP+yM)</c> — на главном только Forward.
45 /// Веса <c>x</c>/<c>y</c> меняют только доли колонок; условие по составу якорей не зависит от чисел.
46 /// </summary>
47 public static bool ShouldMaximizeMainWindowAtStartup(IReadOnlyList<IReadOnlyList<PresentationAnchorSlot>> screens) =>
48 IsPfdForwardCombinedOnFirstScreen(screens)
49 || IsTripleOneAnchorPerZonePreset(screens)
50 || IsPmPlusForwardTwoScreenPreset(screens)
51 || IsForwardMfdTwoScreenPreset(screens);
52
53 /// <summary>
54 /// Два дисплея: на одном — только Forward, на другом — только MFD.
55 /// Симметрично <c>(F)(M)</c> и <c>(M)(F)</c> (ADR 0017, operator 2-monitor default).
56 /// </summary>
57 public static bool IsForwardMfdTwoScreenPreset(IReadOnlyList<IReadOnlyList<PresentationAnchorSlot>> screens)
58 {
59 if (screens.Count != 2)
60 return false;
61
62 var a = screens[0];
63 var b = screens[1];
64 return IsForwardOnlyScreen(a) && IsSingleAnchor(b, PresentationAnchorKind.Mfd)
65 || IsSingleAnchor(a, PresentationAnchorKind.Mfd) && IsForwardOnlyScreen(b);
66 }
67
68 /// <summary>
69 /// Два дисплея: на одном — только Forward, на другом — только PFD+MFD (без лобового), с весами <c>xP+yM</c>.
70 /// Симметрично <c>(F)(xP+yM)</c> и <c>(xP+yM)(F)</c> (ADR 0017).
71 /// </summary>
72 public static bool IsPmPlusForwardTwoScreenPreset(IReadOnlyList<IReadOnlyList<PresentationAnchorSlot>> screens)
73 {
74 if (screens.Count != 2)
75 return false;
76
77 var a = screens[0];
78 var b = screens[1];
79 return IsPmCombinedScreen(a) && IsForwardOnlyScreen(b)
80 || IsForwardOnlyScreen(a) && IsPmCombinedScreen(b);
81 }
82
83 /// <summary>
84 /// Индекс группы, на которой показывается только Forward в пресете <see cref="IsPmPlusForwardTwoScreenPreset"/>; иначе <c>false</c>.
85 /// Главное окно (лобовое) сопоставляется с этим экраном в порядке <see cref="PresentationMonitorTopology.OrderScreensForPresentation"/>.
86 /// </summary>
87 public static bool TryGetMainWindowPresentationScreenIndex(
88 IReadOnlyList<IReadOnlyList<PresentationAnchorSlot>> screens,
89 out int index)
90 {
91 index = -1;
92 if (TryGetForwardOnlyMainWindowScreenIndex(screens, out index))
93 return true;
94
95 // (P)(F)(M) и перестановки: лобовое — экран с единственным F, не первый экран в строке (ADR 0017).
96 return TryGetSingleAnchorScreenIndex(screens, PresentationAnchorKind.Forward, out index);
97 }
98
99 /// <summary>Индекс экрана с объединённым <c>P+M</c> для окна-хоста сплита (симметрично <c>(F)</c>).</summary>
100 public static bool TryGetPmSplitHostPresentationScreenIndex(
101 IReadOnlyList<IReadOnlyList<PresentationAnchorSlot>> screens,
102 out int index)
103 {
104 index = -1;
105 if (!IsPmPlusForwardTwoScreenPreset(screens))
106 return false;
107
108 if (IsPmCombinedScreen(screens[0]))
109 {
110 index = 0;
111 return true;
112 }
113
114 if (IsPmCombinedScreen(screens[1]))
115 {
116 index = 1;
117 return true;
118 }
119
120 return false;
121 }
122
123 /// <summary>
124 /// Индекс группы главного окна (лобовое): <see cref="IsPmPlusForwardTwoScreenPreset"/> или тройной <c>(P)(F)(M)</c> — экран с <c>F</c>; иначе <c>0</c>.
125 /// </summary>
126 public static int GetMainWindowPresentationScreenIndexOrDefault(PresentationParseResult parse)
127 {
128 if (!parse.IsSuccess || parse.Screens.Count == 0)
129 return 0;
130 return TryGetMainWindowPresentationScreenIndex(parse.Screens, out var idx) ? idx : 0;
131 }
132
133 private static bool IsForwardOnlyScreen(IReadOnlyList<PresentationAnchorSlot> screen) =>
134 screen.Count == 1 && screen[0].Kind == PresentationAnchorKind.Forward;
135
136 private static bool TryGetForwardOnlyMainWindowScreenIndex(
137 IReadOnlyList<IReadOnlyList<PresentationAnchorSlot>> screens,
138 out int index)
139 {
140 index = -1;
141 if (IsPmPlusForwardTwoScreenPreset(screens) || IsForwardMfdTwoScreenPreset(screens))
142 {
143 if (IsForwardOnlyScreen(screens[0]))
144 {
145 index = 0;
146 return true;
147 }
148
149 if (IsForwardOnlyScreen(screens[1]))
150 {
151 index = 1;
152 return true;
153 }
154 }
155
156 return false;
157 }
158
159 private static bool TryGetForwardMfdHostPresentationScreenIndex(
160 IReadOnlyList<IReadOnlyList<PresentationAnchorSlot>> screens,
161 out int index)
162 {
163 index = -1;
164 if (!IsForwardMfdTwoScreenPreset(screens))
165 return false;
166
167 if (IsSingleAnchor(screens[0], PresentationAnchorKind.Mfd))
168 {
169 index = 0;
170 return true;
171 }
172
173 if (IsSingleAnchor(screens[1], PresentationAnchorKind.Mfd))
174 {
175 index = 1;
176 return true;
177 }
178
179 return false;
180 }
181
182 /// <summary>На экране есть и PFD, и MFD, и нет лобового (одна группа <c>xP+yM</c>).</summary>
183 private static bool IsPmCombinedScreen(IReadOnlyList<PresentationAnchorSlot> screen) =>
184 ContainsAnchor(screen, PresentationAnchorKind.Pfd)
185 && ContainsAnchor(screen, PresentationAnchorKind.Mfd)
186 && !ContainsAnchor(screen, PresentationAnchorKind.Forward);
187
188 /// <summary>Три дисплея: по одному якорю — <c>(PFD) (Forward) (MFD)</c> в этом порядке (ADR 0017).</summary>
189 public static bool IsTriplePfdForwardMfdPreset(IReadOnlyList<IReadOnlyList<PresentationAnchorSlot>> screens)
190 {
191 if (screens.Count != 3)
192 return false;
193
194 return IsSingleAnchor(screens[0], PresentationAnchorKind.Pfd)
195 && IsSingleAnchor(screens[1], PresentationAnchorKind.Forward)
196 && IsSingleAnchor(screens[2], PresentationAnchorKind.Mfd);
197 }
198
199 /// <summary>
200 /// Три дисплея: ровно по одному якорю на экран, набор <c>P</c>/<c>F</c>/<c>M</c> без повторов (любой порядок групп в строке — ADR 0017).
201 /// </summary>
202 public static bool IsTripleOneAnchorPerZonePreset(IReadOnlyList<IReadOnlyList<PresentationAnchorSlot>> screens)
203 {
204 if (screens.Count != 3)
205 return false;
206
207 PresentationAnchorKind? a0 = SingleAnchorKind(screens[0]);
208 PresentationAnchorKind? a1 = SingleAnchorKind(screens[1]);
209 PresentationAnchorKind? a2 = SingleAnchorKind(screens[2]);
210 if (a0 is null || a1 is null || a2 is null)
211 return false;
212
213 return a0 != a1 && a1 != a2 && a0 != a2
214 && HasAllZoneKinds(a0.Value, a1.Value, a2.Value);
215 }
216
217 /// <summary>
218 /// Индекс группы в строке <c>presentation</c>, которой соответствует окно-хост MFD (сопоставляется с N-м дисплеем в порядке
219 /// <see cref="PresentationMonitorTopology.OrderScreensForPresentation"/>). Иначе <c>false</c> — плейсмент без семантики.
220 /// </summary>
221 public static bool TryGetMfdHostPresentationScreenIndex(
222 IReadOnlyList<IReadOnlyList<PresentationAnchorSlot>> screens,
223 out int index)
224 {
225 index = -1;
226 if (IsDedicatedMfdSecondScreenPreset(screens))
227 {
228 index = 1;
229 return true;
230 }
231
232 if (TryGetForwardMfdHostPresentationScreenIndex(screens, out index))
233 return true;
234
235 if (TryGetSingleAnchorScreenIndex(screens, PresentationAnchorKind.Mfd, out index))
236 return true;
237
238 return false;
239 }
240
241 /// <summary>Индекс экрана с единственным якорем <c>P</c> в тройном пресете <c>(…) (…) (…)</c>.</summary>
242 public static bool TryGetPfdHostPresentationScreenIndex(
243 IReadOnlyList<IReadOnlyList<PresentationAnchorSlot>> screens,
244 out int index) =>
245 TryGetSingleAnchorScreenIndex(screens, PresentationAnchorKind.Pfd, out index);
246
247 private static bool TryGetSingleAnchorScreenIndex(
248 IReadOnlyList<IReadOnlyList<PresentationAnchorSlot>> screens,
249 PresentationAnchorKind kind,
250 out int index)
251 {
252 index = -1;
253 if (!IsTripleOneAnchorPerZonePreset(screens))
254 return false;
255
256 for (var i = 0; i < screens.Count; i++)
257 {
258 if (screens[i].Count != 1)
259 continue;
260 if (screens[i][0].Kind != kind)
261 continue;
262 index = i;
263 return true;
264 }
265
266 return false;
267 }
268
269 private static PresentationAnchorKind? SingleAnchorKind(IReadOnlyList<PresentationAnchorSlot> screen) =>
270 screen.Count == 1 ? screen[0].Kind : null;
271
272 private static bool HasAllZoneKinds(PresentationAnchorKind a, PresentationAnchorKind b, PresentationAnchorKind c)
273 {
274 var mask = 0;
275 void Add(PresentationAnchorKind k) =>
276 mask |= k switch
277 {
278 PresentationAnchorKind.Pfd => 1,
279 PresentationAnchorKind.Forward => 2,
280 PresentationAnchorKind.Mfd => 4,
281 _ => 0
282 };
283 Add(a);
284 Add(b);
285 Add(c);
286 return mask == 7;
287 }
288
289 private static bool IsSingleAnchor(IReadOnlyList<PresentationAnchorSlot> screen, PresentationAnchorKind kind) =>
290 screen.Count == 1 && screen[0].Kind == kind;
291
292 private static bool ContainsAnchor(IReadOnlyList<PresentationAnchorSlot> screen, PresentationAnchorKind kind)
293 {
294 for (var i = 0; i < screen.Count; i++)
295 {
296 if (screen[i].Kind == kind)
297 return true;
298 }
299
300 return false;
301 }
302}
303
View only · write via MCP/CIDE