mirror of https://github.com/ppy/osu
Merge pull request #15767 from bdach/beatmap-card/track-preview
Add preview track playback function to beatmap card
This commit is contained in:
commit
1533e245de
|
@ -23,6 +23,11 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
|||
{
|
||||
public class TestSceneBeatmapCard : OsuTestScene
|
||||
{
|
||||
/// <summary>
|
||||
/// All cards on this scene use a common online ID to ensure that map download, preview tracks, etc. can be tested manually with online sources.
|
||||
/// </summary>
|
||||
private const int online_id = 163112;
|
||||
|
||||
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
||||
|
||||
private APIBeatmapSet[] testCases;
|
||||
|
@ -38,7 +43,6 @@ private void load()
|
|||
var normal = CreateAPIBeatmapSet(Ruleset.Value);
|
||||
normal.HasVideo = true;
|
||||
normal.HasStoryboard = true;
|
||||
normal.OnlineID = 241526;
|
||||
|
||||
var withStatistics = CreateAPIBeatmapSet(Ruleset.Value);
|
||||
withStatistics.Title = withStatistics.TitleUnicode = "play favourite stats";
|
||||
|
@ -106,6 +110,9 @@ private void load()
|
|||
explicitFeaturedMap,
|
||||
longName
|
||||
};
|
||||
|
||||
foreach (var testCase in testCases)
|
||||
testCase.OnlineID = online_id;
|
||||
}
|
||||
|
||||
private APIBeatmapSet getUndownloadableBeatmapSet() => new APIBeatmapSet
|
||||
|
@ -191,9 +198,9 @@ public void SetUpSteps()
|
|||
private void ensureSoleilyRemoved()
|
||||
{
|
||||
AddUntilStep("ensure manager loaded", () => beatmaps != null);
|
||||
AddStep("remove soleily", () =>
|
||||
AddStep("remove map", () =>
|
||||
{
|
||||
var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineID == 241526);
|
||||
var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineID == online_id);
|
||||
|
||||
if (beatmap != null) beatmaps.Delete(beatmap);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps.Drawables.Cards;
|
||||
using osu.Game.Beatmaps.Drawables.Cards.Buttons;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Beatmaps
|
||||
{
|
||||
public class TestSceneBeatmapCardThumbnail : OsuManualInputManagerTestScene
|
||||
{
|
||||
private PlayButton playButton => this.ChildrenOfType<PlayButton>().Single();
|
||||
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
|
||||
[Test]
|
||||
public void TestThumbnailPreview()
|
||||
{
|
||||
BeatmapCardThumbnail thumbnail = null;
|
||||
|
||||
AddStep("create thumbnail", () =>
|
||||
{
|
||||
var beatmapSet = CreateAPIBeatmapSet(Ruleset.Value);
|
||||
beatmapSet.OnlineID = 241526; // ID hardcoded to ensure that the preview track exists online.
|
||||
|
||||
Child = thumbnail = new BeatmapCardThumbnail(beatmapSet)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(200)
|
||||
};
|
||||
});
|
||||
AddStep("enable dim", () => thumbnail.Dimmed.Value = true);
|
||||
AddUntilStep("button visible", () => playButton.IsPresent);
|
||||
|
||||
AddStep("click button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(playButton);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddUntilStep("wait for start", () => playButton.Playing.Value && playButton.Enabled.Value);
|
||||
iconIs(FontAwesome.Solid.Stop);
|
||||
|
||||
AddStep("click again", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(playButton);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddUntilStep("wait for stop", () => !playButton.Playing.Value && playButton.Enabled.Value);
|
||||
iconIs(FontAwesome.Solid.Play);
|
||||
|
||||
AddStep("click again", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(playButton);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddUntilStep("wait for start", () => playButton.Playing.Value && playButton.Enabled.Value);
|
||||
iconIs(FontAwesome.Solid.Stop);
|
||||
|
||||
AddStep("disable dim", () => thumbnail.Dimmed.Value = false);
|
||||
AddWaitStep("wait some", 3);
|
||||
AddAssert("button still visible", () => playButton.IsPresent);
|
||||
|
||||
AddUntilStep("wait for track to end", () => !playButton.Playing.Value);
|
||||
AddUntilStep("button hidden", () => !playButton.IsPresent);
|
||||
}
|
||||
|
||||
private void iconIs(IconUsage usage) => AddUntilStep("icon is correct", () => playButton.ChildrenOfType<SpriteIcon>().Any(icon => icon.Icon.Equals(usage)));
|
||||
}
|
||||
}
|
|
@ -23,7 +23,6 @@
|
|||
using osuTK;
|
||||
using osu.Game.Overlays.BeatmapListing.Panels;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osuTK.Graphics;
|
||||
using DownloadButton = osu.Game.Beatmaps.Drawables.Cards.Buttons.DownloadButton;
|
||||
|
||||
namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
|
@ -42,7 +41,7 @@ public class BeatmapCard : OsuClickableContainer
|
|||
|
||||
private readonly BeatmapDownloadTracker downloadTracker;
|
||||
|
||||
private UpdateableOnlineBeatmapSetCover leftCover;
|
||||
private BeatmapCardThumbnail thumbnail;
|
||||
private FillFlowContainer leftIconArea;
|
||||
|
||||
private Container rightAreaBackground;
|
||||
|
@ -98,24 +97,17 @@ private void load()
|
|||
Colour = Colour4.White
|
||||
},
|
||||
},
|
||||
new Container
|
||||
thumbnail = new BeatmapCardThumbnail(beatmapSet)
|
||||
{
|
||||
Name = @"Left (icon) area",
|
||||
Size = new Vector2(height),
|
||||
Children = new Drawable[]
|
||||
Padding = new MarginPadding { Right = corner_radius },
|
||||
Child = leftIconArea = new FillFlowContainer
|
||||
{
|
||||
leftCover = new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.List)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
OnlineInfo = beatmapSet
|
||||
},
|
||||
leftIconArea = new FillFlowContainer
|
||||
{
|
||||
Margin = new MarginPadding(5),
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(1)
|
||||
}
|
||||
Margin = new MarginPadding(5),
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(1)
|
||||
}
|
||||
},
|
||||
new Container
|
||||
|
@ -319,10 +311,10 @@ private void load()
|
|||
};
|
||||
|
||||
if (beatmapSet.HasVideo)
|
||||
leftIconArea.Add(new IconPill(FontAwesome.Solid.Film));
|
||||
leftIconArea.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) });
|
||||
|
||||
if (beatmapSet.HasStoryboard)
|
||||
leftIconArea.Add(new IconPill(FontAwesome.Solid.Image));
|
||||
leftIconArea.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) });
|
||||
|
||||
if (beatmapSet.HasExplicitContent)
|
||||
{
|
||||
|
@ -395,10 +387,11 @@ private void updateState()
|
|||
if (IsHovered)
|
||||
targetWidth = targetWidth - icon_area_width + corner_radius;
|
||||
|
||||
thumbnail.Dimmed.Value = IsHovered;
|
||||
|
||||
mainContent.ResizeWidthTo(targetWidth, TRANSITION_DURATION, Easing.OutQuint);
|
||||
mainContentBackground.Dimmed.Value = IsHovered;
|
||||
|
||||
leftCover.FadeColour(IsHovered ? OsuColour.Gray(0.2f) : Color4.White, TRANSITION_DURATION, Easing.OutQuint);
|
||||
statisticsContainer.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint);
|
||||
|
||||
rightAreaBackground.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, TRANSITION_DURATION, Easing.OutQuint);
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps.Drawables.Cards.Buttons;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Ranking.Expanded.Accuracy;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
{
|
||||
public class BeatmapCardThumbnail : Container
|
||||
{
|
||||
public BindableBool Dimmed { get; } = new BindableBool();
|
||||
|
||||
public new MarginPadding Padding
|
||||
{
|
||||
get => foreground.Padding;
|
||||
set => foreground.Padding = value;
|
||||
}
|
||||
|
||||
private readonly UpdateableOnlineBeatmapSetCover cover;
|
||||
private readonly Container foreground;
|
||||
private readonly PlayButton playButton;
|
||||
private readonly SmoothCircularProgress progress;
|
||||
private readonly Container content;
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
|
||||
public BeatmapCardThumbnail(APIBeatmapSet beatmapSetInfo)
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
cover = new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.List)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
OnlineInfo = beatmapSetInfo
|
||||
},
|
||||
foreground = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
playButton = new PlayButton(beatmapSetInfo)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
progress = new SmoothCircularProgress
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(50),
|
||||
InnerRadius = 0.2f
|
||||
},
|
||||
content = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
progress.Colour = colourProvider.Highlight1;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
Dimmed.BindValueChanged(_ => updateState());
|
||||
|
||||
playButton.Playing.BindValueChanged(_ => updateState(), true);
|
||||
((IBindable<double>)progress.Current).BindTo(playButton.Progress);
|
||||
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
bool shouldDim = Dimmed.Value || playButton.Playing.Value;
|
||||
|
||||
playButton.FadeTo(shouldDim ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
||||
cover.FadeColour(shouldDim ? OsuColour.Gray(0.2f) : Color4.White, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
||||
{
|
||||
public class PlayButton : OsuHoverContainer
|
||||
{
|
||||
public IBindable<double> Progress => progress;
|
||||
private readonly BindableDouble progress = new BindableDouble();
|
||||
|
||||
public BindableBool Playing { get; } = new BindableBool();
|
||||
|
||||
private readonly IBeatmapSetInfo beatmapSetInfo;
|
||||
|
||||
protected override IEnumerable<Drawable> EffectTargets => icon.Yield();
|
||||
|
||||
private readonly SpriteIcon icon;
|
||||
private readonly LoadingSpinner loadingSpinner;
|
||||
|
||||
[Resolved]
|
||||
private PreviewTrackManager previewTrackManager { get; set; } = null!;
|
||||
|
||||
private PreviewTrack? previewTrack;
|
||||
|
||||
public PlayButton(IBeatmapSetInfo beatmapSetInfo)
|
||||
{
|
||||
this.beatmapSetInfo = beatmapSetInfo;
|
||||
|
||||
Anchor = Origin = Anchor.Centre;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
icon = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Icon = FontAwesome.Solid.Play,
|
||||
Size = new Vector2(14)
|
||||
},
|
||||
loadingSpinner = new LoadingSpinner
|
||||
{
|
||||
Size = new Vector2(14)
|
||||
}
|
||||
};
|
||||
|
||||
Action = () => Playing.Toggle();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
HoverColour = colours.Yellow;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Playing.BindValueChanged(updateState, true);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (Playing.Value && previewTrack != null && previewTrack.TrackLoaded)
|
||||
progress.Value = previewTrack.CurrentTime / previewTrack.Length;
|
||||
else
|
||||
progress.Value = 0;
|
||||
}
|
||||
|
||||
private void updateState(ValueChangedEvent<bool> playing)
|
||||
{
|
||||
icon.Icon = playing.NewValue ? FontAwesome.Solid.Stop : FontAwesome.Solid.Play;
|
||||
|
||||
if (!playing.NewValue)
|
||||
{
|
||||
stopPreview();
|
||||
return;
|
||||
}
|
||||
|
||||
if (previewTrack == null)
|
||||
{
|
||||
toggleLoading(true);
|
||||
LoadComponentAsync(previewTrack = previewTrackManager.Get(beatmapSetInfo), onPreviewLoaded);
|
||||
}
|
||||
else
|
||||
tryStartPreview();
|
||||
}
|
||||
|
||||
private void stopPreview()
|
||||
{
|
||||
toggleLoading(false);
|
||||
Playing.Value = false;
|
||||
previewTrack?.Stop();
|
||||
}
|
||||
|
||||
private void onPreviewLoaded(PreviewTrack loadedPreview)
|
||||
{
|
||||
// another async load might have completed before this one.
|
||||
// if so, do not make any changes.
|
||||
if (loadedPreview != previewTrack)
|
||||
return;
|
||||
|
||||
AddInternal(loadedPreview);
|
||||
toggleLoading(false);
|
||||
|
||||
loadedPreview.Stopped += () => Schedule(() => Playing.Value = false);
|
||||
|
||||
if (Playing.Value)
|
||||
tryStartPreview();
|
||||
}
|
||||
|
||||
private void tryStartPreview()
|
||||
{
|
||||
if (previewTrack?.Start() == false)
|
||||
Playing.Value = false;
|
||||
}
|
||||
|
||||
private void toggleLoading(bool loading)
|
||||
{
|
||||
Enabled.Value = !loading;
|
||||
icon.FadeTo(loading ? 0 : 1, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
||||
loadingSpinner.State.Value = loading ? Visibility.Visible : Visibility.Hidden;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue