Forge
csharpdeeb25a2
1using System;
2using Avalonia.Controls;
3using CascadeIDE.Services;
4using CascadeIDE.Services.Presentation;
5
6namespace CascadeIDE.Views;
7
8/// <summary>Вторичные окна презентации (PFD/MFD-хосты): открытие, плейсмент, persist, автозапуск — ADR 0017.</summary>
9public partial class MainWindow
10{
11 private MfdHostWindow? _mfdHostWindow;
12 private PfdHostWindow? _pfdHostWindow;
13 private PmSplitHostWindow? _pmSplitHostWindow;
14
15 private static bool TryActivateSecondary(Window? window)
16 {
17 if (window is not { IsVisible: true })
18 return false;
19 if (window.WindowState == WindowState.Minimized)
20 window.WindowState = WindowState.Normal;
21 window.Activate();
22 return true;
23 }
24
25 private void CloseSecondaryHostWindow<T>(ref T? windowField, Action<ViewModels.MainWindowViewModel> setShellOpenFalse)
26 where T : Window
27 {
28 if (windowField is null)
29 return;
30 if (DataContext is ViewModels.MainWindowViewModel vm)
31 setShellOpenFalse(vm);
32 try
33 {
34 windowField.Close();
35 }
36 catch
37 {
38 // окно уже уничтожено
39 }
40
41 windowField = null;
42 }
43
44 private void ToggleMfdHostWindow()
45 {
46 if (DataContext is not ViewModels.MainWindowViewModel vm)
47 return;
48
49 if (TryActivateSecondary(_mfdHostWindow))
50 return;
51
52 var w = new MfdHostWindow { DataContext = vm };
53 w.Closing += (_, _) =>
54 {
55 vm.PersistMfdHostWindowBounds(w.Position.X, w.Position.Y, w.Width, w.Height);
56 };
57 w.Closed += (_, _) =>
58 {
59 if (ReferenceEquals(_mfdHostWindow, w))
60 {
61 _mfdHostWindow = null;
62 // Закрытие второго TopLevel не должно менять раскладку main-window:
63 // сохраняем suppress-флаг MFD-колонки до явного смены пресета/режима.
64 }
65 };
66 PresentationHostWindowPlacement.PresentationHostWindowBounds? savedBounds =
67 vm.TryGetSavedMfdHostWindowBounds(out var b) ? b : null;
68 PresentationHostWindowPlacement.PlaceOrRestore(
69 this,
70 w,
71 savedBounds,
72 vm.MfdHostPresentationScreenIndex,
73 vm.MaximizePresentationHostWindowsOnDedicatedScreens);
74 vm.SetMfdHostWindowShellOpen(true);
75 _mfdHostWindow = w;
76 w.Show(this);
77 }
78
79 private void TogglePmSplitHostWindow()
80 {
81 if (DataContext is not ViewModels.MainWindowViewModel vm)
82 return;
83
84 if (TryActivateSecondary(_pmSplitHostWindow))
85 return;
86
87 var w = new PmSplitHostWindow { DataContext = vm };
88 w.Closing += (_, _) =>
89 {
90 vm.PersistPmSplitHostWindowBounds(w.Position.X, w.Position.Y, w.Width, w.Height);
91 };
92 w.Closed += (_, _) =>
93 {
94 if (ReferenceEquals(_pmSplitHostWindow, w))
95 _pmSplitHostWindow = null;
96 };
97 PresentationHostWindowPlacement.PresentationHostWindowBounds? savedBounds =
98 vm.TryGetSavedPmSplitHostWindowBounds(out var pb) ? pb : null;
99 var pmPlacementIndex = ResolvePmSplitHostPlacementScreenIndex(vm);
100 PresentationHostWindowPlacement.PlaceOrRestore(
101 this,
102 w,
103 savedBounds,
104 pmPlacementIndex,
105 vm.MaximizePresentationHostWindowsOnDedicatedScreens);
106 _pmSplitHostWindow = w;
107 w.Show(this);
108 }
109
110 private void TogglePfdHostWindow()
111 {
112 if (DataContext is not ViewModels.MainWindowViewModel vm)
113 return;
114
115 if (TryActivateSecondary(_pfdHostWindow))
116 return;
117
118 var w = new PfdHostWindow { DataContext = vm };
119 w.Closing += (_, _) =>
120 {
121 vm.PersistPfdHostWindowBounds(w.Position.X, w.Position.Y, w.Width, w.Height);
122 };
123 w.Closed += (_, _) =>
124 {
125 if (ReferenceEquals(_pfdHostWindow, w))
126 _pfdHostWindow = null;
127 };
128 PresentationHostWindowPlacement.PresentationHostWindowBounds? savedBounds =
129 vm.TryGetSavedPfdHostWindowBounds(out var pb) ? pb : null;
130 PresentationHostWindowPlacement.PlaceOrRestore(
131 this,
132 w,
133 savedBounds,
134 vm.PfdHostPresentationScreenIndex,
135 vm.MaximizePresentationHostWindowsOnDedicatedScreens);
136 vm.SetPfdHostWindowShellOpen(true);
137 _pfdHostWindow = w;
138 w.Show(this);
139 }
140
141 /// <summary>Минимум мониторов для автозапуска MFD-хоста (индекс из пресета или хотя бы два экрана).</summary>
142 private static int MinScreensForMfdHostStartup(ViewModels.MainWindowViewModel vm) =>
143 vm.MfdHostPresentationScreenIndex is int idx ? idx + 1 : 2;
144
145 /// <summary>Минимум мониторов для автозапуска PFD-хоста при тройном пресете (учитываются индексы P и M).</summary>
146 private static int MinScreensForPfdHostStartup(ViewModels.MainWindowViewModel vm)
147 {
148 var min = 1;
149 if (vm.PfdHostPresentationScreenIndex is int p)
150 min = Math.Max(min, p + 1);
151 if (vm.MfdHostPresentationScreenIndex is int m)
152 min = Math.Max(min, m + 1);
153 return min;
154 }
155
156 private int MinScreensForPmSplitHostStartup(ViewModels.MainWindowViewModel vm)
157 {
158 if (PresentationPmPlusForwardPlacement.TryGetOrderedIndices(Screens, vm.PresentationParse.Screens, out var forwardIdx, out var pmIdx))
159 return Math.Max(forwardIdx, pmIdx) + 1;
160 return vm.PmSplitHostPresentationScreenIndex is int idx ? idx + 1 : 2;
161 }
162
163 /// <summary>Индекс дисплея для <see cref="PmSplitHostWindow"/> в порядке LTR-топологии; primary+сосед для симметрии <c>(F)(P+M)</c>/<c>(P+M)(F)</c>.</summary>
164 private int? ResolvePmSplitHostPlacementScreenIndex(ViewModels.MainWindowViewModel vm)
165 {
166 if (PresentationPmPlusForwardPlacement.TryGetOrderedIndices(Screens, vm.PresentationParse.Screens, out _, out var pmIdx))
167 return pmIdx;
168 return vm.PmSplitHostPresentationScreenIndex;
169 }
170
171 private void TryOpenMfdHostWindowOnStartup()
172 {
173 if (DataContext is not ViewModels.MainWindowViewModel vm)
174 return;
175 if (!vm.PresentationRequestsMfdHostWindow || !vm.OpenMfdHostWindowOnStartup)
176 return;
177 if (Screens.All.Count < MinScreensForMfdHostStartup(vm))
178 return;
179 ToggleMfdHostWindow();
180 }
181
182 private void TryOpenPfdHostWindowOnStartup()
183 {
184 if (DataContext is not ViewModels.MainWindowViewModel vm)
185 return;
186 if (!vm.PresentationRequestsPfdHostWindow || !vm.OpenPfdHostWindowOnStartup)
187 return;
188 if (Screens.All.Count < MinScreensForPfdHostStartup(vm))
189 return;
190 TogglePfdHostWindow();
191 }
192
193 private void TryOpenPmSplitHostWindowOnStartup()
194 {
195 if (DataContext is not ViewModels.MainWindowViewModel vm)
196 return;
197 if (!vm.PresentationRequestsPmSplitHostWindow || !vm.OpenPmSplitHostWindowOnStartup)
198 return;
199 if (Screens.All.Count < MinScreensForPmSplitHostStartup(vm))
200 return;
201 TogglePmSplitHostWindow();
202 }
203
204 private void CloseMfdHostWindowIfOpen() =>
205 CloseSecondaryHostWindow(ref _mfdHostWindow, static vm => vm.SetMfdHostWindowShellOpen(false));
206
207 private void ClosePfdHostWindowIfOpen() =>
208 CloseSecondaryHostWindow(ref _pfdHostWindow, static vm => vm.SetPfdHostWindowShellOpen(false));
209
210 private void ClosePmSplitHostWindowIfOpen()
211 {
212 if (_pmSplitHostWindow is null)
213 return;
214 try
215 {
216 _pmSplitHostWindow.Close();
217 }
218 catch
219 {
220 // окно уже уничтожено
221 }
222
223 _pmSplitHostWindow = null;
224 }
225}
226
View only · write via MCP/CIDE