1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-01 21:22:57 +08:00

Merge branch 'master' into timeline-adjustments

This commit is contained in:
Dean Herbert 2021-04-15 20:42:38 +09:00 committed by GitHub
commit ad671ee07a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 253 additions and 82 deletions

View File

@ -52,6 +52,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.412.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.412.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.413.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2021.415.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -7,6 +7,8 @@ using Android.OS;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game; using osu.Game;
using osu.Game.Updater; using osu.Game.Updater;
using osu.Game.Utils;
using Xamarin.Essentials;
namespace osu.Android namespace osu.Android
{ {
@ -72,5 +74,14 @@ namespace osu.Android
} }
protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager();
protected override BatteryInfo CreateBatteryInfo() => new AndroidBatteryInfo();
private class AndroidBatteryInfo : BatteryInfo
{
public override double ChargeLevel => Battery.ChargeLevel;
public override bool IsCharging => Battery.PowerSource != BatteryPowerSource.Battery;
}
} }
} }

View File

@ -6,5 +6,6 @@
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" /> <uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.BATTERY_STATS" />
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!" android:icon="@drawable/lazer" /> <application android:allowBackup="true" android:supportsRtl="true" android:label="osu!" android:icon="@drawable/lazer" />
</manifest> </manifest>

View File

@ -63,5 +63,8 @@
<Version>5.0.0</Version> <Version>5.0.0</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Xamarin.Essentials" Version="1.6.1" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
</Project> </Project>

View File

@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string Name => "Mirror"; public override string Name => "Mirror";
public override string Acronym => "MR"; public override string Acronym => "MR";
public override ModType Type => ModType.Conversion; public override ModType Type => ModType.Conversion;
public override string Description => "Notes are flipped horizontally.";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override bool Ranked => true; public override bool Ranked => true;

View File

@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{ {
public override string Name => "Touch Device"; public override string Name => "Touch Device";
public override string Acronym => "TD"; public override string Acronym => "TD";
public override string Description => "Automatically applied to plays on devices with a touchscreen.";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override ModType Type => ModType.System; public override ModType Type => ModType.System;

View File

@ -144,6 +144,7 @@ namespace osu.Game.Tests.NonVisual
{ {
public override string Name => nameof(ModA); public override string Name => nameof(ModA);
public override string Acronym => nameof(ModA); public override string Acronym => nameof(ModA);
public override string Description => string.Empty;
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithA), typeof(ModIncompatibleWithAAndB) }; public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithA), typeof(ModIncompatibleWithAAndB) };
@ -152,6 +153,7 @@ namespace osu.Game.Tests.NonVisual
private class ModB : Mod private class ModB : Mod
{ {
public override string Name => nameof(ModB); public override string Name => nameof(ModB);
public override string Description => string.Empty;
public override string Acronym => nameof(ModB); public override string Acronym => nameof(ModB);
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
@ -162,6 +164,7 @@ namespace osu.Game.Tests.NonVisual
{ {
public override string Name => nameof(ModC); public override string Name => nameof(ModC);
public override string Acronym => nameof(ModC); public override string Acronym => nameof(ModC);
public override string Description => string.Empty;
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
} }
@ -169,6 +172,7 @@ namespace osu.Game.Tests.NonVisual
{ {
public override string Name => $"Incompatible With {nameof(ModA)}"; public override string Name => $"Incompatible With {nameof(ModA)}";
public override string Acronym => $"Incompatible With {nameof(ModA)}"; public override string Acronym => $"Incompatible With {nameof(ModA)}";
public override string Description => string.Empty;
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModA) }; public override Type[] IncompatibleMods => new[] { typeof(ModA) };
@ -187,6 +191,7 @@ namespace osu.Game.Tests.NonVisual
{ {
public override string Name => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}"; public override string Name => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}";
public override string Acronym => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}"; public override string Acronym => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}";
public override string Description => string.Empty;
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModA), typeof(ModB) }; public override Type[] IncompatibleMods => new[] { typeof(ModA), typeof(ModB) };

