1 #region Copyright & License Information
2 /*
3  * Copyright 2007-2020 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 
12 using System;
13 using System.Collections.Generic;
14 using System.Linq;
15 using OpenRA.Graphics;
16 using OpenRA.Mods.Common.Traits;
17 using OpenRA.Primitives;
18 using OpenRA.Widgets;
19 
20 namespace OpenRA.Mods.Common.Widgets
21 {
22 	public class ObserverArmyIconsWidget : Widget
23 	{
24 		public Func<Player> GetPlayer;
25 		readonly World world;
26 		readonly WorldRenderer worldRenderer;
27 
28 		public int IconWidth = 32;
29 		public int IconHeight = 24;
30 		public int IconSpacing = 1;
31 
32 		float2 iconSize;
33 		public int MinWidth = 240;
34 
35 		public ArmyUnit TooltipUnit { get; private set; }
36 		public Func<ArmyUnit> GetTooltipUnit;
37 
38 		public readonly string TooltipTemplate = "ARMY_TOOLTIP";
39 		public readonly string TooltipContainer;
40 
41 		readonly Lazy<TooltipContainerWidget> tooltipContainer;
42 		readonly List<ArmyIcon> armyIcons = new List<ArmyIcon>();
43 
44 		readonly CachedTransform<Player, PlayerStatistics> stats = new CachedTransform<Player, PlayerStatistics>(player => player.PlayerActor.TraitOrDefault<PlayerStatistics>());
45 
46 		int lastIconIdx;
47 		int currentTooltipToken;
48 
49 		[ObjectCreator.UseCtor]
ObserverArmyIconsWidget(World world, WorldRenderer worldRenderer)50 		public ObserverArmyIconsWidget(World world, WorldRenderer worldRenderer)
51 		{
52 			this.world = world;
53 			this.worldRenderer = worldRenderer;
54 
55 			GetTooltipUnit = () => TooltipUnit;
56 			tooltipContainer = Exts.Lazy(() =>
57 				Ui.Root.Get<TooltipContainerWidget>(TooltipContainer));
58 		}
59 
ObserverArmyIconsWidget(ObserverArmyIconsWidget other)60 		protected ObserverArmyIconsWidget(ObserverArmyIconsWidget other)
61 			: base(other)
62 		{
63 			GetPlayer = other.GetPlayer;
64 			world = other.world;
65 			worldRenderer = other.worldRenderer;
66 
67 			IconWidth = other.IconWidth;
68 			IconHeight = other.IconHeight;
69 			IconSpacing = other.IconSpacing;
70 			iconSize = new float2(IconWidth, IconHeight);
71 
72 			MinWidth = other.MinWidth;
73 
74 			TooltipUnit = other.TooltipUnit;
75 			GetTooltipUnit = () => TooltipUnit;
76 
77 			TooltipTemplate = other.TooltipTemplate;
78 			TooltipContainer = other.TooltipContainer;
79 
80 			tooltipContainer = Exts.Lazy(() =>
81 				Ui.Root.Get<TooltipContainerWidget>(TooltipContainer));
82 		}
83 
Draw()84 		public override void Draw()
85 		{
86 			armyIcons.Clear();
87 
88 			var player = GetPlayer();
89 			if (player == null)
90 				return;
91 
92 			var playerStatistics = stats.Update(player);
93 
94 			var items = playerStatistics.Units.Values
95 				.Where(u => u.Count > 0 && u.Icon != null)
96 				.OrderBy(u => u.ProductionQueueOrder)
97 				.ThenBy(u => u.BuildPaletteOrder);
98 
99 			Game.Renderer.EnableAntialiasingFilter();
100 
101 			var queueCol = 0;
102 			foreach (var unit in items)
103 			{
104 				var icon = unit.Icon;
105 				var topLeftOffset = new int2(queueCol * (IconWidth + IconSpacing), 0);
106 
107 				var iconTopLeft = RenderOrigin + topLeftOffset;
108 				var centerPosition = iconTopLeft;
109 
110 				WidgetUtils.DrawSHPCentered(icon.Image, centerPosition + 0.5f * iconSize, worldRenderer.Palette(unit.IconPalette), 0.5f);
111 
112 				armyIcons.Add(new ArmyIcon
113 				{
114 					Bounds = new Rectangle(iconTopLeft.X, iconTopLeft.Y, (int)iconSize.X, (int)iconSize.Y),
115 					Unit = unit
116 				});
117 
118 				queueCol++;
119 			}
120 
121 			var newWidth = Math.Max(queueCol * (IconWidth + IconSpacing), MinWidth);
122 			if (newWidth != Bounds.Width)
123 			{
124 				var wasInBounds = EventBounds.Contains(Viewport.LastMousePos);
125 				Bounds.Width = newWidth;
126 				var isInBounds = EventBounds.Contains(Viewport.LastMousePos);
127 
128 				// HACK: Ui.MouseOverWidget is normally only updated when the mouse moves
129 				// Call ResetTooltips to force a fake mouse movement so the checks in Tick will work properly
130 				if (wasInBounds != isInBounds)
131 					Game.RunAfterTick(Ui.ResetTooltips);
132 			}
133 
134 			Game.Renderer.DisableAntialiasingFilter();
135 
136 			var bold = Game.Renderer.Fonts["TinyBold"];
137 			foreach (var armyIcon in armyIcons)
138 			{
139 				var text = armyIcon.Unit.Count.ToString();
140 				bold.DrawTextWithContrast(text, armyIcon.Bounds.Location + new float2(iconSize.X, 0) - new float2(bold.Measure(text).X, bold.TopOffset),
141 					Color.White, Color.Black, 1);
142 			}
143 
144 			var parentWidth = Bounds.X + Bounds.Width;
145 			Parent.Bounds.Width = parentWidth;
146 
147 			var gradient = Parent.Get<GradientColorBlockWidget>("PLAYER_GRADIENT");
148 
149 			var offset = gradient.Bounds.X - Bounds.X;
150 			var gradientWidth = Math.Max(MinWidth - offset, queueCol * (IconWidth + IconSpacing));
151 
152 			gradient.Bounds.Width = gradientWidth;
153 			var widestChildWidth = Parent.Parent.Children.Max(x => x.Bounds.Width);
154 
155 			Parent.Parent.Bounds.Width = Math.Max(25 + widestChildWidth, Bounds.Left + MinWidth);
156 		}
157 
Clone()158 		public override Widget Clone()
159 		{
160 			return new ObserverArmyIconsWidget(this);
161 		}
162 
Tick()163 		public override void Tick()
164 		{
165 			if (TooltipContainer == null)
166 				return;
167 
168 			if (Ui.MouseOverWidget != this)
169 			{
170 				if (TooltipUnit != null)
171 				{
172 					tooltipContainer.Value.RemoveTooltip(currentTooltipToken);
173 					lastIconIdx = 0;
174 					TooltipUnit = null;
175 				}
176 
177 				return;
178 			}
179 
180 			if (TooltipUnit != null && lastIconIdx < armyIcons.Count)
181 			{
182 				var armyIcon = armyIcons[lastIconIdx];
183 				if (armyIcon.Unit.ActorInfo == TooltipUnit.ActorInfo && armyIcon.Bounds.Contains(Viewport.LastMousePos))
184 					return;
185 			}
186 
187 			for (var i = 0; i < armyIcons.Count; i++)
188 			{
189 				var armyIcon = armyIcons[i];
190 				if (!armyIcon.Bounds.Contains(Viewport.LastMousePos))
191 					continue;
192 
193 				lastIconIdx = i;
194 				TooltipUnit = armyIcon.Unit;
195 				currentTooltipToken = tooltipContainer.Value.SetTooltip(TooltipTemplate, new WidgetArgs { { "getTooltipUnit", GetTooltipUnit } });
196 
197 				return;
198 			}
199 
200 			TooltipUnit = null;
201 		}
202 
203 		class ArmyIcon
204 		{
205 			public Rectangle Bounds { get; set; }
206 			public ArmyUnit Unit { get; set; }
207 		}
208 	}
209 }
210