Merge remote-tracking branch 'upstream/master' into tournament-tools

This commit is contained in:
Dean Herbert
2019-04-03 16:22:41 +09:00
525 changed files with 7063 additions and 5102 deletions

View File

@ -11,6 +11,7 @@ namespace osu.Game.Screens
public class BackgroundScreenStack : ScreenStack
{
public BackgroundScreenStack()
: base(false)
{
Scale = new Vector2(1.06f);
RelativeSizeAxes = Axes.Both;

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -11,8 +12,10 @@ using osu.Game.Graphics.Containers;
namespace osu.Game.Screens.Backgrounds
{
public class BackgroundScreenBeatmap : BlurrableBackgroundScreen
public class BackgroundScreenBeatmap : BackgroundScreen
{
protected Background Background;
private WorkingBeatmap beatmap;
/// <summary>
@ -22,11 +25,34 @@ namespace osu.Game.Screens.Backgrounds
public readonly Bindable<bool> StoryboardReplacesBackground = new Bindable<bool>();
/// <summary>
/// The amount of blur to be applied in addition to user-specified blur.
/// </summary>
public readonly Bindable<float> BlurAmount = new Bindable<float>();
private readonly UserDimContainer fadeContainer;
protected virtual UserDimContainer CreateFadeContainer() => new UserDimContainer { RelativeSizeAxes = Axes.Both };
public virtual WorkingBeatmap Beatmap
public BackgroundScreenBeatmap(WorkingBeatmap beatmap = null)
{
Beatmap = beatmap;
InternalChild = fadeContainer = CreateFadeContainer();
fadeContainer.EnableUserDim.BindTo(EnableUserDim);
fadeContainer.BlurAmount.BindTo(BlurAmount);
}
[BackgroundDependencyLoader]
private void load()
{
var background = new BeatmapBackground(beatmap);
LoadComponent(background);
switchBackground(background);
}
private CancellationTokenSource cancellationSource;
public WorkingBeatmap Beatmap
{
get => beatmap;
set
@ -38,54 +64,51 @@ namespace osu.Game.Screens.Backgrounds
Schedule(() =>
{
LoadComponentAsync(new BeatmapBackground(beatmap), b => Schedule(() =>
{
float newDepth = 0;
if (Background != null)
{
newDepth = Background.Depth + 1;
Background.FinishTransforms();
Background.FadeOut(250);
Background.Expire();
}
if ((Background as BeatmapBackground)?.Beatmap == beatmap)
return;
b.Depth = newDepth;
fadeContainer.Add(Background = b);
Background.BlurSigma = BlurTarget;
StoryboardReplacesBackground.BindTo(fadeContainer.StoryboardReplacesBackground);
}));
cancellationSource?.Cancel();
LoadComponentAsync(new BeatmapBackground(beatmap), switchBackground, (cancellationSource = new CancellationTokenSource()).Token);
});
}
}
public BackgroundScreenBeatmap(WorkingBeatmap beatmap = null)
private void switchBackground(BeatmapBackground b)
{
Beatmap = beatmap;
InternalChild = fadeContainer = CreateFadeContainer();
fadeContainer.EnableUserDim.BindTo(EnableUserDim);
float newDepth = 0;
if (Background != null)
{
newDepth = Background.Depth + 1;
Background.FinishTransforms();
Background.FadeOut(250);
Background.Expire();
}
b.Depth = newDepth;
fadeContainer.Background = Background = b;
StoryboardReplacesBackground.BindTo(fadeContainer.StoryboardReplacesBackground);
}
public override bool Equals(BackgroundScreen other)
{
var otherBeatmapBackground = other as BackgroundScreenBeatmap;
if (otherBeatmapBackground == null) return false;
if (!(other is BackgroundScreenBeatmap otherBeatmapBackground)) return false;
return base.Equals(other) && beatmap == otherBeatmapBackground.Beatmap;
}
protected class BeatmapBackground : Background
{
private readonly WorkingBeatmap beatmap;
public readonly WorkingBeatmap Beatmap;
public BeatmapBackground(WorkingBeatmap beatmap)
{
this.beatmap = beatmap;
Beatmap = beatmap;
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
Sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg1");
Sprite.Texture = Beatmap?.Background ?? textures.Get(@"Backgrounds/bg1");
}
}
}

View File

@ -13,8 +13,10 @@ using osu.Game.Users;
namespace osu.Game.Screens.Backgrounds
{
public class BackgroundScreenDefault : BlurrableBackgroundScreen
public class BackgroundScreenDefault : BackgroundScreen
{
private Background background;
private int currentDisplay;
private const int background_count = 5;
@ -34,15 +36,15 @@ namespace osu.Game.Screens.Backgrounds
currentDisplay = RNG.Next(0, background_count);
Next();
display(createBackground());
}
private void display(Background newBackground)
{
Background?.FadeOut(800, Easing.InOutSine);
Background?.Expire();
background?.FadeOut(800, Easing.InOutSine);
background?.Expire();
AddInternal(Background = newBackground);
AddInternal(background = newBackground);
currentDisplay++;
}
@ -51,19 +53,21 @@ namespace osu.Game.Screens.Backgrounds
public void Next()
{
nextTask?.Cancel();
nextTask = Scheduler.AddDelayed(() =>
{
Background background;
nextTask = Scheduler.AddDelayed(() => { LoadComponentAsync(createBackground(), display); }, 100);
}
if (user.Value?.IsSupporter ?? false)
background = new SkinnedBackground(skin.Value, backgroundName);
else
background = new Background(backgroundName);
private Background createBackground()
{
Background newBackground;
background.Depth = currentDisplay;
if (user.Value?.IsSupporter ?? false)
newBackground = new SkinnedBackground(skin.Value, backgroundName);
else
newBackground = new Background(backgroundName);
LoadComponentAsync(background, display);
}, 100);
newBackground.Depth = currentDisplay;
return newBackground;
}
private class SkinnedBackground : Background

View File

@ -1,23 +0,0 @@
// 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.Graphics;
using osu.Framework.Graphics.Transforms;
using osu.Game.Graphics.Backgrounds;
using osuTK;
namespace osu.Game.Screens
{
public abstract class BlurrableBackgroundScreen : BackgroundScreen
{
protected Background Background;
protected Vector2 BlurTarget;
public TransformSequence<Background> BlurTo(Vector2 sigma, double duration, Easing easing = Easing.None)
{
BlurTarget = sigma;
return Background?.BlurTo(BlurTarget, duration, easing);
}
}
}

View File

@ -7,6 +7,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.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Timing;
@ -38,7 +39,7 @@ namespace osu.Game.Screens.Edit.Components
Origin = Anchor.Centre,
Scale = new Vector2(1.4f),
IconScale = new Vector2(1.4f),
Icon = FontAwesome.fa_play_circle_o,
Icon = FontAwesome.Regular.PlayCircle,
Action = togglePause,
Padding = new MarginPadding { Left = 20 }
},
@ -88,7 +89,7 @@ namespace osu.Game.Screens.Edit.Components
{
base.Update();
playButton.Icon = adjustableClock.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o;
playButton.Icon = adjustableClock.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle;
}
private class PlaybackTabControl : OsuTabControl<double>

View File

@ -94,13 +94,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
{
new DivisorButton
{
Icon = FontAwesome.fa_chevron_left,
Icon = FontAwesome.Solid.ChevronLeft,
Action = beatDivisor.Previous
},
new DivisorText(beatDivisor),
new DivisorButton
{
Icon = FontAwesome.fa_chevron_right,
Icon = FontAwesome.Solid.ChevronRight,
Action = beatDivisor.Next
}
},

View File

@ -4,6 +4,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osuTK;
@ -90,7 +91,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
RelativeSizeAxes = Axes.Y,
Height = 0.5f,
Icon = FontAwesome.fa_search_plus,
Icon = FontAwesome.Solid.SearchPlus,
Action = () => changeZoom(1)
},
new TimelineButton
@ -99,7 +100,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Y,
Height = 0.5f,
Icon = FontAwesome.fa_search_minus,
Icon = FontAwesome.Solid.SearchMinus,
Action = () => changeZoom(-1)
},
}

View File

@ -5,6 +5,7 @@ using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osuTK;
@ -17,7 +18,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
public Action Action;
public readonly BindableBool Enabled = new BindableBool(true);
public FontAwesome Icon
public IconUsage Icon
{
get => button.Icon;
set => button.Icon = value;

View File

@ -55,7 +55,7 @@ namespace osu.Game.Screens.Edit
{
this.host = host;
// TODO: should probably be done at a RulesetContainer level to share logic with Player.
// TODO: should probably be done at a DrawableRuleset level to share logic with Player.
var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock();
clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false };
clock.ChangeSource(sourceClock);

View File

@ -19,7 +19,7 @@ namespace osu.Game.Screens
/// <summary>
/// Whether a top-level component should be allowed to exit the current screen to, for example,
/// complete an import.
/// complete an import. Note that this can be overridden by a user if they specifically request.
/// </summary>
bool AllowExternalScreenChange { get; }

View File

@ -36,6 +36,7 @@ namespace osu.Game.Screens
logo.BeatMatching = false;
logo.Triangles = false;
logo.RelativePositionAxes = Axes.None;
logo.Origin = Anchor.BottomRight;
logo.Anchor = Anchor.BottomRight;
logo.Position = new Vector2(-40);
@ -89,7 +90,7 @@ namespace osu.Game.Screens
/// </summary>
public class ShaderPrecompiler : Drawable
{
private readonly List<Shader> loadTargets = new List<Shader>();
private readonly List<IShader> loadTargets = new List<IShader>();
public bool FinishedCompiling { get; private set; }
@ -106,7 +107,7 @@ namespace osu.Game.Screens
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE));
}
protected virtual bool AllLoaded => loadTargets.All(s => s.Loaded);
protected virtual bool AllLoaded => loadTargets.All(s => s.IsLoaded);
protected override void Update()
{

View File

@ -9,7 +9,6 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
@ -17,6 +16,7 @@ using osuTK.Input;
using osu.Framework.Extensions.Color4Extensions;
using osu.Game.Graphics.Containers;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps.ControlPoints;
@ -48,7 +48,7 @@ namespace osu.Game.Screens.Menu
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos);
public Button(string text, string sampleName, FontAwesome symbol, Color4 colour, Action clickAction = null, float extraWidth = 0, Key triggerKey = Key.Unknown)
public Button(string text, string sampleName, IconUsage symbol, Color4 colour, Action clickAction = null, float extraWidth = 0, Key triggerKey = Key.Unknown)
{
this.sampleName = sampleName;
this.clickAction = clickAction;

View File

@ -11,6 +11,7 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Framework.Logging;
using osu.Framework.Platform;
@ -79,8 +80,8 @@ namespace osu.Game.Screens.Menu
buttonArea.AddRange(new[]
{
new Button(@"settings", string.Empty, FontAwesome.fa_gear, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O),
backButton = new Button(@"back", @"button-back-select", FontAwesome.fa_osu_left_o, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH)
new Button(@"settings", string.Empty, FontAwesome.Solid.Cog, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O),
backButton = new Button(@"back", @"button-back-select", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH)
{
VisibleState = ButtonSystemState.Play,
},
@ -97,7 +98,7 @@ namespace osu.Game.Screens.Menu
private OsuGame game { get; set; }
[Resolved]
private APIAccess api { get; set; }
private IAPIProvider api { get; set; }
[Resolved(CanBeNull = true)]
private NotificationOverlay notifications { get; set; }
@ -105,17 +106,17 @@ namespace osu.Game.Screens.Menu
[BackgroundDependencyLoader(true)]
private void load(AudioManager audio, IdleTracker idleTracker, GameHost host)
{
buttonsPlay.Add(new Button(@"solo", @"button-solo-select", FontAwesome.fa_user, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P));
buttonsPlay.Add(new Button(@"multi", @"button-generic-select", FontAwesome.fa_users, new Color4(94, 63, 186, 255), onMulti, 0, Key.M));
buttonsPlay.Add(new Button(@"chart", @"button-generic-select", FontAwesome.fa_osu_charts, new Color4(80, 53, 160, 255), () => OnChart?.Invoke()));
buttonsPlay.Add(new Button(@"solo", @"button-solo-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P));
buttonsPlay.Add(new Button(@"multi", @"button-generic-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMulti, 0, Key.M));
buttonsPlay.Add(new Button(@"chart", @"button-generic-select", OsuIcon.Charts, new Color4(80, 53, 160, 255), () => OnChart?.Invoke()));
buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play);
buttonsTopLevel.Add(new Button(@"play", @"button-play-select", FontAwesome.fa_osu_logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P));
buttonsTopLevel.Add(new Button(@"osu!editor", @"button-generic-select", FontAwesome.fa_osu_edit_o, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E));
buttonsTopLevel.Add(new Button(@"osu!direct", @"button-direct-select", FontAwesome.fa_osu_chevron_down_o, new Color4(165, 204, 0, 255), () => OnDirect?.Invoke(), 0, Key.D));
buttonsTopLevel.Add(new Button(@"play", @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P));
buttonsTopLevel.Add(new Button(@"osu!editor", @"button-generic-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E));
buttonsTopLevel.Add(new Button(@"osu!direct", @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnDirect?.Invoke(), 0, Key.D));
if (host.CanExit)
buttonsTopLevel.Add(new Button(@"exit", string.Empty, FontAwesome.fa_osu_cross_o, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q));
buttonsTopLevel.Add(new Button(@"exit", string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q));
buttonArea.AddRange(buttonsPlay);
buttonArea.AddRange(buttonsTopLevel);
@ -134,7 +135,7 @@ namespace osu.Game.Screens.Menu
notifications?.Post(new SimpleNotification
{
Text = "You gotta be logged in to multi 'yo!",
Icon = FontAwesome.fa_globe
Icon = FontAwesome.Solid.Globe
});
return;

View File

@ -2,11 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
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.Framework.Screens;
using osu.Game.Graphics;
@ -25,16 +26,17 @@ namespace osu.Game.Screens.Menu
private SpriteIcon icon;
private Color4 iconColour;
private LinkFlowContainer textFlow;
private LinkFlowContainer supportFlow;
public override bool HideOverlaysOnEnter => true;
public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled;
public override bool CursorVisible => false;
private readonly List<Drawable> supporterDrawables = new List<Drawable>();
private Drawable heart;
private const float icon_y = -85;
private const float icon_size = 30;
private readonly Bindable<User> currentUser = new Bindable<User>();
@ -44,7 +46,7 @@ namespace osu.Game.Screens.Menu
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, APIAccess api)
private void load(OsuColour colours, IAPIProvider api)
{
InternalChildren = new Drawable[]
{
@ -52,20 +54,40 @@ namespace osu.Game.Screens.Menu
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.fa_warning,
Size = new Vector2(30),
Icon = FontAwesome.Solid.ExclamationTriangle,
Size = new Vector2(icon_size),
Y = icon_y,
},
textFlow = new LinkFlowContainer
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding(50),
TextAnchor = Anchor.TopCentre,
Y = -110,
Direction = FillDirection.Vertical,
Y = icon_y + icon_size,
Anchor = Anchor.Centre,
Origin = Anchor.TopCentre,
Spacing = new Vector2(0, 2),
Children = new Drawable[]
{
textFlow = new LinkFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
TextAnchor = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Spacing = new Vector2(0, 2),
},
supportFlow = new LinkFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
TextAnchor = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Alpha = 0,
Spacing = new Vector2(0, 2),
},
}
}
};
@ -88,28 +110,45 @@ namespace osu.Game.Screens.Menu
textFlow.NewParagraph();
textFlow.NewParagraph();
supporterDrawables.AddRange(textFlow.AddText("Consider becoming an ", format));
supporterDrawables.AddRange(textFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", creationParameters: format));
supporterDrawables.AddRange(textFlow.AddText(" to help support the game", format));
supporterDrawables.Add(heart = textFlow.AddIcon(FontAwesome.fa_heart, t =>
{
t.Padding = new MarginPadding { Left = 5 };
t.Font = t.Font.With(size: 12);
t.Colour = colours.Pink;
t.Origin = Anchor.Centre;
}).First());
iconColour = colours.Yellow;
currentUser.BindTo(api.LocalUser);
currentUser.BindValueChanged(e =>
{
supportFlow.Children.ForEach(d => d.FadeOut().Expire());
if (e.NewValue.IsSupporter)
supporterDrawables.ForEach(d => d.FadeOut(500, Easing.OutQuint).Expire());
{
supportFlow.AddText("Thank you for supporting osu!", format);
}
else
{
supportFlow.AddText("Consider becoming an ", format);
supportFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", creationParameters: format);
supportFlow.AddText(" to help support the game", format);
}
heart = supportFlow.AddIcon(FontAwesome.Solid.Heart, t =>
{
t.Padding = new MarginPadding { Left = 5 };
t.Font = t.Font.With(size: 12);
t.Origin = Anchor.Centre;
t.Colour = colours.Pink;
}).First();
if (IsLoaded)
animateHeart();
if (supportFlow.IsPresent)
supportFlow.FadeInFromZero(500);
}, true);
}
private void animateHeart()
{
heart.FlashColour(Color4.White, 750, Easing.OutQuint).Loop();
}
protected override void LoadComplete()
{
base.LoadComplete();
@ -128,7 +167,9 @@ namespace osu.Game.Screens.Menu
.MoveToY(icon_y, 160, Easing.InCirc)
.RotateTo(0, 160, Easing.InCirc);
supporterDrawables.ForEach(d => d.FadeOut().Delay(2000).FadeIn(500));
supportFlow.FadeOut().Delay(2000).FadeIn(500);
animateHeart();
this
.FadeInFromZero(500)
@ -136,8 +177,6 @@ namespace osu.Game.Screens.Menu
.FadeOut(250)
.ScaleTo(0.9f, 250, Easing.InQuint)
.Finally(d => this.Push(intro));
heart.FlashColour(Color4.White, 750, Easing.OutQuint).Loop();
}
}
}

View File

@ -12,9 +12,13 @@ using osu.Framework.Graphics.Shaders;
using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Skinning;
using osu.Game.Online.API;
using osu.Game.Users;
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
namespace osu.Game.Screens.Menu
{
@ -63,21 +67,28 @@ namespace osu.Game.Screens.Menu
private readonly float[] frequencyAmplitudes = new float[256];
private Shader shader;
private IShader shader;
private readonly Texture texture;
private Bindable<User> user;
private Bindable<Skin> skin;
public LogoVisualisation()
{
texture = Texture.WhitePixel;
AccentColour = new Color4(1, 1, 1, 0.2f);
Blending = BlendingMode.Additive;
}
[BackgroundDependencyLoader]
private void load(ShaderManager shaders, IBindable<WorkingBeatmap> beatmap)
private void load(ShaderManager shaders, IBindable<WorkingBeatmap> beatmap, IAPIProvider api, SkinManager skinManager)
{
this.beatmap.BindTo(beatmap);
shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED);
user = api.LocalUser.GetBoundCopy();
skin = skinManager.CurrentSkin.GetBoundCopy();
user.ValueChanged += _ => updateColour();
skin.BindValueChanged(_ => updateColour(), true);
}
private void updateAmplitudes()
@ -107,6 +118,16 @@ namespace osu.Game.Screens.Menu
Scheduler.AddDelayed(updateAmplitudes, time_between_updates);
}
private void updateColour()
{
Color4 defaultColour = Color4.White.Opacity(0.2f);
if (user.Value?.IsSupporter ?? false)
AccentColour = skin.Value.GetValue<SkinConfiguration, Color4?>(s => s.CustomColours.ContainsKey("MenuGlow") ? s.CustomColours["MenuGlow"] : (Color4?)null) ?? defaultColour;
else
AccentColour = defaultColour;
}
protected override void LoadComplete()
{
base.LoadComplete();
@ -131,8 +152,6 @@ namespace osu.Game.Screens.Menu
protected override DrawNode CreateDrawNode() => new VisualisationDrawNode();
private readonly VisualiserSharedData sharedData = new VisualiserSharedData();
protected override void ApplyDrawNode(DrawNode node)
{
base.ApplyDrawNode(node);
@ -142,29 +161,23 @@ namespace osu.Game.Screens.Menu
visNode.Shader = shader;
visNode.Texture = texture;
visNode.Size = DrawSize.X;
visNode.Shared = sharedData;
visNode.Colour = AccentColour;
visNode.AudioData = frequencyAmplitudes;
}
private class VisualiserSharedData
{
public readonly QuadBatch<TexturedVertex2D> VertexBatch = new QuadBatch<TexturedVertex2D>(100, 10);
}
private class VisualisationDrawNode : DrawNode
{
public Shader Shader;
public IShader Shader;
public Texture Texture;
public VisualiserSharedData Shared;
//Asuming the logo is a circle, we don't need a second dimension.
public float Size;
public Color4 Colour;
public float[] AudioData;
private readonly QuadBatch<TexturedVertex2D> vertexBatch = new QuadBatch<TexturedVertex2D>(100, 10);
public override void Draw(Action<TexturedVertex2D> vertexAction)
{
base.Draw(vertexAction);
@ -209,7 +222,7 @@ namespace osu.Game.Screens.Menu
rectangle,
colourInfo,
null,
Shared.VertexBatch.AddAction,
vertexBatch.AddAction,
//barSize by itself will make it smooth more in the X axis than in the Y axis, this reverts that.
Vector2.Divide(inflation, barSize.Yx));
}
@ -218,6 +231,13 @@ namespace osu.Game.Screens.Menu
Shader.Unbind();
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
vertexBatch.Dispose();
}
}
}
}

View File

@ -37,7 +37,9 @@ namespace osu.Game.Screens.Menu
[Resolved]
private GameHost host { get; set; }
protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault();
private BackgroundScreenDefault background;
protected override BackgroundScreen CreateBackground() => background;
[BackgroundDependencyLoader(true)]
private void load(OsuGame game = null)
@ -86,6 +88,7 @@ namespace osu.Game.Screens.Menu
buttons.OnDirect = game.ToggleDirect;
}
LoadComponentAsync(background = new BackgroundScreenDefault());
preloadSongSelect();
}

View File

@ -12,6 +12,9 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Skinning;
using osu.Game.Online.API;
using osu.Game.Users;
using System;
using osu.Framework.Bindables;
@ -32,6 +35,12 @@ namespace osu.Game.Screens.Menu
private const double box_fade_in_time = 65;
private const int box_width = 200;
private Bindable<User> user;
private Bindable<Skin> skin;
[Resolved]
private OsuColour colours { get; set; }
public MenuSideFlashes()
{
EarlyActivationMilliseconds = box_fade_in_time;
@ -42,13 +51,12 @@ namespace osu.Game.Screens.Menu
}
[BackgroundDependencyLoader]
private void load(IBindable<WorkingBeatmap> beatmap, OsuColour colours)
private void load(IBindable<WorkingBeatmap> beatmap, IAPIProvider api, SkinManager skinManager)
{
this.beatmap.BindTo(beatmap);
// linear colour looks better in this case, so let's use it for now.
Color4 gradientDark = colours.Blue.Opacity(0).ToLinear();
Color4 gradientLight = colours.Blue.Opacity(0.6f).ToLinear();
user = api.LocalUser.GetBoundCopy();
skin = skinManager.CurrentSkin.GetBoundCopy();
Children = new Drawable[]
{
@ -62,8 +70,7 @@ namespace osu.Game.Screens.Menu
// align off-screen to make sure our edges don't become visible during parallax.
X = -box_width,
Alpha = 0,
Blending = BlendingMode.Additive,
Colour = ColourInfo.GradientHorizontal(gradientLight, gradientDark)
Blending = BlendingMode.Additive
},
rightBox = new Box
{
@ -74,10 +81,12 @@ namespace osu.Game.Screens.Menu
Height = 1.5f,
X = box_width,
Alpha = 0,
Blending = BlendingMode.Additive,
Colour = ColourInfo.GradientHorizontal(gradientDark, gradientLight)
Blending = BlendingMode.Additive
}
};
user.ValueChanged += _ => updateColour();
skin.BindValueChanged(_ => updateColour(), true);
}
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
@ -97,5 +106,20 @@ namespace osu.Game.Screens.Menu
.Then()
.FadeOut(beatLength, Easing.In);
}
private void updateColour()
{
Color4 baseColour = colours.Blue;
if (user.Value?.IsSupporter ?? false)
baseColour = skin.Value.GetValue<SkinConfiguration, Color4?>(s => s.CustomColours.ContainsKey("MenuGlow") ? s.CustomColours["MenuGlow"] : (Color4?)null) ?? baseColour;
// linear colour looks better in this case, so let's use it for now.
Color4 gradientDark = baseColour.Opacity(0).ToLinear();
Color4 gradientLight = baseColour.Opacity(0.6f).ToLinear();
leftBox.Colour = ColourInfo.GradientHorizontal(gradientLight, gradientDark);
rightBox.Colour = ColourInfo.GradientHorizontal(gradientDark, gradientLight);
}
}
}

View File

@ -15,7 +15,7 @@ namespace osu.Game.Screens.Multi.Components
private const float height = 30;
private const float transition_duration = 100;
private Container rulesetContainer;
private Container drawableRuleset;
public ModeTypeInfo()
{
@ -35,7 +35,7 @@ namespace osu.Game.Screens.Multi.Components
LayoutDuration = 100,
Children = new[]
{
rulesetContainer = new Container
drawableRuleset = new Container
{
AutoSizeAxes = Axes.Both,
},
@ -55,11 +55,11 @@ namespace osu.Game.Screens.Multi.Components
{
if (item?.Beatmap != null)
{
rulesetContainer.FadeIn(transition_duration);
rulesetContainer.Child = new DifficultyIcon(item.Beatmap, item.Ruleset) { Size = new Vector2(height) };
drawableRuleset.FadeIn(transition_duration);
drawableRuleset.Child = new DifficultyIcon(item.Beatmap, item.Ruleset) { Size = new Vector2(height) };
}
else
rulesetContainer.FadeOut(transition_duration);
drawableRuleset.FadeOut(transition_duration);
}
}
}

View File

@ -9,6 +9,13 @@ namespace osu.Game.Screens.Multi.Components
{
public class MultiplayerBackgroundSprite : MultiplayerComposite
{
private readonly BeatmapSetCoverType beatmapSetCoverType;
public MultiplayerBackgroundSprite(BeatmapSetCoverType beatmapSetCoverType = BeatmapSetCoverType.Cover)
{
this.beatmapSetCoverType = beatmapSetCoverType;
}
[BackgroundDependencyLoader]
private void load()
{
@ -19,6 +26,6 @@ namespace osu.Game.Screens.Multi.Components
CurrentItem.BindValueChanged(item => sprite.Beatmap.Value = item.NewValue?.Beatmap, true);
}
protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both };
protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(beatmapSetCoverType) { RelativeSizeAxes = Axes.Both };
}
}

View File

@ -7,10 +7,8 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Screens;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.SearchableList;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Multi
@ -19,11 +17,11 @@ namespace osu.Game.Screens.Multi
{
public const float HEIGHT = 121;
private readonly OsuSpriteText screenType;
private readonly HeaderBreadcrumbControl breadcrumbs;
public Header(ScreenStack stack)
{
MultiHeaderTitle title;
RelativeSizeAxes = Axes.X;
Height = HEIGHT;
@ -40,39 +38,11 @@ namespace osu.Game.Screens.Multi
Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING + OsuScreen.HORIZONTAL_OVERFLOW_PADDING },
Children = new Drawable[]
{
new FillFlowContainer
title = new MultiHeaderTitle
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.BottomLeft,
Position = new Vector2(-35f, 5f),
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10f, 0f),
Children = new Drawable[]
{
new SpriteIcon
{
Size = new Vector2(25),
Icon = FontAwesome.fa_osu_multi,
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new[]
{
new OsuSpriteText
{
Text = "multiplayer ",
Font = OsuFont.GetFont(size: 25)
},
screenType = new OsuSpriteText
{
Font = OsuFont.GetFont(weight: FontWeight.Light, size: 25)
},
},
},
},
X = -35,
},
breadcrumbs = new HeaderBreadcrumbControl(stack)
{
@ -84,10 +54,10 @@ namespace osu.Game.Screens.Multi
},
};
breadcrumbs.Current.ValueChanged += scren =>
breadcrumbs.Current.ValueChanged += screen =>
{
if (scren.NewValue is IMultiplayerSubScreen multiScreen)
screenType.Text = multiScreen.ShortTitle.ToLowerInvariant();
if (screen.NewValue is IMultiplayerSubScreen multiScreen)
title.Screen = multiScreen;
};
breadcrumbs.Current.TriggerChange();
@ -96,10 +66,25 @@ namespace osu.Game.Screens.Multi
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
screenType.Colour = colours.Yellow;
breadcrumbs.StripColour = colours.Green;
}
private class MultiHeaderTitle : ScreenTitle
{
public IMultiplayerSubScreen Screen
{
set => Section = value.ShortTitle.ToLowerInvariant();
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Title = "multiplayer";
Icon = OsuIcon.Multi;
AccentColour = colours.Yellow;
}
}
private class HeaderBreadcrumbControl : ScreenBreadcrumbControl
{
public HeaderBreadcrumbControl(ScreenStack stack)

View File

@ -11,6 +11,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
@ -76,6 +77,8 @@ namespace osu.Game.Screens.Multi.Lounge.Components
}
}
public bool FilteringActive { get; set; }
public DrawableRoom(Room room)
{
Room = room;
@ -137,7 +140,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components
Width = cover_width,
Masking = true,
Margin = new MarginPadding { Left = side_strip_width },
Child = new MultiplayerBackgroundSprite { RelativeSizeAxes = Axes.Both }
Child = new MultiplayerBackgroundSprite(BeatmapSetCoverType.List) { RelativeSizeAxes = Axes.Both }
},
new Container
{

View File

@ -246,7 +246,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components
}
[Resolved]
private APIAccess api { get; set; }
private IAPIProvider api { get; set; }
private GetRoomScoresRequest request;

View File

@ -107,6 +107,14 @@ namespace osu.Game.Screens.Multi.Lounge
Filter.Search.HoldFocus = false;
}
public override void OnResuming(IScreen last)
{
base.OnResuming(last);
if (currentRoom.Value?.RoomID.Value == null)
currentRoom.Value = new Room();
}
private void joinRequested(Room room)
{
processingOverlay.Show();

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
@ -13,6 +14,7 @@ using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Online.Multiplayer;
using osu.Game.Overlays.SearchableList;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Multi.Components;
using osu.Game.Screens.Play.HUD;
using osuTK;
@ -108,7 +110,7 @@ namespace osu.Game.Screens.Multi.Match.Components
},
};
CurrentItem.BindValueChanged(item => modDisplay.Current.Value = item.NewValue?.RequiredMods, true);
CurrentItem.BindValueChanged(item => modDisplay.Current.Value = item.NewValue?.RequiredMods ?? Enumerable.Empty<Mod>(), true);
beatmapButton.Action = () => RequestBeatmapSelection?.Invoke();
}

View File

@ -28,13 +28,15 @@ namespace osu.Game.Screens.Multi.Match.Components
{
base.LoadComplete();
roomId.BindValueChanged(_ => updateChannel(), true);
channelId.BindValueChanged(_ => updateChannel(), true);
}
private void updateChannel()
{
if (roomId.Value != null)
Channel.Value = channelManager?.JoinChannel(new Channel { Id = channelId.Value, Type = ChannelType.Multiplayer, Name = $"#mp_{roomId.Value}" });
if (roomId.Value == null || channelId.Value == 0)
return;
Channel.Value = channelManager?.JoinChannel(new Channel { Id = channelId.Value, Type = ChannelType.Multiplayer, Name = $"#lazermp_{roomId.Value}" });
}
}
}

View File

@ -3,7 +3,7 @@
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Game.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Leaderboards;
using osu.Game.Scoring;
@ -25,9 +25,9 @@ namespace osu.Game.Screens.Multi.Match.Components
protected override IEnumerable<LeaderboardScoreStatistic> GetStatistics(ScoreInfo model) => new[]
{
new LeaderboardScoreStatistic(FontAwesome.fa_crosshairs, "Accuracy", string.Format(model.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", model.Accuracy)),
new LeaderboardScoreStatistic(FontAwesome.fa_refresh, "Total Attempts", ((APIRoomScoreInfo)model).TotalAttempts.ToString()),
new LeaderboardScoreStatistic(FontAwesome.fa_check, "Completed Beatmaps", ((APIRoomScoreInfo)model).CompletedBeatmaps.ToString()),
new LeaderboardScoreStatistic(FontAwesome.Solid.Crosshairs, "Accuracy", string.Format(model.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", model.Accuracy)),
new LeaderboardScoreStatistic(FontAwesome.Solid.Sync, "Total Attempts", ((APIRoomScoreInfo)model).TotalAttempts.ToString()),
new LeaderboardScoreStatistic(FontAwesome.Solid.Check, "Completed Beatmaps", ((APIRoomScoreInfo)model).CompletedBeatmaps.ToString()),
};
}
}

View File

@ -316,8 +316,12 @@ namespace osu.Game.Screens.Multi.Match.Components
private class SettingsTextBox : OsuTextBox
{
protected override Color4 BackgroundUnfocused => Color4.Black;
protected override Color4 BackgroundFocused => Color4.Black;
[BackgroundDependencyLoader]
private void load()
{
BackgroundUnfocused = Color4.Black;
BackgroundFocused = Color4.Black;
}
}
private class SettingsNumberTextBox : SettingsTextBox
@ -327,8 +331,12 @@ namespace osu.Game.Screens.Multi.Match.Components
private class SettingsPasswordTextBox : OsuPasswordTextBox
{
protected override Color4 BackgroundUnfocused => Color4.Black;
protected override Color4 BackgroundFocused => Color4.Black;
[BackgroundDependencyLoader]
private void load()
{
BackgroundUnfocused = Color4.Black;
BackgroundFocused = Color4.Black;
}
}
private class SectionContainer : FillFlowContainer<Section>

View File

@ -52,12 +52,18 @@ namespace osu.Game.Screens.Multi.Match.Components
Participants.BindValueChanged(participants =>
{
usersFlow.Children = participants.NewValue.Select(u => new UserPanel(u)
usersFlow.Children = participants.NewValue.Select(u =>
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Width = 300,
OnLoadComplete = d => d.FadeInFromZero(60),
var panel = new UserPanel(u)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Width = 300,
};
panel.OnLoadComplete += d => d.FadeInFromZero(60);
return panel;
}).ToList();
}, true);
}

View File

@ -54,7 +54,7 @@ namespace osu.Game.Screens.Multi.Match.Components
hasBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmap.OnlineBeatmapID) != null;
}
private void beatmapAdded(BeatmapSetInfo model, bool existing, bool silent)
private void beatmapAdded(BeatmapSetInfo model, bool existing)
{
if (Beatmap.Value == null)
return;

View File

@ -202,7 +202,7 @@ namespace osu.Game.Screens.Multi.Match
/// <summary>
/// Handle the case where a beatmap is imported (and can be used by this match).
/// </summary>
private void beatmapAdded(BeatmapSetInfo model, bool existing, bool silent) => Schedule(() =>
private void beatmapAdded(BeatmapSetInfo model, bool existing) => Schedule(() =>
{
if (Beatmap.Value != beatmapManager.DefaultBeatmap)
return;

View File

@ -54,7 +54,7 @@ namespace osu.Game.Screens.Multi
private OsuGameBase game { get; set; }
[Resolved]
private APIAccess api { get; set; }
private IAPIProvider api { get; set; }
[Resolved(CanBeNull = true)]
private OsuLogo logo { get; set; }
@ -95,7 +95,7 @@ namespace osu.Game.Screens.Multi
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = Header.HEIGHT },
Child = screenStack = new ScreenStack(loungeSubScreen = new LoungeSubScreen()) { RelativeSizeAxes = Axes.Both }
Child = screenStack = new OsuScreenStack(loungeSubScreen = new LoungeSubScreen()) { RelativeSizeAxes = Axes.Both }
},
new Header(screenStack),
createButton = new HeaderButton
@ -163,7 +163,7 @@ namespace osu.Game.Screens.Multi
this.Push(new PlayerLoader(player));
}
public void APIStateChanged(APIAccess api, APIState state)
public void APIStateChanged(IAPIProvider api, APIState state)
{
if (state != APIState.Online)
forcefullyExit();

View File

@ -14,8 +14,6 @@ namespace osu.Game.Screens.Multi
{
public override bool DisallowExternalBeatmapRulesetChanges => false;
public override bool RemoveWhenNotAlive => false;
public virtual string ShortTitle => Title;
[Resolved(CanBeNull = true)]

View File

@ -32,7 +32,7 @@ namespace osu.Game.Screens.Multi.Play
private readonly PlaylistItem playlistItem;
[Resolved]
private APIAccess api { get; set; }
private IAPIProvider api { get; set; }
[Resolved]
private IBindable<RulesetInfo> ruleset { get; set; }

View File

@ -1,8 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Scoring;
using osu.Game.Screens.Multi.Ranking.Pages;
using osu.Game.Screens.Ranking;
@ -20,7 +20,7 @@ namespace osu.Game.Screens.Multi.Ranking.Types
this.beatmap = beatmap;
}
public FontAwesome Icon => FontAwesome.fa_users;
public IconUsage Icon => FontAwesome.Solid.Users;
public string Name => "Room Leaderboard";

View File

@ -30,7 +30,7 @@ namespace osu.Game.Screens.Multi
private Bindable<FilterCriteria> currentFilter { get; set; }
[Resolved]
private APIAccess api { get; set; }
private IAPIProvider api { get; set; }
[Resolved]
private RulesetStore rulesets { get; set; }

View File

@ -0,0 +1,48 @@
// 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.Graphics;
using osu.Framework.Screens;
using osu.Game.Graphics.Containers;
namespace osu.Game.Screens
{
public class OsuScreenStack : ScreenStack
{
[Cached]
private BackgroundScreenStack backgroundScreenStack;
private ParallaxContainer parallaxContainer;
protected float ParallaxAmount => parallaxContainer.ParallaxAmount;
public OsuScreenStack()
{
initializeStack();
}
public OsuScreenStack(IScreen baseScreen)
: base(baseScreen)
{
initializeStack();
}
private void initializeStack()
{
InternalChild = parallaxContainer = new ParallaxContainer
{
RelativeSizeAxes = Axes.Both,
Child = backgroundScreenStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both },
};
ScreenPushed += onScreenChange;
ScreenExited += onScreenChange;
}
private void onScreenChange(IScreen prev, IScreen next)
{
parallaxContainer.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * ((IOsuScreen)next)?.BackgroundParallaxAmount ?? 1.0f;
}
}
}

View File

@ -4,6 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osuTK;
@ -13,7 +14,7 @@ namespace osu.Game.Screens.Play.Break
{
private readonly SpriteIcon icon;
public FontAwesome Icon
public IconUsage Icon
{
set => icon.Icon = value;
get => icon.Icon;

View File

@ -3,6 +3,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Containers;
using osuTK;
@ -41,7 +42,7 @@ namespace osu.Game.Screens.Play.Break
Anchor = Anchor.Centre,
Origin = Anchor.CentreRight,
X = -glow_icon_offscreen_offset,
Icon = Graphics.FontAwesome.fa_chevron_right,
Icon = FontAwesome.Solid.ChevronRight,
BlurSigma = new Vector2(glow_icon_blur_sigma),
Size = new Vector2(glow_icon_size),
},
@ -50,7 +51,7 @@ namespace osu.Game.Screens.Play.Break
Anchor = Anchor.Centre,
Origin = Anchor.CentreLeft,
X = glow_icon_offscreen_offset,
Icon = Graphics.FontAwesome.fa_chevron_left,
Icon = FontAwesome.Solid.ChevronLeft,
BlurSigma = new Vector2(glow_icon_blur_sigma),
Size = new Vector2(glow_icon_size),
},
@ -67,7 +68,7 @@ namespace osu.Game.Screens.Play.Break
Origin = Anchor.CentreRight,
Alpha = 0.7f,
X = -blurred_icon_offscreen_offset,
Icon = Graphics.FontAwesome.fa_chevron_right,
Icon = FontAwesome.Solid.ChevronRight,
BlurSigma = new Vector2(blurred_icon_blur_sigma),
Size = new Vector2(blurred_icon_size),
},
@ -77,7 +78,7 @@ namespace osu.Game.Screens.Play.Break
Origin = Anchor.CentreLeft,
Alpha = 0.7f,
X = blurred_icon_offscreen_offset,
Icon = Graphics.FontAwesome.fa_chevron_left,
Icon = FontAwesome.Solid.ChevronLeft,
BlurSigma = new Vector2(blurred_icon_blur_sigma),
Size = new Vector2(blurred_icon_size),
},

View File

@ -4,6 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osuTK;
@ -30,7 +31,7 @@ namespace osu.Game.Screens.Play.Break
set => blurredIcon.BlurSigma = value;
}
public FontAwesome Icon
public IconUsage Icon
{
get => spriteIcon.Icon;
set => spriteIcon.Icon = blurredIcon.Icon = value;

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@ -40,13 +41,7 @@ namespace osu.Game.Screens.Play
private readonly BreakInfo info;
private readonly BreakArrows breakArrows;
public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor)
: this(letterboxing)
{
bindProcessor(scoreProcessor);
}
public BreakOverlay(bool letterboxing)
public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor = null)
{
RelativeSizeAxes = Axes.Both;
Child = fadeContainer = new Container
@ -98,6 +93,14 @@ namespace osu.Game.Screens.Play
}
}
};
if (scoreProcessor != null) bindProcessor(scoreProcessor);
}
[BackgroundDependencyLoader(true)]
private void load(GameplayClock clock)
{
if (clock != null) Clock = clock;
}
protected override void LoadComplete()

View File

@ -0,0 +1,46 @@
// 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.Bindables;
using osu.Framework.Timing;
namespace osu.Game.Screens.Play
{
/// <summary>
/// A clock which is used for gameplay elements that need to follow audio time 1:1.
/// Exposed via DI by <see cref="PausableGameplayContainer"/>.
/// <remarks>
/// The main purpose of this clock is to stop components using it from accidentally processing the main
/// <see cref="IFrameBasedClock"/>, as this should only be done once to ensure accuracy.
/// </remarks>
/// </summary>
public class GameplayClock : IFrameBasedClock
{
private readonly IFrameBasedClock underlyingClock;
public readonly BindableBool IsPaused = new BindableBool();
public GameplayClock(IFrameBasedClock underlyingClock)
{
this.underlyingClock = underlyingClock;
}
public double CurrentTime => underlyingClock.CurrentTime;
public double Rate => underlyingClock.Rate;
public bool IsRunning => underlyingClock.IsRunning;
public void ProcessFrame()
{
// we do not want to process the underlying clock.
// this is handled by PauseContainer.
}
public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime;
public double FramesPerSecond => underlyingClock.FramesPerSecond;
public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo;
}
}

View File

@ -0,0 +1,161 @@
// 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 System.Linq;
using System.Threading.Tasks;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Screens.Play
{
/// <summary>
/// Encapsulates gameplay timing logic and provides a <see cref="Play.GameplayClock"/> for children.
/// </summary>
public class GameplayClockContainer : Container
{
private readonly WorkingBeatmap beatmap;
/// <summary>
/// The original source (usually a <see cref="WorkingBeatmap"/>'s track).
/// </summary>
private readonly IAdjustableClock sourceClock;
public readonly BindableBool IsPaused = new BindableBool();
/// <summary>
/// The decoupled clock used for gameplay. Should be used for seeks and clock control.
/// </summary>
private readonly DecoupleableInterpolatingFramedClock adjustableClock;
public readonly Bindable<double> UserPlaybackRate = new BindableDouble(1)
{
Default = 1,
MinValue = 0.5,
MaxValue = 2,
Precision = 0.1,
};
/// <summary>
/// The final clock which is exposed to underlying components.
/// </summary>
[Cached]
public readonly GameplayClock GameplayClock;
private Bindable<double> userAudioOffset;
private readonly FramedOffsetClock offsetClock;
public GameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime)
{
this.beatmap = beatmap;
RelativeSizeAxes = Axes.Both;
sourceClock = (IAdjustableClock)beatmap.Track ?? new StopwatchClock();
adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
adjustableClock.Seek(Math.Min(0, gameplayStartTime - beatmap.BeatmapInfo.AudioLeadIn));
adjustableClock.ProcessFrame();
// Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited.
// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck.
var platformOffsetClock = new FramedOffsetClock(adjustableClock) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 22 : 0 };
// the final usable gameplay clock with user-set offsets applied.
offsetClock = new FramedOffsetClock(platformOffsetClock);
// the clock to be exposed via DI to children.
GameplayClock = new GameplayClock(offsetClock);
GameplayClock.IsPaused.BindTo(IsPaused);
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
userAudioOffset = config.GetBindable<double>(OsuSetting.AudioOffset);
userAudioOffset.BindValueChanged(offset => offsetClock.Offset = offset.NewValue, true);
UserPlaybackRate.ValueChanged += _ => updateRate();
}
public void Restart()
{
Task.Run(() =>
{
sourceClock.Reset();
Schedule(() =>
{
adjustableClock.ChangeSource(sourceClock);
updateRate();
this.Delay(750).Schedule(() =>
{
if (!IsPaused.Value)
{
adjustableClock.Start();
}
});
});
});
}
public void Start()
{
// Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time
// This accounts for the audio clock source potentially taking time to enter a completely stopped state
adjustableClock.Seek(adjustableClock.CurrentTime);
adjustableClock.Start();
IsPaused.Value = false;
}
public void Seek(double time) => adjustableClock.Seek(time);
public void Stop()
{
adjustableClock.Stop();
IsPaused.Value = true;
}
public void ResetLocalAdjustments()
{
// In the case of replays, we may have changed the playback rate.
UserPlaybackRate.Value = 1;
}
protected override void Update()
{
if (!IsPaused.Value)
offsetClock.ProcessFrame();
base.Update();
}
private void updateRate()
{
if (sourceClock == null) return;
sourceClock.ResetSpeedAdjustments();
if (sourceClock is IHasTempoAdjust tempo)
tempo.TempoAdjust = UserPlaybackRate.Value;
else
sourceClock.Rate = UserPlaybackRate.Value;
foreach (var mod in beatmap.Mods.Value.OfType<IApplicableToClock>())
mod.ApplyToClock(sourceClock);
}
}
}

View File

@ -38,9 +38,15 @@ namespace osu.Game.Screens.Play
/// <summary>
/// Action that is invoked when <see cref="GlobalAction.Back"/> is triggered.
/// </summary>
protected virtual Action BackAction => () => InternalButtons.Children.Last().Click();
protected virtual Action BackAction => () => InternalButtons.Children.LastOrDefault()?.Click();
/// <summary>
/// Action that is invoked when <see cref="GlobalAction.Select"/> is triggered.
/// </summary>
protected virtual Action SelectAction => () => InternalButtons.Children.FirstOrDefault(f => f.Selected.Value)?.Click();
public abstract string Header { get; }
public abstract string Description { get; }
protected internal FillFlowContainer<DialogButton> InternalButtons;
@ -229,16 +235,30 @@ namespace osu.Game.Screens.Play
public bool OnPressed(GlobalAction action)
{
if (action == GlobalAction.Back)
switch (action)
{
BackAction.Invoke();
return true;
case GlobalAction.Back:
BackAction.Invoke();
return true;
case GlobalAction.Select:
SelectAction.Invoke();
return true;
}
return false;
}
public bool OnReleased(GlobalAction action) => action == GlobalAction.Back;
public bool OnReleased(GlobalAction action)
{
switch (action)
{
case GlobalAction.Back:
case GlobalAction.Select:
return true;
}
return false;
}
private void buttonSelectionChanged(DialogButton button, bool isSelected)
{
@ -288,15 +308,6 @@ namespace osu.Game.Screens.Play
Selected.Value = true;
return base.OnMouseMove(e);
}
protected override bool OnKeyDown(KeyDownEvent e)
{
if (e.Repeat || e.Key != Key.Enter || !Selected.Value)
return false;
Click();
return true;
}
}
}
}

View File

@ -7,6 +7,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
@ -92,30 +93,6 @@ namespace osu.Game.Screens.Play.HUD
public Action HoverGained;
public Action HoverLost;
public bool OnPressed(GlobalAction action)
{
switch (action)
{
case GlobalAction.Back:
BeginConfirm();
return true;
}
return false;
}
public bool OnReleased(GlobalAction action)
{
switch (action)
{
case GlobalAction.Back:
AbortConfirm();
return true;
}
return false;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
@ -152,7 +129,7 @@ namespace osu.Game.Screens.Play.HUD
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(15),
Icon = FontAwesome.fa_close
Icon = FontAwesome.Solid.TimesCircle
},
}
};
@ -178,7 +155,7 @@ namespace osu.Game.Screens.Play.HUD
// avoid starting a new confirm call until we finish animating.
pendingAnimation = true;
Progress.Value = 0;
AbortConfirm();
overlayCircle.ScaleTo(0, 100)
.Then().FadeOut().ScaleTo(1).FadeIn(500)
@ -207,6 +184,31 @@ namespace osu.Game.Screens.Play.HUD
base.OnHoverLost(e);
}
public bool OnPressed(GlobalAction action)
{
switch (action)
{
case GlobalAction.Back:
if (!pendingAnimation)
BeginConfirm();
return true;
}
return false;
}
public bool OnReleased(GlobalAction action)
{
switch (action)
{
case GlobalAction.Back:
AbortConfirm();
return true;
}
return false;
}
protected override bool OnMouseDown(MouseDownEvent e)
{
if (!pendingAnimation && e.CurrentState.Mouse.Buttons.Count() == 1)

View File

@ -19,7 +19,9 @@ namespace osu.Game.Screens.Play.HUD
public readonly PlaybackSettings PlaybackSettings;
public readonly VisualSettings VisualSettings;
//public readonly CollectionSettings CollectionSettings;
//public readonly DiscussionSettings DiscussionSettings;
public PlayerSettingsOverlay()

View File

@ -1,12 +1,12 @@
// 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.Input.Events;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
@ -24,7 +24,7 @@ namespace osu.Game.Screens.Play
{
private const int duration = 100;
public readonly KeyCounterCollection KeyCounter;
public readonly KeyCounterDisplay KeyCounter;
public readonly RollingCounter<int> ComboCounter;
public readonly ScoreCounter ScoreCounter;
public readonly RollingCounter<double> AccuracyCounter;
@ -40,7 +40,9 @@ namespace osu.Game.Screens.Play
private static bool hasShownNotificationOnce;
public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, WorkingBeatmap working, IClock offsetClock, IAdjustableClock adjustableClock)
public Action<double> RequestSeek;
public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, WorkingBeatmap working)
{
RelativeSizeAxes = Axes.Both;
@ -81,23 +83,20 @@ namespace osu.Game.Screens.Play
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
KeyCounter = CreateKeyCounter(adjustableClock as IFrameBasedClock),
KeyCounter = CreateKeyCounter(),
HoldToQuit = CreateHoldForMenuButton(),
}
}
};
BindProcessor(scoreProcessor);
BindRulesetContainer(rulesetContainer);
BindDrawableRuleset(drawableRuleset);
Progress.Objects = rulesetContainer.Objects;
Progress.AudioClock = offsetClock;
Progress.AllowSeeking = rulesetContainer.HasReplayLoaded.Value;
Progress.OnSeek = pos => adjustableClock.Seek(pos);
Progress.Objects = drawableRuleset.Objects;
Progress.AllowSeeking = drawableRuleset.HasReplayLoaded.Value;
Progress.RequestSeek = time => RequestSeek(time);
ModDisplay.Current.BindTo(working.Mods);
PlayerSettingsOverlay.PlaybackSettings.AdjustableClock = adjustableClock;
}
[BackgroundDependencyLoader(true)]
@ -144,13 +143,13 @@ namespace osu.Game.Screens.Play
}
}
protected virtual void BindRulesetContainer(RulesetContainer rulesetContainer)
protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset)
{
(rulesetContainer.KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(KeyCounter);
(drawableRuleset as ICanAttachKeyCounter)?.Attach(KeyCounter);
replayLoaded.BindTo(rulesetContainer.HasReplayLoaded);
replayLoaded.BindTo(drawableRuleset.HasReplayLoaded);
Progress.BindRulestContainer(rulesetContainer);
Progress.BindDrawableRuleset(drawableRuleset);
}
protected override bool OnKeyDown(KeyDownEvent e)
@ -202,13 +201,12 @@ namespace osu.Game.Screens.Play
Margin = new MarginPadding { Top = 20 }
};
protected virtual KeyCounterCollection CreateKeyCounter(IFrameBasedClock offsetClock) => new KeyCounterCollection
protected virtual KeyCounterDisplay CreateKeyCounter() => new KeyCounterDisplay
{
FadeTime = 50,
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
Margin = new MarginPadding(10),
AudioClock = offsetClock
};
protected virtual SongProgress CreateProgress() => new SongProgress

View File

@ -71,9 +71,12 @@ namespace osu.Game.Screens.Play
Name = name;
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
[BackgroundDependencyLoader(true)]
private void load(TextureStore textures, GameplayClock clock)
{
if (clock != null)
Clock = clock;
Children = new Drawable[]
{
buttonSprite = new Sprite

View File

@ -8,21 +8,20 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Framework.Timing;
using osu.Game.Configuration;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Play
{
public class KeyCounterCollection : FillFlowContainer<KeyCounter>
public class KeyCounterDisplay : FillFlowContainer<KeyCounter>
{
private const int duration = 100;
public readonly Bindable<bool> Visible = new Bindable<bool>(true);
private readonly Bindable<bool> configVisibility = new Bindable<bool>();
public KeyCounterCollection()
public KeyCounterDisplay()
{
Direction = FillDirection.Horizontal;
AutoSizeAxes = Axes.Both;
@ -37,9 +36,6 @@ namespace osu.Game.Screens.Play
key.FadeTime = FadeTime;
key.KeyDownTextColor = KeyDownTextColor;
key.KeyUpTextColor = KeyUpTextColor;
// Use the same clock object as SongProgress for saving KeyCounter state
if (AudioClock != null)
key.Clock = AudioClock;
}
public void ResetCount()
@ -125,8 +121,6 @@ namespace osu.Game.Screens.Play
public override bool HandleNonPositionalInput => receptor == null;
public override bool HandlePositionalInput => receptor == null;
public IFrameBasedClock AudioClock { get; set; }
private Receptor receptor;
public Receptor GetReceptor()
@ -144,9 +138,9 @@ namespace osu.Game.Screens.Play
public class Receptor : Drawable
{
protected readonly KeyCounterCollection Target;
protected readonly KeyCounterDisplay Target;
public Receptor(KeyCounterCollection target)
public Receptor(KeyCounterDisplay target)
{
RelativeSizeAxes = Axes.Both;
Depth = float.MinValue;

View File

@ -1,149 +0,0 @@
// 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 System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Graphics;
using osuTK.Graphics;
namespace osu.Game.Screens.Play
{
/// <summary>
/// A container which handles pausing children, displaying a pause overlay with choices etc.
/// This alleviates a lot of the intricate pause logic from being in <see cref="Player"/>
/// </summary>
public class PauseContainer : Container
{
public readonly BindableBool IsPaused = new BindableBool();
public Func<bool> CheckCanPause;
private const double pause_cooldown = 1000;
private double lastPauseActionTime;
private readonly PauseOverlay pauseOverlay;
private readonly Container content;
protected override Container<Drawable> Content => content;
public int Retries
{
set => pauseOverlay.Retries = value;
}
public bool CanPause => (CheckCanPause?.Invoke() ?? true) && Time.Current >= lastPauseActionTime + pause_cooldown;
public bool IsResuming { get; private set; }
public Action OnRetry;
public Action OnQuit;
private readonly FramedClock framedClock;
private readonly DecoupleableInterpolatingFramedClock decoupledClock;
/// <summary>
/// Creates a new <see cref="PauseContainer"/>.
/// </summary>
/// <param name="framedClock">The gameplay clock. This is the clock that will process frames.</param>
/// <param name="decoupledClock">The seekable clock. This is the clock that will be paused and resumed.</param>
public PauseContainer(FramedClock framedClock, DecoupleableInterpolatingFramedClock decoupledClock)
{
this.framedClock = framedClock;
this.decoupledClock = decoupledClock;
RelativeSizeAxes = Axes.Both;
AddInternal(content = new Container
{
Clock = this.framedClock,
ProcessCustomClock = false,
RelativeSizeAxes = Axes.Both
});
AddInternal(pauseOverlay = new PauseOverlay
{
OnResume = () =>
{
IsResuming = true;
this.Delay(400).Schedule(Resume);
},
OnRetry = () => OnRetry(),
OnQuit = () => OnQuit(),
});
}
public void Pause(bool force = false) => Schedule(() => // Scheduled to ensure a stable position in execution order, no matter how it was called.
{
if (!CanPause && !force) return;
if (IsPaused.Value) return;
// stop the seekable clock (stops the audio eventually)
decoupledClock.Stop();
IsPaused.Value = true;
pauseOverlay.Show();
lastPauseActionTime = Time.Current;
});
public void Resume()
{
if (!IsPaused.Value) return;
IsPaused.Value = false;
IsResuming = false;
lastPauseActionTime = Time.Current;
// Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time
// This accounts for the audio clock source potentially taking time to enter a completely stopped state
decoupledClock.Seek(decoupledClock.CurrentTime);
decoupledClock.Start();
pauseOverlay.Hide();
}
private OsuGameBase game;
[BackgroundDependencyLoader]
private void load(OsuGameBase game)
{
this.game = game;
}
protected override void Update()
{
// eagerly pause when we lose window focus (if we are locally playing).
if (!game.IsActive.Value && CanPause)
Pause();
if (!IsPaused.Value)
framedClock.ProcessFrame();
base.Update();
}
public class PauseOverlay : GameplayMenuOverlay
{
public Action OnResume;
public override string Header => "paused";
public override string Description => "you're not going to do what i think you're going to do, are ya?";
protected override Action BackAction => () => InternalButtons.Children.First().Click();
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
AddButton("Continue", colours.Green, () => OnResume?.Invoke());
AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke());
AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke());
}
}
}
}

View File

@ -0,0 +1,29 @@
// 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 System.Linq;
using osu.Framework.Allocation;
using osu.Game.Graphics;
using osuTK.Graphics;
namespace osu.Game.Screens.Play
{
public class PauseOverlay : GameplayMenuOverlay
{
public Action OnResume;
public override string Header => "paused";
public override string Description => "you're not going to do what i think you're going to do, are ya?";
protected override Action BackAction => () => InternalButtons.Children.First().Click();
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
AddButton("Continue", colours.Green, () => OnResume?.Invoke());
AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke());
AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke());
}
}
}

