Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix storyboard videos not accepting transforms #27967

Merged
merged 8 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 76 additions & 23 deletions osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.IO.Stores;
using osu.Framework.Testing;
Expand Down Expand Up @@ -40,7 +41,7 @@ public void TestSkinSpriteDisallowedByDefault()
AddStep("disallow all lookups", () =>
{
storyboard.UseSkinSprites = false;
storyboard.AlwaysProvideTexture = false;
storyboard.ProvideResources = false;
});

AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
Expand All @@ -55,7 +56,7 @@ public void TestLookupFromStoryboard()
AddStep("allow storyboard lookup", () =>
{
storyboard.UseSkinSprites = false;
storyboard.AlwaysProvideTexture = true;
storyboard.ProvideResources = true;
});

AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
Expand All @@ -67,13 +68,48 @@ public void TestLookupFromStoryboard()
assertStoryboardSourced();
}

[TestCase(false)]
[TestCase(true)]
public void TestVideo(bool scaleTransformProvided)
{
AddStep("allow storyboard lookup", () =>
{
storyboard.ProvideResources = true;
});

AddStep("create video", () => SetContents(_ =>
{
var layer = storyboard.GetLayer("Video");

var sprite = new StoryboardVideo("Videos/test-video.mp4", Time.Current);

if (scaleTransformProvided)
{
sprite.TimelineGroup.Scale.Add(Easing.None, Time.Current, Time.Current + 1000, 1, 2);
sprite.TimelineGroup.Scale.Add(Easing.None, Time.Current + 1000, Time.Current + 2000, 2, 1);
}

layer.Elements.Clear();
layer.Add(sprite);

return new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
storyboard.CreateDrawable()
}
};
}));
}

[Test]
public void TestSkinLookupPreferredOverStoryboard()
{
AddStep("allow all lookups", () =>
{
storyboard.UseSkinSprites = true;
storyboard.AlwaysProvideTexture = true;
storyboard.ProvideResources = true;
});

AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
Expand All @@ -91,7 +127,7 @@ public void TestAllowLookupFromSkin()
AddStep("allow skin lookup", () =>
{
storyboard.UseSkinSprites = true;
storyboard.AlwaysProvideTexture = false;
storyboard.ProvideResources = false;
});

AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
Expand All @@ -109,7 +145,7 @@ public void TestFlippedSprite()
AddStep("allow all lookups", () =>
{
storyboard.UseSkinSprites = true;
storyboard.AlwaysProvideTexture = true;
storyboard.ProvideResources = true;
});

AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
Expand All @@ -127,7 +163,7 @@ public void TestZeroScale()
AddStep("allow all lookups", () =>
{
storyboard.UseSkinSprites = true;
storyboard.AlwaysProvideTexture = true;
storyboard.ProvideResources = true;
});

AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
Expand All @@ -142,7 +178,7 @@ public void TestNegativeScale()
AddStep("allow all lookups", () =>
{
storyboard.UseSkinSprites = true;
storyboard.AlwaysProvideTexture = true;
storyboard.ProvideResources = true;
});

AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
Expand All @@ -156,7 +192,7 @@ public void TestNegativeScaleWithFlippedSprite()
AddStep("allow all lookups", () =>
{
storyboard.UseSkinSprites = true;
storyboard.AlwaysProvideTexture = true;
storyboard.ProvideResources = true;
});

AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
Expand All @@ -170,7 +206,7 @@ public void TestNegativeScaleWithFlippedSprite()
AddAssert("origin back", () => sprites.All(s => s.Origin == Anchor.TopLeft));
}

private DrawableStoryboard createSprite(string lookupName, Anchor origin, Vector2 initialPosition)
private Drawable createSprite(string lookupName, Anchor origin, Vector2 initialPosition)
{
var layer = storyboard.GetLayer("Background");

Expand All @@ -180,7 +216,14 @@ private DrawableStoryboard createSprite(string lookupName, Anchor origin, Vector
layer.Elements.Clear();
layer.Add(sprite);

return storyboard.CreateDrawable().With(s => s.RelativeSizeAxes = Axes.Both);
return new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
storyboard.CreateDrawable()
}
};
}

private void assertStoryboardSourced()
Expand All @@ -202,42 +245,52 @@ public override DrawableStoryboard CreateDrawable(IReadOnlyList<Mod>? mods = nul
return new TestDrawableStoryboard(this, mods);
}

public bool AlwaysProvideTexture { get; set; }
public bool ProvideResources { get; set; }

public override string GetStoragePathFromStoryboardPath(string path) => AlwaysProvideTexture ? path : string.Empty;
public override string GetStoragePathFromStoryboardPath(string path) => ProvideResources ? path : string.Empty;

