ScreenManager.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using Microsoft.Xna.Framework;
  5. using MonoGame.Extended.Screens.Transitions;
  6. namespace MonoGame.Extended.Screens;
  7. /// <summary>
  8. /// Manages a stack of <see cref="Screen"/> instances in a game.
  9. /// </summary>
  10. /// <remarks>
  11. /// <para>
  12. /// Screens are managed using a stack based approach where the topmost screen is considered the active screen,
  13. /// while underlying screens can continue to update and draw in the background based on their settings.
  14. /// </para>
  15. /// <para>
  16. /// Note: Developers should be aware of performance considerations when keeping multiple screens active in the
  17. /// background for updates and/or drawing.
  18. /// </para>
  19. /// </remarks>
  20. public class ScreenManager : SimpleDrawableGameComponent
  21. {
  22. private readonly Stack<Screen> _screens;
  23. private Screen _activeScreen;
  24. private Transition _activeTransition;
  25. private Screen[] _cachedScreens;
  26. private bool _isScreenArrayDirty;
  27. /// <summary>
  28. /// Gets the currently active screen at the top of the screen stack,
  29. /// or <see langword="null"/> if no screens are loaded.
  30. /// </summary>
  31. public Screen ActiveScreen => _activeScreen;
  32. /// <summary>
  33. /// Gets a read-only list of all screens in the stack, ordered from bottom to top.
  34. /// </summary>
  35. /// <remarks>
  36. /// For performance, the list is cached internally and only rebuilt when the screen stack has been modified.
  37. /// </remarks>
  38. public IReadOnlyList<Screen> Screens
  39. {
  40. get
  41. {
  42. if (_isScreenArrayDirty)
  43. {
  44. _cachedScreens = _screens.Reverse().ToArray();
  45. _isScreenArrayDirty = false;
  46. }
  47. return _cachedScreens;
  48. }
  49. }
  50. /// <summary>
  51. /// Initializes a new instance of the <see cref="ScreenManager"/> class.
  52. /// </summary>
  53. public ScreenManager()
  54. {
  55. _screens = new Stack<Screen>();
  56. _cachedScreens = Array.Empty<Screen>();
  57. _isScreenArrayDirty = true;
  58. }
  59. /// <summary>
  60. /// Pushes a new screen onto the stack and makes it the active screen.
  61. /// </summary>
  62. /// <remarks>
  63. /// The previous active screen remains in the screen stack but becomes inactive.
  64. /// </remarks>
  65. /// <param name="screen">The screen to show.</param>
  66. /// <exception cref="ArgumentNullException">Thrown when <paramref name="screen"/> is <see langword="null"/>.</exception>
  67. public void ShowScreen(Screen screen)
  68. {
  69. ArgumentNullException.ThrowIfNull(screen);
  70. if (_activeScreen != null)
  71. {
  72. _activeScreen.IsActive = false;
  73. }
  74. screen.ScreenManager = this;
  75. screen.IsActive = true;
  76. screen.Initialize();
  77. screen.LoadContent();
  78. _screens.Push(screen);
  79. _activeScreen = screen;
  80. _isScreenArrayDirty = true;
  81. }
  82. /// <summary>
  83. /// Pushes a new screen onto the stack with a transition effect and makes it the active screen.
  84. /// </summary>
  85. /// <remarks>
  86. /// The previous active screen remains in the screen stack but becomes inactive.
  87. /// </remarks>
  88. /// <param name="screen">The screen to show.</param>
  89. /// <param name="transition">The transition effect to use when showing the screen.</param>
  90. public void ShowScreen(Screen screen, Transition transition)
  91. {
  92. if (_activeTransition != null)
  93. {
  94. return;
  95. }
  96. _activeTransition = transition;
  97. _activeTransition.StateChanged += (sender, args) => ShowScreen(screen);
  98. _activeTransition.Completed += (sender, args) =>
  99. {
  100. _activeTransition.Dispose();
  101. _activeTransition = null;
  102. };
  103. }
  104. /// <summary>
  105. /// Pops the current active screen from the stack, disposing it and making the next screen active.
  106. /// </summary>
  107. /// <remarks>
  108. /// If the screen stack becomes empty, no screen will be active.
  109. /// </remarks>
  110. public void CloseScreen()
  111. {
  112. if (!_screens.TryPop(out Screen screen))
  113. {
  114. return;
  115. }
  116. screen.IsActive = false;
  117. screen.UnloadContent();
  118. screen.Dispose();
  119. _isScreenArrayDirty = true;
  120. if (_screens.TryPeek(out _activeScreen))
  121. {
  122. _activeScreen.IsActive = true;
  123. }
  124. }
  125. /// <summary>
  126. /// Pops the current active screen from the stack with a transition effect, disposing it and making the next screen active.
  127. /// </summary>
  128. /// <remarks>
  129. /// If the screen stack becomes empty, no screen will be active.
  130. /// </remarks>
  131. /// <param name="transition">The transition effect to use when closing the screen.</param>
  132. public void CloseScreen(Transition transition)
  133. {
  134. if (_activeTransition != null)
  135. {
  136. return;
  137. }
  138. _activeTransition = transition;
  139. _activeTransition.StateChanged += (sender, args) => CloseScreen();
  140. _activeTransition.Completed += (sender, args) =>
  141. {
  142. _activeTransition.Dispose();
  143. _activeTransition = null;
  144. };
  145. }
  146. /// <summary>
  147. /// Replaces the current active screen with a new screen by closing the current screen and showing the new one.
  148. /// </summary>
  149. /// <remarks>
  150. /// This is equivalent to calling <see cref="CloseScreen()"/> followed by <see cref="ShowScreen(Screen)"/>
  151. /// </remarks>
  152. /// <param name="screen">The screen to replace the current active screen with.</param>
  153. public void ReplaceScreen(Screen screen)
  154. {
  155. CloseScreen();
  156. ShowScreen(screen);
  157. }
  158. /// <summary>
  159. /// Replaces the current active screen with a new screen using a transition effect.
  160. /// </summary>
  161. /// <remarks>
  162. /// This is equivalent to calling <see cref="CloseScreen(Transition)"/> followed by <see cref="ShowScreen(Screen, Transition)"/>.
  163. /// </remarks>
  164. /// <param name="screen">The screen to replace the current active screen with.</param>
  165. /// <param name="transition">The transition effect to use when replacing the screen.</param>
  166. public void ReplaceScreen(Screen screen, Transition transition)
  167. {
  168. if (_activeTransition != null)
  169. {
  170. return;
  171. }
  172. _activeTransition = transition;
  173. _activeTransition.StateChanged += (sender, args) => ReplaceScreen(screen);
  174. _activeTransition.Completed += (sender, args) =>
  175. {
  176. _activeTransition.Dispose();
  177. _activeTransition = null;
  178. };
  179. }
  180. /// <summary>
  181. /// Loads a screen, replacing any existing screens.
  182. /// </summary>
  183. /// <param name="screen">The screen to load</param>
  184. [Obsolete("This method is provided for backward compatibility and will be removed in the next major release. For new code, use ShowScreen(Screen), CloseScreen(), or ReplaceScreen(Screen).")]
  185. public void LoadScreen(Screen screen)
  186. {
  187. ReplaceScreen(screen);
  188. }
  189. /// <summary>
  190. /// Loads a screen with a transition effect, replacing any existing screens.
  191. /// </summary>
  192. /// <param name="screen">The screen to load</param>
  193. /// <param name="transition">The transition effect to use.</param>
  194. [Obsolete("This method is provided for backward compatibility and will be removed in the next major release. For new code, use ShowScreen(Screen), CloseScreen(), or ReplaceScreen(Screen).")]
  195. public void LoadScreen(Screen screen, Transition transition)
  196. {
  197. ReplaceScreen(screen, transition);
  198. }
  199. /// <summary>
  200. /// Clears all screens from the stack, disposing them and setting no active screen.
  201. /// </summary>
  202. public void ClearScreens()
  203. {
  204. while (_screens.TryPop(out Screen screen))
  205. {
  206. screen.IsActive = false;
  207. screen.UnloadContent();
  208. screen.Dispose();
  209. }
  210. _activeScreen = null;
  211. _isScreenArrayDirty = true;
  212. }
  213. /// <summary>
  214. /// Initializes the screen manager and the currently active screen if one exists.
  215. /// </summary>
  216. public override void Initialize()
  217. {
  218. base.Initialize();
  219. if (_activeScreen != null)
  220. {
  221. _activeScreen.Initialize();
  222. }
  223. }
  224. /// <summary>
  225. /// Loads content for the screen manager and the currently active screen if one exists.
  226. /// </summary>
  227. protected override void LoadContent()
  228. {
  229. base.LoadContent();
  230. if (_activeScreen != null)
  231. {
  232. _activeScreen.LoadContent();
  233. }
  234. }
  235. /// <summary>
  236. /// Unloads content for the screen manager and the currently active screen if one exists.
  237. /// </summary>
  238. protected override void UnloadContent()
  239. {
  240. base.UnloadContent();
  241. if (_activeScreen != null)
  242. {
  243. _activeScreen.UnloadContent();
  244. }
  245. }
  246. /// <summary>
  247. /// Updates all screens in the stack where the <see cref="Screen.IsActive"/> or
  248. /// <see cref="Screen.UpdateWhenInactive"/> property is set to <see langword="true"/>.
  249. /// </summary>
  250. /// <remarks>
  251. /// Update order for screens is done from the bottom of the stack to the top of.
  252. /// </remarks>
  253. /// <param name="gameTime">Provides a snapshot of timing values.</param>
  254. public override void Update(GameTime gameTime)
  255. {
  256. IReadOnlyList<Screen> screens = Screens;
  257. for (int i = 0; i < screens.Count; i++)
  258. {
  259. Screen screen = screens[i];
  260. if (screen.IsActive || screen.UpdateWhenInactive)
  261. {
  262. screen.Update(gameTime);
  263. }
  264. }
  265. if (_activeTransition != null)
  266. {
  267. _activeTransition.Update(gameTime);
  268. }
  269. }
  270. /// <summary>
  271. /// Draws all screens in the stack where the <see cref="Screen.IsActive"/> or
  272. /// <see cref="Screen.DrawWhenInactive"/> property is set to <see langword="true"/>.
  273. /// </summary>
  274. /// <remarks>
  275. /// Draw order for screens is done from the bottom of the stack to the top to allow background screens
  276. /// to draw before foreground ones for property visual stacking.
  277. /// </remarks>
  278. /// <param name="gameTime">Provides a snapshot of timing values.</param>
  279. public override void Draw(GameTime gameTime)
  280. {
  281. IReadOnlyList<Screen> screens = Screens;
  282. for (int i = 0; i < screens.Count; i++)
  283. {
  284. Screen screen = screens[i];
  285. if (screen.IsActive || screen.DrawWhenInactive)
  286. {
  287. screen.Draw(gameTime);
  288. }
  289. }
  290. if (_activeTransition != null)
  291. {
  292. _activeTransition.Draw(gameTime);
  293. }
  294. }
  295. }