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