ContentDialogHelper.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. using Avalonia;
  2. using Avalonia.Controls;
  3. using Avalonia.Controls.ApplicationLifetimes;
  4. using Avalonia.Controls.Primitives;
  5. using Avalonia.Media;
  6. using Avalonia.Threading;
  7. using FluentAvalonia.UI.Controls;
  8. using Ryujinx.Ava.Common.Locale;
  9. using Ryujinx.Ava.Ui.Models;
  10. using Ryujinx.Ava.Ui.Windows;
  11. using Ryujinx.Common.Logging;
  12. using System;
  13. using System.Threading;
  14. using System.Threading.Tasks;
  15. namespace Ryujinx.Ava.Ui.Controls
  16. {
  17. public static class ContentDialogHelper
  18. {
  19. private static bool _isChoiceDialogOpen;
  20. private async static Task<UserResult> ShowContentDialog(
  21. string title,
  22. string primaryText,
  23. string secondaryText,
  24. string primaryButton,
  25. string secondaryButton,
  26. string closeButton,
  27. int iconSymbol,
  28. UserResult primaryButtonResult = UserResult.Ok)
  29. {
  30. UserResult result = UserResult.None;
  31. bool useOverlay = false;
  32. Window mainWindow = null;
  33. if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime al)
  34. {
  35. foreach (var item in al.Windows)
  36. {
  37. if (item.IsActive && item is MainWindow window && window.ViewModel.IsGameRunning)
  38. {
  39. mainWindow = window;
  40. useOverlay = true;
  41. break;
  42. }
  43. }
  44. }
  45. ContentDialog contentDialog = null;
  46. ContentDialogOverlayWindow overlay = null;
  47. if (useOverlay)
  48. {
  49. overlay = new ContentDialogOverlayWindow()
  50. {
  51. Height = mainWindow.Bounds.Height,
  52. Width = mainWindow.Bounds.Width,
  53. Position = mainWindow.PointToScreen(new Point())
  54. };
  55. mainWindow.PositionChanged += OverlayOnPositionChanged;
  56. void OverlayOnPositionChanged(object sender, PixelPointEventArgs e)
  57. {
  58. overlay.Position = mainWindow.PointToScreen(new Point());
  59. }
  60. contentDialog = overlay.ContentDialog;
  61. bool opened = false;
  62. overlay.Opened += OverlayOnActivated;
  63. async void OverlayOnActivated(object sender, EventArgs e)
  64. {
  65. if (opened)
  66. {
  67. return;
  68. }
  69. opened = true;
  70. overlay.Position = mainWindow.PointToScreen(new Point());
  71. await ShowDialog();
  72. }
  73. await overlay.ShowDialog(mainWindow);
  74. }
  75. else
  76. {
  77. contentDialog = new ContentDialog();
  78. await ShowDialog();
  79. }
  80. async Task ShowDialog()
  81. {
  82. contentDialog.Title = title;
  83. contentDialog.PrimaryButtonText = primaryButton;
  84. contentDialog.SecondaryButtonText = secondaryButton;
  85. contentDialog.CloseButtonText = closeButton;
  86. contentDialog.Content = CreateDialogTextContent(primaryText, secondaryText, iconSymbol);
  87. contentDialog.PrimaryButtonCommand = MiniCommand.Create(() =>
  88. {
  89. result = primaryButtonResult;
  90. });
  91. contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
  92. {
  93. result = UserResult.No;
  94. });
  95. contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
  96. {
  97. result = UserResult.Cancel;
  98. });
  99. await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
  100. overlay?.Close();
  101. }
  102. if (useOverlay)
  103. {
  104. overlay.Content = null;
  105. overlay.Close();
  106. }
  107. return result;
  108. }
  109. public async static Task<UserResult> ShowDeferredContentDialog(
  110. StyleableWindow window,
  111. string title,
  112. string primaryText,
  113. string secondaryText,
  114. string primaryButton,
  115. string secondaryButton,
  116. string closeButton,
  117. int iconSymbol,
  118. ManualResetEvent deferResetEvent,
  119. Func<Window, Task> doWhileDeferred = null)
  120. {
  121. bool startedDeferring = false;
  122. UserResult result = UserResult.None;
  123. ContentDialog contentDialog = new ContentDialog
  124. {
  125. Title = title,
  126. PrimaryButtonText = primaryButton,
  127. SecondaryButtonText = secondaryButton,
  128. CloseButtonText = closeButton,
  129. Content = CreateDialogTextContent(primaryText, secondaryText, iconSymbol),
  130. PrimaryButtonCommand = MiniCommand.Create(() =>
  131. {
  132. result = primaryButton == LocaleManager.Instance["InputDialogYes"] ? UserResult.Yes : UserResult.Ok;
  133. }),
  134. };
  135. contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
  136. {
  137. contentDialog.PrimaryButtonClick -= DeferClose;
  138. result = UserResult.No;
  139. });
  140. contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
  141. {
  142. contentDialog.PrimaryButtonClick -= DeferClose;
  143. result = UserResult.Cancel;
  144. });
  145. contentDialog.PrimaryButtonClick += DeferClose;
  146. await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
  147. return result;
  148. async void DeferClose(ContentDialog sender, ContentDialogButtonClickEventArgs args)
  149. {
  150. if (startedDeferring)
  151. {
  152. return;
  153. }
  154. contentDialog.PrimaryButtonClick -= DeferClose;
  155. startedDeferring = true;
  156. var deferral = args.GetDeferral();
  157. result = primaryButton == LocaleManager.Instance["InputDialogYes"] ? UserResult.Yes : UserResult.Ok;
  158. contentDialog.PrimaryButtonClick -= DeferClose;
  159. #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
  160. Task.Run(() =>
  161. {
  162. deferResetEvent.WaitOne();
  163. Dispatcher.UIThread.Post(() =>
  164. {
  165. deferral.Complete();
  166. });
  167. });
  168. #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
  169. if (doWhileDeferred != null)
  170. {
  171. await doWhileDeferred(window);
  172. deferResetEvent.Set();
  173. }
  174. }
  175. }
  176. private static Grid CreateDialogTextContent(string primaryText, string secondaryText, int symbol)
  177. {
  178. Grid content = new Grid();
  179. content.RowDefinitions = new RowDefinitions() { new RowDefinition(), new RowDefinition() };
  180. content.ColumnDefinitions = new ColumnDefinitions() { new ColumnDefinition(GridLength.Auto), new ColumnDefinition() };
  181. content.MinHeight = 80;
  182. SymbolIcon icon = new SymbolIcon { Symbol = (Symbol)symbol, Margin = new Avalonia.Thickness(10) };
  183. icon.FontSize = 40;
  184. icon.VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center;
  185. Grid.SetColumn(icon, 0);
  186. Grid.SetRowSpan(icon, 2);
  187. Grid.SetRow(icon, 0);
  188. TextBlock primaryLabel = new TextBlock()
  189. {
  190. Text = primaryText,
  191. Margin = new Avalonia.Thickness(5),
  192. TextWrapping = Avalonia.Media.TextWrapping.Wrap,
  193. MaxWidth = 450
  194. };
  195. TextBlock secondaryLabel = new TextBlock()
  196. {
  197. Text = secondaryText,
  198. Margin = new Avalonia.Thickness(5),
  199. TextWrapping = Avalonia.Media.TextWrapping.Wrap,
  200. MaxWidth = 450
  201. };
  202. Grid.SetColumn(primaryLabel, 1);
  203. Grid.SetColumn(secondaryLabel, 1);
  204. Grid.SetRow(primaryLabel, 0);
  205. Grid.SetRow(secondaryLabel, 1);
  206. content.Children.Add(icon);
  207. content.Children.Add(primaryLabel);
  208. content.Children.Add(secondaryLabel);
  209. return content;
  210. }
  211. public static async Task<UserResult> CreateInfoDialog(
  212. string primary,
  213. string secondaryText,
  214. string acceptButton,
  215. string closeButton,
  216. string title)
  217. {
  218. return await ShowContentDialog(
  219. title,
  220. primary,
  221. secondaryText,
  222. acceptButton,
  223. "",
  224. closeButton,
  225. (int)Symbol.Important);
  226. }
  227. internal static async Task<UserResult> CreateConfirmationDialog(
  228. string primaryText,
  229. string secondaryText,
  230. string acceptButtonText,
  231. string cancelButtonText,
  232. string title,
  233. UserResult primaryButtonResult = UserResult.Yes)
  234. {
  235. return await ShowContentDialog(
  236. string.IsNullOrWhiteSpace(title) ? LocaleManager.Instance["DialogConfirmationTitle"] : title,
  237. primaryText,
  238. secondaryText,
  239. acceptButtonText,
  240. "",
  241. cancelButtonText,
  242. (int)Symbol.Help,
  243. primaryButtonResult);
  244. }
  245. internal static UpdateWaitWindow CreateWaitingDialog(string mainText, string secondaryText)
  246. {
  247. return new(mainText, secondaryText);
  248. }
  249. internal static async Task CreateUpdaterInfoDialog(string primary, string secondaryText)
  250. {
  251. await ShowContentDialog(
  252. LocaleManager.Instance["DialogUpdaterTitle"],
  253. primary,
  254. secondaryText,
  255. "",
  256. "",
  257. LocaleManager.Instance["InputDialogOk"],
  258. (int)Symbol.Important);
  259. }
  260. internal static async Task CreateWarningDialog(string primary, string secondaryText)
  261. {
  262. await ShowContentDialog(
  263. LocaleManager.Instance["DialogWarningTitle"],
  264. primary,
  265. secondaryText,
  266. "",
  267. "",
  268. LocaleManager.Instance["InputDialogOk"],
  269. (int)Symbol.Important);
  270. }
  271. internal static async Task CreateErrorDialog(string errorMessage, string secondaryErrorMessage = "")
  272. {
  273. Logger.Error?.Print(LogClass.Application, errorMessage);
  274. await ShowContentDialog(
  275. LocaleManager.Instance["DialogErrorTitle"],
  276. LocaleManager.Instance["DialogErrorMessage"],
  277. errorMessage,
  278. secondaryErrorMessage,
  279. "",
  280. LocaleManager.Instance["InputDialogOk"],
  281. (int)Symbol.Dismiss);
  282. }
  283. internal static async Task<bool> CreateChoiceDialog(string title, string primary, string secondaryText)
  284. {
  285. if (_isChoiceDialogOpen)
  286. {
  287. return false;
  288. }
  289. _isChoiceDialogOpen = true;
  290. UserResult response =
  291. await ShowContentDialog(
  292. title,
  293. primary,
  294. secondaryText,
  295. LocaleManager.Instance["InputDialogYes"],
  296. "",
  297. LocaleManager.Instance["InputDialogNo"],
  298. (int)Symbol.Help,
  299. UserResult.Yes);
  300. _isChoiceDialogOpen = false;
  301. return response == UserResult.Yes;
  302. }
  303. internal static async Task<bool> CreateExitDialog()
  304. {
  305. return await CreateChoiceDialog(
  306. LocaleManager.Instance["DialogExitTitle"],
  307. LocaleManager.Instance["DialogExitMessage"],
  308. LocaleManager.Instance["DialogExitSubMessage"]);
  309. }
  310. internal static async Task<bool> CreateStopEmulationDialog()
  311. {
  312. return await CreateChoiceDialog(
  313. LocaleManager.Instance["DialogStopEmulationTitle"],
  314. LocaleManager.Instance["DialogStopEmulationMessage"],
  315. LocaleManager.Instance["DialogExitSubMessage"]);
  316. }
  317. internal static async Task<string> CreateInputDialog(
  318. string title,
  319. string mainText,
  320. string subText,
  321. uint maxLength = int.MaxValue,
  322. string input = "")
  323. {
  324. var result = await InputDialog.ShowInputDialog(
  325. title,
  326. mainText,
  327. input,
  328. subText,
  329. maxLength);
  330. if (result.Result == UserResult.Ok)
  331. {
  332. return result.Input;
  333. }
  334. return string.Empty;
  335. }
  336. }
  337. }