1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 18:47:27 +08:00

Merge pull request #26446 from LeNitrous/add/replay-playback-controls

Add replay playback controls
This commit is contained in:
Dean Herbert 2024-01-17 17:06:15 +09:00 committed by GitHub
commit c1c2e61723
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 190 additions and 26 deletions

View File

@ -0,0 +1,24 @@
// 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.Localisation;
namespace osu.Game.Localisation
{
public static class PlayerSettingsOverlayStrings
{
private const string prefix = @"osu.Game.Resources.Localisation.PlaybackSettings";
/// <summary>
/// "Seek backward {0} seconds"
/// </summary>
public static LocalisableString SeekBackwardSeconds(double arg0) => new TranslatableString(getKey(@"seek_backward_seconds"), @"Seek backward {0} seconds", arg0);
/// <summary>
/// "Seek forward {0} seconds"
/// </summary>
public static LocalisableString SeekForwardSeconds(double arg0) => new TranslatableString(getKey(@"seek_forward_seconds"), @"Seek forward {0} seconds", arg0);
private static string getKey(string key) => $@"{prefix}:{key}";
}
}

View File

@ -12,17 +12,19 @@ namespace osu.Game.Screens.Play.HUD
{
private const int fade_duration = 200;
public readonly PlaybackSettings PlaybackSettings;
public readonly VisualSettings VisualSettings;
protected override Container<Drawable> Content => content;
private readonly FillFlowContainer content;
public PlayerSettingsOverlay()
{
Anchor = Anchor.TopRight;
Origin = Anchor.TopRight;
AutoSizeAxes = Axes.Both;
Child = new FillFlowContainer<PlayerSettingsGroup>
InternalChild = content = new FillFlowContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
@ -31,7 +33,6 @@ namespace osu.Game.Screens.Play.HUD
Spacing = new Vector2(0, 20),
Children = new PlayerSettingsGroup[]
{
PlaybackSettings = new PlaybackSettings { Expanded = { Value = false } },
VisualSettings = new VisualSettings { Expanded = { Value = false } },
new AudioSettings { Expanded = { Value = false } }
}
@ -40,5 +41,7 @@ namespace osu.Game.Screens.Play.HUD
protected override void PopIn() => this.FadeIn(fade_duration);
protected override void PopOut() => this.FadeOut(fade_duration);
public void AddAtStart(PlayerSettingsGroup drawable) => content.Insert(-1, drawable);
}
}

View File

@ -35,9 +35,9 @@ namespace osu.Game.Screens.Play
public readonly BindableNumber<double> UserPlaybackRate = new BindableDouble(1)
{
MinValue = 0.5,
MinValue = 0.05,
MaxValue = 2,
Precision = 0.1,
Precision = 0.01,
};
/// <summary>
@ -274,7 +274,7 @@ namespace osu.Game.Screens.Play
track.BindAdjustments(AdjustmentsFromMods);
track.AddAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust);
track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate);
track.AddAdjustment(AdjustableProperty.Frequency, UserPlaybackRate);
speedAdjustmentsApplied = true;
}
@ -286,7 +286,7 @@ namespace osu.Game.Screens.Play
track.UnbindAdjustments(AdjustmentsFromMods);
track.RemoveAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust);
track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate);
track.RemoveAdjustment(AdjustableProperty.Frequency, UserPlaybackRate);
speedAdjustmentsApplied = false;
}

View File

@ -470,9 +470,6 @@ namespace osu.Game.Screens.Play
skipOutroOverlay.Expire();
}
if (GameplayClockContainer is MasterGameplayClockContainer master)
HUDOverlay.PlayerSettingsOverlay.PlaybackSettings.UserPlaybackRate.BindTarget = master.UserPlaybackRate;
return container;
}

View File