View File

@ -3,24 +3,19 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Input.Events;
using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Framework.Threading;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Cursor;
using osu.Game.Online.API;
using osu.Game.Overlays;
using osu.Game.Rulesets;
@ -34,7 +29,7 @@ using osu.Game.Storyboards.Drawables;
namespace osu.Game.Screens.Play
{
public class Player : ScreenWithBeatmapBackground, IProvideCursor
public class Player : ScreenWithBeatmapBackground
{
protected override bool AllowBackButton => false; // handled by HoldForMenuButton
@ -48,179 +43,108 @@ namespace osu.Game.Screens.Play
public bool HasFailed { get; private set; }
public bool AllowPause { get; set; } = true;
public bool AllowLeadIn { get; set; } = true;
public bool AllowResults { get; set; } = true;
public bool PauseOnFocusLost { get; set; } = true;
private Bindable<bool> mouseWheelDisabled;
private Bindable<double> userAudioOffset;
private readonly Bindable<bool> storyboardReplacesBackground = new Bindable<bool>();
public int RestartCount;
public CursorContainer Cursor => RulesetContainer.Cursor;
public bool ProvidingUserCursor => RulesetContainer?.Cursor != null && !RulesetContainer.HasReplayLoaded.Value;
private IAdjustableClock sourceClock;
/// <summary>
/// The decoupled clock used for gameplay. Should be used for seeks and clock control.
/// </summary>
private DecoupleableInterpolatingFramedClock adjustableClock;
[Resolved]
private ScoreManager scoreManager { get; set; }
protected PauseContainer PauseContainer { get; private set; }
private RulesetInfo ruleset;
private APIAccess api;
private IAPIProvider api;
private SampleChannel sampleRestart;
protected ScoreProcessor ScoreProcessor { get; private set; }
protected RulesetContainer RulesetContainer { get; private set; }
protected DrawableRuleset DrawableRuleset { get; private set; }
protected HUDOverlay HUDOverlay { get; private set; }
private FailOverlay failOverlay;
private DrawableStoryboard storyboard;
protected UserDimContainer StoryboardContainer { get; private set; }
public bool LoadedBeatmapSuccessfully => DrawableRuleset?.Objects.Any() == true;
protected virtual UserDimContainer CreateStoryboardContainer() => new UserDimContainer(true)
protected GameplayClockContainer GameplayClockContainer { get; private set; }
private readonly bool allowPause;
private readonly bool showResults;
/// <summary>
/// Create a new player instance.
/// </summary>
/// <param name="allowPause">Whether pausing should be allowed. If not allowed, attempting to pause will quit.</param>
/// <param name="showResults">Whether results screen should be pushed on completion.</param>
public Player(bool allowPause = true, bool showResults = true)
{
RelativeSizeAxes = Axes.Both,
Alpha = 1,
EnableUserDim = { Value = true }
};
public bool LoadedBeatmapSuccessfully => RulesetContainer?.Objects.Any() == true;
this.allowPause = allowPause;
this.showResults = showResults;
}
[BackgroundDependencyLoader]
private void load(AudioManager audio, APIAccess api, OsuConfigManager config)
private void load(AudioManager audio, IAPIProvider api, OsuConfigManager config)
{
this.api = api;
WorkingBeatmap working = Beatmap.Value;
if (working is DummyWorkingBeatmap)
WorkingBeatmap working = loadBeatmap();
if (working == null)
return;
sampleRestart = audio.Sample.Get(@"Gameplay/restart");
mouseWheelDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableWheel);
userAudioOffset = config.GetBindable<double>(OsuSetting.AudioOffset);
showStoryboard = config.GetBindable<bool>(OsuSetting.ShowStoryboard);
IBeatmap beatmap;
try
{
beatmap = working.Beatmap;
if (beatmap == null)
throw new InvalidOperationException("Beatmap was not loaded");
ruleset = Ruleset.Value ?? beatmap.BeatmapInfo.Ruleset;
var rulesetInstance = ruleset.CreateInstance();
try
{
RulesetContainer = rulesetInstance.CreateRulesetContainerWith(working);
}
catch (BeatmapInvalidForRulesetException)
{
// we may fail to create a RulesetContainer if the beatmap cannot be loaded with the user's preferred ruleset
// let's try again forcing the beatmap's ruleset.
ruleset = beatmap.BeatmapInfo.Ruleset;
rulesetInstance = ruleset.CreateInstance();
RulesetContainer = rulesetInstance.CreateRulesetContainerWith(Beatmap.Value);
}
if (!RulesetContainer.Objects.Any())
{
Logger.Log("Beatmap contains no hit objects!", level: LogLevel.Error);
return;
}
}
catch (Exception e)
{
Logger.Error(e, "Could not load beatmap sucessfully!");
//couldn't load, hard abort!
return;
}
sourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock();
adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
adjustableClock.Seek(AllowLeadIn
? Math.Min(0, RulesetContainer.GameplayStartTime - beatmap.BeatmapInfo.AudioLeadIn)
: RulesetContainer.GameplayStartTime);
adjustableClock.ProcessFrame();
// Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited.
// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck.
var platformOffsetClock = new FramedOffsetClock(adjustableClock) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 22 : 0 };
// the final usable gameplay clock with user-set offsets applied.
var offsetClock = new FramedOffsetClock(platformOffsetClock);
userAudioOffset.ValueChanged += offset => offsetClock.Offset = offset.NewValue;
userAudioOffset.TriggerChange();
ScoreProcessor = RulesetContainer.CreateScoreProcessor();
ScoreProcessor = DrawableRuleset.CreateScoreProcessor();
if (!ScoreProcessor.Mode.Disabled)
config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode);
InternalChildren = new Drawable[]
InternalChild = GameplayClockContainer = new GameplayClockContainer(working, DrawableRuleset.GameplayStartTime);
GameplayClockContainer.Children = new[]
{
PauseContainer = new PauseContainer(offsetClock, adjustableClock)
StoryboardContainer = CreateStoryboardContainer(),
new ScalingContainer(ScalingMode.Gameplay)
{
Retries = RestartCount,
OnRetry = Restart,
OnQuit = performUserRequestedExit,
CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded.Value,
Children = new Container[]
Child = new LocalSkinOverrideContainer(working.Skin)
{
StoryboardContainer = CreateStoryboardContainer(),
new ScalingContainer(ScalingMode.Gameplay)
{
Child = new LocalSkinOverrideContainer(working.Skin)
{
RelativeSizeAxes = Axes.Both,
Child = RulesetContainer
}
},
new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
ProcessCustomClock = false,
Breaks = beatmap.Breaks
},
new ScalingContainer(ScalingMode.Gameplay)
{
Child = RulesetContainer.Cursor?.CreateProxy() ?? new Container(),
},
HUDOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working, offsetClock, adjustableClock)
{
Clock = Clock, // hud overlay doesn't want to use the audio clock directly
ProcessCustomClock = false,
Anchor = Anchor.Centre,
Origin = Anchor.Centre
},
new SkipOverlay(RulesetContainer.GameplayStartTime)
{
Clock = Clock, // skip button doesn't want to use the audio clock directly
ProcessCustomClock = false,
AdjustableClock = adjustableClock,
FramedClock = offsetClock,
},
RelativeSizeAxes = Axes.Both,
Child = DrawableRuleset
}
},
failOverlay = new FailOverlay
new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Breaks = working.Beatmap.Breaks
},
// display the cursor above some HUD elements.
DrawableRuleset.Cursor?.CreateProxy() ?? new Container(),
HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, working)
{
HoldToQuit = { Action = performUserRequestedExit },
PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } },
KeyCounter = { Visible = { BindTarget = DrawableRuleset.HasReplayLoaded } },
RequestSeek = GameplayClockContainer.Seek,
Anchor = Anchor.Centre,
Origin = Anchor.Centre
},
new SkipOverlay(DrawableRuleset.GameplayStartTime)
{
RequestSeek = GameplayClockContainer.Seek
},
FailOverlay = new FailOverlay
{
OnRetry = Restart,
OnQuit = performUserRequestedExit,
},
PauseOverlay = new PauseOverlay
{
OnResume = Resume,
Retries = RestartCount,
OnRetry = Restart,
OnQuit = performUserRequestedExit,
},
@ -236,13 +160,11 @@ namespace osu.Game.Screens.Play
}
};
HUDOverlay.HoldToQuit.Action = performUserRequestedExit;
HUDOverlay.KeyCounter.Visible.BindTo(RulesetContainer.HasReplayLoaded);
// bind clock into components that require it
DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused);
RulesetContainer.IsPaused.BindTo(PauseContainer.IsPaused);
if (ShowStoryboard.Value)
initializeStoryboard(false);
// load storyboard as part of player's load if we can
initializeStoryboard(false);
// Bind ScoreProcessor to ourselves
ScoreProcessor.AllJudged += onCompletion;
@ -252,13 +174,49 @@ namespace osu.Game.Screens.Play
mod.ApplyToScoreProcessor(ScoreProcessor);
}
private void applyRateFromMods()
private WorkingBeatmap loadBeatmap()
{
if (sourceClock == null) return;
WorkingBeatmap working = Beatmap.Value;
if (working is DummyWorkingBeatmap)
return null;
sourceClock.Rate = 1;
foreach (var mod in Beatmap.Value.Mods.Value.OfType<IApplicableToClock>())
mod.ApplyToClock(sourceClock);
try
{
var beatmap = working.Beatmap;
if (beatmap == null)
throw new InvalidOperationException("Beatmap was not loaded");
ruleset = Ruleset.Value ?? beatmap.BeatmapInfo.Ruleset;
var rulesetInstance = ruleset.CreateInstance();
try
{
DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(working);
}
catch (BeatmapInvalidForRulesetException)
{
// we may fail to create a DrawableRuleset if the beatmap cannot be loaded with the user's preferred ruleset
// let's try again forcing the beatmap's ruleset.
ruleset = beatmap.BeatmapInfo.Ruleset;
rulesetInstance = ruleset.CreateInstance();
DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(Beatmap.Value);
}
if (!DrawableRuleset.Objects.Any())
{
Logger.Log("Beatmap contains no hit objects!", level: LogLevel.Error);
return null;
}
}
catch (Exception e)
{
Logger.Error(e, "Could not load beatmap sucessfully!");
//couldn't load, hard abort!
return null;
}
return working;
}
private void performUserRequestedExit()
@ -288,7 +246,7 @@ namespace osu.Game.Screens.Play
ValidForResume = false;
if (!AllowResults) return;
if (!showResults) return;
using (BeginDelayedSequence(1000))
{
@ -297,8 +255,8 @@ namespace osu.Game.Screens.Play
if (!this.IsCurrentScreen()) return;
var score = CreateScore();
if (RulesetContainer.ReplayScore == null)
scoreManager.Import(score, true);
if (DrawableRuleset.ReplayScore == null)
scoreManager.Import(score);
this.Push(CreateResults(score));
@ -309,7 +267,7 @@ namespace osu.Game.Screens.Play
protected virtual ScoreInfo CreateScore()
{
var score = RulesetContainer.ReplayScore?.ScoreInfo ?? new ScoreInfo
var score = DrawableRuleset.ReplayScore?.ScoreInfo ?? new ScoreInfo
{
Beatmap = Beatmap.Value.BeatmapInfo,
Ruleset = ruleset,
@ -322,19 +280,148 @@ namespace osu.Game.Screens.Play
return score;
}
protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value;
protected virtual Results CreateResults(ScoreInfo score) => new SoloResults(score);
#region Storyboard
private DrawableStoryboard storyboard;
protected UserDimContainer StoryboardContainer { get; private set; }
protected virtual UserDimContainer CreateStoryboardContainer() => new UserDimContainer(true)
{
RelativeSizeAxes = Axes.Both,
Alpha = 1,
EnableUserDim = { Value = true }
};
private Bindable<bool> showStoryboard;
private void initializeStoryboard(bool asyncLoad)
{
if (StoryboardContainer == null || storyboard != null)
return;
if (!showStoryboard.Value)
return;
var beatmap = Beatmap.Value;
storyboard = beatmap.Storyboard.CreateDrawable();
storyboard.Masking = true;
if (asyncLoad)
LoadComponentAsync(storyboard, StoryboardContainer.Add);
else
StoryboardContainer.Add(storyboard);
}
#endregion
#region Fail Logic
protected FailOverlay FailOverlay { get; private set; }
private bool onFail()
{
if (Beatmap.Value.Mods.Value.OfType<IApplicableFailOverride>().Any(m => !m.AllowFail))
return false;
adjustableClock.Stop();
GameplayClockContainer.Stop();
HasFailed = true;
failOverlay.Retries = RestartCount;
failOverlay.Show();
// There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer)
// could process an extra frame after the GameplayClock is stopped.
// In such cases we want the fail state to precede a user triggered pause.
if (PauseOverlay.State == Visibility.Visible)
PauseOverlay.Hide();
FailOverlay.Retries = RestartCount;
FailOverlay.Show();
return true;
}
#endregion
#region Pause Logic
public bool IsResuming { get; private set; }
/// <summary>
/// The amount of gameplay time after which a second pause is allowed.
/// </summary>
private const double pause_cooldown = 1000;
protected PauseOverlay PauseOverlay { get; private set; }
private double? lastPauseActionTime;
private bool canPause =>
// must pass basic screen conditions (beatmap loaded, instance allows pause)
LoadedBeatmapSuccessfully && allowPause && ValidForResume
// replays cannot be paused and exit immediately
&& !DrawableRuleset.HasReplayLoaded.Value
// cannot pause if we are already in a fail state
&& !HasFailed
// cannot pause if already paused (or in a cooldown state) unless we are in a resuming state.
&& (IsResuming || (GameplayClockContainer.IsPaused.Value == false && !pauseCooldownActive));
private bool pauseCooldownActive =>
lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown;
private bool canResume =>
// cannot resume from a non-paused state
GameplayClockContainer.IsPaused.Value
// cannot resume if we are already in a fail state
&& !HasFailed
// already resuming
&& !IsResuming;
protected override void Update()
{
base.Update();
// eagerly pause when we lose window focus (if we are locally playing).
if (PauseOnFocusLost && !Game.IsActive.Value)
Pause();
}
public void Pause()
{
if (!canPause) return;
IsResuming = false;
GameplayClockContainer.Stop();
PauseOverlay.Show();
lastPauseActionTime = GameplayClockContainer.GameplayClock.CurrentTime;
}
public void Resume()
{
if (!canResume) return;
IsResuming = true;
PauseOverlay.Hide();
// time-based conditions may allow instant resume.
if (GameplayClockContainer.GameplayClock.CurrentTime < Beatmap.Value.Beatmap.HitObjects.First().StartTime)
completeResume();
else
DrawableRuleset.RequestResume(completeResume);
void completeResume()
{
GameplayClockContainer.Start();
IsResuming = false;
}
}
#endregion
#region Screen Logic
public override void OnEntering(IScreen last)
{
base.OnEntering(last);
@ -349,39 +436,18 @@ namespace osu.Game.Screens.Play
.Delay(250)
.FadeIn(250);
ShowStoryboard.ValueChanged += enabled =>
{
if (enabled.NewValue) initializeStoryboard(true);
};
showStoryboard.ValueChanged += _ => initializeStoryboard(true);
Background.EnableUserDim.Value = true;
Background.BlurAmount.Value = 0;
Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground);
StoryboardContainer.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground);
storyboardReplacesBackground.Value = Beatmap.Value.Storyboard.ReplacesBackground && Beatmap.Value.Storyboard.HasDrawable;
Task.Run(() =>
{
sourceClock.Reset();
Schedule(() =>
{
adjustableClock.ChangeSource(sourceClock);
applyRateFromMods();
this.Delay(750).Schedule(() =>
{
if (!PauseContainer.IsPaused.Value)
{
adjustableClock.Start();
}
});
});
});
PauseContainer.Alpha = 0;
PauseContainer.FadeIn(750, Easing.OutQuint);
GameplayClockContainer.Restart();
GameplayClockContainer.FadeInFromZero(750, Easing.OutQuint);
}
public override void OnSuspending(IScreen next)
@ -399,18 +465,20 @@ namespace osu.Game.Screens.Play
return true;
}
if ((!AllowPause || HasFailed || !ValidForResume || PauseContainer?.IsPaused.Value != false || RulesetContainer?.HasReplayLoaded.Value != false) && (!PauseContainer?.IsResuming ?? true))
if (canPause)
{
// In the case of replays, we may have changed the playback rate.
applyRateFromMods();
fadeOut();
return base.OnExiting(next);
Pause();
return true;
}
if (LoadedBeatmapSuccessfully)
PauseContainer?.Pause();
if (pauseCooldownActive && !GameplayClockContainer.IsPaused.Value)
// still want to block if we are within the cooldown period and not already paused.
return true;
return true;
GameplayClockContainer.ResetLocalAdjustments();
fadeOut();
return base.OnExiting(next);
}
private void fadeOut(bool instant = false)
@ -422,24 +490,6 @@ namespace osu.Game.Screens.Play
storyboardReplacesBackground.Value = false;
}
protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !PauseContainer.IsPaused.Value;
private void initializeStoryboard(bool asyncLoad)
{
if (StoryboardContainer == null || storyboard != null)
return;
var beatmap = Beatmap.Value;
storyboard = beatmap.Storyboard.CreateDrawable();
storyboard.Masking = true;
if (asyncLoad)
LoadComponentAsync(storyboard, StoryboardContainer.Add);
else
StoryboardContainer.Add(storyboard);
}
protected virtual Results CreateResults(ScoreInfo score) => new SoloResults(score);
#endregion
}
}