View File

@ -140,6 +140,7 @@ namespace osu.Game.Tests.Online
{ {
public override string Name => "Test Mod"; public override string Name => "Test Mod";
public override string Acronym => "TM"; public override string Acronym => "TM";
public override string Description => "This is a test mod.";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
[SettingSource("Test")] [SettingSource("Test")]
@ -156,6 +157,7 @@ namespace osu.Game.Tests.Online
{ {
public override string Name => "Test Mod"; public override string Name => "Test Mod";
public override string Acronym => "TMTR"; public override string Acronym => "TMTR";
public override string Description => "This is a test mod.";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
[SettingSource("Initial rate", "The starting speed of the track")] [SettingSource("Initial rate", "The starting speed of the track")]

View File

@ -100,6 +100,7 @@ namespace osu.Game.Tests.Online
{ {
public override string Name => "Test Mod"; public override string Name => "Test Mod";
public override string Acronym => "TM"; public override string Acronym => "TM";
public override string Description => "This is a test mod.";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
[SettingSource("Test")] [SettingSource("Test")]
@ -116,6 +117,7 @@ namespace osu.Game.Tests.Online
{ {
public override string Name => "Test Mod"; public override string Name => "Test Mod";
public override string Acronym => "TMTR"; public override string Acronym => "TMTR";
public override string Description => "This is a test mod.";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
[SettingSource("Initial rate", "The starting speed of the track")] [SettingSource("Initial rate", "The starting speed of the track")]
@ -150,6 +152,7 @@ namespace osu.Game.Tests.Online
{ {
public override string Name => "Test Mod"; public override string Name => "Test Mod";
public override string Acronym => "TM"; public override string Acronym => "TM";
public override string Description => "This is a test mod.";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
[SettingSource("Test")] [SettingSource("Test")]

View File

@ -65,6 +65,21 @@ namespace osu.Game.Tests.Visual.Background
stack.Push(songSelect = new DummySongSelect()); stack.Push(songSelect = new DummySongSelect());
}); });
/// <summary>
/// User settings should always be ignored on song select screen.
/// </summary>
[Test]
public void TestUserSettingsIgnoredOnSongSelect()
{
setupUserSettings();
AddUntilStep("Screen is undimmed", () => songSelect.IsBackgroundUndimmed());
AddUntilStep("Screen using background blur", () => songSelect.IsBackgroundBlur());
performFullSetup();
AddStep("Exit to song select", () => player.Exit());
AddUntilStep("Screen is undimmed", () => songSelect.IsBackgroundUndimmed());
AddUntilStep("Screen using background blur", () => songSelect.IsBackgroundBlur());
}
/// <summary> /// <summary>
/// Check if <see cref="PlayerLoader"/> properly triggers the visual settings preview when a user hovers over the visual settings panel. /// Check if <see cref="PlayerLoader"/> properly triggers the visual settings preview when a user hovers over the visual settings panel.
/// </summary> /// </summary>
@ -227,17 +242,6 @@ namespace osu.Game.Tests.Visual.Background
songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && songSelect.CheckBackgroundBlur(results.ExpectedBackgroundBlur)); songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && songSelect.CheckBackgroundBlur(results.ExpectedBackgroundBlur));
} }
/// <summary>
/// Check if background gets undimmed and unblurred when leaving <see cref="Player"/> for <see cref="PlaySongSelect"/>
/// </summary>
[Test]
public void TestTransitionOut()
{
performFullSetup();
AddStep("Exit to song select", () => player.Exit());
AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBlurCorrect());
}
/// <summary> /// <summary>
/// Check if hovering on the visual settings dialogue after resuming from player still previews the background dim. /// Check if hovering on the visual settings dialogue after resuming from player still previews the background dim.
/// </summary> /// </summary>
@ -333,7 +337,7 @@ namespace osu.Game.Tests.Visual.Background
public bool IsBackgroundVisible() => background.CurrentAlpha == 1; public bool IsBackgroundVisible() => background.CurrentAlpha == 1;
public bool IsBlurCorrect() => background.CurrentBlur == new Vector2(BACKGROUND_BLUR); public bool IsBackgroundBlur() => background.CurrentBlur == new Vector2(BACKGROUND_BLUR);
public bool CheckBackgroundBlur(Vector2 expected) => background.CurrentBlur == expected; public bool CheckBackgroundBlur(Vector2 expected) => background.CurrentBlur == expected;

View File

@ -5,7 +5,6 @@ using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Screens.Edit.Components.Timelines.Summary;
using osuTK; using osuTK;
@ -16,18 +15,28 @@ namespace osu.Game.Tests.Visual.Editing
public class TestSceneEditorSummaryTimeline : EditorClockTestScene public class TestSceneEditorSummaryTimeline : EditorClockTestScene
{ {
[Cached(typeof(EditorBeatmap))] [Cached(typeof(EditorBeatmap))]
private readonly EditorBeatmap editorBeatmap = new EditorBeatmap(new OsuBeatmap()); private readonly EditorBeatmap editorBeatmap;
[BackgroundDependencyLoader] public TestSceneEditorSummaryTimeline()
private void load()
{ {
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); editorBeatmap = new EditorBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo));
}
Add(new SummaryTimeline protected override void LoadComplete()
{
base.LoadComplete();
AddStep("create timeline", () =>
{ {
Anchor = Anchor.Centre, // required for track
Origin = Anchor.Centre, Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
Size = new Vector2(500, 50)
Add(new SummaryTimeline
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500, 50)
});
}); });
} }
} }

