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

Merge branch 'master' into crop-texture-uploads-song-select

This commit is contained in:
Bartłomiej Dach 2023-06-08 12:24:16 +02:00 committed by GitHub
commit e6503c24c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 203 additions and 123 deletions

View File

@ -15,187 +15,175 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements
[Test] [Test]
public void TestHitAllDrumRoll() public void TestHitAllDrumRoll()
{ {
const double hit_time = 1000;
PerformTest(new List<ReplayFrame> PerformTest(new List<ReplayFrame>
{ {
new TaikoReplayFrame(0), new TaikoReplayFrame(0),
new TaikoReplayFrame(1000, TaikoAction.LeftCentre), new TaikoReplayFrame(1000, TaikoAction.LeftCentre),
new TaikoReplayFrame(1001), new TaikoReplayFrame(1001),
new TaikoReplayFrame(1250, TaikoAction.LeftCentre),
new TaikoReplayFrame(1251),
new TaikoReplayFrame(1500, TaikoAction.LeftCentre),
new TaikoReplayFrame(1501),
new TaikoReplayFrame(1750, TaikoAction.LeftCentre),
new TaikoReplayFrame(1751),
new TaikoReplayFrame(2000, TaikoAction.LeftCentre), new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
new TaikoReplayFrame(2001), new TaikoReplayFrame(2001),
}, CreateBeatmap(new DrumRoll }, CreateBeatmap(createDrumRoll(false)));
{
StartTime = hit_time,
Duration = 1000
}));
AssertJudgementCount(3); AssertJudgementCount(6);
AssertResult<DrumRollTick>(0, HitResult.SmallBonus); AssertResult<DrumRollTick>(0, HitResult.SmallBonus);
AssertResult<DrumRollTick>(1, HitResult.SmallBonus); AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
AssertResult<DrumRollTick>(2, HitResult.SmallBonus);
AssertResult<DrumRollTick>(3, HitResult.SmallBonus);
AssertResult<DrumRollTick>(4, HitResult.SmallBonus);
AssertResult<DrumRoll>(0, HitResult.IgnoreHit); AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
} }
[Test] [Test]
public void TestHitSomeDrumRoll() public void TestHitSomeDrumRoll()
{ {
const double hit_time = 1000;
PerformTest(new List<ReplayFrame> PerformTest(new List<ReplayFrame>
{ {
new TaikoReplayFrame(0), new TaikoReplayFrame(0),
new TaikoReplayFrame(2000, TaikoAction.LeftCentre), new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
new TaikoReplayFrame(2001), new TaikoReplayFrame(2001),
}, CreateBeatmap(new DrumRoll }, CreateBeatmap(createDrumRoll(false)));
{
StartTime = hit_time,
Duration = 1000
}));
AssertJudgementCount(3); AssertJudgementCount(6);
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss); AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(1, HitResult.SmallBonus); AssertResult<DrumRollTick>(1, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(2, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(3, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(4, HitResult.SmallBonus);
AssertResult<DrumRoll>(0, HitResult.IgnoreHit); AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
} }
[Test] [Test]
public void TestHitNoneDrumRoll() public void TestHitNoneDrumRoll()
{ {
const double hit_time = 1000;
PerformTest(new List<ReplayFrame> PerformTest(new List<ReplayFrame>
{ {
new TaikoReplayFrame(0), new TaikoReplayFrame(0),
}, CreateBeatmap(new DrumRoll }, CreateBeatmap(createDrumRoll(false)));
{
StartTime = hit_time,
Duration = 1000
}));
AssertJudgementCount(3); AssertJudgementCount(6);
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss); AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(1, HitResult.IgnoreMiss); AssertResult<DrumRollTick>(1, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(2, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(3, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(4, HitResult.IgnoreMiss);
AssertResult<DrumRoll>(0, HitResult.IgnoreHit); AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
} }
[Test] [Test]
public void TestHitAllStrongDrumRollWithOneKey() public void TestHitAllStrongDrumRollWithOneKey()
{ {
const double hit_time = 1000;
PerformTest(new List<ReplayFrame> PerformTest(new List<ReplayFrame>
{ {
new TaikoReplayFrame(0), new TaikoReplayFrame(0),
new TaikoReplayFrame(1000, TaikoAction.LeftCentre), new TaikoReplayFrame(1000, TaikoAction.LeftCentre),
new TaikoReplayFrame(1001), new TaikoReplayFrame(1001),
new TaikoReplayFrame(1250, TaikoAction.LeftCentre),
new TaikoReplayFrame(1251),
new TaikoReplayFrame(1500, TaikoAction.LeftCentre),
new TaikoReplayFrame(1501),
new TaikoReplayFrame(1750, TaikoAction.LeftCentre),
new TaikoReplayFrame(1751),
new TaikoReplayFrame(2000, TaikoAction.LeftCentre), new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
new TaikoReplayFrame(2001), new TaikoReplayFrame(2001),
}, CreateBeatmap(new DrumRoll }, CreateBeatmap(createDrumRoll(true)));
AssertJudgementCount(12);
for (int i = 0; i < 5; i++)
{ {
StartTime = hit_time, AssertResult<DrumRollTick>(i, HitResult.SmallBonus);
Duration = 1000, AssertResult<StrongNestedHitObject>(i, HitResult.LargeBonus);
IsStrong = true }
}));
AssertJudgementCount(6);
AssertResult<DrumRollTick>(0, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(0, HitResult.LargeBonus);
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(1, HitResult.LargeBonus);
AssertResult<DrumRoll>(0, HitResult.IgnoreHit); AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
AssertResult<StrongNestedHitObject>(2, HitResult.IgnoreHit); AssertResult<StrongNestedHitObject>(5, HitResult.IgnoreHit);
} }
[Test] [Test]
public void TestHitSomeStrongDrumRollWithOneKey() public void TestHitSomeStrongDrumRollWithOneKey()
{ {
const double hit_time = 1000;
PerformTest(new List<ReplayFrame> PerformTest(new List<ReplayFrame>
{ {
new TaikoReplayFrame(0), new TaikoReplayFrame(0),
new TaikoReplayFrame(2000, TaikoAction.LeftCentre), new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
new TaikoReplayFrame(2001), new TaikoReplayFrame(2001),
}, CreateBeatmap(new DrumRoll }, CreateBeatmap(createDrumRoll(true)));
{
StartTime = hit_time,
Duration = 1000,
IsStrong = true
}));
AssertJudgementCount(6); AssertJudgementCount(12);
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss); AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
AssertResult<StrongNestedHitObject>(0, HitResult.IgnoreMiss); AssertResult<StrongNestedHitObject>(0, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(1, HitResult.SmallBonus); AssertResult<DrumRollTick>(4, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(1, HitResult.LargeBonus); AssertResult<StrongNestedHitObject>(4, HitResult.LargeBonus);
AssertResult<DrumRoll>(0, HitResult.IgnoreHit); AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
AssertResult<StrongNestedHitObject>(2, HitResult.IgnoreHit); AssertResult<StrongNestedHitObject>(5, HitResult.IgnoreHit);
} }
[Test] [Test]
public void TestHitAllStrongDrumRollWithBothKeys() public void TestHitAllStrongDrumRollWithBothKeys()
{ {
const double hit_time = 1000;
PerformTest(new List<ReplayFrame> PerformTest(new List<ReplayFrame>
{ {
new TaikoReplayFrame(0), new TaikoReplayFrame(0),
new TaikoReplayFrame(1000, TaikoAction.LeftCentre, TaikoAction.RightCentre), new TaikoReplayFrame(1000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
new TaikoReplayFrame(1001), new TaikoReplayFrame(1001),
new TaikoReplayFrame(1250, TaikoAction.LeftCentre, TaikoAction.RightCentre),
new TaikoReplayFrame(1251),
new TaikoReplayFrame(1500, TaikoAction.LeftCentre, TaikoAction.RightCentre),
new TaikoReplayFrame(1501),
new TaikoReplayFrame(1750, TaikoAction.LeftCentre, TaikoAction.RightCentre),
new TaikoReplayFrame(1751),
new TaikoReplayFrame(2000, TaikoAction.LeftCentre, TaikoAction.RightCentre), new TaikoReplayFrame(2000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
new TaikoReplayFrame(2001), new TaikoReplayFrame(2001),
}, CreateBeatmap(new DrumRoll }, CreateBeatmap(createDrumRoll(true)));
AssertJudgementCount(12);
for (int i = 0; i < 5; i++)
{ {
StartTime = hit_time, AssertResult<DrumRollTick>(i, HitResult.SmallBonus);
Duration = 1000, AssertResult<StrongNestedHitObject>(i, HitResult.LargeBonus);
IsStrong = true }
}));
AssertJudgementCount(6);
AssertResult<DrumRollTick>(0, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(0, HitResult.LargeBonus);
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(1, HitResult.LargeBonus);
AssertResult<DrumRoll>(0, HitResult.IgnoreHit); AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
AssertResult<StrongNestedHitObject>(2, HitResult.IgnoreHit); AssertResult<StrongNestedHitObject>(5, HitResult.IgnoreHit);
} }
[Test] [Test]
public void TestHitSomeStrongDrumRollWithBothKeys() public void TestHitSomeStrongDrumRollWithBothKeys()
{ {
const double hit_time = 1000;
PerformTest(new List<ReplayFrame> PerformTest(new List<ReplayFrame>
{ {
new TaikoReplayFrame(0), new TaikoReplayFrame(0),
new TaikoReplayFrame(2000, TaikoAction.LeftCentre, TaikoAction.RightCentre), new TaikoReplayFrame(2000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
new TaikoReplayFrame(2001), new TaikoReplayFrame(2001),
}, CreateBeatmap(new DrumRoll }, CreateBeatmap(createDrumRoll(true)));
{
StartTime = hit_time,
Duration = 1000,
IsStrong = true
}));
AssertJudgementCount(6); AssertJudgementCount(12);
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss); AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
AssertResult<StrongNestedHitObject>(0, HitResult.IgnoreMiss); AssertResult<StrongNestedHitObject>(0, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(1, HitResult.SmallBonus); AssertResult<DrumRollTick>(4, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(1, HitResult.LargeBonus); AssertResult<StrongNestedHitObject>(4, HitResult.LargeBonus);
AssertResult<DrumRoll>(0, HitResult.IgnoreHit); AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
AssertResult<StrongNestedHitObject>(2, HitResult.IgnoreHit); AssertResult<StrongNestedHitObject>(5, HitResult.IgnoreHit);
} }
private DrumRoll createDrumRoll(bool strong) => new DrumRoll
{
StartTime = 1000,
Duration = 1000,
IsStrong = strong
};
} }
} }

View File

@ -92,6 +92,14 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
}).ToList(); }).ToList();
} }
// TODO: stable makes the last tick of a drumroll non-required when the next object is too close.
// This probably needs to be reimplemented:
//
// List<HitObject> hitobjects = hitObjectManager.hitObjects;
// int ind = hitobjects.IndexOf(this);
// if (i < hitobjects.Count - 1 && hitobjects[i + 1].HittableStartTime - (EndTime + (int)TickSpacing) <= (int)TickSpacing)
// lastTickHittable = false;
return converted; return converted;
} }
@ -133,7 +141,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
StartTime = obj.StartTime, StartTime = obj.StartTime,
Samples = obj.Samples, Samples = obj.Samples,
Duration = taikoDuration, Duration = taikoDuration,
TickRate = beatmap.Difficulty.SliderTickRate == 3 ? 3 : 4,
SliderVelocity = obj is IHasSliderVelocity velocityData ? velocityData.SliderVelocity : 1 SliderVelocity = obj is IHasSliderVelocity velocityData ? velocityData.SliderVelocity : 1
}; };
} }

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using System.Threading; using System.Threading;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -69,6 +67,8 @@ namespace osu.Game.Rulesets.Taiko.Objects
double scoringDistance = base_distance * difficulty.SliderMultiplier * SliderVelocity; double scoringDistance = base_distance * difficulty.SliderMultiplier * SliderVelocity;
Velocity = scoringDistance / timingPoint.BeatLength; Velocity = scoringDistance / timingPoint.BeatLength;
TickRate = difficulty.SliderTickRate == 3 ? 3 : 4;
tickSpacing = timingPoint.BeatLength / TickRate; tickSpacing = timingPoint.BeatLength / TickRate;
} }

