Button.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. //-----------------------------------------------------------------------------
  2. // Button.cs
  3. //
  4. // Microsoft XNA Community Game Platform
  5. // Copyright (C) Microsoft Corporation. All rights reserved.
  6. //-----------------------------------------------------------------------------
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Text;
  10. using Microsoft.Xna.Framework;
  11. using Microsoft.Xna.Framework.Graphics;
  12. using Microsoft.Xna.Framework.Input;
  13. using GameStateManagement;
  14. using CardsFramework;
  15. using Microsoft.Xna.Framework.Input.Touch;
  16. using System.IO;
  17. namespace Blackjack
  18. {
  19. public class Button : AnimatedGameComponent
  20. {
  21. bool isPressed = false;
  22. SpriteBatch spriteBatch;
  23. public Texture2D RegularTexture { get; set; }
  24. public Texture2D PressedTexture { get; set; }
  25. public SpriteFont Font { get; set; }
  26. public Rectangle Bounds { get; set; }
  27. // Icon support properties
  28. public Texture2D IconTexture { get; set; }
  29. public float IconScale { get; set; } = 1.0f;
  30. public Vector2 IconOffset { get; set; } = Vector2.Zero;
  31. public float TextIconSpacing { get; set; } = 10f;
  32. public Color IconTint { get; set; } = Color.White;
  33. public Color IconPressedTint { get; set; } = new Color(200, 200, 200);
  34. public Rectangle? IconSourceRect { get; set; } = null;
  35. public Color Color { get; set; } = Color.White;
  36. string regularTexture;
  37. string pressedTexture;
  38. string iconTexture;
  39. public event EventHandler Click;
  40. InputState input;
  41. private Matrix globalTransformation;
  42. /// <summary>
  43. /// Creates a new instance of the <see cref="Button"/> class.
  44. /// </summary>
  45. /// <param name="regularTexture">The name of the button's texture.</param>
  46. /// <param name="pressedTexture">The name of the texture to display when the
  47. /// button is pressed.</param>
  48. /// <param name="input">A <see cref="GameStateManagement.InputState"/> object
  49. /// which can be used to retrieve user input.</param>
  50. /// <param name="cardGame">The associated card game.</param>
  51. /// <param name="sharedSpriteBatch">The sprite batch used for drawing.</param>
  52. /// <param name="globalTransformation">The global transformation matrix.</param>
  53. /// <remarks>Texture names are relative to the "Images" content
  54. /// folder.</remarks>
  55. public Button(string regularTexture, string pressedTexture, InputState input,
  56. CardsGame cardGame, SpriteBatch sharedSpriteBatch, Matrix globalTransformation)
  57. : this(regularTexture, pressedTexture, null, input, cardGame, sharedSpriteBatch, globalTransformation)
  58. {
  59. }
  60. /// <summary>
  61. /// Creates a new instance of the <see cref="Button"/> class with icon support.
  62. /// </summary>
  63. /// <param name="regularTexture">The name of the button's texture.</param>
  64. /// <param name="pressedTexture">The name of the texture to display when the
  65. /// button is pressed.</param>
  66. /// <param name="iconTexture">The name of the icon texture (optional).</param>
  67. /// <param name="input">A <see cref="GameStateManagement.InputState"/> object
  68. /// which can be used to retrieve user input.</param>
  69. /// <param name="cardGame">The associated card game.</param>
  70. /// <param name="sharedSpriteBatch">The sprite batch used for drawing.</param>
  71. /// <param name="globalTransformation">The global transformation matrix.</param>
  72. /// <remarks>Texture names are relative to the "Images" content
  73. /// folder.</remarks>
  74. public Button(string regularTexture, string pressedTexture, string iconTexture, InputState input,
  75. CardsGame cardGame, SpriteBatch sharedSpriteBatch, Matrix globalTransformation)
  76. : base(cardGame, null, sharedSpriteBatch, globalTransformation)
  77. {
  78. this.input = input;
  79. this.regularTexture = regularTexture;
  80. this.pressedTexture = pressedTexture;
  81. this.iconTexture = iconTexture;
  82. this.spriteBatch = sharedSpriteBatch;
  83. this.globalTransformation = globalTransformation;
  84. }
  85. /// <summary>
  86. /// Loads the content required by the button.
  87. /// </summary>
  88. protected override void LoadContent()
  89. {
  90. if (regularTexture != null)
  91. {
  92. RegularTexture = Game.Content.Load<Texture2D>(Path.Combine("Images", regularTexture));
  93. }
  94. if (pressedTexture != null)
  95. {
  96. PressedTexture = Game.Content.Load<Texture2D>(Path.Combine("Images", pressedTexture));
  97. }
  98. if (iconTexture != null)
  99. {
  100. IconTexture = Game.Content.Load<Texture2D>(Path.Combine("Images", iconTexture));
  101. }
  102. base.LoadContent();
  103. }
  104. /// <summary>
  105. /// Performs update logic for the button.
  106. /// </summary>
  107. /// <param name="gameTime">The time that has passed since the last call to
  108. /// this method.</param>
  109. public override void Update(GameTime gameTime)
  110. {
  111. if (RegularTexture != null)
  112. {
  113. HandleInput();
  114. }
  115. base.Update(gameTime);
  116. }
  117. /// <summary>
  118. /// Handle the input of adding chip on all platform
  119. /// </summary>
  120. private void HandleInput()
  121. {
  122. bool clicked = false;
  123. if (UIUtility.IsDesktop)
  124. {
  125. // For desktop, check for a click at the unified cursor position (mouse or gamepad)
  126. if (IntersectWith(input.CurrentCursorLocation))
  127. {
  128. PlayerIndex playerIndex;
  129. // A "MenuSelect" can be a gamepad A button, or keyboard Enter/Space.
  130. // We also check for a raw mouse click.
  131. if (input.IsMenuSelect(null, out playerIndex) || input.IsLeftMouseButtonClicked())
  132. {
  133. clicked = true;
  134. }
  135. }
  136. // The button's visual "pressed" state is determined by whether the mouse button
  137. // is held down, or if the gamepad 'A' button is held down over the button.
  138. isPressed = IntersectWith(input.CurrentCursorLocation) &&
  139. (input.CurrentMouseState.LeftButton == ButtonState.Pressed ||
  140. (input.CurrentGamePadStates.Length > 0 && input.CurrentGamePadStates[0].IsButtonDown(Buttons.A)));
  141. }
  142. else if (UIUtility.IsMobile)
  143. {
  144. // For mobile, we rely on the simple Tap gesture for the click event.
  145. foreach (var gesture in input.Gestures)
  146. {
  147. if (gesture.GestureType == GestureType.Tap)
  148. {
  149. // Use the gesture's position for the intersection test
  150. if (IntersectWith(input.TransformCursorLocation(gesture.Position)))
  151. {
  152. clicked = true;
  153. // One click is enough per frame.
  154. break;
  155. }
  156. }
  157. }
  158. // The visual "pressed" state is determined by whether a finger is currently
  159. // touching the button.
  160. isPressed = false;
  161. foreach (var touch in input.CurrentTouchState)
  162. {
  163. if ((touch.State == TouchLocationState.Pressed || touch.State == TouchLocationState.Moved) &&
  164. IntersectWith(input.TransformCursorLocation(touch.Position)))
  165. {
  166. isPressed = true;
  167. break;
  168. }
  169. }
  170. }
  171. if (clicked)
  172. {
  173. FireClick();
  174. }
  175. }
  176. /// <summary>
  177. /// Checks if the button intersects with a specified position
  178. /// </summary>
  179. /// <param name="position">The position to check intersection against.</param>
  180. /// <returns>True if the position intersects with the button,
  181. /// false otherwise.</returns>
  182. private bool IntersectWith(Vector2 position)
  183. {
  184. Rectangle touchTap = new Rectangle((int)position.X - 1, (int)position.Y - 1, 2, 2);
  185. return Bounds.Intersects(touchTap);
  186. }
  187. /// <summary>
  188. /// Fires the button's click event.
  189. /// </summary>
  190. public void FireClick()
  191. {
  192. if (Click != null)
  193. {
  194. Click(this, EventArgs.Empty);
  195. }
  196. }
  197. /// <summary>
  198. /// Draws the button.
  199. /// </summary>
  200. /// <param name="gameTime">The time that has passed since the last call to
  201. /// this method.</param>
  202. public override void Draw(GameTime gameTime)
  203. {
  204. spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, null, null, globalTransformation);
  205. // Draw button background
  206. spriteBatch.Draw(isPressed ? PressedTexture : RegularTexture, Bounds, this.Color);
  207. // Calculate if we have icon and/or text
  208. bool hasIcon = IconTexture != null;
  209. bool hasText = Font != null && !string.IsNullOrEmpty(Text);
  210. if (hasIcon || hasText)
  211. {
  212. // Measure content sizes
  213. Vector2 iconSize = Vector2.Zero;
  214. Vector2 textSize = Vector2.Zero;
  215. if (hasIcon)
  216. {
  217. Rectangle sourceRect = IconSourceRect ?? new Rectangle(0, 0, IconTexture.Width, IconTexture.Height);
  218. iconSize = new Vector2(sourceRect.Width * IconScale, sourceRect.Height * IconScale);
  219. }
  220. if (hasText)
  221. {
  222. textSize = Font.MeasureString(Text);
  223. }
  224. // Calculate total content height and starting Y position
  225. float totalContentHeight = iconSize.Y + (hasIcon && hasText ? TextIconSpacing : 0) + textSize.Y;
  226. float startY = Bounds.Y + (Bounds.Height - totalContentHeight) / 2;
  227. // Apply pressed offset for tactile feedback
  228. Vector2 pressedOffset = isPressed ? new Vector2(0, 2) : Vector2.Zero;
  229. // Draw icon if present
  230. if (hasIcon)
  231. {
  232. Rectangle sourceRect = IconSourceRect ?? new Rectangle(0, 0, IconTexture.Width, IconTexture.Height);
  233. Vector2 iconPosition = new Vector2(
  234. Bounds.X + (Bounds.Width - iconSize.X) / 2,
  235. startY
  236. );
  237. iconPosition += IconOffset + pressedOffset;
  238. Color iconColor = isPressed ? IconPressedTint : IconTint;
  239. spriteBatch.Draw(
  240. IconTexture,
  241. iconPosition,
  242. sourceRect,
  243. iconColor,
  244. 0f,
  245. Vector2.Zero,
  246. IconScale,
  247. SpriteEffects.None,
  248. 0f
  249. );
  250. startY += iconSize.Y + TextIconSpacing;
  251. }
  252. // Draw text if present
  253. if (hasText)
  254. {
  255. Vector2 textPosition = new Vector2(
  256. Bounds.X + (Bounds.Width - textSize.X) / 2,
  257. startY
  258. );
  259. textPosition += pressedOffset;
  260. spriteBatch.DrawString(Font, Text, textPosition, Color.White);
  261. }
  262. }
  263. spriteBatch.End();
  264. base.Draw(gameTime);
  265. }
  266. protected override void Dispose(bool disposing)
  267. {
  268. Click = null;
  269. base.Dispose(disposing);
  270. }
  271. }
  272. }