private partial class TestDrawableStoryboard : DrawableStoryboard
{
private readonly bool alwaysProvideTexture;
private readonly bool provideResources;

public TestDrawableStoryboard(TestStoryboard storyboard, IReadOnlyList<Mod>? mods)
: base(storyboard, mods)
{
alwaysProvideTexture = storyboard.AlwaysProvideTexture;
provideResources = storyboard.ProvideResources;
}

protected override IResourceStore<byte[]> CreateResourceLookupStore() => alwaysProvideTexture
? new AlwaysReturnsTextureStore()
protected override IResourceStore<byte[]> CreateResourceLookupStore() => provideResources
? new ResourcesTextureStore()
: new ResourceStore<byte[]>();

internal class AlwaysReturnsTextureStore : IResourceStore<byte[]>
internal class ResourcesTextureStore : IResourceStore<byte[]>
{
private const string test_image = "Resources/Textures/test-image.png";

private readonly DllResourceStore store;

public AlwaysReturnsTextureStore()
public ResourcesTextureStore()
{
store = TestResources.GetStore();
}

public void Dispose() => store.Dispose();

public byte[] Get(string name) => store.Get(test_image);
public byte[] Get(string name) => store.Get(map(name));

public Task<byte[]> GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) => store.GetAsync(test_image, cancellationToken);
public Task<byte[]> GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) => store.GetAsync(map(name), cancellationToken);

public Stream GetStream(string name) => store.GetStream(test_image);
public Stream GetStream(string name) => store.GetStream(map(name));

private string map(string name)
{
switch (name)
{
case lookup_name:
return "Resources/Textures/test-image.png";

default:
return $"Resources/{name}";
}
}

public IEnumerable<string> GetAvailableResources() => store.GetAvailableResources();
}
Expand Down
2 changes: 1 addition & 1 deletion osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ private void handleEvents(string line)
if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(path).ToLowerInvariant()))
break;

storyboard.GetLayer("Video").Add(new StoryboardVideo(path, offset));
storyboard.GetLayer("Video").Add(storyboardSprite = new StoryboardVideo(path, offset));
break;
}

Expand Down
20 changes: 15 additions & 5 deletions osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@
// See the LICENCE file in the repository root for full licence text.

using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.Video;
using osu.Game.Beatmaps;

namespace osu.Game.Storyboards.Drawables
{
Expand All @@ -23,11 +21,21 @@ public DrawableStoryboardVideo(StoryboardVideo video)
{
Video = video;

RelativeSizeAxes = Axes.Both;
// In osu-stable, a mapper can add a scale command for a storyboard video.
// This allows scaling based on the video's absolute size.
//
// If not specified we take up the full available space.
bool useRelative = !video.TimelineGroup.Scale.HasCommands;

RelativeSizeAxes = useRelative ? Axes.Both : Axes.None;
AutoSizeAxes = useRelative ? Axes.None : Axes.Both;

Anchor = Anchor.Centre;
Origin = Anchor.Centre;
}

[BackgroundDependencyLoader(true)]
private void load(IBindable<WorkingBeatmap> beatmap, TextureStore textureStore)
private void load(TextureStore textureStore)
{
var stream = textureStore.GetStream(Video.Path);

Expand All @@ -36,12 +44,14 @@ private void load(IBindable<WorkingBeatmap> beatmap, TextureStore textureStore)

InternalChild = drawableVideo = new Video(stream, false)
{
RelativeSizeAxes = Axes.Both,
RelativeSizeAxes = RelativeSizeAxes,
FillMode = FillMode.Fill,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0,
};

Video.ApplyTransforms(drawableVideo);
}

protected override void LoadComplete()
Expand Down
17 changes: 7 additions & 10 deletions osu.Game/Storyboards/StoryboardVideo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,20 @@

using osu.Framework.Graphics;
using osu.Game.Storyboards.Drawables;
using osuTK;

namespace osu.Game.Storyboards
{
public class StoryboardVideo : IStoryboardElement
public class StoryboardVideo : StoryboardSprite
{
public string Path { get; }

public bool IsDrawable => true;

public double StartTime { get; }

public StoryboardVideo(string path, double offset)
: base(path, Anchor.Centre, Vector2.Zero)
{
Path = path;
StartTime = offset;
// This is just required to get a valid StartTime based on the incoming offset.
// Actual fades are handled inside DrawableStoryboardVideo for now.
TimelineGroup.Alpha.Add(Easing.None, offset, offset, 0, 0);
}

public Drawable CreateDrawable() => new DrawableStoryboardVideo(this);
public override Drawable CreateDrawable() => new DrawableStoryboardVideo(this);
}
}
Loading