1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 18:07:24 +08:00

Merge remote-tracking branch 'upstream/master' into user-status-wiring

This commit is contained in:
Lucas A 2019-05-12 11:53:30 +02:00
commit 5ac6bd8204
47 changed files with 698 additions and 258 deletions

View File

@ -26,9 +26,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="System.IO.Packaging" Version="4.5.0" /> <PackageReference Include="System.IO.Packaging" Version="4.5.0" />
<PackageReference Include="ppy.squirrel.windows" Version="1.9.0.3" /> <PackageReference Include="ppy.squirrel.windows" Version="1.9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.4" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Resources"> <ItemGroup Label="Resources">
<EmbeddedResource Include="lazer.ico" /> <EmbeddedResource Include="lazer.ico" />

View File

@ -0,0 +1,19 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.Play;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestCaseOsuFlashlight : TestCaseOsuPlayer
{
protected override Player CreatePlayer(Ruleset ruleset)
{
Mods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), };
return base.CreatePlayer(ruleset);
}
}
}

View File

@ -353,6 +353,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
protected override bool PauseOnFocusLost => false;
public ScoreAccessibleReplayPlayer(Score score) public ScoreAccessibleReplayPlayer(Score score)
: base(score, false, false) : base(score, false, false)
{ {

View File

@ -1,23 +1,37 @@
// 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. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Osu.Mods namespace osu.Game.Rulesets.Osu.Mods
{ {
public class OsuModFlashlight : ModFlashlight<OsuHitObject> public class OsuModFlashlight : ModFlashlight<OsuHitObject>, IApplicableToDrawableHitObjects
{ {
public override double ScoreMultiplier => 1.12; public override double ScoreMultiplier => 1.12;
private const float default_flashlight_size = 180; private const float default_flashlight_size = 180;
public override Flashlight CreateFlashlight() => new OsuFlashlight(); private OsuFlashlight flashlight;
public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight();
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{
foreach (var s in drawables.OfType<DrawableSlider>())
{
s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange;
}
}
private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition
{ {
@ -26,6 +40,12 @@ namespace osu.Game.Rulesets.Osu.Mods
FlashlightSize = new Vector2(0, getSizeFor(0)); FlashlightSize = new Vector2(0, getSizeFor(0));
} }
public void OnSliderTrackingChange(ValueChangedEvent<bool> e)
{
// If a slider is in a tracking state, a further dim should be applied to the (remaining) visible portion of the playfield over a brief duration.
this.TransformTo(nameof(FlashlightDim), e.NewValue ? 0.8f : 0.0f, 50);
}
protected override bool OnMouseMove(MouseMoveEvent e) protected override bool OnMouseMove(MouseMoveEvent e)
{ {
FlashlightPosition = e.MousePosition; FlashlightPosition = e.MousePosition;

View File

@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (repeatPoint.StartTime <= Time.Current) if (repeatPoint.StartTime <= Time.Current)
ApplyResult(r => r.Type = drawableSlider.Tracking ? HitResult.Great : HitResult.Miss); ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? HitResult.Great : HitResult.Miss);
} }
protected override void UpdatePreemptState() protected override void UpdatePreemptState()

View File

@ -130,13 +130,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
} }
} }
public bool Tracking; public readonly Bindable<bool> Tracking = new Bindable<bool>();
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
Tracking = Ball.Tracking; Tracking.Value = Ball.Tracking;
double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);

View File

@ -328,7 +328,7 @@ namespace osu.Game.Tests.Visual.Background
public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR); public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
} }
private class TestPlayer : Player private class TestPlayer : Visual.TestPlayer
{ {
protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value); protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Framework.Testing;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Cursor;
using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Edit.Compose.Components.Timeline;
@ -18,38 +19,49 @@ namespace osu.Game.Tests.Visual.Editor
{ {
public class TestCaseZoomableScrollContainer : ManualInputManagerTestCase public class TestCaseZoomableScrollContainer : ManualInputManagerTestCase
{ {
private readonly ZoomableScrollContainer scrollContainer; private ZoomableScrollContainer scrollContainer;
private readonly Drawable innerBox; private Drawable innerBox;
public TestCaseZoomableScrollContainer() [SetUpSteps]
public void SetUpSteps()
{ {
Children = new Drawable[] AddStep("Add new scroll container", () =>
{ {
new Container Children = new Drawable[]
{ {
Anchor = Anchor.Centre, new Container
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Height = 250,
Width = 0.75f,
Children = new Drawable[]
{ {
new Box Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Height = 250,
Width = 0.75f,
Children = new Drawable[]
{ {
RelativeSizeAxes = Axes.Both, new Box
Colour = OsuColour.Gray(30) {
}, RelativeSizeAxes = Axes.Both,
scrollContainer = new ZoomableScrollContainer { RelativeSizeAxes = Axes.Both } Colour = OsuColour.Gray(30)
} },
}, scrollContainer = new ZoomableScrollContainer { RelativeSizeAxes = Axes.Both }
new MenuCursor() }
}; },
new MenuCursor()
};
scrollContainer.Add(innerBox = new Box scrollContainer.Add(innerBox = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientHorizontal(new Color4(0.8f, 0.6f, 0.4f, 1f), new Color4(0.4f, 0.6f, 0.8f, 1f)) Colour = ColourInfo.GradientHorizontal(new Color4(0.8f, 0.6f, 0.4f, 1f), new Color4(0.4f, 0.6f, 0.8f, 1f))
});
}); });
AddUntilStep("Scroll container is loaded", () => scrollContainer.LoadState >= LoadState.Loaded);
}
[Test]
public void TestWidthInitialization()
{
AddAssert("Inner container width was initialized", () => innerBox.DrawWidth > 0);
} }
[Test] [Test]

View File

@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0)); AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0));
} }
private class ScoreAccessiblePlayer : Player private class ScoreAccessiblePlayer : TestPlayer
{ {
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
public new HUDOverlay HUDOverlay => base.HUDOverlay; public new HUDOverlay HUDOverlay => base.HUDOverlay;

View File

@ -73,6 +73,26 @@ namespace osu.Game.Tests.Visual.Gameplay
checkFrameCount(2); checkFrameCount(2);
} }
[Test]
public void TestInitialSeekWithGameplayStart()
{
seekManualTo(1000);
createStabilityContainer(30000);
confirmSeek(1000);
checkFrameCount(0);
seekManualTo(10000);
confirmSeek(10000);
checkFrameCount(1);
seekManualTo(130000);
confirmSeek(130000);
checkFrameCount(6002);
}
[Test] [Test]
public void TestInitialSeek() public void TestInitialSeek()
{ {
@ -83,7 +103,11 @@ namespace osu.Game.Tests.Visual.Gameplay
checkFrameCount(0); checkFrameCount(0);
} }
private void createStabilityContainer() => AddStep("create container", () => mainContainer.Child = new FrameStabilityContainer().WithChild(consumer = new ClockConsumingChild())); private const int max_frames_catchup = 50;
private void createStabilityContainer(double gameplayStartTime = double.MinValue) => AddStep("create container", () =>
mainContainer.Child = new FrameStabilityContainer(gameplayStartTime) { MaxCatchUpFrames = max_frames_catchup }
.WithChild(consumer = new ClockConsumingChild()));
private void seekManualTo(double time) => AddStep($"seek manual clock to {time}", () => manualClock.CurrentTime = time); private void seekManualTo(double time) => AddStep($"seek manual clock to {time}", () => manualClock.CurrentTime = time);

View File