View File

@ -25,6 +25,7 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Utils;
using osuTK.Input; using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
@ -48,6 +49,9 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached] [Cached]
private readonly VolumeOverlay volumeOverlay; private readonly VolumeOverlay volumeOverlay;
[Cached(typeof(BatteryInfo))]
private readonly LocalBatteryInfo batteryInfo = new LocalBatteryInfo();
private readonly ChangelogOverlay changelogOverlay; private readonly ChangelogOverlay changelogOverlay;
public TestScenePlayerLoader() public TestScenePlayerLoader()
@ -288,6 +292,33 @@ namespace osu.Game.Tests.Visual.Gameplay
} }
} }
[TestCase(false, 1.0, false)] // not charging, above cutoff --> no warning
[TestCase(true, 0.1, false)] // charging, below cutoff --> no warning
[TestCase(false, 0.25, true)] // not charging, at cutoff --> warning
public void TestLowBatteryNotification(bool isCharging, double chargeLevel, bool shouldWarn)
{
AddStep("reset notification lock", () => sessionStatics.GetBindable<bool>(Static.LowBatteryNotificationShownOnce).Value = false);
// set charge status and level
AddStep("load player", () => resetPlayer(false, () =>
{
batteryInfo.SetCharging(isCharging);
batteryInfo.SetChargeLevel(chargeLevel);
}));
AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready);
AddAssert($"notification {(shouldWarn ? "triggered" : "not triggered")}", () => notificationOverlay.UnreadCount.Value == (shouldWarn ? 1 : 0));
AddStep("click notification", () =>
{
var scrollContainer = (OsuScrollContainer)notificationOverlay.Children.Last();
var flowContainer = scrollContainer.Children.OfType<FillFlowContainer<NotificationSection>>().First();
var notification = flowContainer.First();
InputManager.MoveMouseTo(notification);
InputManager.Click(MouseButton.Left);
});
AddUntilStep("wait for player load", () => player.IsLoaded);
}
[Test] [Test]
public void TestEpilepsyWarningEarlyExit() public void TestEpilepsyWarningEarlyExit()
{ {
@ -321,6 +352,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public override string Name => string.Empty; public override string Name => string.Empty;
public override string Acronym => string.Empty; public override string Acronym => string.Empty;
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override string Description => string.Empty;
public bool Applied { get; private set; } public bool Applied { get; private set; }
@ -348,5 +380,29 @@ namespace osu.Game.Tests.Visual.Gameplay
throw new TimeoutException(); throw new TimeoutException();
} }
} }
/// <summary>
/// Mutable dummy BatteryInfo class for <see cref="TestScenePlayerLoader.TestLowBatteryNotification"/>
/// </summary>
/// <inheritdoc/>
private class LocalBatteryInfo : BatteryInfo
{
private bool isCharging = true;
private double chargeLevel = 1;
public override bool IsCharging => isCharging;
public override double ChargeLevel => chargeLevel;
public void SetCharging(bool value)
{
isCharging = value;
}
public void SetChargeLevel(double value)
{
chargeLevel = value;
}
}
} }
} }

