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 }