@ -6,6 +6,7 @@ using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Cursor;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -31,6 +32,14 @@ namespace osu.Game.Tests.Visual.Gameplay
base.Content.Add(content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }); base.Content.Add(content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both });
} }
[SetUpSteps]
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("resume player", () => Player.GameplayClockContainer.Start());
confirmClockRunning(true);
}
[Test] [Test]
public void TestPauseResume() public void TestPauseResume()
{ {
@ -157,6 +166,8 @@ namespace osu.Game.Tests.Visual.Gameplay
private void confirmPaused() private void confirmPaused()
{ {
confirmClockRunning(false); confirmClockRunning(false);
AddAssert("player not exited", () => Player.IsCurrentScreen());
AddAssert("player not failed", () => !Player.HasFailed);
AddAssert("pause overlay shown", () => Player.PauseOverlayVisible); AddAssert("pause overlay shown", () => Player.PauseOverlayVisible);
} }
@ -184,7 +195,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override Player CreatePlayer(Ruleset ruleset) => new PausePlayer(); protected override Player CreatePlayer(Ruleset ruleset) => new PausePlayer();
protected class PausePlayer : Player protected class PausePlayer : TestPlayer
{ {
public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
@ -196,10 +207,10 @@ namespace osu.Game.Tests.Visual.Gameplay
public bool PauseOverlayVisible => PauseOverlay.State == Visibility.Visible; public bool PauseOverlayVisible => PauseOverlay.State == Visibility.Visible;
protected override void LoadComplete() public override void OnEntering(IScreen last)
{ {
base.LoadComplete(); base.OnEntering(last);
HUDOverlay.HoldToQuit.PauseOnFocusLost = false; GameplayClockContainer.Stop();
} }
} }
} }

View File

@ -1,6 +1,7 @@
// 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. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@ -34,20 +35,20 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestLoadContinuation() public void TestLoadContinuation()
{ {
AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => new Player(false, false)))); Player player = null;
SlowLoadPlayer slowPlayer = null;
AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => player = new TestPlayer(false, false))));
AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddUntilStep("wait for current", () => loader.IsCurrentScreen());
AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre));
AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen()); AddUntilStep("wait for player to be current", () => player.IsCurrentScreen());
AddStep("load slow dummy beatmap", () => AddStep("load slow dummy beatmap", () =>
{ {
SlowLoadPlayer slow = null; stack.Push(loader = new PlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false)));
Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000);
stack.Push(loader = new PlayerLoader(() => slow = new SlowLoadPlayer(false, false)));
Scheduler.AddDelayed(() => slow.Ready = true, 5000);
}); });
AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen()); AddUntilStep("wait for player to be current", () => slowPlayer.IsCurrentScreen());
} }
[Test] [Test]
@ -101,19 +102,19 @@ namespace osu.Game.Tests.Visual.Gameplay
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
} }
private class TestPlayer : Player private class TestPlayer : Visual.TestPlayer
{ {
public new Bindable<IReadOnlyList<Mod>> Mods => base.Mods; public new Bindable<IReadOnlyList<Mod>> Mods => base.Mods;
public TestPlayer() public TestPlayer(bool allowPause = true, bool showResults = true)
: base(false, false) : base(allowPause, showResults)
{ {
} }
} }
protected class SlowLoadPlayer : Player protected class SlowLoadPlayer : Visual.TestPlayer
{ {
public bool Ready; public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim(false);
public SlowLoadPlayer(bool allowPause = true, bool showResults = true) public SlowLoadPlayer(bool allowPause = true, bool showResults = true)
: base(allowPause, showResults) : base(allowPause, showResults)
@ -123,8 +124,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
while (!Ready) if (!AllowLoad.Wait(TimeSpan.FromSeconds(10)))
Thread.Sleep(1); throw new TimeoutException();
} }
} }
} }

View File

@ -33,6 +33,8 @@ namespace osu.Game.Tests.Visual.Gameplay
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
public new HUDOverlay HUDOverlay => base.HUDOverlay; public new HUDOverlay HUDOverlay => base.HUDOverlay;
protected override bool PauseOnFocusLost => false;
public ScoreAccessibleReplayPlayer(Score score) public ScoreAccessibleReplayPlayer(Score score)
: base(score) : base(score)
{ {

View File

@ -1,22 +1,27 @@
// 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. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables; using osu.Game.Beatmaps.Drawables;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Beatmaps.IO;
using osuTK;
namespace osu.Game.Tests.Visual.UserInterface namespace osu.Game.Tests.Visual.UserInterface
{ {
public class TestCaseUpdateableBeatmapBackgroundSprite : OsuTestCase public class TestCaseUpdateableBeatmapBackgroundSprite : OsuTestCase
{ {
private TestUpdateableBeatmapBackgroundSprite backgroundSprite; private BeatmapSetInfo testBeatmap;
private IAPIProvider api;
private RulesetStore rulesets;
[Resolved] [Resolved]
private BeatmapManager beatmaps { get; set; } private BeatmapManager beatmaps { get; set; }
@ -24,40 +29,119 @@ namespace osu.Game.Tests.Visual.UserInterface
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuGameBase osu, IAPIProvider api, RulesetStore rulesets) private void load(OsuGameBase osu, IAPIProvider api, RulesetStore rulesets)
{ {
Bindable<BeatmapInfo> beatmapBindable = new Bindable<BeatmapInfo>(); this.api = api;
this.rulesets = rulesets;
var imported = ImportBeatmapTest.LoadOszIntoOsu(osu); testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu);
}
Child = backgroundSprite = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }; [Test]
public void TestNullBeatmap()
{
TestUpdateableBeatmapBackgroundSprite background = null;
backgroundSprite.Beatmap.BindTo(beatmapBindable); AddStep("load null beatmap", () => Child = background = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both });
AddUntilStep("wait for load", () => background.ContentLoaded);
}
var req = new GetBeatmapSetRequest(1); [Test]
api.Queue(req); public void TestLocalBeatmap()
{
TestUpdateableBeatmapBackgroundSprite background = null;
AddStep("load null beatmap", () => beatmapBindable.Value = null); AddStep("load local beatmap", () =>
AddUntilStep("wait for cleanup...", () => backgroundSprite.ChildCount == 1); {
AddStep("load imported beatmap", () => beatmapBindable.Value = imported.Beatmaps.First()); Child = background = new TestUpdateableBeatmapBackgroundSprite
AddUntilStep("wait for cleanup...", () => backgroundSprite.ChildCount == 1); {
RelativeSizeAxes = Axes.Both,
Beatmap = { Value = testBeatmap.Beatmaps.First() }
};
});
AddUntilStep("wait for load", () => background.ContentLoaded);
}
[Test]
public void TestOnlineBeatmap()
{
if (api.IsLoggedIn) if (api.IsLoggedIn)
{ {
var req = new GetBeatmapSetRequest(1);
api.Queue(req);
AddUntilStep("wait for api response", () => req.Result != null); AddUntilStep("wait for api response", () => req.Result != null);
AddStep("load online beatmap", () => beatmapBindable.Value = new BeatmapInfo
TestUpdateableBeatmapBackgroundSprite background = null;
AddStep("load online beatmap", () =>
{ {
BeatmapSet = req.Result?.ToBeatmapSet(rulesets) Child = background = new TestUpdateableBeatmapBackgroundSprite
{
RelativeSizeAxes = Axes.Both,
Beatmap = { Value = new BeatmapInfo { BeatmapSet = req.Result?.ToBeatmapSet(rulesets) } }
};
}); });
AddUntilStep("wait for cleanup...", () => backgroundSprite.ChildCount == 1);
AddUntilStep("wait for load", () => background.ContentLoaded);
} }
else else
{
AddStep("online (login first)", () => { }); AddStep("online (login first)", () => { });
} }
[Test]
public void TestUnloadAndReload()
{
var backgrounds = new List<TestUpdateableBeatmapBackgroundSprite>();
ScrollContainer scrollContainer = null;
AddStep("create backgrounds hierarchy", () =>
{
FillFlowContainer backgroundFlow;
Child = scrollContainer = new ScrollContainer
{
Size = new Vector2(500),
Child = backgroundFlow = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(10),
Padding = new MarginPadding { Bottom = 550 }
}
};
for (int i = 0; i < 25; i++)
{
var background = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both };
if (i % 2 == 0)
background.Beatmap.Value = testBeatmap.Beatmaps.First();
backgroundFlow.Add(new Container
{
RelativeSizeAxes = Axes.X,
Height = 100,
Masking = true,
Child = background
});
backgrounds.Add(background);
}
});
var loadedBackgrounds = backgrounds.Where(b => b.ContentLoaded);
AddUntilStep("some loaded", () => loadedBackgrounds.Any());
AddStep("scroll to bottom", () => scrollContainer.ScrollToEnd());
AddUntilStep("all unloaded", () => !loadedBackgrounds.Any());
} }
private class TestUpdateableBeatmapBackgroundSprite : UpdateableBeatmapBackgroundSprite private class TestUpdateableBeatmapBackgroundSprite : UpdateableBeatmapBackgroundSprite
{ {
public int ChildCount => InternalChildren.Count; protected override double UnloadDelay => 2000;
public bool ContentLoaded => ((DelayedLoadUnloadWrapper)InternalChildren.LastOrDefault())?.Content?.IsLoaded ?? false;
} }
} }
} }

