1
0
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:
Dan Balasescu 2021-07-07 15:57:34 +09:00 committed by GitHub
commit 066d0a1ef1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 405 additions and 130 deletions

View File

@ -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. -->

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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()
{

View 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();
}
}
}

View File

@ -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]

View File

@ -168,7 +168,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void Disable()
{
allow = false;
OnSourceChanged();
TriggerSourceChanged();
}
public SwitchableSkinProvidingContainer(ISkin skin)

View File

@ -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());
}
}
}

View File

@ -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();
}
}

View File

@ -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

View File

@ -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" />

View File

@ -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" />