diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 65ac05261a..cbd0231fdb 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -21,7 +21,7 @@ ] }, "ppy.localisationanalyser.tools": { - "version": "2022.417.0", + "version": "2022.607.0", "commands": [ "localisation" ] diff --git a/osu.Android.props b/osu.Android.props index 116c7dbfcd..aad8cf10d0 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,10 +52,10 @@ - + - + diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs index 715614a201..a5bd126782 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs @@ -14,11 +14,11 @@ namespace osu.Game.Rulesets.Mania.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; - [TestCase(2.3449735700206298d, 151, "diffcalc-test")] + [TestCase(2.3449735700206298d, 242, "diffcalc-test")] public void Test(double expectedStarRating, int expectedMaxCombo, string name) => base.Test(expectedStarRating, expectedMaxCombo, name); - [TestCase(2.7879104989252959d, 151, "diffcalc-test")] + [TestCase(2.7879104989252959d, 242, "diffcalc-test")] public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) => Test(expectedStarRating, expectedMaxCombo, name, new ManiaModDoubleTime()); diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs index 5b7a460079..c35a3dcdc2 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty foreach (var v in base.ToDatabaseAttributes()) yield return v; - // Todo: osu!mania doesn't output MaxCombo attribute for some reason. + yield return (ATTRIB_ID_MAX_COMBO, MaxCombo); yield return (ATTRIB_ID_DIFFICULTY, StarRating); yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow); yield return (ATTRIB_ID_SCORE_MULTIPLIER, ScoreMultiplier); @@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty { base.FromDatabaseAttributes(values); + MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO]; StarRating = values[ATTRIB_ID_DIFFICULTY]; GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW]; ScoreMultiplier = values[ATTRIB_ID_SCORE_MULTIPLIER]; diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index b17aa7fc4d..88f51bf961 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -52,10 +52,18 @@ namespace osu.Game.Rulesets.Mania.Difficulty // This is done the way it is to introduce fractional differences in order to match osu-stable for the time being. GreatHitWindow = Math.Ceiling((int)(getHitWindow300(mods) * clockRate) / clockRate), ScoreMultiplier = getScoreMultiplier(mods), - MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1), + MaxCombo = beatmap.HitObjects.Sum(maxComboForObject) }; } + private static int maxComboForObject(HitObject hitObject) + { + if (hitObject is HoldNote hold) + return 1 + (int)((hold.EndTime - hold.StartTime) / 100); + + return 1; + } + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { var sortedObjects = beatmap.HitObjects.ToArray(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index a904658a4c..fa095edafa 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -79,7 +79,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { Result = { BindTarget = SpinsPerMinute }, }, - ticks = new Container(), + ticks = new Container + { + RelativeSizeAxes = Axes.Both, + }, new AspectContainer { Anchor = Anchor.Centre, diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index 726fbd3ea6..39239c8233 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics; + namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSpinnerTick : DrawableOsuHitObject @@ -10,13 +12,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected DrawableSpinner DrawableSpinner => (DrawableSpinner)ParentHitObject; public DrawableSpinnerTick() - : base(null) + : this(null) { } public DrawableSpinnerTick(SpinnerTick spinnerTick) : base(spinnerTick) { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; } protected override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration; diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index ddee4d3ebd..1a130e96b3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -69,8 +69,8 @@ namespace osu.Game.Rulesets.Osu.Objects double startTime = StartTime + (float)(i + 1) / totalSpins * Duration; AddNested(i < SpinsRequired - ? new SpinnerTick { StartTime = startTime, Position = Position } - : new SpinnerBonusTick { StartTime = startTime, Position = Position }); + ? new SpinnerTick { StartTime = startTime } + : new SpinnerBonusTick { StartTime = startTime }); } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs index 1e170036e4..a58f62736b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs @@ -78,7 +78,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy } }); - if (!(source.FindProvider(s => s.GetTexture("spinner-top") != null) is DefaultLegacySkin)) + var topProvider = source.FindProvider(s => s.GetTexture("spinner-top") != null); + + if (topProvider is LegacySkinTransformer transformer && !(transformer.Skin is DefaultLegacySkin)) { AddInternal(ApproachCircle = new Sprite { diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 504b10e9bc..2dd332fc13 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; +using osu.Framework.Graphics.Primitives; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Judgements; @@ -321,12 +322,14 @@ namespace osu.Game.Rulesets.Taiko.UI private class ProxyContainer : LifetimeManagementContainer { - public new MarginPadding Padding - { - set => base.Padding = value; - } - public void Add(Drawable proxy) => AddInternal(proxy); + + public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) + { + // DrawableHitObject disables masking. + // Hitobject content is proxied and unproxied based on hit status and the IsMaskedAway value could get stuck because of this. + return false; + } } } } diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 00276955aa..d4956e97e0 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -710,7 +710,7 @@ namespace osu.Game.Tests.Database var imported = await LoadOszIntoStore(importer, realm.Realm); - realm.Realm.Write(() => + await realm.Realm.WriteAsync(() => { foreach (var b in imported.Beatmaps) b.OnlineID = -1; diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index af4b002bc9..97be1dcfaa 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -59,12 +59,14 @@ namespace osu.Game.Tests.Gameplay scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TestJudgement(HitResult.Great)) { Type = HitResult.Great }); Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000)); Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); + Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(1)); // No header shouldn't cause any change scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame()); Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000)); Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); + Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(1)); // Reset with a miss instead. scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame @@ -74,6 +76,7 @@ namespace osu.Game.Tests.Gameplay Assert.That(scoreProcessor.TotalScore.Value, Is.Zero); Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); + Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0)); // Reset with no judged hit. scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame @@ -83,6 +86,7 @@ namespace osu.Game.Tests.Gameplay Assert.That(scoreProcessor.TotalScore.Value, Is.Zero); Assert.That(scoreProcessor.JudgedHits, Is.Zero); + Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0)); } private class TestJudgement : Judgement diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs index d3cacaa88c..d68398236a 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs @@ -59,11 +59,13 @@ namespace osu.Game.Tests.Skins AddAssert("Check float parse lookup", () => requester.GetConfig("FloatTest")?.Value == 1.1f); } - [Test] - public void TestBoolLookup() + [TestCase("0", false)] + [TestCase("1", true)] + [TestCase("2", true)] // https://github.com/ppy/osu/issues/18579 + public void TestBoolLookup(string originalValue, bool expectedParsedValue) { - AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["BoolTest"] = "1"); - AddAssert("Check bool parse lookup", () => requester.GetConfig("BoolTest")?.Value == true); + AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["BoolTest"] = originalValue); + AddAssert("Check bool parse lookup", () => requester.GetConfig("BoolTest")?.Value == expectedParsedValue); } [Test] diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTapButton.cs b/osu.Game.Tests/Visual/Editing/TestSceneTapButton.cs new file mode 100644 index 0000000000..d8141619ab --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneTapButton.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Overlays; +using osu.Game.Screens.Edit.Timing; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneTapButton : OsuManualInputManagerTestScene + { + private TapButton tapButton; + + [Cached] + private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + + [Test] + public void TestBasic() + { + AddStep("create button", () => + { + Child = tapButton = new TapButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(4), + }; + }); + + bool pressed = false; + + AddRepeatStep("Press button", () => + { + InputManager.MoveMouseTo(tapButton); + if (!pressed) + InputManager.PressButton(MouseButton.Left); + else + InputManager.ReleaseButton(MouseButton.Left); + + pressed = !pressed; + }, 100); + } + } +} diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs index 8dd368f2a9..a1218aa3e7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs @@ -11,7 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Timing; @@ -77,34 +77,6 @@ namespace osu.Game.Tests.Visual.Editing timingInfo.Text = $"offset: {selectedGroup.Value.Time:N2} bpm: {selectedGroup.Value.ControlPoints.OfType().First().BPM:N2}"; } - [Test] - public void TestNoop() - { - AddStep("do nothing", () => { }); - } - - [Test] - public void TestTapThenReset() - { - AddStep("click tap button", () => - { - control.ChildrenOfType() - .Last() - .TriggerClick(); - }); - - AddUntilStep("wait for track playing", () => Clock.IsRunning); - - AddStep("click reset button", () => - { - control.ChildrenOfType() - .First() - .TriggerClick(); - }); - - AddUntilStep("wait for track stopped", () => !Clock.IsRunning); - } - [Test] public void TestBasic() { @@ -115,7 +87,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("click tap button", () => { - control.ChildrenOfType() + control.ChildrenOfType() .Last() .TriggerClick(); }); @@ -129,6 +101,28 @@ namespace osu.Game.Tests.Visual.Editing }); } + [Test] + public void TestTapThenReset() + { + AddStep("click tap button", () => + { + control.ChildrenOfType() + .Last() + .TriggerClick(); + }); + + AddUntilStep("wait for track playing", () => Clock.IsRunning); + + AddStep("click reset button", () => + { + control.ChildrenOfType() + .First() + .TriggerClick(); + }); + + AddUntilStep("wait for track stopped", () => !Clock.IsRunning); + } + protected override void Dispose(bool isDisposing) { Beatmap.Disabled = false; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs new file mode 100644 index 0000000000..d726bd004e --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Utils; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneTimelineZoom : TimelineTestScene + { + public override Drawable CreateTestComponent() => Empty(); + + [Test] + public void TestVisibleRangeUpdatesOnZoomChange() + { + double initialVisibleRange = 0; + + AddStep("reset zoom", () => TimelineArea.Timeline.Zoom = 100); + AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange); + + AddStep("scale zoom", () => TimelineArea.Timeline.Zoom = 200); + AddAssert("range halved", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange / 2, 1)); + AddStep("descale zoom", () => TimelineArea.Timeline.Zoom = 50); + AddAssert("range doubled", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange * 2, 1)); + + AddStep("restore zoom", () => TimelineArea.Timeline.Zoom = 100); + AddAssert("range restored", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange, 1)); + } + + [Test] + public void TestVisibleRangeConstantOnSizeChange() + { + double initialVisibleRange = 0; + + AddStep("reset timeline size", () => TimelineArea.Timeline.Width = 1); + AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange); + + AddStep("scale timeline size", () => TimelineArea.Timeline.Width = 2); + AddAssert("same range", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange); + AddStep("descale timeline size", () => TimelineArea.Timeline.Width = 0.5f); + AddAssert("same range", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange); + + AddStep("restore timeline size", () => TimelineArea.Timeline.Width = 1); + AddAssert("same range", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange); + } + } +} diff --git a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs index 95d11d6909..2d056bafdd 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs @@ -44,7 +44,12 @@ namespace osu.Game.Tests.Visual.Editing RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(30) }, - scrollContainer = new ZoomableScrollContainer { RelativeSizeAxes = Axes.Both } + scrollContainer = new ZoomableScrollContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + } } }, new MenuCursor() @@ -62,7 +67,15 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestWidthInitialization() { - AddAssert("Inner container width was initialized", () => innerBox.DrawWidth > 0); + AddAssert("Inner container width was initialized", () => innerBox.DrawWidth == scrollContainer.DrawWidth); + } + + [Test] + public void TestWidthUpdatesOnDrawSizeChanges() + { + AddStep("Shrink scroll container", () => scrollContainer.Width = 0.5f); + AddAssert("Scroll container width shrunk", () => scrollContainer.DrawWidth == scrollContainer.Parent.DrawWidth / 2); + AddAssert("Inner container width matches scroll container", () => innerBox.DrawWidth == scrollContainer.DrawWidth); } [Test] diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index 79f62a16e3..5f7c8b3c51 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -178,6 +179,36 @@ namespace osu.Game.Tests.Visual.Online AddAssert("1 notification fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 1); } + /// + /// Ensures that handles channels which have not been or could not be resolved (i.e. = 0). + /// + [Test] + public void TestSendInUnresolvedChannel() + { + int i = 1; + Channel unresolved = null; + + AddRepeatStep("join unresolved channels", () => testContainer.ChannelManager.JoinChannel(unresolved = new Channel(new APIUser + { + Id = 100 + i, + Username = $"Foreign #{i++}", + })), 5); + + AddStep("send message in unresolved channel", () => + { + Debug.Assert(unresolved.Id == 0); + + unresolved.AddLocalEcho(new LocalEchoMessage + { + Sender = API.LocalUser.Value, + ChannelId = unresolved.Id, + Content = "Some message", + }); + }); + + AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0); + } + private void receiveMessage(APIUser sender, Channel channel, string content) => channel.AddNewMessages(createMessage(sender, channel, content)); private Message createMessage(APIUser sender, Channel channel, string content) => new Message(messageIdCounter++) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 860ef5d565..cb52f41c33 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -128,11 +128,11 @@ namespace osu.Game.Tests.Visual.Online AddAssert("Ensure no adjacent day separators", () => { - var indices = chatDisplay.FillFlow.OfType().Select(ds => chatDisplay.FillFlow.IndexOf(ds)); + var indices = chatDisplay.FillFlow.OfType().Select(ds => chatDisplay.FillFlow.IndexOf(ds)); foreach (int i in indices) { - if (i < chatDisplay.FillFlow.Count && chatDisplay.FillFlow[i + 1] is DrawableChannel.DaySeparator) + if (i < chatDisplay.FillFlow.Count && chatDisplay.FillFlow[i + 1] is DaySeparator) return false; } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs index 8b646df362..2a31728f87 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs @@ -96,6 +96,7 @@ namespace osu.Game.Tests.Visual.Ranking beatmap.Metadata.Author = author; beatmap.Metadata.Title = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap title"; beatmap.Metadata.Artist = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap artist"; + beatmap.DifficultyName = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong difficulty name"; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 5f7de0d762..dba457c81c 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -319,6 +319,15 @@ namespace osu.Game.Beatmaps }); } + public void DeleteAllVideos() + { + realm.Write(r => + { + var items = r.All().Where(s => !s.DeletePending && !s.Protected); + beatmapModelManager.DeleteVideos(items.ToList()); + }); + } + public void UndeleteAll() { realm.Run(r => beatmapModelManager.Undelete(r.All().Where(s => s.DeletePending).ToList())); diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 4c680bbcc9..277047348e 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -16,6 +16,7 @@ using osu.Game.Database; using osu.Game.Extensions; using osu.Game.Skinning; using osu.Game.Stores; +using osu.Game.Overlays.Notifications; #nullable enable @@ -33,6 +34,8 @@ namespace osu.Game.Beatmaps protected override string[] HashableFileTypes => new[] { ".osu" }; + public static readonly string[] VIDEO_EXTENSIONS = { ".mp4", ".mov", ".avi", ".flv" }; + public BeatmapModelManager(RealmAccess realm, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null) : base(realm, storage, onlineLookupQueue) { @@ -114,5 +117,50 @@ namespace osu.Game.Beatmaps item.CopyChangesToRealm(existing); }); } + + /// + /// Delete videos from a list of beatmaps. + /// This will post notifications tracking progress. + /// + public void DeleteVideos(List items, bool silent = false) + { + if (items.Count == 0) return; + + var notification = new ProgressNotification + { + Progress = 0, + Text = $"Preparing to delete all {HumanisedModelName} videos...", + CompletionText = "No videos found to delete!", + State = ProgressNotificationState.Active, + }; + + if (!silent) + PostNotification?.Invoke(notification); + + int i = 0; + int deleted = 0; + + foreach (var b in items) + { + if (notification.State == ProgressNotificationState.Cancelled) + // user requested abort + return; + + var video = b.Files.FirstOrDefault(f => VIDEO_EXTENSIONS.Any(ex => f.Filename.EndsWith(ex, StringComparison.Ordinal))); + + if (video != null) + { + DeleteFile(b, video); + deleted++; + notification.CompletionText = $"Deleted {deleted} {HumanisedModelName} video(s)!"; + } + + notification.Text = $"Deleting videos from {HumanisedModelName}s ({deleted} deleted)"; + + notification.Progress = (float)++i / items.Count; + } + + notification.State = ProgressNotificationState.Completed; + } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 9a17d6dc9b..08aaa8da42 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -35,7 +35,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards protected readonly BeatmapDownloadTracker DownloadTracker; protected BeatmapCard(APIBeatmapSet beatmapSet, bool allowExpansion = true) - : base(HoverSampleSet.Submit) + : base(HoverSampleSet.Button) { Expanded = new BindableBool { Disabled = !allowExpansion }; diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs index 58c1ebee0f..7826d64296 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Beatmaps.Drawables.Cards.Statistics; using osu.Game.Graphics; @@ -245,10 +244,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards }); if (BeatmapSet.HasVideo) - leftIconArea.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) }); + leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(20) }); if (BeatmapSet.HasStoryboard) - leftIconArea.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) }); + leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(20) }); if (BeatmapSet.FeaturedInSpotlight) { diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs index 3d7e81de21..c1ba521925 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Beatmaps.Drawables.Cards.Statistics; using osu.Game.Graphics; @@ -226,10 +225,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards }); if (BeatmapSet.HasVideo) - leftIconArea.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) }); + leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(20) }); if (BeatmapSet.HasStoryboard) - leftIconArea.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) }); + leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(20) }); if (BeatmapSet.FeaturedInSpotlight) { diff --git a/osu.Game/Beatmaps/Drawables/Cards/IconPill.cs b/osu.Game/Beatmaps/Drawables/Cards/IconPill.cs index c55e9c0a28..1b2c5d3ffc 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/IconPill.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/IconPill.cs @@ -3,14 +3,16 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osuTK; using osuTK.Graphics; namespace osu.Game.Beatmaps.Drawables.Cards { - public class IconPill : CircularContainer + public abstract class IconPill : CircularContainer, IHasTooltip { public Vector2 IconSize { @@ -20,7 +22,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards private readonly Container iconContainer; - public IconPill(IconUsage icon) + protected IconPill(IconUsage icon) { AutoSizeAxes = Axes.Both; Masking = true; @@ -47,5 +49,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards }, }; } + + public abstract LocalisableString TooltipText { get; } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/StoryboardIconPill.cs b/osu.Game/Beatmaps/Drawables/Cards/StoryboardIconPill.cs new file mode 100644 index 0000000000..2ebf9107f5 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/StoryboardIconPill.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Beatmaps.Drawables.Cards +{ + public class StoryboardIconPill : IconPill + { + public StoryboardIconPill() + : base(FontAwesome.Solid.Image) + { + } + + public override LocalisableString TooltipText => BeatmapsetsStrings.ShowInfoStoryboard; + } +} diff --git a/osu.Game/Beatmaps/Drawables/Cards/VideoIconPill.cs b/osu.Game/Beatmaps/Drawables/Cards/VideoIconPill.cs new file mode 100644 index 0000000000..b81e18b0dd --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/VideoIconPill.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Beatmaps.Drawables.Cards +{ + public class VideoIconPill : IconPill + { + public VideoIconPill() + : base(FontAwesome.Solid.Film) + { + } + + public override LocalisableString TooltipText => BeatmapsetsStrings.ShowInfoVideo; + } +} diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index dbd3b96763..086ec52d80 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -392,7 +392,7 @@ namespace osu.Game.Database { total_writes_async.Value++; using (var realm = getRealmInstance()) - await realm.WriteAsync(action); + await realm.WriteAsync(() => action(realm)); } /// diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 11bfd80ec1..5c6e315225 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Graphics.Containers /// public class ScalingContainer : Container { - private const float duration = 500; + internal const float TRANSITION_DURATION = 500; private Bindable sizeX; private Bindable sizeY; @@ -99,7 +99,7 @@ namespace osu.Game.Graphics.Containers if (applyUIScale) { uiScale = osuConfig.GetBindable(OsuSetting.UIScale); - uiScale.BindValueChanged(args => this.TransformTo(nameof(CurrentScale), args.NewValue, duration, Easing.OutQuart), true); + uiScale.BindValueChanged(args => this.TransformTo(nameof(CurrentScale), args.NewValue, TRANSITION_DURATION, Easing.OutQuart), true); } } @@ -163,10 +163,10 @@ namespace osu.Game.Graphics.Containers backgroundStack.Push(new ScalingBackgroundScreen()); } - backgroundStack.FadeIn(duration); + backgroundStack.FadeIn(TRANSITION_DURATION); } else - backgroundStack?.FadeOut(duration); + backgroundStack?.FadeOut(TRANSITION_DURATION); } RectangleF targetRect = new RectangleF(Vector2.Zero, Vector2.One); @@ -195,13 +195,13 @@ namespace osu.Game.Graphics.Containers if (requiresMasking) sizableContainer.Masking = true; - sizableContainer.MoveTo(targetRect.Location, duration, Easing.OutQuart); - sizableContainer.ResizeTo(targetRect.Size, duration, Easing.OutQuart); + sizableContainer.MoveTo(targetRect.Location, TRANSITION_DURATION, Easing.OutQuart); + sizableContainer.ResizeTo(targetRect.Size, TRANSITION_DURATION, Easing.OutQuart); // Of note, this will not work great in the case of nested ScalingContainers where multiple are applying corner radius. // Masking and corner radius should likely only be applied at one point in the full game stack to fix this. // An example of how this can occur is when the skin editor is visible and the game screen scaling is set to "Everything". - sizableContainer.TransformTo(nameof(CornerRadius), requiresMasking ? corner_radius : 0, duration, requiresMasking ? Easing.OutQuart : Easing.None) + sizableContainer.TransformTo(nameof(CornerRadius), requiresMasking ? corner_radius : 0, TRANSITION_DURATION, requiresMasking ? Easing.OutQuart : Easing.None) .OnComplete(_ => { sizableContainer.Masking = requiresMasking; }); } diff --git a/osu.Game/Graphics/UserInterface/BackButton.cs b/osu.Game/Graphics/UserInterface/BackButton.cs index 1b564ef1b4..2b59ee0282 100644 --- a/osu.Game/Graphics/UserInterface/BackButton.cs +++ b/osu.Game/Graphics/UserInterface/BackButton.cs @@ -21,7 +21,7 @@ namespace osu.Game.Graphics.UserInterface { Size = TwoLayerButton.SIZE_EXTENDED; - Child = button = new TwoLayerButton(HoverSampleSet.Submit) + Child = button = new TwoLayerButton { Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index ad69ec4078..69fbd744c9 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -56,8 +56,8 @@ namespace osu.Game.Graphics.UserInterface private readonly SpriteText spriteText; private Vector2 hoverSpacing => new Vector2(3f, 0f); - public DialogButton() - : base(HoverSampleSet.Submit) + public DialogButton(HoverSampleSet sampleSet = HoverSampleSet.Button) + : base(sampleSet) { RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 0df69a5b54..1730e1478f 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -36,7 +36,7 @@ namespace osu.Game.Graphics.UserInterface Icon = FontAwesome.Solid.ExternalLinkAlt, RelativeSizeAxes = Axes.Both }, - new HoverClickSounds(HoverSampleSet.Submit) + new HoverClickSounds() }; } diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs index 12819840e5..ba253a7c71 100644 --- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs @@ -7,6 +7,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Extensions; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osuTK.Input; namespace osu.Game.Graphics.UserInterface @@ -37,7 +38,10 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnClick(ClickEvent e) { if (buttons.Contains(e.Button) && Contains(e.ScreenSpaceMousePosition)) - sampleClick?.Play(); + { + sampleClick.Frequency.Value = 0.99 + RNG.NextDouble(0.02); + sampleClick.Play(); + } return base.OnClick(e); } diff --git a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs index a5ea6fcfbf..b88f81a143 100644 --- a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs +++ b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs @@ -10,9 +10,6 @@ namespace osu.Game.Graphics.UserInterface [Description("default")] Default, - [Description("submit")] - Submit, - [Description("button")] Button, diff --git a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs index 4780270f66..ee59da7279 100644 --- a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs @@ -13,6 +13,7 @@ namespace osu.Game.Graphics.UserInterface { public class ShearedToggleButton : ShearedButton { + private Sample? sampleClick; private Sample? sampleOff; private Sample? sampleOn; @@ -39,8 +40,9 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(AudioManager audio) { - sampleOn = audio.Samples.Get(@"UI/check-on"); - sampleOff = audio.Samples.Get(@"UI/check-off"); + sampleClick = audio.Samples.Get(@"UI/default-select"); + sampleOn = audio.Samples.Get(@"UI/dropdown-open"); + sampleOff = audio.Samples.Get(@"UI/dropdown-close"); } protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet); @@ -67,6 +69,8 @@ namespace osu.Game.Graphics.UserInterface private void playSample() { + sampleClick?.Play(); + if (Active.Value) sampleOn?.Play(); else diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs index 3d09d09833..d9dbf4974b 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.IO; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; +using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -65,6 +67,9 @@ namespace osu.Game.Graphics.UserInterfaceV2 { get { + if (BeatmapModelManager.VIDEO_EXTENSIONS.Contains(File.Extension)) + return FontAwesome.Regular.FileVideo; + switch (File.Extension) { case @".ogg": @@ -77,12 +82,6 @@ namespace osu.Game.Graphics.UserInterfaceV2 case @".png": return FontAwesome.Regular.FileImage; - case @".mp4": - case @".avi": - case @".mov": - case @".flv": - return FontAwesome.Regular.FileVideo; - default: return FontAwesome.Regular.File; } diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 69ea6b00ca..3da5f3212e 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -80,6 +80,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight), new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode), new KeyBinding(new[] { InputKey.F5 }, GlobalAction.EditorTestGameplay), + new KeyBinding(new[] { InputKey.T }, GlobalAction.EditorTapForBPM), new KeyBinding(new[] { InputKey.Control, InputKey.H }, GlobalAction.EditorFlipHorizontally), new KeyBinding(new[] { InputKey.Control, InputKey.J }, GlobalAction.EditorFlipVertically), new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.MouseWheelDown }, GlobalAction.EditorDecreaseDistanceSpacing), @@ -322,5 +323,8 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DeselectAllMods))] DeselectAllMods, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTapForBPM))] + EditorTapForBPM, } } diff --git a/osu.Game/Localisation/AudioSettingsStrings.cs b/osu.Game/Localisation/AudioSettingsStrings.cs index f298717c99..0f0f560df9 100644 --- a/osu.Game/Localisation/AudioSettingsStrings.cs +++ b/osu.Game/Localisation/AudioSettingsStrings.cs @@ -30,12 +30,12 @@ namespace osu.Game.Localisation public static LocalisableString OutputDevice => new TranslatableString(getKey(@"output_device"), @"Output device"); /// - /// "Master" + /// "Hitsound stereo separation" /// public static LocalisableString PositionalLevel => new TranslatableString(getKey(@"positional_hitsound_audio_level"), @"Hitsound stereo separation"); /// - /// "Level" + /// "Master" /// public static LocalisableString MasterVolume => new TranslatableString(getKey(@"master_volume"), @"Master"); @@ -69,6 +69,6 @@ namespace osu.Game.Localisation /// public static LocalisableString OffsetWizard => new TranslatableString(getKey(@"offset_wizard"), @"Offset wizard"); - private static string getKey(string key) => $"{prefix}:{key}"; + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index e392ae619f..82d03dbb5b 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -174,6 +174,11 @@ namespace osu.Game.Localisation /// public static LocalisableString EditorTimingMode => new TranslatableString(getKey(@"editor_timing_mode"), @"Timing mode"); + /// + /// "Tap for BPM" + /// + public static LocalisableString EditorTapForBPM => new TranslatableString(getKey(@"editor_tap_for_bpm"), @"Tap for BPM"); + /// /// "Cycle grid display mode" /// @@ -205,7 +210,7 @@ namespace osu.Game.Localisation public static LocalisableString ToggleInGameInterface => new TranslatableString(getKey(@"toggle_in_game_interface"), @"Toggle in-game interface"); /// - /// "Toggle Mod Select" + /// "Toggle mod select" /// public static LocalisableString ToggleModSelection => new TranslatableString(getKey(@"toggle_mod_selection"), @"Toggle mod select"); @@ -294,6 +299,6 @@ namespace osu.Game.Localisation /// public static LocalisableString ToggleChatFocus => new TranslatableString(getKey(@"toggle_chat_focus"), @"Toggle chat focus"); - private static string getKey(string key) => $"{prefix}:{key}"; + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Localisation/JoystickSettingsStrings.cs b/osu.Game/Localisation/JoystickSettingsStrings.cs index 410cd0a6f5..976ec1adde 100644 --- a/osu.Game/Localisation/JoystickSettingsStrings.cs +++ b/osu.Game/Localisation/JoystickSettingsStrings.cs @@ -15,10 +15,10 @@ namespace osu.Game.Localisation public static LocalisableString JoystickGamepad => new TranslatableString(getKey(@"joystick_gamepad"), @"Joystick / Gamepad"); /// - /// "Deadzone Threshold" + /// "Deadzone" /// public static LocalisableString DeadzoneThreshold => new TranslatableString(getKey(@"deadzone_threshold"), @"Deadzone"); private static string getKey(string key) => $@"{prefix}:{key}"; } -} \ No newline at end of file +} diff --git a/osu.Game/Localisation/LayoutSettingsStrings.cs b/osu.Game/Localisation/LayoutSettingsStrings.cs index 5ac28f19b3..b4326b8e39 100644 --- a/osu.Game/Localisation/LayoutSettingsStrings.cs +++ b/osu.Game/Localisation/LayoutSettingsStrings.cs @@ -24,6 +24,11 @@ namespace osu.Game.Localisation /// public static LocalisableString UnableToRunExclusiveFullscreen => new TranslatableString(getKey(@"unable_to_run_exclusive_fullscreen"), @"Unable to run exclusive fullscreen. You'll still experience some input latency."); + /// + /// "Using fullscreen on macOS makes interacting with the menu bar and spaces no longer work, and may lead to freezes if a system dialog is presented. Using borderless is recommended." + /// + public static LocalisableString FullscreenMacOSNote => new TranslatableString(getKey(@"fullscreen_macos_note"), @"Using fullscreen on macOS makes interacting with the menu bar and spaces no longer work, and may lead to freezes if a system dialog is presented. Using borderless is recommended."); + private static string getKey(string key) => $@"{prefix}:{key}"; } -} \ No newline at end of file +} diff --git a/osu.Game/Localisation/MaintenanceSettingsStrings.cs b/osu.Game/Localisation/MaintenanceSettingsStrings.cs index a0e1a9ddab..7a04bcd1ca 100644 --- a/osu.Game/Localisation/MaintenanceSettingsStrings.cs +++ b/osu.Game/Localisation/MaintenanceSettingsStrings.cs @@ -29,6 +29,11 @@ namespace osu.Game.Localisation /// public static LocalisableString DeleteAllBeatmaps => new TranslatableString(getKey(@"delete_all_beatmaps"), @"Delete ALL beatmaps"); + /// + /// "Delete ALL beatmap videos" + /// + public static LocalisableString DeleteAllBeatmapVideos => new TranslatableString(getKey(@"delete_all_beatmap_videos"), @"Delete ALL beatmap videos"); + /// /// "Import scores from stable" /// diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 776ff5fd8f..451ea117d5 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -121,8 +121,16 @@ namespace osu.Game.Online.API if (isFailing) return; - Logger.Log($@"Performing request {this}", LoggingTarget.Network); - WebRequest.Perform(); + try + { + Logger.Log($@"Performing request {this}", LoggingTarget.Network); + WebRequest.Perform(); + } + catch (OperationCanceledException) + { + // ignore this. internally Perform is running async and the fail state may have changed since + // the last check of `isFailing` above. + } if (isFailing) return; diff --git a/osu.Game/Online/Chat/DrawableLinkCompiler.cs b/osu.Game/Online/Chat/DrawableLinkCompiler.cs index 8356b36667..3b0d049528 100644 --- a/osu.Game/Online/Chat/DrawableLinkCompiler.cs +++ b/osu.Game/Online/Chat/DrawableLinkCompiler.cs @@ -38,7 +38,6 @@ namespace osu.Game.Online.Chat } public DrawableLinkCompiler(IEnumerable parts) - : base(HoverSampleSet.Submit) { Parts = parts.ToList(); } diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index ca6082e19b..fbc5ef79ef 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -77,7 +77,7 @@ namespace osu.Game.Online.Chat if (!messages.Any()) return; - var channel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id == messages.First().ChannelId); + var channel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id > 0 && c.Id == messages.First().ChannelId); if (channel == null) return; diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index b7e1bc999b..f57ffcfe25 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -155,9 +155,6 @@ namespace osu.Game.Online.Chat { public Func CreateChatLineAction; - [Resolved] - private OsuColour colours { get; set; } - public StandAloneDrawableChannel(Channel channel) : base(channel) { @@ -166,25 +163,40 @@ namespace osu.Game.Online.Chat [BackgroundDependencyLoader] private void load() { + // TODO: Remove once DrawableChannel & ChatLine padding is fixed ChatLineFlow.Padding = new MarginPadding { Horizontal = 0 }; } protected override ChatLine CreateChatLine(Message m) => CreateChatLineAction(m); - protected override Drawable CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time) + protected override DaySeparator CreateDaySeparator(DateTimeOffset time) => new StandAloneDaySeparator(time); + } + + protected class StandAloneDaySeparator : DaySeparator + { + protected override float TextSize => 14; + protected override float LineHeight => 1; + protected override float Spacing => 10; + protected override float DateAlign => 120; + + public StandAloneDaySeparator(DateTimeOffset time) + : base(time) { - TextSize = 14, - Colour = colours.Yellow, - LineHeight = 1, - Padding = new MarginPadding { Horizontal = 10 }, - Margin = new MarginPadding { Vertical = 5 }, - }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Height = 25; + Colour = colours.Yellow; + // TODO: Remove once DrawableChannel & ChatLine padding is fixed + Padding = new MarginPadding { Horizontal = 10 }; + } } protected class StandAloneMessage : ChatLine { protected override float TextSize => 15; - protected override float HorizontalPadding => 10; protected override float MessagePadding => 120; protected override float TimestampPadding => 50; diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index cae675b406..c95f3fa579 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -197,6 +197,7 @@ namespace osu.Game.Online.Multiplayer APIRoom.Playlist.Clear(); APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(createPlaylistItem)); + APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId); Debug.Assert(LocalUser != null); addUserToAPIRoom(LocalUser); @@ -737,6 +738,7 @@ namespace osu.Game.Online.Multiplayer APIRoom.Type.Value = Room.Settings.MatchType; APIRoom.QueueMode.Value = Room.Settings.QueueMode; APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration; + APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == settings.PlaylistItemId); RoomUpdated?.Invoke(); } diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index 6ec884d79c..12091bae88 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -61,7 +61,13 @@ namespace osu.Game.Online.Rooms /// Used for serialising to the API. /// [JsonProperty("beatmap_id")] - private int onlineBeatmapId => Beatmap.OnlineID; + private int onlineBeatmapId + { + get => Beatmap.OnlineID; + // This setter is only required for client-side serialise-then-deserialise operations. + // Serialisation is supposed to emit only a `beatmap_id`, but a (non-null) `beatmap` is required on deserialise. + set => Beatmap = new APIBeatmap { OnlineID = value }; + } /// /// A beatmap representing this playlist item. diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 60c0503ddd..1933267efd 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -162,6 +162,13 @@ namespace osu.Game.Online.Rooms Password.BindValueChanged(p => HasPassword.Value = !string.IsNullOrEmpty(p.NewValue)); } + /// + /// Copies values from another into this one. + /// + /// + /// **Beware**: This will store references between s. + /// + /// The to copy values from. public void CopyFrom(Room other) { RoomID.Value = other.RoomID.Value; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 785881d97a..b91d523151 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Humanizer; using JetBrains.Annotations; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; @@ -658,11 +659,14 @@ namespace osu.Game } protected override IDictionary GetFrameworkConfigDefaults() - => new Dictionary + { + return new Dictionary { - // General expectation that osu! starts in fullscreen by default (also gives the most predictable performance) - { FrameworkSetting.WindowMode, WindowMode.Fullscreen } + // General expectation that osu! starts in fullscreen by default (also gives the most predictable performance). + // However, macOS is bound to have issues when using exclusive fullscreen as it takes full control away from OS, therefore borderless is default there. + { FrameworkSetting.WindowMode, RuntimeInfo.OS == RuntimeInfo.Platform.macOS ? WindowMode.Borderless : WindowMode.Fullscreen } }; + } protected override void LoadComplete() { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 666413004a..5dbdf6f602 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -80,6 +80,9 @@ namespace osu.Game public virtual bool UseDevelopmentServer => DebugUtils.IsDebugBuild; + internal EndpointConfiguration CreateEndpoints() => + UseDevelopmentServer ? (EndpointConfiguration)new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration(); + public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); /// @@ -268,7 +271,7 @@ namespace osu.Game dependencies.Cache(SkinManager = new SkinManager(Storage, realm, Host, Resources, Audio, Scheduler)); dependencies.CacheAs(SkinManager); - EndpointConfiguration endpoints = UseDevelopmentServer ? (EndpointConfiguration)new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration(); + EndpointConfiguration endpoints = CreateEndpoints(); MessageFormatter.WebsiteRootUrl = endpoints.WebsiteRootUrl; diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index 1be1321d85..f4f958e4a4 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; @@ -69,7 +70,7 @@ namespace osu.Game.Overlays.AccountCreation }, usernameTextBox = new OsuTextBox { - PlaceholderText = UsersStrings.LoginUsername, + PlaceholderText = UsersStrings.LoginUsername.ToLower(), RelativeSizeAxes = Axes.X, TabbableContentContainer = this }, @@ -91,7 +92,7 @@ namespace osu.Game.Overlays.AccountCreation }, passwordTextBox = new OsuPasswordTextBox { - PlaceholderText = "password", + PlaceholderText = UsersStrings.LoginPassword.ToLower(), RelativeSizeAxes = Axes.X, TabbableContentContainer = this, }, diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 52dfcad2cc..ff43170207 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -40,7 +40,7 @@ namespace osu.Game.Overlays.BeatmapListing Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular), Text = LabelFor(Value) }, - new HoverClickSounds() + new HoverClickSounds(HoverSampleSet.TabSelect) }); Enabled.Value = true; diff --git a/osu.Game/Overlays/Chat/ChatOverlayDrawableChannel.cs b/osu.Game/Overlays/Chat/ChatOverlayDrawableChannel.cs index 3b47adc4b7..a5d22f678e 100644 --- a/osu.Game/Overlays/Chat/ChatOverlayDrawableChannel.cs +++ b/osu.Game/Overlays/Chat/ChatOverlayDrawableChannel.cs @@ -6,10 +6,6 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; namespace osu.Game.Overlays.Chat @@ -24,85 +20,19 @@ namespace osu.Game.Overlays.Chat [BackgroundDependencyLoader] private void load() { + // TODO: Remove once DrawableChannel & ChatLine padding is fixed ChatLineFlow.Padding = new MarginPadding(0); } - protected override Drawable CreateDaySeparator(DateTimeOffset time) => new ChatOverlayDaySeparator(time); + protected override DaySeparator CreateDaySeparator(DateTimeOffset time) => new ChatOverlayDaySeparator(time); - private class ChatOverlayDaySeparator : Container + private class ChatOverlayDaySeparator : DaySeparator { - private readonly DateTimeOffset time; - public ChatOverlayDaySeparator(DateTimeOffset time) + : base(time) { - this.time = time; - } - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Padding = new MarginPadding { Horizontal = 15, Vertical = 20 }; - Child = new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 200), - new Dimension(GridSizeMode.Absolute, 15), - new Dimension(), - }, - Content = new[] - { - new[] - { - new GridContainer - { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 15), - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new[] - { - new Circle - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Colour = colourProvider.Background5, - RelativeSizeAxes = Axes.X, - Height = 2, - }, - Drawable.Empty(), - new OsuSpriteText - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Text = time.ToLocalTime().ToString("dd MMMM yyyy").ToUpper(), - Font = OsuFont.Torus.With(size: 15, weight: FontWeight.SemiBold), - Colour = colourProvider.Content1, - }, - }, - }, - }, - Drawable.Empty(), - new Circle - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Colour = colourProvider.Background5, - RelativeSizeAxes = Axes.X, - Height = 2, - }, - }, - }, - }; + // TODO: Remove once DrawableChannel & ChatLine padding is fixed + Padding = new MarginPadding { Horizontal = 15 }; } } } diff --git a/osu.Game/Overlays/Chat/DaySeparator.cs b/osu.Game/Overlays/Chat/DaySeparator.cs new file mode 100644 index 0000000000..2e3796151c --- /dev/null +++ b/osu.Game/Overlays/Chat/DaySeparator.cs @@ -0,0 +1,105 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Overlays.Chat +{ + public class DaySeparator : Container + { + protected virtual float TextSize => 15; + + protected virtual float LineHeight => 2; + + protected virtual float DateAlign => 200; + + protected virtual float Spacing => 15; + + private readonly DateTimeOffset time; + + [Resolved(CanBeNull = true)] + private OverlayColourProvider? colourProvider { get; set; } + + public DaySeparator(DateTimeOffset time) + { + this.time = time; + Height = 40; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RowDimensions = new[] { new Dimension() }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, DateAlign), + new Dimension(GridSizeMode.Absolute, Spacing), + new Dimension(), + }, + Content = new[] + { + new[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { new Dimension() }, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, Spacing), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new[] + { + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = LineHeight, + Colour = colourProvider?.Background5 ?? Colour4.White, + }, + Drawable.Empty(), + new OsuSpriteText + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Text = time.ToLocalTime().ToString("dd MMMM yyyy").ToUpper(), + Font = OsuFont.Torus.With(size: TextSize, weight: FontWeight.SemiBold), + Colour = colourProvider?.Content1 ?? Colour4.White, + }, + } + }, + }, + Drawable.Empty(), + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = LineHeight, + Colour = colourProvider?.Background5 ?? Colour4.White, + }, + } + } + }; + } + } +} diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index f2d4a3e301..f0d4d12f4f 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -7,14 +7,9 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; using osu.Game.Graphics.Cursor; -using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; using osuTK.Graphics; @@ -40,9 +35,6 @@ namespace osu.Game.Overlays.Chat } } - [Resolved] - private OsuColour colours { get; set; } - public DrawableChannel(Channel channel) { Channel = channel; @@ -67,7 +59,7 @@ namespace osu.Game.Overlays.Chat Padding = new MarginPadding { Bottom = 5 }, Child = ChatLineFlow = new FillFlowContainer { - Padding = new MarginPadding { Left = 20, Right = 20 }, + Padding = new MarginPadding { Horizontal = 15 }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, @@ -121,11 +113,7 @@ namespace osu.Game.Overlays.Chat protected virtual ChatLine CreateChatLine(Message m) => new ChatLine(m); - protected virtual Drawable CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time) - { - Colour = colours.ChatBlue.Lighten(0.7f), - Margin = new MarginPadding { Vertical = 10 }, - }; + protected virtual DaySeparator CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time); private void newMessagesArrived(IEnumerable newMessages) => Schedule(() => { @@ -203,69 +191,5 @@ namespace osu.Game.Overlays.Chat }); private IEnumerable chatLines => ChatLineFlow.Children.OfType(); - - public class DaySeparator : Container - { - public float TextSize - { - get => text.Font.Size; - set => text.Font = text.Font.With(size: value); - } - - private float lineHeight = 2; - - public float LineHeight - { - get => lineHeight; - set => lineHeight = leftBox.Height = rightBox.Height = value; - } - - private readonly SpriteText text; - private readonly Box leftBox; - private readonly Box rightBox; - - public DaySeparator(DateTimeOffset time) - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Child = new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - }, - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), }, - Content = new[] - { - new Drawable[] - { - leftBox = new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = lineHeight, - }, - text = new OsuSpriteText - { - Margin = new MarginPadding { Horizontal = 10 }, - Text = time.ToLocalTime().ToString("dd MMM yyyy"), - }, - rightBox = new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = lineHeight, - }, - } - } - }; - } - } } } diff --git a/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs index 86c81d5d79..2a21d30a4a 100644 --- a/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs +++ b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs @@ -6,6 +6,8 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,6 +18,7 @@ using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.Chat; using osuTK; @@ -32,6 +35,8 @@ namespace osu.Game.Overlays.Chat.Listing public IEnumerable FilterTerms => new LocalisableString[] { Channel.Name, Channel.Topic ?? string.Empty }; public bool MatchingFilter { set => this.FadeTo(value ? 1f : 0f, 100); } + protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(); + private Box hoverBox = null!; private SpriteIcon checkbox = null!; private OsuSpriteText channelText = null!; @@ -46,14 +51,20 @@ namespace osu.Game.Overlays.Chat.Listing private const float vertical_margin = 1.5f; + private Sample? sampleJoin; + private Sample? sampleLeave; + public ChannelListingItem(Channel channel) { Channel = channel; } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { + sampleJoin = audio.Samples.Get(@"UI/check-on"); + sampleLeave = audio.Samples.Get(@"UI/check-off"); + Masking = true; CornerRadius = 5; RelativeSizeAxes = Axes.X; @@ -156,7 +167,19 @@ namespace osu.Game.Overlays.Chat.Listing } }, true); - Action = () => (channelJoined.Value ? OnRequestLeave : OnRequestJoin)?.Invoke(Channel); + Action = () => + { + if (channelJoined.Value) + { + OnRequestLeave?.Invoke(Channel); + sampleLeave?.Play(); + } + else + { + OnRequestJoin?.Invoke(Channel); + sampleJoin?.Play(); + } + }; } protected override bool OnHover(HoverEvent e) diff --git a/osu.Game/Overlays/News/NewsCard.cs b/osu.Game/Overlays/News/NewsCard.cs index 68d0704825..5ce0b9df9c 100644 --- a/osu.Game/Overlays/News/NewsCard.cs +++ b/osu.Game/Overlays/News/NewsCard.cs @@ -14,7 +14,6 @@ using osu.Framework.Platform; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.News @@ -29,7 +28,6 @@ namespace osu.Game.Overlays.News private TextFlowContainer main; public NewsCard(APINewsPost post) - : base(HoverSampleSet.Submit) { this.post = post; diff --git a/osu.Game/Overlays/News/Sidebar/MonthSection.cs b/osu.Game/Overlays/News/Sidebar/MonthSection.cs index aa83f89689..e2ce25660e 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthSection.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthSection.cs @@ -18,7 +18,6 @@ using System.Diagnostics; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Platform; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.News.Sidebar { @@ -129,7 +128,6 @@ namespace osu.Game.Overlays.News.Sidebar private readonly APINewsPost post; public PostButton(APINewsPost post) - : base(HoverSampleSet.Submit) { this.post = post; diff --git a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs index b4a5d5e31b..4cfdf5cc86 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private Sample sampleOpen; private Sample sampleClose; - protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(); + protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverClickSounds(); public ExpandDetailsButton() { diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs index 94ef5e5d86..13465f3bf8 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Profile.Sections { @@ -18,7 +17,6 @@ namespace osu.Game.Overlays.Profile.Sections private readonly IBeatmapInfo beatmapInfo; protected BeatmapMetadataContainer(IBeatmapInfo beatmapInfo) - : base(HoverSampleSet.Submit) { this.beatmapInfo = beatmapInfo; diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index d79ba593f7..05890ad882 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -4,6 +4,7 @@ using System; using System.Drawing; using System.Linq; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Configuration; @@ -224,6 +225,16 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private void updateScreenModeWarning() { + if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS) + { + if (windowModeDropdown.Current.Value == WindowMode.Fullscreen) + windowModeDropdown.SetNoticeText(LayoutSettingsStrings.FullscreenMacOSNote, true); + else + windowModeDropdown.ClearNoticeText(); + + return; + } + if (windowModeDropdown.Current.Value != WindowMode.Fullscreen) { windowModeDropdown.SetNoticeText(GraphicsSettingsStrings.NotFullscreenNote, true); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index be4b0decd9..054de8dbd7 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -28,6 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private SettingsButton deleteSkinsButton; private SettingsButton restoreButton; private SettingsButton undeleteButton; + private SettingsButton deleteBeatmapVideosButton; [BackgroundDependencyLoader(permitNulls: true)] private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) @@ -58,6 +59,19 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance } }); + Add(deleteBeatmapVideosButton = new DangerousSettingsButton + { + Text = MaintenanceSettingsStrings.DeleteAllBeatmapVideos, + Action = () => + { + dialogOverlay?.Push(new MassVideoDeleteConfirmationDialog(() => + { + deleteBeatmapVideosButton.Enabled.Value = false; + Task.Run(beatmaps.DeleteAllVideos).ContinueWith(t => Schedule(() => deleteBeatmapVideosButton.Enabled.Value = true)); + })); + } + }); + if (legacyImportManager?.SupportsImportFromStable == true) { Add(importScoresButton = new SettingsButton diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs new file mode 100644 index 0000000000..fc8c9d497b --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Overlays.Settings.Sections.Maintenance +{ + public class MassVideoDeleteConfirmationDialog : MassDeleteConfirmationDialog + { + public MassVideoDeleteConfirmationDialog(Action deleteAction) + : base(deleteAction) + { + BodyText = "All beatmap videos? This cannot be undone!"; + } + } +} diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 4a839b048c..b686f11c13 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -20,7 +20,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osuTK; using osuTK.Graphics; @@ -83,7 +82,6 @@ namespace osu.Game.Overlays.Toolbar private RealmAccess realm { get; set; } protected ToolbarButton() - : base(HoverSampleSet.Toolbar) { Width = Toolbar.HEIGHT; RelativeSizeAxes = Axes.Y; diff --git a/osu.Game/Overlays/Toolbar/ToolbarClock.cs b/osu.Game/Overlays/Toolbar/ToolbarClock.cs index 308359570f..12529da07f 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarClock.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarClock.cs @@ -11,7 +11,6 @@ using osu.Framework.Input.Events; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; using osuTK; using osuTK.Graphics; @@ -29,7 +28,6 @@ namespace osu.Game.Overlays.Toolbar private AnalogClockDisplay analog; public ToolbarClock() - : base(HoverSampleSet.Toolbar) { RelativeSizeAxes = Axes.Y; AutoSizeAxes = Axes.X; diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 929c362bd8..24d9f785f2 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Framework.Utils; @@ -28,7 +27,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Volume { - public class VolumeMeter : Container, IKeyBindingHandler, IStateful + public class VolumeMeter : Container, IStateful { private CircularProgress volumeCircle; private CircularProgress volumeCircleGlow; @@ -80,7 +79,7 @@ namespace osu.Game.Overlays.Volume [BackgroundDependencyLoader] private void load(OsuColour colours, AudioManager audio) { - hoverSample = audio.Samples.Get($"UI/{HoverSampleSet.Button.GetDescription()}-hover"); + hoverSample = audio.Samples.Get($@"UI/{HoverSampleSet.Button.GetDescription()}-hover"); notchSample = audio.Samples.Get(@"UI/notch-tick"); sampleLastPlaybackTime = Time.Current; @@ -132,7 +131,7 @@ namespace osu.Game.Overlays.Volume { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Name = "Progress under covers for smoothing", + Name = @"Progress under covers for smoothing", RelativeSizeAxes = Axes.Both, Rotation = 180, Child = volumeCircle = new CircularProgress @@ -144,7 +143,7 @@ namespace osu.Game.Overlays.Volume }, new Circle { - Name = "Inner Cover", + Name = @"Inner Cover", Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, @@ -153,7 +152,7 @@ namespace osu.Game.Overlays.Volume }, new Container { - Name = "Progress overlay for glow", + Name = @"Progress overlay for glow", Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, @@ -365,27 +364,6 @@ namespace osu.Game.Overlays.Volume { } - public bool OnPressed(KeyBindingPressEvent e) - { - if (!IsHovered) - return false; - - switch (e.Action) - { - case GlobalAction.SelectPreviousGroup: - State = SelectionState.Selected; - adjust(1, false); - return true; - - case GlobalAction.SelectNextGroup: - State = SelectionState.Selected; - adjust(-1, false); - return true; - } - - return false; - } - public void OnReleased(KeyBindingReleaseEvent e) { } diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index a96949e96f..9d2ed3f837 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -17,6 +17,7 @@ using osu.Game.Input.Bindings; using osu.Game.Overlays.Volume; using osuTK; using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Overlays { @@ -171,6 +172,30 @@ namespace osu.Game.Overlays return base.OnMouseMove(e); } + protected override bool OnKeyDown(KeyDownEvent e) + { + switch (e.Key) + { + case Key.Left: + Adjust(GlobalAction.PreviousVolumeMeter); + return true; + + case Key.Right: + Adjust(GlobalAction.NextVolumeMeter); + return true; + + case Key.Down: + Adjust(GlobalAction.DecreaseVolume); + return true; + + case Key.Up: + Adjust(GlobalAction.IncreaseVolume); + return true; + } + + return base.OnKeyDown(e); + } + protected override bool OnHover(HoverEvent e) { schedulePopOut(); diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 4a5897c621..df094ddb7c 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -460,6 +460,7 @@ namespace osu.Game.Rulesets.Scoring currentMaximumScoringValues.BaseScore = maximum.BaseScore; currentMaximumScoringValues.MaxCombo = maximum.MaxCombo; + Combo.Value = frame.Header.Combo; HighestCombo.Value = frame.Header.MaxCombo; scoreResultCounts.Clear(); diff --git a/osu.Game/Screens/Edit/Components/EditorSidebar.cs b/osu.Game/Screens/Edit/Components/EditorSidebar.cs index 4edcef41b1..4e9b1d5222 100644 --- a/osu.Game/Screens/Edit/Components/EditorSidebar.cs +++ b/osu.Game/Screens/Edit/Components/EditorSidebar.cs @@ -16,13 +16,15 @@ namespace osu.Game.Screens.Edit.Components /// internal class EditorSidebar : Container { + public const float WIDTH = 250; + private readonly Box background; protected override Container Content { get; } public EditorSidebar() { - Width = 250; + Width = WIDTH; RelativeSizeAxes = Axes.Y; InternalChildren = new Drawable[] diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs index 5550c6a748..e0b21b2e22 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs @@ -1,99 +1,29 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; -using osu.Framework.Threading; -using osu.Game.Graphics; +using osu.Framework.Allocation; using osu.Game.Graphics.UserInterface; -using osuTK; -using osuTK.Graphics; -using osuTK.Input; +using osu.Game.Overlays; +using osu.Game.Screens.Edit.Timing; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - public class TimelineButton : CompositeDrawable + public class TimelineButton : IconButton { - public Action Action; - public readonly BindableBool Enabled = new BindableBool(true); - - public IconUsage Icon + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) { - get => button.Icon; - set => button.Icon = value; + // These are using colourProvider but don't match the design. + // Just something to fit until someone implements the updated design. + IconColour = colourProvider.Background1; + IconHoverColour = colourProvider.Content2; + + HoverColour = colourProvider.Background1; + FlashColour = colourProvider.Content2; + + Add(new RepeatingButtonBehaviour(this)); } - private readonly IconButton button; - - public TimelineButton() - { - InternalChild = button = new TimelineIconButton { Action = () => Action?.Invoke() }; - - button.Enabled.BindTo(Enabled); - Width = button.Width; - } - - protected override void Update() - { - base.Update(); - - button.Size = new Vector2(button.Width, DrawHeight); - } - - private class TimelineIconButton : IconButton - { - public TimelineIconButton() - { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - IconColour = OsuColour.Gray(0.35f); - IconHoverColour = Color4.White; - HoverColour = OsuColour.Gray(0.25f); - FlashColour = OsuColour.Gray(0.5f); - } - - private ScheduledDelegate repeatSchedule; - - /// - /// The initial delay before mouse down repeat begins. - /// - private const int repeat_initial_delay = 250; - - /// - /// The delay between mouse down repeats after the initial repeat. - /// - private const int repeat_tick_rate = 70; - - protected override bool OnClick(ClickEvent e) - { - // don't actuate a click since we are manually handling repeats. - return true; - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - if (e.Button == MouseButton.Left) - { - Action clickAction = () => base.OnClick(new ClickEvent(e.CurrentState, e.Button)); - - // run once for initial down - clickAction(); - - Scheduler.Add(repeatSchedule = new ScheduledDelegate(clickAction, Clock.CurrentTime + repeat_initial_delay, repeat_tick_rate)); - } - - return base.OnMouseDown(e); - } - - protected override void OnMouseUp(MouseUpEvent e) - { - repeatSchedule?.Cancel(); - base.OnMouseUp(e); - } - } + protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 35d103ddf1..d008368b69 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Transforms; using osu.Framework.Input.Events; +using osu.Framework.Layout; using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Graphics.Containers; @@ -40,10 +41,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved(canBeNull: true)] private IFrameBasedClock editorClock { get; set; } + private readonly LayoutValue zoomedContentWidthCache = new LayoutValue(Invalidation.DrawSize); + public ZoomableScrollContainer() : base(Direction.Horizontal) { base.Content.Add(zoomedContent = new Container { RelativeSizeAxes = Axes.Y }); + + AddLayout(zoomedContentWidthCache); } private float minZoom = 1; @@ -103,12 +108,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - protected override void LoadComplete() + protected override void Update() { - base.LoadComplete(); + base.Update(); - // This width only gets updated on the application of a transform, so this needs to be initialized here. - updateZoomedContentWidth(); + if (!zoomedContentWidthCache.IsValid) + updateZoomedContentWidth(); } protected override bool OnScroll(ScrollEvent e) @@ -128,7 +133,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return base.OnScroll(e); } - private void updateZoomedContentWidth() => zoomedContent.Width = DrawWidth * currentZoom; + private void updateZoomedContentWidth() + { + zoomedContent.Width = DrawWidth * currentZoom; + zoomedContentWidthCache.Validate(); + } private float zoomTarget = 1; @@ -199,8 +208,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline float targetOffset = expectedWidth * (focusPoint / contentSize) - focusOffset; d.currentZoom = newZoom; - d.updateZoomedContentWidth(); + // Temporarily here to make sure ScrollTo gets the correct DrawSize for scrollable area. // TODO: Make sure draw size gets invalidated properly on the framework side, and remove this once it is. d.Invalidate(Invalidation.DrawSize); diff --git a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs index aae19396db..fd916894ea 100644 --- a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs +++ b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using System.Collections.Generic; using System.IO; @@ -9,6 +11,7 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; @@ -27,10 +30,10 @@ namespace osu.Game.Screens.Edit.Setup public IEnumerable HandledExtensions => handledExtensions; - private readonly Bindable currentFile = new Bindable(); + private readonly Bindable currentFile = new Bindable(); [Resolved] - private OsuGameBase game { get; set; } + private OsuGameBase game { get; set; } = null!; public FileChooserLabelledTextBox(params string[] handledExtensions) { @@ -45,7 +48,7 @@ namespace osu.Game.Screens.Edit.Setup currentFile.BindValueChanged(onFileSelected); } - private void onFileSelected(ValueChangedEvent file) + private void onFileSelected(ValueChangedEvent file) { if (file.NewValue == null) return; @@ -65,14 +68,16 @@ namespace osu.Game.Screens.Edit.Setup protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - game.UnregisterImportHandler(this); + + if (game.IsNotNull()) + game.UnregisterImportHandler(this); } public override Popover GetPopover() => new FileChooserPopover(handledExtensions, currentFile); private class FileChooserPopover : OsuPopover { - public FileChooserPopover(string[] handledExtensions, Bindable currentFile) + public FileChooserPopover(string[] handledExtensions, Bindable currentFile) { Child = new Container { diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs index bb2dd35a9c..f613488aae 100644 --- a/osu.Game/Screens/Edit/Timing/GroupSection.cs +++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Edit.Timing RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Padding = new MarginPadding(10); + Padding = new MarginPadding(10) { Bottom = 0 }; InternalChildren = new Drawable[] { diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 4143c5ea55..2ecd66a05f 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -38,6 +38,8 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private OverlayColourProvider overlayColourProvider { get; set; } + public bool EnableClicking { get; set; } = true; + [BackgroundDependencyLoader] private void load(AudioManager audio) { @@ -281,6 +283,9 @@ namespace osu.Game.Screens.Edit.Timing Schedule(() => { + if (!EnableClicking) + return; + var channel = clunk?.GetChannel(); if (channel != null) diff --git a/osu.Game/Screens/Edit/Timing/RepeatingButtonBehaviour.cs b/osu.Game/Screens/Edit/Timing/RepeatingButtonBehaviour.cs new file mode 100644 index 0000000000..595305b20f --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/RepeatingButtonBehaviour.cs @@ -0,0 +1,92 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Framework.Threading; + +namespace osu.Game.Screens.Edit.Timing +{ + /// + /// Represents a component that provides the behaviour of triggering button clicks repeatedly while holding with mouse. + /// + public class RepeatingButtonBehaviour : Component + { + private const double initial_delay = 300; + private const double minimum_delay = 80; + + private readonly Drawable button; + + private Sample sample; + + /// + /// An additive modifier for the frequency of the sample played on next actuation. + /// This can be adjusted during the button's event to affect the repeat sample playback of that click. + /// + public double SampleFrequencyModifier { get; set; } + + public RepeatingButtonBehaviour(Drawable button) + { + this.button = button; + + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + sample = audio.Samples.Get(@"UI/notch-tick"); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + beginRepeat(); + return true; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + adjustDelegate?.Cancel(); + base.OnMouseUp(e); + } + + private ScheduledDelegate adjustDelegate; + private double adjustDelay = initial_delay; + + private void beginRepeat() + { + adjustDelegate?.Cancel(); + + adjustDelay = initial_delay; + adjustNext(); + + void adjustNext() + { + if (IsHovered) + { + button.TriggerClick(); + adjustDelay = Math.Max(minimum_delay, adjustDelay * 0.9f); + + var channel = sample?.GetChannel(); + + if (channel != null) + { + double repeatModifier = 0.05f * (Math.Abs(adjustDelay - initial_delay) / minimum_delay); + channel.Frequency.Value = 1 + repeatModifier + SampleFrequencyModifier; + channel.Play(); + } + } + else + { + adjustDelay = initial_delay; + } + + adjustDelegate = Scheduler.AddDelayed(adjustNext, adjustDelay); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/Section.cs b/osu.Game/Screens/Edit/Timing/Section.cs index 139abfb187..17147c21f4 100644 --- a/osu.Game/Screens/Edit/Timing/Section.cs +++ b/osu.Game/Screens/Edit/Timing/Section.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Edit.Timing { Flow = new FillFlowContainer { - Padding = new MarginPadding(20), + Padding = new MarginPadding(10) { Top = 0 }, Spacing = new Vector2(20), RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Screens/Edit/Timing/TapButton.cs b/osu.Game/Screens/Edit/Timing/TapButton.cs new file mode 100644 index 0000000000..a6227cbe27 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/TapButton.cs @@ -0,0 +1,418 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Framework.Threading; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Input.Bindings; +using osu.Game.Overlays; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class TapButton : CircularContainer, IKeyBindingHandler + { + public const float SIZE = 140; + + public readonly BindableBool IsHandlingTapping = new BindableBool(); + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [Resolved(canBeNull: true)] + private Bindable? selectedGroup { get; set; } + + [Resolved(canBeNull: true)] + private IBeatSyncProvider? beatSyncSource { get; set; } + + private Circle hoverLayer = null!; + + private CircularContainer innerCircle = null!; + private Box innerCircleHighlight = null!; + + private int currentLight; + + private Container scaleContainer = null!; + private Container lights = null!; + private Container lightsGlow = null!; + private OsuSpriteText bpmText = null!; + private Container textContainer = null!; + + private bool grabbedMouseDown; + + private ScheduledDelegate? resetDelegate; + + private const int light_count = 8; + + private const int initial_taps_to_ignore = 4; + + private const int max_taps_to_consider = 128; + + private const double transition_length = 500; + + private const float angular_light_gap = 0.007f; + + private readonly List tapTimings = new List(); + + [BackgroundDependencyLoader] + private void load() + { + Size = new Vector2(SIZE); + + const float ring_width = 22; + const float light_padding = 3; + + InternalChild = scaleContainer = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Circle + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4 + }, + lights = new Container + { + RelativeSizeAxes = Axes.Both, + }, + new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Name = @"outer masking", + Masking = true, + BorderThickness = light_padding, + BorderColour = colourProvider.Background4, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + } + }, + new Circle + { + Name = @"inner masking", + Size = new Vector2(SIZE - ring_width * 2 + light_padding * 2), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = colourProvider.Background4, + }, + lightsGlow = new Container + { + RelativeSizeAxes = Axes.Both, + }, + innerCircle = new CircularContainer + { + Size = new Vector2(SIZE - ring_width * 2), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background2, + RelativeSizeAxes = Axes.Both, + }, + innerCircleHighlight = new Box + { + Colour = colourProvider.Colour3, + Blending = BlendingParameters.Additive, + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + textContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background1, + Children = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.Torus.With(size: 34, weight: FontWeight.SemiBold), + Anchor = Anchor.Centre, + Origin = Anchor.BottomCentre, + Y = 5, + Text = "Tap", + }, + bpmText = new OsuSpriteText + { + Font = OsuFont.Torus.With(size: 23, weight: FontWeight.Regular), + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + Y = -1, + }, + } + }, + hoverLayer = new Circle + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background1.Opacity(0.3f), + Blending = BlendingParameters.Additive, + Alpha = 0, + }, + } + }, + } + }; + + for (int i = 0; i < light_count; i++) + { + var light = new Light + { + Rotation = (i + 1) * (360f / light_count) + 360 * angular_light_gap / 2, + }; + + lights.Add(light); + lightsGlow.Add(light.Glow.CreateProxy()); + } + + reset(); + } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => + hoverLayer.ReceivePositionalInputAt(screenSpacePos); + + private ColourInfo textColour + { + get + { + if (grabbedMouseDown) + return colourProvider.Background4; + + if (IsHovered) + return colourProvider.Content2; + + return colourProvider.Background1; + } + } + + protected override bool OnHover(HoverEvent e) + { + hoverLayer.FadeIn(transition_length, Easing.OutQuint); + textContainer.FadeColour(textColour, transition_length, Easing.OutQuint); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hoverLayer.FadeOut(transition_length, Easing.OutQuint); + textContainer.FadeColour(textColour, transition_length, Easing.OutQuint); + base.OnHoverLost(e); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + const double in_duration = 100; + + grabbedMouseDown = true; + IsHandlingTapping.Value = true; + + resetDelegate?.Cancel(); + + handleTap(); + + textContainer.FadeColour(textColour, in_duration, Easing.OutQuint); + + scaleContainer.ScaleTo(0.99f, in_duration, Easing.OutQuint); + innerCircle.ScaleTo(0.96f, in_duration, Easing.OutQuint); + + innerCircleHighlight + .FadeIn(50, Easing.OutQuint) + .FlashColour(Color4.White, 1000, Easing.OutQuint); + + lights[currentLight % light_count].Hide(); + lights[(currentLight + light_count / 2) % light_count].Hide(); + + currentLight++; + + lights[currentLight % light_count].Show(); + lights[(currentLight + light_count / 2) % light_count].Show(); + + return true; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + const double out_duration = 800; + + grabbedMouseDown = false; + + textContainer.FadeColour(textColour, out_duration, Easing.OutQuint); + + scaleContainer.ScaleTo(1, out_duration, Easing.OutQuint); + innerCircle.ScaleTo(1, out_duration, Easing.OutQuint); + + innerCircleHighlight.FadeOut(out_duration, Easing.OutQuint); + + resetDelegate = Scheduler.AddDelayed(reset, 1000); + + base.OnMouseUp(e); + } + + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == GlobalAction.EditorTapForBPM && !e.Repeat) + { + // Direct through mouse handling to achieve animation + OnMouseDown(new MouseDownEvent(e.CurrentState, MouseButton.Left)); + return true; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + if (e.Action == GlobalAction.EditorTapForBPM) + OnMouseUp(new MouseUpEvent(e.CurrentState, MouseButton.Left)); + } + + private void handleTap() + { + tapTimings.Add(Clock.CurrentTime); + + if (tapTimings.Count > initial_taps_to_ignore + max_taps_to_consider) + tapTimings.RemoveAt(0); + + if (tapTimings.Count < initial_taps_to_ignore * 2) + { + bpmText.Text = new string('.', tapTimings.Count); + return; + } + + double averageBeatLength = (tapTimings.Last() - tapTimings.Skip(initial_taps_to_ignore).First()) / (tapTimings.Count - initial_taps_to_ignore - 1); + double clockRate = beatSyncSource?.Clock?.Rate ?? 1; + + double bpm = Math.Round(60000 / averageBeatLength / clockRate); + + bpmText.Text = $"{bpm} BPM"; + + var timingPoint = selectedGroup?.Value.ControlPoints.OfType().FirstOrDefault(); + + if (timingPoint != null) + { + // Intentionally use the rounded BPM here. + timingPoint.BeatLength = 60000 / bpm; + } + } + + private void reset() + { + bpmText.FadeOut(transition_length, Easing.OutQuint); + + using (BeginDelayedSequence(tapTimings.Count > 0 ? transition_length : 0)) + { + Schedule(() => bpmText.Text = "the beat!"); + bpmText.FadeIn(800, Easing.OutQuint); + } + + foreach (var light in lights) + light.Hide(); + + tapTimings.Clear(); + currentLight = 0; + IsHandlingTapping.Value = false; + } + + private class Light : CompositeDrawable + { + public Drawable Glow { get; private set; } = null!; + + private Container fillContent = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Size = new Vector2(0.98f); // Avoid bleed into masking edge. + + InternalChildren = new Drawable[] + { + new CircularProgress + { + RelativeSizeAxes = Axes.Both, + Current = { Value = 1f / light_count - angular_light_gap }, + Colour = colourProvider.Background2, + }, + fillContent = new Container + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Colour = colourProvider.Colour1, + Children = new[] + { + new CircularProgress + { + RelativeSizeAxes = Axes.Both, + Current = { Value = 1f / light_count - angular_light_gap }, + Blending = BlendingParameters.Additive + }, + // Please do not try and make sense of this. + // Getting the visual effect I was going for relies on what I can only imagine is broken implementation + // of `PadExtent`. If that's ever fixed in the future this will likely need to be adjusted. + Glow = new CircularProgress + { + RelativeSizeAxes = Axes.Both, + Current = { Value = 1f / light_count - 0.01f }, + Blending = BlendingParameters.Additive + }.WithEffect(new GlowEffect + { + Colour = colourProvider.Colour1.Opacity(0.4f), + BlurSigma = new Vector2(9f), + Strength = 10, + PadExtent = true + }), + } + }, + }; + } + + public override void Show() + { + fillContent + .FadeIn(50, Easing.OutQuint) + .FlashColour(Color4.White, 1000, Easing.OutQuint); + } + + public override void Hide() + { + fillContent + .FadeOut(300, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 990f8d2ce0..9b5574d3cb 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -1,33 +1,45 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; +using osuTK; namespace osu.Game.Screens.Edit.Timing { public class TapTimingControl : CompositeDrawable { [Resolved] - private EditorClock editorClock { get; set; } + private EditorClock editorClock { get; set; } = null!; [Resolved] - private EditorBeatmap beatmap { get; set; } + private EditorBeatmap beatmap { get; set; } = null!; [Resolved] - private Bindable selectedGroup { get; set; } + private Bindable selectedGroup { get; set; } = null!; + + private readonly BindableBool isHandlingTapping = new BindableBool(); + + private MetronomeDisplay metronome = null!; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuColour colours) { + const float padding = 10; + RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -48,8 +60,8 @@ namespace osu.Game.Screens.Edit.Timing RowDimensions = new[] { new Dimension(GridSizeMode.Absolute, 200), - new Dimension(GridSizeMode.Absolute, 60), - new Dimension(GridSizeMode.Absolute, 60), + new Dimension(GridSizeMode.Absolute, 50), + new Dimension(GridSizeMode.Absolute, TapButton.SIZE + padding), }, Content = new[] { @@ -58,6 +70,7 @@ namespace osu.Game.Screens.Edit.Timing new Container { RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(padding), Children = new Drawable[] { new GridContainer @@ -72,7 +85,7 @@ namespace osu.Game.Screens.Edit.Timing { new Drawable[] { - new MetronomeDisplay + metronome = new MetronomeDisplay { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -89,15 +102,14 @@ namespace osu.Game.Screens.Edit.Timing new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(10), + Padding = new MarginPadding { Bottom = padding, Horizontal = padding }, Children = new Drawable[] { new TimingAdjustButton(1) { Text = "Offset", - RelativeSizeAxes = Axes.X, - Width = 0.48f, - Height = 50, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.48f, 1), Action = adjustOffset, }, new TimingAdjustButton(0.1) @@ -105,9 +117,8 @@ namespace osu.Game.Screens.Edit.Timing Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Text = "BPM", - RelativeSizeAxes = Axes.X, - Width = 0.48f, - Height = 50, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.48f, 1), Action = adjustBpm, } } @@ -118,33 +129,70 @@ namespace osu.Game.Screens.Edit.Timing new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(10), + Padding = new MarginPadding { Bottom = padding, Horizontal = padding }, Children = new Drawable[] { - new RoundedButton + new Container { - Text = "Reset", - BackgroundColour = colours.Pink, - RelativeSizeAxes = Axes.X, - Width = 0.3f, - Action = reset, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + Height = 0.98f, + Width = TapButton.SIZE / 1.3f, + Masking = true, + CornerRadius = 15, + Children = new Drawable[] + { + new InlineButton(FontAwesome.Solid.Stop, Anchor.TopLeft) + { + BackgroundColour = colourProvider.Background1, + RelativeSizeAxes = Axes.Both, + Height = 0.49f, + Action = reset, + }, + new InlineButton(FontAwesome.Solid.Play, Anchor.BottomLeft) + { + BackgroundColour = colourProvider.Background1, + RelativeSizeAxes = Axes.Both, + Height = 0.49f, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Action = start, + }, + }, }, - new RoundedButton + new TapButton { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Text = "Play from start", - RelativeSizeAxes = Axes.X, - BackgroundColour = colourProvider.Background1, - Width = 0.68f, - Action = tap, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + IsHandlingTapping = { BindTarget = isHandlingTapping } } } }, - } + }, } }, }; + + isHandlingTapping.BindValueChanged(handling => + { + metronome.EnableClicking = !handling.NewValue; + + if (handling.NewValue) + start(); + }, true); + } + + private void start() + { + editorClock.Seek(selectedGroup.Value.Time); + editorClock.Start(); + } + + private void reset() + { + editorClock.Stop(); + editorClock.Seek(selectedGroup.Value.Time); } private void adjustOffset(double adjust) @@ -176,16 +224,66 @@ namespace osu.Game.Screens.Edit.Timing timing.BeatLength = 60000 / (timing.BPM + adjust); } - private void tap() + private class InlineButton : OsuButton { - editorClock.Seek(selectedGroup.Value.Time); - editorClock.Start(); - } + private readonly IconUsage icon; + private readonly Anchor anchor; - private void reset() - { - editorClock.Stop(); - editorClock.Seek(selectedGroup.Value.Time); + private SpriteIcon spriteIcon = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + public InlineButton(IconUsage icon, Anchor anchor) + { + this.icon = icon; + this.anchor = anchor; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Content.CornerRadius = 0; + Content.Masking = false; + + BackgroundColour = colourProvider.Background2; + + Content.Add(new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(15), + Children = new Drawable[] + { + spriteIcon = new SpriteIcon + { + Icon = icon, + Size = new Vector2(22), + Anchor = anchor, + Origin = anchor, + Colour = colourProvider.Background1, + }, + } + }); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + // scale looks bad so don't call base. + return false; + } + + protected override bool OnHover(HoverEvent e) + { + spriteIcon.FadeColour(colourProvider.Content2, 200, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + spriteIcon.FadeColour(colourProvider.Background1, 200, Easing.OutQuint); + base.OnHoverLost(e); + } } } } diff --git a/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs b/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs index 9fc7e56a3d..9540547d89 100644 --- a/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs +++ b/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs @@ -4,14 +4,11 @@ using System; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Localisation; -using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; @@ -26,32 +23,24 @@ namespace osu.Game.Screens.Edit.Timing public Action Action; private readonly double adjustAmount; - private ScheduledDelegate adjustDelegate; private const int max_multiplier = 10; - private const int adjust_levels = 4; - private const double initial_delay = 300; - - private const double minimum_delay = 80; - public Container Content { get; set; } - private double adjustDelay = initial_delay; - private readonly Box background; private readonly OsuSpriteText text; - private Sample sample; - public LocalisableString Text { get => text.Text; set => text.Text = value; } + private readonly RepeatingButtonBehaviour repeatBehaviour; + [Resolved] private OverlayColourProvider colourProvider { get; set; } @@ -82,13 +71,13 @@ namespace osu.Game.Screens.Edit.Timing } } }); + + AddInternal(repeatBehaviour = new RepeatingButtonBehaviour(this)); } [BackgroundDependencyLoader] - private void load(AudioManager audio) + private void load() { - sample = audio.Samples.Get(@"UI/notch-tick"); - background.Colour = colourProvider.Background3; for (int i = 1; i <= adjust_levels; i++) @@ -98,57 +87,22 @@ namespace osu.Game.Screens.Edit.Timing } } - protected override bool OnMouseDown(MouseDownEvent e) + protected override bool OnHover(HoverEvent e) => true; + + protected override bool OnClick(ClickEvent e) { - beginRepeat(); + var hoveredBox = Content.OfType().FirstOrDefault(d => d.IsHovered); + if (hoveredBox == null) + return false; + + Action(adjustAmount * hoveredBox.Multiplier); + + hoveredBox.Flash(); + + repeatBehaviour.SampleFrequencyModifier = (hoveredBox.Multiplier / max_multiplier) * 0.2; return true; } - protected override void OnMouseUp(MouseUpEvent e) - { - adjustDelegate?.Cancel(); - base.OnMouseUp(e); - } - - private void beginRepeat() - { - adjustDelegate?.Cancel(); - - adjustDelay = initial_delay; - adjustNext(); - - void adjustNext() - { - var hoveredBox = Content.OfType().FirstOrDefault(d => d.IsHovered); - - if (hoveredBox != null) - { - Action(adjustAmount * hoveredBox.Multiplier); - - adjustDelay = Math.Max(minimum_delay, adjustDelay * 0.9f); - - hoveredBox.Flash(); - - var channel = sample?.GetChannel(); - - if (channel != null) - { - double repeatModifier = 0.05f * (Math.Abs(adjustDelay - initial_delay) / minimum_delay); - double multiplierModifier = (hoveredBox.Multiplier / max_multiplier) * 0.2f; - - channel.Frequency.Value = 1 + multiplierModifier + repeatModifier; - channel.Play(); - } - } - else - { - adjustDelay = initial_delay; - } - - adjustDelegate = Scheduler.AddDelayed(adjustNext, adjustDelay); - } - } - private class IncrementBox : CompositeDrawable { public readonly float Multiplier; @@ -187,7 +141,7 @@ namespace osu.Game.Screens.Edit.Timing Origin = direction, Font = OsuFont.Default.With(size: 10, weight: FontWeight.Bold), Text = $"{(index > 0 ? "+" : "-")}{Math.Abs(Multiplier * amount)}", - Padding = new MarginPadding(5), + Padding = new MarginPadding(2), Alpha = 0, } }; diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 40e6e8082a..f498aa917e 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -1,12 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -51,6 +53,8 @@ namespace osu.Game.Screens.Edit.Timing private readonly IBindableList controlPointGroups = new BindableList(); + private RoundedButton addButton; + [Resolved] private EditorClock clock { get; set; } @@ -105,9 +109,8 @@ namespace osu.Game.Screens.Edit.Timing Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, }, - new RoundedButton + addButton = new RoundedButton { - Text = "+ Add at current time", Action = addNew, Size = new Vector2(160, 30), Anchor = Anchor.BottomRight, @@ -122,7 +125,14 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - selectedGroup.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true); + selectedGroup.BindValueChanged(selected => + { + deleteButton.Enabled.Value = selected.NewValue != null; + + addButton.Text = selected.NewValue != null + ? "+ Clone to current time" + : "+ Add at current time"; + }, true); controlPointGroups.BindTo(Beatmap.ControlPointInfo.Groups); controlPointGroups.BindCollectionChanged((sender, args) => @@ -132,13 +142,23 @@ namespace osu.Game.Screens.Edit.Timing }, true); } + protected override bool OnClick(ClickEvent e) + { + selectedGroup.Value = null; + return true; + } + protected override void Update() { base.Update(); trackActivePoint(); + + addButton.Enabled.Value = clock.CurrentTimeAccurate != selectedGroup.Value?.Time; } + private Type trackedType; + /// /// Given the user has selected a control point group, we want to track any group which is /// active at the current point in time which matches the type the user has selected. @@ -149,16 +169,27 @@ namespace osu.Game.Screens.Edit.Timing private void trackActivePoint() { // For simplicity only match on the first type of the active control point. - var selectedPointType = selectedGroup.Value?.ControlPoints.FirstOrDefault()?.GetType(); + if (selectedGroup.Value == null) + trackedType = null; + else + { + // If the selected group only has one control point, update the tracking type. + if (selectedGroup.Value.ControlPoints.Count == 1) + trackedType = selectedGroup.Value?.ControlPoints.Single().GetType(); + // If the selected group has more than one control point, choose the first as the tracking type + // if we don't already have a singular tracked type. + else if (trackedType == null) + trackedType = selectedGroup.Value?.ControlPoints.FirstOrDefault()?.GetType(); + } - if (selectedPointType != null) + if (trackedType != null) { // We don't have an efficient way of looking up groups currently, only individual point types. // To improve the efficiency of this in the future, we should reconsider the overall structure of ControlPointInfo. // Find the next group which has the same type as the selected one. var found = Beatmap.ControlPointInfo.Groups - .Where(g => g.ControlPoints.Any(cp => cp.GetType() == selectedPointType)) + .Where(g => g.ControlPoints.Any(cp => cp.GetType() == trackedType)) .LastOrDefault(g => g.Time <= clock.CurrentTimeAccurate); if (found != null) @@ -189,7 +220,7 @@ namespace osu.Game.Screens.Edit.Timing // Try and create matching types from the currently selected control point. var selected = selectedGroup.Value; - if (selected != null) + if (selected != null && selected != group) { foreach (var controlPoint in selected.ControlPoints) { diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 35c903eb0c..0015cf8bf9 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge private void load(AudioManager audio) { sampleSelect = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select"); - sampleJoin = audio.Samples.Get($@"UI/{HoverSampleSet.Submit.GetDescription()}-select"); + sampleJoin = audio.Samples.Get($@"UI/{HoverSampleSet.Button.GetDescription()}-select"); AddRangeInternal(new Drawable[] { diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 5b3129dad6..b924fbd5df 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -159,6 +159,8 @@ namespace osu.Game.Screens.Ranking.Expanded Origin = Anchor.TopCentre, Text = beatmap.DifficultyName, Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold), + MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2, + Truncate = true, }, new OsuTextFlowContainer(s => s.Font = OsuFont.Torus.With(size: 12)) { diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index a59f14647d..8a588c519b 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -10,6 +10,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Caching; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; @@ -633,7 +634,7 @@ namespace osu.Game.Screens.Select protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) { // handles the vertical size of the carousel changing (ie. on window resize when aspect ratio has changed). - if ((invalidation & Invalidation.Layout) > 0) + if (invalidation.HasFlagFast(Invalidation.DrawSize)) itemsCache.Invalidate(); return base.OnInvalidate(invalidation, source); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 9772b1feb3..98b885eb43 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -8,6 +8,7 @@ using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -244,7 +245,7 @@ namespace osu.Game.Screens.Select.Carousel } if (hideRequested != null) - items.Add(new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested(beatmapInfo))); + items.Add(new OsuMenuItem(CommonStrings.ButtonsHide.ToSentence(), MenuItemType.Destructive, () => hideRequested(beatmapInfo))); return items.ToArray(); } diff --git a/osu.Game/Screens/Select/Filter/SortMode.cs b/osu.Game/Screens/Select/Filter/SortMode.cs index 5dfa2a2664..3dd3381059 100644 --- a/osu.Game/Screens/Select/Filter/SortMode.cs +++ b/osu.Game/Screens/Select/Filter/SortMode.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Select.Filter [Description("Author")] Author, - [LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowStatsBpm))] + [LocalisableDescription(typeof(SortStrings), nameof(SortStrings.ArtistTracksBpm))] BPM, [Description("Date Added")] @@ -28,10 +28,10 @@ namespace osu.Game.Screens.Select.Filter Length, // todo: pending support (https://github.com/ppy/osu/issues/4917) - // [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchFiltersRank))] + // [Description("Rank Achieved")] // RankAchieved, - [LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowInfoSource))] + [Description("Source")] Source, [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchSortingTitle))] diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index 9cb178ca8b..1587f43258 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Select private readonly Box light; public FooterButton() - : base(HoverSampleSet.Button) + : base(HoverSampleSet.Toolbar) { AutoSizeAxes = Axes.Both; Shear = SHEAR; diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs index 6ecb96f723..df7dd47ef3 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Select.Options public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos); public BeatmapOptionsButton() - : base(HoverSampleSet.Submit) + : base(HoverSampleSet.Button) { Width = width; RelativeSizeAxes = Axes.Y; diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index e36d5ca3c6..095763de18 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -29,6 +29,8 @@ namespace osu.Game.Skinning.Editor { public const double TRANSITION_DURATION = 500; + public const float MENU_HEIGHT = 40; + public readonly BindableList SelectedComponents = new BindableList(); protected override bool StartHidden => true; @@ -78,8 +80,6 @@ namespace osu.Game.Skinning.Editor { RelativeSizeAxes = Axes.Both; - const float menu_height = 40; - InternalChild = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, @@ -102,7 +102,7 @@ namespace osu.Game.Skinning.Editor Name = "Menu container", RelativeSizeAxes = Axes.X, Depth = float.MinValue, - Height = menu_height, + Height = MENU_HEIGHT, Children = new Drawable[] { new EditorMenuBar @@ -322,7 +322,10 @@ namespace osu.Game.Skinning.Editor protected override void PopIn() { - this.FadeIn(TRANSITION_DURATION, Easing.OutQuint); + this + // align animation to happen after the majority of the ScalingContainer animation completes. + .Delay(ScalingContainer.TRANSITION_DURATION * 0.3f) + .FadeIn(TRANSITION_DURATION, Easing.OutQuint); } protected override void PopOut() diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 497283a820..a3110ced24 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -12,6 +12,8 @@ using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using osu.Game.Screens; +using osu.Game.Screens.Edit.Components; +using osuTK; namespace osu.Game.Skinning.Editor { @@ -33,6 +35,8 @@ namespace osu.Game.Skinning.Editor private OsuScreen lastTargetScreen; + private Vector2 lastDrawSize; + public SkinEditorOverlay(ScalingContainer scalingContainer) { this.scalingContainer = scalingContainer; @@ -81,15 +85,42 @@ namespace osu.Game.Skinning.Editor protected override void PopOut() => skinEditor?.Hide(); + protected override void Update() + { + base.Update(); + + if (game.DrawSize != lastDrawSize) + { + lastDrawSize = game.DrawSize; + updateScreenSizing(); + } + } + + private void updateScreenSizing() + { + if (skinEditor?.State.Value != Visibility.Visible) return; + + const float padding = 10; + + float relativeSidebarWidth = (EditorSidebar.WIDTH + padding) / DrawWidth; + float relativeToolbarHeight = (SkinEditorSceneLibrary.HEIGHT + SkinEditor.MENU_HEIGHT + padding) / DrawHeight; + + var rect = new RectangleF( + relativeSidebarWidth, + relativeToolbarHeight, + 1 - relativeSidebarWidth * 2, + 1f - relativeToolbarHeight - padding / DrawHeight); + + scalingContainer.SetCustomRect(rect, true); + } + private void updateComponentVisibility() { Debug.Assert(skinEditor != null); - const float toolbar_padding_requirement = 0.18f; - if (skinEditor.State.Value == Visibility.Visible) { - scalingContainer.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.2f, 0.8f - toolbar_padding_requirement, 0.7f), true); + Scheduler.AddOnce(updateScreenSizing); game?.Toolbar.Hide(); game?.CloseAllOverlays(); @@ -127,6 +158,9 @@ namespace osu.Game.Skinning.Editor private void setTarget(OsuScreen target) { + if (target == null) + return; + Debug.Assert(skinEditor != null); if (!target.IsLoaded) diff --git a/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs b/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs index 2124ba9b6d..dc5a8aefc0 100644 --- a/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs +++ b/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs @@ -27,6 +27,8 @@ namespace osu.Game.Skinning.Editor { public class SkinEditorSceneLibrary : CompositeDrawable { + public const float HEIGHT = BUTTON_HEIGHT + padding * 2; + public const float BUTTON_HEIGHT = 40; private const float padding = 10; @@ -42,7 +44,7 @@ namespace osu.Game.Skinning.Editor public SkinEditorSceneLibrary() { - Height = BUTTON_HEIGHT + padding * 2; + Height = HEIGHT; } [BackgroundDependencyLoader] diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index b65ba8b04c..9524d3f615 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -303,8 +303,13 @@ namespace osu.Game.Skinning if (Configuration.ConfigDictionary.TryGetValue(lookup.ToString(), out string val)) { // special case for handling skins which use 1 or 0 to signify a boolean state. + // ..or in some cases 2 (https://github.com/ppy/osu/issues/18579). if (typeof(TValue) == typeof(bool)) - val = val == "1" ? "true" : "false"; + { + val = bool.TryParse(val, out bool boolVal) + ? Convert.ChangeType(boolVal, typeof(bool)).ToString() + : Convert.ChangeType(Convert.ToInt32(val), typeof(bool)).ToString(); + } var bindable = new Bindable(); if (val != null) diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index 9481fc7182..34714b9dc5 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -23,7 +23,7 @@ namespace osu.Game.Skinning /// The which is being transformed. /// [NotNull] - protected internal ISkin Skin { get; } + public ISkin Skin { get; } protected LegacySkinTransformer([NotNull] ISkin skin) { diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index 8290af8f78..8fea77833e 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -44,9 +46,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay switch (request) { case CreateRoomRequest createRoomRequest: - var apiRoom = new Room(); - - apiRoom.CopyFrom(createRoomRequest.Room); + var apiRoom = cloneRoom(createRoomRequest.Room); // Passwords are explicitly not copied between rooms. apiRoom.HasPassword.Value = !string.IsNullOrEmpty(createRoomRequest.Room.Password.Value); @@ -178,12 +178,31 @@ namespace osu.Game.Tests.Visual.OnlinePlay private Room createResponseRoom(Room room, bool withParticipants) { - var responseRoom = new Room(); - responseRoom.CopyFrom(room); + var responseRoom = cloneRoom(room); + + // Password is hidden from the response, and is only propagated via HasPassword. + bool hadPassword = responseRoom.HasPassword.Value; responseRoom.Password.Value = null; + responseRoom.HasPassword.Value = hadPassword; + if (!withParticipants) responseRoom.RecentParticipants.Clear(); + return responseRoom; } + + private Room cloneRoom(Room source) + { + var result = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source)); + Debug.Assert(result != null); + + // Playlist item IDs aren't serialised. + if (source.CurrentPlaylistItem.Value != null) + result.CurrentPlaylistItem.Value.ID = source.CurrentPlaylistItem.Value.ID; + for (int i = 0; i < source.Playlist.Count; i++) + result.Playlist[i].ID = source.Playlist[i].ID; + + return result; + } } } diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index 0dd135b500..d85648c078 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Users.Drawables @@ -74,11 +73,6 @@ namespace osu.Game.Users.Drawables { private LocalisableString tooltip = default_tooltip_text; - public ClickableArea() - : base(HoverSampleSet.Submit) - { - } - public override LocalisableString TooltipText { get => Enabled.Value ? tooltip : default; diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs index e5debc0683..d0ef760e59 100644 --- a/osu.Game/Users/Drawables/UpdateableFlag.cs +++ b/osu.Game/Users/Drawables/UpdateableFlag.cs @@ -49,7 +49,7 @@ namespace osu.Game.Users.Drawables { RelativeSizeAxes = Axes.Both }, - new HoverClickSounds(HoverSampleSet.Submit) + new HoverClickSounds() } }; } diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 248debf1d3..40d70ca406 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -32,7 +32,7 @@ namespace osu.Game.Users protected Drawable Background { get; private set; } protected UserPanel(APIUser user) - : base(HoverSampleSet.Submit) + : base(HoverSampleSet.Button) { if (user == null) throw new ArgumentNullException(nameof(user)); diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index ecc7fc4774..c12fd607b4 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -43,7 +43,7 @@ namespace osu.Game.Utils sentrySession = SentrySdk.Init(options => { // Not setting the dsn will completely disable sentry. - if (game.IsDeployedBuild) + if (game.IsDeployedBuild && game.CreateEndpoints().WebsiteRootUrl.EndsWith(@".ppy.sh", StringComparison.Ordinal)) options.Dsn = "https://ad9f78529cef40ac874afb95a9aca04e@sentry.ppy.sh/2"; options.AutoSessionTracking = true; @@ -159,6 +159,7 @@ namespace osu.Game.Utils Game = game.Clock.CurrentTime, }; + scope.SetTag(@"beatmap", $"{beatmap.OnlineID}"); scope.SetTag(@"ruleset", ruleset.ShortName); scope.SetTag(@"os", $"{RuntimeInfo.OS} ({Environment.OSVersion})"); scope.SetTag(@"processor count", Environment.ProcessorCount.ToString()); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index eb47d0468f..63b8cf4cb5 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -31,12 +31,12 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/osu.iOS.props b/osu.iOS.props index ccecad6f82..a0fafa635b 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,11 +84,11 @@ - + - + diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 68cf8138e2..286a1eb29f 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -790,6 +790,15 @@ See the LICENCE file in the repository root for full licence text. <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True + True + True + True + True + True + True + True + True True True True