View File

@ -65,7 +65,6 @@ namespace osu.Game.Beatmaps
protected override IQueryable<BeatmapSetInfo> AddIncludesForDeletion(IQueryable<BeatmapSetInfo> query) => protected override IQueryable<BeatmapSetInfo> AddIncludesForDeletion(IQueryable<BeatmapSetInfo> query) =>
base.AddIncludesForDeletion(query) base.AddIncludesForDeletion(query)
.Include(s => s.Metadata) .Include(s => s.Metadata)
.Include(s => s.Beatmaps).ThenInclude(b => b.Scores)
.Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
.Include(s => s.Beatmaps).ThenInclude(b => b.Metadata); .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata);

View File

@ -26,6 +26,11 @@ namespace osu.Game.Beatmaps.Drawables
this.beatmapSetCoverType = beatmapSetCoverType; this.beatmapSetCoverType = beatmapSetCoverType;
} }
/// <summary>
/// Delay before the background is unloaded while off-screen.
/// </summary>
protected virtual double UnloadDelay => 10000;
private BeatmapInfo lastModel; private BeatmapInfo lastModel;
protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Drawable content, double timeBeforeLoad) protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Drawable content, double timeBeforeLoad)
@ -34,13 +39,13 @@ namespace osu.Game.Beatmaps.Drawables
{ {
// If DelayedLoadUnloadWrapper is attempting to RELOAD the same content (Beatmap), that means that it was // If DelayedLoadUnloadWrapper is attempting to RELOAD the same content (Beatmap), that means that it was
// previously UNLOADED and thus its children have been disposed of, so we need to recreate them here. // previously UNLOADED and thus its children have been disposed of, so we need to recreate them here.
if (lastModel == Beatmap.Value && Beatmap.Value != null) if (lastModel == Beatmap.Value)
return CreateDrawable(Beatmap.Value); return CreateDrawable(Beatmap.Value);
// If the model has changed since the previous unload (or if there was no load), then we can safely use the given content // If the model has changed since the previous unload (or if there was no load), then we can safely use the given content
lastModel = Beatmap.Value; lastModel = Beatmap.Value;
return content; return content;
}, timeBeforeLoad, 10000); }, timeBeforeLoad, UnloadDelay);
} }
protected override Drawable CreateDrawable(BeatmapInfo model) protected override Drawable CreateDrawable(BeatmapInfo model)

View File

@ -29,5 +29,12 @@ namespace osu.Game.Beatmaps.Timing
/// Whether the break has any effect. Breaks that are too short are culled before they are added to the beatmap. /// Whether the break has any effect. Breaks that are too short are culled before they are added to the beatmap.
/// </summary> /// </summary>
public bool HasEffect => Duration >= MIN_BREAK_DURATION; public bool HasEffect => Duration >= MIN_BREAK_DURATION;
/// <summary>
/// Whether this break contains a specified time.
/// </summary>
/// <param name="time">The time to check in milliseconds.</param>
/// <returns>Whether the time falls within this <see cref="BreakPeriod"/>.</returns>
public bool Contains(double time) => time >= StartTime && time <= EndTime;
} }
} }

View File

@ -385,7 +385,7 @@ namespace osu.Game.Database
/// Delete multiple items. /// Delete multiple items.
/// This will post notifications tracking progress. /// This will post notifications tracking progress.
/// </summary> /// </summary>
public void Delete(List<TModel> items) public void Delete(List<TModel> items, bool silent = false)
{ {
if (items.Count == 0) return; if (items.Count == 0) return;
@ -396,7 +396,8 @@ namespace osu.Game.Database
State = ProgressNotificationState.Active, State = ProgressNotificationState.Active,
}; };
PostNotification?.Invoke(notification); if (!silent)
PostNotification?.Invoke(notification);
int i = 0; int i = 0;
@ -423,7 +424,7 @@ namespace osu.Game.Database
/// Restore multiple items that were previously deleted. /// Restore multiple items that were previously deleted.
/// This will post notifications tracking progress. /// This will post notifications tracking progress.
/// </summary> /// </summary>
public void Undelete(List<TModel> items) public void Undelete(List<TModel> items, bool silent = false)
{ {
if (!items.Any()) return; if (!items.Any()) return;
@ -434,7 +435,8 @@ namespace osu.Game.Database
State = ProgressNotificationState.Active, State = ProgressNotificationState.Active,
}; };
PostNotification?.Invoke(notification); if (!silent)
PostNotification?.Invoke(notification);
int i = 0; int i = 0;

View File

@ -77,13 +77,13 @@ namespace osu.Game.Online.API
/// <param name="component"></param> /// <param name="component"></param>
public void Register(IOnlineComponent component) public void Register(IOnlineComponent component)
{ {
Scheduler.Add(delegate { components.Add(component); }); Schedule(() => components.Add(component));
component.APIStateChanged(this, state); component.APIStateChanged(this, state);
} }
public void Unregister(IOnlineComponent component) public void Unregister(IOnlineComponent component)
{ {
Scheduler.Add(delegate { components.Remove(component); }); Schedule(() => components.Remove(component));
} }
public string AccessToken => authentication.RequestAccessToken(); public string AccessToken => authentication.RequestAccessToken();
@ -274,7 +274,7 @@ namespace osu.Game.Online.API
state = value; state = value;
log.Add($@"We just went {state}!"); log.Add($@"We just went {state}!");
Scheduler.Add(delegate Schedule(() =>
{ {
components.ForEach(c => c.APIStateChanged(this, state)); components.ForEach(c => c.APIStateChanged(this, state));
OnStateChange?.Invoke(oldState, state); OnStateChange?.Invoke(oldState, state);
@ -352,9 +352,13 @@ namespace osu.Game.Online.API
public void Logout() public void Logout()
{ {
flushQueue(); flushQueue();
password = null; password = null;
authentication.Clear(); authentication.Clear();
LocalUser.Value = createGuestUser();
// Scheduled prior to state change such that the state changed event is invoked with the correct user present
Schedule(() => LocalUser.Value = createGuestUser());
State = APIState.Offline; State = APIState.Offline;
} }

View File

@ -155,8 +155,23 @@ namespace osu.Game
dependencies.Cache(RulesetStore = new RulesetStore(contextFactory)); dependencies.Cache(RulesetStore = new RulesetStore(contextFactory));
dependencies.Cache(FileStore = new FileStore(contextFactory, Host.Storage)); dependencies.Cache(FileStore = new FileStore(contextFactory, Host.Storage));
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Host.Storage, contextFactory, Host));
dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap)); dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap));
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, BeatmapManager, Host.Storage, contextFactory, Host));
// this should likely be moved to ArchiveModelManager when another case appers where it is necessary
// to have inter-dependent model managers. this could be obtained with an IHasForeign<T> interface to
// allow lookups to be done on the child (ScoreManager in this case) to perform the cascading delete.
List<ScoreInfo> getBeatmapScores(BeatmapSetInfo set)
{
var beatmapIds = BeatmapManager.QueryBeatmaps(b => b.BeatmapSetInfoID == set.ID).Select(b => b.ID).ToList();
return ScoreManager.QueryScores(s => beatmapIds.Contains(s.Beatmap.ID)).ToList();
}
BeatmapManager.ItemRemoved += i => ScoreManager.Delete(getBeatmapScores(i), true);
BeatmapManager.ItemAdded += (i, existing) => ScoreManager.Undelete(getBeatmapScores(i), true);
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));
dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore));

View File

