Merge branch 'master' into locale/skin-editor-components

This commit is contained in:
Dean Herbert
2023-02-03 15:24:00 +09:00
45 changed files with 593 additions and 213 deletions

View File

@ -0,0 +1,63 @@
// 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.IO;
using System.Threading.Tasks;
using Android.Content;
using Android.Net;
using Android.Provider;
using osu.Game.Database;
namespace osu.Android
{
public class AndroidImportTask : ImportTask
{
private readonly ContentResolver contentResolver;
private readonly Uri uri;
private AndroidImportTask(Stream stream, string filename, ContentResolver contentResolver, Uri uri)
: base(stream, filename)
{
this.contentResolver = contentResolver;
this.uri = uri;
}
public override void DeleteFile()
{
contentResolver.Delete(uri, null, null);
}
public static async Task<AndroidImportTask?> Create(ContentResolver contentResolver, Uri uri)
{
// there are more performant overloads of this method, but this one is the most backwards-compatible
// (dates back to API 1).
var cursor = contentResolver.Query(uri, null, null, null, null);
if (cursor == null)
return null;
if (!cursor.MoveToFirst())
return null;
int filenameColumn = cursor.GetColumnIndex(IOpenableColumns.DisplayName);
string filename = cursor.GetString(filenameColumn) ?? uri.Path ?? string.Empty;
// SharpCompress requires archive streams to be seekable, which the stream opened by
// OpenInputStream() seems to not necessarily be.
// copy to an arbitrary-access memory stream to be able to proceed with the import.
var copy = new MemoryStream();
using (var stream = contentResolver.OpenInputStream(uri))
{
if (stream == null)
return null;
await stream.CopyToAsync(copy).ConfigureAwait(false);
}
return new AndroidImportTask(copy, filename, contentResolver, uri);
}
}
}

View File

@ -5,7 +5,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -14,7 +13,6 @@ using Android.Content;
using Android.Content.PM; using Android.Content.PM;
using Android.Graphics; using Android.Graphics;
using Android.OS; using Android.OS;
using Android.Provider;
using Android.Views; using Android.Views;
using osu.Framework.Android; using osu.Framework.Android;
using osu.Game.Database; using osu.Game.Database;
@ -131,28 +129,14 @@ namespace osu.Android
await Task.WhenAll(uris.Select(async uri => await Task.WhenAll(uris.Select(async uri =>
{ {
// there are more performant overloads of this method, but this one is the most backwards-compatible var task = await AndroidImportTask.Create(ContentResolver!, uri).ConfigureAwait(false);
// (dates back to API 1).
var cursor = ContentResolver?.Query(uri, null, null, null, null);
if (cursor == null) if (task != null)
return;
cursor.MoveToFirst();
int filenameColumn = cursor.GetColumnIndex(IOpenableColumns.DisplayName);
string filename = cursor.GetString(filenameColumn);
// SharpCompress requires archive streams to be seekable, which the stream opened by
// OpenInputStream() seems to not necessarily be.
// copy to an arbitrary-access memory stream to be able to proceed with the import.
var copy = new MemoryStream();
using (var stream = ContentResolver.OpenInputStream(uri))
await stream.CopyToAsync(copy).ConfigureAwait(false);
lock (tasks)
{ {
tasks.Add(new ImportTask(copy, filename)); lock (tasks)
{
tasks.Add(task);
}
} }
})).ConfigureAwait(false); })).ConfigureAwait(false);

View File

@ -376,7 +376,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
protected override void OnFree() protected override void OnFree()
{ {
slidingSample.Samples = null; slidingSample.ClearSamples();
base.OnFree(); base.OnFree();
} }
} }

View File

@ -5,11 +5,11 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Skinning.Argon namespace osu.Game.Rulesets.Mania.Skinning.Argon
@ -19,8 +19,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>(); private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private readonly IBindable<Color4> accentColour = new Bindable<Color4>(); private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
private readonly Box colouredBox; private readonly Box shadeBackground;
private readonly Box shadow; private readonly Box shadeForeground;
public ArgonHoldNoteTailPiece() public ArgonHoldNoteTailPiece()
{ {
@ -32,32 +32,25 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
shadow = new Box shadeBackground = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
new Container new Container
{ {
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Height = 0.82f, Height = ArgonNotePiece.NOTE_ACCENT_RATIO,
Masking = true, Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
CornerRadius = ArgonNotePiece.CORNER_RADIUS, CornerRadius = ArgonNotePiece.CORNER_RADIUS,
Masking = true,
Children = new Drawable[] Children = new Drawable[]
{ {
colouredBox = new Box shadeForeground = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
} },
} },
},
new Circle
{
RelativeSizeAxes = Axes.X,
Height = ArgonNotePiece.CORNER_RADIUS * 2,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
}, },
}; };
} }
@ -77,19 +70,13 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction) private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
{ {
colouredBox.Anchor = colouredBox.Origin = direction.NewValue == ScrollingDirection.Up Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Up ? -1 : 1);
? Anchor.TopCentre
: Anchor.BottomCentre;
} }
private void onAccentChanged(ValueChangedEvent<Color4> accent) private void onAccentChanged(ValueChangedEvent<Color4> accent)
{ {
colouredBox.Colour = ColourInfo.GradientVertical( shadeBackground.Colour = accent.NewValue.Darken(1.7f);
accent.NewValue, shadeForeground.Colour = accent.NewValue.Darken(1.1f);
accent.NewValue.Darken(0.1f)
);
shadow.Colour = accent.NewValue.Darken(0.5f);
} }
} }
} }

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
internal partial class ArgonNotePiece : CompositeDrawable internal partial class ArgonNotePiece : CompositeDrawable
{ {
public const float NOTE_HEIGHT = 42; public const float NOTE_HEIGHT = 42;
public const float NOTE_ACCENT_RATIO = 0.82f;
public const float CORNER_RADIUS = 3.4f; public const float CORNER_RADIUS = 3.4f;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>(); private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Height = 0.82f, Height = NOTE_ACCENT_RATIO,
Masking = true, Masking = true,
CornerRadius = CORNER_RADIUS, CornerRadius = CORNER_RADIUS,
Children = new Drawable[] Children = new Drawable[]
@ -95,6 +95,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
colouredBox.Anchor = colouredBox.Origin = direction.NewValue == ScrollingDirection.Up colouredBox.Anchor = colouredBox.Origin = direction.NewValue == ScrollingDirection.Up
? Anchor.TopCentre ? Anchor.TopCentre
: Anchor.BottomCentre; : Anchor.BottomCentre;
Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Up ? -1 : 1);
} }
private void onAccentChanged(ValueChangedEvent<Color4> accent) private void onAccentChanged(ValueChangedEvent<Color4> accent)

View File

