osu/osu.Game/Skinning/SkinnableSprite.cs
Dean Herbert 92306b9123 Combine localisations for abstract skinnable components into a single file
Generally we don't want localisation files with only one to two
translations. It makes it harder for translators to handle in crowdin (a
lot of file changes for small results).

So for cases like this I believe we should be grouping translations
where it makes sense.

I've left individual components in their own files as I can see
potential for more settings to be added in the future. Plus it gives a
bit of extra context.
2023-02-03 16:02:18 +09:00

146 lines
5.5 KiB
C#

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Configuration;
using osu.Game.Graphics.Sprites;
using osu.Game.Localisation.SkinComponents;
using osu.Game.Overlays.Settings;
using osuTK;
namespace osu.Game.Skinning
{
/// <summary>
/// A skinnable element which uses a single texture backing.
/// </summary>
public partial class SkinnableSprite : SkinnableDrawable, ISkinnableDrawable
{
protected override bool ApplySizeRestrictionsToDefault => true;
[Resolved]
private TextureStore textures { get; set; } = null!;
[SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.SpriteName), nameof(SkinnableComponentStrings.SpriteNameDescription), SettingControlType = typeof(SpriteSelectorControl))]
public Bindable<string> SpriteName { get; } = new Bindable<string>(string.Empty);
[Resolved]
private ISkinSource source { get; set; } = null!;
public SkinnableSprite(string textureName, ConfineMode confineMode = ConfineMode.NoScaling)
: base(new SpriteComponentLookup(textureName), confineMode)
{
SpriteName.Value = textureName;
}
public SkinnableSprite()
: base(new SpriteComponentLookup(string.Empty), ConfineMode.NoScaling)
{
RelativeSizeAxes = Axes.None;
AutoSizeAxes = Axes.Both;
SpriteName.BindValueChanged(name =>
{
((SpriteComponentLookup)ComponentLookup).LookupName = name.NewValue ?? string.Empty;
if (IsLoaded)
SkinChanged(CurrentSkin);
});
}
protected override Drawable CreateDefault(ISkinComponentLookup lookup)
{
var texture = textures.Get(((SpriteComponentLookup)lookup).LookupName);
if (texture == null)
return new SpriteNotFound(((SpriteComponentLookup)lookup).LookupName);
return new Sprite { Texture = texture };
}
public bool UsesFixedAnchor { get; set; }
internal class SpriteComponentLookup : ISkinComponentLookup
{
public string LookupName { get; set; }
public SpriteComponentLookup(string textureName)
{
LookupName = textureName;
}
}
public partial class SpriteSelectorControl : SettingsDropdown<string>
{
protected override void LoadComplete()
{
base.LoadComplete();
// Round-about way of getting the user's skin to find available resources.
// In the future we'll probably want to allow access to resources from the fallbacks, or potentially other skins
// but that requires further thought.
var highestPrioritySkin = getHighestPriorityUserSkin(((SkinnableSprite)SettingSourceObject).source.AllSources) as Skin;
string[]? availableFiles = highestPrioritySkin?.SkinInfo.PerformRead(s => s.Files
.Where(f => f.Filename.EndsWith(".png", StringComparison.Ordinal)
|| f.Filename.EndsWith(".jpg", StringComparison.Ordinal))
.Select(f => f.Filename).Distinct()).ToArray();
if (availableFiles?.Length > 0)
Items = availableFiles;
static ISkin? getHighestPriorityUserSkin(IEnumerable<ISkin> skins)
{
foreach (var skin in skins)
{
if (skin is ISkinTransformer transformer && isUserSkin(transformer.Skin))
return transformer.Skin;
if (isUserSkin(skin))
return skin;
}
return null;
}
// Temporarily used to exclude undesirable ISkin implementations
static bool isUserSkin(ISkin skin)
=> skin.GetType() == typeof(TrianglesSkin)
|| skin.GetType() == typeof(ArgonProSkin)
|| skin.GetType() == typeof(ArgonSkin)
|| skin.GetType() == typeof(DefaultLegacySkin)
|| skin.GetType() == typeof(LegacySkin);
}
}
public partial class SpriteNotFound : CompositeDrawable
{
public SpriteNotFound(string lookup)
{
AutoSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
new SpriteIcon
{
Size = new Vector2(50),
Icon = FontAwesome.Solid.QuestionCircle
},
new OsuSpriteText
{
Position = new Vector2(25, 50),
Text = $"missing: {lookup}",
Origin = Anchor.TopCentre,
}
};
}
}
}
}