Files
OpenRA/OpenRA.Mods.Common/Widgets/ObserverArmyIconsWidget.cs
RoosterDragon aa121dcb2f Use covariant return types.
Use the covariant return type feature of C# 9 to allow method overrides to be more specific about their return type. By exposing this information, e.g. for Widget.Clone(), callers will have more information. This allows various casts to be removed as the information is now available within the type system.
2025-01-15 17:29:02 +02:00

212 lines
5.9 KiB
C#

#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Traits;
using OpenRA.Primitives;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets
{
public class ObserverArmyIconsWidget : Widget
{
public Func<Player> GetPlayer;
readonly World world;
readonly WorldRenderer worldRenderer;
public int IconWidth = 32;
public int IconHeight = 24;
public int IconSpacing = 1;
readonly float2 iconSize;
public int MinWidth = 240;
public ArmyUnit TooltipUnit { get; private set; }
public Func<ArmyUnit> GetTooltipUnit;
public readonly string TooltipTemplate = "ARMY_TOOLTIP";
public readonly string TooltipContainer;
readonly Lazy<TooltipContainerWidget> tooltipContainer;
readonly List<ArmyIcon> armyIcons = new();
readonly CachedTransform<Player, PlayerStatistics> stats = new(player => player.PlayerActor.TraitOrDefault<PlayerStatistics>());
int lastIconIdx;
int currentTooltipToken;
[ObjectCreator.UseCtor]
public ObserverArmyIconsWidget(World world, WorldRenderer worldRenderer)
{
this.world = world;
this.worldRenderer = worldRenderer;
GetTooltipUnit = () => TooltipUnit;
tooltipContainer = Exts.Lazy(() =>
Ui.Root.Get<TooltipContainerWidget>(TooltipContainer));
}
protected ObserverArmyIconsWidget(ObserverArmyIconsWidget other)
: base(other)
{
GetPlayer = other.GetPlayer;
world = other.world;
worldRenderer = other.worldRenderer;
IconWidth = other.IconWidth;
IconHeight = other.IconHeight;
IconSpacing = other.IconSpacing;
iconSize = new float2(IconWidth, IconHeight);
MinWidth = other.MinWidth;
TooltipUnit = other.TooltipUnit;
GetTooltipUnit = () => TooltipUnit;
TooltipTemplate = other.TooltipTemplate;
TooltipContainer = other.TooltipContainer;
tooltipContainer = Exts.Lazy(() =>
Ui.Root.Get<TooltipContainerWidget>(TooltipContainer));
}
public override void Draw()
{
armyIcons.Clear();
var player = GetPlayer();
if (player == null)
return;
var playerStatistics = stats.Update(player);
var items = playerStatistics.Units.Values
.Where(u => u.Count > 0 && u.Icon != null)
.OrderBy(u => u.ProductionQueueOrder)
.ThenBy(u => u.BuildPaletteOrder);
Game.Renderer.EnableAntialiasingFilter();
var queueCol = 0;
foreach (var unit in items)
{
var icon = unit.Icon;
var topLeftOffset = new int2(queueCol * (IconWidth + IconSpacing), 0);
var iconTopLeft = RenderOrigin + topLeftOffset;
var centerPosition = iconTopLeft;
var palette = unit.IconPaletteIsPlayerPalette ? unit.IconPalette + player.InternalName : unit.IconPalette;
WidgetUtils.DrawSpriteCentered(icon.Image, worldRenderer.Palette(palette), centerPosition + 0.5f * iconSize, 0.5f);
armyIcons.Add(new ArmyIcon
{
Bounds = new Rectangle(iconTopLeft.X, iconTopLeft.Y, (int)iconSize.X, (int)iconSize.Y),
Unit = unit
});
queueCol++;
}
var newWidth = Math.Max(queueCol * (IconWidth + IconSpacing), MinWidth);
if (newWidth != Bounds.Width)
{
var wasInBounds = EventBounds.Contains(Viewport.LastMousePos);
Bounds.Width = newWidth;
var isInBounds = EventBounds.Contains(Viewport.LastMousePos);
// HACK: Ui.MouseOverWidget is normally only updated when the mouse moves
// Call ResetTooltips to force a fake mouse movement so the checks in Tick will work properly
if (wasInBounds != isInBounds)
Game.RunAfterTick(Ui.ResetTooltips);
}
Game.Renderer.DisableAntialiasingFilter();
var bold = Game.Renderer.Fonts["TinyBold"];
foreach (var armyIcon in armyIcons)
{
var text = armyIcon.Unit.Count.ToString(NumberFormatInfo.CurrentInfo);
bold.DrawTextWithContrast(text, armyIcon.Bounds.Location + new float2(iconSize.X, 0) - new float2(bold.Measure(text).X, bold.TopOffset),
Color.White, Color.Black, 1);
}
var parentWidth = Bounds.X + Bounds.Width;
Parent.Bounds.Width = parentWidth;
var gradient = Parent.Get<GradientColorBlockWidget>("PLAYER_GRADIENT");
var offset = gradient.Bounds.X - Bounds.X;
var gradientWidth = Math.Max(MinWidth - offset, queueCol * (IconWidth + IconSpacing));
gradient.Bounds.Width = gradientWidth;
var widestChildWidth = Parent.Parent.Children.Max(x => x.Bounds.Width);
Parent.Parent.Bounds.Width = Math.Max(25 + widestChildWidth, Bounds.Left + MinWidth);
}
public override ObserverArmyIconsWidget Clone()
{
return new ObserverArmyIconsWidget(this);
}
public override void Tick()
{
if (TooltipContainer == null)
return;
if (Ui.MouseOverWidget != this)
{
if (TooltipUnit != null)
{
tooltipContainer.Value.RemoveTooltip(currentTooltipToken);
lastIconIdx = 0;
TooltipUnit = null;
}
return;
}
if (TooltipUnit != null && lastIconIdx < armyIcons.Count)
{
var armyIcon = armyIcons[lastIconIdx];
if (armyIcon.Unit.ActorInfo == TooltipUnit.ActorInfo && armyIcon.Bounds.Contains(Viewport.LastMousePos))
return;
}
for (var i = 0; i < armyIcons.Count; i++)
{
var armyIcon = armyIcons[i];
if (!armyIcon.Bounds.Contains(Viewport.LastMousePos))
continue;
lastIconIdx = i;
TooltipUnit = armyIcon.Unit;
currentTooltipToken = tooltipContainer.Value.SetTooltip(TooltipTemplate, new WidgetArgs { { "getTooltipUnit", GetTooltipUnit } });
return;
}
TooltipUnit = null;
}
sealed class ArmyIcon
{
public Rectangle Bounds { get; set; }
public ArmyUnit Unit { get; set; }
}
}
}