- Rename the filename parameter to name and make it mandatory. Review all callers and ensure a useful string is provided as input, to ensure sufficient context is included for logging and debugging. This can be a filename, url, or any arbitrary text so include whatever context seems reasonable. - When several MiniYamls are created that have similar content, provide a shared string pool. This allows strings that are common between all the yaml to be shared, reducing long term memory usage. We also change the pool from a dictionary to a set. Originally a Dictionary had to be used so we could call TryGetValue to get a reference to the pooled string. Now that more recent versions of dotnet provide a TryGetValue on HashSet, we can use a set directly without the memory wasted by having to store both keys and values in a dictionary.
356 lines
12 KiB
C#
356 lines
12 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.Linq;
|
|
using System.Threading.Tasks;
|
|
using OpenRA.Graphics;
|
|
using OpenRA.Network;
|
|
using OpenRA.Support;
|
|
using OpenRA.Widgets;
|
|
|
|
namespace OpenRA.Mods.Common.Widgets.Logic
|
|
{
|
|
public class LocalProfileLogic : ChromeLogic
|
|
{
|
|
readonly WorldRenderer worldRenderer;
|
|
readonly LocalPlayerProfile localProfile;
|
|
readonly Widget badgeContainer;
|
|
readonly Widget widget;
|
|
bool notFound;
|
|
bool badgesVisible;
|
|
|
|
[ObjectCreator.UseCtor]
|
|
public LocalProfileLogic(Widget widget, WorldRenderer worldRenderer, Func<bool> minimalProfile)
|
|
{
|
|
this.worldRenderer = worldRenderer;
|
|
this.widget = widget;
|
|
localProfile = Game.LocalPlayerProfile;
|
|
|
|
// Key registration
|
|
widget.Get("GENERATE_KEYS").IsVisible = () => localProfile.State == LocalPlayerProfile.LinkState.Uninitialized && !minimalProfile();
|
|
widget.Get("GENERATING_KEYS").IsVisible = () => localProfile.State == LocalPlayerProfile.LinkState.GeneratingKeys && !minimalProfile();
|
|
|
|
var lastProfileState = LocalPlayerProfile.LinkState.CheckingLink;
|
|
widget.Get("REGISTER_FINGERPRINT").IsVisible = () =>
|
|
{
|
|
// Take a copy of the state to avoid race conditions
|
|
var state = localProfile.State;
|
|
|
|
// Copy the key to the clipboard when displaying the link instructions
|
|
if (state != lastProfileState && state == LocalPlayerProfile.LinkState.Unlinked)
|
|
Game.SetClipboardText(localProfile.PublicKey);
|
|
|
|
lastProfileState = state;
|
|
return localProfile.State == LocalPlayerProfile.LinkState.Unlinked && !notFound && !minimalProfile();
|
|
};
|
|
|
|
widget.Get("CHECKING_FINGERPRINT").IsVisible = () => localProfile.State == LocalPlayerProfile.LinkState.CheckingLink && !minimalProfile();
|
|
widget.Get("FINGERPRINT_NOT_FOUND").IsVisible = () => localProfile.State == LocalPlayerProfile.LinkState.Unlinked && notFound && !minimalProfile();
|
|
widget.Get("CONNECTION_ERROR").IsVisible = () => localProfile.State == LocalPlayerProfile.LinkState.ConnectionFailed && !minimalProfile();
|
|
|
|
widget.Get<ButtonWidget>("GENERATE_KEY").OnClick = localProfile.GenerateKeypair;
|
|
|
|
widget.Get<ButtonWidget>("CHECK_KEY").OnClick = () => localProfile.RefreshPlayerData(() => RefreshComplete(true));
|
|
|
|
widget.Get<ButtonWidget>("DELETE_KEY").OnClick = () =>
|
|
{
|
|
localProfile.DeleteKeypair();
|
|
Game.RunAfterTick(Ui.ResetTooltips);
|
|
};
|
|
|
|
widget.Get<ButtonWidget>("FINGERPRINT_NOT_FOUND_CONTINUE").OnClick = () =>
|
|
{
|
|
notFound = false;
|
|
Game.RunAfterTick(Ui.ResetTooltips);
|
|
};
|
|
|
|
widget.Get<ButtonWidget>("CONNECTION_ERROR_RETRY").OnClick = () => localProfile.RefreshPlayerData(() => RefreshComplete(true));
|
|
|
|
// Profile view
|
|
widget.Get("PROFILE_HEADER").IsVisible = () => localProfile.State == LocalPlayerProfile.LinkState.Linked;
|
|
widget.Get<LabelWidget>("PROFILE_NAME").GetText = () => localProfile.ProfileData.ProfileName;
|
|
widget.Get<LabelWidget>("PROFILE_RANK").GetText = () => localProfile.ProfileData.ProfileRank;
|
|
|
|
var destroyKey = widget.Get<ButtonWidget>("DESTROY_KEY");
|
|
destroyKey.OnClick = localProfile.DeleteKeypair;
|
|
destroyKey.IsDisabled = minimalProfile;
|
|
|
|
badgeContainer = widget.Get("BADGES_CONTAINER");
|
|
badgeContainer.IsVisible = () => badgesVisible && !minimalProfile()
|
|
&& localProfile.State == LocalPlayerProfile.LinkState.Linked;
|
|
|
|
localProfile.RefreshPlayerData(() => RefreshComplete(false));
|
|
}
|
|
|
|
public void RefreshComplete(bool updateNotFound)
|
|
{
|
|
if (updateNotFound)
|
|
notFound = localProfile.State == LocalPlayerProfile.LinkState.Unlinked;
|
|
|
|
Game.RunAfterTick(() =>
|
|
{
|
|
badgesVisible = false;
|
|
|
|
if (localProfile.State == LocalPlayerProfile.LinkState.Linked && localProfile.ProfileData.Badges.Count > 0)
|
|
{
|
|
Func<int, int> negotiateWidth = _ => widget.Get("PROFILE_HEADER").Bounds.Width;
|
|
|
|
// Remove any stale badges that may be left over from a previous session
|
|
badgeContainer.RemoveChildren();
|
|
|
|
var badges = Ui.LoadWidget("PLAYER_PROFILE_BADGES_INSERT", badgeContainer, new WidgetArgs()
|
|
{
|
|
{ "worldRenderer", worldRenderer },
|
|
{ "profile", localProfile.ProfileData },
|
|
{ "negotiateWidth", negotiateWidth }
|
|
});
|
|
|
|
if (badges.Bounds.Height > 0)
|
|
{
|
|
badgeContainer.Bounds.Height = badges.Bounds.Height;
|
|
badgesVisible = true;
|
|
}
|
|
}
|
|
|
|
Ui.ResetTooltips();
|
|
});
|
|
}
|
|
}
|
|
|
|
public class RegisteredProfileTooltipLogic : ChromeLogic
|
|
{
|
|
[TranslationReference]
|
|
const string LoadingPlayerProfile = "label-loading-player-profile";
|
|
|
|
[TranslationReference]
|
|
const string LoadingPlayerProfileFailed = "label-loading-player-profile-failed";
|
|
|
|
readonly PlayerDatabase playerDatabase;
|
|
PlayerProfile profile;
|
|
bool profileLoaded;
|
|
|
|
[ObjectCreator.UseCtor]
|
|
public RegisteredProfileTooltipLogic(Widget widget, WorldRenderer worldRenderer, ModData modData, Session.Client client)
|
|
{
|
|
playerDatabase = modData.Manifest.Get<PlayerDatabase>();
|
|
|
|
var header = widget.Get("HEADER");
|
|
var badgeContainer = widget.Get("BADGES_CONTAINER");
|
|
var badgeSeparator = badgeContainer.GetOrNull("SEPARATOR");
|
|
|
|
var profileHeader = header.Get("PROFILE_HEADER");
|
|
var messageHeader = header.Get("MESSAGE_HEADER");
|
|
var message = messageHeader.Get<LabelWidget>("MESSAGE");
|
|
var messageFont = Game.Renderer.Fonts[message.Font];
|
|
|
|
profileHeader.IsVisible = () => profileLoaded;
|
|
messageHeader.IsVisible = () => !profileLoaded;
|
|
|
|
var profileWidth = 0;
|
|
var maxProfileWidth = widget.Bounds.Width;
|
|
var messageText = TranslationProvider.GetString(LoadingPlayerProfile);
|
|
var messageWidth = messageFont.Measure(messageText).X + 2 * message.Bounds.Left;
|
|
|
|
Task.Run(async () =>
|
|
{
|
|
try
|
|
{
|
|
var httpClient = HttpClientFactory.Create();
|
|
|
|
var url = playerDatabase.Profile + client.Fingerprint;
|
|
var httpResponseMessage = await httpClient.GetAsync(url);
|
|
var result = await httpResponseMessage.Content.ReadAsStreamAsync();
|
|
|
|
var yaml = MiniYaml.FromStream(result, url).First();
|
|
if (yaml.Key == "Player")
|
|
{
|
|
profile = FieldLoader.Load<PlayerProfile>(yaml.Value);
|
|
Game.RunAfterTick(() =>
|
|
{
|
|
var nameLabel = profileHeader.Get<LabelWidget>("PROFILE_NAME");
|
|
var nameFont = Game.Renderer.Fonts[nameLabel.Font];
|
|
var rankLabel = profileHeader.Get<LabelWidget>("PROFILE_RANK");
|
|
var rankFont = Game.Renderer.Fonts[rankLabel.Font];
|
|
|
|
var adminContainer = profileHeader.Get("GAME_ADMIN");
|
|
var adminLabel = adminContainer.Get<LabelWidget>("LABEL");
|
|
var adminFont = Game.Renderer.Fonts[adminLabel.Font];
|
|
|
|
var headerSizeOffset = profileHeader.Bounds.Height - messageHeader.Bounds.Height;
|
|
|
|
nameLabel.GetText = () => profile.ProfileName;
|
|
rankLabel.GetText = () => profile.ProfileRank;
|
|
|
|
profileWidth = Math.Max(profileWidth, nameFont.Measure(profile.ProfileName).X + 2 * nameLabel.Bounds.Left);
|
|
profileWidth = Math.Max(profileWidth, rankFont.Measure(profile.ProfileRank).X + 2 * rankLabel.Bounds.Left);
|
|
|
|
header.Bounds.Height += headerSizeOffset;
|
|
badgeContainer.Bounds.Y += header.Bounds.Height;
|
|
if (client.IsAdmin)
|
|
{
|
|
profileWidth = Math.Max(profileWidth, adminFont.Measure(adminLabel.GetText()).X + 2 * adminLabel.Bounds.Left);
|
|
|
|
adminContainer.IsVisible = () => true;
|
|
profileHeader.Bounds.Height += adminLabel.Bounds.Height;
|
|
header.Bounds.Height += adminLabel.Bounds.Height;
|
|
badgeContainer.Bounds.Y += adminLabel.Bounds.Height;
|
|
}
|
|
|
|
Func<int, int> negotiateWidth = badgeWidth =>
|
|
{
|
|
profileWidth = Math.Min(Math.Max(badgeWidth, profileWidth), maxProfileWidth);
|
|
return profileWidth;
|
|
};
|
|
|
|
if (profile.Badges.Count > 0)
|
|
{
|
|
var badges = Ui.LoadWidget("PLAYER_PROFILE_BADGES_INSERT", badgeContainer, new WidgetArgs()
|
|
{
|
|
{ nameof(worldRenderer), worldRenderer },
|
|
{ nameof(profile), profile },
|
|
{ nameof(negotiateWidth), negotiateWidth }
|
|
});
|
|
|
|
if (badges.Bounds.Height > 0)
|
|
{
|
|
badgeContainer.Bounds.Height = badges.Bounds.Height;
|
|
badgeContainer.IsVisible = () => true;
|
|
}
|
|
}
|
|
|
|
profileWidth = Math.Min(profileWidth, maxProfileWidth);
|
|
header.Bounds.Width = widget.Bounds.Width = badgeContainer.Bounds.Width = profileWidth;
|
|
widget.Bounds.Height = header.Bounds.Height + badgeContainer.Bounds.Height;
|
|
|
|
if (badgeSeparator != null)
|
|
badgeSeparator.Bounds.Width = profileWidth - 2 * badgeSeparator.Bounds.X;
|
|
|
|
profileLoaded = true;
|
|
});
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Write("debug", "Failed to parse player data result with exception");
|
|
Log.Write("debug", e);
|
|
}
|
|
finally
|
|
{
|
|
if (profile == null)
|
|
{
|
|
messageText = TranslationProvider.GetString(LoadingPlayerProfileFailed);
|
|
messageWidth = messageFont.Measure(messageText).X + 2 * message.Bounds.Left;
|
|
header.Bounds.Width = widget.Bounds.Width = messageWidth;
|
|
}
|
|
}
|
|
});
|
|
|
|
message.GetText = () => messageText;
|
|
header.Bounds.Height += messageHeader.Bounds.Height;
|
|
header.Bounds.Width = widget.Bounds.Width = messageWidth;
|
|
widget.Bounds.Height = header.Bounds.Height;
|
|
badgeContainer.Visible = false;
|
|
}
|
|
}
|
|
|
|
public class PlayerProfileBadgesLogic : ChromeLogic
|
|
{
|
|
[ObjectCreator.UseCtor]
|
|
public PlayerProfileBadgesLogic(Widget widget, PlayerProfile profile, Func<int, int> negotiateWidth)
|
|
{
|
|
var showBadges = profile.Badges.Count > 0;
|
|
widget.IsVisible = () => showBadges;
|
|
|
|
var badgeTemplate = widget.Get("BADGE_TEMPLATE");
|
|
widget.RemoveChild(badgeTemplate);
|
|
|
|
// Negotiate the label length that the tooltip will allow
|
|
var maxLabelWidth = 0;
|
|
var templateIcon = badgeTemplate.Get("ICON");
|
|
var templateLabel = badgeTemplate.Get<LabelWidget>("LABEL");
|
|
var templateLabelFont = Game.Renderer.Fonts[templateLabel.Font];
|
|
foreach (var badge in profile.Badges)
|
|
maxLabelWidth = Math.Max(maxLabelWidth, templateLabelFont.Measure(badge.Label).X);
|
|
|
|
widget.Bounds.Width = negotiateWidth(2 * templateLabel.Bounds.Left - templateIcon.Bounds.Right + maxLabelWidth);
|
|
|
|
var badgeOffset = badgeTemplate.Bounds.Y;
|
|
if (profile.Badges.Count > 0)
|
|
badgeOffset += 3;
|
|
|
|
foreach (var badge in profile.Badges)
|
|
{
|
|
var b = badgeTemplate.Clone();
|
|
var icon = b.Get<BadgeWidget>("ICON");
|
|
icon.Badge = badge;
|
|
|
|
var label = b.Get<LabelWidget>("LABEL");
|
|
var labelFont = Game.Renderer.Fonts[label.Font];
|
|
|
|
var labelText = WidgetUtils.TruncateText(badge.Label, widget.Bounds.Width - label.Bounds.X - icon.Bounds.X, labelFont);
|
|
label.GetText = () => labelText;
|
|
|
|
b.Bounds.Y = badgeOffset;
|
|
widget.AddChild(b);
|
|
|
|
badgeOffset += badgeTemplate.Bounds.Height;
|
|
}
|
|
|
|
if (badgeOffset > badgeTemplate.Bounds.Y)
|
|
badgeOffset += 5;
|
|
|
|
widget.Bounds.Height = badgeOffset;
|
|
}
|
|
}
|
|
|
|
public class AnonymousProfileTooltipLogic : ChromeLogic
|
|
{
|
|
[ObjectCreator.UseCtor]
|
|
public AnonymousProfileTooltipLogic(Widget widget, Session.Client client)
|
|
{
|
|
var nameLabel = widget.Get<LabelWidget>("NAME");
|
|
var nameFont = Game.Renderer.Fonts[nameLabel.Font];
|
|
widget.Bounds.Width = nameFont.Measure(nameLabel.GetText()).X + 2 * nameLabel.Bounds.Left;
|
|
|
|
var locationLabel = widget.Get<LabelWidget>("LOCATION");
|
|
var ipLabel = widget.Get<LabelWidget>("IP");
|
|
var adminLabel = widget.Get("GAME_ADMIN");
|
|
|
|
if (client.Location != null)
|
|
{
|
|
var locationFont = Game.Renderer.Fonts[locationLabel.Font];
|
|
var locationWidth = widget.Bounds.Width - 2 * locationLabel.Bounds.X;
|
|
var location = WidgetUtils.TruncateText(client.Location, locationWidth, locationFont);
|
|
locationLabel.IsVisible = () => true;
|
|
locationLabel.GetText = () => location;
|
|
widget.Bounds.Height += locationLabel.Bounds.Height;
|
|
ipLabel.Bounds.Y += locationLabel.Bounds.Height;
|
|
adminLabel.Bounds.Y += locationLabel.Bounds.Height;
|
|
}
|
|
|
|
if (client.AnonymizedIPAddress != null)
|
|
{
|
|
ipLabel.IsVisible = () => true;
|
|
ipLabel.GetText = () => client.AnonymizedIPAddress;
|
|
widget.Bounds.Height += ipLabel.Bounds.Height;
|
|
adminLabel.Bounds.Y += locationLabel.Bounds.Height;
|
|
}
|
|
|
|
if (client.IsAdmin)
|
|
{
|
|
adminLabel.IsVisible = () => true;
|
|
widget.Bounds.Height += adminLabel.Bounds.Height;
|
|
}
|
|
}
|
|
}
|
|
}
|