@ -4,14 +4,12 @@
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Online.Leaderboards;
using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Overlays.Profile.Header.Components;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Users; using osu.Game.Users;
@ -185,8 +183,6 @@ namespace osu.Game.Overlays.Profile.Header
private class ScoreRankInfo : CompositeDrawable private class ScoreRankInfo : CompositeDrawable
{ {
private readonly ScoreRank rank;
private readonly Sprite rankSprite;
private readonly OsuSpriteText rankCount; private readonly OsuSpriteText rankCount;
public int RankCount public int RankCount
@ -196,8 +192,6 @@ namespace osu.Game.Overlays.Profile.Header
public ScoreRankInfo(ScoreRank rank) public ScoreRankInfo(ScoreRank rank)
{ {
this.rank = rank;
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
InternalChild = new FillFlowContainer InternalChild = new FillFlowContainer
{ {
@ -206,10 +200,10 @@ namespace osu.Game.Overlays.Profile.Header
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Children = new Drawable[] Children = new Drawable[]
{ {
rankSprite = new Sprite new DrawableRank(rank)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.X,
FillMode = FillMode.Fit Height = 30,
}, },
rankCount = new OsuSpriteText rankCount = new OsuSpriteText
{ {
@ -220,12 +214,6 @@ namespace osu.Game.Overlays.Profile.Header
} }
}; };
} }
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
rankSprite.Texture = textures.Get($"Grades/{rank.GetDescription()}");
}
} }
} }
} }

View File

@ -85,14 +85,15 @@ namespace osu.Game.Rulesets.Mods
Combo.ValueChanged += OnComboChange; Combo.ValueChanged += OnComboChange;
this.FadeInFromZero(FLASHLIGHT_FADE_DURATION); using (BeginAbsoluteSequence(0))
foreach (var breakPeriod in Breaks)
{ {
if (breakPeriod.Duration < FLASHLIGHT_FADE_DURATION * 2) continue; foreach (var breakPeriod in Breaks)
{
if (breakPeriod.Duration < FLASHLIGHT_FADE_DURATION * 2) continue;
this.Delay(breakPeriod.StartTime + FLASHLIGHT_FADE_DURATION).FadeOutFromOne(FLASHLIGHT_FADE_DURATION); this.Delay(breakPeriod.StartTime + FLASHLIGHT_FADE_DURATION).FadeOutFromOne(FLASHLIGHT_FADE_DURATION);
this.Delay(breakPeriod.EndTime - FLASHLIGHT_FADE_DURATION).FadeInFromZero(FLASHLIGHT_FADE_DURATION); this.Delay(breakPeriod.EndTime - FLASHLIGHT_FADE_DURATION).FadeInFromZero(FLASHLIGHT_FADE_DURATION);
}
} }
} }
@ -128,6 +129,20 @@ namespace osu.Game.Rulesets.Mods
} }
} }
private float flashlightDim;
public float FlashlightDim
{
get => flashlightDim;
set
{
if (flashlightDim == value) return;
flashlightDim = value;
Invalidate(Invalidation.DrawNode);
}
}
private class FlashlightDrawNode : DrawNode private class FlashlightDrawNode : DrawNode
{ {
protected new Flashlight Source => (Flashlight)base.Source; protected new Flashlight Source => (Flashlight)base.Source;
@ -136,6 +151,7 @@ namespace osu.Game.Rulesets.Mods
private Quad screenSpaceDrawQuad; private Quad screenSpaceDrawQuad;
private Vector2 flashlightPosition; private Vector2 flashlightPosition;
private Vector2 flashlightSize; private Vector2 flashlightSize;
private float flashlightDim;
public FlashlightDrawNode(Flashlight source) public FlashlightDrawNode(Flashlight source)
: base(source) : base(source)
@ -149,7 +165,8 @@ namespace osu.Game.Rulesets.Mods
shader = Source.shader; shader = Source.shader;
screenSpaceDrawQuad = Source.ScreenSpaceDrawQuad; screenSpaceDrawQuad = Source.ScreenSpaceDrawQuad;
flashlightPosition = Vector2Extensions.Transform(Source.FlashlightPosition, DrawInfo.Matrix); flashlightPosition = Vector2Extensions.Transform(Source.FlashlightPosition, DrawInfo.Matrix);
flashlightSize = Vector2Extensions.Transform(Source.FlashlightSize, DrawInfo.Matrix); flashlightSize = Source.FlashlightSize * DrawInfo.Matrix.ExtractScale().Xy;
flashlightDim = Source.FlashlightDim;
} }
public override void Draw(Action<TexturedVertex2D> vertexAction) public override void Draw(Action<TexturedVertex2D> vertexAction)
@ -160,6 +177,7 @@ namespace osu.Game.Rulesets.Mods
shader.GetUniform<Vector2>("flashlightPos").UpdateValue(ref flashlightPosition); shader.GetUniform<Vector2>("flashlightPos").UpdateValue(ref flashlightPosition);
shader.GetUniform<Vector2>("flashlightSize").UpdateValue(ref flashlightSize); shader.GetUniform<Vector2>("flashlightSize").UpdateValue(ref flashlightSize);
shader.GetUniform<float>("flashlightDim").UpdateValue(ref flashlightDim);
Texture.WhitePixel.DrawQuad(screenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction); Texture.WhitePixel.DrawQuad(screenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction);

View File

@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic; using System.Collections.Concurrent;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Configuration;
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets
/// </summary> /// </summary>
public class RulesetConfigCache : Component public class RulesetConfigCache : Component
{ {
private readonly Dictionary<int, IRulesetConfigManager> configCache = new Dictionary<int, IRulesetConfigManager>(); private readonly ConcurrentDictionary<int, IRulesetConfigManager> configCache = new ConcurrentDictionary<int, IRulesetConfigManager>();
private readonly SettingsStore settingsStore; private readonly SettingsStore settingsStore;
public RulesetConfigCache(SettingsStore settingsStore) public RulesetConfigCache(SettingsStore settingsStore)
@ -34,10 +34,7 @@ namespace osu.Game.Rulesets
if (ruleset.RulesetInfo.ID == null) if (ruleset.RulesetInfo.ID == null)
throw new InvalidOperationException("The provided ruleset doesn't have a valid id."); throw new InvalidOperationException("The provided ruleset doesn't have a valid id.");
if (configCache.TryGetValue(ruleset.RulesetInfo.ID.Value, out var existing)) return configCache.GetOrAdd(ruleset.RulesetInfo.ID.Value, _ => ruleset.CreateConfig(settingsStore));
return existing;
return configCache[ruleset.RulesetInfo.ID.Value] = ruleset.CreateConfig(settingsStore);
} }
} }
} }

View File