@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
PathVersion.UnbindFrom(HitObject.Path.Version); PathVersion.UnbindFrom(HitObject.Path.Version);
slidingSample.Samples = null; slidingSample?.ClearSamples();
} }
protected override void LoadSamples() protected override void LoadSamples()

View File

@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
base.OnFree(); base.OnFree();
spinningSample.Samples = null; spinningSample.ClearSamples();
} }
protected override void LoadSamples() protected override void LoadSamples()

View File

@ -0,0 +1,88 @@
// 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.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Checks;
using osu.Game.Rulesets.Objects;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Editing.Checks
{
public class CheckPreviewTimeTest
{
private CheckPreviewTime check = null!;
private IBeatmap beatmap = null!;
[SetUp]
public void Setup()
{
check = new CheckPreviewTime();
}
[Test]
public void TestPreviewTimeNotSet()
{
setNoPreviewTimeBeatmap();
var content = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
var issues = check.Run(content).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckPreviewTime.IssueTemplateHasNoPreviewTime);
}
[Test]
public void TestPreviewTimeconflict()
{
setPreviewTimeConflictBeatmap();
var content = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
var issues = check.Run(content).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckPreviewTime.IssueTemplatePreviewTimeConflict);
Assert.That(issues.Single().Arguments.FirstOrDefault()?.ToString() == "Test1");
}
private void setNoPreviewTimeBeatmap()
{
beatmap = new Beatmap<HitObject>
{
BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata { PreviewTime = -1 },
}
};
}
private void setPreviewTimeConflictBeatmap()
{
beatmap = new Beatmap<HitObject>
{
BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata { PreviewTime = 10 },
BeatmapSet = new BeatmapSetInfo(new List<BeatmapInfo>
{
new BeatmapInfo
{
DifficultyName = "Test1",
Metadata = new BeatmapMetadata { PreviewTime = 5 },
},
new BeatmapInfo
{
DifficultyName = "Test2",
Metadata = new BeatmapMetadata { PreviewTime = 10 },
},
})
}
};
}
}
}

View File

@ -8,12 +8,14 @@ using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Backgrounds;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Tests.Visual.Background namespace osu.Game.Tests.Visual.Background
{ {
public partial class TestSceneTrianglesV2Background : OsuTestScene public partial class TestSceneTrianglesV2Background : OsuTestScene
{ {
private readonly TrianglesV2 triangles; private readonly TrianglesV2 triangles;
private readonly TrianglesV2 maskedTriangles;
private readonly Box box; private readonly Box box;
public TestSceneTrianglesV2Background() public TestSceneTrianglesV2Background()
@ -31,12 +33,20 @@ namespace osu.Game.Tests.Visual.Background
Origin = Anchor.Centre, Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5), Spacing = new Vector2(0, 10),
Children = new Drawable[] Children = new Drawable[]
{ {
new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "Masked"
},
new Container new Container
{ {
Size = new Vector2(500, 100), Size = new Vector2(500, 100),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Masking = true, Masking = true,
CornerRadius = 40, CornerRadius = 40,
Children = new Drawable[] Children = new Drawable[]
@ -54,9 +64,43 @@ namespace osu.Game.Tests.Visual.Background
} }
} }
}, },
new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "Non-masked"
},
new Container new Container
{ {
Size = new Vector2(500, 100), Size = new Vector2(500, 100),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Red
},
maskedTriangles = new TrianglesV2
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both
}
}
},
new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "Gradient comparison box"
},
new Container
{
Size = new Vector2(500, 100),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Masking = true, Masking = true,
CornerRadius = 40, CornerRadius = 40,
Child = box = new Box Child = box = new Box
@ -75,14 +119,16 @@ namespace osu.Game.Tests.Visual.Background
AddSliderStep("Spawn ratio", 0f, 10f, 1f, s => AddSliderStep("Spawn ratio", 0f, 10f, 1f, s =>
{ {
triangles.SpawnRatio = s; triangles.SpawnRatio = maskedTriangles.SpawnRatio = s;
triangles.Reset(1234); triangles.Reset(1234);
maskedTriangles.Reset(1234);
}); });
AddSliderStep("Thickness", 0f, 1f, 0.02f, t => triangles.Thickness = t); AddSliderStep("Thickness", 0f, 1f, 0.02f, t => triangles.Thickness = maskedTriangles.Thickness = t);
AddStep("White colour", () => box.Colour = triangles.Colour = Color4.White); AddStep("White colour", () => box.Colour = triangles.Colour = maskedTriangles.Colour = Color4.White);
AddStep("Vertical gradient", () => box.Colour = triangles.Colour = ColourInfo.GradientVertical(Color4.White, Color4.Red)); AddStep("Vertical gradient", () => box.Colour = triangles.Colour = maskedTriangles.Colour = ColourInfo.GradientVertical(Color4.White, Color4.Red));
AddStep("Horizontal gradient", () => box.Colour = triangles.Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.Red)); AddStep("Horizontal gradient", () => box.Colour = triangles.Colour = maskedTriangles.Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.Red));
AddToggleStep("Masking", m => maskedTriangles.Masking = m);
} }
} }
} }

View File