View File

@ -57,6 +57,8 @@ namespace osu.Game.Tests.Visual.UserInterface
private abstract class TestMod : Mod, IApplicableMod private abstract class TestMod : Mod, IApplicableMod
{ {
public override double ScoreMultiplier => 1.0; public override double ScoreMultiplier => 1.0;
public override string Description => "This is a test mod.";
} }
} }
} }

View File

@ -226,6 +226,8 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
public override double ScoreMultiplier => 1.0; public override double ScoreMultiplier => 1.0;
public override string Description => "This is a customisable test mod.";
public override ModType Type => ModType.Conversion; public override ModType Type => ModType.Conversion;
[SettingSource("Sample float", "Change something for a mod")] [SettingSource("Sample float", "Change something for a mod")]

View File

@ -16,6 +16,7 @@ namespace osu.Game.Configuration
{ {
SetDefault(Static.LoginOverlayDisplayed, false); SetDefault(Static.LoginOverlayDisplayed, false);
SetDefault(Static.MutedAudioNotificationShownOnce, false); SetDefault(Static.MutedAudioNotificationShownOnce, false);
SetDefault(Static.LowBatteryNotificationShownOnce, false);
SetDefault(Static.LastHoverSoundPlaybackTime, (double?)null); SetDefault(Static.LastHoverSoundPlaybackTime, (double?)null);
SetDefault<APISeasonalBackgrounds>(Static.SeasonalBackgrounds, null); SetDefault<APISeasonalBackgrounds>(Static.SeasonalBackgrounds, null);
} }
@ -25,6 +26,7 @@ namespace osu.Game.Configuration
{ {
LoginOverlayDisplayed, LoginOverlayDisplayed,
MutedAudioNotificationShownOnce, MutedAudioNotificationShownOnce,
LowBatteryNotificationShownOnce,
/// <summary> /// <summary>
/// Info about seasonal backgrounds available fetched from API - see <see cref="APISeasonalBackgrounds"/>. /// Info about seasonal backgrounds available fetched from API - see <see cref="APISeasonalBackgrounds"/>.

View File

@ -40,6 +40,7 @@ using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Utils;
using osuTK.Input; using osuTK.Input;
using RuntimeInfo = osu.Framework.RuntimeInfo; using RuntimeInfo = osu.Framework.RuntimeInfo;
@ -156,6 +157,8 @@ namespace osu.Game
protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager(); protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager();
protected virtual BatteryInfo CreateBatteryInfo() => null;
/// <summary> /// <summary>
/// The maximum volume at which audio tracks should playback. This can be set lower than 1 to create some head-room for sound effects. /// The maximum volume at which audio tracks should playback. This can be set lower than 1 to create some head-room for sound effects.
/// </summary> /// </summary>
@ -281,6 +284,11 @@ namespace osu.Game
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));
dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore));
var powerStatus = CreateBatteryInfo();
if (powerStatus != null)
dependencies.CacheAs(powerStatus);
dependencies.Cache(new SessionStatics()); dependencies.Cache(new SessionStatics());
dependencies.Cache(new OsuColour()); dependencies.Cache(new OsuColour());