@ -360,6 +360,9 @@ namespace osu.Game.Rulesets.Scoring
JudgedHits--; JudgedHits--;
if (result.Type != HitResult.None)
scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) - 1;
if (result.Judgement.IsBonus) if (result.Judgement.IsBonus)
{ {
if (result.IsHit) if (result.IsHit)

View File

@ -12,6 +12,7 @@ using osu.Game.Rulesets.Objects.Drawables;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Framework.Input; using osu.Framework.Input;
@ -59,6 +60,8 @@ namespace osu.Game.Rulesets.UI
/// </summary> /// </summary>
public Container Overlays { get; private set; } public Container Overlays { get; private set; }
public override GameplayClock FrameStableClock => frameStabilityContainer.GameplayClock;
/// <summary> /// <summary>
/// Invoked when a <see cref="JudgementResult"/> has been applied by a <see cref="DrawableHitObject"/>. /// Invoked when a <see cref="JudgementResult"/> has been applied by a <see cref="DrawableHitObject"/>.
/// </summary> /// </summary>
@ -140,11 +143,11 @@ namespace osu.Game.Rulesets.UI
public virtual PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new PlayfieldAdjustmentContainer(); public virtual PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new PlayfieldAdjustmentContainer();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config) private void load(OsuConfigManager config, CancellationToken? cancellationToken)
{ {
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
frameStabilityContainer = new FrameStabilityContainer frameStabilityContainer = new FrameStabilityContainer(GameplayStartTime)
{ {
Child = KeyBindingInputManager Child = KeyBindingInputManager
.WithChild(CreatePlayfieldAdjustmentContainer() .WithChild(CreatePlayfieldAdjustmentContainer()
@ -163,16 +166,21 @@ namespace osu.Game.Rulesets.UI
applyRulesetMods(mods, config); applyRulesetMods(mods, config);
loadObjects(); loadObjects(cancellationToken);
} }
/// <summary> /// <summary>
/// Creates and adds drawable representations of hit objects to the play field. /// Creates and adds drawable representations of hit objects to the play field.
/// </summary> /// </summary>
private void loadObjects() private void loadObjects(CancellationToken? cancellationToken)
{ {
foreach (TObject h in Beatmap.HitObjects) foreach (TObject h in Beatmap.HitObjects)
{
cancellationToken?.ThrowIfCancellationRequested();
addHitObject(h); addHitObject(h);
}
cancellationToken?.ThrowIfCancellationRequested();
Playfield.PostProcess(); Playfield.PostProcess();
@ -334,6 +342,11 @@ namespace osu.Game.Rulesets.UI
/// </summary> /// </summary>
public readonly BindableBool IsPaused = new BindableBool(); public readonly BindableBool IsPaused = new BindableBool();
/// <summary>
/// The frame-stable clock which is being used for playfield display.
/// </summary>
public abstract GameplayClock FrameStableClock { get; }
/// <summary>~ /// <summary>~
/// The associated ruleset. /// The associated ruleset.
/// </summary> /// </summary>

View File

@ -17,19 +17,29 @@ namespace osu.Game.Rulesets.UI
/// </summary> /// </summary>
public class FrameStabilityContainer : Container, IHasReplayHandler public class FrameStabilityContainer : Container, IHasReplayHandler
{ {
public FrameStabilityContainer() private readonly double gameplayStartTime;
/// <summary>
/// The number of frames (per parent frame) which can be run in an attempt to catch-up to real-time.
/// </summary>
public int MaxCatchUpFrames { get; set; } = 5;
[Cached]
public GameplayClock GameplayClock { get; }
public FrameStabilityContainer(double gameplayStartTime = double.MinValue)
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
gameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock()));
GameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock()));
this.gameplayStartTime = gameplayStartTime;
} }
private readonly ManualClock manualClock; private readonly ManualClock manualClock;
private readonly FramedClock framedClock; private readonly FramedClock framedClock;
[Cached]
private GameplayClock gameplayClock;
private IFrameBasedClock parentGameplayClock; private IFrameBasedClock parentGameplayClock;
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
@ -38,7 +48,7 @@ namespace osu.Game.Rulesets.UI
if (clock != null) if (clock != null)
{ {
parentGameplayClock = clock; parentGameplayClock = clock;
gameplayClock.IsPaused.BindTo(clock.IsPaused); GameplayClock.IsPaused.BindTo(clock.IsPaused);
} }
} }
@ -64,8 +74,6 @@ namespace osu.Game.Rulesets.UI
private bool isAttached => ReplayInputHandler != null; private bool isAttached => ReplayInputHandler != null;
private const int max_catch_up_updates_per_frame = 50;
private const double sixty_frame_time = 1000.0 / 60; private const double sixty_frame_time = 1000.0 / 60;
private bool firstConsumption = true; private bool firstConsumption = true;
@ -73,11 +81,11 @@ namespace osu.Game.Rulesets.UI
public override bool UpdateSubTree() public override bool UpdateSubTree()
{ {
requireMoreUpdateLoops = true; requireMoreUpdateLoops = true;
validState = !gameplayClock.IsPaused.Value; validState = !GameplayClock.IsPaused.Value;
int loops = 0; int loops = 0;
while (validState && requireMoreUpdateLoops && loops++ < max_catch_up_updates_per_frame) while (validState && requireMoreUpdateLoops && loops++ < MaxCatchUpFrames)
{ {
updateClock(); updateClock();
@ -116,6 +124,8 @@ namespace osu.Game.Rulesets.UI
firstConsumption = false; firstConsumption = false;
} }
else if (manualClock.CurrentTime < gameplayStartTime)
manualClock.CurrentTime = newProposedTime = Math.Min(gameplayStartTime, newProposedTime);
else if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) else if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f)
{ {
newProposedTime = newProposedTime > manualClock.CurrentTime newProposedTime = newProposedTime > manualClock.CurrentTime
@ -160,7 +170,7 @@ namespace osu.Game.Rulesets.UI
if (parentGameplayClock == null) if (parentGameplayClock == null)
parentGameplayClock = Clock; parentGameplayClock = Clock;
Clock = gameplayClock; Clock = GameplayClock;
ProcessCustomClock = false; ProcessCustomClock = false;
} }

View File

@ -25,9 +25,9 @@ namespace osu.Game.Scoring
protected override string ImportFromStablePath => "Replays"; protected override string ImportFromStablePath => "Replays";
private readonly RulesetStore rulesets; private readonly RulesetStore rulesets;
private readonly BeatmapManager beatmaps; private readonly Func<BeatmapManager> beatmaps;
public ScoreManager(RulesetStore rulesets, BeatmapManager beatmaps, Storage storage, IDatabaseContextFactory contextFactory, IIpcHost importHost = null) public ScoreManager(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, IDatabaseContextFactory contextFactory, IIpcHost importHost = null)
: base(storage, contextFactory, new ScoreStore(contextFactory, storage), importHost) : base(storage, contextFactory, new ScoreStore(contextFactory, storage), importHost)
{ {
this.rulesets = rulesets; this.rulesets = rulesets;
@ -43,7 +43,7 @@ namespace osu.Game.Scoring
{ {
try try
{ {
return new DatabasedLegacyScoreParser(rulesets, beatmaps).Parse(stream).ScoreInfo; return new DatabasedLegacyScoreParser(rulesets, beatmaps()).Parse(stream).ScoreInfo;
} }
catch (LegacyScoreParser.BeatmapNotFoundException e) catch (LegacyScoreParser.BeatmapNotFoundException e)
{ {
@ -53,7 +53,7 @@ namespace osu.Game.Scoring
} }
} }
public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps, Files.Store); public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps(), Files.Store);
public List<ScoreInfo> GetAllUsableScores() => ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList(); public List<ScoreInfo> GetAllUsableScores() => ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList();

View File

@ -92,6 +92,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
} }
} }
protected override void LoadComplete()
{
base.LoadComplete();
// This width only gets updated on the application of a transform, so this needs to be initialized here.
updateZoomedContentWidth();
}
protected override bool OnScroll(ScrollEvent e) protected override bool OnScroll(ScrollEvent e)
{ {
if (e.IsPrecise) if (e.IsPrecise)
@ -102,6 +110,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
return true; return true;
} }
private void updateZoomedContentWidth() => zoomedContent.Width = DrawWidth * currentZoom;
private float zoomTarget = 1; private float zoomTarget = 1;
private void setZoomTarget(float newZoom, float focusPoint) private void setZoomTarget(float newZoom, float focusPoint)
@ -163,7 +173,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
d.currentZoom = newZoom; d.currentZoom = newZoom;
d.zoomedContent.Width = d.DrawWidth * d.currentZoom; d.updateZoomedContentWidth();
// Temporarily here to make sure ScrollTo gets the correct DrawSize for scrollable area. // Temporarily here to make sure ScrollTo gets the correct DrawSize for scrollable area.
// TODO: Make sure draw size gets invalidated properly on the framework side, and remove this once it is. // TODO: Make sure draw size gets invalidated properly on the framework side, and remove this once it is.
d.Invalidate(Invalidation.DrawSize); d.Invalidate(Invalidation.DrawSize);

View File

