/ src / Ryujinx / UI / Helpers / ContentDialogHelper.cs
ContentDialogHelper.cs
  1  using Avalonia;
  2  using Avalonia.Controls;
  3  using Avalonia.Controls.ApplicationLifetimes;
  4  using Avalonia.Layout;
  5  using Avalonia.Media;
  6  using Avalonia.Threading;
  7  using FluentAvalonia.Core;
  8  using FluentAvalonia.UI.Controls;
  9  using Ryujinx.Ava.Common.Locale;
 10  using Ryujinx.Ava.UI.Windows;
 11  using Ryujinx.Common.Logging;
 12  using System;
 13  using System.Threading;
 14  using System.Threading.Tasks;
 15  
 16  namespace Ryujinx.Ava.UI.Helpers
 17  {
 18      public static class ContentDialogHelper
 19      {
 20          private static bool _isChoiceDialogOpen;
 21          private static ContentDialogOverlayWindow _contentDialogOverlayWindow;
 22  
 23          private async static Task<UserResult> ShowContentDialog(
 24               string title,
 25               object content,
 26               string primaryButton,
 27               string secondaryButton,
 28               string closeButton,
 29               UserResult primaryButtonResult = UserResult.Ok,
 30               ManualResetEvent deferResetEvent = null,
 31               TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null)
 32          {
 33              UserResult result = UserResult.None;
 34  
 35              ContentDialog contentDialog = new()
 36              {
 37                  Title = title,
 38                  PrimaryButtonText = primaryButton,
 39                  SecondaryButtonText = secondaryButton,
 40                  CloseButtonText = closeButton,
 41                  Content = content,
 42                  PrimaryButtonCommand = MiniCommand.Create(() =>
 43                  {
 44                      result = primaryButtonResult;
 45                  }),
 46              };
 47  
 48              contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
 49              {
 50                  result = UserResult.No;
 51                  contentDialog.PrimaryButtonClick -= deferCloseAction;
 52              });
 53  
 54              contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
 55              {
 56                  result = UserResult.Cancel;
 57                  contentDialog.PrimaryButtonClick -= deferCloseAction;
 58              });
 59  
 60              if (deferResetEvent != null)
 61              {
 62                  contentDialog.PrimaryButtonClick += deferCloseAction;
 63              }
 64  
 65              await ShowAsync(contentDialog);
 66  
 67              return result;
 68          }
 69  
 70          public async static Task<UserResult> ShowTextDialog(
 71              string title,
 72              string primaryText,
 73              string secondaryText,
 74              string primaryButton,
 75              string secondaryButton,
 76              string closeButton,
 77              int iconSymbol,
 78              UserResult primaryButtonResult = UserResult.Ok,
 79              ManualResetEvent deferResetEvent = null,
 80              TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null)
 81          {
 82              Grid content = CreateTextDialogContent(primaryText, secondaryText, iconSymbol);
 83  
 84              return await ShowContentDialog(title, content, primaryButton, secondaryButton, closeButton, primaryButtonResult, deferResetEvent, deferCloseAction);
 85          }
 86  
 87          public async static Task<UserResult> ShowDeferredContentDialog(
 88              StyleableWindow window,
 89              string title,
 90              string primaryText,
 91              string secondaryText,
 92              string primaryButton,
 93              string secondaryButton,
 94              string closeButton,
 95              int iconSymbol,
 96              ManualResetEvent deferResetEvent,
 97              Func<Window, Task> doWhileDeferred = null)
 98          {
 99              bool startedDeferring = false;
100  
101              return await ShowTextDialog(
102                  title,
103                  primaryText,
104                  secondaryText,
105                  primaryButton,
106                  secondaryButton,
107                  closeButton,
108                  iconSymbol,
109                  primaryButton == LocaleManager.Instance[LocaleKeys.InputDialogYes] ? UserResult.Yes : UserResult.Ok,
110                  deferResetEvent,
111                  DeferClose);
112  
113              async void DeferClose(ContentDialog sender, ContentDialogButtonClickEventArgs args)
114              {
115                  if (startedDeferring)
116                  {
117                      return;
118                  }
119  
120                  sender.PrimaryButtonClick -= DeferClose;
121  
122                  startedDeferring = true;
123  
124                  var deferral = args.GetDeferral();
125  
126                  sender.PrimaryButtonClick -= DeferClose;
127  
128                  _ = Task.Run(() =>
129                  {
130                      deferResetEvent.WaitOne();
131  
132                      Dispatcher.UIThread.Post(() =>
133                      {
134                          deferral.Complete();
135                      });
136                  });
137  
138                  if (doWhileDeferred != null)
139                  {
140                      await doWhileDeferred(window);
141  
142                      deferResetEvent.Set();
143                  }
144              }
145          }
146  
147          private static Grid CreateTextDialogContent(string primaryText, string secondaryText, int symbol)
148          {
149              Grid content = new()
150              {
151                  RowDefinitions = new RowDefinitions { new(), new() },
152                  ColumnDefinitions = new ColumnDefinitions { new(GridLength.Auto), new() },
153  
154                  MinHeight = 80,
155              };
156  
157              SymbolIcon icon = new()
158              {
159                  Symbol = (Symbol)symbol,
160                  Margin = new Thickness(10),
161                  FontSize = 40,
162                  VerticalAlignment = VerticalAlignment.Center,
163              };
164  
165              Grid.SetColumn(icon, 0);
166              Grid.SetRowSpan(icon, 2);
167              Grid.SetRow(icon, 0);
168  
169              TextBlock primaryLabel = new()
170              {
171                  Text = primaryText,
172                  Margin = new Thickness(5),
173                  TextWrapping = TextWrapping.Wrap,
174                  MaxWidth = 450,
175              };
176  
177              TextBlock secondaryLabel = new()
178              {
179                  Text = secondaryText,
180                  Margin = new Thickness(5),
181                  TextWrapping = TextWrapping.Wrap,
182                  MaxWidth = 450,
183              };
184  
185              Grid.SetColumn(primaryLabel, 1);
186              Grid.SetColumn(secondaryLabel, 1);
187              Grid.SetRow(primaryLabel, 0);
188              Grid.SetRow(secondaryLabel, 1);
189  
190              content.Children.Add(icon);
191              content.Children.Add(primaryLabel);
192              content.Children.Add(secondaryLabel);
193  
194              return content;
195          }
196  
197          public static async Task<UserResult> CreateInfoDialog(
198              string primary,
199              string secondaryText,
200              string acceptButton,
201              string closeButton,
202              string title)
203          {
204              return await ShowTextDialog(
205                  title,
206                  primary,
207                  secondaryText,
208                  acceptButton,
209                  "",
210                  closeButton,
211                  (int)Symbol.Important);
212          }
213  
214          internal static async Task<UserResult> CreateConfirmationDialog(
215              string primaryText,
216              string secondaryText,
217              string acceptButtonText,
218              string cancelButtonText,
219              string title,
220              UserResult primaryButtonResult = UserResult.Yes)
221          {
222              return await ShowTextDialog(
223                  string.IsNullOrWhiteSpace(title) ? LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle] : title,
224                  primaryText,
225                  secondaryText,
226                  acceptButtonText,
227                  "",
228                  cancelButtonText,
229                  (int)Symbol.Help,
230                  primaryButtonResult);
231          }
232  
233          internal static async Task CreateUpdaterInfoDialog(string primary, string secondaryText)
234          {
235              await ShowTextDialog(
236                  LocaleManager.Instance[LocaleKeys.DialogUpdaterTitle],
237                  primary,
238                  secondaryText,
239                  "",
240                  "",
241                  LocaleManager.Instance[LocaleKeys.InputDialogOk],
242                  (int)Symbol.Important);
243          }
244  
245          internal static async Task CreateWarningDialog(string primary, string secondaryText)
246          {
247              await ShowTextDialog(
248                  LocaleManager.Instance[LocaleKeys.DialogWarningTitle],
249                  primary,
250                  secondaryText,
251                  "",
252                  "",
253                  LocaleManager.Instance[LocaleKeys.InputDialogOk],
254                  (int)Symbol.Important);
255          }
256  
257          internal static async Task CreateErrorDialog(string errorMessage, string secondaryErrorMessage = "")
258          {
259              Logger.Error?.Print(LogClass.Application, errorMessage);
260  
261              await ShowTextDialog(
262                  LocaleManager.Instance[LocaleKeys.DialogErrorTitle],
263                  LocaleManager.Instance[LocaleKeys.DialogErrorMessage],
264                  errorMessage,
265                  secondaryErrorMessage,
266                  "",
267                  LocaleManager.Instance[LocaleKeys.InputDialogOk],
268                  (int)Symbol.Dismiss);
269          }
270  
271          internal static async Task<bool> CreateChoiceDialog(string title, string primary, string secondaryText)
272          {
273              if (_isChoiceDialogOpen)
274              {
275                  return false;
276              }
277  
278              _isChoiceDialogOpen = true;
279  
280              UserResult response = await ShowTextDialog(
281                  title,
282                  primary,
283                  secondaryText,
284                  LocaleManager.Instance[LocaleKeys.InputDialogYes],
285                  "",
286                  LocaleManager.Instance[LocaleKeys.InputDialogNo],
287                  (int)Symbol.Help,
288                  UserResult.Yes);
289  
290              _isChoiceDialogOpen = false;
291  
292              return response == UserResult.Yes;
293          }
294  
295          internal static async Task<bool> CreateExitDialog()
296          {
297              return await CreateChoiceDialog(
298                  LocaleManager.Instance[LocaleKeys.DialogExitTitle],
299                  LocaleManager.Instance[LocaleKeys.DialogExitMessage],
300                  LocaleManager.Instance[LocaleKeys.DialogExitSubMessage]);
301          }
302  
303          internal static async Task<bool> CreateStopEmulationDialog()
304          {
305              return await CreateChoiceDialog(
306                  LocaleManager.Instance[LocaleKeys.DialogStopEmulationTitle],
307                  LocaleManager.Instance[LocaleKeys.DialogStopEmulationMessage],
308                  LocaleManager.Instance[LocaleKeys.DialogExitSubMessage]);
309          }
310  
311          public static async Task<ContentDialogResult> ShowAsync(ContentDialog contentDialog)
312          {
313              ContentDialogResult result;
314              bool isTopDialog = true;
315  
316              Window parent = GetMainWindow();
317  
318              if (_contentDialogOverlayWindow != null)
319              {
320                  isTopDialog = false;
321              }
322  
323              if (parent is MainWindow window)
324              {
325                  parent.Activate();
326  
327                  _contentDialogOverlayWindow = new ContentDialogOverlayWindow
328                  {
329                      Height = parent.Bounds.Height,
330                      Width = parent.Bounds.Width,
331                      Position = parent.PointToScreen(new Point()),
332                      ShowInTaskbar = false,
333                  };
334  
335                  parent.PositionChanged += OverlayOnPositionChanged;
336  
337                  void OverlayOnPositionChanged(object sender, PixelPointEventArgs e)
338                  {
339                      if (_contentDialogOverlayWindow is null)
340                      {
341                          return;
342                      }
343  
344                      _contentDialogOverlayWindow.Position = parent.PointToScreen(new Point());
345                  }
346  
347                  _contentDialogOverlayWindow.ContentDialog = contentDialog;
348  
349                  bool opened = false;
350  
351                  _contentDialogOverlayWindow.Opened += OverlayOnActivated;
352  
353                  async void OverlayOnActivated(object sender, EventArgs e)
354                  {
355                      if (opened)
356                      {
357                          return;
358                      }
359  
360                      opened = true;
361  
362                      _contentDialogOverlayWindow.Position = parent.PointToScreen(new Point());
363  
364                      result = await ShowDialog();
365                  }
366  
367                  result = await _contentDialogOverlayWindow.ShowDialog<ContentDialogResult>(parent);
368              }
369              else
370              {
371                  result = await ShowDialog();
372              }
373  
374              async Task<ContentDialogResult> ShowDialog()
375              {
376                  if (_contentDialogOverlayWindow is not null)
377                  {
378                      result = await contentDialog.ShowAsync(_contentDialogOverlayWindow);
379  
380                      _contentDialogOverlayWindow!.Close();
381                  }
382                  else
383                  {
384                      result = ContentDialogResult.None;
385  
386                      Logger.Warning?.Print(LogClass.UI, "Content dialog overlay failed to populate. Default value has been returned.");
387                  }
388  
389                  return result;
390              }
391  
392              if (isTopDialog && _contentDialogOverlayWindow is not null)
393              {
394                  _contentDialogOverlayWindow.Content = null;
395                  _contentDialogOverlayWindow.Close();
396                  _contentDialogOverlayWindow = null;
397              }
398  
399              return result;
400          }
401  
402          public static Task ShowWindowAsync(Window dialogWindow, Window mainWindow = null)
403          {
404              mainWindow ??= GetMainWindow();
405  
406              return dialogWindow.ShowDialog(_contentDialogOverlayWindow ?? mainWindow);
407          }
408  
409          private static Window GetMainWindow()
410          {
411              if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime al)
412              {
413                  foreach (Window item in al.Windows)
414                  {
415                      if (item is MainWindow window)
416                      {
417                          return window;
418                      }
419                  }
420              }
421  
422              return null;
423          }
424      }
425  }