View File

@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mods
/// The user readable description of this mod. /// The user readable description of this mod.
/// </summary> /// </summary>
[JsonIgnore] [JsonIgnore]
public virtual string Description => string.Empty; public abstract string Description { get; }
/// <summary> /// <summary>
/// The tooltip to display for this mod when used in a <see cref="ModIcon"/>. /// The tooltip to display for this mod when used in a <see cref="ModIcon"/>.

View File

@ -12,6 +12,7 @@ namespace osu.Game.Rulesets.Mods
{ {
public override string Name => "No Mod"; public override string Name => "No Mod";
public override string Acronym => "NM"; public override string Acronym => "NM";
public override string Description => "No mods applied.";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override IconUsage? Icon => FontAwesome.Solid.Ban; public override IconUsage? Icon => FontAwesome.Solid.Ban;
public override ModType Type => ModType.System; public override ModType Type => ModType.System;

View File

@ -56,8 +56,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
public virtual IEnumerable<HitSampleInfo> GetSamples() => HitObject.Samples; public virtual IEnumerable<HitSampleInfo> GetSamples() => HitObject.Samples;
private readonly Lazy<List<DrawableHitObject>> nestedHitObjects = new Lazy<List<DrawableHitObject>>(); private readonly List<DrawableHitObject> nestedHitObjects = new List<DrawableHitObject>();
public IReadOnlyList<DrawableHitObject> NestedHitObjects => nestedHitObjects.IsValueCreated ? nestedHitObjects.Value : (IReadOnlyList<DrawableHitObject>)Array.Empty<DrawableHitObject>(); public IReadOnlyList<DrawableHitObject> NestedHitObjects => nestedHitObjects;
/// <summary> /// <summary>
/// Whether this object should handle any user input events. /// Whether this object should handle any user input events.
@ -249,7 +249,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
// Must be done before the nested DHO is added to occur before the nested Apply()! // Must be done before the nested DHO is added to occur before the nested Apply()!
drawableNested.ParentHitObject = this; drawableNested.ParentHitObject = this;
nestedHitObjects.Value.Add(drawableNested); nestedHitObjects.Add(drawableNested);
AddNestedHitObject(drawableNested); AddNestedHitObject(drawableNested);
} }
@ -305,19 +305,16 @@ namespace osu.Game.Rulesets.Objects.Drawables
if (Samples != null) if (Samples != null)
Samples.Samples = null; Samples.Samples = null;
if (nestedHitObjects.IsValueCreated) foreach (var obj in nestedHitObjects)
{ {
foreach (var obj in nestedHitObjects.Value) obj.OnNewResult -= onNewResult;
{ obj.OnRevertResult -= onRevertResult;
obj.OnNewResult -= onNewResult; obj.ApplyCustomUpdateState -= onApplyCustomUpdateState;
obj.OnRevertResult -= onRevertResult;
obj.ApplyCustomUpdateState -= onApplyCustomUpdateState;
}
nestedHitObjects.Value.Clear();
ClearNestedHitObjects();
} }
nestedHitObjects.Clear();
ClearNestedHitObjects();
HitObject.DefaultsApplied -= onDefaultsApplied; HitObject.DefaultsApplied -= onDefaultsApplied;
OnFree(); OnFree();

View File

