mirror of
https://github.com/osukey/osukey.git
synced 2025-08-05 07:33:55 +09:00
Merge remote-tracking branch 'upstream/master' into tournament-tools
This commit is contained in:
@ -11,6 +11,7 @@ namespace osu.Game.Screens
|
||||
public class BackgroundScreenStack : ScreenStack
|
||||
{
|
||||
public BackgroundScreenStack()
|
||||
: base(false)
|
||||
{
|
||||
Scale = new Vector2(1.06f);
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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
|
||||
}
|
||||
},
|
||||
|
@ -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)
|
||||
},
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 };
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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}" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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)]
|
||||
|
@ -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; }
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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; }
|
||||
|
48
osu.Game/Screens/OsuScreenStack.cs
Normal file
48
osu.Game/Screens/OsuScreenStack.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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),
|
||||
},
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
|
46
osu.Game/Screens/Play/GameplayClock.cs
Normal file
46
osu.Game/Screens/Play/GameplayClock.cs
Normal 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;
|
||||
}
|
||||
}
|
161
osu.Game/Screens/Play/GameplayClockContainer.cs
Normal file
161
osu.Game/Screens/Play/GameplayClockContainer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
29
osu.Game/Screens/Play/PauseOverlay.cs
Normal file
29
osu.Game/Screens/Play/PauseOverlay.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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;
|
||||
|
74
osu.Game/Screens/Play/ResumeOverlay.cs
Normal file
74
osu.Game/Screens/Play/ResumeOverlay.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 =>
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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[]
|
||||
{
|
||||
|
@ -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[]
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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[]
|
||||
{
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
Reference in New Issue
Block a user