View File

@ -8,7 +8,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Input;
using osu.Framework.Localisation;
using osu.Framework.Screens;
using osu.Framework.Threading;
@ -17,6 +17,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.PlayerSettings;
using osuTK;
using osuTK.Graphics;
@ -25,8 +26,9 @@ namespace osu.Game.Screens.Play
{
public class PlayerLoader : ScreenWithBeatmapBackground
{
protected const float BACKGROUND_BLUR = 15;
private readonly Func<Player> createPlayer;
private static readonly Vector2 background_blur = new Vector2(15);
private Player player;
@ -41,6 +43,8 @@ namespace osu.Game.Screens.Play
private Task loadTask;
private InputManager inputManager;
public PlayerLoader(Func<Player> createPlayer)
{
this.createPlayer = createPlayer;
@ -132,6 +136,7 @@ namespace osu.Game.Screens.Play
base.OnEntering(last);
content.ScaleTo(0.7f);
Background?.FadeColour(Color4.White, 800, Easing.OutQuint);
contentIn();
@ -150,43 +155,20 @@ namespace osu.Game.Screens.Play
logo.Delay(resuming ? 0 : 500).MoveToOffset(new Vector2(0, -0.24f), 500, Easing.InOutExpo);
}
protected override void LoadComplete()
{
inputManager = GetContainingInputManager();
base.LoadComplete();
}
private ScheduledDelegate pushDebounce;
protected VisualSettings VisualSettings;
// Hhere because IsHovered will not update unless we do so.
public override bool HandlePositionalInput => true;
private bool readyForPush => player.LoadState == LoadState.Ready && IsHovered && GetContainingInputManager()?.DraggedDrawable == null;
protected override bool OnHover(HoverEvent e)
{
// restore our screen defaults
if (this.IsCurrentScreen())
{
InitializeBackgroundElements();
Background.EnableUserDim.Value = false;
}
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
if (GetContainingInputManager()?.HoveredDrawables.Contains(VisualSettings) == true)
{
// Update background elements is only being called here because blur logic still exists in Player.
// Will need to be removed when resolving https://github.com/ppy/osu/issues/4322
UpdateBackgroundElements();
if (this.IsCurrentScreen())
Background.EnableUserDim.Value = true;
}
base.OnHoverLost(e);
}
protected override void InitializeBackgroundElements()
{
Background?.FadeColour(Color4.White, BACKGROUND_FADE_DURATION, Easing.OutQuint);
Background?.BlurTo(background_blur, BACKGROUND_FADE_DURATION, Easing.OutQuint);
}
private void pushWhenLoaded()
{
if (!this.IsCurrentScreen()) return;
@ -265,6 +247,29 @@ namespace osu.Game.Screens.Play
}
}
protected override void Update()
{
base.Update();
if (!this.IsCurrentScreen())
return;
// We need to perform this check here rather than in OnHover as any number of children of VisualSettings
// may also be handling the hover events.
if (inputManager.HoveredDrawables.Contains(VisualSettings))
{
// Preview user-defined background dim and blur when hovered on the visual settings panel.
Background.EnableUserDim.Value = true;
Background.BlurAmount.Value = 0;
}
else
{
// Returns background dim and blur to the values specified by PlayerLoader.
Background.EnableUserDim.Value = false;
Background.BlurAmount.Value = BACKGROUND_BLUR;
}
}
private class BeatmapMetadataDisplay : Container
{
private class MetadataLine : Container
@ -296,6 +301,7 @@ namespace osu.Game.Screens.Play
private readonly WorkingBeatmap beatmap;
private LoadingAnimation loading;
private Sprite backgroundSprite;
private ModDisplay modDisplay;
public bool Loading
{
@ -322,7 +328,7 @@ namespace osu.Game.Screens.Play
[BackgroundDependencyLoader]
private void load()
{
var metadata = beatmap?.BeatmapInfo?.Metadata ?? new BeatmapMetadata();
var metadata = beatmap.BeatmapInfo?.Metadata ?? new BeatmapMetadata();
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
@ -391,6 +397,14 @@ namespace osu.Game.Screens.Play
Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
},
new ModDisplay
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = 20 },
Current = beatmap.Mods
}
},
}
};

View File

@ -4,7 +4,6 @@
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
@ -16,7 +15,13 @@ namespace osu.Game.Screens.Play.PlayerSettings
protected override string Title => @"playback";
public IAdjustableClock AdjustableClock { set; get; }
public readonly Bindable<double> UserPlaybackRate = new BindableDouble(1)
{
Default = 1,
MinValue = 0.5,
MaxValue = 2,
Precision = 0.1,
};
private readonly PlayerSliderBar<double> rateSlider;
@ -47,31 +52,13 @@ namespace osu.Game.Screens.Play.PlayerSettings
}
},
},
rateSlider = new PlayerSliderBar<double>
{
Bindable = new BindableDouble(1)
{
Default = 1,
MinValue = 0.5,
MaxValue = 2,
Precision = 0.1,
},
}
rateSlider = new PlayerSliderBar<double> { Bindable = UserPlaybackRate }
};
}
protected override void LoadComplete()
{
base.LoadComplete();
if (AdjustableClock == null)
return;
var clockRate = AdjustableClock.Rate;
// can't trigger this line instantly as the underlying clock may not be ready to accept adjustments yet.
rateSlider.Bindable.ValueChanged += multiplier => AdjustableClock.Rate = clockRate * multiplier.NewValue;
rateSlider.Bindable.BindValueChanged(multiplier => multiplierText.Text = $"{multiplier.NewValue:0.0}x", true);
}
}

View File

@ -5,6 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
@ -103,7 +104,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
Origin = Anchor.Centre,
Anchor = Anchor.CentreRight,
Position = new Vector2(-15, 0),
Icon = FontAwesome.fa_bars,
Icon = FontAwesome.Solid.Bars,
Scale = new Vector2(0.75f),
Action = () => Expanded = !Expanded,
},

View File

@ -9,7 +9,8 @@ namespace osu.Game.Screens.Play
{
private readonly Score score;
public ReplayPlayer(Score score)
public ReplayPlayer(Score score, bool allowPause = true, bool showResults = true)
: base(allowPause, showResults)
{
this.score = score;
}
@ -17,7 +18,7 @@ namespace osu.Game.Screens.Play
protected override void LoadComplete()
{
base.LoadComplete();
RulesetContainer?.SetReplayScore(score);
DrawableRuleset?.SetReplayScore(score);
}
protected override ScoreInfo CreateScore() => score.ScoreInfo;

View File

@ -0,0 +1,74 @@
// 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.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Play
{
/// <summary>
/// An overlay which can be used to require further user actions before gameplay is resumed.
/// </summary>
public abstract class ResumeOverlay : OverlayContainer
{
public CursorContainer GameplayCursor { get; set; }
/// <summary>
/// The action to be performed to complete resuming.
/// </summary>
public Action ResumeAction { private get; set; }
public virtual CursorContainer LocalCursor => null;
protected const float TRANSITION_TIME = 500;
protected override bool BlockPositionalInput => false;
protected abstract string Message { get; }
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
protected ResumeOverlay()
{
RelativeSizeAxes = Axes.Both;
}
protected void Resume()
{
ResumeAction?.Invoke();
Hide();
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
AddRange(new Drawable[]
{
new OsuSpriteText
{
RelativePositionAxes = Axes.Both,
Y = 0.4f,
Text = Message,
Font = OsuFont.GetFont(size: 30),
Spacing = new Vector2(5, 0),
Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
Colour = colours.Yellow,
Shadow = true,
ShadowColour = new Color4(0, 0, 0, 0.25f)
}
});
}
protected override void PopIn() => this.FadeIn(TRANSITION_TIME, Easing.OutQuint);
protected override void PopOut() => this.FadeOut(TRANSITION_TIME, Easing.OutQuint);
}
}

View File

@ -1,13 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Game.Configuration;
using osu.Game.Screens.Backgrounds;
using osuTK;
namespace osu.Game.Screens.Play
{
@ -16,50 +10,5 @@ namespace osu.Game.Screens.Play
protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value);
protected new BackgroundScreenBeatmap Background => (BackgroundScreenBeatmap)base.Background;
protected const float BACKGROUND_FADE_DURATION = 800;
#region User Settings
protected Bindable<double> BlurLevel;
protected Bindable<bool> ShowStoryboard;
#endregion
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
BlurLevel = config.GetBindable<double>(OsuSetting.BlurLevel);
ShowStoryboard = config.GetBindable<bool>(OsuSetting.ShowStoryboard);
}
public override void OnEntering(IScreen last)
{
base.OnEntering(last);
BlurLevel.ValueChanged += _ => UpdateBackgroundElements();
InitializeBackgroundElements();
}
public override void OnResuming(IScreen last)
{
base.OnResuming(last);
InitializeBackgroundElements();
}
/// <summary>
/// Called once on entering screen. By Default, performs a full <see cref="UpdateBackgroundElements"/> call.
/// </summary>
protected virtual void InitializeBackgroundElements() => UpdateBackgroundElements();
/// <summary>
/// Called when background elements require updates, usually due to a user changing a setting.
/// </summary>
/// <param name="userChange"></param>
protected virtual void UpdateBackgroundElements()
{
if (!this.IsCurrentScreen()) return;
Background?.BlurTo(new Vector2((float)BlurLevel.Value * 25), BACKGROUND_FADE_DURATION, Easing.OutQuint);
}
}
}