@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.UI
var enumerable = HitObjectContainer.Objects; var enumerable = HitObjectContainer.Objects;
if (nestedPlayfields.IsValueCreated) if (nestedPlayfields.Count != 0)
enumerable = enumerable.Concat(NestedPlayfields.SelectMany(p => p.AllHitObjects)); enumerable = enumerable.Concat(NestedPlayfields.SelectMany(p => p.AllHitObjects));
return enumerable; return enumerable;
@ -76,9 +76,9 @@ namespace osu.Game.Rulesets.UI
/// <summary> /// <summary>
/// All <see cref="Playfield"/>s nested inside this <see cref="Playfield"/>. /// All <see cref="Playfield"/>s nested inside this <see cref="Playfield"/>.
/// </summary> /// </summary>
public IEnumerable<Playfield> NestedPlayfields => nestedPlayfields.IsValueCreated ? nestedPlayfields.Value : Enumerable.Empty<Playfield>(); public IEnumerable<Playfield> NestedPlayfields => nestedPlayfields;
private readonly Lazy<List<Playfield>> nestedPlayfields = new Lazy<List<Playfield>>(); private readonly List<Playfield> nestedPlayfields = new List<Playfield>();
/// <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.
@ -217,7 +217,7 @@ namespace osu.Game.Rulesets.UI
otherPlayfield.HitObjectUsageBegan += h => HitObjectUsageBegan?.Invoke(h); otherPlayfield.HitObjectUsageBegan += h => HitObjectUsageBegan?.Invoke(h);
otherPlayfield.HitObjectUsageFinished += h => HitObjectUsageFinished?.Invoke(h); otherPlayfield.HitObjectUsageFinished += h => HitObjectUsageFinished?.Invoke(h);
nestedPlayfields.Value.Add(otherPlayfield); nestedPlayfields.Add(otherPlayfield);
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -279,12 +279,7 @@ namespace osu.Game.Rulesets.UI
return true; return true;
} }
bool removedFromNested = false; return nestedPlayfields.Any(p => p.Remove(hitObject));
if (nestedPlayfields.IsValueCreated)
removedFromNested = nestedPlayfields.Value.Any(p => p.Remove(hitObject));
return removedFromNested;
} }
/// <summary> /// <summary>
@ -429,10 +424,7 @@ namespace osu.Game.Rulesets.UI
return; return;
} }
if (!nestedPlayfields.IsValueCreated) foreach (var p in nestedPlayfields)
return;
foreach (var p in nestedPlayfields.Value)
p.SetKeepAlive(hitObject, keepAlive); p.SetKeepAlive(hitObject, keepAlive);
} }
@ -444,10 +436,7 @@ namespace osu.Game.Rulesets.UI
foreach (var (_, entry) in lifetimeEntryMap) foreach (var (_, entry) in lifetimeEntryMap)
entry.KeepAlive = true; entry.KeepAlive = true;
if (!nestedPlayfields.IsValueCreated) foreach (var p in nestedPlayfields)
return;
foreach (var p in nestedPlayfields.Value)
p.KeepAllAlive(); p.KeepAllAlive();
} }
@ -461,10 +450,7 @@ namespace osu.Game.Rulesets.UI
{ {
HitObjectContainer.PastLifetimeExtension = value; HitObjectContainer.PastLifetimeExtension = value;
if (!nestedPlayfields.IsValueCreated) foreach (var nested in nestedPlayfields)
return;
foreach (var nested in nestedPlayfields.Value)
nested.PastLifetimeExtension = value; nested.PastLifetimeExtension = value;
} }
} }
@ -479,10 +465,7 @@ namespace osu.Game.Rulesets.UI
{ {
HitObjectContainer.FutureLifetimeExtension = value; HitObjectContainer.FutureLifetimeExtension = value;
if (!nestedPlayfields.IsValueCreated) foreach (var nested in nestedPlayfields)
return;
foreach (var nested in nestedPlayfields.Value)
nested.FutureLifetimeExtension = value; nested.FutureLifetimeExtension = value;
} }
} }

View File

@ -27,9 +27,12 @@ namespace osu.Game.Screens.Backgrounds
private WorkingBeatmap beatmap; private WorkingBeatmap beatmap;
/// <summary> /// <summary>
/// Whether or not user-configured settings relating to brightness of elements should be ignored /// Whether or not user-configured settings relating to brightness of elements should be ignored.
/// </summary> /// </summary>
public readonly Bindable<bool> IgnoreUserSettings = new Bindable<bool>(); /// <remarks>
/// Beatmap background screens should not apply user settings by default.
/// </remarks>
public readonly Bindable<bool> IgnoreUserSettings = new Bindable<bool>(true);
public readonly Bindable<bool> StoryboardReplacesBackground = new Bindable<bool>(); public readonly Bindable<bool> StoryboardReplacesBackground = new Bindable<bool>();

