// Copyright (c) ppy Pty Ltd . 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.Text.RegularExpressions; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics.Sprites; using osu.Game.IO; using osu.Game.Rulesets; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; namespace osu.Game.Tests.Visual { public abstract class SkinnableTestScene : OsuGridTestScene, IStorageResourceProvider { private Skin metricsSkin; private Skin defaultSkin; private Skin specialSkin; private Skin oldSkin; [Resolved] private GameHost host { get; set; } protected SkinnableTestScene() : base(2, 3) { } [BackgroundDependencyLoader] private void load() { var dllStore = new DllResourceStore(GetType().Assembly); metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), this, true); defaultSkin = new DefaultLegacySkin(this); specialSkin = new TestLegacySkin(new SkinInfo { Name = "special-skin" }, new NamespacedResourceStore(dllStore, "Resources/special_skin"), this, true); oldSkin = new TestLegacySkin(new SkinInfo { Name = "old-skin" }, new NamespacedResourceStore(dllStore, "Resources/old_skin"), this, true); } private readonly List createdDrawables = new List(); protected void SetContents(Func creationFunction) { createdDrawables.Clear(); var beatmap = CreateBeatmapForSkinProvider(); Cell(0).Child = createProvider(null, creationFunction, beatmap); Cell(1).Child = createProvider(metricsSkin, creationFunction, beatmap); Cell(2).Child = createProvider(defaultSkin, creationFunction, beatmap); Cell(3).Child = createProvider(specialSkin, creationFunction, beatmap); Cell(4).Child = createProvider(oldSkin, creationFunction, beatmap); } protected IEnumerable CreatedDrawables => createdDrawables; private Drawable createProvider(Skin skin, Func creationFunction, IBeatmap beatmap) { var created = creationFunction(skin); createdDrawables.Add(created); SkinProvidingContainer mainProvider; Container childContainer; OutlineBox outlineBox; SkinProvidingContainer skinProvider; var children = new Container { RelativeSizeAxes = Axes.Both, BorderColour = Color4.White, BorderThickness = 5, Masking = true, Children = new Drawable[] { new Box { AlwaysPresent = true, Alpha = 0, RelativeSizeAxes = Axes.Both, }, new OsuSpriteText { Text = skin?.SkinInfo?.Value.Name ?? "none", Scale = new Vector2(1.5f), Padding = new MarginPadding(5), }, childContainer = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, Children = new Drawable[] { outlineBox = new OutlineBox(), (mainProvider = new SkinProvidingContainer(skin)).WithChild( skinProvider = new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider, beatmap)) { Child = created, } ) } }, } }; // run this once initially to bring things into a sane state as early as possible. updateSizing(); // run this once after construction to handle the case the changes are made in a BDL/LoadComplete call. Schedule(updateSizing); return children; void updateSizing() { bool autoSize = created.RelativeSizeAxes == Axes.None; foreach (var c in new[] { mainProvider, childContainer, skinProvider }) { c.RelativeSizeAxes = Axes.None; c.AutoSizeAxes = Axes.None; c.RelativeSizeAxes = !autoSize ? Axes.Both : Axes.None; c.AutoSizeAxes = autoSize ? Axes.Both : Axes.None; } outlineBox.Alpha = autoSize ? 1 : 0; } } /// /// Creates the ruleset for adding the corresponding skin transforming component. /// [NotNull] protected abstract Ruleset CreateRulesetForSkinProvider(); protected sealed override Ruleset CreateRuleset() => CreateRulesetForSkinProvider(); protected virtual IBeatmap CreateBeatmapForSkinProvider() => CreateWorkingBeatmap(Ruleset.Value).GetPlayableBeatmap(Ruleset.Value); #region IResourceStorageProvider public AudioManager AudioManager => Audio; public IResourceStore Files => null; public new IResourceStore Resources => base.Resources; public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => host.CreateTextureLoaderStore(underlyingStore); RealmAccess IStorageResourceProvider.RealmAccess => null; #endregion private class OutlineBox : CompositeDrawable { public OutlineBox() { BorderColour = Color4.IndianRed; BorderThickness = 5; Masking = true; RelativeSizeAxes = Axes.Both; InternalChild = new Box { RelativeSizeAxes = Axes.Both, Alpha = 0, Colour = Color4.Brown, AlwaysPresent = true }; } } private class TestLegacySkin : LegacySkin { private readonly bool extrapolateAnimations; public TestLegacySkin(SkinInfo skin, IResourceStore storage, IStorageResourceProvider resources, bool extrapolateAnimations) : base(skin, storage, resources, "skin.ini") { this.extrapolateAnimations = extrapolateAnimations; } public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) { var lookup = base.GetTexture(componentName, wrapModeS, wrapModeT); if (lookup != null) return lookup; // extrapolate frames to test longer animations if (extrapolateAnimations) { var match = Regex.Match(componentName, "-([0-9]*)"); if (match.Length > 0 && int.TryParse(match.Groups[1].Value, out int number) && number < 60) return base.GetTexture(componentName.Replace($"-{number}", $"-{number % 2}"), wrapModeS, wrapModeT); } return null; } } } }