@ -25,6 +25,8 @@ namespace osu.Game.Screens.Play.HUD
{ {
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
public readonly Bindable<bool> IsPaused = new Bindable<bool>();
private readonly Button button; private readonly Button button;
public Action Action public Action Action
@ -51,7 +53,8 @@ namespace osu.Game.Screens.Play.HUD
button = new Button button = new Button
{ {
HoverGained = () => text.FadeIn(500, Easing.OutQuint), HoverGained = () => text.FadeIn(500, Easing.OutQuint),
HoverLost = () => text.FadeOut(500, Easing.OutQuint) HoverLost = () => text.FadeOut(500, Easing.OutQuint),
IsPaused = { BindTarget = IsPaused }
} }
}; };
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
@ -94,6 +97,8 @@ namespace osu.Game.Screens.Play.HUD
private CircularProgress circularProgress; private CircularProgress circularProgress;
private Circle overlayCircle; private Circle overlayCircle;
public readonly Bindable<bool> IsPaused = new Bindable<bool>();
protected override bool AllowMultipleFires => true; protected override bool AllowMultipleFires => true;
public Action HoverGained; public Action HoverGained;
@ -217,7 +222,7 @@ namespace osu.Game.Screens.Play.HUD
private void updateActive() private void updateActive()
{ {
if (!pauseOnFocusLost) return; if (!pauseOnFocusLost || IsPaused.Value) return;
if (gameActive.Value) if (gameActive.Value)
AbortConfirm(); AbortConfirm();

View File

@ -24,6 +24,8 @@ namespace osu.Game.Screens.Play.HUD
public bool DisplayUnrankedText = true; public bool DisplayUnrankedText = true;
public bool AllowExpand = true;
private readonly Bindable<IReadOnlyList<Mod>> current = new Bindable<IReadOnlyList<Mod>>(); private readonly Bindable<IReadOnlyList<Mod>> current = new Bindable<IReadOnlyList<Mod>>();
public Bindable<IReadOnlyList<Mod>> Current public Bindable<IReadOnlyList<Mod>> Current
@ -88,7 +90,9 @@ namespace osu.Game.Screens.Play.HUD
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
appearTransform(); appearTransform();
iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint);
} }
private void appearTransform() private void appearTransform()
@ -98,14 +102,17 @@ namespace osu.Game.Screens.Play.HUD
else else
unrankedText.Hide(); unrankedText.Hide();
iconsContainer.FinishTransforms();
iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint);
expand(); expand();
using (iconsContainer.BeginDelayedSequence(1200)) using (iconsContainer.BeginDelayedSequence(1200))
contract(); contract();
} }
private void expand() => iconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint); private void expand()
{
if (AllowExpand)
iconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint);
}
private void contract() => iconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint); private void contract() => iconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint);

View File

@ -35,6 +35,10 @@ namespace osu.Game.Screens.Play
public readonly HoldForMenuButton HoldToQuit; public readonly HoldForMenuButton HoldToQuit;
public readonly PlayerSettingsOverlay PlayerSettingsOverlay; public readonly PlayerSettingsOverlay PlayerSettingsOverlay;
private readonly ScoreProcessor scoreProcessor;
private readonly DrawableRuleset drawableRuleset;
private readonly IReadOnlyList<Mod> mods;
private Bindable<bool> showHud; private Bindable<bool> showHud;
private readonly Container visibilityContainer; private readonly Container visibilityContainer;
private readonly BindableBool replayLoaded = new BindableBool(); private readonly BindableBool replayLoaded = new BindableBool();
@ -45,6 +49,10 @@ namespace osu.Game.Screens.Play
public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, IReadOnlyList<Mod> mods) public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, IReadOnlyList<Mod> mods)
{ {
this.scoreProcessor = scoreProcessor;
this.drawableRuleset = drawableRuleset;
this.mods = mods;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
Children = new Drawable[] Children = new Drawable[]
@ -89,20 +97,21 @@ namespace osu.Game.Screens.Play
} }
} }
}; };
}
[BackgroundDependencyLoader(true)]
private void load(OsuConfigManager config, NotificationOverlay notificationOverlay)
{
BindProcessor(scoreProcessor); BindProcessor(scoreProcessor);
BindDrawableRuleset(drawableRuleset); BindDrawableRuleset(drawableRuleset);
Progress.Objects = drawableRuleset.Objects; Progress.Objects = drawableRuleset.Objects;
Progress.AllowSeeking = drawableRuleset.HasReplayLoaded.Value; Progress.AllowSeeking = drawableRuleset.HasReplayLoaded.Value;
Progress.RequestSeek = time => RequestSeek(time); Progress.RequestSeek = time => RequestSeek(time);
Progress.ReferenceClock = drawableRuleset.FrameStableClock;
ModDisplay.Current.Value = mods; ModDisplay.Current.Value = mods;
}
[BackgroundDependencyLoader(true)]
private void load(OsuConfigManager config, NotificationOverlay notificationOverlay)
{
showHud = config.GetBindable<bool>(OsuSetting.ShowInterface); showHud = config.GetBindable<bool>(OsuSetting.ShowInterface);
showHud.ValueChanged += visible => visibilityContainer.FadeTo(visible.NewValue ? 1 : 0, duration); showHud.ValueChanged += visible => visibilityContainer.FadeTo(visible.NewValue ? 1 : 0, duration);
showHud.TriggerChange(); showHud.TriggerChange();
@ -122,8 +131,7 @@ namespace osu.Game.Screens.Play
{ {
base.LoadComplete(); base.LoadComplete();
replayLoaded.ValueChanged += replayLoadedValueChanged; replayLoaded.BindValueChanged(replayLoadedValueChanged, true);
replayLoaded.TriggerChange();
} }
private void replayLoadedValueChanged(ValueChangedEvent<bool> e) private void replayLoadedValueChanged(ValueChangedEvent<bool> e)

View File

@ -43,6 +43,11 @@ namespace osu.Game.Screens.Play
public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered; public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered;
/// <summary>
/// Whether gameplay should pause when the game window focus is lost.
/// </summary>
protected virtual bool PauseOnFocusLost => true;
public Action RestartRequested; public Action RestartRequested;
public bool HasFailed { get; private set; } public bool HasFailed { get; private set; }
@ -135,7 +140,11 @@ namespace osu.Game.Screens.Play
DrawableRuleset.Cursor?.CreateProxy() ?? new Container(), DrawableRuleset.Cursor?.CreateProxy() ?? new Container(),
HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, Mods.Value) HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, Mods.Value)
{ {
HoldToQuit = { Action = performUserRequestedExit }, HoldToQuit =
{
Action = performUserRequestedExit,
IsPaused = { BindTarget = GameplayClockContainer.IsPaused }
},
PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } }, PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } },
KeyCounter = { Visible = { BindTarget = DrawableRuleset.HasReplayLoaded } }, KeyCounter = { Visible = { BindTarget = DrawableRuleset.HasReplayLoaded } },
RequestSeek = GameplayClockContainer.Seek, RequestSeek = GameplayClockContainer.Seek,
@ -170,6 +179,8 @@ namespace osu.Game.Screens.Play
} }
}; };
DrawableRuleset.HasReplayLoaded.BindValueChanged(e => HUDOverlay.HoldToQuit.PauseOnFocusLost = !e.NewValue && PauseOnFocusLost, true);
// bind clock into components that require it // bind clock into components that require it
DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused);
@ -406,8 +417,9 @@ namespace osu.Game.Screens.Play
IsResuming = true; IsResuming = true;
PauseOverlay.Hide(); PauseOverlay.Hide();
// time-based conditions may allow instant resume. // breaks and time-based conditions may allow instant resume.
if (GameplayClockContainer.GameplayClock.CurrentTime < Beatmap.Value.Beatmap.HitObjects.First().StartTime) double time = GameplayClockContainer.GameplayClock.CurrentTime;
if (Beatmap.Value.Beatmap.Breaks.Any(b => b.Contains(time)) || time < Beatmap.Value.Beatmap.HitObjects.First().StartTime)
completeResume(); completeResume();
else else
DrawableRuleset.RequestResume(completeResume); DrawableRuleset.RequestResume(completeResume);

View File