@ -4,6 +4,7 @@
#nullable disable #nullable disable
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Screens;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osuTK.Input; using osuTK.Input;
@ -24,13 +25,40 @@ namespace osu.Game.Tests.Visual.Gameplay
} }
[Test] [Test]
public void TestPause() public void TestPauseViaSpace()
{ {
double? lastTime = null; double? lastTime = null;
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0); AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
AddStep("Pause playback", () => InputManager.Key(Key.Space)); AddStep("Pause playback with space", () => InputManager.Key(Key.Space));
AddAssert("player not exited", () => Player.IsCurrentScreen());
AddUntilStep("Time stopped progressing", () =>
{
double current = Player.GameplayClockContainer.CurrentTime;
bool changed = lastTime != current;
lastTime = current;
return !changed;
});
AddWaitStep("wait some", 10);
AddAssert("Time still stopped", () => lastTime == Player.GameplayClockContainer.CurrentTime);
}
[Test]
public void TestPauseViaMiddleMouse()
{
double? lastTime = null;
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
AddStep("Pause playback with middle mouse", () => InputManager.Click(MouseButton.Middle));
AddAssert("player not exited", () => Player.IsCurrentScreen());
AddUntilStep("Time stopped progressing", () => AddUntilStep("Time stopped progressing", () =>
{ {

View File

@ -9,11 +9,11 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osu.Game.Overlays.SkinEditor;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play.HUD.HitErrorMeters; using osu.Game.Screens.Play.HUD.HitErrorMeters;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Skinning.Editor;
using osuTK.Input; using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay

View File

@ -7,9 +7,9 @@ using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.SkinEditor;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Skinning.Editor;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
{ {

View File

@ -8,11 +8,11 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Overlays.SkinEditor;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Skinning.Editor;
using osu.Game.Tests.Gameplay; using osu.Game.Tests.Gameplay;
using osuTK.Input; using osuTK.Input;

View File

@ -8,8 +8,8 @@ using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Overlays.Settings.Sections; using osu.Game.Overlays.Settings.Sections;
using osu.Game.Overlays.SkinEditor;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Skinning.Editor;
namespace osu.Game.Tests.Visual.Navigation namespace osu.Game.Tests.Visual.Navigation
{ {

View File

@ -12,11 +12,11 @@ using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osu.Game.Overlays.SkinEditor;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD.HitErrorMeters; using osu.Game.Screens.Play.HUD.HitErrorMeters;
using osu.Game.Skinning.Editor;
using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Beatmaps.IO;
using osuTK; using osuTK;
using osuTK.Input; using osuTK.Input;

View File

@ -51,6 +51,15 @@ namespace osu.Game.Database
: getReaderFrom(Path); : getReaderFrom(Path);
} }
/// <summary>
/// Deletes the file that is encapsulated by this <see cref="ImportTask"/>.
/// </summary>
public virtual void DeleteFile()
{
if (File.Exists(Path))
File.Delete(Path);
}
/// <summary> /// <summary>
/// Creates an <see cref="ArchiveReader"/> from a stream. /// Creates an <see cref="ArchiveReader"/> from a stream.
/// </summary> /// </summary>

View File

@ -201,8 +201,8 @@ namespace osu.Game.Database
// TODO: Add a check to prevent files from storage to be deleted. // TODO: Add a check to prevent files from storage to be deleted.
try try
{ {
if (import != null && File.Exists(task.Path) && ShouldDeleteArchive(task.Path)) if (import != null && ShouldDeleteArchive(task.Path))
File.Delete(task.Path); task.DeleteFile();
} }
catch (Exception e) catch (Exception e)
{ {

View File

@ -34,6 +34,12 @@ namespace osu.Game.Graphics.Backgrounds
/// </summary> /// </summary>
protected virtual bool CreateNewTriangles => true; protected virtual bool CreateNewTriangles => true;
/// <summary>
/// If enabled, only the portion of triangles that falls within this <see cref="Drawable"/>'s
/// shape is drawn to the screen.
/// </summary>
public bool Masking { get; set; }
private readonly BindableFloat spawnRatio = new BindableFloat(1f); private readonly BindableFloat spawnRatio = new BindableFloat(1f);
/// <summary> /// <summary>
@ -189,6 +195,7 @@ namespace osu.Game.Graphics.Backgrounds
private Vector2 size; private Vector2 size;
private float thickness; private float thickness;
private float texelSize; private float texelSize;
private bool masking;
private IVertexBatch<TexturedVertex2D>? vertexBatch; private IVertexBatch<TexturedVertex2D>? vertexBatch;
@ -205,6 +212,7 @@ namespace osu.Game.Graphics.Backgrounds
texture = Source.texture; texture = Source.texture;
size = Source.DrawSize; size = Source.DrawSize;
thickness = Source.Thickness; thickness = Source.Thickness;
masking = Source.Masking;
Quad triangleQuad = new Quad( Quad triangleQuad = new Quad(
Vector2Extensions.Transform(Vector2.Zero, DrawInfo.Matrix), Vector2Extensions.Transform(Vector2.Zero, DrawInfo.Matrix),
@ -236,26 +244,31 @@ namespace osu.Game.Graphics.Backgrounds
shader.GetUniform<float>("thickness").UpdateValue(ref thickness); shader.GetUniform<float>("thickness").UpdateValue(ref thickness);
shader.GetUniform<float>("texelSize").UpdateValue(ref texelSize); shader.GetUniform<float>("texelSize").UpdateValue(ref texelSize);
float relativeHeight = triangleSize.Y / size.Y; Vector2 relativeSize = Vector2.Divide(triangleSize, size);
float relativeWidth = triangleSize.X / size.X;
foreach (TriangleParticle particle in parts) foreach (TriangleParticle particle in parts)
{ {
Vector2 topLeft = particle.Position - new Vector2(relativeWidth * 0.5f, 0f); Vector2 topLeft = particle.Position - new Vector2(relativeSize.X * 0.5f, 0f);
Vector2 topRight = topLeft + new Vector2(relativeWidth, 0f);
Vector2 bottomLeft = topLeft + new Vector2(0f, relativeHeight); Quad triangleQuad = masking ? clampToDrawable(topLeft, relativeSize) : new Quad(topLeft.X, topLeft.Y, relativeSize.X, relativeSize.Y);
Vector2 bottomRight = bottomLeft + new Vector2(relativeWidth, 0f);
var drawQuad = new Quad( var drawQuad = new Quad(
Vector2Extensions.Transform(topLeft * size, DrawInfo.Matrix), Vector2Extensions.Transform(triangleQuad.TopLeft * size, DrawInfo.Matrix),
Vector2Extensions.Transform(topRight * size, DrawInfo.Matrix), Vector2Extensions.Transform(triangleQuad.TopRight * size, DrawInfo.Matrix),
Vector2Extensions.Transform(bottomLeft * size, DrawInfo.Matrix), Vector2Extensions.Transform(triangleQuad.BottomLeft * size, DrawInfo.Matrix),
Vector2Extensions.Transform(bottomRight * size, DrawInfo.Matrix) Vector2Extensions.Transform(triangleQuad.BottomRight * size, DrawInfo.Matrix)
); );
ColourInfo colourInfo = triangleColourInfo(DrawColourInfo.Colour, new Quad(topLeft, topRight, bottomLeft, bottomRight)); ColourInfo colourInfo = triangleColourInfo(DrawColourInfo.Colour, triangleQuad);
renderer.DrawQuad(texture, drawQuad, colourInfo, vertexAction: vertexBatch.AddAction); RectangleF textureCoords = new RectangleF(
triangleQuad.TopLeft.X - topLeft.X,
triangleQuad.TopLeft.Y - topLeft.Y,
triangleQuad.Width,
triangleQuad.Height
) / relativeSize;
renderer.DrawQuad(texture, drawQuad, colourInfo, new RectangleF(0, 0, 1, 1), vertexBatch.AddAction, textureCoords: textureCoords);
} }
shader.Unbind(); shader.Unbind();
@ -272,6 +285,19 @@ namespace osu.Game.Graphics.Backgrounds
}; };
} }
private static Quad clampToDrawable(Vector2 topLeft, Vector2 size)
{
float leftClamped = Math.Clamp(topLeft.X, 0f, 1f);
float topClamped = Math.Clamp(topLeft.Y, 0f, 1f);
return new Quad(
leftClamped,
topClamped,
Math.Clamp(topLeft.X + size.X, 0f, 1f) - leftClamped,
Math.Clamp(topLeft.Y + size.Y, 0f, 1f) - topClamped
);
}
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {
base.Dispose(isDisposing); base.Dispose(isDisposing);

View File

@ -35,6 +35,7 @@ namespace osu.Game.Input.Bindings
// It is used to decide the order of precedence, with the earlier items having higher precedence. // It is used to decide the order of precedence, with the earlier items having higher precedence.
public override IEnumerable<IKeyBinding> DefaultKeyBindings => GlobalKeyBindings public override IEnumerable<IKeyBinding> DefaultKeyBindings => GlobalKeyBindings
.Concat(EditorKeyBindings) .Concat(EditorKeyBindings)
.Concat(ReplayKeyBindings)
.Concat(InGameKeyBindings) .Concat(InGameKeyBindings)
.Concat(SongSelectKeyBindings) .Concat(SongSelectKeyBindings)
.Concat(AudioControlKeyBindings) .Concat(AudioControlKeyBindings)
@ -112,13 +113,18 @@ namespace osu.Game.Input.Bindings
new KeyBinding(new[] { InputKey.F4 }, GlobalAction.IncreaseScrollSpeed), new KeyBinding(new[] { InputKey.F4 }, GlobalAction.IncreaseScrollSpeed),
new KeyBinding(new[] { InputKey.Shift, InputKey.Tab }, GlobalAction.ToggleInGameInterface), new KeyBinding(new[] { InputKey.Shift, InputKey.Tab }, GlobalAction.ToggleInGameInterface),
new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay), new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay),
new KeyBinding(InputKey.Space, GlobalAction.TogglePauseReplay),
new KeyBinding(InputKey.Left, GlobalAction.SeekReplayBackward),
new KeyBinding(InputKey.Right, GlobalAction.SeekReplayForward),
new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD), new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD),
new KeyBinding(InputKey.Tab, GlobalAction.ToggleChatFocus), new KeyBinding(InputKey.Tab, GlobalAction.ToggleChatFocus),
}; };
public IEnumerable<KeyBinding> ReplayKeyBindings => new[]
{
new KeyBinding(InputKey.Space, GlobalAction.TogglePauseReplay),
new KeyBinding(InputKey.MouseMiddle, GlobalAction.TogglePauseReplay),
new KeyBinding(InputKey.Left, GlobalAction.SeekReplayBackward),
new KeyBinding(InputKey.Right, GlobalAction.SeekReplayForward),
};
public IEnumerable<KeyBinding> SongSelectKeyBindings => new[] public IEnumerable<KeyBinding> SongSelectKeyBindings => new[]
{ {
new KeyBinding(InputKey.F1, GlobalAction.ToggleModSelection), new KeyBinding(InputKey.F1, GlobalAction.ToggleModSelection),

View File

@ -34,6 +34,11 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString InGameSection => new TranslatableString(getKey(@"in_game_section"), @"In Game"); public static LocalisableString InGameSection => new TranslatableString(getKey(@"in_game_section"), @"In Game");
/// <summary>
/// "Replay"
/// </summary>
public static LocalisableString ReplaySection => new TranslatableString(getKey(@"replay_section"), @"Replay");
/// <summary> /// <summary>
/// "Audio" /// "Audio"
/// </summary> /// </summary>

View File

@ -49,6 +49,7 @@ using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.BeatmapListing;
using osu.Game.Overlays.Music; using osu.Game.Overlays.Music;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
using osu.Game.Overlays.SkinEditor;
using osu.Game.Overlays.Toolbar; using osu.Game.Overlays.Toolbar;
using osu.Game.Overlays.Volume; using osu.Game.Overlays.Volume;
using osu.Game.Performance; using osu.Game.Performance;
@ -59,7 +60,6 @@ using osu.Game.Screens.Menu;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Skinning.Editor;
using osu.Game.Updater; using osu.Game.Updater;
using osu.Game.Users; using osu.Game.Users;
using osu.Game.Utils; using osu.Game.Utils;

View File

@ -25,6 +25,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Add(new AudioControlKeyBindingsSubsection(manager)); Add(new AudioControlKeyBindingsSubsection(manager));
Add(new SongSelectKeyBindingSubsection(manager)); Add(new SongSelectKeyBindingSubsection(manager));
Add(new InGameKeyBindingsSubsection(manager)); Add(new InGameKeyBindingsSubsection(manager));
Add(new ReplayKeyBindingsSubsection(manager));
Add(new EditorKeyBindingsSubsection(manager)); Add(new EditorKeyBindingsSubsection(manager));
} }
@ -72,6 +73,17 @@ namespace osu.Game.Overlays.Settings.Sections.Input
} }
} }
private partial class ReplayKeyBindingsSubsection : KeyBindingsSubsection
{
protected override LocalisableString Header => InputSettingsStrings.ReplaySection;
public ReplayKeyBindingsSubsection(GlobalActionContainer manager)
: base(null)
{
Defaults = manager.ReplayKeyBindings;
}
}
private partial class AudioControlKeyBindingsSubsection : KeyBindingsSubsection private partial class AudioControlKeyBindingsSubsection : KeyBindingsSubsection
{ {
protected override LocalisableString Header => InputSettingsStrings.AudioSection; protected override LocalisableString Header => InputSettingsStrings.AudioSection;

View File

@ -17,9 +17,9 @@ using osu.Framework.Platform;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation; using osu.Game.Localisation;
using osu.Game.Overlays.SkinEditor;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Skinning.Editor;
using Realms; using Realms;
namespace osu.Game.Overlays.Settings.Sections namespace osu.Game.Overlays.Settings.Sections

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
@ -13,25 +11,26 @@ using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Skinning;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Skinning.Editor namespace osu.Game.Overlays.SkinEditor
{ {
public partial class SkinBlueprint : SelectionBlueprint<ISkinnableDrawable> public partial class SkinBlueprint : SelectionBlueprint<ISkinnableDrawable>
{ {
private Container box; private Container box = null!;
private Container outlineBox; private Container outlineBox = null!;
private AnchorOriginVisualiser anchorOriginVisualiser; private AnchorOriginVisualiser anchorOriginVisualiser = null!;
private Drawable drawable => (Drawable)Item; private Drawable drawable => (Drawable)Item;
protected override bool ShouldBeAlive => drawable.IsAlive && Item.IsPresent; protected override bool ShouldBeAlive => drawable.IsAlive && Item.IsPresent;
[Resolved] [Resolved]
private OsuColour colours { get; set; } private OsuColour colours { get; set; } = null!;
public SkinBlueprint(ISkinnableDrawable component) public SkinBlueprint(ISkinnableDrawable component)
: base(component) : base(component)

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Diagnostics; using System.Diagnostics;
@ -10,16 +8,21 @@ using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Screens; using osu.Framework.Graphics.Containers;
using osu.Framework.Testing; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Testing;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Screens;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Skinning;
using osuTK; using osuTK;
using osuTK.Input; using osuTK.Input;
namespace osu.Game.Skinning.Editor namespace osu.Game.Overlays.SkinEditor
{ {
public partial class SkinBlueprintContainer : BlueprintContainer<ISkinnableDrawable> public partial class SkinBlueprintContainer : BlueprintContainer<ISkinnableDrawable>
{ {
@ -28,7 +31,7 @@ namespace osu.Game.Skinning.Editor
private readonly List<BindableList<ISkinnableDrawable>> targetComponents = new List<BindableList<ISkinnableDrawable>>(); private readonly List<BindableList<ISkinnableDrawable>> targetComponents = new List<BindableList<ISkinnableDrawable>>();
[Resolved] [Resolved]
private SkinEditor editor { get; set; } private SkinEditor editor { get; set; } = null!;
public SkinBlueprintContainer(Drawable target) public SkinBlueprintContainer(Drawable target)
{ {
@ -46,9 +49,7 @@ namespace osu.Game.Skinning.Editor
if (targetContainers.Length == 0) if (targetContainers.Length == 0)
{ {
string targetScreen = target.ChildrenOfType<Screen>().LastOrDefault()?.GetType().Name ?? "this screen"; AddInternal(new NonSkinnableScreenPlaceholder());
AddInternal(new ScreenWhiteBox.UnderConstructionMessage(targetScreen, "doesn't support skin customisation just yet."));
return; return;
} }
@ -61,7 +62,7 @@ namespace osu.Game.Skinning.Editor
} }
} }
private void componentsChanged(object sender, NotifyCollectionChangedEventArgs e) => Schedule(() => private void componentsChanged(object? sender, NotifyCollectionChangedEventArgs e) => Schedule(() =>
{ {
switch (e.Action) switch (e.Action)
{ {
@ -159,5 +160,65 @@ namespace osu.Game.Skinning.Editor
foreach (var list in targetComponents) foreach (var list in targetComponents)
list.UnbindAll(); list.UnbindAll();
} }
public partial class NonSkinnableScreenPlaceholder : CompositeDrawable
{
[Resolved]
private SkinEditorOverlay? skinEditorOverlay { get; set; }
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
RelativeSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
new Box
{
Colour = colourProvider.Dark6,
RelativeSizeAxes = Axes.Both,
Alpha = 0.95f,
},
new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(0, 5),
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new SpriteIcon
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Icon = FontAwesome.Solid.ExclamationCircle,
Size = new Vector2(24),
Y = -5,
},
new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 18))
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
TextAnchor = Anchor.Centre,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Text = "Please navigate to a skinnable screen using the scene library",
},
new RoundedButton
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Width = 200,
Margin = new MarginPadding { Top = 20 },
Action = () => skinEditorOverlay?.Hide(),
Text = "Return to game"
}
}
},
};
}
}
} }
} }

