mirror of
https://github.com/ppy/osu.git
synced 2025-01-15 06:42:56 +08:00
Merge branch 'master' into new-chat-drawable-channel
This commit is contained in:
commit
1c63c27fdf
@ -2,12 +2,6 @@
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-format": {
|
||||
"version": "3.1.37601",
|
||||
"commands": [
|
||||
"dotnet-format"
|
||||
]
|
||||
},
|
||||
"jetbrains.resharper.globaltools": {
|
||||
"version": "2022.1.0-eap10",
|
||||
"commands": [
|
||||
|
@ -1,6 +1,14 @@
|
||||
# EditorConfig is awesome: http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*.{csproj,props,targets}]
|
||||
charset = utf-8-bom
|
||||
end_of_line = crlf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.cs]
|
||||
end_of_line = crlf
|
||||
insert_final_newline = true
|
||||
@ -8,8 +16,19 @@ indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
#license header
|
||||
file_header_template = Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.\nSee the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#Roslyn naming styles
|
||||
|
||||
#PascalCase for public and protected members
|
||||
dotnet_naming_style.pascalcase.capitalization = pascal_case
|
||||
dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
|
||||
dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event
|
||||
dotnet_naming_rule.public_members_pascalcase.severity = error
|
||||
dotnet_naming_rule.public_members_pascalcase.symbols = public_members
|
||||
dotnet_naming_rule.public_members_pascalcase.style = pascalcase
|
||||
|
||||
#camelCase for private members
|
||||
dotnet_naming_style.camelcase.capitalization = camel_case
|
||||
|
||||
@ -172,24 +191,11 @@ csharp_style_prefer_index_operator = false:silent
|
||||
csharp_style_prefer_range_operator = false:silent
|
||||
csharp_style_prefer_switch_expression = false:none
|
||||
|
||||
#Supressing roslyn built-in analyzers
|
||||
# Suppress: EC112
|
||||
|
||||
#Private method is unused
|
||||
dotnet_diagnostic.IDE0051.severity = silent
|
||||
#Private member is unused
|
||||
dotnet_diagnostic.IDE0052.severity = silent
|
||||
|
||||
#Rules for disposable
|
||||
dotnet_diagnostic.IDE0067.severity = none
|
||||
dotnet_diagnostic.IDE0068.severity = none
|
||||
dotnet_diagnostic.IDE0069.severity = none
|
||||
|
||||
#Disable operator overloads requiring alternate named methods
|
||||
dotnet_diagnostic.CA2225.severity = none
|
||||
|
||||
# Banned APIs
|
||||
dotnet_diagnostic.RS0030.severity = error
|
||||
[*.{yaml,yml}]
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
dotnet_diagnostic.OLOC001.words_in_name = 5
|
||||
dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text.
|
||||
|
9
.github/workflows/ci.yml
vendored
9
.github/workflows/ci.yml
vendored
@ -33,6 +33,9 @@ jobs:
|
||||
path: ${{ github.workspace }}/inspectcode
|
||||
key: inspectcode-${{ hashFiles('.config/dotnet-tools.json') }}-${{ hashFiles('.github/workflows/ci.yml' ) }}
|
||||
|
||||
- name: Dotnet code style
|
||||
run: dotnet build -c Debug -warnaserror osu.Desktop.slnf -p:EnforceCodeStyleInBuild=true
|
||||
|
||||
- name: CodeFileSanity
|
||||
run: |
|
||||
# TODO: Add ignore filters and GitHub Workflow Command Reporting in CFS. That way we don't have to do this workaround.
|
||||
@ -46,10 +49,6 @@ jobs:
|
||||
done <<< $(dotnet codefilesanity)
|
||||
exit $exit_code
|
||||
|
||||
# Temporarily disabled due to test failures, but it won't work anyway until the tool is upgraded.
|
||||
# - name: .NET Format (Dry Run)
|
||||
# run: dotnet format --dry-run --check
|
||||
|
||||
- name: InspectCode
|
||||
run: dotnet jb inspectcode $(pwd)/osu.Desktop.slnf --no-build --output="inspectcodereport.xml" --caches-home="inspectcode" --verbosity=WARN
|
||||
|
||||
@ -147,4 +146,4 @@ jobs:
|
||||
# cannot accept .sln(f) files as arguments.
|
||||
# Build just the main game for now.
|
||||
- name: Build
|
||||
run: msbuild osu.iOS/osu.iOS.csproj /restore /p:Configuration=Debug
|
||||
run: msbuild osu.iOS/osu.iOS.csproj /restore /p:Configuration=Debug
|
||||
|
55
.globalconfig
Normal file
55
.globalconfig
Normal file
@ -0,0 +1,55 @@
|
||||
is_global = true
|
||||
|
||||
# .NET Code Style
|
||||
# IDE styles reference: https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/
|
||||
|
||||
# IDE0001: Simplify names
|
||||
dotnet_diagnostic.IDE0001.severity = warning
|
||||
|
||||
# IDE0002: Simplify member access
|
||||
dotnet_diagnostic.IDE0002.severity = warning
|
||||
|
||||
# IDE0003: Remove qualification
|
||||
dotnet_diagnostic.IDE0003.severity = warning
|
||||
|
||||
# IDE0004: Remove unnecessary cast
|
||||
dotnet_diagnostic.IDE0004.severity = warning
|
||||
|
||||
# IDE0005: Remove unnecessary imports
|
||||
dotnet_diagnostic.IDE0005.severity = warning
|
||||
|
||||
# IDE0034: Simplify default literal
|
||||
dotnet_diagnostic.IDE0034.severity = warning
|
||||
|
||||
# IDE0036: Sort modifiers
|
||||
dotnet_diagnostic.IDE0036.severity = warning
|
||||
|
||||
# IDE0040: Add accessibility modifier
|
||||
dotnet_diagnostic.IDE0040.severity = warning
|
||||
|
||||
# IDE0049: Use keyword for type name
|
||||
dotnet_diagnostic.IDE0040.severity = warning
|
||||
|
||||
# IDE0055: Fix formatting
|
||||
dotnet_diagnostic.IDE0055.severity = warning
|
||||
|
||||
# IDE0051: Private method is unused
|
||||
dotnet_diagnostic.IDE0051.severity = silent
|
||||
|
||||
# IDE0052: Private member is unused
|
||||
dotnet_diagnostic.IDE0052.severity = silent
|
||||
|
||||
# IDE0073: File header
|
||||
dotnet_diagnostic.IDE0073.severity = warning
|
||||
|
||||
# IDE0130: Namespace mismatch with folder
|
||||
dotnet_diagnostic.IDE0130.severity = warning
|
||||
|
||||
# IDE1006: Naming style
|
||||
dotnet_diagnostic.IDE1006.severity = warning
|
||||
|
||||
#Disable operator overloads requiring alternate named methods
|
||||
dotnet_diagnostic.CA2225.severity = none
|
||||
|
||||
# Banned APIs
|
||||
dotnet_diagnostic.RS0030.severity = error
|
@ -1,4 +1,4 @@
|
||||
<!-- Contains required properties for osu!framework projects. -->
|
||||
<!-- Contains required properties for osu!framework projects. -->
|
||||
<Project>
|
||||
<PropertyGroup Label="C#">
|
||||
<LangVersion>8.0</LangVersion>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<OutputPath>bin\$(Configuration)</OutputPath>
|
||||
@ -52,7 +52,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.422.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.430.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.509.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Transitive Dependencies">
|
||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||
|
@ -29,13 +29,14 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
|
||||
protected CatchSelectionBlueprintTestScene()
|
||||
{
|
||||
EditorBeatmap = new EditorBeatmap(new CatchBeatmap
|
||||
var catchBeatmap = new CatchBeatmap
|
||||
{
|
||||
BeatmapInfo =
|
||||
{
|
||||
Ruleset = new CatchRuleset().RulesetInfo,
|
||||
}
|
||||
}) { Difficulty = { CircleSize = 0 } };
|
||||
};
|
||||
EditorBeatmap = new EditorBeatmap(catchBeatmap) { Difficulty = { CircleSize = 0 } };
|
||||
EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint
|
||||
{
|
||||
BeatLength = 100
|
||||
|
@ -55,7 +55,10 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
|
||||
AddMoveStep(end_time, 0);
|
||||
AddClickStep(MouseButton.Left);
|
||||
|
||||
AddMoveStep(start_time, 0);
|
||||
AddAssert("duration is positive", () => ((BananaShower)CurrentBlueprint.HitObject).Duration > 0);
|
||||
|
||||
AddClickStep(MouseButton.Right);
|
||||
AddAssert("start time is correct", () => Precision.AlmostEquals(LastObject.HitObject.StartTime, start_time));
|
||||
AddAssert("end time is correct", () => Precision.AlmostEquals(LastObject.HitObject.GetEndTime(), end_time));
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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 osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
@ -13,11 +14,21 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||
{
|
||||
private readonly TimeSpanOutline outline;
|
||||
|
||||
private double placementStartTime;
|
||||
private double placementEndTime;
|
||||
|
||||
public BananaShowerPlacementBlueprint()
|
||||
{
|
||||
InternalChild = outline = new TimeSpanOutline();
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
BeginPlacement();
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
@ -38,13 +49,6 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||
case PlacementState.Active:
|
||||
if (e.Button != MouseButton.Right) break;
|
||||
|
||||
// If the duration is negative, swap the start and the end time to make the duration positive.
|
||||
if (HitObject.Duration < 0)
|
||||
{
|
||||
HitObject.StartTime = HitObject.EndTime;
|
||||
HitObject.Duration = -HitObject.Duration;
|
||||
}
|
||||
|
||||
EndPlacement(HitObject.Duration > 0);
|
||||
return true;
|
||||
}
|
||||
@ -61,13 +65,16 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||
switch (PlacementActive)
|
||||
{
|
||||
case PlacementState.Waiting:
|
||||
HitObject.StartTime = time;
|
||||
placementStartTime = placementEndTime = time;
|
||||
break;
|
||||
|
||||
case PlacementState.Active:
|
||||
HitObject.EndTime = time;
|
||||
placementEndTime = time;
|
||||
break;
|
||||
}
|
||||
|
||||
HitObject.StartTime = Math.Min(placementStartTime, placementEndTime);
|
||||
HitObject.EndTime = Math.Max(placementStartTime, placementEndTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||
base.LoadComplete();
|
||||
|
||||
inputManager = GetContainingInputManager();
|
||||
|
||||
BeginPlacement();
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
|
@ -107,6 +107,18 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
switch (e.Type)
|
||||
{
|
||||
case SliderEventType.Tick:
|
||||
AddNested(new SliderTick
|
||||
{
|
||||
SpanIndex = e.SpanIndex,
|
||||
SpanStartTime = e.SpanStartTime,
|
||||
StartTime = e.Time,
|
||||
Position = Position + Path.PositionAt(e.PathProgress),
|
||||
StackHeight = StackHeight,
|
||||
Scale = Scale,
|
||||
});
|
||||
break;
|
||||
|
||||
case SliderEventType.Head:
|
||||
AddNested(HeadCircle = new SliderHeadCircle
|
||||
{
|
||||
|
@ -137,33 +137,137 @@ namespace osu.Game.Tests.Mods
|
||||
// incompatible pair.
|
||||
new object[]
|
||||
{
|
||||
new Mod[] { new OsuModDoubleTime(), new OsuModHalfTime() },
|
||||
new[] { typeof(OsuModDoubleTime), typeof(OsuModHalfTime) }
|
||||
new Mod[] { new OsuModHidden(), new OsuModApproachDifferent() },
|
||||
new[] { typeof(OsuModHidden), typeof(OsuModApproachDifferent) }
|
||||
},
|
||||
// incompatible pair with derived class.
|
||||
new object[]
|
||||
{
|
||||
new Mod[] { new OsuModNightcore(), new OsuModHalfTime() },
|
||||
new[] { typeof(OsuModNightcore), typeof(OsuModHalfTime) }
|
||||
new Mod[] { new OsuModDeflate(), new OsuModApproachDifferent() },
|
||||
new[] { typeof(OsuModDeflate), typeof(OsuModApproachDifferent) }
|
||||
},
|
||||
// system mod.
|
||||
new object[]
|
||||
{
|
||||
new Mod[] { new OsuModDoubleTime(), new OsuModTouchDevice() },
|
||||
new Mod[] { new OsuModHidden(), new OsuModTouchDevice() },
|
||||
new[] { typeof(OsuModTouchDevice) }
|
||||
},
|
||||
// multi mod.
|
||||
new object[]
|
||||
{
|
||||
new Mod[] { new MultiMod(new OsuModHalfTime()), new OsuModDaycore() },
|
||||
new Mod[] { new MultiMod(new OsuModSuddenDeath(), new OsuModPerfect()) },
|
||||
new[] { typeof(MultiMod) }
|
||||
},
|
||||
// invalid multiplayer mod is valid for local.
|
||||
new object[]
|
||||
{
|
||||
new Mod[] { new OsuModHidden(), new InvalidMultiplayerMod() },
|
||||
null
|
||||
},
|
||||
// invalid free mod is valid for local.
|
||||
new object[]
|
||||
{
|
||||
new Mod[] { new OsuModHidden(), new InvalidMultiplayerFreeMod() },
|
||||
null
|
||||
},
|
||||
// valid pair.
|
||||
new object[]
|
||||
{
|
||||
new Mod[] { new OsuModDoubleTime(), new OsuModHardRock() },
|
||||
new Mod[] { new OsuModHidden(), new OsuModHardRock() },
|
||||
null
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
private static readonly object[] invalid_multiplayer_mod_test_scenarios =
|
||||
{
|
||||
// incompatible pair.
|
||||
new object[]
|
||||
{
|
||||
new Mod[] { new OsuModHidden(), new OsuModApproachDifferent() },
|
||||
new[] { typeof(OsuModHidden), typeof(OsuModApproachDifferent) }
|
||||
},
|
||||
// incompatible pair with derived class.
|
||||
new object[]
|
||||
{
|
||||
new Mod[] { new OsuModDeflate(), new OsuModApproachDifferent() },
|
||||
new[] { typeof(OsuModDeflate), typeof(OsuModApproachDifferent) }
|
||||
},
|
||||
// system mod.
|
||||
new object[]
|
||||
{
|
||||
new Mod[] { new OsuModHidden(), new OsuModTouchDevice() },
|
||||
new[] { typeof(OsuModTouchDevice) }
|
||||
},
|
||||
// multi mod.
|
||||
new object[]
|
||||
{
|
||||
new Mod[] { new MultiMod(new OsuModSuddenDeath(), new OsuModPerfect()) },
|
||||
new[] { typeof(MultiMod) }
|
||||
},
|
||||
// invalid multiplayer mod.
|
||||
new object[]
|
||||
{
|
||||
new Mod[] { new OsuModHidden(), new InvalidMultiplayerMod() },
|
||||
new[] { typeof(InvalidMultiplayerMod) }
|
||||
},
|
||||
// invalid free mod is valid for multiplayer global.
|
||||
new object[]
|
||||
{
|
||||
new Mod[] { new OsuModHidden(), new InvalidMultiplayerFreeMod() },
|
||||
null
|
||||
},
|
||||
// valid pair.
|
||||
new object[]
|
||||
{
|
||||
new Mod[] { new OsuModHidden(), new OsuModHardRock() },
|
||||
null
|
||||
},
|
||||
};
|
||||
|
||||
private static readonly object[] invalid_free_mod_test_scenarios =
|
||||
{
|
||||
// system mod.
|
||||
new object[]
|
||||
{
|
||||
new Mod[] { new OsuModHidden(), new OsuModTouchDevice() },
|
||||
new[] { typeof(OsuModTouchDevice) }
|
||||
},
|
||||
// multi mod.
|
||||
new object[]
|
||||
{
|
||||
new Mod[] { new MultiMod(new OsuModSuddenDeath(), new OsuModPerfect()) },
|
||||
new[] { typeof(MultiMod) }
|
||||
},
|
||||
// invalid multiplayer mod.
|
||||
new object[]
|
||||
{
|
||||
new Mod[] { new OsuModHidden(), new InvalidMultiplayerMod() },
|
||||
new[] { typeof(InvalidMultiplayerMod) }
|
||||
},
|
||||
// invalid free mod.
|
||||
new object[]
|
||||
{
|
||||
new Mod[] { new OsuModHidden(), new InvalidMultiplayerFreeMod() },
|
||||
new[] { typeof(InvalidMultiplayerFreeMod) }
|
||||
},
|
||||
// incompatible pair is valid for free mods.
|
||||
new object[]
|
||||
{
|
||||
new Mod[] { new OsuModHidden(), new OsuModApproachDifferent() },
|
||||
null,
|
||||
},
|
||||
// incompatible pair with derived class is valid for free mods.
|
||||
new object[]
|
||||
{
|
||||
new Mod[] { new OsuModDeflate(), new OsuModSpinIn() },
|
||||
null,
|
||||
},
|
||||
// valid pair.
|
||||
new object[]
|
||||
{
|
||||
new Mod[] { new OsuModHidden(), new OsuModHardRock() },
|
||||
null
|
||||
},
|
||||
};
|
||||
|
||||
[TestCaseSource(nameof(invalid_mod_test_scenarios))]
|
||||
@ -179,6 +283,32 @@ namespace osu.Game.Tests.Mods
|
||||
Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid));
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(invalid_multiplayer_mod_test_scenarios))]
|
||||
public void TestInvalidMultiplayerModScenarios(Mod[] inputMods, Type[] expectedInvalid)
|
||||
{
|
||||
bool isValid = ModUtils.CheckValidRequiredModsForMultiplayer(inputMods, out var invalid);
|
||||
|
||||
Assert.That(isValid, Is.EqualTo(expectedInvalid == null));
|
||||
|
||||
if (isValid)
|
||||
Assert.IsNull(invalid);
|
||||
else
|
||||
Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid));
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(invalid_free_mod_test_scenarios))]
|
||||
public void TestInvalidFreeModScenarios(Mod[] inputMods, Type[] expectedInvalid)
|
||||
{
|
||||
bool isValid = ModUtils.CheckValidFreeModsForMultiplayer(inputMods, out var invalid);
|
||||
|
||||
Assert.That(isValid, Is.EqualTo(expectedInvalid == null));
|
||||
|
||||
if (isValid)
|
||||
Assert.IsNull(invalid);
|
||||
else
|
||||
Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid));
|
||||
}
|
||||
|
||||
public abstract class CustomMod1 : Mod, IModCompatibilitySpecification
|
||||
{
|
||||
}
|
||||
@ -187,6 +317,27 @@ namespace osu.Game.Tests.Mods
|
||||
{
|
||||
}
|
||||
|
||||
public class InvalidMultiplayerMod : Mod
|
||||
{
|
||||
public override string Name => string.Empty;
|
||||
public override string Description => string.Empty;
|
||||
public override string Acronym => string.Empty;
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override bool HasImplementation => true;
|
||||
public override bool ValidForMultiplayer => false;
|
||||
public override bool ValidForMultiplayerAsFreeMod => false;
|
||||
}
|
||||
|
||||
private class InvalidMultiplayerFreeMod : Mod
|
||||
{
|
||||
public override string Name => string.Empty;
|
||||
public override string Description => string.Empty;
|
||||
public override string Acronym => string.Empty;
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override bool HasImplementation => true;
|
||||
public override bool ValidForMultiplayerAsFreeMod => false;
|
||||
}
|
||||
|
||||
public interface IModCompatibilitySpecification
|
||||
{
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
// 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.Linq;
|
||||
using System.Threading;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
@ -283,7 +282,7 @@ namespace osu.Game.Tests.Visual.Background
|
||||
AddUntilStep("Song select has selection", () => songSelect.Carousel?.SelectedBeatmapInfo != null);
|
||||
AddStep("Set default user settings", () =>
|
||||
{
|
||||
SelectedMods.Value = SelectedMods.Value.Concat(new[] { new OsuModNoFail() }).ToArray();
|
||||
SelectedMods.Value = new[] { new OsuModNoFail() };
|
||||
songSelect.DimLevel.Value = 0.7f;
|
||||
songSelect.BlurLevel.Value = 0.4f;
|
||||
});
|
||||
|
@ -142,13 +142,12 @@ namespace osu.Game.Tests.Visual.Collections
|
||||
AddStep("add dropdown", () =>
|
||||
{
|
||||
Add(new CollectionFilterDropdown
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.4f,
|
||||
}
|
||||
);
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.4f,
|
||||
});
|
||||
});
|
||||
AddStep("add two collections with same name", () => manager.Collections.AddRange(new[]
|
||||
{
|
||||
|
@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
|
@ -15,7 +15,6 @@ using osu.Framework.Graphics.OpenGL.Textures;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
@ -131,7 +130,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
}
|
||||
|
||||
[Cached(typeof(ISkinSource))]
|
||||
[Cached(typeof(ISamplePlaybackDisabler))]
|
||||
private class TestSkinSourceContainer : Container, ISkinSource, ISamplePlaybackDisabler
|
||||
{
|
||||
[Resolved]
|
||||
|
@ -1,29 +1,37 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.OnlinePlay;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public class TestSceneFreeModSelectScreen : MultiplayerTestScene
|
||||
{
|
||||
private FreeModSelectScreen freeModSelectScreen;
|
||||
private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuGameBase osuGameBase)
|
||||
{
|
||||
availableMods.BindTo(osuGameBase.AvailableMods);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFreeModSelect()
|
||||
{
|
||||
FreeModSelectScreen freeModSelectScreen = null;
|
||||
|
||||
AddStep("create free mod select screen", () => Child = freeModSelectScreen = new FreeModSelectScreen
|
||||
{
|
||||
State = { Value = Visibility.Visible }
|
||||
});
|
||||
AddUntilStep("all column content loaded",
|
||||
() => freeModSelectScreen.ChildrenOfType<ModColumn>().Any()
|
||||
&& freeModSelectScreen.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded));
|
||||
createFreeModSelect();
|
||||
|
||||
AddUntilStep("all visible mods are playable",
|
||||
() => this.ChildrenOfType<ModPanel>()
|
||||
@ -36,5 +44,62 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
freeModSelectScreen.State.Value = visible ? Visibility.Visible : Visibility.Hidden;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCustomisationNotAvailable()
|
||||
{
|
||||
createFreeModSelect();
|
||||
|
||||
AddStep("select difficulty adjust", () => freeModSelectScreen.SelectedMods.Value = new[] { new OsuModDifficultyAdjust() });
|
||||
AddWaitStep("wait some", 3);
|
||||
AddAssert("customisation area not expanded", () => this.ChildrenOfType<ModSettingsArea>().Single().Height == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSelectDeselectAll()
|
||||
{
|
||||
createFreeModSelect();
|
||||
|
||||
AddStep("click select all button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<ShearedButton>().ElementAt(1));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddUntilStep("all mods selected", assertAllAvailableModsSelected);
|
||||
|
||||
AddStep("click deselect all button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<ShearedButton>().Last());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddUntilStep("all mods deselected", () => !freeModSelectScreen.SelectedMods.Value.Any());
|
||||
}
|
||||
|
||||
private void createFreeModSelect()
|
||||
{
|
||||
AddStep("create free mod select screen", () => Child = freeModSelectScreen = new FreeModSelectScreen
|
||||
{
|
||||
State = { Value = Visibility.Visible }
|
||||
});
|
||||
AddUntilStep("all column content loaded",
|
||||
() => freeModSelectScreen.ChildrenOfType<ModColumn>().Any()
|
||||
&& freeModSelectScreen.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded));
|
||||
}
|
||||
|
||||
private bool assertAllAvailableModsSelected()
|
||||
{
|
||||
var allAvailableMods = availableMods.Value
|
||||
.SelectMany(pair => pair.Value)
|
||||
.Where(mod => mod.UserPlayable && mod.HasImplementation)
|
||||
.ToList();
|
||||
|
||||
foreach (var availableMod in allAvailableMods)
|
||||
{
|
||||
if (freeModSelectScreen.SelectedMods.Value.All(selectedMod => selectedMod.GetType() != availableMod.GetType()))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -627,7 +627,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("invoke on back button", () => multiplayerComponents.OnBackButton());
|
||||
|
||||
AddAssert("mod overlay is hidden", () => this.ChildrenOfType<UserModSelectOverlay>().Single().State.Value == Visibility.Hidden);
|
||||
AddAssert("mod overlay is hidden", () => this.ChildrenOfType<UserModSelectScreen>().Single().State.Value == Visibility.Hidden);
|
||||
|
||||
AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden);
|
||||
|
||||
|
@ -132,7 +132,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
private void assertHasFreeModButton(Type type, bool hasButton = true)
|
||||
{
|
||||
AddAssert($"{type.ReadableName()} {(hasButton ? "displayed" : "not displayed")} in freemod overlay",
|
||||
() => songSelect.ChildrenOfType<FreeModSelectOverlay>().Single().ChildrenOfType<ModButton>().All(b => b.Mod.GetType() != type));
|
||||
() => this.ChildrenOfType<FreeModSelectScreen>()
|
||||
.Single()
|
||||
.ChildrenOfType<ModPanel>()
|
||||
.Where(panel => !panel.Filtered.Value)
|
||||
.All(b => b.Mod.GetType() != type));
|
||||
}
|
||||
|
||||
private class TestMultiplayerMatchSongSelect : MultiplayerMatchSongSelect
|
||||
|
@ -168,8 +168,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
ClickButtonWhenEnabled<RoomSubScreen.UserModSelectButton>();
|
||||
|
||||
AddUntilStep("mod select contents loaded",
|
||||
() => this.ChildrenOfType<ModColumn>().Any() && this.ChildrenOfType<ModColumn>().All(col => col.IsLoaded && col.ItemsLoaded));
|
||||
AddUntilStep("mod select contains only double time mod",
|
||||
() => this.ChildrenOfType<UserModSelectOverlay>().SingleOrDefault()?.ChildrenOfType<ModButton>().SingleOrDefault()?.Mod is OsuModDoubleTime);
|
||||
() => this.ChildrenOfType<UserModSelectScreen>()
|
||||
.SingleOrDefault()?
|
||||
.ChildrenOfType<ModPanel>()
|
||||
.SingleOrDefault(panel => !panel.Filtered.Value)?.Mod is OsuModDoubleTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,11 +8,23 @@ using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public class TestSceneMultiplayerTeamResults : ScreenTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestScaling()
|
||||
{
|
||||
// scheduling is needed as scaling the content immediately causes the entire scene to shake badly, for some odd reason.
|
||||
AddSliderStep("scale", 0.5f, 1.6f, 1f, v => Schedule(() =>
|
||||
{
|
||||
Stack.Scale = new Vector2(v);
|
||||
Stack.Size = new Vector2(1f / v);
|
||||
}));
|
||||
}
|
||||
|
||||
[TestCase(7483253, 1048576)]
|
||||
[TestCase(1048576, 7483253)]
|
||||
[TestCase(1048576, 1048576)]
|
||||
|
@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Overlays;
|
||||
@ -253,12 +254,12 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
||||
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
||||
AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition));
|
||||
|
||||
// BackButton handles hover using its child button, so this checks whether or not any of BackButton's children are hovered.
|
||||
AddUntilStep("Back button is hovered", () => Game.ChildrenOfType<BackButton>().First().Children.Any(c => c.IsHovered));
|
||||
AddStep("Move mouse to dimmed area", () => InputManager.MoveMouseTo(new Vector2(
|
||||
songSelect.ScreenSpaceDrawQuad.TopLeft.X + 1,
|
||||
songSelect.ScreenSpaceDrawQuad.TopLeft.Y + songSelect.ScreenSpaceDrawQuad.Height / 2)));
|
||||
AddStep("Click left mouse button", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddStep("Click back button", () => InputManager.Click(MouseButton.Left));
|
||||
AddUntilStep("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden);
|
||||
exitViaBackButtonAndConfirm();
|
||||
}
|
||||
@ -503,6 +504,22 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddStep("test dispose doesn't crash", () => Game.Dispose());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRapidBackButtonExit()
|
||||
{
|
||||
AddStep("set hold delay to 0", () => Game.LocalConfig.SetValue(OsuSetting.UIHoldActivationDelay, 0.0));
|
||||
|
||||
AddStep("press escape twice rapidly", () =>
|
||||
{
|
||||
InputManager.Key(Key.Escape);
|
||||
InputManager.Key(Key.Escape);
|
||||
});
|
||||
|
||||
pushEscape();
|
||||
|
||||
AddAssert("exit dialog is shown", () => Game.Dependencies.Get<IDialogOverlay>().CurrentDialog != null);
|
||||
}
|
||||
|
||||
private Func<Player> playToResults()
|
||||
{
|
||||
Player player = null;
|
||||
@ -551,7 +568,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
public class TestPlaySongSelect : PlaySongSelect
|
||||
{
|
||||
public ModSelectOverlay ModSelectOverlay => ModSelect;
|
||||
public ModSelectScreen ModSelectOverlay => ModSelect;
|
||||
|
||||
public BeatmapOptionsOverlay BeatmapOptionsOverlay => BeatmapOptions;
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Overlays.Settings;
|
||||
@ -18,22 +19,23 @@ using static osu.Game.Tests.Visual.Navigation.TestSceneScreenNavigation;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
public class TestSceneSkinEditorSceneLibrary : OsuGameTestScene
|
||||
public class TestSceneSkinEditorNavigation : OsuGameTestScene
|
||||
{
|
||||
private SkinEditor skinEditor;
|
||||
private TestPlaySongSelect songSelect;
|
||||
private SkinEditor skinEditor => Game.ChildrenOfType<SkinEditor>().FirstOrDefault();
|
||||
|
||||
public override void SetUpSteps()
|
||||
private void advanceToSongSelect()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
Screens.Select.SongSelect songSelect = null;
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
}
|
||||
|
||||
private void openSkinEditor()
|
||||
{
|
||||
AddStep("open skin editor", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ControlLeft);
|
||||
@ -42,13 +44,15 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
InputManager.ReleaseKey(Key.ControlLeft);
|
||||
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||
});
|
||||
|
||||
AddUntilStep("get skin editor", () => (skinEditor = Game.ChildrenOfType<SkinEditor>().FirstOrDefault()) != null);
|
||||
AddUntilStep("skin editor loaded", () => skinEditor != null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEditComponentDuringGameplay()
|
||||
{
|
||||
advanceToSongSelect();
|
||||
openSkinEditor();
|
||||
|
||||
switchToGameplayScene();
|
||||
|
||||
BarHitErrorMeter hitErrorMeter = null;
|
||||
@ -85,6 +89,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestAutoplayCompatibleModsRetainedOnEnteringGameplay()
|
||||
{
|
||||
advanceToSongSelect();
|
||||
openSkinEditor();
|
||||
AddStep("select DT", () => Game.SelectedMods.Value = new Mod[] { new OsuModDoubleTime() });
|
||||
|
||||
switchToGameplayScene();
|
||||
@ -95,6 +101,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestAutoplayIncompatibleModsRemovedOnEnteringGameplay()
|
||||
{
|
||||
advanceToSongSelect();
|
||||
openSkinEditor();
|
||||
AddStep("select no fail and spun out", () => Game.SelectedMods.Value = new Mod[] { new OsuModNoFail(), new OsuModSpunOut() });
|
||||
|
||||
switchToGameplayScene();
|
||||
@ -105,6 +113,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestDuplicateAutoplayModRemovedOnEnteringGameplay()
|
||||
{
|
||||
advanceToSongSelect();
|
||||
openSkinEditor();
|
||||
AddStep("select autoplay", () => Game.SelectedMods.Value = new Mod[] { new OsuModAutoplay() });
|
||||
|
||||
switchToGameplayScene();
|
||||
@ -115,6 +125,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestCinemaModRemovedOnEnteringGameplay()
|
||||
{
|
||||
advanceToSongSelect();
|
||||
openSkinEditor();
|
||||
AddStep("select cinema", () => Game.SelectedMods.Value = new Mod[] { new OsuModCinema() });
|
||||
|
||||
switchToGameplayScene();
|
||||
@ -122,6 +134,16 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddAssert("no mod selected", () => !((Player)Game.ScreenStack.CurrentScreen).Mods.Value.Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestModOverlayClosesOnOpeningSkinEditor()
|
||||
{
|
||||
advanceToSongSelect();
|
||||
AddStep("open mod overlay", () => songSelect.ModSelectOverlay.Show());
|
||||
|
||||
openSkinEditor();
|
||||
AddUntilStep("mod overlay closed", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
private void switchToGameplayScene()
|
||||
{
|
||||
AddStep("Click gameplay scene button", () => skinEditor.ChildrenOfType<SkinEditorSceneLibrary.SceneButton>().First(b => b.Text == "Gameplay").TriggerClick());
|
@ -56,6 +56,17 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestScaling()
|
||||
{
|
||||
// scheduling is needed as scaling the content immediately causes the entire scene to shake badly, for some odd reason.
|
||||
AddSliderStep("scale", 0.5f, 1.6f, 1f, v => Schedule(() =>
|
||||
{
|
||||
Content.Scale = new Vector2(v);
|
||||
Content.Size = new Vector2(1f / v);
|
||||
}));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestResultsWithoutPlayer()
|
||||
{
|
||||
|
@ -8,6 +8,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
@ -18,6 +19,7 @@ using osu.Game.Extensions;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
@ -918,6 +920,19 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddAssert("check ruleset is correct for score", () => Ruleset.Value.OnlineID == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestModOverlayToggling()
|
||||
{
|
||||
changeRuleset(0);
|
||||
createSongSelect();
|
||||
|
||||
AddStep("toggle mod overlay on", () => InputManager.Key(Key.F1));
|
||||
AddUntilStep("mod overlay shown", () => songSelect.ModSelect.State.Value == Visibility.Visible);
|
||||
|
||||
AddStep("toggle mod overlay off", () => InputManager.Key(Key.F1));
|
||||
AddUntilStep("mod overlay hidden", () => songSelect.ModSelect.State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
private void waitForInitialSelection()
|
||||
{
|
||||
AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault);
|
||||
@ -993,6 +1008,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
public WorkingBeatmap CurrentBeatmap => Beatmap.Value;
|
||||
public IWorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap;
|
||||
public new BeatmapCarousel Carousel => base.Carousel;
|
||||
public new ModSelectScreen ModSelect => base.ModSelect;
|
||||
|
||||
public new void PresentScore(ScoreInfo score) => base.PresentScore(score);
|
||||
|
||||
|
@ -415,6 +415,72 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddAssert("unimplemented mod panel is filtered", () => getPanelForMod(typeof(TestUnimplementedMod)).Filtered.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDeselectAllViaButton()
|
||||
{
|
||||
createScreen();
|
||||
changeRuleset(0);
|
||||
|
||||
AddStep("select DT + HD", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden() });
|
||||
AddAssert("DT + HD selected", () => modSelectScreen.ChildrenOfType<ModPanel>().Count(panel => panel.Active.Value) == 2);
|
||||
|
||||
AddStep("click deselect all button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<ShearedButton>().Last());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddUntilStep("all mods deselected", () => !SelectedMods.Value.Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCloseViaBackButton()
|
||||
{
|
||||
createScreen();
|
||||
changeRuleset(0);
|
||||
|
||||
AddStep("select difficulty adjust", () => SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() });
|
||||
assertCustomisationToggleState(disabled: false, active: true);
|
||||
AddAssert("back button disabled", () => !this.ChildrenOfType<ShearedButton>().First().Enabled.Value);
|
||||
|
||||
AddStep("dismiss customisation area", () => InputManager.Key(Key.Escape));
|
||||
AddStep("click back button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<ShearedButton>().First());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("mod select hidden", () => modSelectScreen.State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestColumnHiding()
|
||||
{
|
||||
AddStep("create screen", () => Child = modSelectScreen = new UserModSelectScreen
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
State = { Value = Visibility.Visible },
|
||||
SelectedMods = { BindTarget = SelectedMods },
|
||||
IsValidMod = mod => mod.Type == ModType.DifficultyIncrease || mod.Type == ModType.Conversion
|
||||
});
|
||||
waitForColumnLoad();
|
||||
changeRuleset(0);
|
||||
|
||||
AddAssert("two columns visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 2);
|
||||
|
||||
AddStep("unset filter", () => modSelectScreen.IsValidMod = _ => true);
|
||||
AddAssert("all columns visible", () => this.ChildrenOfType<ModColumn>().All(col => col.IsPresent));
|
||||
|
||||
AddStep("filter out everything", () => modSelectScreen.IsValidMod = _ => false);
|
||||
AddAssert("no columns visible", () => this.ChildrenOfType<ModColumn>().All(col => !col.IsPresent));
|
||||
|
||||
AddStep("hide", () => modSelectScreen.Hide());
|
||||
AddStep("set filter for 3 columns", () => modSelectScreen.IsValidMod = mod => mod.Type == ModType.DifficultyReduction
|
||||
|| mod.Type == ModType.Automation
|
||||
|| mod.Type == ModType.Conversion);
|
||||
|
||||
AddStep("show", () => modSelectScreen.Show());
|
||||
AddUntilStep("3 columns visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 3);
|
||||
}
|
||||
|
||||
private void waitForColumnLoad() => AddUntilStep("all column content loaded",
|
||||
() => modSelectScreen.ChildrenOfType<ModColumn>().Any() && modSelectScreen.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded));
|
||||
|
||||
|
@ -66,7 +66,10 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
public class TestShearedOverlayContainer : ShearedOverlayContainer
|
||||
{
|
||||
protected override OverlayColourScheme ColourScheme => OverlayColourScheme.Green;
|
||||
public TestShearedOverlayContainer()
|
||||
: base(OverlayColourScheme.Green)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
|
@ -1,15 +1,18 @@
|
||||
// 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.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Screens.Play
|
||||
namespace osu.Game.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows a component to disable sample playback dynamically as required.
|
||||
/// Handled by <see cref="PausableSkinnableSound"/>.
|
||||
/// Automatically handled by <see cref="PausableSkinnableSound"/>.
|
||||
/// May also be manually handled locally to particular components.
|
||||
/// </summary>
|
||||
[Cached]
|
||||
public interface ISamplePlaybackDisabler
|
||||
{
|
||||
/// <summary>
|
@ -153,7 +153,17 @@ namespace osu.Game.Beatmaps
|
||||
}
|
||||
};
|
||||
|
||||
Task.Run(() => cacheDownloadRequest.PerformAsync());
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await cacheDownloadRequest.PerformAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Prevent throwing unobserved exceptions, as they will be logged from the network request to the log file anyway.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private bool checkLocalCache(BeatmapSetInfo set, BeatmapInfo beatmapInfo)
|
||||
|
@ -431,9 +431,10 @@ namespace osu.Game.Beatmaps.Formats
|
||||
OmitFirstBarLine = omitFirstBarSignature,
|
||||
};
|
||||
|
||||
bool isOsuRuleset = beatmap.BeatmapInfo.Ruleset.OnlineID == 0;
|
||||
// scrolling rulesets use effect points rather than difficulty points for scroll speed adjustments.
|
||||
if (!isOsuRuleset)
|
||||
int onlineRulesetID = beatmap.BeatmapInfo.Ruleset.OnlineID;
|
||||
|
||||
// osu!taiko and osu!mania use effect points rather than difficulty points for scroll speed adjustments.
|
||||
if (onlineRulesetID == 1 || onlineRulesetID == 3)
|
||||
effectPoint.ScrollSpeed = speedMultiplier;
|
||||
|
||||
addControlPoint(time, effectPoint, timingChange);
|
||||
|
@ -183,15 +183,15 @@ namespace osu.Game.Beatmaps.Formats
|
||||
SampleControlPoint lastRelevantSamplePoint = null;
|
||||
DifficultyControlPoint lastRelevantDifficultyPoint = null;
|
||||
|
||||
bool isOsuRuleset = onlineRulesetID == 0;
|
||||
// In osu!taiko and osu!mania, a scroll speed is stored as "slider velocity" in legacy formats.
|
||||
// In that case, a scrolling speed change is a global effect and per-hit object difficulty control points are ignored.
|
||||
bool scrollSpeedEncodedAsSliderVelocity = onlineRulesetID == 1 || onlineRulesetID == 3;
|
||||
|
||||
// iterate over hitobjects and pull out all required sample and difficulty changes
|
||||
extractDifficultyControlPoints(beatmap.HitObjects);
|
||||
extractSampleControlPoints(beatmap.HitObjects);
|
||||
|
||||
// handle scroll speed, which is stored as "slider velocity" in legacy formats.
|
||||
// this is relevant for scrolling ruleset beatmaps.
|
||||
if (!isOsuRuleset)
|
||||
if (scrollSpeedEncodedAsSliderVelocity)
|
||||
{
|
||||
foreach (var point in legacyControlPoints.EffectPoints)
|
||||
legacyControlPoints.Add(point.Time, new DifficultyControlPoint { SliderVelocity = point.ScrollSpeed });
|
||||
@ -242,7 +242,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
IEnumerable<DifficultyControlPoint> collectDifficultyControlPoints(IEnumerable<HitObject> hitObjects)
|
||||
{
|
||||
if (!isOsuRuleset)
|
||||
if (scrollSpeedEncodedAsSliderVelocity)
|
||||
yield break;
|
||||
|
||||
foreach (var hitObject in hitObjects)
|
||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Graphics.Containers
|
||||
protected virtual bool DimMainContent => true;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private OsuGame game { get; set; }
|
||||
private IOverlayManager overlayManager { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private PreviewTrackManager previewTrackManager { get; set; }
|
||||
@ -50,8 +50,8 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
if (game != null)
|
||||
OverlayActivationMode.BindTo(game.OverlayActivationMode);
|
||||
if (overlayManager != null)
|
||||
OverlayActivationMode.BindTo(overlayManager.OverlayActivationMode);
|
||||
|
||||
OverlayActivationMode.BindValueChanged(mode =>
|
||||
{
|
||||
@ -127,14 +127,14 @@ namespace osu.Game.Graphics.Containers
|
||||
if (didChange)
|
||||
samplePopIn?.Play();
|
||||
|
||||
if (BlockScreenWideMouse && DimMainContent) game?.AddBlockingOverlay(this);
|
||||
if (BlockScreenWideMouse && DimMainContent) overlayManager?.ShowBlockingOverlay(this);
|
||||
break;
|
||||
|
||||
case Visibility.Hidden:
|
||||
if (didChange)
|
||||
samplePopOut?.Play();
|
||||
|
||||
if (BlockScreenWideMouse) game?.RemoveBlockingOverlay(this);
|
||||
if (BlockScreenWideMouse) overlayManager?.HideBlockingOverlay(this);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -150,7 +150,7 @@ namespace osu.Game.Graphics.Containers
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
game?.RemoveBlockingOverlay(this);
|
||||
overlayManager?.HideBlockingOverlay(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +59,16 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString Importing => new TranslatableString(getKey(@"importing"), @"Importing...");
|
||||
|
||||
/// <summary>
|
||||
/// "Deselect All"
|
||||
/// </summary>
|
||||
public static LocalisableString DeselectAll => new TranslatableString(getKey(@"deselect_all"), @"Deselect All");
|
||||
|
||||
/// <summary>
|
||||
/// "Select All"
|
||||
/// </summary>
|
||||
public static LocalisableString SelectAll => new TranslatableString(getKey(@"select_all"), @"Select All");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
19
osu.Game/Localisation/DifficultyMultiplierDisplayStrings.cs
Normal file
19
osu.Game/Localisation/DifficultyMultiplierDisplayStrings.cs
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Localisation;
|
||||
|
||||
namespace osu.Game.Localisation
|
||||
{
|
||||
public static class DifficultyMultiplierDisplayStrings
|
||||
{
|
||||
private const string prefix = @"osu.Game.Resources.Localisation.DifficultyMultiplierDisplay";
|
||||
|
||||
/// <summary>
|
||||
/// "Difficulty Multiplier"
|
||||
/// </summary>
|
||||
public static LocalisableString DifficultyMultiplier => new TranslatableString(getKey(@"difficulty_multiplier"), @"Difficulty Multiplier");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
29
osu.Game/Localisation/ModSelectScreenStrings.cs
Normal file
29
osu.Game/Localisation/ModSelectScreenStrings.cs
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Localisation;
|
||||
|
||||
namespace osu.Game.Localisation
|
||||
{
|
||||
public static class ModSelectScreenStrings
|
||||
{
|
||||
private const string prefix = @"osu.Game.Resources.Localisation.ModSelectScreen";
|
||||
|
||||
/// <summary>
|
||||
/// "Mod Select"
|
||||
/// </summary>
|
||||
public static LocalisableString ModSelectTitle => new TranslatableString(getKey(@"mod_select_title"), @"Mod Select");
|
||||
|
||||
/// <summary>
|
||||
/// "Mods provide different ways to enjoy gameplay. Some have an effect on the score you can achieve during ranked play. Others are just for fun."
|
||||
/// </summary>
|
||||
public static LocalisableString ModSelectDescription => new TranslatableString(getKey(@"mod_select_description"), @"Mods provide different ways to enjoy gameplay. Some have an effect on the score you can achieve during ranked play. Others are just for fun.");
|
||||
|
||||
/// <summary>
|
||||
/// "Mod Customisation"
|
||||
/// </summary>
|
||||
public static LocalisableString ModCustomisation => new TranslatableString(getKey(@"mod_customisation"), @"Mod Customisation");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Input.Bindings;
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
using System.IO;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
|
@ -1,4 +1,7 @@
|
||||
using System;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
|
@ -1,4 +1,7 @@
|
||||
using System;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
// 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 Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
|
@ -63,7 +63,7 @@ namespace osu.Game
|
||||
/// The full osu! experience. Builds on top of <see cref="OsuGameBase"/> to add menus and binding logic
|
||||
/// for initial components that are generally retrieved via DI.
|
||||
/// </summary>
|
||||
public class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction>, ILocalUserPlayInfo, IPerformFromScreenRunner
|
||||
public class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction>, ILocalUserPlayInfo, IPerformFromScreenRunner, IOverlayManager
|
||||
{
|
||||
/// <summary>
|
||||
/// The amount of global offset to apply when a left/right anchored overlay is displayed (ie. settings or notifications).
|
||||
@ -171,6 +171,7 @@ namespace osu.Game
|
||||
private readonly string[] args;
|
||||
|
||||
private readonly List<OsuFocusedOverlayContainer> focusedOverlays = new List<OsuFocusedOverlayContainer>();
|
||||
private readonly List<OverlayContainer> externalOverlays = new List<OverlayContainer>();
|
||||
|
||||
private readonly List<OverlayContainer> visibleBlockingOverlays = new List<OverlayContainer>();
|
||||
|
||||
@ -183,22 +184,58 @@ namespace osu.Game
|
||||
SentryLogger = new SentryLogger(this);
|
||||
}
|
||||
|
||||
#region IOverlayManager
|
||||
|
||||
IBindable<OverlayActivation> IOverlayManager.OverlayActivationMode => OverlayActivationMode;
|
||||
|
||||
private void updateBlockingOverlayFade() =>
|
||||
ScreenContainer.FadeColour(visibleBlockingOverlays.Any() ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint);
|
||||
|
||||
public void AddBlockingOverlay(OverlayContainer overlay)
|
||||
IDisposable IOverlayManager.RegisterBlockingOverlay(OverlayContainer overlayContainer)
|
||||
{
|
||||
if (overlayContainer.Parent != null)
|
||||
throw new ArgumentException($@"Overlays registered via {nameof(IOverlayManager.RegisterBlockingOverlay)} should not be added to the scene graph.");
|
||||
|
||||
if (externalOverlays.Contains(overlayContainer))
|
||||
throw new ArgumentException($@"{overlayContainer} has already been registered via {nameof(IOverlayManager.RegisterBlockingOverlay)} once.");
|
||||
|
||||
externalOverlays.Add(overlayContainer);
|
||||
overlayContent.Add(overlayContainer);
|
||||
|
||||
if (overlayContainer is OsuFocusedOverlayContainer focusedOverlayContainer)
|
||||
focusedOverlays.Add(focusedOverlayContainer);
|
||||
|
||||
return new InvokeOnDisposal(() => unregisterBlockingOverlay(overlayContainer));
|
||||
}
|
||||
|
||||
void IOverlayManager.ShowBlockingOverlay(OverlayContainer overlay)
|
||||
{
|
||||
if (!visibleBlockingOverlays.Contains(overlay))
|
||||
visibleBlockingOverlays.Add(overlay);
|
||||
updateBlockingOverlayFade();
|
||||
}
|
||||
|
||||
public void RemoveBlockingOverlay(OverlayContainer overlay) => Schedule(() =>
|
||||
void IOverlayManager.HideBlockingOverlay(OverlayContainer overlay) => Schedule(() =>
|
||||
{
|
||||
visibleBlockingOverlays.Remove(overlay);
|
||||
updateBlockingOverlayFade();
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a blocking <see cref="OverlayContainer"/> that was not created by <see cref="OsuGame"/> itself.
|
||||
/// </summary>
|
||||
private void unregisterBlockingOverlay(OverlayContainer overlayContainer)
|
||||
{
|
||||
externalOverlays.Remove(overlayContainer);
|
||||
|
||||
if (overlayContainer is OsuFocusedOverlayContainer focusedOverlayContainer)
|
||||
focusedOverlays.Remove(focusedOverlayContainer);
|
||||
|
||||
overlayContainer.Expire();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Close all game-wide overlays.
|
||||
/// </summary>
|
||||
@ -1153,6 +1190,7 @@ namespace osu.Game
|
||||
horizontalOffset += (Content.ToLocalSpace(Notifications.ScreenSpaceDrawQuad.TopLeft).X - Content.DrawWidth) * SIDE_OVERLAY_OFFSET_RATIO;
|
||||
|
||||
ScreenOffsetContainer.X = horizontalOffset;
|
||||
overlayContent.X = horizontalOffset * 1.2f;
|
||||
|
||||
MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false;
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
maxComboColumn.Text = value.MaxCombo.ToLocalisableString(@"0\x");
|
||||
|
||||
ppColumn.Alpha = value.BeatmapInfo.Status.GrantsPerformancePoints() ? 1 : 0;
|
||||
ppColumn.Text = value.PP?.ToLocalisableString(@"N0") ?? default(LocalisableString);
|
||||
ppColumn.Text = value.PP?.ToLocalisableString(@"N0") ?? default;
|
||||
|
||||
statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn);
|
||||
modsColumn.Mods = value.Mods;
|
||||
|
@ -100,10 +100,6 @@ namespace osu.Game.Overlays.Dialog
|
||||
}
|
||||
}
|
||||
|
||||
// We always want dialogs to show their appear animation, so we request they start hidden.
|
||||
// Normally this would not be required, but is here due to the manual Show() call that occurs before LoadComplete().
|
||||
protected override bool StartHidden => true;
|
||||
|
||||
protected PopupDialog()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
@ -272,7 +268,7 @@ namespace osu.Game.Overlays.Dialog
|
||||
|
||||
protected override void PopOut()
|
||||
{
|
||||
if (!actionInvoked && content.IsPresent)
|
||||
if (!actionInvoked)
|
||||
// In the case a user did not choose an action before a hide was triggered, press the last button.
|
||||
// This is presumed to always be a sane default "cancel" action.
|
||||
buttonsContainer.Last().TriggerClick();
|
||||
|
@ -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.Audio;
|
||||
@ -19,6 +20,7 @@ using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.Select;
|
||||
@ -131,6 +133,10 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
[Cached(typeof(IBindable<WorkingBeatmap>))]
|
||||
protected Bindable<WorkingBeatmap> Beatmap { get; private set; } = new Bindable<WorkingBeatmap>();
|
||||
|
||||
[Cached]
|
||||
[Cached(typeof(IBindable<IReadOnlyList<Mod>>))]
|
||||
protected Bindable<IReadOnlyList<Mod>> SelectedMods { get; private set; } = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||
|
||||
public override bool HandlePositionalInput => false;
|
||||
public override bool HandleNonPositionalInput => false;
|
||||
public override bool PropagatePositionalInputSubTree => false;
|
||||
|
@ -31,8 +31,6 @@ namespace osu.Game.Overlays
|
||||
[Cached]
|
||||
public class FirstRunSetupOverlay : ShearedOverlayContainer
|
||||
{
|
||||
protected override OverlayColourScheme ColourScheme => OverlayColourScheme.Purple;
|
||||
|
||||
[Resolved]
|
||||
private IPerformFromScreenRunner performer { get; set; } = null!;
|
||||
|
||||
@ -70,6 +68,11 @@ namespace osu.Game.Overlays
|
||||
|
||||
private Container content = null!;
|
||||
|
||||
public FirstRunSetupOverlay()
|
||||
: base(OverlayColourScheme.Purple)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
|
44
osu.Game/Overlays/IOverlayManager.cs
Normal file
44
osu.Game/Overlays/IOverlayManager.cs
Normal file
@ -0,0 +1,44 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Screens.Select;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
[Cached]
|
||||
internal interface IOverlayManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether overlays should be able to be opened game-wide. Value is sourced from the current active screen.
|
||||
/// </summary>
|
||||
IBindable<OverlayActivation> OverlayActivationMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Registers a blocking <see cref="OverlayContainer"/> that was not created by <see cref="OsuGame"/> itself for later use.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The goal of this method is to allow child screens, like <see cref="SongSelect"/> to register their own full-screen blocking overlays
|
||||
/// with background dim.
|
||||
/// In those cases, for the dim to work correctly, the overlays need to be added at a game level directly, rather as children of the screens.
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// An <see cref="IDisposable"/> that should be disposed of when the <paramref name="overlayContainer"/> should be unregistered.
|
||||
/// Disposing of this <see cref="IDisposable"/> will automatically expire the <paramref name="overlayContainer"/>.
|
||||
/// </returns>
|
||||
IDisposable RegisterBlockingOverlay(OverlayContainer overlayContainer);
|
||||
|
||||
/// <summary>
|
||||
/// Should be called when <paramref name="overlay"/> has been shown and should begin blocking background input.
|
||||
/// </summary>
|
||||
void ShowBlockingOverlay(OverlayContainer overlay);
|
||||
|
||||
/// <summary>
|
||||
/// Should be called when a blocking <paramref name="overlay"/> has been hidden and should stop blocking background input.
|
||||
/// </summary>
|
||||
void HideBlockingOverlay(OverlayContainer overlay);
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osuTK;
|
||||
using osu.Game.Localisation;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
@ -99,7 +100,7 @@ namespace osu.Game.Overlays.Mods
|
||||
Origin = Anchor.Centre,
|
||||
Margin = new MarginPadding { Horizontal = 18 },
|
||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
||||
Text = "Difficulty Multiplier",
|
||||
Text = DifficultyMultiplierDisplayStrings.DifficultyMultiplier,
|
||||
Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold)
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -22,6 +23,7 @@ using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
@ -39,7 +41,7 @@ namespace osu.Game.Overlays.Mods
|
||||
private Func<Mod, bool>? filter;
|
||||
|
||||
/// <summary>
|
||||
/// Function determining whether each mod in the column should be displayed.
|
||||
/// A function determining whether each mod in the column should be displayed.
|
||||
/// A return value of <see langword="true"/> means that the mod is not filtered and therefore its corresponding panel should be displayed.
|
||||
/// A return value of <see langword="false"/> means that the mod is filtered out and therefore its corresponding panel should be hidden.
|
||||
/// </summary>
|
||||
@ -49,12 +51,22 @@ namespace osu.Game.Overlays.Mods
|
||||
set
|
||||
{
|
||||
filter = value;
|
||||
updateFilter();
|
||||
updateState();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this column should accept user input.
|
||||
/// </summary>
|
||||
public Bindable<bool> Active = new BindableBool(true);
|
||||
|
||||
private readonly Bindable<bool> allFiltered = new BindableBool();
|
||||
|
||||
/// <summary>
|
||||
/// True if all of the panels in this column have been filtered out by the current <see cref="Filter"/>.
|
||||
/// </summary>
|
||||
public IBindable<bool> AllFiltered => allFiltered;
|
||||
|
||||
/// <summary>
|
||||
/// List of mods marked as selected in this column.
|
||||
/// </summary>
|
||||
@ -186,7 +198,7 @@ namespace osu.Game.Overlays.Mods
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new NestedVerticalScrollContainer
|
||||
new OsuScrollContainer(Direction.Vertical)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ClampExtension = 100,
|
||||
@ -220,7 +232,6 @@ namespace osu.Game.Overlays.Mods
|
||||
Origin = Anchor.CentreLeft,
|
||||
Scale = new Vector2(0.8f),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
LabelText = "Enable All",
|
||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0)
|
||||
});
|
||||
panelFlow.Padding = new MarginPadding
|
||||
@ -249,9 +260,8 @@ namespace osu.Game.Overlays.Mods
|
||||
private void load(OsuGameBase game, OverlayColourProvider colourProvider, OsuColour colours)
|
||||
{
|
||||
availableMods.BindTo(game.AvailableMods);
|
||||
// this `BindValueChanged` callback is intentionally here, to ensure that local available mods are constructed as early as possible.
|
||||
// this is needed to make sure no external changes to mods are dropped while mod panels are asynchronously loading.
|
||||
availableMods.BindValueChanged(_ => updateLocalAvailableMods(), true);
|
||||
updateLocalAvailableMods(asyncLoadContent: false);
|
||||
availableMods.BindValueChanged(_ => updateLocalAvailableMods(asyncLoadContent: true));
|
||||
|
||||
headerBackground.Colour = accentColour = colours.ForModType(ModType);
|
||||
|
||||
@ -265,7 +275,20 @@ namespace osu.Game.Overlays.Mods
|
||||
contentBackground.Colour = colourProvider.Background4;
|
||||
}
|
||||
|
||||
private void updateLocalAvailableMods()
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
toggleAllCheckbox?.Current.BindValueChanged(_ => updateToggleAllText(), true);
|
||||
}
|
||||
|
||||
private void updateToggleAllText()
|
||||
{
|
||||
Debug.Assert(toggleAllCheckbox != null);
|
||||
toggleAllCheckbox.LabelText = toggleAllCheckbox.Current.Value ? CommonStrings.DeselectAll : CommonStrings.SelectAll;
|
||||
}
|
||||
|
||||
private void updateLocalAvailableMods(bool asyncLoadContent)
|
||||
{
|
||||
var newMods = ModUtils.FlattenMods(availableMods.Value.GetValueOrDefault(ModType) ?? Array.Empty<Mod>())
|
||||
.Select(m => m.DeepClone())
|
||||
@ -275,32 +298,24 @@ namespace osu.Game.Overlays.Mods
|
||||
return;
|
||||
|
||||
localAvailableMods = newMods;
|
||||
Scheduler.AddOnce(loadPanels);
|
||||
|
||||
if (asyncLoadContent)
|
||||
asyncLoadPanels();
|
||||
else
|
||||
onPanelsLoaded(createPanels());
|
||||
}
|
||||
|
||||
private CancellationTokenSource? cancellationTokenSource;
|
||||
|
||||
private void loadPanels()
|
||||
private void asyncLoadPanels()
|
||||
{
|
||||
cancellationTokenSource?.Cancel();
|
||||
|
||||
var panels = localAvailableMods.Select(mod => CreateModPanel(mod).With(panel => panel.Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0)));
|
||||
var panels = createPanels();
|
||||
|
||||
Task? loadTask;
|
||||
|
||||
latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded =>
|
||||
{
|
||||
panelFlow.ChildrenEnumerable = loaded;
|
||||
|
||||
updateActiveState();
|
||||
updateToggleAllState();
|
||||
updateFilter();
|
||||
|
||||
foreach (var panel in panelFlow)
|
||||
{
|
||||
panel.Active.BindValueChanged(_ => panelStateChanged(panel));
|
||||
}
|
||||
}, (cancellationTokenSource = new CancellationTokenSource()).Token);
|
||||
latestLoadTask = loadTask = LoadComponentsAsync(panels, onPanelsLoaded, (cancellationTokenSource = new CancellationTokenSource()).Token);
|
||||
loadTask.ContinueWith(_ =>
|
||||
{
|
||||
if (loadTask == latestLoadTask)
|
||||
@ -308,10 +323,39 @@ namespace osu.Game.Overlays.Mods
|
||||
});
|
||||
}
|
||||
|
||||
private void updateActiveState()
|
||||
private IEnumerable<ModPanel> createPanels()
|
||||
{
|
||||
var panels = localAvailableMods.Select(mod => CreateModPanel(mod).With(panel => panel.Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0)));
|
||||
return panels;
|
||||
}
|
||||
|
||||
private void onPanelsLoaded(IEnumerable<ModPanel> loaded)
|
||||
{
|
||||
panelFlow.ChildrenEnumerable = loaded;
|
||||
|
||||
updateState();
|
||||
|
||||
foreach (var panel in panelFlow)
|
||||
{
|
||||
panel.Active.BindValueChanged(_ => panelStateChanged(panel));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
foreach (var panel in panelFlow)
|
||||
{
|
||||
panel.Active.Value = SelectedMods.Contains(panel.Mod);
|
||||
panel.ApplyFilter(Filter);
|
||||
}
|
||||
|
||||
allFiltered.Value = panelFlow.All(panel => panel.Filtered.Value);
|
||||
|
||||
if (toggleAllCheckbox != null && !SelectionAnimationRunning)
|
||||
{
|
||||
toggleAllCheckbox.Alpha = panelFlow.Any(panel => !panel.Filtered.Value) ? 1 : 0;
|
||||
toggleAllCheckbox.Current.Value = panelFlow.Where(panel => !panel.Filtered.Value).All(panel => panel.Active.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -323,15 +367,16 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
private void panelStateChanged(ModPanel panel)
|
||||
{
|
||||
updateToggleAllState();
|
||||
if (externalSelectionUpdateInProgress)
|
||||
return;
|
||||
|
||||
var newSelectedMods = panel.Active.Value
|
||||
? SelectedMods.Append(panel.Mod)
|
||||
: SelectedMods.Except(panel.Mod.Yield());
|
||||
|
||||
SelectedMods = newSelectedMods.ToArray();
|
||||
if (!externalSelectionUpdateInProgress)
|
||||
SelectionChangedByUser?.Invoke();
|
||||
updateState();
|
||||
SelectionChangedByUser?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -364,7 +409,7 @@ namespace osu.Game.Overlays.Mods
|
||||
}
|
||||
|
||||
SelectedMods = newSelection;
|
||||
updateActiveState();
|
||||
updateState();
|
||||
|
||||
externalSelectionUpdateInProgress = false;
|
||||
}
|
||||
@ -378,7 +423,7 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
private readonly Queue<Action> pendingSelectionOperations = new Queue<Action>();
|
||||
|
||||
protected bool SelectionAnimationRunning => pendingSelectionOperations.Count > 0;
|
||||
internal bool SelectionAnimationRunning => pendingSelectionOperations.Count > 0;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
@ -403,15 +448,6 @@ namespace osu.Game.Overlays.Mods
|
||||
}
|
||||
}
|
||||
|
||||
private void updateToggleAllState()
|
||||
{
|
||||
if (toggleAllCheckbox != null && !SelectionAnimationRunning)
|
||||
{
|
||||
toggleAllCheckbox.Alpha = panelFlow.Any(panel => !panel.Filtered.Value) ? 1 : 0;
|
||||
toggleAllCheckbox.Current.Value = panelFlow.Where(panel => !panel.Filtered.Value).All(panel => panel.Active.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects all mods.
|
||||
/// </summary>
|
||||
@ -507,18 +543,6 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
#endregion
|
||||
|
||||
#region Filtering support
|
||||
|
||||
private void updateFilter()
|
||||
{
|
||||
foreach (var modPanel in panelFlow)
|
||||
modPanel.ApplyFilter(Filter);
|
||||
|
||||
updateToggleAllState();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Keyboard selection support
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
|
@ -1,6 +1,8 @@
|
||||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
@ -12,6 +14,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@ -21,8 +24,6 @@ using osu.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
public class ModPanel : OsuClickableContainer
|
||||
@ -50,6 +51,7 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
private Colour4 activeColour;
|
||||
|
||||
private readonly Bindable<bool> samplePlaybackDisabled = new BindableBool();
|
||||
private Sample? sampleOff;
|
||||
private Sample? sampleOn;
|
||||
|
||||
@ -139,13 +141,16 @@ namespace osu.Game.Overlays.Mods
|
||||
Action = Active.Toggle;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio, OsuColour colours)
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(AudioManager audio, OsuColour colours, ISamplePlaybackDisabler? samplePlaybackDisabler)
|
||||
{
|
||||
sampleOn = audio.Samples.Get(@"UI/check-on");
|
||||
sampleOff = audio.Samples.Get(@"UI/check-off");
|
||||
|
||||
activeColour = colours.ForModType(Mod.Type);
|
||||
|
||||
if (samplePlaybackDisabler != null)
|
||||
((IBindable<bool>)samplePlaybackDisabled).BindTo(samplePlaybackDisabler.SamplePlaybackDisabled);
|
||||
}
|
||||
|
||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet);
|
||||
@ -166,6 +171,9 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
private void playStateChangeSamples()
|
||||
{
|
||||
if (samplePlaybackDisabled.Value)
|
||||
return;
|
||||
|
||||
if (Active.Value)
|
||||
sampleOn?.Play();
|
||||
else
|
||||
|
@ -13,27 +13,35 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Layout;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
public abstract class ModSelectScreen : ShearedOverlayContainer
|
||||
public abstract class ModSelectScreen : ShearedOverlayContainer, ISamplePlaybackDisabler
|
||||
{
|
||||
protected override OverlayColourScheme ColourScheme => OverlayColourScheme.Green;
|
||||
protected const int BUTTON_WIDTH = 200;
|
||||
|
||||
[Cached]
|
||||
public Bindable<IReadOnlyList<Mod>> SelectedMods { get; private set; } = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||
|
||||
private Func<Mod, bool> isValidMod = m => true;
|
||||
|
||||
/// <summary>
|
||||
/// A function determining whether each mod in the column should be displayed.
|
||||
/// A return value of <see langword="true"/> means that the mod is not filtered and therefore its corresponding panel should be displayed.
|
||||
/// A return value of <see langword="false"/> means that the mod is filtered out and therefore its corresponding panel should be hidden.
|
||||
/// </summary>
|
||||
public Func<Mod, bool> IsValidMod
|
||||
{
|
||||
get => isValidMod;
|
||||
@ -46,11 +54,6 @@ namespace osu.Game.Overlays.Mods
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether configurable <see cref="Mod"/>s can be configured by the local user.
|
||||
/// </summary>
|
||||
protected virtual bool AllowCustomisation => true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the total score multiplier calculated from the current selected set of mods should be shown.
|
||||
/// </summary>
|
||||
@ -58,18 +61,32 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
protected virtual ModColumn CreateModColumn(ModType modType, Key[]? toggleKeys = null) => new ModColumn(modType, false, toggleKeys);
|
||||
|
||||
protected virtual IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection) => newSelection;
|
||||
|
||||
protected virtual IEnumerable<ShearedButton> CreateFooterButtons() => createDefaultFooterButtons();
|
||||
|
||||
private readonly BindableBool customisationVisible = new BindableBool();
|
||||
|
||||
private DifficultyMultiplierDisplay? multiplierDisplay;
|
||||
private ModSettingsArea modSettingsArea = null!;
|
||||
private ColumnScrollContainer columnScroll = null!;
|
||||
private ColumnFlowContainer columnFlow = null!;
|
||||
private FillFlowContainer<ShearedButton> footerButtonFlow = null!;
|
||||
private ShearedButton backButton = null!;
|
||||
|
||||
private DifficultyMultiplierDisplay? multiplierDisplay;
|
||||
|
||||
private ShearedToggleButton? customisationButton;
|
||||
|
||||
protected ModSelectScreen(OverlayColourScheme colourScheme = OverlayColourScheme.Green)
|
||||
: base(colourScheme)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
Header.Title = "Mod Select";
|
||||
Header.Description = "Mods provide different ways to enjoy gameplay. Some have an effect on the score you can achieve during ranked play. Others are just for fun.";
|
||||
Header.Title = ModSelectScreenStrings.ModSelectTitle;
|
||||
Header.Description = ModSelectScreenStrings.ModSelectDescription;
|
||||
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
@ -94,6 +111,7 @@ namespace osu.Game.Overlays.Mods
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Top = (ShowTotalMultiplier ? DifficultyMultiplierDisplay.HEIGHT : 0) + PADDING,
|
||||
Bottom = PADDING
|
||||
},
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RelativePositionAxes = Axes.Both,
|
||||
@ -111,7 +129,6 @@ namespace osu.Game.Overlays.Mods
|
||||
Shear = new Vector2(SHEAR, 0),
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Spacing = new Vector2(10, 0),
|
||||
Margin = new MarginPadding { Horizontal = 70 },
|
||||
Children = new[]
|
||||
{
|
||||
@ -144,31 +161,35 @@ namespace osu.Game.Overlays.Mods
|
||||
});
|
||||
}
|
||||
|
||||
if (AllowCustomisation)
|
||||
FooterContent.Child = footerButtonFlow = new FillFlowContainer<ShearedButton>
|
||||
{
|
||||
Footer.Add(new ShearedToggleButton(200)
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Margin = new MarginPadding { Vertical = PADDING, Left = 70 },
|
||||
Text = "Mod Customisation",
|
||||
Active = { BindTarget = customisationVisible }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private ColumnDimContainer createModColumnContent(ModType modType, Key[]? toggleKeys = null)
|
||||
=> new ColumnDimContainer(CreateModColumn(modType, toggleKeys))
|
||||
{
|
||||
AutoSizeAxes = Axes.X,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
RequestScroll = column => columnScroll.ScrollIntoView(column, extraScroll: 140)
|
||||
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
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
State.BindValueChanged(_ => samplePlaybackDisabled.Value = State.Value == Visibility.Hidden, true);
|
||||
|
||||
((IBindable<IReadOnlyList<Mod>>)modSettingsArea.SelectedMods).BindTo(SelectedMods);
|
||||
|
||||
SelectedMods.BindValueChanged(val =>
|
||||
@ -186,8 +207,66 @@ namespace osu.Game.Overlays.Mods
|
||||
customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true);
|
||||
|
||||
updateAvailableMods();
|
||||
|
||||
// Start scrolled slightly to the right to give the user a sense that
|
||||
// there is more horizontal content available.
|
||||
ScheduleAfterChildren(() =>
|
||||
{
|
||||
columnScroll.ScrollTo(200, false);
|
||||
columnScroll.ScrollToStart();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Select all visible mods in all columns.
|
||||
/// </summary>
|
||||
protected void SelectAll()
|
||||
{
|
||||
foreach (var column in columnFlow.Columns)
|
||||
column.SelectAll();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deselect all visible mods in all columns.
|
||||
/// </summary>
|
||||
protected void DeselectAll()
|
||||
{
|
||||
foreach (var column in columnFlow.Columns)
|
||||
column.DeselectAll();
|
||||
}
|
||||
|
||||
private ColumnDimContainer createModColumnContent(ModType modType, Key[]? toggleKeys = null)
|
||||
{
|
||||
var column = CreateModColumn(modType, toggleKeys).With(column =>
|
||||
{
|
||||
column.Filter = IsValidMod;
|
||||
// spacing applied here rather than via `columnFlow.Spacing` to avoid uneven gaps when some of the columns are hidden.
|
||||
column.Margin = new MarginPadding { Right = 10 };
|
||||
});
|
||||
|
||||
return new ColumnDimContainer(column)
|
||||
{
|
||||
AutoSizeAxes = Axes.X,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
RequestScroll = col => columnScroll.ScrollIntoView(col, extraScroll: 140),
|
||||
};
|
||||
}
|
||||
|
||||
private ShearedButton[] createDefaultFooterButtons()
|
||||
=> new[]
|
||||
{
|
||||
customisationButton = new ShearedToggleButton(BUTTON_WIDTH)
|
||||
{
|
||||
Text = ModSelectScreenStrings.ModCustomisation,
|
||||
Active = { BindTarget = customisationVisible }
|
||||
},
|
||||
new ShearedButton(BUTTON_WIDTH)
|
||||
{
|
||||
Text = CommonStrings.DeselectAll,
|
||||
Action = DeselectAll
|
||||
}
|
||||
};
|
||||
|
||||
private void updateMultiplier()
|
||||
{
|
||||
if (multiplierDisplay == null)
|
||||
@ -209,7 +288,7 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
private void updateCustomisation(ValueChangedEvent<IReadOnlyList<Mod>> valueChangedEvent)
|
||||
{
|
||||
if (!AllowCustomisation)
|
||||
if (customisationButton == null)
|
||||
return;
|
||||
|
||||
bool anyCustomisableMod = false;
|
||||
@ -243,6 +322,12 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
MainAreaContent.FadeColour(customisationVisible.Value ? Colour4.Gray : Colour4.White, transition_duration, Easing.InOutCubic);
|
||||
|
||||
foreach (var button in footerButtonFlow)
|
||||
{
|
||||
if (button != customisationButton)
|
||||
button.Enabled.Value = !customisationVisible.Value;
|
||||
}
|
||||
|
||||
float modAreaHeight = customisationVisible.Value ? ModSettingsArea.HEIGHT : 0;
|
||||
|
||||
modSettingsArea.ResizeHeightTo(modAreaHeight, transition_duration, Easing.InOutCubic);
|
||||
@ -264,13 +349,17 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
var candidateSelection = columnFlow.Columns.SelectMany(column => column.SelectedMods).ToArray();
|
||||
|
||||
if (candidateSelection.SequenceEqual(SelectedMods.Value))
|
||||
// the following guard intends to check cases where we've already replaced potentially-external mod references with our own and avoid endless recursion.
|
||||
// TODO: replace custom comparer with System.Collections.Generic.ReferenceEqualityComparer when fully on .NET 6
|
||||
if (candidateSelection.SequenceEqual(SelectedMods.Value, new FuncEqualityComparer<Mod>(ReferenceEquals)))
|
||||
return;
|
||||
|
||||
SelectedMods.Value = ComputeNewModsFromSelection(SelectedMods.Value, candidateSelection);
|
||||
}
|
||||
|
||||
protected virtual IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection) => newSelection;
|
||||
#region Transition handling
|
||||
|
||||
private const float distance = 700;
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
@ -283,13 +372,26 @@ namespace osu.Game.Overlays.Mods
|
||||
.FadeIn(fade_in_duration / 2, Easing.OutQuint)
|
||||
.ScaleTo(1, fade_in_duration, Easing.OutElastic);
|
||||
|
||||
int nonFilteredColumnCount = 0;
|
||||
|
||||
for (int i = 0; i < columnFlow.Count; i++)
|
||||
{
|
||||
columnFlow[i].Column
|
||||
.TopLevelContent
|
||||
.Delay(i * 30)
|
||||
.MoveToY(0, fade_in_duration, Easing.OutQuint)
|
||||
.FadeIn(fade_in_duration, Easing.OutQuint);
|
||||
var column = columnFlow[i].Column;
|
||||
|
||||
double delay = column.AllFiltered.Value ? 0 : nonFilteredColumnCount * 30;
|
||||
double duration = column.AllFiltered.Value ? 0 : fade_in_duration;
|
||||
float startingYPosition = 0;
|
||||
if (!column.AllFiltered.Value)
|
||||
startingYPosition = nonFilteredColumnCount % 2 == 0 ? -distance : distance;
|
||||
|
||||
column.TopLevelContent
|
||||
.MoveToY(startingYPosition)
|
||||
.Delay(delay)
|
||||
.MoveToY(0, duration, Easing.OutQuint)
|
||||
.FadeIn(duration, Easing.OutQuint);
|
||||
|
||||
if (!column.AllFiltered.Value)
|
||||
nonFilteredColumnCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -303,30 +405,83 @@ namespace osu.Game.Overlays.Mods
|
||||
.FadeOut(fade_out_duration / 2, Easing.OutQuint)
|
||||
.ScaleTo(0.75f, fade_out_duration, Easing.OutQuint);
|
||||
|
||||
int nonFilteredColumnCount = 0;
|
||||
|
||||
for (int i = 0; i < columnFlow.Count; i++)
|
||||
{
|
||||
const float distance = 700;
|
||||
|
||||
var column = columnFlow[i].Column;
|
||||
|
||||
double duration = column.AllFiltered.Value ? 0 : fade_out_duration;
|
||||
float newYPosition = 0;
|
||||
if (!column.AllFiltered.Value)
|
||||
newYPosition = nonFilteredColumnCount % 2 == 0 ? -distance : distance;
|
||||
|
||||
column.FlushPendingSelections();
|
||||
column.TopLevelContent
|
||||
.MoveToY(i % 2 == 0 ? -distance : distance, fade_out_duration, Easing.OutQuint)
|
||||
.FadeOut(fade_out_duration, Easing.OutQuint);
|
||||
.MoveToY(newYPosition, duration, Easing.OutQuint)
|
||||
.FadeOut(duration, Easing.OutQuint);
|
||||
|
||||
if (!column.AllFiltered.Value)
|
||||
nonFilteredColumnCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Input handling
|
||||
|
||||
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
if (e.Action == GlobalAction.Back && customisationVisible.Value)
|
||||
if (e.Repeat)
|
||||
return false;
|
||||
|
||||
switch (e.Action)
|
||||
{
|
||||
customisationVisible.Value = false;
|
||||
return true;
|
||||
case GlobalAction.Back:
|
||||
// Pressing the back binding should only go back one step at a time.
|
||||
hideOverlay(false);
|
||||
return true;
|
||||
|
||||
// This is handled locally here because this overlay is being registered at the game level
|
||||
// and therefore takes away keyboard focus from the screen stack.
|
||||
case GlobalAction.ToggleModSelection:
|
||||
case GlobalAction.Select:
|
||||
{
|
||||
// Pressing toggle or select should completely hide the overlay in one shot.
|
||||
hideOverlay(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnPressed(e);
|
||||
|
||||
void hideOverlay(bool immediate)
|
||||
{
|
||||
if (customisationVisible.Value)
|
||||
{
|
||||
Debug.Assert(customisationButton != null);
|
||||
customisationButton.TriggerClick();
|
||||
|
||||
if (!immediate)
|
||||
return;
|
||||
}
|
||||
|
||||
backButton.TriggerClick();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Sample playback control
|
||||
|
||||
private readonly Bindable<bool> samplePlaybackDisabled = new BindableBool(true);
|
||||
IBindable<bool> ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Manages horizontal scrolling of mod columns, along with the "active" states of each column based on visibility.
|
||||
/// </summary>
|
||||
internal class ColumnScrollContainer : OsuScrollContainer<ColumnFlowContainer>
|
||||
{
|
||||
public ColumnScrollContainer()
|
||||
@ -365,6 +520,9 @@ namespace osu.Game.Overlays.Mods
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manages padding and layout of mod columns.
|
||||
/// </summary>
|
||||
internal class ColumnFlowContainer : FillFlowContainer<ColumnDimContainer>
|
||||
{
|
||||
public IEnumerable<ModColumn> Columns => Children.Select(dimWrapper => dimWrapper.Column);
|
||||
@ -401,11 +559,21 @@ namespace osu.Game.Overlays.Mods
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encapsulates a column and provides dim and input blocking based on an externally managed "active" state.
|
||||
/// </summary>
|
||||
internal class ColumnDimContainer : Container
|
||||
{
|
||||
public ModColumn Column { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Tracks whether this column is in an interactive state. Generally only the case when the column is on-screen.
|
||||
/// </summary>
|
||||
public readonly Bindable<bool> Active = new BindableBool();
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the column is clicked while not active, requesting a scroll to be performed to bring it on-screen.
|
||||
/// </summary>
|
||||
public Action<ColumnDimContainer>? RequestScroll { get; set; }
|
||||
|
||||
[Resolved]
|
||||
@ -420,15 +588,20 @@ namespace osu.Game.Overlays.Mods
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
Active.BindValueChanged(_ => updateDim(), true);
|
||||
Active.BindValueChanged(_ => updateState());
|
||||
Column.AllFiltered.BindValueChanged(_ => updateState(), true);
|
||||
FinishTransforms();
|
||||
}
|
||||
|
||||
private void updateDim()
|
||||
protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate || Column.SelectionAnimationRunning;
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
Colour4 targetColour;
|
||||
|
||||
if (Active.Value)
|
||||
Column.Alpha = Column.AllFiltered.Value ? 0 : 1;
|
||||
|
||||
if (Column.Active.Value)
|
||||
targetColour = Colour4.White;
|
||||
else
|
||||
targetColour = IsHovered ? colours.GrayC : colours.Gray8;
|
||||
@ -447,17 +620,20 @@ namespace osu.Game.Overlays.Mods
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
base.OnHover(e);
|
||||
updateDim();
|
||||
updateState();
|
||||
return Active.Value;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
base.OnHoverLost(e);
|
||||
updateDim();
|
||||
updateState();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A container which blocks and handles input, managing the "return from customisation" state change.
|
||||
/// </summary>
|
||||
private class ClickToReturnContainer : Container
|
||||
{
|
||||
public BindableBool HandleMouse { get; } = new BindableBool();
|
||||
|
@ -158,7 +158,7 @@ namespace osu.Game.Overlays.Mods
|
||||
new[] { Empty() },
|
||||
new Drawable[]
|
||||
{
|
||||
new NestedVerticalScrollContainer
|
||||
new OsuScrollContainer(Direction.Vertical)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ClampExtension = 100,
|
||||
|
@ -1,48 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
/// <summary>
|
||||
/// A scroll container that handles the case of vertically scrolling content inside a larger horizontally scrolling parent container.
|
||||
/// </summary>
|
||||
public class NestedVerticalScrollContainer : OsuScrollContainer
|
||||
{
|
||||
private ModSelectScreen.ColumnScrollContainer? parentScrollContainer;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
parentScrollContainer = this.FindClosestParent<ModSelectScreen.ColumnScrollContainer>();
|
||||
}
|
||||
|
||||
protected override bool OnScroll(ScrollEvent e)
|
||||
{
|
||||
if (parentScrollContainer == null)
|
||||
return base.OnScroll(e);
|
||||
|
||||
bool topRightInView = parentScrollContainer.ScreenSpaceDrawQuad.Contains(ScreenSpaceDrawQuad.TopRight);
|
||||
bool bottomLeftInView = parentScrollContainer.ScreenSpaceDrawQuad.Contains(ScreenSpaceDrawQuad.BottomLeft);
|
||||
|
||||
// If not completely on-screen, handle scroll but also allow parent to scroll at the same time (to hopefully bring our content into full view).
|
||||
if (!topRightInView || !bottomLeftInView)
|
||||
return false;
|
||||
|
||||
bool scrollingPastEnd = e.ScrollDelta.Y < 0 && IsScrolledToEnd();
|
||||
bool scrollingPastStart = e.ScrollDelta.Y > 0 && Target <= 0;
|
||||
|
||||
// If at either of our extents, delegate scroll to the horizontal parent container.
|
||||
if (scrollingPastStart || scrollingPastEnd)
|
||||
return false;
|
||||
|
||||
return base.OnScroll(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -51,17 +51,15 @@ namespace osu.Game.Overlays.Mods
|
||||
/// </summary>
|
||||
protected Container FooterContent { get; private set; }
|
||||
|
||||
protected abstract OverlayColourScheme ColourScheme { get; }
|
||||
|
||||
protected override bool StartHidden => true;
|
||||
|
||||
protected override bool BlockNonPositionalInput => true;
|
||||
|
||||
protected ShearedOverlayContainer()
|
||||
protected ShearedOverlayContainer(OverlayColourScheme colourScheme)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
ColourProvider = new OverlayColourProvider(ColourScheme);
|
||||
ColourProvider = new OverlayColourProvider(colourScheme);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
@ -12,6 +12,11 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
public class UserModSelectScreen : ModSelectScreen
|
||||
{
|
||||
public UserModSelectScreen(OverlayColourScheme colourScheme = OverlayColourScheme.Green)
|
||||
: base(colourScheme)
|
||||
{
|
||||
}
|
||||
|
||||
protected override ModColumn CreateModColumn(ModType modType, Key[] toggleKeys = null) => new UserModColumn(modType, false, toggleKeys);
|
||||
|
||||
protected override IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection)
|
||||
|
@ -62,7 +62,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
private void updateProgress(APIUser user)
|
||||
{
|
||||
levelProgressBar.Length = user?.Statistics?.Level.Progress / 100f ?? 0;
|
||||
levelProgressText.Text = user?.Statistics?.Level.Progress.ToLocalisableString("0'%'") ?? default(LocalisableString);
|
||||
levelProgressText.Text = user?.Statistics?.Level.Progress.ToLocalisableString("0'%'") ?? default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Rankings
|
||||
startDateColumn.Value = dateToString(response.Spotlight.StartDate);
|
||||
endDateColumn.Value = dateToString(response.Spotlight.EndDate);
|
||||
mapCountColumn.Value = response.BeatmapSets.Count.ToLocalisableString(@"N0");
|
||||
participantsColumn.Value = response.Spotlight.Participants?.ToLocalisableString(@"N0") ?? default(LocalisableString);
|
||||
participantsColumn.Value = response.Spotlight.Participants?.ToLocalisableString(@"N0") ?? default;
|
||||
}
|
||||
|
||||
private LocalisableString dateToString(DateTimeOffset date) => date.ToLocalisableString(@"yyyy-MM-dd");
|
||||
|
@ -5,7 +5,6 @@ using System.Collections.Generic;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Users;
|
||||
|
||||
@ -25,7 +24,7 @@ namespace osu.Game.Overlays.Rankings.Tables
|
||||
|
||||
protected override Drawable[] CreateUniqueContent(UserStatistics item) => new Drawable[]
|
||||
{
|
||||
new RowText { Text = item.PP?.ToLocalisableString(@"N0") ?? default(LocalisableString), }
|
||||
new RowText { Text = item.PP?.ToLocalisableString(@"N0") ?? default, }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,18 @@ namespace osu.Game.Rulesets.Mods
|
||||
/// </summary>
|
||||
bool UserPlayable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this mod is valid for multiplayer matches.
|
||||
/// Should be <c>false</c> for mods that make gameplay duration dependent on user input (e.g. <see cref="ModAdaptiveSpeed"/>).
|
||||
/// </summary>
|
||||
bool ValidForMultiplayer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this mod is valid as a free mod in multiplayer matches.
|
||||
/// Should be <c>false</c> for mods that affect the gameplay duration (e.g. <see cref="ModRateAdjust"/> and <see cref="ModTimeRamp"/>).
|
||||
/// </summary>
|
||||
bool ValidForMultiplayerAsFreeMod { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a fresh <see cref="Mod"/> instance based on this mod.
|
||||
/// </summary>
|
||||
|
@ -94,6 +94,12 @@ namespace osu.Game.Rulesets.Mods
|
||||
[JsonIgnore]
|
||||
public virtual bool UserPlayable => true;
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual bool ValidForMultiplayer => true;
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual bool ValidForMultiplayerAsFreeMod => true;
|
||||
|
||||
[Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override and set UserPlayable to false.")] // Can be removed 20211009
|
||||
public virtual bool Ranked => false;
|
||||
|
||||
|
@ -31,6 +31,9 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public override bool ValidForMultiplayer => false;
|
||||
public override bool ValidForMultiplayerAsFreeMod => false;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModTimeRamp) };
|
||||
|
||||
[SettingSource("Initial rate", "The starting speed of the track")]
|
||||
|
@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Mods
|
||||
public bool RestartOnFail => false;
|
||||
|
||||
public override bool UserPlayable => false;
|
||||
public override bool ValidForMultiplayer => false;
|
||||
public override bool ValidForMultiplayerAsFreeMod => false;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModCinema), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail) };
|
||||
|
||||
|
@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
public abstract class ModRateAdjust : Mod, IApplicableToRate
|
||||
{
|
||||
public override bool ValidForMultiplayerAsFreeMod => false;
|
||||
|
||||
public abstract BindableNumber<double> SpeedChange { get; }
|
||||
|
||||
public virtual void ApplyToTrack(ITrack track)
|
||||
|
@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Mods
|
||||
[SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
|
||||
public abstract BindableBool AdjustPitch { get; }
|
||||
|
||||
public override bool ValidForMultiplayerAsFreeMod => false;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModAdaptiveSpeed) };
|
||||
|
||||
public override string SettingDescription => $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x";
|
||||
|
@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override double ScoreMultiplier => 0;
|
||||
|
||||
public override bool UserPlayable => false;
|
||||
public override bool ValidForMultiplayer => false;
|
||||
public override bool ValidForMultiplayerAsFreeMod => false;
|
||||
|
||||
public override ModType Type => ModType.System;
|
||||
|
||||
|
@ -2,11 +2,15 @@
|
||||
// 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;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
@ -51,14 +55,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
float diameter = (i + 1) * DistanceBetweenTicks * 2;
|
||||
|
||||
AddInternal(new CircularProgress
|
||||
AddInternal(new Ring(ReferenceObject, GetColourForIndexFromPlacement(i))
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Position = StartPosition,
|
||||
Current = { Value = 1 },
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(diameter),
|
||||
InnerRadius = 4 * 1f / diameter,
|
||||
Colour = GetColourForIndexFromPlacement(i)
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -100,5 +102,45 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
return (snappedPosition, snappedTime);
|
||||
}
|
||||
|
||||
private class Ring : CircularProgress
|
||||
{
|
||||
[Resolved]
|
||||
private IDistanceSnapProvider snapProvider { get; set; }
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private EditorClock editorClock { get; set; }
|
||||
|
||||
private readonly HitObject referenceObject;
|
||||
|
||||
private readonly Color4 baseColour;
|
||||
|
||||
public Ring(HitObject referenceObject, Color4 baseColour)
|
||||
{
|
||||
this.referenceObject = referenceObject;
|
||||
|
||||
Colour = this.baseColour = baseColour;
|
||||
|
||||
Current.Value = 1;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (editorClock == null)
|
||||
return;
|
||||
|
||||
float distanceSpacingMultiplier = (float)snapProvider.DistanceSpacingMultiplier.Value;
|
||||
double timeFromReferencePoint = editorClock.CurrentTime - referenceObject.GetEndTime();
|
||||
|
||||
float distanceForCurrentTime = snapProvider.DurationToDistance(referenceObject, timeFromReferencePoint)
|
||||
* distanceSpacingMultiplier;
|
||||
|
||||
float timeBasedAlpha = 1 - Math.Clamp(Math.Abs(distanceForCurrentTime - Size.X / 2) / 30, 0, 1);
|
||||
|
||||
Colour = baseColour.Opacity(Math.Max(baseColour.A, timeBasedAlpha));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,14 +4,15 @@
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Layout;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
@ -135,7 +136,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
/// </summary>
|
||||
/// <param name="placementIndex">The 0-based beat index from the point of placement.</param>
|
||||
/// <returns>The applicable colour.</returns>
|
||||
protected ColourInfo GetColourForIndexFromPlacement(int placementIndex)
|
||||
protected Color4 GetColourForIndexFromPlacement(int placementIndex)
|
||||
{
|
||||
var timingPoint = Beatmap.ControlPointInfo.TimingPointAt(StartTime);
|
||||
double beatLength = timingPoint.BeatLength / beatDivisor.Value;
|
||||
@ -144,7 +145,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
var colour = BindableBeatDivisor.GetColourFor(BindableBeatDivisor.GetDivisorForBeatIndex(beatIndex + placementIndex + 1, beatDivisor.Value), Colours);
|
||||
|
||||
int repeatIndex = placementIndex / beatDivisor.Value;
|
||||
return ColourInfo.SingleColour(colour).MultiplyAlpha(0.5f / (repeatIndex + 1));
|
||||
return colour.Opacity(0.5f / (repeatIndex + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
private void updateTooltipText()
|
||||
{
|
||||
TooltipText = cumulativeRotation.Value?.ToLocalisableString("0.0°") ?? default(LocalisableString);
|
||||
TooltipText = cumulativeRotation.Value?.ToLocalisableString("0.0°") ?? default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ using osu.Framework.Input.Events;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
@ -50,7 +51,6 @@ using osuTK.Input;
|
||||
namespace osu.Game.Screens.Edit
|
||||
{
|
||||
[Cached(typeof(IBeatSnapProvider))]
|
||||
[Cached(typeof(ISamplePlaybackDisabler))]
|
||||
[Cached]
|
||||
public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler<GlobalAction>, IKeyBindingHandler<PlatformAction>, IBeatSnapProvider, ISamplePlaybackDisabler
|
||||
{
|
||||
|
@ -87,44 +87,46 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
},
|
||||
};
|
||||
|
||||
// case MatchType.TagCoop:
|
||||
// return new SpriteIcon
|
||||
// {
|
||||
// Anchor = Anchor.Centre,
|
||||
// Origin = Anchor.Centre,
|
||||
// Size = new Vector2(size),
|
||||
// Icon = FontAwesome.Solid.Sync,
|
||||
// Colour = colours.Blue,
|
||||
//
|
||||
// Shadow = false
|
||||
// };
|
||||
#pragma warning disable IDE0055 // Indentation of commented code
|
||||
// case MatchType.TagCoop:
|
||||
// return new SpriteIcon
|
||||
// {
|
||||
// Anchor = Anchor.Centre,
|
||||
// Origin = Anchor.Centre,
|
||||
// Size = new Vector2(size),
|
||||
// Icon = FontAwesome.Solid.Sync,
|
||||
// Colour = colours.Blue,
|
||||
//
|
||||
// Shadow = false
|
||||
// };
|
||||
|
||||
// case MatchType.TagTeamCoop:
|
||||
// return new FillFlowContainer
|
||||
// {
|
||||
// Anchor = Anchor.Centre,
|
||||
// Origin = Anchor.Centre,
|
||||
// AutoSizeAxes = Axes.Both,
|
||||
// Direction = FillDirection.Horizontal,
|
||||
// Spacing = new Vector2(2f),
|
||||
// Children = new[]
|
||||
// {
|
||||
// new SpriteIcon
|
||||
// {
|
||||
// Icon = FontAwesome.Solid.Sync,
|
||||
// Size = new Vector2(size * 0.75f),
|
||||
// Colour = colours.Blue,
|
||||
// Shadow = false,
|
||||
// },
|
||||
// new SpriteIcon
|
||||
// {
|
||||
// Icon = FontAwesome.Solid.Sync,
|
||||
// Size = new Vector2(size * 0.75f),
|
||||
// Colour = colours.Pink,
|
||||
// Shadow = false,
|
||||
// },
|
||||
// },
|
||||
// };
|
||||
// case MatchType.TagTeamCoop:
|
||||
// return new FillFlowContainer
|
||||
// {
|
||||
// Anchor = Anchor.Centre,
|
||||
// Origin = Anchor.Centre,
|
||||
// AutoSizeAxes = Axes.Both,
|
||||
// Direction = FillDirection.Horizontal,
|
||||
// Spacing = new Vector2(2f),
|
||||
// Children = new[]
|
||||
// {
|
||||
// new SpriteIcon
|
||||
// {
|
||||
// Icon = FontAwesome.Solid.Sync,
|
||||
// Size = new Vector2(size * 0.75f),
|
||||
// Colour = colours.Blue,
|
||||
// Shadow = false,
|
||||
// },
|
||||
// new SpriteIcon
|
||||
// {
|
||||
// Icon = FontAwesome.Solid.Sync,
|
||||
// Size = new Vector2(size * 0.75f),
|
||||
// Colour = colours.Pink,
|
||||
// Shadow = false,
|
||||
// },
|
||||
// },
|
||||
// };
|
||||
#pragma warning restore IDE0055
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,15 +2,19 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Game.Overlays;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osuTK.Input;
|
||||
using osu.Game.Localisation;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
public class FreeModSelectScreen : ModSelectScreen
|
||||
{
|
||||
protected override bool AllowCustomisation => false;
|
||||
protected override bool ShowTotalMultiplier => false;
|
||||
|
||||
public new Func<Mod, bool> IsValidMod
|
||||
@ -20,10 +24,29 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
}
|
||||
|
||||
public FreeModSelectScreen()
|
||||
: base(OverlayColourScheme.Plum)
|
||||
{
|
||||
IsValidMod = _ => true;
|
||||
}
|
||||
|
||||
protected override ModColumn CreateModColumn(ModType modType, Key[] toggleKeys = null) => new ModColumn(modType, true, toggleKeys);
|
||||
|
||||
protected override IEnumerable<ShearedButton> CreateFooterButtons() => new[]
|
||||
{
|
||||
new ShearedButton(BUTTON_WIDTH)
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Text = CommonStrings.SelectAll,
|
||||
Action = SelectAll
|
||||
},
|
||||
new ShearedButton(BUTTON_WIDTH)
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Text = CommonStrings.DeselectAll,
|
||||
Action = DeselectAll
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
@ -57,6 +58,9 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
|
||||
protected readonly IBindable<long?> RoomId = new Bindable<long?>();
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IOverlayManager overlayManager { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private MusicController music { get; set; }
|
||||
|
||||
@ -77,7 +81,11 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
public readonly Room Room;
|
||||
private readonly bool allowEdit;
|
||||
|
||||
private ModSelectOverlay userModsSelectOverlay;
|
||||
private ModSelectScreen userModsSelectOverlay;
|
||||
|
||||
[CanBeNull]
|
||||
private IDisposable userModsSelectOverlayRegistration;
|
||||
|
||||
private RoomSettingsOverlay settingsOverlay;
|
||||
private Drawable mainContent;
|
||||
|
||||
@ -180,11 +188,6 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Child = userModsSelectOverlay = new UserModSelectOverlay
|
||||
{
|
||||
SelectedMods = { BindTarget = UserMods },
|
||||
IsValidMod = _ => false
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -227,6 +230,12 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
LoadComponent(userModsSelectOverlay = new UserModSelectScreen(OverlayColourScheme.Plum)
|
||||
{
|
||||
SelectedMods = { BindTarget = UserMods },
|
||||
IsValidMod = _ => false
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -254,6 +263,8 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
|
||||
beatmapAvailabilityTracker.SelectedItem.BindTo(SelectedItem);
|
||||
beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateWorkingBeatmap());
|
||||
|
||||
userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(userModsSelectOverlay);
|
||||
}
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
@ -298,7 +309,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
|
||||
public override void OnSuspending(ScreenTransitionEvent e)
|
||||
{
|
||||
endHandlingTrack();
|
||||
onLeaving();
|
||||
base.OnSuspending(e);
|
||||
}
|
||||
|
||||
@ -316,7 +327,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
RoomManager?.PartRoom();
|
||||
Mods.Value = Array.Empty<Mod>();
|
||||
|
||||
endHandlingTrack();
|
||||
onLeaving();
|
||||
|
||||
return base.OnExiting(e);
|
||||
}
|
||||
@ -412,6 +423,12 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
Beatmap.BindValueChanged(applyLoopingToTrack, true);
|
||||
}
|
||||
|
||||
private void onLeaving()
|
||||
{
|
||||
userModsSelectOverlay.Hide();
|
||||
endHandlingTrack();
|
||||
}
|
||||
|
||||
private void endHandlingTrack()
|
||||
{
|
||||
Beatmap.ValueChanged -= applyLoopingToTrack;
|
||||
@ -459,5 +476,12 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
public class UserModSelectButton : PurpleTriangleButton
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
userModsSelectOverlayRegistration?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -95,6 +95,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
|
||||
protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea();
|
||||
|
||||
protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && !(mod is ModTimeRamp) && !(mod is ModRateAdjust);
|
||||
protected override bool IsValidMod(Mod mod) => base.IsValidMod(mod) && mod.ValidForMultiplayer;
|
||||
|
||||
protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && mod.ValidForMultiplayerAsFreeMod;
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ using osu.Framework.Screens;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -45,7 +46,6 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
protected readonly Bindable<IReadOnlyList<Mod>> FreeMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||
|
||||
private readonly FreeModSelectOverlay freeModSelectOverlay;
|
||||
private readonly Room room;
|
||||
|
||||
private WorkingBeatmap initialBeatmap;
|
||||
@ -53,13 +53,16 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
private IReadOnlyList<Mod> initialMods;
|
||||
private bool itemSelected;
|
||||
|
||||
private readonly FreeModSelectScreen freeModSelectOverlay;
|
||||
private IDisposable freeModSelectOverlayRegistration;
|
||||
|
||||
protected OnlinePlaySongSelect(Room room)
|
||||
{
|
||||
this.room = room;
|
||||
|
||||
Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING };
|
||||
|
||||
freeModSelectOverlay = new FreeModSelectOverlay
|
||||
freeModSelectOverlay = new FreeModSelectScreen
|
||||
{
|
||||
SelectedMods = { BindTarget = FreeMods },
|
||||
IsValidMod = IsValidFreeMod,
|
||||
@ -75,7 +78,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
initialRuleset = Ruleset.Value;
|
||||
initialMods = Mods.Value.ToList();
|
||||
|
||||
FooterPanels.Add(freeModSelectOverlay);
|
||||
LoadComponent(freeModSelectOverlay);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -94,6 +97,8 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
Mods.BindValueChanged(onModsChanged);
|
||||
Ruleset.BindValueChanged(onRulesetChanged);
|
||||
|
||||
freeModSelectOverlayRegistration = OverlayManager?.RegisterBlockingOverlay(freeModSelectOverlay);
|
||||
}
|
||||
|
||||
private void onModsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods)
|
||||
@ -150,10 +155,12 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
Mods.Value = initialMods;
|
||||
}
|
||||
|
||||
freeModSelectOverlay.Hide();
|
||||
|
||||
return base.OnExiting(e);
|
||||
}
|
||||
|
||||
protected override ModSelectOverlay CreateModSelectOverlay() => new UserModSelectOverlay
|
||||
protected override ModSelectScreen CreateModSelectOverlay() => new UserModSelectScreen(OverlayColourScheme.Plum)
|
||||
{
|
||||
IsValidMod = IsValidMod
|
||||
};
|
||||
@ -182,5 +189,12 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
private bool checkCompatibleFreeMod(Mod mod)
|
||||
=> Mods.Value.All(m => m.Acronym != mod.Acronym) // Mod must not be contained in the required mods.
|
||||
&& ModUtils.CheckCompatibleSet(Mods.Value.Append(mod).ToArray()); // Mod must be compatible with all the required mods.
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
freeModSelectOverlayRegistration?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ using osu.Framework.Input.Events;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Containers;
|
||||
@ -37,7 +38,6 @@ using osuTK.Graphics;
|
||||
namespace osu.Game.Screens.Play
|
||||
{
|
||||
[Cached]
|
||||
[Cached(typeof(ISamplePlaybackDisabler))]
|
||||
public abstract class Player : ScreenWithBeatmapBackground, ISamplePlaybackDisabler, ILocalUserPlayInfo
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -85,7 +85,6 @@ namespace osu.Game.Screens.Ranking
|
||||
InternalChild = scroll = new Scroll
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
HandleScroll = () => expandedPanel?.IsHovered != true, // handle horizontal scroll only when not hovering the expanded panel.
|
||||
Child = flow = new Flow
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
@ -359,11 +358,6 @@ namespace osu.Game.Screens.Ranking
|
||||
/// </summary>
|
||||
public float? InstantScrollTarget;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this container should handle scroll trigger events.
|
||||
/// </summary>
|
||||
public Func<bool> HandleScroll;
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
if (InstantScrollTarget != null)
|
||||
@ -374,10 +368,6 @@ namespace osu.Game.Screens.Ranking
|
||||
|
||||
base.UpdateAfterChildren();
|
||||
}
|
||||
|
||||
public override bool HandlePositionalInput => HandleScroll();
|
||||
|
||||
public override bool HandleNonPositionalInput => HandleScroll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using System.Diagnostics;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Skinning;
|
||||
@ -101,7 +102,7 @@ namespace osu.Game.Screens.Select
|
||||
[Resolved(CanBeNull = true)]
|
||||
private LegacyImportManager legacyImportManager { get; set; }
|
||||
|
||||
protected ModSelectOverlay ModSelect { get; private set; }
|
||||
protected ModSelectScreen ModSelect { get; private set; }
|
||||
|
||||
protected Sample SampleConfirm { get; private set; }
|
||||
|
||||
@ -116,9 +117,15 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
private double audioFeedbackLastPlaybackTime;
|
||||
|
||||
[CanBeNull]
|
||||
private IDisposable modSelectOverlayRegistration;
|
||||
|
||||
[Resolved]
|
||||
private MusicController music { get; set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
internal IOverlayManager OverlayManager { get; private set; }
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(AudioManager audio, IDialogOverlay dialog, OsuColour colours, ManageCollectionsDialog manageCollectionsDialog, DifficultyRecommender recommender)
|
||||
{
|
||||
@ -252,38 +259,25 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
new GridContainer // used for max height implementation
|
||||
FooterPanels = new Container
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RowDimensions = new[]
|
||||
Padding = new MarginPadding { Bottom = Footer.HEIGHT },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Relative, 1f, maxSize: ModSelectOverlay.HEIGHT + Footer.HEIGHT),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
null,
|
||||
new Drawable[]
|
||||
{
|
||||
FooterPanels = new Container
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Bottom = Footer.HEIGHT },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
BeatmapOptions = new BeatmapOptionsOverlay(),
|
||||
ModSelect = CreateModSelectOverlay()
|
||||
}
|
||||
}
|
||||
}
|
||||
BeatmapOptions = new BeatmapOptionsOverlay(),
|
||||
}
|
||||
},
|
||||
Footer = new Footer()
|
||||
Footer = new Footer(),
|
||||
});
|
||||
}
|
||||
|
||||
// preload the mod select overlay for later use in `LoadComplete()`.
|
||||
// therein it will be registered at the `OsuGame` level to properly function as a blocking overlay.
|
||||
LoadComponent(ModSelect = CreateModSelectOverlay());
|
||||
|
||||
if (Footer != null)
|
||||
{
|
||||
foreach (var (button, overlay) in CreateFooterButtons())
|
||||
@ -317,6 +311,13 @@ namespace osu.Game.Screens.Select
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
modSelectOverlayRegistration = OverlayManager?.RegisterBlockingOverlay(ModSelect);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the buttons to be displayed in the footer.
|
||||
/// </summary>
|
||||
@ -332,7 +333,7 @@ namespace osu.Game.Screens.Select
|
||||
(new FooterButtonOptions(), BeatmapOptions)
|
||||
};
|
||||
|
||||
protected virtual ModSelectOverlay CreateModSelectOverlay() => new UserModSelectOverlay();
|
||||
protected virtual ModSelectScreen CreateModSelectOverlay() => new UserModSelectScreen();
|
||||
|
||||
protected virtual void ApplyFilterToCarousel(FilterCriteria criteria)
|
||||
{
|
||||
@ -658,6 +659,7 @@ namespace osu.Game.Screens.Select
|
||||
return true;
|
||||
|
||||
beatmapInfoWedge.Hide();
|
||||
ModSelect.Hide();
|
||||
|
||||
this.FadeOut(100);
|
||||
|
||||
@ -716,6 +718,8 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
if (music != null)
|
||||
music.TrackChanged -= ensureTrackLooping;
|
||||
|
||||
modSelectOverlayRegistration?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -8,7 +8,6 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
|
@ -1,12 +1,15 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Development;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens;
|
||||
|
||||
@ -15,11 +18,12 @@ namespace osu.Game.Tests.Visual
|
||||
/// <summary>
|
||||
/// A test case which can be used to test a screen (that relies on OnEntering being called to execute startup instructions).
|
||||
/// </summary>
|
||||
public abstract class ScreenTestScene : OsuManualInputManagerTestScene
|
||||
public abstract class ScreenTestScene : OsuManualInputManagerTestScene, IOverlayManager
|
||||
{
|
||||
protected readonly OsuScreenStack Stack;
|
||||
|
||||
private readonly Container content;
|
||||
private readonly Container overlayContent;
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
|
||||
@ -36,7 +40,11 @@ namespace osu.Game.Tests.Visual
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
content = new Container { RelativeSizeAxes = Axes.Both },
|
||||
DialogOverlay = new DialogOverlay()
|
||||
overlayContent = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = DialogOverlay = new DialogOverlay()
|
||||
}
|
||||
});
|
||||
|
||||
Stack.ScreenPushed += (lastScreen, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed → {newScreen}");
|
||||
@ -65,5 +73,26 @@ namespace osu.Game.Tests.Visual
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
#region IOverlayManager
|
||||
|
||||
IBindable<OverlayActivation> IOverlayManager.OverlayActivationMode { get; } = new Bindable<OverlayActivation>(OverlayActivation.All);
|
||||
|
||||
// in the blocking methods below it is important to be careful about threading (e.g. use `Expire()` rather than `Remove()`, and schedule transforms),
|
||||
// because in the worst case the clean-up methods could be called from async disposal.
|
||||
|
||||
IDisposable IOverlayManager.RegisterBlockingOverlay(OverlayContainer overlayContainer)
|
||||
{
|
||||
overlayContent.Add(overlayContainer);
|
||||
return new InvokeOnDisposal(() => overlayContainer.Expire());
|
||||
}
|
||||
|
||||
void IOverlayManager.ShowBlockingOverlay(OverlayContainer overlay)
|
||||
=> Schedule(() => Stack.FadeColour(OsuColour.Gray(0.5f), 500, Easing.OutQuint));
|
||||
|
||||
void IOverlayManager.HideBlockingOverlay(OverlayContainer overlay)
|
||||
=> Schedule(() => Stack.FadeColour(Colour4.White, 500, Easing.OutQuint));
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@ -106,22 +106,69 @@ namespace osu.Game.Utils
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check the provided combination of mods are valid for a local gameplay session.
|
||||
/// Checks that all <see cref="Mod"/>s in a combination are valid for a local gameplay session.
|
||||
/// </summary>
|
||||
/// <param name="mods">The mods to check.</param>
|
||||
/// <param name="invalidMods">Invalid mods, if any were found. Can be null if all mods were valid.</param>
|
||||
/// <param name="invalidMods">Invalid mods, if any were found. Will be null if all mods were valid.</param>
|
||||
/// <returns>Whether the input mods were all valid. If false, <paramref name="invalidMods"/> will contain all invalid entries.</returns>
|
||||
public static bool CheckValidForGameplay(IEnumerable<Mod> mods, [NotNullWhen(false)] out List<Mod>? invalidMods)
|
||||
{
|
||||
mods = mods.ToArray();
|
||||
|
||||
// exclude multi mods from compatibility checks.
|
||||
// the loop below automatically marks all multi mods as not valid for gameplay anyway.
|
||||
CheckCompatibleSet(mods.Where(m => !(m is MultiMod)), out invalidMods);
|
||||
// checking compatibility of multi mods would try to flatten them and return incompatible mods.
|
||||
// in gameplay context, we never want MultiMod selected in the first place, therefore check against it first.
|
||||
if (!checkValid(mods, m => !(m is MultiMod), out invalidMods))
|
||||
return false;
|
||||
|
||||
if (!CheckCompatibleSet(mods, out invalidMods))
|
||||
return false;
|
||||
|
||||
return checkValid(mods, m => m.Type != ModType.System && m.HasImplementation, out invalidMods);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks that all <see cref="Mod"/>s in a combination are valid as "required mods" in a multiplayer match session.
|
||||
/// </summary>
|
||||
/// <param name="mods">The mods to check.</param>
|
||||
/// <param name="invalidMods">Invalid mods, if any were found. Will be null if all mods were valid.</param>
|
||||
/// <returns>Whether the input mods were all valid. If false, <paramref name="invalidMods"/> will contain all invalid entries.</returns>
|
||||
public static bool CheckValidRequiredModsForMultiplayer(IEnumerable<Mod> mods, [NotNullWhen(false)] out List<Mod>? invalidMods)
|
||||
{
|
||||
mods = mods.ToArray();
|
||||
|
||||
// checking compatibility of multi mods would try to flatten them and return incompatible mods.
|
||||
// in gameplay context, we never want MultiMod selected in the first place, therefore check against it first.
|
||||
if (!checkValid(mods, m => !(m is MultiMod), out invalidMods))
|
||||
return false;
|
||||
|
||||
if (!CheckCompatibleSet(mods, out invalidMods))
|
||||
return false;
|
||||
|
||||
return checkValid(mods, m => m.Type != ModType.System && m.HasImplementation && m.ValidForMultiplayer, out invalidMods);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks that all <see cref="Mod"/>s in a combination are valid as "free mods" in a multiplayer match session.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that this does not check compatibility between mods,
|
||||
/// given that the passed mods are expected to be the ones to be allowed for the multiplayer match,
|
||||
/// not to be confused with the list of mods the user currently has selected for the multiplayer match.
|
||||
/// </remarks>
|
||||
/// <param name="mods">The mods to check.</param>
|
||||
/// <param name="invalidMods">Invalid mods, if any were found. Will be null if all mods were valid.</param>
|
||||
/// <returns>Whether the input mods were all valid. If false, <paramref name="invalidMods"/> will contain all invalid entries.</returns>
|
||||
public static bool CheckValidFreeModsForMultiplayer(IEnumerable<Mod> mods, [NotNullWhen(false)] out List<Mod>? invalidMods)
|
||||
=> checkValid(mods, m => m.Type != ModType.System && m.HasImplementation && m.ValidForMultiplayerAsFreeMod && !(m is MultiMod), out invalidMods);
|
||||
|
||||
private static bool checkValid(IEnumerable<Mod> mods, Predicate<Mod> valid, [NotNullWhen(false)] out List<Mod>? invalidMods)
|
||||
{
|
||||
mods = mods.ToArray();
|
||||
invalidMods = null;
|
||||
|
||||
foreach (var mod in mods)
|
||||
{
|
||||
if (mod.Type == ModType.System || !mod.HasImplementation || mod is MultiMod)
|
||||
if (!valid(mod))
|
||||
{
|
||||
invalidMods ??= new List<Mod>();
|
||||
invalidMods.Add(mod);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user