@ -10,6 +10,7 @@ using osu.Game.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using System.Linq; using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Timing;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -55,7 +56,9 @@ namespace osu.Game.Screens.Play
private readonly BindableBool replayLoaded = new BindableBool(); private readonly BindableBool replayLoaded = new BindableBool();
private GameplayClock gameplayClock; public IClock ReferenceClock;
private IClock gameplayClock;
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(OsuColour colours, GameplayClock clock) private void load(OsuColour colours, GameplayClock clock)
@ -154,10 +157,12 @@ namespace osu.Game.Screens.Play
if (objects == null) if (objects == null)
return; return;
double position = gameplayClock?.CurrentTime ?? Time.Current; double gameplayTime = gameplayClock?.CurrentTime ?? Time.Current;
double progress = Math.Min(1, (position - firstHitTime) / (lastHitTime - firstHitTime)); double frameStableTime = ReferenceClock?.CurrentTime ?? gameplayTime;
bar.CurrentTime = position; double progress = Math.Min(1, (frameStableTime - firstHitTime) / (lastHitTime - firstHitTime));
bar.CurrentTime = gameplayTime;
graph.Progress = (int)(graph.ColumnCount * progress); graph.Progress = (int)(graph.ColumnCount * progress);
} }
} }

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.MathUtils;
namespace osu.Game.Screens.Play namespace osu.Game.Screens.Play
{ {
@ -107,9 +108,17 @@ namespace osu.Game.Screens.Play
protected override void UpdateValue(float value) protected override void UpdateValue(float value)
{ {
var xFill = value * UsableWidth; // handled in update
fill.Width = xFill; }
handleBase.X = xFill;
protected override void Update()
{
base.Update();
float newX = (float)Interpolation.Lerp(handleBase.X, NormalizedValue * UsableWidth, MathHelper.Clamp(Time.Elapsed / 40, 0, 1));
fill.Width = newX;
handleBase.X = newX;
} }
protected override void OnUserChange(double value) => OnSeek?.Invoke(value); protected override void OnUserChange(double value) => OnSeek?.Invoke(value);

View File

@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osuTK.Input;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -20,9 +19,6 @@ namespace osu.Game.Screens.Select
{ {
private readonly Box modeLight; private readonly Box modeLight;
private const float play_song_select_button_width = 100;
private const float play_song_select_button_height = 50;
public const float HEIGHT = 50; public const float HEIGHT = 50;
public const int TRANSITION_LENGTH = 300; public const int TRANSITION_LENGTH = 300;
@ -33,57 +29,36 @@ namespace osu.Game.Screens.Select
private readonly FillFlowContainer<FooterButton> buttons; private readonly FillFlowContainer<FooterButton> buttons;
/// <param name="text">Text on the button.</param>
/// <param name="colour">Colour of the button.</param>
/// <param name="hotkey">Hotkey of the button.</param>
/// <param name="action">Action the button does.</param>
/// <param name="depth">
/// <para>Higher depth to be put on the left, and lower to be put on the right.</para>
/// <para>Notice this is different to <see cref="Options.BeatmapOptionsOverlay"/>!</para>
/// </param>
public void AddButton(string text, Color4 colour, Action action, Key? hotkey = null, float depth = 0)
{
var button = new FooterButton
{
Text = text,
Height = play_song_select_button_height,
Width = play_song_select_button_width,
Depth = depth,
SelectedColour = colour,
DeselectedColour = colour.Opacity(0.5f),
Hotkey = hotkey,
Hovered = updateModeLight,
HoverLost = updateModeLight,
Action = action,
};
buttons.Add(button);
buttons.SetLayoutPosition(button, -depth);
}
private readonly List<OverlayContainer> overlays = new List<OverlayContainer>(); private readonly List<OverlayContainer> overlays = new List<OverlayContainer>();
/// <param name="text">Text on the button.</param> /// <param name="button">THe button to be added.</param>
/// <param name="colour">Colour of the button.</param>
/// <param name="hotkey">Hotkey of the button.</param>
/// <param name="overlay">The <see cref="OverlayContainer"/> to be toggled by this button.</param> /// <param name="overlay">The <see cref="OverlayContainer"/> to be toggled by this button.</param>
/// <param name="depth"> public void AddButton(FooterButton button, OverlayContainer overlay)
/// <para>Higher depth to be put on the left, and lower to be put on the right.</para>
/// <para>Notice this is different to <see cref="Options.BeatmapOptionsOverlay"/>!</para>
/// </param>
public void AddButton(string text, Color4 colour, OverlayContainer overlay, Key? hotkey = null, float depth = 0)
{ {
overlays.Add(overlay); overlays.Add(overlay);
AddButton(text, colour, () => button.Action = () => showOverlay(overlay);
AddButton(button);
}
/// <param name="button">Button to be added.</param>
public void AddButton(FooterButton button)
{
button.Hovered = updateModeLight;
button.HoverLost = updateModeLight;
buttons.Add(button);
}
private void showOverlay(OverlayContainer overlay)
{
foreach (var o in overlays)
{ {
foreach (var o in overlays) if (o == overlay)
{ o.ToggleVisibility();
if (o == overlay) else
o.ToggleVisibility(); o.Hide();
else }
o.Hide();
}
}, hotkey, depth);
} }
private void updateModeLight() => modeLight.FadeColour(buttons.FirstOrDefault(b => b.IsHovered)?.SelectedColour ?? Color4.Transparent, TRANSITION_LENGTH, Easing.OutQuint); private void updateModeLight() => modeLight.FadeColour(buttons.FirstOrDefault(b => b.IsHovered)?.SelectedColour ?? Color4.Transparent, TRANSITION_LENGTH, Easing.OutQuint);

View File

@ -6,6 +6,7 @@ using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osuTK.Input; using osuTK.Input;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
@ -20,11 +21,11 @@ namespace osu.Game.Screens.Select
public string Text public string Text
{ {
get => spriteText?.Text; get => SpriteText?.Text;
set set
{ {
if (spriteText != null) if (SpriteText != null)
spriteText.Text = value; SpriteText.Text = value;
} }
} }
@ -53,7 +54,8 @@ namespace osu.Game.Screens.Select
} }
} }
private readonly SpriteText spriteText; protected readonly Container TextContainer;
protected readonly SpriteText SpriteText;
private readonly Box box; private readonly Box box;
private readonly Box light; private readonly Box light;
@ -61,8 +63,18 @@ namespace osu.Game.Screens.Select
public FooterButton() public FooterButton()
{ {
AutoSizeAxes = Axes.Both;
Children = new Drawable[] Children = new Drawable[]
{ {
TextContainer = new Container
{
Size = new Vector2(100, 50),
Child = SpriteText = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
},
box = new Box box = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -78,11 +90,6 @@ namespace osu.Game.Screens.Select
EdgeSmoothness = new Vector2(2, 0), EdgeSmoothness = new Vector2(2, 0),
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
}, },
spriteText = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
}; };
} }

View File

@ -0,0 +1,60 @@
// 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.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Screens.Play.HUD;
using osu.Game.Rulesets.Mods;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Game.Graphics;
using osuTK;
using osuTK.Input;
namespace osu.Game.Screens.Select
{
public class FooterButtonMods : FooterButton
{
public FooterButtonMods(Bindable<IReadOnlyList<Mod>> mods)
{
FooterModDisplay modDisplay;
Add(new Container
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Child = modDisplay = new FooterModDisplay
{
DisplayUnrankedText = false,
Scale = new Vector2(0.8f)
},
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Left = 70 }
});
if (mods != null)
modDisplay.Current = mods;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
SelectedColour = colours.Yellow;
DeselectedColour = SelectedColour.Opacity(0.5f);
Text = @"mods";
Hotkey = Key.F1;
}
private class FooterModDisplay : ModDisplay
{
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.Parent?.ReceivePositionalInputAt(screenSpacePos) ?? false;
public FooterModDisplay()
{
AllowExpand = false;
}
}
}
}

View File

@ -0,0 +1,22 @@
// 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.Extensions.Color4Extensions;
using osu.Game.Graphics;
using osuTK.Input;
namespace osu.Game.Screens.Select
{
public class FooterButtonOptions : FooterButton
{
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
SelectedColour = colours.Blue;
DeselectedColour = SelectedColour.Opacity(0.5f);
Text = @"options";
Hotkey = Key.F3;
}
}
}

View File

@ -0,0 +1,68 @@
// 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.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK.Input;
namespace osu.Game.Screens.Select
{
public class FooterButtonRandom : FooterButton
{
private readonly SpriteText secondaryText;
private bool secondaryActive;
public FooterButtonRandom()
{
TextContainer.Add(secondaryText = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = @"rewind",
Alpha = 0
});
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
SelectedColour = colours.Green;
DeselectedColour = SelectedColour.Opacity(0.5f);
Text = @"random";
Hotkey = Key.F2;
}
protected override bool OnKeyDown(KeyDownEvent e)
{
secondaryActive = e.ShiftPressed;
updateText();
return base.OnKeyDown(e);
}
protected override bool OnKeyUp(KeyUpEvent e)
{
secondaryActive = e.ShiftPressed;
updateText();
return base.OnKeyUp(e);
}
private void updateText()
{
if (secondaryActive)
{
SpriteText.FadeOut(120, Easing.InQuad);
secondaryText.FadeIn(120, Easing.InQuad);
}
else
{
SpriteText.FadeIn(120, Easing.InQuad);
secondaryText.FadeOut(120, Easing.InQuad);
}
}
}
}

