Merge branch 'master' into beatmap-overlay-null-pp

This commit is contained in:
Salman Ahmed 2022-07-25 07:18:03 +03:00
commit 3beb1da3de
11 changed files with 474 additions and 238 deletions

View File

@ -3,10 +3,10 @@
#nullable disable #nullable disable
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
@ -40,8 +40,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private TestReplayRecorder recorder; private TestReplayRecorder recorder;
[Cached] private GameplayState gameplayState;
private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
[SetUpSteps] [SetUpSteps]
public void SetUpSteps() public void SetUpSteps()
@ -52,81 +51,15 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
replay = new Replay(); replay = new Replay();
Add(new GridContainer gameplayState = TestGameplayState.Create(new OsuRuleset());
gameplayState.Score.Replay = replay;
Child = new DependencyProvidingContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Content = new[] CachedDependencies = new (Type, object)[] { (typeof(GameplayState), gameplayState) },
{ Child = createContent(),
new Drawable[] };
{
recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
Recorder = recorder = new TestReplayRecorder(new Score
{
Replay = replay,
ScoreInfo =
{
BeatmapInfo = gameplayState.Beatmap.BeatmapInfo,
Ruleset = new OsuRuleset().RulesetInfo,
}
})
{
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
},
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Colour = Color4.Brown,
RelativeSizeAxes = Axes.Both,
},
new OsuSpriteText
{
Text = "Recording",
Scale = new Vector2(3),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new TestInputConsumer()
}
},
}
},
new Drawable[]
{
playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
ReplayInputHandler = new TestFramedReplayInputHandler(replay)
{
GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
},
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Colour = Color4.DarkBlue,
RelativeSizeAxes = Axes.Both,
},
new OsuSpriteText
{
Text = "Playback",
Scale = new Vector2(3),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new TestInputConsumer()
}
},
}
}
}
});
}); });
} }
@ -203,6 +136,74 @@ namespace osu.Game.Tests.Visual.Gameplay
recorder = null; recorder = null;
} }
private Drawable createContent() => new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
Recorder = recorder = new TestReplayRecorder(gameplayState.Score)
{
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
},
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Colour = Color4.Brown,
RelativeSizeAxes = Axes.Both,
},
new OsuSpriteText
{
Text = "Recording",
Scale = new Vector2(3),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new TestInputConsumer()
}
},
}
},
new Drawable[]
{
playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
ReplayInputHandler = new TestFramedReplayInputHandler(replay)
{
GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
},
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Colour = Color4.DarkBlue,
RelativeSizeAxes = Axes.Both,
},
new OsuSpriteText
{
Text = "Playback",
Scale = new Vector2(3),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new TestInputConsumer()
}
},
}
}
}
};
public class TestFramedReplayInputHandler : FramedReplayInputHandler<TestReplayFrame> public class TestFramedReplayInputHandler : FramedReplayInputHandler<TestReplayFrame>
{ {
public TestFramedReplayInputHandler(Replay replay) public TestFramedReplayInputHandler(Replay replay)

View File

@ -5,6 +5,7 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Spectator; using osu.Game.Online.Spectator;
@ -43,6 +44,21 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("spectator client sent correct ruleset", () => spectatorClient.WatchedUserStates[dummy_user_id].RulesetID == Ruleset.Value.OnlineID); AddAssert("spectator client sent correct ruleset", () => spectatorClient.WatchedUserStates[dummy_user_id].RulesetID == Ruleset.Value.OnlineID);
} }
[Test]
public void TestRestart()
{
AddAssert("spectator client sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing);
AddStep("exit player", () => Player.Exit());
AddStep("reload player", LoadPlayer);
AddUntilStep("wait for player load", () => Player.IsLoaded && Player.Alpha == 1);
AddAssert("spectator client sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing);
AddWaitStep("wait", 5);
AddUntilStep("spectator client still sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing);
}
public override void TearDownSteps() public override void TearDownSteps()
{ {
base.TearDownSteps(); base.TearDownSteps();

View File

@ -8,8 +8,10 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Overlays; using osu.Game.Overlays;
@ -17,11 +19,26 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
public class TestSceneLabelledSliderBar : OsuTestScene public class TestSceneLabelledSliderBar : OsuTestScene
{ {
[TestCase(false)] [Test]
[TestCase(true)] public void TestBasic() => createSliderBar();
public void TestSliderBar(bool hasDescription) => createSliderBar(hasDescription);
private void createSliderBar(bool hasDescription = false) [Test]
public void TestDescription()
{
createSliderBar();
AddStep("set description", () => this.ChildrenOfType<LabelledSliderBar<double>>().ForEach(l => l.Description = "this text describes the component"));
}
[Test]
public void TestSize()
{
createSliderBar();
AddStep("set zero width", () => this.ChildrenOfType<LabelledSliderBar<double>>().ForEach(l => l.ResizeWidthTo(0, 200, Easing.OutQuint)));
AddStep("set negative width", () => this.ChildrenOfType<LabelledSliderBar<double>>().ForEach(l => l.ResizeWidthTo(-1, 200, Easing.OutQuint)));
AddStep("revert back", () => this.ChildrenOfType<LabelledSliderBar<double>>().ForEach(l => l.ResizeWidthTo(1, 200, Easing.OutQuint)));
}
private void createSliderBar()
{ {
AddStep("create component", () => AddStep("create component", () =>
{ {
@ -38,6 +55,8 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
new LabelledSliderBar<double> new LabelledSliderBar<double>
{ {
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Current = new BindableDouble(5) Current = new BindableDouble(5)
{ {
MinValue = 0, MinValue = 0,
@ -45,7 +64,6 @@ namespace osu.Game.Tests.Visual.UserInterface
Precision = 1, Precision = 1,
}, },
Label = "a sample component", Label = "a sample component",
Description = hasDescription ? "this text describes the component" : string.Empty,
}, },
}, },
}; };
@ -54,10 +72,14 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
flow.Add(new OverlayColourContainer(colour) flow.Add(new OverlayColourContainer(colour)
{ {
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Child = new LabelledSliderBar<double> Child = new LabelledSliderBar<double>
{ {
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Current = new BindableDouble(5) Current = new BindableDouble(5)
{ {
MinValue = 0, MinValue = 0,
@ -65,7 +87,6 @@ namespace osu.Game.Tests.Visual.UserInterface
Precision = 1, Precision = 1,
}, },
Label = "a sample component", Label = "a sample component",
Description = hasDescription ? "this text describes the component" : string.Empty,
} }
}); });
} }

View File

@ -0,0 +1,77 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneModPresetColumn : OsuTestScene
{
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
[Test]
public void TestBasicAppearance()
{
ModPresetColumn modPresetColumn = null!;
AddStep("create content", () => Child = new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(30),
Child = modPresetColumn = new ModPresetColumn
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Presets = createTestPresets().ToArray()
}
});
AddStep("change presets", () => modPresetColumn.Presets = createTestPresets().Skip(1).ToArray());
}
private static IEnumerable<ModPreset> createTestPresets() => new[]
{
new ModPreset
{
Name = "First preset",
Description = "Please ignore",
Mods = new Mod[]
{
new OsuModHardRock(),
new OsuModDoubleTime()
}
},
new ModPreset
{
Name = "AR0",
Description = "For good readers",
Mods = new Mod[]
{
new OsuModDifficultyAdjust
{
ApproachRate = { Value = 0 }
}
}
},
new ModPreset
{
Name = "This preset is going to have an extraordinarily long name",
Description = "This is done so that the capability to truncate overlong texts may be demonstrated",
Mods = new Mod[]
{
new OsuModFlashlight(),
new OsuModSpinIn()
}
}
};
}
}

View File

@ -228,10 +228,8 @@ namespace osu.Game.Graphics.UserInterface
protected override void UpdateAfterChildren() protected override void UpdateAfterChildren()
{ {
base.UpdateAfterChildren(); base.UpdateAfterChildren();
LeftBox.Scale = new Vector2(Math.Clamp( LeftBox.Scale = new Vector2(Math.Clamp(RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, Math.Max(0, DrawWidth)), 1);
RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1); RightBox.Scale = new Vector2(Math.Clamp(DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2, 0, Math.Max(0, DrawWidth)), 1);
RightBox.Scale = new Vector2(Math.Clamp(
DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2, 0, DrawWidth), 1);
} }
protected override void UpdateValue(float value) protected override void UpdateValue(float value)

View File

@ -24,6 +24,11 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString ModCustomisation => new TranslatableString(getKey(@"mod_customisation"), @"Mod Customisation"); public static LocalisableString ModCustomisation => new TranslatableString(getKey(@"mod_customisation"), @"Mod Customisation");
/// <summary>
/// "Personal Presets"
/// </summary>
public static LocalisableString PersonalPresets => new TranslatableString(getKey(@"personal_presets"), @"Personal Presets");
private static string getKey(string key) => $@"{prefix}:{key}"; private static string getKey(string key) => $@"{prefix}:{key}";
} }
} }

View File

@ -206,6 +206,11 @@ namespace osu.Game.Online.Spectator
if (!IsPlaying) if (!IsPlaying)
return; return;
// Disposal can take some time, leading to EndPlaying potentially being called after a future play session.
// Account for this by ensuring the score of the current play matches the one in the provided state.
if (currentScore != state.Score)
return;
if (pendingFrames.Count > 0) if (pendingFrames.Count > 0)
purgePendingFrames(); purgePendingFrames();

View File

@ -12,14 +12,10 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation; using osu.Game.Localisation;
using osu.Game.Overlays.Mods.Input; using osu.Game.Overlays.Mods.Input;
@ -29,10 +25,8 @@ using osuTK.Graphics;
namespace osu.Game.Overlays.Mods namespace osu.Game.Overlays.Mods
{ {
public class ModColumn : CompositeDrawable public class ModColumn : ModSelectColumn
{ {
public readonly Container TopLevelContent;
public readonly ModType ModType; public readonly ModType ModType;
private IReadOnlyList<ModState> availableMods = Array.Empty<ModState>(); private IReadOnlyList<ModState> availableMods = Array.Empty<ModState>();
@ -62,149 +56,29 @@ namespace osu.Game.Overlays.Mods
} }
} }
/// <summary>
/// Determines whether this column should accept user input.
/// </summary>
public Bindable<bool> Active = new BindableBool(true);
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && Active.Value;
protected virtual ModPanel CreateModPanel(ModState mod) => new ModPanel(mod); protected virtual ModPanel CreateModPanel(ModState mod) => new ModPanel(mod);
private readonly bool allowIncompatibleSelection; private readonly bool allowIncompatibleSelection;
private readonly TextFlowContainer headerText;
private readonly Box headerBackground;
private readonly Container contentContainer;
private readonly Box contentBackground;
private readonly FillFlowContainer<ModPanel> panelFlow;
private readonly ToggleAllCheckbox? toggleAllCheckbox; private readonly ToggleAllCheckbox? toggleAllCheckbox;
private Colour4 accentColour;
private Bindable<ModSelectHotkeyStyle> hotkeyStyle = null!; private Bindable<ModSelectHotkeyStyle> hotkeyStyle = null!;
private IModHotkeyHandler hotkeyHandler = null!; private IModHotkeyHandler hotkeyHandler = null!;
private Task? latestLoadTask; private Task? latestLoadTask;
internal bool ItemsLoaded => latestLoadTask == null; internal bool ItemsLoaded => latestLoadTask == null;
private const float header_height = 42;
public ModColumn(ModType modType, bool allowIncompatibleSelection) public ModColumn(ModType modType, bool allowIncompatibleSelection)
{ {
ModType = modType; ModType = modType;
this.allowIncompatibleSelection = allowIncompatibleSelection; this.allowIncompatibleSelection = allowIncompatibleSelection;
Width = 320; HeaderText = ModType.Humanize(LetterCasing.Title);
RelativeSizeAxes = Axes.Y;
Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0);
Container controlContainer;
InternalChildren = new Drawable[]
{
TopLevelContent = new Container
{
RelativeSizeAxes = Axes.Both,
CornerRadius = ModSelectPanel.CORNER_RADIUS,
Masking = true,
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.X,
Height = header_height + ModSelectPanel.CORNER_RADIUS,
Children = new Drawable[]
{
headerBackground = new Box
{
RelativeSizeAxes = Axes.X,
Height = header_height + ModSelectPanel.CORNER_RADIUS
},
headerText = new OsuTextFlowContainer(t =>
{
t.Font = OsuFont.TorusAlternate.With(size: 17);
t.Shadow = false;
t.Colour = Colour4.Black;
})
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
Padding = new MarginPadding
{
Horizontal = 17,
Bottom = ModSelectPanel.CORNER_RADIUS
}
}
}
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = header_height },
Child = contentContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = ModSelectPanel.CORNER_RADIUS,
BorderThickness = 3,
Children = new Drawable[]
{
contentBackground = new Box
{
RelativeSizeAxes = Axes.Both
},
new GridContainer
{
RelativeSizeAxes = Axes.Both,
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension()
},
Content = new[]
{
new Drawable[]
{
controlContainer = new Container
{
RelativeSizeAxes = Axes.X,
Padding = new MarginPadding { Horizontal = 14 }
}
},
new Drawable[]
{
new OsuScrollContainer(Direction.Vertical)
{
RelativeSizeAxes = Axes.Both,
ClampExtension = 100,
ScrollbarOverlapsContent = false,
Child = panelFlow = new FillFlowContainer<ModPanel>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(0, 7),
Padding = new MarginPadding(7)
}
}
}
}
}
}
}
}
}
}
};
createHeaderText();
if (allowIncompatibleSelection) if (allowIncompatibleSelection)
{ {
controlContainer.Height = 35; ControlContainer.Height = 35;
controlContainer.Add(toggleAllCheckbox = new ToggleAllCheckbox(this) ControlContainer.Add(toggleAllCheckbox = new ToggleAllCheckbox(this)
{ {
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
@ -212,7 +86,7 @@ namespace osu.Game.Overlays.Mods
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0) Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0)
}); });
panelFlow.Padding = new MarginPadding ItemsFlow.Padding = new MarginPadding
{ {
Top = 0, Top = 0,
Bottom = 7, Bottom = 7,
@ -221,33 +95,17 @@ namespace osu.Game.Overlays.Mods
} }
} }
private void createHeaderText()
{
IEnumerable<string> headerTextWords = ModType.Humanize(LetterCasing.Title).Split(' ');
if (headerTextWords.Count() > 1)
{
headerText.AddText($"{headerTextWords.First()} ", t => t.Font = t.Font.With(weight: FontWeight.SemiBold));
headerTextWords = headerTextWords.Skip(1);
}
headerText.AddText(string.Join(' ', headerTextWords));
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider, OsuColour colours, OsuConfigManager configManager) private void load(OsuColour colours, OsuConfigManager configManager)
{ {
headerBackground.Colour = accentColour = colours.ForModType(ModType); AccentColour = colours.ForModType(ModType);
if (toggleAllCheckbox != null) if (toggleAllCheckbox != null)
{ {
toggleAllCheckbox.AccentColour = accentColour; toggleAllCheckbox.AccentColour = AccentColour;
toggleAllCheckbox.AccentHoverColour = accentColour.Lighten(0.3f); toggleAllCheckbox.AccentHoverColour = AccentColour.Lighten(0.3f);
} }
contentContainer.BorderColour = ColourInfo.GradientVertical(colourProvider.Background4, colourProvider.Background3);
contentBackground.Colour = colourProvider.Background4;
hotkeyStyle = configManager.GetBindable<ModSelectHotkeyStyle>(OsuSetting.ModSelectHotkeyStyle); hotkeyStyle = configManager.GetBindable<ModSelectHotkeyStyle>(OsuSetting.ModSelectHotkeyStyle);
} }
@ -278,7 +136,7 @@ namespace osu.Game.Overlays.Mods
latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded => latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded =>
{ {
panelFlow.ChildrenEnumerable = loaded; ItemsFlow.ChildrenEnumerable = loaded;
updateState(); updateState();
}, (cancellationTokenSource = new CancellationTokenSource()).Token); }, (cancellationTokenSource = new CancellationTokenSource()).Token);
loadTask.ContinueWith(_ => loadTask.ContinueWith(_ =>

View File

@ -0,0 +1,77 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Game.Graphics;
using osu.Game.Localisation;
using osu.Game.Rulesets.Mods;
using osuTK;
namespace osu.Game.Overlays.Mods
{
public class ModPresetColumn : ModSelectColumn
{
private IReadOnlyList<ModPreset> presets = Array.Empty<ModPreset>();
/// <summary>
/// Sets the collection of available mod presets.
/// </summary>
public IReadOnlyList<ModPreset> Presets
{
get => presets;
set
{
presets = value;
if (IsLoaded)
asyncLoadPanels();
}
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
AccentColour = colours.Orange1;
HeaderText = ModSelectOverlayStrings.PersonalPresets;
}
protected override void LoadComplete()
{
base.LoadComplete();
asyncLoadPanels();
}
private CancellationTokenSource? cancellationTokenSource;
private Task? latestLoadTask;
internal bool ItemsLoaded => latestLoadTask == null;
private void asyncLoadPanels()
{
cancellationTokenSource?.Cancel();
var panels = presets.Select(preset => new ModPresetPanel(preset)
{
Shear = Vector2.Zero
});
Task? loadTask;
latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded =>
{
ItemsFlow.ChildrenEnumerable = loaded;
}, (cancellationTokenSource = new CancellationTokenSource()).Token);
loadTask.ContinueWith(_ =>
{
if (loadTask == latestLoadTask)
latestLoadTask = null;
});
}
}
}

View File

@ -0,0 +1,177 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.Mods
{
public abstract class ModSelectColumn : CompositeDrawable, IHasAccentColour
{
public readonly Container TopLevelContent;
public LocalisableString HeaderText
{
set => createHeaderText(value);
}
public Color4 AccentColour
{
get => headerBackground.Colour;
set => headerBackground.Colour = value;
}
/// <summary>
/// Determines whether this column should accept user input.
/// </summary>
public readonly Bindable<bool> Active = new BindableBool(true);
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && Active.Value;
protected readonly Container ControlContainer;
protected readonly FillFlowContainer ItemsFlow;
private readonly TextFlowContainer headerText;
private readonly Box headerBackground;
private readonly Container contentContainer;
private readonly Box contentBackground;
private const float header_height = 42;
protected ModSelectColumn()
{
Width = 320;
RelativeSizeAxes = Axes.Y;
Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0);
InternalChildren = new Drawable[]
{
TopLevelContent = new Container
{
RelativeSizeAxes = Axes.Both,
CornerRadius = ModSelectPanel.CORNER_RADIUS,
Masking = true,
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.X,
Height = header_height + ModSelectPanel.CORNER_RADIUS,
Children = new Drawable[]
{
headerBackground = new Box
{
RelativeSizeAxes = Axes.X,
Height = header_height + ModSelectPanel.CORNER_RADIUS
},
headerText = new OsuTextFlowContainer(t =>
{
t.Font = OsuFont.TorusAlternate.With(size: 17);
t.Shadow = false;
t.Colour = Colour4.Black;
})
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
Padding = new MarginPadding
{
Horizontal = 17,
Bottom = ModSelectPanel.CORNER_RADIUS
}
}
}
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = header_height },
Child = contentContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = ModSelectPanel.CORNER_RADIUS,
BorderThickness = 3,
Children = new Drawable[]
{
contentBackground = new Box
{
RelativeSizeAxes = Axes.Both
},
new GridContainer
{
RelativeSizeAxes = Axes.Both,
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension()
},
Content = new[]
{
new Drawable[]
{
ControlContainer = new Container
{
RelativeSizeAxes = Axes.X,
Padding = new MarginPadding { Horizontal = 14 }
}
},
new Drawable[]
{
new OsuScrollContainer(Direction.Vertical)
{
RelativeSizeAxes = Axes.Both,
ClampExtension = 100,
ScrollbarOverlapsContent = false,
Child = ItemsFlow = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(0, 7),
Padding = new MarginPadding(7)
}
}
}
}
}
}
}
}
}
}
};
}
private void createHeaderText(LocalisableString text)
{
headerText.Clear();
int wordIndex = 0;
headerText.AddText(text, t =>
{
if (wordIndex == 0)
t.Font = t.Font.With(weight: FontWeight.SemiBold);
wordIndex += 1;
});
}
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
contentContainer.BorderColour = ColourInfo.GradientVertical(colourProvider.Background4, colourProvider.Background3);
contentBackground.Colour = colourProvider.Background4;
}
}
}

View File

@ -70,6 +70,7 @@ namespace osu.Game.Screens.Play
{ {
ScoreInfo = ScoreInfo =
{ {
BeatmapInfo = beatmap.BeatmapInfo,
Ruleset = ruleset.RulesetInfo Ruleset = ruleset.RulesetInfo
} }
}; };