@ -1,11 +1,19 @@
// 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;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Edit.Timing;
using osuTK;
using osu.Game.Localisation;
namespace osu.Game.Screens.Play.PlayerSettings
{
@ -15,49 +23,161 @@ namespace osu.Game.Screens.Play.PlayerSettings
public readonly Bindable<double> UserPlaybackRate = new BindableDouble(1)
{
MinValue = 0.5,
MinValue = 0.05,
MaxValue = 2,
Precision = 0.1,
Precision = 0.01,
};
private readonly PlayerSliderBar<double> rateSlider;
private readonly OsuSpriteText multiplierText;
private readonly BindableBool isPaused = new BindableBool();
[Resolved]
private GameplayClockContainer? gameplayClock { get; set; }
[Resolved]
private GameplayState? gameplayState { get; set; }
public PlaybackSettings()
: base("playback")
{
const double seek_amount = 5000;
const double seek_fast_amount = 10000;
IconButton play;
Children = new Drawable[]
{
new Container
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Horizontal = padding },
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, padding),
Children = new Drawable[]
{
new OsuSpriteText
new FillFlowContainer
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Text = "Playback speed",
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5, 0),
Children = new Drawable[]
{
new SeekButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.Solid.FastBackward,
Action = () => seek(-1, seek_fast_amount),
TooltipText = PlayerSettingsOverlayStrings.SeekBackwardSeconds(seek_fast_amount / 1000),
},
new SeekButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.Solid.Backward,
Action = () => seek(-1, seek_amount),
TooltipText = PlayerSettingsOverlayStrings.SeekBackwardSeconds(seek_amount / 1000),
},
play = new IconButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(1.4f),
IconScale = new Vector2(1.4f),
Icon = FontAwesome.Regular.PlayCircle,
Action = () =>
{
if (gameplayClock != null)
{
if (gameplayClock.IsRunning)
gameplayClock.Stop();
else
gameplayClock.Start();
}
},
},
new SeekButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.Solid.Forward,
Action = () => seek(1, seek_amount),
TooltipText = PlayerSettingsOverlayStrings.SeekForwardSeconds(seek_amount / 1000),
},
new SeekButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.Solid.FastForward,
Action = () => seek(1, seek_fast_amount),
TooltipText = PlayerSettingsOverlayStrings.SeekForwardSeconds(seek_fast_amount / 1000),
},
},
},
multiplierText = new OsuSpriteText
new Container
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Font = OsuFont.GetFont(weight: FontWeight.Bold),
}
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
rateSlider = new PlayerSliderBar<double>
{
LabelText = "Playback speed",
Current = UserPlaybackRate,
},
multiplierText = new OsuSpriteText
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Font = OsuFont.GetFont(weight: FontWeight.Bold),
Margin = new MarginPadding { Right = 20 },
}
},
},
},
},
rateSlider = new PlayerSliderBar<double> { Current = UserPlaybackRate }
};
isPaused.BindValueChanged(paused =>
{
if (!paused.NewValue)
{
play.TooltipText = ToastStrings.PauseTrack;
play.Icon = FontAwesome.Regular.PauseCircle;
}
else
{
play.TooltipText = ToastStrings.PlayTrack;
play.Icon = FontAwesome.Regular.PlayCircle;
}
}, true);
void seek(int direction, double amount)
{
double target = Math.Clamp((gameplayClock?.CurrentTime ?? 0) + (direction * amount), 0, gameplayState?.Beatmap.GetLastObjectTime() ?? 0);
gameplayClock?.Seek(target);
}
}
protected override void LoadComplete()
{
base.LoadComplete();
rateSlider.Current.BindValueChanged(multiplier => multiplierText.Text = $"{multiplier.NewValue:0.0}x", true);
rateSlider.Current.BindValueChanged(multiplier => multiplierText.Text = $"{multiplier.NewValue:0.00}x", true);
if (gameplayClock != null)
isPaused.BindTarget = gameplayClock.IsPaused;
}
private partial class SeekButton : IconButton
{
public SeekButton()
{
AddInternal(new RepeatingButtonBehaviour(this));
}
}
}
}

View File

@ -7,6 +7,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
@ -15,6 +16,7 @@ using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Screens.Ranking;
using osu.Game.Users;
@ -49,6 +51,24 @@ namespace osu.Game.Screens.Play
this.createScore = createScore;
}
[BackgroundDependencyLoader]
private void load()
{
if (!LoadedBeatmapSuccessfully)
return;
var playbackSettings = new PlaybackSettings
{
Depth = float.MaxValue,
Expanded = { Value = false }
};
if (GameplayClockContainer is MasterGameplayClockContainer master)
playbackSettings.UserPlaybackRate.BindTo(master.UserPlaybackRate);
HUDOverlay.PlayerSettingsOverlay.AddAtStart(playbackSettings);
}
protected override void PrepareReplay()
{
DrawableRuleset?.SetReplayScore(Score);