PageRenderTime 55ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/OpenRA.Mods.Common/Widgets/ProductionTabsWidget.cs

https://github.com/OpenRA/OpenRA
C# | 333 lines | 248 code | 66 blank | 19 comment | 54 complexity | 2b6475cb2e18f9de8ae1f5da5baf6431 MD5 | raw file
  1. #region Copyright & License Information
  2. /*
  3. * Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
  4. * This file is part of OpenRA, which is free software. It is made
  5. * available to you under the terms of the GNU General Public License
  6. * as published by the Free Software Foundation, either version 3 of
  7. * the License, or (at your option) any later version. For more
  8. * information, see COPYING.
  9. */
  10. #endregion
  11. using System;
  12. using System.Collections.Generic;
  13. using System.Linq;
  14. using OpenRA.Graphics;
  15. using OpenRA.Mods.Common.Traits;
  16. using OpenRA.Primitives;
  17. using OpenRA.Widgets;
  18. namespace OpenRA.Mods.Common.Widgets
  19. {
  20. public class ProductionTab
  21. {
  22. public string Name;
  23. public ProductionQueue Queue;
  24. }
  25. public class ProductionTabGroup
  26. {
  27. public List<ProductionTab> Tabs = new List<ProductionTab>();
  28. public string Group;
  29. public int NextQueueName = 1;
  30. public bool Alert { get { return Tabs.Any(t => t.Queue.AllQueued().Any(i => i.Done)); } }
  31. public void Update(IEnumerable<ProductionQueue> allQueues)
  32. {
  33. var queues = allQueues.Where(q => q.Info.Group == Group).ToList();
  34. var tabs = new List<ProductionTab>();
  35. var largestUsedName = 0;
  36. // Remove stale queues
  37. foreach (var t in Tabs)
  38. {
  39. if (!queues.Contains(t.Queue))
  40. continue;
  41. tabs.Add(t);
  42. queues.Remove(t.Queue);
  43. largestUsedName = Math.Max(int.Parse(t.Name), largestUsedName);
  44. }
  45. NextQueueName = largestUsedName + 1;
  46. // Add new queues
  47. foreach (var queue in queues)
  48. tabs.Add(new ProductionTab()
  49. {
  50. Name = (NextQueueName++).ToString(),
  51. Queue = queue
  52. });
  53. Tabs = tabs;
  54. }
  55. }
  56. public class ProductionTabsWidget : Widget
  57. {
  58. readonly World world;
  59. public readonly string PaletteWidget = null;
  60. public readonly string TypesContainer = null;
  61. public readonly string BackgroundContainer = null;
  62. public readonly int TabWidth = 30;
  63. public readonly int ArrowWidth = 20;
  64. public readonly string ClickSound = ChromeMetrics.Get<string>("ClickSound");
  65. public readonly string ClickDisabledSound = ChromeMetrics.Get<string>("ClickDisabledSound");
  66. public readonly HotkeyReference PreviousProductionTabKey = new HotkeyReference();
  67. public readonly HotkeyReference NextProductionTabKey = new HotkeyReference();
  68. public readonly Dictionary<string, ProductionTabGroup> Groups;
  69. public string Button = "button";
  70. public string Background = "panel-black";
  71. public readonly string Decorations = "scrollpanel-decorations";
  72. public readonly string DecorationScrollLeft = "left";
  73. public readonly string DecorationScrollRight = "right";
  74. readonly CachedTransform<(bool Disabled, bool Pressed, bool Hover, bool Focused), Sprite> getLeftArrowImage;
  75. readonly CachedTransform<(bool Disabled, bool Pressed, bool Hover, bool Focused), Sprite> getRightArrowImage;
  76. int contentWidth = 0;
  77. float listOffset = 0;
  78. bool leftPressed = false;
  79. bool rightPressed = false;
  80. SpriteFont font;
  81. Rectangle leftButtonRect;
  82. Rectangle rightButtonRect;
  83. readonly Lazy<ProductionPaletteWidget> paletteWidget;
  84. string queueGroup;
  85. [ObjectCreator.UseCtor]
  86. public ProductionTabsWidget(World world)
  87. {
  88. this.world = world;
  89. Groups = world.Map.Rules.Actors.Values.SelectMany(a => a.TraitInfos<ProductionQueueInfo>())
  90. .Select(q => q.Group).Distinct().ToDictionary(g => g, g => new ProductionTabGroup() { Group = g });
  91. // Only visible if the production palette has icons to display
  92. IsVisible = () => queueGroup != null && Groups[queueGroup].Tabs.Count > 0;
  93. paletteWidget = Exts.Lazy(() => Ui.Root.Get<ProductionPaletteWidget>(PaletteWidget));
  94. getLeftArrowImage = WidgetUtils.GetCachedStatefulImage(Decorations, DecorationScrollLeft);
  95. getRightArrowImage = WidgetUtils.GetCachedStatefulImage(Decorations, DecorationScrollRight);
  96. }
  97. public override void Initialize(WidgetArgs args)
  98. {
  99. base.Initialize(args);
  100. var rb = RenderBounds;
  101. leftButtonRect = new Rectangle(rb.X, rb.Y, ArrowWidth, rb.Height);
  102. rightButtonRect = new Rectangle(rb.Right - ArrowWidth, rb.Y, ArrowWidth, rb.Height);
  103. font = Game.Renderer.Fonts["TinyBold"];
  104. }
  105. public bool SelectNextTab(bool reverse)
  106. {
  107. if (queueGroup == null)
  108. return true;
  109. // Prioritize alerted queues
  110. var queues = Groups[queueGroup].Tabs.Select(t => t.Queue)
  111. .OrderByDescending(q => q.AllQueued().Any(i => i.Done) ? 1 : 0)
  112. .ToList();
  113. if (reverse) queues.Reverse();
  114. CurrentQueue = queues.SkipWhile(q => q != CurrentQueue)
  115. .Skip(1).FirstOrDefault() ?? queues.FirstOrDefault();
  116. return true;
  117. }
  118. public void PickUpCompletedBuilding()
  119. {
  120. // This is called from ProductionTabsLogic
  121. paletteWidget.Value.PickUpCompletedBuilding();
  122. }
  123. public string QueueGroup
  124. {
  125. get => queueGroup;
  126. set
  127. {
  128. listOffset = 0;
  129. queueGroup = value;
  130. SelectNextTab(false);
  131. }
  132. }
  133. public ProductionQueue CurrentQueue
  134. {
  135. get => paletteWidget.Value.CurrentQueue;
  136. set
  137. {
  138. paletteWidget.Value.CurrentQueue = value;
  139. queueGroup = value != null ? value.Info.Group : null;
  140. // TODO: Scroll tabs so selected queue is visible
  141. }
  142. }
  143. public override void Draw()
  144. {
  145. var tabs = Groups[queueGroup].Tabs.Where(t => t.Queue.BuildableItems().Any());
  146. if (!tabs.Any())
  147. return;
  148. var rb = RenderBounds;
  149. var leftDisabled = listOffset >= 0;
  150. var leftHover = Ui.MouseOverWidget == this && leftButtonRect.Contains(Viewport.LastMousePos);
  151. var rightDisabled = listOffset <= Bounds.Width - rightButtonRect.Width - leftButtonRect.Width - contentWidth;
  152. var rightHover = Ui.MouseOverWidget == this && rightButtonRect.Contains(Viewport.LastMousePos);
  153. WidgetUtils.DrawPanel(Background, rb);
  154. ButtonWidget.DrawBackground(Button, leftButtonRect, leftDisabled, leftPressed, leftHover, false);
  155. ButtonWidget.DrawBackground(Button, rightButtonRect, rightDisabled, rightPressed, rightHover, false);
  156. var leftArrowImage = getLeftArrowImage.Update((leftDisabled, leftPressed, leftHover, false));
  157. WidgetUtils.DrawSprite(leftArrowImage,
  158. new float2(leftButtonRect.Left + 2, leftButtonRect.Top + 2));
  159. var rightArrowImage = getRightArrowImage.Update((rightDisabled, rightPressed, rightHover, false));
  160. WidgetUtils.DrawSprite(rightArrowImage,
  161. new float2(rightButtonRect.Left + 2, rightButtonRect.Top + 2));
  162. // Draw tab buttons
  163. Game.Renderer.EnableScissor(new Rectangle(leftButtonRect.Right, rb.Y + 1, rightButtonRect.Left - leftButtonRect.Right - 1, rb.Height));
  164. var origin = new int2(leftButtonRect.Right - 1 + (int)listOffset, leftButtonRect.Y);
  165. contentWidth = 0;
  166. foreach (var tab in tabs)
  167. {
  168. var rect = new Rectangle(origin.X + contentWidth, origin.Y, TabWidth, rb.Height);
  169. var hover = !leftHover && !rightHover && Ui.MouseOverWidget == this && rect.Contains(Viewport.LastMousePos);
  170. var highlighted = tab.Queue == CurrentQueue;
  171. ButtonWidget.DrawBackground(Button, rect, false, false, hover, highlighted);
  172. contentWidth += TabWidth - 1;
  173. var textSize = font.Measure(tab.Name);
  174. var position = new int2(rect.X + (rect.Width - textSize.X) / 2, rect.Y + (rect.Height - textSize.Y) / 2);
  175. font.DrawTextWithContrast(tab.Name, position, tab.Queue.AllQueued().Any(i => i.Done) ? Color.Gold : Color.White, Color.Black, 1);
  176. }
  177. Game.Renderer.DisableScissor();
  178. }
  179. void Scroll(int amount)
  180. {
  181. listOffset += amount * Game.Settings.Game.UIScrollSpeed;
  182. listOffset = Math.Min(0, Math.Max(Bounds.Width - rightButtonRect.Width - leftButtonRect.Width - contentWidth, listOffset));
  183. }
  184. // Is added to world.ActorAdded by the SidebarLogic handler
  185. public void ActorChanged(Actor a)
  186. {
  187. if (a.Info.HasTraitInfo<ProductionQueueInfo>())
  188. {
  189. var allQueues = a.World.ActorsWithTrait<ProductionQueue>()
  190. .Where(p => p.Actor.Owner == p.Actor.World.LocalPlayer && p.Actor.IsInWorld && p.Trait.Enabled)
  191. .Select(p => p.Trait).ToList();
  192. foreach (var g in Groups.Values)
  193. g.Update(allQueues);
  194. if (queueGroup == null)
  195. return;
  196. // Queue destroyed, was last of type: switch to a new group
  197. if (Groups[queueGroup].Tabs.Count == 0)
  198. QueueGroup = Groups.Where(g => g.Value.Tabs.Count > 0)
  199. .Select(g => g.Key).FirstOrDefault();
  200. // Queue destroyed, others of same type: switch to another tab
  201. else if (!Groups[queueGroup].Tabs.Select(t => t.Queue).Contains(CurrentQueue))
  202. SelectNextTab(false);
  203. }
  204. }
  205. public override void Tick()
  206. {
  207. if (leftPressed) Scroll(1);
  208. if (rightPressed) Scroll(-1);
  209. }
  210. public override bool YieldMouseFocus(MouseInput mi)
  211. {
  212. leftPressed = rightPressed = false;
  213. return base.YieldMouseFocus(mi);
  214. }
  215. public override bool HandleMouseInput(MouseInput mi)
  216. {
  217. if (mi.Event == MouseInputEvent.Scroll)
  218. {
  219. Scroll(mi.Delta.Y);
  220. return true;
  221. }
  222. if (mi.Button != MouseButton.Left)
  223. return true;
  224. if (mi.Event == MouseInputEvent.Down && !TakeMouseFocus(mi))
  225. return true;
  226. if (!HasMouseFocus)
  227. return true;
  228. if (HasMouseFocus && mi.Event == MouseInputEvent.Up)
  229. return YieldMouseFocus(mi);
  230. leftPressed = leftButtonRect.Contains(mi.Location);
  231. rightPressed = rightButtonRect.Contains(mi.Location);
  232. var leftDisabled = listOffset >= 0;
  233. var rightDisabled = listOffset <= Bounds.Width - rightButtonRect.Width - leftButtonRect.Width - contentWidth;
  234. if (leftPressed || rightPressed)
  235. {
  236. if ((leftPressed && !leftDisabled) || (rightPressed && !rightDisabled))
  237. Game.Sound.PlayNotification(world.Map.Rules, null, "Sounds", ClickSound, null);
  238. else
  239. Game.Sound.PlayNotification(world.Map.Rules, null, "Sounds", ClickDisabledSound, null);
  240. }
  241. // Check production tabs
  242. var offsetloc = mi.Location - new int2(leftButtonRect.Right - 1 + (int)listOffset, leftButtonRect.Y);
  243. if (offsetloc.X > 0 && offsetloc.X < contentWidth)
  244. {
  245. CurrentQueue = Groups[queueGroup].Tabs[offsetloc.X / (TabWidth - 1)].Queue;
  246. Game.Sound.PlayNotification(world.Map.Rules, null, "Sounds", ClickSound, null);
  247. }
  248. return true;
  249. }
  250. public override bool HandleKeyPress(KeyInput e)
  251. {
  252. if (e.Event != KeyInputEvent.Down)
  253. return false;
  254. if (PreviousProductionTabKey.IsActivatedBy(e))
  255. {
  256. Game.Sound.PlayNotification(world.Map.Rules, null, "Sounds", ClickSound, null);
  257. return SelectNextTab(true);
  258. }
  259. if (NextProductionTabKey.IsActivatedBy(e))
  260. {
  261. Game.Sound.PlayNotification(world.Map.Rules, null, "Sounds", ClickSound, null);
  262. return SelectNextTab(false);
  263. }
  264. return false;
  265. }
  266. }
  267. }