From 84df61c67289c85955f033a1066d1eac82fdb368 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Tue, 11 Feb 2020 21:57:45 +0000 Subject: [PATCH] Add multi-resolution badge support. --- OpenRA.Game/PlayerDatabase.cs | 121 +++++++++++------- OpenRA.Game/PlayerProfile.cs | 11 +- OpenRA.Mods.Common/Widgets/BadgeWidget.cs | 46 +++++++ .../Widgets/Logic/PlayerProfileLogic.cs | 6 +- mods/cnc/chrome/playerprofile.yaml | 2 +- mods/common/chrome/playerprofile.yaml | 2 +- 6 files changed, 134 insertions(+), 54 deletions(-) create mode 100644 OpenRA.Mods.Common/Widgets/BadgeWidget.cs diff --git a/OpenRA.Game/PlayerDatabase.cs b/OpenRA.Game/PlayerDatabase.cs index 7a058ef08f..70dff9a647 100644 --- a/OpenRA.Game/PlayerDatabase.cs +++ b/OpenRA.Game/PlayerDatabase.cs @@ -23,67 +23,98 @@ namespace OpenRA public class PlayerDatabase : IGlobalModData { public readonly string Profile = "https://forum.openra.net/openra/info/"; + public readonly int IconSize = 24; - [FieldLoader.Ignore] - readonly object syncObject = new object(); - - [FieldLoader.Ignore] - readonly Dictionary spriteCache = new Dictionary(); - - // 128x128 is large enough for 25 unique 24x24 sprites + // 512x512 is large enough for 49 unique 72x72 badges + // or 100 unique 42x42 badges + // or 441 unique 24x24 badges + // or some combination of the above if the DPI changes ingame [FieldLoader.Ignore] SheetBuilder sheetBuilder; + [FieldLoader.Ignore] + Cache, Sprite> iconCache; + + Sprite LoadSprite(string url, int density) + { + var spriteSize = IconSize * density; + var sprite = sheetBuilder.Allocate(new Size(spriteSize, spriteSize), 1f / density); + + Action onComplete = i => + { + if (i.Error != null) + return; + + try + { + var icon = new Png(new MemoryStream(i.Result)); + if (icon.Width == spriteSize && icon.Height == spriteSize) + { + Game.RunAfterTick(() => + { + Util.FastCopyIntoSprite(sprite, icon); + sprite.Sheet.CommitBufferedData(); + }); + } + } + catch { } + }; + + new Download(url, _ => { }, onComplete); + + return sprite; + } + + Sheet CreateSheet() + { + var sheet = new Sheet(SheetType.BGRA, new Size(512, 512)); + + // We must manually force the buffer creation to avoid a crash + // that is indirectly triggered by rendering from a Sheet that + // has not yet been written to. + sheet.CreateBuffer(); + sheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear; + + return sheet; + } + public PlayerBadge LoadBadge(MiniYaml yaml) { if (sheetBuilder == null) { - sheetBuilder = new SheetBuilder(SheetType.BGRA, 128); + sheetBuilder = new SheetBuilder(SheetType.BGRA, CreateSheet); - // We must manually force the buffer creation to avoid a crash - // that is indirectly triggered by rendering from a Sheet that - // has not yet been written to. - sheetBuilder.Current.CreateBuffer(); + iconCache = new Cache, Sprite>(p => + { + if (p.Second > 2 && !string.IsNullOrEmpty(p.First.Icon3x)) + return LoadSprite(p.First.Icon3x, 3); + + if (p.Second > 1 && !string.IsNullOrEmpty(p.First.Icon2x)) + return LoadSprite(p.First.Icon2x, 2); + + return LoadSprite(p.First.Icon, 1); + }); } var labelNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Label"); var icon24Node = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon24"); - if (labelNode == null || icon24Node == null) + var icon48Node = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon48"); + var icon72Node = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon72"); + if (labelNode == null) return null; - Sprite sprite; - lock (syncObject) - { - if (!spriteCache.TryGetValue(icon24Node.Value.Value, out sprite)) - { - sprite = spriteCache[icon24Node.Value.Value] = sheetBuilder.Allocate(new Size(24, 24)); - sprite.Sheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear; + return new PlayerBadge( + labelNode.Value.Value, + icon24Node != null ? icon24Node.Value.Value : null, + icon48Node != null ? icon48Node.Value.Value : null, + icon72Node != null ? icon72Node.Value.Value : null); + } - Action onComplete = i => - { - if (i.Error != null) - return; - - try - { - var icon = new Png(new MemoryStream(i.Result)); - if (icon.Width == 24 && icon.Height == 24) - { - Game.RunAfterTick(() => - { - Util.FastCopyIntoSprite(sprite, icon); - sprite.Sheet.CommitBufferedData(); - }); - } - } - catch { } - }; - - new Download(icon24Node.Value.Value, _ => { }, onComplete); - } - } - - return new PlayerBadge(labelNode.Value.Value, sprite); + public Sprite GetIcon(PlayerBadge badge) + { + var ws = Game.Renderer.WindowScale; + var density = ws > 2 ? 3 : ws > 1 ? 2 : 1; + return iconCache[Pair.New(badge, density)]; } } } diff --git a/OpenRA.Game/PlayerProfile.cs b/OpenRA.Game/PlayerProfile.cs index 6d1bded77b..acc81bdc5f 100644 --- a/OpenRA.Game/PlayerProfile.cs +++ b/OpenRA.Game/PlayerProfile.cs @@ -11,7 +11,6 @@ using System.Collections.Generic; using System.Linq; -using OpenRA.Graphics; namespace OpenRA { @@ -59,12 +58,16 @@ namespace OpenRA public class PlayerBadge { public readonly string Label; - public readonly Sprite Icon24; + public readonly string Icon; + public readonly string Icon2x; + public readonly string Icon3x; - public PlayerBadge(string label, Sprite icon24) + public PlayerBadge(string label, string icon, string icon2x, string icon3x) { Label = label; - Icon24 = icon24; + Icon = icon; + Icon2x = icon2x; + Icon3x = icon3x; } } } diff --git a/OpenRA.Mods.Common/Widgets/BadgeWidget.cs b/OpenRA.Mods.Common/Widgets/BadgeWidget.cs new file mode 100644 index 0000000000..6e050db224 --- /dev/null +++ b/OpenRA.Mods.Common/Widgets/BadgeWidget.cs @@ -0,0 +1,46 @@ +#region Copyright & License Information +/* + * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) + * 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 OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets +{ + public class BadgeWidget : Widget + { + public PlayerBadge Badge; + readonly PlayerDatabase playerDatabase; + + [ObjectCreator.UseCtor] + public BadgeWidget(ModData modData) + { + playerDatabase = modData.Manifest.Get(); + } + + protected BadgeWidget(BadgeWidget other) + : base(other) + { + Badge = other.Badge; + playerDatabase = other.playerDatabase; + } + + public override Widget Clone() { return new BadgeWidget(this); } + + public override void Draw() + { + if (Badge == null) + return; + + var icon = playerDatabase.GetIcon(Badge); + if (icon != null) + Game.Renderer.RgbaSpriteRenderer.DrawSprite(icon, RenderOrigin); + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/Logic/PlayerProfileLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/PlayerProfileLogic.cs index 69e090afa2..dcb17c1ce4 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/PlayerProfileLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/PlayerProfileLogic.cs @@ -270,7 +270,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic // Negotiate the label length that the tooltip will allow var maxLabelWidth = 0; - var templateIcon = badgeTemplate.Get("ICON"); + var templateIcon = badgeTemplate.Get("ICON"); var templateLabel = badgeTemplate.Get("LABEL"); var templateLabelFont = Game.Renderer.Fonts[templateLabel.Font]; foreach (var badge in profile.Badges) @@ -285,8 +285,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic foreach (var badge in profile.Badges) { var b = badgeTemplate.Clone(); - var icon = b.Get("ICON"); - icon.GetSprite = () => badge.Icon24; + var icon = b.Get("ICON"); + icon.Badge = badge; var label = b.Get("LABEL"); var labelFont = Game.Renderer.Fonts[label.Font]; diff --git a/mods/cnc/chrome/playerprofile.yaml b/mods/cnc/chrome/playerprofile.yaml index 9c2c6fbb45..13d4704502 100644 --- a/mods/cnc/chrome/playerprofile.yaml +++ b/mods/cnc/chrome/playerprofile.yaml @@ -216,7 +216,7 @@ Container@PLAYER_PROFILE_BADGES_INSERT: Width: PARENT_RIGHT Height: 25 Children: - Sprite@ICON: + Badge@ICON: X: 6 Y: 1 Width: 24 diff --git a/mods/common/chrome/playerprofile.yaml b/mods/common/chrome/playerprofile.yaml index 7bb532acd8..8efdd69825 100644 --- a/mods/common/chrome/playerprofile.yaml +++ b/mods/common/chrome/playerprofile.yaml @@ -217,7 +217,7 @@ Container@PLAYER_PROFILE_BADGES_INSERT: Width: PARENT_RIGHT Height: 25 Children: - Sprite@ICON: + Badge@ICON: X: 6 Y: 1 Width: 24