SettingsScreen.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778
  1. //-----------------------------------------------------------------------------
  2. // SettingsScreen.cs
  3. //
  4. // Settings screen with custom layout for adjusting game options
  5. //-----------------------------------------------------------------------------
  6. using System;
  7. using System.Collections.Generic;
  8. using GameStateManagement;
  9. using Microsoft.Xna.Framework;
  10. using Microsoft.Xna.Framework.Graphics;
  11. using Microsoft.Xna.Framework.Input;
  12. using Microsoft.Xna.Framework.Input.Touch;
  13. using Microsoft.Xna.Framework.Content;
  14. using System.IO;
  15. using CardsFramework;
  16. namespace Blackjack
  17. {
  18. class SettingsScreen : GameScreen
  19. {
  20. private Texture2D background;
  21. private Texture2D cardBackTexture;
  22. private Texture2D buttonRegularTexture;
  23. private Texture2D buttonPressedTexture;
  24. // Fonts are accessed directly from ScreenManager to ensure we always use current font
  25. private GameSettings settings;
  26. private Rectangle safeArea;
  27. // Card preview
  28. private Vector2 cardPreviewPosition;
  29. private Vector2 cardSize;
  30. // Setting item areas
  31. private List<SettingItem> settingItems = new List<SettingItem>();
  32. private int selectedIndex = -1;
  33. private int hoveredIndex = -1;
  34. private int pressedButtonIndex = -1; // Which button is being pressed (-1 for none, 0 for left, 1 for right)
  35. // Pagination
  36. private int currentPage = 0;
  37. private const int TotalPages = 2;
  38. private Rectangle nextButtonBounds;
  39. private Rectangle prevButtonBounds;
  40. private bool isNextButtonPressed = false;
  41. private bool isPrevButtonPressed = false;
  42. private Rectangle backButtonBounds;
  43. private bool isBackButtonPressed = false;
  44. private bool isMouseDown = false;
  45. private float itemSpacing;
  46. private float groupSpacing;
  47. private float leftMargin;
  48. private float buttonSize;
  49. private float rightButtonMargin;
  50. private float buttonSpacing;
  51. public SettingsScreen()
  52. {
  53. // Settings screen needs Tap gestures for mobile
  54. EnabledGestures = GestureType.Tap;
  55. TransitionOnTime = TimeSpan.FromSeconds(0.5);
  56. TransitionOffTime = TimeSpan.FromSeconds(0.5);
  57. settings = GameSettings.Instance;
  58. }
  59. public override void LoadContent()
  60. {
  61. ContentManager content = ScreenManager.Game.Content;
  62. background = content.Load<Texture2D>("Images/UI/table");
  63. // Fonts are accessed directly from ScreenManager properties (no caching)
  64. buttonRegularTexture = content.Load<Texture2D>("Images/ButtonRegular");
  65. buttonPressedTexture = content.Load<Texture2D>("Images/ButtonPressed");
  66. // Load current theme card back using invariant theme value
  67. string themeCardBack = $"CardBack_{settings.Theme}";
  68. cardBackTexture = content.Load<Texture2D>($"Images/Cards/{themeCardBack}");
  69. safeArea = ScreenManager.SafeArea;
  70. // Calculate proportional spacing and sizes based on screen height
  71. float heightScale = safeArea.Height / 720f; // Scale based on 720p baseline
  72. itemSpacing = 45f * heightScale;
  73. groupSpacing = 60f * heightScale;
  74. leftMargin = 80f * heightScale;
  75. buttonSize = 32f * heightScale;
  76. rightButtonMargin = 10f * heightScale;
  77. buttonSpacing = 168f * heightScale; // Space between left and right buttons
  78. // Calculate card preview position and size (proportional to screen)
  79. cardSize = new Vector2(80 * heightScale, 120 * heightScale);
  80. cardPreviewPosition = new Vector2(
  81. safeArea.Center.X - cardSize.X / 2,
  82. safeArea.Top + 50 * heightScale
  83. );
  84. BuildSettingItems();
  85. base.LoadContent();
  86. }
  87. /// <summary>
  88. /// Reloads fonts when language changes between CJK and non-CJK languages.
  89. /// </summary>
  90. public void ReloadFonts(bool useCJKFont)
  91. {
  92. // No need to cache fonts - they're accessed directly from ScreenManager
  93. // Don't rebuild items here - will be done after language is set to avoid
  94. // measuring text in new language with old fonts
  95. }
  96. private void BuildSettingItems()
  97. {
  98. settingItems.Clear();
  99. float yPos = cardPreviewPosition.Y + cardSize.Y + 20; // Start below card preview
  100. if (currentPage == 0)
  101. {
  102. // Page 1: DISPLAY and AUDIO
  103. AddHeader(Resources.SettingsDisplay, ref yPos);
  104. AddCycleSetting(Resources.SettingsLanguage, GetLanguageDisplay,
  105. CycleToPreviousLanguage,
  106. CycleToNextLanguage,
  107. null, ref yPos);
  108. AddCycleSetting(Resources.SettingsCardBackTheme, GetThemeDisplay,
  109. () => settings.Theme = settings.Theme == "Red" ? "Blue" : "Red",
  110. () => settings.Theme = settings.Theme == "Red" ? "Blue" : "Red",
  111. () =>
  112. {
  113. // Reload card back texture using invariant theme value
  114. string themeCardBack = $"CardBack_{settings.Theme}";
  115. cardBackTexture = ScreenManager.Game.Content.Load<Texture2D>($"Images/Cards/{themeCardBack}");
  116. MainMenuScreen.Theme = settings.Theme;
  117. },
  118. ref yPos);
  119. AddCycleSetting(Resources.SettingsCurrency, () => settings.Currency,
  120. CycleToPreviousCurrency,
  121. CycleToNextCurrency,
  122. null, ref yPos);
  123. yPos += groupSpacing - itemSpacing; // Extra space before next group
  124. // AUDIO section
  125. AddHeader(Resources.SettingsAudio, ref yPos);
  126. AddSliderSetting(Resources.SettingsSoundVolume, () => settings.SoundVolume,
  127. (v) => settings.SoundVolume = v, ref yPos);
  128. AddSliderSetting(Resources.SettingsMusicVolume, () => settings.MusicVolume,
  129. (v) => settings.MusicVolume = v, ref yPos);
  130. }
  131. else if (currentPage == 1)
  132. {
  133. // Page 2: GAMEPLAY and NPC Players
  134. AddHeader(Resources.SettingsGameplay, ref yPos);
  135. AddCycleSetting(Resources.SettingsAnimationSpeed, () => settings.AnimationSpeed.ToString(),
  136. () =>
  137. {
  138. settings.AnimationSpeed = settings.AnimationSpeed switch
  139. {
  140. AnimationSpeed.Fast => AnimationSpeed.Normal,
  141. AnimationSpeed.Normal => AnimationSpeed.Slow,
  142. _ => AnimationSpeed.Fast
  143. };
  144. },
  145. () =>
  146. {
  147. settings.AnimationSpeed = settings.AnimationSpeed switch
  148. {
  149. AnimationSpeed.Slow => AnimationSpeed.Normal,
  150. AnimationSpeed.Normal => AnimationSpeed.Fast,
  151. _ => AnimationSpeed.Slow
  152. };
  153. },
  154. null, ref yPos);
  155. AddCheckboxSetting(Resources.SettingsAutoStandOn21, () => settings.AutoStandOn21,
  156. (v) => settings.AutoStandOn21 = v, ref yPos);
  157. AddCheckboxSetting(Resources.SettingsShowCardCount, () => settings.ShowCardCount,
  158. (v) => settings.ShowCardCount = v, ref yPos);
  159. AddCheckboxSetting(Resources.SettingsPersistWinnings, () => settings.PersistWinnings,
  160. (v) => settings.PersistWinnings = v, ref yPos);
  161. yPos += groupSpacing - itemSpacing; // Extra space before next group
  162. // NPC Players section
  163. AddHeader(Resources.SettingsNPCPlayers, ref yPos);
  164. AddCounterSetting(Resources.SettingsMaxNPCPlayers, () => settings.MaxNPCPlayers, 0, GameSettings.GetPlatformMaxNPCPlayers(),
  165. (v) => settings.MaxNPCPlayers = (byte)v, ref yPos);
  166. AddCheckboxSetting(Resources.SettingsFillEmptySlots, () => settings.FillEmptySlotsWithNPC,
  167. (v) => settings.FillEmptySlotsWithNPC = v, ref yPos);
  168. }
  169. // Calculate navigation button bounds at bottom (proportional to screen)
  170. float heightScale = safeArea.Height / 720f;
  171. int navButtonY = safeArea.Bottom - (int)(60 * heightScale);
  172. int navButtonHeight = (int)(40 * heightScale);
  173. // Calculate button widths based on text size with padding
  174. Vector2 prevTextSize = ScreenManager.RegularFont.MeasureString(Resources.Previous);
  175. Vector2 nextTextSize = ScreenManager.RegularFont.MeasureString(Resources.Next);
  176. int prevButtonWidth = (int)(prevTextSize.X + 40 * heightScale);
  177. int nextButtonWidth = (int)(nextTextSize.X + 40 * heightScale);
  178. prevButtonBounds = new Rectangle(safeArea.Left + (int)(100 * heightScale), navButtonY, prevButtonWidth, navButtonHeight);
  179. nextButtonBounds = new Rectangle(safeArea.Right - nextButtonWidth - (int)(100 * heightScale), navButtonY, nextButtonWidth, navButtonHeight);
  180. // Calculate back button bounds in top-left corner
  181. int backButtonSize = (int)(50 * heightScale);
  182. int backButtonPadding = (int)(20 * heightScale);
  183. backButtonBounds = new Rectangle(
  184. safeArea.Left + backButtonPadding,
  185. safeArea.Top + backButtonPadding,
  186. backButtonSize,
  187. backButtonSize);
  188. }
  189. private string GetLanguageDisplay()
  190. {
  191. // Language names are already stored in native format
  192. return settings.Language;
  193. }
  194. private string GetThemeDisplay()
  195. {
  196. // Return the localized theme name based on current language
  197. // Theme is stored as invariant "Red" or "Blue" but displayed in user's language
  198. return settings.Theme == "Red" ? Resources.CardBackColorRed : Resources.CardBackColorBlue;
  199. }
  200. private void CycleToNextLanguage()
  201. {
  202. // Determine next language
  203. string nextLanguage = settings.Language switch
  204. {
  205. "English" => "Français",
  206. "Français" => "Español",
  207. "Español" => "Italiano",
  208. "Italiano" => "Русский",
  209. "Русский" => "日本語",
  210. "日本語" => "中文",
  211. "中文" => "English",
  212. _ => "English"
  213. };
  214. // Phase 1: Reload fonts BEFORE changing language
  215. ScreenManager.ReloadFontForLanguage(nextLanguage);
  216. // Phase 2: Set the language (this will change CurrentUICulture)
  217. settings.Language = nextLanguage;
  218. // Phase 3: Refresh all screens now that language and fonts are both updated
  219. BuildSettingItems();
  220. ScreenManager.RefreshScreensAfterLanguageChange();
  221. }
  222. private void CycleToPreviousLanguage()
  223. {
  224. // Determine previous language
  225. string previousLanguage = settings.Language switch
  226. {
  227. "English" => "中文",
  228. "中文" => "日本語",
  229. "日本語" => "Русский",
  230. "Русский" => "Italiano",
  231. "Italiano" => "Español",
  232. "Español" => "Français",
  233. "Français" => "English",
  234. _ => "中文" // Default to last active language
  235. };
  236. // Phase 1: Reload fonts BEFORE changing language
  237. ScreenManager.ReloadFontForLanguage(previousLanguage);
  238. // Phase 2: Set the language (this will change CurrentUICulture)
  239. settings.Language = previousLanguage;
  240. // Phase 3: Refresh all screens now that language and fonts are both updated
  241. BuildSettingItems();
  242. ScreenManager.RefreshScreensAfterLanguageChange();
  243. }
  244. private void CycleToNextCurrency()
  245. {
  246. settings.Currency = settings.Currency switch
  247. {
  248. "$" => "€",
  249. "€" => "£",
  250. "£" => "¥",
  251. "¥" => "R",
  252. "R" => "$",
  253. _ => "$"
  254. };
  255. GameSettings.Save();
  256. }
  257. private void CycleToPreviousCurrency()
  258. {
  259. settings.Currency = settings.Currency switch
  260. {
  261. "$" => "R",
  262. "R" => "¥",
  263. "¥" => "£",
  264. "£" => "€",
  265. "€" => "$",
  266. _ => "$"
  267. };
  268. GameSettings.Save();
  269. }
  270. private void AddHeader(string text, ref float yPos)
  271. {
  272. settingItems.Add(new SettingItem
  273. {
  274. Type = SettingType.Header,
  275. Label = text,
  276. Bounds = new Rectangle((int)leftMargin, (int)yPos, safeArea.Width - (int)leftMargin * 2, (int)(40 * (safeArea.Height / 720f)))
  277. });
  278. yPos += itemSpacing;
  279. }
  280. private void AddCounterSetting(string label, Func<int> getValue, int min, int max,
  281. Action<int> setValue, ref float yPos)
  282. {
  283. settingItems.Add(new SettingItem
  284. {
  285. Type = SettingType.Counter,
  286. Label = label,
  287. GetValue = () => getValue().ToString(),
  288. OnDecrease = () =>
  289. {
  290. int val = getValue() - 1;
  291. if (val < min) val = max;
  292. setValue(val);
  293. GameSettings.Save();
  294. },
  295. OnIncrease = () =>
  296. {
  297. int val = getValue() + 1;
  298. if (val > max) val = min;
  299. setValue(val);
  300. GameSettings.Save();
  301. },
  302. Bounds = new Rectangle((int)leftMargin, (int)yPos, safeArea.Width - (int)leftMargin * 2, (int)(40 * (safeArea.Height / 720f)))
  303. });
  304. yPos += itemSpacing;
  305. }
  306. private void AddSliderSetting(string label, Func<float> getValue, Action<float> setValue, ref float yPos)
  307. {
  308. settingItems.Add(new SettingItem
  309. {
  310. Type = SettingType.Slider,
  311. Label = label,
  312. GetValue = () => $"{(int)(getValue() * 100)}%",
  313. OnDecrease = () =>
  314. {
  315. float val = getValue() - 0.1f;
  316. if (val < 0) val = 1.0f;
  317. setValue(val);
  318. GameSettings.Save();
  319. },
  320. OnIncrease = () =>
  321. {
  322. float val = getValue() + 0.1f;
  323. if (val > 1.0f) val = 0f;
  324. setValue(val);
  325. GameSettings.Save();
  326. },
  327. Bounds = new Rectangle((int)leftMargin, (int)yPos, safeArea.Width - (int)leftMargin * 2, (int)(40 * (safeArea.Height / 720f)))
  328. });
  329. yPos += itemSpacing;
  330. }
  331. private void AddCheckboxSetting(string label, Func<bool> getValue, Action<bool> setValue, ref float yPos)
  332. {
  333. settingItems.Add(new SettingItem
  334. {
  335. Type = SettingType.Checkbox,
  336. Label = label,
  337. GetValue = () => getValue() ? "[X]" : "[ ]",
  338. OnClick = () =>
  339. {
  340. setValue(!getValue());
  341. GameSettings.Save();
  342. },
  343. Bounds = new Rectangle((int)leftMargin, (int)yPos, safeArea.Width - (int)leftMargin * 2, (int)(40 * (safeArea.Height / 720f)))
  344. });
  345. yPos += itemSpacing;
  346. }
  347. private void AddCycleSetting(string label, Func<string> getValue, Action onPrevious, Action onNext,
  348. Action onChange, ref float yPos)
  349. {
  350. settingItems.Add(new SettingItem
  351. {
  352. Type = SettingType.Cycle,
  353. Label = label,
  354. GetValue = getValue,
  355. OnDecrease = () =>
  356. {
  357. onPrevious();
  358. onChange?.Invoke();
  359. GameSettings.Save();
  360. },
  361. OnIncrease = () =>
  362. {
  363. onNext();
  364. onChange?.Invoke();
  365. GameSettings.Save();
  366. },
  367. Bounds = new Rectangle((int)leftMargin, (int)yPos, safeArea.Width - (int)leftMargin * 2, (int)(40 * (safeArea.Height / 720f)))
  368. });
  369. yPos += itemSpacing;
  370. }
  371. public override void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen)
  372. {
  373. base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
  374. }
  375. /// <summary>
  376. /// Responds to user input.
  377. /// </summary>
  378. public override void HandleInput(InputState inputState)
  379. {
  380. // Cancel the settings screen if the user presses the back button
  381. PlayerIndex player;
  382. if (inputState.IsNewButtonPress(Buttons.Back, ControllingPlayer, out player))
  383. {
  384. ExitScreen();
  385. return;
  386. }
  387. // Check for escape key
  388. if (inputState.IsNewKeyPress(Keys.Escape, ControllingPlayer, out player))
  389. {
  390. ExitScreen();
  391. return;
  392. }
  393. // Get transformed cursor position (handles scaling/letterboxing on mobile)
  394. Vector2 cursorPos = inputState.CurrentCursorLocation;
  395. Point mousePos = new Point((int)cursorPos.X, (int)cursorPos.Y);
  396. // Track button press state
  397. pressedButtonIndex = -1;
  398. isNextButtonPressed = false;
  399. isPrevButtonPressed = false;
  400. isBackButtonPressed = false;
  401. if (UIUtility.IsDesktop)
  402. {
  403. // Handle mouse input - detect click (press + release)
  404. if (inputState.CurrentMouseState.LeftButton == ButtonState.Released)
  405. {
  406. if (isMouseDown)
  407. {
  408. isMouseDown = false;
  409. HandleSettingItemClick(mousePos);
  410. }
  411. }
  412. else if (inputState.CurrentMouseState.LeftButton == ButtonState.Pressed)
  413. {
  414. isMouseDown = true;
  415. // Track hover on press
  416. TrackHoverState(mousePos);
  417. }
  418. }
  419. else if (UIUtility.IsMobile)
  420. {
  421. // Handle touch input with gestures (like MenuScreen does)
  422. foreach (GestureSample gesture in inputState.Gestures)
  423. {
  424. if (gesture.GestureType == GestureType.Tap)
  425. {
  426. HandleSettingItemClick(mousePos);
  427. }
  428. }
  429. }
  430. // Track hover state for visual feedback on desktop
  431. if (UIUtility.IsDesktop && inputState.CurrentMouseState.LeftButton == ButtonState.Released)
  432. {
  433. TrackHoverState(mousePos);
  434. }
  435. }
  436. private void HandleSettingItemClick(Point clickLocation)
  437. {
  438. // Check back button
  439. if (backButtonBounds.Contains(clickLocation))
  440. {
  441. isBackButtonPressed = true;
  442. AudioManager.PlaySound("menu_select");
  443. ExitScreen();
  444. return;
  445. }
  446. // Check navigation buttons
  447. if (currentPage < TotalPages - 1 && nextButtonBounds.Contains(clickLocation))
  448. {
  449. isNextButtonPressed = true;
  450. currentPage++;
  451. BuildSettingItems();
  452. AudioManager.PlaySound("menu_select");
  453. return;
  454. }
  455. else if (currentPage > 0 && prevButtonBounds.Contains(clickLocation))
  456. {
  457. isPrevButtonPressed = true;
  458. currentPage--;
  459. BuildSettingItems();
  460. AudioManager.PlaySound("menu_select");
  461. return;
  462. }
  463. // Check setting items
  464. for (int i = 0; i < settingItems.Count; i++)
  465. {
  466. SettingItem item = settingItems[i];
  467. if (item.Type == SettingType.Header) continue;
  468. if (item.Bounds.Contains(clickLocation))
  469. {
  470. // Button bounds
  471. Rectangle leftButton = GetLeftButtonBounds(item.Bounds);
  472. Rectangle rightButton = GetRightButtonBounds(item.Bounds);
  473. if (item.Type == SettingType.Checkbox)
  474. {
  475. // Get checkbox bounds (centered in value column)
  476. Rectangle checkboxBounds = GetCheckboxButtonBounds(item.Bounds);
  477. if (checkboxBounds.Contains(clickLocation))
  478. {
  479. item.OnClick?.Invoke();
  480. AudioManager.PlaySound("menu_select");
  481. }
  482. }
  483. else if (leftButton.Contains(clickLocation))
  484. {
  485. item.OnDecrease?.Invoke();
  486. AudioManager.PlaySound("menu_select");
  487. pressedButtonIndex = i * 2; // Even indices for left buttons
  488. }
  489. else if (rightButton.Contains(clickLocation))
  490. {
  491. item.OnIncrease?.Invoke();
  492. AudioManager.PlaySound("menu_select");
  493. pressedButtonIndex = i * 2 + 1; // Odd indices for right buttons
  494. }
  495. break;
  496. }
  497. }
  498. }
  499. private void TrackHoverState(Point mousePos)
  500. {
  501. // Track hover state for visual feedback
  502. hoveredIndex = -1;
  503. for (int i = 0; i < settingItems.Count; i++)
  504. {
  505. if (settingItems[i].Type != SettingType.Header &&
  506. settingItems[i].Bounds.Contains(mousePos))
  507. {
  508. hoveredIndex = i;
  509. break;
  510. }
  511. }
  512. }
  513. private Rectangle GetLeftButtonBounds(Rectangle itemBounds)
  514. {
  515. return new Rectangle(
  516. itemBounds.Right - (int)(buttonSpacing + buttonSize + rightButtonMargin),
  517. itemBounds.Y + (itemBounds.Height - (int)buttonSize) / 2,
  518. (int)buttonSize, (int)buttonSize);
  519. }
  520. private Rectangle GetRightButtonBounds(Rectangle itemBounds)
  521. {
  522. return new Rectangle(
  523. itemBounds.Right - (int)buttonSize - (int)rightButtonMargin,
  524. itemBounds.Y + (itemBounds.Height - (int)buttonSize) / 2,
  525. (int)buttonSize, (int)buttonSize);
  526. }
  527. private Rectangle GetCheckboxButtonBounds(Rectangle itemBounds)
  528. {
  529. // Center in the value column, matching the position where counter values appear
  530. int leftButtonRight = itemBounds.Right - (int)(buttonSpacing + buttonSize + rightButtonMargin) + (int)buttonSize;
  531. int rightButtonLeft = itemBounds.Right - (int)buttonSize - (int)rightButtonMargin;
  532. int centerX = (leftButtonRight + rightButtonLeft) / 2 - (int)buttonSize / 2;
  533. return new Rectangle(
  534. centerX,
  535. itemBounds.Y + (itemBounds.Height - (int)buttonSize) / 2,
  536. (int)buttonSize, (int)buttonSize);
  537. }
  538. public override void Draw(GameTime gameTime)
  539. {
  540. SpriteBatch spriteBatch = ScreenManager.SpriteBatch;
  541. spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, null, null, ScreenManager.GlobalTransformation);
  542. // Draw background
  543. spriteBatch.Draw(background, safeArea, Color.White * TransitionAlpha);
  544. // Draw title
  545. string title = Resources.SettingsTitle;
  546. Vector2 titleSize = ScreenManager.Font.MeasureString(title);
  547. Vector2 titlePos = new Vector2(safeArea.Center.X - titleSize.X / 2, safeArea.Top + 10);
  548. spriteBatch.DrawString(ScreenManager.Font, title, titlePos, Color.White * TransitionAlpha);
  549. // Draw card preview
  550. Rectangle cardRect = new Rectangle((int)cardPreviewPosition.X, (int)cardPreviewPosition.Y,
  551. (int)cardSize.X, (int)cardSize.Y);
  552. spriteBatch.Draw(cardBackTexture, cardRect, Color.White * TransitionAlpha);
  553. // Draw all setting items
  554. for (int i = 0; i < settingItems.Count; i++)
  555. {
  556. SettingItem item = settingItems[i];
  557. Color color = Color.White * TransitionAlpha;
  558. if (item.Type == SettingType.Header)
  559. {
  560. // Draw header with larger font
  561. Vector2 headerPos = new Vector2(item.Bounds.X, item.Bounds.Y);
  562. spriteBatch.DrawString(ScreenManager.Font, item.Label, headerPos,
  563. Color.Gold * TransitionAlpha);
  564. }
  565. else
  566. {
  567. // Highlight if hovered
  568. if (i == hoveredIndex)
  569. {
  570. spriteBatch.Draw(background, item.Bounds, Color.White * 0.2f * TransitionAlpha);
  571. }
  572. // Draw label
  573. Vector2 labelPos = new Vector2(item.Bounds.X + 10,
  574. item.Bounds.Y + (item.Bounds.Height - ScreenManager.RegularFont.LineSpacing) / 2);
  575. spriteBatch.DrawString(ScreenManager.RegularFont, item.Label, labelPos, color);
  576. // Draw value and controls
  577. string valueText = item.GetValue?.Invoke() ?? "";
  578. Vector2 valueSize = ScreenManager.RegularFont.MeasureString(valueText);
  579. if (item.Type == SettingType.Checkbox)
  580. {
  581. // Draw checkbox as a button in the center of the value column
  582. bool isChecked = item.GetValue?.Invoke() == "[X]";
  583. Rectangle checkboxBounds = GetCheckboxButtonBounds(item.Bounds);
  584. Texture2D checkboxTexture = isChecked ? buttonPressedTexture : buttonRegularTexture;
  585. spriteBatch.Draw(checkboxTexture, checkboxBounds, Color.White * TransitionAlpha);
  586. // Draw checkmark if checked
  587. if (isChecked)
  588. {
  589. Vector2 checkSize = ScreenManager.RegularFont.MeasureString("X");
  590. Vector2 checkPos = new Vector2(
  591. checkboxBounds.X + (checkboxBounds.Width - checkSize.X) / 2,
  592. checkboxBounds.Y + (checkboxBounds.Height - checkSize.Y) / 2);
  593. spriteBatch.DrawString(ScreenManager.RegularFont, "X", checkPos, Color.Black * TransitionAlpha);
  594. }
  595. }
  596. else
  597. {
  598. // Get button bounds
  599. Rectangle leftButton = GetLeftButtonBounds(item.Bounds);
  600. Rectangle rightButton = GetRightButtonBounds(item.Bounds);
  601. // Draw left button [-]
  602. bool leftPressed = (pressedButtonIndex == i * 2);
  603. Texture2D leftTexture = leftPressed ? buttonPressedTexture : buttonRegularTexture;
  604. spriteBatch.Draw(leftTexture, leftButton, Color.White * TransitionAlpha);
  605. Vector2 minusSize = ScreenManager.RegularFont.MeasureString("-");
  606. Vector2 minusPos = new Vector2(
  607. leftButton.X + (leftButton.Width - minusSize.X) / 2,
  608. leftButton.Y + (leftButton.Height - minusSize.Y) / 2);
  609. spriteBatch.DrawString(ScreenManager.RegularFont, "-", minusPos, Color.Black * TransitionAlpha);
  610. // Draw value in center
  611. Vector2 valuePos = new Vector2(
  612. leftButton.Right + (rightButton.Left - leftButton.Right - valueSize.X) / 2,
  613. item.Bounds.Y + (item.Bounds.Height - ScreenManager.RegularFont.LineSpacing) / 2);
  614. spriteBatch.DrawString(ScreenManager.RegularFont, valueText, valuePos, color);
  615. // Draw right button [+]
  616. bool rightPressed = (pressedButtonIndex == i * 2 + 1);
  617. Texture2D rightTexture = rightPressed ? buttonPressedTexture : buttonRegularTexture;
  618. spriteBatch.Draw(rightTexture, rightButton, Color.White * TransitionAlpha);
  619. Vector2 plusSize = ScreenManager.RegularFont.MeasureString("+");
  620. Vector2 plusPos = new Vector2(
  621. rightButton.X + (rightButton.Width - plusSize.X) / 2,
  622. rightButton.Y + (rightButton.Height - plusSize.Y) / 2);
  623. spriteBatch.DrawString(ScreenManager.RegularFont, "+", plusPos, Color.Black * TransitionAlpha);
  624. }
  625. }
  626. }
  627. // Draw back button
  628. Texture2D backTexture = isBackButtonPressed ? buttonPressedTexture : buttonRegularTexture;
  629. spriteBatch.Draw(backTexture, backButtonBounds, Color.Red * TransitionAlpha);
  630. string backText = "X";
  631. Vector2 backTextSize = ScreenManager.Font.MeasureString(backText);
  632. Vector2 backTextPos = new Vector2(
  633. backButtonBounds.X + (backButtonBounds.Width - backTextSize.X) / 2,
  634. backButtonBounds.Y + (backButtonBounds.Height - backTextSize.Y) / 2);
  635. spriteBatch.DrawString(ScreenManager.Font, backText, backTextPos, Color.White * TransitionAlpha);
  636. // Draw page navigation buttons
  637. if (currentPage > 0)
  638. {
  639. Texture2D prevTexture = isPrevButtonPressed ? buttonPressedTexture : buttonRegularTexture;
  640. spriteBatch.Draw(prevTexture, prevButtonBounds, Color.White * TransitionAlpha);
  641. string prevText = Resources.Previous;
  642. Vector2 prevTextSize = ScreenManager.RegularFont.MeasureString(prevText);
  643. Vector2 prevTextPos = new Vector2(
  644. prevButtonBounds.X + (prevButtonBounds.Width - prevTextSize.X) / 2,
  645. prevButtonBounds.Y + (prevButtonBounds.Height - prevTextSize.Y) / 2);
  646. spriteBatch.DrawString(ScreenManager.RegularFont, prevText, prevTextPos, Color.Black * TransitionAlpha);
  647. }
  648. if (currentPage < TotalPages - 1)
  649. {
  650. Texture2D nextTexture = isNextButtonPressed ? buttonPressedTexture : buttonRegularTexture;
  651. spriteBatch.Draw(nextTexture, nextButtonBounds, Color.White * TransitionAlpha);
  652. string nextText = Resources.Next;
  653. Vector2 nextTextSize = ScreenManager.RegularFont.MeasureString(nextText);
  654. Vector2 nextTextPos = new Vector2(
  655. nextButtonBounds.X + (nextButtonBounds.Width - nextTextSize.X) / 2,
  656. nextButtonBounds.Y + (nextButtonBounds.Height - nextTextSize.Y) / 2);
  657. spriteBatch.DrawString(ScreenManager.RegularFont, nextText, nextTextPos, Color.Black * TransitionAlpha);
  658. }
  659. // Draw page indicator
  660. string pageText = string.Format(Resources.PageIndicator, currentPage + 1, TotalPages);
  661. Vector2 pageTextSize = ScreenManager.RegularFont.MeasureString(pageText);
  662. Vector2 pageTextPos = new Vector2(
  663. safeArea.Center.X - pageTextSize.X / 2,
  664. safeArea.Bottom - 55);
  665. spriteBatch.DrawString(ScreenManager.RegularFont, pageText, pageTextPos, Color.White * TransitionAlpha);
  666. spriteBatch.End();
  667. base.Draw(gameTime);
  668. }
  669. }
  670. enum SettingType
  671. {
  672. Header,
  673. Counter,
  674. Slider,
  675. Cycle,
  676. Checkbox
  677. }
  678. class SettingItem
  679. {
  680. public SettingType Type { get; set; }
  681. public string Label { get; set; }
  682. public Func<string> GetValue { get; set; }
  683. public Action OnClick { get; set; }
  684. public Action OnIncrease { get; set; }
  685. public Action OnDecrease { get; set; }
  686. public Rectangle Bounds { get; set; }
  687. }
  688. }