mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 17:43:05 +08:00
Merge branch 'master' into fix-bpm-differences
This commit is contained in:
commit
1a831145ce
@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.217.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.221.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
48
osu.Game.Benchmarks/BenchmarkStringComparison.cs
Normal file
48
osu.Game.Benchmarks/BenchmarkStringComparison.cs
Normal file
@ -0,0 +1,48 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Benchmarks
|
||||
{
|
||||
public class BenchmarkStringComparison
|
||||
{
|
||||
private string[] strings = null!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetUp()
|
||||
{
|
||||
strings = new string[10000];
|
||||
|
||||
for (int i = 0; i < strings.Length; ++i)
|
||||
strings[i] = Guid.NewGuid().ToString();
|
||||
|
||||
for (int i = 0; i < strings.Length; ++i)
|
||||
{
|
||||
if (i % 2 == 0)
|
||||
strings[i] = strings[i].ToUpperInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void OrdinalIgnoreCase() => compare(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
[Benchmark]
|
||||
public void OrdinalSortByCase() => compare(OrdinalSortByCaseStringComparer.DEFAULT);
|
||||
|
||||
[Benchmark]
|
||||
public void InvariantCulture() => compare(StringComparer.InvariantCulture);
|
||||
|
||||
private void compare(IComparer<string> comparer)
|
||||
{
|
||||
for (int i = 0; i < strings.Length; ++i)
|
||||
{
|
||||
for (int j = i + 1; j < strings.Length; ++j)
|
||||
_ = comparer.Compare(strings[i], strings[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
@ -17,9 +18,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
[Resolved]
|
||||
private Playfield playfield { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IScrollingInfo scrollingInfo { get; set; } = null!;
|
||||
|
||||
protected ScrollingHitObjectContainer HitObjectContainer => ((ManiaPlayfield)playfield).GetColumn(HitObject.Column).HitObjectContainer;
|
||||
|
||||
protected ManiaSelectionBlueprint(T hitObject)
|
||||
@ -28,14 +26,31 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
RelativeSizeAxes = Axes.None;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
private readonly IBindable<ScrollingDirection> directionBindable = new Bindable<ScrollingDirection>();
|
||||
|
||||
var anchor = scrollingInfo.Direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IScrollingInfo scrollingInfo)
|
||||
{
|
||||
directionBindable.BindTo(scrollingInfo.Direction);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
directionBindable.BindValueChanged(onDirectionChanged, true);
|
||||
}
|
||||
|
||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||
{
|
||||
var anchor = direction.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
||||
Anchor = Origin = anchor;
|
||||
foreach (var child in InternalChildren)
|
||||
child.Anchor = child.Origin = anchor;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
Position = Parent!.ToLocalSpace(HitObjectContainer.ScreenSpacePositionAtTime(HitObject.StartTime)) - AnchorPosition;
|
||||
Width = HitObjectContainer.DrawWidth;
|
||||
|
@ -7,7 +7,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
@ -149,7 +148,18 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
/// <summary>
|
||||
/// Retrieves the total amount of columns across all stages in this playfield.
|
||||
/// </summary>
|
||||
public int TotalColumns => stages.Sum(s => s.Columns.Length);
|
||||
public int TotalColumns
|
||||
{
|
||||
get
|
||||
{
|
||||
int sum = 0;
|
||||
|
||||
foreach (var stage in stages)
|
||||
sum += stage.Columns.Length;
|
||||
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
|
||||
private Stage getStageByColumn(int column)
|
||||
{
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
|
@ -78,9 +78,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
|
||||
|
||||
Scale = new Vector2(hitObject.Scale);
|
||||
|
||||
if (hitObject is IHasComboInformation combo)
|
||||
ring.BorderColour = combo.GetComboColour(skin);
|
||||
|
||||
double editorTime = editorClock.CurrentTime;
|
||||
double hitObjectTime = hitObject.StartTime;
|
||||
bool hasReachedObject = editorTime >= hitObjectTime;
|
||||
@ -92,6 +89,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
|
||||
|
||||
ring.Scale = new Vector2(1 + 0.1f * ringScale);
|
||||
content.Alpha = 0.9f * (1 - alpha);
|
||||
|
||||
// TODO: should only update colour on skin/combo/object change.
|
||||
if (hitObject is IHasComboInformation combo && content.Alpha > 0)
|
||||
ring.BorderColour = combo.GetComboColour(skin);
|
||||
}
|
||||
else
|
||||
content.Alpha = 0;
|
||||
|
@ -416,8 +416,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathEndOffset) ?? BodyPiece.ToScreenSpace(BodyPiece.PathEndLocation)
|
||||
};
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
||||
BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true;
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
||||
{
|
||||
if (BodyPiece.ReceivePositionalInputAt(screenSpacePos))
|
||||
return true;
|
||||
|
||||
if (ControlPointVisualiser == null)
|
||||
return false;
|
||||
|
||||
foreach (var p in ControlPointVisualiser.Pieces)
|
||||
{
|
||||
if (p.ReceivePositionalInputAt(screenSpacePos))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new SliderCircleOverlay(slider, position);
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
{
|
||||
public partial class OsuInputManager : RulesetInputManager<OsuAction>
|
||||
{
|
||||
public IEnumerable<OsuAction> PressedActions => KeyBindingContainer.PressedActions;
|
||||
public SlimReadOnlyListWrapper<OsuAction> PressedActions => KeyBindingContainer.PressedActions;
|
||||
|
||||
/// <summary>
|
||||
/// Whether gameplay input buttons should be allowed.
|
||||
|
@ -25,8 +25,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
{
|
||||
Texture = textures.Get(@"Gameplay/osu/approachcircle").WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS * 2);
|
||||
|
||||
// account for the sprite being used for the default approach circle being taken from stable,
|
||||
// when hitcircles have 5px padding on each size. this should be removed if we update the sprite.
|
||||
// In triangles and argon skins, we expanded hitcircles to take up the full 128 px which are clickable,
|
||||
// but still use the old approach circle sprite. To make it feel correct (ie. disappear as it collides
|
||||
// with the hitcircle, *not when it overlaps the border*) we need to expand it slightly.
|
||||
Scale = new Vector2(128 / 118f);
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,6 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
@ -26,10 +25,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
var texture = skin.GetTexture(@"approachcircle");
|
||||
Debug.Assert(texture != null);
|
||||
Texture = texture.WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS * 2);
|
||||
|
||||
// account for the sprite being used for the default approach circle being taken from stable,
|
||||
// when hitcircles have 5px padding on each size. this should be removed if we update the sprite.
|
||||
Scale = new Vector2(128 / 118f);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
// Note that the scale adjust here is 2 instead of DrawableSliderBall.FOLLOW_AREA to match legacy behaviour.
|
||||
// This means the actual tracking area for gameplay purposes is larger than the sprite (but skins may be accounting for this).
|
||||
this.ScaleTo(0.5f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out)
|
||||
this.ScaleTo(1f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out)
|
||||
.FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime));
|
||||
}
|
||||
|
||||
|
@ -8,11 +8,13 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Login;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Users.Drawables;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -25,6 +27,9 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
|
||||
private LoginOverlay loginOverlay = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager configManager { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@ -156,5 +161,36 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
});
|
||||
AddAssert("login overlay is hidden", () => loginOverlay.State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUncheckingRememberUsernameClearsIt()
|
||||
{
|
||||
AddStep("logout", () => API.Logout());
|
||||
AddStep("set username", () => configManager.SetValue(OsuSetting.Username, "test_user"));
|
||||
AddStep("set remember password", () => configManager.SetValue(OsuSetting.SavePassword, true));
|
||||
AddStep("uncheck remember username", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(loginOverlay.ChildrenOfType<SettingsCheckbox>().First());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("remember username off", () => configManager.Get<bool>(OsuSetting.SaveUsername), () => Is.False);
|
||||
AddAssert("remember password off", () => configManager.Get<bool>(OsuSetting.SavePassword), () => Is.False);
|
||||
AddAssert("username cleared", () => configManager.Get<string>(OsuSetting.Username), () => Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUncheckingRememberPasswordClearsToken()
|
||||
{
|
||||
AddStep("logout", () => API.Logout());
|
||||
AddStep("set token", () => configManager.SetValue(OsuSetting.Token, "test_token"));
|
||||
AddStep("set remember password", () => configManager.SetValue(OsuSetting.SavePassword, true));
|
||||
AddStep("uncheck remember token", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(loginOverlay.ChildrenOfType<SettingsCheckbox>().Last());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("remember password off", () => configManager.Get<bool>(OsuSetting.SavePassword), () => Is.False);
|
||||
AddAssert("token cleared", () => configManager.Get<string>(OsuSetting.Token), () => Is.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
@ -18,6 +19,7 @@ using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
@ -221,6 +223,67 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAttemptPlayBeatmapWrongHashFails()
|
||||
{
|
||||
Screens.Select.SongSelect songSelect = null;
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).GetResultSafely());
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
AddStep("change beatmap files", () =>
|
||||
{
|
||||
foreach (var file in Game.Beatmap.Value.BeatmapSetInfo.Files.Where(f => Path.GetExtension(f.Filename) == ".osu"))
|
||||
{
|
||||
using (var stream = Game.Storage.GetStream(Path.Combine("files", file.File.GetStoragePath()), FileAccess.ReadWrite))
|
||||
stream.WriteByte(0);
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("invalidate cache", () =>
|
||||
{
|
||||
((IWorkingBeatmapCache)Game.BeatmapManager).Invalidate(Game.Beatmap.Value.BeatmapSetInfo);
|
||||
});
|
||||
|
||||
AddStep("select next difficulty", () => InputManager.Key(Key.Down));
|
||||
AddStep("press enter", () => InputManager.Key(Key.Enter));
|
||||
|
||||
AddUntilStep("wait for player loader", () => Game.ScreenStack.CurrentScreen is PlayerLoader);
|
||||
AddUntilStep("wait for song select", () => songSelect.IsCurrentScreen());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAttemptPlayBeatmapMissingFails()
|
||||
{
|
||||
Screens.Select.SongSelect songSelect = null;
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).GetResultSafely());
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
AddStep("delete beatmap files", () =>
|
||||
{
|
||||
foreach (var file in Game.Beatmap.Value.BeatmapSetInfo.Files.Where(f => Path.GetExtension(f.Filename) == ".osu"))
|
||||
Game.Storage.Delete(Path.Combine("files", file.File.GetStoragePath()));
|
||||
});
|
||||
|
||||
AddStep("invalidate cache", () =>
|
||||
{
|
||||
((IWorkingBeatmapCache)Game.BeatmapManager).Invalidate(Game.Beatmap.Value.BeatmapSetInfo);
|
||||
});
|
||||
|
||||
AddStep("select next difficulty", () => InputManager.Key(Key.Down));
|
||||
AddStep("press enter", () => InputManager.Key(Key.Enter));
|
||||
|
||||
AddUntilStep("wait for player loader", () => Game.ScreenStack.CurrentScreen is PlayerLoader);
|
||||
AddUntilStep("wait for song select", () => songSelect.IsCurrentScreen());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRetryCountIncrements()
|
||||
{
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Threading;
|
||||
@ -301,6 +302,25 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
switchToGameplayScene();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRulesetInputDisabledWhenSkinEditorOpen()
|
||||
{
|
||||
advanceToSongSelect();
|
||||
openSkinEditor();
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
switchToGameplayScene();
|
||||
AddUntilStep("nested input disabled", () => ((Player)Game.ScreenStack.CurrentScreen).ChildrenOfType<PassThroughInputManager>().All(manager => !manager.UseParentInput));
|
||||
|
||||
toggleSkinEditor();
|
||||
AddUntilStep("nested input enabled", () => ((Player)Game.ScreenStack.CurrentScreen).ChildrenOfType<PassThroughInputManager>().Any(manager => manager.UseParentInput));
|
||||
|
||||
toggleSkinEditor();
|
||||
AddUntilStep("nested input disabled", () => ((Player)Game.ScreenStack.CurrentScreen).ChildrenOfType<PassThroughInputManager>().All(manager => !manager.UseParentInput));
|
||||
}
|
||||
|
||||
private void advanceToSongSelect()
|
||||
{
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
|
@ -81,16 +81,17 @@ namespace osu.Game.Tests.Visual.Online
|
||||
},
|
||||
|
||||
// Taken from https://github.com/ppy/osu/issues/13993#issuecomment-885994077
|
||||
new[] { "Problematic", @"My tablet doesn't work :( It's a Huion 420 and it's apparently incompatible with OpenTablet Driver. The warning I get is: ""DeviceInUseException: Device is currently in use by another kernel module. To fix this issue, please follow the instructions from https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ#arg umentoutofrangeexception-value-0-15"" and it repeats 4 times on the notification before logging subsequent warnings. Checking the logs, it looks for other Huion tablets before sending the notification (e.g. ""2021-07-23 03:52:33 [verbose]: Detect: Searching for tablet 'Huion WH1409 V2' 20 2021-07-23 03:52:33 [error]: DeviceInUseException: Device is currently in use by another kernel module. To fix this issue, please follow the instructions from https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ#arg umentoutofrangeexception-value-0-15"") I use an Arch based installation of Linux and the tablet runs perfectly with Digimend kernel driver, with area configuration, pen pressure, etc. On osu!lazer the cursor disappears until I set it to ""Borderless"" instead of ""Fullscreen"" and even after it shows up, it goes to the bottom left corner as soon as a map starts. I have honestly 0 idea of whats going on at this point.", },
|
||||
new[]
|
||||
{
|
||||
"Problematic", @"My tablet doesn't work :(
|
||||
It's a Huion 420 and it's apparently incompatible with OpenTablet Driver. The warning I get is: ""DeviceInUseException: Device is currently in use by another kernel module. To fix this issue, please follow the instructions from https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ#arg umentoutofrangeexception-value-0-15"" and it repeats 4 times on the notification before logging subsequent warnings.
|
||||
Checking the logs, it looks for other Huion tablets before sending the notification (e.g.
|
||||
""2021-07-23 03:52:33 [verbose]: Detect: Searching for tablet 'Huion WH1409 V2'
|
||||
20 2021-07-23 03:52:33 [error]: DeviceInUseException: Device is currently in use by another kernel module. To fix this issue, please follow the instructions from https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ#arg umentoutofrangeexception-value-0-15"")
|
||||
I use an Arch based installation of Linux and the tablet runs perfectly with Digimend kernel driver, with area configuration, pen pressure, etc. On osu!lazer the cursor disappears until I set it to ""Borderless"" instead of ""Fullscreen"" and even after it shows up, it goes to the bottom left corner as soon as a map starts.
|
||||
I have honestly 0 idea of whats going on at this point."
|
||||
}
|
||||
"Code Block", @"User not found! ;_;
|
||||
|
||||
There are a few possible reasons for this:
|
||||
|
||||
They may have changed their username.
|
||||
The account may be temporarily unavailable due to security or abuse issues.
|
||||
You may have made a typo!"
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -137,6 +137,11 @@ namespace osu.Game.Tests.Visual.Online
|
||||
@"top_ranks",
|
||||
@"medals"
|
||||
},
|
||||
RankHighest = new APIUser.UserRankHighest
|
||||
{
|
||||
Rank = 1,
|
||||
UpdatedAt = DateTimeOffset.Now,
|
||||
},
|
||||
Statistics = new UserStatistics
|
||||
{
|
||||
IsRanked = true,
|
||||
|
@ -629,7 +629,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
var sets = new List<BeatmapSetInfo>();
|
||||
|
||||
const string zzz_string = "zzzzz";
|
||||
const string zzz_lowercase = "zzzzz";
|
||||
const string zzz_uppercase = "ZZZZZ";
|
||||
|
||||
AddStep("Populuate beatmap sets", () =>
|
||||
{
|
||||
@ -640,10 +641,16 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
var set = TestResources.CreateTestBeatmapSetInfo();
|
||||
|
||||
if (i == 4)
|
||||
set.Beatmaps.ForEach(b => b.Metadata.Artist = zzz_string);
|
||||
set.Beatmaps.ForEach(b => b.Metadata.Artist = zzz_uppercase);
|
||||
|
||||
if (i == 8)
|
||||
set.Beatmaps.ForEach(b => b.Metadata.Artist = zzz_lowercase);
|
||||
|
||||
if (i == 12)
|
||||
set.Beatmaps.ForEach(b => b.Metadata.Author.Username = zzz_uppercase);
|
||||
|
||||
if (i == 16)
|
||||
set.Beatmaps.ForEach(b => b.Metadata.Author.Username = zzz_string);
|
||||
set.Beatmaps.ForEach(b => b.Metadata.Author.Username = zzz_lowercase);
|
||||
|
||||
sets.Add(set);
|
||||
}
|
||||
@ -652,9 +659,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
loadBeatmaps(sets);
|
||||
|
||||
AddStep("Sort by author", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Author }, false));
|
||||
AddAssert($"Check {zzz_string} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Author.Username == zzz_string);
|
||||
AddAssert($"Check {zzz_uppercase} is last", () => carousel.BeatmapSets.Last().Metadata.Author.Username == zzz_uppercase);
|
||||
AddAssert($"Check {zzz_lowercase} is second last", () => carousel.BeatmapSets.SkipLast(1).Last().Metadata.Author.Username == zzz_lowercase);
|
||||
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
||||
AddAssert($"Check {zzz_string} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Artist == zzz_string);
|
||||
AddAssert($"Check {zzz_uppercase} is last", () => carousel.BeatmapSets.Last().Metadata.Artist == zzz_uppercase);
|
||||
AddAssert($"Check {zzz_lowercase} is second last", () => carousel.BeatmapSets.SkipLast(1).Last().Metadata.Artist == zzz_lowercase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System.Diagnostics;
|
||||
using ManagedBass.Fx;
|
||||
using osu.Framework.Audio.Mixing;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Graphics;
|
||||
|
||||
namespace osu.Game.Audio.Effects
|
||||
@ -22,6 +23,8 @@ namespace osu.Game.Audio.Effects
|
||||
|
||||
private bool isAttached;
|
||||
|
||||
private readonly Cached filterApplication = new Cached();
|
||||
|
||||
private int cutoff;
|
||||
|
||||
/// <summary>
|
||||
@ -36,7 +39,7 @@ namespace osu.Game.Audio.Effects
|
||||
return;
|
||||
|
||||
cutoff = value;
|
||||
updateFilter(cutoff);
|
||||
filterApplication.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,6 +64,17 @@ namespace osu.Game.Audio.Effects
|
||||
Cutoff = getInitialCutoff(type);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!filterApplication.IsValid)
|
||||
{
|
||||
updateFilter(cutoff);
|
||||
filterApplication.Validate();
|
||||
}
|
||||
}
|
||||
|
||||
private int getInitialCutoff(BQFType type)
|
||||
{
|
||||
switch (type)
|
||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
private readonly OffsetCorrectionClock? userGlobalOffsetClock;
|
||||
private readonly OffsetCorrectionClock? platformOffsetClock;
|
||||
private readonly OffsetCorrectionClock? userBeatmapOffsetClock;
|
||||
private readonly FramedOffsetClock? userBeatmapOffsetClock;
|
||||
|
||||
private readonly IFrameBasedClock finalClockSource;
|
||||
|
||||
@ -70,7 +70,7 @@ namespace osu.Game.Beatmaps
|
||||
userGlobalOffsetClock = new OffsetCorrectionClock(platformOffsetClock);
|
||||
|
||||
// User per-beatmap offset will be applied to this final clock.
|
||||
finalClockSource = userBeatmapOffsetClock = new OffsetCorrectionClock(userGlobalOffsetClock);
|
||||
finalClockSource = userBeatmapOffsetClock = new FramedOffsetClock(userGlobalOffsetClock);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -122,7 +122,7 @@ namespace osu.Game.Beatmaps
|
||||
Debug.Assert(userBeatmapOffsetClock != null);
|
||||
Debug.Assert(platformOffsetClock != null);
|
||||
|
||||
return userGlobalOffsetClock.RateAdjustedOffset + userBeatmapOffsetClock.RateAdjustedOffset + platformOffsetClock.RateAdjustedOffset;
|
||||
return userGlobalOffsetClock.RateAdjustedOffset + userBeatmapOffsetClock.Offset + platformOffsetClock.RateAdjustedOffset;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics.Rendering;
|
||||
using osu.Framework.Graphics.Rendering.Dummy;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
@ -143,8 +144,6 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
string fileStorePath = BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path);
|
||||
|
||||
// TODO: check validity of file
|
||||
|
||||
var stream = GetStream(fileStorePath);
|
||||
|
||||
if (stream == null)
|
||||
@ -153,6 +152,12 @@ namespace osu.Game.Beatmaps
|
||||
return null;
|
||||
}
|
||||
|
||||
if (stream.ComputeMD5Hash() != BeatmapInfo.MD5Hash)
|
||||
{
|
||||
Logger.Log($"Beatmap failed to load (file {BeatmapInfo.Path} does not have the expected hash).", level: LogLevel.Error);
|
||||
return null;
|
||||
}
|
||||
|
||||
using (var reader = new LineBufferedReader(stream))
|
||||
return Decoder.GetDecoder<Beatmap>(reader).Decode(reader);
|
||||
}
|
||||
|
@ -77,12 +77,19 @@ namespace osu.Game.Configuration
|
||||
|
||||
SetDefault(OsuSetting.SavePassword, false).ValueChanged += enabled =>
|
||||
{
|
||||
if (enabled.NewValue) SetValue(OsuSetting.SaveUsername, true);
|
||||
if (enabled.NewValue)
|
||||
SetValue(OsuSetting.SaveUsername, true);
|
||||
else
|
||||
GetBindable<string>(OsuSetting.Token).SetDefault();
|
||||
};
|
||||
|
||||
SetDefault(OsuSetting.SaveUsername, true).ValueChanged += enabled =>
|
||||
{
|
||||
if (!enabled.NewValue) SetValue(OsuSetting.SavePassword, false);
|
||||
if (!enabled.NewValue)
|
||||
{
|
||||
GetBindable<string>(OsuSetting.Username).SetDefault();
|
||||
SetValue(OsuSetting.SavePassword, false);
|
||||
}
|
||||
};
|
||||
|
||||
SetDefault(OsuSetting.ExternalLinkWarning, true);
|
||||
|
@ -10,11 +10,11 @@ using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Graphics.Containers.Markdown
|
||||
{
|
||||
public partial class OsuMarkdownFencedCodeBlock : MarkdownFencedCodeBlock
|
||||
public partial class OsuMarkdownCodeBlock : MarkdownCodeBlock
|
||||
{
|
||||
// TODO : change to monospace font for this component
|
||||
public OsuMarkdownFencedCodeBlock(FencedCodeBlock fencedCodeBlock)
|
||||
: base(fencedCodeBlock)
|
||||
public OsuMarkdownCodeBlock(CodeBlock codeBlock)
|
||||
: base(codeBlock)
|
||||
{
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ namespace osu.Game.Graphics.Containers.Markdown
|
||||
|
||||
protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new OsuMarkdownHeading(headingBlock);
|
||||
|
||||
protected override MarkdownFencedCodeBlock CreateFencedCodeBlock(FencedCodeBlock fencedCodeBlock) => new OsuMarkdownFencedCodeBlock(fencedCodeBlock);
|
||||
protected override MarkdownCodeBlock CreateCodeBlock(CodeBlock codeBlock) => new OsuMarkdownCodeBlock(codeBlock);
|
||||
|
||||
protected override MarkdownSeparator CreateSeparator(ThematicBreakBlock thematicBlock) => new OsuMarkdownSeparator();
|
||||
|
||||
|
49
osu.Game/Localisation/StorageErrorDialogStrings.cs
Normal file
49
osu.Game/Localisation/StorageErrorDialogStrings.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 StorageErrorDialogStrings
|
||||
{
|
||||
private const string prefix = @"osu.Game.Resources.Localisation.StorageErrorDialog";
|
||||
|
||||
/// <summary>
|
||||
/// "osu! storage error"
|
||||
/// </summary>
|
||||
public static LocalisableString StorageError => new TranslatableString(getKey(@"storage_error"), @"osu! storage error");
|
||||
|
||||
/// <summary>
|
||||
/// "The specified osu! data location ("{0}") is not accessible. If it is on external storage, please reconnect the device and try again."
|
||||
/// </summary>
|
||||
public static LocalisableString LocationIsNotAccessible(string? loc) => new TranslatableString(getKey(@"location_is_not_accessible"), @"The specified osu! data location (""{0}"") is not accessible. If it is on external storage, please reconnect the device and try again.", loc);
|
||||
|
||||
/// <summary>
|
||||
/// "The specified osu! data location ("{0}") is empty. If you have moved the files, please close osu! and move them back."
|
||||
/// </summary>
|
||||
public static LocalisableString LocationIsEmpty(string? loc2) => new TranslatableString(getKey(@"location_is_empty"), @"The specified osu! data location (""{0}"") is empty. If you have moved the files, please close osu! and move them back.", loc2);
|
||||
|
||||
/// <summary>
|
||||
/// "Try again"
|
||||
/// </summary>
|
||||
public static LocalisableString TryAgain => new TranslatableString(getKey(@"try_again"), @"Try again");
|
||||
|
||||
/// <summary>
|
||||
/// "Use default location until restart"
|
||||
/// </summary>
|
||||
public static LocalisableString UseDefaultLocation => new TranslatableString(getKey(@"use_default_location"), @"Use default location until restart");
|
||||
|
||||
/// <summary>
|
||||
/// "Reset to default location"
|
||||
/// </summary>
|
||||
public static LocalisableString ResetToDefaultLocation => new TranslatableString(getKey(@"reset_to_default_location"), @"Reset to default location");
|
||||
|
||||
/// <summary>
|
||||
/// "Start fresh at specified location"
|
||||
/// </summary>
|
||||
public static LocalisableString StartFresh => new TranslatableString(getKey(@"start_fresh"), @"Start fresh at specified location");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
@ -34,6 +34,19 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
[JsonProperty(@"previous_usernames")]
|
||||
public string[] PreviousUsernames;
|
||||
|
||||
[JsonProperty(@"rank_highest")]
|
||||
[CanBeNull]
|
||||
public UserRankHighest RankHighest;
|
||||
|
||||
public class UserRankHighest
|
||||
{
|
||||
[JsonProperty(@"rank")]
|
||||
public int Rank;
|
||||
|
||||
[JsonProperty(@"updated_at")]
|
||||
public DateTimeOffset UpdatedAt;
|
||||
}
|
||||
|
||||
[JsonProperty(@"country_code")]
|
||||
private string countryCodeString;
|
||||
|
||||
|
@ -66,41 +66,37 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
Colour = colourProvider.Background4,
|
||||
Alpha = 0f,
|
||||
},
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Left = 18, Right = 10 },
|
||||
Child = new GridContainer
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable?[]
|
||||
{
|
||||
createIcon(),
|
||||
text = new TruncatingSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Text = Channel.Name,
|
||||
Font = OsuFont.Torus.With(size: 17, weight: FontWeight.SemiBold),
|
||||
Colour = colourProvider.Light3,
|
||||
Margin = new MarginPadding { Bottom = 2 },
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
createMentionPill(),
|
||||
close = createCloseButton(),
|
||||
}
|
||||
},
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable?[]
|
||||
{
|
||||
createIcon(),
|
||||
text = new TruncatingSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Text = Channel.Name,
|
||||
Font = OsuFont.Torus.With(size: 17, weight: FontWeight.SemiBold),
|
||||
Colour = colourProvider.Light3,
|
||||
Margin = new MarginPadding { Bottom = 2 },
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
createMentionPill(),
|
||||
close = createCloseButton(),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Action = () => OnRequestSelect?.Invoke(Channel);
|
||||
|
@ -143,6 +143,13 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0;
|
||||
|
||||
detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
|
||||
|
||||
var rankHighest = user?.RankHighest;
|
||||
|
||||
detailGlobalRank.ContentTooltipText = rankHighest != null
|
||||
? UsersStrings.ShowRankHighest(rankHighest.Rank.ToLocalisableString("\\##,##0"), rankHighest.UpdatedAt.ToLocalisableString(@"d MMM yyyy"))
|
||||
: string.Empty;
|
||||
|
||||
detailCountryRank.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
|
||||
|
||||
rankGraph.Statistics.Value = user?.Statistics;
|
||||
|
@ -4,6 +4,7 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@ -13,7 +14,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
public partial class ProfileValueDisplay : CompositeDrawable
|
||||
{
|
||||
private readonly OsuSpriteText title;
|
||||
private readonly OsuSpriteText content;
|
||||
private readonly ContentText content;
|
||||
|
||||
public LocalisableString Title
|
||||
{
|
||||
@ -25,6 +26,11 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
set => content.Text = value;
|
||||
}
|
||||
|
||||
public LocalisableString ContentTooltipText
|
||||
{
|
||||
set => content.TooltipText = value;
|
||||
}
|
||||
|
||||
public ProfileValueDisplay(bool big = false, int minimumWidth = 60)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
@ -38,9 +44,9 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 12)
|
||||
},
|
||||
content = new OsuSpriteText
|
||||
content = new ContentText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: big ? 30 : 20, weight: FontWeight.Light)
|
||||
Font = OsuFont.GetFont(size: big ? 30 : 20, weight: FontWeight.Light),
|
||||
},
|
||||
new Container // Add a minimum size to the FillFlowContainer
|
||||
{
|
||||
@ -56,5 +62,10 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
title.Colour = colourProvider.Content1;
|
||||
content.Colour = colourProvider.Content2;
|
||||
}
|
||||
|
||||
private partial class ContentText : OsuSpriteText, IHasTooltip
|
||||
{
|
||||
public LocalisableString TooltipText { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,25 +5,19 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
|
||||
namespace osu.Game.Overlays.Profile.Header.Components
|
||||
{
|
||||
public partial class TotalPlayTime : CompositeDrawable, IHasTooltip
|
||||
public partial class TotalPlayTime : CompositeDrawable
|
||||
{
|
||||
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>();
|
||||
|
||||
public LocalisableString TooltipText { get; set; }
|
||||
|
||||
private ProfileValueDisplay info = null!;
|
||||
|
||||
public TotalPlayTime()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
TooltipText = "0 hours";
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -32,6 +26,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
InternalChild = info = new ProfileValueDisplay(minimumWidth: 140)
|
||||
{
|
||||
Title = UsersStrings.ShowStatsPlayTime,
|
||||
ContentTooltipText = "0 hours",
|
||||
};
|
||||
|
||||
User.BindValueChanged(updateTime, true);
|
||||
@ -40,7 +35,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
private void updateTime(ValueChangedEvent<UserProfileData?> user)
|
||||
{
|
||||
int? playTime = user.NewValue?.User.Statistics?.PlayTime;
|
||||
TooltipText = (playTime ?? 0) / 3600 + " hours";
|
||||
info.ContentTooltipText = (playTime ?? 0) / 3600 + " hours";
|
||||
info.Content = formatTime(playTime);
|
||||
}
|
||||
|
||||
|
@ -26,47 +26,42 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING, Vertical = 10 },
|
||||
Child = new GridContainer
|
||||
RowDimensions = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RowDimensions = new[]
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
new MainDetails
|
||||
{
|
||||
new MainDetails
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
User = { BindTarget = User }
|
||||
},
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = 2,
|
||||
Colour = colourProvider.Background6,
|
||||
Margin = new MarginPadding { Horizontal = 15 }
|
||||
},
|
||||
new ExtendedDetails
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
User = { BindTarget = User }
|
||||
}
|
||||
RelativeSizeAxes = Axes.X,
|
||||
User = { BindTarget = User }
|
||||
},
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = 2,
|
||||
Colour = colourProvider.Background6,
|
||||
Margin = new MarginPadding { Horizontal = 15 }
|
||||
},
|
||||
new ExtendedDetails
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
User = { BindTarget = User }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions;
|
||||
@ -28,15 +27,16 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
var renderer = config.GetBindable<RendererType>(FrameworkSetting.Renderer);
|
||||
automaticRendererInUse = renderer.Value == RendererType.Automatic;
|
||||
|
||||
SettingsEnumDropdown<RendererType> rendererDropdown;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
rendererDropdown = new RendererSettingsDropdown
|
||||
new RendererSettingsDropdown
|
||||
{
|
||||
LabelText = GraphicsSettingsStrings.Renderer,
|
||||
Current = renderer,
|
||||
Items = host.GetPreferredRenderersForCurrentPlatform().Order().Where(t => t != RendererType.Vulkan),
|
||||
Items = host.GetPreferredRenderersForCurrentPlatform().Order()
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
.Where(t => t != RendererType.Vulkan && t != RendererType.OpenGLLegacy),
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
Keywords = new[] { @"compatibility", @"directx" },
|
||||
},
|
||||
// TODO: this needs to be a custom dropdown at some point
|
||||
@ -79,13 +79,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: remove this once we support SDL+android.
|
||||
if (RuntimeInfo.OS == RuntimeInfo.Platform.Android)
|
||||
{
|
||||
rendererDropdown.Items = new[] { RendererType.Automatic, RendererType.OpenGLLegacy };
|
||||
rendererDropdown.SetNoticeText("New renderer support for android is coming soon!", true);
|
||||
}
|
||||
}
|
||||
|
||||
private partial class RendererSettingsDropdown : SettingsEnumDropdown<RendererType>
|
||||
|
@ -10,9 +10,11 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Containers;
|
||||
@ -66,6 +68,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
||||
|
||||
private OsuScreen? lastTargetScreen;
|
||||
private InvokeOnDisposal? nestedInputManagerDisable;
|
||||
|
||||
private Vector2 lastDrawSize;
|
||||
|
||||
@ -105,6 +108,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
|
||||
if (skinEditor != null)
|
||||
{
|
||||
disableNestedInputManagers();
|
||||
skinEditor.Show();
|
||||
return;
|
||||
}
|
||||
@ -132,6 +136,8 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
skinEditor?.Save(false);
|
||||
skinEditor?.Hide();
|
||||
nestedInputManagerDisable?.Dispose();
|
||||
nestedInputManagerDisable = null;
|
||||
|
||||
globallyReenableBeatmapSkinSetting();
|
||||
}
|
||||
@ -243,6 +249,9 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
/// </summary>
|
||||
public void SetTarget(OsuScreen screen)
|
||||
{
|
||||
nestedInputManagerDisable?.Dispose();
|
||||
nestedInputManagerDisable = null;
|
||||
|
||||
lastTargetScreen = screen;
|
||||
|
||||
if (skinEditor == null) return;
|
||||
@ -271,6 +280,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
skinEditor.Save(false);
|
||||
skinEditor.UpdateTargetScreen(target);
|
||||
disableNestedInputManagers();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -280,6 +290,21 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
}
|
||||
}
|
||||
|
||||
private void disableNestedInputManagers()
|
||||
{
|
||||
if (lastTargetScreen == null)
|
||||
return;
|
||||
|
||||
var nestedInputManagers = lastTargetScreen.ChildrenOfType<PassThroughInputManager>().Where(manager => manager.UseParentInput).ToArray();
|
||||
foreach (var inputManager in nestedInputManagers)
|
||||
inputManager.UseParentInput = false;
|
||||
nestedInputManagerDisable = new InvokeOnDisposal(() =>
|
||||
{
|
||||
foreach (var inputManager in nestedInputManagers)
|
||||
inputManager.UseParentInput = true;
|
||||
});
|
||||
}
|
||||
|
||||
private readonly Bindable<bool> beatmapSkins = new Bindable<bool>();
|
||||
private LeasedBindable<bool>? leasedBeatmapSkins;
|
||||
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
@ -124,12 +123,34 @@ namespace osu.Game.Rulesets.Edit
|
||||
|
||||
private (HitObject before, HitObject after)? getObjectsOnEitherSideOfCurrentTime()
|
||||
{
|
||||
HitObject? lastBefore = playfield.HitObjectContainer.AliveObjects.LastOrDefault(h => h.HitObject.StartTime < editorClock.CurrentTime)?.HitObject;
|
||||
HitObject? lastBefore = null;
|
||||
|
||||
foreach (var entry in playfield.HitObjectContainer.AliveEntries)
|
||||
{
|
||||
double objTime = entry.Value.HitObject.StartTime;
|
||||
|
||||
if (objTime >= editorClock.CurrentTime)
|
||||
continue;
|
||||
|
||||
if (objTime > lastBefore?.StartTime)
|
||||
lastBefore = entry.Value.HitObject;
|
||||
}
|
||||
|
||||
if (lastBefore == null)
|
||||
return null;
|
||||
|
||||
HitObject? firstAfter = playfield.HitObjectContainer.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime >= editorClock.CurrentTime)?.HitObject;
|
||||
HitObject? firstAfter = null;
|
||||
|
||||
foreach (var entry in playfield.HitObjectContainer.AliveEntries)
|
||||
{
|
||||
double objTime = entry.Value.HitObject.StartTime;
|
||||
|
||||
if (objTime < editorClock.CurrentTime)
|
||||
continue;
|
||||
|
||||
if (objTime < firstAfter?.StartTime)
|
||||
firstAfter = entry.Value.HitObject;
|
||||
}
|
||||
|
||||
if (firstAfter == null)
|
||||
return null;
|
||||
|
@ -47,35 +47,31 @@ namespace osu.Game.Screens.Edit
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background4,
|
||||
},
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new GridContainer
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.Absolute, 170),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 220),
|
||||
new Dimension(GridSizeMode.Absolute, HitObjectComposer.TOOLBOX_CONTRACTED_SIZE_RIGHT),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new TimeInfoContainer { RelativeSizeAxes = Axes.Both },
|
||||
new SummaryTimeline { RelativeSizeAxes = Axes.Both },
|
||||
new PlaybackControl { RelativeSizeAxes = Axes.Both },
|
||||
TestGameplayButton = new TestGameplayButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(1),
|
||||
Action = editor.TestGameplay,
|
||||
}
|
||||
},
|
||||
}
|
||||
new Dimension(GridSizeMode.Absolute, 170),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 220),
|
||||
new Dimension(GridSizeMode.Absolute, HitObjectComposer.TOOLBOX_CONTRACTED_SIZE_RIGHT),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new TimeInfoContainer { RelativeSizeAxes = Axes.Both },
|
||||
new SummaryTimeline { RelativeSizeAxes = Axes.Both },
|
||||
new PlaybackControl { RelativeSizeAxes = Axes.Both },
|
||||
TestGameplayButton = new TestGameplayButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(1),
|
||||
Action = editor.TestGameplay,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -97,11 +97,14 @@ namespace osu.Game.Screens.Edit.Components
|
||||
editorClock.Start();
|
||||
}
|
||||
|
||||
private static readonly IconUsage play_icon = FontAwesome.Regular.PlayCircle;
|
||||
private static readonly IconUsage pause_icon = FontAwesome.Regular.PauseCircle;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
playButton.Icon = editorClock.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle;
|
||||
playButton.Icon = editorClock.IsRunning ? pause_icon : play_icon;
|
||||
}
|
||||
|
||||
private partial class PlaybackTabControl : OsuTabControl<double>
|
||||
|
@ -47,11 +47,26 @@ namespace osu.Game.Screens.Edit.Components
|
||||
};
|
||||
}
|
||||
|
||||
private double? lastTime;
|
||||
private double? lastBPM;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
trackTimer.Text = editorClock.CurrentTime.ToEditorFormattedString();
|
||||
bpm.Text = @$"{editorBeatmap.ControlPointInfo.TimingPointAt(editorClock.CurrentTime).BPM:0} BPM";
|
||||
|
||||
if (lastTime != editorClock.CurrentTime)
|
||||
{
|
||||
lastTime = editorClock.CurrentTime;
|
||||
trackTimer.Text = editorClock.CurrentTime.ToEditorFormattedString();
|
||||
}
|
||||
|
||||
double newBPM = editorBeatmap.ControlPointInfo.TimingPointAt(editorClock.CurrentTime).BPM;
|
||||
|
||||
if (lastBPM != newBPM)
|
||||
{
|
||||
lastBPM = newBPM;
|
||||
bpm.Text = @$"{newBPM:0} BPM";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,35 +86,31 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background3
|
||||
},
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new GridContainer
|
||||
Content = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
new Drawable[]
|
||||
{
|
||||
new Drawable[]
|
||||
new ChevronButton
|
||||
{
|
||||
new ChevronButton
|
||||
{
|
||||
Icon = FontAwesome.Solid.ChevronLeft,
|
||||
Action = beatDivisor.SelectPrevious
|
||||
},
|
||||
new DivisorDisplay { BeatDivisor = { BindTarget = beatDivisor } },
|
||||
new ChevronButton
|
||||
{
|
||||
Icon = FontAwesome.Solid.ChevronRight,
|
||||
Action = beatDivisor.SelectNext
|
||||
}
|
||||
Icon = FontAwesome.Solid.ChevronLeft,
|
||||
Action = beatDivisor.SelectPrevious
|
||||
},
|
||||
new DivisorDisplay { BeatDivisor = { BindTarget = beatDivisor } },
|
||||
new ChevronButton
|
||||
{
|
||||
Icon = FontAwesome.Solid.ChevronRight,
|
||||
Action = beatDivisor.SelectNext
|
||||
}
|
||||
},
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.Absolute, 20),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 20)
|
||||
}
|
||||
},
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.Absolute, 20),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 20)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -122,42 +118,31 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
Content = new[]
|
||||
{
|
||||
new Container
|
||||
new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new GridContainer
|
||||
new ChevronButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new ChevronButton
|
||||
{
|
||||
Icon = FontAwesome.Solid.ChevronLeft,
|
||||
Action = () => cycleDivisorType(-1)
|
||||
},
|
||||
new DivisorTypeText { BeatDivisor = { BindTarget = beatDivisor } },
|
||||
new ChevronButton
|
||||
{
|
||||
Icon = FontAwesome.Solid.ChevronRight,
|
||||
Action = () => cycleDivisorType(1)
|
||||
}
|
||||
},
|
||||
},
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.Absolute, 20),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 20)
|
||||
}
|
||||
Icon = FontAwesome.Solid.ChevronLeft,
|
||||
Action = () => cycleDivisorType(-1)
|
||||
},
|
||||
new DivisorTypeText { BeatDivisor = { BindTarget = beatDivisor } },
|
||||
new ChevronButton
|
||||
{
|
||||
Icon = FontAwesome.Solid.ChevronRight,
|
||||
Action = () => cycleDivisorType(1)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.Absolute, 20),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 20)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -116,6 +116,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
updateStacking();
|
||||
}
|
||||
|
||||
private readonly Stack<HitObject> currentConcurrentObjects = new Stack<HitObject>();
|
||||
|
||||
private void updateStacking()
|
||||
{
|
||||
// because only blueprints of objects which are alive (via pooling) are displayed in the timeline, it's feasible to do this every-update.
|
||||
@ -125,10 +127,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
// after the stack gets this tall, we can presume there is space underneath to draw subsequent blueprints.
|
||||
const int stack_reset_count = 3;
|
||||
|
||||
Stack<HitObject> currentConcurrentObjects = new Stack<HitObject>();
|
||||
currentConcurrentObjects.Clear();
|
||||
|
||||
foreach (var b in SelectionBlueprints.Reverse())
|
||||
for (int i = SelectionBlueprints.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var b = SelectionBlueprints[i];
|
||||
|
||||
// remove objects from the stack as long as their end time is in the past.
|
||||
while (currentConcurrentObjects.TryPeek(out HitObject hitObject))
|
||||
{
|
||||
|
@ -57,37 +57,32 @@ namespace osu.Game.Screens.Edit
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background4
|
||||
},
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
Name = "Timeline content",
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Horizontal = PADDING, Top = PADDING },
|
||||
Child = new GridContainer
|
||||
Content = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Content = new[]
|
||||
new Drawable[]
|
||||
{
|
||||
new Drawable[]
|
||||
TimelineContent = new Container
|
||||
{
|
||||
TimelineContent = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
},
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
},
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 90),
|
||||
}
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 90),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -65,35 +65,28 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(padding),
|
||||
Children = new Drawable[]
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new GridContainer
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension()
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
metronome = new MetronomeDisplay
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension()
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
metronome = new MetronomeDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
new WaveformComparisonDisplay()
|
||||
}
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
new WaveformComparisonDisplay()
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
|
@ -34,25 +34,21 @@ namespace osu.Game.Screens.Edit.Verify
|
||||
InterpretedDifficulty.Default = StarDifficulty.GetDifficultyRating(EditorBeatmap.BeatmapInfo.StarRating);
|
||||
InterpretedDifficulty.SetDefault();
|
||||
|
||||
Child = new Container
|
||||
Child = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new GridContainer
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 250),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 250),
|
||||
IssueList = new IssueList(),
|
||||
new IssueSettings(),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
IssueList = new IssueList(),
|
||||
new IssueSettings(),
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
|
||||
@ -17,7 +18,7 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
public StorageErrorDialog(OsuStorage storage, OsuStorageError error)
|
||||
{
|
||||
HeaderText = "osu! storage error";
|
||||
HeaderText = StorageErrorDialogStrings.StorageError;
|
||||
Icon = FontAwesome.Solid.ExclamationTriangle;
|
||||
|
||||
var buttons = new List<PopupDialogButton>();
|
||||
@ -25,13 +26,13 @@ namespace osu.Game.Screens.Menu
|
||||
switch (error)
|
||||
{
|
||||
case OsuStorageError.NotAccessible:
|
||||
BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is not accessible. If it is on external storage, please reconnect the device and try again.";
|
||||
BodyText = StorageErrorDialogStrings.LocationIsNotAccessible(storage.CustomStoragePath);
|
||||
|
||||
buttons.AddRange(new PopupDialogButton[]
|
||||
{
|
||||
new PopupDialogCancelButton
|
||||
{
|
||||
Text = "Try again",
|
||||
Text = StorageErrorDialogStrings.TryAgain,
|
||||
Action = () =>
|
||||
{
|
||||
if (!storage.TryChangeToCustomStorage(out var nextError))
|
||||
@ -40,29 +41,29 @@ namespace osu.Game.Screens.Menu
|
||||
},
|
||||
new PopupDialogCancelButton
|
||||
{
|
||||
Text = "Use default location until restart",
|
||||
Text = StorageErrorDialogStrings.UseDefaultLocation,
|
||||
},
|
||||
new PopupDialogOkButton
|
||||
{
|
||||
Text = "Reset to default location",
|
||||
Text = StorageErrorDialogStrings.ResetToDefaultLocation,
|
||||
Action = storage.ResetCustomStoragePath
|
||||
},
|
||||
});
|
||||
break;
|
||||
|
||||
case OsuStorageError.AccessibleButEmpty:
|
||||
BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is empty. If you have moved the files, please close osu! and move them back.";
|
||||
BodyText = StorageErrorDialogStrings.LocationIsEmpty(storage.CustomStoragePath);
|
||||
|
||||
// Todo: Provide the option to search for the files similar to migration.
|
||||
buttons.AddRange(new PopupDialogButton[]
|
||||
{
|
||||
new PopupDialogCancelButton
|
||||
{
|
||||
Text = "Start fresh at specified location"
|
||||
Text = StorageErrorDialogStrings.StartFresh
|
||||
},
|
||||
new PopupDialogOkButton
|
||||
{
|
||||
Text = "Reset to default location",
|
||||
Text = StorageErrorDialogStrings.ResetToDefaultLocation,
|
||||
Action = storage.ResetCustomStoragePath
|
||||
},
|
||||
});
|
||||
|
@ -26,48 +26,44 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
[Resolved(typeof(Room))]
|
||||
protected BindableList<PlaylistItem> Playlist { get; private set; }
|
||||
|
||||
private readonly Drawable playlistArea;
|
||||
private readonly GridContainer playlistArea;
|
||||
private readonly DrawableRoomPlaylist playlist;
|
||||
|
||||
public MatchBeatmapDetailArea()
|
||||
{
|
||||
Add(playlistArea = new Container
|
||||
Add(playlistArea = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Vertical = 10 },
|
||||
Child = new GridContainer
|
||||
Content = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
new Drawable[]
|
||||
{
|
||||
new Drawable[]
|
||||
new Container
|
||||
{
|
||||
new Container
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Bottom = 10 },
|
||||
Child = playlist = new PlaylistsRoomSettingsPlaylist
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Bottom = 10 },
|
||||
Child = playlist = new PlaylistsRoomSettingsPlaylist
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new RoundedButton
|
||||
{
|
||||
Text = "Add new playlist entry",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = Vector2.One,
|
||||
Action = () => CreateNewItem?.Invoke()
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
RowDimensions = new[]
|
||||
new Drawable[]
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 50),
|
||||
}
|
||||
new RoundedButton
|
||||
{
|
||||
Text = "Add new playlist entry",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = Vector2.One,
|
||||
Action = () => CreateNewItem?.Invoke()
|
||||
}
|
||||
},
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 50),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -40,35 +40,31 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
Colour = Color4.Black,
|
||||
Alpha = 0.5f
|
||||
},
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Horizontal = padding },
|
||||
Child = new GridContainer
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
new Dimension(GridSizeMode.AutoSize, minSize: 80 - 2 * padding)
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize, minSize: 80 - 2 * padding)
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new[]
|
||||
new Container
|
||||
{
|
||||
new Container
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Padding = new MarginPadding { Bottom = 2 },
|
||||
Child = content = new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Padding = new MarginPadding { Bottom = 2 },
|
||||
Child = content = new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -95,38 +95,34 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
new Drawable[]
|
||||
{
|
||||
// Playlist items column
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Right = 5 },
|
||||
Child = new GridContainer
|
||||
Content = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
new Drawable[] { new OverlinedPlaylistHeader(), },
|
||||
new Drawable[]
|
||||
{
|
||||
new Drawable[] { new OverlinedPlaylistHeader(), },
|
||||
new Drawable[]
|
||||
new DrawableRoomPlaylist
|
||||
{
|
||||
new DrawableRoomPlaylist
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Items = { BindTarget = Room.Playlist },
|
||||
SelectedItem = { BindTarget = SelectedItem },
|
||||
AllowSelection = true,
|
||||
AllowShowingResults = true,
|
||||
RequestResults = item =>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Items = { BindTarget = Room.Playlist },
|
||||
SelectedItem = { BindTarget = SelectedItem },
|
||||
AllowSelection = true,
|
||||
AllowShowingResults = true,
|
||||
RequestResults = item =>
|
||||
{
|
||||
Debug.Assert(RoomId.Value != null);
|
||||
ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false));
|
||||
}
|
||||
Debug.Assert(RoomId.Value != null);
|
||||
ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false));
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
}
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
}
|
||||
},
|
||||
// Spacer
|
||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Screens.Play
|
||||
Scores = { BindTarget = LeaderboardScores }
|
||||
};
|
||||
|
||||
protected override bool HandleTokenRetrievalFailure(Exception exception) => false;
|
||||
protected override bool ShouldExitOnTokenRetrievalFailure(Exception exception) => false;
|
||||
|
||||
protected override Task ImportScore(Score score)
|
||||
{
|
||||
|
@ -118,7 +118,7 @@ namespace osu.Game.Screens.Play
|
||||
token = r.ID;
|
||||
tcs.SetResult(true);
|
||||
};
|
||||
req.Failure += handleTokenFailure;
|
||||
req.Failure += ex => handleTokenFailure(ex, displayNotification: true);
|
||||
|
||||
api.Queue(req);
|
||||
|
||||
@ -128,40 +128,49 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
return true;
|
||||
|
||||
void handleTokenFailure(Exception exception)
|
||||
void handleTokenFailure(Exception exception, bool displayNotification = false)
|
||||
{
|
||||
tcs.SetResult(false);
|
||||
|
||||
if (HandleTokenRetrievalFailure(exception))
|
||||
bool shouldExit = ShouldExitOnTokenRetrievalFailure(exception);
|
||||
|
||||
if (displayNotification || shouldExit)
|
||||
{
|
||||
string whatWillHappen = shouldExit
|
||||
? "Play in this state is not permitted."
|
||||
: "Your score will not be submitted.";
|
||||
|
||||
if (string.IsNullOrEmpty(exception.Message))
|
||||
Logger.Error(exception, "Failed to retrieve a score submission token.");
|
||||
Logger.Error(exception, $"Failed to retrieve a score submission token.\n\n{whatWillHappen}");
|
||||
else
|
||||
{
|
||||
switch (exception.Message)
|
||||
{
|
||||
case "expired token":
|
||||
Logger.Log("Score submission failed because your system clock is set incorrectly. Please check your system time, date and timezone.", level: LogLevel.Important);
|
||||
case @"missing token header":
|
||||
case @"invalid client hash":
|
||||
case @"invalid verification hash":
|
||||
Logger.Log($"Please ensure that you are using the latest version of the official game releases.\n\n{whatWillHappen}", level: LogLevel.Important);
|
||||
break;
|
||||
|
||||
case @"expired token":
|
||||
Logger.Log($"Your system clock is set incorrectly. Please check your system time, date and timezone.\n\n{whatWillHappen}", level: LogLevel.Important);
|
||||
break;
|
||||
|
||||
default:
|
||||
Logger.Log($"You are not able to submit a score: {exception.Message}", level: LogLevel.Important);
|
||||
Logger.Log($"{whatWillHappen} {exception.Message}", level: LogLevel.Important);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldExit)
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
ValidForResume = false;
|
||||
this.Exit();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Gameplay is allowed to continue, but we still should keep track of the error.
|
||||
// In the future, this should be visible to the user in some way.
|
||||
Logger.Log($"Score submission token retrieval failed ({exception.Message})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,7 +179,7 @@ namespace osu.Game.Screens.Play
|
||||
/// </summary>
|
||||
/// <param name="exception">The error causing the failure.</param>
|
||||
/// <returns>Whether gameplay should be immediately exited as a result. Returning false allows the gameplay session to continue. Defaults to true.</returns>
|
||||
protected virtual bool HandleTokenRetrievalFailure(Exception exception) => true;
|
||||
protected virtual bool ShouldExitOnTokenRetrievalFailure(Exception exception) => true;
|
||||
|
||||
protected override async Task PrepareScoreForResultsAsync(Score score)
|
||||
{
|
||||
@ -231,7 +240,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
/// <summary>
|
||||
/// Construct a request to be used for retrieval of the score token.
|
||||
/// Can return null, at which point <see cref="HandleTokenRetrievalFailure"/> will be fired.
|
||||
/// Can return null, at which point <see cref="ShouldExitOnTokenRetrievalFailure"/> will be fired.
|
||||
/// </summary>
|
||||
[CanBeNull]
|
||||
protected abstract APIRequest<APIScoreToken> CreateTokenRequest();
|
||||
|
@ -150,44 +150,40 @@ namespace osu.Game.Screens.Ranking.Contracted
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Vertical = 5 },
|
||||
Child = new GridContainer
|
||||
Content = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
new Drawable[]
|
||||
{
|
||||
new Drawable[]
|
||||
new OsuSpriteText
|
||||
{
|
||||
new OsuSpriteText
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Current = scoreManager.GetBindableTotalScoreString(score),
|
||||
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, fixedWidth: true),
|
||||
Spacing = new Vector2(-1, 0)
|
||||
},
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Top = 2 },
|
||||
Child = new DrawableRank(score.Rank)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Current = scoreManager.GetBindableTotalScoreString(score),
|
||||
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, fixedWidth: true),
|
||||
Spacing = new Vector2(-1, 0)
|
||||
},
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Top = 2 },
|
||||
Child = new DrawableRank(score.Rank)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
}
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -75,99 +75,92 @@ namespace osu.Game.Screens.Select
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Colour4.Black.Opacity(0.3f),
|
||||
},
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Horizontal = spacing },
|
||||
Children = new Drawable[]
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new GridContainer
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension()
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RowDimensions = new[]
|
||||
new FillFlowContainer
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension()
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new Drawable[]
|
||||
Width = 0.5f,
|
||||
Spacing = new Vector2(spacing),
|
||||
Padding = new MarginPadding { Right = spacing / 2 },
|
||||
Children = new[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
new DetailBox().WithChild(new OnlineViewContainer(string.Empty)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Width = 0.5f,
|
||||
Spacing = new Vector2(spacing),
|
||||
Padding = new MarginPadding { Right = spacing / 2 },
|
||||
Children = new[]
|
||||
Height = 134,
|
||||
Padding = new MarginPadding { Horizontal = spacing, Top = spacing },
|
||||
Child = ratingsDisplay = new UserRatings
|
||||
{
|
||||
new DetailBox().WithChild(new OnlineViewContainer(string.Empty)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 134,
|
||||
Padding = new MarginPadding { Horizontal = spacing, Top = spacing },
|
||||
Child = ratingsDisplay = new UserRatings
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
}),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
},
|
||||
new OsuScrollContainer
|
||||
}),
|
||||
},
|
||||
},
|
||||
new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 250,
|
||||
Width = 0.5f,
|
||||
ScrollbarVisible = false,
|
||||
Padding = new MarginPadding { Left = spacing / 2 },
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
LayoutDuration = transition_duration,
|
||||
LayoutEasing = Easing.OutQuad,
|
||||
Children = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 250,
|
||||
Width = 0.5f,
|
||||
ScrollbarVisible = false,
|
||||
Padding = new MarginPadding { Left = spacing / 2 },
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
LayoutDuration = transition_duration,
|
||||
LayoutEasing = Easing.OutQuad,
|
||||
Children = new[]
|
||||
{
|
||||
description = new MetadataSectionDescription(query => songSelect?.Search(query)),
|
||||
source = new MetadataSectionSource(query => songSelect?.Search(query)),
|
||||
tags = new MetadataSectionTags(query => songSelect?.Search(query)),
|
||||
},
|
||||
},
|
||||
description = new MetadataSectionDescription(query => songSelect?.Search(query)),
|
||||
source = new MetadataSectionSource(query => songSelect?.Search(query)),
|
||||
tags = new MetadataSectionTags(query => songSelect?.Search(query)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
new Drawable[]
|
||||
},
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
failRetryContainer = new OnlineViewContainer("Sign in to view more details")
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
failRetryContainer = new OnlineViewContainer("Sign in to view more details")
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = BeatmapsetsStrings.ShowInfoPointsOfFailure,
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14),
|
||||
},
|
||||
failRetryGraph = new FailRetryGraph
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = BeatmapsetsStrings.ShowInfoPointsOfFailure,
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14),
|
||||
},
|
||||
failRetryGraph = new FailRetryGraph
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Top = 14 + spacing / 2 },
|
||||
},
|
||||
},
|
||||
Padding = new MarginPadding { Top = 14 + spacing / 2 },
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
loading = new LoadingLayer(true)
|
||||
};
|
||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Screens.Select.Carousel
|
||||
{
|
||||
@ -67,19 +68,19 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
{
|
||||
default:
|
||||
case SortMode.Artist:
|
||||
comparison = string.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist, StringComparison.Ordinal);
|
||||
comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist);
|
||||
break;
|
||||
|
||||
case SortMode.Title:
|
||||
comparison = string.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title, StringComparison.Ordinal);
|
||||
comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title);
|
||||
break;
|
||||
|
||||
case SortMode.Author:
|
||||
comparison = string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.Ordinal);
|
||||
comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username);
|
||||
break;
|
||||
|
||||
case SortMode.Source:
|
||||
comparison = string.Compare(BeatmapSet.Metadata.Source, otherSet.BeatmapSet.Metadata.Source, StringComparison.Ordinal);
|
||||
comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(BeatmapSet.Metadata.Source, otherSet.BeatmapSet.Metadata.Source);
|
||||
break;
|
||||
|
||||
case SortMode.DateAdded:
|
||||
|
@ -153,6 +153,8 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
public new Bindable<IReadOnlyList<Mod>> SelectedMods => base.SelectedMods;
|
||||
|
||||
public new Storage Storage => base.Storage;
|
||||
|
||||
public new SpectatorClient SpectatorClient => base.SpectatorClient;
|
||||
|
||||
// if we don't apply these changes, when running under nUnit the version that gets populated is that of nUnit.
|
||||
@ -166,7 +168,7 @@ namespace osu.Game.Tests.Visual
|
||||
public TestOsuGame(Storage storage, IAPIProvider api, string[] args = null)
|
||||
: base(args)
|
||||
{
|
||||
Storage = storage;
|
||||
base.Storage = storage;
|
||||
API = api;
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual
|
||||
PauseOnFocusLost = pauseOnFocusLost;
|
||||
}
|
||||
|
||||
protected override bool HandleTokenRetrievalFailure(Exception exception) => false;
|
||||
protected override bool ShouldExitOnTokenRetrievalFailure(Exception exception) => false;
|
||||
|
||||
protected override APIRequest<APIScoreToken> CreateTokenRequest()
|
||||
{
|
||||
|
@ -166,6 +166,9 @@ namespace osu.Game.Users
|
||||
globalRankDisplay = new ProfileValueDisplay(true)
|
||||
{
|
||||
Title = UsersStrings.ShowRankGlobalSimple,
|
||||
// TODO: implement highest rank tooltip
|
||||
// `RankHighest` resides in `APIUser`, but `api.LocalUser` doesn't update
|
||||
// maybe move to `UserStatistics` in api, so `SoloStatisticsWatcher` can update the value
|
||||
},
|
||||
countryRankDisplay = new ProfileValueDisplay(true)
|
||||
{
|
||||
|
49
osu.Game/Utils/OrdinalSortByCaseStringComparer.cs
Normal file
49
osu.Game/Utils/OrdinalSortByCaseStringComparer.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 System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// This string comparer is something of a cross-over between <see cref="StringComparer.Ordinal"/> and <see cref="StringComparer.OrdinalIgnoreCase"/>.
|
||||
/// <see cref="StringComparer.OrdinalIgnoreCase"/> is used first, but <see cref="StringComparer.Ordinal"/> is used as a tie-breaker.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This comparer's behaviour somewhat emulates <see cref="StringComparer.InvariantCulture"/>,
|
||||
/// but non-ordinal comparers - both culture-aware and culture-invariant - have huge performance overheads due to i18n factors (up to 5x slower).
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// Given the following strings to sort: <c>[A, B, C, D, a, b, c, d, A]</c> and a stable sorting algorithm:
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <see cref="StringComparer.Ordinal"/> would return <c>[A, A, B, C, D, a, b, c, d]</c>.
|
||||
/// This is undesirable as letters are interleaved.
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <see cref="StringComparer.OrdinalIgnoreCase"/> would return <c>[A, a, A, B, b, C, c, D, d]</c>.
|
||||
/// Different letters are not interleaved, but because case is ignored, the As are left in arbitrary order.
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// <item>
|
||||
/// <see cref="OrdinalSortByCaseStringComparer"/> would return <c>[a, A, A, b, B, c, C, d, D]</c>, which is the expected behaviour.
|
||||
/// </item>
|
||||
/// </example>
|
||||
public class OrdinalSortByCaseStringComparer : IComparer<string>
|
||||
{
|
||||
public static readonly OrdinalSortByCaseStringComparer DEFAULT = new OrdinalSortByCaseStringComparer();
|
||||
|
||||
private OrdinalSortByCaseStringComparer()
|
||||
{
|
||||
}
|
||||
|
||||
public int Compare(string? a, string? b)
|
||||
{
|
||||
int result = StringComparer.OrdinalIgnoreCase.Compare(a, b);
|
||||
if (result == 0)
|
||||
result = -StringComparer.Ordinal.Compare(a, b); // negative to place lowercase letters before uppercase.
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@ -36,7 +36,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="11.5.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2024.217.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2024.221.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.207.0" />
|
||||
<PackageReference Include="Sentry" Version="3.41.3" />
|
||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||
|
@ -23,6 +23,6 @@
|
||||
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.217.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.221.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
Loading…
Reference in New Issue
Block a user