View File

@ -9,13 +9,13 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Threading;
using osu.Framework.Timing;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Screens.Ranking;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
@ -27,8 +27,7 @@ namespace osu.Game.Screens.Play
{
private readonly double startTime;
public IAdjustableClock AdjustableClock;
public IFrameBasedClock FramedClock;
public Action<double> RequestSeek;
private Button button;
private Box remainingTimeBox;
@ -54,16 +53,13 @@ namespace osu.Game.Screens.Play
Origin = Anchor.Centre;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
[BackgroundDependencyLoader(true)]
private void load(OsuColour colours, GameplayClock clock)
{
var baseClock = Clock;
if (FramedClock != null)
{
Clock = FramedClock;
ProcessCustomClock = false;
}
if (clock != null)
Clock = clock;
Children = new Drawable[]
{
@ -111,7 +107,7 @@ namespace osu.Game.Screens.Play
using (BeginAbsoluteSequence(beginFadeTime))
this.FadeOut(fade_time);
button.Action = () => AdjustableClock?.Seek(startTime - skip_required_cutoff - fade_time);
button.Action = () => RequestSeek?.Invoke(startTime - skip_required_cutoff - fade_time);
displayTime = Time.Current;
@ -263,9 +259,9 @@ namespace osu.Game.Screens.Play
Direction = FillDirection.Horizontal,
Children = new[]
{
new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.fa_chevron_right },
new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.fa_chevron_right },
new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.fa_chevron_right },
new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.Solid.ChevronRight },
new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.Solid.ChevronRight },
new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.Solid.ChevronRight },
}
},
new OsuSpriteText

View File

@ -10,7 +10,6 @@ using osu.Game.Graphics;
using osu.Framework.Allocation;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Timing;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.UI;
@ -29,18 +28,11 @@ namespace osu.Game.Screens.Play
private readonly SongProgressGraph graph;
private readonly SongProgressInfo info;
public Action<double> OnSeek;
public Action<double> RequestSeek;
public override bool HandleNonPositionalInput => AllowSeeking;
public override bool HandlePositionalInput => AllowSeeking;
private IClock audioClock;
public IClock AudioClock
{
set => audioClock = info.AudioClock = value;
}
private double lastHitTime => ((objects.Last() as IHasEndTime)?.EndTime ?? objects.Last().StartTime) + 1;
private double firstHitTime => objects.First().StartTime;
@ -63,9 +55,14 @@ namespace osu.Game.Screens.Play
private readonly BindableBool replayLoaded = new BindableBool();
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private GameplayClock gameplayClock;
[BackgroundDependencyLoader(true)]
private void load(OsuColour colours, GameplayClock clock)
{
if (clock != null)
gameplayClock = clock;
graph.FillColour = bar.FillColour = colours.BlueLighter;
}
@ -99,7 +96,7 @@ namespace osu.Game.Screens.Play
Alpha = 0,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
OnSeek = position => OnSeek?.Invoke(position),
OnSeek = time => RequestSeek?.Invoke(time),
},
};
}
@ -112,9 +109,9 @@ namespace osu.Game.Screens.Play
replayLoaded.TriggerChange();
}
public void BindRulestContainer(RulesetContainer rulesetContainer)
public void BindDrawableRuleset(DrawableRuleset drawableRuleset)
{
replayLoaded.BindTo(rulesetContainer.HasReplayLoaded);
replayLoaded.BindTo(drawableRuleset.HasReplayLoaded);
}
private bool allowSeeking;
@ -157,14 +154,11 @@ namespace osu.Game.Screens.Play
if (objects == null)
return;
double position = audioClock?.CurrentTime ?? Time.Current;
double progress = (position - firstHitTime) / (lastHitTime - firstHitTime);
double position = gameplayClock?.CurrentTime ?? Time.Current;
double progress = Math.Min(1, (position - firstHitTime) / (lastHitTime - firstHitTime));
if (progress < 1)
{
bar.CurrentTime = position;
graph.Progress = (int)(graph.ColumnCount * progress);
}
bar.CurrentTime = position;
graph.Progress = (int)(graph.ColumnCount * progress);
}
}
}

View File

