mirror of
https://github.com/ppy/osu.git
synced 2024-12-15 12:42:54 +08:00
Merge branch 'master' into realm-property-watching
This commit is contained in:
commit
31d6c75f40
@ -50,7 +50,7 @@ Please make sure you have the following prerequisites:
|
|||||||
|
|
||||||
- A desktop platform with the [.NET 6.0 SDK](https://dotnet.microsoft.com/download) installed.
|
- A desktop platform with the [.NET 6.0 SDK](https://dotnet.microsoft.com/download) installed.
|
||||||
- When developing with mobile, [Xamarin](https://docs.microsoft.com/en-us/xamarin/) is required, which is shipped together with Visual Studio or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).
|
- When developing with mobile, [Xamarin](https://docs.microsoft.com/en-us/xamarin/) is required, which is shipped together with Visual Studio or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).
|
||||||
- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
|
- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as the latest version of [Visual Studio](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
|
||||||
- When running on Linux, please have a system-wide FFmpeg installation available to support video decoding.
|
- When running on Linux, please have a system-wide FFmpeg installation available to support video decoding.
|
||||||
|
|
||||||
### Downloading the source code
|
### Downloading the source code
|
||||||
@ -72,7 +72,7 @@ git pull
|
|||||||
|
|
||||||
Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this is provided [below](#contributing).
|
Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this is provided [below](#contributing).
|
||||||
|
|
||||||
- Visual Studio / Rider users should load the project via one of the platform-specific `.slnf` files, rather than the main `.sln.` This will allow access to template run configurations.
|
- Visual Studio / Rider users should load the project via one of the platform-specific `.slnf` files, rather than the main `.sln`. This will allow access to template run configurations.
|
||||||
|
|
||||||
You can also build and run *osu!* from the command-line with a single command:
|
You can also build and run *osu!* from the command-line with a single command:
|
||||||
|
|
||||||
|
@ -51,8 +51,8 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.211.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.304.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.223.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.304.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||||
|
@ -10,6 +10,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
@ -108,10 +109,7 @@ namespace osu.Desktop
|
|||||||
presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty);
|
presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty);
|
||||||
|
|
||||||
// update ruleset
|
// update ruleset
|
||||||
int onlineID = ruleset.Value.OnlineID;
|
presence.Assets.SmallImageKey = ruleset.Value.IsLegacyRuleset() ? $"mode_{ruleset.Value.OnlineID}" : "mode_custom";
|
||||||
bool isLegacyRuleset = onlineID >= 0 && onlineID <= ILegacyRuleset.MAX_LEGACY_RULESET_ID;
|
|
||||||
|
|
||||||
presence.Assets.SmallImageKey = isLegacyRuleset ? $"mode_{onlineID}" : "mode_custom";
|
|
||||||
presence.Assets.SmallImageText = ruleset.Value.Name;
|
presence.Assets.SmallImageText = ruleset.Value.Name;
|
||||||
|
|
||||||
client.SetPresence(presence);
|
client.SetPresence(presence);
|
||||||
|
@ -45,10 +45,5 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TimeSlider : OsuSliderBar<double>
|
|
||||||
{
|
|
||||||
public override LocalisableString TooltipText => Current.Value.ToString(@"N0") + "ms";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,23 @@
|
|||||||
// 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.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Graphics.Audio;
|
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -118,59 +112,6 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
AddUntilStep("sample has lifetime end", () => sample.LifetimeEnd < double.MaxValue);
|
AddUntilStep("sample has lifetime end", () => sample.LifetimeEnd < double.MaxValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(typeof(OsuModDoubleTime), 1.5)]
|
|
||||||
[TestCase(typeof(OsuModHalfTime), 0.75)]
|
|
||||||
[TestCase(typeof(ModWindUp), 1.5)]
|
|
||||||
[TestCase(typeof(ModWindDown), 0.75)]
|
|
||||||
[TestCase(typeof(OsuModDoubleTime), 2)]
|
|
||||||
[TestCase(typeof(OsuModHalfTime), 0.5)]
|
|
||||||
[TestCase(typeof(ModWindUp), 2)]
|
|
||||||
[TestCase(typeof(ModWindDown), 0.5)]
|
|
||||||
public void TestSamplePlaybackWithRateMods(Type expectedMod, double expectedRate)
|
|
||||||
{
|
|
||||||
GameplayClockContainer gameplayContainer = null;
|
|
||||||
StoryboardSampleInfo sampleInfo = null;
|
|
||||||
TestDrawableStoryboardSample sample = null;
|
|
||||||
|
|
||||||
Mod testedMod = Activator.CreateInstance(expectedMod) as Mod;
|
|
||||||
|
|
||||||
switch (testedMod)
|
|
||||||
{
|
|
||||||
case ModRateAdjust m:
|
|
||||||
m.SpeedChange.Value = expectedRate;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ModTimeRamp m:
|
|
||||||
m.FinalRate.Value = m.InitialRate.Value = expectedRate;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
AddStep("setup storyboard sample", () =>
|
|
||||||
{
|
|
||||||
Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, this);
|
|
||||||
SelectedMods.Value = new[] { testedMod };
|
|
||||||
|
|
||||||
var beatmapSkinSourceContainer = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
|
|
||||||
|
|
||||||
Add(gameplayContainer = new MasterGameplayClockContainer(Beatmap.Value, 0)
|
|
||||||
{
|
|
||||||
Child = beatmapSkinSourceContainer
|
|
||||||
});
|
|
||||||
|
|
||||||
beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(sampleInfo = new StoryboardSampleInfo("test-sample", 1, 1))
|
|
||||||
{
|
|
||||||
Clock = gameplayContainer.GameplayClock
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
AddStep("start", () => gameplayContainer.Start());
|
|
||||||
|
|
||||||
AddAssert("sample playback rate matches mod rates", () =>
|
|
||||||
testedMod != null && Precision.AlmostEquals(
|
|
||||||
sample.ChildrenOfType<DrawableSample>().First().AggregateFrequency.Value,
|
|
||||||
((IApplicableToRate)testedMod).ApplyToRate(sampleInfo.StartTime)));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSamplePlaybackWithBeatmapHitsoundsOff()
|
public void TestSamplePlaybackWithBeatmapHitsoundsOff()
|
||||||
{
|
{
|
||||||
|
@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestDisplay()
|
public void TestCalibrationFromZero()
|
||||||
{
|
{
|
||||||
const double average_error = -4.5;
|
const double average_error = -4.5;
|
||||||
|
|
||||||
@ -62,11 +62,39 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("Has calibration button", () => offsetControl.ChildrenOfType<SettingsButton>().Any());
|
AddUntilStep("Has calibration button", () => offsetControl.ChildrenOfType<SettingsButton>().Any());
|
||||||
AddStep("Press button", () => offsetControl.ChildrenOfType<SettingsButton>().Single().TriggerClick());
|
AddStep("Press button", () => offsetControl.ChildrenOfType<SettingsButton>().Single().TriggerClick());
|
||||||
AddAssert("Offset is adjusted", () => offsetControl.Current.Value == -average_error);
|
AddAssert("Offset is adjusted", () => offsetControl.Current.Value == -average_error);
|
||||||
|
|
||||||
AddAssert("Button is disabled", () => !offsetControl.ChildrenOfType<SettingsButton>().Single().Enabled.Value);
|
AddUntilStep("Button is disabled", () => !offsetControl.ChildrenOfType<SettingsButton>().Single().Enabled.Value);
|
||||||
|
AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null);
|
||||||
|
AddAssert("No calibration button", () => !offsetControl.ChildrenOfType<SettingsButton>().Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When a beatmap offset was already set, the calibration should take it into account.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestCalibrationFromNonZero()
|
||||||
|
{
|
||||||
|
const double average_error = -4.5;
|
||||||
|
const double initial_offset = -2;
|
||||||
|
|
||||||
|
AddStep("Set offset non-neutral", () => offsetControl.Current.Value = initial_offset);
|
||||||
|
AddAssert("No calibration button", () => !offsetControl.ChildrenOfType<SettingsButton>().Any());
|
||||||
|
AddStep("Set reference score", () =>
|
||||||
|
{
|
||||||
|
offsetControl.ReferenceScore.Value = new ScoreInfo
|
||||||
|
{
|
||||||
|
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("Has calibration button", () => offsetControl.ChildrenOfType<SettingsButton>().Any());
|
||||||
|
AddStep("Press button", () => offsetControl.ChildrenOfType<SettingsButton>().Single().TriggerClick());
|
||||||
|
AddAssert("Offset is adjusted", () => offsetControl.Current.Value == initial_offset - average_error);
|
||||||
|
|
||||||
|
AddUntilStep("Button is disabled", () => !offsetControl.ChildrenOfType<SettingsButton>().Single().Enabled.Value);
|
||||||
AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null);
|
AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null);
|
||||||
AddAssert("No calibration button", () => !offsetControl.ChildrenOfType<SettingsButton>().Any());
|
AddAssert("No calibration button", () => !offsetControl.ChildrenOfType<SettingsButton>().Any());
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true };
|
var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true };
|
||||||
storyboardContainer.Clock = decoupledClock;
|
storyboardContainer.Clock = decoupledClock;
|
||||||
|
|
||||||
storyboard = working.Storyboard.CreateDrawable(Beatmap.Value);
|
storyboard = working.Storyboard.CreateDrawable(SelectedMods.Value);
|
||||||
storyboard.Passing = false;
|
storyboard.Passing = false;
|
||||||
|
|
||||||
storyboardContainer.Add(storyboard);
|
storyboardContainer.Add(storyboard);
|
||||||
@ -118,7 +118,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
sb = decoder.Decode(bfr);
|
sb = decoder.Decode(bfr);
|
||||||
}
|
}
|
||||||
|
|
||||||
storyboard = sb.CreateDrawable(Beatmap.Value);
|
storyboard = sb.CreateDrawable(SelectedMods.Value);
|
||||||
|
|
||||||
storyboardContainer.Add(storyboard);
|
storyboardContainer.Add(storyboard);
|
||||||
decoupledClock.ChangeSource(Beatmap.Value.Track);
|
decoupledClock.ChangeSource(Beatmap.Value.Track);
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
// 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 NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Graphics.Audio;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Storyboards;
|
using osu.Game.Storyboards;
|
||||||
using osu.Game.Storyboards.Drawables;
|
using osu.Game.Storyboards.Drawables;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
{
|
{
|
||||||
@ -19,6 +25,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
private Storyboard storyboard;
|
private Storyboard storyboard;
|
||||||
|
|
||||||
|
private IReadOnlyList<Mod> storyboardMods;
|
||||||
|
|
||||||
|
protected override bool HasCustomSteps => true;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuConfigManager config)
|
private void load(OsuConfigManager config)
|
||||||
{
|
{
|
||||||
@ -31,42 +41,107 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
backgroundLayer.Add(new StoryboardSampleInfo("Intro/welcome.mp3", time: 0, volume: 20));
|
backgroundLayer.Add(new StoryboardSampleInfo("Intro/welcome.mp3", time: 0, volume: 20));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => storyboardMods = Array.Empty<Mod>();
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestStoryboardSamplesStopDuringPause()
|
public void TestStoryboardSamplesStopDuringPause()
|
||||||
{
|
{
|
||||||
checkForFirstSamplePlayback();
|
createPlayerTest();
|
||||||
|
|
||||||
AddStep("player paused", () => Player.Pause());
|
AddStep("player paused", () => Player.Pause());
|
||||||
AddAssert("player is currently paused", () => Player.GameplayClockContainer.IsPaused.Value);
|
AddAssert("player is currently paused", () => Player.GameplayClockContainer.IsPaused.Value);
|
||||||
AddAssert("all storyboard samples stopped immediately", () => allStoryboardSamples.All(sound => !sound.IsPlaying));
|
allStoryboardSamplesStopped();
|
||||||
|
|
||||||
AddStep("player resume", () => Player.Resume());
|
AddStep("player resume", () => Player.Resume());
|
||||||
AddUntilStep("any storyboard samples playing after resume", () => allStoryboardSamples.Any(sound => sound.IsPlaying));
|
waitUntilStoryboardSamplesPlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestStoryboardSamplesStopOnSkip()
|
public void TestStoryboardSamplesStopOnSkip()
|
||||||
{
|
{
|
||||||
checkForFirstSamplePlayback();
|
createPlayerTest();
|
||||||
|
|
||||||
AddStep("skip intro", () => InputManager.Key(osuTK.Input.Key.Space));
|
skipIntro();
|
||||||
AddAssert("all storyboard samples stopped immediately", () => allStoryboardSamples.All(sound => !sound.IsPlaying));
|
allStoryboardSamplesStopped();
|
||||||
|
|
||||||
AddUntilStep("any storyboard samples playing after skip", () => allStoryboardSamples.Any(sound => sound.IsPlaying));
|
waitUntilStoryboardSamplesPlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkForFirstSamplePlayback()
|
[TestCase(typeof(OsuModDoubleTime), 1.5)]
|
||||||
|
[TestCase(typeof(OsuModDoubleTime), 2)]
|
||||||
|
[TestCase(typeof(OsuModHalfTime), 0.75)]
|
||||||
|
[TestCase(typeof(OsuModHalfTime), 0.5)]
|
||||||
|
public void TestStoryboardSamplesPlaybackWithRateAdjustMods(Type expectedMod, double expectedRate)
|
||||||
{
|
{
|
||||||
AddAssert("storyboard loaded", () => Player.Beatmap.Value.Storyboard != null);
|
AddStep("setup mod", () =>
|
||||||
AddUntilStep("any storyboard samples playing", () => allStoryboardSamples.Any(sound => sound.IsPlaying));
|
{
|
||||||
|
ModRateAdjust testedMod = (ModRateAdjust)Activator.CreateInstance(expectedMod).AsNonNull();
|
||||||
|
testedMod.SpeedChange.Value = expectedRate;
|
||||||
|
storyboardMods = new[] { testedMod };
|
||||||
|
});
|
||||||
|
|
||||||
|
createPlayerTest();
|
||||||
|
skipIntro();
|
||||||
|
|
||||||
|
AddAssert("sample playback rate matches mod rates", () => allStoryboardSamples.All(sound =>
|
||||||
|
sound.ChildrenOfType<DrawableSample>().First().AggregateFrequency.Value == expectedRate));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase(typeof(ModWindUp), 0.5, 2)]
|
||||||
|
[TestCase(typeof(ModWindUp), 1.51, 2)]
|
||||||
|
[TestCase(typeof(ModWindDown), 2, 0.5)]
|
||||||
|
[TestCase(typeof(ModWindDown), 0.99, 0.5)]
|
||||||
|
public void TestStoryboardSamplesPlaybackWithTimeRampMods(Type expectedMod, double initialRate, double finalRate)
|
||||||
|
{
|
||||||
|
AddStep("setup mod", () =>
|
||||||
|
{
|
||||||
|
ModTimeRamp testedMod = (ModTimeRamp)Activator.CreateInstance(expectedMod).AsNonNull();
|
||||||
|
testedMod.InitialRate.Value = initialRate;
|
||||||
|
testedMod.FinalRate.Value = finalRate;
|
||||||
|
storyboardMods = new[] { testedMod };
|
||||||
|
});
|
||||||
|
|
||||||
|
createPlayerTest();
|
||||||
|
skipIntro();
|
||||||
|
|
||||||
|
ModTimeRamp gameplayMod = null;
|
||||||
|
|
||||||
|
AddUntilStep("mod speed change updated", () =>
|
||||||
|
{
|
||||||
|
gameplayMod = Player.GameplayState.Mods.OfType<ModTimeRamp>().Single();
|
||||||
|
return gameplayMod.SpeedChange.Value != initialRate;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("sample playback rate matches mod rates", () => allStoryboardSamples.All(sound =>
|
||||||
|
sound.ChildrenOfType<DrawableSample>().First().AggregateFrequency.Value == gameplayMod.SpeedChange.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createPlayerTest()
|
||||||
|
{
|
||||||
|
CreateTest(null);
|
||||||
|
|
||||||
|
AddAssert("storyboard loaded", () => Player.Beatmap.Value.Storyboard != null);
|
||||||
|
waitUntilStoryboardSamplesPlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitUntilStoryboardSamplesPlay() => AddUntilStep("any storyboard samples playing", () => allStoryboardSamples.Any(sound => sound.IsPlaying));
|
||||||
|
|
||||||
|
private void allStoryboardSamplesStopped() => AddAssert("all storyboard samples stopped immediately", () => allStoryboardSamples.All(sound => !sound.IsPlaying));
|
||||||
|
|
||||||
|
private void skipIntro() => AddStep("skip intro", () => InputManager.Key(Key.Space));
|
||||||
|
|
||||||
private IEnumerable<DrawableStoryboardSample> allStoryboardSamples => Player.ChildrenOfType<DrawableStoryboardSample>();
|
private IEnumerable<DrawableStoryboardSample> allStoryboardSamples => Player.ChildrenOfType<DrawableStoryboardSample>();
|
||||||
|
|
||||||
protected override bool AllowFail => false;
|
protected override bool AllowFail => false;
|
||||||
|
|
||||||
|
protected override TestPlayer CreatePlayer(Ruleset ruleset)
|
||||||
|
{
|
||||||
|
SelectedMods.Value = SelectedMods.Value.Concat(storyboardMods).ToArray();
|
||||||
|
return new TestPlayer(true, false);
|
||||||
|
}
|
||||||
|
|
||||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||||
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, false);
|
|
||||||
|
|
||||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) =>
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) =>
|
||||||
new ClockBackedTestWorkingBeatmap(beatmap, storyboard ?? this.storyboard, Clock, Audio);
|
new ClockBackedTestWorkingBeatmap(beatmap, storyboard ?? this.storyboard, Clock, Audio);
|
||||||
|
@ -9,7 +9,6 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays.Chat;
|
using osu.Game.Overlays.Chat;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -207,7 +206,28 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestUserScrollOverride()
|
public void TestOverrideChatScrolling()
|
||||||
|
{
|
||||||
|
fillChat();
|
||||||
|
|
||||||
|
sendMessage();
|
||||||
|
checkScrolledToBottom();
|
||||||
|
|
||||||
|
AddStep("Scroll to start", () => chatDisplay.ScrollContainer.ScrollToStart());
|
||||||
|
|
||||||
|
checkNotScrolledToBottom();
|
||||||
|
sendMessage();
|
||||||
|
checkNotScrolledToBottom();
|
||||||
|
|
||||||
|
AddStep("Scroll to bottom", () => chatDisplay.ScrollContainer.ScrollToEnd());
|
||||||
|
|
||||||
|
checkScrolledToBottom();
|
||||||
|
sendMessage();
|
||||||
|
checkScrolledToBottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOverrideChatScrollingByUser()
|
||||||
{
|
{
|
||||||
fillChat();
|
fillChat();
|
||||||
|
|
||||||
@ -314,9 +334,9 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected DrawableChannel DrawableChannel => InternalChildren.OfType<DrawableChannel>().First();
|
public DrawableChannel DrawableChannel => InternalChildren.OfType<DrawableChannel>().First();
|
||||||
|
|
||||||
protected UserTrackingScrollContainer ScrollContainer => (UserTrackingScrollContainer)((Container)DrawableChannel.Child).Child;
|
public ChannelScrollContainer ScrollContainer => (ChannelScrollContainer)((Container)DrawableChannel.Child).Child;
|
||||||
|
|
||||||
public FillFlowContainer FillFlow => (FillFlowContainer)ScrollContainer.Child;
|
public FillFlowContainer FillFlow => (FillFlowContainer)ScrollContainer.Child;
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Screens.Ranking.Statistics;
|
using osu.Game.Screens.Ranking.Statistics;
|
||||||
@ -17,22 +18,33 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
{
|
{
|
||||||
public class TestSceneHitEventTimingDistributionGraph : OsuTestScene
|
public class TestSceneHitEventTimingDistributionGraph : OsuTestScene
|
||||||
{
|
{
|
||||||
|
private HitEventTimingDistributionGraph graph;
|
||||||
|
|
||||||
|
private static readonly HitObject placeholder_object = new HitCircle();
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestManyDistributedEvents()
|
public void TestManyDistributedEvents()
|
||||||
{
|
{
|
||||||
createTest(CreateDistributedHitEvents());
|
createTest(CreateDistributedHitEvents());
|
||||||
|
AddStep("add adjustment", () => graph.UpdateOffset(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestManyDistributedEventsOffset()
|
||||||
|
{
|
||||||
|
createTest(CreateDistributedHitEvents(-3.5));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestAroundCentre()
|
public void TestAroundCentre()
|
||||||
{
|
{
|
||||||
createTest(Enumerable.Range(-150, 300).Select(i => new HitEvent(i / 50f, HitResult.Perfect, new HitCircle(), new HitCircle(), null)).ToList());
|
createTest(Enumerable.Range(-150, 300).Select(i => new HitEvent(i / 50f, HitResult.Perfect, placeholder_object, placeholder_object, null)).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestZeroTimeOffset()
|
public void TestZeroTimeOffset()
|
||||||
{
|
{
|
||||||
createTest(Enumerable.Range(0, 100).Select(_ => new HitEvent(0, HitResult.Perfect, new HitCircle(), new HitCircle(), null)).ToList());
|
createTest(Enumerable.Range(0, 100).Select(_ => new HitEvent(0, HitResult.Perfect, placeholder_object, placeholder_object, null)).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -47,9 +59,9 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
createTest(Enumerable.Range(0, 100).Select(i =>
|
createTest(Enumerable.Range(0, 100).Select(i =>
|
||||||
{
|
{
|
||||||
if (i % 2 == 0)
|
if (i % 2 == 0)
|
||||||
return new HitEvent(0, HitResult.Perfect, new HitCircle(), new HitCircle(), null);
|
return new HitEvent(0, HitResult.Perfect, placeholder_object, placeholder_object, null);
|
||||||
|
|
||||||
return new HitEvent(30, HitResult.Miss, new HitCircle(), new HitCircle(), null);
|
return new HitEvent(30, HitResult.Miss, placeholder_object, placeholder_object, null);
|
||||||
}).ToList());
|
}).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +74,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = Color4Extensions.FromHex("#333")
|
Colour = Color4Extensions.FromHex("#333")
|
||||||
},
|
},
|
||||||
new HitEventTimingDistributionGraph(events)
|
graph = new HitEventTimingDistributionGraph(events)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -77,10 +89,10 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
|
|
||||||
for (int i = 0; i < range * 2; i++)
|
for (int i = 0; i < range * 2; i++)
|
||||||
{
|
{
|
||||||
int count = (int)(Math.Pow(range - Math.Abs(i - range), 2));
|
int count = (int)(Math.Pow(range - Math.Abs(i - range), 2)) / 10;
|
||||||
|
|
||||||
for (int j = 0; j < count; j++)
|
for (int j = 0; j < count; j++)
|
||||||
hitEvents.Add(new HitEvent(centre + i - range, HitResult.Perfect, new HitCircle(), new HitCircle(), null));
|
hitEvents.Add(new HitEvent(centre + i - range, HitResult.Perfect, placeholder_object, placeholder_object, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
return hitEvents;
|
return hitEvents;
|
||||||
|
@ -119,7 +119,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddStep(@"Network failure", () => leaderboard.SetErrorState(LeaderboardState.NetworkFailure));
|
AddStep(@"Network failure", () => leaderboard.SetErrorState(LeaderboardState.NetworkFailure));
|
||||||
AddStep(@"No supporter", () => leaderboard.SetErrorState(LeaderboardState.NotSupporter));
|
AddStep(@"No supporter", () => leaderboard.SetErrorState(LeaderboardState.NotSupporter));
|
||||||
AddStep(@"Not logged in", () => leaderboard.SetErrorState(LeaderboardState.NotLoggedIn));
|
AddStep(@"Not logged in", () => leaderboard.SetErrorState(LeaderboardState.NotLoggedIn));
|
||||||
AddStep(@"Unavailable", () => leaderboard.SetErrorState(LeaderboardState.Unavailable));
|
AddStep(@"Ruleset unavailable", () => leaderboard.SetErrorState(LeaderboardState.RulesetUnavailable));
|
||||||
|
AddStep(@"Beatmap unavailable", () => leaderboard.SetErrorState(LeaderboardState.BeatmapUnavailable));
|
||||||
AddStep(@"None selected", () => leaderboard.SetErrorState(LeaderboardState.NoneSelected));
|
AddStep(@"None selected", () => leaderboard.SetErrorState(LeaderboardState.NoneSelected));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestScenePopupScreenTitle : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPopupScreenTitle()
|
||||||
|
{
|
||||||
|
AddStep("create content", () =>
|
||||||
|
{
|
||||||
|
Child = new PopupScreenTitle
|
||||||
|
{
|
||||||
|
Title = "Popup Screen Title",
|
||||||
|
Description = string.Join(" ", Enumerable.Repeat("This is a description.", 20)),
|
||||||
|
Close = () => { }
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDisabledExit()
|
||||||
|
{
|
||||||
|
AddStep("create content", () =>
|
||||||
|
{
|
||||||
|
Child = new PopupScreenTitle
|
||||||
|
{
|
||||||
|
Title = "Popup Screen Title",
|
||||||
|
Description = "This is a description."
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Extensions;
|
||||||
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;
|
||||||
@ -83,7 +84,7 @@ namespace osu.Game.Beatmaps
|
|||||||
requestedUserId = api.LocalUser.Value.Id;
|
requestedUserId = api.LocalUser.Value.Id;
|
||||||
|
|
||||||
// only query API for built-in rulesets
|
// only query API for built-in rulesets
|
||||||
rulesets.AvailableRulesets.Where(ruleset => ruleset.OnlineID >= 0 && ruleset.OnlineID <= ILegacyRuleset.MAX_LEGACY_RULESET_ID).ForEach(rulesetInfo =>
|
rulesets.AvailableRulesets.Where(ruleset => ruleset.IsLegacyRuleset()).ForEach(rulesetInfo =>
|
||||||
{
|
{
|
||||||
var req = new GetUserRequest(api.LocalUser.Value.Id, rulesetInfo);
|
var req = new GetUserRequest(api.LocalUser.Value.Id, rulesetInfo);
|
||||||
|
|
||||||
|
@ -140,7 +140,7 @@ namespace osu.Game.Configuration
|
|||||||
|
|
||||||
SetDefault(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f);
|
SetDefault(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f);
|
||||||
|
|
||||||
SetDefault(OsuSetting.UIHoldActivationDelay, 200f, 0f, 500f, 50f);
|
SetDefault(OsuSetting.UIHoldActivationDelay, 200.0, 0.0, 500.0, 50.0);
|
||||||
|
|
||||||
SetDefault(OsuSetting.IntroSequence, IntroSequence.Triangles);
|
SetDefault(OsuSetting.IntroSequence, IntroSequence.Triangles);
|
||||||
|
|
||||||
@ -240,9 +240,9 @@ namespace osu.Game.Configuration
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public Func<Guid, string> LookupSkinName { private get; set; }
|
public Func<Guid, string> LookupSkinName { private get; set; } = _ => @"unknown";
|
||||||
|
|
||||||
public Func<GlobalAction, LocalisableString> LookupKeyBindings { get; set; }
|
public Func<GlobalAction, LocalisableString> LookupKeyBindings { get; set; } = _ => @"unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
// IMPORTANT: These are used in user configuration files.
|
// IMPORTANT: These are used in user configuration files.
|
||||||
@ -270,7 +270,13 @@ namespace osu.Game.Configuration
|
|||||||
MouseDisableButtons,
|
MouseDisableButtons,
|
||||||
MouseDisableWheel,
|
MouseDisableWheel,
|
||||||
ConfineMouseMode,
|
ConfineMouseMode,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Globally applied audio offset.
|
||||||
|
/// This is added to the audio track's current time. Higher values will cause gameplay to occur earlier, relative to the audio track.
|
||||||
|
/// </summary>
|
||||||
AudioOffset,
|
AudioOffset,
|
||||||
|
|
||||||
VolumeInactive,
|
VolumeInactive,
|
||||||
MenuMusic,
|
MenuMusic,
|
||||||
MenuVoice,
|
MenuVoice,
|
||||||
|
@ -72,6 +72,11 @@ namespace osu.Game.Extensions
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check whether this <see cref="IRulesetInfo"/>'s online ID is within the range that defines it as a legacy ruleset (ie. either osu!, osu!taiko, osu!catch or osu!mania).
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsLegacyRuleset(this IRulesetInfo ruleset) => ruleset.OnlineID >= 0 && ruleset.OnlineID <= ILegacyRuleset.MAX_LEGACY_RULESET_ID;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check whether the online ID of two <see cref="IBeatmapSetInfo"/>s match.
|
/// Check whether the online ID of two <see cref="IBeatmapSetInfo"/>s match.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -3,12 +3,15 @@
|
|||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
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.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Storyboards.Drawables;
|
using osu.Game.Storyboards.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.Backgrounds
|
namespace osu.Game.Graphics.Backgrounds
|
||||||
@ -20,6 +23,9 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private MusicController? musicController { get; set; }
|
private MusicController? musicController { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IBindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
|
||||||
|
|
||||||
public BeatmapBackgroundWithStoryboard(WorkingBeatmap beatmap, string fallbackTextureName = "Backgrounds/bg1")
|
public BeatmapBackgroundWithStoryboard(WorkingBeatmap beatmap, string fallbackTextureName = "Backgrounds/bg1")
|
||||||
: base(beatmap, fallbackTextureName)
|
: base(beatmap, fallbackTextureName)
|
||||||
{
|
{
|
||||||
@ -39,7 +45,7 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Volume = { Value = 0 },
|
Volume = { Value = 0 },
|
||||||
Child = new DrawableStoryboard(Beatmap.Storyboard) { Clock = storyboardClock }
|
Child = new DrawableStoryboard(Beatmap.Storyboard, mods.Value) { Clock = storyboardClock }
|
||||||
}, AddInternal);
|
}, AddInternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,12 +30,12 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
public Bindable<double> Progress = new BindableDouble();
|
public Bindable<double> Progress = new BindableDouble();
|
||||||
|
|
||||||
private Bindable<float> holdActivationDelay;
|
private Bindable<double> holdActivationDelay;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuConfigManager config)
|
private void load(OsuConfigManager config)
|
||||||
{
|
{
|
||||||
holdActivationDelay = config.GetBindable<float>(OsuSetting.UIHoldActivationDelay);
|
holdActivationDelay = config.GetBindable<double>(OsuSetting.UIHoldActivationDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void BeginConfirm()
|
protected void BeginConfirm()
|
||||||
|
@ -5,6 +5,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
@ -38,24 +39,24 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
private BackgroundScreenStack backgroundStack;
|
private BackgroundScreenStack backgroundStack;
|
||||||
|
|
||||||
private bool allowScaling = true;
|
private RectangleF? customRect;
|
||||||
|
private bool customRectIsRelativePosition;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether user scaling preferences should be applied. Enabled by default.
|
/// Set a custom position and scale which overrides any user specification.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AllowScaling
|
/// <param name="rect">A rectangle with positional and sizing information for this container to conform to. <c>null</c> will clear the custom rect and revert to user settings.</param>
|
||||||
|
/// <param name="relativePosition">Whether the position portion of the provided rect is in relative coordinate space or not.</param>
|
||||||
|
public void SetCustomRect(RectangleF? rect, bool relativePosition = false)
|
||||||
{
|
{
|
||||||
get => allowScaling;
|
customRect = rect;
|
||||||
set
|
customRectIsRelativePosition = relativePosition;
|
||||||
{
|
|
||||||
if (value == allowScaling)
|
|
||||||
return;
|
|
||||||
|
|
||||||
allowScaling = value;
|
if (IsLoaded) Scheduler.AddOnce(updateSize);
|
||||||
if (IsLoaded) Scheduler.AddOnce(updateSize);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const float corner_radius = 10;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new instance.
|
/// Create a new instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -69,7 +70,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
RelativePositionAxes = Axes.Both,
|
RelativePositionAxes = Axes.Both,
|
||||||
CornerRadius = 10,
|
CornerRadius = corner_radius,
|
||||||
Child = content = new ScalingDrawSizePreservingFillContainer(targetMode != ScalingMode.Gameplay)
|
Child = content = new ScalingDrawSizePreservingFillContainer(targetMode != ScalingMode.Gameplay)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -137,7 +138,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
private void updateSize()
|
private void updateSize()
|
||||||
{
|
{
|
||||||
const float fade_time = 500;
|
const float duration = 500;
|
||||||
|
|
||||||
if (targetMode == ScalingMode.Everything)
|
if (targetMode == ScalingMode.Everything)
|
||||||
{
|
{
|
||||||
@ -156,17 +157,31 @@ namespace osu.Game.Graphics.Containers
|
|||||||
backgroundStack.Push(new ScalingBackgroundScreen());
|
backgroundStack.Push(new ScalingBackgroundScreen());
|
||||||
}
|
}
|
||||||
|
|
||||||
backgroundStack.FadeIn(fade_time);
|
backgroundStack.FadeIn(duration);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
backgroundStack?.FadeOut(fade_time);
|
backgroundStack?.FadeOut(duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool scaling = AllowScaling && (targetMode == null || scalingMode.Value == targetMode);
|
RectangleF targetRect = new RectangleF(Vector2.Zero, Vector2.One);
|
||||||
|
|
||||||
var targetSize = scaling ? new Vector2(sizeX.Value, sizeY.Value) : Vector2.One;
|
if (customRect != null)
|
||||||
var targetPosition = scaling ? new Vector2(posX.Value, posY.Value) * (Vector2.One - targetSize) : Vector2.Zero;
|
{
|
||||||
bool requiresMasking = (scaling && targetSize != Vector2.One)
|
sizableContainer.RelativePositionAxes = customRectIsRelativePosition ? Axes.Both : Axes.None;
|
||||||
|
|
||||||
|
targetRect = customRect.Value;
|
||||||
|
}
|
||||||
|
else if (targetMode == null || scalingMode.Value == targetMode)
|
||||||
|
{
|
||||||
|
sizableContainer.RelativePositionAxes = Axes.Both;
|
||||||
|
|
||||||
|
Vector2 scale = new Vector2(sizeX.Value, sizeY.Value);
|
||||||
|
Vector2 pos = new Vector2(posX.Value, posY.Value) * (Vector2.One - scale);
|
||||||
|
|
||||||
|
targetRect = new RectangleF(pos, scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool requiresMasking = targetRect.Size != Vector2.One
|
||||||
// For the top level scaling container, for now we apply masking if safe areas are in use.
|
// For the top level scaling container, for now we apply masking if safe areas are in use.
|
||||||
// In the future this can likely be removed as more of the actual UI supports overflowing into the safe areas.
|
// In the future this can likely be removed as more of the actual UI supports overflowing into the safe areas.
|
||||||
|| (targetMode == ScalingMode.Everything && safeAreaPadding.Value.Total != Vector2.Zero);
|
|| (targetMode == ScalingMode.Everything && safeAreaPadding.Value.Total != Vector2.Zero);
|
||||||
@ -174,8 +189,14 @@ namespace osu.Game.Graphics.Containers
|
|||||||
if (requiresMasking)
|
if (requiresMasking)
|
||||||
sizableContainer.Masking = true;
|
sizableContainer.Masking = true;
|
||||||
|
|
||||||
sizableContainer.MoveTo(targetPosition, 500, Easing.OutQuart);
|
sizableContainer.MoveTo(targetRect.Location, duration, Easing.OutQuart);
|
||||||
sizableContainer.ResizeTo(targetSize, 500, Easing.OutQuart).OnComplete(_ => { sizableContainer.Masking = requiresMasking; });
|
sizableContainer.ResizeTo(targetRect.Size, duration, Easing.OutQuart);
|
||||||
|
|
||||||
|
// Of note, this will not work great in the case of nested ScalingContainers where multiple are applying corner radius.
|
||||||
|
// Masking and corner radius should likely only be applied at one point in the full game stack to fix this.
|
||||||
|
// An example of how this can occur is when the skin editor is visible and the game screen scaling is set to "Everything".
|
||||||
|
sizableContainer.TransformTo(nameof(CornerRadius), requiresMasking ? corner_radius : 0, duration, requiresMasking ? Easing.OutQuart : Easing.None)
|
||||||
|
.OnComplete(_ => { sizableContainer.Masking = requiresMasking; });
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ScalingBackgroundScreen : BackgroundScreenDefault
|
private class ScalingBackgroundScreen : BackgroundScreenDefault
|
||||||
|
@ -25,8 +25,6 @@ namespace osu.Game.Graphics.Containers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UserScrolling { get; private set; }
|
public bool UserScrolling { get; private set; }
|
||||||
|
|
||||||
public void CancelUserScroll() => UserScrolling = false;
|
|
||||||
|
|
||||||
public UserTrackingScrollContainer()
|
public UserTrackingScrollContainer()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -140,6 +140,7 @@ namespace osu.Game.Graphics.Cursor
|
|||||||
// Scale to [-0.75, 0.75] so that the sample isn't fully panned left or right (sounds weird)
|
// Scale to [-0.75, 0.75] so that the sample isn't fully panned left or right (sounds weird)
|
||||||
channel.Balance.Value = ((activeCursor.X / DrawWidth) * 2 - 1) * 0.75;
|
channel.Balance.Value = ((activeCursor.X / DrawWidth) * 2 - 1) * 0.75;
|
||||||
channel.Frequency.Value = baseFrequency - (random_range / 2f) + RNG.NextDouble(random_range);
|
channel.Frequency.Value = baseFrequency - (random_range / 2f) + RNG.NextDouble(random_range);
|
||||||
|
channel.Volume.Value = baseFrequency;
|
||||||
|
|
||||||
channel.Play();
|
channel.Play();
|
||||||
}
|
}
|
||||||
|
@ -148,7 +148,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
CurrentNumber.BindValueChanged(current => updateTooltipText(current.NewValue), true);
|
CurrentNumber.BindValueChanged(current => TooltipText = getTooltipText(current.NewValue), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
@ -178,7 +178,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
base.OnUserChange(value);
|
base.OnUserChange(value);
|
||||||
playSample(value);
|
playSample(value);
|
||||||
updateTooltipText(value);
|
TooltipText = getTooltipText(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void playSample(T value)
|
private void playSample(T value)
|
||||||
@ -203,28 +203,22 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
channel.Play();
|
channel.Play();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTooltipText(T value)
|
private LocalisableString getTooltipText(T value)
|
||||||
{
|
{
|
||||||
if (CurrentNumber.IsInteger)
|
if (CurrentNumber.IsInteger)
|
||||||
TooltipText = value.ToInt32(NumberFormatInfo.InvariantInfo).ToString("N0");
|
return value.ToInt32(NumberFormatInfo.InvariantInfo).ToString("N0");
|
||||||
else
|
|
||||||
{
|
|
||||||
double floatValue = value.ToDouble(NumberFormatInfo.InvariantInfo);
|
|
||||||
|
|
||||||
if (DisplayAsPercentage)
|
double floatValue = value.ToDouble(NumberFormatInfo.InvariantInfo);
|
||||||
{
|
|
||||||
TooltipText = floatValue.ToString("0%");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
decimal decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits);
|
|
||||||
|
|
||||||
// Find the number of significant digits (we could have less than 5 after normalize())
|
if (DisplayAsPercentage)
|
||||||
int significantDigits = FormatUtils.FindPrecision(decimalPrecision);
|
return floatValue.ToString("0%");
|
||||||
|
|
||||||
TooltipText = floatValue.ToString($"N{significantDigits}");
|
decimal decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits);
|
||||||
}
|
|
||||||
}
|
// Find the number of significant digits (we could have less than 5 after normalize())
|
||||||
|
int significantDigits = FormatUtils.FindPrecision(decimalPrecision);
|
||||||
|
|
||||||
|
return floatValue.ToString($"N{significantDigits}");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateAfterChildren()
|
protected override void UpdateAfterChildren()
|
||||||
|
154
osu.Game/Graphics/UserInterface/PopupScreenTitle.cs
Normal file
154
osu.Game/Graphics/UserInterface/PopupScreenTitle.cs
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterface
|
||||||
|
{
|
||||||
|
public class PopupScreenTitle : CompositeDrawable
|
||||||
|
{
|
||||||
|
public LocalisableString Title
|
||||||
|
{
|
||||||
|
set => titleSpriteText.Text = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalisableString Description
|
||||||
|
{
|
||||||
|
set => descriptionText.Text = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action? Close
|
||||||
|
{
|
||||||
|
get => closeButton.Action;
|
||||||
|
set => closeButton.Action = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private const float corner_radius = 14;
|
||||||
|
private const float main_area_height = 70;
|
||||||
|
|
||||||
|
private readonly Container underlayContainer;
|
||||||
|
private readonly Box underlayBackground;
|
||||||
|
private readonly Container contentContainer;
|
||||||
|
private readonly Box contentBackground;
|
||||||
|
private readonly OsuSpriteText titleSpriteText;
|
||||||
|
private readonly OsuTextFlowContainer descriptionText;
|
||||||
|
private readonly IconButton closeButton;
|
||||||
|
|
||||||
|
public PopupScreenTitle()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
InternalChild = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Horizontal = 70,
|
||||||
|
Top = -corner_radius
|
||||||
|
},
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
underlayContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = main_area_height + 2 * corner_radius,
|
||||||
|
CornerRadius = corner_radius,
|
||||||
|
Masking = true,
|
||||||
|
BorderThickness = 2,
|
||||||
|
Child = underlayBackground = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
}
|
||||||
|
},
|
||||||
|
contentContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = main_area_height + corner_radius,
|
||||||
|
CornerRadius = corner_radius,
|
||||||
|
Masking = true,
|
||||||
|
BorderThickness = 2,
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Colour = Colour4.Black.Opacity(0.1f),
|
||||||
|
Offset = new Vector2(0, 1),
|
||||||
|
Radius = 3
|
||||||
|
},
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
contentBackground = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Margin = new MarginPadding { Top = corner_radius },
|
||||||
|
Padding = new MarginPadding { Horizontal = 100 },
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
titleSpriteText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Font = OsuFont.TorusAlternate.With(size: 20)
|
||||||
|
},
|
||||||
|
descriptionText = new OsuTextFlowContainer(t =>
|
||||||
|
{
|
||||||
|
t.Font = OsuFont.Default.With(size: 12);
|
||||||
|
})
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeButton = new IconButton
|
||||||
|
{
|
||||||
|
Icon = FontAwesome.Solid.Times,
|
||||||
|
Scale = new Vector2(0.6f),
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
Margin = new MarginPadding
|
||||||
|
{
|
||||||
|
Right = 21,
|
||||||
|
Top = corner_radius
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
underlayContainer.BorderColour = ColourInfo.GradientVertical(Colour4.Black, colourProvider.Dark4);
|
||||||
|
underlayBackground.Colour = colourProvider.Dark4;
|
||||||
|
|
||||||
|
contentContainer.BorderColour = ColourInfo.GradientVertical(colourProvider.Dark3, colourProvider.Dark1);
|
||||||
|
contentBackground.Colour = colourProvider.Dark3;
|
||||||
|
|
||||||
|
closeButton.IconHoverColour = colourProvider.Highlight1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
osu.Game/Graphics/UserInterface/TimeSlider.cs
Normal file
15
osu.Game/Graphics/UserInterface/TimeSlider.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterface
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A slider bar which displays a millisecond time value.
|
||||||
|
/// </summary>
|
||||||
|
public class TimeSlider : OsuSliderBar<double>
|
||||||
|
{
|
||||||
|
public override LocalisableString TooltipText => $"{Current.Value:N0} ms";
|
||||||
|
}
|
||||||
|
}
|
@ -35,7 +35,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
set => Component.Text = value;
|
set => Component.Text = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Container TabbableContentContainer
|
public CompositeDrawable TabbableContentContainer
|
||||||
{
|
{
|
||||||
set => Component.TabbableContentContainer = value;
|
set => Component.TabbableContentContainer = value;
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,16 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString CalibrateUsingLastPlay => new TranslatableString(getKey(@"calibrate_using_last_play"), @"Calibrate using last play");
|
public static LocalisableString CalibrateUsingLastPlay => new TranslatableString(getKey(@"calibrate_using_last_play"), @"Calibrate using last play");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "(hit objects appear later)"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString HitObjectsAppearLater => new TranslatableString(getKey(@"hit_objects_appear_later"), @"(hit objects appear later)");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "(hit objects appear earlier)"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString HitObjectsAppearEarlier => new TranslatableString(getKey(@"hit_objects_appear_earlier"), @"(hit objects appear earlier)");
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString Resolution => new TranslatableString(getKey(@"resolution"), @"Resolution");
|
public static LocalisableString Resolution => new TranslatableString(getKey(@"resolution"), @"Resolution");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Display"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString Display => new TranslatableString(getKey(@"display"), @"Display");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "UI scaling"
|
/// "UI scaling"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
49
osu.Game/Localisation/LeaderboardStrings.cs
Normal file
49
osu.Game/Localisation/LeaderboardStrings.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
|
namespace osu.Game.Localisation
|
||||||
|
{
|
||||||
|
public static class LeaderboardStrings
|
||||||
|
{
|
||||||
|
private const string prefix = @"osu.Game.Resources.Localisation.Leaderboard";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Couldn't fetch scores!"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString CouldntFetchScores => new TranslatableString(getKey(@"couldnt_fetch_scores"), @"Couldn't fetch scores!");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Please select a beatmap!"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString PleaseSelectABeatmap => new TranslatableString(getKey(@"please_select_a_beatmap"), @"Please select a beatmap!");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Leaderboards are not available for this ruleset!"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString LeaderboardsAreNotAvailableForThisRuleset => new TranslatableString(getKey(@"leaderboards_are_not_available_for_this_ruleset"), @"Leaderboards are not available for this ruleset!");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Leaderboards are not available for this beatmap!"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString LeaderboardsAreNotAvailableForThisBeatmap => new TranslatableString(getKey(@"leaderboards_are_not_available_for_this_beatmap"), @"Leaderboards are not available for this beatmap!");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "No records yet!"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString NoRecordsYet => new TranslatableString(getKey(@"no_records_yet"), @"No records yet!");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Please sign in to view online leaderboards!"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString PleaseSignInToViewOnlineLeaderboards => new TranslatableString(getKey(@"please_sign_in_to_view_online_leaderboards"), @"Please sign in to view online leaderboards!");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Please invest in an osu!supporter tag to view this leaderboard!"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString PleaseInvestInAnOsuSupporterTagToViewThisLeaderboard => new TranslatableString(getKey(@"please_invest_in_an_osu_supporter_tag_to_view_this_leaderboard"), @"Please invest in an osu!supporter tag to view this leaderboard!");
|
||||||
|
|
||||||
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
// 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 osu.Game.Extensions;
|
||||||
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
|
||||||
namespace osu.Game.Online.API.Requests
|
namespace osu.Game.Online.API.Requests
|
||||||
@ -8,14 +10,14 @@ namespace osu.Game.Online.API.Requests
|
|||||||
public class GetWikiRequest : APIRequest<APIWikiPage>
|
public class GetWikiRequest : APIRequest<APIWikiPage>
|
||||||
{
|
{
|
||||||
private readonly string path;
|
private readonly string path;
|
||||||
private readonly string locale;
|
private readonly Language language;
|
||||||
|
|
||||||
public GetWikiRequest(string path, string locale = "en")
|
public GetWikiRequest(string path, Language language = Language.en)
|
||||||
{
|
{
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.locale = locale;
|
this.language = language;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string Target => $"wiki/{locale}/{path}";
|
protected override string Target => $"wiki/{language.ToCultureCode()}/{path}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ using osu.Game.Online.API;
|
|||||||
using osu.Game.Online.Placeholders;
|
using osu.Game.Online.Placeholders;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Online.Leaderboards
|
namespace osu.Game.Online.Leaderboards
|
||||||
{
|
{
|
||||||
@ -311,25 +312,28 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case LeaderboardState.NetworkFailure:
|
case LeaderboardState.NetworkFailure:
|
||||||
return new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync)
|
return new ClickablePlaceholder(LeaderboardStrings.CouldntFetchScores, FontAwesome.Solid.Sync)
|
||||||
{
|
{
|
||||||
Action = RefetchScores
|
Action = RefetchScores
|
||||||
};
|
};
|
||||||
|
|
||||||
case LeaderboardState.NoneSelected:
|
case LeaderboardState.NoneSelected:
|
||||||
return new MessagePlaceholder(@"Please select a beatmap!");
|
return new MessagePlaceholder(LeaderboardStrings.PleaseSelectABeatmap);
|
||||||
|
|
||||||
case LeaderboardState.Unavailable:
|
case LeaderboardState.RulesetUnavailable:
|
||||||
return new MessagePlaceholder(@"Leaderboards are not available for this beatmap!");
|
return new MessagePlaceholder(LeaderboardStrings.LeaderboardsAreNotAvailableForThisRuleset);
|
||||||
|
|
||||||
|
case LeaderboardState.BeatmapUnavailable:
|
||||||
|
return new MessagePlaceholder(LeaderboardStrings.LeaderboardsAreNotAvailableForThisBeatmap);
|
||||||
|
|
||||||
case LeaderboardState.NoScores:
|
case LeaderboardState.NoScores:
|
||||||
return new MessagePlaceholder(@"No records yet!");
|
return new MessagePlaceholder(LeaderboardStrings.NoRecordsYet);
|
||||||
|
|
||||||
case LeaderboardState.NotLoggedIn:
|
case LeaderboardState.NotLoggedIn:
|
||||||
return new LoginPlaceholder(@"Please sign in to view online leaderboards!");
|
return new LoginPlaceholder(LeaderboardStrings.PleaseSignInToViewOnlineLeaderboards);
|
||||||
|
|
||||||
case LeaderboardState.NotSupporter:
|
case LeaderboardState.NotSupporter:
|
||||||
return new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!");
|
return new MessagePlaceholder(LeaderboardStrings.PleaseInvestInAnOsuSupporterTagToViewThisLeaderboard);
|
||||||
|
|
||||||
case LeaderboardState.Retrieving:
|
case LeaderboardState.Retrieving:
|
||||||
return null;
|
return null;
|
||||||
|
@ -8,7 +8,8 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
Success,
|
Success,
|
||||||
Retrieving,
|
Retrieving,
|
||||||
NetworkFailure,
|
NetworkFailure,
|
||||||
Unavailable,
|
BeatmapUnavailable,
|
||||||
|
RulesetUnavailable,
|
||||||
NoneSelected,
|
NoneSelected,
|
||||||
NoScores,
|
NoScores,
|
||||||
NotLoggedIn,
|
NotLoggedIn,
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
namespace osu.Game.Online.Placeholders
|
namespace osu.Game.Online.Placeholders
|
||||||
@ -12,7 +13,7 @@ namespace osu.Game.Online.Placeholders
|
|||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private LoginOverlay login { get; set; }
|
private LoginOverlay login { get; set; }
|
||||||
|
|
||||||
public LoginPlaceholder(string actionMessage)
|
public LoginPlaceholder(LocalisableString actionMessage)
|
||||||
: base(actionMessage, FontAwesome.Solid.UserLock)
|
: base(actionMessage, FontAwesome.Solid.UserLock)
|
||||||
{
|
{
|
||||||
Action = () => login?.Show();
|
Action = () => login?.Show();
|
||||||
|
70
osu.Game/Overlays/Chat/ChannelScrollContainer.cs
Normal file
70
osu.Game/Overlays/Chat/ChannelScrollContainer.cs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// 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.Game.Graphics.Containers;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Chat
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="OsuScrollContainer"/> with functionality to automatically scroll whenever the maximum scrollable distance increases.
|
||||||
|
/// </summary>
|
||||||
|
public class ChannelScrollContainer : OsuScrollContainer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The chat will be automatically scrolled to end if and only if
|
||||||
|
/// the distance between the current scroll position and the end of the scroll
|
||||||
|
/// is less than this value.
|
||||||
|
/// </summary>
|
||||||
|
private const float auto_scroll_leniency = 10f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to keep this container scrolled to end on new content.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is specifically controlled by whether the latest scroll operation made the container scrolled to end.
|
||||||
|
/// </remarks>
|
||||||
|
private bool trackNewContent = true;
|
||||||
|
|
||||||
|
protected override void UpdateAfterChildren()
|
||||||
|
{
|
||||||
|
base.UpdateAfterChildren();
|
||||||
|
|
||||||
|
if (trackNewContent && !IsScrolledToEnd())
|
||||||
|
ScrollToEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTrackState() => trackNewContent = IsScrolledToEnd(auto_scroll_leniency);
|
||||||
|
|
||||||
|
// todo: we may eventually want this encapsulated in a "OnScrollChange" event handler method provided by ScrollContainer.
|
||||||
|
// important to note that this intentionally doesn't consider OffsetScrollPosition, but could make it do so with side changes.
|
||||||
|
|
||||||
|
#region Scroll handling
|
||||||
|
|
||||||
|
protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = null)
|
||||||
|
{
|
||||||
|
base.OnUserScroll(value, animated, distanceDecay);
|
||||||
|
updateTrackState();
|
||||||
|
}
|
||||||
|
|
||||||
|
public new void ScrollIntoView(Drawable d, bool animated = true)
|
||||||
|
{
|
||||||
|
base.ScrollIntoView(d, animated);
|
||||||
|
updateTrackState();
|
||||||
|
}
|
||||||
|
|
||||||
|
public new void ScrollToStart(bool animated = true, bool allowDuringDrag = false)
|
||||||
|
{
|
||||||
|
base.ScrollToStart(animated, allowDuringDrag);
|
||||||
|
updateTrackState();
|
||||||
|
}
|
||||||
|
|
||||||
|
public new void ScrollToEnd(bool animated = true, bool allowDuringDrag = false)
|
||||||
|
{
|
||||||
|
base.ScrollToEnd(animated, allowDuringDrag);
|
||||||
|
updateTrackState();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
@ -11,9 +11,7 @@ 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.Sprites;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osu.Game.Graphics.Cursor;
|
using osu.Game.Graphics.Cursor;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Online.Chat;
|
using osu.Game.Online.Chat;
|
||||||
@ -236,52 +234,5 @@ namespace osu.Game.Overlays.Chat
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// An <see cref="OsuScrollContainer"/> with functionality to automatically scroll whenever the maximum scrollable distance increases.
|
|
||||||
/// </summary>
|
|
||||||
private class ChannelScrollContainer : UserTrackingScrollContainer
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The chat will be automatically scrolled to end if and only if
|
|
||||||
/// the distance between the current scroll position and the end of the scroll
|
|
||||||
/// is less than this value.
|
|
||||||
/// </summary>
|
|
||||||
private const float auto_scroll_leniency = 10f;
|
|
||||||
|
|
||||||
private float? lastExtent;
|
|
||||||
|
|
||||||
protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default)
|
|
||||||
{
|
|
||||||
base.OnUserScroll(value, animated, distanceDecay);
|
|
||||||
lastExtent = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
|
|
||||||
// If the user has scrolled to the bottom of the container, we should resume tracking new content.
|
|
||||||
if (UserScrolling && IsScrolledToEnd(auto_scroll_leniency))
|
|
||||||
CancelUserScroll();
|
|
||||||
|
|
||||||
// If the user hasn't overridden our behaviour and there has been new content added to the container, we should update our scroll position to track it.
|
|
||||||
bool requiresScrollUpdate = !UserScrolling && (lastExtent == null || Precision.AlmostBigger(ScrollableExtent, lastExtent.Value));
|
|
||||||
|
|
||||||
if (requiresScrollUpdate)
|
|
||||||
{
|
|
||||||
// Schedule required to allow FillFlow to be the correct size.
|
|
||||||
Schedule(() =>
|
|
||||||
{
|
|
||||||
if (!UserScrolling)
|
|
||||||
{
|
|
||||||
if (Current < ScrollableExtent)
|
|
||||||
ScrollToEnd();
|
|
||||||
lastExtent = ScrollableExtent;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
@ -63,11 +64,17 @@ namespace osu.Game.Overlays.Profile.Header
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CancellationTokenSource cancellationTokenSource;
|
||||||
|
|
||||||
private void updateDisplay(APIUser user)
|
private void updateDisplay(APIUser user)
|
||||||
{
|
{
|
||||||
var badges = user.Badges;
|
cancellationTokenSource?.Cancel();
|
||||||
|
cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
badgeFlowContainer.Clear();
|
badgeFlowContainer.Clear();
|
||||||
|
|
||||||
|
var badges = user.Badges;
|
||||||
|
|
||||||
if (badges?.Length > 0)
|
if (badges?.Length > 0)
|
||||||
{
|
{
|
||||||
Show();
|
Show();
|
||||||
@ -79,7 +86,7 @@ namespace osu.Game.Overlays.Profile.Header
|
|||||||
{
|
{
|
||||||
// load in stable order regardless of async load order.
|
// load in stable order regardless of async load order.
|
||||||
badgeFlowContainer.Insert(displayIndex, asyncBadge);
|
badgeFlowContainer.Insert(displayIndex, asyncBadge);
|
||||||
});
|
}, cancellationTokenSource.Token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -87,5 +94,11 @@ namespace osu.Game.Overlays.Profile.Header
|
|||||||
Hide();
|
Hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
cancellationTokenSource?.Cancel();
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
|
|||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new SettingsSlider<double, OffsetSlider>
|
new SettingsSlider<double, TimeSlider>
|
||||||
{
|
{
|
||||||
LabelText = AudioSettingsStrings.AudioOffset,
|
LabelText = AudioSettingsStrings.AudioOffset,
|
||||||
Current = config.GetBindable<double>(OsuSetting.AudioOffset),
|
Current = config.GetBindable<double>(OsuSetting.AudioOffset),
|
||||||
@ -35,10 +35,5 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private class OffsetSlider : OsuSliderBar<double>
|
|
||||||
{
|
|
||||||
public override LocalisableString TooltipText => Current.Value.ToString(@"0ms");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
|
|
||||||
private FillFlowContainer<SettingsSlider<float>> scalingSettings;
|
private FillFlowContainer<SettingsSlider<float>> scalingSettings;
|
||||||
|
|
||||||
private readonly IBindable<Display> currentDisplay = new Bindable<Display>();
|
private readonly Bindable<Display> currentDisplay = new Bindable<Display>();
|
||||||
private readonly IBindableList<WindowMode> windowModes = new BindableList<WindowMode>();
|
private readonly IBindableList<WindowMode> windowModes = new BindableList<WindowMode>();
|
||||||
|
|
||||||
private Bindable<ScalingMode> scalingMode;
|
private Bindable<ScalingMode> scalingMode;
|
||||||
@ -39,6 +39,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
private OsuGameBase game { get; set; }
|
private OsuGameBase game { get; set; }
|
||||||
|
|
||||||
private SettingsDropdown<Size> resolutionDropdown;
|
private SettingsDropdown<Size> resolutionDropdown;
|
||||||
|
private SettingsDropdown<Display> displayDropdown;
|
||||||
private SettingsDropdown<WindowMode> windowModeDropdown;
|
private SettingsDropdown<WindowMode> windowModeDropdown;
|
||||||
|
|
||||||
private Bindable<float> scalingPositionX;
|
private Bindable<float> scalingPositionX;
|
||||||
@ -72,6 +73,12 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
ItemSource = windowModes,
|
ItemSource = windowModes,
|
||||||
Current = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode),
|
Current = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode),
|
||||||
},
|
},
|
||||||
|
displayDropdown = new DisplaySettingsDropdown
|
||||||
|
{
|
||||||
|
LabelText = GraphicsSettingsStrings.Display,
|
||||||
|
Items = host.Window?.Displays,
|
||||||
|
Current = currentDisplay,
|
||||||
|
},
|
||||||
resolutionDropdown = new ResolutionSettingsDropdown
|
resolutionDropdown = new ResolutionSettingsDropdown
|
||||||
{
|
{
|
||||||
LabelText = GraphicsSettingsStrings.Resolution,
|
LabelText = GraphicsSettingsStrings.Resolution,
|
||||||
@ -142,7 +149,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
|
|
||||||
windowModeDropdown.Current.BindValueChanged(mode =>
|
windowModeDropdown.Current.BindValueChanged(mode =>
|
||||||
{
|
{
|
||||||
updateResolutionDropdown();
|
updateDisplayModeDropdowns();
|
||||||
|
|
||||||
windowModeDropdown.WarningText = mode.NewValue != WindowMode.Fullscreen ? GraphicsSettingsStrings.NotFullscreenNote : default;
|
windowModeDropdown.WarningText = mode.NewValue != WindowMode.Fullscreen ? GraphicsSettingsStrings.NotFullscreenNote : default;
|
||||||
}, true);
|
}, true);
|
||||||
@ -168,7 +175,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
.Distinct());
|
.Distinct());
|
||||||
}
|
}
|
||||||
|
|
||||||
updateResolutionDropdown();
|
updateDisplayModeDropdowns();
|
||||||
}), true);
|
}), true);
|
||||||
|
|
||||||
scalingMode.BindValueChanged(mode =>
|
scalingMode.BindValueChanged(mode =>
|
||||||
@ -183,12 +190,17 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
// initial update bypasses transforms
|
// initial update bypasses transforms
|
||||||
updateScalingModeVisibility();
|
updateScalingModeVisibility();
|
||||||
|
|
||||||
void updateResolutionDropdown()
|
void updateDisplayModeDropdowns()
|
||||||
{
|
{
|
||||||
if (resolutions.Count > 1 && windowModeDropdown.Current.Value == WindowMode.Fullscreen)
|
if (resolutions.Count > 1 && windowModeDropdown.Current.Value == WindowMode.Fullscreen)
|
||||||
resolutionDropdown.Show();
|
resolutionDropdown.Show();
|
||||||
else
|
else
|
||||||
resolutionDropdown.Hide();
|
resolutionDropdown.Hide();
|
||||||
|
|
||||||
|
if (displayDropdown.Items.Count() > 1)
|
||||||
|
displayDropdown.Show();
|
||||||
|
else
|
||||||
|
displayDropdown.Hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateScalingModeVisibility()
|
void updateScalingModeVisibility()
|
||||||
@ -243,6 +255,19 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
public override LocalisableString TooltipText => base.TooltipText + "x";
|
public override LocalisableString TooltipText => base.TooltipText + "x";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class DisplaySettingsDropdown : SettingsDropdown<Display>
|
||||||
|
{
|
||||||
|
protected override OsuDropdown<Display> CreateDropdown() => new DisplaySettingsDropdownControl();
|
||||||
|
|
||||||
|
private class DisplaySettingsDropdownControl : DropdownControl
|
||||||
|
{
|
||||||
|
protected override LocalisableString GenerateItemText(Display item)
|
||||||
|
{
|
||||||
|
return $"{item.Index}: {item.Name} ({item.Bounds.Width}x{item.Bounds.Height})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class ResolutionSettingsDropdown : SettingsDropdown<Size>
|
private class ResolutionSettingsDropdown : SettingsDropdown<Size>
|
||||||
{
|
{
|
||||||
protected override OsuDropdown<Size> CreateDropdown() => new ResolutionDropdownControl();
|
protected override OsuDropdown<Size> CreateDropdown() => new ResolutionDropdownControl();
|
||||||
|
@ -35,18 +35,13 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
|||||||
LabelText = UserInterfaceStrings.Parallax,
|
LabelText = UserInterfaceStrings.Parallax,
|
||||||
Current = config.GetBindable<bool>(OsuSetting.MenuParallax)
|
Current = config.GetBindable<bool>(OsuSetting.MenuParallax)
|
||||||
},
|
},
|
||||||
new SettingsSlider<float, TimeSlider>
|
new SettingsSlider<double, TimeSlider>
|
||||||
{
|
{
|
||||||
LabelText = UserInterfaceStrings.HoldToConfirmActivationTime,
|
LabelText = UserInterfaceStrings.HoldToConfirmActivationTime,
|
||||||
Current = config.GetBindable<float>(OsuSetting.UIHoldActivationDelay),
|
Current = config.GetBindable<double>(OsuSetting.UIHoldActivationDelay),
|
||||||
KeyboardStep = 50
|
KeyboardStep = 50
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TimeSlider : OsuSliderBar<float>
|
|
||||||
{
|
|
||||||
public override LocalisableString TooltipText => Current.Value.ToString(@"N0") + "ms";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,9 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
public class SettingsToolboxGroup : Container, IExpandable
|
public class SettingsToolboxGroup : Container, IExpandable
|
||||||
{
|
{
|
||||||
|
public const int CONTAINER_WIDTH = 270;
|
||||||
|
|
||||||
private const float transition_duration = 250;
|
private const float transition_duration = 250;
|
||||||
private const int container_width = 270;
|
|
||||||
private const int border_thickness = 2;
|
private const int border_thickness = 2;
|
||||||
private const int header_height = 30;
|
private const int header_height = 30;
|
||||||
private const int corner_radius = 5;
|
private const int corner_radius = 5;
|
||||||
@ -49,7 +50,7 @@ namespace osu.Game.Overlays
|
|||||||
public SettingsToolboxGroup(string title)
|
public SettingsToolboxGroup(string title)
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
Width = container_width;
|
Width = CONTAINER_WIDTH;
|
||||||
Masking = true;
|
Masking = true;
|
||||||
CornerRadius = corner_radius;
|
CornerRadius = corner_radius;
|
||||||
BorderColour = Color4.Black;
|
BorderColour = Color4.Black;
|
||||||
@ -201,7 +202,5 @@ namespace osu.Game.Overlays
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override Container<Drawable> Content => content;
|
protected override Container<Drawable> Content => content;
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e) => true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using System.Threading;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Extensions;
|
||||||
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.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
@ -100,7 +101,12 @@ namespace osu.Game.Overlays
|
|||||||
cancellationToken?.Cancel();
|
cancellationToken?.Cancel();
|
||||||
request?.Cancel();
|
request?.Cancel();
|
||||||
|
|
||||||
request = new GetWikiRequest(e.NewValue);
|
string[] values = e.NewValue.Split('/', 2);
|
||||||
|
|
||||||
|
if (values.Length > 1 && LanguageExtensions.TryParseCultureCode(values[0], out var language))
|
||||||
|
request = new GetWikiRequest(values[1], language);
|
||||||
|
else
|
||||||
|
request = new GetWikiRequest(e.NewValue);
|
||||||
|
|
||||||
Loading.Show();
|
Loading.Show();
|
||||||
|
|
||||||
|
@ -5,8 +5,19 @@ using osu.Game.Rulesets.UI;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mods
|
namespace osu.Game.Rulesets.Mods
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An interface for <see cref="Mod"/>s that are updated every frame by a <see cref="Playfield"/>.
|
||||||
|
/// </summary>
|
||||||
public interface IUpdatableByPlayfield : IApplicableMod
|
public interface IUpdatableByPlayfield : IApplicableMod
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Update this <see cref="Mod"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="playfield">The main <see cref="Playfield"/></param>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is called once per frame during gameplay by the main <see cref="Playfield"/> only.
|
||||||
|
/// To access nested <see cref="Playfield"/>s, use <see cref="Playfield.NestedPlayfields"/>.
|
||||||
|
/// </remarks>
|
||||||
void Update(Playfield playfield);
|
void Update(Playfield playfield);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,11 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
private readonly List<Playfield> nestedPlayfields = new List<Playfield>();
|
private readonly List<Playfield> nestedPlayfields = new List<Playfield>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this <see cref="Playfield"/> is nested in another <see cref="Playfield"/>.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsNested { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether judgements should be displayed by this and and all nested <see cref="Playfield"/>s.
|
/// Whether judgements should be displayed by this and and all nested <see cref="Playfield"/>s.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -206,6 +211,8 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// <param name="otherPlayfield">The <see cref="Playfield"/> to add.</param>
|
/// <param name="otherPlayfield">The <see cref="Playfield"/> to add.</param>
|
||||||
protected void AddNested(Playfield otherPlayfield)
|
protected void AddNested(Playfield otherPlayfield)
|
||||||
{
|
{
|
||||||
|
otherPlayfield.IsNested = true;
|
||||||
|
|
||||||
otherPlayfield.DisplayJudgements.BindTo(DisplayJudgements);
|
otherPlayfield.DisplayJudgements.BindTo(DisplayJudgements);
|
||||||
|
|
||||||
otherPlayfield.NewResult += (d, r) => NewResult?.Invoke(d, r);
|
otherPlayfield.NewResult += (d, r) => NewResult?.Invoke(d, r);
|
||||||
@ -229,7 +236,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
if (mods != null)
|
if (!IsNested && mods != null)
|
||||||
{
|
{
|
||||||
foreach (var mod in mods)
|
foreach (var mod in mods)
|
||||||
{
|
{
|
||||||
|
@ -7,9 +7,9 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Extensions;
|
||||||
using osu.Game.IO.Legacy;
|
using osu.Game.IO.Legacy;
|
||||||
using osu.Game.Replays.Legacy;
|
using osu.Game.Replays.Legacy;
|
||||||
using osu.Game.Rulesets;
|
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
using osu.Game.Rulesets.Replays.Types;
|
using osu.Game.Rulesets.Replays.Types;
|
||||||
using SharpCompress.Compressors.LZMA;
|
using SharpCompress.Compressors.LZMA;
|
||||||
@ -48,7 +48,7 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
if (beatmap == null && !score.Replay.Frames.All(f => f is LegacyReplayFrame))
|
if (beatmap == null && !score.Replay.Frames.All(f => f is LegacyReplayFrame))
|
||||||
throw new ArgumentException(@"Beatmap must be provided if frames are not already legacy frames.", nameof(beatmap));
|
throw new ArgumentException(@"Beatmap must be provided if frames are not already legacy frames.", nameof(beatmap));
|
||||||
|
|
||||||
if (score.ScoreInfo.Ruleset.OnlineID < 0 || score.ScoreInfo.Ruleset.OnlineID > ILegacyRuleset.MAX_LEGACY_RULESET_ID)
|
if (!score.ScoreInfo.Ruleset.IsLegacyRuleset())
|
||||||
throw new ArgumentException(@"Only scores in the osu, taiko, catch, or mania rulesets can be encoded to the legacy score format.", nameof(score));
|
throw new ArgumentException(@"Only scores in the osu, taiko, catch, or mania rulesets can be encoded to the legacy score format.", nameof(score));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,9 +75,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
FillFlowContainer flow;
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new FillFlowContainer
|
flow = new FillFlowContainer
|
||||||
{
|
{
|
||||||
Width = 200,
|
Width = 200,
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
@ -94,6 +96,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bank.TabbableContentContainer = flow;
|
||||||
|
volume.TabbableContentContainer = flow;
|
||||||
|
|
||||||
// if the piece belongs to a currently selected object, assume that the user wants to change all selected objects.
|
// if the piece belongs to a currently selected object, assume that the user wants to change all selected objects.
|
||||||
// if the piece belongs to an unselected object, operate on that object alone, independently of the selection.
|
// if the piece belongs to an unselected object, operate on that object alone, independently of the selection.
|
||||||
var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray();
|
var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray();
|
||||||
|
@ -32,6 +32,11 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
set => slider.KeyboardStep = value;
|
set => slider.KeyboardStep = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CompositeDrawable TabbableContentContainer
|
||||||
|
{
|
||||||
|
set => textBox.TabbableContentContainer = value;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly BindableWithCurrent<T?> current = new BindableWithCurrent<T?>();
|
private readonly BindableWithCurrent<T?> current = new BindableWithCurrent<T?>();
|
||||||
|
|
||||||
public Bindable<T?> Current
|
public Bindable<T?> Current
|
||||||
|
@ -66,7 +66,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
protected override BackgroundScreen CreateBackground() => background;
|
protected override BackgroundScreen CreateBackground() => background;
|
||||||
|
|
||||||
private Bindable<float> holdDelay;
|
private Bindable<double> holdDelay;
|
||||||
private Bindable<bool> loginDisplayed;
|
private Bindable<bool> loginDisplayed;
|
||||||
|
|
||||||
private ExitConfirmOverlay exitConfirmOverlay;
|
private ExitConfirmOverlay exitConfirmOverlay;
|
||||||
@ -77,7 +77,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics)
|
private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics)
|
||||||
{
|
{
|
||||||
holdDelay = config.GetBindable<float>(OsuSetting.UIHoldActivationDelay);
|
holdDelay = config.GetBindable<double>(OsuSetting.UIHoldActivationDelay);
|
||||||
loginDisplayed = statics.GetBindable<bool>(Static.LoginOverlayDisplayed);
|
loginDisplayed = statics.GetBindable<bool>(Static.LoginOverlayDisplayed);
|
||||||
|
|
||||||
if (host.CanExit)
|
if (host.CanExit)
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
// 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 osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Storyboards;
|
using osu.Game.Storyboards;
|
||||||
using osu.Game.Storyboards.Drawables;
|
using osu.Game.Storyboards.Drawables;
|
||||||
|
|
||||||
@ -18,6 +20,8 @@ namespace osu.Game.Screens.Play
|
|||||||
public Container OverlayLayerContainer { get; private set; }
|
public Container OverlayLayerContainer { get; private set; }
|
||||||
|
|
||||||
private readonly Storyboard storyboard;
|
private readonly Storyboard storyboard;
|
||||||
|
private readonly IReadOnlyList<Mod> mods;
|
||||||
|
|
||||||
private DrawableStoryboard drawableStoryboard;
|
private DrawableStoryboard drawableStoryboard;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -28,9 +32,10 @@ namespace osu.Game.Screens.Play
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public IBindable<bool> HasStoryboardEnded = new BindableBool(true);
|
public IBindable<bool> HasStoryboardEnded = new BindableBool(true);
|
||||||
|
|
||||||
public DimmableStoryboard(Storyboard storyboard)
|
public DimmableStoryboard(Storyboard storyboard, IReadOnlyList<Mod> mods)
|
||||||
{
|
{
|
||||||
this.storyboard = storyboard;
|
this.storyboard = storyboard;
|
||||||
|
this.mods = mods;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -57,7 +62,7 @@ namespace osu.Game.Screens.Play
|
|||||||
if (!ShowStoryboard.Value && !IgnoreUserSettings.Value)
|
if (!ShowStoryboard.Value && !IgnoreUserSettings.Value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
drawableStoryboard = storyboard.CreateDrawable();
|
drawableStoryboard = storyboard.CreateDrawable(mods);
|
||||||
HasStoryboardEnded.BindTo(drawableStoryboard.HasStoryboardEnded);
|
HasStoryboardEnded.BindTo(drawableStoryboard.HasStoryboardEnded);
|
||||||
|
|
||||||
if (async)
|
if (async)
|
||||||
|
@ -63,11 +63,11 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuConfigManager config { get; set; }
|
private OsuConfigManager config { get; set; }
|
||||||
|
|
||||||
private Bindable<float> activationDelay;
|
private Bindable<double> activationDelay;
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
activationDelay = config.GetBindable<float>(OsuSetting.UIHoldActivationDelay);
|
activationDelay = config.GetBindable<double>(OsuSetting.UIHoldActivationDelay);
|
||||||
activationDelay.BindValueChanged(v =>
|
activationDelay.BindValueChanged(v =>
|
||||||
{
|
{
|
||||||
text.Text = v.NewValue > 0
|
text.Text = v.NewValue > 0
|
||||||
|
@ -359,7 +359,7 @@ namespace osu.Game.Screens.Play
|
|||||||
protected virtual GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart);
|
protected virtual GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart);
|
||||||
|
|
||||||
private Drawable createUnderlayComponents() =>
|
private Drawable createUnderlayComponents() =>
|
||||||
DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both };
|
DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard, GameplayState.Mods) { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
private Drawable createGameplayComponents(IWorkingBeatmap working) => new ScalingContainer(ScalingMode.Gameplay)
|
private Drawable createGameplayComponents(IWorkingBeatmap working) => new ScalingContainer(ScalingMode.Gameplay)
|
||||||
{
|
{
|
||||||
|
@ -143,6 +143,8 @@ namespace osu.Game.Screens.Play
|
|||||||
muteWarningShownOnce = sessionStatics.GetBindable<bool>(Static.MutedAudioNotificationShownOnce);
|
muteWarningShownOnce = sessionStatics.GetBindable<bool>(Static.MutedAudioNotificationShownOnce);
|
||||||
batteryWarningShownOnce = sessionStatics.GetBindable<bool>(Static.LowBatteryNotificationShownOnce);
|
batteryWarningShownOnce = sessionStatics.GetBindable<bool>(Static.LowBatteryNotificationShownOnce);
|
||||||
|
|
||||||
|
const float padding = 25;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
(content = new LogoTrackingContainer
|
(content = new LogoTrackingContainer
|
||||||
@ -158,20 +160,27 @@ namespace osu.Game.Screens.Play
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
},
|
},
|
||||||
PlayerSettings = new FillFlowContainer<PlayerSettingsGroup>
|
new OsuScrollContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopRight,
|
Anchor = Anchor.TopRight,
|
||||||
Origin = Anchor.TopRight,
|
Origin = Anchor.TopRight,
|
||||||
AutoSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Y,
|
||||||
Direction = FillDirection.Vertical,
|
Width = SettingsToolboxGroup.CONTAINER_WIDTH + padding * 2,
|
||||||
Spacing = new Vector2(0, 20),
|
Padding = new MarginPadding { Vertical = padding },
|
||||||
Margin = new MarginPadding(25),
|
Masking = false,
|
||||||
Children = new PlayerSettingsGroup[]
|
Child = PlayerSettings = new FillFlowContainer<PlayerSettingsGroup>
|
||||||
{
|
{
|
||||||
VisualSettings = new VisualSettings(),
|
AutoSizeAxes = Axes.Both,
|
||||||
AudioSettings = new AudioSettings(),
|
Direction = FillDirection.Vertical,
|
||||||
new InputSettings()
|
Spacing = new Vector2(0, 20),
|
||||||
}
|
Padding = new MarginPadding { Horizontal = padding },
|
||||||
|
Children = new PlayerSettingsGroup[]
|
||||||
|
{
|
||||||
|
VisualSettings = new VisualSettings(),
|
||||||
|
AudioSettings = new AudioSettings(),
|
||||||
|
new InputSettings()
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
idleTracker = new IdleTracker(750),
|
idleTracker = new IdleTracker(750),
|
||||||
}),
|
}),
|
||||||
|
@ -2,12 +2,14 @@
|
|||||||
// 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.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
@ -50,6 +52,8 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
private OsuColour colours { get; set; } = null!;
|
private OsuColour colours { get; set; } = null!;
|
||||||
|
|
||||||
private double lastPlayAverage;
|
private double lastPlayAverage;
|
||||||
|
private double lastPlayBeatmapOffset;
|
||||||
|
private HitEventTimingDistributionGraph? lastPlayGraph;
|
||||||
|
|
||||||
private SettingsButton? useAverageButton;
|
private SettingsButton? useAverageButton;
|
||||||
|
|
||||||
@ -70,7 +74,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
Spacing = new Vector2(10),
|
Spacing = new Vector2(10),
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new PlayerSliderBar<double>
|
new OffsetSliderBar
|
||||||
{
|
{
|
||||||
KeyboardStep = 5,
|
KeyboardStep = 5,
|
||||||
LabelText = BeatmapOffsetControlStrings.BeatmapOffset,
|
LabelText = BeatmapOffsetControlStrings.BeatmapOffset,
|
||||||
@ -87,6 +91,28 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class OffsetSliderBar : PlayerSliderBar<double>
|
||||||
|
{
|
||||||
|
protected override Drawable CreateControl() => new CustomSliderBar();
|
||||||
|
|
||||||
|
protected class CustomSliderBar : SliderBar
|
||||||
|
{
|
||||||
|
public override LocalisableString TooltipText =>
|
||||||
|
Current.Value == 0
|
||||||
|
? new TranslatableString("_", @"{0} ms", base.TooltipText)
|
||||||
|
: new TranslatableString("_", @"{0} ms {1}", base.TooltipText, getEarlyLateText(Current.Value));
|
||||||
|
|
||||||
|
private LocalisableString getEarlyLateText(double value)
|
||||||
|
{
|
||||||
|
Debug.Assert(value != 0);
|
||||||
|
|
||||||
|
return value > 0
|
||||||
|
? BeatmapOffsetControlStrings.HitObjectsAppearEarlier
|
||||||
|
: BeatmapOffsetControlStrings.HitObjectsAppearLater;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
@ -107,6 +133,12 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
|
|
||||||
void updateOffset()
|
void updateOffset()
|
||||||
{
|
{
|
||||||
|
// the last play graph is relative to the offset at the point of the last play, so we need to factor that out.
|
||||||
|
double adjustmentSinceLastPlay = lastPlayBeatmapOffset - Current.Value;
|
||||||
|
|
||||||
|
// Negative is applied here because the play graph is considering a hit offset, not track (as we currently use for clocks).
|
||||||
|
lastPlayGraph?.UpdateOffset(-adjustmentSinceLastPlay);
|
||||||
|
|
||||||
// ensure the previous write has completed. ignoring performance concerns, if we don't do this, the async writes could be out of sequence.
|
// ensure the previous write has completed. ignoring performance concerns, if we don't do this, the async writes could be out of sequence.
|
||||||
if (realmWriteTask?.IsCompleted == false)
|
if (realmWriteTask?.IsCompleted == false)
|
||||||
{
|
{
|
||||||
@ -115,7 +147,9 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (useAverageButton != null)
|
if (useAverageButton != null)
|
||||||
useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, -Current.Value, Current.Precision / 2);
|
{
|
||||||
|
useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, adjustmentSinceLastPlay, Current.Precision / 2);
|
||||||
|
}
|
||||||
|
|
||||||
realmWriteTask = realm.WriteAsync(r =>
|
realmWriteTask = realm.WriteAsync(r =>
|
||||||
{
|
{
|
||||||
@ -172,10 +206,11 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
}
|
}
|
||||||
|
|
||||||
lastPlayAverage = average;
|
lastPlayAverage = average;
|
||||||
|
lastPlayBeatmapOffset = Current.Value;
|
||||||
|
|
||||||
referenceScoreContainer.AddRange(new Drawable[]
|
referenceScoreContainer.AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
new HitEventTimingDistributionGraph(hitEvents)
|
lastPlayGraph = new HitEventTimingDistributionGraph(hitEvents)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = 50,
|
Height = 50,
|
||||||
@ -184,7 +219,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
useAverageButton = new SettingsButton
|
useAverageButton = new SettingsButton
|
||||||
{
|
{
|
||||||
Text = BeatmapOffsetControlStrings.CalibrateUsingLastPlay,
|
Text = BeatmapOffsetControlStrings.CalibrateUsingLastPlay,
|
||||||
Action = () => Current.Value = -lastPlayAverage
|
Action = () => Current.Value = lastPlayBeatmapOffset - lastPlayAverage
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -15,13 +15,15 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
{
|
{
|
||||||
public OsuSliderBar<T> Bar => (OsuSliderBar<T>)Control;
|
public OsuSliderBar<T> Bar => (OsuSliderBar<T>)Control;
|
||||||
|
|
||||||
protected override Drawable CreateControl() => new SliderBar
|
protected override Drawable CreateControl() => new SliderBar();
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X
|
|
||||||
};
|
|
||||||
|
|
||||||
private class SliderBar : OsuSliderBar<T>
|
protected class SliderBar : OsuSliderBar<T>
|
||||||
{
|
{
|
||||||
|
public SliderBar()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
|
@ -4,10 +4,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Online.Solo;
|
using osu.Game.Online.Solo;
|
||||||
using osu.Game.Rulesets;
|
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Screens.Play
|
||||||
@ -32,7 +32,7 @@ namespace osu.Game.Screens.Play
|
|||||||
if (beatmapId <= 0)
|
if (beatmapId <= 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (rulesetId < 0 || rulesetId > ILegacyRuleset.MAX_LEGACY_RULESET_ID)
|
if (!Ruleset.Value.IsLegacyRuleset())
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return new CreateSoloScoreRequest(beatmapId, rulesetId, Game.VersionHash);
|
return new CreateSoloScoreRequest(beatmapId, rulesetId, Game.VersionHash);
|
||||||
|
@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Ranking.Statistics
|
namespace osu.Game.Screens.Ranking.Statistics
|
||||||
{
|
{
|
||||||
@ -40,6 +41,9 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const float axis_points = 5;
|
private const float axis_points = 5;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The currently displayed hit events.
|
||||||
|
/// </summary>
|
||||||
private readonly IReadOnlyList<HitEvent> hitEvents;
|
private readonly IReadOnlyList<HitEvent> hitEvents;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -51,123 +55,223 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()).ToList();
|
this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int[] bins;
|
||||||
|
private double binSize;
|
||||||
|
private double hitOffset;
|
||||||
|
|
||||||
|
private Bar[] barDrawables;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
if (hitEvents == null || hitEvents.Count == 0)
|
if (hitEvents == null || hitEvents.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int[] bins = new int[total_timing_distribution_bins];
|
bins = new int[total_timing_distribution_bins];
|
||||||
|
|
||||||
double binSize = Math.Ceiling(hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins);
|
binSize = Math.Ceiling(hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins);
|
||||||
|
|
||||||
// Prevent div-by-0 by enforcing a minimum bin size
|
// Prevent div-by-0 by enforcing a minimum bin size
|
||||||
binSize = Math.Max(1, binSize);
|
binSize = Math.Max(1, binSize);
|
||||||
|
|
||||||
|
Scheduler.AddOnce(updateDisplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateOffset(double hitOffset)
|
||||||
|
{
|
||||||
|
this.hitOffset = hitOffset;
|
||||||
|
Scheduler.AddOnce(updateDisplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDisplay()
|
||||||
|
{
|
||||||
|
bool roundUp = true;
|
||||||
|
|
||||||
|
Array.Clear(bins, 0, bins.Length);
|
||||||
|
|
||||||
foreach (var e in hitEvents)
|
foreach (var e in hitEvents)
|
||||||
{
|
{
|
||||||
int binOffset = (int)Math.Round(e.TimeOffset / binSize, MidpointRounding.AwayFromZero);
|
double time = e.TimeOffset + hitOffset;
|
||||||
bins[timing_distribution_centre_bin_index + binOffset]++;
|
|
||||||
|
double binOffset = time / binSize;
|
||||||
|
|
||||||
|
// .NET's round midpoint handling doesn't provide a behaviour that works amazingly for display
|
||||||
|
// purposes here. We want midpoint rounding to roughly distribute evenly to each adjacent bucket
|
||||||
|
// so the easiest way is to cycle between downwards and upwards rounding as we process events.
|
||||||
|
if (Math.Abs(binOffset - (int)binOffset) == 0.5)
|
||||||
|
{
|
||||||
|
binOffset = (int)binOffset + Math.Sign(binOffset) * (roundUp ? 1 : 0);
|
||||||
|
roundUp = !roundUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = timing_distribution_centre_bin_index + (int)Math.Round(binOffset, MidpointRounding.AwayFromZero);
|
||||||
|
|
||||||
|
// may be out of range when applying an offset. for such cases we can just drop the results.
|
||||||
|
if (index >= 0 && index < bins.Length)
|
||||||
|
bins[index]++;
|
||||||
}
|
}
|
||||||
|
|
||||||
int maxCount = bins.Max();
|
if (barDrawables != null)
|
||||||
var bars = new Drawable[total_timing_distribution_bins];
|
|
||||||
for (int i = 0; i < bars.Length; i++)
|
|
||||||
bars[i] = new Bar { Height = Math.Max(0.05f, (float)bins[i] / maxCount) };
|
|
||||||
|
|
||||||
Container axisFlow;
|
|
||||||
|
|
||||||
InternalChild = new GridContainer
|
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
for (int i = 0; i < barDrawables.Length; i++)
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Width = 0.8f,
|
|
||||||
Content = new[]
|
|
||||||
{
|
{
|
||||||
new Drawable[]
|
barDrawables[i].UpdateOffset(bins[i]);
|
||||||
{
|
|
||||||
new GridContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Content = new[] { bars }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new Drawable[]
|
|
||||||
{
|
|
||||||
axisFlow = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RowDimensions = new[]
|
|
||||||
{
|
|
||||||
new Dimension(),
|
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
else
|
||||||
// Our axis will contain one centre element + 5 points on each side, each with a value depending on the number of bins * bin size.
|
|
||||||
double maxValue = timing_distribution_bins * binSize;
|
|
||||||
double axisValueStep = maxValue / axis_points;
|
|
||||||
|
|
||||||
axisFlow.Add(new OsuSpriteText
|
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
int maxCount = bins.Max();
|
||||||
Origin = Anchor.Centre,
|
barDrawables = new Bar[total_timing_distribution_bins];
|
||||||
Text = "0",
|
|
||||||
Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold)
|
|
||||||
});
|
|
||||||
|
|
||||||
for (int i = 1; i <= axis_points; i++)
|
for (int i = 0; i < barDrawables.Length; i++)
|
||||||
{
|
barDrawables[i] = new Bar(bins[i], maxCount, i == timing_distribution_centre_bin_index);
|
||||||
double axisValue = i * axisValueStep;
|
|
||||||
float position = (float)(axisValue / maxValue);
|
Container axisFlow;
|
||||||
float alpha = 1f - position * 0.8f;
|
|
||||||
|
const float axis_font_size = 12;
|
||||||
|
|
||||||
|
InternalChild = new GridContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Width = 0.8f,
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Content = new[] { barDrawables }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
axisFlow = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = axis_font_size,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RowDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Our axis will contain one centre element + 5 points on each side, each with a value depending on the number of bins * bin size.
|
||||||
|
double maxValue = timing_distribution_bins * binSize;
|
||||||
|
double axisValueStep = maxValue / axis_points;
|
||||||
|
|
||||||
axisFlow.Add(new OsuSpriteText
|
axisFlow.Add(new OsuSpriteText
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativePositionAxes = Axes.X,
|
Text = "0",
|
||||||
X = -position / 2,
|
Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold)
|
||||||
Alpha = alpha,
|
|
||||||
Text = axisValue.ToString("-0"),
|
|
||||||
Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
axisFlow.Add(new OsuSpriteText
|
for (int i = 1; i <= axis_points; i++)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
double axisValue = i * axisValueStep;
|
||||||
Origin = Anchor.Centre,
|
float position = (float)(axisValue / maxValue);
|
||||||
RelativePositionAxes = Axes.X,
|
float alpha = 1f - position * 0.8f;
|
||||||
X = position / 2,
|
|
||||||
Alpha = alpha,
|
axisFlow.Add(new OsuSpriteText
|
||||||
Text = axisValue.ToString("+0"),
|
{
|
||||||
Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold)
|
Anchor = Anchor.Centre,
|
||||||
});
|
Origin = Anchor.Centre,
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
X = -position / 2,
|
||||||
|
Alpha = alpha,
|
||||||
|
Text = axisValue.ToString("-0"),
|
||||||
|
Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold)
|
||||||
|
});
|
||||||
|
|
||||||
|
axisFlow.Add(new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
X = position / 2,
|
||||||
|
Alpha = alpha,
|
||||||
|
Text = axisValue.ToString("+0"),
|
||||||
|
Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Bar : CompositeDrawable
|
private class Bar : CompositeDrawable
|
||||||
{
|
{
|
||||||
public Bar()
|
private readonly float value;
|
||||||
|
private readonly float maxValue;
|
||||||
|
|
||||||
|
private readonly Circle boxOriginal;
|
||||||
|
private Circle boxAdjustment;
|
||||||
|
|
||||||
|
private const float minimum_height = 0.05f;
|
||||||
|
|
||||||
|
public Bar(float value, float maxValue, bool isCentre)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.BottomCentre;
|
this.value = value;
|
||||||
Origin = Anchor.BottomCentre;
|
this.maxValue = maxValue;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
Masking = true;
|
||||||
|
|
||||||
Padding = new MarginPadding { Horizontal = 1 };
|
InternalChildren = new Drawable[]
|
||||||
|
|
||||||
InternalChild = new Circle
|
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
boxOriginal = new Circle
|
||||||
Colour = Color4Extensions.FromHex("#66FFCC")
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
Colour = isCentre ? Color4.White : Color4Extensions.FromHex("#66FFCC"),
|
||||||
|
Height = minimum_height,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const double duration = 300;
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
float height = Math.Clamp(value / maxValue, minimum_height, 1);
|
||||||
|
|
||||||
|
if (height > minimum_height)
|
||||||
|
boxOriginal.ResizeHeightTo(height, duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateOffset(float adjustment)
|
||||||
|
{
|
||||||
|
bool hasAdjustment = adjustment != value && adjustment / maxValue >= minimum_height;
|
||||||
|
|
||||||
|
if (boxAdjustment == null)
|
||||||
|
{
|
||||||
|
if (!hasAdjustment)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AddInternal(boxAdjustment = new Circle
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
Colour = Color4.Yellow,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Alpha = 0.6f,
|
||||||
|
Height = 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
boxAdjustment.ResizeHeightTo(Math.Clamp(adjustment / maxValue, minimum_height, 1), duration, Easing.OutQuint);
|
||||||
|
boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,9 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
{
|
{
|
||||||
public class SetPanelContent : CompositeDrawable
|
public class SetPanelContent : CompositeDrawable
|
||||||
{
|
{
|
||||||
|
// Disallow interacting with difficulty icons on a panel until the panel has been selected.
|
||||||
|
public override bool PropagatePositionalInputSubTree => carouselSet.State.Value == CarouselItemState.Selected;
|
||||||
|
|
||||||
private readonly CarouselBeatmapSet carouselSet;
|
private readonly CarouselBeatmapSet carouselSet;
|
||||||
|
|
||||||
public SetPanelContent(CarouselBeatmapSet carouselSet)
|
public SetPanelContent(CarouselBeatmapSet carouselSet)
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Extensions;
|
||||||
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.Online.Leaderboards;
|
using osu.Game.Online.Leaderboards;
|
||||||
@ -98,6 +99,7 @@ namespace osu.Game.Screens.Select.Leaderboards
|
|||||||
protected override APIRequest FetchScores(CancellationToken cancellationToken)
|
protected override APIRequest FetchScores(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var fetchBeatmapInfo = BeatmapInfo;
|
var fetchBeatmapInfo = BeatmapInfo;
|
||||||
|
var fetchRuleset = ruleset.Value ?? fetchBeatmapInfo.Ruleset;
|
||||||
|
|
||||||
if (fetchBeatmapInfo == null)
|
if (fetchBeatmapInfo == null)
|
||||||
{
|
{
|
||||||
@ -117,9 +119,15 @@ namespace osu.Game.Screens.Select.Leaderboards
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!fetchRuleset.IsLegacyRuleset())
|
||||||
|
{
|
||||||
|
SetErrorState(LeaderboardState.RulesetUnavailable);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (fetchBeatmapInfo.OnlineID <= 0 || fetchBeatmapInfo.Status <= BeatmapOnlineStatus.Pending)
|
if (fetchBeatmapInfo.OnlineID <= 0 || fetchBeatmapInfo.Status <= BeatmapOnlineStatus.Pending)
|
||||||
{
|
{
|
||||||
SetErrorState(LeaderboardState.Unavailable);
|
SetErrorState(LeaderboardState.BeatmapUnavailable);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,7 +145,7 @@ namespace osu.Game.Screens.Select.Leaderboards
|
|||||||
else if (filterMods)
|
else if (filterMods)
|
||||||
requestMods = mods.Value;
|
requestMods = mods.Value;
|
||||||
|
|
||||||
var req = new GetScoresRequest(fetchBeatmapInfo, ruleset.Value ?? fetchBeatmapInfo.Ruleset, Scope, requestMods);
|
var req = new GetScoresRequest(fetchBeatmapInfo, fetchRuleset, Scope, requestMods);
|
||||||
|
|
||||||
req.Success += r =>
|
req.Success += r =>
|
||||||
{
|
{
|
||||||
|
@ -23,6 +23,8 @@ namespace osu.Game.Skinning.Editor
|
|||||||
{
|
{
|
||||||
public class SkinComponentToolbox : ScrollingToolboxGroup
|
public class SkinComponentToolbox : ScrollingToolboxGroup
|
||||||
{
|
{
|
||||||
|
public const float WIDTH = 200;
|
||||||
|
|
||||||
public Action<Type> RequestPlacement;
|
public Action<Type> RequestPlacement;
|
||||||
|
|
||||||
private const float component_display_scale = 0.8f;
|
private const float component_display_scale = 0.8f;
|
||||||
@ -41,7 +43,7 @@ namespace osu.Game.Skinning.Editor
|
|||||||
: base("Components", height)
|
: base("Components", height)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.None;
|
RelativeSizeAxes = Axes.None;
|
||||||
Width = 200;
|
Width = WIDTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
|
@ -5,6 +5,7 @@ using JetBrains.Annotations;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
@ -100,30 +101,14 @@ namespace osu.Game.Skinning.Editor
|
|||||||
{
|
{
|
||||||
if (visibility.NewValue == Visibility.Visible)
|
if (visibility.NewValue == Visibility.Visible)
|
||||||
{
|
{
|
||||||
updateMasking();
|
target.SetCustomRect(new RectangleF(0.18f, 0.1f, VISIBLE_TARGET_SCALE, VISIBLE_TARGET_SCALE), true);
|
||||||
target.AllowScaling = false;
|
|
||||||
target.RelativePositionAxes = Axes.Both;
|
|
||||||
|
|
||||||
target.ScaleTo(VISIBLE_TARGET_SCALE, SkinEditor.TRANSITION_DURATION, Easing.OutQuint);
|
|
||||||
target.MoveToX(0.095f, SkinEditor.TRANSITION_DURATION, Easing.OutQuint);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
target.AllowScaling = true;
|
target.SetCustomRect(null);
|
||||||
|
|
||||||
target.ScaleTo(1, SkinEditor.TRANSITION_DURATION, Easing.OutQuint).OnComplete(_ => updateMasking());
|
|
||||||
target.MoveToX(0f, SkinEditor.TRANSITION_DURATION, Easing.OutQuint);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateMasking()
|
|
||||||
{
|
|
||||||
if (skinEditor == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
target.Masking = skinEditor.State.Value == Visibility.Visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// 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.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -11,6 +13,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Stores;
|
using osu.Game.Stores;
|
||||||
|
|
||||||
@ -50,14 +53,18 @@ namespace osu.Game.Storyboards.Drawables
|
|||||||
|
|
||||||
private double? lastEventEndTime;
|
private double? lastEventEndTime;
|
||||||
|
|
||||||
|
[Cached(typeof(IReadOnlyList<Mod>))]
|
||||||
|
public IReadOnlyList<Mod> Mods { get; }
|
||||||
|
|
||||||
private DependencyContainer dependencies;
|
private DependencyContainer dependencies;
|
||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
|
||||||
dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||||
|
|
||||||
public DrawableStoryboard(Storyboard storyboard)
|
public DrawableStoryboard(Storyboard storyboard, IReadOnlyList<Mod> mods = null)
|
||||||
{
|
{
|
||||||
Storyboard = storyboard;
|
Storyboard = storyboard;
|
||||||
|
Mods = mods ?? Array.Empty<Mod>();
|
||||||
|
|
||||||
Size = new Vector2(640, 480);
|
Size = new Vector2(640, 480);
|
||||||
|
|
||||||
|
@ -28,17 +28,20 @@ namespace osu.Game.Storyboards.Drawables
|
|||||||
LifetimeStart = sampleInfo.StartTime;
|
LifetimeStart = sampleInfo.StartTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Resolved]
|
[Resolved(CanBeNull = true)]
|
||||||
private IBindable<IReadOnlyList<Mod>> mods { get; set; }
|
private IReadOnlyList<Mod> mods { get; set; }
|
||||||
|
|
||||||
protected override void SkinChanged(ISkinSource skin)
|
protected override void SkinChanged(ISkinSource skin)
|
||||||
{
|
{
|
||||||
base.SkinChanged(skin);
|
base.SkinChanged(skin);
|
||||||
|
|
||||||
foreach (var mod in mods.Value.OfType<IApplicableToSample>())
|
if (mods != null)
|
||||||
{
|
{
|
||||||
foreach (var sample in DrawableSamples)
|
foreach (var mod in mods.OfType<IApplicableToSample>())
|
||||||
mod.ApplyToSample(sample);
|
{
|
||||||
|
foreach (var sample in DrawableSamples)
|
||||||
|
mod.ApplyToSample(sample);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Storyboards.Drawables;
|
using osu.Game.Storyboards.Drawables;
|
||||||
|
|
||||||
@ -90,8 +91,8 @@ namespace osu.Game.Storyboards
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public DrawableStoryboard CreateDrawable(IWorkingBeatmap working = null) =>
|
public DrawableStoryboard CreateDrawable(IReadOnlyList<Mod> mods = null) =>
|
||||||
new DrawableStoryboard(this);
|
new DrawableStoryboard(this, mods);
|
||||||
|
|
||||||
public Drawable CreateSpriteFromResourcePath(string path, TextureStore textureStore)
|
public Drawable CreateSpriteFromResourcePath(string path, TextureStore textureStore)
|
||||||
{
|
{
|
||||||
|
@ -26,9 +26,6 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
public new DrawableRuleset DrawableRuleset => base.DrawableRuleset;
|
public new DrawableRuleset DrawableRuleset => base.DrawableRuleset;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Mods from *player* (not OsuScreen).
|
|
||||||
/// </summary>
|
|
||||||
public new Bindable<IReadOnlyList<Mod>> Mods => base.Mods;
|
public new Bindable<IReadOnlyList<Mod>> Mods => base.Mods;
|
||||||
|
|
||||||
public new HUDOverlay HUDOverlay => base.HUDOverlay;
|
public new HUDOverlay HUDOverlay => base.HUDOverlay;
|
||||||
|
@ -36,8 +36,8 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="10.9.0" />
|
<PackageReference Include="Realm" Version="10.9.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2022.223.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2022.304.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.211.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.304.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.14.0" />
|
<PackageReference Include="Sentry" Version="3.14.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.30.1" />
|
<PackageReference Include="SharpCompress" Version="0.30.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
|
@ -61,8 +61,8 @@
|
|||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.223.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.304.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.211.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.304.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
|
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
@ -84,7 +84,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2022.223.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2022.304.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.30.1" />
|
<PackageReference Include="SharpCompress" Version="0.30.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||||
|
Loading…
Reference in New Issue
Block a user