From 467d7c4f54fc833be0d92bc941db11224155b787 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jun 2024 10:17:40 +0300 Subject: [PATCH 001/266] Refactor game-wide layout order of footer to fix depth issues with overlays and improve UX With this new order, the logo can be easily moved to display in front of the footer in `SongSelectV2` without breaking experience when footer-based overlays are present. Such overlays (i.e. mod select overlay) will also be dimmed alongside the current screen when a game-wide overlay is open (e.g. settings). --- .../SongSelect/TestSceneSongSelectV2.cs | 6 --- osu.Game/OsuGame.cs | 31 ++++++++++---- .../Overlays/Mods/ShearedOverlayContainer.cs | 24 +++++++---- osu.Game/Screens/Footer/ScreenFooter.cs | 33 ++++++++++++--- osu.Game/Screens/SelectV2/SongSelectV2.cs | 41 ++++++++----------- 5 files changed, 82 insertions(+), 53 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs index 674eaa2ff82a..0a632793cc2c 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs @@ -181,12 +181,6 @@ public void TestOverlayPresent() #endregion - protected override void Update() - { - base.Update(); - Stack.Padding = new MarginPadding { Bottom = screenScreenFooter.DrawHeight - screenScreenFooter.Y }; - } - private void updateFooter(IScreen? _, IScreen? newScreen) { if (newScreen is IOsuScreen osuScreen && osuScreen.ShowFooter) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index cf32daab00ff..3862fea0e22c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -51,6 +51,7 @@ using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; +using osu.Game.Overlays.Mods; using osu.Game.Overlays.Music; using osu.Game.Overlays.Notifications; using osu.Game.Overlays.SkinEditor; @@ -132,8 +133,12 @@ public partial class OsuGame : OsuGameBase, IKeyBindingHandler, IL private Container topMostOverlayContent; + private Container footerBasedOverlayContent; + protected ScalingContainer ScreenContainer { get; private set; } + private Container logoContainer; + protected Container ScreenOffsetContainer { get; private set; } private Container overlayOffsetContainer; @@ -156,8 +161,6 @@ public partial class OsuGame : OsuGameBase, IKeyBindingHandler, IL private float toolbarOffset => (Toolbar?.Position.Y ?? 0) + (Toolbar?.DrawHeight ?? 0); - private float screenFooterOffset => (ScreenFooter?.DrawHeight ?? 0) - (ScreenFooter?.Position.Y ?? 0); - private IdleTracker idleTracker; /// @@ -242,7 +245,11 @@ IDisposable IOverlayManager.RegisterBlockingOverlay(OverlayContainer overlayCont throw new ArgumentException($@"{overlayContainer} has already been registered via {nameof(IOverlayManager.RegisterBlockingOverlay)} once."); externalOverlays.Add(overlayContainer); - overlayContent.Add(overlayContainer); + + if (overlayContainer is ShearedOverlayContainer) + footerBasedOverlayContent.Add(overlayContainer); + else + overlayContent.Add(overlayContainer); if (overlayContainer is OsuFocusedOverlayContainer focusedOverlayContainer) focusedOverlays.Add(focusedOverlayContainer); @@ -290,6 +297,8 @@ public void CloseAllOverlays(bool hideToolbar = true) if (hideToolbar) Toolbar.Hide(); } + public void ChangeLogoDepth(bool inFrontOfFooter) => ScreenContainer.ChangeChildDepth(logoContainer, inFrontOfFooter ? float.MinValue : 0); + protected override UserInputManager CreateUserInputManager() { var userInputManager = base.CreateUserInputManager(); @@ -934,7 +943,6 @@ protected override void LoadComplete() return string.Join(" / ", combinations); }; - Container logoContainer; ScreenFooter.BackReceptor backReceptor; dependencies.CacheAs(idleTracker = new GameIdleTracker(6000)); @@ -976,8 +984,15 @@ protected override void LoadComplete() Origin = Anchor.BottomLeft, Action = () => ScreenFooter.OnBack?.Invoke(), }, + logoContainer = new Container { RelativeSizeAxes = Axes.Both }, + footerBasedOverlayContent = new Container + { + Depth = -1, + RelativeSizeAxes = Axes.Both, + }, new PopoverContainer { + Depth = -1, RelativeSizeAxes = Axes.Both, Child = ScreenFooter = new ScreenFooter(backReceptor) { @@ -991,7 +1006,6 @@ protected override void LoadComplete() } }, }, - logoContainer = new Container { RelativeSizeAxes = Axes.Both }, } }, } @@ -1025,7 +1039,7 @@ protected override void LoadComplete() if (!IsDeployedBuild) { - dependencies.Cache(versionManager = new VersionManager { Depth = int.MinValue }); + dependencies.Cache(versionManager = new VersionManager()); loadComponentSingleFile(versionManager, ScreenContainer.Add); } @@ -1072,7 +1086,7 @@ protected override void LoadComplete() loadComponentSingleFile(CreateUpdateManager(), Add, true); // overlay elements - loadComponentSingleFile(FirstRunOverlay = new FirstRunSetupOverlay(), overlayContent.Add, true); + loadComponentSingleFile(FirstRunOverlay = new FirstRunSetupOverlay(), footerBasedOverlayContent.Add, true); loadComponentSingleFile(new ManageCollectionsDialog(), overlayContent.Add, true); loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true); loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true); @@ -1137,7 +1151,7 @@ protected override void LoadComplete() } // ensure only one of these overlays are open at once. - var singleDisplayOverlays = new OverlayContainer[] { FirstRunOverlay, chatOverlay, news, dashboard, beatmapListing, changelogOverlay, rankingsOverlay, wikiOverlay }; + var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, news, dashboard, beatmapListing, changelogOverlay, rankingsOverlay, wikiOverlay }; foreach (var overlay in singleDisplayOverlays) { @@ -1485,7 +1499,6 @@ protected override void UpdateAfterChildren() ScreenOffsetContainer.Padding = new MarginPadding { Top = toolbarOffset }; overlayOffsetContainer.Padding = new MarginPadding { Top = toolbarOffset }; - ScreenStack.Padding = new MarginPadding { Bottom = screenFooterOffset }; float horizontalOffset = 0f; diff --git a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs index c9c3c6240414..b5435e7e5851 100644 --- a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs +++ b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs @@ -1,10 +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 disable - -using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -30,16 +28,15 @@ public abstract partial class ShearedOverlayContainer : OsuFocusedOverlayContain /// /// The overlay's header. /// - protected ShearedOverlayHeader Header { get; private set; } + protected ShearedOverlayHeader Header { get; private set; } = null!; /// /// The overlay's footer. /// protected Container Footer { get; private set; } - [Resolved(canBeNull: true)] - [CanBeNull] - private ScreenFooter footer { get; set; } + [Resolved] + private ScreenFooter? footer { get; set; } // todo: very temporary property that will be removed once ModSelectOverlay and FirstRunSetupOverlay are updated to use new footer. public virtual bool UseNewFooter => false; @@ -48,12 +45,12 @@ public abstract partial class ShearedOverlayContainer : OsuFocusedOverlayContain /// A container containing all content, including the header and footer. /// May be used for overlay-wide animations. /// - protected Container TopLevelContent { get; private set; } + protected Container TopLevelContent { get; private set; } = null!; /// /// A container for content that is to be displayed between the header and footer. /// - protected Container MainAreaContent { get; private set; } + protected Container MainAreaContent { get; private set; } = null!; /// /// A container for content that is to be displayed inside the footer. @@ -64,6 +61,10 @@ public abstract partial class ShearedOverlayContainer : OsuFocusedOverlayContain protected override bool BlockNonPositionalInput => true; + // ShearedOverlayContainers are placed at a layer within the screen container as they rely on ScreenFooter which must be placed there. + // Therefore, dimming must be managed locally, since DimMainContent dims the entire screen layer. + protected sealed override bool DimMainContent => false; + protected ShearedOverlayContainer(OverlayColourScheme colourScheme) { RelativeSizeAxes = Axes.Both; @@ -81,6 +82,11 @@ private void load() RelativeSizeAxes = Axes.Both, Children = new Drawable[] { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourProvider.Background6.Opacity(0.75f), + }, Header = new ShearedOverlayHeader { Anchor = Anchor.TopCentre, diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index cef891f8c052..dcf64e92915b 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Threading; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using osu.Game.Overlays; @@ -24,6 +25,7 @@ public partial class ScreenFooter : OverlayContainer { private const int padding = 60; private const float delay_per_button = 30; + private const double transition_duration = 400; public const int HEIGHT = 50; @@ -37,6 +39,9 @@ public partial class ScreenFooter : OverlayContainer [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + [Resolved] + private OsuGame? game { get; set; } + public ScreenBackButton BackButton { get; private set; } = null!; public Action? OnBack; @@ -101,19 +106,35 @@ private void load() }; } - public void StartTrackingLogo(OsuLogo logo, float duration = 0, Easing easing = Easing.None) => logoTrackingContainer.StartTracking(logo, duration, easing); - public void StopTrackingLogo() => logoTrackingContainer.StopTracking(); + private ScheduledDelegate? changeLogoDepthDelegate; + + public void StartTrackingLogo(OsuLogo logo, float duration = 0, Easing easing = Easing.None) + { + changeLogoDepthDelegate?.Cancel(); + changeLogoDepthDelegate = null; + + logoTrackingContainer.StartTracking(logo, duration, easing); + game?.ChangeLogoDepth(inFrontOfFooter: true); + } + + public void StopTrackingLogo() + { + logoTrackingContainer.StopTracking(); + + if (game != null) + changeLogoDepthDelegate = Scheduler.AddDelayed(() => game.ChangeLogoDepth(inFrontOfFooter: false), transition_duration); + } protected override void PopIn() { - this.MoveToY(0, 400, Easing.OutQuint) - .FadeIn(400, Easing.OutQuint); + this.MoveToY(0, transition_duration, Easing.OutQuint) + .FadeIn(transition_duration, Easing.OutQuint); } protected override void PopOut() { - this.MoveToY(HEIGHT, 400, Easing.OutQuint) - .FadeOut(400, Easing.OutQuint); + this.MoveToY(HEIGHT, transition_duration, Easing.OutQuint) + .FadeOut(transition_duration, Easing.OutQuint); } public void SetButtons(IReadOnlyList buttons) diff --git a/osu.Game/Screens/SelectV2/SongSelectV2.cs b/osu.Game/Screens/SelectV2/SongSelectV2.cs index 10ed7783c4a7..a8730ad80858 100644 --- a/osu.Game/Screens/SelectV2/SongSelectV2.cs +++ b/osu.Game/Screens/SelectV2/SongSelectV2.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Screens; using osu.Game.Overlays; using osu.Game.Overlays.Mods; @@ -23,6 +22,8 @@ namespace osu.Game.Screens.SelectV2 /// public partial class SongSelectV2 : OsuScreen { + private const float logo_scale = 0.4f; + private readonly ModSelectOverlay modSelectOverlay = new SoloModSelectOverlay(); [Cached] @@ -30,15 +31,14 @@ public partial class SongSelectV2 : OsuScreen public override bool ShowFooter => true; + [Resolved] + private OsuLogo? logo { get; set; } + [BackgroundDependencyLoader] private void load() { AddRangeInternal(new Drawable[] { - new PopoverContainer - { - RelativeSizeAxes = Axes.Both, - }, modSelectOverlay, }); } @@ -50,6 +50,17 @@ private void load() new ScreenFooterButtonOptions(), }; + protected override void LoadComplete() + { + base.LoadComplete(); + + modSelectOverlay.State.BindValueChanged(v => + { + logo?.ScaleTo(v.NewValue == Visibility.Visible ? 0f : logo_scale, 400, Easing.OutQuint) + .FadeTo(v.NewValue == Visibility.Visible ? 0f : 1f, 200, Easing.OutQuint); + }, true); + } + public override void OnEntering(ScreenTransitionEvent e) { this.FadeIn(); @@ -74,17 +85,6 @@ public override bool OnExiting(ScreenExitEvent e) return base.OnExiting(e); } - public override bool OnBackButton() - { - if (modSelectOverlay.State.Value == Visibility.Visible) - { - modSelectOverlay.Hide(); - return true; - } - - return false; - } - protected override void LogoArriving(OsuLogo logo, bool resuming) { base.LogoArriving(logo, resuming); @@ -99,7 +99,7 @@ protected override void LogoArriving(OsuLogo logo, bool resuming) } logo.FadeIn(240, Easing.OutQuint); - logo.ScaleTo(0.4f, 240, Easing.OutQuint); + logo.ScaleTo(logo_scale, 240, Easing.OutQuint); logo.Action = () => { @@ -122,14 +122,9 @@ protected override void LogoExiting(OsuLogo logo) logo.FadeOut(120, Easing.Out); } - private partial class SoloModSelectOverlay : ModSelectOverlay + private partial class SoloModSelectOverlay : UserModSelectOverlay { protected override bool ShowPresets => true; - - public SoloModSelectOverlay() - : base(OverlayColourScheme.Aquamarine) - { - } } private partial class PlayerLoaderV2 : PlayerLoader From 48bf3f1385e6b99fd5bd0e8104d40487b22c781a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jun 2024 08:38:43 +0300 Subject: [PATCH 002/266] Migrate mod select overlay footer content --- .../TestSceneModSelectOverlay.cs | 39 ++-- .../Overlays/Mods/ModSelectFooterContent.cs | 177 ++++++++++++++++++ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 165 ++-------------- .../OnlinePlay/FreeModSelectOverlay.cs | 41 +++- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- .../Tests/Visual/Gameplay/ScoringTestScene.cs | 1 - 7 files changed, 257 insertions(+), 170 deletions(-) create mode 100644 osu.Game/Overlays/Mods/ModSelectFooterContent.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index dedad3a40ac7..8b79320ffb8e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -24,6 +24,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Taiko.Mods; +using osu.Game.Screens.Footer; using osu.Game.Tests.Mods; using osuTK; using osuTK.Input; @@ -93,12 +94,28 @@ public void SetUpSteps() private void createScreen() { - AddStep("create screen", () => Child = modSelectOverlay = new TestModSelectOverlay + AddStep("create screen", () => { - RelativeSizeAxes = Axes.Both, - State = { Value = Visibility.Visible }, - Beatmap = Beatmap.Value, - SelectedMods = { BindTarget = SelectedMods } + var receptor = new ScreenFooter.BackReceptor(); + var footer = new ScreenFooter(receptor); + + Child = new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new[] { (typeof(ScreenFooter), (object)footer) }, + Children = new Drawable[] + { + receptor, + modSelectOverlay = new TestModSelectOverlay + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible }, + Beatmap = { Value = Beatmap.Value }, + SelectedMods = { BindTarget = SelectedMods }, + }, + footer, + } + }; }); waitForColumnLoad(); } @@ -119,7 +136,7 @@ public void TestPreexistingSelection() AddAssert("mod multiplier correct", () => { double multiplier = SelectedMods.Value.Aggregate(1d, (m, mod) => m * mod.ScoreMultiplier); - return Precision.AlmostEquals(multiplier, modSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value); + return Precision.AlmostEquals(multiplier, this.ChildrenOfType().Single().ModMultiplier.Value); }); assertCustomisationToggleState(disabled: false, active: false); AddAssert("setting items created", () => modSelectOverlay.ChildrenOfType().Any()); @@ -134,7 +151,7 @@ public void TestExternalSelection() AddAssert("mod multiplier correct", () => { double multiplier = SelectedMods.Value.Aggregate(1d, (m, mod) => m * mod.ScoreMultiplier); - return Precision.AlmostEquals(multiplier, modSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value); + return Precision.AlmostEquals(multiplier, this.ChildrenOfType().Single().ModMultiplier.Value); }); assertCustomisationToggleState(disabled: false, active: false); AddAssert("setting items created", () => modSelectOverlay.ChildrenOfType().Any()); @@ -756,7 +773,7 @@ public void TestCloseViaBackButton() AddStep("click back button", () => { - InputManager.MoveMouseTo(modSelectOverlay.BackButton); + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); InputManager.Click(MouseButton.Left); }); AddAssert("mod select hidden", () => modSelectOverlay.State.Value == Visibility.Hidden); @@ -884,7 +901,7 @@ public void TestModMultiplierUpdates() InputManager.Click(MouseButton.Left); }); AddAssert("difficulty multiplier display shows correct value", - () => modSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(0.1).Within(Precision.DOUBLE_EPSILON)); + () => this.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(0.1).Within(Precision.DOUBLE_EPSILON)); // this is highly unorthodox in a test, but because the `ModSettingChangeTracker` machinery heavily leans on events and object disposal and re-creation, // it is instrumental in the reproduction of the failure scenario that this test is supposed to cover. @@ -894,7 +911,7 @@ public void TestModMultiplierUpdates() AddStep("reset half time speed to default", () => modSelectOverlay.ChildrenOfType().Single() .ChildrenOfType>().Single().TriggerClick()); AddUntilStep("difficulty multiplier display shows correct value", - () => modSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(0.3).Within(Precision.DOUBLE_EPSILON)); + () => this.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(0.3).Within(Precision.DOUBLE_EPSILON)); } [Test] @@ -1014,8 +1031,6 @@ private ModPanel getPanelForMod(Type modType) private partial class TestModSelectOverlay : UserModSelectOverlay { protected override bool ShowPresets => true; - - public new ShearedButton BackButton => base.BackButton; } private class TestUnimplementedMod : Mod diff --git a/osu.Game/Overlays/Mods/ModSelectFooterContent.cs b/osu.Game/Overlays/Mods/ModSelectFooterContent.cs new file mode 100644 index 000000000000..146b8e4ebe92 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModSelectFooterContent.cs @@ -0,0 +1,177 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Mods; +using osuTK; + +namespace osu.Game.Overlays.Mods +{ + public partial class ModSelectFooterContent : VisibilityContainer + { + private readonly ModSelectOverlay overlay; + + private RankingInformationDisplay? rankingInformationDisplay; + private BeatmapAttributesDisplay? beatmapAttributesDisplay; + private FillFlowContainer buttonFlow = null!; + private FillFlowContainer contentFlow = null!; + + public DeselectAllModsButton? DeselectAllModsButton { get; set; } + + public readonly IBindable Beatmap = new Bindable(); + public readonly IBindable> ActiveMods = new Bindable>(); + + /// + /// Whether the effects (on score multiplier, on or beatmap difficulty) of the current selected set of mods should be shown. + /// + protected virtual bool ShowModEffects => true; + + /// + /// Whether the ranking information and beatmap attributes displays are stacked vertically due to small space. + /// + public bool DisplaysStackedVertically { get; private set; } + + public ModSelectFooterContent(ModSelectOverlay overlay) + { + this.overlay = overlay; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = buttonFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Padding = new MarginPadding { Horizontal = 20 }, + Spacing = new Vector2(10), + ChildrenEnumerable = CreateButtons(), + }; + + if (ShowModEffects) + { + AddInternal(contentFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(30, 10), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Margin = new MarginPadding { Horizontal = 20 }, + Children = new Drawable[] + { + rankingInformationDisplay = new RankingInformationDisplay + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight + }, + beatmapAttributesDisplay = new BeatmapAttributesDisplay + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + BeatmapInfo = { Value = Beatmap.Value?.BeatmapInfo }, + }, + } + }); + } + } + + private ModSettingChangeTracker? modSettingChangeTracker; + + protected override void LoadComplete() + { + base.LoadComplete(); + + Beatmap.BindValueChanged(b => + { + if (beatmapAttributesDisplay != null) + beatmapAttributesDisplay.BeatmapInfo.Value = b.NewValue?.BeatmapInfo; + }, true); + + ActiveMods.BindValueChanged(m => + { + updateInformation(); + + modSettingChangeTracker?.Dispose(); + + // Importantly, use ActiveMods.Value here (and not the ValueChanged NewValue) as the latter can + // potentially be stale, due to complexities in the way change trackers work. + // + // See https://github.com/ppy/osu/pull/23284#issuecomment-1529056988 + modSettingChangeTracker = new ModSettingChangeTracker(ActiveMods.Value); + modSettingChangeTracker.SettingChanged += _ => updateInformation(); + }, true); + } + + private void updateInformation() + { + if (rankingInformationDisplay != null) + { + double multiplier = 1.0; + + foreach (var mod in ActiveMods.Value) + multiplier *= mod.ScoreMultiplier; + + rankingInformationDisplay.ModMultiplier.Value = multiplier; + rankingInformationDisplay.Ranked.Value = ActiveMods.Value.All(m => m.Ranked); + } + + if (beatmapAttributesDisplay != null) + beatmapAttributesDisplay.Mods.Value = ActiveMods.Value; + } + + protected override void Update() + { + base.Update(); + + if (beatmapAttributesDisplay != null) + { + float rightEdgeOfLastButton = buttonFlow[^1].ScreenSpaceDrawQuad.TopRight.X; + + // this is cheating a bit; the 640 value is hardcoded based on how wide the expanded panel _generally_ is. + // due to the transition applied, the raw screenspace quad of the panel cannot be used, as it will trigger an ugly feedback cycle of expanding and collapsing. + float projectedLeftEdgeOfExpandedBeatmapAttributesDisplay = buttonFlow.ToScreenSpace(buttonFlow.DrawSize - new Vector2(640, 0)).X; + + DisplaysStackedVertically = rightEdgeOfLastButton > projectedLeftEdgeOfExpandedBeatmapAttributesDisplay; + + // only update preview panel's collapsed state after we are fully visible, to ensure all the buttons are where we expect them to be. + if (Alpha == 1) + beatmapAttributesDisplay.Collapsed.Value = DisplaysStackedVertically; + + contentFlow.LayoutDuration = 200; + contentFlow.LayoutEasing = Easing.OutQuint; + contentFlow.Direction = DisplaysStackedVertically ? FillDirection.Vertical : FillDirection.Horizontal; + } + } + + protected virtual IEnumerable CreateButtons() => new[] + { + DeselectAllModsButton = new DeselectAllModsButton(overlay) + }; + + protected override void PopIn() + { + this.MoveToY(0, 400, Easing.OutQuint) + .FadeIn(400, Easing.OutQuint); + } + + protected override void PopOut() + { + this.MoveToY(-20f, 200, Easing.OutQuint) + .FadeOut(200, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index b0d58480db78..40be4e08a628 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -27,6 +27,7 @@ using osu.Game.Input.Bindings; using osu.Game.Localisation; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Footer; using osu.Game.Utils; using osuTK; using osuTK.Graphics; @@ -87,11 +88,6 @@ public string SearchTerm public ShearedSearchTextBox SearchTextBox { get; private set; } = null!; - /// - /// Whether the effects (on score multiplier, on or beatmap difficulty) of the current selected set of mods should be shown. - /// - protected virtual bool ShowModEffects => true; - /// /// Whether per-mod customisation controls are visible. /// @@ -108,11 +104,6 @@ public string SearchTerm protected virtual IReadOnlyList ComputeActiveMods() => SelectedMods.Value; - protected virtual IEnumerable CreateFooterButtons() - { - yield return deselectAllModsButton = new DeselectAllModsButton(this); - } - private readonly Bindable>> globalAvailableMods = new Bindable>>(); public IEnumerable AllAvailableMods => AvailableMods.Value.SelectMany(pair => pair.Value); @@ -121,34 +112,18 @@ protected virtual IEnumerable CreateFooterButtons() private ColumnScrollContainer columnScroll = null!; private ColumnFlowContainer columnFlow = null!; - private FillFlowContainer footerButtonFlow = null!; - private FillFlowContainer footerContentFlow = null!; - private DeselectAllModsButton deselectAllModsButton = null!; private Container aboveColumnsContent = null!; - private RankingInformationDisplay? rankingInformationDisplay; - private BeatmapAttributesDisplay? beatmapAttributesDisplay; private ModCustomisationPanel customisationPanel = null!; - protected ShearedButton BackButton { get; private set; } = null!; - protected SelectAllModsButton? SelectAllModsButton { get; set; } + protected virtual SelectAllModsButton? SelectAllModsButton => null; private Sample? columnAppearSample; - private WorkingBeatmap? beatmap; - - public WorkingBeatmap? Beatmap - { - get => beatmap; - set - { - if (beatmap == value) return; + public readonly Bindable Beatmap = new Bindable(); - beatmap = value; - if (IsLoaded && beatmapAttributesDisplay != null) - beatmapAttributesDisplay.BeatmapInfo.Value = beatmap?.BeatmapInfo; - } - } + [Resolved] + private ScreenFooter? footer { get; set; } protected ModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Green) : base(colourScheme) @@ -226,59 +201,6 @@ private void load(OsuGameBase game, OsuColour colours, AudioManager audio, OsuCo } }); - FooterContent.Add(footerButtonFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Padding = new MarginPadding - { - Vertical = PADDING, - Horizontal = 70 - }, - Spacing = new Vector2(10), - ChildrenEnumerable = CreateFooterButtons().Prepend(BackButton = new ShearedButton(BUTTON_WIDTH) - { - Text = CommonStrings.Back, - Action = Hide, - DarkerColour = colours.Pink2, - LighterColour = colours.Pink1 - }) - }); - - if (ShowModEffects) - { - FooterContent.Add(footerContentFlow = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(30, 10), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Margin = new MarginPadding - { - Vertical = PADDING, - Horizontal = 20 - }, - Children = new Drawable[] - { - rankingInformationDisplay = new RankingInformationDisplay - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight - }, - beatmapAttributesDisplay = new BeatmapAttributesDisplay - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - BeatmapInfo = { Value = Beatmap?.BeatmapInfo }, - }, - } - }); - } - globalAvailableMods.BindTo(game.AvailableMods); textSearchStartsActive = configManager.GetBindable(OsuSetting.ModSelectTextSearchStartsActive); @@ -292,8 +214,6 @@ public override void Hide() SearchTextBox.Current.Value = string.Empty; } - private ModSettingChangeTracker? modSettingChangeTracker; - protected override void LoadComplete() { // this is called before base call so that the mod state is populated early, and the transition in `PopIn()` can play out properly. @@ -316,23 +236,6 @@ protected override void LoadComplete() ActiveMods.Value = ComputeActiveMods(); }, true); - ActiveMods.BindValueChanged(_ => - { - updateOverlayInformation(); - - modSettingChangeTracker?.Dispose(); - - if (AllowCustomisation) - { - // Importantly, use ActiveMods.Value here (and not the ValueChanged NewValue) as the latter can - // potentially be stale, due to complexities in the way change trackers work. - // - // See https://github.com/ppy/osu/pull/23284#issuecomment-1529056988 - modSettingChangeTracker = new ModSettingChangeTracker(ActiveMods.Value); - modSettingChangeTracker.SettingChanged += _ => updateOverlayInformation(); - } - }, true); - customisationPanel.Expanded.BindValueChanged(_ => updateCustomisationVisualState(), true); SearchTextBox.Current.BindValueChanged(query => @@ -350,6 +253,16 @@ protected override void LoadComplete() }); } + private ModSelectFooterContent? currentFooterContent; + + public override bool UseNewFooter => true; + + public override Drawable CreateFooterContent() => currentFooterContent = new ModSelectFooterContent(this) + { + Beatmap = { BindTarget = Beatmap }, + ActiveMods = { BindTarget = ActiveMods }, + }; + private static readonly LocalisableString input_search_placeholder = Resources.Localisation.Web.CommonStrings.InputSearch; private static readonly LocalisableString tab_to_search_placeholder = ModSelectOverlayStrings.TabToSearch; @@ -358,26 +271,7 @@ protected override void Update() base.Update(); SearchTextBox.PlaceholderText = SearchTextBox.HasFocus ? input_search_placeholder : tab_to_search_placeholder; - - if (beatmapAttributesDisplay != null) - { - float rightEdgeOfLastButton = footerButtonFlow[^1].ScreenSpaceDrawQuad.TopRight.X; - - // this is cheating a bit; the 640 value is hardcoded based on how wide the expanded panel _generally_ is. - // due to the transition applied, the raw screenspace quad of the panel cannot be used, as it will trigger an ugly feedback cycle of expanding and collapsing. - float projectedLeftEdgeOfExpandedBeatmapAttributesDisplay = footerButtonFlow.ToScreenSpace(footerButtonFlow.DrawSize - new Vector2(640, 0)).X; - - bool screenIsntWideEnough = rightEdgeOfLastButton > projectedLeftEdgeOfExpandedBeatmapAttributesDisplay; - - // only update preview panel's collapsed state after we are fully visible, to ensure all the buttons are where we expect them to be. - if (Alpha == 1) - beatmapAttributesDisplay.Collapsed.Value = screenIsntWideEnough; - - footerContentFlow.LayoutDuration = 200; - footerContentFlow.LayoutEasing = Easing.OutQuint; - footerContentFlow.Direction = screenIsntWideEnough ? FillDirection.Vertical : FillDirection.Horizontal; - aboveColumnsContent.Padding = aboveColumnsContent.Padding with { Bottom = screenIsntWideEnough ? 70f : 15f }; - } + aboveColumnsContent.Padding = aboveColumnsContent.Padding with { Bottom = currentFooterContent?.DisplaysStackedVertically == true ? 75f : 15f }; } /// @@ -455,27 +349,6 @@ private void filterMods() modState.ValidForSelection.Value = modState.Mod.Type != ModType.System && modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod); } - /// - /// Updates any information displayed on the overlay regarding the effects of the active mods. - /// This reads from instead of . - /// - private void updateOverlayInformation() - { - if (rankingInformationDisplay != null) - { - double multiplier = 1.0; - - foreach (var mod in ActiveMods.Value) - multiplier *= mod.ScoreMultiplier; - - rankingInformationDisplay.ModMultiplier.Value = multiplier; - rankingInformationDisplay.Ranked.Value = ActiveMods.Value.All(m => m.Ranked); - } - - if (beatmapAttributesDisplay != null) - beatmapAttributesDisplay.Mods.Value = ActiveMods.Value; - } - private void updateCustomisation() { if (!AllowCustomisation) @@ -701,7 +574,7 @@ public override bool OnPressed(KeyBindingPressEvent e) { if (!SearchTextBox.HasFocus && !customisationPanel.Expanded.Value) { - deselectAllModsButton.TriggerClick(); + currentFooterContent?.DeselectAllModsButton?.TriggerClick(); return true; } @@ -732,7 +605,7 @@ public override bool OnPressed(KeyBindingPressEvent e) return base.OnPressed(e); - void hideOverlay() => BackButton.TriggerClick(); + void hideOverlay() => footer?.BackButton.TriggerClick(); } /// @@ -740,7 +613,7 @@ public override bool OnPressed(KeyBindingPressEvent e) /// This is handled locally here due to conflicts in input handling between the search text box and the select all mods button. /// Attempting to handle this action locally in both places leads to a possible scenario /// wherein activating the "select all" platform binding will both select all text in the search box and select all mods. - /// > + /// public bool OnPressed(KeyBindingPressEvent e) { if (e.Repeat || e.Action != PlatformAction.SelectAll || SelectAllModsButton == null) diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 7f090aca5718..0ed45161f24e 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -14,8 +14,6 @@ namespace osu.Game.Screens.OnlinePlay { public partial class FreeModSelectOverlay : ModSelectOverlay { - protected override bool ShowModEffects => false; - protected override bool AllowCustomisation => false; public new Func IsValidMod @@ -24,6 +22,10 @@ public partial class FreeModSelectOverlay : ModSelectOverlay set => base.IsValidMod = m => m.UserPlayable && value.Invoke(m); } + private FreeModSelectFooterContent? currentFooterContent; + + protected override SelectAllModsButton? SelectAllModsButton => currentFooterContent?.SelectAllModsButton; + public FreeModSelectOverlay() : base(OverlayColourScheme.Plum) { @@ -32,12 +34,33 @@ public FreeModSelectOverlay() protected override ModColumn CreateModColumn(ModType modType) => new ModColumn(modType, true); - protected override IEnumerable CreateFooterButtons() - => base.CreateFooterButtons() - .Prepend(SelectAllModsButton = new SelectAllModsButton(this) - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - }); + public override Drawable CreateFooterContent() => currentFooterContent = new FreeModSelectFooterContent(this) + { + Beatmap = { BindTarget = Beatmap }, + ActiveMods = { BindTarget = ActiveMods }, + }; + + private partial class FreeModSelectFooterContent : ModSelectFooterContent + { + private readonly FreeModSelectOverlay overlay; + + protected override bool ShowModEffects => false; + + public SelectAllModsButton? SelectAllModsButton; + + public FreeModSelectFooterContent(FreeModSelectOverlay overlay) + : base(overlay) + { + this.overlay = overlay; + } + + protected override IEnumerable CreateButtons() + => base.CreateButtons() + .Prepend(SelectAllModsButton = new SelectAllModsButton(overlay) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }); + } } } diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 4eb092d08b50..78ab8cfa6cd2 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -453,7 +453,7 @@ private void updateWorkingBeatmap() // Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info var localBeatmap = beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineID == beatmap.OnlineID); - UserModsSelectOverlay.Beatmap = Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); + UserModsSelectOverlay.Beatmap.Value = Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); } protected virtual void UpdateMods() diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index df7eabfd2182..ecf821000243 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -851,7 +851,7 @@ private void updateComponentFromBeatmap(WorkingBeatmap beatmap) BeatmapDetails.Beatmap = beatmap; - ModSelect.Beatmap = beatmap; + ModSelect.Beatmap.Value = beatmap; advancedStats.BeatmapInfo = beatmap.BeatmapInfo; diff --git a/osu.Game/Tests/Visual/Gameplay/ScoringTestScene.cs b/osu.Game/Tests/Visual/Gameplay/ScoringTestScene.cs index e7053e42024b..6908f7f1b480 100644 --- a/osu.Game/Tests/Visual/Gameplay/ScoringTestScene.cs +++ b/osu.Game/Tests/Visual/Gameplay/ScoringTestScene.cs @@ -658,7 +658,6 @@ private void updateState() private partial class TestModSelectOverlay : UserModSelectOverlay { - protected override bool ShowModEffects => true; protected override bool ShowPresets => false; public TestModSelectOverlay() From 5dd822ea3899eafde0b2beee191243e65378928a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jun 2024 09:39:09 +0300 Subject: [PATCH 003/266] Migrate first-run setup overlay footer content --- .../TestSceneFirstRunSetupOverlay.cs | 62 ++++--- .../Overlays/FirstRunSetup/ScreenUIScale.cs | 8 +- osu.Game/Overlays/FirstRunSetupOverlay.cs | 165 +++++++++--------- 3 files changed, 127 insertions(+), 108 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs index 51da4d87550b..2ca06bf2f461 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs @@ -11,6 +11,8 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Testing; @@ -20,6 +22,7 @@ using osu.Game.Overlays.FirstRunSetup; using osu.Game.Overlays.Notifications; using osu.Game.Screens; +using osu.Game.Screens.Footer; using osuTK; using osuTK.Input; @@ -28,6 +31,7 @@ namespace osu.Game.Tests.Visual.UserInterface public partial class TestSceneFirstRunSetupOverlay : OsuManualInputManagerTestScene { private FirstRunSetupOverlay overlay; + private ScreenFooter footer; private readonly Mock performer = new Mock(); @@ -60,19 +64,16 @@ public void SetUpSteps() .Callback((Notification n) => lastNotification = n); }); - AddStep("add overlay", () => - { - Child = overlay = new FirstRunSetupOverlay - { - State = { Value = Visibility.Visible } - }; - }); + createOverlay(); + + AddStep("show overlay", () => overlay.Show()); } [Test] public void TestBasic() { AddAssert("overlay visible", () => overlay.State.Value == Visibility.Visible); + AddAssert("footer visible", () => footer.State.Value == Visibility.Visible); } [Test] @@ -82,16 +83,13 @@ public void TestDoesntOpenOnSecondRun() AddUntilStep("step through", () => { - if (overlay.CurrentScreen?.IsLoaded != false) overlay.NextButton.TriggerClick(); + if (overlay.CurrentScreen?.IsLoaded != false) overlay.NextButton.AsNonNull().TriggerClick(); return overlay.State.Value == Visibility.Hidden; }); AddAssert("first run false", () => !LocalConfig.Get(OsuSetting.ShowFirstRunSetup)); - AddStep("add overlay", () => - { - Child = overlay = new FirstRunSetupOverlay(); - }); + createOverlay(); AddWaitStep("wait some", 5); @@ -109,7 +107,7 @@ public void TestOverlayRunsToFinish(bool keyboard) if (keyboard) InputManager.Key(Key.Enter); else - overlay.NextButton.TriggerClick(); + overlay.NextButton.AsNonNull().TriggerClick(); } return overlay.State.Value == Visibility.Hidden; @@ -128,11 +126,9 @@ public void TestOverlayRunsToFinish(bool keyboard) [TestCase(true)] public void TestBackButton(bool keyboard) { - AddAssert("back button disabled", () => !overlay.BackButton.Enabled.Value); - AddUntilStep("step to last", () => { - var nextButton = overlay.NextButton; + var nextButton = overlay.NextButton.AsNonNull(); if (overlay.CurrentScreen?.IsLoaded != false) nextButton.TriggerClick(); @@ -142,24 +138,29 @@ public void TestBackButton(bool keyboard) AddUntilStep("step back to start", () => { - if (overlay.CurrentScreen?.IsLoaded != false) + if (overlay.CurrentScreen?.IsLoaded != false && !(overlay.CurrentScreen is ScreenWelcome)) { if (keyboard) InputManager.Key(Key.Escape); else - overlay.BackButton.TriggerClick(); + footer.BackButton.TriggerClick(); } return overlay.CurrentScreen is ScreenWelcome; }); - AddAssert("back button disabled", () => !overlay.BackButton.Enabled.Value); + AddAssert("overlay not dismissed", () => overlay.State.Value == Visibility.Visible); if (keyboard) { AddStep("exit via keyboard", () => InputManager.Key(Key.Escape)); AddAssert("overlay dismissed", () => overlay.State.Value == Visibility.Hidden); } + else + { + AddStep("press back button", () => footer.BackButton.TriggerClick()); + AddAssert("overlay dismissed", () => overlay.State.Value == Visibility.Hidden); + } } [Test] @@ -185,7 +186,7 @@ public void TestClickAwayToExit() [Test] public void TestResumeViaNotification() { - AddStep("step to next", () => overlay.NextButton.TriggerClick()); + AddStep("step to next", () => overlay.NextButton.AsNonNull().TriggerClick()); AddAssert("is at known screen", () => overlay.CurrentScreen is ScreenUIScale); @@ -200,6 +201,27 @@ public void TestResumeViaNotification() AddAssert("is resumed", () => overlay.CurrentScreen is ScreenUIScale); } + private void createOverlay() + { + AddStep("add overlay", () => + { + var receptor = new ScreenFooter.BackReceptor(); + footer = new ScreenFooter(receptor); + + Child = new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new[] { (typeof(ScreenFooter), (object)footer) }, + Children = new Drawable[] + { + receptor, + overlay = new FirstRunSetupOverlay(), + footer, + } + }; + }); + } + // interface mocks break hot reload, mocking this stub implementation instead works around it. // see: https://github.com/moq/moq4/issues/1252 [UsedImplicitly] diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs index 02f0ad95063f..d0eefa55c543 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs @@ -23,6 +23,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens; +using osu.Game.Screens.Footer; using osu.Game.Screens.Menu; using osu.Game.Screens.Select; using osu.Game.Tests.Visual; @@ -153,6 +154,7 @@ private void load(AudioManager audio, TextureStore textures, RulesetStore rulese OsuScreenStack stack; OsuLogo logo; + ScreenFooter footer; Padding = new MarginPadding(5); @@ -166,7 +168,8 @@ private void load(AudioManager audio, TextureStore textures, RulesetStore rulese { RelativePositionAxes = Axes.Both, Position = new Vector2(0.5f), - }) + }), + (typeof(ScreenFooter), footer = new ScreenFooter()), }, RelativeSizeAxes = Axes.Both, Children = new Drawable[] @@ -178,7 +181,8 @@ private void load(AudioManager audio, TextureStore textures, RulesetStore rulese Children = new Drawable[] { stack = new OsuScreenStack(), - logo + footer, + logo, }, }, } diff --git a/osu.Game/Overlays/FirstRunSetupOverlay.cs b/osu.Game/Overlays/FirstRunSetupOverlay.cs index f2fdaefbb4bf..bc11e5d0d219 100644 --- a/osu.Game/Overlays/FirstRunSetupOverlay.cs +++ b/osu.Game/Overlays/FirstRunSetupOverlay.cs @@ -26,6 +26,7 @@ using osu.Game.Overlays.Mods; using osu.Game.Overlays.Notifications; using osu.Game.Screens; +using osu.Game.Screens.Footer; using osu.Game.Screens.Menu; namespace osu.Game.Overlays @@ -44,8 +45,7 @@ public partial class FirstRunSetupOverlay : ShearedOverlayContainer private ScreenStack? stack; - public ShearedButton NextButton = null!; - public ShearedButton BackButton = null!; + public ShearedButton? NextButton => currentFooterContent?.NextButton; private readonly Bindable showFirstRunSetup = new Bindable(); @@ -90,7 +90,7 @@ private void load(OsuColour colours, LegacyImportManager? legacyImportManager) Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Bottom = 20, }, + Padding = new MarginPadding { Bottom = 20 }, Child = new GridContainer { Anchor = Anchor.Centre, @@ -134,51 +134,6 @@ private void load(OsuColour colours, LegacyImportManager? legacyImportManager) } }, }); - - FooterContent.Add(new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Vertical = PADDING }, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 10), - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 10), - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new[] - { - Empty(), - BackButton = new ShearedButton(300) - { - Text = CommonStrings.Back, - Action = showPreviousStep, - Enabled = { Value = false }, - DarkerColour = colours.Pink2, - LighterColour = colours.Pink1, - }, - NextButton = new ShearedButton(0) - { - RelativeSizeAxes = Axes.X, - Width = 1, - Text = FirstRunSetupOverlayStrings.GetStarted, - DarkerColour = ColourProvider.Colour2, - LighterColour = ColourProvider.Colour1, - Action = showNextStep - }, - Empty(), - }, - } - }); } protected override void LoadComplete() @@ -190,6 +145,32 @@ protected override void LoadComplete() if (showFirstRunSetup.Value) Show(); } + [Resolved] + private ScreenFooter footer { get; set; } = null!; + + private FirstRunSetupFooterContent? currentFooterContent; + + public override bool UseNewFooter => true; + + public override Drawable CreateFooterContent() => currentFooterContent = new FirstRunSetupFooterContent + { + ShowNextStep = showNextStep, + }; + + public override bool OnBackButton() + { + if (currentStepIndex == 0) + return false; + + Debug.Assert(stack != null); + + stack.CurrentScreen.Exit(); + currentStepIndex--; + + updateButtons(); + return true; + } + public override bool OnPressed(KeyBindingPressEvent e) { if (!e.Repeat) @@ -197,19 +178,12 @@ public override bool OnPressed(KeyBindingPressEvent e) switch (e.Action) { case GlobalAction.Select: - NextButton.TriggerClick(); + currentFooterContent?.NextButton.TriggerClick(); return true; case GlobalAction.Back: - if (BackButton.Enabled.Value) - { - BackButton.TriggerClick(); - return true; - } - - // If back button is disabled, we are at the first step. - // The base call will handle dismissal of the overlay. - break; + footer.BackButton.TriggerClick(); + return false; } } @@ -279,19 +253,6 @@ private void showFirstStep() showNextStep(); } - private void showPreviousStep() - { - if (currentStepIndex == 0) - return; - - Debug.Assert(stack != null); - - stack.CurrentScreen.Exit(); - currentStepIndex--; - - updateButtons(); - } - private void showNextStep() { Debug.Assert(currentStepIndex != null); @@ -322,29 +283,61 @@ private void showNextStep() updateButtons(); } - private void updateButtons() + private void updateButtons() => currentFooterContent?.UpdateButtons(currentStepIndex, steps); + + private partial class FirstRunSetupFooterContent : VisibilityContainer { - BackButton.Enabled.Value = currentStepIndex > 0; - NextButton.Enabled.Value = currentStepIndex != null; + public ShearedButton NextButton { get; private set; } = null!; - if (currentStepIndex == null) - return; + public Action? ShowNextStep; + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + RelativeSizeAxes = Axes.Both; - bool isFirstStep = currentStepIndex == 0; - bool isLastStep = currentStepIndex == steps.Count - 1; + InternalChild = NextButton = new ShearedButton(0) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Margin = new MarginPadding { Right = 12f }, + RelativeSizeAxes = Axes.X, + Width = 1, + Text = FirstRunSetupOverlayStrings.GetStarted, + DarkerColour = colourProvider.Colour2, + LighterColour = colourProvider.Colour1, + Action = () => ShowNextStep?.Invoke(), + }; + } - if (isFirstStep) + public void UpdateButtons(int? currentStep, IReadOnlyList steps) { - BackButton.Text = CommonStrings.Back; - NextButton.Text = FirstRunSetupOverlayStrings.GetStarted; + NextButton.Enabled.Value = currentStep != null; + + if (currentStep == null) + return; + + bool isFirstStep = currentStep == 0; + bool isLastStep = currentStep == steps.Count - 1; + + if (isFirstStep) + NextButton.Text = FirstRunSetupOverlayStrings.GetStarted; + else + { + NextButton.Text = isLastStep + ? CommonStrings.Finish + : LocalisableString.Interpolate($@"{CommonStrings.Next} ({steps[currentStep.Value + 1].GetLocalisableDescription()})"); + } } - else + + protected override void PopIn() { - BackButton.Text = LocalisableString.Interpolate($@"{CommonStrings.Back} ({steps[currentStepIndex.Value - 1].GetLocalisableDescription()})"); + this.FadeIn(); + } - NextButton.Text = isLastStep - ? CommonStrings.Finish - : LocalisableString.Interpolate($@"{CommonStrings.Next} ({steps[currentStepIndex.Value + 1].GetLocalisableDescription()})"); + protected override void PopOut() + { + this.Delay(400).FadeOut(); } } } From e57a0029f16619ed857ed400f5e3695db2f1d3a9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 30 Jun 2024 04:22:59 +0300 Subject: [PATCH 004/266] Remove local footer from `ShearedOverlayContainer` --- .../UserInterface/TestSceneScreenFooter.cs | 2 - osu.Game/Overlays/FirstRunSetupOverlay.cs | 2 - osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 - .../Overlays/Mods/ShearedOverlayContainer.cs | 47 ++----------------- 4 files changed, 3 insertions(+), 50 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs index 70c3664b9a29..de2026e538e4 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs @@ -185,8 +185,6 @@ private partial class TestModSelectOverlay : UserModSelectOverlay private partial class TestShearedOverlayContainer : ShearedOverlayContainer { - public override bool UseNewFooter => true; - public TestShearedOverlayContainer() : base(OverlayColourScheme.Orange) { diff --git a/osu.Game/Overlays/FirstRunSetupOverlay.cs b/osu.Game/Overlays/FirstRunSetupOverlay.cs index bc11e5d0d219..47f53a3fa659 100644 --- a/osu.Game/Overlays/FirstRunSetupOverlay.cs +++ b/osu.Game/Overlays/FirstRunSetupOverlay.cs @@ -150,8 +150,6 @@ protected override void LoadComplete() private FirstRunSetupFooterContent? currentFooterContent; - public override bool UseNewFooter => true; - public override Drawable CreateFooterContent() => currentFooterContent = new FirstRunSetupFooterContent { ShowNextStep = showNextStep, diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 40be4e08a628..8e18c39cc2d7 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -255,8 +255,6 @@ protected override void LoadComplete() private ModSelectFooterContent? currentFooterContent; - public override bool UseNewFooter => true; - public override Drawable CreateFooterContent() => currentFooterContent = new ModSelectFooterContent(this) { Beatmap = { BindTarget = Beatmap }, diff --git a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs index b5435e7e5851..1ccf274d0d70 100644 --- a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs +++ b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Footer; @@ -30,17 +29,9 @@ public abstract partial class ShearedOverlayContainer : OsuFocusedOverlayContain /// protected ShearedOverlayHeader Header { get; private set; } = null!; - /// - /// The overlay's footer. - /// - protected Container Footer { get; private set; } - [Resolved] private ScreenFooter? footer { get; set; } - // todo: very temporary property that will be removed once ModSelectOverlay and FirstRunSetupOverlay are updated to use new footer. - public virtual bool UseNewFooter => false; - /// /// A container containing all content, including the header and footer. /// May be used for overlay-wide animations. @@ -52,11 +43,6 @@ public abstract partial class ShearedOverlayContainer : OsuFocusedOverlayContain /// protected Container MainAreaContent { get; private set; } = null!; - /// - /// A container for content that is to be displayed inside the footer. - /// - protected Container FooterContent { get; private set; } - protected override bool StartHidden => true; protected override bool BlockNonPositionalInput => true; @@ -75,8 +61,6 @@ protected ShearedOverlayContainer(OverlayColourScheme colourScheme) [BackgroundDependencyLoader] private void load() { - const float footer_height = ScreenFooter.HEIGHT; - Child = TopLevelContent = new Container { RelativeSizeAxes = Axes.Both, @@ -100,30 +84,9 @@ private void load() Padding = new MarginPadding { Top = ShearedOverlayHeader.HEIGHT, - Bottom = footer_height + PADDING, + Bottom = ScreenFooter.HEIGHT + PADDING, } }, - Footer = new InputBlockingContainer - { - RelativeSizeAxes = Axes.X, - Depth = float.MinValue, - Height = footer_height, - Margin = new MarginPadding { Top = PADDING }, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourProvider.Background5 - }, - FooterContent = new Container - { - RelativeSizeAxes = Axes.Both, - }, - } - } } }; } @@ -160,7 +123,7 @@ protected override void PopIn() Header.MoveToY(0, fade_in_duration, Easing.OutQuint); - if (UseNewFooter && footer != null) + if (footer != null) { footer.SetOverlayContent(this); @@ -170,8 +133,6 @@ protected override void PopIn() hideFooterOnPopOut = true; } } - else - Footer.MoveToY(0, fade_in_duration, Easing.OutQuint); } protected override void PopOut() @@ -183,7 +144,7 @@ protected override void PopOut() Header.MoveToY(-Header.DrawHeight, fade_out_duration, Easing.OutQuint); - if (UseNewFooter && footer != null) + if (footer != null) { footer.ClearOverlayContent(); @@ -193,8 +154,6 @@ protected override void PopOut() hideFooterOnPopOut = false; } } - else - Footer.MoveToY(Footer.DrawHeight, fade_out_duration, Easing.OutQuint); } } } From 58c7d1e7724b6a08b01f4682ccc4684bc311dba0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jun 2024 10:19:22 +0300 Subject: [PATCH 005/266] Bind game-wide mods bindable to mod select overlay in new song select screen --- osu.Game/Screens/SelectV2/SongSelectV2.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Screens/SelectV2/SongSelectV2.cs b/osu.Game/Screens/SelectV2/SongSelectV2.cs index a8730ad80858..2f9667793fe5 100644 --- a/osu.Game/Screens/SelectV2/SongSelectV2.cs +++ b/osu.Game/Screens/SelectV2/SongSelectV2.cs @@ -64,18 +64,29 @@ protected override void LoadComplete() public override void OnEntering(ScreenTransitionEvent e) { this.FadeIn(); + + modSelectOverlay.SelectedMods.BindTo(Mods); + base.OnEntering(e); } public override void OnResuming(ScreenTransitionEvent e) { this.FadeIn(); + + // required due to https://github.com/ppy/osu-framework/issues/3218 + modSelectOverlay.SelectedMods.Disabled = false; + modSelectOverlay.SelectedMods.BindTo(Mods); + base.OnResuming(e); } public override void OnSuspending(ScreenTransitionEvent e) { this.Delay(400).FadeOut(); + + modSelectOverlay.SelectedMods.UnbindFrom(Mods); + base.OnSuspending(e); } From a65af8249c74e32e512e146943687af33555d155 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 30 Jun 2024 07:27:19 +0300 Subject: [PATCH 006/266] Fix first-run setup buttons reset after reopening from dismiss --- osu.Game/Overlays/FirstRunSetupOverlay.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetupOverlay.cs b/osu.Game/Overlays/FirstRunSetupOverlay.cs index 47f53a3fa659..6412297663f4 100644 --- a/osu.Game/Overlays/FirstRunSetupOverlay.cs +++ b/osu.Game/Overlays/FirstRunSetupOverlay.cs @@ -150,10 +150,16 @@ protected override void LoadComplete() private FirstRunSetupFooterContent? currentFooterContent; - public override Drawable CreateFooterContent() => currentFooterContent = new FirstRunSetupFooterContent + public override Drawable CreateFooterContent() { - ShowNextStep = showNextStep, - }; + currentFooterContent = new FirstRunSetupFooterContent + { + ShowNextStep = showNextStep, + }; + + currentFooterContent.OnLoadComplete += _ => updateButtons(); + return currentFooterContent; + } public override bool OnBackButton() { From 50818da166110bbaadf47ad7c27d05dd9c5008ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jul 2024 12:33:00 +0200 Subject: [PATCH 007/266] Ensure timeline ticks aren't hidden by other pieces Addresses https://github.com/ppy/osu/issues/28667. --- .../Compose/Components/BeatDivisorControl.cs | 6 ++--- .../Compose/Components/Timeline/Timeline.cs | 12 +++++++--- .../Timeline/TimelineTickDisplay.cs | 23 +++++++++++++++++-- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index faab5e7f7899..1d8266d610e8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -150,7 +150,7 @@ private void load(OverlayColourProvider colourProvider) { new TextFlowContainer(s => s.Font = s.Font.With(size: 14)) { - Padding = new MarginPadding { Horizontal = 15, Vertical = 8 }, + Padding = new MarginPadding { Horizontal = 15, Vertical = 2 }, Text = "beat snap", RelativeSizeAxes = Axes.X, TextAnchor = Anchor.TopCentre, @@ -159,7 +159,7 @@ private void load(OverlayColourProvider colourProvider) }, RowDimensions = new[] { - new Dimension(GridSizeMode.Absolute, 30), + new Dimension(GridSizeMode.Absolute, 40), new Dimension(GridSizeMode.Absolute, 20), new Dimension(GridSizeMode.Absolute, 15) } @@ -526,7 +526,7 @@ public Tick(int divisor, bool alwaysDisplayed) AlwaysDisplayed = alwaysDisplayed; Divisor = divisor; - Size = new Vector2(6f, 12) * BindableBeatDivisor.GetSize(divisor); + Size = new Vector2(6f, 18) * BindableBeatDivisor.GetSize(divisor); Alpha = alwaysDisplayed ? 1 : 0; InternalChild = new Box { RelativeSizeAxes = Axes.Both }; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index fa9964b104d7..05e44d473760 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Cached] public partial class Timeline : ZoomableScrollContainer, IPositionSnapProvider { - private const float timeline_height = 72; + private const float timeline_height = 80; private const float timeline_expanded_height = 94; private readonly Drawable userContent; @@ -97,6 +97,11 @@ private void load(IBindable beatmap, OsuColour colours, OsuConfi // We don't want the centre marker to scroll AddInternal(centreMarker = new CentreMarker()); + ticks = new TimelineTickDisplay + { + Padding = new MarginPadding { Vertical = 2, }, + }; + AddRange(new Drawable[] { controlPoints = new TimelineControlPointDisplay @@ -104,6 +109,7 @@ private void load(IBindable beatmap, OsuColour colours, OsuConfi RelativeSizeAxes = Axes.X, Height = timeline_expanded_height, }, + ticks, mainContent = new Container { RelativeSizeAxes = Axes.X, @@ -120,7 +126,7 @@ private void load(IBindable beatmap, OsuColour colours, OsuConfi HighColour = colours.BlueDarker, }, centreMarker.CreateProxy(), - ticks = new TimelineTickDisplay(), + ticks.CreateProxy(), new Box { Name = "zero marker", @@ -175,7 +181,7 @@ protected override void LoadComplete() if (visible.NewValue) { this.ResizeHeightTo(timeline_expanded_height, 200, Easing.OutQuint); - mainContent.MoveToY(20, 200, Easing.OutQuint); + mainContent.MoveToY(15, 200, Easing.OutQuint); // delay the fade in else masking looks weird. controlPoints.Delay(180).FadeIn(400, Easing.OutQuint); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index e16c8519e576..8de508785013 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; @@ -41,16 +42,21 @@ public TimelineTickDisplay() RelativeSizeAxes = Axes.Both; } + private readonly BindableBool showTimingChanges = new BindableBool(true); + private readonly Cached tickCache = new Cached(); [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager configManager) { beatDivisor.BindValueChanged(_ => invalidateTicks()); if (changeHandler != null) // currently this is the best way to handle any kind of timing changes. changeHandler.OnStateChange += invalidateTicks; + + configManager.BindWith(OsuSetting.EditorTimelineShowTimingChanges, showTimingChanges); + showTimingChanges.BindValueChanged(_ => invalidateTicks()); } private void invalidateTicks() @@ -142,7 +148,20 @@ private void createTicks() var line = getNextUsableLine(); line.X = xPos; line.Width = PointVisualisation.MAX_WIDTH * size.X; - line.Height = 0.9f * size.Y; + + if (showTimingChanges.Value) + { + line.Anchor = Anchor.BottomLeft; + line.Origin = Anchor.BottomCentre; + line.Height = 0.7f + size.Y * 0.28f; + } + else + { + line.Anchor = Anchor.CentreLeft; + line.Origin = Anchor.Centre; + line.Height = 0.92f + size.Y * 0.07f; + } + line.Colour = colour; } From 29b89486097bc3c5877e32d78817f2d3f46d0d73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jul 2024 12:33:32 +0200 Subject: [PATCH 008/266] Slim down bottom timeline This removes the BPM display, which is commonly cited to have no functional purpose by users, and reduces the height of the bottom bar in exchange for more space for the playfield. --- osu.Game/Screens/Edit/BottomBar.cs | 2 +- .../Edit/Components/PlaybackControl.cs | 4 +-- .../Edit/Components/TimeInfoContainer.cs | 26 +------------------ osu.Game/Screens/Edit/Editor.cs | 2 +- 4 files changed, 5 insertions(+), 29 deletions(-) diff --git a/osu.Game/Screens/Edit/BottomBar.cs b/osu.Game/Screens/Edit/BottomBar.cs index 6118adc0d720..fb233381aa3b 100644 --- a/osu.Game/Screens/Edit/BottomBar.cs +++ b/osu.Game/Screens/Edit/BottomBar.cs @@ -31,7 +31,7 @@ private void load(Editor editor) RelativeSizeAxes = Axes.X; - Height = 60; + Height = 40; Masking = true; EdgeEffect = new EdgeEffectParameters diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 6319dc892e5b..9fe6160ab435 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -46,8 +46,8 @@ private void load(OverlayColourProvider colourProvider, Editor? editor) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Scale = new Vector2(1.4f), - IconScale = new Vector2(1.4f), + Scale = new Vector2(1.2f), + IconScale = new Vector2(1.2f), Icon = FontAwesome.Regular.PlayCircle, Action = togglePause, }, diff --git a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs index 7c03198ec001..37facb3b9568 100644 --- a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs +++ b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs @@ -17,8 +17,6 @@ namespace osu.Game.Screens.Edit.Components { public partial class TimeInfoContainer : BottomBarContainer { - private OsuSpriteText bpm = null!; - [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; @@ -26,38 +24,16 @@ public partial class TimeInfoContainer : BottomBarContainer private EditorClock editorClock { get; set; } = null!; [BackgroundDependencyLoader] - private void load(OsuColour colours, OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider) { Background.Colour = colourProvider.Background5; Children = new Drawable[] { new TimestampControl(), - bpm = new OsuSpriteText - { - Colour = colours.Orange1, - Anchor = Anchor.CentreLeft, - Font = OsuFont.Torus.With(size: 18, weight: FontWeight.SemiBold), - Position = new Vector2(2, 5), - } }; } - private double? lastBPM; - - protected override void Update() - { - base.Update(); - - double newBPM = editorBeatmap.ControlPointInfo.TimingPointAt(editorClock.CurrentTime).BPM; - - if (lastBPM != newBPM) - { - lastBPM = newBPM; - bpm.Text = @$"{newBPM:0} BPM"; - } - } - private partial class TimestampControl : OsuClickableContainer { private Container hoverLayer = null!; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index a8a28ef0b88d..3f117ebcff7d 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -325,7 +325,7 @@ private void load(OsuConfigManager config) { Name = "Screen container", RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 40, Bottom = 60 }, + Padding = new MarginPadding { Top = 40, Bottom = 40 }, Child = screenContainer = new Container { RelativeSizeAxes = Axes.Both, From 5d5dd0de00feb6a06250f6fc427c343d39492b7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jul 2024 12:41:23 +0200 Subject: [PATCH 009/266] Redesign bottom timeline pieces to match stable better --- .../ControlPoints/EffectControlPoint.cs | 2 +- .../ControlPoints/TimingControlPoint.cs | 2 +- .../Timelines/Summary/Parts/BookmarkPart.cs | 1 + .../Parts/ControlPointVisualisation.cs | 4 +--- .../Summary/Parts/EffectPointVisualisation.cs | 9 +++++---- .../Summary/Parts/GroupVisualisation.cs | 12 ++--------- .../Timelines/Summary/Parts/MarkerPart.cs | 20 ++----------------- .../Summary/Parts/PreviewTimePart.cs | 1 + .../Timelines/Summary/SummaryTimeline.cs | 7 +++---- .../Components/Timeline/CentreMarker.cs | 6 +++--- 10 files changed, 20 insertions(+), 44 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index d48ed957ee7b..4b73994dcf36 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -34,7 +34,7 @@ public double ScrollSpeed set => ScrollSpeedBindable.Value = value; } - public override Color4 GetRepresentingColour(OsuColour colours) => colours.Purple; + public override Color4 GetRepresentingColour(OsuColour colours) => colours.Orange1; /// /// Whether this control point enables Kiai mode. diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 9ac361cffefa..3360b1d1fae3 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -26,7 +26,7 @@ public class TimingControlPoint : ControlPoint, IEquatable /// private const double default_beat_length = 60000.0 / 60.0; - public override Color4 GetRepresentingColour(OsuColour colours) => colours.Orange1; + public override Color4 GetRepresentingColour(OsuColour colours) => colours.Red1; public static readonly TimingControlPoint DEFAULT = new TimingControlPoint { diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs index 3102bf7c0617..ea71f24e9cb3 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs @@ -24,6 +24,7 @@ private partial class BookmarkVisualisation : PointVisualisation public BookmarkVisualisation(double startTime) : base(startTime) { + Width = 2; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs index 12620963e11d..9ba0cac53fcb 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs @@ -16,9 +16,7 @@ public partial class ControlPointVisualisation : PointVisualisation, IControlPoi public ControlPointVisualisation(ControlPoint point) { Point = point; - - Height = 0.25f; - Origin = Anchor.TopCentre; + Width = 2; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs index bf87470e015b..a3885bc2cc19 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs @@ -6,9 +6,9 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; -using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { @@ -91,12 +91,13 @@ private void refreshDisplay() Width = (float)(nextControlPoint.Time - effect.Time); - AddInternal(new PointVisualisation + AddInternal(new Circle { RelativeSizeAxes = Axes.Both, - Origin = Anchor.TopLeft, + Anchor = Anchor.BottomLeft, + Origin = Anchor.CentreLeft, Width = 1, - Height = 0.25f, + Height = 0.75f, Depth = float.MaxValue, Colour = effect.GetRepresentingColour(colours).Darken(0.5f), }); diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs index b39365277f06..2c806be162a9 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs @@ -39,19 +39,11 @@ public GroupVisualisation(ControlPointGroup group) switch (point) { case TimingControlPoint: - AddInternal(new ControlPointVisualisation(point) { Y = 0, }); - break; - - case DifficultyControlPoint: - AddInternal(new ControlPointVisualisation(point) { Y = 0.25f, }); - break; - - case SampleControlPoint: - AddInternal(new ControlPointVisualisation(point) { Y = 0.5f, }); + AddInternal(new ControlPointVisualisation(point)); break; case EffectControlPoint effect: - AddInternal(new EffectPointVisualisation(effect) { Y = 0.75f }); + AddInternal(new EffectPointVisualisation(effect)); break; } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs index ff707407dda2..21b3b3838850 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Threading; -using osu.Game.Graphics; +using osu.Game.Overlays; using osuTK; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts @@ -73,8 +73,6 @@ private partial class MarkerVisualisation : CompositeDrawable { public MarkerVisualisation() { - const float box_height = 4; - Anchor = Anchor.CentreLeft; Origin = Anchor.Centre; RelativePositionAxes = Axes.X; @@ -82,32 +80,18 @@ public MarkerVisualisation() AutoSizeAxes = Axes.X; InternalChildren = new Drawable[] { - new Box - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Size = new Vector2(14, box_height), - }, new Triangle { Anchor = Anchor.TopCentre, Origin = Anchor.BottomCentre, Scale = new Vector2(1, -1), Size = new Vector2(10, 5), - Y = box_height, }, new Triangle { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Size = new Vector2(10, 5), - Y = -box_height, - }, - new Box - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Size = new Vector2(14, box_height), }, new Box { @@ -121,7 +105,7 @@ public MarkerVisualisation() } [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.Red1; + private void load(OverlayColourProvider colours) => Colour = colours.Highlight1; } } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs index c63bb7ac243e..407173034ea8 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs @@ -32,6 +32,7 @@ private partial class PreviewTimeVisualisation : PointVisualisation public PreviewTimeVisualisation(double time) : base(time) { + Width = 2; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs index 6199cefb576a..92012936bcda 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -23,13 +23,11 @@ private void load(OverlayColourProvider colourProvider) Children = new Drawable[] { - new MarkerPart { RelativeSizeAxes = Axes.Both }, new ControlPointPart { Anchor = Anchor.Centre, Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.Both, - Y = -10, Height = 0.35f }, new BookmarkPart @@ -80,8 +78,9 @@ private void load(OverlayColourProvider colourProvider) Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Height = 0.10f - } + Height = 0.15f + }, + new MarkerPart { RelativeSizeAxes = Axes.Both }, }; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs index 74786cc0c9c3..be1888684e4f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs @@ -5,7 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; +using osu.Game.Overlays; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components.Timeline @@ -44,9 +44,9 @@ public CentreMarker() } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OverlayColourProvider colours) { - Colour = colours.Red1; + Colour = colours.Highlight1; } } } From ca4c0aa7e211471f26ee5aa776cd85bc786577c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Jul 2024 10:57:21 +0200 Subject: [PATCH 010/266] Remove unused using --- .../Timelines/Summary/Parts/ControlPointVisualisation.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs index 9ba0cac53fcb..47169481e227 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; From 002679ebb0e75ee6d72e9e0e8bfb08a3be4314e9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 10 Jul 2024 11:12:52 +0300 Subject: [PATCH 011/266] Ask for `VisibilityContainer` explicitly --- osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs | 2 +- osu.Game/Overlays/FirstRunSetupOverlay.cs | 2 +- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- osu.Game/Overlays/Mods/ShearedOverlayContainer.cs | 2 +- osu.Game/Screens/Footer/ScreenFooter.cs | 2 +- osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs | 3 ++- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs index de2026e538e4..c8cf6a6ffbee 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs @@ -210,7 +210,7 @@ public override bool OnBackButton() return false; } - public override Drawable CreateFooterContent() => new TestFooterContent(); + public override VisibilityContainer CreateFooterContent() => new TestFooterContent(); public partial class TestFooterContent : VisibilityContainer { diff --git a/osu.Game/Overlays/FirstRunSetupOverlay.cs b/osu.Game/Overlays/FirstRunSetupOverlay.cs index 6412297663f4..2c8ceba82cfd 100644 --- a/osu.Game/Overlays/FirstRunSetupOverlay.cs +++ b/osu.Game/Overlays/FirstRunSetupOverlay.cs @@ -150,7 +150,7 @@ protected override void LoadComplete() private FirstRunSetupFooterContent? currentFooterContent; - public override Drawable CreateFooterContent() + public override VisibilityContainer CreateFooterContent() { currentFooterContent = new FirstRunSetupFooterContent { diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index bd04a1f6b349..da935396798a 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -256,7 +256,7 @@ protected override void LoadComplete() private ModSelectFooterContent? currentFooterContent; - public override Drawable CreateFooterContent() => currentFooterContent = new ModSelectFooterContent(this) + public override VisibilityContainer CreateFooterContent() => currentFooterContent = new ModSelectFooterContent(this) { Beatmap = { BindTarget = Beatmap }, ActiveMods = { BindTarget = ActiveMods }, diff --git a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs index 9e5a336c179d..9ea98c1ae42b 100644 --- a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs +++ b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs @@ -104,7 +104,7 @@ private void load() /// /// Creates content to be displayed on the game-wide footer. /// - public virtual Drawable CreateFooterContent() => Empty(); + public virtual VisibilityContainer? CreateFooterContent() => null; /// /// Invoked when the back button in the footer is pressed. diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index a841f2a50b92..4464b9d7dac1 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -218,7 +218,7 @@ public void SetActiveOverlayContainer(ShearedOverlayContainer overlay) updateColourScheme(overlay.ColourProvider.ColourScheme); - var content = overlay.CreateFooterContent(); + var content = overlay.CreateFooterContent() ?? Empty(); Add(contentContainer = new Container { diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 0ed45161f24e..2b3ab9491677 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; @@ -34,7 +35,7 @@ public FreeModSelectOverlay() protected override ModColumn CreateModColumn(ModType modType) => new ModColumn(modType, true); - public override Drawable CreateFooterContent() => currentFooterContent = new FreeModSelectFooterContent(this) + public override VisibilityContainer CreateFooterContent() => currentFooterContent = new FreeModSelectFooterContent(this) { Beatmap = { BindTarget = Beatmap }, ActiveMods = { BindTarget = ActiveMods }, From f0a7a3f85657caf09c639bd5cef154d339e5b9de Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 10 Jul 2024 11:51:49 +0300 Subject: [PATCH 012/266] Add failing test case for edge case --- .../UserInterface/TestSceneScreenFooter.cs | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs index c8cf6a6ffbee..a4cf8a276f12 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs @@ -23,7 +23,7 @@ public partial class TestSceneScreenFooter : OsuManualInputManagerTestScene { private DependencyProvidingContainer contentContainer = null!; private ScreenFooter screenFooter = null!; - private TestModSelectOverlay overlay = null!; + private TestModSelectOverlay modOverlay = null!; [SetUp] public void SetUp() => Schedule(() => @@ -39,7 +39,7 @@ public void SetUp() => Schedule(() => }, Children = new Drawable[] { - overlay = new TestModSelectOverlay(), + modOverlay = new TestModSelectOverlay(), new PopoverContainer { RelativeSizeAxes = Axes.Both, @@ -51,7 +51,7 @@ public void SetUp() => Schedule(() => screenFooter.SetButtons(new ScreenFooterButton[] { - new ScreenFooterButtonMods(overlay) { Current = SelectedMods }, + new ScreenFooterButtonMods(modOverlay) { Current = SelectedMods }, new ScreenFooterButtonRandom(), new ScreenFooterButtonOptions(), }); @@ -178,6 +178,24 @@ public void TestBackButton() AddAssert("footer hidden", () => screenFooter.State.Value == Visibility.Hidden); } + [Test] + public void TestLoadOverlayAfterFooterIsDisplayed() + { + TestShearedOverlayContainer externalOverlay = null!; + + AddStep("show mod overlay", () => modOverlay.Show()); + AddUntilStep("mod footer content shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent, () => Is.True); + + AddStep("add external overlay", () => contentContainer.Add(externalOverlay = new TestShearedOverlayContainer())); + AddUntilStep("wait for load", () => externalOverlay.IsLoaded); + AddAssert("mod footer content still shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent, () => Is.True); + AddAssert("external overlay content not shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent, () => Is.Not.True); + + AddStep("hide mod overlay", () => modOverlay.Hide()); + AddUntilStep("mod footer content hidden", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent, () => Is.Not.True); + AddAssert("external overlay content still not shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent, () => Is.Not.True); + } + private partial class TestModSelectOverlay : UserModSelectOverlay { protected override bool ShowPresets => true; From 337f05f9a454019bed39d6a32f7da94ac4be43fb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 10 Jul 2024 11:54:04 +0300 Subject: [PATCH 013/266] Fix loading (but not showing) a sheared overlay hiding displayed footer content Identified by tests. See https://github.com/ppy/osu/actions/runs/9869382635/job/27253010485 & https://github.com/ppy/osu/actions/runs/9869382635/job/27253009622. This change also prevents the initial `PopOut` call in overlays from calling `clearActiveOverlayContainer`, since it's not in the update thread and it's never meant to be called at that point anyway (it's supposed to be accompanied by a previous `PopIn` call adding the footer content). --- .../Overlays/Mods/ShearedOverlayContainer.cs | 7 +++-- osu.Game/Screens/Footer/ScreenFooter.cs | 26 ++++++++++--------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs index 9ea98c1ae42b..8c6b9e805b80 100644 --- a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs +++ b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs @@ -1,6 +1,7 @@ // 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.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -123,6 +124,7 @@ protected override bool OnClick(ClickEvent e) return base.OnClick(e); } + private IDisposable? activeOverlayRegistration; private bool hideFooterOnPopOut; protected override void PopIn() @@ -135,7 +137,7 @@ protected override void PopIn() if (footer != null) { - footer.SetActiveOverlayContainer(this); + activeOverlayRegistration = footer.RegisterActiveOverlayContainer(this); if (footer.State.Value == Visibility.Hidden) { @@ -156,7 +158,8 @@ protected override void PopOut() if (footer != null) { - footer.ClearActiveOverlayContainer(); + activeOverlayRegistration?.Dispose(); + activeOverlayRegistration = null; if (hideFooterOnPopOut) { diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index 4464b9d7dac1..f8d222e51068 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -142,7 +142,7 @@ public void SetButtons(IReadOnlyList buttons) temporarilyHiddenButtons.Clear(); overlays.Clear(); - ClearActiveOverlayContainer(); + clearActiveOverlayContainer(); var oldButtons = buttonsFlow.ToArray(); @@ -187,14 +187,15 @@ public void SetButtons(IReadOnlyList buttons) private ShearedOverlayContainer? activeOverlay; private Container? contentContainer; + private readonly List temporarilyHiddenButtons = new List(); - public void SetActiveOverlayContainer(ShearedOverlayContainer overlay) + public IDisposable RegisterActiveOverlayContainer(ShearedOverlayContainer overlay) { - if (contentContainer != null) + if (activeOverlay != null) { throw new InvalidOperationException(@"Cannot set overlay content while one is already present. " + - $@"The previous overlay whose content is {contentContainer.Child.GetType().Name} should be hidden first."); + $@"The previous overlay ({activeOverlay.GetType().Name}) should be hidden first."); } activeOverlay = overlay; @@ -232,29 +233,30 @@ public void SetActiveOverlayContainer(ShearedOverlayContainer overlay) this.Delay(60).Schedule(() => content.Show()); else content.Show(); + + return new InvokeOnDisposal(clearActiveOverlayContainer); } - public void ClearActiveOverlayContainer() + private void clearActiveOverlayContainer() { - if (contentContainer == null) + if (activeOverlay == null) return; + Debug.Assert(contentContainer != null); contentContainer.Child.Hide(); double timeUntilRun = contentContainer.Child.LatestTransformEndTime - Time.Current; - Container expireTarget = contentContainer; - contentContainer = null; - activeOverlay = null; - for (int i = 0; i < temporarilyHiddenButtons.Count; i++) makeButtonAppearFromBottom(temporarilyHiddenButtons[i], 0); temporarilyHiddenButtons.Clear(); - expireTarget.Delay(timeUntilRun).Expire(); - updateColourScheme(OverlayColourScheme.Aquamarine); + + contentContainer.Delay(timeUntilRun).Expire(); + contentContainer = null; + activeOverlay = null; } private void updateColourScheme(OverlayColourScheme colourScheme) From 8ca8648a0916099b85fb034e472cbf630d31845d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 10 Jul 2024 16:14:12 +0300 Subject: [PATCH 014/266] Add failing test case --- .../Visual/Navigation/TestSceneScreenNavigation.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 88235d58d311..e81c6d2e8698 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -838,18 +838,25 @@ public void TestOverlayClosing() [Test] public void TestExitWithOperationInProgress() { - AddUntilStep("wait for dialog overlay", () => Game.ChildrenOfType().SingleOrDefault() != null); + int x = 0; + + AddUntilStep("wait for dialog overlay", () => + { + x = 0; + return Game.ChildrenOfType().SingleOrDefault() != null; + }); AddRepeatStep("start ongoing operation", () => { Game.Notifications.Post(new ProgressNotification { - Text = "Something is still running", + Text = $"Something is still running #{++x}", Progress = 0.5f, State = ProgressNotificationState.Active, }); }, 15); + AddAssert("all notifications = 15", () => Game.Notifications.AllNotifications.Count(), () => Is.EqualTo(15)); AddStep("Hold escape", () => InputManager.PressKey(Key.Escape)); AddUntilStep("confirmation dialog shown", () => Game.ChildrenOfType().Single().CurrentDialog is ConfirmExitDialog); AddStep("Release escape", () => InputManager.ReleaseKey(Key.Escape)); From 846fd73ac9c55405d4605c6968f82b78f79bb488 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 10 Jul 2024 16:15:02 +0300 Subject: [PATCH 015/266] Fix notification toast tray potentially hiding some notifications --- .../Overlays/NotificationOverlayToastTray.cs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs index 0ebaff94377b..e019b3162060 100644 --- a/osu.Game/Overlays/NotificationOverlayToastTray.cs +++ b/osu.Game/Overlays/NotificationOverlayToastTray.cs @@ -24,16 +24,16 @@ namespace osu.Game.Overlays /// public partial class NotificationOverlayToastTray : CompositeDrawable { - public override bool IsPresent => toastContentBackground.Height > 0 || toastFlow.Count > 0; + public override bool IsPresent => toastContentBackground.Height > 0 || Notifications.Any(); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => toastFlow.ReceivePositionalInputAt(screenSpacePos); /// /// All notifications currently being displayed by the toast tray. /// - public IEnumerable Notifications => toastFlow; + public IEnumerable Notifications => toastFlow.Concat(InternalChildren.OfType()); - public bool IsDisplayingToasts => toastFlow.Count > 0; + public bool IsDisplayingToasts => Notifications.Any(); private FillFlowContainer toastFlow = null!; private BufferedContainer toastContentBackground = null!; @@ -43,12 +43,7 @@ public partial class NotificationOverlayToastTray : CompositeDrawable public Action? ForwardNotificationToPermanentStore { get; set; } - public int UnreadCount => allDisplayedNotifications.Count(n => !n.WasClosed && !n.Read); - - /// - /// Notifications contained in the toast flow, or in a detached state while they animate during forwarding to the main overlay. - /// - private IEnumerable allDisplayedNotifications => toastFlow.Concat(InternalChildren.OfType()); + public int UnreadCount => Notifications.Count(n => !n.WasClosed && !n.Read); private int runningDepth; @@ -91,11 +86,7 @@ private void load() }; } - public void MarkAllRead() - { - toastFlow.Children.ForEach(n => n.Read = true); - InternalChildren.OfType().ForEach(n => n.Read = true); - } + public void MarkAllRead() => Notifications.ForEach(n => n.Read = true); public void FlushAllToasts() { From fe421edd8f2b86d1097c650cb89771af67be8f80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Jul 2024 16:41:21 +0900 Subject: [PATCH 016/266] Fix editor UI transparency being incorrectly opaque when hovering slider control points As mentioned at https://github.com/ppy/osu/pull/28787#issuecomment-2221150025. --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 30 +++---------------- osu.Game/Screens/Edit/BottomBar.cs | 1 + .../Components/Timeline/TimelineArea.cs | 1 + osu.Game/Screens/Edit/Editor.cs | 3 ++ 4 files changed, 9 insertions(+), 26 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 1ba488d027b7..3c38a7258e4a 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -132,7 +132,7 @@ private void load(OsuConfigManager config, [CanBeNull] Editor editor) InternalChildren = new[] { - PlayfieldContentContainer = new ContentContainer + PlayfieldContentContainer = new Container { Name = "Playfield content", RelativeSizeAxes = Axes.Y, @@ -269,6 +269,7 @@ protected override void LoadComplete() composerFocusMode.BindValueChanged(_ => { + // Transforms should be kept in sync with other usages of composer focus mode. if (!composerFocusMode.Value) { leftToolboxBackground.FadeIn(750, Easing.OutQuint); @@ -303,6 +304,8 @@ protected override void Update() PlayfieldContentContainer.Width = Math.Max(1024, DrawWidth) - (TOOLBOX_CONTRACTED_SIZE_LEFT + TOOLBOX_CONTRACTED_SIZE_RIGHT); PlayfieldContentContainer.X = TOOLBOX_CONTRACTED_SIZE_LEFT; } + + composerFocusMode.Value = PlayfieldContentContainer.Contains(InputManager.CurrentState.Mouse.Position); } public override Playfield Playfield => drawableRulesetWrapper.Playfield; @@ -529,31 +532,6 @@ public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePositio } #endregion - - private partial class ContentContainer : Container - { - public override bool HandlePositionalInput => true; - - private readonly Bindable composerFocusMode = new Bindable(); - - [BackgroundDependencyLoader(true)] - private void load([CanBeNull] Editor editor) - { - if (editor != null) - composerFocusMode.BindTo(editor.ComposerFocusMode); - } - - protected override bool OnHover(HoverEvent e) - { - composerFocusMode.Value = true; - return false; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - composerFocusMode.Value = false; - } - } } /// diff --git a/osu.Game/Screens/Edit/BottomBar.cs b/osu.Game/Screens/Edit/BottomBar.cs index dd5675211980..680f61ceaa68 100644 --- a/osu.Game/Screens/Edit/BottomBar.cs +++ b/osu.Game/Screens/Edit/BottomBar.cs @@ -82,6 +82,7 @@ protected override void LoadComplete() saveInProgress.BindValueChanged(_ => TestGameplayButton.Enabled.Value = !saveInProgress.Value, true); composerFocusMode.BindValueChanged(_ => { + // Transforms should be kept in sync with other usages of composer focus mode. foreach (var c in this.ChildrenOfType()) { if (!composerFocusMode.Value) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index ff92e658d9f7..1db067c846ed 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -134,6 +134,7 @@ protected override void LoadComplete() composerFocusMode.BindValueChanged(_ => { + // Transforms should be kept in sync with other usages of composer focus mode. if (!composerFocusMode.Value) timelineBackground.FadeIn(750, Easing.OutQuint); else diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 27d0392b1e9d..db94b084063f 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -218,6 +218,9 @@ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnl /// This controls the opacity of components like the timelines, sidebars, etc. /// In "composer focus" mode the opacity of the aforementioned components is reduced so that the user can focus on the composer better. /// + /// + /// The state of this bindable is controlled by . + /// public Bindable ComposerFocusMode { get; } = new Bindable(); public Editor(EditorLoader loader = null) From 6801ccbbc51da07312bb228aa64c0eb87ff70d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Jul 2024 11:23:09 +0200 Subject: [PATCH 017/266] Fix editor UI remaining transparent when switching away from compose tab Could still happen if using the keyboard F-key shortcuts. In that case the composer becomes non-present, so its `Update()` can't really do anything. --- osu.Game/Screens/Edit/Editor.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index db94b084063f..6be8dafa66e4 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -219,7 +219,7 @@ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnl /// In "composer focus" mode the opacity of the aforementioned components is reduced so that the user can focus on the composer better. /// /// - /// The state of this bindable is controlled by . + /// The state of this bindable is controlled by when in mode. /// public Bindable ComposerFocusMode { get; } = new Bindable(); @@ -1018,6 +1018,9 @@ private void onModeChanged(ValueChangedEvent e) } finally { + if (Mode.Value != EditorScreenMode.Compose) + ComposerFocusMode.Value = false; + updateSampleDisabledState(); rebindClipboardBindables(); } From 7b541d378c5cb896ee0dc3bbdf2ec377b87587e7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 11 Jul 2024 14:22:23 +0300 Subject: [PATCH 018/266] Revert some changes I can see `IsDisplayingToast` being removed and `IsPresent` becoming `=> Notifications.Any()` but I'll just leave this for another day. --- osu.Game/Overlays/NotificationOverlayToastTray.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs index e019b3162060..d2899f29b8fa 100644 --- a/osu.Game/Overlays/NotificationOverlayToastTray.cs +++ b/osu.Game/Overlays/NotificationOverlayToastTray.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays /// public partial class NotificationOverlayToastTray : CompositeDrawable { - public override bool IsPresent => toastContentBackground.Height > 0 || Notifications.Any(); + public override bool IsPresent => toastContentBackground.Height > 0 || toastFlow.Count > 0; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => toastFlow.ReceivePositionalInputAt(screenSpacePos); @@ -33,7 +33,7 @@ public partial class NotificationOverlayToastTray : CompositeDrawable /// public IEnumerable Notifications => toastFlow.Concat(InternalChildren.OfType()); - public bool IsDisplayingToasts => Notifications.Any(); + public bool IsDisplayingToasts => toastFlow.Count > 0; private FillFlowContainer toastFlow = null!; private BufferedContainer toastContentBackground = null!; From 3190f8bb7e69e28dcef6cccfa260ca8f6483c910 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 11 Jul 2024 14:29:30 +0300 Subject: [PATCH 019/266] Remove key arrow handling in `VolumeOverlay` --- osu.Game/Overlays/VolumeOverlay.cs | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index 5470c70400a1..9747a543fc22 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -179,30 +179,6 @@ protected override bool OnMouseMove(MouseMoveEvent e) 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(); From bd44c17079764184e9ebd591825ed536221b0acf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 11 Jul 2024 14:29:56 +0300 Subject: [PATCH 020/266] Enable NRT in `VolumeOverlay` --- osu.Game/Overlays/VolumeOverlay.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index 9747a543fc22..6f9861c703a0 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; @@ -19,7 +17,6 @@ using osu.Game.Overlays.Volume; using osuTK; using osuTK.Graphics; -using osuTK.Input; namespace osu.Game.Overlays { @@ -27,17 +24,17 @@ public partial class VolumeOverlay : VisibilityContainer { private const float offset = 10; - private VolumeMeter volumeMeterMaster; - private VolumeMeter volumeMeterEffect; - private VolumeMeter volumeMeterMusic; - private MuteButton muteButton; + private VolumeMeter volumeMeterMaster = null!; + private VolumeMeter volumeMeterEffect = null!; + private VolumeMeter volumeMeterMusic = null!; + private MuteButton muteButton = null!; + + private SelectionCycleFillFlowContainer volumeMeters = null!; private readonly BindableDouble muteAdjustment = new BindableDouble(); public Bindable IsMuted { get; } = new Bindable(); - private SelectionCycleFillFlowContainer volumeMeters; - [BackgroundDependencyLoader] private void load(AudioManager audio, OsuColour colours) { @@ -140,8 +137,6 @@ public bool Adjust(GlobalAction action, float amount = 1, bool isPrecise = false return false; } - private ScheduledDelegate popOutDelegate; - public void FocusMasterVolume() { volumeMeters.Select(volumeMeterMaster); @@ -191,6 +186,8 @@ protected override void OnHoverLost(HoverLostEvent e) base.OnHoverLost(e); } + private ScheduledDelegate? popOutDelegate; + private void schedulePopOut() { popOutDelegate?.Cancel(); From 669e945fc334c544274c22b63ebb856ed394b37a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 11 Jul 2024 14:56:17 +0300 Subject: [PATCH 021/266] Fix `ModSelectOverlay` not hiding without a footer --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index da935396798a..4143a7fe8ad0 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -604,7 +604,13 @@ public override bool OnPressed(KeyBindingPressEvent e) return base.OnPressed(e); - void hideOverlay() => footer?.BackButton.TriggerClick(); + void hideOverlay() + { + if (footer != null) + footer.BackButton.TriggerClick(); + else + Hide(); + } } /// From 7a5624fd0efd1c422afc4595b284bf8af444df3e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 11 Jul 2024 15:30:07 +0300 Subject: [PATCH 022/266] Add screen footer to `ScreenTestScene` --- .../Multiplayer/TestSceneMultiplayerMatchSubScreen.cs | 8 ++++---- osu.Game/Tests/Visual/ScreenTestScene.cs | 7 ++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index f9ef08583838..e2593e68e52d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -312,14 +312,14 @@ public void TestModSelectOverlay() AddUntilStep("wait for join", () => RoomJoined); ClickButtonWhenEnabled(); - AddAssert("mod select shows unranked", () => screen.UserModsSelectOverlay.ChildrenOfType().Single().Ranked.Value == false); - AddAssert("score multiplier = 1.20", () => screen.UserModsSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01)); + AddAssert("mod select shows unranked", () => this.ChildrenOfType().Single().Ranked.Value == false); + AddAssert("score multiplier = 1.20", () => this.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01)); AddStep("select flashlight", () => screen.UserModsSelectOverlay.ChildrenOfType().Single(m => m.Mod is ModFlashlight).TriggerClick()); - AddAssert("score multiplier = 1.35", () => screen.UserModsSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(1.35).Within(0.01)); + AddAssert("score multiplier = 1.35", () => this.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(1.35).Within(0.01)); AddStep("change flashlight setting", () => ((OsuModFlashlight)screen.UserModsSelectOverlay.SelectedMods.Value.Single()).FollowDelay.Value = 1200); - AddAssert("score multiplier = 1.20", () => screen.UserModsSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01)); + AddAssert("score multiplier = 1.20", () => this.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01)); } private partial class TestMultiplayerMatchSubScreen : MultiplayerMatchSubScreen diff --git a/osu.Game/Tests/Visual/ScreenTestScene.cs b/osu.Game/Tests/Visual/ScreenTestScene.cs index 3cca1e59cc63..f780b1a8f8e5 100644 --- a/osu.Game/Tests/Visual/ScreenTestScene.cs +++ b/osu.Game/Tests/Visual/ScreenTestScene.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Screens; +using osu.Game.Screens.Footer; namespace osu.Game.Tests.Visual { @@ -30,6 +31,9 @@ public abstract partial class ScreenTestScene : OsuManualInputManagerTestScene, [Cached(typeof(IDialogOverlay))] protected DialogOverlay DialogOverlay { get; private set; } + [Cached] + private ScreenFooter footer; + protected ScreenTestScene() { base.Content.AddRange(new Drawable[] @@ -44,7 +48,8 @@ protected ScreenTestScene() { RelativeSizeAxes = Axes.Both, Child = DialogOverlay = new DialogOverlay() - } + }, + footer = new ScreenFooter(), }); Stack.ScreenPushed += (_, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed → {newScreen}"); From d4a4a059d4a68affe7563d63ede5c9112a919482 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 11 Jul 2024 15:31:02 +0300 Subject: [PATCH 023/266] Fix footer content not accessible by overlay when overriden by a subclass --- osu.Game/Overlays/FirstRunSetupOverlay.cs | 16 ++++++++-------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 8 ++++---- .../Overlays/Mods/ShearedOverlayContainer.cs | 6 +++++- osu.Game/Screens/Footer/ScreenFooter.cs | 6 ++++-- .../Screens/OnlinePlay/FreeModSelectOverlay.cs | 10 +++++----- 5 files changed, 26 insertions(+), 20 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetupOverlay.cs b/osu.Game/Overlays/FirstRunSetupOverlay.cs index 2c8ceba82cfd..1a302cf51d46 100644 --- a/osu.Game/Overlays/FirstRunSetupOverlay.cs +++ b/osu.Game/Overlays/FirstRunSetupOverlay.cs @@ -45,7 +45,7 @@ public partial class FirstRunSetupOverlay : ShearedOverlayContainer private ScreenStack? stack; - public ShearedButton? NextButton => currentFooterContent?.NextButton; + public ShearedButton? NextButton => DisplayedFooterContent?.NextButton; private readonly Bindable showFirstRunSetup = new Bindable(); @@ -148,17 +148,17 @@ protected override void LoadComplete() [Resolved] private ScreenFooter footer { get; set; } = null!; - private FirstRunSetupFooterContent? currentFooterContent; + public new FirstRunSetupFooterContent? DisplayedFooterContent => base.DisplayedFooterContent as FirstRunSetupFooterContent; public override VisibilityContainer CreateFooterContent() { - currentFooterContent = new FirstRunSetupFooterContent + var footerContent = new FirstRunSetupFooterContent { ShowNextStep = showNextStep, }; - currentFooterContent.OnLoadComplete += _ => updateButtons(); - return currentFooterContent; + footerContent.OnLoadComplete += _ => updateButtons(); + return footerContent; } public override bool OnBackButton() @@ -182,7 +182,7 @@ public override bool OnPressed(KeyBindingPressEvent e) switch (e.Action) { case GlobalAction.Select: - currentFooterContent?.NextButton.TriggerClick(); + DisplayedFooterContent?.NextButton.TriggerClick(); return true; case GlobalAction.Back: @@ -287,9 +287,9 @@ private void showNextStep() updateButtons(); } - private void updateButtons() => currentFooterContent?.UpdateButtons(currentStepIndex, steps); + private void updateButtons() => DisplayedFooterContent?.UpdateButtons(currentStepIndex, steps); - private partial class FirstRunSetupFooterContent : VisibilityContainer + public partial class FirstRunSetupFooterContent : VisibilityContainer { public ShearedButton NextButton { get; private set; } = null!; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 4143a7fe8ad0..746959089520 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -254,9 +254,9 @@ protected override void LoadComplete() }); } - private ModSelectFooterContent? currentFooterContent; + public new ModSelectFooterContent? DisplayedFooterContent => base.DisplayedFooterContent as ModSelectFooterContent; - public override VisibilityContainer CreateFooterContent() => currentFooterContent = new ModSelectFooterContent(this) + public override VisibilityContainer CreateFooterContent() => new ModSelectFooterContent(this) { Beatmap = { BindTarget = Beatmap }, ActiveMods = { BindTarget = ActiveMods }, @@ -270,7 +270,7 @@ protected override void Update() base.Update(); SearchTextBox.PlaceholderText = SearchTextBox.HasFocus ? input_search_placeholder : tab_to_search_placeholder; - aboveColumnsContent.Padding = aboveColumnsContent.Padding with { Bottom = currentFooterContent?.DisplaysStackedVertically == true ? 75f : 15f }; + aboveColumnsContent.Padding = aboveColumnsContent.Padding with { Bottom = DisplayedFooterContent?.DisplaysStackedVertically == true ? 75f : 15f }; } /// @@ -573,7 +573,7 @@ public override bool OnPressed(KeyBindingPressEvent e) { if (!SearchTextBox.HasFocus && !customisationPanel.Expanded.Value) { - currentFooterContent?.DeselectAllModsButton?.TriggerClick(); + DisplayedFooterContent?.DeselectAllModsButton?.TriggerClick(); return true; } diff --git a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs index 8c6b9e805b80..dfa49f377900 100644 --- a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs +++ b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs @@ -102,6 +102,8 @@ private void load() }; } + public VisibilityContainer? DisplayedFooterContent { get; private set; } + /// /// Creates content to be displayed on the game-wide footer. /// @@ -137,7 +139,8 @@ protected override void PopIn() if (footer != null) { - activeOverlayRegistration = footer.RegisterActiveOverlayContainer(this); + activeOverlayRegistration = footer.RegisterActiveOverlayContainer(this, out var footerContent); + DisplayedFooterContent = footerContent; if (footer.State.Value == Visibility.Hidden) { @@ -160,6 +163,7 @@ protected override void PopOut() { activeOverlayRegistration?.Dispose(); activeOverlayRegistration = null; + DisplayedFooterContent = null; if (hideFooterOnPopOut) { diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index f8d222e51068..4c020fc95e2b 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -190,7 +190,7 @@ public void SetButtons(IReadOnlyList buttons) private readonly List temporarilyHiddenButtons = new List(); - public IDisposable RegisterActiveOverlayContainer(ShearedOverlayContainer overlay) + public IDisposable RegisterActiveOverlayContainer(ShearedOverlayContainer overlay, out VisibilityContainer? footerContent) { if (activeOverlay != null) { @@ -219,7 +219,9 @@ public IDisposable RegisterActiveOverlayContainer(ShearedOverlayContainer overla updateColourScheme(overlay.ColourProvider.ColourScheme); - var content = overlay.CreateFooterContent() ?? Empty(); + footerContent = overlay.CreateFooterContent(); + + var content = footerContent ?? Empty(); Add(contentContainer = new Container { diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 2b3ab9491677..8937abb775ad 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -23,9 +23,7 @@ public partial class FreeModSelectOverlay : ModSelectOverlay set => base.IsValidMod = m => m.UserPlayable && value.Invoke(m); } - private FreeModSelectFooterContent? currentFooterContent; - - protected override SelectAllModsButton? SelectAllModsButton => currentFooterContent?.SelectAllModsButton; + protected override SelectAllModsButton? SelectAllModsButton => DisplayedFooterContent?.SelectAllModsButton; public FreeModSelectOverlay() : base(OverlayColourScheme.Plum) @@ -35,13 +33,15 @@ public FreeModSelectOverlay() protected override ModColumn CreateModColumn(ModType modType) => new ModColumn(modType, true); - public override VisibilityContainer CreateFooterContent() => currentFooterContent = new FreeModSelectFooterContent(this) + public new FreeModSelectFooterContent? DisplayedFooterContent => base.DisplayedFooterContent as FreeModSelectFooterContent; + + public override VisibilityContainer CreateFooterContent() => new FreeModSelectFooterContent(this) { Beatmap = { BindTarget = Beatmap }, ActiveMods = { BindTarget = ActiveMods }, }; - private partial class FreeModSelectFooterContent : ModSelectFooterContent + public partial class FreeModSelectFooterContent : ModSelectFooterContent { private readonly FreeModSelectOverlay overlay; From 3ea0f58daabed92d8e86191379f7d417f9176738 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 11 Jul 2024 15:31:23 +0300 Subject: [PATCH 024/266] Update `TestSceneFreeModSelectOverlay` to work again --- .../TestSceneFreeModSelectOverlay.cs | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index 938ab1e9f4c2..497faa28d0d6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -16,6 +17,7 @@ using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Footer; using osu.Game.Screens.OnlinePlay; using osu.Game.Utils; using osuTK.Input; @@ -26,6 +28,7 @@ public partial class TestSceneFreeModSelectOverlay : MultiplayerTestScene { private FreeModSelectOverlay freeModSelectOverlay; private FooterButtonFreeMods footerButtonFreeMods; + private ScreenFooter footer; private readonly Bindable>> availableMods = new Bindable>>(); [BackgroundDependencyLoader] @@ -127,7 +130,7 @@ public void TestSelectAllViaFooterButtonThenDeselectFromOverlay() { createFreeModSelect(); - AddAssert("overlay select all button enabled", () => freeModSelectOverlay.ChildrenOfType().Single().Enabled.Value); + AddAssert("overlay select all button enabled", () => this.ChildrenOfType().Single().Enabled.Value); AddAssert("footer button displays off", () => footerButtonFreeMods.ChildrenOfType().Any(t => t.Text == "off")); AddStep("click footer select all button", () => @@ -150,19 +153,27 @@ public void TestSelectAllViaFooterButtonThenDeselectFromOverlay() private void createFreeModSelect() { - AddStep("create free mod select screen", () => Children = new Drawable[] + AddStep("create free mod select screen", () => Child = new DependencyProvidingContainer { - freeModSelectOverlay = new FreeModSelectOverlay + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - State = { Value = Visibility.Visible } - }, - footerButtonFreeMods = new FooterButtonFreeMods(freeModSelectOverlay) - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Current = { BindTarget = freeModSelectOverlay.SelectedMods }, + freeModSelectOverlay = new FreeModSelectOverlay + { + State = { Value = Visibility.Visible } + }, + footerButtonFreeMods = new FooterButtonFreeMods(freeModSelectOverlay) + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Y = -ScreenFooter.HEIGHT, + Current = { BindTarget = freeModSelectOverlay.SelectedMods }, + }, + footer = new ScreenFooter(), }, + CachedDependencies = new (Type, object)[] { (typeof(ScreenFooter), footer) }, }); + AddUntilStep("all column content loaded", () => freeModSelectOverlay.ChildrenOfType().Any() && freeModSelectOverlay.ChildrenOfType().All(column => column.IsLoaded && column.ItemsLoaded)); From fb4f620c90409c19be1ae49893813f8bd2b95ae2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 12:34:43 +0900 Subject: [PATCH 025/266] Add back BPM and adjust sizing of bottom bar a bit more --- osu.Game/Screens/Edit/BottomBar.cs | 4 +- .../Edit/Components/TimeInfoContainer.cs | 46 ++++++++++++++++--- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/BottomBar.cs b/osu.Game/Screens/Edit/BottomBar.cs index a11f40c8fd25..514a06f1c5e4 100644 --- a/osu.Game/Screens/Edit/BottomBar.cs +++ b/osu.Game/Screens/Edit/BottomBar.cs @@ -31,7 +31,7 @@ private void load(Editor editor) RelativeSizeAxes = Axes.X; - Height = 40; + Height = 50; Masking = true; EdgeEffect = new EdgeEffectParameters @@ -48,7 +48,7 @@ private void load(Editor editor) RelativeSizeAxes = Axes.Both, ColumnDimensions = new[] { - new Dimension(GridSizeMode.Absolute, 170), + new Dimension(GridSizeMode.Absolute, 150), new Dimension(), new Dimension(GridSizeMode.Absolute, 220), new Dimension(GridSizeMode.Absolute, HitObjectComposer.TOOLBOX_CONTRACTED_SIZE_RIGHT), diff --git a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs index 37facb3b9568..8f2a3d49caca 100644 --- a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs +++ b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs @@ -17,6 +17,8 @@ namespace osu.Game.Screens.Edit.Components { public partial class TimeInfoContainer : BottomBarContainer { + private OsuSpriteText bpm = null!; + [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; @@ -24,16 +26,38 @@ public partial class TimeInfoContainer : BottomBarContainer private EditorClock editorClock { get; set; } = null!; [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OsuColour colours, OverlayColourProvider colourProvider) { Background.Colour = colourProvider.Background5; Children = new Drawable[] { new TimestampControl(), + bpm = new OsuSpriteText + { + Colour = colours.Orange1, + Anchor = Anchor.CentreLeft, + Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold), + Position = new Vector2(2, 4), + } }; } + private double? lastBPM; + + protected override void Update() + { + base.Update(); + + double newBPM = editorBeatmap.ControlPointInfo.TimingPointAt(editorClock.CurrentTime).BPM; + + if (lastBPM != newBPM) + { + lastBPM = newBPM; + bpm.Text = @$"{newBPM:0} BPM"; + } + } + private partial class TimestampControl : OsuClickableContainer { private Container hoverLayer = null!; @@ -63,7 +87,8 @@ private void load() RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { - Top = 5, + Top = 4, + Bottom = 1, Horizontal = -2 }, Child = new Container @@ -83,12 +108,13 @@ private void load() Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Spacing = new Vector2(-2, 0), - Font = OsuFont.Torus.With(size: 36, fixedWidth: true, weight: FontWeight.Light), + Font = OsuFont.Torus.With(size: 32, fixedWidth: true, weight: FontWeight.Light), }, - inputTextBox = new OsuTextBox + inputTextBox = new TimestampTextBox { - Width = 150, - Height = 36, + Position = new Vector2(-2, 4), + Width = 128, + Height = 26, Alpha = 0, CommitOnFocusLost = true, }, @@ -136,6 +162,14 @@ protected override void Update() showingHoverLayer = shouldShowHoverLayer; } } + + private partial class TimestampTextBox : OsuTextBox + { + public TimestampTextBox() + { + TextContainer.Height = 0.8f; + } + } } } } From e6b7d2530eef28859c4bde9fde5dc3e2c31afea7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 13:39:40 +0900 Subject: [PATCH 026/266] Change red shade for timing control points --- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 3360b1d1fae3..db1d440f1844 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -26,7 +26,7 @@ public class TimingControlPoint : ControlPoint, IEquatable /// private const double default_beat_length = 60000.0 / 60.0; - public override Color4 GetRepresentingColour(OsuColour colours) => colours.Red1; + public override Color4 GetRepresentingColour(OsuColour colours) => colours.Red2; public static readonly TimingControlPoint DEFAULT = new TimingControlPoint { From 685b19a5714435b3a9f9f3442504b74002a65122 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 13:57:36 +0900 Subject: [PATCH 027/266] Adjust various visuals of summary timeline in a hope for user acceptance .. with a somewhat appealing design. --- .../Timelines/Summary/Parts/BreakPart.cs | 2 +- .../Parts/ControlPointVisualisation.cs | 4 +- .../Summary/Parts/EffectPointVisualisation.cs | 6 +-- .../Summary/Parts/GroupVisualisation.cs | 5 ++- .../Summary/Parts/PreviewTimePart.cs | 2 +- .../Timelines/Summary/SummaryTimeline.cs | 42 +++++++++---------- .../Visualisations/PointVisualisation.cs | 5 ++- 7 files changed, 36 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs index 100f37fd2702..ef1a82596974 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs @@ -64,7 +64,7 @@ private void load(OsuColour colours) RelativeSizeAxes = Axes.Both; InternalChild = new Circle { RelativeSizeAxes = Axes.Both }; - Colour = colours.Gray7; + Colour = colours.Gray6; } } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs index 47169481e227..1df128461e9c 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; @@ -15,7 +16,8 @@ public partial class ControlPointVisualisation : PointVisualisation, IControlPoi public ControlPointVisualisation(ControlPoint point) { Point = point; - Width = 2; + Alpha = 0.3f; + Blending = BlendingParameters.Additive; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs index a3885bc2cc19..41f4b3a36511 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs @@ -3,7 +3,6 @@ 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; @@ -96,10 +95,9 @@ private void refreshDisplay() RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomLeft, Origin = Anchor.CentreLeft, - Width = 1, - Height = 0.75f, + Height = 0.4f, Depth = float.MaxValue, - Colour = effect.GetRepresentingColour(colours).Darken(0.5f), + Colour = effect.GetRepresentingColour(colours), }); } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs index 2c806be162a9..e01900b12900 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs @@ -39,7 +39,10 @@ public GroupVisualisation(ControlPointGroup group) switch (point) { case TimingControlPoint: - AddInternal(new ControlPointVisualisation(point)); + AddInternal(new ControlPointVisualisation(point) + { + Y = -0.4f, + }); break; case EffectControlPoint effect: diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs index 407173034ea8..3a63d1e9b358 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs @@ -32,7 +32,7 @@ private partial class PreviewTimeVisualisation : PointVisualisation public PreviewTimeVisualisation(double time) : base(time) { - Width = 2; + Alpha = 0.8f; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs index 92012936bcda..49110ccee34d 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -23,27 +23,6 @@ private void load(OverlayColourProvider colourProvider) Children = new Drawable[] { - new ControlPointPart - { - Anchor = Anchor.Centre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.Both, - Height = 0.35f - }, - new BookmarkPart - { - Anchor = Anchor.Centre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Both, - Height = 0.35f - }, - new PreviewTimePart - { - Anchor = Anchor.Centre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Both, - Height = 0.35f - }, new Container { Name = "centre line", @@ -73,6 +52,27 @@ private void load(OverlayColourProvider colourProvider) }, } }, + new PreviewTimePart + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Both, + Height = 0.4f, + }, + new ControlPointPart + { + Anchor = Anchor.Centre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.Both, + Height = 0.4f + }, + new BookmarkPart + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Both, + Height = 0.35f + }, new BreakPart { Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs index 3f0c125ada37..571494860fb5 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs @@ -11,12 +11,15 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations /// public partial class PointVisualisation : Circle { + public readonly double StartTime; + public const float MAX_WIDTH = 4; public PointVisualisation(double startTime) : this() { X = (float)startTime; + StartTime = startTime; } public PointVisualisation() @@ -28,7 +31,7 @@ public PointVisualisation() Origin = Anchor.Centre; Width = MAX_WIDTH; - Height = 0.75f; + Height = 0.4f; } } } From 9a7a0cdb341f507ea3ed0f4f9e25f5a01aef6fbf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 14:29:18 +0900 Subject: [PATCH 028/266] Adjust timeline ticks to add a bit more body back --- .../Timeline/TimelineTickDisplay.cs | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 8de508785013..1f357283bdf6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -147,20 +147,12 @@ private void createTicks() var line = getNextUsableLine(); line.X = xPos; - line.Width = PointVisualisation.MAX_WIDTH * size.X; - - if (showTimingChanges.Value) - { - line.Anchor = Anchor.BottomLeft; - line.Origin = Anchor.BottomCentre; - line.Height = 0.7f + size.Y * 0.28f; - } - else - { - line.Anchor = Anchor.CentreLeft; - line.Origin = Anchor.Centre; - line.Height = 0.92f + size.Y * 0.07f; - } + + line.Anchor = Anchor.CentreLeft; + line.Origin = Anchor.Centre; + + line.Height = 0.6f + size.Y * 0.4f; + line.Width = PointVisualisation.MAX_WIDTH * (0.6f + 0.4f * size.X); line.Colour = colour; } From 2ad2ae0c16a969bdb7337b85087d70d54f94f449 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 14:34:01 +0900 Subject: [PATCH 029/266] Add slight offset for timeline BPM display to avoid overlaps --- .../Compose/Components/Timeline/TimelineControlPointGroup.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs index c1b60695235f..98556fda4520 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs @@ -24,7 +24,8 @@ public TimelineControlPointGroup(ControlPointGroup group) Origin = Anchor.TopLeft; - X = (float)group.Time; + // offset visually to avoid overlapping timeline tick display. + X = (float)group.Time + 6; } protected override void LoadComplete() From f65ab6736db5fd5854224ee7a00e6a383e025b1f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 15:41:51 +0900 Subject: [PATCH 030/266] Adjust breaks in timeline to be centered with waveform / hitobjects --- .../Components/Timeline/TimelineBreak.cs | 32 +++---------------- .../Screens/Edit/Compose/ComposeScreen.cs | 8 ++++- 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs index 29030099c8aa..721af9767427 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs @@ -29,11 +29,6 @@ public partial class TimelineBreak : CompositeDrawable, IHasContextMenu public Action? OnDeleted { get; init; } - private Box background = null!; - - [Resolved] - private OsuColour colours { get; set; } = null!; - public TimelineBreak(BreakPeriod b) { Break.Value = b; @@ -53,11 +48,11 @@ private void load(OsuColour colours) { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Horizontal = 5 }, - Child = background = new Box + Child = new Box { RelativeSizeAxes = Axes.Both, Colour = colours.Gray5, - Alpha = 0.7f, + Alpha = 0.9f, }, }, new DragHandle(isStartHandle: true) @@ -88,23 +83,6 @@ protected override void LoadComplete() }, true); } - protected override bool OnHover(HoverEvent e) - { - updateState(); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - updateState(); - base.OnHoverLost(e); - } - - private void updateState() - { - background.FadeColour(IsHovered ? colours.Gray6 : colours.Gray5, 400, Easing.OutQuint); - } - public MenuItem[] ContextMenuItems => new MenuItem[] { new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => OnDeleted?.Invoke(Break.Value)), @@ -159,7 +137,7 @@ private void load() Anchor = Anchor, Origin = Anchor, RelativeSizeAxes = Axes.Y, - CornerRadius = 5, + CornerRadius = 4, Masking = true, Child = new Box { @@ -249,8 +227,8 @@ private void updateState() if (active) colour = colour.Lighten(0.3f); - this.FadeColour(colour, 400, Easing.OutQuint); - handle.ResizeWidthTo(active ? 20 : 10, 400, Easing.OutElasticHalf); + handle.FadeColour(colour, 400, Easing.OutQuint); + handle.ResizeWidthTo(active ? 10 : 8, 400, Easing.OutElasticHalf); } } } diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 9b945e1d6d27..cc338409298c 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -75,7 +75,13 @@ protected override Drawable CreateTimelineContent() Children = new Drawable[] { new TimelineBlueprintContainer(composer), - new TimelineBreakDisplay { RelativeSizeAxes = Axes.Both, }, + new TimelineBreakDisplay + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Height = 0.75f, + }, } }); } From ca2fc7295967bec77de252d39091e04acdccadd9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 16:26:38 +0900 Subject: [PATCH 031/266] Adjust timeline centre marker visuals and bring in front of ticks --- .../Compose/Components/Timeline/CentreMarker.cs | 17 ++++++++++------- .../Compose/Components/Timeline/Timeline.cs | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs index be1888684e4f..e3542cbf9bb9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs @@ -3,10 +3,12 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Overlays; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { @@ -23,7 +25,11 @@ public CentreMarker() Anchor = Anchor.TopCentre; Origin = Anchor.TopCentre; + } + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colours) + { InternalChildren = new Drawable[] { new Box @@ -32,21 +38,18 @@ public CentreMarker() Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, Width = bar_width, + Blending = BlendingParameters.Additive, + Colour = ColourInfo.GradientVertical(colours.Colour2, Color4.Black), }, new Triangle { Anchor = Anchor.TopCentre, Origin = Anchor.BottomCentre, Size = new Vector2(triangle_width, triangle_width * 0.8f), - Scale = new Vector2(1, -1) + Scale = new Vector2(1, -1), + Colour = colours.Colour2, }, }; } - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colours) - { - Colour = colours.Highlight1; - } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 05e44d473760..6ce5c068010d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -125,8 +125,8 @@ private void load(IBindable beatmap, OsuColour colours, OsuConfi MidColour = colours.BlueDark, HighColour = colours.BlueDarker, }, - centreMarker.CreateProxy(), ticks.CreateProxy(), + centreMarker.CreateProxy(), new Box { Name = "zero marker", From 43addc84003c32caa2b5417143f5843dace4cba9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 16:28:31 +0900 Subject: [PATCH 032/266] Fix test regression --- osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs b/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs index 3319788c8a1c..ad8c29d180e1 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs @@ -29,7 +29,7 @@ public void TestScenePreviewTimeline() AddStep("set preview time to -1", () => EditorBeatmap.PreviewTime.Value = -1); AddAssert("preview time line should not show", () => !Editor.ChildrenOfType().Single().Children.Any()); AddStep("set preview time to 1000", () => EditorBeatmap.PreviewTime.Value = 1000); - AddAssert("preview time line should show", () => Editor.ChildrenOfType().Single().Children.Single().Alpha == 1); + AddAssert("preview time line should show", () => Editor.ChildrenOfType().Single().Children.Single().Alpha, () => Is.GreaterThan(0)); } } } From 275e7aa451f5f9a0d8de2889a307903fa653124e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 16:26:38 +0900 Subject: [PATCH 033/266] Adjust timeline centre marker visuals and bring in front of ticks --- .../Screens/Edit/Compose/Components/Timeline/CentreMarker.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs index e3542cbf9bb9..3d76656a1450 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; From 24547226019dc7a080f317b436a98c5765f82bb0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 14:18:41 +0900 Subject: [PATCH 034/266] Add tooltips to summary timeline display --- .../Timelines/Summary/Parts/BreakPart.cs | 10 +++++++- .../Parts/ControlPointVisualisation.cs | 24 ++++++++++++++++++- .../Summary/Parts/EffectPointVisualisation.cs | 19 ++++++++++++++- .../Summary/Parts/PreviewTimePart.cs | 7 +++++- .../Visualisations/PointVisualisation.cs | 10 +++----- .../Timeline/TimelineTickDisplay.cs | 2 +- 6 files changed, 60 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs index ef1a82596974..c20738bbd910 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs @@ -4,9 +4,12 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Beatmaps.Timing; +using osu.Game.Extensions; using osu.Game.Graphics; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts @@ -46,12 +49,15 @@ protected override void LoadComplete() }, true); } - private partial class BreakVisualisation : PoolableDrawable + private partial class BreakVisualisation : PoolableDrawable, IHasTooltip { + private BreakPeriod breakPeriod; + public BreakPeriod BreakPeriod { set { + breakPeriod = value; X = (float)value.StartTime; Width = (float)value.Duration; } @@ -66,6 +72,8 @@ private void load(OsuColour colours) InternalChild = new Circle { RelativeSizeAxes = Axes.Both }; Colour = colours.Gray6; } + + public LocalisableString TooltipText => $"{breakPeriod.StartTime.ToEditorFormattedString()} - {breakPeriod.EndTime.ToEditorFormattedString()} break time"; } } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs index 1df128461e9c..977aadd6c34f 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs @@ -2,18 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { - public partial class ControlPointVisualisation : PointVisualisation, IControlPointVisualisation + public partial class ControlPointVisualisation : PointVisualisation, IControlPointVisualisation, IHasTooltip { protected readonly ControlPoint Point; public ControlPointVisualisation(ControlPoint point) + : base(point.Time) { Point = point; Alpha = 0.3f; @@ -27,5 +32,22 @@ private void load(OsuColour colours) } public bool IsVisuallyRedundant(ControlPoint other) => other.GetType() == Point.GetType(); + + public LocalisableString TooltipText + { + get + { + switch (Point) + { + case EffectControlPoint effect: + return $"{StartTime.ToEditorFormattedString()} effect [{effect.ScrollSpeed:N2}x scroll{(effect.KiaiMode ? " kiai" : "")}]"; + + case TimingControlPoint timing: + return $"{StartTime.ToEditorFormattedString()} timing [{timing.BPM:N2} bpm {timing.TimeSignature.GetDescription()}]"; + } + + return string.Empty; + } + } } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs index 41f4b3a36511..f1e2b52ad8be 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs @@ -5,8 +5,11 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Extensions; using osu.Game.Graphics; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts @@ -90,7 +93,7 @@ private void refreshDisplay() Width = (float)(nextControlPoint.Time - effect.Time); - AddInternal(new Circle + AddInternal(new KiaiVisualisation(effect.Time, nextControlPoint.Time) { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomLeft, @@ -102,6 +105,20 @@ private void refreshDisplay() } } + private partial class KiaiVisualisation : Circle, IHasTooltip + { + private readonly double startTime; + private readonly double endTime; + + public KiaiVisualisation(double startTime, double endTime) + { + this.startTime = startTime; + this.endTime = endTime; + } + + public LocalisableString TooltipText => $"{startTime.ToEditorFormattedString()} - {endTime.ToEditorFormattedString()} kiai time"; + } + // kiai sections display duration, so are required to be visualised. public bool IsVisuallyRedundant(ControlPoint other) => other is EffectControlPoint otherEffect && effect.KiaiMode == otherEffect.KiaiMode; } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs index 3a63d1e9b358..67bb1ef5005f 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs @@ -3,6 +3,9 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; +using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; @@ -27,7 +30,7 @@ protected override void LoadBeatmap(EditorBeatmap beatmap) }, true); } - private partial class PreviewTimeVisualisation : PointVisualisation + private partial class PreviewTimeVisualisation : PointVisualisation, IHasTooltip { public PreviewTimeVisualisation(double time) : base(time) @@ -37,6 +40,8 @@ public PreviewTimeVisualisation(double time) [BackgroundDependencyLoader] private void load(OsuColour colours) => Colour = colours.Green1; + + public LocalisableString TooltipText => $"{StartTime.ToEditorFormattedString()} preview time"; } } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs index 571494860fb5..9c16f457f751 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs @@ -16,13 +16,6 @@ public partial class PointVisualisation : Circle public const float MAX_WIDTH = 4; public PointVisualisation(double startTime) - : this() - { - X = (float)startTime; - StartTime = startTime; - } - - public PointVisualisation() { RelativePositionAxes = Axes.Both; RelativeSizeAxes = Axes.Y; @@ -32,6 +25,9 @@ public PointVisualisation() Width = MAX_WIDTH; Height = 0.4f; + + X = (float)startTime; + StartTime = startTime; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 1f357283bdf6..def528d9e58d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -191,7 +191,7 @@ Drawable getNextUsableLine() { PointVisualisation point; if (drawableIndex >= Count) - Add(point = new PointVisualisation()); + Add(point = new PointVisualisation(0)); else point = Children[drawableIndex]; From d2cb07b15796d24e2b20753a06c520d059131ad7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 17:22:36 +0900 Subject: [PATCH 035/266] Add a bit more padding between node overlays and hitobjects in timeline --- .../Edit/Compose/Components/Timeline/DifficultyPointPiece.cs | 1 + .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 1 + .../Compose/Components/Timeline/TimelineHitObjectBlueprint.cs | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index 3ad6095965b3..44235e5d0b27 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -33,6 +33,7 @@ public partial class DifficultyPointPiece : HitObjectPointPiece, IHasPopover public DifficultyPointPiece(HitObject hitObject) { HitObject = hitObject; + Y = -2.5f; speedMultiplier = (hitObject as IHasSliderVelocity)?.SliderVelocityMultiplierBindable.GetBoundCopy(); } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 1f9c7a891bfe..8c7603021a0b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -35,6 +35,7 @@ public partial class SamplePointPiece : HitObjectPointPiece, IHasPopover public SamplePointPiece(HitObject hitObject) { HitObject = hitObject; + Y = 2.5f; } public bool AlternativeColor { get; init; } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 753856199a56..a168dcbd3e3f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -519,7 +519,7 @@ public ExtendableCircle() { Type = EdgeEffectType.Shadow, Radius = 5, - Colour = Color4.Black.Opacity(0.4f) + Colour = Color4.Black.Opacity(0.05f) } }; } From 4be5d056c573234912aafb5b19f769fd8e81afca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 17:23:48 +0900 Subject: [PATCH 036/266] Reduce opacity of centre marker slightly --- .../Screens/Edit/Compose/Components/Timeline/CentreMarker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs index e3542cbf9bb9..7d8622905cda 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs @@ -2,13 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Overlays; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { @@ -39,7 +39,7 @@ private void load(OverlayColourProvider colours) RelativeSizeAxes = Axes.Y, Width = bar_width, Blending = BlendingParameters.Additive, - Colour = ColourInfo.GradientVertical(colours.Colour2, Color4.Black), + Colour = ColourInfo.GradientVertical(colours.Colour2.Opacity(0.6f), colours.Colour2.Opacity(0)), }, new Triangle { From 2453e2fd00b3b484ccc9f14e1555c87fc0f6beee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 18:11:23 +0900 Subject: [PATCH 037/266] Fix nullability issue --- .../Edit/Components/Timelines/Summary/Parts/BreakPart.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs index c20738bbd910..17e0d47676c8 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs @@ -51,7 +51,7 @@ protected override void LoadComplete() private partial class BreakVisualisation : PoolableDrawable, IHasTooltip { - private BreakPeriod breakPeriod; + private BreakPeriod breakPeriod = null!; public BreakPeriod BreakPeriod { From 217b01d3031b550e5e35f9a39142dbe9da27fd84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Jul 2024 11:10:52 +0200 Subject: [PATCH 038/266] Apply tooltip to bookmark pieces too Bookmarks don't show on real beatmaps, but they do show in test scenes (namely `TestSceneEditorSummaryTimeline`). Also does some more changes to adjust the markers to the latest updates to other markers. --- .../Components/Timelines/Summary/Parts/BookmarkPart.cs | 8 ++++++-- .../Edit/Components/Timelines/Summary/SummaryTimeline.cs | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs index ea71f24e9cb3..189cb4ba4a43 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs @@ -2,6 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; +using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; @@ -19,16 +22,17 @@ protected override void LoadBeatmap(EditorBeatmap beatmap) Add(new BookmarkVisualisation(bookmark)); } - private partial class BookmarkVisualisation : PointVisualisation + private partial class BookmarkVisualisation : PointVisualisation, IHasTooltip { public BookmarkVisualisation(double startTime) : base(startTime) { - Width = 2; } [BackgroundDependencyLoader] private void load(OsuColour colours) => Colour = colours.Blue; + + public LocalisableString TooltipText => $"{StartTime.ToEditorFormattedString()} bookmark"; } } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs index 49110ccee34d..a495442c1d46 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -71,7 +71,7 @@ private void load(OverlayColourProvider colourProvider) Anchor = Anchor.Centre, Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.Both, - Height = 0.35f + Height = 0.4f }, new BreakPart { From ad2b354d9c2543d4bedc1316b9401b455703e3c3 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 11 Jul 2024 17:01:07 +0900 Subject: [PATCH 039/266] Update sample looping behaviour to better suit new sample --- osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs b/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs index fec36fa7fa64..d84e1d760d74 100644 --- a/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs +++ b/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs @@ -67,7 +67,7 @@ private void load(AudioManager audio) protected override void LoadComplete() { base.LoadComplete(); - Progress.BindValueChanged(progressChanged, true); + Progress.BindValueChanged(progressChanged); } protected override void Confirm() @@ -114,13 +114,13 @@ private void progressChanged(ValueChangedEvent progress) if (progress.NewValue < progress.OldValue) return; - if (Clock.CurrentTime - lastTickPlaybackTime < 30) + if (Clock.CurrentTime - lastTickPlaybackTime < 40) return; var channel = tickSample.GetChannel(); - channel.Frequency.Value = 1 + progress.NewValue * 0.5f; - channel.Volume.Value = 0.5f + progress.NewValue / 2f; + channel.Frequency.Value = 1 + progress.NewValue; + channel.Volume.Value = 0.1f + progress.NewValue / 2f; channel.Play(); From 320df7da2b564b53820860439ac39f64499b56a5 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 11 Jul 2024 18:22:27 +0900 Subject: [PATCH 040/266] Use separate samples for scrolling to top and scrolling to previous --- .../Graphics/UserInterface/HoverSampleSet.cs | 6 ------ osu.Game/Overlays/OverlayScrollContainer.cs | 19 +++++++++++++++++-- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs index 72d50eb042cf..5b0fbc693eee 100644 --- a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs +++ b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs @@ -16,15 +16,9 @@ public enum HoverSampleSet [Description("button-sidebar")] ButtonSidebar, - [Description("toolbar")] - Toolbar, - [Description("tabselect")] TabSelect, - [Description("scrolltotop")] - ScrollToTop, - [Description("dialog-cancel")] DialogCancel, diff --git a/osu.Game/Overlays/OverlayScrollContainer.cs b/osu.Game/Overlays/OverlayScrollContainer.cs index a99cf08abb3e..4328977a8df3 100644 --- a/osu.Game/Overlays/OverlayScrollContainer.cs +++ b/osu.Game/Overlays/OverlayScrollContainer.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -112,8 +114,12 @@ public Visibility State public Bindable LastScrollTarget = new Bindable(); + protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(); + + private Sample scrollToTopSample; + private Sample scrollToPreviousSample; + public ScrollBackButton() - : base(HoverSampleSet.ScrollToTop) { Size = new Vector2(50); Alpha = 0; @@ -150,11 +156,14 @@ public ScrollBackButton() } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider, AudioManager audio) { IdleColour = colourProvider.Background6; HoverColour = colourProvider.Background5; flashColour = colourProvider.Light1; + + scrollToTopSample = audio.Samples.Get(@"UI/scroll-to-top"); + scrollToPreviousSample = audio.Samples.Get(@"UI/scroll-to-previous"); } protected override void LoadComplete() @@ -171,6 +180,12 @@ protected override void LoadComplete() protected override bool OnClick(ClickEvent e) { background.FlashColour(flashColour, 800, Easing.OutQuint); + + if (LastScrollTarget.Value == null) + scrollToTopSample?.Play(); + else + scrollToPreviousSample?.Play(); + return base.OnClick(e); } From e8f7213b3b2907c1c2aee8d0d4be92aeee23f0c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 19:20:41 +0900 Subject: [PATCH 041/266] Move logo depth to a forward passing --- osu.Game/OsuGame.cs | 3 +-- osu.Game/Screens/Footer/ScreenFooter.cs | 6 ++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index dfd9f41e1a37..2580e44e73d9 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -297,8 +297,6 @@ public void CloseAllOverlays(bool hideToolbar = true) if (hideToolbar) Toolbar.Hide(); } - public void ChangeLogoDepth(bool inFrontOfFooter) => ScreenContainer.ChangeChildDepth(logoContainer, inFrontOfFooter ? float.MinValue : 0); - protected override UserInputManager CreateUserInputManager() { var userInputManager = base.CreateUserInputManager(); @@ -996,6 +994,7 @@ protected override void LoadComplete() RelativeSizeAxes = Axes.Both, Child = ScreenFooter = new ScreenFooter(backReceptor) { + RequestLogoInFront = inFront => ScreenContainer.ChangeChildDepth(logoContainer, inFront ? float.MinValue : 0), OnBack = () => { if (!(ScreenStack.CurrentScreen is IOsuScreen currentScreen)) diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index 4c020fc95e2b..6a1efcf87a2b 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -44,6 +44,8 @@ public partial class ScreenFooter : OverlayContainer public ScreenBackButton BackButton { get; private set; } = null!; + public Action? RequestLogoInFront { get; set; } + public Action? OnBack; public ScreenFooter(BackReceptor? receptor = null) @@ -114,7 +116,7 @@ public void StartTrackingLogo(OsuLogo logo, float duration = 0, Easing easing = changeLogoDepthDelegate = null; logoTrackingContainer.StartTracking(logo, duration, easing); - game?.ChangeLogoDepth(inFrontOfFooter: true); + RequestLogoInFront?.Invoke(true); } public void StopTrackingLogo() @@ -122,7 +124,7 @@ public void StopTrackingLogo() logoTrackingContainer.StopTracking(); if (game != null) - changeLogoDepthDelegate = Scheduler.AddDelayed(() => game.ChangeLogoDepth(inFrontOfFooter: false), transition_duration); + changeLogoDepthDelegate = Scheduler.AddDelayed(() => RequestLogoInFront?.Invoke(false), transition_duration); } protected override void PopIn() From 9a1939a922c29657e60745348469f024c00ff99f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 21:25:21 +0900 Subject: [PATCH 042/266] Move `logoContainer` local again --- osu.Game/OsuGame.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 2580e44e73d9..388a98d94703 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -84,7 +84,7 @@ namespace osu.Game public partial class OsuGame : OsuGameBase, IKeyBindingHandler, ILocalUserPlayInfo, IPerformFromScreenRunner, IOverlayManager, ILinkHandler { #if DEBUG - // Different port allows runnning release and debug builds alongside each other. + // Different port allows running release and debug builds alongside each other. public const int IPC_PORT = 44824; #else public const int IPC_PORT = 44823; @@ -137,8 +137,6 @@ public partial class OsuGame : OsuGameBase, IKeyBindingHandler, IL protected ScalingContainer ScreenContainer { get; private set; } - private Container logoContainer; - protected Container ScreenOffsetContainer { get; private set; } private Container overlayOffsetContainer; @@ -954,6 +952,8 @@ protected override void LoadComplete() Add(sessionIdleTracker); + Container logoContainer; + AddRange(new Drawable[] { new VolumeControlReceptor From 3eaac11b4426de652087eb1f18d9e4496eb941d8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Jul 2024 11:26:45 +0300 Subject: [PATCH 043/266] Add profile hue attribute to API model --- osu.Game/Online/API/Requests/Responses/APIUser.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 4a31718f2884..1c07b38667ec 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -201,6 +201,10 @@ private string[] playStyle [JsonProperty(@"playmode")] public string PlayMode; + [JsonProperty(@"profile_hue")] + [CanBeNull] + public int? ProfileHue; + [JsonProperty(@"profile_order")] public string[] ProfileOrder; From 933626a64b855fced565cc2f4ccc516abac73da5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Jul 2024 11:46:39 +0300 Subject: [PATCH 044/266] Support using custom hue in `OverlayColourProvider` --- osu.Game/Overlays/OverlayColourProvider.cs | 57 +++++----------------- 1 file changed, 11 insertions(+), 46 deletions(-) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index 06b42eafc0d5..3b0af77365b9 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -1,7 +1,6 @@ // 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 osuTK; using osuTK.Graphics; @@ -59,54 +58,20 @@ public void ChangeColourScheme(OverlayColourScheme colourScheme) private Color4 getColour(float saturation, float lightness) => Color4.FromHsl(new Vector4(getBaseHue(ColourScheme), saturation, lightness, 1)); - // See https://github.com/ppy/osu-web/blob/5a536d217a21582aad999db50a981003d3ad5659/app/helpers.php#L1620-L1628 - private static float getBaseHue(OverlayColourScheme colourScheme) - { - switch (colourScheme) - { - default: - throw new ArgumentException($@"{colourScheme} colour scheme does not provide a hue value in {nameof(getBaseHue)}."); - - case OverlayColourScheme.Red: - return 0; - - case OverlayColourScheme.Pink: - return 333 / 360f; - - case OverlayColourScheme.Orange: - return 45 / 360f; - - case OverlayColourScheme.Lime: - return 90 / 360f; - - case OverlayColourScheme.Green: - return 125 / 360f; - - case OverlayColourScheme.Aquamarine: - return 160 / 360f; - - case OverlayColourScheme.Purple: - return 255 / 360f; - - case OverlayColourScheme.Blue: - return 200 / 360f; - - case OverlayColourScheme.Plum: - return 320 / 360f; - } - } + private static float getBaseHue(OverlayColourScheme colourScheme) => (int)colourScheme / 360f; } + // See https://github.com/ppy/osu-web/blob/5a536d217a21582aad999db50a981003d3ad5659/app/helpers.php#L1620-L1628 public enum OverlayColourScheme { - Red, - Pink, - Orange, - Lime, - Green, - Purple, - Blue, - Plum, - Aquamarine + Red = 0, + Orange = 45, + Lime = 90, + Green = 125, + Aquamarine = 160, + Blue = 200, + Purple = 255, + Plum = 320, + Pink = 333, } } From b292bf383205fa5d5994b95355a22abfa1d87f07 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Jul 2024 11:47:36 +0300 Subject: [PATCH 045/266] Support using custom hue in user profile overlay --- osu.Game/Overlays/FullscreenOverlay.cs | 29 ++++-- osu.Game/Overlays/UserProfileOverlay.cs | 115 +++++++++++++----------- 2 files changed, 86 insertions(+), 58 deletions(-) diff --git a/osu.Game/Overlays/FullscreenOverlay.cs b/osu.Game/Overlays/FullscreenOverlay.cs index 6ddf1eecf07c..2a09147c7634 100644 --- a/osu.Game/Overlays/FullscreenOverlay.cs +++ b/osu.Game/Overlays/FullscreenOverlay.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics.CodeAnalysis; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -22,7 +23,7 @@ public abstract partial class FullscreenOverlay : WaveOverlayContainer, IName public virtual LocalisableString Title => Header.Title.Title; public virtual LocalisableString Description => Header.Title.Description; - public T Header { get; } + public T Header { get; private set; } protected virtual Color4 BackgroundColour => ColourProvider.Background5; @@ -34,11 +35,12 @@ public abstract partial class FullscreenOverlay : WaveOverlayContainer, IName protected override Container Content => content; + private readonly Box background; private readonly Container content; protected FullscreenOverlay(OverlayColourScheme colourScheme) { - Header = CreateHeader(); + RecreateHeader(); ColourProvider = new OverlayColourProvider(colourScheme); @@ -60,10 +62,9 @@ protected FullscreenOverlay(OverlayColourScheme colourScheme) base.Content.AddRange(new Drawable[] { - new Box + background = new Box { RelativeSizeAxes = Axes.Both, - Colour = BackgroundColour }, content = new Container { @@ -75,14 +76,17 @@ protected FullscreenOverlay(OverlayColourScheme colourScheme) [BackgroundDependencyLoader] private void load() { - Waves.FirstWaveColour = ColourProvider.Light4; - Waves.SecondWaveColour = ColourProvider.Light3; - Waves.ThirdWaveColour = ColourProvider.Dark4; - Waves.FourthWaveColour = ColourProvider.Dark3; + UpdateColours(); } protected abstract T CreateHeader(); + [MemberNotNull(nameof(Header))] + protected void RecreateHeader() + { + Header = CreateHeader(); + } + public override void Show() { if (State.Value == Visibility.Visible) @@ -96,6 +100,15 @@ public override void Show() } } + public void UpdateColours() + { + Waves.FirstWaveColour = ColourProvider.Light4; + Waves.SecondWaveColour = ColourProvider.Light3; + Waves.ThirdWaveColour = ColourProvider.Dark4; + Waves.FourthWaveColour = ColourProvider.Dark3; + background.Colour = BackgroundColour; + } + protected override void PopIn() { base.PopIn(); diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 9840551d9f25..c8b64bf2f14c 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -99,11 +99,11 @@ private void fetchAndSetContent() if (user.OnlineID == Header.User.Value?.User.Id && ruleset?.MatchesOnlineID(Header.User.Value?.Ruleset) == true) return; - if (sectionsContainer != null) - sectionsContainer.ExpandableHeader = null; + sectionsContainer?.ScrollToTop(); + sectionsContainer?.Clear(); + tabs?.Clear(); userReq?.Cancel(); - Clear(); lastSection = null; sections = !user.IsBot @@ -119,20 +119,74 @@ private void fetchAndSetContent() } : Array.Empty(); - tabs = new ProfileSectionTabControl + setupBaseContent(OverlayColourScheme.Pink); + + if (API.State.Value != APIState.Offline) { - RelativeSizeAxes = Axes.X, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }; + userReq = user.OnlineID > 1 ? new GetUserRequest(user.OnlineID, ruleset) : new GetUserRequest(user.Username, ruleset); + userReq.Success += u => userLoadComplete(u, ruleset); + + API.Queue(userReq); + loadingLayer.Show(); + } + } - Add(new OsuContextMenuContainer + private void userLoadComplete(APIUser loadedUser, IRulesetInfo? userRuleset) + { + Debug.Assert(sections != null && sectionsContainer != null && tabs != null); + + // reuse header and content if same colour scheme, otherwise recreate both. + var profileScheme = (OverlayColourScheme?)loadedUser.ProfileHue ?? OverlayColourScheme.Pink; + if (profileScheme != ColourProvider.ColourScheme) + setupBaseContent(profileScheme); + + var actualRuleset = rulesets.GetRuleset(userRuleset?.ShortName ?? loadedUser.PlayMode).AsNonNull(); + + var userProfile = new UserProfileData(loadedUser, actualRuleset); + Header.User.Value = userProfile; + + if (loadedUser.ProfileOrder != null) + { + foreach (string id in loadedUser.ProfileOrder) + { + var sec = sections.FirstOrDefault(s => s.Identifier == id); + + if (sec != null) + { + sec.User.Value = userProfile; + + sectionsContainer.Add(sec); + tabs.AddItem(sec); + } + } + } + + loadingLayer.Hide(); + } + + private void setupBaseContent(OverlayColourScheme colourScheme) + { + var previousColourScheme = ColourProvider.ColourScheme; + ColourProvider.ChangeColourScheme(colourScheme); + + if (sectionsContainer != null && colourScheme == previousColourScheme) + return; + + RecreateHeader(); + UpdateColours(); + + Child = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, Child = sectionsContainer = new ProfileSectionsContainer { ExpandableHeader = Header, - FixedHeader = tabs, + FixedHeader = tabs = new ProfileSectionTabControl + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, HeaderBackground = new Box { // this is only visible as the ProfileTabControl background @@ -140,7 +194,7 @@ private void fetchAndSetContent() RelativeSizeAxes = Axes.Both }, } - }); + }; sectionsContainer.SelectedSection.ValueChanged += section => { @@ -167,45 +221,6 @@ private void fetchAndSetContent() sectionsContainer.ScrollTo(lastSection); } }; - - sectionsContainer.ScrollToTop(); - - if (API.State.Value != APIState.Offline) - { - userReq = user.OnlineID > 1 ? new GetUserRequest(user.OnlineID, ruleset) : new GetUserRequest(user.Username, ruleset); - userReq.Success += u => userLoadComplete(u, ruleset); - - API.Queue(userReq); - loadingLayer.Show(); - } - } - - private void userLoadComplete(APIUser loadedUser, IRulesetInfo? userRuleset) - { - Debug.Assert(sections != null && sectionsContainer != null && tabs != null); - - var actualRuleset = rulesets.GetRuleset(userRuleset?.ShortName ?? loadedUser.PlayMode).AsNonNull(); - - var userProfile = new UserProfileData(loadedUser, actualRuleset); - Header.User.Value = userProfile; - - if (loadedUser.ProfileOrder != null) - { - foreach (string id in loadedUser.ProfileOrder) - { - var sec = sections.FirstOrDefault(s => s.Identifier == id); - - if (sec != null) - { - sec.User.Value = userProfile; - - sectionsContainer.Add(sec); - tabs.AddItem(sec); - } - } - } - - loadingLayer.Hide(); } private partial class ProfileSectionTabControl : OsuTabControl From be1d3c0ea4e0fc3527c0d660e6432481bc0f9c3c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Jul 2024 11:47:48 +0300 Subject: [PATCH 046/266] Add test coverage --- .../Online/TestSceneUserProfileOverlay.cs | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index fa68c931d870..8dbd493920c7 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -111,6 +111,87 @@ public void TestLogin() AddStep("complete request", () => pendingRequest.TriggerSuccess(TEST_USER)); } + [Test] + public void TestCustomColourScheme() + { + int hue = 0; + + AddSliderStep("hue", 0, 360, 222, h => hue = h); + + AddStep("set up request handling", () => + { + dummyAPI.HandleRequest = req => + { + if (req is GetUserRequest getUserRequest) + { + getUserRequest.TriggerSuccess(new APIUser + { + Username = $"Colorful #{hue}", + Id = 1, + CountryCode = CountryCode.JP, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", + ProfileHue = hue, + }); + return true; + } + + return false; + }; + }); + + AddStep("show user", () => profile.ShowUser(new APIUser { Id = 1 })); + } + + [Test] + public void TestCustomColourSchemeWithReload() + { + int hue = 0; + GetUserRequest pendingRequest = null!; + + AddSliderStep("hue", 0, 360, 222, h => hue = h); + + AddStep("set up request handling", () => + { + dummyAPI.HandleRequest = req => + { + if (req is GetUserRequest getUserRequest) + { + pendingRequest = getUserRequest; + return true; + } + + return false; + }; + }); + + AddStep("show user", () => profile.ShowUser(new APIUser { Id = 1 })); + + AddWaitStep("wait some", 3); + AddStep("complete request", () => pendingRequest.TriggerSuccess(new APIUser + { + Username = $"Colorful #{hue}", + Id = 1, + CountryCode = CountryCode.JP, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", + ProfileHue = hue, + })); + + int hue2 = 0; + + AddSliderStep("hue 2", 0, 360, 50, h => hue2 = h); + AddStep("show user", () => profile.ShowUser(new APIUser { Id = 1 })); + AddWaitStep("wait some", 3); + + AddStep("complete request", () => pendingRequest.TriggerSuccess(new APIUser + { + Username = $"Colorful #{hue2}", + Id = 1, + CountryCode = CountryCode.JP, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", + ProfileHue = hue2, + })); + } + public static readonly APIUser TEST_USER = new APIUser { Username = @"Somebody", From 9ed97d03a8143e74064ddb76f07f6dcd61af04ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 13 Jul 2024 20:22:52 +0900 Subject: [PATCH 047/266] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 447c783b2911..4d9e7dea33d9 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From b12790c684bd0474d3f36c18cb71dd21c2922c92 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Jul 2024 18:29:03 +0300 Subject: [PATCH 048/266] Fix hue number 360 giving off a gray colour scheme They say it's not a bug, it's a feature...I dunno maybe later. --- osu.Game/Overlays/OverlayColourProvider.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index 3b0af77365b9..5b6579e6cf2d 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -58,7 +58,12 @@ public void ChangeColourScheme(OverlayColourScheme colourScheme) private Color4 getColour(float saturation, float lightness) => Color4.FromHsl(new Vector4(getBaseHue(ColourScheme), saturation, lightness, 1)); - private static float getBaseHue(OverlayColourScheme colourScheme) => (int)colourScheme / 360f; + private static float getBaseHue(OverlayColourScheme colourScheme) + { + // intentionally round hue number back to zero when it's 360, because that number apparently gives off a nice-looking gray colour scheme but is totally against expectation (maybe we can use this one day). + int hueNumber = (int)colourScheme % 360; + return hueNumber / 360f; + } } // See https://github.com/ppy/osu-web/blob/5a536d217a21582aad999db50a981003d3ad5659/app/helpers.php#L1620-L1628 From 43d08f702aa550aa2e41117c8970ae2b6fc0865d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Jul 2024 18:43:46 +0300 Subject: [PATCH 049/266] Or just use `Colour4` where we have that fixed --- osu.Game/Overlays/OverlayColourProvider.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index 5b6579e6cf2d..9613bc28570c 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; using osuTK.Graphics; namespace osu.Game.Overlays @@ -56,14 +55,9 @@ public void ChangeColourScheme(OverlayColourScheme colourScheme) ColourScheme = colourScheme; } - private Color4 getColour(float saturation, float lightness) => Color4.FromHsl(new Vector4(getBaseHue(ColourScheme), saturation, lightness, 1)); + private Color4 getColour(float saturation, float lightness) => Framework.Graphics.Colour4.FromHSL(getBaseHue(ColourScheme), saturation, lightness); - private static float getBaseHue(OverlayColourScheme colourScheme) - { - // intentionally round hue number back to zero when it's 360, because that number apparently gives off a nice-looking gray colour scheme but is totally against expectation (maybe we can use this one day). - int hueNumber = (int)colourScheme % 360; - return hueNumber / 360f; - } + private static float getBaseHue(OverlayColourScheme colourScheme) => (int)colourScheme / 360f; } // See https://github.com/ppy/osu-web/blob/5a536d217a21582aad999db50a981003d3ad5659/app/helpers.php#L1620-L1628 From 2c102fc9d01726f84d185f0da1373e38f5c0163b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 13 Jul 2024 23:54:10 +0900 Subject: [PATCH 050/266] Fix test failure in `TestMetadataTransferred` --- osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs index 16e66cb2c5ba..a47da4d505cd 100644 --- a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs @@ -479,6 +479,7 @@ public void TestMetadataTransferred() using var rulesets = new RealmRulesetStore(realm, storage); using var __ = getBeatmapArchive(out string pathOriginal); + using var _ = getBeatmapArchiveWithModifications(out string pathMissingOneBeatmap, directory => { // arbitrary beatmap removal @@ -496,7 +497,7 @@ public void TestMetadataTransferred() Debug.Assert(importAfterUpdate != null); Assert.That(importBeforeUpdate.ID, Is.Not.EqualTo(importAfterUpdate.ID)); - Assert.That(importBeforeUpdate.Value.DateAdded, Is.EqualTo(importAfterUpdate.Value.DateAdded)); + Assert.That(importBeforeUpdate.Value.DateAdded, Is.EqualTo(importAfterUpdate.Value.DateAdded).Within(TimeSpan.FromSeconds(1))); }); } From adb803c7a9b41d60e27f9965e470ee83921bae70 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 14 Jul 2024 15:19:26 +0300 Subject: [PATCH 051/266] Force recreating sections container when loading new user to avoid weird UX when scrolled away --- osu.Game/Overlays/UserProfileOverlay.cs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index c8b64bf2f14c..8c750b5d8340 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -99,9 +99,8 @@ private void fetchAndSetContent() if (user.OnlineID == Header.User.Value?.User.Id && ruleset?.MatchesOnlineID(Header.User.Value?.Ruleset) == true) return; - sectionsContainer?.ScrollToTop(); - sectionsContainer?.Clear(); - tabs?.Clear(); + if (sectionsContainer != null) + sectionsContainer.ExpandableHeader = null; userReq?.Cancel(); lastSection = null; @@ -119,7 +118,7 @@ private void fetchAndSetContent() } : Array.Empty(); - setupBaseContent(OverlayColourScheme.Pink); + setupBaseContent(OverlayColourScheme.Pink, forceContentRecreation: true); if (API.State.Value != APIState.Offline) { @@ -138,7 +137,7 @@ private void userLoadComplete(APIUser loadedUser, IRulesetInfo? userRuleset) // reuse header and content if same colour scheme, otherwise recreate both. var profileScheme = (OverlayColourScheme?)loadedUser.ProfileHue ?? OverlayColourScheme.Pink; if (profileScheme != ColourProvider.ColourScheme) - setupBaseContent(profileScheme); + setupBaseContent(profileScheme, forceContentRecreation: false); var actualRuleset = rulesets.GetRuleset(userRuleset?.ShortName ?? loadedUser.PlayMode).AsNonNull(); @@ -164,17 +163,19 @@ private void userLoadComplete(APIUser loadedUser, IRulesetInfo? userRuleset) loadingLayer.Hide(); } - private void setupBaseContent(OverlayColourScheme colourScheme) + private void setupBaseContent(OverlayColourScheme colourScheme, bool forceContentRecreation) { var previousColourScheme = ColourProvider.ColourScheme; ColourProvider.ChangeColourScheme(colourScheme); - if (sectionsContainer != null && colourScheme == previousColourScheme) + if (colourScheme != previousColourScheme) + { + RecreateHeader(); + UpdateColours(); + } + else if (!forceContentRecreation) return; - RecreateHeader(); - UpdateColours(); - Child = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, From 6cdcd6136d157f91b17a4a7a6ff579a516705ac4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jul 2024 21:02:54 +0900 Subject: [PATCH 052/266] Fix editor toolboxes being incorrectly chopped --- osu.Game/Rulesets/Edit/ExpandingToolboxContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/ExpandingToolboxContainer.cs b/osu.Game/Rulesets/Edit/ExpandingToolboxContainer.cs index 36cbf4988582..c2ab5a6eb9be 100644 --- a/osu.Game/Rulesets/Edit/ExpandingToolboxContainer.cs +++ b/osu.Game/Rulesets/Edit/ExpandingToolboxContainer.cs @@ -18,7 +18,7 @@ public ExpandingToolboxContainer(float contractedWidth, float expandedWidth) RelativeSizeAxes = Axes.Y; FillFlow.Spacing = new Vector2(5); - Padding = new MarginPadding { Vertical = 5 }; + FillFlow.Padding = new MarginPadding { Vertical = 5 }; } protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && anyToolboxHovered(screenSpacePos); From 1083e71ce6aadd9b9921b7dfbc8f1502390ccfd2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jul 2024 03:02:04 +0900 Subject: [PATCH 053/266] Fix potential crash when exiting daily challenge screen Without the schedule this will potentially run after disposal of the local drawable hierarchy. Closes https://github.com/ppy/osu/issues/28875. --- .../OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs index 4d4ae755fc09..2b2c3a5e1f56 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs @@ -122,7 +122,7 @@ public void RefetchScores() { var request = new IndexPlaylistScoresRequest(room.RoomID.Value!.Value, playlistItem.ID); - request.Success += req => + request.Success += req => Schedule(() => { var best = req.Scores.Select(s => s.CreateScoreInfo(scoreManager, rulesets, playlistItem, beatmap.Value.BeatmapInfo)).ToArray(); var userBest = req.UserScore?.CreateScoreInfo(scoreManager, rulesets, playlistItem, beatmap.Value.BeatmapInfo); @@ -165,7 +165,7 @@ public void RefetchScores() } userBestHeader.FadeTo(userBest == null ? 0 : 1); - }; + }); loadingLayer.Show(); scoreFlow.FadeTo(0.5f, 400, Easing.OutQuint); From bd4f3e28d90127b9b3a99d9594bb9056c7dea20f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Jul 2024 17:32:59 +0900 Subject: [PATCH 054/266] Fix judgement animation getting cut early --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 5 +++++ osu.Game/Rulesets/UI/JudgementContainer.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 37a9766b71b0..189be44033ab 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; @@ -25,6 +26,8 @@ public partial class DrawableJudgement : PoolableDrawable public DrawableHitObject? JudgedObject { get; private set; } + public HitObject? JudgedHitObject { get; private set; } + public override bool RemoveCompletedTransforms => false; protected SkinnableDrawable? JudgementBody { get; private set; } @@ -98,6 +101,7 @@ public void Apply(JudgementResult result, DrawableHitObject? judgedObject) { Result = result; JudgedObject = judgedObject; + JudgedHitObject = judgedObject?.HitObject; } protected override void FreeAfterUse() @@ -105,6 +109,7 @@ protected override void FreeAfterUse() base.FreeAfterUse(); JudgedObject = null; + JudgedHitObject = null; } protected override void PrepareForUse() diff --git a/osu.Game/Rulesets/UI/JudgementContainer.cs b/osu.Game/Rulesets/UI/JudgementContainer.cs index 886dd34fc7d8..86ab213ca19d 100644 --- a/osu.Game/Rulesets/UI/JudgementContainer.cs +++ b/osu.Game/Rulesets/UI/JudgementContainer.cs @@ -16,7 +16,7 @@ public override void Add(T judgement) // remove any existing judgements for the judged object. // this can be the case when rewinding. - RemoveAll(c => c.JudgedObject == judgement.JudgedObject, false); + RemoveAll(c => c.JudgedHitObject == judgement.JudgedHitObject, false); base.Add(judgement); } From 063377f47cd96bfff5a27a7c5e4c10220badc5ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jul 2024 17:45:25 +0900 Subject: [PATCH 055/266] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 9fd0df303696..fe0a452e9236 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 48d9c2564a08..acfcae7c9336 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From d4ea604ad081788028352a55327e8fef72be2d91 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Jul 2024 18:14:37 +0900 Subject: [PATCH 056/266] Add test --- .../Gameplay/TestSceneJudgementContainer.cs | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneJudgementContainer.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementContainer.cs new file mode 100644 index 000000000000..508877859cbe --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementContainer.cs @@ -0,0 +1,98 @@ +// 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.Testing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneJudgementContainer : OsuTestScene + { + private JudgementContainer judgementContainer = null!; + + [SetUpSteps] + public void SetUp() + { + AddStep("create judgement container", () => Child = judgementContainer = new JudgementContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }); + } + + [Test] + public void TestJudgementFromSameHitObjectIsRemoved() + { + DrawableHitCircle drawableHitCircle1 = null!; + DrawableHitCircle drawableHitCircle2 = null!; + + AddStep("create hit circles", () => + { + Add(drawableHitCircle1 = new DrawableHitCircle(createHitCircle())); + Add(drawableHitCircle2 = new DrawableHitCircle(createHitCircle())); + }); + + int judgementCount = 0; + + AddStep("judge the same hitobject twice via different drawables", () => + { + addDrawableJudgement(drawableHitCircle1); + drawableHitCircle2.Apply(drawableHitCircle1.HitObject); + addDrawableJudgement(drawableHitCircle2); + judgementCount = judgementContainer.Count; + }); + + AddAssert("one judgement in container", () => judgementCount, () => Is.EqualTo(1)); + } + + [Test] + public void TestJudgementFromDifferentHitObjectIsNotRemoved() + { + DrawableHitCircle drawableHitCircle = null!; + + AddStep("create hit circle", () => Add(drawableHitCircle = new DrawableHitCircle(createHitCircle()))); + + int judgementCount = 0; + + AddStep("judge two hitobjects via the same drawable", () => + { + addDrawableJudgement(drawableHitCircle); + drawableHitCircle.Apply(createHitCircle()); + addDrawableJudgement(drawableHitCircle); + judgementCount = judgementContainer.Count; + }); + + AddAssert("two judgements in container", () => judgementCount, () => Is.EqualTo(2)); + } + + private void addDrawableJudgement(DrawableHitObject drawableHitObject) + { + var judgement = new DrawableOsuJudgement(); + + judgement.Apply(new JudgementResult(drawableHitObject.HitObject, new OsuJudgement()) + { + Type = HitResult.Great, + TimeOffset = Time.Current + }, drawableHitObject); + + judgementContainer.Add(judgement); + } + + private HitCircle createHitCircle() + { + var circle = new HitCircle(); + circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + return circle; + } + } +} From 4ad7d900c17ed3de6e405f6f25d29f2b5f40bb5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jul 2024 18:20:33 +0900 Subject: [PATCH 057/266] Fix incorrect editor screen padding --- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index cab0ba9bcb2b..d40db329ec15 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -328,7 +328,7 @@ private void load(OsuConfigManager config) { Name = "Screen container", RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 40, Bottom = 40 }, + Padding = new MarginPadding { Top = 40, Bottom = 50 }, Child = screenContainer = new Container { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 75b86650af94..2204fabf572d 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -104,10 +104,11 @@ protected override void LoadComplete() // child items valid coordinates from the start, so ballpark something similar // using estimated row height. var row = Items.FlowingChildren.SingleOrDefault(item => item.Row.Equals(val.NewValue)); + if (row == null) return; - float minPos = Items.GetLayoutPosition(row) * row_height; + float minPos = row.Y; float maxPos = minPos + row_height; if (minPos < Scroll.Current) From 76d016df348025562fe028a249945d072a90b1ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Jul 2024 11:31:16 +0200 Subject: [PATCH 058/266] Fix code inspection --- osu.Game.Tests/Visual/Gameplay/TestSceneJudgementContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementContainer.cs index 508877859cbe..0ba67c0bb06c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementContainer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementContainer.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneJudgementContainer : OsuTestScene + public partial class TestSceneJudgementContainer : OsuTestScene { private JudgementContainer judgementContainer = null!; From f1325386f071adcf60b5666c2638e16cf07de671 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jul 2024 18:32:54 +0900 Subject: [PATCH 059/266] Fix summary timeline timing points having x position applied twice --- .../Components/Timelines/Summary/Parts/GroupVisualisation.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs index e01900b12900..b872c3725c15 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs @@ -41,6 +41,8 @@ public GroupVisualisation(ControlPointGroup group) case TimingControlPoint: AddInternal(new ControlPointVisualisation(point) { + // importantly, override the x position being set since we do that above. + X = 0, Y = -0.4f, }); break; From ae5b0aa54b463ace43b0a95e02576b33d27f651d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Jul 2024 19:59:13 +0900 Subject: [PATCH 060/266] Fix BackgroundDataStoreProcessor test failure --- osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index e960995c4585..70b6e32363bc 100644 --- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs @@ -172,7 +172,7 @@ public void TestScoreUpgradeFailed() Ruleset = r.All().First(), }) { - TotalScoreVersion = 30000002, + TotalScoreVersion = 30000013, IsLegacyScore = true, }); }); @@ -181,7 +181,7 @@ public void TestScoreUpgradeFailed() AddStep("Run background processor", () => Add(new TestBackgroundDataStoreProcessor())); AddUntilStep("Score marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.True); - AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000002)); + AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000013)); } [Test] From 6db135279fa27cf8c2abfac1c6bb27a2688a1269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Jul 2024 14:03:33 +0200 Subject: [PATCH 061/266] Restore test coverage of original fail case --- .../Database/BackgroundDataStoreProcessorTests.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index 70b6e32363bc..65a8bcd3c2eb 100644 --- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs @@ -157,8 +157,9 @@ public void TestScoreUpgradeSuccess(int scoreVersion) AddAssert("Score not marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.False); } - [Test] - public void TestScoreUpgradeFailed() + [TestCase(30000002)] + [TestCase(30000013)] + public void TestScoreUpgradeFailed(int scoreVersion) { ScoreInfo scoreInfo = null!; @@ -172,7 +173,7 @@ public void TestScoreUpgradeFailed() Ruleset = r.All().First(), }) { - TotalScoreVersion = 30000013, + TotalScoreVersion = scoreVersion, IsLegacyScore = true, }); }); @@ -181,7 +182,7 @@ public void TestScoreUpgradeFailed() AddStep("Run background processor", () => Add(new TestBackgroundDataStoreProcessor())); AddUntilStep("Score marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.True); - AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000013)); + AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(scoreVersion)); } [Test] From 53b6f9e3854db7933ec06a28164078ddc566fcbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Jul 2024 14:01:33 +0200 Subject: [PATCH 062/266] Fix test not waiting properly for background processing to complete --- osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index 65a8bcd3c2eb..f9f9fa2622f0 100644 --- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs @@ -179,7 +179,9 @@ public void TestScoreUpgradeFailed(int scoreVersion) }); }); - AddStep("Run background processor", () => Add(new TestBackgroundDataStoreProcessor())); + TestBackgroundDataStoreProcessor processor = null!; + AddStep("Run background processor", () => Add(processor = new TestBackgroundDataStoreProcessor())); + AddUntilStep("Wait for completion", () => processor.Completed); AddUntilStep("Score marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.True); AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(scoreVersion)); From 7ba1f142e573f8ca8173a8f64f139b4fd240be52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Jul 2024 14:01:50 +0200 Subject: [PATCH 063/266] Fix rank upgrade path upgrading scores that failed background reprocessing earlier --- osu.Game/Database/BackgroundDataStoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/BackgroundDataStoreProcessor.cs b/osu.Game/Database/BackgroundDataStoreProcessor.cs index 7074c89b84ba..16ff766ea4ed 100644 --- a/osu.Game/Database/BackgroundDataStoreProcessor.cs +++ b/osu.Game/Database/BackgroundDataStoreProcessor.cs @@ -389,7 +389,7 @@ private void upgradeScoreRanks() HashSet scoreIds = realmAccess.Run(r => new HashSet( r.All() - .Where(s => s.TotalScoreVersion < 30000013) // last total score version with a significant change to ranks + .Where(s => s.TotalScoreVersion < 30000013 && !s.BackgroundReprocessingFailed) // last total score version with a significant change to ranks .AsEnumerable() // must be done after materialisation, as realm doesn't support // filtering on nested property predicates or projection via `.Select()` From 4c1f902969f82ecc4e974e869110281601079d2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Jul 2024 11:46:17 +0200 Subject: [PATCH 064/266] Do not allow working beatmap to switch to protected beatmap in song select Principal fix to https://github.com/ppy/osu/issues/28880. --- osu.Game/Screens/Select/SongSelect.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index ecf821000243..14c4a34d1466 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -505,6 +505,13 @@ private void updateCarouselSelection(ValueChangedEvent? e = null var beatmap = e?.NewValue ?? Beatmap.Value; if (beatmap is DummyWorkingBeatmap || !this.IsCurrentScreen()) return; + if (beatmap.BeatmapSetInfo.Protected && e != null) + { + Logger.Log($"Denying working beatmap switch to protected beatmap {beatmap}"); + Beatmap.Value = e.OldValue; + return; + } + Logger.Log($"Song select working beatmap updated to {beatmap}"); if (!Carousel.SelectBeatmap(beatmap.BeatmapInfo, false)) From 1ffc34b6518f55079728cd61aa47c5cab2fbc4e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Jul 2024 11:46:59 +0200 Subject: [PATCH 065/266] Do not show protected beatmaps in playlist overlay Secondary fix to https://github.com/ppy/osu/issues/28880. --- osu.Game/Overlays/Music/PlaylistOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 2d03a4a26de2..b49c794aa30b 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -102,7 +102,7 @@ protected override void LoadComplete() { base.LoadComplete(); - beatmapSubscription = realm.RegisterForNotifications(r => r.All().Where(s => !s.DeletePending), beatmapsChanged); + beatmapSubscription = realm.RegisterForNotifications(r => r.All().Where(s => !s.DeletePending && !s.Protected), beatmapsChanged); list.Items.BindTo(beatmapSets); beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo.ToLive(realm), true); From e4ff6b5c8b5f2a384f636e26f917c9f1e529ff2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Jul 2024 12:02:42 +0200 Subject: [PATCH 066/266] Add flags allowing excluding protected beatmaps from consideration in music controller This means that the protected beatmap can not be skipped forward/back to. Incidentally closes https://github.com/ppy/osu/issues/23199. --- osu.Game/Overlays/MusicController.cs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 116e60a0143f..b6553779bc1f 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -133,7 +133,7 @@ public void EnsurePlayingSomething() return; Logger.Log($"{nameof(MusicController)} skipping next track to {nameof(EnsurePlayingSomething)}"); - NextTrack(); + NextTrack(allowProtectedTracks: true); } else if (!IsPlaying) { @@ -207,9 +207,10 @@ public bool TogglePause() /// Play the previous track or restart the current track if it's current time below . /// /// Invoked when the operation has been performed successfully. - public void PreviousTrack(Action? onSuccess = null) => Schedule(() => + /// Whether to include beatmap sets when navigating. + public void PreviousTrack(Action? onSuccess = null, bool allowProtectedTracks = false) => Schedule(() => { - PreviousTrackResult res = prev(); + PreviousTrackResult res = prev(allowProtectedTracks); if (res != PreviousTrackResult.None) onSuccess?.Invoke(res); }); @@ -217,8 +218,9 @@ public void PreviousTrack(Action? onSuccess = null) => Sche /// /// Play the previous track or restart the current track if it's current time below . /// + /// Whether to include beatmap sets when navigating. /// The that indicate the decided action. - private PreviousTrackResult prev() + private PreviousTrackResult prev(bool allowProtectedTracks) { if (beatmap.Disabled || !AllowTrackControl.Value) return PreviousTrackResult.None; @@ -233,8 +235,8 @@ private PreviousTrackResult prev() queuedDirection = TrackChangeDirection.Prev; - var playableSet = getBeatmapSets().AsEnumerable().TakeWhile(i => !i.Equals(current?.BeatmapSetInfo)).LastOrDefault() - ?? getBeatmapSets().LastOrDefault(); + var playableSet = getBeatmapSets().AsEnumerable().TakeWhile(i => !i.Equals(current?.BeatmapSetInfo)).LastOrDefault(s => !s.Protected || allowProtectedTracks) + ?? getBeatmapSets().AsEnumerable().LastOrDefault(s => !s.Protected || allowProtectedTracks); if (playableSet != null) { @@ -250,10 +252,11 @@ private PreviousTrackResult prev() /// Play the next random or playlist track. /// /// Invoked when the operation has been performed successfully. + /// Whether to include beatmap sets when navigating. /// A of the operation. - public void NextTrack(Action? onSuccess = null) => Schedule(() => + public void NextTrack(Action? onSuccess = null, bool allowProtectedTracks = false) => Schedule(() => { - bool res = next(); + bool res = next(allowProtectedTracks); if (res) onSuccess?.Invoke(); }); @@ -306,15 +309,15 @@ public void DuckMomentarily(double delayUntilRestore, DuckParameters? parameters Scheduler.AddDelayed(() => duckOperation.Dispose(), delayUntilRestore); } - private bool next() + private bool next(bool allowProtectedTracks) { if (beatmap.Disabled || !AllowTrackControl.Value) return false; queuedDirection = TrackChangeDirection.Next; - var playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo)).ElementAtOrDefault(1) - ?? getBeatmapSets().FirstOrDefault(); + var playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo) && (!i.Protected || allowProtectedTracks)).ElementAtOrDefault(1) + ?? getBeatmapSets().AsEnumerable().FirstOrDefault(i => !i.Protected || allowProtectedTracks); var playableBeatmap = playableSet?.Beatmaps.FirstOrDefault(); @@ -432,7 +435,7 @@ private DrawableTrack getQueuedTrack() private void onTrackCompleted() { if (!CurrentTrack.Looping && !beatmap.Disabled && AllowTrackControl.Value) - NextTrack(); + NextTrack(allowProtectedTracks: true); } private bool applyModTrackAdjustments; From c4141fff07a4378a0dc8c8b94fd4f64715367511 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Jul 2024 14:47:15 +0300 Subject: [PATCH 067/266] Fix storyboard sprites leaving gaps on edges when resolving from an atlas --- osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index c5d70ddecc45..e25c915d8b6c 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -100,14 +100,15 @@ private void load(Storyboard storyboard) skinSourceChanged(); } else - Texture = textureStore.Get(Sprite.Path); + Texture = textureStore.Get(Sprite.Path, WrapMode.ClampToEdge, WrapMode.ClampToEdge); Sprite.ApplyTransforms(this); } private void skinSourceChanged() { - Texture = skin.GetTexture(Sprite.Path) ?? textureStore.Get(Sprite.Path); + Texture = skin.GetTexture(Sprite.Path, WrapMode.ClampToEdge, WrapMode.ClampToEdge) ?? + textureStore.Get(Sprite.Path, WrapMode.ClampToEdge, WrapMode.ClampToEdge); // Setting texture will only update the size if it's zero. // So let's force an explicit update. From 3006bae0d8ea9d42ed887862dcb6e56e0b9be081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Jul 2024 14:11:35 +0200 Subject: [PATCH 068/266] Send client-generated session GUID for identification purposes This is the first half of a change that *may* fix https://github.com/ppy/osu/issues/26338 (it definitely fixes *one case* where the issue happens, but I'm not sure if it will cover all of them). As described in the issue thread, using the `jti` claim from the JWT used for authorisation seemed like a decent idea. However, upon closer inspection the scheme falls over badly in a specific scenario where: 1. A client instance connects to spectator server using JWT A. 2. At some point, JWT A expires, and is silently rotated by the game in exchange for JWT B. The spectator server knows nothing of this, and continues to only track JWT A, including the old `jti` claim in said JWT. 3. At some later point, the client's connection to one of the spectator server hubs drops out. A reconnection is automatically attempted, *but* it is attempted using JWT B. The spectator server was not aware of JWT B until now, and said JWT has a different `jti` claim than the old one, so to the spectator server, it looks like a completely different client connecting, which boots the user out of their account. This PR adds a per-session GUID which is sent in a HTTP header on every connection attempt to spectator server. This GUID will be used instead of the `jti` claim in JWTs as a persistent identifier of a single user's single lazer session, which bypasses the failure scenario described above. I don't think any stronger primitive than this is required. As far as I can tell this is as strong a protection as the JWT was (which is to say, not *very* strong), and doing this removes a lot of weird complexity that would be otherwise incurred by attempting to have client ferry all of its newly issued JWTs to the server so that it can be aware of them. --- osu.Game/Online/API/APIAccess.cs | 2 ++ osu.Game/Online/API/DummyAPIAccess.cs | 2 ++ osu.Game/Online/API/IAPIProvider.cs | 6 ++++++ osu.Game/Online/HubClientConnector.cs | 8 ++++++-- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 923f841bd848..0cf344ecafd9 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -164,6 +164,8 @@ private WebSocketNotificationsClientConnector setUpNotificationsClient() public string AccessToken => authentication.RequestAccessToken(); + public Guid SessionIdentifier { get; } = Guid.NewGuid(); + /// /// Number of consecutive requests which failed due to network issues. /// diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 960941fc05f7..0af76537cd76 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -39,6 +39,8 @@ public partial class DummyAPIAccess : Component, IAPIProvider public string AccessToken => "token"; + public Guid SessionIdentifier { get; } = Guid.NewGuid(); + /// public bool IsLoggedIn => State.Value > APIState.Offline; diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index 7b95b68ec3b4..d8194dc32b19 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -44,6 +44,12 @@ public interface IAPIProvider /// string AccessToken { get; } + /// + /// Used as an identifier of a single local lazer session. + /// Sent across the wire for the purposes of concurrency control to spectator server. + /// + Guid SessionIdentifier { get; } + /// /// Returns whether the local user is logged in. /// diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index 9d414deade75..dc9ed7cc2e40 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -19,6 +19,9 @@ public class HubClientConnector : PersistentEndpointClientConnector, IHubClientC { public const string SERVER_SHUTDOWN_MESSAGE = "Server is shutting down."; + public const string VERSION_HASH_HEADER = @"OsuVersionHash"; + public const string CLIENT_SESSION_ID_HEADER = @"X-Client-Session-ID"; + /// /// Invoked whenever a new hub connection is built, to configure it before it's started. /// @@ -68,8 +71,9 @@ protected override Task BuildConnectionAsync(Cancellat options.Proxy.Credentials = CredentialCache.DefaultCredentials; } - options.Headers.Add("Authorization", $"Bearer {API.AccessToken}"); - options.Headers.Add("OsuVersionHash", versionHash); + options.Headers.Add(@"Authorization", @$"Bearer {API.AccessToken}"); + options.Headers.Add(VERSION_HASH_HEADER, versionHash); + options.Headers.Add(CLIENT_SESSION_ID_HEADER, API.SessionIdentifier.ToString()); }); if (RuntimeFeature.IsDynamicCodeCompiled && preferMessagePack) From 2a601ce9617d0ef55bcb36b4de4e87278179b72e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Jul 2024 16:21:33 +0200 Subject: [PATCH 069/266] Also send version hash header under more accepted convention of name --- osu.Game/Online/HubClientConnector.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index dc9ed7cc2e40..9288a32052dd 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -19,7 +19,7 @@ public class HubClientConnector : PersistentEndpointClientConnector, IHubClientC { public const string SERVER_SHUTDOWN_MESSAGE = "Server is shutting down."; - public const string VERSION_HASH_HEADER = @"OsuVersionHash"; + public const string VERSION_HASH_HEADER = @"X-Osu-Version-Hash"; public const string CLIENT_SESSION_ID_HEADER = @"X-Client-Session-ID"; /// @@ -72,6 +72,8 @@ protected override Task BuildConnectionAsync(Cancellat } options.Headers.Add(@"Authorization", @$"Bearer {API.AccessToken}"); + // non-standard header name kept for backwards compatibility, can be removed after server side has migrated to `VERSION_HASH_HEADER` + options.Headers.Add(@"OsuVersionHash", versionHash); options.Headers.Add(VERSION_HASH_HEADER, versionHash); options.Headers.Add(CLIENT_SESSION_ID_HEADER, API.SessionIdentifier.ToString()); }); From 102da0f98c783fecb55736c574ee14e639fa9b6c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Jul 2024 23:58:38 +0300 Subject: [PATCH 070/266] Remove incorrect `[CanBeNull]` attribute --- osu.Game/Online/API/Requests/Responses/APIUser.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 1c07b38667ec..a2836476c562 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -202,7 +202,6 @@ private string[] playStyle public string PlayMode; [JsonProperty(@"profile_hue")] - [CanBeNull] public int? ProfileHue; [JsonProperty(@"profile_order")] From 4eb4d35e2f4c5b6edca8c10692165a22915195af Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Jul 2024 23:58:47 +0300 Subject: [PATCH 071/266] Make `UpdateColours` method protected --- osu.Game/Overlays/FullscreenOverlay.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/FullscreenOverlay.cs b/osu.Game/Overlays/FullscreenOverlay.cs index 2a09147c7634..c2ecb558140e 100644 --- a/osu.Game/Overlays/FullscreenOverlay.cs +++ b/osu.Game/Overlays/FullscreenOverlay.cs @@ -100,7 +100,10 @@ public override void Show() } } - public void UpdateColours() + /// + /// Updates the colours of the background and the top waves with the latest colour shades provided by . + /// + protected void UpdateColours() { Waves.FirstWaveColour = ColourProvider.Light4; Waves.SecondWaveColour = ColourProvider.Light3; From d61a72b8fbda78a475428549090e90ee9e840a97 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 18 Jul 2024 00:05:44 +0300 Subject: [PATCH 072/266] Explicitly define `Hue` rather than implicitly provide it by enum value --- osu.Game/Overlays/OverlayColourProvider.cs | 42 +++++++-------- osu.Game/Overlays/OverlayColourScheme.cs | 60 ++++++++++++++++++++++ osu.Game/Overlays/UserProfileOverlay.cs | 16 +++--- osu.Game/Screens/Footer/ScreenFooter.cs | 8 +-- 4 files changed, 91 insertions(+), 35 deletions(-) create mode 100644 osu.Game/Overlays/OverlayColourScheme.cs diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index 9613bc28570c..9f5583cf73bc 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -7,11 +7,19 @@ namespace osu.Game.Overlays { public class OverlayColourProvider { - public OverlayColourScheme ColourScheme { get; private set; } + /// + /// The hue degree associated with the colour shades provided by this . + /// + public int Hue { get; private set; } public OverlayColourProvider(OverlayColourScheme colourScheme) + : this(colourScheme.GetHue()) { - ColourScheme = colourScheme; + } + + public OverlayColourProvider(int hue) + { + Hue = hue; } // Note that the following five colours are also defined in `OsuColour` as `{colourScheme}{0,1,2,3,4}`. @@ -46,31 +54,19 @@ public OverlayColourProvider(OverlayColourScheme colourScheme) public Color4 Background6 => getColour(0.1f, 0.1f); /// - /// Changes the value of to a different colour scheme. + /// Changes the to a different degree. /// Note that this does not trigger any kind of signal to any drawable that received colours from here, all drawables need to be updated manually. /// /// The proposed colour scheme. - public void ChangeColourScheme(OverlayColourScheme colourScheme) - { - ColourScheme = colourScheme; - } + public void ChangeColourScheme(OverlayColourScheme colourScheme) => ChangeColourScheme(colourScheme.GetHue()); - private Color4 getColour(float saturation, float lightness) => Framework.Graphics.Colour4.FromHSL(getBaseHue(ColourScheme), saturation, lightness); - - private static float getBaseHue(OverlayColourScheme colourScheme) => (int)colourScheme / 360f; - } + /// + /// Changes the to a different degree. + /// Note that this does not trigger any kind of signal to any drawable that received colours from here, all drawables need to be updated manually. + /// + /// The proposed hue degree. + public void ChangeColourScheme(int hue) => Hue = hue; - // See https://github.com/ppy/osu-web/blob/5a536d217a21582aad999db50a981003d3ad5659/app/helpers.php#L1620-L1628 - public enum OverlayColourScheme - { - Red = 0, - Orange = 45, - Lime = 90, - Green = 125, - Aquamarine = 160, - Blue = 200, - Purple = 255, - Plum = 320, - Pink = 333, + private Color4 getColour(float saturation, float lightness) => Framework.Graphics.Colour4.FromHSL(Hue / 360f, saturation, lightness); } } diff --git a/osu.Game/Overlays/OverlayColourScheme.cs b/osu.Game/Overlays/OverlayColourScheme.cs new file mode 100644 index 000000000000..0126f9060f5c --- /dev/null +++ b/osu.Game/Overlays/OverlayColourScheme.cs @@ -0,0 +1,60 @@ +// 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 +{ + public enum OverlayColourScheme + { + Red, + Orange, + Lime, + Green, + Aquamarine, + Blue, + Purple, + Plum, + Pink, + } + + public static class OverlayColourSchemeExtensions + { + public static int GetHue(this OverlayColourScheme colourScheme) + { + // See https://github.com/ppy/osu-web/blob/5a536d217a21582aad999db50a981003d3ad5659/app/helpers.php#L1620-L1628 + switch (colourScheme) + { + default: + throw new ArgumentOutOfRangeException(nameof(colourScheme)); + + case OverlayColourScheme.Red: + return 0; + + case OverlayColourScheme.Orange: + return 45; + + case OverlayColourScheme.Lime: + return 90; + + case OverlayColourScheme.Green: + return 125; + + case OverlayColourScheme.Aquamarine: + return 160; + + case OverlayColourScheme.Blue: + return 200; + + case OverlayColourScheme.Purple: + return 255; + + case OverlayColourScheme.Plum: + return 320; + + case OverlayColourScheme.Pink: + return 333; + } + } + } +} diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 8c750b5d8340..815f4b545fda 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -118,7 +118,7 @@ private void fetchAndSetContent() } : Array.Empty(); - setupBaseContent(OverlayColourScheme.Pink, forceContentRecreation: true); + setupBaseContent(OverlayColourScheme.Pink.GetHue(), forceContentRecreation: true); if (API.State.Value != APIState.Offline) { @@ -135,9 +135,9 @@ private void userLoadComplete(APIUser loadedUser, IRulesetInfo? userRuleset) Debug.Assert(sections != null && sectionsContainer != null && tabs != null); // reuse header and content if same colour scheme, otherwise recreate both. - var profileScheme = (OverlayColourScheme?)loadedUser.ProfileHue ?? OverlayColourScheme.Pink; - if (profileScheme != ColourProvider.ColourScheme) - setupBaseContent(profileScheme, forceContentRecreation: false); + int profileHue = loadedUser.ProfileHue ?? OverlayColourScheme.Pink.GetHue(); + if (profileHue != ColourProvider.Hue) + setupBaseContent(profileHue, forceContentRecreation: false); var actualRuleset = rulesets.GetRuleset(userRuleset?.ShortName ?? loadedUser.PlayMode).AsNonNull(); @@ -163,12 +163,12 @@ private void userLoadComplete(APIUser loadedUser, IRulesetInfo? userRuleset) loadingLayer.Hide(); } - private void setupBaseContent(OverlayColourScheme colourScheme, bool forceContentRecreation) + private void setupBaseContent(int hue, bool forceContentRecreation) { - var previousColourScheme = ColourProvider.ColourScheme; - ColourProvider.ChangeColourScheme(colourScheme); + int previousHue = ColourProvider.Hue; + ColourProvider.ChangeColourScheme(hue); - if (colourScheme != previousColourScheme) + if (hue != previousHue) { RecreateHeader(); UpdateColours(); diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index 6a1efcf87a2b..ea32507ca01b 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -219,7 +219,7 @@ public IDisposable RegisterActiveOverlayContainer(ShearedOverlayContainer overla var targetPosition = targetButton?.ToSpaceOfOtherDrawable(targetButton.LayoutRectangle.TopRight, this) ?? fallbackPosition; - updateColourScheme(overlay.ColourProvider.ColourScheme); + updateColourScheme(overlay.ColourProvider.Hue); footerContent = overlay.CreateFooterContent(); @@ -256,16 +256,16 @@ private void clearActiveOverlayContainer() temporarilyHiddenButtons.Clear(); - updateColourScheme(OverlayColourScheme.Aquamarine); + updateColourScheme(OverlayColourScheme.Aquamarine.GetHue()); contentContainer.Delay(timeUntilRun).Expire(); contentContainer = null; activeOverlay = null; } - private void updateColourScheme(OverlayColourScheme colourScheme) + private void updateColourScheme(int hue) { - colourProvider.ChangeColourScheme(colourScheme); + colourProvider.ChangeColourScheme(hue); background.FadeColour(colourProvider.Background5, 150, Easing.OutQuint); From 5317086171ee7da13154b434620b856994d013a6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 18 Jul 2024 00:26:37 +0300 Subject: [PATCH 073/266] Split content recreation methods --- osu.Game/Overlays/UserProfileOverlay.cs | 33 ++++++++++++++----------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 815f4b545fda..ac1fc44cd6d7 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -118,7 +118,8 @@ private void fetchAndSetContent() } : Array.Empty(); - setupBaseContent(OverlayColourScheme.Pink.GetHue(), forceContentRecreation: true); + changeOverlayColours(OverlayColourScheme.Pink.GetHue()); + recreateBaseContent(); if (API.State.Value != APIState.Offline) { @@ -136,8 +137,9 @@ private void userLoadComplete(APIUser loadedUser, IRulesetInfo? userRuleset) // reuse header and content if same colour scheme, otherwise recreate both. int profileHue = loadedUser.ProfileHue ?? OverlayColourScheme.Pink.GetHue(); - if (profileHue != ColourProvider.Hue) - setupBaseContent(profileHue, forceContentRecreation: false); + + if (changeOverlayColours(profileHue)) + recreateBaseContent(); var actualRuleset = rulesets.GetRuleset(userRuleset?.ShortName ?? loadedUser.PlayMode).AsNonNull(); @@ -163,19 +165,8 @@ private void userLoadComplete(APIUser loadedUser, IRulesetInfo? userRuleset) loadingLayer.Hide(); } - private void setupBaseContent(int hue, bool forceContentRecreation) + private void recreateBaseContent() { - int previousHue = ColourProvider.Hue; - ColourProvider.ChangeColourScheme(hue); - - if (hue != previousHue) - { - RecreateHeader(); - UpdateColours(); - } - else if (!forceContentRecreation) - return; - Child = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, @@ -224,6 +215,18 @@ private void setupBaseContent(int hue, bool forceContentRecreation) }; } + private bool changeOverlayColours(int hue) + { + if (hue == ColourProvider.Hue) + return false; + + ColourProvider.ChangeColourScheme(hue); + + RecreateHeader(); + UpdateColours(); + return true; + } + private partial class ProfileSectionTabControl : OsuTabControl { public ProfileSectionTabControl() From 7a394350170d804d2744706cf0d03b815f47a9cf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 18 Jul 2024 01:11:39 +0300 Subject: [PATCH 074/266] Fix intermitent test failure in `TestSceneArgonHealthDisplay` --- osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs index 5d2921107e8a..319efee1a748 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs @@ -99,6 +99,7 @@ public void TestLateMissAfterConsequentMisses() Scheduler.AddDelayed(applyMiss, 500 + 30); }); + AddUntilStep("wait for sequence", () => !Scheduler.HasPendingTasks); } [Test] @@ -120,6 +121,7 @@ public void TestMissAlmostExactlyAfterLastMissAnimation() } } }); + AddUntilStep("wait for sequence", () => !Scheduler.HasPendingTasks); } [Test] From 1906c2f72537fde9386f03313afa3e95ba9b1663 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 18 Jul 2024 15:57:57 +0900 Subject: [PATCH 075/266] Fix TestTouchScreenDetectionAtSongSelect test failure https://github.com/ppy/osu/actions/runs/9985890747/job/27597501295 In this case, the settings overlay is taking a very long time to load (on a background thread), and pops in when it finishes loading because it's been requested to open. The opens the settings overlay, closes it (by pressing escape, this does not actually close it because it's not loaded yet), and then enters song select by pressing 'P' 3 times. The settings overlay finishes loading at just the right opportune moment to eat one of the 'P' key presses. --- osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index e81c6d2e8698..3ae1d9786d06 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -952,6 +952,8 @@ public void TestQuickSkinEditorDoesntNukeSkin() [Test] public void TestTouchScreenDetectionAtSongSelect() { + AddUntilStep("wait for settings", () => Game.Settings.IsLoaded); + AddStep("touch logo", () => { var button = Game.ChildrenOfType().Single(); From 7bb680a8a445cd7a87ea206627f925a08dd0a337 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 18 Jul 2024 16:01:09 +0900 Subject: [PATCH 076/266] Raise workflow timeout time https://github.com/ppy/osu/actions/runs/9985890747/job/27597500883 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ea46545632f..dc1cb6c186f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,7 +67,7 @@ jobs: - { prettyname: macOS, fullname: macos-latest } - { prettyname: Linux, fullname: ubuntu-latest } threadingMode: ['SingleThread', 'MultiThreaded'] - timeout-minutes: 60 + timeout-minutes: 120 steps: - name: Checkout uses: actions/checkout@v4 From f3cd3d7d3b8c7d56746b6d93a103d71ef6de991e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 18 Jul 2024 16:22:39 +0900 Subject: [PATCH 077/266] Fix TestAllSamplesStopDuringSeek test failure https://github.com/smoogipoo/osu/actions/runs/9986761756/job/27599851263 This is a bit of a workaround, likely timing related. I don't foresee an until step in this case to cause false-passes. --- .../Visual/Gameplay/TestSceneGameplaySamplePlayback.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs index ad3fe7cb7e09..21c83d521c2a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs @@ -48,7 +48,7 @@ public void TestAllSamplesStopDuringSeek() return true; }); - AddAssert("sample playback disabled", () => sampleDisabler.SamplePlaybackDisabled.Value); + AddUntilStep("sample playback disabled", () => sampleDisabler.SamplePlaybackDisabled.Value); // because we are in frame stable context, it's quite likely that not all samples are "played" at this point. // the important thing is that at least one started, and that sample has since stopped. From 00ed7a7a2f19f9770c0772bc9af0093d0dfd07c5 Mon Sep 17 00:00:00 2001 From: Nathan Du Date: Thu, 18 Jul 2024 16:08:30 +0800 Subject: [PATCH 078/266] Fix hold note light lingering with No Release Turns out endHold() is not called in the Tail.IsHit branch of the hold notes' CheckForResult method. --- .../Objects/Drawables/DrawableHoldNote.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 2b55e81788ad..9c56f0473c4d 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -268,11 +268,14 @@ protected override void CheckForResult(bool userTriggered, double timeOffset) ApplyMaxResult(); else MissForcefully(); - } - // Make sure that the hold note is fully judged by giving the body a judgement. - if (Tail.AllJudged && !Body.AllJudged) - Body.TriggerResult(Tail.IsHit); + // Make sure that the hold note is fully judged by giving the body a judgement. + if (!Body.AllJudged) + Body.TriggerResult(Tail.IsHit); + + // Important that this is always called when a result is applied. + endHold(); + } } public override void MissForcefully() From 33a81d818107b80c73c4dd7ad483a9c59abb474a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 18 Jul 2024 18:34:08 +0900 Subject: [PATCH 079/266] Use constraint to improve assertion message --- .../Visual/Navigation/TestSceneBeatmapEditorNavigation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index b5dfa9a87f6d..99d1ff93c5c7 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -209,7 +209,7 @@ public void TestCancelNavigationToEditor() AddStep("Close editor while loading", () => Game.ScreenStack.CurrentScreen.Exit()); AddUntilStep("Wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu); - AddAssert("Check no new beatmaps were made", () => allBeatmapSets().SequenceEqual(beatmapSets)); + AddAssert("Check no new beatmaps were made", allBeatmapSets, () => Is.EquivalentTo(beatmapSets)); BeatmapSetInfo[] allBeatmapSets() => Game.Realm.Run(realm => realm.All().Where(x => !x.DeletePending).ToArray()); } From c9517aeebf0c9a726436d2d1776e7c69266c7df4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jul 2024 18:37:07 +0900 Subject: [PATCH 080/266] Fix tab extension dropdown having dead non-clickable hover area Closes https://github.com/ppy/osu/issues/28899. --- osu.Game/Graphics/UserInterface/OsuTabDropdown.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabDropdown.cs b/osu.Game/Graphics/UserInterface/OsuTabDropdown.cs index 6272f95510ed..5924ee005ada 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabDropdown.cs @@ -123,7 +123,7 @@ public OsuTabDropdownHeader() } }; - Padding = new MarginPadding { Left = 5, Right = 5 }; + Margin = new MarginPadding { Left = 5, Right = 5 }; } protected override bool OnHover(HoverEvent e) From 70985d3b2234d746275bc9e1f45891cc41ea0ab9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jul 2024 19:01:52 +0900 Subject: [PATCH 081/266] Remove margin completely --- osu.Game/Graphics/UserInterface/OsuTabDropdown.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabDropdown.cs b/osu.Game/Graphics/UserInterface/OsuTabDropdown.cs index 5924ee005ada..7a17be57a8d7 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabDropdown.cs @@ -122,8 +122,6 @@ public OsuTabDropdownHeader() Anchor = Anchor.Centre, } }; - - Margin = new MarginPadding { Left = 5, Right = 5 }; } protected override bool OnHover(HoverEvent e) From a7e110f6693beca6f6e6a20efb69a6913d58550e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 18 Jul 2024 19:07:02 +0900 Subject: [PATCH 082/266] Don't rely on single-use properties --- .../Objects/Drawables/DrawableOsuJudgement.cs | 51 ++++++++++--------- .../Objects/Drawables/SkinnableLighting.cs | 18 +++---- .../Rulesets/Judgements/DrawableJudgement.cs | 6 +-- 3 files changed, 36 insertions(+), 39 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 0630ecfbb5d1..8b3fcb23cda4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -5,19 +5,23 @@ using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables { public partial class DrawableOsuJudgement : DrawableJudgement { + internal Color4 AccentColour { get; private set; } + internal SkinnableLighting Lighting { get; private set; } = null!; [Resolved] private OsuConfigManager config { get; set; } = null!; - private bool positionTransferred; + private Vector2 screenSpacePosition; [BackgroundDependencyLoader] private void load() @@ -32,37 +36,36 @@ private void load() }); } - protected override void PrepareForUse() + public override void Apply(JudgementResult result, DrawableHitObject? judgedObject) { - base.PrepareForUse(); + base.Apply(result, judgedObject); - Lighting.ResetAnimation(); - Lighting.SetColourFrom(JudgedObject, Result); + if (judgedObject is not DrawableOsuHitObject osuObject) + return; - positionTransferred = false; - } + AccentColour = osuObject.AccentColour.Value; - protected override void Update() - { - base.Update(); - - if (!positionTransferred && JudgedObject is DrawableOsuHitObject osuObject && JudgedObject.IsInUse) + switch (osuObject) { - switch (osuObject) - { - case DrawableSlider slider: - Position = slider.TailCircle.ToSpaceOfOtherDrawable(slider.TailCircle.OriginPosition, Parent!); - break; + case DrawableSlider slider: + screenSpacePosition = slider.TailCircle.ToScreenSpace(slider.TailCircle.OriginPosition); + break; - default: - Position = osuObject.ToSpaceOfOtherDrawable(osuObject.OriginPosition, Parent!); - break; - } + default: + screenSpacePosition = osuObject.ToScreenSpace(osuObject.OriginPosition); + break; + } - positionTransferred = true; + Scale = new Vector2(osuObject.HitObject.Scale); + } - Scale = new Vector2(osuObject.HitObject.Scale); - } + protected override void PrepareForUse() + { + base.PrepareForUse(); + + Lighting.ResetAnimation(); + Lighting.SetColourFrom(this, Result); + Position = Parent!.ToLocalSpace(screenSpacePosition); } protected override void ApplyHitAnimations() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/SkinnableLighting.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/SkinnableLighting.cs index b39b9c4c549a..377620162640 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/SkinnableLighting.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/SkinnableLighting.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Skinning; @@ -12,8 +10,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { internal partial class SkinnableLighting : SkinnableSprite { - private DrawableHitObject targetObject; - private JudgementResult targetResult; + private DrawableOsuJudgement? targetJudgement; + private JudgementResult? targetResult; public SkinnableLighting() : base("lighting") @@ -29,11 +27,11 @@ protected override void SkinChanged(ISkinSource skin) /// /// Updates the lighting colour from a given hitobject and result. /// - /// The that's been judged. - /// The that was judged with. - public void SetColourFrom(DrawableHitObject targetObject, JudgementResult targetResult) + /// The that's been judged. + /// The that was judged with. + public void SetColourFrom(DrawableOsuJudgement targetJudgement, JudgementResult? targetResult) { - this.targetObject = targetObject; + this.targetJudgement = targetJudgement; this.targetResult = targetResult; updateColour(); @@ -41,10 +39,10 @@ public void SetColourFrom(DrawableHitObject targetObject, JudgementResult target private void updateColour() { - if (targetObject == null || targetResult == null) + if (targetJudgement == null || targetResult == null) Colour = Color4.White; else - Colour = targetResult.IsHit ? targetObject.AccentColour.Value : Color4.Transparent; + Colour = targetResult.IsHit ? targetJudgement.AccentColour : Color4.Transparent; } } } diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 189be44033ab..bdeadfd201b6 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -24,8 +24,6 @@ public partial class DrawableJudgement : PoolableDrawable public JudgementResult? Result { get; private set; } - public DrawableHitObject? JudgedObject { get; private set; } - public HitObject? JudgedHitObject { get; private set; } public override bool RemoveCompletedTransforms => false; @@ -97,10 +95,9 @@ protected virtual void ApplyMissAnimations() /// /// The applicable judgement. /// The drawable object. - public void Apply(JudgementResult result, DrawableHitObject? judgedObject) + public virtual void Apply(JudgementResult result, DrawableHitObject? judgedObject) { Result = result; - JudgedObject = judgedObject; JudgedHitObject = judgedObject?.HitObject; } @@ -108,7 +105,6 @@ protected override void FreeAfterUse() { base.FreeAfterUse(); - JudgedObject = null; JudgedHitObject = null; } From 3f4e56be3ce3532b6c7b75f530a1a2b7c45a99ca Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 18 Jul 2024 20:53:59 +0900 Subject: [PATCH 083/266] Fix TestPostAsOwner test failure https://github.com/smoogipoo/osu/actions/runs/9990112749/job/27610257309 Comments are loaded asynchronously, both from the initial request and the following message-post request. By sheer timing luck, these could be out of order and the assertion on the posted message could fail. --- osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index fd3552f67500..acc3c9b8b466 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -157,6 +157,7 @@ public void TestPostWithExistingComments() { setUpCommentsResponse(getExampleComments()); AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123)); + AddUntilStep("comments shown", () => commentsContainer.ChildrenOfType().Any()); setUpPostResponse(); AddStep("enter text", () => editorTextBox.Current.Value = "comm"); @@ -175,6 +176,7 @@ public void TestPostAsOwner() { setUpCommentsResponse(getExampleComments()); AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123)); + AddUntilStep("comments shown", () => commentsContainer.ChildrenOfType().Any()); setUpPostResponse(true); AddStep("enter text", () => editorTextBox.Current.Value = "comm"); From a570949459b3e66fab91fd87834df01547ace2b8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Jul 2024 03:00:44 +0300 Subject: [PATCH 084/266] Fix selection box initialy visible despite no items selected --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index b68d5cd5409e..16d11ccd1afe 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -85,10 +85,7 @@ private void load() SelectionBox = CreateSelectionBox(), }); - SelectedItems.CollectionChanged += (_, _) => - { - Scheduler.AddOnce(updateVisibility); - }; + SelectedItems.BindCollectionChanged((_, _) => Scheduler.AddOnce(updateVisibility), true); } public SelectionBox CreateSelectionBox() From dd2454ba10532798d9e5c59136123507efd098a6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Jul 2024 03:17:50 +0300 Subject: [PATCH 085/266] Disable trailing comma inspections entirely --- osu.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 25bbc4beb566..0c52f8d82a6b 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -20,6 +20,7 @@ WARNING WARNING True + DO_NOT_SHOW WARNING WARNING HINT From 2ad8eeb918b7129f13df5f985c6f0739ab8ff5d5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Jul 2024 03:25:12 +0300 Subject: [PATCH 086/266] Fix beatmap attributes display in mod select recreating star difficulty bindable every setting change --- .../Overlays/Mods/BeatmapAttributesDisplay.cs | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 1f4e007f4794..2670c20d262b 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -108,8 +108,6 @@ protected override void LoadComplete() updateValues(); }, true); - BeatmapInfo.BindValueChanged(_ => updateValues()); - Collapsed.BindValueChanged(_ => { // Only start autosize animations on first collapse toggle. This avoids an ugly initial presentation. @@ -120,12 +118,32 @@ protected override void LoadComplete() GameRuleset = game.Ruleset.GetBoundCopy(); GameRuleset.BindValueChanged(_ => updateValues()); - BeatmapInfo.BindValueChanged(_ => updateValues()); + BeatmapInfo.BindValueChanged(_ => + { + updateStarDifficultyBindable(); + updateValues(); + }, true); - updateValues(); updateCollapsedState(); } + private void updateStarDifficultyBindable() + { + cancellationSource?.Cancel(); + + if (BeatmapInfo.Value == null) + return; + + starDifficulty = difficultyCache.GetBindableDifficulty(BeatmapInfo.Value, (cancellationSource = new CancellationTokenSource()).Token); + starDifficulty.BindValueChanged(s => + { + starRatingDisplay.Current.Value = s.NewValue ?? default; + + if (!starRatingDisplay.IsPresent) + starRatingDisplay.FinishTransforms(true); + }); + } + protected override bool OnHover(HoverEvent e) { startAnimating(); @@ -154,17 +172,6 @@ private void updateValues() => Scheduler.AddOnce(() => if (BeatmapInfo.Value == null) return; - cancellationSource?.Cancel(); - - starDifficulty = difficultyCache.GetBindableDifficulty(BeatmapInfo.Value, (cancellationSource = new CancellationTokenSource()).Token); - starDifficulty.BindValueChanged(s => - { - starRatingDisplay.Current.Value = s.NewValue ?? default; - - if (!starRatingDisplay.IsPresent) - starRatingDisplay.FinishTransforms(true); - }); - double rate = ModUtils.CalculateRateWithMods(Mods.Value); bpmDisplay.Current.Value = FormatUtils.RoundBPM(BeatmapInfo.Value.BPM, rate); From 73edb324403c4ea1e1716547e2756f6fd5df001a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Jul 2024 07:30:55 +0200 Subject: [PATCH 087/266] Add failing test coverage --- .../Visual/Menus/TestSceneMusicActionHandling.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index f17433244b84..9936b24a0691 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -62,14 +62,22 @@ public void TestMusicNavigationActions() AddUntilStep("track restarted", () => Game.MusicController.CurrentTrack.CurrentTime < 5000); AddStep("press previous", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPrev)); - AddAssert("track changed to previous", () => + AddUntilStep("track changed to previous", () => trackChangeQueue.Count == 1 && trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Prev); AddStep("press next", () => globalActionContainer.TriggerPressed(GlobalAction.MusicNext)); - AddAssert("track changed to next", () => + AddUntilStep("track changed to next", () => trackChangeQueue.Count == 1 && - trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Next); + trackChangeQueue.Peek().changeDirection == TrackChangeDirection.Next); + + AddUntilStep("wait until track switches", () => trackChangeQueue.Count == 2); + + AddStep("press next", () => globalActionContainer.TriggerPressed(GlobalAction.MusicNext)); + AddUntilStep("track changed to next", () => + trackChangeQueue.Count == 3 && + trackChangeQueue.Peek().changeDirection == TrackChangeDirection.Next); + AddAssert("track actually changed", () => !trackChangeQueue.First().working.BeatmapInfo.Equals(trackChangeQueue.Last().working.BeatmapInfo)); } } } From 9fe6354afc5517bb4527397777e2299e2cf8e7c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Jul 2024 07:32:29 +0200 Subject: [PATCH 088/266] Fix backwards conditional --- osu.Game/Overlays/MusicController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index b6553779bc1f..d9bb92b4b700 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -316,7 +316,7 @@ private bool next(bool allowProtectedTracks) queuedDirection = TrackChangeDirection.Next; - var playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo) && (!i.Protected || allowProtectedTracks)).ElementAtOrDefault(1) + var playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo) || (i.Protected && !allowProtectedTracks)).ElementAtOrDefault(1) ?? getBeatmapSets().AsEnumerable().FirstOrDefault(i => !i.Protected || allowProtectedTracks); var playableBeatmap = playableSet?.Beatmaps.FirstOrDefault(); From 79cf644b8def9d767348a643d610933400c25c91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Jul 2024 07:33:58 +0200 Subject: [PATCH 089/266] Enable NRT while we're here --- osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index 9936b24a0691..03b3b94bd874 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -34,7 +32,7 @@ public void TestMusicPlayAction() [Test] public void TestMusicNavigationActions() { - Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null; + Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null!; // ensure we have at least two beatmaps available to identify the direction the music controller navigated to. AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()), 5); From d7ae9505b2c3fe826d769c64279468671de94d82 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 19 Jul 2024 14:08:05 +0900 Subject: [PATCH 090/266] Fix TestCancelNavigationToEditor test failure https://github.com/ppy/osu/actions/runs/10002179087/job/27648253709 The editor could be pushed before the exit actually occurs. --- .../TestSceneBeatmapEditorNavigation.cs | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index 99d1ff93c5c7..5640682d0637 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -1,12 +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; using System.IO; using System.Linq; +using System.Threading; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Screens; @@ -204,9 +208,13 @@ public void TestCancelNavigationToEditor() AddStep("Set current beatmap to default", () => Game.Beatmap.SetDefault()); - AddStep("Push editor loader", () => Game.ScreenStack.Push(new EditorLoader())); + DelayedLoadEditorLoader loader = null!; + AddStep("Push editor loader", () => Game.ScreenStack.Push(loader = new DelayedLoadEditorLoader())); AddUntilStep("Wait for loader current", () => Game.ScreenStack.CurrentScreen is EditorLoader); + AddUntilStep("wait for editor load start", () => loader.Editor != null); AddStep("Close editor while loading", () => Game.ScreenStack.CurrentScreen.Exit()); + AddStep("allow editor load", () => loader.AllowLoad.Set()); + AddUntilStep("wait for editor ready", () => loader.Editor!.LoadState >= LoadState.Ready); AddUntilStep("Wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu); AddAssert("Check no new beatmaps were made", allBeatmapSets, () => Is.EquivalentTo(beatmapSets)); @@ -356,5 +364,33 @@ private void openEditor() private EditorBeatmap getEditorBeatmap() => getEditor().ChildrenOfType().Single(); private Editor getEditor() => (Editor)Game.ScreenStack.CurrentScreen; + + private partial class DelayedLoadEditorLoader : EditorLoader + { + public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim(); + public Editor? Editor { get; private set; } + + protected override Editor CreateEditor() => Editor = new DelayedLoadEditor(this); + } + + private partial class DelayedLoadEditor : Editor + { + private readonly DelayedLoadEditorLoader loader; + + public DelayedLoadEditor(DelayedLoadEditorLoader loader) + : base(loader) + { + this.loader = loader; + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + // Importantly, this occurs before base.load(). + if (!loader.AllowLoad.Wait(TimeSpan.FromSeconds(10))) + throw new TimeoutException(); + + return base.CreateChildDependencies(parent); + } + } } } From 4dd225fdc8184f062292ff15c258076f29e0bfa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Jul 2024 08:26:53 +0200 Subject: [PATCH 091/266] Fix compose blueprint container not unsubscribing from event Closes https://github.com/ppy/osu/issues/28938. This is related to reloading the composer on timing point changes in scrolling rulesets. The lack of unsubscription from this would cause blueprints to be created for disposed composers via the `hitObjectAdded()` flow. The following line looks as if a sync load should be forced on a newly created placement blueprint: https://github.com/ppy/osu/blob/da4d37c4aded5e10d0a65ff44a08a886e3897e19/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs#L364 however, it is not the case if the parent (`placementBlueprintContainer`) is disposed, which it would be in this case. Therefore, the blueprint stays `NotLoaded` rather than `Ready`, therefore it never receives its DI dependencies, therefore it dies on an `EditorBeatmap` nullref. --- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index fc8bce4c96ac..f1294ccc3cb8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -9,6 +9,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -405,5 +406,13 @@ public HitObjectCompositionTool CurrentTool CommitIfPlacementActive(); } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (Beatmap.IsNotNull()) + Beatmap.HitObjectAdded -= hitObjectAdded; + } } } From 0560214d5b7fd9f71cb1abdd4f32d878c511373b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2024 15:15:49 +0900 Subject: [PATCH 092/266] Fix beatmap carousel performance regression with large databases --- osu.Game/Screens/Select/BeatmapCarousel.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 3f9e676068ae..c76dbf95029e 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -278,13 +278,27 @@ private void beatmapSetsChanged(IRealmCollection sender, ChangeS if (changes == null) { + // Usually we'd handle the initial load case here, but that's already done in load() as an optimisation. + // So the only thing we need to handle here is the edge case where realm blocks-resumes all operations. realmBeatmapSets.Clear(); realmBeatmapSets.AddRange(sender.Select(r => r.ID)); + // Do a full two-way check on missing beatmaps. + // Let's assume that the worst that can happen is deletions or additions. setsRequiringRemoval.Clear(); setsRequiringUpdate.Clear(); - loadBeatmapSets(sender); + foreach (Guid id in realmBeatmapSets) + { + if (!root.BeatmapSetsByID.ContainsKey(id)) + setsRequiringUpdate.Add(id); + } + + foreach (Guid id in root.BeatmapSetsByID.Keys) + { + if (!realmBeatmapSets.Contains(id)) + setsRequiringRemoval.Add(id); + } } else { From 0f29ed618a691e419926de8b5b95df53af2aba47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2024 17:39:43 +0900 Subject: [PATCH 093/266] Don't attempt to clear the carousel during realm blocking operation --- osu.Game/Screens/Select/BeatmapCarousel.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c76dbf95029e..cd0d2eea2c5e 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -278,12 +278,21 @@ private void beatmapSetsChanged(IRealmCollection sender, ChangeS if (changes == null) { - // Usually we'd handle the initial load case here, but that's already done in load() as an optimisation. - // So the only thing we need to handle here is the edge case where realm blocks-resumes all operations. realmBeatmapSets.Clear(); realmBeatmapSets.AddRange(sender.Select(r => r.ID)); - // Do a full two-way check on missing beatmaps. + if (originalBeatmapSetsDetached.Count > 0 && sender.Count == 0) + { + // Usually we'd reset stuff here, but doing so triggers a silly flow which ends up deadlocking realm. + // Additionally, user should not be at song select when realm is blocking all operations in the first place. + // + // Note that due to the catch-up logic below, once operations are restored we will still be in a roughly + // correct state. The only things that this return will change is the carousel will not empty *during* the blocking + // operation. + return; + } + + // Do a full two-way check for missing (or incorrectly present) beatmaps. // Let's assume that the worst that can happen is deletions or additions. setsRequiringRemoval.Clear(); setsRequiringUpdate.Clear(); From 7a4758d8ccbf10e8118f4e71ff0075b4107b3c4d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 19 Jul 2024 17:49:13 +0900 Subject: [PATCH 094/266] Attempt to fix TestSelectableMouseHandling test failure https://github.com/ppy/osu/pull/28900/checks?check_run_id=27652166871 This is an attempt. Going frame-by-frame I noticed that there's one frame in which the text is loaded but the FillFlowContainer/GridContainer haven't properly validated so the text is not positioned correctly (it's overflowing the panel to the left). If the cursor is moved at this exact time, then it may not be properly positioned for the following assertion, even though it is _somewhere_ on the panel. If the above is the case, then this is a known o!f issue, but not a simple one to solve. I haven't reproed this locally. --- .../Multiplayer/TestSceneDrawableRoomPlaylist.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index bd62a8b1311f..2ef56bd54ed2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -12,6 +12,7 @@ using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -319,16 +320,17 @@ public void TestSelectableMouseHandling() }); AddUntilStep("wait for load", () => playlist.ChildrenOfType().Any() && playlist.ChildrenOfType().First().DrawWidth > 0); - AddStep("move mouse to first item title", () => + + AddStep("move mouse to first item title", () => InputManager.MoveMouseTo(playlist.ChildrenOfType().First().ChildrenOfType().First())); + AddAssert("first item title not hovered", () => playlist.ChildrenOfType().First().IsHovered, () => Is.False); + + AddStep("click title", () => { - var drawQuad = playlist.ChildrenOfType().First().ScreenSpaceDrawQuad; - var location = (drawQuad.TopLeft + drawQuad.BottomLeft) / 2 + new Vector2(drawQuad.Width * 0.2f, 0); - InputManager.MoveMouseTo(location); + InputManager.MoveMouseTo(playlist.ChildrenOfType().First().ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); }); - AddAssert("first item title not hovered", () => playlist.ChildrenOfType().First().IsHovered, () => Is.False); - AddStep("click left mouse", () => InputManager.Click(MouseButton.Left)); + AddUntilStep("first item selected", () => playlist.ChildrenOfType().First().IsSelectedItem, () => Is.True); - // implies being clickable. AddUntilStep("first item title hovered", () => playlist.ChildrenOfType().First().IsHovered, () => Is.True); AddStep("move mouse to second item results button", () => InputManager.MoveMouseTo(playlist.ChildrenOfType().ElementAt(5))); From 5af39aad00878d577fb57fcd020ffd8340cfffe9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 19 Jul 2024 19:02:41 +0900 Subject: [PATCH 095/266] Add beatmap name to log string Makes it easy to compare this line versus the one in OsuGame.PresentBeatmap(). At the moment it's just GUID which is... not useful! --- osu.Game/Screens/Select/SongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 14c4a34d1466..307043a31251 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -609,7 +609,7 @@ void run() // clear pending task immediately to track any potential nested debounce operation. selectionChangedDebounce = null; - Logger.Log($"Song select updating selection with beatmap:{beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ShortName ?? "null"}"); + Logger.Log($"Song select updating selection with beatmap: {beatmap} {beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ShortName ?? "null"}"); if (transferRulesetValue()) { From f11f01f9b70fb3548f8e86470ed27289a3c66560 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2024 19:16:37 +0900 Subject: [PATCH 096/266] Fix various visuals of playlist beatmap panels Supersedes https://github.com/ppy/osu/pull/28907. - Fix border being fat - Fix thumbnail not masking correctly - Fix background layer not being correctly fit to the panel - Dim the main background on hover - Minor tweaks to dimming --- .../Drawables/Cards/BeatmapCardThumbnail.cs | 2 +- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 232 ++++++++++-------- 2 files changed, 129 insertions(+), 105 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs index 7b668d7dc477..976f797760fb 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs @@ -101,7 +101,7 @@ private void updateState() bool shouldDim = Dimmed.Value || playButton.Playing.Value; playButton.FadeTo(shouldDim ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - background.FadeColour(colourProvider.Background6.Opacity(shouldDim ? 0.8f : 0f), BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + background.FadeColour(colourProvider.Background6.Opacity(shouldDim ? 0.6f : 0f), BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index ab32ca255852..43ffaf947e1a 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -49,6 +49,8 @@ public partial class DrawableRoomPlaylistItem : OsuRearrangeableListItem /// Invoked when this item requests to be deleted. /// @@ -81,7 +83,7 @@ public partial class DrawableRoomPlaylistItem : OsuRearrangeableListItem(); - private Container maskingContainer; + private Container borderContainer; private FillFlowContainer difficultyIconContainer; private LinkFlowContainer beatmapText; private LinkFlowContainer authorText; @@ -134,7 +136,7 @@ public DrawableRoomPlaylistItem(PlaylistItem item) [BackgroundDependencyLoader] private void load() { - maskingContainer.BorderColour = colours.Yellow; + borderContainer.BorderColour = colours.Yellow; ruleset = rulesets.GetRuleset(Item.RulesetID); var rulesetInstance = ruleset?.CreateInstance(); @@ -161,7 +163,7 @@ protected override void LoadComplete() return; } - maskingContainer.BorderThickness = IsSelectedItem ? 5 : 0; + borderContainer.BorderThickness = IsSelectedItem ? border_thickness : 0; }, true); valid.BindValueChanged(_ => Scheduler.AddOnce(refresh)); @@ -278,8 +280,8 @@ private void refresh() { if (!valid.Value) { - maskingContainer.BorderThickness = 5; - maskingContainer.BorderColour = colours.Red; + borderContainer.BorderThickness = border_thickness; + borderContainer.BorderColour = colours.Red; } if (beatmap != null) @@ -291,12 +293,14 @@ private void refresh() Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Width = 60, + Masking = true, + CornerRadius = 10, RelativeSizeAxes = Axes.Y, Dimmed = { Value = IsHovered } }, new DifficultyIcon(beatmap, ruleset, requiredMods) { - Size = new Vector2(icon_height), + Size = new Vector2(24), TooltipType = DifficultyIconTooltipType.Extended, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -346,136 +350,153 @@ protected override Drawable CreateContent() { Action fontParameters = s => s.Font = OsuFont.Default.With(size: 14, weight: FontWeight.SemiBold); - return maskingContainer = new Container + return new Container { RelativeSizeAxes = Axes.X, Height = HEIGHT, - Masking = true, - CornerRadius = 10, Children = new Drawable[] { - new Box // A transparent box that forces the border to be drawn if the panel background is opaque - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - }, - onScreenLoader, - panelBackground = new PanelBackground - { - RelativeSizeAxes = Axes.Both, - }, - new GridContainer + new Container { RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + Masking = true, + CornerRadius = 10, + Children = new Drawable[] { - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] + onScreenLoader, + panelBackground = new PanelBackground { - difficultyIconContainer = new FillFlowContainer + RelativeSizeAxes = Axes.Both, + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(4), - Margin = new MarginPadding { Right = 4 }, + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize) }, - mainFillFlow = new MainFlow(() => SelectedItem.Value == Model || !AllowSelection) + Content = new[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Children = new Drawable[] + new Drawable[] { - beatmapText = new LinkFlowContainer(fontParameters) + difficultyIconContainer = new FillFlowContainer { - RelativeSizeAxes = Axes.X, - // workaround to ensure only the first line of text shows, emulating truncation (but without ellipsis at the end). - // TODO: remove when text/link flow can support truncation with ellipsis natively. - Height = OsuFont.DEFAULT_FONT_SIZE, - Masking = true + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(4), + Margin = new MarginPadding { Right = 4 }, }, - new FillFlowContainer + mainFillFlow = new MainFlow(() => SelectedItem.Value == Model || !AllowSelection) { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10f, 0), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, -2), Children = new Drawable[] { + beatmapText = new LinkFlowContainer(fontParameters) + { + RelativeSizeAxes = Axes.X, + // workaround to ensure only the first line of text shows, emulating truncation (but without ellipsis at the end). + // TODO: remove when text/link flow can support truncation with ellipsis natively. + Height = OsuFont.DEFAULT_FONT_SIZE, + Masking = true + }, new FillFlowContainer { AutoSizeAxes = Axes.Both, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, Direction = FillDirection.Horizontal, Spacing = new Vector2(10f, 0), Children = new Drawable[] { - authorText = new LinkFlowContainer(fontParameters) { AutoSizeAxes = Axes.Both }, - explicitContent = new ExplicitContentBeatmapBadge + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10f, 0), + Children = new Drawable[] + { + authorText = new LinkFlowContainer(fontParameters) { AutoSizeAxes = Axes.Both }, + explicitContent = new ExplicitContentBeatmapBadge + { + Alpha = 0f, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Top = 3f }, + } + }, + }, + new Container { - Alpha = 0f, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Margin = new MarginPadding { Top = 3f }, + AutoSizeAxes = Axes.Both, + Child = modDisplay = new ModDisplay + { + Scale = new Vector2(0.4f), + ExpansionMode = ExpansionMode.AlwaysExpanded, + Margin = new MarginPadding { Vertical = -6 }, + } } - }, - }, - new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Child = modDisplay = new ModDisplay - { - Scale = new Vector2(0.4f), - ExpansionMode = ExpansionMode.AlwaysExpanded, - Margin = new MarginPadding { Vertical = -6 }, } } } - } + }, + buttonsFlow = new FillFlowContainer + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Horizontal = 8 }, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(5), + ChildrenEnumerable = createButtons().Select(button => button.With(b => + { + b.Anchor = Anchor.Centre; + b.Origin = Anchor.Centre; + })) + }, + ownerAvatar = new OwnerAvatar + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(icon_height), + Margin = new MarginPadding { Right = 8 }, + Masking = true, + CornerRadius = 4, + Alpha = ShowItemOwner ? 1 : 0 + }, } - }, - buttonsFlow = new FillFlowContainer - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Horizontal = 8 }, - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(5), - ChildrenEnumerable = createButtons().Select(button => button.With(b => - { - b.Anchor = Anchor.Centre; - b.Origin = Anchor.Centre; - })) - }, - ownerAvatar = new OwnerAvatar - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(icon_height), - Margin = new MarginPadding { Right = 8 }, - Masking = true, - CornerRadius = 4, - Alpha = ShowItemOwner ? 1 : 0 - }, - } - } + } + }, + }, }, - }, + borderContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10, + Children = new Drawable[] + { + new Box // A transparent box that forces the border to be drawn if the panel background is opaque + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + }, + } + } + } }; } @@ -509,6 +530,8 @@ protected override bool OnHover(HoverEvent e) { if (thumbnail != null) thumbnail.Dimmed.Value = true; + + panelBackground.FadeColour(OsuColour.Gray(0.7f), BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); return base.OnHover(e); } @@ -516,6 +539,8 @@ protected override void OnHoverLost(HoverLostEvent e) { if (thumbnail != null) thumbnail.Dimmed.Value = false; + + panelBackground.FadeColour(OsuColour.Gray(1f), BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); base.OnHoverLost(e); } @@ -642,7 +667,6 @@ public PanelBackground() backgroundSprite = new UpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fill, }, new FillFlowContainer { @@ -651,7 +675,7 @@ public PanelBackground() Direction = FillDirection.Horizontal, // This makes the gradient not be perfectly horizontal, but diagonal at a ~40° angle Shear = new Vector2(0.8f, 0), - Alpha = 0.5f, + Alpha = 0.6f, Children = new[] { // The left half with no gradient applied From 5ee645ac8f9eb630d58951a7843f3b688e2b885c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2024 19:50:21 +0900 Subject: [PATCH 097/266] Increase opacity of control points slightly --- .../Timelines/Summary/Parts/ControlPointVisualisation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs index 977aadd6c34f..17c98003b068 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs @@ -21,7 +21,7 @@ public ControlPointVisualisation(ControlPoint point) : base(point.Time) { Point = point; - Alpha = 0.3f; + Alpha = 0.5f; Blending = BlendingParameters.Additive; } From c4de2bbb60b7052a2ccad915cc10148d00df51f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2024 19:50:40 +0900 Subject: [PATCH 098/266] Ignore "too many ticks" in timeline (triggers in normal cases) --- .../Components/Timeline/TimelineTickDisplay.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index def528d9e58d..4796c08809ff 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -2,12 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; -using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; @@ -161,20 +159,6 @@ private void createTicks() } } - if (Children.Count > 512) - { - // There should always be a sanely small number of ticks rendered. - // If this assertion triggers, either the zoom logic is broken or a beatmap is - // probably doing weird things... - // - // Let's hope the latter never happens. - // If it does, we can choose to either fix it or ignore it as an outlier. - string message = $"Timeline is rendering many ticks ({Children.Count})"; - - Logger.Log(message); - Debug.Fail(message); - } - int usedDrawables = drawableIndex; // save a few drawables beyond the currently used for edge cases. From c2cc85e6f023bb8089c421ce9f9aa6e676953003 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2024 19:59:38 +0900 Subject: [PATCH 099/266] Use purple again for kiai time specifically --- .../Timelines/Summary/Parts/EffectPointVisualisation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs index f1e2b52ad8be..17fedb933a89 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs @@ -100,7 +100,7 @@ private void refreshDisplay() Origin = Anchor.CentreLeft, Height = 0.4f, Depth = float.MaxValue, - Colour = effect.GetRepresentingColour(colours), + Colour = colours.Purple1, }); } } From f500abd4f74f4a5a6f5f717a1e865d9956d1427d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2024 20:02:17 +0900 Subject: [PATCH 100/266] Make "Hold Off" and "No Release" mod incompatible --- osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs | 2 +- osu.Game.Rulesets.Mania/Mods/ManiaModNoRelease.cs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index 4e6cc4f1d676..eba0b2effe2f 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -27,7 +27,7 @@ public class ManiaModHoldOff : Mod, IApplicableAfterBeatmapConversion public override ModType Type => ModType.Conversion; - public override Type[] IncompatibleMods => new[] { typeof(ManiaModInvert) }; + public override Type[] IncompatibleMods => new[] { typeof(ManiaModInvert), typeof(ManiaModNoRelease) }; public void ApplyToBeatmap(IBeatmap beatmap) { diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoRelease.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoRelease.cs index 8cb2e821e62c..b5490aa9502c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoRelease.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNoRelease.cs @@ -1,6 +1,7 @@ // 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 System.Threading; using osu.Framework.Localisation; @@ -27,6 +28,8 @@ public partial class ManiaModNoRelease : Mod, IApplicableAfterBeatmapConversion, public override ModType Type => ModType.DifficultyReduction; + public override Type[] IncompatibleMods => new[] { typeof(ManiaModHoldOff) }; + public void ApplyToBeatmap(IBeatmap beatmap) { var maniaBeatmap = (ManiaBeatmap)beatmap; From d7651ef38728b31a2ae011ea4b428483cdc24cc7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Jul 2024 16:52:10 +0300 Subject: [PATCH 101/266] Add extensive test cases for correct input handling while paused in osu! & non-osu! --- .../Gameplay/TestScenePauseInputHandling.cs | 267 ++++++++++++++++++ osu.Game/Screens/Play/HUD/KeyCounter.cs | 8 +- osu.Game/Screens/Play/Player.cs | 4 +- 3 files changed, 274 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs new file mode 100644 index 000000000000..d778f2e99138 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs @@ -0,0 +1,267 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; +using osu.Game.Storyboards; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public partial class TestScenePauseInputHandling : PlayerTestScene + { + private Ruleset currentRuleset = new OsuRuleset(); + + protected override Ruleset CreatePlayerRuleset() => currentRuleset; + + protected override bool HasCustomSteps => true; + + [Resolved] + private AudioManager audioManager { get; set; } = null!; + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) => + new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); + + [SetUp] + public void SetUp() => Schedule(() => + { + foreach (var key in InputManager.CurrentState.Keyboard.Keys) + InputManager.ReleaseKey(key); + + InputManager.MoveMouseTo(Content); + LocalConfig.SetValue(OsuSetting.KeyOverlay, true); + }); + + [Test] + public void TestOsuInputNotReceivedWhilePaused() + { + KeyCounter counter = null!; + + loadPlayer(() => new OsuRuleset()); + AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == OsuAction.LeftButton)); + checkKey(() => counter, 0, false); + + AddStep("press Z", () => InputManager.PressKey(Key.Z)); + checkKey(() => counter, 1, true); + + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + checkKey(() => counter, 1, false); + + AddStep("pause", () => Player.Pause()); + AddStep("press Z", () => InputManager.PressKey(Key.Z)); + checkKey(() => counter, 1, false); + + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + checkKey(() => counter, 1, false); + + AddStep("resume", () => Player.Resume()); + AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("press Z to resume", () => InputManager.PressKey(Key.Z)); + + // Z key was released before pause, resuming should not trigger it + checkKey(() => counter, 1, false); + + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + checkKey(() => counter, 1, false); + + AddStep("press Z", () => InputManager.PressKey(Key.Z)); + checkKey(() => counter, 2, true); + + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + checkKey(() => counter, 2, false); + } + + [Test] + public void TestManiaInputNotReceivedWhilePaused() + { + KeyCounter counter = null!; + + loadPlayer(() => new ManiaRuleset()); + AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Key1)); + checkKey(() => counter, 0, false); + + AddStep("press D", () => InputManager.PressKey(Key.D)); + checkKey(() => counter, 1, true); + + AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + checkKey(() => counter, 1, false); + + AddStep("pause", () => Player.Pause()); + AddStep("press D", () => InputManager.PressKey(Key.D)); + checkKey(() => counter, 1, false); + + AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + checkKey(() => counter, 1, false); + + AddStep("resume", () => Player.Resume()); + AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning); + checkKey(() => counter, 1, false); + + AddStep("press D", () => InputManager.PressKey(Key.D)); + checkKey(() => counter, 2, true); + + AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + checkKey(() => counter, 2, false); + } + + [Test] + public void TestOsuPreviouslyHeldInputReleaseOnResume() + { + KeyCounter counterZ = null!; + KeyCounter counterX = null!; + + loadPlayer(() => new OsuRuleset()); + AddStep("get key counter Z", () => counterZ = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == OsuAction.LeftButton)); + AddStep("get key counter X", () => counterX = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == OsuAction.RightButton)); + + AddStep("press Z", () => InputManager.PressKey(Key.Z)); + AddStep("pause", () => Player.Pause()); + + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + + AddStep("resume", () => Player.Resume()); + AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("press and release Z", () => InputManager.Key(Key.Z)); + checkKey(() => counterZ, 1, false); + + AddStep("press X", () => InputManager.PressKey(Key.X)); + AddStep("pause", () => Player.Pause()); + AddStep("release X", () => InputManager.ReleaseKey(Key.X)); + checkKey(() => counterX, 1, true); + + AddStep("resume", () => Player.Resume()); + AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("press Z to resume", () => InputManager.PressKey(Key.Z)); + checkKey(() => counterZ, 1, false); + checkKey(() => counterX, 1, false); + } + + [Test] + public void TestManiaPreviouslyHeldInputReleaseOnResume() + { + KeyCounter counter = null!; + + loadPlayer(() => new ManiaRuleset()); + AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Key1)); + + AddStep("press D", () => InputManager.PressKey(Key.D)); + AddStep("pause", () => Player.Pause()); + + AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + checkKey(() => counter, 1, true); + + AddStep("resume", () => Player.Resume()); + AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning); + checkKey(() => counter, 1, false); + } + + [Test] + public void TestOsuHeldInputRemainHeldAfterResume() + { + KeyCounter counterZ = null!; + KeyCounter counterX = null!; + + loadPlayer(() => new OsuRuleset()); + AddStep("get key counter Z", () => counterZ = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == OsuAction.LeftButton)); + AddStep("get key counter X", () => counterX = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == OsuAction.RightButton)); + + AddStep("press Z", () => InputManager.PressKey(Key.Z)); + AddStep("pause", () => Player.Pause()); + + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + + AddStep("resume", () => Player.Resume()); + AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("press Z to resume", () => InputManager.PressKey(Key.Z)); + checkKey(() => counterZ, 1, true); + + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + checkKey(() => counterZ, 1, false); + + AddStep("press X", () => InputManager.PressKey(Key.X)); + checkKey(() => counterX, 1, true); + + AddStep("pause", () => Player.Pause()); + + AddStep("release X", () => InputManager.ReleaseKey(Key.X)); + AddStep("press X", () => InputManager.PressKey(Key.X)); + + AddStep("resume", () => Player.Resume()); + AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("press Z to resume", () => InputManager.PressKey(Key.Z)); + checkKey(() => counterZ, 1, false); + checkKey(() => counterX, 1, true); + + AddStep("release X", () => InputManager.ReleaseKey(Key.X)); + checkKey(() => counterZ, 1, false); + checkKey(() => counterX, 1, false); + } + + [Test] + public void TestManiaHeldInputRemainHeldAfterResume() + { + KeyCounter counter = null!; + + loadPlayer(() => new ManiaRuleset()); + AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Key1)); + + AddStep("press D", () => InputManager.PressKey(Key.D)); + checkKey(() => counter, 1, true); + + AddStep("pause", () => Player.Pause()); + + AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + AddStep("press D", () => InputManager.PressKey(Key.D)); + + AddStep("resume", () => Player.Resume()); + AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning); + checkKey(() => counter, 1, true); + + AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + checkKey(() => counter, 1, false); + } + + private void loadPlayer(Func createRuleset) + { + AddStep("set ruleset", () => currentRuleset = createRuleset()); + AddStep("load player", LoadPlayer); + AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); + AddUntilStep("wait for hud", () => Player.HUDOverlay.ChildrenOfType().All(s => s.ComponentsLoaded)); + + AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(20000)); + AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(20000).Within(500)); + AddAssert("not in break", () => !Player.IsBreakTime.Value); + } + + private void checkKey(Func counter, int count, bool active) + { + AddAssert($"key count = {count}", () => counter().CountPresses.Value, () => Is.EqualTo(count)); + AddAssert($"key active = {active}", () => counter().IsActive.Value, () => Is.EqualTo(active)); + } + + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new PausePlayer(); + + private partial class PausePlayer : TestPlayer + { + protected override double PauseCooldownDuration => 0; + + public PausePlayer() + : base(allowPause: true, showResults: false) + { + } + } + } +} diff --git a/osu.Game/Screens/Play/HUD/KeyCounter.cs b/osu.Game/Screens/Play/HUD/KeyCounter.cs index f12d2166fcea..66f9dfd6f224 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounter.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounter.cs @@ -24,7 +24,9 @@ public abstract partial class KeyCounter : Container /// /// Whether this is currently in the "activated" state because the associated key is currently pressed. /// - protected readonly Bindable IsActive = new BindableBool(); + public IBindable IsActive => isActive; + + private readonly Bindable isActive = new BindableBool(); protected KeyCounter(InputTrigger trigger) { @@ -36,12 +38,12 @@ protected KeyCounter(InputTrigger trigger) protected virtual void Activate(bool forwardPlayback = true) { - IsActive.Value = true; + isActive.Value = true; } protected virtual void Deactivate(bool forwardPlayback = true) { - IsActive.Value = false; + isActive.Value = false; } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 3a08d3be249f..4a419e1431d1 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -987,14 +987,14 @@ private void onFailComplete() /// /// The amount of gameplay time after which a second pause is allowed. /// - private const double pause_cooldown = 1000; + protected virtual double PauseCooldownDuration => 1000; protected PauseOverlay PauseOverlay { get; private set; } private double? lastPauseActionTime; protected bool PauseCooldownActive => - lastPauseActionTime.HasValue && GameplayClockContainer.CurrentTime < lastPauseActionTime + pause_cooldown; + lastPauseActionTime.HasValue && GameplayClockContainer.CurrentTime < lastPauseActionTime + PauseCooldownDuration; /// /// A set of conditionals which defines whether the current game state and configuration allows for From 4f6c7fe7c3c6941db20abc28cf321c03fa58646c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Jul 2024 16:52:48 +0300 Subject: [PATCH 102/266] Schedule resume operation by one frame to ensure the triggered key down event does not cause a gameplay press --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index a04ea806403e..8a137e666528 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -116,7 +116,7 @@ public bool OnPressed(KeyBindingPressEvent e) scaleTransitionContainer.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint); - ResumeRequested?.Invoke(); + Schedule(() => ResumeRequested?.Invoke()); return true; } From 818b60a3d80aa5e3c702fadf9cd83397e56e2a66 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Jul 2024 18:18:37 +0300 Subject: [PATCH 103/266] Fix pause overlay hiding input from ruleset input manager If a key is pressed while the pause overlay is visible, the ruleset input manager will not see it, therefore if the user resumes while the key is held then releases the key, the ruleset input manager will not receive the key up event. --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index da239d585ece..2b961278d533 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -32,8 +32,6 @@ public abstract partial class GameplayMenuOverlay : OverlayContainer, IKeyBindin private const int button_height = 70; private const float background_alpha = 0.75f; - protected override bool BlockNonPositionalInput => true; - protected override bool BlockScrollInput => false; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; From e539670df1d6d73c077b5a3792ea27c3318e464e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Jul 2024 19:19:36 +0300 Subject: [PATCH 104/266] Add explanatory note --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 8a137e666528..d809f2b31896 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -116,6 +116,8 @@ public bool OnPressed(KeyBindingPressEvent e) scaleTransitionContainer.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint); + // When resuming with a button, we do not want the osu! input manager to see this button press and include it in the score. + // To ensure that this works correctly, schedule the resume operation one frame forward, since the resume operation enables the input manager to see input events. Schedule(() => ResumeRequested?.Invoke()); return true; } From d914b990f3ee62b5aacb48c5893340aff396c73c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 20 Jul 2024 14:08:00 +0900 Subject: [PATCH 105/266] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index fe0a452e9236..7785cb3c9466 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index acfcae7c9336..dceb88c6f7ac 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From e2fe1935a92943c5e505bf82cf76e4e59ab93298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 11:22:36 +0200 Subject: [PATCH 106/266] Add failing test case --- .../Editor/TestSceneEditorTestGameplay.cs | 70 +++++++++++++++++++ .../osu.Game.Rulesets.Taiko.Tests.csproj | 1 + 2 files changed, 71 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorTestGameplay.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorTestGameplay.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorTestGameplay.cs new file mode 100644 index 000000000000..2422e62571a1 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorTestGameplay.cs @@ -0,0 +1,70 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Components.Timelines.Summary; +using osu.Game.Screens.Edit.GameplayTest; +using osu.Game.Storyboards; +using osu.Game.Tests.Beatmaps.IO; +using osu.Game.Tests.Visual; +using osuTK.Input; + +namespace osu.Game.Rulesets.Taiko.Tests.Editor +{ + public partial class TestSceneTaikoEditorTestGameplay : EditorTestScene + { + protected override bool IsolateSavingFromDatabase => false; + + protected override Ruleset CreateEditorRuleset() => new TaikoRuleset(); + + [Resolved] + private OsuGameBase game { get; set; } = null!; + + [Resolved] + private BeatmapManager beatmaps { get; set; } = null!; + + private BeatmapSetInfo importedBeatmapSet = null!; + + public override void SetUpSteps() + { + AddStep("import test beatmap", () => importedBeatmapSet = BeatmapImportHelper.LoadOszIntoOsu(game).GetResultSafely()); + base.SetUpSteps(); + } + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) + => beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == 1)); + + [Test] + public void TestBasicGameplayTest() + { + AddStep("add objects", () => + { + EditorBeatmap.Clear(); + EditorBeatmap.Add(new Swell { StartTime = 500, EndTime = 1500 }); + EditorBeatmap.Add(new Hit { StartTime = 3000 }); + }); + AddStep("seek to 250", () => EditorClock.Seek(250)); + AddUntilStep("wait for seek", () => EditorClock.CurrentTime, () => Is.EqualTo(250)); + + AddStep("click test gameplay button", () => + { + var button = Editor.ChildrenOfType().Single(); + + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("save prompt shown", () => DialogOverlay.CurrentDialog is SaveRequiredPopupDialog); + + AddStep("save changes", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + AddUntilStep("player pushed", () => Stack.CurrentScreen is EditorPlayer); + AddUntilStep("wait for return to editor", () => Stack.CurrentScreen is Screens.Edit.Editor); + } + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index 26afd4244584..a2420fc679ab 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -11,5 +11,6 @@ + From 157cc884f4c48c3e130f655fe60343a3dfda1cf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 11:23:28 +0200 Subject: [PATCH 107/266] Fix swells not being correctly treated in editor gameplay test Closes https://github.com/ppy/osu/issues/28989. Because swell ticks are judged manually by their parenting objects, swell ticks were not given a start time (with the thinking that there isn't really one *to* give). This tripped up the "judge past objects" logic in `EditorPlayer`, since it would enumerate all objects (regardless of nesting) that are prior to current time and mark them as judged. With all swell ticks having the default start time of 0 they would get judged more often than not, leading to behaviour weirdness. To resolve, give swell ticks a *relatively* sane start time equal to the start time of the swell itself. --- osu.Game.Rulesets.Taiko/Objects/Swell.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index a8db8df021d5..d9e8c77ea78a 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -33,6 +33,7 @@ protected override void CreateNestedHitObjects(CancellationToken cancellationTok cancellationToken.ThrowIfCancellationRequested(); AddNested(new SwellTick { + StartTime = StartTime, Samples = Samples }); } From 636e965868866283b3848e74281ab6df897a9e53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 11:26:04 +0200 Subject: [PATCH 108/266] Remove no-longer-valid test remark & adjust test --- .../TestSceneDrumSampleTriggerSource.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumSampleTriggerSource.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumSampleTriggerSource.cs index 6c925f566bf2..b47f02afa30a 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumSampleTriggerSource.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumSampleTriggerSource.cs @@ -315,10 +315,7 @@ public void TestNormalSwell() hitObjectContainer.Add(drawableSwell); }); - // You might think that this should be a SwellTick since we're before the swell, but SwellTicks get no StartTime (ie. they are zero). - // This works fine in gameplay because they are judged whenever the user pressed, rather than being timed hits. - // But for sample playback purposes they can be ignored as noise. - AddAssert("most valid object is swell", () => triggerSource.GetMostValidObject(), Is.InstanceOf); + AddAssert("most valid object is swell tick", () => triggerSource.GetMostValidObject(), Is.InstanceOf); checkSamples(HitType.Centre, false, HitSampleInfo.HIT_NORMAL, SampleControlPoint.DEFAULT_BANK); checkSamples(HitType.Rim, false, HitSampleInfo.HIT_CLAP, SampleControlPoint.DEFAULT_BANK); @@ -352,10 +349,7 @@ public void TestDrumSwell() hitObjectContainer.Add(drawableSwell); }); - // You might think that this should be a SwellTick since we're before the swell, but SwellTicks get no StartTime (ie. they are zero). - // This works fine in gameplay because they are judged whenever the user pressed, rather than being timed hits. - // But for sample playback purposes they can be ignored as noise. - AddAssert("most valid object is swell", () => triggerSource.GetMostValidObject(), Is.InstanceOf); + AddAssert("most valid object is swell tick", () => triggerSource.GetMostValidObject(), Is.InstanceOf); checkSamples(HitType.Centre, false, HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_DRUM); checkSamples(HitType.Rim, false, HitSampleInfo.HIT_CLAP, HitSampleInfo.BANK_DRUM); From 57fa502786c2c7a609b8b9428a3b4fd082fd546d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 11:57:46 +0200 Subject: [PATCH 109/266] Fix editor UI dimming when hovering over expanded part of toolboxes Closes https://github.com/ppy/osu/issues/28969. --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 3c38a7258e4a..c2a7bec9f9df 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -305,7 +305,9 @@ protected override void Update() PlayfieldContentContainer.X = TOOLBOX_CONTRACTED_SIZE_LEFT; } - composerFocusMode.Value = PlayfieldContentContainer.Contains(InputManager.CurrentState.Mouse.Position); + composerFocusMode.Value = PlayfieldContentContainer.Contains(InputManager.CurrentState.Mouse.Position) + && !LeftToolbox.Contains(InputManager.CurrentState.Mouse.Position) + && !RightToolbox.Contains(InputManager.CurrentState.Mouse.Position); } public override Playfield Playfield => drawableRulesetWrapper.Playfield; From 64381d4087994086ee9b0bdada5e06e23a0f3f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 12:18:53 +0200 Subject: [PATCH 110/266] Fix catch juice stream vertex add operation not undoing --- .../Edit/Blueprints/Components/SelectionEditablePath.cs | 9 ++++++++- .../Edit/Blueprints/JuiceStreamSelectionBlueprint.cs | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs index c7a26ca15a32..c4e906d5dce9 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Catch.Objects; using osu.Game.Screens.Edit; using osuTK; using osuTK.Input; @@ -19,22 +20,28 @@ public partial class SelectionEditablePath : EditablePath, IHasContextMenu { public MenuItem[] ContextMenuItems => getContextMenuItems().ToArray(); + private readonly JuiceStream juiceStream; + // To handle when the editor is scrolled while dragging. private Vector2 dragStartPosition; [Resolved] private IEditorChangeHandler? changeHandler { get; set; } - public SelectionEditablePath(Func positionToTime) + public SelectionEditablePath(JuiceStream juiceStream, Func positionToTime) : base(positionToTime) { + this.juiceStream = juiceStream; } public void AddVertex(Vector2 relativePosition) { + changeHandler?.BeginChange(); double time = Math.Max(0, PositionToTime(relativePosition.Y)); int index = AddVertex(time, relativePosition.X); + UpdateHitObjectFromPath(juiceStream); selectOnly(index); + changeHandler?.EndChange(); } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => InternalChildren.Any(d => d.ReceivePositionalInputAt(screenSpacePos)); diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs index 49d778ad0810..a492920d3a29 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs @@ -60,7 +60,7 @@ public JuiceStreamSelectionBlueprint(JuiceStream hitObject) { scrollingPath = new ScrollingPath(), nestedOutlineContainer = new NestedOutlineContainer(), - editablePath = new SelectionEditablePath(positionToTime) + editablePath = new SelectionEditablePath(hitObject, positionToTime) }; } From 47964f33d77aca5dd4305313aac0f7216648a000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 13:21:49 +0200 Subject: [PATCH 111/266] Fix catch juice stream vertex remove operation not undoing --- .../Blueprints/Components/SelectionEditablePath.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs index c4e906d5dce9..904d7a25791f 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs @@ -54,7 +54,11 @@ protected override bool OnMouseDown(MouseDownEvent e) if (e.Button == MouseButton.Left && e.ShiftPressed) { + changeHandler?.BeginChange(); RemoveVertex(index); + UpdateHitObjectFromPath(juiceStream); + changeHandler?.EndChange(); + return true; } @@ -125,11 +129,17 @@ private void selectOnly(int index) private void deleteSelectedVertices() { + changeHandler?.BeginChange(); + for (int i = VertexCount - 1; i >= 0; i--) { if (VertexStates[i].IsSelected) RemoveVertex(i); } + + UpdateHitObjectFromPath(juiceStream); + + changeHandler?.EndChange(); } } } From 6b3c1f4e47abc22a60bddaa6dbc0c8a43d13bff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 13:26:16 +0200 Subject: [PATCH 112/266] Unify juice stream piece UX with osu! control point pieces - Use same hover state - Use shift-right click for quick delete rather than shift-left click --- .../Components/SelectionEditablePath.cs | 2 +- .../Edit/Blueprints/Components/VertexPiece.cs | 30 ++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs index 904d7a25791f..6a4e35b1f9e2 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs @@ -52,7 +52,7 @@ protected override bool OnMouseDown(MouseDownEvent e) if (index == -1 || VertexStates[index].IsFixed) return false; - if (e.Button == MouseButton.Left && e.ShiftPressed) + if (e.Button == MouseButton.Right && e.ShiftPressed) { changeHandler?.BeginChange(); RemoveVertex(index); diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/VertexPiece.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/VertexPiece.cs index 07d7c7269896..a3f8e85278ab 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/VertexPiece.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/VertexPiece.cs @@ -5,6 +5,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osuTK; @@ -12,6 +13,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components { public partial class VertexPiece : Circle { + private VertexState state = new VertexState(); + [Resolved] private OsuColour osuColour { get; set; } = null!; @@ -24,7 +27,32 @@ public VertexPiece() public void UpdateFrom(VertexState state) { - Colour = state.IsSelected ? osuColour.Yellow.Lighten(1) : osuColour.Yellow; + this.state = state; + updateMarkerDisplay(); + } + + protected override bool OnHover(HoverEvent e) + { + updateMarkerDisplay(); + return false; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateMarkerDisplay(); + } + + /// + /// Updates the state of the circular control point marker. + /// + private void updateMarkerDisplay() + { + var colour = osuColour.Yellow; + + if (IsHovered || state.IsSelected) + colour = colour.Lighten(1); + + Colour = colour; Alpha = state.IsFixed ? 0.5f : 1; } } From 1d91201c4303e1cb32470b86cb2ff1d8f306b0de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 13:33:51 +0200 Subject: [PATCH 113/266] Fix tests --- .../Editor/TestSceneJuiceStreamSelectionBlueprint.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs index c96f32d87c19..10cf294a36aa 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs @@ -158,14 +158,14 @@ public void TestAddVertex() float[] positions = { 200, 200 }; addBlueprintStep(times, positions, 0.2); - addAddVertexSteps(500, 150); - addVertexCheckStep(3, 1, 500, 150); + addAddVertexSteps(500, 180); + addVertexCheckStep(3, 1, 500, 180); addAddVertexSteps(90, 200); addVertexCheckStep(4, 1, times[0], positions[0]); - addAddVertexSteps(750, 180); - addVertexCheckStep(5, 4, 750, 180); + addAddVertexSteps(750, 200); + addVertexCheckStep(5, 4, 750, 200); AddAssert("duration is changed", () => Precision.AlmostEquals(hitObject.Duration, 800 - times[0], 1e-3)); } @@ -265,7 +265,7 @@ private void addDeleteVertexSteps(double time, float x) AddStep("delete vertex", () => { InputManager.PressKey(Key.ShiftLeft); - InputManager.Click(MouseButton.Left); + InputManager.Click(MouseButton.Right); InputManager.ReleaseKey(Key.ShiftLeft); }); } From f86ab1a64e8137bf89d6c082eefe4d3d72ed7466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 13:49:52 +0200 Subject: [PATCH 114/266] Fix filename --- ...eEditorTestGameplay.cs => TestSceneTaikoEditorTestGameplay.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Rulesets.Taiko.Tests/Editor/{TestSceneEditorTestGameplay.cs => TestSceneTaikoEditorTestGameplay.cs} (100%) diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorTestGameplay.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorTestGameplay.cs similarity index 100% rename from osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorTestGameplay.cs rename to osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorTestGameplay.cs From 56af009e7759d364b6ded4d57d0f9c32c1f6dbd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 14:47:33 +0200 Subject: [PATCH 115/266] Fix `EditablePath.UpdateHitObjectFromPath()` not automatically updating object This is important because the editable path conversions heavily depend on the value of `JuiceStream.Velocity` being correct. The value is only guaranteed to be correct after an `ApplyDefaults()` call, which is triggered by updating the object via `EditorBeatmap`. --- .../Blueprints/Components/EditablePath.cs | 5 ++++ .../Components/SelectionEditablePath.cs | 23 ++++++++----------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs index 86f92d16ca52..857c00cd4130 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Screens.Edit; using osuTK; namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components @@ -42,6 +43,9 @@ public abstract partial class EditablePath : CompositeDrawable [Resolved] private IBeatSnapProvider? beatSnapProvider { get; set; } + [Resolved] + protected EditorBeatmap? EditorBeatmap { get; private set; } + protected EditablePath(Func positionToTime) { PositionToTime = positionToTime; @@ -112,6 +116,7 @@ public void UpdateHitObjectFromPath(JuiceStream hitObject) double endTime = hitObject.StartTime + path.Duration; double snappedEndTime = beatSnapProvider.SnapTime(endTime, hitObject.StartTime); hitObject.Path.ExpectedDistance.Value = (snappedEndTime - hitObject.StartTime) * hitObject.Velocity; + EditorBeatmap?.Update(hitObject); } public Vector2 ToRelativePosition(Vector2 screenSpacePosition) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs index 6a4e35b1f9e2..b2ee43ba163a 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs @@ -4,13 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Screens.Edit; using osuTK; using osuTK.Input; @@ -25,9 +23,6 @@ public partial class SelectionEditablePath : EditablePath, IHasContextMenu // To handle when the editor is scrolled while dragging. private Vector2 dragStartPosition; - [Resolved] - private IEditorChangeHandler? changeHandler { get; set; } - public SelectionEditablePath(JuiceStream juiceStream, Func positionToTime) : base(positionToTime) { @@ -36,12 +31,14 @@ public SelectionEditablePath(JuiceStream juiceStream, Func positi public void AddVertex(Vector2 relativePosition) { - changeHandler?.BeginChange(); + EditorBeatmap?.BeginChange(); + double time = Math.Max(0, PositionToTime(relativePosition.Y)); int index = AddVertex(time, relativePosition.X); UpdateHitObjectFromPath(juiceStream); selectOnly(index); - changeHandler?.EndChange(); + + EditorBeatmap?.EndChange(); } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => InternalChildren.Any(d => d.ReceivePositionalInputAt(screenSpacePos)); @@ -54,10 +51,10 @@ protected override bool OnMouseDown(MouseDownEvent e) if (e.Button == MouseButton.Right && e.ShiftPressed) { - changeHandler?.BeginChange(); + EditorBeatmap?.BeginChange(); RemoveVertex(index); UpdateHitObjectFromPath(juiceStream); - changeHandler?.EndChange(); + EditorBeatmap?.EndChange(); return true; } @@ -85,7 +82,7 @@ protected override bool OnDragStart(DragStartEvent e) for (int i = 0; i < VertexCount; i++) VertexStates[i].VertexBeforeChange = Vertices[i]; - changeHandler?.BeginChange(); + EditorBeatmap?.BeginChange(); return true; } @@ -99,7 +96,7 @@ protected override void OnDrag(DragEvent e) protected override void OnDragEnd(DragEndEvent e) { - changeHandler?.EndChange(); + EditorBeatmap?.EndChange(); } private int getMouseTargetVertex(Vector2 screenSpacePosition) @@ -129,7 +126,7 @@ private void selectOnly(int index) private void deleteSelectedVertices() { - changeHandler?.BeginChange(); + EditorBeatmap?.BeginChange(); for (int i = VertexCount - 1; i >= 0; i--) { @@ -139,7 +136,7 @@ private void deleteSelectedVertices() UpdateHitObjectFromPath(juiceStream); - changeHandler?.EndChange(); + EditorBeatmap?.EndChange(); } } } From f3617eadad1f997d5cd4eea45eb28c22467c25f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 14:51:45 +0200 Subject: [PATCH 116/266] Fix editing juice stream path sometimes changing its duration I'm not *super* sure why this works, but it appears to, and my educated guess as to why is that it counteracts the effects of a change in the SV of the juice stream by artificially increasing or decreasing the velocity when running the appropriate path conversions and expected distance calculations. The actual SV change takes effect on the next default application, which is triggered by the `Update()` call at the end of the method. --- .../Edit/Blueprints/Components/EditablePath.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs index 857c00cd4130..e626392234cc 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs @@ -107,15 +107,22 @@ public void UpdateHitObjectFromPath(JuiceStream hitObject) // // The value is clamped here by the bindable min and max values. // In case the required velocity is too large, the path is not preserved. + double previousVelocity = svBindable.Value; svBindable.Value = Math.Ceiling(requiredVelocity / svToVelocityFactor); - path.ConvertToSliderPath(hitObject.Path, hitObject.LegacyConvertedY, hitObject.Velocity); + // adjust velocity locally, so that once the SV change is applied by applying defaults + // (triggered by `EditorBeatmap.Update()` call at end of method), + // it results in the outcome desired by the user. + double relativeChange = svBindable.Value / previousVelocity; + double localVelocity = hitObject.Velocity * relativeChange; + path.ConvertToSliderPath(hitObject.Path, hitObject.LegacyConvertedY, localVelocity); if (beatSnapProvider == null) return; double endTime = hitObject.StartTime + path.Duration; double snappedEndTime = beatSnapProvider.SnapTime(endTime, hitObject.StartTime); - hitObject.Path.ExpectedDistance.Value = (snappedEndTime - hitObject.StartTime) * hitObject.Velocity; + hitObject.Path.ExpectedDistance.Value = (snappedEndTime - hitObject.StartTime) * localVelocity; + EditorBeatmap?.Update(hitObject); } From 6100f5269d757177d557af714a17a8c116530dbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 14:57:36 +0200 Subject: [PATCH 117/266] Fix tests --- .../TestSceneJuiceStreamSelectionBlueprint.cs | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs index 10cf294a36aa..7b665b1ff91f 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs @@ -82,6 +82,7 @@ public void TestVertexDrag() AddMouseMoveStep(-100, 100); addVertexCheckStep(3, 1, times[0], positions[0]); + addDragEndStep(); } [Test] @@ -100,6 +101,9 @@ public void TestMultipleDrag() AddMouseMoveStep(times[2] - 50, positions[2] - 50); addVertexCheckStep(4, 1, times[1] - 50, positions[1] - 50); addVertexCheckStep(4, 2, times[2] - 50, positions[2] - 50); + + AddStep("release control", () => InputManager.ReleaseKey(Key.ControlLeft)); + addDragEndStep(); } [Test] @@ -113,6 +117,7 @@ public void TestSliderVelocityChange() addDragStartStep(times[1], positions[1]); AddMouseMoveStep(times[1], 400); AddAssert("slider velocity changed", () => !hitObject.SliderVelocityMultiplierBindable.IsDefault); + addDragEndStep(); } [Test] @@ -129,6 +134,7 @@ public void TestScrollWhileDrag() AddStep("scroll playfield", () => manualClock.CurrentTime += 200); AddMouseMoveStep(times[1] + 200, positions[1] + 100); addVertexCheckStep(2, 1, times[1] + 200, positions[1] + 100); + addDragEndStep(); } [Test] @@ -158,21 +164,21 @@ public void TestAddVertex() float[] positions = { 200, 200 }; addBlueprintStep(times, positions, 0.2); - addAddVertexSteps(500, 180); - addVertexCheckStep(3, 1, 500, 180); + addAddVertexSteps(500, 150); + addVertexCheckStep(3, 1, 500, 150); - addAddVertexSteps(90, 200); - addVertexCheckStep(4, 1, times[0], positions[0]); + addAddVertexSteps(160, 200); + addVertexCheckStep(4, 1, 160, 200); - addAddVertexSteps(750, 200); - addVertexCheckStep(5, 4, 750, 200); + addAddVertexSteps(750, 180); + addVertexCheckStep(5, 4, 800, 160); AddAssert("duration is changed", () => Precision.AlmostEquals(hitObject.Duration, 800 - times[0], 1e-3)); } [Test] public void TestDeleteVertex() { - double[] times = { 100, 300, 500 }; + double[] times = { 100, 300, 400 }; float[] positions = { 100, 200, 150 }; addBlueprintStep(times, positions); From 38fc6f70f63a1dbff8b1b1848ea61c9c6b9bd0a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Jul 2024 11:05:53 +0200 Subject: [PATCH 118/266] Add tolerance when drag-scrolling editor timeline Closes https://github.com/ppy/osu/issues/28983. While the direct cause of this is most likely mouse confine in full-screen, it shouldn't/can't really be disabled just for this, and I also get this on linux in *windowed* mode. In checking other apps, adding some tolerance to this sort of drag-scroll behaviour seems like a sane UX improvement anyways. --- .../Timeline/TimelineBlueprintContainer.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 9a8fdc3dac6c..62c15996e07d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -198,11 +198,20 @@ private void handleScrollViaDrag() var timelineQuad = timeline.ScreenSpaceDrawQuad; float mouseX = InputManager.CurrentState.Mouse.Position.X; + // for better UX do not require the user to drag all the way to the edge and beyond to initiate a drag-scroll. + // this is especially important in scenarios like fullscreen, where mouse confine will usually be on + // and the user physically *won't be able to* drag beyond the edge of the timeline + // (since its left edge is co-incident with the window edge). + const float scroll_tolerance = 20; + + float leftBound = timelineQuad.TopLeft.X + scroll_tolerance; + float rightBound = timelineQuad.TopRight.X - scroll_tolerance; + // scroll if in a drag and dragging outside visible extents - if (mouseX > timelineQuad.TopRight.X) - timeline.ScrollBy((float)((mouseX - timelineQuad.TopRight.X) / 10 * Clock.ElapsedFrameTime)); - else if (mouseX < timelineQuad.TopLeft.X) - timeline.ScrollBy((float)((mouseX - timelineQuad.TopLeft.X) / 10 * Clock.ElapsedFrameTime)); + if (mouseX > rightBound) + timeline.ScrollBy((float)((mouseX - rightBound) / 10 * Clock.ElapsedFrameTime)); + else if (mouseX < leftBound) + timeline.ScrollBy((float)((mouseX - leftBound) / 10 * Clock.ElapsedFrameTime)); } private partial class SelectableAreaBackground : CompositeDrawable From cc4ed0ff3f9892052128adfe5b46ded90e918f95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jul 2024 18:59:22 +0900 Subject: [PATCH 119/266] Use non-screen-space coordinates and add time-based drag ramping for better control --- .../Timeline/TimelineBlueprintContainer.cs | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 62c15996e07d..ca23e3e88f2c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -100,10 +101,14 @@ protected override bool OnDragStart(DragStartEvent e) return base.OnDragStart(e); } + private float dragTimeAccumulated; + protected override void Update() { if (IsDragged || hitObjectDragged) handleScrollViaDrag(); + else + dragTimeAccumulated = 0; if (Composer != null && timeline != null) { @@ -193,25 +198,42 @@ bool shouldBeSelected(HitObject hitObject) private void handleScrollViaDrag() { + // The amount of time dragging before we reach maximum drag speed. + const float time_ramp_multiplier = 5000; + + // A maximum drag speed to ensure things don't get out of hand. + const float max_velocity = 10; + if (timeline == null) return; - var timelineQuad = timeline.ScreenSpaceDrawQuad; - float mouseX = InputManager.CurrentState.Mouse.Position.X; + var mousePos = timeline.ToLocalSpace(InputManager.CurrentState.Mouse.Position); // for better UX do not require the user to drag all the way to the edge and beyond to initiate a drag-scroll. // this is especially important in scenarios like fullscreen, where mouse confine will usually be on // and the user physically *won't be able to* drag beyond the edge of the timeline // (since its left edge is co-incident with the window edge). - const float scroll_tolerance = 20; + const float scroll_tolerance = 40; + + float leftBound = timeline.BoundingBox.TopLeft.X + scroll_tolerance; + float rightBound = timeline.BoundingBox.TopRight.X - scroll_tolerance; + + float amount = 0; + + if (mousePos.X > rightBound) + amount = mousePos.X - rightBound; + else if (mousePos.X < leftBound) + amount = mousePos.X - leftBound; + + if (amount == 0) + { + dragTimeAccumulated = 0; + return; + } - float leftBound = timelineQuad.TopLeft.X + scroll_tolerance; - float rightBound = timelineQuad.TopRight.X - scroll_tolerance; + amount = Math.Sign(amount) * Math.Min(max_velocity, Math.Abs(MathF.Pow(amount, 2) / (MathF.Pow(scroll_tolerance, 2)))); + dragTimeAccumulated += (float)Clock.ElapsedFrameTime; - // scroll if in a drag and dragging outside visible extents - if (mouseX > rightBound) - timeline.ScrollBy((float)((mouseX - rightBound) / 10 * Clock.ElapsedFrameTime)); - else if (mouseX < leftBound) - timeline.ScrollBy((float)((mouseX - leftBound) / 10 * Clock.ElapsedFrameTime)); + timeline.ScrollBy(amount * (float)Clock.ElapsedFrameTime * Math.Min(1, dragTimeAccumulated / time_ramp_multiplier)); } private partial class SelectableAreaBackground : CompositeDrawable From 25d63ac6a59ae4c4ada282eaf7c5d0b20376331e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Jul 2024 13:25:38 +0200 Subject: [PATCH 120/266] Move editor beatmap processor test cases off of `OsuHitObject`s Most of them are about to become obsolete once consideration for `TimePreempt` is re-added. --- .../TestSceneEditorBeatmapProcessor.cs | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs b/osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs index 251099c0e270..c4a61177a9da 100644 --- a/osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs +++ b/osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs @@ -8,7 +8,6 @@ using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; namespace osu.Game.Tests.Editing @@ -45,7 +44,7 @@ public void TestSingleObjectBeatmap() BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 1000 }, + new Note { StartTime = 1000 }, } }); @@ -67,8 +66,8 @@ public void TestTwoObjectsCloseTogether() BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 1000 }, - new HitCircle { StartTime = 2000 }, + new Note { StartTime = 1000 }, + new Note { StartTime = 2000 }, } }); @@ -136,8 +135,8 @@ public void TestTwoObjectsFarApart() BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 1000 }, - new HitCircle { StartTime = 5000 }, + new Note { StartTime = 1000 }, + new Note { StartTime = 5000 }, } }); @@ -164,8 +163,8 @@ public void TestBreaksAreFused() BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 1000 }, - new HitCircle { StartTime = 9000 }, + new Note { StartTime = 1000 }, + new Note { StartTime = 9000 }, }, Breaks = { @@ -197,9 +196,9 @@ public void TestBreaksAreSplit() BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 1000 }, - new HitCircle { StartTime = 5000 }, - new HitCircle { StartTime = 9000 }, + new Note { StartTime = 1000 }, + new Note { StartTime = 5000 }, + new Note { StartTime = 9000 }, }, Breaks = { @@ -232,8 +231,8 @@ public void TestBreaksAreNudged() BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 1100 }, - new HitCircle { StartTime = 9000 }, + new Note { StartTime = 1100 }, + new Note { StartTime = 9000 }, }, Breaks = { @@ -264,8 +263,8 @@ public void TestManualBreaksAreNotFused() BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 1000 }, - new HitCircle { StartTime = 9000 }, + new Note { StartTime = 1000 }, + new Note { StartTime = 9000 }, }, Breaks = { @@ -299,9 +298,9 @@ public void TestManualBreaksAreSplit() BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 1000 }, - new HitCircle { StartTime = 5000 }, - new HitCircle { StartTime = 9000 }, + new Note { StartTime = 1000 }, + new Note { StartTime = 5000 }, + new Note { StartTime = 9000 }, }, Breaks = { @@ -334,8 +333,8 @@ public void TestManualBreaksAreNotNudged() BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 1000 }, - new HitCircle { StartTime = 9000 }, + new Note { StartTime = 1000 }, + new Note { StartTime = 9000 }, }, Breaks = { @@ -366,8 +365,8 @@ public void TestBreaksAtEndOfBeatmapAreRemoved() BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 1000 }, - new HitCircle { StartTime = 2000 }, + new Note { StartTime = 1000 }, + new Note { StartTime = 2000 }, }, Breaks = { @@ -393,8 +392,8 @@ public void TestManualBreaksAtEndOfBeatmapAreRemoved() BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 1000 }, - new HitCircle { StartTime = 2000 }, + new Note { StartTime = 1000 }, + new Note { StartTime = 2000 }, }, Breaks = { @@ -447,8 +446,8 @@ public void TestBreaksAtStartOfBeatmapAreRemoved() BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 10000 }, - new HitCircle { StartTime = 11000 }, + new Note { StartTime = 10000 }, + new Note { StartTime = 11000 }, }, Breaks = { @@ -474,8 +473,8 @@ public void TestManualBreaksAtStartOfBeatmapAreRemoved() BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 10000 }, - new HitCircle { StartTime = 11000 }, + new Note { StartTime = 10000 }, + new Note { StartTime = 11000 }, }, Breaks = { From 088e8ad0a27415fd0b93e79e0126a18051191d0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Jul 2024 13:30:10 +0200 Subject: [PATCH 121/266] Respect pre-empt time when auto-generating breaks Closes https://github.com/ppy/osu/issues/28703. --- .../Objects/CatchHitObject.cs | 2 +- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 4 ++-- .../Rulesets/Objects/Types/IHasTimePreempt.cs | 13 +++++++++++++ .../Screens/Edit/EditorBeatmapProcessor.cs | 18 +++++++++++++----- 4 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Types/IHasTimePreempt.cs diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 52c42dfddbdc..329055b3ddd5 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Objects { - public abstract class CatchHitObject : HitObject, IHasPosition, IHasComboInformation + public abstract class CatchHitObject : HitObject, IHasPosition, IHasComboInformation, IHasTimePreempt { public const float OBJECT_RADIUS = 64; diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 6c77d9189caf..1b0993b69889 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Objects { - public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition + public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition, IHasTimePreempt { /// /// The radius of hit objects (ie. the radius of a ). @@ -46,7 +46,7 @@ public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPositi /// public const double PREEMPT_MAX = 1800; - public double TimePreempt = 600; + public double TimePreempt { get; set; } = 600; public double TimeFadeIn = 400; private HitObjectProperty position; diff --git a/osu.Game/Rulesets/Objects/Types/IHasTimePreempt.cs b/osu.Game/Rulesets/Objects/Types/IHasTimePreempt.cs new file mode 100644 index 000000000000..e7239515f683 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Types/IHasTimePreempt.cs @@ -0,0 +1,13 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Objects.Types +{ + /// + /// A that appears on screen at a fixed time interval before its . + /// + public interface IHasTimePreempt + { + double TimePreempt { get; } + } +} diff --git a/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs b/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs index 9b6d956a4ce1..5c435e771dc1 100644 --- a/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs +++ b/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs @@ -8,6 +8,7 @@ using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Screens.Edit { @@ -67,19 +68,26 @@ private void autoGenerateBreaks() for (int i = 1; i < Beatmap.HitObjects.Count; ++i) { + var previousObject = Beatmap.HitObjects[i - 1]; + var nextObject = Beatmap.HitObjects[i]; + // Keep track of the maximum end time encountered thus far. // This handles cases like osu!mania's hold notes, which could have concurrent other objects after their start time. // Note that we're relying on the implicit assumption that objects are sorted by start time, // which is why similar tracking is not done for start time. - currentMaxEndTime = Math.Max(currentMaxEndTime, Beatmap.HitObjects[i - 1].GetEndTime()); - - double nextObjectStartTime = Beatmap.HitObjects[i].StartTime; + currentMaxEndTime = Math.Max(currentMaxEndTime, previousObject.GetEndTime()); - if (nextObjectStartTime - currentMaxEndTime < BreakPeriod.MIN_GAP_DURATION) + if (nextObject.StartTime - currentMaxEndTime < BreakPeriod.MIN_GAP_DURATION) continue; double breakStartTime = currentMaxEndTime + BreakPeriod.GAP_BEFORE_BREAK; - double breakEndTime = nextObjectStartTime - Math.Max(BreakPeriod.GAP_AFTER_BREAK, Beatmap.ControlPointInfo.TimingPointAt(nextObjectStartTime).BeatLength * 2); + + double breakEndTime = nextObject.StartTime; + + if (nextObject is IHasTimePreempt hasTimePreempt) + breakEndTime -= hasTimePreempt.TimePreempt; + else + breakEndTime -= Math.Max(BreakPeriod.GAP_AFTER_BREAK, Beatmap.ControlPointInfo.TimingPointAt(nextObject.StartTime).BeatLength * 2); if (breakEndTime - breakStartTime < BreakPeriod.MIN_BREAK_DURATION) continue; From c2fa30bf81b46316c6043944c73d4519db4a73cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Jul 2024 13:38:25 +0200 Subject: [PATCH 122/266] Add test coverage for break generation respecting pre-empt time --- .../TestSceneEditorBeatmapProcessor.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs b/osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs index c4a61177a9da..bbcf6aac2cac 100644 --- a/osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs +++ b/osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; namespace osu.Game.Tests.Editing @@ -488,5 +489,55 @@ public void TestManualBreaksAtStartOfBeatmapAreRemoved() Assert.That(beatmap.Breaks, Is.Empty); } + + [Test] + public void TestTimePreemptIsRespected() + { + var controlPoints = new ControlPointInfo(); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + var beatmap = new EditorBeatmap(new Beatmap + { + ControlPointInfo = controlPoints, + BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, + Difficulty = + { + ApproachRate = 10, + }, + HitObjects = + { + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 5000 }, + } + }); + + foreach (var ho in beatmap.HitObjects) + ho.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); + + var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset()); + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.Multiple(() => + { + Assert.That(beatmap.Breaks, Has.Count.EqualTo(1)); + Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200)); + Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(5000 - OsuHitObject.PREEMPT_MIN)); + }); + + beatmap.Difficulty.ApproachRate = 0; + + foreach (var ho in beatmap.HitObjects) + ho.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); + + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.Multiple(() => + { + Assert.That(beatmap.Breaks, Has.Count.EqualTo(1)); + Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200)); + Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(5000 - OsuHitObject.PREEMPT_MAX)); + }); + } } } From c3062f96eee5d3a7a92a5f105bf4c62d024d3572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Jul 2024 13:38:50 +0200 Subject: [PATCH 123/266] Fix autogenerated breaks not invalidating on change to pre-empt time --- osu.Game/Screens/Edit/EditorBeatmapProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs b/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs index 5c435e771dc1..4fe431498fcc 100644 --- a/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs +++ b/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs @@ -45,7 +45,7 @@ public void PostProcess() private void autoGenerateBreaks() { - var objectDuration = Beatmap.HitObjects.Select(ho => (ho.StartTime, ho.GetEndTime())).ToHashSet(); + var objectDuration = Beatmap.HitObjects.Select(ho => (ho.StartTime - ((ho as IHasTimePreempt)?.TimePreempt ?? 0), ho.GetEndTime())).ToHashSet(); if (objectDuration.SetEquals(objectDurationCache)) return; From aed7ba9508b636a00a7e38c7e6519fe2bbb87af3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jul 2024 20:56:21 +0900 Subject: [PATCH 124/266] Change order of application to avoid bias to side with more room to drag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Compose/Components/Timeline/TimelineBlueprintContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index ca23e3e88f2c..740f0b6aac24 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -230,7 +230,7 @@ private void handleScrollViaDrag() return; } - amount = Math.Sign(amount) * Math.Min(max_velocity, Math.Abs(MathF.Pow(amount, 2) / (MathF.Pow(scroll_tolerance, 2)))); + amount = Math.Sign(amount) * Math.Min(max_velocity, MathF.Pow(Math.Clamp(Math.Abs(amount), 0, scroll_tolerance), 2)); dragTimeAccumulated += (float)Clock.ElapsedFrameTime; timeline.ScrollBy(amount * (float)Clock.ElapsedFrameTime * Math.Min(1, dragTimeAccumulated / time_ramp_multiplier)); From bf4bf4d39e126e67d3125a4e03a04c7fda2af677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jul 2024 08:54:30 +0200 Subject: [PATCH 125/266] Fill daily challenge top 50 position numbers client-side Only doing this client-side, because doing this server-side is expensive: https://github.com/ppy/osu-web/pull/11354#discussion_r1689224285 --- .../OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs index 2b2c3a5e1f56..8ba490b14dea 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs @@ -138,9 +138,9 @@ public void RefetchScores() } else { - LoadComponentsAsync(best.Select(s => new LeaderboardScoreV2(s, sheared: false) + LoadComponentsAsync(best.Select((s, index) => new LeaderboardScoreV2(s, sheared: false) { - Rank = s.Position, + Rank = index + 1, IsPersonalBest = s.UserID == api.LocalUser.Value.Id, Action = () => PresentScore?.Invoke(s.OnlineID), }), loaded => From 788b70469d855f46d08adce40bd5ab983d381bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jul 2024 09:15:32 +0200 Subject: [PATCH 126/266] Exit daily challenge screen when going offline This sort of thing is bound to happen when rewriting screens from scratch without invoking abstract eldritch entities sometimes. Damned if you do, damned if you don't... --- .../DailyChallenge/DailyChallenge.cs | 28 +++++++++++++++++++ .../Screens/OnlinePlay/OnlinePlayScreen.cs | 1 + 2 files changed, 29 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 2d58b3b82cfb..21014447287d 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -21,6 +21,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Metadata; using osu.Game.Online.Multiplayer; @@ -48,6 +49,8 @@ public partial class DailyChallenge : OsuScreen /// private readonly Bindable> userMods = new Bindable>(Array.Empty()); + private readonly IBindable apiState = new Bindable(); + private OnlinePlayScreenWaveContainer waves = null!; private DailyChallengeLeaderboard leaderboard = null!; private RoomModSelectOverlay userModsSelectOverlay = null!; @@ -84,6 +87,9 @@ public partial class DailyChallenge : OsuScreen [Resolved] private UserLookupCache userLookupCache { get; set; } = null!; + [Resolved] + protected IAPIProvider API { get; private set; } = null!; + public override bool DisallowExternalBeatmapRulesetChanges => true; public DailyChallenge(Room room) @@ -358,6 +364,9 @@ protected override void LoadComplete() userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(userModsSelectOverlay); userModsSelectOverlay.SelectedItem.Value = playlistItem; userMods.BindValueChanged(_ => Scheduler.AddOnce(updateMods), true); + + apiState.BindTo(API.State); + apiState.BindValueChanged(onlineStateChanged, true); } private void trySetDailyChallengeBeatmap() @@ -368,6 +377,25 @@ private void trySetDailyChallengeBeatmap() applyLoopingToTrack(); } + private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => + { + if (state.NewValue != APIState.Online) + Schedule(forcefullyExit); + }); + + private void forcefullyExit() + { + Logger.Log($"{this} forcefully exiting due to loss of API connection"); + + // This is temporary since we don't currently have a way to force screens to be exited + // See also: `OnlinePlayScreen.forcefullyExit()` + if (this.IsCurrentScreen()) + { + while (this.IsCurrentScreen()) + this.Exit(); + } + } + public override void OnEntering(ScreenTransitionEvent e) { base.OnEntering(e); diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index 9b6284fb8914..1b7041c9bb3b 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -99,6 +99,7 @@ private void forcefullyExit() Logger.Log($"{this} forcefully exiting due to loss of API connection"); // This is temporary since we don't currently have a way to force screens to be exited + // See also: `DailyChallenge.forcefullyExit()` if (this.IsCurrentScreen()) { while (this.IsCurrentScreen()) From 55382a4ba6edea3f1d26227bd125f8db7f3c8c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jul 2024 12:08:12 +0200 Subject: [PATCH 127/266] Add test coverage for expected sample popover behaviour --- .../TestSceneHitObjectSampleAdjustments.cs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs index 558d8dce9488..75a68237c87d 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs @@ -402,6 +402,70 @@ public void TestHotkeysDuringPlacement() void checkPlacementSample(string expected) => AddAssert($"Placement sample is {expected}", () => EditorBeatmap.PlacementObject.Value.Samples.First().Bank, () => Is.EqualTo(expected)); } + [Test] + public void PopoverForMultipleSelectionChangesAllSamples() + { + AddStep("add slider", () => + { + EditorBeatmap.Add(new Slider + { + Position = new Vector2(256, 256), + StartTime = 1000, + Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero), new PathControlPoint(new Vector2(250, 0)) }), + Samples = + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL) + }, + NodeSamples = new List> + { + new List + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_DRUM), + new HitSampleInfo(HitSampleInfo.HIT_CLAP, bank: HitSampleInfo.BANK_DRUM), + }, + new List + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_SOFT), + new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, bank: HitSampleInfo.BANK_SOFT), + }, + } + }); + }); + AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + + clickSamplePiece(0); + + setBankViaPopover(HitSampleInfo.BANK_DRUM); + samplePopoverHasSingleBank(HitSampleInfo.BANK_DRUM); + hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleNormalBank(2, HitSampleInfo.BANK_DRUM); + hitObjectNodeHasSampleNormalBank(2, 0, HitSampleInfo.BANK_DRUM); + hitObjectNodeHasSampleNormalBank(2, 1, HitSampleInfo.BANK_DRUM); + + setVolumeViaPopover(30); + samplePopoverHasSingleVolume(30); + hitObjectHasSampleVolume(0, 30); + hitObjectHasSampleVolume(1, 30); + hitObjectHasSampleVolume(2, 30); + hitObjectNodeHasSampleVolume(2, 0, 30); + hitObjectNodeHasSampleVolume(2, 1, 30); + + toggleAdditionViaPopover(0); + hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + hitObjectHasSamples(1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + hitObjectHasSamples(2, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + hitObjectNodeHasSamples(2, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE); + hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + + setAdditionBankViaPopover(HitSampleInfo.BANK_SOFT); + hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT); + hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_SOFT); + hitObjectHasSampleAdditionBank(2, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSampleAdditionBank(2, 0, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSampleAdditionBank(2, 1, HitSampleInfo.BANK_SOFT); + } + [Test] public void TestHotkeysAffectNodeSamples() { From 1ed7e4b075bafe9ccf2e33ef252e272c61bb7d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Jul 2024 14:34:48 +0200 Subject: [PATCH 128/266] Make sample popover change properties of all samples in multiple selection Closes https://github.com/ppy/osu/issues/28916. The previous behaviour *may* have been intended, but it was honestly quite baffling. This seems like a saner variant. --- .../Timeline/NodeSamplePointPiece.cs | 8 +-- .../Components/Timeline/SamplePointPiece.cs | 51 +++++++++++++++---- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs index ae3838bc413e..9cc1268db7ab 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs @@ -34,12 +34,12 @@ public partial class NodeSampleEditPopover : SampleEditPopover { private readonly int nodeIndex; - protected override IList GetRelevantSamples(HitObject ho) + protected override IEnumerable<(HitObject hitObject, IList samples)> GetRelevantSamples(HitObject[] hitObjects) { - if (ho is not IHasRepeats hasRepeats) - return ho.Samples; + if (hitObjects.Length > 1 || hitObjects[0] is not IHasRepeats hasRepeats) + return base.GetRelevantSamples(hitObjects); - return nodeIndex < hasRepeats.NodeSamples.Count ? hasRepeats.NodeSamples[nodeIndex] : ho.Samples; + return [(hitObjects[0], nodeIndex < hasRepeats.NodeSamples.Count ? hasRepeats.NodeSamples[nodeIndex] : hitObjects[0].Samples)]; } public NodeSampleEditPopover(HitObject hitObject, int nodeIndex) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 8c7603021a0b..8bfb0a33587d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Components.TernaryButtons; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit.Timing; using osuTK; using osuTK.Graphics; @@ -106,15 +107,34 @@ public partial class SampleEditPopover : OsuPopover private FillFlowContainer togglesCollection = null!; private HitObject[] relevantObjects = null!; - private IList[] allRelevantSamples = null!; + private (HitObject hitObject, IList samples)[] allRelevantSamples = null!; /// /// Gets the sub-set of samples relevant to this sample point piece. /// For example, to edit node samples this should return the samples at the index of the node. /// - /// The hit object to get the relevant samples from. + /// The hit objects to get the relevant samples from. /// The relevant list of samples. - protected virtual IList GetRelevantSamples(HitObject ho) => ho.Samples; + protected virtual IEnumerable<(HitObject hitObject, IList samples)> GetRelevantSamples(HitObject[] hitObjects) + { + if (hitObjects.Length == 1) + { + yield return (hitObjects[0], hitObjects[0].Samples); + + yield break; + } + + foreach (var ho in hitObjects) + { + yield return (ho, ho.Samples); + + if (ho is IHasRepeats hasRepeats) + { + foreach (var node in hasRepeats.NodeSamples) + yield return (ho, node); + } + } + } [Resolved(canBeNull: true)] private EditorBeatmap beatmap { get; set; } = null!; @@ -172,7 +192,7 @@ private void load() // if the piece belongs to a currently selected object, assume that the user wants to change all selected objects. // if the piece belongs to an unselected object, operate on that object alone, independently of the selection. relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray(); - allRelevantSamples = relevantObjects.Select(GetRelevantSamples).ToArray(); + allRelevantSamples = GetRelevantSamples(relevantObjects).ToArray(); // even if there are multiple objects selected, we can still display sample volume or bank if they all have the same value. int? commonVolume = getCommonVolume(); @@ -214,9 +234,19 @@ private void load() togglesCollection.AddRange(createTernaryButtons().Select(b => new DrawableTernaryButton(b) { RelativeSizeAxes = Axes.None, Size = new Vector2(40, 40) })); } - private string? getCommonBank() => allRelevantSamples.Select(GetBankValue).Distinct().Count() == 1 ? GetBankValue(allRelevantSamples.First()) : null; - private string? getCommonAdditionBank() => allRelevantSamples.Select(GetAdditionBankValue).Where(o => o is not null).Distinct().Count() == 1 ? GetAdditionBankValue(allRelevantSamples.First()) : null; - private int? getCommonVolume() => allRelevantSamples.Select(GetVolumeValue).Distinct().Count() == 1 ? GetVolumeValue(allRelevantSamples.First()) : null; + private string? getCommonBank() => allRelevantSamples.Select(h => GetBankValue(h.samples)).Distinct().Count() == 1 + ? GetBankValue(allRelevantSamples.First().samples) + : null; + + private string? getCommonAdditionBank() + { + string[] additionBanks = allRelevantSamples.Select(h => GetAdditionBankValue(h.samples)).Where(o => o is not null).Cast().Distinct().ToArray(); + return additionBanks.Length == 1 ? additionBanks[0] : null; + } + + private int? getCommonVolume() => allRelevantSamples.Select(h => GetVolumeValue(h.samples)).Distinct().Count() == 1 + ? GetVolumeValue(allRelevantSamples.First().samples) + : null; private void updatePrimaryBankState() { @@ -231,7 +261,7 @@ private void updateAdditionBankState() additionBank.PlaceholderText = string.IsNullOrEmpty(commonAdditionBank) ? "(multiple)" : string.Empty; additionBank.Current.Value = commonAdditionBank; - bool anyAdditions = allRelevantSamples.Any(o => o.Any(s => s.Name != HitSampleInfo.HIT_NORMAL)); + bool anyAdditions = allRelevantSamples.Any(o => o.samples.Any(s => s.Name != HitSampleInfo.HIT_NORMAL)); if (anyAdditions) additionBank.Show(); else @@ -247,9 +277,8 @@ private void updateAllRelevantSamples(Action> up { beatmap.BeginChange(); - foreach (var relevantHitObject in relevantObjects) + foreach (var (relevantHitObject, relevantSamples) in GetRelevantSamples(relevantObjects)) { - var relevantSamples = GetRelevantSamples(relevantHitObject); updateAction(relevantHitObject, relevantSamples); beatmap.Update(relevantHitObject); } @@ -333,7 +362,7 @@ private void updateTernaryStates() { foreach ((string sampleName, var bindable) in selectionSampleStates) { - bindable.Value = SelectionHandler.GetStateFromSelection(relevantObjects, h => GetRelevantSamples(h).Any(s => s.Name == sampleName)); + bindable.Value = SelectionHandler.GetStateFromSelection(GetRelevantSamples(relevantObjects), h => h.samples.Any(s => s.Name == sampleName)); } } From 6645dac71d418499b1d758e06fcf859b4329005f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 24 Jul 2024 23:19:04 +0300 Subject: [PATCH 129/266] Fix dragging number boxes overwritten by select-all-on-focus feature --- .../UserInterface/TestSceneOsuTextBox.cs | 18 ++++++++++++++++++ osu.Game/Graphics/UserInterface/OsuTextBox.cs | 3 ++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs index 921c5bbbfaea..435fe2f7a2ed 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs @@ -76,6 +76,24 @@ public void TestSelectAllOnFocus() InputManager.Click(MouseButton.Left); }); AddAssert("text selected", () => numberBoxes.First().SelectedText == "987654321"); + + AddStep("click away", () => + { + InputManager.MoveMouseTo(Vector2.Zero); + InputManager.Click(MouseButton.Left); + }); + + Drawable textContainer = null!; + + AddStep("move mouse to end of text", () => + { + textContainer = numberBoxes.First().ChildrenOfType().ElementAt(1); + InputManager.MoveMouseTo(textContainer.ScreenSpaceDrawQuad.TopRight); + }); + AddStep("hold mouse", () => InputManager.PressButton(MouseButton.Left)); + AddStep("drag to half", () => InputManager.MoveMouseTo(textContainer.ScreenSpaceDrawQuad.BottomRight - new Vector2(textContainer.ScreenSpaceDrawQuad.Width / 2, 0))); + AddStep("release mouse", () => InputManager.ReleaseButton(MouseButton.Left)); + AddAssert("half text selected", () => numberBoxes.First().SelectedText == "54321"); } private void clearTextboxes(IEnumerable textBoxes) => AddStep("clear textbox", () => textBoxes.ForEach(textBox => textBox.Text = null)); diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 90a000d4414d..6388f56f6180 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -261,7 +261,8 @@ protected override void OnFocus(FocusEvent e) base.OnFocus(e); - if (SelectAllOnFocus) + // we may become focused from an ongoing drag operation, we don't want to overwrite selection in that case. + if (SelectAllOnFocus && string.IsNullOrEmpty(SelectedText)) SelectAll(); } From b3e3bf7ceca3b67663b38036ba3002cc4e6dc68b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 24 Jul 2024 23:26:23 +0300 Subject: [PATCH 130/266] Add lenience to avoid floating point errors --- osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs index 435fe2f7a2ed..abad7e775cd6 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs @@ -91,7 +91,7 @@ public void TestSelectAllOnFocus() InputManager.MoveMouseTo(textContainer.ScreenSpaceDrawQuad.TopRight); }); AddStep("hold mouse", () => InputManager.PressButton(MouseButton.Left)); - AddStep("drag to half", () => InputManager.MoveMouseTo(textContainer.ScreenSpaceDrawQuad.BottomRight - new Vector2(textContainer.ScreenSpaceDrawQuad.Width / 2, 0))); + AddStep("drag to half", () => InputManager.MoveMouseTo(textContainer.ScreenSpaceDrawQuad.BottomRight - new Vector2(textContainer.ScreenSpaceDrawQuad.Width / 2 + 1f, 0))); AddStep("release mouse", () => InputManager.ReleaseButton(MouseButton.Left)); AddAssert("half text selected", () => numberBoxes.First().SelectedText == "54321"); } From 4cc07badbd7f3c68b39456d51bfd98d0dd093b1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jul 2024 09:05:58 +0900 Subject: [PATCH 131/266] Disable macOS test runs for now We are seeing update frames run as little as [once per second](https://github.com/ppy/osu/blob/aa4d16bdb873d7296b899cee7b7491ffdf5cd6ab/osu.Game/Overlays/BeatmapListingOverlay.cs#L141). Until we can ascertain why this is happening, let's reduce developer stress by not running macOS tests for now. --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc1cb6c186f7..ba65cfa33ac8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,7 +64,8 @@ jobs: matrix: os: - { prettyname: Windows, fullname: windows-latest } - - { prettyname: macOS, fullname: macos-latest } + # macOS runner performance has gotten unbearably slow so let's turn them off temporarily. + # - { prettyname: macOS, fullname: macos-latest } - { prettyname: Linux, fullname: ubuntu-latest } threadingMode: ['SingleThread', 'MultiThreaded'] timeout-minutes: 120 From d63335082ec4be44a40cc4386852bf876dba501c Mon Sep 17 00:00:00 2001 From: Cyrus Yip Date: Wed, 24 Jul 2024 18:24:52 -0700 Subject: [PATCH 132/266] fix link to good first issues --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0fe6b6fb4d52..ebe1e0807477 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,7 +55,7 @@ When in doubt, it's probably best to start with a discussion first. We will esca While pull requests from unaffiliated contributors are welcome, please note that due to significant community interest and limited review throughput, the core team's primary focus is on the issues which are currently [on the roadmap](https://github.com/orgs/ppy/projects/7/views/6). Reviewing PRs that fall outside of the scope of the roadmap is done on a best-effort basis, so please be aware that it may take a while before a core maintainer gets around to review your change. -The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues to start with. We also have a [`good-first-issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue) label, although from experience it is not used very often, as it is relatively rare that we can spot an issue that will definitively be a good first issue for a new contributor regardless of their programming experience. +The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues to start with. We also have a [`good first issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) label, although from experience it is not used very often, as it is relatively rare that we can spot an issue that will definitively be a good first issue for a new contributor regardless of their programming experience. In the case of simple issues, a direct PR is okay. However, if you decide to work on an existing issue which doesn't seem trivial, **please ask us first**. This way we can try to estimate if it is a good fit for you and provide the correct direction on how to address it. In addition, note that while we do not rule out external contributors from working on roadmapped issues, we will generally prefer to handle them ourselves unless they're not very time sensitive. From a696e3c2612bd079ba061e6b7715a1757dd0fbe2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 25 Jul 2024 10:44:44 +0900 Subject: [PATCH 133/266] Add reference to android project --- .../osu.Game.Rulesets.Taiko.Tests.Android.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj b/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj index ee973e854488..88aa137797a6 100644 --- a/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj +++ b/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj @@ -19,6 +19,7 @@ + From 9ec687caab0178cf2cb17ea9872a548bbcda70d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jul 2024 12:55:45 +0900 Subject: [PATCH 134/266] Avoid reloading the daily challenge leaderboard when already requested --- .../OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs index 8ba490b14dea..4c4622bba33e 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs @@ -118,9 +118,14 @@ protected override void LoadComplete() RefetchScores(); } + private IndexPlaylistScoresRequest? request; + public void RefetchScores() { - var request = new IndexPlaylistScoresRequest(room.RoomID.Value!.Value, playlistItem.ID); + if (request?.CompletionState == APIRequestCompletionState.Waiting) + return; + + request = new IndexPlaylistScoresRequest(room.RoomID.Value!.Value, playlistItem.ID); request.Success += req => Schedule(() => { From aac98ab6b25fd5a6fe5ccf57e219eb5b4dc3431f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jul 2024 12:58:32 +0900 Subject: [PATCH 135/266] Debounce leaderboard refetches to stop excessive operations after returning from gameplay --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 21014447287d..aab04582754c 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -349,7 +349,7 @@ private void onRoomScoreSet(MultiplayerRoomScoreSetEvent e) feed.AddNewScore(ev); if (e.NewRank <= 50) - Schedule(() => leaderboard.RefetchScores()); + Scheduler.AddOnce(() => leaderboard.RefetchScores()); }); }); } @@ -486,7 +486,7 @@ private void startPlay() sampleStart?.Play(); this.Push(new PlayerLoader(() => new PlaylistsPlayer(room, playlistItem) { - Exited = () => leaderboard.RefetchScores() + Exited = () => Scheduler.AddOnce(() => leaderboard.RefetchScores()) })); } From 0182f3d7c3e52fd63f9267148498ee069d5644f0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 25 Jul 2024 07:39:58 +0300 Subject: [PATCH 136/266] Add failing test case --- .../DailyChallenge/TestSceneDailyChallenge.cs | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index cd09a1d20fdf..0e0927a678be 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -4,9 +4,12 @@ using System; using System.Linq; using NUnit.Framework; +using osu.Framework.Testing; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.Ranking; +using osu.Game.Screens.SelectV2.Leaderboards; using osu.Game.Tests.Resources; using osu.Game.Tests.Visual.OnlinePlay; @@ -14,10 +17,14 @@ namespace osu.Game.Tests.Visual.DailyChallenge { public partial class TestSceneDailyChallenge : OnlinePlayTestScene { - [Test] - public void TestDailyChallenge() + [SetUpSteps] + public override void SetUpSteps() { - var room = new Room + base.SetUpSteps(); + + Room room = null!; + + AddStep("add room", () => API.Perform(new CreateRoomRequest(room = new Room { RoomID = { Value = 1234 }, Name = { Value = "Daily Challenge: June 4, 2024" }, @@ -31,10 +38,22 @@ public void TestDailyChallenge() }, EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, Category = { Value = RoomCategory.DailyChallenge } - }; + }))); - AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room))); } + + [Test] + public void TestDailyChallenge() + { + } + + [Test] + public void TestScoreNavigation() + { + AddStep("click on score", () => this.ChildrenOfType().First().TriggerClick()); + AddUntilStep("wait for load", () => Stack.CurrentScreen is ResultsScreen results && results.IsLoaded); + AddAssert("replay download button exists", () => this.ChildrenOfType().Count(), () => Is.EqualTo(1)); + } } } From dad8e28446fe82940d0fd5cbb8e99f63d083c5de Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 25 Jul 2024 07:40:17 +0300 Subject: [PATCH 137/266] Fix replay download button not added when no score is selected initially --- osu.Game/Screens/Ranking/ReplayDownloadButton.cs | 10 +++++----- osu.Game/Screens/Ranking/ResultsScreen.cs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs index df5f9c7a8a28..aac29ad26965 100644 --- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Ranking { public partial class ReplayDownloadButton : CompositeDrawable, IKeyBindingHandler { - public readonly Bindable Score = new Bindable(); + public readonly Bindable Score = new Bindable(); protected readonly Bindable State = new Bindable(); @@ -44,7 +44,7 @@ private ReplayAvailability replayAvailability } } - public ReplayDownloadButton(ScoreInfo score) + public ReplayDownloadButton(ScoreInfo? score) { Score.Value = score; Size = new Vector2(50, 30); @@ -67,11 +67,11 @@ private void load(OsuGame? game, ScoreModelDownloader scoreDownloader) switch (State.Value) { case DownloadState.LocallyAvailable: - game?.PresentScore(Score.Value, ScorePresentType.Gameplay); + game?.PresentScore(Score.Value!, ScorePresentType.Gameplay); break; case DownloadState.NotDownloaded: - scoreDownloader.Download(Score.Value); + scoreDownloader.Download(Score.Value!); break; case DownloadState.Importing: @@ -147,7 +147,7 @@ private void exportWhenReady(ValueChangedEvent state) { if (state.NewValue != DownloadState.LocallyAvailable) return; - scoreManager.Export(Score.Value); + scoreManager.Export(Score.Value!); State.ValueChanged -= exportWhenReady; } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 44b270db5392..07936978331e 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -179,11 +179,11 @@ private void load(AudioManager audio) Scheduler.AddDelayed(() => OverlayActivationMode.Value = OverlayActivation.All, shouldFlair ? AccuracyCircle.TOTAL_DURATION + 1000 : 0); } - if (SelectedScore.Value != null && AllowWatchingReplay) + if (AllowWatchingReplay) { buttons.Add(new ReplayDownloadButton(SelectedScore.Value) { - Score = { BindTarget = SelectedScore! }, + Score = { BindTarget = SelectedScore }, Width = 300 }); } From bba151a776bb910c0db8514aa66564130e4a6ead Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jul 2024 14:24:04 +0900 Subject: [PATCH 138/266] Make event feed test show more realistic content automatically --- .../TestSceneDailyChallengeEventFeed.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs index d9a8ccd510b5..77ae5b653ade 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs @@ -35,6 +35,7 @@ public void TestBasicAppearance() feed = new DailyChallengeEventFeed { RelativeSizeAxes = Axes.Both, + Height = 0.3f, Anchor = Anchor.Centre, Origin = Anchor.Centre, } @@ -44,13 +45,13 @@ public void TestBasicAppearance() if (feed.IsNotNull()) feed.Width = width; }); - AddSliderStep("adjust height", 0.1f, 1, 1, height => + AddSliderStep("adjust height", 0.1f, 1, 0.3f, height => { if (feed.IsNotNull()) feed.Height = height; }); - AddStep("add normal score", () => + AddRepeatStep("add normal score", () => { var ev = new NewScoreEvent(1, new APIUser { @@ -60,9 +61,9 @@ public void TestBasicAppearance() }, RNG.Next(1_000_000), null); feed.AddNewScore(ev); - }); + }, 50); - AddStep("add new user best", () => + AddRepeatStep("add new user best", () => { var ev = new NewScoreEvent(1, new APIUser { @@ -75,9 +76,9 @@ public void TestBasicAppearance() testScore.TotalScore = RNG.Next(1_000_000); feed.AddNewScore(ev); - }); + }, 50); - AddStep("add top 10 score", () => + AddRepeatStep("add top 10 score", () => { var ev = new NewScoreEvent(1, new APIUser { @@ -87,7 +88,7 @@ public void TestBasicAppearance() }, RNG.Next(1_000_000), RNG.Next(1, 10)); feed.AddNewScore(ev); - }); + }, 50); } } } From c90d345ff98ec5ba15ffcc1cc2c0a46cf193b352 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jul 2024 14:20:17 +0900 Subject: [PATCH 139/266] Scroll content forever rather than aggressively fading --- .../OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs index e76238abad3e..24e530223e36 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs @@ -54,7 +54,6 @@ public void AddNewScore(NewScoreEvent newScoreEvent) PresentScore = PresentScore, }; flow.Add(row); - row.Delay(15000).Then().FadeOut(300, Easing.OutQuint).Expire(); } protected override void Update() @@ -65,6 +64,8 @@ protected override void Update() { var row = flow[i]; + row.Alpha = Math.Max(0, (row.Y + flow.DrawHeight) / flow.DrawHeight); + if (row.Y < -flow.DrawHeight) { row.RemoveAndDisposeImmediately(); From f1dda4ab1ed5c040569c4224389abfe4b30290b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jul 2024 14:28:09 +0900 Subject: [PATCH 140/266] Fix too many event rows displaying after spending a long time in gameplay/results --- .../TestSceneDailyChallengeEventFeed.cs | 33 ++++++++++++++++--- .../DailyChallenge/DailyChallengeEventFeed.cs | 24 ++++++++++---- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs index 77ae5b653ade..4b784f661dd9 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs @@ -6,6 +6,7 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; @@ -17,14 +18,14 @@ namespace osu.Game.Tests.Visual.DailyChallenge { public partial class TestSceneDailyChallengeEventFeed : OsuTestScene { + private DailyChallengeEventFeed feed = null!; + [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); - [Test] - public void TestBasicAppearance() + [SetUpSteps] + public void SetUpSteps() { - DailyChallengeEventFeed feed = null!; - AddStep("create content", () => Children = new Drawable[] { new Box @@ -40,6 +41,7 @@ public void TestBasicAppearance() Origin = Anchor.Centre, } }); + AddSliderStep("adjust width", 0.1f, 1, 1, width => { if (feed.IsNotNull()) @@ -50,7 +52,11 @@ public void TestBasicAppearance() if (feed.IsNotNull()) feed.Height = height; }); + } + [Test] + public void TestBasicAppearance() + { AddRepeatStep("add normal score", () => { var ev = new NewScoreEvent(1, new APIUser @@ -90,5 +96,24 @@ public void TestBasicAppearance() feed.AddNewScore(ev); }, 50); } + + [Test] + public void TestMassAdd() + { + AddStep("add 1000 scores at once", () => + { + for (int i = 0; i < 1000; i++) + { + var ev = new NewScoreEvent(1, new APIUser + { + Id = 2, + Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, RNG.Next(1_000_000), null); + + feed.AddNewScore(ev); + } + }); + } } } diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs index 24e530223e36..c23deec8ac91 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs @@ -22,6 +22,8 @@ public partial class DailyChallengeEventFeed : CompositeDrawable public Action? PresentScore { get; init; } + private readonly Queue newScores = new Queue(); + [BackgroundDependencyLoader] private void load() { @@ -47,19 +49,27 @@ private void load() public void AddNewScore(NewScoreEvent newScoreEvent) { - var row = new NewScoreEventRow(newScoreEvent) - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - PresentScore = PresentScore, - }; - flow.Add(row); + newScores.Enqueue(newScoreEvent); + + // ensure things don't get too out-of-hand. + if (newScores.Count > 25) + newScores.Dequeue(); } protected override void Update() { base.Update(); + while (newScores.TryDequeue(out var newScore)) + { + flow.Add(new NewScoreEventRow(newScore) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + PresentScore = PresentScore, + }); + } + for (int i = 0; i < flow.Count; ++i) { var row = flow[i]; From e1ccf688019cd871e02aed858e07e0439243879d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 25 Jul 2024 08:51:48 +0300 Subject: [PATCH 141/266] Revert "Add failing test case" This reverts commit 0182f3d7c3e52fd63f9267148498ee069d5644f0. --- .../DailyChallenge/TestSceneDailyChallenge.cs | 29 ++++--------------- 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index 0e0927a678be..cd09a1d20fdf 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -4,12 +4,9 @@ using System; using System.Linq; using NUnit.Framework; -using osu.Framework.Testing; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Screens.Ranking; -using osu.Game.Screens.SelectV2.Leaderboards; using osu.Game.Tests.Resources; using osu.Game.Tests.Visual.OnlinePlay; @@ -17,14 +14,10 @@ namespace osu.Game.Tests.Visual.DailyChallenge { public partial class TestSceneDailyChallenge : OnlinePlayTestScene { - [SetUpSteps] - public override void SetUpSteps() + [Test] + public void TestDailyChallenge() { - base.SetUpSteps(); - - Room room = null!; - - AddStep("add room", () => API.Perform(new CreateRoomRequest(room = new Room + var room = new Room { RoomID = { Value = 1234 }, Name = { Value = "Daily Challenge: June 4, 2024" }, @@ -38,22 +31,10 @@ public override void SetUpSteps() }, EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, Category = { Value = RoomCategory.DailyChallenge } - }))); + }; + AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room))); } - - [Test] - public void TestDailyChallenge() - { - } - - [Test] - public void TestScoreNavigation() - { - AddStep("click on score", () => this.ChildrenOfType().First().TriggerClick()); - AddUntilStep("wait for load", () => Stack.CurrentScreen is ResultsScreen results && results.IsLoaded); - AddAssert("replay download button exists", () => this.ChildrenOfType().Count(), () => Is.EqualTo(1)); - } } } From 9d5fbb8b4fa1739dd07b16eab84663a826eacc95 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 25 Jul 2024 07:59:33 +0300 Subject: [PATCH 142/266] Fix target score selection abruptly discarded after opening results screen --- .../OnlinePlay/Playlists/PlaylistItemScoreResultsScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemScoreResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemScoreResultsScreen.cs index 831b6538a730..32be7f21b0cf 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemScoreResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemScoreResultsScreen.cs @@ -29,7 +29,7 @@ protected override ScoreInfo[] PerformSuccessCallback(Action SelectedScore.Value = scoreInfos.SingleOrDefault(score => score.OnlineID == scoreId)); + Schedule(() => SelectedScore.Value ??= scoreInfos.SingleOrDefault(score => score.OnlineID == scoreId)); return scoreInfos; } From f3dd1facf15935fcbd93b1b5383e5a69997170b4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 25 Jul 2024 08:38:20 +0300 Subject: [PATCH 143/266] Add failing test case --- .../Visual/Playlists/TestScenePlaylistsResultsScreen.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index a52d29a12074..7527647b9c9d 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -135,7 +135,7 @@ public void TestFetchWhenScrolledToTheRight() AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible); waitForDisplay(); - AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() >= beforePanelCount + scores_per_result); + AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() == beforePanelCount + scores_per_result); AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden); } } @@ -156,7 +156,7 @@ public void TestNoMoreScoresToTheRight() AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible); waitForDisplay(); - AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() >= beforePanelCount + scores_per_result); + AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() == beforePanelCount + scores_per_result); AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden); AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType().Count()); @@ -191,7 +191,7 @@ public void TestFetchWhenScrolledToTheLeft() AddAssert("left loading spinner shown", () => resultsScreen.LeftSpinner.State.Value == Visibility.Visible); waitForDisplay(); - AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() >= beforePanelCount + scores_per_result); + AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() == beforePanelCount + scores_per_result); AddAssert("left loading spinner hidden", () => resultsScreen.LeftSpinner.State.Value == Visibility.Hidden); } } From 615f07d54cdb6db9ae7c58fddbb48c1ff3ed63ae Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 25 Jul 2024 09:09:53 +0300 Subject: [PATCH 144/266] Fix results screen fetching more scores twice --- osu.Game/Screens/Ranking/ResultsScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 44b270db5392..4fdfc2beb8ef 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -267,7 +267,8 @@ private void fetchScoresCallback(IEnumerable scores) => Schedule(() = foreach (var s in scores) addScore(s); - lastFetchCompleted = true; + // allow a frame for scroll container to adjust its dimensions with the added scores before fetching again. + Schedule(() => lastFetchCompleted = true); if (ScorePanelList.IsEmpty) { From 8d89557ab88b0bb0508626d91fd69276b0ef0eff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 25 Jul 2024 11:11:54 +0200 Subject: [PATCH 145/266] Fix not being able to send chat reports on daily challenge screen Something something some people cannot be trusted with a textbox. --- .../DailyChallenge/DailyChallenge.cs | 261 +++++++++--------- 1 file changed, 133 insertions(+), 128 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index aab04582754c..235361dfaa2f 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -13,6 +13,7 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Logging; using osu.Framework.Screens; @@ -126,169 +127,173 @@ private void load(AudioManager audio) RelativeSizeAxes = Axes.Both, }, new Header(ButtonSystemStrings.DailyChallenge.ToSentence(), null), - new GridContainer + new PopoverContainer { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding + Child = new GridContainer { - Horizontal = WaveOverlayContainer.WIDTH_PADDING, - Top = Header.HEIGHT, - }, - RowDimensions = - [ - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Absolute, 10), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 30), - new Dimension(GridSizeMode.Absolute, 50) - ], - Content = new[] - { - new Drawable[] + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { - new DrawableRoomPlaylistItem(playlistItem) - { - RelativeSizeAxes = Axes.X, - AllowReordering = false, - Scale = new Vector2(1.4f), - Width = 1 / 1.4f, - } + Horizontal = WaveOverlayContainer.WIDTH_PADDING, + Top = Header.HEIGHT, }, - null, + RowDimensions = [ - new Container + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 30), + new Dimension(GridSizeMode.Absolute, 50) + ], + Content = new[] + { + new Drawable[] { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 10, - Children = new Drawable[] + new DrawableRoomPlaylistItem(playlistItem) { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background4, - }, - new GridContainer + RelativeSizeAxes = Axes.X, + AllowReordering = false, + Scale = new Vector2(1.4f), + Width = 1 / 1.4f, + } + }, + null, + [ + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(10), - ColumnDimensions = - [ - new Dimension(), - new Dimension(GridSizeMode.Absolute, 10), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 10), - new Dimension() - ], - Content = new[] + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4, + }, + new GridContainer { - new Drawable?[] + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + ColumnDimensions = + [ + new Dimension(), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension() + ], + Content = new[] { - new GridContainer + new Drawable?[] { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RowDimensions = - [ - new Dimension(), - new Dimension() - ], - Content = new[] + new GridContainer { - new Drawable[] + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RowDimensions = + [ + new Dimension(), + new Dimension() + ], + Content = new[] { - new DailyChallengeCarousel + new Drawable[] { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new Drawable[] + new DailyChallengeCarousel { - new DailyChallengeTimeRemainingRing(), - breakdown = new DailyChallengeScoreBreakdown(), + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new DailyChallengeTimeRemainingRing(), + breakdown = new DailyChallengeScoreBreakdown(), + } } - } + }, + [ + feed = new DailyChallengeEventFeed + { + RelativeSizeAxes = Axes.Both, + PresentScore = presentScore + } + ], }, - [ - feed = new DailyChallengeEventFeed - { - RelativeSizeAxes = Axes.Both, - PresentScore = presentScore - } - ], }, - }, - null, - // Middle column (leaderboard) - leaderboard = new DailyChallengeLeaderboard(room, playlistItem) - { - RelativeSizeAxes = Axes.Both, - PresentScore = presentScore, - }, - // Spacer - null, - // Main right column - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] + null, + // Middle column (leaderboard) + leaderboard = new DailyChallengeLeaderboard(room, playlistItem) { - new Drawable[] + RelativeSizeAxes = Axes.Both, + PresentScore = presentScore, + }, + // Spacer + null, + // Main right column + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] { - new SectionHeader("Chat") + new Drawable[] + { + new SectionHeader("Chat") + }, + [new MatchChatDisplay(room) { RelativeSizeAxes = Axes.Both }] }, - [new MatchChatDisplay(room) { RelativeSizeAxes = Axes.Both }] + RowDimensions = + [ + new Dimension(GridSizeMode.AutoSize), + new Dimension() + ] }, - RowDimensions = - [ - new Dimension(GridSizeMode.AutoSize), - new Dimension() - ] - }, + } } } } } - } - ], - null, - [ - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding + ], + null, + [ + new Container { - Horizontal = -WaveOverlayContainer.WIDTH_PADDING, - }, - Children = new Drawable[] - { - new Box + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background5, + Horizontal = -WaveOverlayContainer.WIDTH_PADDING, }, - footerButtons = new FillFlowContainer + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Padding = new MarginPadding(5), - Spacing = new Vector2(10), - Children = new Drawable[] + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5, + }, + footerButtons = new FillFlowContainer { - new PlaylistsReadyButton + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding(5), + Spacing = new Vector2(10), + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Size = new Vector2(250, 1), - Action = startPlay + new PlaylistsReadyButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Size = new Vector2(250, 1), + Action = startPlay + } } - } - }, + }, + } } - } - ], + ], + } } } } From 8dbd4d70ff36734c1af64471eed86932dfb55ea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 25 Jul 2024 11:42:56 +0200 Subject: [PATCH 146/266] Fix crash when toggling extended statistics visibility during results load Closes https://github.com/ppy/osu/issues/29066. Initially I fixed this at where the assert is right now: https://github.com/ppy/osu/blob/9790c5a574b782c41c8c6da99ad8c42dfadc9de8/osu.Game/Screens/Ranking/ResultsScreen.cs#L333 but because of the weird way that visible state management is done in this screen that made it possible for the extended statistics to be visible *behind* the score panels, without the score panels making way for it. So this is in a way safer, because it prevents the visibility state of the extended statistics from changing in the first place if there is no score selected (yet). This can be also seen in playlists, at least. --- osu.Game/Screens/Ranking/ResultsScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 44b270db5392..283a74e35f25 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -398,7 +398,8 @@ public bool OnPressed(KeyBindingPressEvent e) break; case GlobalAction.Select: - StatisticsPanel.ToggleVisibility(); + if (SelectedScore.Value != null) + StatisticsPanel.ToggleVisibility(); return true; } From e564b1dc9e470cc77af1ba38eacb8b3c08966409 Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Thu, 25 Jul 2024 18:23:01 +0800 Subject: [PATCH 147/266] Fix cursor trail alignment issue with UI panels - Convert cursor trail coordinates to local space before storing. - Apply necessary transformations to align with other UI elements. - Ensure cursor trail remains connected during UI panel movements. --- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 30a77db5a106..49e4ee18c175 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -193,7 +193,7 @@ protected void AddTrail(Vector2 position) private void addPart(Vector2 screenSpacePosition) { - parts[currentIndex].Position = screenSpacePosition; + parts[currentIndex].Position = ToLocalSpace(screenSpacePosition); parts[currentIndex].Time = time + 1; ++parts[currentIndex].InvalidationID; @@ -285,9 +285,11 @@ protected override void Draw(IRenderer renderer) if (time - part.Time >= 1) continue; + Vector2 screenSpacePos = Source.ToScreenSpace(part.Position); + vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X - size.X * originPosition.X, part.Position.Y + size.Y * (1 - originPosition.Y)), + Position = new Vector2(screenSpacePos.X - size.X * originPosition.X, screenSpacePos.Y + size.Y * (1 - originPosition.Y)), TexturePosition = textureRect.BottomLeft, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.BottomLeft.Linear, @@ -296,7 +298,7 @@ protected override void Draw(IRenderer renderer) vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X + size.X * (1 - originPosition.X), part.Position.Y + size.Y * (1 - originPosition.Y)), + Position = new Vector2(screenSpacePos.X + size.X * (1 - originPosition.X), screenSpacePos.Y + size.Y * (1 - originPosition.Y)), TexturePosition = textureRect.BottomRight, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.BottomRight.Linear, @@ -305,7 +307,7 @@ protected override void Draw(IRenderer renderer) vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X + size.X * (1 - originPosition.X), part.Position.Y - size.Y * originPosition.Y), + Position = new Vector2(screenSpacePos.X + size.X * (1 - originPosition.X), screenSpacePos.Y - size.Y * originPosition.Y), TexturePosition = textureRect.TopRight, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.TopRight.Linear, @@ -314,7 +316,7 @@ protected override void Draw(IRenderer renderer) vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X - size.X * originPosition.X, part.Position.Y - size.Y * originPosition.Y), + Position = new Vector2(screenSpacePos.X - size.X * originPosition.X, screenSpacePos.Y - size.Y * originPosition.Y), TexturePosition = textureRect.TopLeft, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.TopLeft.Linear, From 3bb30d7ff93edbf655e67dd6c25edb0516d300c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 25 Jul 2024 13:06:18 +0200 Subject: [PATCH 148/266] Fix several missing properties on `MultiplayerScore` You wouldn't think this would be an actual thing that can happen to us, but it is. The most important one by far is `MaximumStatistics`; that is the root cause behind why stuff like spinner ticks or slider tails wasn't showing. On a better day we should probably do cleanup to unify these models better, but today is not that day. --- osu.Game/Online/Rooms/MultiplayerScore.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game/Online/Rooms/MultiplayerScore.cs b/osu.Game/Online/Rooms/MultiplayerScore.cs index f1b9584d5754..faa66c571da6 100644 --- a/osu.Game/Online/Rooms/MultiplayerScore.cs +++ b/osu.Game/Online/Rooms/MultiplayerScore.cs @@ -46,6 +46,9 @@ public class MultiplayerScore [JsonProperty("statistics")] public Dictionary Statistics = new Dictionary(); + [JsonProperty("maximum_statistics")] + public Dictionary MaximumStatistics = new Dictionary(); + [JsonProperty("passed")] public bool Passed { get; set; } @@ -58,9 +61,15 @@ public class MultiplayerScore [JsonProperty("position")] public int? Position { get; set; } + [JsonProperty("pp")] + public double? PP { get; set; } + [JsonProperty("has_replay")] public bool HasReplay { get; set; } + [JsonProperty("ranked")] + public bool Ranked { get; set; } + /// /// Any scores in the room around this score. /// @@ -83,13 +92,17 @@ public ScoreInfo CreateScoreInfo(ScoreManager scoreManager, RulesetStore ruleset MaxCombo = MaxCombo, BeatmapInfo = beatmap, Ruleset = rulesets.GetRuleset(playlistItem.RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {playlistItem.RulesetID} not found locally"), + Passed = Passed, Statistics = Statistics, + MaximumStatistics = MaximumStatistics, User = User, Accuracy = Accuracy, Date = EndedAt, HasOnlineReplay = HasReplay, Rank = Rank, Mods = Mods?.Select(m => m.ToMod(rulesetInstance)).ToArray() ?? Array.Empty(), + PP = PP, + Ranked = Ranked, Position = Position, }; From 3e8917cadb46c051bc2b399fababf491cd08c298 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 26 Jul 2024 05:08:13 +0300 Subject: [PATCH 149/266] Add test case against resetting score in download button --- .../Visual/Gameplay/TestSceneReplayDownloadButton.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 5b32f380b9ca..061e8ea7e1aa 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -117,6 +117,9 @@ public void TestButtonWithReplayStartsDownload() AddAssert("state entered downloading", () => downloadStarted); AddUntilStep("state left downloading", () => downloadFinished); + + AddStep("change score to null", () => downloadButton.Score.Value = null); + AddUntilStep("state changed to unknown", () => downloadButton.State.Value, () => Is.EqualTo(DownloadState.Unknown)); } [Test] From c558dfdf138523482dfed8640d5f276e648af970 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 26 Jul 2024 05:11:54 +0300 Subject: [PATCH 150/266] Reset download state when score is changed --- osu.Game/Screens/Ranking/ReplayDownloadButton.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs index aac29ad26965..5e2161c251b3 100644 --- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs @@ -88,6 +88,8 @@ private void load(OsuGame? game, ScoreModelDownloader scoreDownloader) State.ValueChanged -= exportWhenReady; downloadTracker?.RemoveAndDisposeImmediately(); + downloadTracker = null; + State.SetDefault(); if (score.NewValue != null) { From c17cabd98136f94ffd3b3854cc9c85d191b47ac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jul 2024 07:44:02 +0200 Subject: [PATCH 151/266] Adjust alpha for rows for better visibility --- .../OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs index c23deec8ac91..160ad83c8a68 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -74,7 +75,7 @@ protected override void Update() { var row = flow[i]; - row.Alpha = Math.Max(0, (row.Y + flow.DrawHeight) / flow.DrawHeight); + row.Alpha = Interpolation.ValueAt(Math.Clamp(row.Y + flow.DrawHeight, 0, flow.DrawHeight), 0f, 1f, 0, flow.DrawHeight, Easing.Out); if (row.Y < -flow.DrawHeight) { From 662e9eab8c77bd49be3d557d437ac497464a8d31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jul 2024 08:07:27 +0200 Subject: [PATCH 152/266] Don't force exit to main menu when presenting scores from within online screens Struck me as weird when reviewing https://github.com/ppy/osu/pull/29057. Like sure, that PR adds the replay button, but it's a bit terrible that clicking the button quits the daily challenge screen and you're back at main menu when done watching...? Also extended to cover playlists and multiplayer, which have the same issue. --- osu.Game/OsuGame.cs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 388a98d94703..2195576be11b 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -63,6 +63,8 @@ using osu.Game.Screens.Edit; using osu.Game.Screens.Footer; using osu.Game.Screens.Menu; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.DailyChallenge; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; @@ -749,23 +751,34 @@ public void PresentScore(IScoreInfo score, ScorePresentType presentType = ScoreP return; } - // This should be able to be performed from song select, but that is disabled for now + // This should be able to be performed from song select always, but that is disabled for now // due to the weird decoupled ruleset logic (which can cause a crash in certain filter scenarios). // // As a special case, if the beatmap and ruleset already match, allow immediately displaying the score from song select. // This is guaranteed to not crash, and feels better from a user's perspective (ie. if they are clicking a score in the // song select leaderboard). + // Similar exemptions are made here for online flows where there are good chances that beatmap and ruleset match + // (playlists / multiplayer / daily challenge). IEnumerable validScreens = Beatmap.Value.BeatmapInfo.Equals(databasedBeatmap) && Ruleset.Value.Equals(databasedScore.ScoreInfo.Ruleset) - ? new[] { typeof(SongSelect) } + ? new[] { typeof(SongSelect), typeof(OnlinePlayScreen), typeof(DailyChallenge) } : Array.Empty(); PerformFromScreen(screen => { Logger.Log($"{nameof(PresentScore)} updating beatmap ({databasedBeatmap}) and ruleset ({databasedScore.ScoreInfo.Ruleset}) to match score"); - Ruleset.Value = databasedScore.ScoreInfo.Ruleset; - Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); + // some screens (mostly online) disable the ruleset/beatmap bindable. + // attempting to set the ruleset/beatmap in that state will crash. + // however, the `validScreens` pre-check above should ensure that we actually never come from one of those screens + // while simultaneously having mismatched ruleset/beatmap. + // therefore this is just a safety against touching the possibly-disabled bindables if we don't actually have to touch them. + // if it ever fails, then this probably *should* crash anyhow (so that we can fix it). + if (!Ruleset.Value.Equals(databasedScore.ScoreInfo.Ruleset)) + Ruleset.Value = databasedScore.ScoreInfo.Ruleset; + + if (!Beatmap.Value.BeatmapInfo.Equals(databasedBeatmap)) + Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); switch (presentType) { From 174dc91f4ba0f34f9bfe5772157896eb75affb2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jul 2024 09:49:36 +0200 Subject: [PATCH 153/266] Implement component for displaying running totals in daily challenge Total pass count and cumulative total score, to be more precise. --- .../TestSceneDailyChallengeTotalsDisplay.cs | 86 ++++++++++++ .../DailyChallengeTotalsDisplay.cs | 126 ++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTotalsDisplay.cs create mode 100644 osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTotalsDisplay.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTotalsDisplay.cs new file mode 100644 index 000000000000..ba5a0989d493 --- /dev/null +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTotalsDisplay.cs @@ -0,0 +1,86 @@ +// 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.Extensions.ObjectExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Screens.OnlinePlay.DailyChallenge; +using osu.Game.Screens.OnlinePlay.DailyChallenge.Events; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Visual.DailyChallenge +{ + public partial class TestSceneDailyChallengeTotalsDisplay : OsuTestScene + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); + + [Test] + public void TestBasicAppearance() + { + DailyChallengeTotalsDisplay totals = null!; + + AddStep("create content", () => Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4, + }, + totals = new DailyChallengeTotalsDisplay + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }); + AddSliderStep("adjust width", 0.1f, 1, 1, width => + { + if (totals.IsNotNull()) + totals.Width = width; + }); + AddSliderStep("adjust height", 0.1f, 1, 1, height => + { + if (totals.IsNotNull()) + totals.Height = height; + }); + + AddStep("set counts", () => totals.SetInitialCounts(totalPassCount: 9650, cumulativeTotalScore: 10_000_000_000)); + + AddStep("add normal score", () => + { + var ev = new NewScoreEvent(1, new APIUser + { + Id = 2, + Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, RNG.Next(1_000_000), null); + + totals.AddNewScore(ev); + }); + + AddStep("spam scores", () => + { + for (int i = 0; i < 1000; ++i) + { + var ev = new NewScoreEvent(1, new APIUser + { + Id = 2, + Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, RNG.Next(1_000_000), RNG.Next(11, 1000)); + + var testScore = TestResources.CreateTestScoreInfo(); + testScore.TotalScore = RNG.Next(1_000_000); + + totals.AddNewScore(ev); + } + }); + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs new file mode 100644 index 000000000000..464022639f5f --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs @@ -0,0 +1,126 @@ +// 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.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.OnlinePlay.DailyChallenge.Events; +using osuTK; + +namespace osu.Game.Screens.OnlinePlay.DailyChallenge +{ + public partial class DailyChallengeTotalsDisplay : CompositeDrawable + { + private Container passCountContainer = null!; + private TotalRollingCounter passCounter = null!; + private Container totalScoreContainer = null!; + private TotalRollingCounter totalScoreCounter = null!; + + private long totalPassCountInstantaneous; + private long cumulativeTotalScoreInstantaneous; + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = + [ + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + ], + Content = new[] + { + new Drawable[] + { + new SectionHeader("Total pass count") + }, + new Drawable[] + { + passCountContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = passCounter = new TotalRollingCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + } + }, + new Drawable[] + { + new SectionHeader("Cumulative total score") + }, + new Drawable[] + { + totalScoreContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = totalScoreCounter = new TotalRollingCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + } + }, + } + }; + } + + public void SetInitialCounts(long totalPassCount, long cumulativeTotalScore) + { + totalPassCountInstantaneous = totalPassCount; + cumulativeTotalScoreInstantaneous = cumulativeTotalScore; + } + + public void AddNewScore(NewScoreEvent ev) + { + totalPassCountInstantaneous += 1; + cumulativeTotalScoreInstantaneous += ev.TotalScore; + } + + protected override void Update() + { + base.Update(); + + passCounter.Current.Value = totalPassCountInstantaneous; + totalScoreCounter.Current.Value = cumulativeTotalScoreInstantaneous; + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + var totalPassCountProportionOfParent = Vector2.Divide(passCountContainer.DrawSize, passCounter.DrawSize); + passCounter.Scale = new Vector2(Math.Min(Math.Min(totalPassCountProportionOfParent.X, totalPassCountProportionOfParent.Y) * 0.8f, 1)); + + var totalScoreTextProportionOfParent = Vector2.Divide(totalScoreContainer.DrawSize, totalScoreCounter.DrawSize); + totalScoreCounter.Scale = new Vector2(Math.Min(Math.Min(totalScoreTextProportionOfParent.X, totalScoreTextProportionOfParent.Y) * 0.8f, 1)); + } + + private partial class TotalRollingCounter : RollingCounter + { + protected override double RollingDuration => 400; + + protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText + { + Font = OsuFont.Default.With(size: 80f, fixedWidth: true), + }; + + protected override LocalisableString FormatCount(long count) => count.ToLocalisableString(@"N0"); + } + } +} From a8851950bccaf8abbe11f05ce1329ebde256ceb6 Mon Sep 17 00:00:00 2001 From: Cameron Brown Date: Fri, 26 Jul 2024 18:10:11 +1000 Subject: [PATCH 154/266] Update the beatmap of Daily Challenge's mods overlay when beatmap is set - #29094 --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 235361dfaa2f..057bbd6be4b0 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -379,6 +379,9 @@ private void trySetDailyChallengeBeatmap() var beatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == playlistItem.Beatmap.OnlineID); Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmap); // this will gracefully fall back to dummy beatmap if missing locally. Ruleset.Value = rulesets.GetRuleset(playlistItem.RulesetID); + + userModsSelectOverlay.Beatmap.Value = Beatmap.Value; + applyLoopingToTrack(); } From 17f00ec0a6e87e184ae895df4ec05f85c11f6cec Mon Sep 17 00:00:00 2001 From: Cameron Brown Date: Fri, 26 Jul 2024 18:29:50 +1000 Subject: [PATCH 155/266] Bind the mod select overlay's Beatmap to OsuScreen.Beatmap in constructor Suggested by @bdach! --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 057bbd6be4b0..a4b251bf5bc5 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -301,6 +301,7 @@ [new MatchChatDisplay(room) { RelativeSizeAxes = Axes.Both }] LoadComponent(userModsSelectOverlay = new RoomModSelectOverlay { + Beatmap = { BindTarget = Beatmap }, SelectedMods = { BindTarget = userMods }, IsValidMod = _ => false }); @@ -380,8 +381,6 @@ private void trySetDailyChallengeBeatmap() Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmap); // this will gracefully fall back to dummy beatmap if missing locally. Ruleset.Value = rulesets.GetRuleset(playlistItem.RulesetID); - userModsSelectOverlay.Beatmap.Value = Beatmap.Value; - applyLoopingToTrack(); } From f9cfc7d96cfbf42a9e40172b55c14ef92a2e9827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jul 2024 10:53:12 +0200 Subject: [PATCH 156/266] Fix preview tracks not stopping playback when suspending/exiting daily challenge screen Closes https://github.com/ppy/osu/issues/29083. --- .../Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 235361dfaa2f..6ff0eb24522e 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -17,6 +17,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Logging; using osu.Framework.Screens; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics.Containers; @@ -40,7 +41,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { - public partial class DailyChallenge : OsuScreen + [Cached(typeof(IPreviewTrackOwner))] + public partial class DailyChallenge : OsuScreen, IPreviewTrackOwner { private readonly Room room; private readonly PlaylistItem playlistItem; @@ -91,6 +93,9 @@ public partial class DailyChallenge : OsuScreen [Resolved] protected IAPIProvider API { get; private set; } = null!; + [Resolved] + private PreviewTrackManager previewTrackManager { get; set; } = null!; + public override bool DisallowExternalBeatmapRulesetChanges => true; public DailyChallenge(Room room) @@ -441,6 +446,7 @@ public override void OnSuspending(ScreenTransitionEvent e) userModsSelectOverlay.Hide(); cancelTrackLooping(); + previewTrackManager.StopAnyPlaying(this); } public override bool OnExiting(ScreenExitEvent e) @@ -448,6 +454,7 @@ public override bool OnExiting(ScreenExitEvent e) waves.Hide(); userModsSelectOverlay.Hide(); cancelTrackLooping(); + previewTrackManager.StopAnyPlaying(this); this.Delay(WaveContainer.DISAPPEAR_DURATION).FadeOut(); roomManager.PartRoom(); From 1abcf16231eebe0d24dd0b7dab1a9ebea86ad7f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jul 2024 11:50:43 +0200 Subject: [PATCH 157/266] Fix daily challenge screen not applying track adjustments from mods Closes https://github.com/ppy/osu/issues/29093. --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index a4b251bf5bc5..56398090d04d 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -93,6 +93,8 @@ public partial class DailyChallenge : OsuScreen public override bool DisallowExternalBeatmapRulesetChanges => true; + public override bool? ApplyModTrackAdjustments => true; + public DailyChallenge(Room room) { this.room = room; From 1ad0b31217d8df8e29ed02165aa83bd3a665a788 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jul 2024 19:04:04 +0900 Subject: [PATCH 158/266] Add required pieces to `MultiplayerPlaylistItemStats` for total score tracking --- osu.Game/Online/Metadata/MultiplayerPlaylistItemStats.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Online/Metadata/MultiplayerPlaylistItemStats.cs b/osu.Game/Online/Metadata/MultiplayerPlaylistItemStats.cs index d13705bf5bed..6e5024255626 100644 --- a/osu.Game/Online/Metadata/MultiplayerPlaylistItemStats.cs +++ b/osu.Game/Online/Metadata/MultiplayerPlaylistItemStats.cs @@ -25,5 +25,14 @@ public class MultiplayerPlaylistItemStats /// [Key(1)] public long[] TotalScoreDistribution { get; set; } = new long[TOTAL_SCORE_DISTRIBUTION_BINS]; + + /// + /// The cumulative total of all passing scores (across all users) in the playlist so far. + /// + [Key(2)] + public long TotalPlaylistScore { get; set; } + + [Key(3)] + public ulong LastProcessedScoreID { get; set; } } } From 2e37f3b5de2be1e94bc3a29f4f608f021aeadb2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jul 2024 12:34:23 +0200 Subject: [PATCH 159/266] Hook up score totals display to daily challenge screen --- .../Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 235361dfaa2f..17241a5fd65e 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -59,6 +59,7 @@ public partial class DailyChallenge : OsuScreen private IDisposable? userModsSelectOverlayRegistration; private DailyChallengeScoreBreakdown breakdown = null!; + private DailyChallengeTotalsDisplay totals = null!; private DailyChallengeEventFeed feed = null!; [Cached] @@ -211,6 +212,7 @@ private void load(AudioManager audio) { new DailyChallengeTimeRemainingRing(), breakdown = new DailyChallengeScoreBreakdown(), + totals = new DailyChallengeTotalsDisplay(), } } }, @@ -351,6 +353,7 @@ private void onRoomScoreSet(MultiplayerRoomScoreSetEvent e) Schedule(() => { breakdown.AddNewScore(ev); + totals.AddNewScore(ev); feed.AddNewScore(ev); if (e.NewRank <= 50) @@ -421,7 +424,11 @@ public override void OnEntering(ScreenTransitionEvent e) var itemStats = stats.SingleOrDefault(item => item.PlaylistItemID == playlistItem.ID); if (itemStats == null) return; - Schedule(() => breakdown.SetInitialCounts(itemStats.TotalScoreDistribution)); + Schedule(() => + { + breakdown.SetInitialCounts(itemStats.TotalScoreDistribution); + totals.SetInitialCounts(itemStats.TotalScoreDistribution.Sum(c => c), itemStats.TotalPlaylistScore); + }); }); beatmapAvailabilityTracker.SelectedItem.Value = playlistItem; From 19affa7062bfd7f82e1a33a61e6427a74a1f2463 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jul 2024 20:42:01 +0900 Subject: [PATCH 160/266] Rename new property to match true usage (per item) Also document a bit more. --- osu.Game/Online/Metadata/MultiplayerPlaylistItemStats.cs | 7 +++++-- .../Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Metadata/MultiplayerPlaylistItemStats.cs b/osu.Game/Online/Metadata/MultiplayerPlaylistItemStats.cs index 6e5024255626..19a2bde497a5 100644 --- a/osu.Game/Online/Metadata/MultiplayerPlaylistItemStats.cs +++ b/osu.Game/Online/Metadata/MultiplayerPlaylistItemStats.cs @@ -27,11 +27,14 @@ public class MultiplayerPlaylistItemStats public long[] TotalScoreDistribution { get; set; } = new long[TOTAL_SCORE_DISTRIBUTION_BINS]; /// - /// The cumulative total of all passing scores (across all users) in the playlist so far. + /// The cumulative total of all passing scores (across all users) for the playlist item so far. /// [Key(2)] - public long TotalPlaylistScore { get; set; } + public long CumulativeScore { get; set; } + /// + /// The last score to have been processed into provided statistics. Generally only for server-side accounting purposes. + /// [Key(3)] public ulong LastProcessedScoreID { get; set; } } diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 17241a5fd65e..ff37d7c9705c 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -427,7 +427,7 @@ public override void OnEntering(ScreenTransitionEvent e) Schedule(() => { breakdown.SetInitialCounts(itemStats.TotalScoreDistribution); - totals.SetInitialCounts(itemStats.TotalScoreDistribution.Sum(c => c), itemStats.TotalPlaylistScore); + totals.SetInitialCounts(itemStats.TotalScoreDistribution.Sum(c => c), itemStats.CumulativeScore); }); }); From 2caaebb6705bf9df5f0fd8572c1a013230cf6ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jul 2024 13:47:41 +0200 Subject: [PATCH 161/266] Add tooltip with counts to daily challenge score breakdown chart --- .../DailyChallengeScoreBreakdown.cs | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs index 0c7202f7cf20..45bda9f18502 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Game.Graphics; @@ -44,23 +45,7 @@ private void load() for (int i = 0; i < bin_count; ++i) { - LocalisableString? label = null; - - switch (i) - { - case 2: - case 4: - case 6: - case 8: - label = @$"{100 * i}k"; - break; - - case 10: - label = @"1M"; - break; - } - - barsContainer.Add(new Bar(label) + barsContainer.Add(new Bar(100_000 * i, 100_000 * (i + 1) - 1) { Width = 1f / bin_count, }); @@ -113,18 +98,20 @@ private void updateCounts() barsContainer[i].UpdateCounts(bins[i], max); } - private partial class Bar : CompositeDrawable + private partial class Bar : CompositeDrawable, IHasTooltip { - private readonly LocalisableString? label; + private readonly int binStart; + private readonly int binEnd; private long count; private long max; public Container CircularBar { get; private set; } = null!; - public Bar(LocalisableString? label = null) + public Bar(int binStart, int binEnd) { - this.label = label; + this.binStart = binStart; + this.binEnd = binEnd; } [BackgroundDependencyLoader] @@ -159,13 +146,29 @@ private void load(OverlayColourProvider colourProvider) } }); + string? label = null; + + switch (binStart) + { + case 200_000: + case 400_000: + case 600_000: + case 800_000: + label = @$"{binStart / 1000}k"; + break; + + case 1_000_000: + label = @"1M"; + break; + } + if (label != null) { AddInternal(new OsuSpriteText { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomCentre, - Text = label.Value, + Text = label, Colour = colourProvider.Content2, }); } @@ -189,6 +192,8 @@ public void UpdateCounts(long newCount, long newMax) if (isIncrement) CircularBar.FlashColour(Colour4.White, 600, Easing.OutQuint); } + + public LocalisableString TooltipText => LocalisableString.Format("{0:N0} passes in {1:N0} - {2:N0} range", count, binStart, binEnd); } } } From fc0ade2c61405b80b3c15acc042d678fa545d720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jul 2024 14:31:21 +0200 Subject: [PATCH 162/266] Highlight where local user's best is on the breakdown --- .../TestSceneDailyChallengeScoreBreakdown.cs | 3 + .../DailyChallenge/DailyChallenge.cs | 2 + .../DailyChallengeLeaderboard.cs | 7 +- .../DailyChallengeScoreBreakdown.cs | 101 ++++++++++++++---- 4 files changed, 92 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs index 81ec95d8d2af..631aafb58f56 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Utils; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.DailyChallenge; using osu.Game.Screens.OnlinePlay.DailyChallenge.Events; @@ -61,6 +62,8 @@ public void TestBasicAppearance() breakdown.AddNewScore(ev); }); + AddStep("set user score", () => breakdown.UserBestScore.Value = new MultiplayerScore { TotalScore = RNG.Next(1_000_000) }); + AddStep("unset user score", () => breakdown.UserBestScore.Value = null); } } } diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index a4b251bf5bc5..44c47e18d643 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -324,6 +324,8 @@ [new MatchChatDisplay(room) { RelativeSizeAxes = Axes.Both }] } metadataClient.MultiplayerRoomScoreSet += onRoomScoreSet; + + ((IBindable)breakdown.UserBestScore).BindTo(leaderboard.UserBestScore); } private void presentScore(long id) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs index 4c4622bba33e..d87a34405d3a 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs @@ -22,6 +22,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { public partial class DailyChallengeLeaderboard : CompositeDrawable { + public IBindable UserBestScore => userBestScore; + private Bindable userBestScore = new Bindable(); + public Action? PresentScore { get; init; } private readonly Room room; @@ -130,7 +133,9 @@ public void RefetchScores() request.Success += req => Schedule(() => { var best = req.Scores.Select(s => s.CreateScoreInfo(scoreManager, rulesets, playlistItem, beatmap.Value.BeatmapInfo)).ToArray(); - var userBest = req.UserScore?.CreateScoreInfo(scoreManager, rulesets, playlistItem, beatmap.Value.BeatmapInfo); + + userBestScore.Value = req.UserScore; + var userBest = userBestScore.Value?.CreateScoreInfo(scoreManager, rulesets, playlistItem, beatmap.Value.BeatmapInfo); cancellationTokenSource?.Cancel(); cancellationTokenSource = null; diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs index 45bda9f18502..fce4f0452bdb 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs @@ -4,6 +4,7 @@ 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.Cursor; @@ -13,6 +14,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Metadata; +using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.DailyChallenge.Events; using osuTK; @@ -21,6 +23,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { public partial class DailyChallengeScoreBreakdown : CompositeDrawable { + public Bindable UserBestScore { get; } = new Bindable(); + private FillFlowContainer barsContainer = null!; private const int bin_count = MultiplayerPlaylistItemStats.TOTAL_SCORE_DISTRIBUTION_BINS; @@ -52,6 +56,17 @@ private void load() } } + protected override void LoadComplete() + { + base.LoadComplete(); + + UserBestScore.BindValueChanged(_ => + { + foreach (var bar in barsContainer) + bar.ContainsLocalUser.Value = UserBestScore.Value is not null && bar.BinStart <= UserBestScore.Value.TotalScore && UserBestScore.Value.TotalScore <= bar.BinEnd; + }); + } + public void AddNewScore(NewScoreEvent newScoreEvent) { int targetBin = (int)Math.Clamp(Math.Floor((float)newScoreEvent.TotalScore / 100000), 0, bin_count - 1); @@ -100,20 +115,32 @@ private void updateCounts() private partial class Bar : CompositeDrawable, IHasTooltip { - private readonly int binStart; - private readonly int binEnd; + public BindableBool ContainsLocalUser { get; } = new BindableBool(); + + public readonly int BinStart; + public readonly int BinEnd; private long count; private long max; public Container CircularBar { get; private set; } = null!; + private Box fill = null!; + private Box flashLayer = null!; + private OsuSpriteText userIndicator = null!; + public Bar(int binStart, int binEnd) { - this.binStart = binStart; - this.binEnd = binEnd; + this.BinStart = binStart; + this.BinEnd = binEnd; } + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { @@ -129,32 +156,52 @@ private void load(OverlayColourProvider colourProvider) }, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Masking = true, - Child = CircularBar = new Container + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Height = 0.01f, - Masking = true, - CornerRadius = 10, - Colour = colourProvider.Highlight1, - Child = new Box + CircularBar = new Container { RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Height = 0.01f, + Masking = true, + CornerRadius = 10, + Children = new Drawable[] + { + fill = new Box + { + RelativeSizeAxes = Axes.Both, + }, + flashLayer = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + } + }, + userIndicator = new OsuSpriteText + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Colour = colours.Orange1, + Text = "You", + Font = OsuFont.Default.With(weight: FontWeight.Bold), + Alpha = 0, + RelativePositionAxes = Axes.Y, + Margin = new MarginPadding { Bottom = 5, }, } - } + }, }); string? label = null; - switch (binStart) + switch (BinStart) { case 200_000: case 400_000: case 600_000: case 800_000: - label = @$"{binStart / 1000}k"; + label = @$"{BinStart / 1000}k"; break; case 1_000_000: @@ -174,6 +221,18 @@ private void load(OverlayColourProvider colourProvider) } } + protected override void LoadComplete() + { + base.LoadComplete(); + + ContainsLocalUser.BindValueChanged(_ => + { + fill.FadeColour(ContainsLocalUser.Value ? colours.Orange1 : colourProvider.Highlight1, 300, Easing.OutQuint); + userIndicator.FadeTo(ContainsLocalUser.Value ? 1 : 0, 300, Easing.OutQuint); + }, true); + FinishTransforms(true); + } + protected override void Update() { base.Update(); @@ -188,12 +247,14 @@ public void UpdateCounts(long newCount, long newMax) count = newCount; max = newMax; - CircularBar.ResizeHeightTo(0.01f + 0.99f * count / max, 300, Easing.OutQuint); + float height = 0.01f + 0.99f * count / max; + CircularBar.ResizeHeightTo(height, 300, Easing.OutQuint); + userIndicator.MoveToY(-height, 300, Easing.OutQuint); if (isIncrement) - CircularBar.FlashColour(Colour4.White, 600, Easing.OutQuint); + flashLayer.FadeOutFromOne(600, Easing.OutQuint); } - public LocalisableString TooltipText => LocalisableString.Format("{0:N0} passes in {1:N0} - {2:N0} range", count, binStart, binEnd); + public LocalisableString TooltipText => LocalisableString.Format("{0:N0} passes in {1:N0} - {2:N0} range", count, BinStart, BinEnd); } } } From a870722ea691297c216283833215cf8316cb2f3f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jul 2024 21:43:23 +0900 Subject: [PATCH 163/266] Adjust easings and reduce character spacing slightly --- .../DailyChallengeTotalsDisplay.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs index 464022639f5f..cf8a60d4a295 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs @@ -113,11 +113,26 @@ protected override void UpdateAfterChildren() private partial class TotalRollingCounter : RollingCounter { - protected override double RollingDuration => 400; + protected override double RollingDuration => 1000; + + protected override Easing RollingEasing => Easing.OutPow10; + + protected override bool IsRollingProportional => true; + + protected override double GetProportionalDuration(long currentValue, long newValue) + { + long change = Math.Abs(newValue - currentValue); + + if (change < 10) + return 0; + + return Math.Min(6000, RollingDuration * Math.Sqrt(change) / 100); + } protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText { Font = OsuFont.Default.With(size: 80f, fixedWidth: true), + Spacing = new Vector2(-2, 0) }; protected override LocalisableString FormatCount(long count) => count.ToLocalisableString(@"N0"); From 0996f9b0b51dbc7d2308c812385e603b6a05036d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jul 2024 14:45:39 +0200 Subject: [PATCH 164/266] Fix code quality --- .../OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs | 2 +- .../OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs index d87a34405d3a..5efb656cea91 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge public partial class DailyChallengeLeaderboard : CompositeDrawable { public IBindable UserBestScore => userBestScore; - private Bindable userBestScore = new Bindable(); + private readonly Bindable userBestScore = new Bindable(); public Action? PresentScore { get; init; } diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs index fce4f0452bdb..cfec170cf663 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs @@ -131,8 +131,8 @@ private partial class Bar : CompositeDrawable, IHasTooltip public Bar(int binStart, int binEnd) { - this.BinStart = binStart; - this.BinEnd = binEnd; + BinStart = binStart; + BinEnd = binEnd; } [Resolved] From 96049807c4392ee68d85134418865cdb9946c296 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jul 2024 23:20:32 +0900 Subject: [PATCH 165/266] Adjust weight and text in event feed output Just some minor adjustments. --- .../DailyChallenge/DailyChallengeEventFeed.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs index 160ad83c8a68..044c599ae9d0 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs @@ -121,7 +121,14 @@ private void load(OsuColour colours) }, text = new LinkFlowContainer(t => { - t.Font = OsuFont.Default.With(weight: newScore.NewRank == null ? FontWeight.Medium : FontWeight.Bold); + FontWeight fontWeight = FontWeight.Medium; + + if (newScore.NewRank < 100) + fontWeight = FontWeight.Bold; + else if (newScore.NewRank < 1000) + fontWeight = FontWeight.SemiBold; + + t.Font = OsuFont.Default.With(weight: fontWeight); t.Colour = newScore.NewRank < 10 ? colours.Orange1 : Colour4.White; }) { @@ -132,8 +139,8 @@ private void load(OsuColour colours) }; text.AddUserLink(newScore.User); - text.AddText(" got "); - text.AddLink($"{newScore.TotalScore:N0} points", () => PresentScore?.Invoke(newScore.ScoreID)); + text.AddText(" scored "); + text.AddLink($"{newScore.TotalScore:N0}", () => PresentScore?.Invoke(newScore.ScoreID)); if (newScore.NewRank != null) text.AddText($" and achieved rank #{newScore.NewRank.Value:N0}"); From 0421e1e9d0bf16e6a947cc122694fff19f4d42c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jul 2024 23:21:44 +0900 Subject: [PATCH 166/266] Reduce number spacing a bit more --- .../OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs index cf8a60d4a295..e2535ed80603 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs @@ -132,7 +132,7 @@ protected override double GetProportionalDuration(long currentValue, long newVal protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText { Font = OsuFont.Default.With(size: 80f, fixedWidth: true), - Spacing = new Vector2(-2, 0) + Spacing = new Vector2(-4, 0) }; protected override LocalisableString FormatCount(long count) => count.ToLocalisableString(@"N0"); From 9323f89357f0fd070f3aa12e7559d8ee9572d925 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 27 Jul 2024 02:06:56 +0900 Subject: [PATCH 167/266] Fix "Beatmap not downloaded" tooltip hint not showing in daily challenge --- osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs | 4 ++-- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs index 813e243449e1..2e669fd1b225 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs @@ -21,8 +21,8 @@ public abstract partial class ReadyButton : RoundedButton, IHasTooltip private void load(OnlinePlayBeatmapAvailabilityTracker beatmapTracker) { availability.BindTo(beatmapTracker.Availability); - availability.BindValueChanged(_ => updateState()); + Enabled.BindValueChanged(_ => updateState(), true); } @@ -33,7 +33,7 @@ public virtual LocalisableString TooltipText { get { - if (Enabled.Value) + if (base.Enabled.Value) return string.Empty; if (availability.Value.State != DownloadState.LocallyAvailable) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs index 91a3edbea3ca..4b00678b012f 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs @@ -68,9 +68,6 @@ public override LocalisableString TooltipText { get { - if (Enabled.Value) - return string.Empty; - if (!enoughTimeLeft) return "No time left!"; From d55e861b906f8b049f0af5248ad78e9a36b99dd7 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 26 Jul 2024 16:55:15 -0700 Subject: [PATCH 168/266] Fix daily challenge background clipping when settings/notifications is opened --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 8538cbbb592e..c8e1434e37cc 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -106,6 +106,7 @@ public DailyChallenge(Room room) this.room = room; playlistItem = room.Playlist.Single(); roomManager = new RoomManager(); + Padding = new MarginPadding { Horizontal = -HORIZONTAL_OVERFLOW_PADDING }; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) From c2711d0c4e3b5e33982f31d1344075a62fc5814a Mon Sep 17 00:00:00 2001 From: normalid Date: Sat, 27 Jul 2024 17:25:44 +0800 Subject: [PATCH 169/266] Implement chatline background altering --- osu.Game/Overlays/Chat/ChatLine.cs | 23 +++++++++++++++++++++++ osu.Game/Overlays/Chat/DrawableChannel.cs | 8 ++++++++ 2 files changed, 31 insertions(+) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 9bcca3ac9dc7..922d040d5461 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -69,6 +69,29 @@ public Message Message private Container? highlight; + private Drawable? background; + + private bool alteringBackground; + + public bool AlteringBackground + { + get => alteringBackground; + set + { + alteringBackground = value; + + if (background == null) + AddInternal(background = new Box + { + BypassAutoSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }); + + background.Alpha = value ? 0.04f : 0f; + } + } + /// /// The colour used to paint the author's username. /// diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index aa17df490773..c817417a44d9 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -104,6 +104,13 @@ private void processMessageHighlighting() => SchedulerAfterChildren.AddOnce(() = highlightedMessage.Value = null; }); + private void processMessageBackgroundAltering() + { + for (int i = 0; i < ChatLineFlow.Count(); i++) + if (ChatLineFlow[i] is ChatLine chatline) + chatline.AlteringBackground = i % 2 == 0; + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -158,6 +165,7 @@ private void newMessagesArrived(IEnumerable newMessages) => Schedule(() scroll.ScrollToEnd(); processMessageHighlighting(); + processMessageBackgroundAltering(); }); private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => From 7f4bfb25a9991a3a4a6145e29c0951a35f97bd99 Mon Sep 17 00:00:00 2001 From: normalid Date: Sat, 27 Jul 2024 18:24:32 +0800 Subject: [PATCH 170/266] Implement unit test --- .../Visual/Online/TestSceneDrawableChannel.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs index 4830c7b85678..798bf481752f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs @@ -4,10 +4,15 @@ using System; using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; using osu.Framework.Testing; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; +using osu.Game.Overlays; using osu.Game.Overlays.Chat; namespace osu.Game.Tests.Visual.Online @@ -30,6 +35,8 @@ public void SetUpSteps() { RelativeSizeAxes = Axes.Both }); + Logger.Log("v.dwadwaawddwa"); + } [Test] @@ -83,5 +90,43 @@ public void TestDaySeparators() AddUntilStep("three day separators present", () => drawableChannel.ChildrenOfType().Count() == 3); AddAssert("last day separator is from correct day", () => drawableChannel.ChildrenOfType().Last().Date.Date == new DateTime(2022, 11, 22)); } + + [Test] + public void TestBackgroundAltering() + { + var localUser = new APIUser + { + Id = 3, + Username = "LocalUser" + }; + + string uuid = Guid.NewGuid().ToString(); + + int messageCount = 1; + + AddRepeatStep($"add messages", () => + { + channel.AddNewMessages(new Message(messageCount) + { + Sender = localUser, + Content = "Hi there all!", + Timestamp = new DateTimeOffset(2022, 11, 21, 20, 11, 13, TimeSpan.Zero), + Uuid = uuid, + }); + messageCount++; + }, 10); + + + AddUntilStep("10 message present", () => drawableChannel.ChildrenOfType().Count() == 10); + + int checkCount = 0; + + AddRepeatStep("check background", () => + { + // +1 because the day separator take one index + Assert.AreEqual((checkCount + 1) % 2 == 0, drawableChannel.ChildrenOfType().ToList()[checkCount].AlteringBackground); + checkCount++; + }, 10); + } } } From 73a98b45e94d0c289efecc7fd08f44c87953e358 Mon Sep 17 00:00:00 2001 From: normalid Date: Sat, 27 Jul 2024 18:48:45 +0800 Subject: [PATCH 171/266] FIx code quality --- osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs | 8 ++------ osu.Game/Overlays/Chat/ChatLine.cs | 2 ++ osu.Game/Overlays/Chat/DrawableChannel.cs | 4 ++++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs index 798bf481752f..19f88826a7c7 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs @@ -4,15 +4,11 @@ using System; using System.Linq; using NUnit.Framework; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Framework.Testing; -using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; -using osu.Game.Overlays; using osu.Game.Overlays.Chat; namespace osu.Game.Tests.Visual.Online @@ -47,6 +43,7 @@ public void TestDaySeparators() Id = 3, Username = "LocalUser" }; + string uuid = Guid.NewGuid().ToString(); AddStep("add local echo message", () => channel.AddLocalEcho(new LocalEchoMessage { @@ -104,7 +101,7 @@ public void TestBackgroundAltering() int messageCount = 1; - AddRepeatStep($"add messages", () => + AddRepeatStep("add messages", () => { channel.AddNewMessages(new Message(messageCount) { @@ -116,7 +113,6 @@ public void TestBackgroundAltering() messageCount++; }, 10); - AddUntilStep("10 message present", () => drawableChannel.ChildrenOfType().Count() == 10); int checkCount = 0; diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 922d040d5461..fc43e3823997 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -81,12 +81,14 @@ public bool AlteringBackground alteringBackground = value; if (background == null) + { AddInternal(background = new Box { BypassAutoSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both, Colour = Color4.White, }); + } background.Alpha = value ? 0.04f : 0f; } diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index c817417a44d9..8e353bfebd9f 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -107,8 +107,12 @@ private void processMessageHighlighting() => SchedulerAfterChildren.AddOnce(() = private void processMessageBackgroundAltering() { for (int i = 0; i < ChatLineFlow.Count(); i++) + { if (ChatLineFlow[i] is ChatLine chatline) + { chatline.AlteringBackground = i % 2 == 0; + } + } } protected override void Dispose(bool isDisposing) From 4e44a6e7f8fbb6f60cb6a67fc57f711fa4335b3c Mon Sep 17 00:00:00 2001 From: normalid Date: Sat, 27 Jul 2024 18:55:17 +0800 Subject: [PATCH 172/266] Clean up code --- osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs | 3 --- osu.Game/Overlays/Chat/DrawableChannel.cs | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs index 19f88826a7c7..d0f9a8c69e25 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs @@ -5,7 +5,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; -using osu.Framework.Logging; using osu.Framework.Testing; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; @@ -31,8 +30,6 @@ public void SetUpSteps() { RelativeSizeAxes = Axes.Both }); - Logger.Log("v.dwadwaawddwa"); - } [Test] diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 8e353bfebd9f..21af8d730563 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -106,7 +106,7 @@ private void processMessageHighlighting() => SchedulerAfterChildren.AddOnce(() = private void processMessageBackgroundAltering() { - for (int i = 0; i < ChatLineFlow.Count(); i++) + for (int i = 0; i < ChatLineFlow.Count; i++) { if (ChatLineFlow[i] is ChatLine chatline) { From 0c89210bd7f1e578476ce9e7d2c1d2f3df7f107c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 28 Jul 2024 05:24:05 +0300 Subject: [PATCH 173/266] Add API models for daily challenge statistics --- .../Online/API/Requests/Responses/APIUser.cs | 3 ++ .../APIUserDailyChallengeStatistics.cs | 41 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 osu.Game/Online/API/Requests/Responses/APIUserDailyChallengeStatistics.cs diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index a2836476c562..c69e45b3fd98 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -272,6 +272,9 @@ private APIRankHistory rankHistory [JsonProperty("groups")] public APIUserGroup[] Groups; + [JsonProperty("daily_challenge_user_stats")] + public APIUserDailyChallengeStatistics DailyChallengeStatistics = new APIUserDailyChallengeStatistics(); + public override string ToString() => Username; /// diff --git a/osu.Game/Online/API/Requests/Responses/APIUserDailyChallengeStatistics.cs b/osu.Game/Online/API/Requests/Responses/APIUserDailyChallengeStatistics.cs new file mode 100644 index 000000000000..e77f2b8f6877 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIUserDailyChallengeStatistics.cs @@ -0,0 +1,41 @@ +// 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 Newtonsoft.Json; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIUserDailyChallengeStatistics + { + [JsonProperty("user_id")] + public int UserID; + + [JsonProperty("daily_streak_best")] + public int DailyStreakBest; + + [JsonProperty("daily_streak_current")] + public int DailyStreakCurrent; + + [JsonProperty("weekly_streak_best")] + public int WeeklyStreakBest; + + [JsonProperty("weekly_streak_current")] + public int WeeklyStreakCurrent; + + [JsonProperty("top_10p_placements")] + public int Top10PercentPlacements; + + [JsonProperty("top_50p_placements")] + public int Top50PercentPlacements; + + [JsonProperty("playcount")] + public int PlayCount; + + [JsonProperty("last_update")] + public DateTimeOffset? LastUpdate; + + [JsonProperty("last_weekly_streak")] + public DateTimeOffset? LastWeeklyStreak; + } +} From 17f5d58be2fcf5c2ead8d8b76a7b2ae5281d1197 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 28 Jul 2024 05:24:29 +0300 Subject: [PATCH 174/266] Add daily challenge streak display and tooltip --- ...tSceneUserProfileDailyChallengeOverview.cs | 63 +++++ .../Components/DailyChallengeStreakDisplay.cs | 112 ++++++++ .../Components/DailyChallengeStreakTooltip.cs | 243 ++++++++++++++++++ 3 files changed, 418 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallengeOverview.cs create mode 100644 osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs create mode 100644 osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallengeOverview.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallengeOverview.cs new file mode 100644 index 000000000000..e2d26f222cba --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallengeOverview.cs @@ -0,0 +1,63 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Overlays.Profile; +using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Rulesets.Osu; +using osuTK; + +namespace osu.Game.Tests.Visual.Online +{ + public partial class TestSceneUserProfileDailyChallenge : OsuManualInputManagerTestScene + { + [Cached] + public readonly Bindable User = new Bindable(new UserProfileData(new APIUser(), new OsuRuleset().RulesetInfo)); + + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); + + protected override void LoadComplete() + { + base.LoadComplete(); + + DailyChallengeStreakDisplay display = null!; + + AddSliderStep("daily", 0, 999, 2, v => update(s => s.DailyStreakCurrent = v)); + AddSliderStep("daily best", 0, 999, 2, v => update(s => s.DailyStreakBest = v)); + AddSliderStep("weekly", 0, 250, 1, v => update(s => s.WeeklyStreakCurrent = v)); + AddSliderStep("weekly best", 0, 250, 1, v => update(s => s.WeeklyStreakBest = v)); + AddSliderStep("top 10%", 0, 999, 0, v => update(s => s.Top10PercentPlacements = v)); + AddSliderStep("top 50%", 0, 999, 0, v => update(s => s.Top50PercentPlacements = v)); + AddStep("create", () => + { + Clear(); + Add(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background2, + }); + Add(display = new DailyChallengeStreakDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(1f), + User = { BindTarget = User }, + }); + }); + AddStep("hover", () => InputManager.MoveMouseTo(display)); + } + + private void update(Action change) + { + change.Invoke(User.Value!.User.DailyChallengeStatistics); + User.Value = new UserProfileData(User.Value.User, User.Value.Ruleset); + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs new file mode 100644 index 000000000000..2d9b1073673f --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs @@ -0,0 +1,112 @@ +// 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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public partial class DailyChallengeStreakDisplay : CompositeDrawable, IHasCustomTooltip + { + public readonly Bindable User = new Bindable(); + + public APIUserDailyChallengeStatistics? TooltipContent { get; private set; } + + private OsuSpriteText dailyStreak = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Both; + CornerRadius = 5; + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4, + }, + new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Padding = new MarginPadding(5f), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12)) + { + AutoSizeAxes = Axes.Both, + // Text = UsersStrings.ShowDailyChallengeTitle + Text = "Daily\nChallenge", + Margin = new MarginPadding { Horizontal = 5f, Bottom = 2f }, + }, + new Container + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + CornerRadius = 5f, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background6, + }, + dailyStreak = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + UseFullGlyphHeight = false, + Colour = colourProvider.Content2, + Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, + }, + } + }, + } + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + User.BindValueChanged(_ => updateDisplay(), true); + } + + private void updateDisplay() + { + if (User.Value == null) + { + dailyStreak.Text = "-"; + return; + } + + var statistics = User.Value.User.DailyChallengeStatistics; + // dailyStreak.Text = UsersStrings.ShowDailyChallengeUnitDay(statistics.DailyStreakCurrent); + dailyStreak.Text = $"{statistics.DailyStreakCurrent}d"; + dailyStreak.Colour = colours.ForRankingTier(DailyChallengeStreakTooltip.TierForDaily(statistics.DailyStreakCurrent)); + TooltipContent = statistics; + } + + public ITooltip GetCustomTooltip() => new DailyChallengeStreakTooltip(colourProvider); + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs new file mode 100644 index 000000000000..a95de8cefde2 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs @@ -0,0 +1,243 @@ +// 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.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Scoring; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public partial class DailyChallengeStreakTooltip : VisibilityContainer, ITooltip + { + [Cached] + private readonly OverlayColourProvider colourProvider; + + private StreakPiece currentDaily = null!; + private StreakPiece currentWeekly = null!; + private StatisticsPiece bestDaily = null!; + private StatisticsPiece bestWeekly = null!; + private StatisticsPiece topTen = null!; + private StatisticsPiece topFifty = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + public DailyChallengeStreakTooltip(OverlayColourProvider colourProvider) + { + this.colourProvider = colourProvider; + } + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Both; + CornerRadius = 20f; + Masking = true; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new Container + { + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding(15f), + Spacing = new Vector2(30f), + Children = new[] + { + // currentDaily = new StreakPiece(UsersStrings.ShowDailyChallengeDailyStreakCurrent), + // currentWeekly = new StreakPiece(UsersStrings.ShowDailyChallengeWeeklyStreakCurrent), + currentDaily = new StreakPiece("Current Daily Streak"), + currentWeekly = new StreakPiece("Current Weekly Streak"), + } + }, + } + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(15f), + Spacing = new Vector2(10f), + Children = new[] + { + // bestDaily = new StatisticsPiece(UsersStrings.ShowDailyChallengeDailyStreakBest), + // bestWeekly = new StatisticsPiece(UsersStrings.ShowDailyChallengeWeeklyStreakBest), + // topTen = new StatisticsPiece(UsersStrings.ShowDailyChallengeTop10pPlacements), + // topFifty = new StatisticsPiece(UsersStrings.ShowDailyChallengeTop50pPlacements), + bestDaily = new StatisticsPiece("Best Daily Streak"), + bestWeekly = new StatisticsPiece("Best Weekly Streak"), + topTen = new StatisticsPiece("Top 10% Placements"), + topFifty = new StatisticsPiece("Top 50% Placements"), + } + }, + } + } + }; + } + + public void SetContent(APIUserDailyChallengeStatistics content) + { + // currentDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(content.DailyStreakCurrent.ToLocalisableString(@"N0")); + currentDaily.Value = $"{content.DailyStreakCurrent:N0}d"; + currentDaily.ValueColour = colours.ForRankingTier(TierForDaily(content.DailyStreakCurrent)); + + // currentWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(content.WeeklyStreakCurrent.ToLocalisableString(@"N0")); + currentWeekly.Value = $"{content.WeeklyStreakCurrent:N0}w"; + currentWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(content.WeeklyStreakCurrent)); + + // bestDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(content.DailyStreakBest.ToLocalisableString(@"N0")); + bestDaily.Value = $"{content.DailyStreakBest:N0}d"; + bestDaily.ValueColour = colours.ForRankingTier(TierForDaily(content.DailyStreakBest)); + + // bestWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(content.WeeklyStreakBest.ToLocalisableString(@"N0")); + bestWeekly.Value = $"{content.WeeklyStreakBest:N0}w"; + bestWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(content.WeeklyStreakBest)); + + topTen.Value = content.Top10PercentPlacements.ToLocalisableString(@"N0"); + topFifty.Value = content.Top50PercentPlacements.ToLocalisableString(@"N0"); + } + + // reference: https://github.com/ppy/osu-web/blob/8206e0e91eeea80ccf92f0586561346dd40e085e/resources/js/profile-page/daily-challenge.tsx#L13-L43 + public static RankingTier TierForDaily(int daily) + { + if (daily > 360) + return RankingTier.Lustrous; + + if (daily > 240) + return RankingTier.Radiant; + + if (daily > 120) + return RankingTier.Rhodium; + + if (daily > 60) + return RankingTier.Platinum; + + if (daily > 30) + return RankingTier.Gold; + + if (daily > 10) + return RankingTier.Silver; + + if (daily > 5) + return RankingTier.Bronze; + + return RankingTier.Iron; + } + + public static RankingTier TierForWeekly(int weekly) => TierForDaily((weekly - 1) * 7); + + protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); + + protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); + + public void Move(Vector2 pos) => Position = pos; + + private partial class StreakPiece : FillFlowContainer + { + private readonly OsuSpriteText valueText; + + public LocalisableString Value + { + set => valueText.Text = value; + } + + public ColourInfo ValueColour + { + set => valueText.Colour = value; + } + + public StreakPiece(LocalisableString title) + { + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Vertical; + + Children = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Text = title, + }, + valueText = new OsuSpriteText + { + // Colour = colour + Font = OsuFont.GetFont(size: 40, weight: FontWeight.Light), + } + }; + } + } + + private partial class StatisticsPiece : CompositeDrawable + { + private readonly OsuSpriteText valueText; + + public LocalisableString Value + { + set => valueText.Text = value; + } + + public ColourInfo ValueColour + { + set => valueText.Colour = value; + } + + public StatisticsPiece(LocalisableString title) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChildren = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Text = title, + }, + valueText = new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Font = OsuFont.GetFont(size: 12), + } + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + valueText.Colour = colourProvider.Content2; + } + } + } +} From e82c54a31cf06fe64ff4a6fad31f7d9eff6aff19 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 28 Jul 2024 05:57:30 +0300 Subject: [PATCH 175/266] Integrate daily challenge streak display with user profile overlay --- .../Online/TestSceneUserProfileOverlay.cs | 9 ++++ .../Profile/Header/Components/MainDetails.cs | 41 ++++++++++++++----- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index 8dbd493920c7..937e08cb9703 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -282,6 +282,15 @@ public void TestCustomColourSchemeWithReload() ImageUrlLowRes = "https://assets.ppy.sh/profile-badges/contributor.png", }, }, + DailyChallengeStatistics = new APIUserDailyChallengeStatistics + { + DailyStreakCurrent = 231, + WeeklyStreakCurrent = 18, + DailyStreakBest = 370, + WeeklyStreakBest = 51, + Top10PercentPlacements = 345, + Top50PercentPlacements = 427, + }, Title = "osu!volunteer", Colour = "ff0000", Achievements = Array.Empty(), diff --git a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs index 2505c1bc8c48..f9a4267ed940 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs @@ -44,22 +44,41 @@ private void load() Spacing = new Vector2(0, 15), Children = new Drawable[] { - new FillFlowContainer + new GridContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(20), - Children = new Drawable[] + ColumnDimensions = new[] { - detailGlobalRank = new ProfileValueDisplay(true) - { - Title = UsersStrings.ShowRankGlobalSimple, - }, - detailCountryRank = new ProfileValueDisplay(true) + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, 20), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new[] { - Title = UsersStrings.ShowRankCountrySimple, - }, + detailGlobalRank = new ProfileValueDisplay(true) + { + Title = UsersStrings.ShowRankGlobalSimple, + }, + Empty(), + detailCountryRank = new ProfileValueDisplay(true) + { + Title = UsersStrings.ShowRankCountrySimple, + }, + new DailyChallengeStreakDisplay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + User = { BindTarget = User }, + } + } } }, new Container From 31787757efefd9c2d0bc9f9f2dfb92942783f4da Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 28 Jul 2024 06:21:21 +0300 Subject: [PATCH 176/266] Provide colour scheme as part of tooltip data to handle reusing tooltip with different profile hues --- .../Components/DailyChallengeStreakDisplay.cs | 9 ++- .../Components/DailyChallengeStreakTooltip.cs | 64 +++++++++---------- 2 files changed, 35 insertions(+), 38 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs index 2d9b1073673f..87f833d1652c 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs @@ -10,15 +10,14 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.Profile.Header.Components { - public partial class DailyChallengeStreakDisplay : CompositeDrawable, IHasCustomTooltip + public partial class DailyChallengeStreakDisplay : CompositeDrawable, IHasCustomTooltip { public readonly Bindable User = new Bindable(); - public APIUserDailyChallengeStatistics? TooltipContent { get; private set; } + public DailyChallengeStreakTooltipData? TooltipContent { get; private set; } private OsuSpriteText dailyStreak = null!; @@ -104,9 +103,9 @@ private void updateDisplay() // dailyStreak.Text = UsersStrings.ShowDailyChallengeUnitDay(statistics.DailyStreakCurrent); dailyStreak.Text = $"{statistics.DailyStreakCurrent}d"; dailyStreak.Colour = colours.ForRankingTier(DailyChallengeStreakTooltip.TierForDaily(statistics.DailyStreakCurrent)); - TooltipContent = statistics; + TooltipContent = new DailyChallengeStreakTooltipData(colourProvider, statistics); } - public ITooltip GetCustomTooltip() => new DailyChallengeStreakTooltip(colourProvider); + public ITooltip GetCustomTooltip() => new DailyChallengeStreakTooltip(); } } diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs index a95de8cefde2..9dc4dfcb9ca0 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs @@ -17,11 +17,8 @@ namespace osu.Game.Overlays.Profile.Header.Components { - public partial class DailyChallengeStreakTooltip : VisibilityContainer, ITooltip + public partial class DailyChallengeStreakTooltip : VisibilityContainer, ITooltip { - [Cached] - private readonly OverlayColourProvider colourProvider; - private StreakPiece currentDaily = null!; private StreakPiece currentWeekly = null!; private StatisticsPiece bestDaily = null!; @@ -29,14 +26,12 @@ public partial class DailyChallengeStreakTooltip : VisibilityContainer, ITooltip private StatisticsPiece topTen = null!; private StatisticsPiece topFifty = null!; + private Box topBackground = null!; + private Box background = null!; + [Resolved] private OsuColour colours { get; set; } = null!; - public DailyChallengeStreakTooltip(OverlayColourProvider colourProvider) - { - this.colourProvider = colourProvider; - } - [BackgroundDependencyLoader] private void load() { @@ -46,10 +41,9 @@ private void load() Children = new Drawable[] { - new Box + background = new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background4, }, new FillFlowContainer { @@ -62,10 +56,9 @@ private void load() AutoSizeAxes = Axes.Both, Children = new Drawable[] { - new Box + topBackground = new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background5, }, new FillFlowContainer { @@ -106,26 +99,35 @@ private void load() }; } - public void SetContent(APIUserDailyChallengeStatistics content) + public void SetContent(DailyChallengeStreakTooltipData content) { + var statistics = content.Statistics; + var colourProvider = content.ColourProvider; + + background.Colour = colourProvider.Background4; + topBackground.Colour = colourProvider.Background5; + // currentDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(content.DailyStreakCurrent.ToLocalisableString(@"N0")); - currentDaily.Value = $"{content.DailyStreakCurrent:N0}d"; - currentDaily.ValueColour = colours.ForRankingTier(TierForDaily(content.DailyStreakCurrent)); + currentDaily.Value = $"{statistics.DailyStreakCurrent:N0}d"; + currentDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakCurrent)); + + // currentWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(statistics.WeeklyStreakCurrent.ToLocalisableString(@"N0")); + currentWeekly.Value = $"{statistics.WeeklyStreakCurrent:N0}w"; + currentWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakCurrent)); - // currentWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(content.WeeklyStreakCurrent.ToLocalisableString(@"N0")); - currentWeekly.Value = $"{content.WeeklyStreakCurrent:N0}w"; - currentWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(content.WeeklyStreakCurrent)); + // bestDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(statistics.DailyStreakBest.ToLocalisableString(@"N0")); + bestDaily.Value = $"{statistics.DailyStreakBest:N0}d"; + bestDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakBest)); - // bestDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(content.DailyStreakBest.ToLocalisableString(@"N0")); - bestDaily.Value = $"{content.DailyStreakBest:N0}d"; - bestDaily.ValueColour = colours.ForRankingTier(TierForDaily(content.DailyStreakBest)); + // bestWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(statistics.WeeklyStreakBest.ToLocalisableString(@"N0")); + bestWeekly.Value = $"{statistics.WeeklyStreakBest:N0}w"; + bestWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakBest)); - // bestWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(content.WeeklyStreakBest.ToLocalisableString(@"N0")); - bestWeekly.Value = $"{content.WeeklyStreakBest:N0}w"; - bestWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(content.WeeklyStreakBest)); + topTen.Value = statistics.Top10PercentPlacements.ToLocalisableString(@"N0"); + topTen.ValueColour = colourProvider.Content2; - topTen.Value = content.Top10PercentPlacements.ToLocalisableString(@"N0"); - topFifty.Value = content.Top50PercentPlacements.ToLocalisableString(@"N0"); + topFifty.Value = statistics.Top50PercentPlacements.ToLocalisableString(@"N0"); + topFifty.ValueColour = colourProvider.Content2; } // reference: https://github.com/ppy/osu-web/blob/8206e0e91eeea80ccf92f0586561346dd40e085e/resources/js/profile-page/daily-challenge.tsx#L13-L43 @@ -232,12 +234,8 @@ public StatisticsPiece(LocalisableString title) } }; } - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - valueText.Colour = colourProvider.Content2; - } } } + + public record DailyChallengeStreakTooltipData(OverlayColourProvider ColourProvider, APIUserDailyChallengeStatistics Statistics); } From 6bdb1107c157a8feb8ded4a383dced04e05026e9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 28 Jul 2024 06:34:59 +0300 Subject: [PATCH 177/266] Add shadow over tooltip --- .../Header/Components/DailyChallengeStreakTooltip.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs index 9dc4dfcb9ca0..02be0f2c9973 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs @@ -2,18 +2,21 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Effects; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Scoring; using osuTK; +using Box = osu.Framework.Graphics.Shapes.Box; +using Color4 = osuTK.Graphics.Color4; namespace osu.Game.Overlays.Profile.Header.Components { @@ -39,6 +42,13 @@ private void load() CornerRadius = 20f; Masking = true; + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.5f), + Radius = 30f, + }; + Children = new Drawable[] { background = new Box From 82fbd5b045b2487c70e69fa0bf3fdd956b81967b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 28 Jul 2024 06:40:16 +0300 Subject: [PATCH 178/266] Rename file --- ...ChallengeOverview.cs => TestSceneUserProfileDailyChallenge.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Tests/Visual/Online/{TestSceneUserProfileDailyChallengeOverview.cs => TestSceneUserProfileDailyChallenge.cs} (100%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallengeOverview.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs similarity index 100% rename from osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallengeOverview.cs rename to osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs From 7fedfd368c83767846e947372e9fba03e07f6ceb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 28 Jul 2024 07:22:58 +0300 Subject: [PATCH 179/266] Fix score breakdown tooltips appearing in other feeds --- .../OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs index cfec170cf663..12401061a3c8 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs @@ -27,6 +27,9 @@ public partial class DailyChallengeScoreBreakdown : CompositeDrawable private FillFlowContainer barsContainer = null!; + // we're always present so that we can update while hidden, but we don't want tooltips to be displayed, therefore directly use alpha comparison here. + public override bool PropagatePositionalInputSubTree => base.PropagatePositionalInputSubTree && Alpha > 0; + private const int bin_count = MultiplayerPlaylistItemStats.TOTAL_SCORE_DISTRIBUTION_BINS; private long[] bins = new long[bin_count]; From f6eb9037df1d1f2bfd3d2285c20752923403f3d1 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 27 Jul 2024 23:50:52 -0700 Subject: [PATCH 180/266] Add ability to copy leaderboard mods in daily challenge --- .../DailyChallenge/DailyChallenge.cs | 6 +++-- .../DailyChallengeLeaderboard.cs | 20 +++++++++++++++++ .../Leaderboards/LeaderboardScoreV2.cs | 22 ++++++++++++++----- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 8538cbbb592e..e1f78129a457 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -21,6 +21,7 @@ using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Online.API; @@ -168,7 +169,7 @@ private void load(AudioManager audio) }, null, [ - new Container + new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, Masking = true, @@ -238,6 +239,7 @@ private void load(AudioManager audio) { RelativeSizeAxes = Axes.Both, PresentScore = presentScore, + SelectedMods = { BindTarget = userMods }, }, // Spacer null, @@ -329,7 +331,7 @@ [new MatchChatDisplay(room) { RelativeSizeAxes = Axes.Both }] var rulesetInstance = rulesets.GetRuleset(playlistItem.RulesetID)!.CreateInstance(); var allowedMods = playlistItem.AllowedMods.Select(m => m.ToMod(rulesetInstance)); - userModsSelectOverlay.IsValidMod = m => allowedMods.Any(a => a.GetType() == m.GetType()); + userModsSelectOverlay.IsValidMod = leaderboard.IsValidMod = m => allowedMods.Any(a => a.GetType() == m.GetType()); } metadataClient.MultiplayerRoomScoreSet += onRoomScoreSet; diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs index 5efb656cea91..f332e717c103 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using osu.Framework.Allocation; @@ -14,6 +15,7 @@ using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.SelectV2.Leaderboards; using osuTK; @@ -24,6 +26,20 @@ public partial class DailyChallengeLeaderboard : CompositeDrawable { public IBindable UserBestScore => userBestScore; private readonly Bindable userBestScore = new Bindable(); + public Bindable> SelectedMods = new Bindable>(); + + private Func isValidMod = _ => true; + + /// + /// A function determining whether each mod in the score can be selected. + /// A return value of means that the mod can be selected in the current context. + /// A return value of means that the mod cannot be selected in the current context. + /// + public Func IsValidMod + { + get => isValidMod; + set => isValidMod = value ?? throw new ArgumentNullException(nameof(value)); + } public Action? PresentScore { get; init; } @@ -153,6 +169,8 @@ public void RefetchScores() Rank = index + 1, IsPersonalBest = s.UserID == api.LocalUser.Value.Id, Action = () => PresentScore?.Invoke(s.OnlineID), + SelectedMods = { BindTarget = SelectedMods }, + IsValidMod = isValidMod, }), loaded => { scoreFlow.Clear(); @@ -171,6 +189,8 @@ public void RefetchScores() Rank = userBest.Position, IsPersonalBest = true, Action = () => PresentScore?.Invoke(userBest.OnlineID), + SelectedMods = { BindTarget = SelectedMods }, + IsValidMod = isValidMod, }); } diff --git a/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs b/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs index 700f889d7f14..6066bc773946 100644 --- a/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs +++ b/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs @@ -43,6 +43,21 @@ namespace osu.Game.Screens.SelectV2.Leaderboards { public partial class LeaderboardScoreV2 : OsuClickableContainer, IHasContextMenu, IHasCustomTooltip { + public Bindable> SelectedMods = new Bindable>(); + + private Func isValidMod = _ => true; + + /// + /// A function determining whether each mod in the score can be selected. + /// A return value of means that the mod can be selected in the current context. + /// A return value of means that the mod cannot be selected in the current context. + /// + public Func IsValidMod + { + get => isValidMod; + set => isValidMod = value ?? throw new ArgumentNullException(nameof(value)); + } + public int? Rank { get; init; } public bool IsPersonalBest { get; init; } @@ -68,9 +83,6 @@ public partial class LeaderboardScoreV2 : OsuClickableContainer, IHasContextMenu [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; - [Resolved] - private SongSelect? songSelect { get; set; } - [Resolved] private IDialogOverlay? dialogOverlay { get; set; } @@ -738,8 +750,8 @@ public MenuItem[] ContextMenuItems { List items = new List(); - if (score.Mods.Length > 0 && songSelect != null) - items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = score.Mods)); + if (score.Mods.Length > 0) + items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => SelectedMods.Value = score.Mods.Where(m => isValidMod.Invoke(m)).ToArray())); if (score.Files.Count <= 0) return items.ToArray(); From e58bdbb8a944b0c73623d3ab0867b51f484fab04 Mon Sep 17 00:00:00 2001 From: normalid Date: Sun, 28 Jul 2024 15:08:36 +0800 Subject: [PATCH 181/266] Improve unit test --- osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs index d0f9a8c69e25..795d49adad64 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs @@ -94,8 +94,6 @@ public void TestBackgroundAltering() Username = "LocalUser" }; - string uuid = Guid.NewGuid().ToString(); - int messageCount = 1; AddRepeatStep("add messages", () => @@ -104,8 +102,8 @@ public void TestBackgroundAltering() { Sender = localUser, Content = "Hi there all!", - Timestamp = new DateTimeOffset(2022, 11, 21, 20, 11, 13, TimeSpan.Zero), - Uuid = uuid, + Timestamp = new DateTimeOffset(2022, 11, 21, 20, messageCount, 13, TimeSpan.Zero), + Uuid = Guid.NewGuid().ToString(), }); messageCount++; }, 10); From 5db0e3640436eaf5fe58f21d98f5ea6d19e3b335 Mon Sep 17 00:00:00 2001 From: normalid Date: Sun, 28 Jul 2024 16:18:43 +0800 Subject: [PATCH 182/266] Use the `TruncatingSpriteText` in `ModPresetTooltip` --- osu.Game/Overlays/Mods/ModPresetTooltip.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModPresetTooltip.cs b/osu.Game/Overlays/Mods/ModPresetTooltip.cs index ec81aa7ceb60..8f4efa766785 100644 --- a/osu.Game/Overlays/Mods/ModPresetTooltip.cs +++ b/osu.Game/Overlays/Mods/ModPresetTooltip.cs @@ -44,10 +44,12 @@ public ModPresetTooltip(OverlayColourProvider colourProvider) Spacing = new Vector2(7), Children = new[] { - descriptionText = new OsuSpriteText + descriptionText = new TruncatingSpriteText { + RelativeSizeAxes = Axes.X, Font = OsuFont.GetFont(weight: FontWeight.Regular), Colour = colourProvider.Content1, + AllowMultiline = true, }, } } From 4e65944609d7c907fa10c8f9af7e376115dcefe2 Mon Sep 17 00:00:00 2001 From: normalid Date: Sun, 28 Jul 2024 16:26:18 +0800 Subject: [PATCH 183/266] Make the tooltips width be dyanmic with the content, so the long text wont occurs wierd line break --- osu.Game/Overlays/Mods/ModPresetRow.cs | 3 +-- osu.Game/Overlays/Mods/ModPresetTooltip.cs | 17 +++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModPresetRow.cs b/osu.Game/Overlays/Mods/ModPresetRow.cs index 4829e93b871c..8614806085f7 100644 --- a/osu.Game/Overlays/Mods/ModPresetRow.cs +++ b/osu.Game/Overlays/Mods/ModPresetRow.cs @@ -24,8 +24,7 @@ public ModPresetRow(Mod mod) { new FillFlowContainer { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(7), Children = new Drawable[] diff --git a/osu.Game/Overlays/Mods/ModPresetTooltip.cs b/osu.Game/Overlays/Mods/ModPresetTooltip.cs index 8f4efa766785..768feb075666 100644 --- a/osu.Game/Overlays/Mods/ModPresetTooltip.cs +++ b/osu.Game/Overlays/Mods/ModPresetTooltip.cs @@ -23,8 +23,7 @@ public partial class ModPresetTooltip : VisibilityContainer, ITooltip public ModPresetTooltip(OverlayColourProvider colourProvider) { - Width = 250; - AutoSizeAxes = Axes.Y; + AutoSizeAxes = Axes.Both; Masking = true; CornerRadius = 7; @@ -38,18 +37,16 @@ public ModPresetTooltip(OverlayColourProvider colourProvider) }, Content = new FillFlowContainer { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, Padding = new MarginPadding { Left = 10, Right = 10, Top = 5, Bottom = 5 }, Spacing = new Vector2(7), Children = new[] { - descriptionText = new TruncatingSpriteText + descriptionText = new OsuSpriteText { - RelativeSizeAxes = Axes.X, Font = OsuFont.GetFont(weight: FontWeight.Regular), Colour = colourProvider.Content1, - AllowMultiline = true, }, } } @@ -68,7 +65,11 @@ public void SetContent(ModPreset preset) lastPreset = preset; Content.RemoveAll(d => d is ModPresetRow, true); - Content.AddRange(preset.Mods.AsOrdered().Select(mod => new ModPresetRow(mod))); + Content.AddRange(preset.Mods.AsOrdered().Select(mod => new ModPresetRow(mod) + { + RelativeSizeAxes = Axes.None, + AutoSizeAxes = Axes.Both, + })); } protected override void PopIn() => this.FadeIn(transition_duration, Easing.OutQuint); From 1c9c3c92fdf539b485bc91173e931d08396de904 Mon Sep 17 00:00:00 2001 From: Shreyas Kadambi Date: Sun, 28 Jul 2024 11:30:42 -0400 Subject: [PATCH 184/266] Add tests for expected timestamp format --- osu.Game.Tests/Editing/EditorTimestampParserTest.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Editing/EditorTimestampParserTest.cs b/osu.Game.Tests/Editing/EditorTimestampParserTest.cs index 9c7fae0eaf85..cb9bd3dafe73 100644 --- a/osu.Game.Tests/Editing/EditorTimestampParserTest.cs +++ b/osu.Game.Tests/Editing/EditorTimestampParserTest.cs @@ -16,7 +16,6 @@ public class EditorTimestampParserTest new object?[] { "1", true, TimeSpan.FromMilliseconds(1), null }, new object?[] { "99", true, TimeSpan.FromMilliseconds(99), null }, new object?[] { "320000", true, TimeSpan.FromMilliseconds(320000), null }, - new object?[] { "1:2", true, new TimeSpan(0, 0, 1, 2), null }, new object?[] { "1:02", true, new TimeSpan(0, 0, 1, 2), null }, new object?[] { "1:92", false, null, null }, new object?[] { "1:002", false, null, null }, @@ -25,6 +24,9 @@ public class EditorTimestampParserTest new object?[] { "1:02:3000", false, null, null }, new object?[] { "1:02:300 ()", false, null, null }, new object?[] { "1:02:300 (1,2,3)", true, new TimeSpan(0, 0, 1, 2, 300), "1,2,3" }, + new object?[] { "1:02:300 (1,2,3) - ", true, new TimeSpan(0, 0, 1, 2, 300), "1,2,3" }, + new object?[] { "1:02:300 (1,2,3) - following mod", true, new TimeSpan(0, 0, 1, 2, 300), "1,2,3" }, + new object?[] { "1:02:300 (1,2,3) - following mod\nwith newlines", true, new TimeSpan(0, 0, 1, 2, 300), "1,2,3" }, }; [TestCaseSource(nameof(test_cases))] From dec6b190f249677f9a9e37a477547f8a6474dff9 Mon Sep 17 00:00:00 2001 From: Shreyas Kadambi Date: Sun, 28 Jul 2024 11:31:36 -0400 Subject: [PATCH 185/266] Add optional 'suffix' to timestamp --- osu.Game/Rulesets/Edit/EditorTimestampParser.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/EditorTimestampParser.cs b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs index 92a692b94eb9..9e637e55bc5b 100644 --- a/osu.Game/Rulesets/Edit/EditorTimestampParser.cs +++ b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs @@ -11,7 +11,8 @@ public static class EditorTimestampParser { /// /// Used for parsing in contexts where we don't want e.g. normal times of day to be parsed as timestamps (e.g. chat) - /// Original osu-web regex: https://github.com/ppy/osu-web/blob/3b1698639244cfdaf0b41c68bfd651ea729ec2e3/resources/js/utils/beatmapset-discussion-helper.ts#L78 + /// Original osu-web regex: + /// https://github.com/ppy/osu-web/blob/3b1698639244cfdaf0b41c68bfd651ea729ec2e3/resources/js/utils/beatmapset-discussion-helper.ts#L78 /// /// /// 00:00:000 (...) - test @@ -32,7 +33,10 @@ public static class EditorTimestampParser /// 1:02:300 (1,2,3) - parses to 01:02:300 with selection /// /// - private static readonly Regex time_regex_lenient = new Regex(@"^(((?\d{1,3}):(?([0-5]?\d))([:.](?\d{0,3}))?)(?\s\([^)]+\))?)$", RegexOptions.Compiled); + private static readonly Regex time_regex_lenient = new Regex( + @"^(((?\d{1,3}):(?([0-5]?\d))([:.](?\d{0,3}))?)(?\s\([^)]+\))?)(?\s-.*)?$", + RegexOptions.Compiled | RegexOptions.Singleline + ); public static bool TryParse(string timestamp, [NotNullWhen(true)] out TimeSpan? parsedTime, out string? parsedSelection) { From ae61df0abe507f675282be5d54146c9e1736a27b Mon Sep 17 00:00:00 2001 From: Shreyas Kadambi Date: Sun, 28 Jul 2024 11:47:00 -0400 Subject: [PATCH 186/266] Add back accidentally removed test --- osu.Game.Tests/Editing/EditorTimestampParserTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Editing/EditorTimestampParserTest.cs b/osu.Game.Tests/Editing/EditorTimestampParserTest.cs index cb9bd3dafe73..49154f1cbb71 100644 --- a/osu.Game.Tests/Editing/EditorTimestampParserTest.cs +++ b/osu.Game.Tests/Editing/EditorTimestampParserTest.cs @@ -16,6 +16,7 @@ public class EditorTimestampParserTest new object?[] { "1", true, TimeSpan.FromMilliseconds(1), null }, new object?[] { "99", true, TimeSpan.FromMilliseconds(99), null }, new object?[] { "320000", true, TimeSpan.FromMilliseconds(320000), null }, + new object?[] { "1:2", true, new TimeSpan(0, 0, 1, 2), null }, new object?[] { "1:02", true, new TimeSpan(0, 0, 1, 2), null }, new object?[] { "1:92", false, null, null }, new object?[] { "1:002", false, null, null }, From 63757a77a54a108c56d9538c229daa15c81c1b7b Mon Sep 17 00:00:00 2001 From: jkh675 Date: Mon, 29 Jul 2024 13:39:08 +0800 Subject: [PATCH 187/266] Extract update background method --- osu.Game/Overlays/Chat/ChatLine.cs | 28 +++++++++++++---------- osu.Game/Overlays/Chat/DrawableChannel.cs | 4 ++-- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index fc43e3823997..81b63d3380f0 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -79,18 +79,7 @@ public bool AlteringBackground set { alteringBackground = value; - - if (background == null) - { - AddInternal(background = new Box - { - BypassAutoSizeAxes = Axes.Both, - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }); - } - - background.Alpha = value ? 0.04f : 0f; + updateBackground(); } } @@ -283,5 +272,20 @@ private void updateTimestamp() Color4Extensions.FromHex("812a96"), Color4Extensions.FromHex("992861"), }; + + private void updateBackground() + { + if (background == null) + { + AddInternal(background = new Box + { + BypassAutoSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }); + } + + background.Alpha = alteringBackground ? 0.04f : 0f; + } } } diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 21af8d730563..b6b89c4201c2 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -104,7 +104,7 @@ private void processMessageHighlighting() => SchedulerAfterChildren.AddOnce(() = highlightedMessage.Value = null; }); - private void processMessageBackgroundAltering() + private void processChatlineBackgroundAltering() { for (int i = 0; i < ChatLineFlow.Count; i++) { @@ -169,7 +169,7 @@ private void newMessagesArrived(IEnumerable newMessages) => Schedule(() scroll.ScrollToEnd(); processMessageHighlighting(); - processMessageBackgroundAltering(); + processChatlineBackgroundAltering(); }); private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => From 54c904d439ae2b6b37b97d29768ad8e04994d054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Jul 2024 10:40:29 +0200 Subject: [PATCH 188/266] Convert into auto-property --- .../SelectV2/Leaderboards/LeaderboardScoreV2.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs b/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs index 6066bc773946..c9584b057bcc 100644 --- a/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs +++ b/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs @@ -45,18 +45,12 @@ public partial class LeaderboardScoreV2 : OsuClickableContainer, IHasContextMenu { public Bindable> SelectedMods = new Bindable>(); - private Func isValidMod = _ => true; - /// /// A function determining whether each mod in the score can be selected. /// A return value of means that the mod can be selected in the current context. /// A return value of means that the mod cannot be selected in the current context. /// - public Func IsValidMod - { - get => isValidMod; - set => isValidMod = value ?? throw new ArgumentNullException(nameof(value)); - } + public Func IsValidMod { get; set; } = _ => true; public int? Rank { get; init; } public bool IsPersonalBest { get; init; } @@ -751,7 +745,7 @@ public MenuItem[] ContextMenuItems List items = new List(); if (score.Mods.Length > 0) - items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => SelectedMods.Value = score.Mods.Where(m => isValidMod.Invoke(m)).ToArray())); + items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => SelectedMods.Value = score.Mods.Where(m => IsValidMod.Invoke(m)).ToArray())); if (score.Files.Count <= 0) return items.ToArray(); From 861b5465628cff64ca1efed3883d0978725bb61c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Jul 2024 10:45:03 +0200 Subject: [PATCH 189/266] Add vague test coverage --- osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index 36e256b9202f..91df38feb9be 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -115,6 +115,7 @@ public bool HandleRequest(APIRequest request, APIUser localUser, BeatmapManager MaxCombo = 1000, TotalScore = 1000000, User = new APIUser { Username = "best user" }, + Mods = [new APIMod { Acronym = @"DT" }], Statistics = new Dictionary() }, new MultiplayerScore From 2ff0a89b4fda97b4fb4b6a634376b5ac4b629b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Jul 2024 10:59:21 +0200 Subject: [PATCH 190/266] Convert into auto-property even more --- .../DailyChallenge/DailyChallengeLeaderboard.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs index f332e717c103..c9152393e771 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs @@ -28,18 +28,12 @@ public partial class DailyChallengeLeaderboard : CompositeDrawable private readonly Bindable userBestScore = new Bindable(); public Bindable> SelectedMods = new Bindable>(); - private Func isValidMod = _ => true; - /// /// A function determining whether each mod in the score can be selected. /// A return value of means that the mod can be selected in the current context. /// A return value of means that the mod cannot be selected in the current context. /// - public Func IsValidMod - { - get => isValidMod; - set => isValidMod = value ?? throw new ArgumentNullException(nameof(value)); - } + public Func IsValidMod { get; set; } = _ => true; public Action? PresentScore { get; init; } @@ -170,7 +164,7 @@ public void RefetchScores() IsPersonalBest = s.UserID == api.LocalUser.Value.Id, Action = () => PresentScore?.Invoke(s.OnlineID), SelectedMods = { BindTarget = SelectedMods }, - IsValidMod = isValidMod, + IsValidMod = IsValidMod, }), loaded => { scoreFlow.Clear(); @@ -190,7 +184,7 @@ public void RefetchScores() IsPersonalBest = true, Action = () => PresentScore?.Invoke(userBest.OnlineID), SelectedMods = { BindTarget = SelectedMods }, - IsValidMod = isValidMod, + IsValidMod = IsValidMod, }); } From 5ec46a79b4d002f78b14f549998ae7fef447bc85 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Mon, 29 Jul 2024 17:50:23 +0800 Subject: [PATCH 191/266] Only create a new drawable object when the background is needed --- osu.Game/Overlays/Chat/ChatLine.cs | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 81b63d3380f0..486beb58b704 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -21,7 +21,6 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osuTK.Graphics; -using Message = osu.Game.Online.Chat.Message; namespace osu.Game.Overlays.Chat { @@ -118,6 +117,11 @@ private void load(OsuConfigManager configManager) InternalChild = new GridContainer { + Margin = new MarginPadding + { + Horizontal = 10, + Vertical = 1, + }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, @@ -275,17 +279,23 @@ private void updateTimestamp() private void updateBackground() { - if (background == null) + if (alteringBackground) { - AddInternal(background = new Box + if (background?.IsAlive != true) { - BypassAutoSizeAxes = Axes.Both, - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }); - } + AddInternal(background = new Circle + { + MaskingSmoothness = 2.5f, + Depth = float.MaxValue, + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }); + } - background.Alpha = alteringBackground ? 0.04f : 0f; + background.Alpha = 0.04f; + } + else + background?.Expire(); } } } From 9b96bd1d730ac95456650c530ba6d6a4afeb6d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Jul 2024 11:53:06 +0200 Subject: [PATCH 192/266] Force exit to main menu when presenting scores from within playlists / multiplayer - Closes https://github.com/ppy/osu/issues/29152 - Partially reverts https://github.com/ppy/osu/pull/29097 - Reopens https://github.com/ppy/osu/issues/26666 When testing I failed to predict that in multiplayer there can be a different beatmap in the playlist queue. If this is the case, `PresentScore()` will exit out to `Multiplayer`, whose `RoomSubScreen` will update the selected item - and thus, the global beatmap - to the next item in queue, at which point trying to play games with "not touching the global beatmap bindable if we don't need to" fail to work, because the bindable *must* be touched for correct operation, yet it cannot (because `OnlinePlayScreen`s disable it). I'm not sure what the fix is here: - making replay player somehow independent of the global beatmap? - not exiting out to multiplayer, but instead doing the present from the results screen itself? if so, then how to ensure the screen stack can't overflow to infinity? so I'm just reverting the broken part. The daily challenge part is left in because as is it should not cause issues. --- osu.Game/OsuGame.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 2195576be11b..53b2fd59042b 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -63,7 +63,6 @@ using osu.Game.Screens.Edit; using osu.Game.Screens.Footer; using osu.Game.Screens.Menu; -using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.DailyChallenge; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.Play; @@ -757,11 +756,13 @@ public void PresentScore(IScoreInfo score, ScorePresentType presentType = ScoreP // As a special case, if the beatmap and ruleset already match, allow immediately displaying the score from song select. // This is guaranteed to not crash, and feels better from a user's perspective (ie. if they are clicking a score in the // song select leaderboard). - // Similar exemptions are made here for online flows where there are good chances that beatmap and ruleset match - // (playlists / multiplayer / daily challenge). + // Similar exemptions are made here for daily challenge where it is guaranteed that beatmap and ruleset match. + // `OnlinePlayScreen` is excluded because when resuming back to it, + // `RoomSubScreen` changes the global beatmap to the next playlist item on resume, + // which may not match the score, and thus crash. IEnumerable validScreens = Beatmap.Value.BeatmapInfo.Equals(databasedBeatmap) && Ruleset.Value.Equals(databasedScore.ScoreInfo.Ruleset) - ? new[] { typeof(SongSelect), typeof(OnlinePlayScreen), typeof(DailyChallenge) } + ? new[] { typeof(SongSelect), typeof(DailyChallenge) } : Array.Empty(); PerformFromScreen(screen => From 90fdf5599fbb6943ef62b2fd5eafe66d954e7bc2 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Mon, 29 Jul 2024 18:14:07 +0800 Subject: [PATCH 193/266] Revert changes --- osu.Game/Overlays/Mods/ModPresetRow.cs | 3 ++- osu.Game/Overlays/Mods/ModPresetTooltip.cs | 13 +++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModPresetRow.cs b/osu.Game/Overlays/Mods/ModPresetRow.cs index 8614806085f7..4829e93b871c 100644 --- a/osu.Game/Overlays/Mods/ModPresetRow.cs +++ b/osu.Game/Overlays/Mods/ModPresetRow.cs @@ -24,7 +24,8 @@ public ModPresetRow(Mod mod) { new FillFlowContainer { - AutoSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Direction = FillDirection.Horizontal, Spacing = new Vector2(7), Children = new Drawable[] diff --git a/osu.Game/Overlays/Mods/ModPresetTooltip.cs b/osu.Game/Overlays/Mods/ModPresetTooltip.cs index 768feb075666..ec81aa7ceb60 100644 --- a/osu.Game/Overlays/Mods/ModPresetTooltip.cs +++ b/osu.Game/Overlays/Mods/ModPresetTooltip.cs @@ -23,7 +23,8 @@ public partial class ModPresetTooltip : VisibilityContainer, ITooltip public ModPresetTooltip(OverlayColourProvider colourProvider) { - AutoSizeAxes = Axes.Both; + Width = 250; + AutoSizeAxes = Axes.Y; Masking = true; CornerRadius = 7; @@ -37,8 +38,8 @@ public ModPresetTooltip(OverlayColourProvider colourProvider) }, Content = new FillFlowContainer { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Left = 10, Right = 10, Top = 5, Bottom = 5 }, Spacing = new Vector2(7), Children = new[] @@ -65,11 +66,7 @@ public void SetContent(ModPreset preset) lastPreset = preset; Content.RemoveAll(d => d is ModPresetRow, true); - Content.AddRange(preset.Mods.AsOrdered().Select(mod => new ModPresetRow(mod) - { - RelativeSizeAxes = Axes.None, - AutoSizeAxes = Axes.Both, - })); + Content.AddRange(preset.Mods.AsOrdered().Select(mod => new ModPresetRow(mod))); } protected override void PopIn() => this.FadeIn(transition_duration, Easing.OutQuint); From 8f8668111077cbb75a29370a4735da60e79cba2e Mon Sep 17 00:00:00 2001 From: jkh675 Date: Mon, 29 Jul 2024 18:29:44 +0800 Subject: [PATCH 194/266] Replace `OsuSpriteText` with `TextFlowContainer` --- osu.Game/Overlays/Mods/ModPresetTooltip.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModPresetTooltip.cs b/osu.Game/Overlays/Mods/ModPresetTooltip.cs index ec81aa7ceb60..6dafe85d89ec 100644 --- a/osu.Game/Overlays/Mods/ModPresetTooltip.cs +++ b/osu.Game/Overlays/Mods/ModPresetTooltip.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osuTK; @@ -19,7 +18,7 @@ public partial class ModPresetTooltip : VisibilityContainer, ITooltip private const double transition_duration = 200; - private readonly OsuSpriteText descriptionText; + private readonly TextFlowContainer descriptionText; public ModPresetTooltip(OverlayColourProvider colourProvider) { @@ -44,11 +43,11 @@ public ModPresetTooltip(OverlayColourProvider colourProvider) Spacing = new Vector2(7), Children = new[] { - descriptionText = new OsuSpriteText + descriptionText = new TextFlowContainer(f => { - Font = OsuFont.GetFont(weight: FontWeight.Regular), - Colour = colourProvider.Content1, - }, + f.Font = OsuFont.GetFont(weight: FontWeight.Regular); + f.Colour = colourProvider.Content1; + }) } } }; From f1a84a5111748a59ee72973cd379850c199751d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Jul 2024 12:52:11 +0200 Subject: [PATCH 195/266] Fix mods persisting after watching replay from daily challenge screen Closes https://github.com/ppy/osu/issues/29133. Hope I can be forgiven for no tests. I had a brief try but writing them is going to take hours. --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 4b4e4a7a62bc..322d855cd360 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -454,6 +454,9 @@ public override void OnResuming(ScreenTransitionEvent e) { base.OnResuming(e); applyLoopingToTrack(); + // re-apply mods as they may have been changed by a child screen + // (one known instance of this is showing a replay). + updateMods(); } public override void OnSuspending(ScreenTransitionEvent e) From c142adf926795b6714311657953edcba8c7ad9d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2024 19:58:19 +0900 Subject: [PATCH 196/266] Fix online status not persisting correctly Regressed at some point. I don't see much reason not to link the bindable directly with config. It seems to work as you'd expect. Tested with logout (resets to "Online") and connection failure (persists). Closes https://github.com/ppy/osu/issues/29173. --- osu.Game/Online/API/APIAccess.cs | 7 +++---- osu.Game/Overlays/Login/LoginPanel.cs | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 0cf344ecafd9..c02ca1bf5e9d 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -118,12 +118,11 @@ public APIAccess(OsuGameBase game, OsuConfigManager config, EndpointConfiguratio u.OldValue?.Activity.UnbindFrom(activity); u.NewValue.Activity.BindTo(activity); - if (u.OldValue != null) - localUserStatus.UnbindFrom(u.OldValue.Status); - localUserStatus.BindTo(u.NewValue.Status); + u.OldValue?.Status.UnbindFrom(localUserStatus); + u.NewValue.Status.BindTo(localUserStatus); }, true); - localUserStatus.BindValueChanged(val => configStatus.Value = val.NewValue); + localUserStatus.BindTo(configStatus); var thread = new Thread(run) { diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs index cb642f9b72a4..84bd0c36b953 100644 --- a/osu.Game/Overlays/Login/LoginPanel.cs +++ b/osu.Game/Overlays/Login/LoginPanel.cs @@ -157,6 +157,7 @@ private void onlineStateChanged(ValueChangedEvent state) => Schedule(( }, }; + updateDropdownCurrent(status.Value); dropdown.Current.BindValueChanged(action => { switch (action.NewValue) From 11265538c484fa22a23c49dc994faac96d8a2bca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2024 20:02:18 +0900 Subject: [PATCH 197/266] Reset online status on logout --- osu.Game/Online/API/APIAccess.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index c02ca1bf5e9d..716d1e44669d 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -599,6 +599,7 @@ public void Logout() password = null; SecondFactorCode = null; authentication.Clear(); + configStatus.Value = UserStatus.Online; // Scheduled prior to state change such that the state changed event is invoked with the correct user and their friends present Schedule(() => From d51a53b051d46c58179f3774d0ea195f4705368c Mon Sep 17 00:00:00 2001 From: jkh675 Date: Mon, 29 Jul 2024 19:08:14 +0800 Subject: [PATCH 198/266] Preventing the mod icon being squashed up --- osu.Game/Overlays/Mods/ModPresetTooltip.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModPresetTooltip.cs b/osu.Game/Overlays/Mods/ModPresetTooltip.cs index 6dafe85d89ec..6ffcfca1e0a7 100644 --- a/osu.Game/Overlays/Mods/ModPresetTooltip.cs +++ b/osu.Game/Overlays/Mods/ModPresetTooltip.cs @@ -48,6 +48,10 @@ public ModPresetTooltip(OverlayColourProvider colourProvider) f.Font = OsuFont.GetFont(weight: FontWeight.Regular); f.Colour = colourProvider.Content1; }) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } } } }; From 8b96b0b9e497f93c5860ad1366b1d7db3363c324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Jul 2024 13:19:01 +0200 Subject: [PATCH 199/266] Add logging when starting and stopping watch operations in online metadata client For future use with debugging issues like https://github.com/ppy/osu/issues/29138, hopefully. --- osu.Game/Online/Metadata/OnlineMetadataClient.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index 911b13ecd8d1..a3041c6753b0 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -215,6 +215,7 @@ public override async Task BeginWatchingUserPresence() Debug.Assert(connection != null); await connection.InvokeAsync(nameof(IMetadataServer.BeginWatchingUserPresence)).ConfigureAwait(false); Schedule(() => isWatchingUserPresence.Value = true); + Logger.Log($@"{nameof(OnlineMetadataClient)} began watching user presence", LoggingTarget.Network); } public override async Task EndWatchingUserPresence() @@ -228,6 +229,7 @@ public override async Task EndWatchingUserPresence() Schedule(() => userStates.Clear()); Debug.Assert(connection != null); await connection.InvokeAsync(nameof(IMetadataServer.EndWatchingUserPresence)).ConfigureAwait(false); + Logger.Log($@"{nameof(OnlineMetadataClient)} stopped watching user presence", LoggingTarget.Network); } finally { @@ -247,7 +249,9 @@ public override async Task BeginWatchingMultipla throw new OperationCanceledException(); Debug.Assert(connection != null); - return await connection.InvokeAsync(nameof(IMetadataServer.BeginWatchingMultiplayerRoom), id).ConfigureAwait(false); + var result = await connection.InvokeAsync(nameof(IMetadataServer.BeginWatchingMultiplayerRoom), id).ConfigureAwait(false); + Logger.Log($@"{nameof(OnlineMetadataClient)} began watching multiplayer room with ID {id}", LoggingTarget.Network); + return result; } public override async Task EndWatchingMultiplayerRoom(long id) @@ -257,6 +261,7 @@ public override async Task EndWatchingMultiplayerRoom(long id) Debug.Assert(connection != null); await connection.InvokeAsync(nameof(IMetadataServer.EndWatchingMultiplayerRoom), id).ConfigureAwait(false); + Logger.Log($@"{nameof(OnlineMetadataClient)} stopped watching multiplayer room with ID {id}", LoggingTarget.Network); } public override async Task DisconnectRequested() From 997b3eb498ecdd689c4f68205bb90fdbd211a0a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2024 20:16:41 +0900 Subject: [PATCH 200/266] Fix typos and visuals --- .../Visual/Online/TestSceneDrawableChannel.cs | 2 +- osu.Game/Overlays/Chat/ChatLine.cs | 120 +++++++++--------- osu.Game/Overlays/Chat/DrawableChannel.cs | 6 +- 3 files changed, 65 insertions(+), 63 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs index 795d49adad64..bb73a458a3fa 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs @@ -115,7 +115,7 @@ public void TestBackgroundAltering() AddRepeatStep("check background", () => { // +1 because the day separator take one index - Assert.AreEqual((checkCount + 1) % 2 == 0, drawableChannel.ChildrenOfType().ToList()[checkCount].AlteringBackground); + Assert.AreEqual((checkCount + 1) % 2 == 0, drawableChannel.ChildrenOfType().ToList()[checkCount].AlternatingBackground); checkCount++; }, 10); } diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 486beb58b704..e7be7e7814e0 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -70,14 +70,14 @@ public Message Message private Drawable? background; - private bool alteringBackground; + private bool alternatingBackground; - public bool AlteringBackground + public bool AlternatingBackground { - get => alteringBackground; + get => alternatingBackground; set { - alteringBackground = value; + alternatingBackground = value; updateBackground(); } } @@ -115,53 +115,70 @@ private void load(OsuConfigManager configManager) configManager.BindWith(OsuSetting.Prefer24HourTime, prefer24HourTime); prefer24HourTime.BindValueChanged(_ => updateTimestamp()); - InternalChild = new GridContainer + InternalChildren = new[] { - Margin = new MarginPadding + background = new Container { - Horizontal = 10, - Vertical = 1, - }, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Absolute, Spacing + UsernameWidth + Spacing), - new Dimension(), + Masking = true, + Blending = BlendingParameters.Additive, + CornerRadius = 4, + RelativeSizeAxes = Axes.Both, + Child = new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + }, }, - Content = new[] + new GridContainer { - new Drawable[] + Margin = new MarginPadding { - drawableTimestamp = new OsuSpriteText - { - Shadow = false, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: FontSize * 0.75f, weight: FontWeight.SemiBold, fixedWidth: true), - AlwaysPresent = true, - }, - drawableUsername = new DrawableChatUsername(message.Sender) + Horizontal = 10, + Vertical = 1, + }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, Spacing + UsernameWidth + Spacing), + new Dimension(), + }, + Content = new[] + { + new Drawable[] { - Width = UsernameWidth, - FontSize = FontSize, - AutoSizeAxes = Axes.Y, - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - Margin = new MarginPadding { Horizontal = Spacing }, - AccentColour = UsernameColour, - Inverted = !string.IsNullOrEmpty(message.Sender.Colour), + drawableTimestamp = new OsuSpriteText + { + Shadow = false, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: FontSize * 0.75f, weight: FontWeight.SemiBold, fixedWidth: true), + AlwaysPresent = true, + }, + drawableUsername = new DrawableChatUsername(message.Sender) + { + Width = UsernameWidth, + FontSize = FontSize, + AutoSizeAxes = Axes.Y, + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + Margin = new MarginPadding { Horizontal = Spacing }, + AccentColour = UsernameColour, + Inverted = !string.IsNullOrEmpty(message.Sender.Colour), + }, + drawableContentFlow = new LinkFlowContainer(styleMessageContent) + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + } }, - drawableContentFlow = new LinkFlowContainer(styleMessageContent) - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - } - }, + } } }; + + updateBackground(); } protected override void LoadComplete() @@ -279,23 +296,8 @@ private void updateTimestamp() private void updateBackground() { - if (alteringBackground) - { - if (background?.IsAlive != true) - { - AddInternal(background = new Circle - { - MaskingSmoothness = 2.5f, - Depth = float.MaxValue, - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }); - } - - background.Alpha = 0.04f; - } - else - background?.Expire(); + if (background != null) + background.Alpha = alternatingBackground ? 0.03f : 0; } } } diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index b6b89c4201c2..f5dd5a24f268 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -104,13 +104,13 @@ private void processMessageHighlighting() => SchedulerAfterChildren.AddOnce(() = highlightedMessage.Value = null; }); - private void processChatlineBackgroundAltering() + private void updateBackgroundAlternating() { for (int i = 0; i < ChatLineFlow.Count; i++) { if (ChatLineFlow[i] is ChatLine chatline) { - chatline.AlteringBackground = i % 2 == 0; + chatline.AlternatingBackground = i % 2 == 0; } } } @@ -169,7 +169,7 @@ private void newMessagesArrived(IEnumerable newMessages) => Schedule(() scroll.ScrollToEnd(); processMessageHighlighting(); - processChatlineBackgroundAltering(); + updateBackgroundAlternating(); }); private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => From 5bc02cc1c69e104dca9a44439218d77a91d93c55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2024 20:25:02 +0900 Subject: [PATCH 201/266] Fix background alternating not updating on message removal --- osu.Game/Overlays/Chat/ChatLine.cs | 4 ++++ osu.Game/Overlays/Chat/DrawableChannel.cs | 23 +++++++++++------------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index e7be7e7814e0..87e1f5699bad 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -77,6 +77,9 @@ public bool AlternatingBackground get => alternatingBackground; set { + if (alternatingBackground == value) + return; + alternatingBackground = value; updateBackground(); } @@ -122,6 +125,7 @@ private void load(OsuConfigManager configManager) Masking = true, Blending = BlendingParameters.Additive, CornerRadius = 4, + Alpha = 0, RelativeSizeAxes = Axes.Both, Child = new Box { diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index f5dd5a24f268..6b3acaa226c4 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -84,6 +84,17 @@ protected override void LoadComplete() highlightedMessage.BindValueChanged(_ => processMessageHighlighting(), true); } + protected override void Update() + { + base.Update(); + + for (int i = 0; i < ChatLineFlow.Count; i++) + { + if (ChatLineFlow[i] is ChatLine chatline) + chatline.AlternatingBackground = i % 2 == 0; + } + } + /// /// Processes any pending message in . /// @@ -104,17 +115,6 @@ private void processMessageHighlighting() => SchedulerAfterChildren.AddOnce(() = highlightedMessage.Value = null; }); - private void updateBackgroundAlternating() - { - for (int i = 0; i < ChatLineFlow.Count; i++) - { - if (ChatLineFlow[i] is ChatLine chatline) - { - chatline.AlternatingBackground = i % 2 == 0; - } - } - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -169,7 +169,6 @@ private void newMessagesArrived(IEnumerable newMessages) => Schedule(() scroll.ScrollToEnd(); processMessageHighlighting(); - updateBackgroundAlternating(); }); private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => From 76cd2df6999866dde279387dfa9efe1ea4f04fc3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2024 20:43:09 +0900 Subject: [PATCH 202/266] Add ability to test daily challenge carousel items when hidden --- .../DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs | 2 ++ .../DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs | 2 ++ .../DailyChallenge/TestSceneDailyChallengeTotalsDisplay.cs | 1 + 3 files changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs index 631aafb58f56..086f3ce1746a 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs @@ -50,6 +50,8 @@ public void TestBasicAppearance() breakdown.Height = height; }); + AddToggleStep("toggle visible", v => breakdown.Alpha = v ? 1 : 0); + AddStep("set initial data", () => breakdown.SetInitialCounts([1, 4, 9, 16, 25, 36, 49, 36, 25, 16, 9, 4, 1])); AddStep("add new score", () => { diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs index 9e21214c1157..baa1eb8318a4 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs @@ -55,6 +55,8 @@ public void TestBasicAppearance() if (ring.IsNotNull()) ring.Height = height; }); + AddToggleStep("toggle visible", v => ring.Alpha = v ? 1 : 0); + AddStep("just started", () => { room.Value.StartDate.Value = DateTimeOffset.Now.AddMinutes(-1); diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTotalsDisplay.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTotalsDisplay.cs index ba5a0989d493..ae212f52127b 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTotalsDisplay.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTotalsDisplay.cs @@ -49,6 +49,7 @@ public void TestBasicAppearance() if (totals.IsNotNull()) totals.Height = height; }); + AddToggleStep("toggle visible", v => totals.Alpha = v ? 1 : 0); AddStep("set counts", () => totals.SetInitialCounts(totalPassCount: 9650, cumulativeTotalScore: 10_000_000_000)); From 5a1002c1a07b2108f73dad4731ecd506a82c6445 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2024 20:43:34 +0900 Subject: [PATCH 203/266] Ensure score breakdown doesn't spam scores when not visible --- .../DailyChallengeScoreBreakdown.cs | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs index 12401061a3c8..79ad77831bea 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs @@ -74,30 +74,34 @@ public void AddNewScore(NewScoreEvent newScoreEvent) { int targetBin = (int)Math.Clamp(Math.Floor((float)newScoreEvent.TotalScore / 100000), 0, bin_count - 1); bins[targetBin] += 1; - updateCounts(); - var text = new OsuSpriteText - { - Text = newScoreEvent.TotalScore.ToString(@"N0"), - Anchor = Anchor.TopCentre, - Origin = Anchor.BottomCentre, - Font = OsuFont.Default.With(size: 30), - RelativePositionAxes = Axes.X, - X = (targetBin + 0.5f) / bin_count - 0.5f, - Alpha = 0, - }; - AddInternal(text); + Scheduler.AddOnce(updateCounts); - Scheduler.AddDelayed(() => + if (Alpha > 0) { - float startY = ToLocalSpace(barsContainer[targetBin].CircularBar.ScreenSpaceDrawQuad.TopLeft).Y; - text.FadeInFromZero() - .ScaleTo(new Vector2(0.8f), 500, Easing.OutElasticHalf) - .MoveToY(startY) - .MoveToOffset(new Vector2(0, -50), 2500, Easing.OutQuint) - .FadeOut(2500, Easing.OutQuint) - .Expire(); - }, 150); + var text = new OsuSpriteText + { + Text = newScoreEvent.TotalScore.ToString(@"N0"), + Anchor = Anchor.TopCentre, + Origin = Anchor.BottomCentre, + Font = OsuFont.Default.With(size: 30), + RelativePositionAxes = Axes.X, + X = (targetBin + 0.5f) / bin_count - 0.5f, + Alpha = 0, + }; + AddInternal(text); + + Scheduler.AddDelayed(() => + { + float startY = ToLocalSpace(barsContainer[targetBin].CircularBar.ScreenSpaceDrawQuad.TopLeft).Y; + text.FadeInFromZero() + .ScaleTo(new Vector2(0.8f), 500, Easing.OutElasticHalf) + .MoveToY(startY) + .MoveToOffset(new Vector2(0, -50), 2500, Easing.OutQuint) + .FadeOut(2500, Easing.OutQuint) + .Expire(); + }, 150); + } } public void SetInitialCounts(long[] counts) From 05056f0e8a107da1b7a54208f3b8aee9139679d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2024 20:37:52 +0900 Subject: [PATCH 204/266] Remove no longer required `AlwaysPresent` definition This also reverts commit 7fedfd368c83767846e947372e9fba03e07f6ceb as no-longer-necessary. --- .../OnlinePlay/DailyChallenge/DailyChallengeCarousel.cs | 1 - .../OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs | 3 --- 2 files changed, 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeCarousel.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeCarousel.cs index a9f9a5cd78f5..09c0c3f01746 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeCarousel.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeCarousel.cs @@ -50,7 +50,6 @@ public override void Add(Drawable drawable) { drawable.RelativeSizeAxes = Axes.Both; drawable.Size = Vector2.One; - drawable.AlwaysPresent = true; drawable.Alpha = 0; base.Add(drawable); diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs index 79ad77831bea..b35379e1266b 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs @@ -27,9 +27,6 @@ public partial class DailyChallengeScoreBreakdown : CompositeDrawable private FillFlowContainer barsContainer = null!; - // we're always present so that we can update while hidden, but we don't want tooltips to be displayed, therefore directly use alpha comparison here. - public override bool PropagatePositionalInputSubTree => base.PropagatePositionalInputSubTree && Alpha > 0; - private const int bin_count = MultiplayerPlaylistItemStats.TOTAL_SCORE_DISTRIBUTION_BINS; private long[] bins = new long[bin_count]; From 7afcd728723b94996112bb34a6de6a96c7041dc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2024 20:58:42 +0900 Subject: [PATCH 205/266] Fix potentially too many scores displaying in breakdown while in gameplay --- .../TestSceneDailyChallengeScoreBreakdown.cs | 33 +++++++++++++++-- .../DailyChallengeScoreBreakdown.cs | 37 ++++++++++++++++--- 2 files changed, 61 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs index 086f3ce1746a..b04696adedc2 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs @@ -6,6 +6,7 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; @@ -20,11 +21,11 @@ public partial class TestSceneDailyChallengeScoreBreakdown : OsuTestScene [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); - [Test] - public void TestBasicAppearance() - { - DailyChallengeScoreBreakdown breakdown = null!; + private DailyChallengeScoreBreakdown breakdown = null!; + [SetUpSteps] + public void SetUpSteps() + { AddStep("create content", () => Children = new Drawable[] { new Box @@ -53,6 +54,11 @@ public void TestBasicAppearance() AddToggleStep("toggle visible", v => breakdown.Alpha = v ? 1 : 0); AddStep("set initial data", () => breakdown.SetInitialCounts([1, 4, 9, 16, 25, 36, 49, 36, 25, 16, 9, 4, 1])); + } + + [Test] + public void TestBasicAppearance() + { AddStep("add new score", () => { var ev = new NewScoreEvent(1, new APIUser @@ -67,5 +73,24 @@ public void TestBasicAppearance() AddStep("set user score", () => breakdown.UserBestScore.Value = new MultiplayerScore { TotalScore = RNG.Next(1_000_000) }); AddStep("unset user score", () => breakdown.UserBestScore.Value = null); } + + [Test] + public void TestMassAdd() + { + AddStep("add 1000 scores at once", () => + { + for (int i = 0; i < 1000; i++) + { + var ev = new NewScoreEvent(1, new APIUser + { + Id = 2, + Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, RNG.Next(1_000_000), null); + + breakdown.AddNewScore(ev); + } + }); + } } } diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs index b35379e1266b..71ab73b53566 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -67,18 +68,39 @@ protected override void LoadComplete() }); } + private readonly Queue newScores = new Queue(); + public void AddNewScore(NewScoreEvent newScoreEvent) { - int targetBin = (int)Math.Clamp(Math.Floor((float)newScoreEvent.TotalScore / 100000), 0, bin_count - 1); - bins[targetBin] += 1; + newScores.Enqueue(newScoreEvent); + + // ensure things don't get too out-of-hand. + if (newScores.Count > 25) + { + bins[getTargetBin(newScores.Dequeue())] += 1; + Scheduler.AddOnce(updateCounts); + } + } + + private double lastScoreDisplay; - Scheduler.AddOnce(updateCounts); + protected override void Update() + { + base.Update(); - if (Alpha > 0) + if (Time.Current - lastScoreDisplay > 150 && newScores.TryDequeue(out var newScore)) { + if (lastScoreDisplay < Time.Current) + lastScoreDisplay = Time.Current; + + int targetBin = getTargetBin(newScore); + bins[targetBin] += 1; + + updateCounts(); + var text = new OsuSpriteText { - Text = newScoreEvent.TotalScore.ToString(@"N0"), + Text = newScore.TotalScore.ToString(@"N0"), Anchor = Anchor.TopCentre, Origin = Anchor.BottomCentre, Font = OsuFont.Default.With(size: 30), @@ -98,6 +120,8 @@ public void AddNewScore(NewScoreEvent newScoreEvent) .FadeOut(2500, Easing.OutQuint) .Expire(); }, 150); + + lastScoreDisplay = Time.Current; } } @@ -110,6 +134,9 @@ public void SetInitialCounts(long[] counts) updateCounts(); } + private static int getTargetBin(NewScoreEvent score) => + (int)Math.Clamp(Math.Floor((float)score.TotalScore / 100000), 0, bin_count - 1); + private void updateCounts() { long max = Math.Max(bins.Max(), 1); From b46f3c97da76f767c455e42dc73a16a0ca73e400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Jul 2024 14:20:47 +0200 Subject: [PATCH 206/266] Add notification on daily challenge conclusion & start of new one Because I wish to stop seeing "DAILY CHALLENGE WHERE" every day on #general. The notifications are constrained to the daily challenge screen only to not spam users who may not care. --- .../DailyChallenge/TestSceneDailyChallenge.cs | 73 +++++++++++++++++++ .../DailyChallenge/DailyChallenge.cs | 33 +++++++++ .../NewDailyChallengeNotification.cs | 44 +++++++++++ .../Visual/Metadata/TestMetadataClient.cs | 2 +- 4 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index cd09a1d20fdf..4a5f452ed17b 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -4,16 +4,32 @@ using System; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Game.Online.API; +using osu.Game.Online.Metadata; using osu.Game.Online.Rooms; +using osu.Game.Overlays; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual.Metadata; using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.DailyChallenge { public partial class TestSceneDailyChallenge : OnlinePlayTestScene { + [Cached(typeof(MetadataClient))] + private TestMetadataClient metadataClient = new TestMetadataClient(); + + [Cached(typeof(INotificationOverlay))] + private NotificationOverlay notificationOverlay = new NotificationOverlay(); + + [BackgroundDependencyLoader] + private void load() + { + base.Content.Add(notificationOverlay); + } + [Test] public void TestDailyChallenge() { @@ -36,5 +52,62 @@ public void TestDailyChallenge() AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room))); } + + [Test] + public void TestNotifications() + { + var room = new Room + { + RoomID = { Value = 1234 }, + Name = { Value = "Daily Challenge: June 4, 2024" }, + Playlist = + { + new PlaylistItem(CreateAPIBeatmapSet().Beatmaps.First()) + { + RequiredMods = [new APIMod(new OsuModTraceable())], + AllowedMods = [new APIMod(new OsuModDoubleTime())] + } + }, + EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, + Category = { Value = RoomCategory.DailyChallenge } + }; + + AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); + AddStep("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1234 }); + AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room))); + AddStep("daily challenge ended", () => metadataClient.DailyChallengeInfo.Value = null); + AddStep("install custom handler", () => + { + ((DummyAPIAccess)API).HandleRequest = req => + { + switch (req) + { + case GetRoomRequest r: + { + r.TriggerSuccess(new Room + { + RoomID = { Value = 1235, }, + Name = { Value = "Daily Challenge: June 5, 2024" }, + Playlist = + { + new PlaylistItem(CreateAPIBeatmapSet().Beatmaps.First()) + { + RequiredMods = [new APIMod(new OsuModTraceable())], + AllowedMods = [new APIMod(new OsuModDoubleTime())] + } + }, + EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, + Category = { Value = RoomCategory.DailyChallenge } + }); + return true; + } + + default: + return false; + } + }; + }); + AddStep("next daily challenge started", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1235 }); + } } } diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 4b4e4a7a62bc..d176f36162e0 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -30,6 +30,7 @@ using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.OnlinePlay.Components; @@ -54,6 +55,7 @@ public partial class DailyChallenge : OsuScreen, IPreviewTrackOwner private readonly Bindable> userMods = new Bindable>(Array.Empty()); private readonly IBindable apiState = new Bindable(); + private readonly IBindable dailyChallengeInfo = new Bindable(); private OnlinePlayScreenWaveContainer waves = null!; private DailyChallengeLeaderboard leaderboard = null!; @@ -65,6 +67,8 @@ public partial class DailyChallenge : OsuScreen, IPreviewTrackOwner private DailyChallengeTotalsDisplay totals = null!; private DailyChallengeEventFeed feed = null!; + private SimpleNotification? waitForNextChallengeNotification; + [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); @@ -98,6 +102,9 @@ public partial class DailyChallenge : OsuScreen, IPreviewTrackOwner [Resolved] private PreviewTrackManager previewTrackManager { get; set; } = null!; + [Resolved] + private INotificationOverlay? notificationOverlay { get; set; } + public override bool DisallowExternalBeatmapRulesetChanges => true; public override bool? ApplyModTrackAdjustments => true; @@ -336,6 +343,7 @@ [new MatchChatDisplay(room) { RelativeSizeAxes = Axes.Both }] } metadataClient.MultiplayerRoomScoreSet += onRoomScoreSet; + dailyChallengeInfo.BindTo(metadataClient.DailyChallengeInfo); ((IBindable)breakdown.UserBestScore).BindTo(leaderboard.UserBestScore); } @@ -388,6 +396,8 @@ protected override void LoadComplete() apiState.BindTo(API.State); apiState.BindValueChanged(onlineStateChanged, true); + + dailyChallengeInfo.BindValueChanged(dailyChallengeChanged); } private void trySetDailyChallengeBeatmap() @@ -405,6 +415,29 @@ private void onlineStateChanged(ValueChangedEvent state) => Schedule(( Schedule(forcefullyExit); }); + private void dailyChallengeChanged(ValueChangedEvent change) + { + if (change.OldValue?.RoomID == room.RoomID.Value && change.NewValue == null) + { + notificationOverlay?.Post(waitForNextChallengeNotification = new SimpleNotification + { + Text = "Today's daily challenge has concluded. Thanks for playing! The next one should appear in a few minutes." + }); + } + + if (change.NewValue != null && change.NewValue.Value.RoomID != room.RoomID.Value) + { + var roomRequest = new GetRoomRequest(change.NewValue.Value.RoomID); + + roomRequest.Success += room => + { + waitForNextChallengeNotification?.Close(false); + notificationOverlay?.Post(new NewDailyChallengeNotification(room)); + }; + API.Queue(roomRequest); + } + } + private void forcefullyExit() { Logger.Log($"{this} forcefully exiting due to loss of API connection"); diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs new file mode 100644 index 000000000000..36ec8b37a7a8 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs @@ -0,0 +1,44 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Screens; +using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Rooms; +using osu.Game.Overlays.Notifications; +using osu.Game.Screens.Menu; + +namespace osu.Game.Screens.OnlinePlay.DailyChallenge +{ + public partial class NewDailyChallengeNotification : SimpleNotification + { + private readonly Room room; + + private BeatmapCardNano card = null!; + + public NewDailyChallengeNotification(Room room) + { + this.room = room; + } + + [BackgroundDependencyLoader] + private void load(OsuGame? game) + { + Text = "Today's daily challenge is here! Click here to play."; + Content.Add(card = new BeatmapCardNano((APIBeatmapSet)room.Playlist.Single().Beatmap.BeatmapSet!)); + Activated = () => + { + game?.PerformFromScreen(s => s.Push(new DailyChallenge(room)), [typeof(MainMenu)]); + return true; + }; + } + + protected override void Update() + { + base.Update(); + card.Width = Content.DrawWidth; + } + } +} diff --git a/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs index fa64a83352f6..2a0af0b10eb3 100644 --- a/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs +++ b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs @@ -21,7 +21,7 @@ public partial class TestMetadataClient : MetadataClient public override IBindableDictionary UserStates => userStates; private readonly BindableDictionary userStates = new BindableDictionary(); - public override IBindable DailyChallengeInfo => dailyChallengeInfo; + public override Bindable DailyChallengeInfo => dailyChallengeInfo; private readonly Bindable dailyChallengeInfo = new Bindable(); [Resolved] From 1daeb7ebd0cb673c6dd63158c5fe70856ff02f61 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2024 22:19:38 +0900 Subject: [PATCH 207/266] Rename typo in test naming --- osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs index bb73a458a3fa..7b7565b13f6c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs @@ -86,7 +86,7 @@ public void TestDaySeparators() } [Test] - public void TestBackgroundAltering() + public void TestBackgroundAlternating() { var localUser = new APIUser { From b77a10b6db27757ee6e8bdd09a5eebccd7557fc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Jul 2024 15:28:52 +0200 Subject: [PATCH 208/266] Fix tests maybe --- .../Visual/DailyChallenge/TestSceneDailyChallenge.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index 4a5f452ed17b..b6dcc82ac1e9 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -76,8 +76,12 @@ public void TestNotifications() AddStep("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1234 }); AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room))); AddStep("daily challenge ended", () => metadataClient.DailyChallengeInfo.Value = null); + + Func? previousHandler = null; + AddStep("install custom handler", () => { + previousHandler = ((DummyAPIAccess)API).HandleRequest; ((DummyAPIAccess)API).HandleRequest = req => { switch (req) @@ -108,6 +112,8 @@ public void TestNotifications() }; }); AddStep("next daily challenge started", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1235 }); + + AddStep("restore previous handler", () => ((DummyAPIAccess)API).HandleRequest = previousHandler); } } } From 621f4dfece39d1323fc209c48293289c992d5b3e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 30 Jul 2024 02:45:12 +0300 Subject: [PATCH 209/266] Enforce new line between X/Y coordinate in editor position inspector --- osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs b/osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs index 2f19888e9ef2..de23147e7bbc 100644 --- a/osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs +++ b/osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs @@ -58,7 +58,8 @@ protected virtual void AddInspectorValues() { case IHasPosition pos: AddHeader("Position"); - AddValue($"x:{pos.X:#,0.##} y:{pos.Y:#,0.##}"); + AddValue($"x:{pos.X:#,0.##}"); + AddValue($"y:{pos.Y:#,0.##}"); break; case IHasXPosition x: From 78417db06d1053cae9288db7afd5a46afa8f5659 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 30 Jul 2024 06:35:09 +0300 Subject: [PATCH 210/266] Remove stray line --- .../Profile/Header/Components/DailyChallengeStreakTooltip.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs index 02be0f2c9973..5f28928665c9 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs @@ -203,7 +203,6 @@ public StreakPiece(LocalisableString title) }, valueText = new OsuSpriteText { - // Colour = colour Font = OsuFont.GetFont(size: 40, weight: FontWeight.Light), } }; From 9868fb4aaa736c6956b549f0317a87e328961dc8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 30 Jul 2024 06:36:02 +0300 Subject: [PATCH 211/266] Remove tier-based colour from the condensed piece to match web --- .../Profile/Header/Components/DailyChallengeStreakDisplay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs index 87f833d1652c..b0f57099f5b5 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs @@ -102,7 +102,6 @@ private void updateDisplay() var statistics = User.Value.User.DailyChallengeStatistics; // dailyStreak.Text = UsersStrings.ShowDailyChallengeUnitDay(statistics.DailyStreakCurrent); dailyStreak.Text = $"{statistics.DailyStreakCurrent}d"; - dailyStreak.Colour = colours.ForRankingTier(DailyChallengeStreakTooltip.TierForDaily(statistics.DailyStreakCurrent)); TooltipContent = new DailyChallengeStreakTooltipData(colourProvider, statistics); } From 8b910e59f68ec871ae3f7f1d7543c14c906ab32d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 30 Jul 2024 06:37:17 +0300 Subject: [PATCH 212/266] Reduce tooltip shadow outline --- .../Profile/Header/Components/DailyChallengeStreakTooltip.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs index 5f28928665c9..a105659ac777 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs @@ -45,7 +45,7 @@ private void load() EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.5f), + Colour = Color4.Black.Opacity(0.25f), Radius = 30f, }; From 33fc6dfaffe694e78ef4fc23c3871ba16eb286a8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 30 Jul 2024 07:05:31 +0300 Subject: [PATCH 213/266] Hide daily challenge display when not selecting osu! Also hide when no user is displayed. --- .../Profile/Header/Components/DailyChallengeStreakDisplay.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs index b0f57099f5b5..a4c62d4357f7 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs @@ -93,9 +93,9 @@ protected override void LoadComplete() private void updateDisplay() { - if (User.Value == null) + if (User.Value == null || User.Value.Ruleset.OnlineID != 0) { - dailyStreak.Text = "-"; + Hide(); return; } @@ -103,6 +103,7 @@ private void updateDisplay() // dailyStreak.Text = UsersStrings.ShowDailyChallengeUnitDay(statistics.DailyStreakCurrent); dailyStreak.Text = $"{statistics.DailyStreakCurrent}d"; TooltipContent = new DailyChallengeStreakTooltipData(colourProvider, statistics); + Show(); } public ITooltip GetCustomTooltip() => new DailyChallengeStreakTooltip(); From 7c3d592a84451a9c2b2ee5a853e43035ed15d458 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 30 Jul 2024 07:04:52 +0300 Subject: [PATCH 214/266] Fix user profile overlay test scene being broke --- .../Visual/Online/TestSceneUserProfileOverlay.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index 937e08cb9703..3bb38f167f2c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Online.API; @@ -24,7 +25,17 @@ public partial class TestSceneUserProfileOverlay : OsuTestScene [SetUpSteps] public void SetUp() { - AddStep("create profile overlay", () => Child = profile = new UserProfileOverlay()); + AddStep("create profile overlay", () => + { + profile = new UserProfileOverlay(); + + Child = new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] { (typeof(UserProfileOverlay), profile) }, + Child = profile, + }; + }); } [Test] @@ -131,6 +142,7 @@ public void TestCustomColourScheme() CountryCode = CountryCode.JP, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", ProfileHue = hue, + PlayMode = "osu", }); return true; } @@ -174,6 +186,7 @@ public void TestCustomColourSchemeWithReload() CountryCode = CountryCode.JP, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", ProfileHue = hue, + PlayMode = "osu", })); int hue2 = 0; @@ -189,6 +202,7 @@ public void TestCustomColourSchemeWithReload() CountryCode = CountryCode.JP, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", ProfileHue = hue2, + PlayMode = "osu", })); } From dca61eb76caca6b063025d39ba4c63d3865d25f6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 30 Jul 2024 07:07:10 +0300 Subject: [PATCH 215/266] Remove no longer used dependency --- .../Profile/Header/Components/DailyChallengeStreakDisplay.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs index a4c62d4357f7..a9d0ab4f01cd 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs @@ -24,9 +24,6 @@ public partial class DailyChallengeStreakDisplay : CompositeDrawable, IHasCustom [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; - [Resolved] - private OsuColour colours { get; set; } = null!; - [BackgroundDependencyLoader] private void load() { From 91dfe4515bbd74d1b0260788c2c1c12517842428 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 30 Jul 2024 08:12:03 +0300 Subject: [PATCH 216/266] Fix daily challenge display showing incorrect statistic --- .../Visual/Online/TestSceneUserProfileDailyChallenge.cs | 1 + .../Profile/Header/Components/DailyChallengeStreakDisplay.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs index e2d26f222cba..c0fb7b49f08a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs @@ -35,6 +35,7 @@ protected override void LoadComplete() AddSliderStep("weekly best", 0, 250, 1, v => update(s => s.WeeklyStreakBest = v)); AddSliderStep("top 10%", 0, 999, 0, v => update(s => s.Top10PercentPlacements = v)); AddSliderStep("top 50%", 0, 999, 0, v => update(s => s.Top50PercentPlacements = v)); + AddSliderStep("playcount", 0, 999, 0, v => update(s => s.PlayCount = v)); AddStep("create", () => { Clear(); diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs index a9d0ab4f01cd..da0e334a4e8e 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs @@ -97,8 +97,8 @@ private void updateDisplay() } var statistics = User.Value.User.DailyChallengeStatistics; - // dailyStreak.Text = UsersStrings.ShowDailyChallengeUnitDay(statistics.DailyStreakCurrent); - dailyStreak.Text = $"{statistics.DailyStreakCurrent}d"; + // dailyStreak.Text = UsersStrings.ShowDailyChallengeUnitDay(statistics.PlayCount); + dailyStreak.Text = $"{statistics.PlayCount}d"; TooltipContent = new DailyChallengeStreakTooltipData(colourProvider, statistics); Show(); } From ae38e66036b62ceb9b3a2b2c8687878ffd5ca710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 30 Jul 2024 08:17:23 +0200 Subject: [PATCH 217/266] Add failing test coverage --- .../Mods/ModDifficultyAdjustTest.cs | 47 +++++++++++++++++-- .../TestSceneModDifficultyAdjustSettings.cs | 29 +++++++++++- 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs index 4101652c4907..e31a3dbdf00c 100644 --- a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs +++ b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs @@ -8,6 +8,8 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; namespace osu.Game.Tests.Mods @@ -105,9 +107,6 @@ public void TestChangedSettingsRevertedToDefault() testMod.ResetSettingsToDefaults(); Assert.That(testMod.DrainRate.Value, Is.Null); - - // ReSharper disable once HeuristicUnreachableCode - // see https://youtrack.jetbrains.com/issue/RIDER-70159. Assert.That(testMod.OverallDifficulty.Value, Is.Null); var applied = applyDifficulty(new BeatmapDifficulty @@ -119,6 +118,48 @@ public void TestChangedSettingsRevertedToDefault() Assert.That(applied.OverallDifficulty, Is.EqualTo(10)); } + [Test] + public void TestDeserializeIncorrectRange() + { + var apiMod = new APIMod + { + Acronym = @"DA", + Settings = new Dictionary + { + [@"circle_size"] = -727, + [@"approach_rate"] = -727, + } + }; + var ruleset = new OsuRuleset(); + + var mod = (OsuModDifficultyAdjust)apiMod.ToMod(ruleset); + + Assert.Multiple(() => + { + Assert.That(mod.CircleSize.Value, Is.GreaterThanOrEqualTo(0).And.LessThanOrEqualTo(11)); + Assert.That(mod.ApproachRate.Value, Is.GreaterThanOrEqualTo(-10).And.LessThanOrEqualTo(11)); + }); + } + + [Test] + public void TestDeserializeNegativeApproachRate() + { + var apiMod = new APIMod + { + Acronym = @"DA", + Settings = new Dictionary + { + [@"approach_rate"] = -9, + } + }; + var ruleset = new OsuRuleset(); + + var mod = (OsuModDifficultyAdjust)apiMod.ToMod(ruleset); + + Assert.That(mod.ApproachRate.Value, Is.GreaterThanOrEqualTo(-10).And.LessThanOrEqualTo(11)); + Assert.That(mod.ApproachRate.Value, Is.EqualTo(-9)); + } + /// /// Applies a to the mod and returns a new /// representing the result if the mod were applied to a fresh instance. diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs index 307f436f84c7..b40d0b10d252 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs @@ -70,7 +70,7 @@ public void TestFollowsBeatmapDefaultsVisually() } [Test] - public void TestOutOfRangeValueStillApplied() + public void TestValueAboveRangeStillApplied() { AddStep("set override cs to 11", () => modDifficultyAdjust.CircleSize.Value = 11); @@ -91,6 +91,28 @@ public void TestOutOfRangeValueStillApplied() checkBindableAtValue("Circle Size", 11); } + [Test] + public void TestValueBelowRangeStillApplied() + { + AddStep("set override cs to -5", () => modDifficultyAdjust.ApproachRate.Value = -5); + + checkSliderAtValue("Approach Rate", -5); + checkBindableAtValue("Approach Rate", -5); + + // this is a no-op, just showing that it won't reset the value during deserialisation. + setExtendedLimits(false); + + checkSliderAtValue("Approach Rate", -5); + checkBindableAtValue("Approach Rate", -5); + + // setting extended limits will reset the serialisation exception. + // this should be fine as the goal is to allow, at most, the value of extended limits. + setExtendedLimits(true); + + checkSliderAtValue("Approach Rate", -5); + checkBindableAtValue("Approach Rate", -5); + } + [Test] public void TestExtendedLimits() { @@ -109,6 +131,11 @@ public void TestExtendedLimits() checkSliderAtValue("Circle Size", 11); checkBindableAtValue("Circle Size", 11); + setSliderValue("Approach Rate", -5); + + checkSliderAtValue("Approach Rate", -5); + checkBindableAtValue("Approach Rate", -5); + setExtendedLimits(false); checkSliderAtValue("Circle Size", 10); From 6813f5ee0a746f4f6782de75c8fc7d46ac16d4e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 30 Jul 2024 08:17:35 +0200 Subject: [PATCH 218/266] Fix incorrect `DifficultyBindable` logic --- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs index 5f6fd21860ca..099806d320ce 100644 --- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -38,6 +38,7 @@ public float Precision public float MinValue { + get => minValue; set { if (value == minValue) @@ -52,6 +53,7 @@ public float MinValue public float MaxValue { + get => maxValue; set { if (value == maxValue) @@ -69,6 +71,7 @@ public float MaxValue /// public float? ExtendedMinValue { + get => extendedMinValue; set { if (value == extendedMinValue) @@ -86,6 +89,7 @@ public float? ExtendedMinValue /// public float? ExtendedMaxValue { + get => extendedMaxValue; set { if (value == extendedMaxValue) @@ -114,9 +118,14 @@ public override float? Value { // Ensure that in the case serialisation runs in the wrong order (and limit extensions aren't applied yet) the deserialised value is still propagated. if (value != null) - CurrentNumber.MaxValue = MathF.Max(CurrentNumber.MaxValue, value.Value); - - base.Value = value; + { + CurrentNumber.MinValue = Math.Clamp(MathF.Min(CurrentNumber.MinValue, value.Value), ExtendedMinValue ?? MinValue, MinValue); + CurrentNumber.MaxValue = Math.Clamp(MathF.Max(CurrentNumber.MaxValue, value.Value), MaxValue, ExtendedMaxValue ?? MaxValue); + + base.Value = Math.Clamp(value.Value, CurrentNumber.MinValue, CurrentNumber.MaxValue); + } + else + base.Value = value; } } @@ -138,6 +147,8 @@ public override void CopyTo(Bindable them) // the following max value copies are only safe as long as these values are effectively constants. otherDifficultyBindable.MaxValue = maxValue; otherDifficultyBindable.ExtendedMaxValue = extendedMaxValue; + otherDifficultyBindable.MinValue = minValue; + otherDifficultyBindable.ExtendedMinValue = extendedMinValue; } public override void BindTo(Bindable them) From bf10a910826fa68b65e1eb4f0e22b5a77003bad5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2024 15:13:07 +0900 Subject: [PATCH 219/266] Adjust colouring to work better across multiple usages --- osu.Game/Overlays/Chat/ChatLine.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 87e1f5699bad..e4564724f015 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -123,13 +123,13 @@ private void load(OsuConfigManager configManager) background = new Container { Masking = true, - Blending = BlendingParameters.Additive, CornerRadius = 4, Alpha = 0, RelativeSizeAxes = Axes.Both, + Blending = BlendingParameters.Additive, Child = new Box { - Colour = Color4.White, + Colour = Colour4.FromHex("#3b3234"), RelativeSizeAxes = Axes.Both, }, }, @@ -301,7 +301,7 @@ private void updateTimestamp() private void updateBackground() { if (background != null) - background.Alpha = alternatingBackground ? 0.03f : 0; + background.Alpha = alternatingBackground ? 0.2f : 0; } } } From fc78dc9f3890bd068b4e5d62202467e45476a15c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2024 16:02:49 +0900 Subject: [PATCH 220/266] Adjust paddings to avoid scrollbar overlap --- osu.Game/Overlays/Chat/ChatLine.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index e4564724f015..a233c1811539 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -118,6 +118,8 @@ private void load(OsuConfigManager configManager) configManager.BindWith(OsuSetting.Prefer24HourTime, prefer24HourTime); prefer24HourTime.BindValueChanged(_ => updateTimestamp()); + Padding = new MarginPadding { Right = 5 }; + InternalChildren = new[] { background = new Container @@ -135,10 +137,10 @@ private void load(OsuConfigManager configManager) }, new GridContainer { - Margin = new MarginPadding + Padding = new MarginPadding { - Horizontal = 10, - Vertical = 1, + Horizontal = 2, + Vertical = 2, }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, From a05f8107247bc6896a8716a22139b6143f332621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 30 Jul 2024 10:07:38 +0200 Subject: [PATCH 221/266] Attempt to fix tests more --- .../Visual/DailyChallenge/TestSceneDailyChallenge.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index b6dcc82ac1e9..b4d0b746a7e0 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -5,6 +5,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Screens; using osu.Game.Online.API; using osu.Game.Online.Metadata; using osu.Game.Online.Rooms; @@ -74,7 +75,10 @@ public void TestNotifications() AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); AddStep("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1234 }); - AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room))); + + Screens.OnlinePlay.DailyChallenge.DailyChallenge screen = null!; + AddStep("push screen", () => LoadScreen(screen = new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room))); + AddUntilStep("wait for screen", () => screen.IsCurrentScreen()); AddStep("daily challenge ended", () => metadataClient.DailyChallengeInfo.Value = null); Func? previousHandler = null; From 1b57a2a136c626939669a1a6f6e425b93f823ebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 30 Jul 2024 10:36:26 +0200 Subject: [PATCH 222/266] Show new daily challenge notification globally --- .../DailyChallenge/TestSceneDailyChallenge.cs | 38 ------------------ .../UserInterface/TestSceneMainMenuButton.cs | 40 ++++++++++++++++--- osu.Game/Screens/Menu/DailyChallengeButton.cs | 16 ++++++-- .../DailyChallenge/DailyChallenge.cs | 12 ------ 4 files changed, 46 insertions(+), 60 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index b4d0b746a7e0..25b3375f9e29 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -80,44 +80,6 @@ public void TestNotifications() AddStep("push screen", () => LoadScreen(screen = new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room))); AddUntilStep("wait for screen", () => screen.IsCurrentScreen()); AddStep("daily challenge ended", () => metadataClient.DailyChallengeInfo.Value = null); - - Func? previousHandler = null; - - AddStep("install custom handler", () => - { - previousHandler = ((DummyAPIAccess)API).HandleRequest; - ((DummyAPIAccess)API).HandleRequest = req => - { - switch (req) - { - case GetRoomRequest r: - { - r.TriggerSuccess(new Room - { - RoomID = { Value = 1235, }, - Name = { Value = "Daily Challenge: June 5, 2024" }, - Playlist = - { - new PlaylistItem(CreateAPIBeatmapSet().Beatmaps.First()) - { - RequiredMods = [new APIMod(new OsuModTraceable())], - AllowedMods = [new APIMod(new OsuModDoubleTime())] - } - }, - EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, - Category = { Value = RoomCategory.DailyChallenge } - }); - return true; - } - - default: - return false; - } - }; - }); - AddStep("next daily challenge started", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1235 }); - - AddStep("restore previous handler", () => ((DummyAPIAccess)API).HandleRequest = previousHandler); } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs index 5914898cb1c1..af98aa21db90 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -10,6 +11,7 @@ using osu.Game.Online.API; using osu.Game.Online.Metadata; using osu.Game.Online.Rooms; +using osu.Game.Overlays; using osu.Game.Screens.Menu; using osuTK.Input; using Color4 = osuTK.Graphics.Color4; @@ -39,8 +41,6 @@ public void TestStandardButton() [Test] public void TestDailyChallengeButton() { - AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null)); - AddStep("set up API", () => dummyAPI.HandleRequest = req => { switch (req) @@ -67,17 +67,45 @@ public void TestDailyChallengeButton() } }); - AddStep("add button", () => Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D) + NotificationOverlay notificationOverlay = null!; + DependencyProvidingContainer buttonContainer = null!; + + AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - ButtonSystemState = ButtonSystemState.TopLevel, + RoomID = 1234, + })); + AddStep("add content", () => + { + notificationOverlay = new NotificationOverlay(); + Children = new Drawable[] + { + notificationOverlay, + buttonContainer = new DependencyProvidingContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + CachedDependencies = [(typeof(INotificationOverlay), notificationOverlay)], + Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + ButtonSystemState = ButtonSystemState.TopLevel, + }, + }, + }; }); + AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero); + + AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null)); + AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero); + AddStep("hide button's parent", () => buttonContainer.Hide()); AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo { RoomID = 1234, })); + AddAssert("notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1)); } } } diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index c365994736bb..a5616b95a0f3 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -23,6 +23,7 @@ using osu.Game.Online.Metadata; using osu.Game.Online.Rooms; using osu.Game.Overlays; +using osu.Game.Screens.OnlinePlay.DailyChallenge; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -44,6 +45,9 @@ public partial class DailyChallengeButton : MainMenuButton, IHasCustomTooltip? clickAction = null, params Key[] triggerKeys) : base(ButtonSystemStrings.DailyChallenge, sampleName, OsuIcon.DailyChallenge, colour, clickAction, triggerKeys) { @@ -100,7 +104,8 @@ protected override void LoadComplete() { base.LoadComplete(); - info.BindValueChanged(updateDisplay, true); + info.BindValueChanged(_ => dailyChallengeChanged(postNotification: true)); + dailyChallengeChanged(postNotification: false); } protected override void Update() @@ -126,27 +131,30 @@ protected override void Update() } } - private void updateDisplay(ValueChangedEvent info) + private void dailyChallengeChanged(bool postNotification) { UpdateState(); scheduledCountdownUpdate?.Cancel(); scheduledCountdownUpdate = null; - if (info.NewValue == null) + if (info.Value == null) { Room = null; cover.OnlineInfo = TooltipContent = null; } else { - var roomRequest = new GetRoomRequest(info.NewValue.Value.RoomID); + var roomRequest = new GetRoomRequest(info.Value.Value.RoomID); roomRequest.Success += room => { Room = room; cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet; + if (postNotification) + notificationOverlay?.Post(new NewDailyChallengeNotification(room)); + updateCountdown(); Scheduler.AddDelayed(updateCountdown, 1000, true); }; diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index d176f36162e0..32209bc3b4e3 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -424,18 +424,6 @@ private void dailyChallengeChanged(ValueChangedEvent change Text = "Today's daily challenge has concluded. Thanks for playing! The next one should appear in a few minutes." }); } - - if (change.NewValue != null && change.NewValue.Value.RoomID != room.RoomID.Value) - { - var roomRequest = new GetRoomRequest(change.NewValue.Value.RoomID); - - roomRequest.Success += room => - { - waitForNextChallengeNotification?.Close(false); - notificationOverlay?.Post(new NewDailyChallengeNotification(room)); - }; - API.Queue(roomRequest); - } } private void forcefullyExit() From e63080eb2e5932ac97a656a0cfb2c06c7b3f96f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2024 16:59:46 +0900 Subject: [PATCH 223/266] Don't show seconds in chat timestamps --- osu.Game/Overlays/Chat/ChatLine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index a233c1811539..6538bcfcf45e 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -258,7 +258,7 @@ private void updateMessageContent() private void updateTimestamp() { - drawableTimestamp.Text = message.Timestamp.LocalDateTime.ToLocalisableString(prefer24HourTime.Value ? @"HH:mm:ss" : @"hh:mm:ss tt"); + drawableTimestamp.Text = message.Timestamp.LocalDateTime.ToLocalisableString(prefer24HourTime.Value ? @"HH:mm" : @"hh:mm tt"); } private static readonly Color4[] default_username_colours = From a2a73232f3bdf6117e2d17bd3ae41423a32cc61b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2024 16:59:32 +0900 Subject: [PATCH 224/266] Avoid showing timestamp in chat line when repeated --- osu.Game/Overlays/Chat/ChatLine.cs | 31 +++++++++++++++++++++-- osu.Game/Overlays/Chat/DrawableChannel.cs | 6 +++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 6538bcfcf45e..4d228b2af025 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -71,6 +71,25 @@ public Message Message private Drawable? background; private bool alternatingBackground; + private bool requiresTimestamp = true; + + + public bool RequiresTimestamp + { + get => requiresTimestamp; + set + { + if (requiresTimestamp == value) + return; + + requiresTimestamp = value; + + if (!IsLoaded) + return; + + updateMessageContent(); + } + } public bool AlternatingBackground { @@ -244,9 +263,17 @@ private void styleMessageContent(SpriteText text) private void updateMessageContent() { this.FadeTo(message is LocalEchoMessage ? 0.4f : 1.0f, 500, Easing.OutQuint); - drawableTimestamp.FadeTo(message is LocalEchoMessage ? 0 : 1, 500, Easing.OutQuint); - updateTimestamp(); + if (requiresTimestamp && !(message is LocalEchoMessage)) + { + drawableTimestamp.Show(); + updateTimestamp(); + } + else + { + drawableTimestamp.Hide(); + } + drawableUsername.Text = $@"{message.Sender.Username}"; // remove non-existent channels from the link list diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 6b3acaa226c4..05d09401a922 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -88,10 +88,16 @@ protected override void Update() { base.Update(); + int? minute = null; + for (int i = 0; i < ChatLineFlow.Count; i++) { if (ChatLineFlow[i] is ChatLine chatline) + { chatline.AlternatingBackground = i % 2 == 0; + chatline.RequiresTimestamp = chatline.Message.Timestamp.Minute != minute; + minute = chatline.Message.Timestamp.Minute; + } } } From 71649005bf02f5a158afdabe9d6ffd9273690777 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2024 17:00:12 +0900 Subject: [PATCH 225/266] Elongate usernames in `DrawableChannel` test --- .../Visual/Online/TestSceneDrawableChannel.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs index 7b7565b13f6c..dd12ee34ede1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osu.Game.Overlays.Chat; @@ -88,19 +89,17 @@ public void TestDaySeparators() [Test] public void TestBackgroundAlternating() { - var localUser = new APIUser - { - Id = 3, - Username = "LocalUser" - }; - int messageCount = 1; AddRepeatStep("add messages", () => { channel.AddNewMessages(new Message(messageCount) { - Sender = localUser, + Sender = new APIUser + { + Id = 3, + Username = "LocalUser " + RNG.Next(0, int.MaxValue - 100).ToString("N") + }, Content = "Hi there all!", Timestamp = new DateTimeOffset(2022, 11, 21, 20, messageCount, 13, TimeSpan.Zero), Uuid = Guid.NewGuid().ToString(), From 4557ad43d5622221471a31d119bb4319d202cea7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2024 17:00:26 +0900 Subject: [PATCH 226/266] Reduce padding on chat lines to give more breathing room --- osu.Game/Overlays/Chat/DrawableChannel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 05d09401a922..d20506ea4cef 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -61,7 +61,7 @@ private void load() Padding = new MarginPadding { Bottom = 5 }, Child = ChatLineFlow = new FillFlowContainer { - Padding = new MarginPadding { Horizontal = 10 }, + Padding = new MarginPadding { Left = 3, Right = 10 }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, From 6670f79258dc6fb9ab25505fc38d92df1158f562 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2024 17:01:16 +0900 Subject: [PATCH 227/266] Reduce overall size of chat text --- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 4 ++-- osu.Game/Overlays/Chat/ChatLine.cs | 2 +- osu.Game/Overlays/Chat/DaySeparator.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index e3b50373672e..440486d6a088 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -178,7 +178,7 @@ public StandAloneDrawableChannel(Channel channel) protected partial class StandAloneDaySeparator : DaySeparator { - protected override float TextSize => 14; + protected override float TextSize => 13; protected override float LineHeight => 1; protected override float Spacing => 5; protected override float DateAlign => 125; @@ -198,7 +198,7 @@ private void load(OsuColour colours) protected partial class StandAloneMessage : ChatLine { - protected override float FontSize => 15; + protected override float FontSize => 13; protected override float Spacing => 5; protected override float UsernameWidth => 75; diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 4d228b2af025..adb193af3247 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -46,7 +46,7 @@ public Message Message public IReadOnlyCollection DrawableContentFlow => drawableContentFlow; - protected virtual float FontSize => 14; + protected virtual float FontSize => 12; protected virtual float Spacing => 15; diff --git a/osu.Game/Overlays/Chat/DaySeparator.cs b/osu.Game/Overlays/Chat/DaySeparator.cs index e737b787baa1..fd6b15c77858 100644 --- a/osu.Game/Overlays/Chat/DaySeparator.cs +++ b/osu.Game/Overlays/Chat/DaySeparator.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.Chat { public partial class DaySeparator : Container { - protected virtual float TextSize => 15; + protected virtual float TextSize => 13; protected virtual float LineHeight => 2; From 7229ae83ea8449be6bd1bf290c69b3cb7bf2e494 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2024 17:16:27 +0900 Subject: [PATCH 228/266] Adjust sizing and distribution of timestamp and username --- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 2 +- osu.Game/Overlays/Chat/ChatLine.cs | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 440486d6a088..3a094cc074be 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -200,7 +200,7 @@ protected partial class StandAloneMessage : ChatLine { protected override float FontSize => 13; protected override float Spacing => 5; - protected override float UsernameWidth => 75; + protected override float UsernameWidth => 90; public StandAloneMessage(Message message) : base(message) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index adb193af3247..29c6ec2564a0 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -20,6 +20,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; +using osuTK; using osuTK.Graphics; namespace osu.Game.Overlays.Chat @@ -50,7 +51,7 @@ public Message Message protected virtual float Spacing => 15; - protected virtual float UsernameWidth => 130; + protected virtual float UsernameWidth => 150; [Resolved] private ChannelManager? chatManager { get; set; } @@ -73,7 +74,6 @@ public Message Message private bool alternatingBackground; private bool requiresTimestamp = true; - public bool RequiresTimestamp { get => requiresTimestamp; @@ -166,7 +166,7 @@ private void load(OsuConfigManager configManager) RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, ColumnDimensions = new[] { - new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, 45), new Dimension(GridSizeMode.Absolute, Spacing + UsernameWidth + Spacing), new Dimension(), }, @@ -177,9 +177,10 @@ private void load(OsuConfigManager configManager) drawableTimestamp = new OsuSpriteText { Shadow = false, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: FontSize * 0.75f, weight: FontWeight.SemiBold, fixedWidth: true), + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Spacing = new Vector2(-1, 0), + Font = OsuFont.GetFont(size: FontSize, weight: FontWeight.SemiBold, fixedWidth: true), AlwaysPresent = true, }, drawableUsername = new DrawableChatUsername(message.Sender) From 25747fdeb3c3a57db92fda84de3189f48c6784b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2024 18:06:56 +0900 Subject: [PATCH 229/266] Fix edge case where minutes are same but hour is different --- osu.Game/Overlays/Chat/DrawableChannel.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index d20506ea4cef..97660a34f183 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -88,15 +88,17 @@ protected override void Update() { base.Update(); - int? minute = null; + int? lastMinutes = null; for (int i = 0; i < ChatLineFlow.Count; i++) { if (ChatLineFlow[i] is ChatLine chatline) { + int minutes = chatline.Message.Timestamp.TotalOffsetMinutes; + chatline.AlternatingBackground = i % 2 == 0; - chatline.RequiresTimestamp = chatline.Message.Timestamp.Minute != minute; - minute = chatline.Message.Timestamp.Minute; + chatline.RequiresTimestamp = minutes != lastMinutes; + lastMinutes = minutes; } } } From d5f9173288ecd3a4030bd07e47b82e5c897f3af5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2024 19:04:43 +0900 Subject: [PATCH 230/266] Remove unused local variable --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 32209bc3b4e3..e98e758cebbc 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -67,8 +67,6 @@ public partial class DailyChallenge : OsuScreen, IPreviewTrackOwner private DailyChallengeTotalsDisplay totals = null!; private DailyChallengeEventFeed feed = null!; - private SimpleNotification? waitForNextChallengeNotification; - [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); @@ -419,7 +417,7 @@ private void dailyChallengeChanged(ValueChangedEvent change { if (change.OldValue?.RoomID == room.RoomID.Value && change.NewValue == null) { - notificationOverlay?.Post(waitForNextChallengeNotification = new SimpleNotification + notificationOverlay?.Post(new SimpleNotification { Text = "Today's daily challenge has concluded. Thanks for playing! The next one should appear in a few minutes." }); From ff7815c3c563b2ad607d47da76ccbb9b1f0cc24b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 30 Jul 2024 20:13:00 +0900 Subject: [PATCH 231/266] Submit vertices in local space to avoid cross-thread access --- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 49e4ee18c175..5e8061bb6a2b 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -220,7 +220,6 @@ private class TrailDrawNode : DrawNode private float fadeExponent; private readonly TrailPart[] parts = new TrailPart[max_sprites]; - private Vector2 size; private Vector2 originPosition; private IVertexBatch vertexBatch; @@ -236,7 +235,6 @@ public override void ApplyState() shader = Source.shader; texture = Source.texture; - size = Source.partSize; time = Source.time; fadeExponent = Source.FadeExponent; @@ -277,6 +275,8 @@ protected override void Draw(IRenderer renderer) RectangleF textureRect = texture.GetTextureRect(); + renderer.PushLocalMatrix(DrawInfo.Matrix); + foreach (var part in parts) { if (part.InvalidationID == -1) @@ -285,11 +285,9 @@ protected override void Draw(IRenderer renderer) if (time - part.Time >= 1) continue; - Vector2 screenSpacePos = Source.ToScreenSpace(part.Position); - vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(screenSpacePos.X - size.X * originPosition.X, screenSpacePos.Y + size.Y * (1 - originPosition.Y)), + Position = new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X, part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y)), TexturePosition = textureRect.BottomLeft, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.BottomLeft.Linear, @@ -298,7 +296,7 @@ protected override void Draw(IRenderer renderer) vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(screenSpacePos.X + size.X * (1 - originPosition.X), screenSpacePos.Y + size.Y * (1 - originPosition.Y)), + Position = new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X), part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y)), TexturePosition = textureRect.BottomRight, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.BottomRight.Linear, @@ -307,7 +305,7 @@ protected override void Draw(IRenderer renderer) vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(screenSpacePos.X + size.X * (1 - originPosition.X), screenSpacePos.Y - size.Y * originPosition.Y), + Position = new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X), part.Position.Y - texture.DisplayHeight * originPosition.Y), TexturePosition = textureRect.TopRight, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.TopRight.Linear, @@ -316,7 +314,7 @@ protected override void Draw(IRenderer renderer) vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(screenSpacePos.X - size.X * originPosition.X, screenSpacePos.Y - size.Y * originPosition.Y), + Position = new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X, part.Position.Y - texture.DisplayHeight * originPosition.Y), TexturePosition = textureRect.TopLeft, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.TopLeft.Linear, @@ -324,6 +322,8 @@ protected override void Draw(IRenderer renderer) }); } + renderer.PopLocalMatrix(); + vertexBatch.Draw(); shader.Unbind(); } From 7f22ade90da1426924b220f76373f2dcc5414911 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2024 21:58:54 +0900 Subject: [PATCH 232/266] Fix oversight in timekeeping --- osu.Game/Overlays/Chat/DrawableChannel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 97660a34f183..41098ef82340 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -88,13 +88,13 @@ protected override void Update() { base.Update(); - int? lastMinutes = null; + long? lastMinutes = null; for (int i = 0; i < ChatLineFlow.Count; i++) { if (ChatLineFlow[i] is ChatLine chatline) { - int minutes = chatline.Message.Timestamp.TotalOffsetMinutes; + long minutes = chatline.Message.Timestamp.ToUnixTimeSeconds() / 60; chatline.AlternatingBackground = i % 2 == 0; chatline.RequiresTimestamp = minutes != lastMinutes; From 5ebb5ad6707f1ca29c18ced13683c5ecfacedcb1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jul 2024 02:53:10 +0900 Subject: [PATCH 233/266] Fix test failure due to `TestMetadataClient` providing null statistics array --- .../Visual/DailyChallenge/TestSceneDailyChallenge.cs | 3 ++- osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index 25b3375f9e29..e10b3f76e68c 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -29,6 +29,7 @@ public partial class TestSceneDailyChallenge : OnlinePlayTestScene private void load() { base.Content.Add(notificationOverlay); + base.Content.Add(metadataClient); } [Test] @@ -63,7 +64,7 @@ public void TestNotifications() Name = { Value = "Daily Challenge: June 4, 2024" }, Playlist = { - new PlaylistItem(CreateAPIBeatmapSet().Beatmaps.First()) + new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First()) { RequiredMods = [new APIMod(new OsuModTraceable())], AllowedMods = [new APIMod(new OsuModDoubleTime())] diff --git a/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs index 2a0af0b10eb3..c9f2b183e329 100644 --- a/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs +++ b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs @@ -88,7 +88,14 @@ public override Task DailyChallengeUpdated(DailyChallengeInfo? info) } public override Task BeginWatchingMultiplayerRoom(long id) - => Task.FromResult(new MultiplayerPlaylistItemStats[MultiplayerPlaylistItemStats.TOTAL_SCORE_DISTRIBUTION_BINS]); + { + var stats = new MultiplayerPlaylistItemStats[MultiplayerPlaylistItemStats.TOTAL_SCORE_DISTRIBUTION_BINS]; + + for (int i = 0; i < stats.Length; i++) + stats[i] = new MultiplayerPlaylistItemStats { PlaylistItemID = i }; + + return Task.FromResult(stats); + } public override Task EndWatchingMultiplayerRoom(long id) => Task.CompletedTask; } From bdc465e1c68c29841ff01dc8efc07817d92f1a51 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jul 2024 03:06:35 +0900 Subject: [PATCH 234/266] Reword notification text slightly --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 2 +- .../OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index db85db2cd34b..8c8b6bdbf047 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -419,7 +419,7 @@ private void dailyChallengeChanged(ValueChangedEvent change { notificationOverlay?.Post(new SimpleNotification { - Text = "Today's daily challenge has concluded. Thanks for playing! The next one should appear in a few minutes." + Text = "Today's daily challenge has concluded – thanks for playing!\n\nTomorrow's challenge is now being prepared and will appear soon." }); } } diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs index 36ec8b37a7a8..3f14e63a2d67 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs @@ -26,7 +26,7 @@ public NewDailyChallengeNotification(Room room) [BackgroundDependencyLoader] private void load(OsuGame? game) { - Text = "Today's daily challenge is here! Click here to play."; + Text = "Today's daily challenge is now live! Click here to play."; Content.Add(card = new BeatmapCardNano((APIBeatmapSet)room.Playlist.Single().Beatmap.BeatmapSet!)); Activated = () => { From e77489f2a9d8e6edd6d75e888512e34064e3644f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jul 2024 03:10:36 +0900 Subject: [PATCH 235/266] Allow notification of new strings --- .../Localisation/DailyChallengeStrings.cs | 29 +++++++++++++++++++ .../DailyChallenge/DailyChallenge.cs | 7 ++--- .../NewDailyChallengeNotification.cs | 3 +- 3 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Localisation/DailyChallengeStrings.cs diff --git a/osu.Game/Localisation/DailyChallengeStrings.cs b/osu.Game/Localisation/DailyChallengeStrings.cs new file mode 100644 index 000000000000..32ff98db063d --- /dev/null +++ b/osu.Game/Localisation/DailyChallengeStrings.cs @@ -0,0 +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 osu.Framework.Localisation; + +namespace osu.Game.Localisation +{ + public static class DailyChallengeStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.DailyChallenge"; + + /// + /// "Today's daily challenge has concluded – thanks for playing! + /// + /// Tomorrow's challenge is now being prepared and will appear soon." + /// + public static LocalisableString ChallengeEndedNotification => new TranslatableString(getKey(@"todays_daily_challenge_has_concluded"), + @"Today's daily challenge has concluded – thanks for playing! + +Tomorrow's challenge is now being prepared and will appear soon."); + + /// + /// "Today's daily challenge is now live! Click here to play." + /// + public static LocalisableString ChallengeLiveNotification => new TranslatableString(getKey(@"todays_daily_challenge_is_now"), @"Today's daily challenge is now live! Click here to play."); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 8c8b6bdbf047..da2d9036c59c 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -417,16 +417,13 @@ private void dailyChallengeChanged(ValueChangedEvent change { if (change.OldValue?.RoomID == room.RoomID.Value && change.NewValue == null) { - notificationOverlay?.Post(new SimpleNotification - { - Text = "Today's daily challenge has concluded – thanks for playing!\n\nTomorrow's challenge is now being prepared and will appear soon." - }); + notificationOverlay?.Post(new SimpleNotification { Text = DailyChallengeStrings.ChallengeEndedNotification }); } } private void forcefullyExit() { - Logger.Log($"{this} forcefully exiting due to loss of API connection"); + Logger.Log(@$"{this} forcefully exiting due to loss of API connection"); // This is temporary since we don't currently have a way to force screens to be exited // See also: `OnlinePlayScreen.forcefullyExit()` diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs index 3f14e63a2d67..ea19828a2106 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs @@ -9,6 +9,7 @@ using osu.Game.Online.Rooms; using osu.Game.Overlays.Notifications; using osu.Game.Screens.Menu; +using osu.Game.Localisation; namespace osu.Game.Screens.OnlinePlay.DailyChallenge { @@ -26,7 +27,7 @@ public NewDailyChallengeNotification(Room room) [BackgroundDependencyLoader] private void load(OsuGame? game) { - Text = "Today's daily challenge is now live! Click here to play."; + Text = DailyChallengeStrings.ChallengeLiveNotification; Content.Add(card = new BeatmapCardNano((APIBeatmapSet)room.Playlist.Single().Beatmap.BeatmapSet!)); Activated = () => { From cbfb569ad47d17a6de63dd6484d3c9a15cd2d452 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jul 2024 14:37:56 +0900 Subject: [PATCH 236/266] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e7d9d4c0227b..3d8b64327976 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From e329427d6e2c66a5c68ee9d6e9858c284abc9d5f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jul 2024 19:28:30 +0900 Subject: [PATCH 237/266] Apply nullability to `Timeline` --- .../Compose/Components/Timeline/Timeline.cs | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 6ce5c068010d..c1a16e250323 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Audio.Track; @@ -31,10 +29,10 @@ public partial class Timeline : ZoomableScrollContainer, IPositionSnapProvider private readonly Drawable userContent; [Resolved] - private EditorClock editorClock { get; set; } + private EditorClock editorClock { get; set; } = null!; [Resolved] - private EditorBeatmap editorBeatmap { get; set; } + private EditorBeatmap editorBeatmap { get; set; } = null!; /// /// The timeline's scroll position in the last frame. @@ -61,6 +59,22 @@ public partial class Timeline : ZoomableScrollContainer, IPositionSnapProvider /// private float defaultTimelineZoom; + private WaveformGraph waveform = null!; + + private TimelineTickDisplay ticks = null!; + + private TimelineControlPointDisplay controlPoints = null!; + + private Container mainContent = null!; + + private Bindable waveformOpacity = null!; + private Bindable controlPointsVisible = null!; + private Bindable ticksVisible = null!; + + private double trackLengthForZoom; + + private readonly IBindable track = new Bindable(); + public Timeline(Drawable userContent) { this.userContent = userContent; @@ -73,22 +87,6 @@ public Timeline(Drawable userContent) ScrollbarVisible = false; } - private WaveformGraph waveform; - - private TimelineTickDisplay ticks; - - private TimelineControlPointDisplay controlPoints; - - private Container mainContent; - - private Bindable waveformOpacity; - private Bindable controlPointsVisible; - private Bindable ticksVisible; - - private double trackLengthForZoom; - - private readonly IBindable track = new Bindable(); - [BackgroundDependencyLoader] private void load(IBindable beatmap, OsuColour colours, OsuConfigManager config) { @@ -318,7 +316,7 @@ private void endUserDrag() } [Resolved] - private IBeatSnapProvider beatSnapProvider { get; set; } + private IBeatSnapProvider beatSnapProvider { get; set; } = null!; /// /// The total amount of time visible on the timeline. From 2d52bab77b7d008e296659e442a0cf2273e1b430 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jul 2024 19:43:08 +0900 Subject: [PATCH 238/266] Always show timing points in timeline when at the timing screen Supersedes https://github.com/ppy/osu/pull/29196. --- .../Compose/Components/Timeline/Timeline.cs | 17 ++++++++++++++++- .../Screens/Edit/EditorScreenWithTimeline.cs | 13 ++++++++++--- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 8 ++++++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index c1a16e250323..7a28f7bbaae7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -28,6 +28,21 @@ public partial class Timeline : ZoomableScrollContainer, IPositionSnapProvider private readonly Drawable userContent; + private bool alwaysShowControlPoints; + + public bool AlwaysShowControlPoints + { + get => alwaysShowControlPoints; + set + { + if (value == alwaysShowControlPoints) + return; + + alwaysShowControlPoints = value; + controlPointsVisible.TriggerChange(); + } + } + [Resolved] private EditorClock editorClock { get; set; } = null!; @@ -176,7 +191,7 @@ protected override void LoadComplete() controlPointsVisible.BindValueChanged(visible => { - if (visible.NewValue) + if (visible.NewValue || alwaysShowControlPoints) { this.ResizeHeightTo(timeline_expanded_height, 200, Easing.OutQuint); mainContent.MoveToY(15, 200, Easing.OutQuint); diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index 38d2a1e7e4f9..01908e45c7cc 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -5,7 +5,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays; using osu.Game.Screens.Edit.Compose.Components.Timeline; namespace osu.Game.Screens.Edit @@ -26,7 +25,7 @@ protected EditorScreenWithTimeline(EditorScreenMode type) } [BackgroundDependencyLoader(true)] - private void load(OverlayColourProvider colourProvider) + private void load() { // Grid with only two rows. // First is the timeline area, which should be allowed to expand as required. @@ -107,10 +106,18 @@ protected override void LoadComplete() MainContent.Add(content); content.FadeInFromZero(300, Easing.OutQuint); - LoadComponentAsync(TimelineArea = new TimelineArea(CreateTimelineContent()), timelineContent.Add); + LoadComponentAsync(TimelineArea = new TimelineArea(CreateTimelineContent()), timeline => + { + ConfigureTimeline(timeline); + timelineContent.Add(timeline); + }); }); } + protected virtual void ConfigureTimeline(TimelineArea timelineArea) + { + } + protected abstract Drawable CreateMainContent(); protected virtual Drawable CreateTimelineContent() => new Container(); diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 3f911f506722..67d4429be835 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Screens.Edit.Compose.Components.Timeline; namespace osu.Game.Screens.Edit.Timing { @@ -53,5 +54,12 @@ protected override void LoadComplete() SelectedGroup.Value = EditorBeatmap.ControlPointInfo.GroupAt(nearestTimingPoint.Time); } } + + protected override void ConfigureTimeline(TimelineArea timelineArea) + { + base.ConfigureTimeline(timelineArea); + + timelineArea.Timeline.AlwaysShowControlPoints = true; + } } } From 5098d637b5c86a36ce2d45be22dadeb39b6022e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jul 2024 19:55:20 +0900 Subject: [PATCH 239/266] Flash customise button on mod overlay when it becomes available --- .../Overlays/Mods/ModCustomisationHeader.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index bf10e1351540..fb9e960f41ca 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; 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; @@ -11,14 +12,16 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osuTK; using osu.Game.Localisation; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.Mods { public partial class ModCustomisationHeader : OsuHoverContainer { private Box background = null!; + private Box backgroundFlash = null!; private SpriteIcon icon = null!; [Resolved] @@ -46,6 +49,12 @@ private void load() { RelativeSizeAxes = Axes.Both, }, + backgroundFlash = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White.Opacity(0.4f), + Blending = BlendingParameters.Additive, + }, new OsuSpriteText { Anchor = Anchor.CentreLeft, @@ -84,6 +93,12 @@ protected override void LoadComplete() TooltipText = e.NewValue ? string.Empty : ModSelectOverlayStrings.CustomisationPanelDisabledReason; + + if (e.NewValue) + { + backgroundFlash.FadeInFromZero(150, Easing.OutQuad).Then() + .FadeOutFromOne(350, Easing.OutQuad); + } }, true); Expanded.BindValueChanged(v => From dab967e6be8603bfb50dc462099dd167c9cda965 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Aug 2024 18:36:33 +0900 Subject: [PATCH 240/266] Fix insane transform allocations in new leaderboard display --- .../Leaderboards/LeaderboardScoreV2.cs | 56 +++++++++++++------ 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs b/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs index c9584b057bcc..b6508e177a31 100644 --- a/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs +++ b/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; -using osu.Framework.Layout; using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Extensions; @@ -38,6 +37,7 @@ using osu.Game.Utils; using osuTK; using osuTK.Graphics; +using CommonStrings = osu.Game.Localisation.CommonStrings; namespace osu.Game.Screens.SelectV2.Leaderboards { @@ -61,7 +61,6 @@ public partial class LeaderboardScoreV2 : OsuClickableContainer, IHasContextMenu private const float statistics_regular_min_width = 175; private const float statistics_compact_min_width = 100; private const float rank_label_width = 65; - private const float rank_label_visibility_width_cutoff = rank_label_width + height + username_min_width + statistics_regular_min_width + expanded_right_content_width; private readonly ScoreInfo score; private readonly bool sheared; @@ -560,33 +559,34 @@ private void updateState() background.FadeColour(IsHovered ? backgroundColour.Lighten(0.2f) : backgroundColour, transition_duration, Easing.OutQuint); totalScoreBackground.FadeColour(IsHovered ? lightenedGradient : totalScoreBackgroundGradient, transition_duration, Easing.OutQuint); - if (DrawWidth < rank_label_visibility_width_cutoff && IsHovered) + if (IsHovered && currentMode != DisplayMode.Full) rankLabelOverlay.FadeIn(transition_duration, Easing.OutQuint); else rankLabelOverlay.FadeOut(transition_duration, Easing.OutQuint); } - protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) + private DisplayMode? currentMode; + + protected override void Update() { - Scheduler.AddOnce(() => - { - // when width decreases - // - hide rank and show rank overlay on avatar when hovered, then - // - compact statistics, then - // - hide statistics + base.Update(); - if (DrawWidth >= rank_label_visibility_width_cutoff) + DisplayMode mode = getCurrentDisplayMode(); + + if (currentMode != mode) + { + if (mode >= DisplayMode.Full) rankLabel.FadeIn(transition_duration, Easing.OutQuint).MoveToX(0, transition_duration, Easing.OutQuint); else rankLabel.FadeOut(transition_duration, Easing.OutQuint).MoveToX(-rankLabel.DrawWidth, transition_duration, Easing.OutQuint); - if (DrawWidth >= height + username_min_width + statistics_regular_min_width + expanded_right_content_width) + if (mode >= DisplayMode.Regular) { statisticsContainer.FadeIn(transition_duration, Easing.OutQuint).MoveToX(0, transition_duration, Easing.OutQuint); statisticsContainer.Direction = FillDirection.Horizontal; statisticsContainer.ScaleTo(1, transition_duration, Easing.OutQuint); } - else if (DrawWidth >= height + username_min_width + statistics_compact_min_width + expanded_right_content_width) + else if (mode >= DisplayMode.Compact) { statisticsContainer.FadeIn(transition_duration, Easing.OutQuint).MoveToX(0, transition_duration, Easing.OutQuint); statisticsContainer.Direction = FillDirection.Vertical; @@ -594,13 +594,35 @@ protected override bool OnInvalidate(Invalidation invalidation, InvalidationSour } else statisticsContainer.FadeOut(transition_duration, Easing.OutQuint).MoveToX(statisticsContainer.DrawWidth, transition_duration, Easing.OutQuint); - }); - return base.OnInvalidate(invalidation, source); + currentMode = mode; + } + } + + private DisplayMode getCurrentDisplayMode() + { + if (DrawWidth >= height + username_min_width + statistics_regular_min_width + expanded_right_content_width + rank_label_width) + return DisplayMode.Full; + + if (DrawWidth >= height + username_min_width + statistics_regular_min_width + expanded_right_content_width) + return DisplayMode.Regular; + + if (DrawWidth >= height + username_min_width + statistics_compact_min_width + expanded_right_content_width) + return DisplayMode.Compact; + + return DisplayMode.Minimal; } #region Subclasses + private enum DisplayMode + { + Minimal, + Compact, + Regular, + Full + } + private partial class DateLabel : DrawableDate { public DateLabel(DateTimeOffset date) @@ -749,8 +771,8 @@ public MenuItem[] ContextMenuItems if (score.Files.Count <= 0) return items.ToArray(); - items.Add(new OsuMenuItem(Localisation.CommonStrings.Export, MenuItemType.Standard, () => scoreManager.Export(score))); - items.Add(new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(score)))); + items.Add(new OsuMenuItem(CommonStrings.Export, MenuItemType.Standard, () => scoreManager.Export(score))); + items.Add(new OsuMenuItem(Resources.Localisation.Web.CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(score)))); return items.ToArray(); } From 548fd9cbf9e96b93cc69add00509e8b0491fce2d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Aug 2024 19:44:42 +0900 Subject: [PATCH 241/266] Show breaks behind objects in timeline Closes https://github.com/ppy/osu/issues/29227. --- .../Screens/Edit/Compose/ComposeScreen.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index cc338409298c..f7e523db2524 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -69,19 +69,24 @@ protected override Drawable CreateTimelineContent() if (ruleset == null || composer == null) return base.CreateTimelineContent(); + TimelineBreakDisplay breakDisplay = new TimelineBreakDisplay + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Height = 0.75f, + }; + return wrapSkinnableContent(new Container { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + Children = new[] { + // We want to display this below hitobjects to better expose placement objects visually. + // It needs to be above the blueprint container to handle drags on breaks though. + breakDisplay.CreateProxy(), new TimelineBlueprintContainer(composer), - new TimelineBreakDisplay - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Height = 0.75f, - }, + breakDisplay } }); } From b883ff6c7be16f2ef6d6bd8456fda38da45d5ba7 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 1 Aug 2024 18:18:00 -0700 Subject: [PATCH 242/266] Fix click sounds playing twice on `OsuRearrangeableListItem` --- osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs b/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs index 39a3edb82c85..445588d52520 100644 --- a/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs +++ b/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs @@ -64,6 +64,7 @@ private void load() { InternalChildren = new Drawable[] { + new HoverClickSounds(), new GridContainer { RelativeSizeAxes = Axes.X, @@ -92,7 +93,6 @@ private void load() ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } }, - new HoverClickSounds() }; } From 0fac8148ed987bcc9f4b3340719bdd77fe57b110 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 1 Aug 2024 18:30:52 -0700 Subject: [PATCH 243/266] Fix collection delete button not having hover click sounds --- .../Collections/DrawableCollectionListItem.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index 596bb5d673ef..3b7649a30c98 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -132,7 +132,7 @@ private void load(OsuColour colours) } } - public partial class DeleteButton : CompositeDrawable + public partial class DeleteButton : OsuClickableContainer { public Func IsTextBoxHovered = null!; @@ -155,7 +155,7 @@ public DeleteButton(Live collection) [BackgroundDependencyLoader] private void load(OsuColour colours) { - InternalChild = fadeContainer = new Container + Child = fadeContainer = new Container { RelativeSizeAxes = Axes.Both, Alpha = 0.1f, @@ -176,6 +176,14 @@ private void load(OsuColour colours) } } }; + + Action = () => + { + if (collection.PerformRead(c => c.BeatmapMD5Hashes.Count) == 0) + deleteCollection(); + else + dialogOverlay?.Push(new DeleteCollectionDialog(collection, deleteCollection)); + }; } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && !IsTextBoxHovered(screenSpacePos); @@ -195,12 +203,7 @@ protected override bool OnClick(ClickEvent e) { background.FlashColour(Color4.White, 150); - if (collection.PerformRead(c => c.BeatmapMD5Hashes.Count) == 0) - deleteCollection(); - else - dialogOverlay?.Push(new DeleteCollectionDialog(collection, deleteCollection)); - - return true; + return base.OnClick(e); } private void deleteCollection() => collection.PerformWrite(c => c.Realm!.Remove(c)); From 1e38d1fa57c1fc37791ce7a58f9f1c1de05e6162 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 1 Aug 2024 18:45:47 -0700 Subject: [PATCH 244/266] Apply corner radius at a higher level so hover click sounds account for it --- osu.Game/Collections/DrawableCollectionListItem.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index 3b7649a30c98..e71368c079b4 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -43,6 +43,9 @@ public DrawableCollectionListItem(Live item, bool isCreated) // // if we want to support user sorting (but changes will need to be made to realm to persist). ShowDragHandle.Value = false; + + Masking = true; + CornerRadius = item_height / 2; } protected override Drawable CreateContent() => new ItemContent(Model); @@ -50,7 +53,7 @@ public DrawableCollectionListItem(Live item, bool isCreated) /// /// The main content of the . /// - private partial class ItemContent : CircularContainer + private partial class ItemContent : CompositeDrawable { private readonly Live collection; @@ -65,13 +68,12 @@ public ItemContent(Live collection) RelativeSizeAxes = Axes.X; Height = item_height; - Masking = true; } [BackgroundDependencyLoader] private void load() { - Children = new[] + InternalChildren = new[] { collection.IsManaged ? new DeleteButton(collection) From 3c1907ced3b084593fb3bc6e3796b811aa717dda Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Aug 2024 14:48:49 +0900 Subject: [PATCH 245/266] Update `LocalisationAnalyser` to latest version --- .config/dotnet-tools.json | 2 +- osu.Game/osu.Game.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index ace7db82f8e5..c4ba6e51430a 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -21,7 +21,7 @@ ] }, "ppy.localisationanalyser.tools": { - "version": "2024.517.0", + "version": "2024.802.0", "commands": [ "localisation" ] diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3d8b64327976..c25f16f1b02f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -30,7 +30,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From d9c965c47b4cc2291403ca5fbe7b4619c3541eff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Aug 2024 15:27:21 +0900 Subject: [PATCH 246/266] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e7d9d4c0227b..2ac086426684 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From d76fc34cf8e9f96890ebc3c2bc3e0ea2c224702e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Aug 2024 15:28:54 +0900 Subject: [PATCH 247/266] Update to use localiastions --- .../Components/DailyChallengeStreakDisplay.cs | 10 +++--- .../Components/DailyChallengeStreakTooltip.cs | 31 +++++++------------ 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs index da0e334a4e8e..289e820e4a63 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -10,6 +11,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Profile.Header.Components { @@ -50,8 +52,7 @@ private void load() new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12)) { AutoSizeAxes = Axes.Both, - // Text = UsersStrings.ShowDailyChallengeTitle - Text = "Daily\nChallenge", + Text = UsersStrings.ShowDailyChallengeTitle, Margin = new MarginPadding { Horizontal = 5f, Bottom = 2f }, }, new Container @@ -97,9 +98,8 @@ private void updateDisplay() } var statistics = User.Value.User.DailyChallengeStatistics; - // dailyStreak.Text = UsersStrings.ShowDailyChallengeUnitDay(statistics.PlayCount); - dailyStreak.Text = $"{statistics.PlayCount}d"; - TooltipContent = new DailyChallengeStreakTooltipData(colourProvider, statistics); + dailyStreak.Text = UsersStrings.ShowDailyChallengeUnitDay(statistics.PlayCount.ToLocalisableString("N0")); + TooltipContent = new DailyChallengeTooltipData(colourProvider, statistics); Show(); } diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs index a105659ac777..d33c7d950427 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Resources.Localisation.Web; using osu.Game.Scoring; using osuTK; using Box = osu.Framework.Graphics.Shapes.Box; @@ -78,10 +79,8 @@ private void load() Spacing = new Vector2(30f), Children = new[] { - // currentDaily = new StreakPiece(UsersStrings.ShowDailyChallengeDailyStreakCurrent), - // currentWeekly = new StreakPiece(UsersStrings.ShowDailyChallengeWeeklyStreakCurrent), - currentDaily = new StreakPiece("Current Daily Streak"), - currentWeekly = new StreakPiece("Current Weekly Streak"), + currentDaily = new StreakPiece(UsersStrings.ShowDailyChallengeDailyStreakCurrent), + currentWeekly = new StreakPiece(UsersStrings.ShowDailyChallengeWeeklyStreakCurrent), } }, } @@ -94,14 +93,10 @@ private void load() Spacing = new Vector2(10f), Children = new[] { - // bestDaily = new StatisticsPiece(UsersStrings.ShowDailyChallengeDailyStreakBest), - // bestWeekly = new StatisticsPiece(UsersStrings.ShowDailyChallengeWeeklyStreakBest), - // topTen = new StatisticsPiece(UsersStrings.ShowDailyChallengeTop10pPlacements), - // topFifty = new StatisticsPiece(UsersStrings.ShowDailyChallengeTop50pPlacements), - bestDaily = new StatisticsPiece("Best Daily Streak"), - bestWeekly = new StatisticsPiece("Best Weekly Streak"), - topTen = new StatisticsPiece("Top 10% Placements"), - topFifty = new StatisticsPiece("Top 50% Placements"), + bestDaily = new StatisticsPiece(UsersStrings.ShowDailyChallengeDailyStreakBest), + bestWeekly = new StatisticsPiece(UsersStrings.ShowDailyChallengeWeeklyStreakBest), + topTen = new StatisticsPiece(UsersStrings.ShowDailyChallengeTop10pPlacements), + topFifty = new StatisticsPiece(UsersStrings.ShowDailyChallengeTop50pPlacements), } }, } @@ -117,20 +112,16 @@ public void SetContent(DailyChallengeStreakTooltipData content) background.Colour = colourProvider.Background4; topBackground.Colour = colourProvider.Background5; - // currentDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(content.DailyStreakCurrent.ToLocalisableString(@"N0")); - currentDaily.Value = $"{statistics.DailyStreakCurrent:N0}d"; + currentDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(content.Statistics.DailyStreakCurrent.ToLocalisableString(@"N0")); currentDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakCurrent)); - // currentWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(statistics.WeeklyStreakCurrent.ToLocalisableString(@"N0")); - currentWeekly.Value = $"{statistics.WeeklyStreakCurrent:N0}w"; + currentWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(statistics.WeeklyStreakCurrent.ToLocalisableString(@"N0")); currentWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakCurrent)); - // bestDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(statistics.DailyStreakBest.ToLocalisableString(@"N0")); - bestDaily.Value = $"{statistics.DailyStreakBest:N0}d"; + bestDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(statistics.DailyStreakBest.ToLocalisableString(@"N0")); bestDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakBest)); - // bestWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(statistics.WeeklyStreakBest.ToLocalisableString(@"N0")); - bestWeekly.Value = $"{statistics.WeeklyStreakBest:N0}w"; + bestWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(statistics.WeeklyStreakBest.ToLocalisableString(@"N0")); bestWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakBest)); topTen.Value = statistics.Top10PercentPlacements.ToLocalisableString(@"N0"); From 816dee181ab34412940259e89d42eb9cc77230a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Aug 2024 15:31:22 +0900 Subject: [PATCH 248/266] Rename classes to remove "streak" terminology Since the primary display isn't showing a streak. --- .../Visual/Online/TestSceneUserProfileDailyChallenge.cs | 4 ++-- ...llengeStreakDisplay.cs => DailyChallengeStatsDisplay.cs} | 6 +++--- ...llengeStreakTooltip.cs => DailyChallengeStatsTooltip.cs} | 6 +++--- osu.Game/Overlays/Profile/Header/Components/MainDetails.cs | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) rename osu.Game/Overlays/Profile/Header/Components/{DailyChallengeStreakDisplay.cs => DailyChallengeStatsDisplay.cs} (92%) rename osu.Game/Overlays/Profile/Header/Components/{DailyChallengeStreakTooltip.cs => DailyChallengeStatsTooltip.cs} (96%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs index c0fb7b49f08a..f2135ec99289 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs @@ -27,7 +27,7 @@ protected override void LoadComplete() { base.LoadComplete(); - DailyChallengeStreakDisplay display = null!; + DailyChallengeStatsDisplay display = null!; AddSliderStep("daily", 0, 999, 2, v => update(s => s.DailyStreakCurrent = v)); AddSliderStep("daily best", 0, 999, 2, v => update(s => s.DailyStreakBest = v)); @@ -44,7 +44,7 @@ protected override void LoadComplete() RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background2, }); - Add(display = new DailyChallengeStreakDisplay + Add(display = new DailyChallengeStatsDisplay { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs similarity index 92% rename from osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs rename to osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs index 289e820e4a63..e154909139b9 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs @@ -15,11 +15,11 @@ namespace osu.Game.Overlays.Profile.Header.Components { - public partial class DailyChallengeStreakDisplay : CompositeDrawable, IHasCustomTooltip + public partial class DailyChallengeStatsDisplay : CompositeDrawable, IHasCustomTooltip { public readonly Bindable User = new Bindable(); - public DailyChallengeStreakTooltipData? TooltipContent { get; private set; } + public DailyChallengeTooltipData? TooltipContent { get; private set; } private OsuSpriteText dailyStreak = null!; @@ -103,6 +103,6 @@ private void updateDisplay() Show(); } - public ITooltip GetCustomTooltip() => new DailyChallengeStreakTooltip(); + public ITooltip GetCustomTooltip() => new DailyChallengeStatsTooltip(); } } diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs similarity index 96% rename from osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs rename to osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs index d33c7d950427..1b54633b8a9d 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { - public partial class DailyChallengeStreakTooltip : VisibilityContainer, ITooltip + public partial class DailyChallengeStatsTooltip : VisibilityContainer, ITooltip { private StreakPiece currentDaily = null!; private StreakPiece currentWeekly = null!; @@ -104,7 +104,7 @@ private void load() }; } - public void SetContent(DailyChallengeStreakTooltipData content) + public void SetContent(DailyChallengeTooltipData content) { var statistics = content.Statistics; var colourProvider = content.ColourProvider; @@ -237,5 +237,5 @@ public StatisticsPiece(LocalisableString title) } } - public record DailyChallengeStreakTooltipData(OverlayColourProvider ColourProvider, APIUserDailyChallengeStatistics Statistics); + public record DailyChallengeTooltipData(OverlayColourProvider ColourProvider, APIUserDailyChallengeStatistics Statistics); } diff --git a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs index f9a4267ed940..3d97082230f5 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs @@ -72,7 +72,7 @@ private void load() { Title = UsersStrings.ShowRankCountrySimple, }, - new DailyChallengeStreakDisplay + new DailyChallengeStatsDisplay { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, From 729039406bbd32102a2706f207a0c1fcbeba0f12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Aug 2024 15:37:22 +0900 Subject: [PATCH 249/266] Add colouring for play count Matches https://github.com/ppy/osu-web/pull/11381. --- .../Components/DailyChallengeStatsDisplay.cs | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs index e154909139b9..50c9b6e1f97a 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs @@ -11,7 +11,9 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; +using osu.Game.Scoring; namespace osu.Game.Overlays.Profile.Header.Components { @@ -21,7 +23,10 @@ public partial class DailyChallengeStatsDisplay : CompositeDrawable, IHasCustomT public DailyChallengeTooltipData? TooltipContent { get; private set; } - private OsuSpriteText dailyStreak = null!; + private OsuSpriteText dailyPlayCount = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -68,7 +73,7 @@ private void load() RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background6, }, - dailyStreak = new OsuSpriteText + dailyPlayCount = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -97,10 +102,16 @@ private void updateDisplay() return; } - var statistics = User.Value.User.DailyChallengeStatistics; - dailyStreak.Text = UsersStrings.ShowDailyChallengeUnitDay(statistics.PlayCount.ToLocalisableString("N0")); - TooltipContent = new DailyChallengeTooltipData(colourProvider, statistics); + APIUserDailyChallengeStatistics stats = User.Value.User.DailyChallengeStatistics; + + dailyPlayCount.Text = UsersStrings.ShowDailyChallengeUnitDay(stats.PlayCount.ToLocalisableString("N0")); + dailyPlayCount.Colour = colours.ForRankingTier(tierForPlayCount(stats.PlayCount)); + + TooltipContent = new DailyChallengeTooltipData(colourProvider, stats); + Show(); + + static RankingTier tierForPlayCount(int playCount) => DailyChallengeStatsTooltip.TierForDaily(playCount / 3); } public ITooltip GetCustomTooltip() => new DailyChallengeStatsTooltip(); From c3b2d81066d9885e263dc150158aed78cfcefb46 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 2 Aug 2024 09:23:25 +0300 Subject: [PATCH 250/266] Add failing test case --- .../Gameplay/TestScenePauseInputHandling.cs | 103 +++++++++++++----- 1 file changed, 76 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs index d778f2e99138..a6e062242c93 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; @@ -13,10 +14,12 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osu.Game.Storyboards; + using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -32,6 +35,23 @@ public partial class TestScenePauseInputHandling : PlayerTestScene [Resolved] private AudioManager audioManager { get; set; } = null!; + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap + { + HitObjects = + { + new HitCircle + { + Position = OsuPlayfield.BASE_SIZE / 2, + StartTime = 0, + }, + new HitCircle + { + Position = OsuPlayfield.BASE_SIZE / 2, + StartTime = 5000, + } + } + }; + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); @@ -70,18 +90,16 @@ public void TestOsuInputNotReceivedWhilePaused() AddStep("resume", () => Player.Resume()); AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); AddStep("press Z to resume", () => InputManager.PressKey(Key.Z)); - - // Z key was released before pause, resuming should not trigger it - checkKey(() => counter, 1, false); + checkKey(() => counter, 2, true); AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); - checkKey(() => counter, 1, false); + checkKey(() => counter, 2, false); AddStep("press Z", () => InputManager.PressKey(Key.Z)); - checkKey(() => counter, 2, true); + checkKey(() => counter, 3, true); AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); - checkKey(() => counter, 2, false); + checkKey(() => counter, 3, false); } [Test] @@ -90,30 +108,29 @@ public void TestManiaInputNotReceivedWhilePaused() KeyCounter counter = null!; loadPlayer(() => new ManiaRuleset()); - AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Key1)); + AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Special1)); checkKey(() => counter, 0, false); - AddStep("press D", () => InputManager.PressKey(Key.D)); + AddStep("press space", () => InputManager.PressKey(Key.Space)); checkKey(() => counter, 1, true); - AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + AddStep("release space", () => InputManager.ReleaseKey(Key.Space)); checkKey(() => counter, 1, false); AddStep("pause", () => Player.Pause()); - AddStep("press D", () => InputManager.PressKey(Key.D)); + AddStep("press space", () => InputManager.PressKey(Key.Space)); checkKey(() => counter, 1, false); - AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + AddStep("release space", () => InputManager.ReleaseKey(Key.Space)); checkKey(() => counter, 1, false); AddStep("resume", () => Player.Resume()); AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning); - checkKey(() => counter, 1, false); - AddStep("press D", () => InputManager.PressKey(Key.D)); + AddStep("press space", () => InputManager.PressKey(Key.Space)); checkKey(() => counter, 2, true); - AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + AddStep("release space", () => InputManager.ReleaseKey(Key.Space)); checkKey(() => counter, 2, false); } @@ -145,8 +162,11 @@ public void TestOsuPreviouslyHeldInputReleaseOnResume() AddStep("resume", () => Player.Resume()); AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); AddStep("press Z to resume", () => InputManager.PressKey(Key.Z)); - checkKey(() => counterZ, 1, false); + checkKey(() => counterZ, 2, true); checkKey(() => counterX, 1, false); + + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + checkKey(() => counterZ, 2, false); } [Test] @@ -155,12 +175,12 @@ public void TestManiaPreviouslyHeldInputReleaseOnResume() KeyCounter counter = null!; loadPlayer(() => new ManiaRuleset()); - AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Key1)); + AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Special1)); - AddStep("press D", () => InputManager.PressKey(Key.D)); + AddStep("press space", () => InputManager.PressKey(Key.Space)); AddStep("pause", () => Player.Pause()); - AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + AddStep("release space", () => InputManager.ReleaseKey(Key.Space)); checkKey(() => counter, 1, true); AddStep("resume", () => Player.Resume()); @@ -202,12 +222,14 @@ public void TestOsuHeldInputRemainHeldAfterResume() AddStep("resume", () => Player.Resume()); AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); AddStep("press Z to resume", () => InputManager.PressKey(Key.Z)); - checkKey(() => counterZ, 1, false); + checkKey(() => counterZ, 2, true); checkKey(() => counterX, 1, true); AddStep("release X", () => InputManager.ReleaseKey(Key.X)); - checkKey(() => counterZ, 1, false); checkKey(() => counterX, 1, false); + + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + checkKey(() => counterZ, 2, false); } [Test] @@ -216,22 +238,48 @@ public void TestManiaHeldInputRemainHeldAfterResume() KeyCounter counter = null!; loadPlayer(() => new ManiaRuleset()); - AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Key1)); + AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Special1)); - AddStep("press D", () => InputManager.PressKey(Key.D)); + AddStep("press space", () => InputManager.PressKey(Key.Space)); checkKey(() => counter, 1, true); AddStep("pause", () => Player.Pause()); - AddStep("release D", () => InputManager.ReleaseKey(Key.D)); - AddStep("press D", () => InputManager.PressKey(Key.D)); + AddStep("release space", () => InputManager.ReleaseKey(Key.Space)); + AddStep("press space", () => InputManager.PressKey(Key.Space)); AddStep("resume", () => Player.Resume()); AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning); checkKey(() => counter, 1, true); - AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + AddStep("release space", () => InputManager.ReleaseKey(Key.Space)); + checkKey(() => counter, 1, false); + } + + [Test] + public void TestOsuRegisterInputFromPressingOrangeCursorButPressIsBlocked() + { + KeyCounter counter = null!; + + loadPlayer(() => new OsuRuleset()); + AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == OsuAction.LeftButton)); + + AddStep("pause", () => Player.Pause()); + AddStep("resume", () => Player.Resume()); + AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("press Z to resume", () => InputManager.PressKey(Key.Z)); + + // ensure the input manager receives the Z button press... + checkKey(() => counter, 1, true); + AddAssert("button is pressed in kbc", () => Player.DrawableRuleset.Playfield.FindClosestParent()!.PressedActions.Single() == OsuAction.LeftButton); + + // ...but also ensure the hit circle in front of the cursor isn't hit by checking max combo. + AddAssert("circle not hit", () => Player.ScoreProcessor.HighestCombo.Value, () => Is.EqualTo(0)); + + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + checkKey(() => counter, 1, false); + AddAssert("button is released in kbc", () => !Player.DrawableRuleset.Playfield.FindClosestParent()!.PressedActions.Any()); } private void loadPlayer(Func createRuleset) @@ -241,9 +289,10 @@ private void loadPlayer(Func createRuleset) AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); AddUntilStep("wait for hud", () => Player.HUDOverlay.ChildrenOfType().All(s => s.ComponentsLoaded)); - AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(20000)); - AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(20000).Within(500)); + AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(0)); + AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(0).Within(500)); AddAssert("not in break", () => !Player.IsBreakTime.Value); + AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.DrawableRuleset.Playfield)); } private void checkKey(Func counter, int count, bool active) From eafc0f79afcbc64fd1289ebbc8772d1192e5a6b7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 2 Aug 2024 10:21:44 +0300 Subject: [PATCH 251/266] Fix clicking resume cursor not triggering a gameplay press in osu! --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index d809f2b31896..6970e7db1e53 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -115,10 +115,7 @@ public bool OnPressed(KeyBindingPressEvent e) return false; scaleTransitionContainer.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint); - - // When resuming with a button, we do not want the osu! input manager to see this button press and include it in the score. - // To ensure that this works correctly, schedule the resume operation one frame forward, since the resume operation enables the input manager to see input events. - Schedule(() => ResumeRequested?.Invoke()); + ResumeRequested?.Invoke(); return true; } From 5368a43633009f2a158a621d34c2a7fce84bd3e5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 2 Aug 2024 10:22:01 +0300 Subject: [PATCH 252/266] Fix clicking resume overlay hitting underlying hit circle --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 9 ++++ osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 46 +++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index df7f279656f5..dbb63a98c24a 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -206,6 +206,15 @@ private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos); + private OsuResumeOverlay.OsuResumeOverlayInputBlocker resumeInputBlocker; + + public void AttachResumeOverlayInputBlocker(OsuResumeOverlay.OsuResumeOverlayInputBlocker resumeInputBlocker) + { + Debug.Assert(this.resumeInputBlocker == null); + this.resumeInputBlocker = resumeInputBlocker; + AddInternal(resumeInputBlocker); + } + private partial class ProxyContainer : LifetimeManagementContainer { public void Add(Drawable proxy) => AddInternal(proxy); diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 6970e7db1e53..39a77d0b424c 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -33,9 +33,26 @@ public partial class OsuResumeOverlay : ResumeOverlay [BackgroundDependencyLoader] private void load() { + OsuResumeOverlayInputBlocker? inputBlocker = null; + + if (drawableRuleset != null) + { + var osuPlayfield = (OsuPlayfield)drawableRuleset.Playfield; + osuPlayfield.AttachResumeOverlayInputBlocker(inputBlocker = new OsuResumeOverlayInputBlocker()); + } + Add(cursorScaleContainer = new Container { - Child = clickToResumeCursor = new OsuClickToResumeCursor { ResumeRequested = Resume } + Child = clickToResumeCursor = new OsuClickToResumeCursor + { + ResumeRequested = () => + { + if (inputBlocker != null) + inputBlocker.BlockNextPress = true; + + Resume(); + } + } }); } @@ -140,5 +157,32 @@ private void updateColour() this.FadeColour(IsHovered ? Color4.White : Color4.Orange, 400, Easing.OutQuint); } } + + public partial class OsuResumeOverlayInputBlocker : Drawable, IKeyBindingHandler + { + public bool BlockNextPress; + + public OsuResumeOverlayInputBlocker() + { + RelativeSizeAxes = Axes.Both; + Depth = float.MinValue; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + try + { + return BlockNextPress; + } + finally + { + BlockNextPress = false; + } + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + } } } From 76904272e6e153d2f78514f76de76afe08cceabc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Aug 2024 16:56:34 +0900 Subject: [PATCH 253/266] Allow horizontal scrolling on mod select overlay anywhere on the screen Closes https://github.com/ppy/osu/issues/29248. --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 746959089520..858992b8baf7 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -668,6 +668,8 @@ private void setTextBoxFocus(bool focus) [Cached] internal partial class ColumnScrollContainer : OsuScrollContainer { + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + public ColumnScrollContainer() : base(Direction.Horizontal) { From f5a3eb56120503d3fc4ae23fb4efcc2c6a711b0e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 2 Aug 2024 11:01:40 +0300 Subject: [PATCH 254/266] Add comment --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 39a77d0b424c..44a2be0024fb 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -47,6 +47,10 @@ private void load() { ResumeRequested = () => { + // since the user had to press a button to tap the resume cursor, + // block that press event from potentially reaching a hit circle that's behind the cursor. + // we cannot do this from OsuClickToResumeCursor directly since we're in a different input manager tree than the gameplay one, + // so we rely on a dedicated input blocking component that's implanted in there to do that for us. if (inputBlocker != null) inputBlocker.BlockNextPress = true; From dc9f6a07cb751845bb871f88d21ccb614256d0b2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 2 Aug 2024 11:16:32 +0300 Subject: [PATCH 255/266] Fix inspections --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index dbb63a98c24a..7d9f5eb1a89b 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -206,7 +206,7 @@ private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos); - private OsuResumeOverlay.OsuResumeOverlayInputBlocker resumeInputBlocker; + private OsuResumeOverlay.OsuResumeOverlayInputBlocker? resumeInputBlocker; public void AttachResumeOverlayInputBlocker(OsuResumeOverlay.OsuResumeOverlayInputBlocker resumeInputBlocker) { diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs index a6e062242c93..2d03d0cb7ca4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs @@ -19,7 +19,6 @@ using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osu.Game.Storyboards; - using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay From 06af8cb9522fae15daead3c3892301fc5fe1cc46 Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Fri, 2 Aug 2024 16:23:37 +0800 Subject: [PATCH 256/266] interpolate parts in local space to avoid broken trails --- .../UI/Cursor/CursorTrail.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 5e8061bb6a2b..f684bcb58f6c 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -65,6 +65,7 @@ public CursorTrail() } AddLayout(partSizeCache); + AddLayout(scaleRatioCache); } [BackgroundDependencyLoader] @@ -154,8 +155,16 @@ protected override bool OnMouseMove(MouseMoveEvent e) return base.OnMouseMove(e); } + private readonly LayoutValue scaleRatioCache = new LayoutValue(Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence); + + private Vector2 scaleRatio => scaleRatioCache.IsValid + ? scaleRatioCache.Value + : (scaleRatioCache.Value = DrawInfo.MatrixInverse.ExtractScale().Xy); + protected void AddTrail(Vector2 position) { + position = ToLocalSpace(position); + if (InterpolateMovements) { if (!lastPosition.HasValue) @@ -174,10 +183,10 @@ protected void AddTrail(Vector2 position) float distance = diff.Length; Vector2 direction = diff / distance; - float interval = partSize.X / 2.5f * IntervalMultiplier; - float stopAt = distance - (AvoidDrawingNearCursor ? interval : 0); + Vector2 interval = partSize.X / 2.5f * IntervalMultiplier * scaleRatio; + float stopAt = distance - (AvoidDrawingNearCursor ? interval.Length : 0); - for (float d = interval; d < stopAt; d += interval) + for (Vector2 d = interval; d.Length < stopAt; d += interval) { lastPosition = pos1 + direction * d; addPart(lastPosition.Value); @@ -191,9 +200,9 @@ protected void AddTrail(Vector2 position) } } - private void addPart(Vector2 screenSpacePosition) + private void addPart(Vector2 localSpacePosition) { - parts[currentIndex].Position = ToLocalSpace(screenSpacePosition); + parts[currentIndex].Position = localSpacePosition; parts[currentIndex].Time = time + 1; ++parts[currentIndex].InvalidationID; From 4b5c163d93d912cd730d07ae9e76eeb7554a04e7 Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Fri, 2 Aug 2024 17:45:05 +0800 Subject: [PATCH 257/266] remove unnecessary LayoutValue --- .../UI/Cursor/CursorTrail.cs | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index f684bcb58f6c..6452444fedf1 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -16,7 +16,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.Input.Events; -using osu.Framework.Layout; using osu.Framework.Timing; using osuTK; using osuTK.Graphics; @@ -63,9 +62,6 @@ public CursorTrail() // -1 signals that the part is unusable, and should not be drawn parts[i].InvalidationID = -1; } - - AddLayout(partSizeCache); - AddLayout(scaleRatioCache); } [BackgroundDependencyLoader] @@ -96,12 +92,6 @@ public Texture Texture } } - private readonly LayoutValue partSizeCache = new LayoutValue(Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence); - - private Vector2 partSize => partSizeCache.IsValid - ? partSizeCache.Value - : (partSizeCache.Value = new Vector2(Texture.DisplayWidth, Texture.DisplayHeight) * DrawInfo.Matrix.ExtractScale().Xy); - /// /// The amount of time to fade the cursor trail pieces. /// @@ -155,12 +145,6 @@ protected override bool OnMouseMove(MouseMoveEvent e) return base.OnMouseMove(e); } - private readonly LayoutValue scaleRatioCache = new LayoutValue(Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence); - - private Vector2 scaleRatio => scaleRatioCache.IsValid - ? scaleRatioCache.Value - : (scaleRatioCache.Value = DrawInfo.MatrixInverse.ExtractScale().Xy); - protected void AddTrail(Vector2 position) { position = ToLocalSpace(position); @@ -183,10 +167,10 @@ protected void AddTrail(Vector2 position) float distance = diff.Length; Vector2 direction = diff / distance; - Vector2 interval = partSize.X / 2.5f * IntervalMultiplier * scaleRatio; - float stopAt = distance - (AvoidDrawingNearCursor ? interval.Length : 0); + float interval = Texture.DisplayWidth / 2.5f * IntervalMultiplier; + float stopAt = distance - (AvoidDrawingNearCursor ? interval : 0); - for (Vector2 d = interval; d.Length < stopAt; d += interval) + for (float d = interval; d < stopAt; d += interval) { lastPosition = pos1 + direction * d; addPart(lastPosition.Value); From 64b7bab4fbd6f08d3848d18673c0b78d8b33ab91 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 2 Aug 2024 18:59:21 +0900 Subject: [PATCH 258/266] Fix mod panels overflowing into the column borders --- osu.Game/Overlays/Mods/ModSelectColumn.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Mods/ModSelectColumn.cs b/osu.Game/Overlays/Mods/ModSelectColumn.cs index 5ffed24e7a85..8a499a391c36 100644 --- a/osu.Game/Overlays/Mods/ModSelectColumn.cs +++ b/osu.Game/Overlays/Mods/ModSelectColumn.cs @@ -138,6 +138,7 @@ protected ModSelectColumn() }, new GridContainer { + Padding = new MarginPadding { Top = 1, Bottom = 3 }, RelativeSizeAxes = Axes.Both, RowDimensions = new[] { From 531cf64ddbbf2e1673f63e38dfe54423a946779f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Aug 2024 13:15:05 +0900 Subject: [PATCH 259/266] Add failing test showing date added changing when importing as update with no change --- .../Database/BeatmapImporterUpdateTests.cs | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs index a47da4d505cd..3f1bc5814721 100644 --- a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs @@ -260,7 +260,7 @@ public void TestUpdatedImportContainsNothing() } [Test] - public void TestNoChanges() + public void TestNoChangesAfterDelete() { RunTestWithRealmAsync(async (realm, storage) => { @@ -272,21 +272,63 @@ public void TestNoChanges() var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal)); + importBeforeUpdate!.PerformWrite(s => s.DeletePending = true); + + var dateBefore = importBeforeUpdate.Value.DateAdded; + Assert.That(importBeforeUpdate, Is.Not.Null); Debug.Assert(importBeforeUpdate != null); var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginalSecond), importBeforeUpdate.Value); + realm.Run(r => r.Refresh()); + Assert.That(importAfterUpdate, Is.Not.Null); Debug.Assert(importAfterUpdate != null); + checkCount(realm, 1); + checkCount(realm, count_beatmaps); + checkCount(realm, count_beatmaps); + + Assert.That(importBeforeUpdate.Value.Beatmaps.First().OnlineID, Is.GreaterThan(-1)); + Assert.That(importBeforeUpdate.Value.DateAdded, Is.EqualTo(dateBefore)); + Assert.That(importAfterUpdate.Value.DateAdded, Is.EqualTo(dateBefore)); + Assert.That(importBeforeUpdate.ID, Is.EqualTo(importAfterUpdate.ID)); + }); + } + + [Test] + public void TestNoChanges() + { + RunTestWithRealmAsync(async (realm, storage) => + { + var importer = new BeatmapImporter(storage, realm); + using var rulesets = new RealmRulesetStore(realm, storage); + + using var __ = getBeatmapArchive(out string pathOriginal); + using var _ = getBeatmapArchive(out string pathOriginalSecond); + + var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal)); + + var dateBefore = importBeforeUpdate!.Value.DateAdded; + + Assert.That(importBeforeUpdate, Is.Not.Null); + Debug.Assert(importBeforeUpdate != null); + + var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginalSecond), importBeforeUpdate.Value); + realm.Run(r => r.Refresh()); + Assert.That(importAfterUpdate, Is.Not.Null); + Debug.Assert(importAfterUpdate != null); + checkCount(realm, 1); checkCount(realm, count_beatmaps); checkCount(realm, count_beatmaps); Assert.That(importBeforeUpdate.Value.Beatmaps.First().OnlineID, Is.GreaterThan(-1)); + Assert.That(importBeforeUpdate.Value.DateAdded, Is.EqualTo(dateBefore)); + Assert.That(importAfterUpdate.Value.DateAdded, Is.EqualTo(dateBefore)); Assert.That(importBeforeUpdate.ID, Is.EqualTo(importAfterUpdate.ID)); }); } From dc73856f76fe71404aeb512ae4233be00309185b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Aug 2024 20:45:57 +0900 Subject: [PATCH 260/266] Fix original date not being restored when no changes are made on an import-as-update operation --- osu.Game/Beatmaps/BeatmapImporter.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 71aa5b0333f5..8acaebd1a8bb 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -43,6 +43,8 @@ public BeatmapImporter(Storage storage, RealmAccess realm) public override async Task?> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original) { + var originalDateAdded = original.DateAdded; + Guid originalId = original.ID; var imported = await Import(notification, new[] { importTask }).ConfigureAwait(false); @@ -57,8 +59,11 @@ public BeatmapImporter(Storage storage, RealmAccess realm) // If there were no changes, ensure we don't accidentally nuke ourselves. if (first.ID == originalId) { - first.PerformRead(s => + first.PerformWrite(s => { + // Transfer local values which should be persisted across a beatmap update. + s.DateAdded = originalDateAdded; + // Re-run processing even in this case. We might have outdated metadata. ProcessBeatmap?.Invoke(s, MetadataLookupScope.OnlineFirst); }); @@ -79,7 +84,7 @@ public BeatmapImporter(Storage storage, RealmAccess realm) original.DeletePending = true; // Transfer local values which should be persisted across a beatmap update. - updated.DateAdded = original.DateAdded; + updated.DateAdded = originalDateAdded; transferCollectionReferences(realm, original, updated); @@ -278,6 +283,9 @@ protected override bool CanReuseExisting(BeatmapSetInfo existing, BeatmapSetInfo protected override void UndeleteForReuse(BeatmapSetInfo existing) { + if (!existing.DeletePending) + return; + base.UndeleteForReuse(existing); existing.DateAdded = DateTimeOffset.UtcNow; } From c27b35ad14fa50e6f35ac6b8188902e3f189a79d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Aug 2024 20:58:52 +0900 Subject: [PATCH 261/266] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 7785cb3c9466..3b3385ecfe36 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index dceb88c6f7ac..196d5594adb8 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 4ef9f335eef73f607ccd6d2e7ab8bb6d3003775e Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 2 Aug 2024 10:19:59 -0700 Subject: [PATCH 262/266] Fix customise button on mod overlay initially showing flash layer indefinitely --- osu.Game/Overlays/Mods/ModCustomisationHeader.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index fb9e960f41ca..540ed8ee948d 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -54,6 +54,7 @@ private void load() RelativeSizeAxes = Axes.Both, Colour = Color4.White.Opacity(0.4f), Blending = BlendingParameters.Additive, + Alpha = 0, }, new OsuSpriteText { From d95d63d7ee4a9c2a925fdbfb3cea6a8f642ec7c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 3 Aug 2024 22:44:51 +0900 Subject: [PATCH 263/266] Undo localisation of Daily Challenge string for now --- .../Profile/Header/Components/DailyChallengeStatsDisplay.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs index 50c9b6e1f97a..f55eb595d7fd 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs @@ -57,7 +57,9 @@ private void load() new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12)) { AutoSizeAxes = Axes.Both, - Text = UsersStrings.ShowDailyChallengeTitle, + // can't use this because osu-web does weird stuff with \\n. + // Text = UsersStrings.ShowDailyChallengeTitle., + Text = "Daily\nChallenge", Margin = new MarginPadding { Horizontal = 5f, Bottom = 2f }, }, new Container From 2daf1b58f2dcdbd0fbf308547d99c55d6f667f2e Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 3 Aug 2024 14:48:08 -0700 Subject: [PATCH 264/266] Allow searching enum descriptions from `SettingsEnumDropdown`s --- osu.Game/Overlays/Settings/SettingsEnumDropdown.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs index cf6bc30f8516..2b74557c1a8a 100644 --- a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs @@ -2,7 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Extensions; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings @@ -10,6 +14,8 @@ namespace osu.Game.Overlays.Settings public partial class SettingsEnumDropdown : SettingsDropdown where T : struct, Enum { + public override IEnumerable FilterTerms => base.FilterTerms.Concat(Control.Items.Select(i => i.GetLocalisableDescription())); + protected override OsuDropdown CreateDropdown() => new DropdownControl(); protected new partial class DropdownControl : OsuEnumDropdown From a5a392e9fc7e9cc121abfe722ab5ad1d8509f7e0 Mon Sep 17 00:00:00 2001 From: AkiraTenchi <34791734+AkiraTenchi@users.noreply.github.com> Date: Sun, 4 Aug 2024 19:48:29 +0200 Subject: [PATCH 265/266] Update FilterQueryParser.cs Add sr as an alias for star rating in the search parameters --- osu.Game/Screens/Select/FilterQueryParser.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 4e49495f4709..40fd289be620 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -40,6 +40,7 @@ private static bool tryParseKeywordCriteria(FilterCriteria criteria, string key, { case "star": case "stars": + case "sr": return TryUpdateCriteriaRange(ref criteria.StarDifficulty, op, value, 0.01d / 2); case "ar": From 20b890570e2edaf39d2b6e68a9feac15db23d8c4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 5 Aug 2024 13:28:42 +0900 Subject: [PATCH 266/266] Replace try-finally with return Try-finally has a small overhead that's unnecessary in this case given how small the code block is. --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 44a2be0024fb..d90d3d26eb3e 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -174,14 +174,9 @@ public OsuResumeOverlayInputBlocker() public bool OnPressed(KeyBindingPressEvent e) { - try - { - return BlockNextPress; - } - finally - { - BlockNextPress = false; - } + bool block = BlockNextPress; + BlockNextPress = false; + return block; } public void OnReleased(KeyBindingReleaseEvent e)