2
0

MenuScreen.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. using System;
  2. using System.Collections.Generic;
  3. using Microsoft.Xna.Framework;
  4. using Microsoft.Xna.Framework.Graphics;
  5. namespace FarseerPhysics.SamplesFramework
  6. {
  7. /// <summary>
  8. /// Base class for screens that contain a menu of options. The user can
  9. /// move up and down to select an entry, or cancel to back out of the screen.
  10. /// </summary>
  11. public class MenuScreen : GameScreen
  12. {
  13. #if DESKTOP || XBOX
  14. private const float NumEntries = 15;
  15. #elif WINDOWS_PHONE
  16. private const float NumEntries = 9;
  17. #endif
  18. private List<MenuEntry> _menuEntries = new List<MenuEntry>();
  19. private string _menuTitle;
  20. private Vector2 _titlePosition;
  21. private Vector2 _titleOrigin;
  22. private int _selectedEntry;
  23. private float _menuBorderTop;
  24. private float _menuBorderBottom;
  25. private float _menuBorderMargin;
  26. private float _menuOffset;
  27. private float _maxOffset;
  28. private Texture2D _texScrollButton;
  29. private Texture2D _texSlider;
  30. private MenuButton _scrollUp;
  31. private MenuButton _scrollDown;
  32. private MenuButton _scrollSlider;
  33. private bool _scrollLock;
  34. /// <summary>
  35. /// Constructor.
  36. /// </summary>
  37. public MenuScreen(string menuTitle)
  38. {
  39. _menuTitle = menuTitle;
  40. TransitionOnTime = TimeSpan.FromSeconds(0.7);
  41. TransitionOffTime = TimeSpan.FromSeconds(0.7);
  42. HasCursor = true;
  43. }
  44. public void AddMenuItem(string name, EntryType type, GameScreen screen)
  45. {
  46. MenuEntry entry = new MenuEntry(this, name, type, screen);
  47. _menuEntries.Add(entry);
  48. }
  49. public override void LoadContent()
  50. {
  51. base.LoadContent();
  52. Viewport viewport = ScreenManager.GraphicsDevice.Viewport;
  53. SpriteFont font = ScreenManager.Fonts.MenuSpriteFont;
  54. _texScrollButton = ScreenManager.Content.Load<Texture2D>("Common/arrow");
  55. _texSlider = ScreenManager.Content.Load<Texture2D>("Common/slider");
  56. float scrollBarPos = viewport.Width / 2f;
  57. for (int i = 0; i < _menuEntries.Count; ++i)
  58. {
  59. _menuEntries[i].Initialize();
  60. scrollBarPos = Math.Min(scrollBarPos,
  61. (viewport.Width - _menuEntries[i].GetWidth()) / 2f);
  62. }
  63. scrollBarPos -= _texScrollButton.Width + 2f;
  64. _titleOrigin = font.MeasureString(_menuTitle) / 2f;
  65. _titlePosition = new Vector2(viewport.Width / 2f, font.MeasureString("M").Y / 2f + 10f);
  66. _menuBorderMargin = font.MeasureString("M").Y * 0.8f;
  67. _menuBorderTop = (viewport.Height - _menuBorderMargin * (NumEntries - 1)) / 2f;
  68. _menuBorderBottom = (viewport.Height + _menuBorderMargin * (NumEntries - 1)) / 2f;
  69. _menuOffset = 0f;
  70. _maxOffset = Math.Max(0f, (_menuEntries.Count - NumEntries) * _menuBorderMargin);
  71. _scrollUp = new MenuButton(_texScrollButton, false,
  72. new Vector2(scrollBarPos, _menuBorderTop - _texScrollButton.Height), this);
  73. _scrollDown = new MenuButton(_texScrollButton, true,
  74. new Vector2(scrollBarPos, _menuBorderBottom + _texScrollButton.Height), this);
  75. _scrollSlider = new MenuButton(_texSlider, false, new Vector2(scrollBarPos, _menuBorderTop), this);
  76. _scrollLock = false;
  77. }
  78. /// <summary>
  79. /// Returns the index of the menu entry at the position of the given mouse state.
  80. /// </summary>
  81. /// <returns>Index of menu entry if valid, -1 otherwise</returns>
  82. private int GetMenuEntryAt(Vector2 position)
  83. {
  84. int index = 0;
  85. foreach (MenuEntry entry in _menuEntries)
  86. {
  87. float width = entry.GetWidth();
  88. float height = entry.GetHeight();
  89. Rectangle rect = new Rectangle((int)(entry.Position.X - width / 2f),
  90. (int)(entry.Position.Y - height / 2f),
  91. (int)width, (int)height);
  92. if (rect.Contains((int)position.X, (int)position.Y) && entry.Alpha > 0.1f)
  93. {
  94. return index;
  95. }
  96. ++index;
  97. }
  98. return -1;
  99. }
  100. /// <summary>
  101. /// Responds to user input, changing the selected entry and accepting
  102. /// or cancelling the menu.
  103. /// </summary>
  104. public override void HandleInput(InputHelper input, GameTime gameTime)
  105. {
  106. // Mouse or touch on a menu item
  107. int hoverIndex = GetMenuEntryAt(input.Cursor);
  108. if (hoverIndex > -1 && _menuEntries[hoverIndex].IsSelectable() && !_scrollLock)
  109. {
  110. _selectedEntry = hoverIndex;
  111. }
  112. else
  113. {
  114. _selectedEntry = -1;
  115. }
  116. _scrollSlider.Hover = false;
  117. if (input.IsCursorValid)
  118. {
  119. _scrollUp.Collide(input.Cursor);
  120. _scrollDown.Collide(input.Cursor);
  121. _scrollSlider.Collide(input.Cursor);
  122. }
  123. else
  124. {
  125. _scrollUp.Hover = false;
  126. _scrollDown.Hover = false;
  127. _scrollLock = false;
  128. }
  129. // Accept or cancel the menu?
  130. if (input.IsMenuSelect() && _selectedEntry != -1)
  131. {
  132. if (_menuEntries[_selectedEntry].IsExitItem())
  133. {
  134. ScreenManager.Game.Exit();
  135. }
  136. else if (_menuEntries[_selectedEntry].Screen != null)
  137. {
  138. ScreenManager.AddScreen(_menuEntries[_selectedEntry].Screen);
  139. if (_menuEntries[_selectedEntry].Screen is IDemoScreen)
  140. {
  141. ScreenManager.AddScreen(
  142. new MessageBoxScreen((_menuEntries[_selectedEntry].Screen as IDemoScreen).GetDetails()));
  143. }
  144. }
  145. }
  146. else if (input.IsMenuCancel())
  147. {
  148. ScreenManager.Game.Exit();
  149. }
  150. if (input.IsMenuPressed())
  151. {
  152. if (_scrollUp.Hover)
  153. {
  154. _menuOffset = Math.Max(_menuOffset - 200f * (float)gameTime.ElapsedGameTime.TotalSeconds, 0f);
  155. _scrollLock = false;
  156. }
  157. if (_scrollDown.Hover)
  158. {
  159. _menuOffset = Math.Min(_menuOffset + 200f * (float)gameTime.ElapsedGameTime.TotalSeconds, _maxOffset);
  160. _scrollLock = false;
  161. }
  162. if (_scrollSlider.Hover)
  163. {
  164. _scrollLock = true;
  165. }
  166. }
  167. if (input.IsMenuReleased())
  168. {
  169. _scrollLock = false;
  170. }
  171. if (_scrollLock)
  172. {
  173. _scrollSlider.Hover = true;
  174. _menuOffset = Math.Max(Math.Min(((input.Cursor.Y - _menuBorderTop) / (_menuBorderBottom - _menuBorderTop)) * _maxOffset, _maxOffset), 0f);
  175. }
  176. }
  177. /// <summary>
  178. /// Allows the screen the chance to position the menu entries. By default
  179. /// all menu entries are lined up in a vertical list, centered on the screen.
  180. /// </summary>
  181. protected virtual void UpdateMenuEntryLocations()
  182. {
  183. // Make the menu slide into place during transitions, using a
  184. // power curve to make things look more interesting (this makes
  185. // the movement slow down as it nears the end).
  186. float transitionOffset = (float)Math.Pow(TransitionPosition, 2);
  187. Vector2 position = Vector2.Zero;
  188. position.Y = _menuBorderTop - _menuOffset;
  189. // update each menu entry's location in turn
  190. for (int i = 0; i < _menuEntries.Count; ++i)
  191. {
  192. position.X = ScreenManager.GraphicsDevice.Viewport.Width / 2f;
  193. if (ScreenState == ScreenState.TransitionOn)
  194. {
  195. position.X -= transitionOffset * 256;
  196. }
  197. else
  198. {
  199. position.X += transitionOffset * 256;
  200. }
  201. // set the entry's position
  202. _menuEntries[i].Position = position;
  203. if (position.Y < _menuBorderTop)
  204. {
  205. _menuEntries[i].Alpha = 1f -
  206. Math.Min(_menuBorderTop - position.Y, _menuBorderMargin) / _menuBorderMargin;
  207. }
  208. else if (position.Y > _menuBorderBottom)
  209. {
  210. _menuEntries[i].Alpha = 1f -
  211. Math.Min(position.Y - _menuBorderBottom, _menuBorderMargin) /
  212. _menuBorderMargin;
  213. }
  214. else
  215. {
  216. _menuEntries[i].Alpha = 1f;
  217. }
  218. // move down for the next entry the size of this entry
  219. position.Y += _menuEntries[i].GetHeight();
  220. }
  221. Vector2 scrollPos = _scrollSlider.Position;
  222. scrollPos.Y = MathHelper.Lerp(_menuBorderTop, _menuBorderBottom, _menuOffset / _maxOffset);
  223. _scrollSlider.Position = scrollPos;
  224. }
  225. /// <summary>
  226. /// Updates the menu.
  227. /// </summary>
  228. public override void Update(GameTime gameTime, bool otherScreenHasFocus,
  229. bool coveredByOtherScreen)
  230. {
  231. base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
  232. // Update each nested MenuEntry object.
  233. for (int i = 0; i < _menuEntries.Count; ++i)
  234. {
  235. bool isSelected = IsActive && (i == _selectedEntry);
  236. _menuEntries[i].Update(isSelected, gameTime);
  237. }
  238. _scrollUp.Update(gameTime);
  239. _scrollDown.Update(gameTime);
  240. _scrollSlider.Update(gameTime);
  241. }
  242. /// <summary>
  243. /// Draws the menu.
  244. /// </summary>
  245. public override void Draw(GameTime gameTime)
  246. {
  247. // make sure our entries are in the right place before we draw them
  248. UpdateMenuEntryLocations();
  249. SpriteBatch spriteBatch = ScreenManager.SpriteBatch;
  250. SpriteFont font = ScreenManager.Fonts.MenuSpriteFont;
  251. spriteBatch.Begin();
  252. // Draw each menu entry in turn.
  253. for (int i = 0; i < _menuEntries.Count; ++i)
  254. {
  255. bool isSelected = IsActive && (i == _selectedEntry);
  256. _menuEntries[i].Draw();
  257. }
  258. // Make the menu slide into place during transitions, using a
  259. // power curve to make things look more interesting (this makes
  260. // the movement slow down as it nears the end).
  261. Vector2 transitionOffset = new Vector2(0f, (float)Math.Pow(TransitionPosition, 2) * 100f);
  262. spriteBatch.DrawString(font, _menuTitle, _titlePosition - transitionOffset + Vector2.One * 2f, Color.Black, 0,
  263. _titleOrigin, 1f, SpriteEffects.None, 0);
  264. spriteBatch.DrawString(font, _menuTitle, _titlePosition - transitionOffset, new Color(255, 210, 0), 0,
  265. _titleOrigin, 1f, SpriteEffects.None, 0);
  266. _scrollUp.Draw();
  267. _scrollSlider.Draw();
  268. _scrollDown.Draw();
  269. spriteBatch.End();
  270. }
  271. }
  272. }