@ -4,7 +4,6 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using System;
@ -27,8 +26,6 @@ namespace osu.Game.Screens.Play
private const int margin = 10;
public IClock AudioClock;
public double StartTime
{
set => startTime = value;
@ -39,9 +36,14 @@ namespace osu.Game.Screens.Play
set => endTime = value;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private GameplayClock gameplayClock;
[BackgroundDependencyLoader(true)]
private void load(OsuColour colours, GameplayClock clock)
{
if (clock != null)
gameplayClock = clock;
Children = new Drawable[]
{
timeCurrent = new OsuSpriteText
@ -80,7 +82,9 @@ namespace osu.Game.Screens.Play
{
base.Update();
double songCurrentTime = AudioClock.CurrentTime - startTime;
var time = gameplayClock?.CurrentTime ?? Time.Current;
double songCurrentTime = time - startTime;
int currentPercent = Math.Max(0, Math.Min(100, (int)(songCurrentTime / songLength * 100)));
int currentSecond = (int)Math.Floor(songCurrentTime / 1000.0);
@ -93,7 +97,7 @@ namespace osu.Game.Screens.Play
if (currentSecond != previousSecond && songCurrentTime < songLength)
{
timeCurrent.Text = formatTime(TimeSpan.FromSeconds(currentSecond));
timeLeft.Text = formatTime(TimeSpan.FromMilliseconds(endTime - AudioClock.CurrentTime));
timeLeft.Text = formatTime(TimeSpan.FromMilliseconds(endTime - time));
previousSecond = currentSecond;
}

View File

@ -242,7 +242,11 @@ namespace osu.Game.Screens.Play
// Reverse drawableRows so when iterating through them they start at the bottom
drawableRows.Reverse();
}
protected override void LoadComplete()
{
base.LoadComplete();
fillActive();
}

View File

@ -1,13 +1,13 @@
// 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.Game.Graphics;
using osu.Framework.Graphics.Sprites;
namespace osu.Game.Screens.Ranking
{
public interface IResultPageInfo
{
FontAwesome Icon { get; }
IconUsage Icon { get; }
string Name { get; }

View File

@ -11,12 +11,13 @@ using osu.Game.Graphics;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
namespace osu.Game.Screens.Ranking
{
public class ResultModeButton : TabItem<IResultPageInfo>, IHasTooltip
{
private readonly FontAwesome icon;
private readonly IconUsage icon;
private Color4 activeColour;
private Color4 inactiveColour;
private CircularContainer colouredPart;

View File

@ -24,6 +24,8 @@ namespace osu.Game.Screens.Ranking
{
public abstract class Results : OsuScreen
{
protected const float BACKGROUND_BLUR = 20;
private Container circleOuterBackground;
private Container circleOuter;
private Container circleInner;
@ -38,8 +40,6 @@ namespace osu.Game.Screens.Ranking
private Container currentPage;
private static readonly Vector2 background_blur = new Vector2(20);
protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value);
private const float overscan = 1.3f;
@ -58,7 +58,7 @@ namespace osu.Game.Screens.Ranking
public override void OnEntering(IScreen last)
{
base.OnEntering(last);
(Background as BackgroundScreenBeatmap)?.BlurTo(background_blur, 2500, Easing.OutQuint);
((BackgroundScreenBeatmap)Background).BlurAmount.Value = BACKGROUND_BLUR;
Background.ScaleTo(1.1f, transition_time, Easing.OutQuint);
allCircles.ForEach(c =>

View File

@ -1,8 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking.Pages;
@ -19,7 +19,7 @@ namespace osu.Game.Screens.Ranking.Types
this.beatmap = beatmap;
}
public FontAwesome Icon => FontAwesome.fa_user;
public IconUsage Icon => FontAwesome.Solid.User;
public string Name => @"Local Leaderboard";

View File

@ -1,8 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking.Pages;
@ -10,7 +10,7 @@ namespace osu.Game.Screens.Ranking.Types
{
public class ScoreOverviewPageInfo : IResultPageInfo
{
public FontAwesome Icon => FontAwesome.fa_asterisk;
public IconUsage Icon => FontAwesome.Solid.Asterisk;
public string Name => "Overview";
private readonly ScoreInfo score;

View File

@ -14,6 +14,7 @@ using osuTK.Graphics;
using osu.Game.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
namespace osu.Game.Screens
{
@ -112,7 +113,7 @@ namespace osu.Game.Screens
{
new SpriteIcon
{
Icon = FontAwesome.fa_universal_access,
Icon = FontAwesome.Solid.UniversalAccess,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Size = new Vector2(50),
@ -188,7 +189,7 @@ namespace osu.Game.Screens
{
public ChildModeButton()
{
Icon = FontAwesome.fa_osu_right_o;
Icon = OsuIcon.RightCircle;
Anchor = Anchor.BottomRight;
Origin = Anchor.BottomRight;
}

View File

@ -55,9 +55,9 @@ namespace osu.Game.Screens.Select
public override bool HandlePositionalInput => AllowSelection;
/// <summary>
/// Used to avoid firing null selections before the initial beatmaps have been loaded via <see cref="BeatmapSets"/>.
/// Whether carousel items have completed asynchronously loaded.
/// </summary>
private bool initialLoadComplete;
public bool BeatmapSetsLoaded { get; private set; }
private IEnumerable<CarouselBeatmapSet> beatmapSets => root.Children.OfType<CarouselBeatmapSet>();
@ -90,7 +90,7 @@ namespace osu.Game.Screens.Select
Schedule(() =>
{
BeatmapSetsChanged?.Invoke();
initialLoadComplete = true;
BeatmapSetsLoaded = true;
});
}));
}
@ -327,6 +327,9 @@ namespace osu.Game.Screens.Select
private void select(CarouselItem item)
{
if (!AllowSelection)
return;
if (item == null) return;
item.State.Value = CarouselItemState.Selected;
@ -577,7 +580,7 @@ namespace osu.Game.Screens.Select
else
{
float y = currentY;
d.OnLoadComplete = _ => performMove(y, setY);
d.OnLoadComplete += _ => performMove(y, setY);
}
break;
@ -593,7 +596,7 @@ namespace osu.Game.Screens.Select
currentY += DrawHeight / 2;
scrollableContent.Height = currentY;
if (initialLoadComplete && (selectedBeatmapSet == null || selectedBeatmap == null || selectedBeatmapSet.State.Value != CarouselItemState.Selected))
if (BeatmapSetsLoaded && (selectedBeatmapSet == null || selectedBeatmap == null || selectedBeatmapSet.State.Value != CarouselItemState.Selected))
{
selectedBeatmapSet = null;
SelectionChanged?.Invoke(null);

View File

@ -3,12 +3,12 @@
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Overlays.Dialog;
using osu.Game.Scoring;
using System;
using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Graphics.Sprites;
namespace osu.Game.Screens.Select
{
@ -19,7 +19,7 @@ namespace osu.Game.Screens.Select
public BeatmapClearScoresDialog(BeatmapInfo beatmap, Action onCompletion)
{
BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title}";
Icon = FontAwesome.fa_eraser;
Icon = FontAwesome.Solid.Eraser;
HeaderText = @"Clearing all local scores. Are you sure?";
Buttons = new PopupDialogButton[]
{

View File

@ -2,8 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Overlays.Dialog;
namespace osu.Game.Screens.Select
@ -22,7 +22,7 @@ namespace osu.Game.Screens.Select
{
BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title}";
Icon = FontAwesome.fa_trash_o;
Icon = FontAwesome.Regular.TrashAlt;
HeaderText = @"Confirm deletion of";
Buttons = new PopupDialogButton[]
{

View File

@ -36,7 +36,7 @@ namespace osu.Game.Screens.Select
private readonly FailRetryGraph failRetryGraph;
private readonly DimmedLoadingAnimation loading;
private APIAccess api;
private IAPIProvider api;
private ScheduledDelegate pendingBeatmapSwitch;
@ -160,7 +160,7 @@ namespace osu.Game.Screens.Select
}
[BackgroundDependencyLoader]
private void load(APIAccess api)
private void load(IAPIProvider api)
{
this.api = api;
}
@ -175,21 +175,22 @@ namespace osu.Game.Screens.Select
private void updateStatistics()
{
if (Beatmap == null)
advanced.Beatmap = Beatmap;
description.Text = Beatmap?.Version;
source.Text = Beatmap?.Metadata?.Source;
tags.Text = Beatmap?.Metadata?.Tags;
// metrics may have been previously fetched
if (Beatmap?.Metrics != null)
{
clearStats();
updateMetrics(Beatmap.Metrics);
return;
}
ratingsContainer.FadeIn(transition_duration);
advanced.Beatmap = Beatmap;
description.Text = Beatmap.Version;
source.Text = Beatmap.Metadata.Source;
tags.Text = Beatmap.Metadata.Tags;
var requestedBeatmap = Beatmap;
if (requestedBeatmap.Metrics == null)
// metrics may not be fetched but can be
if (Beatmap?.OnlineBeatmapID != null)
{
var requestedBeatmap = Beatmap;
var lookup = new GetBeatmapDetailsRequest(requestedBeatmap);
lookup.Success += res =>
{
@ -198,39 +199,34 @@ namespace osu.Game.Screens.Select
return;
requestedBeatmap.Metrics = res;
Schedule(() => displayMetrics(res));
Schedule(() => updateMetrics(res));
};
lookup.Failure += e => Schedule(() => displayMetrics(null));
lookup.Failure += e => Schedule(() => updateMetrics());
api.Queue(lookup);
loading.Show();
return;
}
displayMetrics(requestedBeatmap.Metrics, false);
updateMetrics();
}
private void displayMetrics(BeatmapMetrics metrics, bool failOnMissing = true)
private void updateMetrics(BeatmapMetrics metrics = null)
{
var hasRatings = metrics?.Ratings?.Any() ?? false;
var hasRetriesFails = (metrics?.Retries?.Any() ?? false) && (metrics.Fails?.Any() ?? false);
if (failOnMissing) loading.Hide();
if (hasRatings)
{
ratings.Metrics = metrics;
ratings.FadeIn(transition_duration);
ratingsContainer.FadeIn(transition_duration);
}
else if (failOnMissing)
else
{
ratings.Metrics = new BeatmapMetrics
{
Ratings = new int[10],
};
}
else
{
ratings.FadeTo(0.25f, transition_duration);
ratingsContainer.FadeTo(0.25f, transition_duration);
}
if (hasRetriesFails)
@ -238,41 +234,17 @@ namespace osu.Game.Screens.Select
failRetryGraph.Metrics = metrics;
failRetryContainer.FadeIn(transition_duration);
}
else if (failOnMissing)
else
{
failRetryGraph.Metrics = new BeatmapMetrics
{
Fails = new int[100],
Retries = new int[100],
};
failRetryContainer.FadeOut(transition_duration);
}
else
{
failRetryContainer.FadeTo(0.25f, transition_duration);
}
}
private void clearStats()
{
description.Text = null;
source.Text = null;
tags.Text = null;
advanced.Beatmap = new BeatmapInfo
{
StarDifficulty = 0,
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 0,
DrainRate = 0,
OverallDifficulty = 0,
ApproachRate = 0,
},
};
loading.Hide();
ratingsContainer.FadeOut(transition_duration);
failRetryContainer.FadeOut(transition_duration);
}
private class DetailBox : Container

View File

@ -22,6 +22,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Rulesets;
using osu.Game.Rulesets.UI;
@ -292,14 +293,14 @@ namespace osu.Game.Screens.Select
labels.Add(new InfoLabel(new BeatmapStatistic
{
Name = "Length",
Icon = FontAwesome.fa_clock_o,
Icon = FontAwesome.Regular.Clock,
Content = TimeSpan.FromMilliseconds(endTime - b.HitObjects.First().StartTime).ToString(@"m\:ss"),
}));
labels.Add(new InfoLabel(new BeatmapStatistic
{
Name = "BPM",
Icon = FontAwesome.fa_circle,
Icon = FontAwesome.Regular.Circle,
Content = getBPMRange(b),
}));
@ -377,7 +378,7 @@ namespace osu.Game.Screens.Select
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex(@"441288"),
Icon = FontAwesome.fa_square,
Icon = FontAwesome.Solid.Square,
Rotation = 45,
},
new SpriteIcon

View File

@ -49,11 +49,16 @@ namespace osu.Game.Screens.Select.Carousel
Children = new Drawable[]
{
new DelayedLoadUnloadWrapper(() =>
new PanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault()))
{
var background = new PanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault()))
{
RelativeSizeAxes = Axes.Both,
OnLoadComplete = d => d.FadeInFromZero(1000, Easing.OutQuint),
}, 300, 5000
};
background.OnLoadComplete += d => d.FadeInFromZero(1000, Easing.OutQuint);
return background;
}, 300, 5000
),
new FillFlowContainer
{

View File

@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Game.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Overlays.Dialog;
namespace osu.Game.Screens.Select
@ -14,7 +14,7 @@ namespace osu.Game.Screens.Select
HeaderText = @"You have no beatmaps!";
BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps (and skins)?";
Icon = FontAwesome.fa_plane;
Icon = FontAwesome.Solid.Plane;
Buttons = new PopupDialogButton[]
{

View File

@ -43,7 +43,7 @@ namespace osu.Game.Screens.Select.Leaderboards
private IBindable<RulesetInfo> ruleset { get; set; }
[Resolved]
private APIAccess api { get; set; }
private IAPIProvider api { get; set; }
[BackgroundDependencyLoader]
private void load()

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Humanizer;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@ -60,9 +61,12 @@ namespace osu.Game.Screens.Select
if (base.OnExiting(next))
return true;
Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value?.Beatmap);
Beatmap.Value.Mods.Value = selectedMods.Value = CurrentItem.Value?.RequiredMods;
Ruleset.Value = CurrentItem.Value?.Ruleset;
if (CurrentItem.Value != null)
{
Ruleset.Value = CurrentItem.Value.Ruleset;
Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value.Beatmap);
Beatmap.Value.Mods.Value = selectedMods.Value = CurrentItem.Value.RequiredMods ?? Enumerable.Empty<Mod>();
}
Beatmap.Disabled = true;
Ruleset.Disabled = true;

View File

@ -5,6 +5,7 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
@ -32,7 +33,7 @@ namespace osu.Game.Screens.Select.Options
set => background.Colour = value;
}
public FontAwesome Icon
public IconUsage Icon
{
get => iconText.Icon;
set => iconText.Icon = value;
@ -140,7 +141,7 @@ namespace osu.Game.Screens.Select.Options
Anchor = Anchor.TopCentre,
Size = new Vector2(30),
Shadow = true,
Icon = FontAwesome.fa_close,
Icon = FontAwesome.Solid.TimesCircle,
Margin = new MarginPadding
{
Bottom = 5,

View File

@ -6,7 +6,7 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Framework.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
@ -93,7 +93,7 @@ namespace osu.Game.Screens.Select.Options
/// <para>Lower depth to be put on the left, and higher to be put on the right.</para>
/// <para>Notice this is different to <see cref="Footer"/>!</para>
/// </param>
public void AddButton(string firstLine, string secondLine, FontAwesome icon, Color4 colour, Action action, Key? hotkey = null, float depth = 0)
public void AddButton(string firstLine, string secondLine, IconUsage icon, Color4 colour, Action action, Key? hotkey = null, float depth = 0)
{
var button = new BeatmapOptionsButton
{

View File

@ -3,6 +3,7 @@
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Screens;
using osu.Game.Graphics;
using osu.Game.Screens.Play;
@ -20,7 +21,7 @@ namespace osu.Game.Screens.Select
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
BeatmapOptions.AddButton(@"Edit", @"beatmap", FontAwesome.fa_pencil, colours.Yellow, () =>
BeatmapOptions.AddButton(@"Edit", @"beatmap", FontAwesome.Solid.PencilAlt, colours.Yellow, () =>
{
ValidForResume = false;
Edit();

View File

@ -1,4 +1,4 @@
// 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.
using osu.Framework.Allocation;
@ -32,13 +32,14 @@ using osuTK.Input;
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics.Sprites;
namespace osu.Game.Screens.Select
{
public abstract class SongSelect : OsuScreen
{
private static readonly Vector2 wedged_container_size = new Vector2(0.5f, 245);
private static readonly Vector2 background_blur = new Vector2(20);
protected const float BACKGROUND_BLUR = 20;
private const float left_area_padding = 20;
public readonly FilterControl FilterControl;
@ -61,7 +62,11 @@ namespace osu.Game.Screens.Select
/// </summary>
protected readonly Container FooterPanels;
protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap();
protected override BackgroundScreen CreateBackground()
{
var background = new BackgroundScreenBeatmap();
return background;
}
protected readonly BeatmapCarousel Carousel;
private readonly BeatmapInfoWedge beatmapInfoWedge;
@ -85,13 +90,12 @@ namespace osu.Game.Screens.Select
protected SongSelect()
{
const float carousel_width = 640;
const float filter_height = 100;
AddRangeInternal(new Drawable[]
{
new ParallaxContainer
{
Padding = new MarginPadding { Top = filter_height },
Masking = true,
ParallaxAmount = 0.005f,
RelativeSizeAxes = Axes.Both,
Children = new[]
@ -150,7 +154,7 @@ namespace osu.Game.Screens.Select
FilterControl = new FilterControl
{
RelativeSizeAxes = Axes.X,
Height = filter_height,
Height = 100,
FilterChanged = c => Carousel.Filter(c),
Background = { Width = 2 },
Exit = () =>
@ -224,9 +228,9 @@ namespace osu.Game.Screens.Select
Footer.AddButton(@"random", colours.Green, triggerRandom, Key.F2);
Footer.AddButton(@"options", colours.Blue, BeatmapOptions, Key.F3);
BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.fa_trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue);
BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.fa_times_circle_o, colours.Purple, null, Key.Number1);
BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.fa_eraser, colours.Purple, () => clearScores(Beatmap.Value.BeatmapInfo), Key.Number2);
BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue);
BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null, Key.Number1);
BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, colours.Purple, () => clearScores(Beatmap.Value.BeatmapInfo), Key.Number2);
}
if (this.beatmaps == null)
@ -296,6 +300,10 @@ namespace osu.Game.Screens.Select
/// <param name="performStartAction">Whether to trigger <see cref="OnStart"/>.</param>
public void FinaliseSelection(BeatmapInfo beatmap = null, bool performStartAction = true)
{
// This is very important as we have not yet bound to screen-level bindables before the carousel load is completed.
if (!Carousel.BeatmapSetsLoaded)
return;
// if we have a pending filter operation, we want to run it now.
// it could change selection (ie. if the ruleset has been changed).
Carousel.FlushPendingFilterOperations();
@ -369,6 +377,13 @@ namespace osu.Game.Screens.Select
var beatmap = beatmapNoDebounce;
var ruleset = rulesetNoDebounce;
selectionChangedDebounce?.Cancel();
if (beatmap == null)
run();
else
selectionChangedDebounce = Scheduler.AddDelayed(run, 200);
void run()
{
Logger.Log($"updating selection with beatmap:{beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ID.ToString() ?? "null"}");
@ -398,7 +413,6 @@ namespace osu.Game.Screens.Select
{
Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\"");
preview = beatmap?.BeatmapSetInfoID != Beatmap.Value?.BeatmapInfo.BeatmapSetInfoID;
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap.Value);
if (beatmap != null)
@ -410,16 +424,10 @@ namespace osu.Game.Screens.Select
}
}
if (this.IsCurrentScreen()) ensurePlayingSelected(preview);
if (this.IsCurrentScreen())
ensurePlayingSelected();
UpdateBeatmap(Beatmap.Value);
}
selectionChangedDebounce?.Cancel();
if (beatmap == null)
run();
else
selectionChangedDebounce = Scheduler.AddDelayed(run, 200);
}
private void triggerRandom()
@ -556,7 +564,7 @@ namespace osu.Game.Screens.Select
if (Background is BackgroundScreenBeatmap backgroundModeBeatmap)
{
backgroundModeBeatmap.Beatmap = beatmap;
backgroundModeBeatmap.BlurTo(background_blur, 750, Easing.OutQuint);
backgroundModeBeatmap.BlurAmount.Value = BACKGROUND_BLUR;
backgroundModeBeatmap.FadeColour(Color4.White, 250);
}
@ -568,39 +576,28 @@ namespace osu.Game.Screens.Select
beatmap.Track.Looping = true;
}
private void ensurePlayingSelected(bool preview = false)
private void ensurePlayingSelected(bool restart = false)
{
Track track = Beatmap.Value.Track;
if (!track.IsRunning)
if (!track.IsRunning || restart)
{
// Ensure the track is added to the TrackManager, since it is removed after the player finishes the map.
// Using AddItemToList rather than AddItem so that it doesn't attempt to register adjustment dependencies more than once.
Game.Audio.Track.AddItemToList(track);
if (preview) track.Seek(Beatmap.Value.Metadata.PreviewTime);
track.Start();
track.RestartPoint = Beatmap.Value.Metadata.PreviewTime;
track.Restart();
}
}
private void onBeatmapSetAdded(BeatmapSetInfo s, bool existing, bool silent) => Carousel.UpdateBeatmapSet(s);
private void onBeatmapSetAdded(BeatmapSetInfo s, bool existing) => Carousel.UpdateBeatmapSet(s);
private void onBeatmapSetRemoved(BeatmapSetInfo s) => Carousel.RemoveBeatmapSet(s);
private void onBeatmapRestored(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID));
private void onBeatmapHidden(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID));
private void carouselBeatmapsLoaded()
{
if (rulesetNoDebounce == null)
{
// manual binding to parent ruleset to allow for delayed load in the incoming direction.
rulesetNoDebounce = decoupledRuleset.Value = Ruleset.Value;
Ruleset.ValueChanged += r => updateSelectedRuleset(r.NewValue);
decoupledRuleset.ValueChanged += r => Ruleset.Value = r.NewValue;
decoupledRuleset.DisabledChanged += r => Ruleset.Disabled = r;
Beatmap.BindDisabledChanged(disabled => Carousel.AllowSelection = !disabled, true);
Beatmap.BindValueChanged(workingBeatmapChanged);
}
bindBindables();
if (!Beatmap.IsDefault && Beatmap.Value.BeatmapSetInfo?.DeletePending == false && Beatmap.Value.BeatmapSetInfo?.Protected == false
&& Carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo, false))
@ -614,6 +611,26 @@ namespace osu.Game.Screens.Select
}
}
private bool boundLocalBindables;
private void bindBindables()
{
if (boundLocalBindables)
return;
// manual binding to parent ruleset to allow for delayed load in the incoming direction.
rulesetNoDebounce = decoupledRuleset.Value = Ruleset.Value;
Ruleset.ValueChanged += r => updateSelectedRuleset(r.NewValue);
decoupledRuleset.ValueChanged += r => Ruleset.Value = r.NewValue;
decoupledRuleset.DisabledChanged += r => Ruleset.Disabled = r;
Beatmap.BindDisabledChanged(disabled => Carousel.AllowSelection = !disabled, true);
Beatmap.BindValueChanged(workingBeatmapChanged);
boundLocalBindables = true;
}
private void delete(BeatmapSetInfo beatmap)
{
if (beatmap == null || beatmap.ID <= 0) return;