View File

@ -11,12 +11,12 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation; using osu.Game.Localisation;
using osu.Game.Overlays;
using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
using osu.Game.Skinning;
using osuTK; using osuTK;
namespace osu.Game.Skinning.Editor namespace osu.Game.Overlays.SkinEditor
{ {
public partial class SkinComponentToolbox : EditorSidebarSection public partial class SkinComponentToolbox : EditorSidebarSection
{ {

View File

@ -1,10 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -24,17 +23,17 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation; using osu.Game.Localisation;
using osu.Game.Overlays;
using osu.Game.Overlays.OSD; using osu.Game.Overlays.OSD;
using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components;
using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Components.Menus;
using osu.Game.Skinning;
namespace osu.Game.Skinning.Editor namespace osu.Game.Overlays.SkinEditor
{ {
[Cached(typeof(SkinEditor))] [Cached(typeof(SkinEditor))]
public partial class SkinEditor : VisibilityContainer, ICanAcceptFiles, IKeyBindingHandler<PlatformAction> public partial class SkinEditor : VisibilityContainer, ICanAcceptFiles, IKeyBindingHandler<PlatformAction>
{ {
public const double TRANSITION_DURATION = 500; public const double TRANSITION_DURATION = 300;
public const float MENU_HEIGHT = 40; public const float MENU_HEIGHT = 40;
@ -42,39 +41,39 @@ namespace osu.Game.Skinning.Editor
protected override bool StartHidden => true; protected override bool StartHidden => true;
private Drawable targetScreen; private Drawable targetScreen = null!;
private OsuTextFlowContainer headerText; private OsuTextFlowContainer headerText = null!;
private Bindable<Skin> currentSkin; private Bindable<Skin> currentSkin = null!;
[Resolved(canBeNull: true)]
private OsuGame game { get; set; }
[Resolved] [Resolved]
private SkinManager skins { get; set; } private OsuGame? game { get; set; }
[Resolved] [Resolved]
private OsuColour colours { get; set; } private SkinManager skins { get; set; } = null!;
[Resolved] [Resolved]
private RealmAccess realm { get; set; } private OsuColour colours { get; set; } = null!;
[Resolved(canBeNull: true)] [Resolved]
private SkinEditorOverlay skinEditorOverlay { get; set; } private RealmAccess realm { get; set; } = null!;
[Resolved]
private SkinEditorOverlay? skinEditorOverlay { get; set; }
[Cached] [Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
private bool hasBegunMutating; private bool hasBegunMutating;
private Container content; private Container? content;
private EditorSidebar componentsSidebar; private EditorSidebar componentsSidebar = null!;
private EditorSidebar settingsSidebar; private EditorSidebar settingsSidebar = null!;
[Resolved(canBeNull: true)] [Resolved]
private OnScreenDisplay onScreenDisplay { get; set; } private OnScreenDisplay? onScreenDisplay { get; set; }
public SkinEditor() public SkinEditor()
{ {
@ -234,11 +233,14 @@ namespace osu.Game.Skinning.Editor
// Immediately clear the previous blueprint container to ensure it doesn't try to interact with the old target. // Immediately clear the previous blueprint container to ensure it doesn't try to interact with the old target.
content?.Clear(); content?.Clear();
Scheduler.AddOnce(loadBlueprintContainer); Scheduler.AddOnce(loadBlueprintContainer);
Scheduler.AddOnce(populateSettings); Scheduler.AddOnce(populateSettings);
void loadBlueprintContainer() void loadBlueprintContainer()
{ {
Debug.Assert(content != null);
content.Child = new SkinBlueprintContainer(targetScreen); content.Child = new SkinBlueprintContainer(targetScreen);
componentsSidebar.Child = new SkinComponentToolbox(getFirstTarget() as CompositeDrawable) componentsSidebar.Child = new SkinComponentToolbox(getFirstTarget() as CompositeDrawable)
@ -311,9 +313,9 @@ namespace osu.Game.Skinning.Editor
private IEnumerable<ISkinnableTarget> availableTargets => targetScreen.ChildrenOfType<ISkinnableTarget>(); private IEnumerable<ISkinnableTarget> availableTargets => targetScreen.ChildrenOfType<ISkinnableTarget>();
private ISkinnableTarget getFirstTarget() => availableTargets.FirstOrDefault(); private ISkinnableTarget? getFirstTarget() => availableTargets.FirstOrDefault();
private ISkinnableTarget getTarget(GlobalSkinComponentLookup.LookupType target) private ISkinnableTarget? getTarget(GlobalSkinComponentLookup.LookupType target)
{ {
return availableTargets.FirstOrDefault(c => c.Target == target); return availableTargets.FirstOrDefault(c => c.Target == target);
} }
@ -327,7 +329,7 @@ namespace osu.Game.Skinning.Editor
currentSkin.Value.ResetDrawableTarget(t); currentSkin.Value.ResetDrawableTarget(t);
// add back default components // add back default components
getTarget(t.Target).Reload(); getTarget(t.Target)?.Reload();
} }
} }
@ -342,7 +344,7 @@ namespace osu.Game.Skinning.Editor
currentSkin.Value.UpdateDrawableTarget(t); currentSkin.Value.UpdateDrawableTarget(t);
skins.Save(skins.CurrentSkin.Value); skins.Save(skins.CurrentSkin.Value);
onScreenDisplay?.Display(new SkinEditorToast(ToastStrings.SkinSaved, currentSkin.Value.SkinInfo.ToString())); onScreenDisplay?.Display(new SkinEditorToast(ToastStrings.SkinSaved, currentSkin.Value.SkinInfo.ToString() ?? "Unknown"));
} }
protected override bool OnHover(HoverEvent e) => true; protected override bool OnHover(HoverEvent e) => true;
@ -357,10 +359,7 @@ namespace osu.Game.Skinning.Editor
protected override void PopIn() protected override void PopIn()
{ {
this this.FadeIn(TRANSITION_DURATION, Easing.OutQuint);
// align animation to happen after the majority of the ScalingContainer animation completes.
.Delay(ScalingContainer.TRANSITION_DURATION * 0.3f)
.FadeIn(TRANSITION_DURATION, Easing.OutQuint);
} }
protected override void PopOut() protected override void PopOut()
@ -394,12 +393,18 @@ namespace osu.Game.Skinning.Editor
// This is the best we can do for now. // This is the best we can do for now.
realm.Run(r => r.Refresh()); realm.Run(r => r.Refresh());
var skinnableTarget = getFirstTarget();
// Import still should happen for now, even if not placeable (as it allows a user to import skin resources that would apply to legacy gameplay skins).
if (skinnableTarget == null)
return;
// place component // place component
var sprite = new SkinnableSprite var sprite = new SkinnableSprite
{ {
SpriteName = { Value = file.Name }, SpriteName = { Value = file.Name },
Origin = Anchor.Centre, Origin = Anchor.Centre,
Position = getFirstTarget().ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position), Position = skinnableTarget.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position),
}; };
placeComponent(sprite, false); placeComponent(sprite, false);

View File

@ -1,10 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Diagnostics; using System.Diagnostics;
using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -17,7 +14,7 @@ using osu.Game.Screens;
using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components;
using osuTK; using osuTK;
namespace osu.Game.Skinning.Editor namespace osu.Game.Overlays.SkinEditor
{ {
/// <summary> /// <summary>
/// A container which handles loading a skin editor on user request for a specified target. /// A container which handles loading a skin editor on user request for a specified target.
@ -29,13 +26,12 @@ namespace osu.Game.Skinning.Editor
protected override bool BlockNonPositionalInput => true; protected override bool BlockNonPositionalInput => true;
[CanBeNull] private SkinEditor? skinEditor;
private SkinEditor skinEditor;
[Resolved(canBeNull: true)] [Resolved]
private OsuGame game { get; set; } private OsuGame game { get; set; } = null!;
private OsuScreen lastTargetScreen; private OsuScreen? lastTargetScreen;
private Vector2 lastDrawSize; private Vector2 lastDrawSize;
@ -81,6 +77,8 @@ namespace osu.Game.Skinning.Editor
AddInternal(editor); AddInternal(editor);
Debug.Assert(lastTargetScreen != null);
SetTarget(lastTargetScreen); SetTarget(lastTargetScreen);
}); });
} }
@ -124,15 +122,15 @@ namespace osu.Game.Skinning.Editor
{ {
Scheduler.AddOnce(updateScreenSizing); Scheduler.AddOnce(updateScreenSizing);
game?.Toolbar.Hide(); game.Toolbar.Hide();
game?.CloseAllOverlays(); game.CloseAllOverlays();
} }
else else
{ {
scalingContainer.SetCustomRect(null); scalingContainer.SetCustomRect(null);
if (lastTargetScreen?.HideOverlaysOnEnter != true) if (lastTargetScreen?.HideOverlaysOnEnter != true)
game?.Toolbar.Show(); game.Toolbar.Show();
} }
} }
@ -158,7 +156,7 @@ namespace osu.Game.Skinning.Editor
Scheduler.AddOnce(setTarget, screen); Scheduler.AddOnce(setTarget, screen);
} }
private void setTarget(OsuScreen target) private void setTarget(OsuScreen? target)
{ {
if (target == null) if (target == null)
return; return;

View File

@ -1,11 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -17,7 +14,6 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation; using osu.Game.Localisation;
using osu.Game.Overlays;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Screens; using osu.Game.Screens;
@ -26,7 +22,7 @@ using osu.Game.Screens.Select;
using osu.Game.Utils; using osu.Game.Utils;
using osuTK; using osuTK;
namespace osu.Game.Skinning.Editor namespace osu.Game.Overlays.SkinEditor
{ {
public partial class SkinEditorSceneLibrary : CompositeDrawable public partial class SkinEditorSceneLibrary : CompositeDrawable
{ {
@ -36,14 +32,14 @@ namespace osu.Game.Skinning.Editor
private const float padding = 10; private const float padding = 10;
[Resolved(canBeNull: true)] [Resolved]
private IPerformFromScreenRunner performer { get; set; } private IPerformFromScreenRunner? performer { get; set; }
[Resolved] [Resolved]
private IBindable<RulesetInfo> ruleset { get; set; } private IBindable<RulesetInfo> ruleset { get; set; } = null!;
[Resolved] [Resolved]
private Bindable<IReadOnlyList<Mod>> mods { get; set; } private Bindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
public SkinEditorSceneLibrary() public SkinEditorSceneLibrary()
{ {
@ -107,7 +103,12 @@ namespace osu.Game.Skinning.Editor
var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod();
if (!ModUtils.CheckCompatibleSet(mods.Value.Append(replayGeneratingMod), out var invalid)) IReadOnlyList<Mod> usableMods = mods.Value;
if (replayGeneratingMod != null)
usableMods = usableMods.Append(replayGeneratingMod).ToArray();
if (!ModUtils.CheckCompatibleSet(usableMods, out var invalid))
mods.Value = mods.Value.Except(invalid).ToArray(); mods.Value = mods.Value.Except(invalid).ToArray();
if (replayGeneratingMod != null) if (replayGeneratingMod != null)
@ -129,8 +130,8 @@ namespace osu.Game.Skinning.Editor
Height = BUTTON_HEIGHT; Height = BUTTON_HEIGHT;
} }
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader]
private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours) private void load(OverlayColourProvider? overlayColourProvider, OsuColour colours)
{ {
BackgroundColour = overlayColourProvider?.Background3 ?? colours.Blue3; BackgroundColour = overlayColourProvider?.Background3 ?? colours.Blue3;
Content.CornerRadius = 5; Content.CornerRadius = 5;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -16,14 +14,15 @@ using osu.Game.Extensions;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Skinning;
using osuTK; using osuTK;
namespace osu.Game.Skinning.Editor namespace osu.Game.Overlays.SkinEditor
{ {
public partial class SkinSelectionHandler : SelectionHandler<ISkinnableDrawable> public partial class SkinSelectionHandler : SelectionHandler<ISkinnableDrawable>
{ {
[Resolved] [Resolved]
private SkinEditor skinEditor { get; set; } private SkinEditor skinEditor { get; set; } = null!;
public override bool HandleRotation(float angle) public override bool HandleRotation(float angle)
{ {

View File

@ -9,7 +9,7 @@ using osu.Game.Localisation;
using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components;
using osuTK; using osuTK;
namespace osu.Game.Skinning.Editor namespace osu.Game.Overlays.SkinEditor
{ {
internal partial class SkinSettingsToolbox : EditorSidebarSection internal partial class SkinSettingsToolbox : EditorSidebarSection
{ {

View File

@ -36,6 +36,9 @@ namespace osu.Game.Rulesets.Edit
new CheckUnsnappedObjects(), new CheckUnsnappedObjects(),
new CheckConcurrentObjects(), new CheckConcurrentObjects(),
new CheckZeroLengthObjects(), new CheckZeroLengthObjects(),
// Timing
new CheckPreviewTime(),
}; };
public IEnumerable<Issue> Run(BeatmapVerifierContext context) public IEnumerable<Issue> Run(BeatmapVerifierContext context)

View File

@ -0,0 +1,62 @@
// 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 osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit.Checks.Components;
namespace osu.Game.Rulesets.Edit.Checks
{
public class CheckPreviewTime : ICheck
{
public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Timing, "Inconsistent or unset preview time");
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
{
new IssueTemplatePreviewTimeConflict(this),
new IssueTemplateHasNoPreviewTime(this),
};
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
{
var diffList = context.Beatmap.BeatmapInfo.BeatmapSet?.Beatmaps ?? new List<BeatmapInfo>();
int previewTime = context.Beatmap.BeatmapInfo.Metadata.PreviewTime;
if (previewTime == -1)
yield return new IssueTemplateHasNoPreviewTime(this).Create();
foreach (var diff in diffList)
{
if (diff.Equals(context.Beatmap.BeatmapInfo))
continue;
if (diff.Metadata.PreviewTime != previewTime)
yield return new IssueTemplatePreviewTimeConflict(this).Create(diff.DifficultyName, previewTime, diff.Metadata.PreviewTime);
}
}
public class IssueTemplatePreviewTimeConflict : IssueTemplate
{
public IssueTemplatePreviewTimeConflict(ICheck check)
: base(check, IssueType.Problem, "Audio preview time ({1}) doesn't match the time specified in \"{0}\" ({2})")
{
}
public Issue Create(string diffName, int originalTime, int conflictTime) =>
// preview time should show (not set) when it is not set.
new Issue(this, diffName,
originalTime != -1 ? $"{originalTime:N0} ms" : "not set",
conflictTime != -1 ? $"{conflictTime:N0} ms" : "not set");
}
public class IssueTemplateHasNoPreviewTime : IssueTemplate
{
public IssueTemplateHasNoPreviewTime(ICheck check)
: base(check, IssueType.Problem, "A preview point for this map is not set. Consider setting one from the Timing menu.")
{
}
public Issue Create() => new Issue(this);
}
}
}

View File

@ -303,8 +303,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
samplesBindable.CollectionChanged -= onSamplesChanged; samplesBindable.CollectionChanged -= onSamplesChanged;
// Release the samples for other hitobjects to use. // Release the samples for other hitobjects to use.
if (Samples != null) Samples?.ClearSamples();
Samples.Samples = null;
foreach (var obj in nestedHitObjects) foreach (var obj in nestedHitObjects)
{ {

View File

@ -31,6 +31,8 @@ namespace osu.Game.Screens.Play.HUD
public readonly Bindable<bool> IsPaused = new Bindable<bool>(); public readonly Bindable<bool> IsPaused = new Bindable<bool>();
public readonly Bindable<bool> ReplayLoaded = new Bindable<bool>();
private HoldButton button; private HoldButton button;
public Action Action { get; set; } public Action Action { get; set; }
@ -60,6 +62,7 @@ namespace osu.Game.Screens.Play.HUD
HoverGained = () => text.FadeIn(500, Easing.OutQuint), HoverGained = () => text.FadeIn(500, Easing.OutQuint),
HoverLost = () => text.FadeOut(500, Easing.OutQuint), HoverLost = () => text.FadeOut(500, Easing.OutQuint),
IsPaused = { BindTarget = IsPaused }, IsPaused = { BindTarget = IsPaused },
ReplayLoaded = { BindTarget = ReplayLoaded },
Action = () => Action(), Action = () => Action(),
} }
}; };
@ -110,6 +113,8 @@ namespace osu.Game.Screens.Play.HUD
public readonly Bindable<bool> IsPaused = new Bindable<bool>(); public readonly Bindable<bool> IsPaused = new Bindable<bool>();
public readonly Bindable<bool> ReplayLoaded = new Bindable<bool>();
protected override bool AllowMultipleFires => true; protected override bool AllowMultipleFires => true;
public Action HoverGained; public Action HoverGained;
@ -251,7 +256,14 @@ namespace osu.Game.Screens.Play.HUD
switch (e.Action) switch (e.Action)
{ {
case GlobalAction.Back: case GlobalAction.Back:
case GlobalAction.PauseGameplay: // in the future this behaviour will differ for replays etc. if (!pendingAnimation)
BeginConfirm();
return true;
case GlobalAction.PauseGameplay:
// handled by replay player
if (ReplayLoaded.Value) return false;
if (!pendingAnimation) if (!pendingAnimation)
BeginConfirm(); BeginConfirm();
return true; return true;
@ -265,7 +277,12 @@ namespace osu.Game.Screens.Play.HUD
switch (e.Action) switch (e.Action)
{ {
case GlobalAction.Back: case GlobalAction.Back:
AbortConfirm();
break;
case GlobalAction.PauseGameplay: case GlobalAction.PauseGameplay:
if (ReplayLoaded.Value) return;
AbortConfirm(); AbortConfirm();
break; break;
} }

View File

@ -435,7 +435,8 @@ namespace osu.Game.Screens.Play
HoldToQuit = HoldToQuit =
{ {
Action = () => PerformExit(true), Action = () => PerformExit(true),
IsPaused = { BindTarget = GameplayClockContainer.IsPaused } IsPaused = { BindTarget = GameplayClockContainer.IsPaused },
ReplayLoaded = { BindTarget = DrawableRuleset.HasReplayLoaded },
}, },
KeyCounter = KeyCounter =
{ {

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;

View File

@ -6,7 +6,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Utils;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -100,21 +99,6 @@ namespace osu.Game.Skinning
switch (result) switch (result)
{ {
case HitResult.Miss:
this.ScaleTo(1.6f);
this.ScaleTo(1, 100, Easing.In);
//todo: this only applies to osu! ruleset apparently.
this.MoveTo(new Vector2(0, -2));
this.MoveToOffset(new Vector2(0, 20), fade_out_delay + fade_out_length, Easing.In);
float rotation = RNG.NextSingle(-8.6f, 8.6f);
this.RotateTo(0);
this.RotateTo(rotation, fade_in_length)
.Then().RotateTo(rotation * 2, fade_out_delay + fade_out_length - fade_in_length, Easing.In);
break;
default: default:
mainPiece.ScaleTo(0.9f); mainPiece.ScaleTo(0.9f);
mainPiece.ScaleTo(1.05f, fade_out_delay + fade_out_length); mainPiece.ScaleTo(1.05f, fade_out_delay + fade_out_length);

View File

@ -1,15 +1,15 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osuTK;
namespace osu.Game.Skinning namespace osu.Game.Skinning
{ {
@ -20,6 +20,9 @@ namespace osu.Game.Skinning
private readonly float finalScale; private readonly float finalScale;
private readonly bool forceTransforms; private readonly bool forceTransforms;
[Resolved]
private ISkinSource skin { get; set; } = null!;
public LegacyJudgementPieceOld(HitResult result, Func<Drawable> createMainDrawable, float finalScale = 1f, bool forceTransforms = false) public LegacyJudgementPieceOld(HitResult result, Func<Drawable> createMainDrawable, float finalScale = 1f, bool forceTransforms = false)
{ {
this.result = result; this.result = result;
@ -55,6 +58,14 @@ namespace osu.Game.Skinning
this.ScaleTo(1.6f); this.ScaleTo(1.6f);
this.ScaleTo(1, 100, Easing.In); this.ScaleTo(1, 100, Easing.In);
decimal? legacyVersion = skin.GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value;
if (legacyVersion >= 2.0m)
{
this.MoveTo(new Vector2(0, -5));
this.MoveToOffset(new Vector2(0, 80), fade_out_delay + fade_out_length, Easing.In);
}
float rotation = RNG.NextSingle(-8.6f, 8.6f); float rotation = RNG.NextSingle(-8.6f, 8.6f);
this.RotateTo(0); this.RotateTo(0);

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Globalization; using System.Globalization;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Threading.Tasks; using System.Threading.Tasks;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
@ -16,7 +14,7 @@ namespace osu.Game.Skinning
{ {
private readonly LegacyFont font; private readonly LegacyFont font;
private LegacyGlyphStore glyphStore; private LegacyGlyphStore glyphStore = null!;
protected override char FixedWidthReferenceCharacter => '5'; protected override char FixedWidthReferenceCharacter => '5';
@ -49,7 +47,7 @@ namespace osu.Game.Skinning
this.skin = skin; this.skin = skin;
} }
public ITexturedCharacterGlyph Get(string fontName, char character) public ITexturedCharacterGlyph? Get(string fontName, char character)
{ {
string lookup = getLookupName(character); string lookup = getLookupName(character);
@ -79,7 +77,7 @@ namespace osu.Game.Skinning
} }
} }
public Task<ITexturedCharacterGlyph> GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character)); public Task<ITexturedCharacterGlyph?> GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character));
} }
} }
} }

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Skinning namespace osu.Game.Skinning
{ {
public class SkinCustomColourLookup public class SkinCustomColourLookup

View File

@ -1,12 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -39,13 +36,12 @@ namespace osu.Game.Skinning
/// <summary> /// <summary>
/// All raw <see cref="DrawableSamples"/>s contained in this <see cref="SkinnableSound"/>. /// All raw <see cref="DrawableSamples"/>s contained in this <see cref="SkinnableSound"/>.
/// </summary> /// </summary>
[NotNull, ItemNotNull]
protected IEnumerable<DrawableSample> DrawableSamples => samplesContainer.Select(c => c.Sample).Where(s => s != null); protected IEnumerable<DrawableSample> DrawableSamples => samplesContainer.Select(c => c.Sample).Where(s => s != null);
private readonly AudioContainer<PoolableSkinnableSample> samplesContainer; private readonly AudioContainer<PoolableSkinnableSample> samplesContainer;
[Resolved(CanBeNull = true)] [Resolved]
private IPooledSampleProvider samplePool { get; set; } private IPooledSampleProvider? samplePool { get; set; }
/// <summary> /// <summary>
/// Creates a new <see cref="SkinnableSound"/>. /// Creates a new <see cref="SkinnableSound"/>.
@ -59,7 +55,7 @@ namespace osu.Game.Skinning
/// Creates a new <see cref="SkinnableSound"/> with some initial samples. /// Creates a new <see cref="SkinnableSound"/> with some initial samples.
/// </summary> /// </summary>
/// <param name="samples">The initial samples.</param> /// <param name="samples">The initial samples.</param>
public SkinnableSound([NotNull] IEnumerable<ISampleInfo> samples) public SkinnableSound(IEnumerable<ISampleInfo> samples)
: this() : this()
{ {
this.samples = samples.ToArray(); this.samples = samples.ToArray();
@ -69,7 +65,7 @@ namespace osu.Game.Skinning
/// Creates a new <see cref="SkinnableSound"/> with an initial sample. /// Creates a new <see cref="SkinnableSound"/> with an initial sample.
/// </summary> /// </summary>
/// <param name="sample">The initial sample.</param> /// <param name="sample">The initial sample.</param>
public SkinnableSound([NotNull] ISampleInfo sample) public SkinnableSound(ISampleInfo sample)
: this(new[] { sample }) : this(new[] { sample })
{ {
} }
@ -84,8 +80,6 @@ namespace osu.Game.Skinning
get => samples; get => samples;
set set
{ {
value ??= Array.Empty<ISampleInfo>();
if (samples == value) if (samples == value)
return; return;
@ -96,6 +90,8 @@ namespace osu.Game.Skinning
} }
} }
public void ClearSamples() => Samples = Array.Empty<ISampleInfo>();
private bool looping; private bool looping;
/// <summary> /// <summary>