View File

@ -131,7 +131,7 @@ namespace osu.Game.Tests.Editing.Checks
var mock = new Mock<IWorkingBeatmap>(); var mock = new Mock<IWorkingBeatmap>();
mock.SetupGet(w => w.Beatmap).Returns(beatmap); mock.SetupGet(w => w.Beatmap).Returns(beatmap);
mock.SetupGet(w => w.GetBackground()).Returns(background); mock.Setup(w => w.GetBackground()).Returns(background);
mock.Setup(w => w.GetStream(It.IsAny<string>())).Returns(stream); mock.Setup(w => w.GetStream(It.IsAny<string>())).Returns(stream);
return mock; return mock;

View File

@ -80,6 +80,24 @@ namespace osu.Game.Tests.Visual.UserInterface
}); });
} }
[Test]
public void TestCorrectScrollToWhenContentLoads()
{
AddRepeatStep("add many sections", () => append(1f), 3);
AddStep("add section with delayed load content", () =>
{
container.Add(new TestDelayedLoadSection("delayed"));
});
AddStep("add final section", () => append(0.5f));
AddStep("scroll to final section", () => container.ScrollTo(container.Children.Last()));
AddUntilStep("correct section selected", () => container.SelectedSection.Value == container.Children.Last());
AddUntilStep("wait for scroll to section", () => container.ScreenSpaceDrawQuad.AABBFloat.Contains(container.Children.Last().ScreenSpaceDrawQuad.AABBFloat));
}
[Test] [Test]
public void TestSelection() public void TestSelection()
{ {
@ -196,6 +214,33 @@ namespace osu.Game.Tests.Visual.UserInterface
InputManager.ScrollVerticalBy(direction); InputManager.ScrollVerticalBy(direction);
} }
private partial class TestDelayedLoadSection : TestSection
{
public TestDelayedLoadSection(string label)
: base(label)
{
BackgroundColour = default_colour;
Width = 300;
AutoSizeAxes = Axes.Y;
}
protected override void LoadComplete()
{
base.LoadComplete();
Box box;
Add(box = new Box
{
Alpha = 0.01f,
RelativeSizeAxes = Axes.X,
});
// Emulate an operation that will be inhibited by IsMaskedAway.
box.ResizeHeightTo(2000, 50);
}
}
private partial class TestSection : TestBox private partial class TestSection : TestBox
{ {
public bool Selected public bool Selected

View File

@ -1,17 +1,16 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Layout; using osu.Framework.Layout;
using osu.Framework.Logging;
using osu.Framework.Threading;
using osu.Framework.Utils; using osu.Framework.Utils;
namespace osu.Game.Graphics.Containers namespace osu.Game.Graphics.Containers
@ -23,11 +22,35 @@ namespace osu.Game.Graphics.Containers
public partial class SectionsContainer<T> : Container<T> public partial class SectionsContainer<T> : Container<T>
where T : Drawable where T : Drawable
{ {
public Bindable<T> SelectedSection { get; } = new Bindable<T>(); public Bindable<T?> SelectedSection { get; } = new Bindable<T?>();
private T lastClickedSection; private T? lastClickedSection;
public Drawable ExpandableHeader protected override Container<T> Content => scrollContentContainer;
private readonly UserTrackingScrollContainer scrollContainer;
private readonly Container headerBackgroundContainer;
private readonly MarginPadding originalSectionsMargin;
private Drawable? fixedHeader;
private Drawable? footer;
private Drawable? headerBackground;
private FlowContainer<T> scrollContentContainer = null!;
private float? headerHeight, footerHeight;
private float? lastKnownScroll;
/// <summary>
/// The percentage of the container to consider the centre-point for deciding the active section (and scrolling to a requested section).
/// </summary>
private const float scroll_y_centre = 0.1f;
private Drawable? expandableHeader;
public Drawable? ExpandableHeader
{ {
get => expandableHeader; get => expandableHeader;
set set
@ -42,11 +65,12 @@ namespace osu.Game.Graphics.Containers
if (value == null) return; if (value == null) return;
AddInternal(expandableHeader); AddInternal(expandableHeader);
lastKnownScroll = null; lastKnownScroll = null;
} }
} }
public Drawable FixedHeader public Drawable? FixedHeader
{ {
get => fixedHeader; get => fixedHeader;
set set
@ -63,7 +87,7 @@ namespace osu.Game.Graphics.Containers
} }
} }
public Drawable Footer public Drawable? Footer
{ {
get => footer; get => footer;
set set
@ -75,16 +99,17 @@ namespace osu.Game.Graphics.Containers
footer = value; footer = value;
if (value == null) return; if (footer == null) return;
footer.Anchor |= Anchor.y2; footer.Anchor |= Anchor.y2;
footer.Origin |= Anchor.y2; footer.Origin |= Anchor.y2;
scrollContainer.Add(footer); scrollContainer.Add(footer);
lastKnownScroll = null; lastKnownScroll = null;
} }
} }
public Drawable HeaderBackground public Drawable? HeaderBackground
{ {
get => headerBackground; get => headerBackground;
set set
@ -102,23 +127,6 @@ namespace osu.Game.Graphics.Containers
} }
} }
protected override Container<T> Content => scrollContentContainer;
private readonly UserTrackingScrollContainer scrollContainer;
private readonly Container headerBackgroundContainer;
private readonly MarginPadding originalSectionsMargin;
private Drawable expandableHeader, fixedHeader, footer, headerBackground;
private FlowContainer<T> scrollContentContainer;
private float? headerHeight, footerHeight;
private float? lastKnownScroll;
/// <summary>
/// The percentage of the container to consider the centre-point for deciding the active section (and scrolling to a requested section).
/// </summary>
private const float scroll_y_centre = 0.1f;
public SectionsContainer() public SectionsContainer()
{ {
AddRangeInternal(new Drawable[] AddRangeInternal(new Drawable[]
@ -150,31 +158,63 @@ namespace osu.Game.Graphics.Containers
footerHeight = null; footerHeight = null;
} }
private ScheduledDelegate? scrollToTargetDelegate;
public void ScrollTo(Drawable target) public void ScrollTo(Drawable target)
{ {
Logger.Log($"Scrolling to {target}..");
lastKnownScroll = null; lastKnownScroll = null;
// implementation similar to ScrollIntoView but a bit more nuanced. float scrollTarget = getScrollTargetForDrawable(target);
float top = scrollContainer.GetChildPosInContent(target);
float bottomScrollExtent = scrollContainer.ScrollableExtent; if (scrollTarget > scrollContainer.ScrollableExtent)
float scrollTarget = top - scrollContainer.DisplayableContent * scroll_y_centre;
if (scrollTarget > bottomScrollExtent)
scrollContainer.ScrollToEnd(); scrollContainer.ScrollToEnd();
else else
scrollContainer.ScrollTo(scrollTarget); scrollContainer.ScrollTo(scrollTarget);
if (target is T section) if (target is T section)
lastClickedSection = section; lastClickedSection = section;
// Content may load in as a scroll occurs, changing the scroll target we need to aim for.
// This scheduled operation ensures that we keep trying until actually arriving at the target.
scrollToTargetDelegate?.Cancel();
scrollToTargetDelegate = Scheduler.AddDelayed(() =>
{
if (scrollContainer.UserScrolling)
{
Logger.Log("Scroll operation interrupted by user scroll");
scrollToTargetDelegate?.Cancel();
scrollToTargetDelegate = null;
return;
}
if (Precision.AlmostEquals(scrollContainer.Current, scrollTarget, 1))
{
Logger.Log($"Finished scrolling to {target}!");
scrollToTargetDelegate?.Cancel();
scrollToTargetDelegate = null;
return;
}
if (!Precision.AlmostEquals(getScrollTargetForDrawable(target), scrollTarget, 1))
{
Logger.Log($"Reattempting scroll to {target} due to change in position");
ScrollTo(target);
}
}, 50, true);
}
private float getScrollTargetForDrawable(Drawable target)
{
// implementation similar to ScrollIntoView but a bit more nuanced.
return scrollContainer.GetChildPosInContent(target) - scrollContainer.DisplayableContent * scroll_y_centre;
} }
public void ScrollToTop() => scrollContainer.ScrollTo(0); public void ScrollToTop() => scrollContainer.ScrollTo(0);
[NotNull]
protected virtual UserTrackingScrollContainer CreateScrollContainer() => new UserTrackingScrollContainer(); protected virtual UserTrackingScrollContainer CreateScrollContainer() => new UserTrackingScrollContainer();
[NotNull]
protected virtual FlowContainer<T> CreateScrollContentContainer() => protected virtual FlowContainer<T> CreateScrollContentContainer() =>
new FillFlowContainer<T> new FillFlowContainer<T>
{ {

View File

@ -328,7 +328,7 @@ namespace osu.Game.Overlays
base.UpdateAfterChildren(); base.UpdateAfterChildren();
// no null check because the usage of this class is strict // no null check because the usage of this class is strict
HeaderBackground.Alpha = -ExpandableHeader.Y / ExpandableHeader.LayoutSize.Y; HeaderBackground!.Alpha = -ExpandableHeader!.Y / ExpandableHeader.LayoutSize.Y;
} }
} }
} }

View File

@ -120,7 +120,7 @@ namespace osu.Game.Overlays
if (lastSection != section.NewValue) if (lastSection != section.NewValue)
{ {
lastSection = section.NewValue; lastSection = section.NewValue;
tabs.Current.Value = lastSection; tabs.Current.Value = lastSection!;
} }
}; };

View File

@ -65,7 +65,7 @@ namespace osu.Game.Screens.Edit.Setup
{ {
base.LoadComplete(); base.LoadComplete();
sections.SelectedSection.BindValueChanged(section => tabControl.Current.Value = section.NewValue); sections.SelectedSection.BindValueChanged(section => tabControl.Current.Value = section.NewValue!);
tabControl.Current.BindValueChanged(section => tabControl.Current.BindValueChanged(section =>
{ {
if (section.NewValue != sections.SelectedSection.Value) if (section.NewValue != sections.SelectedSection.Value)