View File

@ -28,7 +28,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) => Colour = colours.Yellow; private void load(OsuColour colours) => Colour = colours.GreyCarmineLight;
} }
} }
} }

View File

@ -39,7 +39,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
return; return;
} }
Colour = controlPoints.Any(c => c is TimingControlPoint) ? colours.YellowDark : colours.Green; Colour = Group.ControlPoints.First().GetRepresentingColour(colours);
}, true); }, true);
} }
} }

View File

@ -38,6 +38,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary
}, },
new Container new Container
{ {
Name = "centre line",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = colours.Gray5, Colour = colours.Gray5,
Children = new Drawable[] Children = new Drawable[]
@ -45,7 +46,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary
new Circle new Circle
{ {
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreRight, Origin = Anchor.Centre,
Size = new Vector2(5) Size = new Vector2(5)
}, },
new Box new Box
@ -59,7 +60,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary
new Circle new Circle
{ {
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
Origin = Anchor.CentreLeft, Origin = Anchor.Centre,
Size = new Vector2(5) Size = new Vector2(5)
}, },
} }
@ -69,7 +70,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Height = 0.25f Height = 0.10f
} }
}; };
} }

View File

@ -2,7 +2,6 @@
// 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.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations
@ -10,19 +9,15 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations
/// <summary> /// <summary>
/// Represents a spanning point on a timeline part. /// Represents a spanning point on a timeline part.
/// </summary> /// </summary>
public class DurationVisualisation : Container public class DurationVisualisation : Circle
{ {
protected DurationVisualisation(double startTime, double endTime) protected DurationVisualisation(double startTime, double endTime)
{ {
Masking = true;
CornerRadius = 5;
RelativePositionAxes = Axes.X; RelativePositionAxes = Axes.X;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
X = (float)startTime; X = (float)startTime;
Width = (float)(endTime - startTime); Width = (float)(endTime - startTime);
AddInternal(new Box { RelativeSizeAxes = Axes.Both });
} }
} }
} }

View File

