mirror of
https://github.com/ppy/osu.git
synced 2024-12-15 00:33:21 +08:00
Merge branch 'master' into migrate-directory-selector
This commit is contained in:
commit
066d0a1ef1
@ -52,7 +52,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.706.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.702.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.707.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Transitive Dependencies">
|
||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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 JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -18,7 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
{
|
||||
Anchor = Anchor.BottomLeft;
|
||||
Origin = Anchor.Centre;
|
||||
Size = new Vector2(2 * CatchHitObject.OBJECT_RADIUS);
|
||||
InternalChild = new BorderPiece();
|
||||
}
|
||||
|
||||
@ -28,10 +28,10 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
Colour = osuColour.Yellow;
|
||||
}
|
||||
|
||||
public void UpdateFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject)
|
||||
public void UpdateFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject, [CanBeNull] CatchHitObject parent = null)
|
||||
{
|
||||
X = hitObject.EffectiveX;
|
||||
Y = hitObjectContainer.PositionAtTime(hitObject.StartTime);
|
||||
X = hitObject.EffectiveX - (parent?.OriginalX ?? 0);
|
||||
Y = hitObjectContainer.PositionAtTime(hitObject.StartTime, parent?.StartTime ?? hitObjectContainer.Time.Current);
|
||||
Scale = new Vector2(hitObject.Scale);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,53 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
{
|
||||
public class NestedOutlineContainer : CompositeDrawable
|
||||
{
|
||||
private readonly List<CatchHitObject> nestedHitObjects = new List<CatchHitObject>();
|
||||
|
||||
public NestedOutlineContainer()
|
||||
{
|
||||
Anchor = Anchor.BottomLeft;
|
||||
}
|
||||
|
||||
public void UpdatePositionFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject parentHitObject)
|
||||
{
|
||||
X = parentHitObject.OriginalX;
|
||||
Y = hitObjectContainer.PositionAtTime(parentHitObject.StartTime);
|
||||
}
|
||||
|
||||
public void UpdateNestedObjectsFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject parentHitObject)
|
||||
{
|
||||
nestedHitObjects.Clear();
|
||||
nestedHitObjects.AddRange(parentHitObject.NestedHitObjects
|
||||
.OfType<CatchHitObject>()
|
||||
.Where(h => !(h is TinyDroplet)));
|
||||
|
||||
while (nestedHitObjects.Count < InternalChildren.Count)
|
||||
RemoveInternal(InternalChildren[^1]);
|
||||
|
||||
while (InternalChildren.Count < nestedHitObjects.Count)
|
||||
AddInternal(new FruitOutline());
|
||||
|
||||
for (int i = 0; i < nestedHitObjects.Count; i++)
|
||||
{
|
||||
var hitObject = nestedHitObjects[i];
|
||||
var outline = (FruitOutline)InternalChildren[i];
|
||||
outline.UpdateFrom(hitObjectContainer, hitObject, parentHitObject);
|
||||
outline.Scale *= hitObject is Droplet ? 0.5f : 1;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false;
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Lines;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
{
|
||||
public class ScrollingPath : CompositeDrawable
|
||||
{
|
||||
private readonly Path drawablePath;
|
||||
|
||||
private readonly List<(double Distance, float X)> vertices = new List<(double, float)>();
|
||||
|
||||
public ScrollingPath()
|
||||
{
|
||||
Anchor = Anchor.BottomLeft;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
drawablePath = new SmoothPath
|
||||
{
|
||||
PathRadius = 2,
|
||||
Alpha = 0.5f
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public void UpdatePositionFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject)
|
||||
{
|
||||
X = hitObject.OriginalX;
|
||||
Y = hitObjectContainer.PositionAtTime(hitObject.StartTime);
|
||||
}
|
||||
|
||||
public void UpdatePathFrom(ScrollingHitObjectContainer hitObjectContainer, JuiceStream hitObject)
|
||||
{
|
||||
double distanceToYFactor = -hitObjectContainer.LengthAtTime(hitObject.StartTime, hitObject.StartTime + 1 / hitObject.Velocity);
|
||||
|
||||
computeDistanceXs(hitObject);
|
||||
drawablePath.Vertices = vertices
|
||||
.Select(v => new Vector2(v.X, (float)(v.Distance * distanceToYFactor)))
|
||||
.ToArray();
|
||||
drawablePath.OriginPosition = drawablePath.PositionInBoundingBox(Vector2.Zero);
|
||||
}
|
||||
|
||||
private void computeDistanceXs(JuiceStream hitObject)
|
||||
{
|
||||
vertices.Clear();
|
||||
|
||||
var sliderVertices = new List<Vector2>();
|
||||
hitObject.Path.GetPathToProgress(sliderVertices, 0, 1);
|
||||
|
||||
if (sliderVertices.Count == 0)
|
||||
return;
|
||||
|
||||
double distance = 0;
|
||||
Vector2 lastPosition = Vector2.Zero;
|
||||
|
||||
for (int repeat = 0; repeat < hitObject.RepeatCount + 1; repeat++)
|
||||
{
|
||||
foreach (var position in sliderVertices)
|
||||
{
|
||||
distance += Vector2.Distance(lastPosition, position);
|
||||
lastPosition = position;
|
||||
|
||||
vertices.Add((distance, position.X));
|
||||
}
|
||||
|
||||
sliderVertices.Reverse();
|
||||
}
|
||||
}
|
||||
|
||||
// Because this has 0x0 size, the contents are otherwise masked away if the start position is outside the screen.
|
||||
protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false;
|
||||
}
|
||||
}
|
@ -3,7 +3,10 @@
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osuTK;
|
||||
@ -17,9 +20,20 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||
private float minNestedX;
|
||||
private float maxNestedX;
|
||||
|
||||
private readonly ScrollingPath scrollingPath;
|
||||
|
||||
private readonly NestedOutlineContainer nestedOutlineContainer;
|
||||
|
||||
private readonly Cached pathCache = new Cached();
|
||||
|
||||
public JuiceStreamSelectionBlueprint(JuiceStream hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
scrollingPath = new ScrollingPath(),
|
||||
nestedOutlineContainer = new NestedOutlineContainer()
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -29,7 +43,28 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||
computeObjectBounds();
|
||||
}
|
||||
|
||||
private void onDefaultsApplied(HitObject _) => computeObjectBounds();
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!IsSelected) return;
|
||||
|
||||
scrollingPath.UpdatePositionFrom(HitObjectContainer, HitObject);
|
||||
nestedOutlineContainer.UpdatePositionFrom(HitObjectContainer, HitObject);
|
||||
|
||||
if (pathCache.IsValid) return;
|
||||
|
||||
scrollingPath.UpdatePathFrom(HitObjectContainer, HitObject);
|
||||
nestedOutlineContainer.UpdateNestedObjectsFrom(HitObjectContainer, HitObject);
|
||||
|
||||
pathCache.Validate();
|
||||
}
|
||||
|
||||
private void onDefaultsApplied(HitObject _)
|
||||
{
|
||||
computeObjectBounds();
|
||||
pathCache.Invalidate();
|
||||
}
|
||||
|
||||
private void computeObjectBounds()
|
||||
{
|
||||
|
92
osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs
Normal file
92
osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs
Normal file
@ -0,0 +1,92 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.OpenGL.Textures;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Tests.Skins
|
||||
{
|
||||
public class TestSceneSkinProvidingContainer : OsuTestScene
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures that the first inserted skin after resetting (via source change)
|
||||
/// is always prioritised over others when providing the same resource.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPriorityPreservation()
|
||||
{
|
||||
TestSkinProvidingContainer provider = null;
|
||||
TestSkin mostPrioritisedSource = null;
|
||||
|
||||
AddStep("setup sources", () =>
|
||||
{
|
||||
var sources = new List<TestSkin>();
|
||||
for (int i = 0; i < 10; i++)
|
||||
sources.Add(new TestSkin());
|
||||
|
||||
mostPrioritisedSource = sources.First();
|
||||
|
||||
Child = provider = new TestSkinProvidingContainer(sources);
|
||||
});
|
||||
|
||||
AddAssert("texture provided by expected skin", () =>
|
||||
{
|
||||
return provider.FindProvider(s => s.GetTexture(TestSkin.TEXTURE_NAME) != null) == mostPrioritisedSource;
|
||||
});
|
||||
|
||||
AddStep("trigger source change", () => provider.TriggerSourceChanged());
|
||||
|
||||
AddAssert("texture still provided by expected skin", () =>
|
||||
{
|
||||
return provider.FindProvider(s => s.GetTexture(TestSkin.TEXTURE_NAME) != null) == mostPrioritisedSource;
|
||||
});
|
||||
}
|
||||
|
||||
private class TestSkinProvidingContainer : SkinProvidingContainer
|
||||
{
|
||||
private readonly IEnumerable<ISkin> sources;
|
||||
|
||||
public TestSkinProvidingContainer(IEnumerable<ISkin> sources)
|
||||
{
|
||||
this.sources = sources;
|
||||
}
|
||||
|
||||
public new void TriggerSourceChanged() => base.TriggerSourceChanged();
|
||||
|
||||
protected override void OnSourceChanged()
|
||||
{
|
||||
ResetSources();
|
||||
sources.ForEach(AddSource);
|
||||
}
|
||||
}
|
||||
|
||||
private class TestSkin : ISkin
|
||||
{
|
||||
public const string TEXTURE_NAME = "virtual-texture";
|
||||
|
||||
public Drawable GetDrawableComponent(ISkinComponent component) => throw new System.NotImplementedException();
|
||||
|
||||
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
|
||||
{
|
||||
if (componentName == TEXTURE_NAME)
|
||||
return Texture.WhitePixel;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ISample GetSample(ISampleInfo sampleInfo) => throw new System.NotImplementedException();
|
||||
|
||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -4,14 +4,18 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Online.Solo;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
@ -25,6 +29,15 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
protected override bool HasCustomSteps => true;
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
var beatmap = (TestBeatmap)base.CreateBeatmap(ruleset);
|
||||
|
||||
beatmap.HitObjects = beatmap.HitObjects.Take(10).ToList();
|
||||
|
||||
return beatmap;
|
||||
}
|
||||
|
||||
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false);
|
||||
|
||||
[Test]
|
||||
|
@ -168,7 +168,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
public void Disable()
|
||||
{
|
||||
allow = false;
|
||||
OnSourceChanged();
|
||||
TriggerSourceChanged();
|
||||
}
|
||||
|
||||
public SwitchableSkinProvidingContainer(ISkin skin)
|
||||
|
@ -83,9 +83,9 @@ namespace osu.Game.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
beatmapSkins.BindValueChanged(_ => OnSourceChanged());
|
||||
beatmapColours.BindValueChanged(_ => OnSourceChanged());
|
||||
beatmapHitsounds.BindValueChanged(_ => OnSourceChanged());
|
||||
beatmapSkins.BindValueChanged(_ => TriggerSourceChanged());
|
||||
beatmapColours.BindValueChanged(_ => TriggerSourceChanged());
|
||||
beatmapHitsounds.BindValueChanged(_ => TriggerSourceChanged());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
@ -46,57 +48,51 @@ namespace osu.Game.Skinning
|
||||
};
|
||||
}
|
||||
|
||||
private ISkinSource parentSource;
|
||||
|
||||
private ResourceStoreBackedSkin rulesetResourcesSkin;
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
{
|
||||
parentSource = parent.Get<ISkinSource>();
|
||||
parentSource.SourceChanged += OnSourceChanged;
|
||||
|
||||
if (Ruleset.CreateResourceStore() is IResourceStore<byte[]> resources)
|
||||
rulesetResourcesSkin = new ResourceStoreBackedSkin(resources, parent.Get<GameHost>(), parent.Get<AudioManager>());
|
||||
|
||||
// ensure sources are populated and ready for use before childrens' asynchronous load flow.
|
||||
UpdateSkinSources();
|
||||
|
||||
return base.CreateChildDependencies(parent);
|
||||
}
|
||||
|
||||
protected override void OnSourceChanged()
|
||||
{
|
||||
UpdateSkinSources();
|
||||
base.OnSourceChanged();
|
||||
}
|
||||
ResetSources();
|
||||
|
||||
protected virtual void UpdateSkinSources()
|
||||
{
|
||||
SkinSources.Clear();
|
||||
// Populate a local list first so we can adjust the returned order as we go.
|
||||
var sources = new List<ISkin>();
|
||||
|
||||
foreach (var skin in parentSource.AllSources)
|
||||
Debug.Assert(ParentSource != null);
|
||||
|
||||
foreach (var skin in ParentSource.AllSources)
|
||||
{
|
||||
switch (skin)
|
||||
{
|
||||
case LegacySkin legacySkin:
|
||||
SkinSources.Add(GetLegacyRulesetTransformedSkin(legacySkin));
|
||||
sources.Add(GetLegacyRulesetTransformedSkin(legacySkin));
|
||||
break;
|
||||
|
||||
default:
|
||||
SkinSources.Add(skin);
|
||||
sources.Add(skin);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int lastDefaultSkinIndex = SkinSources.IndexOf(SkinSources.OfType<DefaultSkin>().LastOrDefault());
|
||||
int lastDefaultSkinIndex = sources.IndexOf(sources.OfType<DefaultSkin>().LastOrDefault());
|
||||
|
||||
// Ruleset resources should be given the ability to override game-wide defaults
|
||||
// This is achieved by placing them before the last instance of DefaultSkin.
|
||||
// Note that DefaultSkin may not be present in some test scenes.
|
||||
if (lastDefaultSkinIndex >= 0)
|
||||
SkinSources.Insert(lastDefaultSkinIndex, rulesetResourcesSkin);
|
||||
sources.Insert(lastDefaultSkinIndex, rulesetResourcesSkin);
|
||||
else
|
||||
SkinSources.Add(rulesetResourcesSkin);
|
||||
sources.Add(rulesetResourcesSkin);
|
||||
|
||||
foreach (var skin in sources)
|
||||
AddSource(skin);
|
||||
}
|
||||
|
||||
protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin)
|
||||
@ -115,9 +111,6 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (parentSource != null)
|
||||
parentSource.SourceChanged -= OnSourceChanged;
|
||||
|
||||
rulesetResourcesSkin?.Dispose();
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Sample;
|
||||
@ -24,19 +22,8 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
public event Action SourceChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Skins which should be exposed by this container, in order of lookup precedence.
|
||||
/// </summary>
|
||||
protected readonly BindableList<ISkin> SkinSources = new BindableList<ISkin>();
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary mapping each <see cref="ISkin"/> from the <see cref="SkinSources"/>
|
||||
/// to one that performs the "allow lookup" checks before proceeding with a lookup.
|
||||
/// </summary>
|
||||
private readonly Dictionary<ISkin, DisableableSkinSource> disableableSkinSources = new Dictionary<ISkin, DisableableSkinSource>();
|
||||
|
||||
[CanBeNull]
|
||||
private ISkinSource fallbackSource;
|
||||
protected ISkinSource ParentSource { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether falling back to parent <see cref="ISkinSource"/>s is allowed in this container.
|
||||
@ -53,6 +40,11 @@ namespace osu.Game.Skinning
|
||||
|
||||
protected virtual bool AllowColourLookup => true;
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary mapping each <see cref="ISkin"/> source to a wrapper which handles lookup allowances.
|
||||
/// </summary>
|
||||
private readonly List<(ISkin skin, DisableableSkinSource wrapped)> skinSources = new List<(ISkin, DisableableSkinSource)>();
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new <see cref="SkinProvidingContainer"/> initialised with a single skin source.
|
||||
/// </summary>
|
||||
@ -60,87 +52,56 @@ namespace osu.Game.Skinning
|
||||
: this()
|
||||
{
|
||||
if (skin != null)
|
||||
SkinSources.Add(skin);
|
||||
AddSource(skin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new <see cref="SkinProvidingContainer"/> with no sources.
|
||||
/// Implementations can add or change sources through the <see cref="SkinSources"/> list.
|
||||
/// </summary>
|
||||
protected SkinProvidingContainer()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
SkinSources.BindCollectionChanged(((_, args) =>
|
||||
{
|
||||
switch (args.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
foreach (var skin in args.NewItems.Cast<ISkin>())
|
||||
{
|
||||
disableableSkinSources.Add(skin, new DisableableSkinSource(skin, this));
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
{
|
||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
|
||||
if (skin is ISkinSource source)
|
||||
source.SourceChanged += OnSourceChanged;
|
||||
}
|
||||
ParentSource = dependencies.Get<ISkinSource>();
|
||||
if (ParentSource != null)
|
||||
ParentSource.SourceChanged += TriggerSourceChanged;
|
||||
|
||||
break;
|
||||
dependencies.CacheAs<ISkinSource>(this);
|
||||
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
foreach (var skin in args.OldItems.Cast<ISkin>())
|
||||
{
|
||||
disableableSkinSources.Remove(skin);
|
||||
TriggerSourceChanged();
|
||||
|
||||
if (skin is ISkinSource source)
|
||||
source.SourceChanged -= OnSourceChanged;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
foreach (var skin in args.OldItems.Cast<ISkin>())
|
||||
{
|
||||
disableableSkinSources.Remove(skin);
|
||||
|
||||
if (skin is ISkinSource source)
|
||||
source.SourceChanged -= OnSourceChanged;
|
||||
}
|
||||
|
||||
foreach (var skin in args.NewItems.Cast<ISkin>())
|
||||
{
|
||||
disableableSkinSources.Add(skin, new DisableableSkinSource(skin, this));
|
||||
|
||||
if (skin is ISkinSource source)
|
||||
source.SourceChanged += OnSourceChanged;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}), true);
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
public ISkin FindProvider(Func<ISkin, bool> lookupFunction)
|
||||
{
|
||||
foreach (var skin in SkinSources)
|
||||
foreach (var (skin, lookupWrapper) in skinSources)
|
||||
{
|
||||
if (lookupFunction(disableableSkinSources[skin]))
|
||||
if (lookupFunction(lookupWrapper))
|
||||
return skin;
|
||||
}
|
||||
|
||||
return fallbackSource?.FindProvider(lookupFunction);
|
||||
if (!AllowFallingBackToParent)
|
||||
return null;
|
||||
|
||||
return ParentSource?.FindProvider(lookupFunction);
|
||||
}
|
||||
|
||||
public IEnumerable<ISkin> AllSources
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var skin in SkinSources)
|
||||
yield return skin;
|
||||
foreach (var i in skinSources)
|
||||
yield return i.skin;
|
||||
|
||||
if (fallbackSource != null)
|
||||
if (AllowFallingBackToParent && ParentSource != null)
|
||||
{
|
||||
foreach (var skin in fallbackSource.AllSources)
|
||||
foreach (var skin in ParentSource.AllSources)
|
||||
yield return skin;
|
||||
}
|
||||
}
|
||||
@ -148,68 +109,110 @@ namespace osu.Game.Skinning
|
||||
|
||||
public Drawable GetDrawableComponent(ISkinComponent component)
|
||||
{
|
||||
foreach (var skin in SkinSources)
|
||||
foreach (var (_, lookupWrapper) in skinSources)
|
||||
{
|
||||
Drawable sourceDrawable;
|
||||
if ((sourceDrawable = disableableSkinSources[skin]?.GetDrawableComponent(component)) != null)
|
||||
if ((sourceDrawable = lookupWrapper.GetDrawableComponent(component)) != null)
|
||||
return sourceDrawable;
|
||||
}
|
||||
|
||||
return fallbackSource?.GetDrawableComponent(component);
|
||||
if (!AllowFallingBackToParent)
|
||||
return null;
|
||||
|
||||
return ParentSource?.GetDrawableComponent(component);
|
||||
}
|
||||
|
||||
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
|
||||
{
|
||||
foreach (var skin in SkinSources)
|
||||
foreach (var (_, lookupWrapper) in skinSources)
|
||||
{
|
||||
Texture sourceTexture;
|
||||
if ((sourceTexture = disableableSkinSources[skin]?.GetTexture(componentName, wrapModeS, wrapModeT)) != null)
|
||||
if ((sourceTexture = lookupWrapper.GetTexture(componentName, wrapModeS, wrapModeT)) != null)
|
||||
return sourceTexture;
|
||||
}
|
||||
|
||||
return fallbackSource?.GetTexture(componentName, wrapModeS, wrapModeT);
|
||||
if (!AllowFallingBackToParent)
|
||||
return null;
|
||||
|
||||
return ParentSource?.GetTexture(componentName, wrapModeS, wrapModeT);
|
||||
}
|
||||
|
||||
public ISample GetSample(ISampleInfo sampleInfo)
|
||||
{
|
||||
foreach (var skin in SkinSources)
|
||||
foreach (var (_, lookupWrapper) in skinSources)
|
||||
{
|
||||
ISample sourceSample;
|
||||
if ((sourceSample = disableableSkinSources[skin]?.GetSample(sampleInfo)) != null)
|
||||
if ((sourceSample = lookupWrapper.GetSample(sampleInfo)) != null)
|
||||
return sourceSample;
|
||||
}
|
||||
|
||||
return fallbackSource?.GetSample(sampleInfo);
|
||||
if (!AllowFallingBackToParent)
|
||||
return null;
|
||||
|
||||
return ParentSource?.GetSample(sampleInfo);
|
||||
}
|
||||
|
||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||
{
|
||||
foreach (var skin in SkinSources)
|
||||
foreach (var (_, lookupWrapper) in skinSources)
|
||||
{
|
||||
IBindable<TValue> bindable;
|
||||
if ((bindable = disableableSkinSources[skin]?.GetConfig<TLookup, TValue>(lookup)) != null)
|
||||
if ((bindable = lookupWrapper.GetConfig<TLookup, TValue>(lookup)) != null)
|
||||
return bindable;
|
||||
}
|
||||
|
||||
return fallbackSource?.GetConfig<TLookup, TValue>(lookup);
|
||||
if (!AllowFallingBackToParent)
|
||||
return null;
|
||||
|
||||
return ParentSource?.GetConfig<TLookup, TValue>(lookup);
|
||||
}
|
||||
|
||||
protected virtual void OnSourceChanged() => SourceChanged?.Invoke();
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
/// <summary>
|
||||
/// Add a new skin to this provider. Will be added to the end of the lookup order precedence.
|
||||
/// </summary>
|
||||
/// <param name="skin">The skin to add.</param>
|
||||
protected void AddSource(ISkin skin)
|
||||
{
|
||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
skinSources.Add((skin, new DisableableSkinSource(skin, this)));
|
||||
|
||||
if (AllowFallingBackToParent)
|
||||
{
|
||||
fallbackSource = dependencies.Get<ISkinSource>();
|
||||
if (fallbackSource != null)
|
||||
fallbackSource.SourceChanged += OnSourceChanged;
|
||||
}
|
||||
if (skin is ISkinSource source)
|
||||
source.SourceChanged += TriggerSourceChanged;
|
||||
}
|
||||
|
||||
dependencies.CacheAs<ISkinSource>(this);
|
||||
/// <summary>
|
||||
/// Remove a skin from this provider.
|
||||
/// </summary>
|
||||
/// <param name="skin">The skin to remove.</param>
|
||||
protected void RemoveSource(ISkin skin)
|
||||
{
|
||||
if (skinSources.RemoveAll(s => s.skin == skin) == 0)
|
||||
return;
|
||||
|
||||
return dependencies;
|
||||
if (skin is ISkinSource source)
|
||||
source.SourceChanged -= TriggerSourceChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all skin sources.
|
||||
/// </summary>
|
||||
protected void ResetSources()
|
||||
{
|
||||
foreach (var i in skinSources.ToArray())
|
||||
RemoveSource(i.skin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when any source has changed (either <see cref="ParentSource"/> or a source registered via <see cref="AddSource"/>).
|
||||
/// This is also invoked once initially during <see cref="CreateChildDependencies"/> to ensure sources are ready for children consumption.
|
||||
/// </summary>
|
||||
protected virtual void OnSourceChanged() { }
|
||||
|
||||
protected void TriggerSourceChanged()
|
||||
{
|
||||
// Expose to implementations, giving them a chance to react before notifying external consumers.
|
||||
OnSourceChanged();
|
||||
|
||||
SourceChanged?.Invoke();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
@ -219,11 +222,14 @@ namespace osu.Game.Skinning
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (fallbackSource != null)
|
||||
fallbackSource.SourceChanged -= OnSourceChanged;
|
||||
if (ParentSource != null)
|
||||
ParentSource.SourceChanged -= TriggerSourceChanged;
|
||||
|
||||
foreach (var source in SkinSources.OfType<ISkinSource>())
|
||||
source.SourceChanged -= OnSourceChanged;
|
||||
foreach (var i in skinSources)
|
||||
{
|
||||
if (i.skin is ISkinSource source)
|
||||
source.SourceChanged -= TriggerSourceChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private class DisableableSkinSource : ISkin
|
||||
|
@ -36,7 +36,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="10.2.1" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2021.702.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2021.707.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.706.0" />
|
||||
<PackageReference Include="Sentry" Version="3.6.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.28.3" />
|
||||
|
@ -70,7 +70,7 @@
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.702.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.707.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.706.0" />
|
||||
</ItemGroup>
|
||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
||||
@ -93,7 +93,7 @@
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2021.702.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2021.707.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.28.3" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||
|
Loading…
Reference in New Issue
Block a user