NetworkBusyScreen.cs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. //-----------------------------------------------------------------------------
  2. // NetworkBusyScreen.cs
  3. //
  4. // Microsoft XNA Community Game Platform
  5. // Copyright (C) Microsoft Corporation. All rights reserved.
  6. //-----------------------------------------------------------------------------
  7. using System;
  8. using System.Threading.Tasks;
  9. using GameStateManagement;
  10. using Microsoft.Xna.Framework;
  11. using Microsoft.Xna.Framework.Content;
  12. using Microsoft.Xna.Framework.Graphics;
  13. using Microsoft.Xna.Framework.Input;
  14. namespace Blackjack
  15. {
  16. /// <summary>
  17. /// When an asynchronous network operation (for instance searching for or joining a
  18. /// session) is in progress, this screen displays a busy indicator and blocks input.
  19. /// It monitors a Task&lt;T&gt; returned by the async call, showing the indicator while
  20. /// the task is running. When the task completes, it raises an event with the
  21. /// operation result (or null on failure), then automatically dismisses itself.
  22. /// Because this screen sits on top while the async operation is in progress, it
  23. /// captures all user input to prevent interaction with underlying screens until
  24. /// the operation completes.
  25. /// </summary>
  26. class NetworkBusyScreen<T> : GameScreen
  27. {
  28. readonly Task<T> task;
  29. readonly System.Threading.CancellationTokenSource cts;
  30. bool completionRaised;
  31. Texture2D gradientTexture;
  32. Texture2D busyTexture;
  33. event EventHandler<OperationCompletedEventArgs> operationCompleted;
  34. public event EventHandler<OperationCompletedEventArgs> OperationCompleted
  35. {
  36. add { operationCompleted += value; }
  37. remove { operationCompleted -= value; }
  38. }
  39. /// <summary>
  40. /// Constructs a network busy screen for the specified asynchronous operation.
  41. /// Accepts a Task&lt;T&gt; representing the in-flight operation.
  42. /// </summary>
  43. public NetworkBusyScreen(Task<T> task)
  44. {
  45. this.task = task;
  46. this.cts = null;
  47. IsPopup = true;
  48. TransitionOnTime = TimeSpan.FromSeconds(0.1);
  49. TransitionOffTime = TimeSpan.FromSeconds(0.2);
  50. }
  51. /// <summary>
  52. /// Overload that accepts a CancellationTokenSource to allow user cancellation.
  53. /// </summary>
  54. public NetworkBusyScreen(Task<T> task, System.Threading.CancellationTokenSource cts)
  55. {
  56. this.task = task;
  57. this.cts = cts;
  58. IsPopup = true;
  59. TransitionOnTime = TimeSpan.FromSeconds(0.1);
  60. TransitionOffTime = TimeSpan.FromSeconds(0.2);
  61. }
  62. /// <summary>
  63. /// Loads graphics content for this screen. This uses the shared ContentManager
  64. /// provided by the Game class, so the content will remain loaded forever.
  65. /// Whenever a subsequent NetworkBusyScreen tries to load this same content,
  66. /// it will just get back another reference to the already loaded data.
  67. /// </summary>
  68. public override void LoadContent()
  69. {
  70. ContentManager content = ScreenManager.Game.Content;
  71. gradientTexture = content.Load<Texture2D>("Images/UI/gradient");
  72. busyTexture = content.Load<Texture2D>("Images/GamePadCursor"); // TODO change to hourglass
  73. }
  74. /// <summary>
  75. /// Updates the NetworkBusyScreen, checking whether the underlying Task has
  76. /// completed and raising OperationCompleted when it does.
  77. /// </summary>
  78. public override void Update(GameTime gameTime, bool otherScreenHasFocus,
  79. bool coveredByOtherScreen)
  80. {
  81. base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
  82. // Optional: allow user to cancel (Esc or B)
  83. if (!completionRaised && cts != null)
  84. {
  85. var kb = Keyboard.GetState();
  86. if (kb.IsKeyDown(Keys.Escape))
  87. {
  88. cts.Cancel();
  89. }
  90. var gp = GamePad.GetState(PlayerIndex.One);
  91. if (gp.IsConnected && gp.IsButtonDown(Buttons.B))
  92. {
  93. cts.Cancel();
  94. }
  95. }
  96. // Has our asynchronous operation completed?
  97. if (!completionRaised && task != null && task.IsCompleted)
  98. {
  99. object resultObject = default(T);
  100. Exception error = null;
  101. try
  102. {
  103. // Accessing Result will throw if the task faulted/canceled.
  104. // We catch here and allow handlers to present error UI.
  105. resultObject = task.Result;
  106. }
  107. catch (Exception ex)
  108. {
  109. // Leave result as default (usually null) to signal failure to handlers.
  110. error = (task?.Exception?.GetBaseException()) ?? ex;
  111. }
  112. var handler = operationCompleted;
  113. if (handler != null)
  114. {
  115. handler(this, new OperationCompletedEventArgs(resultObject, error));
  116. }
  117. completionRaised = true;
  118. // Clear handlers to avoid reentry if this screen lingers during transition off
  119. operationCompleted = null;
  120. ExitScreen();
  121. }
  122. }
  123. /// <summary>
  124. /// Draws the NetworkBusyScreen.
  125. /// </summary>
  126. public override void Draw(GameTime gameTime)
  127. {
  128. SpriteBatch spriteBatch = ScreenManager.SpriteBatch;
  129. SpriteFont font = ScreenManager.Font;
  130. string message = Resources.NetworkBusy;
  131. const int hPad = 32;
  132. const int vPad = 16;
  133. // Center the message text in the viewport.
  134. Vector2 viewportSize = new Vector2(ScreenManager.BASE_BUFFER_WIDTH, ScreenManager.BASE_BUFFER_HEIGHT);
  135. Vector2 textSize = font.MeasureString(message);
  136. // Add enough room to spin a cat.
  137. Vector2 catSize = new Vector2(busyTexture.Width);
  138. textSize.X = Math.Max(textSize.X, catSize.X);
  139. textSize.Y += catSize.Y + vPad;
  140. Vector2 textPosition = (viewportSize - textSize) / 2;
  141. // The background includes a border somewhat larger than the text itself.
  142. Rectangle backgroundRectangle = new Rectangle((int)textPosition.X - hPad,
  143. (int)textPosition.Y - vPad,
  144. (int)textSize.X + hPad * 2,
  145. (int)textSize.Y + vPad * 2);
  146. // Fade the popup alpha during transitions.
  147. Color color = Color.White * TransitionAlpha;
  148. spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, null, null, ScreenManager.GlobalTransformation);
  149. // Draw the background rectangle.
  150. spriteBatch.Draw(gradientTexture, backgroundRectangle, color);
  151. // Draw the message box text.
  152. spriteBatch.DrawString(font, message, textPosition, color);
  153. // Draw the spinning cat progress indicator.
  154. float catRotation = (float)gameTime.TotalGameTime.TotalSeconds * 3;
  155. Vector2 catPosition = new Vector2(textPosition.X + textSize.X / 2,
  156. textPosition.Y + textSize.Y -
  157. catSize.Y / 2);
  158. spriteBatch.Draw(busyTexture, catPosition, null, color, catRotation,
  159. catSize / 2, 1, SpriteEffects.None, 0);
  160. spriteBatch.End();
  161. }
  162. }
  163. }