View File

@ -39,6 +39,7 @@ namespace osu.Game.Screens.Select
public abstract class SongSelect : OsuScreen public abstract class SongSelect : OsuScreen
{ {
private static readonly Vector2 wedged_container_size = new Vector2(0.5f, 245); private static readonly Vector2 wedged_container_size = new Vector2(0.5f, 245);
protected const float BACKGROUND_BLUR = 20; protected const float BACKGROUND_BLUR = 20;
private const float left_area_padding = 20; private const float left_area_padding = 20;
@ -89,8 +90,6 @@ namespace osu.Game.Screens.Select
protected SongSelect() protected SongSelect()
{ {
const float carousel_width = 640;
AddRangeInternal(new Drawable[] AddRangeInternal(new Drawable[]
{ {
new ParallaxContainer new ParallaxContainer
@ -103,7 +102,8 @@ namespace osu.Game.Screens.Select
new WedgeBackground new WedgeBackground
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Right = carousel_width * 0.76f }, Padding = new MarginPadding { Right = -150 },
Size = new Vector2(wedged_container_size.X, 1),
} }
} }
}, },
@ -144,8 +144,8 @@ namespace osu.Game.Screens.Select
Carousel = new BeatmapCarousel Carousel = new BeatmapCarousel
{ {
Masking = false, Masking = false,
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Both,
Size = new Vector2(carousel_width, 1), Size = new Vector2(1 - wedged_container_size.X, 1),
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
SelectionChanged = updateSelectedBeatmap, SelectionChanged = updateSelectedBeatmap,
@ -223,9 +223,9 @@ namespace osu.Game.Screens.Select
if (Footer != null) if (Footer != null)
{ {
Footer.AddButton(@"mods", colours.Yellow, ModSelect, Key.F1); Footer.AddButton(new FooterButtonMods(mods), ModSelect);
Footer.AddButton(@"random", colours.Green, triggerRandom, Key.F2); Footer.AddButton(new FooterButtonRandom { Action = triggerRandom });
Footer.AddButton(@"options", colours.Blue, BeatmapOptions, Key.F3); Footer.AddButton(new FooterButtonOptions(), BeatmapOptions);
BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); 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(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null, Key.Number1);
@ -505,6 +505,8 @@ namespace osu.Game.Screens.Select
{ {
ModSelect.Hide(); ModSelect.Hide();
BeatmapOptions.Hide();
this.ScaleTo(1.1f, 250, Easing.InSine); this.ScaleTo(1.1f, 250, Easing.InSine);
this.FadeOut(250); this.FadeOut(250);

View File

@ -1,6 +1,7 @@
// 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. // See the LICENCE file in the repository root for full licence text.
using System.Threading;
using osuTK; using osuTK;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -57,7 +58,7 @@ namespace osu.Game.Storyboards.Drawables
} }
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(FileStore fileStore, GameplayClock clock) private void load(FileStore fileStore, GameplayClock clock, CancellationToken? cancellationToken)
{ {
if (clock != null) if (clock != null)
Clock = clock; Clock = clock;
@ -65,7 +66,11 @@ namespace osu.Game.Storyboards.Drawables
dependencies.Cache(new TextureStore(new TextureLoaderStore(fileStore.Store), false, scaleAdjust: 1)); dependencies.Cache(new TextureStore(new TextureLoaderStore(fileStore.Store), false, scaleAdjust: 1));
foreach (var layer in Storyboard.Layers) foreach (var layer in Storyboard.Layers)
{
cancellationToken?.ThrowIfCancellationRequested();
Add(layer.CreateDrawable()); Add(layer.CreateDrawable());
}
} }
private void updateLayerVisibility() private void updateLayerVisibility()

View File

@ -1,6 +1,7 @@
// 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. // See the LICENCE file in the repository root for full licence text.
using System.Threading;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -24,10 +25,12 @@ namespace osu.Game.Storyboards.Drawables
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load(CancellationToken? cancellationToken)
{ {
foreach (var element in Layer.Elements) foreach (var element in Layer.Elements)
{ {
cancellationToken?.ThrowIfCancellationRequested();
if (element.IsDrawable) if (element.IsDrawable)
AddInternal(element.CreateDrawable()); AddInternal(element.CreateDrawable());
} }

View File

@ -5,11 +5,10 @@ using osu.Game.Beatmaps;
using osu.Game.Storyboards.Drawables; using osu.Game.Storyboards.Drawables;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System;
namespace osu.Game.Storyboards namespace osu.Game.Storyboards
{ {
public class Storyboard : IDisposable public class Storyboard
{ {
private readonly Dictionary<string, StoryboardLayer> layers = new Dictionary<string, StoryboardLayer>(); private readonly Dictionary<string, StoryboardLayer> layers = new Dictionary<string, StoryboardLayer>();
public IEnumerable<StoryboardLayer> Layers => layers.Values; public IEnumerable<StoryboardLayer> Layers => layers.Values;
@ -56,30 +55,5 @@ namespace osu.Game.Storyboards
drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard ? 16 / 9f : 4 / 3f); drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard ? 16 / 9f : 4 / 3f);
return drawable; return drawable;
} }
#region Disposal
~Storyboard()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private bool isDisposed;
protected virtual void Dispose(bool isDisposing)
{
if (isDisposed)
return;
isDisposed = true;
}
#endregion
} }
} }

View File

@ -6,6 +6,7 @@ using osu.Framework.Allocation;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
@ -41,6 +42,10 @@ namespace osu.Game.Tests.Visual
AddCheckSteps(); AddCheckSteps();
} }
OsuConfigManager manager;
Dependencies.Cache(manager = new OsuConfigManager(LocalStorage));
manager.GetBindable<double>(OsuSetting.DimLevel).Value = 1.0;
} }
protected abstract void AddCheckSteps(); protected abstract void AddCheckSteps();
@ -71,6 +76,6 @@ namespace osu.Game.Tests.Visual
return Player; return Player;
} }
protected virtual Player CreatePlayer(Ruleset ruleset) => new Player(false, false); protected virtual Player CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false);
} }
} }

View File

@ -1,9 +1,11 @@
// 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. // See the LICENCE file in the repository root for full licence text.
using System.Linq; using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
@ -22,8 +24,16 @@ namespace osu.Game.Tests.Visual
this.ruleset = ruleset; this.ruleset = ruleset;
} }
[BackgroundDependencyLoader]
private void load()
{
OsuConfigManager manager;
Dependencies.Cache(manager = new OsuConfigManager(LocalStorage));
manager.GetBindable<double>(OsuSetting.DimLevel).Value = 1.0;
}
[SetUpSteps] [SetUpSteps]
public void SetUpSteps() public virtual void SetUpSteps()
{ {
AddStep(ruleset.RulesetInfo.Name, loadPlayer); AddStep(ruleset.RulesetInfo.Name, loadPlayer);
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
@ -46,6 +56,6 @@ namespace osu.Game.Tests.Visual
LoadScreen(Player); LoadScreen(Player);
} }
protected virtual Player CreatePlayer(Ruleset ruleset) => new Player(false, false); protected virtual Player CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false);
} }
} }

View File

@ -0,0 +1,17 @@
// 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.Screens.Play;
namespace osu.Game.Tests.Visual
{
public class TestPlayer : Player
{
protected override bool PauseOnFocusLost => false;
public TestPlayer(bool allowPause = true, bool showResults = true)
: base(allowPause, showResults)
{
}
}
}

View File

@ -11,11 +11,11 @@
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Humanizer" Version="2.6.2" /> <PackageReference Include="Humanizer" Version="2.6.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.4" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.128.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2019.502.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.502.0" /> <PackageReference Include="ppy.osu.Framework" Version="2019.508.0" />
<PackageReference Include="SharpCompress" Version="0.23.0" /> <PackageReference Include="SharpCompress" Version="0.23.0" />
<PackageReference Include="NUnit" Version="3.11.0" /> <PackageReference Include="NUnit" Version="3.11.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />