From 199e9847d11ef0f0a354bbe4ff1e21bce9ebb290 Mon Sep 17 00:00:00 2001 From: rob-v Date: Mon, 3 Apr 2017 16:39:59 +0200 Subject: [PATCH] Add tooltips to production statistics #12820 --- .../Logic/Ingame/ObserverStatsLogic.cs | 1 + .../Logic/Ingame/ProductionTooltipLogic.cs | 22 +++-- .../Widgets/ObserverProductionIconsWidget.cs | 96 +++++++++++++++++-- .../Widgets/ProductionPaletteWidget.cs | 8 +- mods/cnc/chrome/ingame-observerstats.yaml | 1 + mods/common/chrome/ingame-observerstats.yaml | 1 + mods/ts/chrome/ingame-observerstats.yaml | 1 + 7 files changed, 112 insertions(+), 18 deletions(-) diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/ObserverStatsLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/ObserverStatsLogic.cs index 6506ff7701..077b0dba55 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/ObserverStatsLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/ObserverStatsLogic.cs @@ -220,6 +220,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic template.Get("PRODUCTION_ICONS").GetPlayer = () => player; template.Get("SUPPORT_POWER_ICONS").GetPlayer = () => player; + template.IgnoreChildMouseOver = false; return template; } diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/ProductionTooltipLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/ProductionTooltipLogic.cs index 7fb44f15c6..b32bb4eaf5 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/ProductionTooltipLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/ProductionTooltipLogic.cs @@ -22,13 +22,14 @@ namespace OpenRA.Mods.Common.Widgets.Logic public class ProductionTooltipLogic : ChromeLogic { [ObjectCreator.UseCtor] - public ProductionTooltipLogic(Widget widget, TooltipContainerWidget tooltipContainer, ProductionPaletteWidget palette, World world) + public ProductionTooltipLogic(Widget widget, TooltipContainerWidget tooltipContainer, Player player, Func getTooltipIcon) { - var mapRules = palette.World.Map.Rules; - var pm = palette.World.LocalPlayer.PlayerActor.Trait(); - var pr = palette.World.LocalPlayer.PlayerActor.Trait(); + var world = player.World; + var mapRules = world.Map.Rules; + var pm = player.PlayerActor.Trait(); + var pr = player.PlayerActor.Trait(); - widget.IsVisible = () => palette.TooltipIcon != null; + widget.IsVisible = () => getTooltipIcon() != null && getTooltipIcon().Actor != null; var nameLabel = widget.Get("NAME"); var hotkeyLabel = widget.Get("HOTKEY"); var requiresLabel = widget.Get("REQUIRES"); @@ -49,10 +50,11 @@ namespace OpenRA.Mods.Common.Widgets.Logic tooltipContainer.BeforeRender = () => { - if (palette.TooltipIcon == null) + var tooltipIcon = getTooltipIcon(); + if (tooltipIcon == null) return; - var actor = palette.TooltipIcon.Actor; + var actor = tooltipIcon.Actor; if (actor == null || actor == lastActor) return; @@ -63,7 +65,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic nameLabel.GetText = () => name; - var hotkey = palette.TooltipIcon.Hotkey; + var hotkey = tooltipIcon.Hotkey; var nameWidth = font.Measure(name).X; var hotkeyText = "({0})".F(hotkey.DisplayString()); var hotkeyWidth = hotkey.IsValid() ? font.Measure(hotkeyText).X + 2 * nameLabel.Bounds.X : 0; @@ -84,8 +86,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic powerIcon.IsVisible = () => power != 0; var lowpower = pm.PowerState != PowerState.Normal; - var time = palette.CurrentQueue == null ? 0 : palette.CurrentQueue.GetBuildTime(actor, buildable) - * (lowpower ? palette.CurrentQueue.Info.LowPowerSlowdown : 1); + var time = tooltipIcon.ProductionQueue == null ? 0 : tooltipIcon.ProductionQueue.GetBuildTime(actor, buildable) + * (lowpower ? tooltipIcon.ProductionQueue.Info.LowPowerSlowdown : 1); var timeString = WidgetUtils.FormatTime(time, world.Timestep); timeLabel.GetText = () => timeString; timeLabel.GetColor = () => lowpower ? Color.Red : Color.White; diff --git a/OpenRA.Mods.Common/Widgets/ObserverProductionIconsWidget.cs b/OpenRA.Mods.Common/Widgets/ObserverProductionIconsWidget.cs index 806d3fd21c..9f828abd5d 100644 --- a/OpenRA.Mods.Common/Widgets/ObserverProductionIconsWidget.cs +++ b/OpenRA.Mods.Common/Widgets/ObserverProductionIconsWidget.cs @@ -22,11 +22,12 @@ namespace OpenRA.Mods.Common.Widgets { public class ObserverProductionIconsWidget : Widget { + public readonly string TooltipTemplate = "PRODUCTION_TOOLTIP"; + public readonly string TooltipContainer; public Func GetPlayer; readonly World world; readonly WorldRenderer worldRenderer; readonly int timestep; - Dictionary clocks; public int IconWidth = 32; public int IconHeight = 24; @@ -36,6 +37,17 @@ namespace OpenRA.Mods.Common.Widgets public string ClockSequence = "idle"; public string ClockPalette = "chrome"; + public ProductionIcon TooltipIcon { get; private set; } + public Func GetTooltipIcon; + + Dictionary clocks; + float2 iconSize; + Rectangle[] iconRects = new Rectangle[0]; + ProductionIcon[] icons; + Rectangle renderBounds; + int lastIconIdx; + Lazy tooltipContainer; + [ObjectCreator.UseCtor] public ObserverProductionIconsWidget(World world, WorldRenderer worldRenderer) { @@ -43,6 +55,10 @@ namespace OpenRA.Mods.Common.Widgets this.worldRenderer = worldRenderer; clocks = new Dictionary(); timestep = world.IsReplay ? world.WorldActor.Trait().GameSpeed.Timestep : world.Timestep; + GetTooltipIcon = () => TooltipIcon; + tooltipContainer = Exts.Lazy(() => + Ui.Root.Get(TooltipContainer)); + iconSize = new float2(IconWidth, IconHeight); } protected ObserverProductionIconsWidget(ObserverProductionIconsWidget other) @@ -57,10 +73,21 @@ namespace OpenRA.Mods.Common.Widgets IconWidth = other.IconWidth; IconHeight = other.IconHeight; IconSpacing = other.IconSpacing; + iconSize = new float2(IconWidth, IconHeight); ClockAnimation = other.ClockAnimation; ClockSequence = other.ClockSequence; ClockPalette = other.ClockPalette; + + TooltipIcon = other.TooltipIcon; + GetTooltipIcon = () => TooltipIcon; + + TooltipTemplate = other.TooltipTemplate; + TooltipContainer = other.TooltipContainer; + + tooltipContainer = Exts.Lazy(() => + Ui.Root.Get(TooltipContainer)); + renderBounds = Rectangle.Empty; } public override void Draw() @@ -73,15 +100,22 @@ namespace OpenRA.Mods.Common.Widgets .Where(a => a.Actor.Owner == player) .Select((a, i) => new { a.Trait, i }); + if (renderBounds != RenderBounds) + { + renderBounds = RenderBounds; + InitIcons(renderBounds); + } + else + for (var i = 0; i < icons.Length; i++) + icons[i].Actor = null; + foreach (var queue in queues) + { if (!clocks.ContainsKey(queue.Trait)) clocks.Add(queue.Trait, new Animation(world, ClockAnimation)); - var iconSize = new float2(IconWidth, IconHeight); - foreach (var queue in queues) - { var current = queue.Trait.CurrentItem(); - if (current == null) + if (current == null || queue.i >= icons.Length) continue; var faction = queue.Trait.Actor.Owner.Faction.InternalName; @@ -93,9 +127,12 @@ namespace OpenRA.Mods.Common.Widgets var icon = new Animation(world, rsi.GetImage(actor, world.Map.Rules.Sequences, faction)); var bi = actor.TraitInfo(); icon.Play(bi.Icon); - var location = new float2(RenderBounds.Location) + new float2(queue.i * (IconWidth + IconSpacing), 0); + var location = new float2(iconRects[queue.i].Location); WidgetUtils.DrawSHPCentered(icon.Image, location + 0.5f * iconSize, worldRenderer.Palette(bi.IconPalette), 0.5f); + icons[queue.i].Actor = actor; + icons[queue.i].ProductionQueue = queue.Trait; + var pio = queue.Trait.Actor.Owner.PlayerActor.TraitsImplementing() .FirstOrDefault(p => p.IsOverlayActive(actor)); if (pio != null) @@ -132,5 +169,52 @@ namespace OpenRA.Mods.Common.Widgets { return new ObserverProductionIconsWidget(this); } + + public override void MouseEntered() + { + if (TooltipContainer != null) + tooltipContainer.Value.SetTooltip(TooltipTemplate, + new WidgetArgs() { { "player", GetPlayer() }, { "getTooltipIcon", GetTooltipIcon } }); + } + + public override void MouseExited() + { + if (TooltipContainer == null) + return; + + tooltipContainer.Value.RemoveTooltip(); + } + + public override void Tick() + { + if (TooltipIcon != null && iconRects[lastIconIdx].Contains(Viewport.LastMousePos)) + return; + + for (var i = 0; i < iconRects.Length; i++) + { + if (iconRects[i].Contains(Viewport.LastMousePos)) + { + lastIconIdx = i; + TooltipIcon = icons[i]; + return; + } + } + + TooltipIcon = null; + } + + void InitIcons(Rectangle renderBounds) + { + var iconWidthWithSpacing = IconWidth + IconSpacing; + var numOfIcons = renderBounds.Width / iconWidthWithSpacing; + iconRects = new Rectangle[numOfIcons]; + icons = new ProductionIcon[numOfIcons]; + + for (var i = 0; i < numOfIcons; i++) + { + iconRects[i] = new Rectangle(renderBounds.X + i * iconWidthWithSpacing, renderBounds.Y, IconWidth, IconHeight); + icons[i] = new ProductionIcon(); + } + } } } diff --git a/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs b/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs index f292ec70ca..89efbd744e 100644 --- a/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs +++ b/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs @@ -33,6 +33,7 @@ namespace OpenRA.Mods.Common.Widgets public PaletteReference IconDarkenPalette; public float2 Pos; public List Queued; + public ProductionQueue ProductionQueue; } public class ProductionPaletteWidget : Widget @@ -66,6 +67,7 @@ namespace OpenRA.Mods.Common.Widgets public event Action OnIconCountChanged = (a, b) => { }; public ProductionIcon TooltipIcon { get; private set; } + public Func GetTooltipIcon; public readonly World World; readonly OrderManager orderManager; @@ -98,6 +100,7 @@ namespace OpenRA.Mods.Common.Widgets this.orderManager = orderManager; World = world; this.worldRenderer = worldRenderer; + GetTooltipIcon = () => TooltipIcon; tooltipContainer = Exts.Lazy(() => Ui.Root.Get(TooltipContainer)); @@ -164,7 +167,7 @@ namespace OpenRA.Mods.Common.Widgets { if (TooltipContainer != null) tooltipContainer.Value.SetTooltip(TooltipTemplate, - new WidgetArgs() { { "world", World }, { "palette", this } }); + new WidgetArgs() { { "player", World.LocalPlayer }, { "getTooltipIcon", GetTooltipIcon } }); } public override void MouseExited() @@ -351,7 +354,8 @@ namespace OpenRA.Mods.Common.Widgets IconClockPalette = worldRenderer.Palette(ClockPalette), IconDarkenPalette = worldRenderer.Palette(NotBuildablePalette), Pos = new float2(rect.Location), - Queued = CurrentQueue.AllQueued().Where(a => a.Item == item.Name).ToList() + Queued = currentQueue.AllQueued().Where(a => a.Item == item.Name).ToList(), + ProductionQueue = currentQueue }; icons.Add(rect, pi); diff --git a/mods/cnc/chrome/ingame-observerstats.yaml b/mods/cnc/chrome/ingame-observerstats.yaml index e83fade1f1..44d7d9a82e 100644 --- a/mods/cnc/chrome/ingame-observerstats.yaml +++ b/mods/cnc/chrome/ingame-observerstats.yaml @@ -457,6 +457,7 @@ Background@INGAME_OBSERVERSTATS_BG: Y: 0 Width: 320 Height: PARENT_BOTTOM + TooltipContainer: TOOLTIP_CONTAINER ObserverSupportPowerIcons@SUPPORT_POWER_ICONS: X: 535 Y: 0 diff --git a/mods/common/chrome/ingame-observerstats.yaml b/mods/common/chrome/ingame-observerstats.yaml index 277c49611b..f6ac2a56b4 100644 --- a/mods/common/chrome/ingame-observerstats.yaml +++ b/mods/common/chrome/ingame-observerstats.yaml @@ -457,6 +457,7 @@ Background@INGAME_OBSERVERSTATS_BG: Y: 0 Width: 320 Height: PARENT_BOTTOM + TooltipContainer: TOOLTIP_CONTAINER ObserverSupportPowerIcons@SUPPORT_POWER_ICONS: X: 535 Y: 0 diff --git a/mods/ts/chrome/ingame-observerstats.yaml b/mods/ts/chrome/ingame-observerstats.yaml index 0ad6f606fb..d14e78af34 100644 --- a/mods/ts/chrome/ingame-observerstats.yaml +++ b/mods/ts/chrome/ingame-observerstats.yaml @@ -458,6 +458,7 @@ Background@INGAME_OBSERVERSTATS_BG: Width: 320 Height: PARENT_BOTTOM ClockPalette: iconclock + TooltipContainer: TOOLTIP_CONTAINER ObserverSupportPowerIcons@SUPPORT_POWER_ICONS: X: 535 Y: 0