@ -9,7 +9,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations
/// <summary> /// <summary>
/// Represents a singular point on a timeline part. /// Represents a singular point on a timeline part.
/// </summary> /// </summary>
public class PointVisualisation : Box public class PointVisualisation : Circle
{ {
public const float MAX_WIDTH = 4; public const float MAX_WIDTH = 4;

View File

@ -24,6 +24,7 @@ using osu.Game.Overlays.Notifications;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Users; using osu.Game.Users;
using osu.Game.Utils;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -112,6 +113,9 @@ namespace osu.Game.Screens.Play
[Resolved] [Resolved]
private AudioManager audioManager { get; set; } private AudioManager audioManager { get; set; }
[Resolved(CanBeNull = true)]
private BatteryInfo batteryInfo { get; set; }
public PlayerLoader(Func<Player> createPlayer) public PlayerLoader(Func<Player> createPlayer)
{ {
this.createPlayer = createPlayer; this.createPlayer = createPlayer;
@ -121,6 +125,7 @@ namespace osu.Game.Screens.Play
private void load(SessionStatics sessionStatics) private void load(SessionStatics sessionStatics)
{ {
muteWarningShownOnce = sessionStatics.GetBindable<bool>(Static.MutedAudioNotificationShownOnce); muteWarningShownOnce = sessionStatics.GetBindable<bool>(Static.MutedAudioNotificationShownOnce);
batteryWarningShownOnce = sessionStatics.GetBindable<bool>(Static.LowBatteryNotificationShownOnce);
InternalChild = (content = new LogoTrackingContainer InternalChild = (content = new LogoTrackingContainer
{ {
@ -196,6 +201,7 @@ namespace osu.Game.Screens.Play
Scheduler.Add(new ScheduledDelegate(pushWhenLoaded, Clock.CurrentTime + 1800, 0)); Scheduler.Add(new ScheduledDelegate(pushWhenLoaded, Clock.CurrentTime + 1800, 0));
showMuteWarningIfNeeded(); showMuteWarningIfNeeded();
showBatteryWarningIfNeeded();
} }
public override void OnResuming(IScreen last) public override void OnResuming(IScreen last)
@ -470,5 +476,48 @@ namespace osu.Game.Screens.Play
} }
#endregion #endregion
#region Low battery warning
private Bindable<bool> batteryWarningShownOnce;
private void showBatteryWarningIfNeeded()
{
if (batteryInfo == null) return;
if (!batteryWarningShownOnce.Value)
{
if (!batteryInfo.IsCharging && batteryInfo.ChargeLevel <= 0.25)
{
notificationOverlay?.Post(new BatteryWarningNotification());
batteryWarningShownOnce.Value = true;
}
}
}
private class BatteryWarningNotification : SimpleNotification
{
public override bool IsImportant => true;
public BatteryWarningNotification()
{
Text = "Your battery level is low! Charge your device to prevent interruptions during gameplay.";
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, NotificationOverlay notificationOverlay)
{
Icon = FontAwesome.Solid.BatteryQuarter;
IconBackgound.Colour = colours.RedDark;
Activated = delegate
{
notificationOverlay.Hide();
return true;
};
}
}
#endregion
} }
} }

View File

@ -0,0 +1,18 @@
// 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.
namespace osu.Game.Utils
{
/// <summary>
/// Provides access to the system's power status.
/// </summary>
public abstract class BatteryInfo
{
/// <summary>
/// The charge level of the battery, from 0 to 1.
/// </summary>
public abstract double ChargeLevel { get; }
public abstract bool IsCharging { get; }
}
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
@ -29,7 +29,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.NETCore.Targets" Version="3.1.0" /> <PackageReference Include="Microsoft.NETCore.Targets" Version="3.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="2021.413.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.415.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.412.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.412.0" />
<PackageReference Include="Sentry" Version="3.2.0" /> <PackageReference Include="Sentry" Version="3.2.0" />
<PackageReference Include="SharpCompress" Version="0.28.1" /> <PackageReference Include="SharpCompress" Version="0.28.1" />

View File

@ -70,7 +70,7 @@
<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="2021.413.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2021.415.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.412.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.412.0" />
</ItemGroup> </ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) --> <!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
@ -93,7 +93,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2021.413.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.415.0" />
<PackageReference Include="SharpCompress" Version="0.28.1" /> <PackageReference Include="SharpCompress" Version="0.28.1" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />

View File

@ -5,6 +5,8 @@ using System;
using Foundation; using Foundation;
using osu.Game; using osu.Game;
using osu.Game.Updater; using osu.Game.Updater;
using osu.Game.Utils;
using Xamarin.Essentials;
namespace osu.iOS namespace osu.iOS
{ {
@ -13,5 +15,14 @@ namespace osu.iOS
public override Version AssemblyVersion => new Version(NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString()); public override Version AssemblyVersion => new Version(NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString());
protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager();
protected override BatteryInfo CreateBatteryInfo() => new IOSBatteryInfo();
private class IOSBatteryInfo : BatteryInfo
{
public override double ChargeLevel => Battery.ChargeLevel;
public override bool IsCharging => Battery.PowerSource != BatteryPowerSource.Battery;
}
} }
} }

View File

@ -116,5 +116,8 @@
<Visible>false</Visible> <Visible>false</Visible>
</ImageAsset> </ImageAsset>
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Xamarin.Essentials" Version="1.6.1" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
</Project> </Project>