mirror of
https://github.com/ppy/osu.git
synced 2025-02-22 02:03:20 +08:00
Merge branch 'master' into hitposition
This commit is contained in:
commit
84c0bd0052
@ -196,6 +196,9 @@ csharp_style_prefer_switch_expression = false:none
|
|||||||
|
|
||||||
csharp_style_namespace_declarations = block_scoped:warning
|
csharp_style_namespace_declarations = block_scoped:warning
|
||||||
|
|
||||||
|
#Style - C# 12 features
|
||||||
|
csharp_style_prefer_primary_constructors = false
|
||||||
|
|
||||||
[*.{yaml,yml}]
|
[*.{yaml,yml}]
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
indent_style = space
|
indent_style = space
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.523.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.528.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||||
|
@ -54,6 +54,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
[TestCase("3949367", new[] { typeof(CatchModDoubleTime), typeof(CatchModEasy) })]
|
[TestCase("3949367", new[] { typeof(CatchModDoubleTime), typeof(CatchModEasy) })]
|
||||||
[TestCase("112643")]
|
[TestCase("112643")]
|
||||||
[TestCase("1041052", new[] { typeof(CatchModHardRock) })]
|
[TestCase("1041052", new[] { typeof(CatchModHardRock) })]
|
||||||
|
[TestCase("high-speed-multiplier-precision")]
|
||||||
public new void Test(string name, params Type[] mods) => base.Test(name, mods);
|
public new void Test(string name, params Type[] mods) => base.Test(name, mods);
|
||||||
|
|
||||||
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
{"Mappings":[{"StartTime":265568.0,"Objects":[{"StartTime":265568.0,"Position":486.0,"HyperDash":false},{"StartTime":265658.0,"Position":465.1873,"HyperDash":false},{"StartTime":265749.0,"Position":463.208435,"HyperDash":false},{"StartTime":265840.0,"Position":465.146484,"HyperDash":false},{"StartTime":265967.0,"Position":459.5862,"HyperDash":false}]}]}
|
@ -0,0 +1,238 @@
|
|||||||
|
osu file format v14
|
||||||
|
|
||||||
|
[General]
|
||||||
|
AudioFilename: audio.mp3
|
||||||
|
AudioLeadIn: 0
|
||||||
|
PreviewTime: 226943
|
||||||
|
Countdown: 0
|
||||||
|
SampleSet: Soft
|
||||||
|
StackLeniency: 0.7
|
||||||
|
Mode: 2
|
||||||
|
LetterboxInBreaks: 0
|
||||||
|
WidescreenStoryboard: 1
|
||||||
|
|
||||||
|
[Editor]
|
||||||
|
Bookmarks: 85568,86768,90968,265568
|
||||||
|
DistanceSpacing: 0.9
|
||||||
|
BeatDivisor: 12
|
||||||
|
GridSize: 16
|
||||||
|
TimelineZoom: 1
|
||||||
|
|
||||||
|
[Metadata]
|
||||||
|
Title:Snow
|
||||||
|
TitleUnicode:Snow
|
||||||
|
Artist:Ricky Montgomery
|
||||||
|
ArtistUnicode:Ricky Montgomery
|
||||||
|
Creator:Crowley
|
||||||
|
Version:Bury Me Six Feet in Snow
|
||||||
|
Source:
|
||||||
|
Tags:indie the honeysticks alternative english
|
||||||
|
BeatmapID:2062131
|
||||||
|
BeatmapSetID:971028
|
||||||
|
|
||||||
|
[Difficulty]
|
||||||
|
HPDrainRate:6
|
||||||
|
CircleSize:4.2
|
||||||
|
OverallDifficulty:8.3
|
||||||
|
ApproachRate:8.3
|
||||||
|
SliderMultiplier:3.59999990463257
|
||||||
|
SliderTickRate:1
|
||||||
|
|
||||||
|
[Events]
|
||||||
|
//Background and Video events
|
||||||
|
0,0,"me.jpg",0,0
|
||||||
|
//Break Periods
|
||||||
|
//Storyboard Layer 0 (Background)
|
||||||
|
//Storyboard Layer 1 (Fail)
|
||||||
|
//Storyboard Layer 2 (Pass)
|
||||||
|
//Storyboard Layer 3 (Foreground)
|
||||||
|
//Storyboard Layer 4 (Overlay)
|
||||||
|
//Storyboard Sound Samples
|
||||||
|
|
||||||
|
[TimingPoints]
|
||||||
|
368,1200,2,2,1,30,1,0
|
||||||
|
368,-66.6666666666667,2,2,1,30,0,0
|
||||||
|
29168,-58.8235294117647,2,2,1,40,0,0
|
||||||
|
30368,-58.8235294117647,2,2,2,40,0,0
|
||||||
|
30568,-58.8235294117647,2,2,1,40,0,0
|
||||||
|
31368,-58.8235294117647,2,2,2,40,0,0
|
||||||
|
31568,-58.8235294117647,2,2,1,40,0,0
|
||||||
|
32768,-58.8235294117647,2,2,2,40,0,0
|
||||||
|
33568,-58.8235294117647,2,2,2,40,0,0
|
||||||
|
33968,-58.8235294117647,2,2,1,40,0,0
|
||||||
|
35168,-58.8235294117647,2,2,2,40,0,0
|
||||||
|
35968,-58.8235294117647,2,2,1,40,0,0
|
||||||
|
36168,-58.8235294117647,2,2,2,40,0,0
|
||||||
|
36368,-58.8235294117647,2,2,1,40,0,0
|
||||||
|
37568,-58.8235294117647,2,2,2,40,0,0
|
||||||
|
37968,-58.8235294117647,2,2,1,40,0,0
|
||||||
|
38368,-58.8235294117647,2,2,2,40,0,0
|
||||||
|
38768,-58.8235294117647,2,2,1,40,0,0
|
||||||
|
39968,-58.8235294117647,2,2,2,40,0,0
|
||||||
|
40168,-58.8235294117647,2,2,1,40,0,0
|
||||||
|
40968,-58.8235294117647,2,2,2,40,0,0
|
||||||
|
41168,-58.8235294117647,2,2,1,40,0,0
|
||||||
|
42368,-58.8235294117647,2,2,2,40,0,0
|
||||||
|
43168,-58.8235294117647,2,2,2,40,0,0
|
||||||
|
43568,-58.8235294117647,2,2,1,40,0,0
|
||||||
|
44768,-58.8235294117647,2,2,2,40,0,0
|
||||||
|
45768,-58.8235294117647,2,2,2,40,0,0
|
||||||
|
45968,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
47168,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
48368,-62.5,2,2,1,50,0,0
|
||||||
|
67568,-58.8235294117647,2,2,1,70,0,1
|
||||||
|
84668,-58.8235294117647,2,2,1,5,0,1
|
||||||
|
84768,-58.8235294117647,2,2,1,70,0,1
|
||||||
|
85068,-58.8235294117647,2,2,1,5,0,1
|
||||||
|
85168,-58.8235294117647,2,2,1,70,0,1
|
||||||
|
85468,-58.8235294117647,2,2,1,5,0,1
|
||||||
|
85568,-58.8235294117647,2,2,1,70,0,1
|
||||||
|
86768,-58.8235294117647,2,2,1,30,0,0
|
||||||
|
91168,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
91568,1200,2,2,1,50,1,0
|
||||||
|
91568,-58.8235294117647,2,2,1,50,0,1
|
||||||
|
91643,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
92768,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
92968,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
95168,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
95368,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
97568,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
97768,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
99968,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
100168,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
100768,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
101168,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
102368,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
102568,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
104768,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
104968,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
107168,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
107368,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
108968,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
109168,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
109568,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
109968,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
110368,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
110768,-100,2,2,1,40,0,0
|
||||||
|
127568,-62.5,2,2,2,50,0,0
|
||||||
|
127968,-62.5,2,2,1,50,0,0
|
||||||
|
128168,-62.5,2,2,2,50,0,0
|
||||||
|
129968,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
131168,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
131368,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
133568,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
133768,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
135968,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
136168,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
138368,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
138568,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
139168,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
139368,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
139568,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
140768,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
140968,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
143168,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
143368,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
145568,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
145768,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
147368,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
147768,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
147968,-58.8235294117647,2,2,1,60,0,0
|
||||||
|
148768,-58.8235294117647,2,2,2,60,0,0
|
||||||
|
149168,-58.8235294117647,2,2,1,70,0,1
|
||||||
|
158268,-58.8235294117647,2,2,2,70,0,1
|
||||||
|
158568,-58.8235294117647,2,2,1,70,0,1
|
||||||
|
166268,-58.8235294117647,2,2,1,5,0,1
|
||||||
|
166368,-58.8235294117647,2,2,1,70,0,1
|
||||||
|
166668,-58.8235294117647,2,2,1,5,0,1
|
||||||
|
166768,-58.8235294117647,2,2,1,70,0,1
|
||||||
|
167068,-58.8235294117647,2,2,1,5,0,1
|
||||||
|
167168,-58.8235294117647,2,2,1,70,0,1
|
||||||
|
168368,-62.5,2,2,1,50,0,0
|
||||||
|
172368,-62.5,2,2,1,50,0,1
|
||||||
|
173168,-62.5,2,2,1,50,0,0
|
||||||
|
185168,-62.5,2,2,1,60,0,0
|
||||||
|
185468,-62.5,2,2,1,5,0,0
|
||||||
|
185568,-62.5,2,2,1,60,0,0
|
||||||
|
185868,-62.5,2,2,1,5,0,0
|
||||||
|
185968,-62.5,2,2,1,60,0,0
|
||||||
|
186268,-62.5,2,2,1,5,0,0
|
||||||
|
186368,-62.5,2,2,1,60,0,0
|
||||||
|
186668,-62.5,2,2,1,5,0,0
|
||||||
|
186768,-52.6315789473684,2,2,1,60,0,0
|
||||||
|
187068,-62.5,2,2,1,5,0,0
|
||||||
|
187168,-62.5,2,2,1,60,0,0
|
||||||
|
187468,-62.5,2,2,1,5,0,0
|
||||||
|
187568,-62.5,2,2,1,20,0,0
|
||||||
|
187768,-62.5,2,2,1,24,0,0
|
||||||
|
187968,-62.5,2,2,1,28,0,0
|
||||||
|
188168,-62.5,2,2,1,32,0,0
|
||||||
|
188368,-62.5,2,2,1,36,0,0
|
||||||
|
188568,-62.5,2,2,1,40,0,0
|
||||||
|
188768,1200,2,2,1,50,1,1
|
||||||
|
188768,-58.8235294117647,2,2,1,50,0,1
|
||||||
|
188843,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
189968,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
190168,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
192368,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
192568,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
194768,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
194968,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
196568,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
196768,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
197168,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
197368,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
197568,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
197968,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
198368,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
199568,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
199768,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
201968,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
202168,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
204368,-58.8235294117647,2,2,2,50,0,0
|
||||||
|
204568,-58.8235294117647,2,2,1,50,0,0
|
||||||
|
206768,-58.8235294117647,2,2,1,60,0,0
|
||||||
|
207168,-58.8235294117647,2,2,2,60,0,0
|
||||||
|
207968,-58.8235294117647,2,2,1,70,0,1
|
||||||
|
216968,-58.8235294117647,2,2,2,70,0,1
|
||||||
|
217168,-58.8235294117647,2,2,1,70,0,1
|
||||||
|
217368,-58.8235294117647,2,2,2,70,0,1
|
||||||
|
217568,-58.8235294117647,2,2,1,70,0,1
|
||||||
|
225068,-58.8235294117647,2,2,1,5,0,1
|
||||||
|
225168,-58.8235294117647,2,2,1,70,0,1
|
||||||
|
225468,-58.8235294117647,2,2,1,5,0,1
|
||||||
|
225568,-58.8235294117647,2,2,1,70,0,1
|
||||||
|
225868,-58.8235294117647,2,2,1,5,0,1
|
||||||
|
225968,-58.8235294117647,2,2,1,70,0,1
|
||||||
|
227168,-58.8235294117647,2,2,1,30,0,0
|
||||||
|
234368,-58.8235294117647,2,2,1,40,0,0
|
||||||
|
236768,-58.8235294117647,2,2,1,70,0,1
|
||||||
|
255968,-58.8235294117647,2,2,1,70,0,1
|
||||||
|
261168,-58.8235294117647,2,2,1,70,0,1
|
||||||
|
263068,-58.8235294117647,2,2,1,70,0,0
|
||||||
|
263168,-58.8235294117647,2,2,1,60,0,1
|
||||||
|
263243,-58.8235294117647,2,2,1,60,0,0
|
||||||
|
264368,-58.8235294117647,2,2,1,60,0,1
|
||||||
|
264443,-58.8235294117647,2,2,1,60,0,0
|
||||||
|
265568,-444.444444444444,2,2,1,50,0,1
|
||||||
|
265643,-444.444444444444,2,2,1,50,0,0
|
||||||
|
266768,-444.444444444444,2,2,1,40,0,0
|
||||||
|
267968,-444.444444444444,2,2,1,30,0,0
|
||||||
|
269168,-444.444444444444,2,2,1,20,0,0
|
||||||
|
270368,-444.444444444444,2,2,1,10,0,0
|
||||||
|
271168,-444.444444444444,2,2,1,9,0,0
|
||||||
|
271568,-444.444444444444,2,2,1,8,0,0
|
||||||
|
271968,-444.444444444444,2,2,1,7,0,0
|
||||||
|
272368,-444.444444444444,2,2,1,6,0,0
|
||||||
|
272768,-444.444444444444,2,2,1,5,0,0
|
||||||
|
275168,-444.444444444444,2,2,1,5,0,0
|
||||||
|
|
||||||
|
|
||||||
|
[Colours]
|
||||||
|
Combo1 : 255,128,128
|
||||||
|
Combo2 : 72,72,255
|
||||||
|
Combo3 : 192,192,192
|
||||||
|
Combo4 : 255,136,79
|
||||||
|
|
||||||
|
[HitObjects]
|
||||||
|
486,179,265568,6,0,P|461:174|454:174,1,26.999997997284,6|0,1:2|0:0,0:0:0:0:
|
@ -29,7 +29,6 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
public BindableNumber<double> SliderVelocityMultiplierBindable { get; } = new BindableDouble(1)
|
public BindableNumber<double> SliderVelocityMultiplierBindable { get; } = new BindableDouble(1)
|
||||||
{
|
{
|
||||||
Precision = 0.01,
|
|
||||||
MinValue = 0.1,
|
MinValue = 0.1,
|
||||||
MaxValue = 10
|
MaxValue = 10
|
||||||
};
|
};
|
||||||
|
@ -140,10 +140,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
private void onIsHittingChanged(ValueChangedEvent<bool> isHitting)
|
private void onIsHittingChanged(ValueChangedEvent<bool> isHitting)
|
||||||
{
|
{
|
||||||
if (bodySprite is TextureAnimation bodyAnimation)
|
if (bodySprite is TextureAnimation bodyAnimation)
|
||||||
{
|
|
||||||
bodyAnimation.GotoFrame(0);
|
|
||||||
bodyAnimation.IsPlaying = isHitting.NewValue;
|
bodyAnimation.IsPlaying = isHitting.NewValue;
|
||||||
}
|
|
||||||
|
|
||||||
if (lightContainer == null)
|
if (lightContainer == null)
|
||||||
return;
|
return;
|
||||||
@ -219,6 +216,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
|
if (!isHitting.Value)
|
||||||
|
(bodySprite as TextureAnimation)?.GotoFrame(0);
|
||||||
|
|
||||||
if (holdNote.Body.HasHoldBreak)
|
if (holdNote.Body.HasHoldBreak)
|
||||||
missFadeTime.Value = holdNote.Body.Result.TimeAbsolute;
|
missFadeTime.Value = holdNote.Body.Result.TimeAbsolute;
|
||||||
|
|
||||||
|
@ -8,7 +8,9 @@ using osu.Framework.Utils;
|
|||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Osu.Edit;
|
using osu.Game.Rulesets.Osu.Edit;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
using osu.Game.Utils;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -25,22 +27,22 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||||
|
|
||||||
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||||
rectangularGridActive(false);
|
gridActive<RectangularPositionSnapGrid>(false);
|
||||||
|
|
||||||
AddStep("enable rectangular grid", () => InputManager.Key(Key.Y));
|
AddStep("enable rectangular grid", () => InputManager.Key(Key.Y));
|
||||||
|
|
||||||
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||||
AddUntilStep("distance snap grid still visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
AddUntilStep("distance snap grid still visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||||
rectangularGridActive(true);
|
gridActive<RectangularPositionSnapGrid>(true);
|
||||||
|
|
||||||
AddStep("disable distance snap grid", () => InputManager.Key(Key.T));
|
AddStep("disable distance snap grid", () => InputManager.Key(Key.T));
|
||||||
AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||||
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||||
rectangularGridActive(true);
|
gridActive<RectangularPositionSnapGrid>(true);
|
||||||
|
|
||||||
AddStep("disable rectangular grid", () => InputManager.Key(Key.Y));
|
AddStep("disable rectangular grid", () => InputManager.Key(Key.Y));
|
||||||
AddUntilStep("distance snap grid still hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
AddUntilStep("distance snap grid still hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||||
rectangularGridActive(false);
|
gridActive<RectangularPositionSnapGrid>(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -117,33 +119,56 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestGridSnapMomentaryToggle()
|
public void TestGridSnapMomentaryToggle()
|
||||||
{
|
{
|
||||||
rectangularGridActive(false);
|
gridActive<RectangularPositionSnapGrid>(false);
|
||||||
AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));
|
AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));
|
||||||
rectangularGridActive(true);
|
gridActive<RectangularPositionSnapGrid>(true);
|
||||||
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
|
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
|
||||||
rectangularGridActive(false);
|
gridActive<RectangularPositionSnapGrid>(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rectangularGridActive(bool active)
|
private void gridActive<T>(bool active) where T : PositionSnapGrid
|
||||||
{
|
{
|
||||||
AddStep("choose placement tool", () => InputManager.Key(Key.Number2));
|
AddStep("choose placement tool", () => InputManager.Key(Key.Number2));
|
||||||
AddStep("move cursor to (1, 1)", () =>
|
AddStep("move cursor to spacing + (1, 1)", () =>
|
||||||
{
|
{
|
||||||
var composer = Editor.ChildrenOfType<OsuRectangularPositionSnapGrid>().Single();
|
var composer = Editor.ChildrenOfType<T>().Single();
|
||||||
InputManager.MoveMouseTo(composer.ToScreenSpace(new Vector2(1, 1)));
|
InputManager.MoveMouseTo(composer.ToScreenSpace(uniqueSnappingPosition(composer) + new Vector2(1, 1)));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (active)
|
if (active)
|
||||||
AddAssert("placement blueprint at (0, 0)", () => Precision.AlmostEquals(Editor.ChildrenOfType<HitCirclePlacementBlueprint>().Single().HitObject.Position, new Vector2(0, 0)));
|
{
|
||||||
|
AddAssert("placement blueprint at spacing + (0, 0)", () =>
|
||||||
|
{
|
||||||
|
var composer = Editor.ChildrenOfType<T>().Single();
|
||||||
|
return Precision.AlmostEquals(Editor.ChildrenOfType<HitCirclePlacementBlueprint>().Single().HitObject.Position,
|
||||||
|
uniqueSnappingPosition(composer));
|
||||||
|
});
|
||||||
|
}
|
||||||
else
|
else
|
||||||
AddAssert("placement blueprint at (1, 1)", () => Precision.AlmostEquals(Editor.ChildrenOfType<HitCirclePlacementBlueprint>().Single().HitObject.Position, new Vector2(1, 1)));
|
{
|
||||||
|
AddAssert("placement blueprint at spacing + (1, 1)", () =>
|
||||||
|
{
|
||||||
|
var composer = Editor.ChildrenOfType<T>().Single();
|
||||||
|
return Precision.AlmostEquals(Editor.ChildrenOfType<HitCirclePlacementBlueprint>().Single().HitObject.Position,
|
||||||
|
uniqueSnappingPosition(composer) + new Vector2(1, 1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector2 uniqueSnappingPosition(PositionSnapGrid grid)
|
||||||
|
{
|
||||||
|
return grid switch
|
||||||
|
{
|
||||||
|
RectangularPositionSnapGrid rectangular => rectangular.StartPosition.Value + GeometryUtils.RotateVector(rectangular.Spacing.Value, -rectangular.GridLineRotation.Value),
|
||||||
|
_ => Vector2.Zero
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestGridSizeToggling()
|
public void TestGridSizeToggling()
|
||||||
{
|
{
|
||||||
AddStep("enable rectangular grid", () => InputManager.Key(Key.Y));
|
AddStep("enable rectangular grid", () => InputManager.Key(Key.Y));
|
||||||
AddUntilStep("rectangular grid visible", () => this.ChildrenOfType<OsuRectangularPositionSnapGrid>().Any());
|
AddUntilStep("rectangular grid visible", () => this.ChildrenOfType<RectangularPositionSnapGrid>().Any());
|
||||||
gridSizeIs(4);
|
gridSizeIs(4);
|
||||||
|
|
||||||
nextGridSizeIs(8);
|
nextGridSizeIs(8);
|
||||||
@ -159,7 +184,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void gridSizeIs(int size)
|
private void gridSizeIs(int size)
|
||||||
=> AddAssert($"grid size is {size}", () => this.ChildrenOfType<OsuRectangularPositionSnapGrid>().Single().Spacing == new Vector2(size)
|
=> AddAssert($"grid size is {size}", () => this.ChildrenOfType<RectangularPositionSnapGrid>().Single().Spacing.Value == new Vector2(size)
|
||||||
&& EditorBeatmap.BeatmapInfo.GridSize == size);
|
&& EditorBeatmap.BeatmapInfo.GridSize == size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,12 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
@ -71,4 +74,120 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
private void moveMouse(Vector2 pos) =>
|
private void moveMouse(Vector2 pos) =>
|
||||||
AddStep($"move mouse to {pos}", () => InputManager.MoveMouseTo(playfield.ToScreenSpace(pos)));
|
AddStep($"move mouse to {pos}", () => InputManager.MoveMouseTo(playfield.ToScreenSpace(pos)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSliderNearLinearScaling
|
||||||
|
{
|
||||||
|
private readonly Random rng = new Random(1337);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScalingSliderFlat()
|
||||||
|
{
|
||||||
|
SliderPath sliderPathPerfect = new SliderPath(
|
||||||
|
[
|
||||||
|
new PathControlPoint(new Vector2(0), PathType.PERFECT_CURVE),
|
||||||
|
new PathControlPoint(new Vector2(50, 25)),
|
||||||
|
new PathControlPoint(new Vector2(25, 100)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
SliderPath sliderPathBezier = new SliderPath(
|
||||||
|
[
|
||||||
|
new PathControlPoint(new Vector2(0), PathType.BEZIER),
|
||||||
|
new PathControlPoint(new Vector2(50, 25)),
|
||||||
|
new PathControlPoint(new Vector2(25, 100)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
scaleSlider(sliderPathPerfect, new Vector2(0.000001f, 1));
|
||||||
|
scaleSlider(sliderPathBezier, new Vector2(0.000001f, 1));
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
Assert.True(Precision.AlmostEquals(sliderPathPerfect.PositionAt(i / 100.0f), sliderPathBezier.PositionAt(i / 100.0f)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPerfectCurveMatchesTheoretical()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 20000; i++)
|
||||||
|
{
|
||||||
|
//Only test points that are in the screen's bounds
|
||||||
|
float p1X = 640.0f * (float)rng.NextDouble();
|
||||||
|
float p2X = 640.0f * (float)rng.NextDouble();
|
||||||
|
|
||||||
|
float p1Y = 480.0f * (float)rng.NextDouble();
|
||||||
|
float p2Y = 480.0f * (float)rng.NextDouble();
|
||||||
|
SliderPath sliderPathPerfect = new SliderPath(
|
||||||
|
[
|
||||||
|
new PathControlPoint(new Vector2(0, 0), PathType.PERFECT_CURVE),
|
||||||
|
new PathControlPoint(new Vector2(p1X, p1Y)),
|
||||||
|
new PathControlPoint(new Vector2(p2X, p2Y)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assertMatchesPerfectCircle(sliderPathPerfect);
|
||||||
|
|
||||||
|
scaleSlider(sliderPathPerfect, new Vector2(0.00001f, 1));
|
||||||
|
|
||||||
|
assertMatchesPerfectCircle(sliderPathPerfect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertMatchesPerfectCircle(SliderPath path)
|
||||||
|
{
|
||||||
|
if (path.ControlPoints.Count != 3)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//Replication of PathApproximator.CircularArcToPiecewiseLinear
|
||||||
|
CircularArcProperties circularArcProperties = new CircularArcProperties(path.ControlPoints.Select(x => x.Position).ToArray());
|
||||||
|
|
||||||
|
if (!circularArcProperties.IsValid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//Addresses cases where circularArcProperties.ThetaRange>0.5
|
||||||
|
//Occurs in code in PathControlPointVisualiser.ensureValidPathType
|
||||||
|
RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(path.ControlPoints.Select(x => x.Position).ToArray());
|
||||||
|
if (boundingBox.Width >= 640 || boundingBox.Height >= 480)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int subpoints = (2f * circularArcProperties.Radius <= 0.1f) ? 2 : Math.Max(2, (int)Math.Ceiling(circularArcProperties.ThetaRange / (2.0 * Math.Acos(1f - (0.1f / circularArcProperties.Radius)))));
|
||||||
|
|
||||||
|
//ignore cases where subpoints is int.MaxValue, result will be garbage
|
||||||
|
//as well, having this many subpoints will cause an out of memory error, so can't happen during normal useage
|
||||||
|
if (subpoints == int.MaxValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int i = 0; i < Math.Min(subpoints, 100); i++)
|
||||||
|
{
|
||||||
|
float progress = (float)rng.NextDouble();
|
||||||
|
|
||||||
|
//To avoid errors from interpolating points, ensure we check only positions that would be subpoints.
|
||||||
|
progress = (float)Math.Ceiling(progress * (subpoints - 1)) / (subpoints - 1);
|
||||||
|
|
||||||
|
//Special case - if few subpoints, ensure checking every single one rather than randomly
|
||||||
|
if (subpoints < 100)
|
||||||
|
progress = i / (float)(subpoints - 1);
|
||||||
|
|
||||||
|
//edge points cause issue with interpolation, so ignore the last two points and first
|
||||||
|
if (progress == 0.0f || progress >= (subpoints - 2) / (float)(subpoints - 1))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
double theta = circularArcProperties.ThetaStart + (circularArcProperties.Direction * progress * circularArcProperties.ThetaRange);
|
||||||
|
Vector2 vector = new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta)) * circularArcProperties.Radius;
|
||||||
|
|
||||||
|
Assert.True(Precision.AlmostEquals(circularArcProperties.Centre + vector, path.PositionAt(progress), 0.01f),
|
||||||
|
"A perfect circle with points " + string.Join(", ", path.ControlPoints.Select(x => x.Position)) + " and radius" + circularArcProperties.Radius + "from SliderPath does not almost equal a theoretical perfect circle with " + subpoints + " subpoints"
|
||||||
|
+ ": " + (circularArcProperties.Centre + vector) + " - " + path.PositionAt(progress)
|
||||||
|
+ " = " + (circularArcProperties.Centre + vector - path.PositionAt(progress))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scaleSlider(SliderPath path, Vector2 scale)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < path.ControlPoints.Count; i++)
|
||||||
|
{
|
||||||
|
path.ControlPoints[i].Position *= scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
private partial class TestDrawableOsuJudgement : DrawableOsuJudgement
|
private partial class TestDrawableOsuJudgement : DrawableOsuJudgement
|
||||||
{
|
{
|
||||||
public new SkinnableSprite Lighting => base.Lighting;
|
public new SkinnableSprite Lighting => base.Lighting;
|
||||||
public new SkinnableDrawable JudgementBody => base.JudgementBody;
|
public new SkinnableDrawable? JudgementBody => base.JudgementBody;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
171
osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
Normal file
171
osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Input.Bindings;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
|
{
|
||||||
|
public partial class OsuGridToolboxGroup : EditorToolboxGroup, IKeyBindingHandler<GlobalAction>
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// X position of the grid's origin.
|
||||||
|
/// </summary>
|
||||||
|
public BindableFloat StartPositionX { get; } = new BindableFloat(OsuPlayfield.BASE_SIZE.X / 2)
|
||||||
|
{
|
||||||
|
MinValue = 0f,
|
||||||
|
MaxValue = OsuPlayfield.BASE_SIZE.X,
|
||||||
|
Precision = 1f
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Y position of the grid's origin.
|
||||||
|
/// </summary>
|
||||||
|
public BindableFloat StartPositionY { get; } = new BindableFloat(OsuPlayfield.BASE_SIZE.Y / 2)
|
||||||
|
{
|
||||||
|
MinValue = 0f,
|
||||||
|
MaxValue = OsuPlayfield.BASE_SIZE.Y,
|
||||||
|
Precision = 1f
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The spacing between grid lines.
|
||||||
|
/// </summary>
|
||||||
|
public BindableFloat Spacing { get; } = new BindableFloat(4f)
|
||||||
|
{
|
||||||
|
MinValue = 4f,
|
||||||
|
MaxValue = 128f,
|
||||||
|
Precision = 1f
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rotation of the grid lines in degrees.
|
||||||
|
/// </summary>
|
||||||
|
public BindableFloat GridLinesRotation { get; } = new BindableFloat(0f)
|
||||||
|
{
|
||||||
|
MinValue = -45f,
|
||||||
|
MaxValue = 45f,
|
||||||
|
Precision = 1f
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read-only bindable representing the grid's origin.
|
||||||
|
/// Equivalent to <code>new Vector2(StartPositionX, StartPositionY)</code>
|
||||||
|
/// </summary>
|
||||||
|
public Bindable<Vector2> StartPosition { get; } = new Bindable<Vector2>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read-only bindable representing the grid's spacing in both the X and Y dimension.
|
||||||
|
/// Equivalent to <code>new Vector2(Spacing)</code>
|
||||||
|
/// </summary>
|
||||||
|
public Bindable<Vector2> SpacingVector { get; } = new Bindable<Vector2>();
|
||||||
|
|
||||||
|
private ExpandableSlider<float> startPositionXSlider = null!;
|
||||||
|
private ExpandableSlider<float> startPositionYSlider = null!;
|
||||||
|
private ExpandableSlider<float> spacingSlider = null!;
|
||||||
|
private ExpandableSlider<float> gridLinesRotationSlider = null!;
|
||||||
|
|
||||||
|
public OsuGridToolboxGroup()
|
||||||
|
: base("grid")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private const float max_automatic_spacing = 64;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
startPositionXSlider = new ExpandableSlider<float>
|
||||||
|
{
|
||||||
|
Current = StartPositionX,
|
||||||
|
KeyboardStep = 1,
|
||||||
|
},
|
||||||
|
startPositionYSlider = new ExpandableSlider<float>
|
||||||
|
{
|
||||||
|
Current = StartPositionY,
|
||||||
|
KeyboardStep = 1,
|
||||||
|
},
|
||||||
|
spacingSlider = new ExpandableSlider<float>
|
||||||
|
{
|
||||||
|
Current = Spacing,
|
||||||
|
KeyboardStep = 1,
|
||||||
|
},
|
||||||
|
gridLinesRotationSlider = new ExpandableSlider<float>
|
||||||
|
{
|
||||||
|
Current = GridLinesRotation,
|
||||||
|
KeyboardStep = 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Spacing.Value = editorBeatmap.BeatmapInfo.GridSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
StartPositionX.BindValueChanged(x =>
|
||||||
|
{
|
||||||
|
startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:N0}";
|
||||||
|
startPositionXSlider.ExpandedLabelText = $"X Offset: {x.NewValue:N0}";
|
||||||
|
StartPosition.Value = new Vector2(x.NewValue, StartPosition.Value.Y);
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
StartPositionY.BindValueChanged(y =>
|
||||||
|
{
|
||||||
|
startPositionYSlider.ContractedLabelText = $"Y: {y.NewValue:N0}";
|
||||||
|
startPositionYSlider.ExpandedLabelText = $"Y Offset: {y.NewValue:N0}";
|
||||||
|
StartPosition.Value = new Vector2(StartPosition.Value.X, y.NewValue);
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
Spacing.BindValueChanged(spacing =>
|
||||||
|
{
|
||||||
|
spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:N0}";
|
||||||
|
spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:N0}";
|
||||||
|
SpacingVector.Value = new Vector2(spacing.NewValue);
|
||||||
|
editorBeatmap.BeatmapInfo.GridSize = (int)spacing.NewValue;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
GridLinesRotation.BindValueChanged(rotation =>
|
||||||
|
{
|
||||||
|
gridLinesRotationSlider.ContractedLabelText = $"R: {rotation.NewValue:#,0.##}";
|
||||||
|
gridLinesRotationSlider.ExpandedLabelText = $"Rotation: {rotation.NewValue:#,0.##}";
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void nextGridSize()
|
||||||
|
{
|
||||||
|
Spacing.Value = Spacing.Value * 2 >= max_automatic_spacing ? Spacing.Value / 8 : Spacing.Value * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||||
|
{
|
||||||
|
switch (e.Action)
|
||||||
|
{
|
||||||
|
case GlobalAction.EditorCycleGridDisplayMode:
|
||||||
|
nextGridSize();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,7 @@ using osu.Game.Rulesets.Edit.Tools;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
@ -65,6 +66,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
[Cached(typeof(IDistanceSnapProvider))]
|
[Cached(typeof(IDistanceSnapProvider))]
|
||||||
protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider();
|
protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider();
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
protected readonly OsuGridToolboxGroup OsuGridToolboxGroup = new OsuGridToolboxGroup();
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
protected readonly FreehandSliderToolboxGroup FreehandlSliderToolboxGroup = new FreehandSliderToolboxGroup();
|
protected readonly FreehandSliderToolboxGroup FreehandlSliderToolboxGroup = new FreehandSliderToolboxGroup();
|
||||||
|
|
||||||
@ -80,10 +84,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
LayerBelowRuleset.AddRange(new Drawable[]
|
LayerBelowRuleset.AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
distanceSnapGridContainer = new Container
|
distanceSnapGridContainer = new Container
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both
|
|
||||||
},
|
|
||||||
rectangularPositionSnapGrid = new OsuRectangularPositionSnapGrid
|
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
}
|
}
|
||||||
@ -99,14 +99,38 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
// we may be entering the screen with a selection already active
|
// we may be entering the screen with a selection already active
|
||||||
updateDistanceSnapGrid();
|
updateDistanceSnapGrid();
|
||||||
|
|
||||||
|
updatePositionSnapGrid();
|
||||||
|
|
||||||
RightToolbox.AddRange(new EditorToolboxGroup[]
|
RightToolbox.AddRange(new EditorToolboxGroup[]
|
||||||
{
|
{
|
||||||
new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, },
|
OsuGridToolboxGroup,
|
||||||
|
new TransformToolboxGroup
|
||||||
|
{
|
||||||
|
RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler,
|
||||||
|
ScaleHandler = (OsuSelectionScaleHandler)BlueprintContainer.SelectionHandler.ScaleHandler,
|
||||||
|
},
|
||||||
FreehandlSliderToolboxGroup
|
FreehandlSliderToolboxGroup
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updatePositionSnapGrid()
|
||||||
|
{
|
||||||
|
if (positionSnapGrid != null)
|
||||||
|
LayerBelowRuleset.Remove(positionSnapGrid, true);
|
||||||
|
|
||||||
|
var rectangularPositionSnapGrid = new RectangularPositionSnapGrid();
|
||||||
|
|
||||||
|
rectangularPositionSnapGrid.StartPosition.BindTo(OsuGridToolboxGroup.StartPosition);
|
||||||
|
rectangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.SpacingVector);
|
||||||
|
rectangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation);
|
||||||
|
|
||||||
|
positionSnapGrid = rectangularPositionSnapGrid;
|
||||||
|
|
||||||
|
positionSnapGrid.RelativeSizeAxes = Axes.Both;
|
||||||
|
LayerBelowRuleset.Add(positionSnapGrid);
|
||||||
|
}
|
||||||
|
|
||||||
protected override ComposeBlueprintContainer CreateBlueprintContainer()
|
protected override ComposeBlueprintContainer CreateBlueprintContainer()
|
||||||
=> new OsuBlueprintContainer(this);
|
=> new OsuBlueprintContainer(this);
|
||||||
|
|
||||||
@ -147,7 +171,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
private readonly Cached distanceSnapGridCache = new Cached();
|
private readonly Cached distanceSnapGridCache = new Cached();
|
||||||
private double? lastDistanceSnapGridTime;
|
private double? lastDistanceSnapGridTime;
|
||||||
|
|
||||||
private RectangularPositionSnapGrid rectangularPositionSnapGrid;
|
private PositionSnapGrid positionSnapGrid;
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
@ -205,9 +229,13 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
if (rectangularGridSnapToggle.Value == TernaryState.True)
|
if (rectangularGridSnapToggle.Value == TernaryState.True)
|
||||||
{
|
{
|
||||||
Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(result.ScreenSpacePosition));
|
Vector2 pos = positionSnapGrid.GetSnappedPosition(positionSnapGrid.ToLocalSpace(result.ScreenSpacePosition));
|
||||||
|
|
||||||
result.ScreenSpacePosition = rectangularPositionSnapGrid.ToScreenSpace(pos);
|
// A grid which doesn't perfectly fit the playfield can produce a position that is outside of the playfield.
|
||||||
|
// We need to clamp the position to the playfield bounds to ensure that the snapped position is always in bounds.
|
||||||
|
pos = Vector2.Clamp(pos, Vector2.Zero, OsuPlayfield.BASE_SIZE);
|
||||||
|
|
||||||
|
result.ScreenSpacePosition = positionSnapGrid.ToScreenSpace(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,69 +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.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Input.Bindings;
|
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Game.Input.Bindings;
|
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
|
||||||
using osu.Game.Screens.Edit;
|
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit
|
|
||||||
{
|
|
||||||
public partial class OsuRectangularPositionSnapGrid : RectangularPositionSnapGrid, IKeyBindingHandler<GlobalAction>
|
|
||||||
{
|
|
||||||
private static readonly int[] grid_sizes = { 4, 8, 16, 32 };
|
|
||||||
|
|
||||||
private int currentGridSizeIndex = grid_sizes.Length - 1;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
|
||||||
|
|
||||||
public OsuRectangularPositionSnapGrid()
|
|
||||||
: base(OsuPlayfield.BASE_SIZE / 2)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
int gridSizeIndex = Array.IndexOf(grid_sizes, editorBeatmap.BeatmapInfo.GridSize);
|
|
||||||
if (gridSizeIndex >= 0)
|
|
||||||
currentGridSizeIndex = gridSizeIndex;
|
|
||||||
updateSpacing();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void nextGridSize()
|
|
||||||
{
|
|
||||||
currentGridSizeIndex = (currentGridSizeIndex + 1) % grid_sizes.Length;
|
|
||||||
updateSpacing();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSpacing()
|
|
||||||
{
|
|
||||||
int gridSize = grid_sizes[currentGridSizeIndex];
|
|
||||||
|
|
||||||
editorBeatmap.BeatmapInfo.GridSize = gridSize;
|
|
||||||
Spacing = new Vector2(gridSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
|
||||||
{
|
|
||||||
switch (e.Action)
|
|
||||||
{
|
|
||||||
case GlobalAction.EditorCycleGridDisplayMode:
|
|
||||||
nextGridSize();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -41,8 +41,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
private void updateState()
|
private void updateState()
|
||||||
{
|
{
|
||||||
var quad = GeometryUtils.GetSurroundingQuad(selectedMovableObjects);
|
var quad = GeometryUtils.GetSurroundingQuad(selectedMovableObjects);
|
||||||
CanRotateSelectionOrigin.Value = quad.Width > 0 || quad.Height > 0;
|
CanRotateAroundSelectionOrigin.Value = quad.Width > 0 || quad.Height > 0;
|
||||||
CanRotatePlayfieldOrigin.Value = selectedMovableObjects.Any();
|
CanRotateAroundPlayfieldOrigin.Value = selectedMovableObjects.Any();
|
||||||
}
|
}
|
||||||
|
|
||||||
private OsuHitObject[]? objectsInRotation;
|
private OsuHitObject[]? objectsInRotation;
|
||||||
|
@ -24,6 +24,16 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
public partial class OsuSelectionScaleHandler : SelectionScaleHandler
|
public partial class OsuSelectionScaleHandler : SelectionScaleHandler
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether scaling anchored by the center of the playfield can currently be performed.
|
||||||
|
/// </summary>
|
||||||
|
public Bindable<bool> CanScaleFromPlayfieldOrigin { get; private set; } = new BindableBool();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether a single slider is currently selected, which results in a different scaling behaviour.
|
||||||
|
/// </summary>
|
||||||
|
public Bindable<bool> IsScalingSlider { get; private set; } = new BindableBool();
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IEditorChangeHandler? changeHandler { get; set; }
|
private IEditorChangeHandler? changeHandler { get; set; }
|
||||||
|
|
||||||
@ -53,6 +63,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
CanScaleX.Value = quad.Width > 0;
|
CanScaleX.Value = quad.Width > 0;
|
||||||
CanScaleY.Value = quad.Height > 0;
|
CanScaleY.Value = quad.Height > 0;
|
||||||
CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value;
|
CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value;
|
||||||
|
CanScaleFromPlayfieldOrigin.Value = selectedMovableObjects.Any();
|
||||||
|
IsScalingSlider.Value = selectedMovableObjects.Count() == 1 && selectedMovableObjects.First() is Slider;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Dictionary<OsuHitObject, OriginalHitObjectState>? objectsInScale;
|
private Dictionary<OsuHitObject, OriginalHitObjectState>? objectsInScale;
|
||||||
@ -67,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
objectsInScale = selectedMovableObjects.ToDictionary(ho => ho, ho => new OriginalHitObjectState(ho));
|
objectsInScale = selectedMovableObjects.ToDictionary(ho => ho, ho => new OriginalHitObjectState(ho));
|
||||||
OriginalSurroundingQuad = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider
|
OriginalSurroundingQuad = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider
|
||||||
? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position))
|
? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => slider.Position + p.Position))
|
||||||
: GeometryUtils.GetSurroundingQuad(objectsInScale.Keys);
|
: GeometryUtils.GetSurroundingQuad(objectsInScale.Keys);
|
||||||
defaultOrigin = OriginalSurroundingQuad.Value.Centre;
|
defaultOrigin = OriginalSurroundingQuad.Value.Centre;
|
||||||
}
|
}
|
||||||
@ -92,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
scale = getClampedScale(OriginalSurroundingQuad.Value, actualOrigin, scale);
|
scale = ClampScaleToPlayfieldBounds(scale, actualOrigin);
|
||||||
|
|
||||||
foreach (var (ho, originalState) in objectsInScale)
|
foreach (var (ho, originalState) in objectsInScale)
|
||||||
{
|
{
|
||||||
@ -158,27 +170,36 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clamp scale for multi-object-scaling where selection does not exceed playfield bounds or flip.
|
/// Clamp scale for multi-object-scaling where selection does not exceed playfield bounds or flip.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="selectionQuad">The quad surrounding the hitobjects</param>
|
|
||||||
/// <param name="origin">The origin from which the scale operation is performed</param>
|
/// <param name="origin">The origin from which the scale operation is performed</param>
|
||||||
/// <param name="scale">The scale to be clamped</param>
|
/// <param name="scale">The scale to be clamped</param>
|
||||||
/// <returns>The clamped scale vector</returns>
|
/// <returns>The clamped scale vector</returns>
|
||||||
private Vector2 getClampedScale(Quad selectionQuad, Vector2 origin, Vector2 scale)
|
public Vector2 ClampScaleToPlayfieldBounds(Vector2 scale, Vector2? origin = null)
|
||||||
{
|
{
|
||||||
//todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead.
|
//todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead.
|
||||||
|
if (objectsInScale == null)
|
||||||
|
return scale;
|
||||||
|
|
||||||
var tl1 = Vector2.Divide(-origin, selectionQuad.TopLeft - origin);
|
Debug.Assert(defaultOrigin != null && OriginalSurroundingQuad != null);
|
||||||
var tl2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - origin, selectionQuad.TopLeft - origin);
|
|
||||||
var br1 = Vector2.Divide(-origin, selectionQuad.BottomRight - origin);
|
|
||||||
var br2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - origin, selectionQuad.BottomRight - origin);
|
|
||||||
|
|
||||||
if (!Precision.AlmostEquals(selectionQuad.TopLeft.X - origin.X, 0))
|
if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider)
|
||||||
scale.X = selectionQuad.TopLeft.X - origin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X);
|
origin = slider.Position;
|
||||||
if (!Precision.AlmostEquals(selectionQuad.TopLeft.Y - origin.Y, 0))
|
|
||||||
scale.Y = selectionQuad.TopLeft.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, tl2.Y, tl1.Y) : MathHelper.Clamp(scale.Y, tl1.Y, tl2.Y);
|
Vector2 actualOrigin = origin ?? defaultOrigin.Value;
|
||||||
if (!Precision.AlmostEquals(selectionQuad.BottomRight.X - origin.X, 0))
|
var selectionQuad = OriginalSurroundingQuad.Value;
|
||||||
scale.X = selectionQuad.BottomRight.X - origin.X < 0 ? MathHelper.Clamp(scale.X, br2.X, br1.X) : MathHelper.Clamp(scale.X, br1.X, br2.X);
|
|
||||||
if (!Precision.AlmostEquals(selectionQuad.BottomRight.Y - origin.Y, 0))
|
var tl1 = Vector2.Divide(-actualOrigin, selectionQuad.TopLeft - actualOrigin);
|
||||||
scale.Y = selectionQuad.BottomRight.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y);
|
var tl2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - actualOrigin, selectionQuad.TopLeft - actualOrigin);
|
||||||
|
var br1 = Vector2.Divide(-actualOrigin, selectionQuad.BottomRight - actualOrigin);
|
||||||
|
var br2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - actualOrigin, selectionQuad.BottomRight - actualOrigin);
|
||||||
|
|
||||||
|
if (!Precision.AlmostEquals(selectionQuad.TopLeft.X - actualOrigin.X, 0))
|
||||||
|
scale.X = selectionQuad.TopLeft.X - actualOrigin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X);
|
||||||
|
if (!Precision.AlmostEquals(selectionQuad.TopLeft.Y - actualOrigin.Y, 0))
|
||||||
|
scale.Y = selectionQuad.TopLeft.Y - actualOrigin.Y < 0 ? MathHelper.Clamp(scale.Y, tl2.Y, tl1.Y) : MathHelper.Clamp(scale.Y, tl1.Y, tl2.Y);
|
||||||
|
if (!Precision.AlmostEquals(selectionQuad.BottomRight.X - actualOrigin.X, 0))
|
||||||
|
scale.X = selectionQuad.BottomRight.X - actualOrigin.X < 0 ? MathHelper.Clamp(scale.X, br2.X, br1.X) : MathHelper.Clamp(scale.X, br1.X, br2.X);
|
||||||
|
if (!Precision.AlmostEquals(selectionQuad.BottomRight.Y - actualOrigin.Y, 0))
|
||||||
|
scale.Y = selectionQuad.BottomRight.Y - actualOrigin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y);
|
||||||
|
|
||||||
return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON));
|
return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON));
|
||||||
}
|
}
|
||||||
|
@ -78,11 +78,15 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
ScheduleAfterChildren(() => angleInput.TakeFocus());
|
ScheduleAfterChildren(() =>
|
||||||
|
{
|
||||||
|
angleInput.TakeFocus();
|
||||||
|
angleInput.SelectAll();
|
||||||
|
});
|
||||||
angleInput.Current.BindValueChanged(angle => rotationInfo.Value = rotationInfo.Value with { Degrees = angle.NewValue });
|
angleInput.Current.BindValueChanged(angle => rotationInfo.Value = rotationInfo.Value with { Degrees = angle.NewValue });
|
||||||
rotationOrigin.Items.First().Select();
|
rotationOrigin.Items.First().Select();
|
||||||
|
|
||||||
rotationHandler.CanRotateSelectionOrigin.BindValueChanged(e =>
|
rotationHandler.CanRotateAroundSelectionOrigin.BindValueChanged(e =>
|
||||||
{
|
{
|
||||||
selectionCentreButton.Selected.Disabled = !e.NewValue;
|
selectionCentreButton.Selected.Disabled = !e.NewValue;
|
||||||
}, true);
|
}, true);
|
||||||
|
212
osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
Normal file
212
osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Screens.Edit.Components.RadioButtons;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
|
{
|
||||||
|
public partial class PreciseScalePopover : OsuPopover
|
||||||
|
{
|
||||||
|
private readonly OsuSelectionScaleHandler scaleHandler;
|
||||||
|
|
||||||
|
private readonly Bindable<PreciseScaleInfo> scaleInfo = new Bindable<PreciseScaleInfo>(new PreciseScaleInfo(1, ScaleOrigin.PlayfieldCentre, true, true));
|
||||||
|
|
||||||
|
private SliderWithTextBoxInput<float> scaleInput = null!;
|
||||||
|
private BindableNumber<float> scaleInputBindable = null!;
|
||||||
|
private EditorRadioButtonCollection scaleOrigin = null!;
|
||||||
|
|
||||||
|
private RadioButton playfieldCentreButton = null!;
|
||||||
|
private RadioButton selectionCentreButton = null!;
|
||||||
|
|
||||||
|
private OsuCheckbox xCheckBox = null!;
|
||||||
|
private OsuCheckbox yCheckBox = null!;
|
||||||
|
|
||||||
|
public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler)
|
||||||
|
{
|
||||||
|
this.scaleHandler = scaleHandler;
|
||||||
|
|
||||||
|
AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight };
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Width = 220,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(20),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
scaleInput = new SliderWithTextBoxInput<float>("Scale:")
|
||||||
|
{
|
||||||
|
Current = scaleInputBindable = new BindableNumber<float>
|
||||||
|
{
|
||||||
|
MinValue = 0.5f,
|
||||||
|
MaxValue = 2,
|
||||||
|
Precision = 0.001f,
|
||||||
|
Value = 1,
|
||||||
|
Default = 1,
|
||||||
|
},
|
||||||
|
Instantaneous = true
|
||||||
|
},
|
||||||
|
scaleOrigin = new EditorRadioButtonCollection
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Items = new[]
|
||||||
|
{
|
||||||
|
playfieldCentreButton = new RadioButton("Playfield centre",
|
||||||
|
() => setOrigin(ScaleOrigin.PlayfieldCentre),
|
||||||
|
() => new SpriteIcon { Icon = FontAwesome.Regular.Square }),
|
||||||
|
selectionCentreButton = new RadioButton("Selection centre",
|
||||||
|
() => setOrigin(ScaleOrigin.SelectionCentre),
|
||||||
|
() => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(4),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
xCheckBox = new OsuCheckbox(false)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
LabelText = "X-axis",
|
||||||
|
Current = { Value = true },
|
||||||
|
},
|
||||||
|
yCheckBox = new OsuCheckbox(false)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
LabelText = "Y-axis",
|
||||||
|
Current = { Value = true },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
playfieldCentreButton.Selected.DisabledChanged += isDisabled =>
|
||||||
|
{
|
||||||
|
playfieldCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to playfield centre." : string.Empty;
|
||||||
|
};
|
||||||
|
selectionCentreButton.Selected.DisabledChanged += isDisabled =>
|
||||||
|
{
|
||||||
|
selectionCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to its centre." : string.Empty;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
ScheduleAfterChildren(() =>
|
||||||
|
{
|
||||||
|
scaleInput.TakeFocus();
|
||||||
|
scaleInput.SelectAll();
|
||||||
|
});
|
||||||
|
scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue });
|
||||||
|
|
||||||
|
xCheckBox.Current.BindValueChanged(x => setAxis(x.NewValue, yCheckBox.Current.Value));
|
||||||
|
yCheckBox.Current.BindValueChanged(y => setAxis(xCheckBox.Current.Value, y.NewValue));
|
||||||
|
|
||||||
|
selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value);
|
||||||
|
playfieldCentreButton.Selected.Disabled = scaleHandler.IsScalingSlider.Value && !selectionCentreButton.Selected.Disabled;
|
||||||
|
|
||||||
|
scaleOrigin.Items.First(b => !b.Selected.Disabled).Select();
|
||||||
|
|
||||||
|
scaleInfo.BindValueChanged(scale =>
|
||||||
|
{
|
||||||
|
var newScale = new Vector2(scale.NewValue.XAxis ? scale.NewValue.Scale : 1, scale.NewValue.YAxis ? scale.NewValue.Scale : 1);
|
||||||
|
scaleHandler.Update(newScale, getOriginPosition(scale.NewValue));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAxisCheckBoxesEnabled()
|
||||||
|
{
|
||||||
|
if (scaleInfo.Value.Origin == ScaleOrigin.PlayfieldCentre)
|
||||||
|
{
|
||||||
|
toggleAxisAvailable(xCheckBox.Current, true);
|
||||||
|
toggleAxisAvailable(yCheckBox.Current, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
toggleAxisAvailable(xCheckBox.Current, scaleHandler.CanScaleX.Value);
|
||||||
|
toggleAxisAvailable(yCheckBox.Current, scaleHandler.CanScaleY.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleAxisAvailable(Bindable<bool> axisBindable, bool available)
|
||||||
|
{
|
||||||
|
// enable the bindable to allow setting the value
|
||||||
|
axisBindable.Disabled = false;
|
||||||
|
// restore the presumed default value given the axis's new availability state
|
||||||
|
axisBindable.Value = available;
|
||||||
|
axisBindable.Disabled = !available;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateMaxScale()
|
||||||
|
{
|
||||||
|
if (!scaleHandler.OriginalSurroundingQuad.HasValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const float max_scale = 10;
|
||||||
|
var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value));
|
||||||
|
|
||||||
|
if (!scaleInfo.Value.XAxis)
|
||||||
|
scale.X = max_scale;
|
||||||
|
if (!scaleInfo.Value.YAxis)
|
||||||
|
scale.Y = max_scale;
|
||||||
|
|
||||||
|
scaleInputBindable.MaxValue = MathF.Max(1, MathF.Min(scale.X, scale.Y));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setOrigin(ScaleOrigin origin)
|
||||||
|
{
|
||||||
|
scaleInfo.Value = scaleInfo.Value with { Origin = origin };
|
||||||
|
updateMaxScale();
|
||||||
|
updateAxisCheckBoxesEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null;
|
||||||
|
|
||||||
|
private void setAxis(bool x, bool y)
|
||||||
|
{
|
||||||
|
scaleInfo.Value = scaleInfo.Value with { XAxis = x, YAxis = y };
|
||||||
|
updateMaxScale();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopIn()
|
||||||
|
{
|
||||||
|
base.PopIn();
|
||||||
|
scaleHandler.Begin();
|
||||||
|
updateMaxScale();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopOut()
|
||||||
|
{
|
||||||
|
base.PopOut();
|
||||||
|
|
||||||
|
if (IsLoaded) scaleHandler.Commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ScaleOrigin
|
||||||
|
{
|
||||||
|
PlayfieldCentre,
|
||||||
|
SelectionCentre
|
||||||
|
}
|
||||||
|
|
||||||
|
public record PreciseScaleInfo(float Scale, ScaleOrigin Origin, bool XAxis, bool YAxis);
|
||||||
|
}
|
@ -18,14 +18,14 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
public partial class TransformToolboxGroup : EditorToolboxGroup, IKeyBindingHandler<GlobalAction>
|
public partial class TransformToolboxGroup : EditorToolboxGroup, IKeyBindingHandler<GlobalAction>
|
||||||
{
|
{
|
||||||
private readonly Bindable<bool> canRotate = new BindableBool();
|
private readonly AggregateBindable<bool> canRotate = new AggregateBindable<bool>((x, y) => x || y);
|
||||||
|
private readonly AggregateBindable<bool> canScale = new AggregateBindable<bool>((x, y) => x || y);
|
||||||
|
|
||||||
private EditorToolButton rotateButton = null!;
|
private EditorToolButton rotateButton = null!;
|
||||||
|
private EditorToolButton scaleButton = null!;
|
||||||
private Bindable<bool> canRotatePlayfieldOrigin = null!;
|
|
||||||
private Bindable<bool> canRotateSelectionOrigin = null!;
|
|
||||||
|
|
||||||
public SelectionRotationHandler RotationHandler { get; init; } = null!;
|
public SelectionRotationHandler RotationHandler { get; init; } = null!;
|
||||||
|
public OsuSelectionScaleHandler ScaleHandler { get; init; } = null!;
|
||||||
|
|
||||||
public TransformToolboxGroup()
|
public TransformToolboxGroup()
|
||||||
: base("transform")
|
: base("transform")
|
||||||
@ -45,7 +45,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
rotateButton = new EditorToolButton("Rotate",
|
rotateButton = new EditorToolButton("Rotate",
|
||||||
() => new SpriteIcon { Icon = FontAwesome.Solid.Undo },
|
() => new SpriteIcon { Icon = FontAwesome.Solid.Undo },
|
||||||
() => new PreciseRotationPopover(RotationHandler)),
|
() => new PreciseRotationPopover(RotationHandler)),
|
||||||
// TODO: scale
|
scaleButton = new EditorToolButton("Scale",
|
||||||
|
() => new SpriteIcon { Icon = FontAwesome.Solid.ArrowsAlt },
|
||||||
|
() => new PreciseScalePopover(ScaleHandler))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -54,21 +56,17 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
// aggregate two values into canRotate
|
canRotate.AddSource(RotationHandler.CanRotateAroundPlayfieldOrigin);
|
||||||
canRotatePlayfieldOrigin = RotationHandler.CanRotatePlayfieldOrigin.GetBoundCopy();
|
canRotate.AddSource(RotationHandler.CanRotateAroundSelectionOrigin);
|
||||||
canRotatePlayfieldOrigin.BindValueChanged(_ => updateCanRotateAggregate());
|
|
||||||
|
|
||||||
canRotateSelectionOrigin = RotationHandler.CanRotateSelectionOrigin.GetBoundCopy();
|
canScale.AddSource(ScaleHandler.CanScaleX);
|
||||||
canRotateSelectionOrigin.BindValueChanged(_ => updateCanRotateAggregate());
|
canScale.AddSource(ScaleHandler.CanScaleY);
|
||||||
|
canScale.AddSource(ScaleHandler.CanScaleFromPlayfieldOrigin);
|
||||||
void updateCanRotateAggregate()
|
|
||||||
{
|
|
||||||
canRotate.Value = RotationHandler.CanRotatePlayfieldOrigin.Value || RotationHandler.CanRotateSelectionOrigin.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// bindings to `Enabled` on the buttons are decoupled on purpose
|
// bindings to `Enabled` on the buttons are decoupled on purpose
|
||||||
// due to the weird `OsuButton` behaviour of resetting `Enabled` to `false` when `Action` is set.
|
// due to the weird `OsuButton` behaviour of resetting `Enabled` to `false` when `Action` is set.
|
||||||
canRotate.BindValueChanged(_ => rotateButton.Enabled.Value = canRotate.Value, true);
|
canRotate.Result.BindValueChanged(rotate => rotateButton.Enabled.Value = rotate.NewValue, true);
|
||||||
|
canScale.Result.BindValueChanged(scale => scaleButton.Enabled.Value = scale.NewValue, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||||
@ -82,6 +80,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
rotateButton.TriggerClick();
|
rotateButton.TriggerClick();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case GlobalAction.EditorToggleScaleControl:
|
||||||
|
{
|
||||||
|
scaleButton.TriggerClick();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -67,8 +67,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
// Generate the replay frames the cursor should follow
|
// Generate the replay frames the cursor should follow
|
||||||
replayFrames = new OsuAutoGenerator(drawableRuleset.Beatmap, drawableRuleset.Mods).Generate().Frames.Cast<OsuReplayFrame>().ToList();
|
replayFrames = new OsuAutoGenerator(drawableRuleset.Beatmap, drawableRuleset.Mods).Generate().Frames.Cast<OsuReplayFrame>().ToList();
|
||||||
|
|
||||||
drawableRuleset.UseResumeOverlay = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,9 +47,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||||
{
|
{
|
||||||
// Hide judgment displays and follow points as they won't make any sense.
|
// Hide follow points as they won't make any sense.
|
||||||
// Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart.
|
// Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart.
|
||||||
drawableRuleset.Playfield.DisplayJudgements.Value = false;
|
|
||||||
(drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide();
|
(drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,9 +39,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||||
{
|
{
|
||||||
// Hide judgment displays and follow points as they won't make any sense.
|
// Hide follow points as they won't make any sense.
|
||||||
// Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart.
|
// Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart.
|
||||||
drawableRuleset.Playfield.DisplayJudgements.Value = false;
|
|
||||||
(drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide();
|
(drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,9 +38,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||||
{
|
{
|
||||||
// Hide judgment displays and follow points as they won't make any sense.
|
// Hide follow points as they won't make any sense.
|
||||||
// Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart.
|
// Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart.
|
||||||
drawableRuleset.Playfield.DisplayJudgements.Value = false;
|
|
||||||
(drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide();
|
(drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
@ -14,10 +12,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
public partial class DrawableOsuJudgement : DrawableJudgement
|
public partial class DrawableOsuJudgement : DrawableJudgement
|
||||||
{
|
{
|
||||||
internal SkinnableLighting Lighting { get; private set; }
|
internal SkinnableLighting Lighting { get; private set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuConfigManager config { get; set; }
|
private OsuConfigManager config { get; set; } = null!;
|
||||||
|
|
||||||
|
private bool positionTransferred;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
@ -39,10 +39,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
Lighting.ResetAnimation();
|
Lighting.ResetAnimation();
|
||||||
Lighting.SetColourFrom(JudgedObject, Result);
|
Lighting.SetColourFrom(JudgedObject, Result);
|
||||||
|
|
||||||
if (JudgedObject?.HitObject is OsuHitObject osuObject)
|
positionTransferred = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (!positionTransferred && JudgedObject is DrawableOsuHitObject osuObject && JudgedObject.IsInUse)
|
||||||
{
|
{
|
||||||
Position = osuObject.StackedEndPosition;
|
Position = osuObject.ToSpaceOfOtherDrawable(osuObject.OriginPosition, Parent!);
|
||||||
Scale = new Vector2(osuObject.Scale);
|
Scale = new Vector2(osuObject.HitObject.Scale);
|
||||||
|
|
||||||
|
positionTransferred = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,9 +10,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
using osu.Game.Screens.Play;
|
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -63,22 +61,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
base.ApplyTransformsAt(time, false);
|
base.ApplyTransformsAt(time, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector2? lastPosition;
|
|
||||||
|
|
||||||
public void UpdateProgress(double completionProgress)
|
public void UpdateProgress(double completionProgress)
|
||||||
{
|
{
|
||||||
Position = drawableSlider.HitObject.CurvePositionAt(completionProgress);
|
Slider slider = drawableSlider.HitObject;
|
||||||
|
Position = slider.CurvePositionAt(completionProgress);
|
||||||
|
|
||||||
var diff = lastPosition.HasValue ? lastPosition.Value - Position : Position - drawableSlider.HitObject.CurvePositionAt(completionProgress + 0.01f);
|
//0.1 / slider.Path.Distance is the additional progress needed to ensure the diff length is 0.1
|
||||||
|
var diff = slider.CurvePositionAt(completionProgress) - slider.CurvePositionAt(Math.Min(1, completionProgress + 0.1 / slider.Path.Distance));
|
||||||
bool rewinding = (Clock as IGameplayClock)?.IsRewinding == true;
|
|
||||||
|
|
||||||
// Ensure the value is substantially high enough to allow for Atan2 to get a valid angle.
|
// Ensure the value is substantially high enough to allow for Atan2 to get a valid angle.
|
||||||
|
// Needed for when near completion, or in case of a very short slider.
|
||||||
if (diff.LengthFast < 0.01f)
|
if (diff.LengthFast < 0.01f)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI) + (rewinding ? 180 : 0);
|
ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI);
|
||||||
lastPosition = Position;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ using osu.Game.Replays;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Configuration;
|
using osu.Game.Rulesets.Osu.Configuration;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Replays;
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
@ -45,7 +46,13 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
|
|
||||||
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer { AlignWithStoryboard = true };
|
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer { AlignWithStoryboard = true };
|
||||||
|
|
||||||
protected override ResumeOverlay CreateResumeOverlay() => new OsuResumeOverlay();
|
protected override ResumeOverlay CreateResumeOverlay()
|
||||||
|
{
|
||||||
|
if (Mods.Any(m => m is OsuModAutopilot))
|
||||||
|
return new DelayedResumeOverlay { Scale = new Vector2(0.65f) };
|
||||||
|
|
||||||
|
return new OsuResumeOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuFramedReplayInputHandler(replay);
|
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuFramedReplayInputHandler(replay);
|
||||||
|
|
||||||
|
@ -53,6 +53,9 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
|
|
||||||
public void SetStrongState(bool state)
|
public void SetStrongState(bool state)
|
||||||
{
|
{
|
||||||
|
if (SelectedItems.OfType<Hit>().All(h => h.IsStrong == state))
|
||||||
|
return;
|
||||||
|
|
||||||
EditorBeatmap.PerformOnSelection(h =>
|
EditorBeatmap.PerformOnSelection(h =>
|
||||||
{
|
{
|
||||||
if (!(h is Hit taikoHit)) return;
|
if (!(h is Hit taikoHit)) return;
|
||||||
@ -67,6 +70,9 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
|
|
||||||
public void SetRimState(bool state)
|
public void SetRimState(bool state)
|
||||||
{
|
{
|
||||||
|
if (SelectedItems.OfType<Hit>().All(h => h.Type == (state ? HitType.Rim : HitType.Centre)))
|
||||||
|
return;
|
||||||
|
|
||||||
EditorBeatmap.PerformOnSelection(h =>
|
EditorBeatmap.PerformOnSelection(h =>
|
||||||
{
|
{
|
||||||
if (h is Hit taikoHit)
|
if (h is Hit taikoHit)
|
||||||
|
@ -33,6 +33,7 @@ using osu.Game.Scoring;
|
|||||||
using osu.Game.Scoring.Legacy;
|
using osu.Game.Scoring.Legacy;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Beatmaps.Formats
|
namespace osu.Game.Tests.Beatmaps.Formats
|
||||||
{
|
{
|
||||||
@ -126,17 +127,20 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
[TestCase(LegacyBeatmapDecoder.LATEST_VERSION, false)]
|
[TestCase(LegacyBeatmapDecoder.LATEST_VERSION, false)]
|
||||||
public void TestLegacyBeatmapReplayOffsetsDecode(int beatmapVersion, bool offsetApplied)
|
public void TestLegacyBeatmapReplayOffsetsDecode(int beatmapVersion, bool offsetApplied)
|
||||||
{
|
{
|
||||||
const double first_frame_time = 48;
|
const double first_frame_time = 31;
|
||||||
const double second_frame_time = 65;
|
const double second_frame_time = 48;
|
||||||
|
const double third_frame_time = 65;
|
||||||
|
|
||||||
var decoder = new TestLegacyScoreDecoder(beatmapVersion);
|
var decoder = new TestLegacyScoreDecoder(beatmapVersion);
|
||||||
|
|
||||||
using (var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr"))
|
using (var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr"))
|
||||||
{
|
{
|
||||||
var score = decoder.Parse(resourceStream);
|
var score = decoder.Parse(resourceStream);
|
||||||
|
int offset = offsetApplied ? LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0;
|
||||||
|
|
||||||
Assert.That(score.Replay.Frames[0].Time, Is.EqualTo(first_frame_time + (offsetApplied ? LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0)));
|
Assert.That(score.Replay.Frames[0].Time, Is.EqualTo(first_frame_time + offset));
|
||||||
Assert.That(score.Replay.Frames[1].Time, Is.EqualTo(second_frame_time + (offsetApplied ? LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0)));
|
Assert.That(score.Replay.Frames[1].Time, Is.EqualTo(second_frame_time + offset));
|
||||||
|
Assert.That(score.Replay.Frames[2].Time, Is.EqualTo(third_frame_time + offset));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,6 +181,94 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
Assert.That(decodedAfterEncode.Replay.Frames[1].Time, Is.EqualTo(second_frame_time));
|
Assert.That(decodedAfterEncode.Replay.Frames[1].Time, Is.EqualTo(second_frame_time));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNegativeFrameSkipped()
|
||||||
|
{
|
||||||
|
var ruleset = new OsuRuleset().RulesetInfo;
|
||||||
|
var scoreInfo = TestResources.CreateTestScoreInfo(ruleset);
|
||||||
|
var beatmap = new TestBeatmap(ruleset);
|
||||||
|
|
||||||
|
var score = new Score
|
||||||
|
{
|
||||||
|
ScoreInfo = scoreInfo,
|
||||||
|
Replay = new Replay
|
||||||
|
{
|
||||||
|
Frames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame(0, new Vector2()),
|
||||||
|
new OsuReplayFrame(1000, OsuPlayfield.BASE_SIZE),
|
||||||
|
new OsuReplayFrame(500, OsuPlayfield.BASE_SIZE / 2),
|
||||||
|
new OsuReplayFrame(2000, OsuPlayfield.BASE_SIZE),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var decodedAfterEncode = encodeThenDecode(LegacyScoreEncoder.LATEST_VERSION, score, beatmap);
|
||||||
|
|
||||||
|
Assert.That(decodedAfterEncode.Replay.Frames, Has.Count.EqualTo(3));
|
||||||
|
Assert.That(decodedAfterEncode.Replay.Frames[0].Time, Is.EqualTo(0));
|
||||||
|
Assert.That(decodedAfterEncode.Replay.Frames[1].Time, Is.EqualTo(1000));
|
||||||
|
Assert.That(decodedAfterEncode.Replay.Frames[2].Time, Is.EqualTo(2000));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void FirstTwoFramesSwappedIfInWrongOrder()
|
||||||
|
{
|
||||||
|
var ruleset = new OsuRuleset().RulesetInfo;
|
||||||
|
var scoreInfo = TestResources.CreateTestScoreInfo(ruleset);
|
||||||
|
var beatmap = new TestBeatmap(ruleset);
|
||||||
|
|
||||||
|
var score = new Score
|
||||||
|
{
|
||||||
|
ScoreInfo = scoreInfo,
|
||||||
|
Replay = new Replay
|
||||||
|
{
|
||||||
|
Frames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame(100, new Vector2()),
|
||||||
|
new OsuReplayFrame(50, OsuPlayfield.BASE_SIZE / 2),
|
||||||
|
new OsuReplayFrame(1000, OsuPlayfield.BASE_SIZE),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var decodedAfterEncode = encodeThenDecode(LegacyScoreEncoder.LATEST_VERSION, score, beatmap);
|
||||||
|
|
||||||
|
Assert.That(decodedAfterEncode.Replay.Frames, Has.Count.EqualTo(3));
|
||||||
|
Assert.That(decodedAfterEncode.Replay.Frames[0].Time, Is.EqualTo(0));
|
||||||
|
Assert.That(decodedAfterEncode.Replay.Frames[1].Time, Is.EqualTo(100));
|
||||||
|
Assert.That(decodedAfterEncode.Replay.Frames[2].Time, Is.EqualTo(1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void FirstTwoFramesPulledTowardThirdIfTheyAreAfterIt()
|
||||||
|
{
|
||||||
|
var ruleset = new OsuRuleset().RulesetInfo;
|
||||||
|
var scoreInfo = TestResources.CreateTestScoreInfo(ruleset);
|
||||||
|
var beatmap = new TestBeatmap(ruleset);
|
||||||
|
|
||||||
|
var score = new Score
|
||||||
|
{
|
||||||
|
ScoreInfo = scoreInfo,
|
||||||
|
Replay = new Replay
|
||||||
|
{
|
||||||
|
Frames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame(0, new Vector2()),
|
||||||
|
new OsuReplayFrame(500, OsuPlayfield.BASE_SIZE / 2),
|
||||||
|
new OsuReplayFrame(-1500, OsuPlayfield.BASE_SIZE),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var decodedAfterEncode = encodeThenDecode(LegacyScoreEncoder.LATEST_VERSION, score, beatmap);
|
||||||
|
|
||||||
|
Assert.That(decodedAfterEncode.Replay.Frames, Has.Count.EqualTo(3));
|
||||||
|
Assert.That(decodedAfterEncode.Replay.Frames[0].Time, Is.EqualTo(-1500));
|
||||||
|
Assert.That(decodedAfterEncode.Replay.Frames[1].Time, Is.EqualTo(-1500));
|
||||||
|
Assert.That(decodedAfterEncode.Replay.Frames[2].Time, Is.EqualTo(-1500));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCultureInvariance()
|
public void TestCultureInvariance()
|
||||||
{
|
{
|
||||||
|
@ -59,7 +59,7 @@ namespace osu.Game.Tests.Chat
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case ChatAckRequest ack:
|
case ChatAckRequest ack:
|
||||||
ack.TriggerSuccess(new ChatAckResponse { Silences = silencedUserIds.Select(u => new ChatSilence { UserId = u }).ToList() });
|
ack.TriggerSuccess(new ChatAckResponse { Silences = silencedUserIds.Select(u => new ChatSilence { UserId = u }).ToArray() });
|
||||||
silencedUserIds.Clear();
|
silencedUserIds.Clear();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
BIN
osu.Game.Tests/Resources/Archives/modified-classic-20230809.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/modified-classic-20230809.osk
Normal file
Binary file not shown.
BIN
osu.Game.Tests/Resources/Archives/modified-default-20230809.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/modified-default-20230809.osk
Normal file
Binary file not shown.
@ -62,6 +62,10 @@ namespace osu.Game.Tests.Skins
|
|||||||
"Archives/modified-argon-20231108.osk",
|
"Archives/modified-argon-20231108.osk",
|
||||||
// Covers "Argon" performance points counter
|
// Covers "Argon" performance points counter
|
||||||
"Archives/modified-argon-20240305.osk",
|
"Archives/modified-argon-20240305.osk",
|
||||||
|
// Covers default rank display
|
||||||
|
"Archives/modified-default-20230809.osk",
|
||||||
|
// Covers legacy rank display
|
||||||
|
"Archives/modified-classic-20230809.osk"
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
{
|
{
|
||||||
this.getTargetContainer = getTargetContainer;
|
this.getTargetContainer = getTargetContainer;
|
||||||
|
|
||||||
CanRotateSelectionOrigin.Value = true;
|
CanRotateAroundSelectionOrigin.Value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
|
@ -16,7 +16,7 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Editing
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
{
|
{
|
||||||
public partial class TestSceneRectangularPositionSnapGrid : OsuManualInputManagerTestScene
|
public partial class TestScenePositionSnapGrid : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
private Container content;
|
private Container content;
|
||||||
protected override Container<Drawable> Content => content;
|
protected override Container<Drawable> Content => content;
|
||||||
@ -33,28 +33,34 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
},
|
},
|
||||||
content = new Container
|
content = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding(10),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly object[][] test_cases =
|
private static readonly object[][] test_cases =
|
||||||
{
|
{
|
||||||
new object[] { new Vector2(0, 0), new Vector2(10, 10) },
|
new object[] { new Vector2(0, 0), new Vector2(10, 10), 0f },
|
||||||
new object[] { new Vector2(240, 180), new Vector2(10, 15) },
|
new object[] { new Vector2(240, 180), new Vector2(10, 15), 10f },
|
||||||
new object[] { new Vector2(160, 120), new Vector2(30, 20) },
|
new object[] { new Vector2(160, 120), new Vector2(30, 20), -10f },
|
||||||
new object[] { new Vector2(480, 360), new Vector2(100, 100) },
|
new object[] { new Vector2(480, 360), new Vector2(100, 100), 0f },
|
||||||
};
|
};
|
||||||
|
|
||||||
[TestCaseSource(nameof(test_cases))]
|
[TestCaseSource(nameof(test_cases))]
|
||||||
public void TestRectangularGrid(Vector2 position, Vector2 spacing)
|
public void TestRectangularGrid(Vector2 position, Vector2 spacing, float rotation)
|
||||||
{
|
{
|
||||||
RectangularPositionSnapGrid grid = null;
|
RectangularPositionSnapGrid grid = null;
|
||||||
|
|
||||||
AddStep("create grid", () => Child = grid = new RectangularPositionSnapGrid(position)
|
AddStep("create grid", () =>
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
Child = grid = new RectangularPositionSnapGrid
|
||||||
Spacing = spacing
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
};
|
||||||
|
grid.StartPosition.Value = position;
|
||||||
|
grid.Spacing.Value = spacing;
|
||||||
|
grid.GridLineRotation.Value = rotation;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("add snapping cursor", () => Add(new SnappingCursorContainer
|
AddStep("add snapping cursor", () => Add(new SnappingCursorContainer
|
@ -0,0 +1,41 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
|
{
|
||||||
|
public partial class TestSceneSkinnableRankDisplay : SkinnableHUDComponentTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset());
|
||||||
|
|
||||||
|
private Bindable<ScoreRank> rank => (Bindable<ScoreRank>)scoreProcessor.Rank;
|
||||||
|
|
||||||
|
protected override Drawable CreateDefaultImplementation() => new DefaultRankDisplay();
|
||||||
|
|
||||||
|
protected override Drawable CreateLegacyImplementation() => new LegacyRankDisplay();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestChangingRank()
|
||||||
|
{
|
||||||
|
AddStep("Set rank to SS Hidden", () => rank.Value = ScoreRank.XH);
|
||||||
|
AddStep("Set rank to SS", () => rank.Value = ScoreRank.X);
|
||||||
|
AddStep("Set rank to S Hidden", () => rank.Value = ScoreRank.SH);
|
||||||
|
AddStep("Set rank to S", () => rank.Value = ScoreRank.S);
|
||||||
|
AddStep("Set rank to A", () => rank.Value = ScoreRank.A);
|
||||||
|
AddStep("Set rank to B", () => rank.Value = ScoreRank.B);
|
||||||
|
AddStep("Set rank to C", () => rank.Value = ScoreRank.C);
|
||||||
|
AddStep("Set rank to D", () => rank.Value = ScoreRank.D);
|
||||||
|
AddStep("Set rank to F", () => rank.Value = ScoreRank.F);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -15,8 +16,14 @@ namespace osu.Game.Tests.Visual.Menus
|
|||||||
{
|
{
|
||||||
private OnlineMenuBanner onlineMenuBanner => Game.ChildrenOfType<OnlineMenuBanner>().Single();
|
private OnlineMenuBanner onlineMenuBanner => Game.ChildrenOfType<OnlineMenuBanner>().Single();
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
AddStep("don't fetch online content", () => onlineMenuBanner.FetchOnlineContent = false);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestOnlineMenuBanner()
|
public void TestOnlineMenuBannerTrusted()
|
||||||
{
|
{
|
||||||
AddStep("set online content", () => onlineMenuBanner.Current.Value = new APIMenuContent
|
AddStep("set online content", () => onlineMenuBanner.Current.Value = new APIMenuContent
|
||||||
{
|
{
|
||||||
@ -25,13 +32,51 @@ namespace osu.Game.Tests.Visual.Menus
|
|||||||
new APIMenuImage
|
new APIMenuImage
|
||||||
{
|
{
|
||||||
Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png",
|
Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png",
|
||||||
Url = @"https://osu.ppy.sh/home/news/2023-12-21-project-loved-december-2023",
|
Url = $@"{API.WebsiteRootUrl}/home/news/2023-12-21-project-loved-december-2023",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
AddAssert("system title not visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Hidden));
|
AddAssert("system title not visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||||
AddStep("enter menu", () => InputManager.Key(Key.Enter));
|
AddStep("enter menu", () => InputManager.Key(Key.Enter));
|
||||||
AddUntilStep("system title visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Visible));
|
AddUntilStep("system title visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||||
|
AddUntilStep("image loaded", () => onlineMenuBanner.ChildrenOfType<OnlineMenuBanner.MenuImage>().FirstOrDefault()?.IsLoaded, () => Is.True);
|
||||||
|
|
||||||
|
AddStep("click banner", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(onlineMenuBanner);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Might not catch every occurrence due to async nature, but works in manual testing and saves annoying test setup.
|
||||||
|
AddAssert("no dialog", () => Game.ChildrenOfType<DialogOverlay>().SingleOrDefault()?.CurrentDialog == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOnlineMenuBannerUntrustedDomain()
|
||||||
|
{
|
||||||
|
AddStep("set online content", () => onlineMenuBanner.Current.Value = new APIMenuContent
|
||||||
|
{
|
||||||
|
Images = new[]
|
||||||
|
{
|
||||||
|
new APIMenuImage
|
||||||
|
{
|
||||||
|
Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png",
|
||||||
|
Url = @"https://google.com",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
AddAssert("system title not visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||||
|
AddStep("enter menu", () => InputManager.Key(Key.Enter));
|
||||||
|
AddUntilStep("system title visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||||
|
AddUntilStep("image loaded", () => onlineMenuBanner.ChildrenOfType<OnlineMenuBanner.MenuImage>().FirstOrDefault()?.IsLoaded, () => Is.True);
|
||||||
|
|
||||||
|
AddStep("click banner", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(onlineMenuBanner);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for dialog", () => Game.ChildrenOfType<DialogOverlay>().SingleOrDefault()?.CurrentDialog != null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,13 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
@ -35,7 +37,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
|
|
||||||
if (bigButton)
|
if (bigButton)
|
||||||
{
|
{
|
||||||
Child = button = new ShearedButton(400)
|
Child = button = new ShearedButton(400, 80)
|
||||||
{
|
{
|
||||||
LighterColour = Colour4.FromHex("#FFFFFF"),
|
LighterColour = Colour4.FromHex("#FFFFFF"),
|
||||||
DarkerColour = Colour4.FromHex("#FFCC22"),
|
DarkerColour = Colour4.FromHex("#FFCC22"),
|
||||||
@ -44,13 +46,12 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Text = "Let's GO!",
|
Text = "Let's GO!",
|
||||||
Height = 80,
|
|
||||||
Action = () => actionFired = true,
|
Action = () => actionFired = true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Child = button = new ShearedButton(200)
|
Child = button = new ShearedButton(200, 80)
|
||||||
{
|
{
|
||||||
LighterColour = Colour4.FromHex("#FF86DD"),
|
LighterColour = Colour4.FromHex("#FF86DD"),
|
||||||
DarkerColour = Colour4.FromHex("#DE31AE"),
|
DarkerColour = Colour4.FromHex("#DE31AE"),
|
||||||
@ -58,7 +59,6 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Text = "Press me",
|
Text = "Press me",
|
||||||
Height = 80,
|
|
||||||
Action = () => actionFired = true,
|
Action = () => actionFired = true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -171,5 +171,48 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
|
|
||||||
void setToggleDisabledState(bool disabled) => AddStep($"{(disabled ? "disable" : "enable")} toggle", () => button.Active.Disabled = disabled);
|
void setToggleDisabledState(bool disabled) => AddStep($"{(disabled ? "disable" : "enable")} toggle", () => button.Active.Disabled = disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestButtons()
|
||||||
|
{
|
||||||
|
AddStep("create buttons", () => Children = new[]
|
||||||
|
{
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Scale = new Vector2(2.5f),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new ShearedButton(120)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Text = "Test",
|
||||||
|
Action = () => { },
|
||||||
|
Padding = new MarginPadding(),
|
||||||
|
},
|
||||||
|
new ShearedButton(120, 40)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Text = "Test",
|
||||||
|
Action = () => { },
|
||||||
|
Padding = new MarginPadding { Left = -1f },
|
||||||
|
},
|
||||||
|
new ShearedButton(120, 70)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Text = "Test",
|
||||||
|
Action = () => { },
|
||||||
|
Padding = new MarginPadding { Left = 3f },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,15 +51,16 @@ namespace osu.Game.Tournament.Screens.Editors
|
|||||||
|
|
||||||
AddInternal(rightClickMessage = new WarningBox("Right click to place and link matches"));
|
AddInternal(rightClickMessage = new WarningBox("Right click to place and link matches"));
|
||||||
|
|
||||||
ScrollContent.Add(grid = new RectangularPositionSnapGrid(Vector2.Zero)
|
ScrollContent.Add(grid = new RectangularPositionSnapGrid
|
||||||
{
|
{
|
||||||
Spacing = new Vector2(GRID_SPACING),
|
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
BypassAutoSizeAxes = Axes.Both,
|
BypassAutoSizeAxes = Axes.Both,
|
||||||
Depth = float.MaxValue
|
Depth = float.MaxValue
|
||||||
});
|
});
|
||||||
|
|
||||||
|
grid.Spacing.Value = new Vector2(GRID_SPACING);
|
||||||
|
|
||||||
LadderInfo.Matches.CollectionChanged += (_, _) => updateMessage();
|
LadderInfo.Matches.CollectionChanged += (_, _) => updateMessage();
|
||||||
updateMessage();
|
updateMessage();
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ using osu.Game.Graphics;
|
|||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Utils;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps.Drawables
|
namespace osu.Game.Beatmaps.Drawables
|
||||||
@ -124,12 +125,8 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
miscFillFlowContainer.Show();
|
miscFillFlowContainer.Show();
|
||||||
|
|
||||||
double rate = 1;
|
double rate = 1;
|
||||||
|
|
||||||
if (displayedContent.Mods != null)
|
if (displayedContent.Mods != null)
|
||||||
{
|
rate = ModUtils.CalculateRateWithMods(displayedContent.Mods);
|
||||||
foreach (var mod in displayedContent.Mods.OfType<IApplicableToRate>())
|
|
||||||
rate = mod.ApplyToRate(0, rate);
|
|
||||||
}
|
|
||||||
|
|
||||||
double bpmAdjusted = displayedContent.BeatmapInfo.BPM * rate;
|
double bpmAdjusted = displayedContent.BeatmapInfo.BPM * rate;
|
||||||
|
|
||||||
|
@ -26,9 +26,12 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
// Right mouse button is a special case where we allow actioning without dismissing the menu.
|
// Right mouse button is a special case where we allow actioning without dismissing the menu.
|
||||||
// This is achieved by not calling `Clicked` (as done by the base implementation in OnClick).
|
// This is achieved by not calling `Clicked` (as done by the base implementation in OnClick).
|
||||||
if (IsActionable && e.Button == MouseButton.Right)
|
if (IsActionable && e.Button == MouseButton.Right)
|
||||||
|
{
|
||||||
Item.Action.Value?.Invoke();
|
Item.Action.Value?.Invoke();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class ToggleTextContainer : TextContainer
|
private partial class ToggleTextContainer : TextContainer
|
||||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
public partial class ShearedButton : OsuClickableContainer
|
public partial class ShearedButton : OsuClickableContainer
|
||||||
{
|
{
|
||||||
public const float HEIGHT = 50;
|
public const float DEFAULT_HEIGHT = 50;
|
||||||
public const float CORNER_RADIUS = 7;
|
public const float CORNER_RADIUS = 7;
|
||||||
public const float BORDER_THICKNESS = 2;
|
public const float BORDER_THICKNESS = 2;
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
private readonly Box background;
|
private readonly Box background;
|
||||||
private readonly OsuSpriteText text;
|
private readonly OsuSpriteText text;
|
||||||
|
|
||||||
private const float shear = 0.2f;
|
private const float shear = OsuGame.SHEAR;
|
||||||
|
|
||||||
private Colour4? darkerColour;
|
private Colour4? darkerColour;
|
||||||
private Colour4? lighterColour;
|
private Colour4? lighterColour;
|
||||||
@ -75,6 +75,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
private readonly Container backgroundLayer;
|
private readonly Container backgroundLayer;
|
||||||
private readonly Box flashLayer;
|
private readonly Box flashLayer;
|
||||||
|
|
||||||
|
protected readonly Container ButtonContent;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="ShearedToggleButton"/>
|
/// Creates a new <see cref="ShearedToggleButton"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -85,10 +87,11 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
/// <item>If a <see langword="null"/> value is provided (or the argument is omitted entirely), the button will autosize in width to fit the text.</item>
|
/// <item>If a <see langword="null"/> value is provided (or the argument is omitted entirely), the button will autosize in width to fit the text.</item>
|
||||||
/// </list>
|
/// </list>
|
||||||
/// </param>
|
/// </param>
|
||||||
public ShearedButton(float? width = null)
|
/// <param name="height">The height of the button.</param>
|
||||||
|
public ShearedButton(float? width = null, float height = DEFAULT_HEIGHT)
|
||||||
{
|
{
|
||||||
Height = HEIGHT;
|
Height = height;
|
||||||
Padding = new MarginPadding { Horizontal = shear * 50 };
|
Padding = new MarginPadding { Horizontal = shear * height };
|
||||||
|
|
||||||
Content.CornerRadius = CORNER_RADIUS;
|
Content.CornerRadius = CORNER_RADIUS;
|
||||||
Content.Shear = new Vector2(shear, 0);
|
Content.Shear = new Vector2(shear, 0);
|
||||||
@ -109,12 +112,16 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
text = new OsuSpriteText
|
ButtonContent = new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Font = OsuFont.TorusAlternate.With(size: 17),
|
AutoSizeAxes = Axes.Both,
|
||||||
Shear = new Vector2(-shear, 0)
|
Shear = new Vector2(-shear, 0),
|
||||||
|
Child = text = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Font = OsuFont.TorusAlternate.With(size: 17),
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -188,7 +195,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
var colourDark = darkerColour ?? ColourProvider.Background3;
|
var colourDark = darkerColour ?? ColourProvider.Background3;
|
||||||
var colourLight = lighterColour ?? ColourProvider.Background1;
|
var colourLight = lighterColour ?? ColourProvider.Background1;
|
||||||
var colourText = textColour ?? ColourProvider.Content1;
|
var colourContent = textColour ?? ColourProvider.Content1;
|
||||||
|
|
||||||
if (!Enabled.Value)
|
if (!Enabled.Value)
|
||||||
{
|
{
|
||||||
@ -205,9 +212,9 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
backgroundLayer.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(colourDark, colourLight), 150, Easing.OutQuint);
|
backgroundLayer.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(colourDark, colourLight), 150, Easing.OutQuint);
|
||||||
|
|
||||||
if (!Enabled.Value)
|
if (!Enabled.Value)
|
||||||
colourText = colourText.Opacity(0.6f);
|
colourContent = colourContent.Opacity(0.6f);
|
||||||
|
|
||||||
text.FadeColour(colourText, 150, Easing.OutQuint);
|
ButtonContent.FadeColour(colourContent, 150, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ using osu.Framework.Graphics.UserInterface;
|
|||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Mods;
|
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -53,7 +52,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
public ShearedSearchTextBox()
|
public ShearedSearchTextBox()
|
||||||
{
|
{
|
||||||
Height = 42;
|
Height = 42;
|
||||||
Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0);
|
Shear = new Vector2(OsuGame.SHEAR, 0);
|
||||||
Masking = true;
|
Masking = true;
|
||||||
CornerRadius = corner_radius;
|
CornerRadius = corner_radius;
|
||||||
|
|
||||||
@ -116,7 +115,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
PlaceholderText = CommonStrings.InputSearch;
|
PlaceholderText = CommonStrings.InputSearch;
|
||||||
|
|
||||||
CornerRadius = corner_radius;
|
CornerRadius = corner_radius;
|
||||||
TextContainer.Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0);
|
TextContainer.Shear = new Vector2(-OsuGame.SHEAR, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SpriteText CreatePlaceholder() => new SearchPlaceholder();
|
protected override SpriteText CreatePlaceholder() => new SearchPlaceholder();
|
||||||
|
@ -50,6 +50,8 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
Component.BorderColour = colours.Blue;
|
Component.BorderColour = colours.Blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool SelectAll() => Component.SelectAll();
|
||||||
|
|
||||||
protected virtual OsuTextBox CreateTextBox() => new OsuTextBox();
|
protected virtual OsuTextBox CreateTextBox() => new OsuTextBox();
|
||||||
|
|
||||||
public override bool AcceptsFocus => true;
|
public override bool AcceptsFocus => true;
|
||||||
|
@ -87,6 +87,8 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
|
|
||||||
public bool TakeFocus() => GetContainingFocusManager().ChangeFocus(textBox);
|
public bool TakeFocus() => GetContainingFocusManager().ChangeFocus(textBox);
|
||||||
|
|
||||||
|
public bool SelectAll() => textBox.SelectAll();
|
||||||
|
|
||||||
private bool updatingFromTextBox;
|
private bool updatingFromTextBox;
|
||||||
|
|
||||||
private void textChanged(ValueChangedEvent<string> change)
|
private void textChanged(ValueChangedEvent<string> change)
|
||||||
|
@ -142,6 +142,7 @@ namespace osu.Game.Input.Bindings
|
|||||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelRight }, GlobalAction.EditorCyclePreviousBeatSnapDivisor),
|
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelRight }, GlobalAction.EditorCyclePreviousBeatSnapDivisor),
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelLeft }, GlobalAction.EditorCycleNextBeatSnapDivisor),
|
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelLeft }, GlobalAction.EditorCycleNextBeatSnapDivisor),
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.R }, GlobalAction.EditorToggleRotateControl),
|
new KeyBinding(new[] { InputKey.Control, InputKey.R }, GlobalAction.EditorToggleRotateControl),
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.E }, GlobalAction.EditorToggleScaleControl),
|
||||||
};
|
};
|
||||||
|
|
||||||
private static IEnumerable<KeyBinding> inGameKeyBindings => new[]
|
private static IEnumerable<KeyBinding> inGameKeyBindings => new[]
|
||||||
@ -411,6 +412,9 @@ namespace osu.Game.Input.Bindings
|
|||||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorToggleRotateControl))]
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorToggleRotateControl))]
|
||||||
EditorToggleRotateControl,
|
EditorToggleRotateControl,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorToggleScaleControl))]
|
||||||
|
EditorToggleScaleControl,
|
||||||
|
|
||||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseOffset))]
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseOffset))]
|
||||||
IncreaseOffset,
|
IncreaseOffset,
|
||||||
|
|
||||||
|
@ -5,14 +5,14 @@ using osu.Framework.Localisation;
|
|||||||
|
|
||||||
namespace osu.Game.Localisation
|
namespace osu.Game.Localisation
|
||||||
{
|
{
|
||||||
public static class DeleteConfirmationDialogStrings
|
public static class DialogStrings
|
||||||
{
|
{
|
||||||
private const string prefix = @"osu.Game.Resources.Localisation.DeleteConfirmationDialog";
|
private const string prefix = @"osu.Game.Resources.Localisation.Dialog";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Caution"
|
/// "Caution"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString HeaderText => new TranslatableString(getKey(@"header_text"), @"Caution");
|
public static LocalisableString Caution => new TranslatableString(getKey(@"header_text"), @"Caution");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Yes. Go for it."
|
/// "Yes. Go for it."
|
@ -369,6 +369,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString EditorToggleRotateControl => new TranslatableString(getKey(@"editor_toggle_rotate_control"), @"Toggle rotate control");
|
public static LocalisableString EditorToggleRotateControl => new TranslatableString(getKey(@"editor_toggle_rotate_control"), @"Toggle rotate control");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Toggle scale control"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString EditorToggleScaleControl => new TranslatableString(getKey(@"editor_toggle_scale_control"), @"Toggle scale control");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Increase mod speed"
|
/// "Increase mod speed"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -42,7 +42,11 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
public string WebsiteRootUrl { get; }
|
public string WebsiteRootUrl { get; }
|
||||||
|
|
||||||
public int APIVersion => 20220705; // We may want to pull this from the game version eventually.
|
/// <summary>
|
||||||
|
/// The API response version.
|
||||||
|
/// See: https://osu.ppy.sh/docs/index.html#api-versions
|
||||||
|
/// </summary>
|
||||||
|
public int APIVersion { get; }
|
||||||
|
|
||||||
public Exception LastLoginError { get; private set; }
|
public Exception LastLoginError { get; private set; }
|
||||||
|
|
||||||
@ -84,12 +88,23 @@ namespace osu.Game.Online.API
|
|||||||
this.config = config;
|
this.config = config;
|
||||||
this.versionHash = versionHash;
|
this.versionHash = versionHash;
|
||||||
|
|
||||||
|
if (game.IsDeployedBuild)
|
||||||
|
APIVersion = game.AssemblyVersion.Major * 10000 + game.AssemblyVersion.Minor;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var now = DateTimeOffset.Now;
|
||||||
|
APIVersion = now.Year * 10000 + now.Month * 100 + now.Day;
|
||||||
|
}
|
||||||
|
|
||||||
APIEndpointUrl = endpointConfiguration.APIEndpointUrl;
|
APIEndpointUrl = endpointConfiguration.APIEndpointUrl;
|
||||||
WebsiteRootUrl = endpointConfiguration.WebsiteRootUrl;
|
WebsiteRootUrl = endpointConfiguration.WebsiteRootUrl;
|
||||||
NotificationsClient = setUpNotificationsClient();
|
NotificationsClient = setUpNotificationsClient();
|
||||||
|
|
||||||
authentication = new OAuth(endpointConfiguration.APIClientID, endpointConfiguration.APIClientSecret, APIEndpointUrl);
|
authentication = new OAuth(endpointConfiguration.APIClientID, endpointConfiguration.APIClientSecret, APIEndpointUrl);
|
||||||
|
|
||||||
log = Logger.GetLogger(LoggingTarget.Network);
|
log = Logger.GetLogger(LoggingTarget.Network);
|
||||||
|
log.Add($@"API endpoint root: {APIEndpointUrl}");
|
||||||
|
log.Add($@"API request version: {APIVersion}");
|
||||||
|
|
||||||
ProvidedUsername = config.Get<string>(OsuSetting.Username);
|
ProvidedUsername = config.Get<string>(OsuSetting.Username);
|
||||||
|
|
||||||
|
@ -84,6 +84,13 @@ namespace osu.Game.Online.API
|
|||||||
{
|
{
|
||||||
if (HandleRequest?.Invoke(request) != true)
|
if (HandleRequest?.Invoke(request) != true)
|
||||||
{
|
{
|
||||||
|
// Noisy so let's silently allow these to succeed.
|
||||||
|
if (request is ChatAckRequest ack)
|
||||||
|
{
|
||||||
|
ack.TriggerSuccess(new ChatAckResponse());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
request.Fail(new InvalidOperationException($@"{nameof(DummyAPIAccess)} cannot process this request."));
|
request.Fail(new InvalidOperationException($@"{nameof(DummyAPIAccess)} cannot process this request."));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -61,7 +61,7 @@ namespace osu.Game.Online.API
|
|||||||
string APIEndpointUrl { get; }
|
string APIEndpointUrl { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The root URL of of the website, excluding the trailing slash.
|
/// The root URL of the website, excluding the trailing slash.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string WebsiteRootUrl { get; }
|
string WebsiteRootUrl { get; }
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace osu.Game.Online.API.Requests.Responses
|
namespace osu.Game.Online.API.Requests.Responses
|
||||||
@ -10,6 +10,6 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
public class ChatAckResponse
|
public class ChatAckResponse
|
||||||
{
|
{
|
||||||
[JsonProperty("silences")]
|
[JsonProperty("silences")]
|
||||||
public List<ChatSilence> Silences { get; set; } = null!;
|
public ChatSilence[] Silences { get; set; } = Array.Empty<ChatSilence>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,10 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Dialog;
|
using osu.Game.Overlays.Dialog;
|
||||||
|
using CommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings;
|
||||||
|
|
||||||
namespace osu.Game.Online.Chat
|
namespace osu.Game.Online.Chat
|
||||||
{
|
{
|
||||||
@ -44,8 +46,8 @@ namespace osu.Game.Online.Chat
|
|||||||
{
|
{
|
||||||
public ExternalLinkDialog(string url, Action openExternalLinkAction, Action copyExternalLinkAction)
|
public ExternalLinkDialog(string url, Action openExternalLinkAction, Action copyExternalLinkAction)
|
||||||
{
|
{
|
||||||
HeaderText = "Just checking...";
|
HeaderText = DialogStrings.Caution;
|
||||||
BodyText = $"You are about to leave osu! and open the following link in a web browser:\n\n{url}";
|
BodyText = $"Are you sure you want to open the following link in a web browser?\n\n{url}";
|
||||||
|
|
||||||
Icon = FontAwesome.Solid.ExclamationTriangle;
|
Icon = FontAwesome.Solid.ExclamationTriangle;
|
||||||
|
|
||||||
@ -53,17 +55,17 @@ namespace osu.Game.Online.Chat
|
|||||||
{
|
{
|
||||||
new PopupDialogOkButton
|
new PopupDialogOkButton
|
||||||
{
|
{
|
||||||
Text = @"Yes. Go for it.",
|
Text = @"Open in browser",
|
||||||
Action = openExternalLinkAction
|
Action = openExternalLinkAction
|
||||||
},
|
},
|
||||||
new PopupDialogCancelButton
|
new PopupDialogCancelButton
|
||||||
{
|
{
|
||||||
Text = @"Copy URL to the clipboard instead.",
|
Text = @"Copy URL to the clipboard",
|
||||||
Action = copyExternalLinkAction
|
Action = copyExternalLinkAction
|
||||||
},
|
},
|
||||||
new PopupDialogCancelButton
|
new PopupDialogCancelButton
|
||||||
{
|
{
|
||||||
Text = @"No! Abort mission!"
|
Text = CommonStrings.ButtonsCancel,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using System;
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Transforms;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Online.Leaderboards
|
namespace osu.Game.Online.Leaderboards
|
||||||
{
|
{
|
||||||
public partial class UpdateableRank : ModelBackedDrawable<ScoreRank?>
|
public partial class UpdateableRank : ModelBackedDrawable<ScoreRank?>
|
||||||
{
|
{
|
||||||
|
protected override double TransformDuration => 600;
|
||||||
|
protected override bool TransformImmediately => true;
|
||||||
|
|
||||||
public ScoreRank? Rank
|
public ScoreRank? Rank
|
||||||
{
|
{
|
||||||
get => Model;
|
get => Model;
|
||||||
@ -22,7 +25,17 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
Rank = rank;
|
Rank = rank;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Drawable CreateDrawable(ScoreRank? rank)
|
protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func<Drawable> createContentFunc, double timeBeforeLoad)
|
||||||
|
{
|
||||||
|
return base.CreateDelayedLoadWrapper(createContentFunc, timeBeforeLoad)
|
||||||
|
.With(w =>
|
||||||
|
{
|
||||||
|
w.Anchor = Anchor.Centre;
|
||||||
|
w.Origin = Anchor.Centre;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Drawable? CreateDrawable(ScoreRank? rank)
|
||||||
{
|
{
|
||||||
if (rank.HasValue)
|
if (rank.HasValue)
|
||||||
{
|
{
|
||||||
@ -35,5 +48,18 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override TransformSequence<Drawable> ApplyShowTransforms(Drawable drawable)
|
||||||
|
{
|
||||||
|
drawable.ScaleTo(1);
|
||||||
|
return base.ApplyShowTransforms(drawable);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override TransformSequence<Drawable> ApplyHideTransforms(Drawable drawable)
|
||||||
|
{
|
||||||
|
drawable.ScaleTo(1.8f, TransformDuration, Easing.Out);
|
||||||
|
|
||||||
|
return base.ApplyHideTransforms(drawable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
|||||||
using Humanizer;
|
using Humanizer;
|
||||||
using Humanizer.Localisation;
|
using Humanizer.Localisation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Online.Rooms
|
namespace osu.Game.Online.Rooms
|
||||||
{
|
{
|
||||||
@ -38,7 +39,21 @@ namespace osu.Game.Online.Rooms
|
|||||||
: GetUpcomingItems(playlist).First();
|
: GetUpcomingItems(playlist).First();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the total duration from the <see cref="PlaylistItem"/> in playlist order from the supplied <paramref name="playlist"/>,
|
||||||
|
/// </summary>
|
||||||
public static string GetTotalDuration(this BindableList<PlaylistItem> playlist) =>
|
public static string GetTotalDuration(this BindableList<PlaylistItem> playlist) =>
|
||||||
playlist.Select(p => p.Beatmap.Length).Sum().Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2);
|
playlist.Select(p =>
|
||||||
|
{
|
||||||
|
double rate = 1;
|
||||||
|
|
||||||
|
if (p.RequiredMods.Length > 0)
|
||||||
|
{
|
||||||
|
var ruleset = p.Beatmap.Ruleset.CreateInstance();
|
||||||
|
rate = ModUtils.CalculateRateWithMods(p.RequiredMods.Select(mod => mod.ToMod(ruleset)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.Beatmap.Length / rate;
|
||||||
|
}).Sum().Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,11 @@ namespace osu.Game
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected const float SIDE_OVERLAY_OFFSET_RATIO = 0.05f;
|
protected const float SIDE_OVERLAY_OFFSET_RATIO = 0.05f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A common shear factor applied to most components of the game.
|
||||||
|
/// </summary>
|
||||||
|
public const float SHEAR = 0.2f;
|
||||||
|
|
||||||
public Toolbar Toolbar { get; private set; }
|
public Toolbar Toolbar { get; private set; }
|
||||||
|
|
||||||
private ChatOverlay chatOverlay;
|
private ChatOverlay chatOverlay;
|
||||||
@ -480,10 +485,19 @@ namespace osu.Game
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
public void OpenUrlExternally(string url, bool bypassExternalUrlWarning = false) => waitForReady(() => externalLinkOpener, _ =>
|
public void OpenUrlExternally(string url, bool forceBypassExternalUrlWarning = false) => waitForReady(() => externalLinkOpener, _ =>
|
||||||
{
|
{
|
||||||
|
bool isTrustedDomain;
|
||||||
|
|
||||||
if (url.StartsWith('/'))
|
if (url.StartsWith('/'))
|
||||||
url = $"{API.APIEndpointUrl}{url}";
|
{
|
||||||
|
url = $"{API.WebsiteRootUrl}{url}";
|
||||||
|
isTrustedDomain = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isTrustedDomain = url.StartsWith(API.WebsiteRootUrl, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
if (!url.CheckIsValidUrl())
|
if (!url.CheckIsValidUrl())
|
||||||
{
|
{
|
||||||
@ -495,7 +509,7 @@ namespace osu.Game
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
externalLinkOpener.OpenUrlExternally(url, bypassExternalUrlWarning);
|
externalLinkOpener.OpenUrlExternally(url, forceBypassExternalUrlWarning || isTrustedDomain);
|
||||||
});
|
});
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Overlays.Dialog
|
|||||||
|
|
||||||
protected DangerousActionDialog()
|
protected DangerousActionDialog()
|
||||||
{
|
{
|
||||||
HeaderText = DeleteConfirmationDialogStrings.HeaderText;
|
HeaderText = DialogStrings.Caution;
|
||||||
|
|
||||||
Icon = FontAwesome.Regular.TrashAlt;
|
Icon = FontAwesome.Regular.TrashAlt;
|
||||||
|
|
||||||
@ -38,12 +38,12 @@ namespace osu.Game.Overlays.Dialog
|
|||||||
{
|
{
|
||||||
new PopupDialogDangerousButton
|
new PopupDialogDangerousButton
|
||||||
{
|
{
|
||||||
Text = DeleteConfirmationDialogStrings.Confirm,
|
Text = DialogStrings.Confirm,
|
||||||
Action = () => DangerousAction?.Invoke()
|
Action = () => DangerousAction?.Invoke()
|
||||||
},
|
},
|
||||||
new PopupDialogCancelButton
|
new PopupDialogCancelButton
|
||||||
{
|
{
|
||||||
Text = DeleteConfirmationDialogStrings.Cancel,
|
Text = DialogStrings.Cancel,
|
||||||
Action = () => CancelAction?.Invoke()
|
Action = () => CancelAction?.Invoke()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -66,7 +66,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
const float shear = ShearedOverlayContainer.SHEAR;
|
const float shear = OsuGame.SHEAR;
|
||||||
|
|
||||||
LeftContent.AddRange(new Drawable[]
|
LeftContent.AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
@ -165,9 +165,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
starRatingDisplay.FinishTransforms(true);
|
starRatingDisplay.FinishTransforms(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
double rate = 1;
|
double rate = ModUtils.CalculateRateWithMods(Mods.Value);
|
||||||
foreach (var mod in Mods.Value.OfType<IApplicableToRate>())
|
|
||||||
rate = mod.ApplyToRate(0, rate);
|
|
||||||
|
|
||||||
bpmDisplay.Current.Value = FormatUtils.RoundBPM(BeatmapInfo.Value.BPM, rate);
|
bpmDisplay.Current.Value = FormatUtils.RoundBPM(BeatmapInfo.Value.BPM, rate);
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Scale = new Vector2(0.8f),
|
Scale = new Vector2(0.8f),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0)
|
Shear = new Vector2(-OsuGame.SHEAR, 0)
|
||||||
});
|
});
|
||||||
ItemsFlow.Padding = new MarginPadding
|
ItemsFlow.Padding = new MarginPadding
|
||||||
{
|
{
|
||||||
|
@ -36,8 +36,8 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Origin = Anchor.BottomRight,
|
Origin = Anchor.BottomRight,
|
||||||
Anchor = Anchor.BottomRight,
|
Anchor = Anchor.BottomRight,
|
||||||
AutoSizeAxes = Axes.X,
|
AutoSizeAxes = Axes.X,
|
||||||
Height = ShearedButton.HEIGHT,
|
Height = ShearedButton.DEFAULT_HEIGHT,
|
||||||
Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0),
|
Shear = new Vector2(OsuGame.SHEAR, 0),
|
||||||
CornerRadius = ShearedButton.CORNER_RADIUS,
|
CornerRadius = ShearedButton.CORNER_RADIUS,
|
||||||
BorderThickness = ShearedButton.BORDER_THICKNESS,
|
BorderThickness = ShearedButton.BORDER_THICKNESS,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Active = { BindTarget = Active },
|
Active = { BindTarget = Active },
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
Shear = new Vector2(-OsuGame.SHEAR, 0),
|
||||||
Scale = new Vector2(HEIGHT / ModSwitchSmall.DEFAULT_SIZE)
|
Scale = new Vector2(HEIGHT / ModSwitchSmall.DEFAULT_SIZE)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
Width = WIDTH;
|
Width = WIDTH;
|
||||||
RelativeSizeAxes = Axes.Y;
|
RelativeSizeAxes = Axes.Y;
|
||||||
Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0);
|
Shear = new Vector2(OsuGame.SHEAR, 0);
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -96,7 +96,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = header_height,
|
Height = header_height,
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
Shear = new Vector2(-OsuGame.SHEAR, 0),
|
||||||
Velocity = 0.7f,
|
Velocity = 0.7f,
|
||||||
ClampAxes = Axes.Y
|
ClampAxes = Axes.Y
|
||||||
},
|
},
|
||||||
@ -111,7 +111,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
Shear = new Vector2(-OsuGame.SHEAR, 0),
|
||||||
Padding = new MarginPadding
|
Padding = new MarginPadding
|
||||||
{
|
{
|
||||||
Horizontal = 17,
|
Horizontal = 17,
|
||||||
|
@ -227,7 +227,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Anchor = Anchor.BottomLeft,
|
Anchor = Anchor.BottomLeft,
|
||||||
Origin = Anchor.BottomLeft,
|
Origin = Anchor.BottomLeft,
|
||||||
Direction = FillDirection.Horizontal,
|
Direction = FillDirection.Horizontal,
|
||||||
Shear = new Vector2(SHEAR, 0),
|
Shear = new Vector2(OsuGame.SHEAR, 0),
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
AutoSizeAxes = Axes.X,
|
AutoSizeAxes = Axes.X,
|
||||||
Margin = new MarginPadding { Horizontal = 70 },
|
Margin = new MarginPadding { Horizontal = 70 },
|
||||||
@ -847,7 +847,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
// DrawWidth/DrawPosition do not include shear effects, and we want to know the full extents of the columns post-shear,
|
// DrawWidth/DrawPosition do not include shear effects, and we want to know the full extents of the columns post-shear,
|
||||||
// so we have to manually compensate.
|
// so we have to manually compensate.
|
||||||
var topLeft = column.ToSpaceOfOtherDrawable(Vector2.Zero, ScrollContent);
|
var topLeft = column.ToSpaceOfOtherDrawable(Vector2.Zero, ScrollContent);
|
||||||
var bottomRight = column.ToSpaceOfOtherDrawable(new Vector2(column.DrawWidth - column.DrawHeight * SHEAR, 0), ScrollContent);
|
var bottomRight = column.ToSpaceOfOtherDrawable(new Vector2(column.DrawWidth - column.DrawHeight * OsuGame.SHEAR, 0), ScrollContent);
|
||||||
|
|
||||||
bool isCurrentlyVisible = Precision.AlmostBigger(topLeft.X, leftVisibleBound)
|
bool isCurrentlyVisible = Precision.AlmostBigger(topLeft.X, leftVisibleBound)
|
||||||
&& Precision.DefinitelyBigger(rightVisibleBound, bottomRight.X);
|
&& Precision.DefinitelyBigger(rightVisibleBound, bottomRight.X);
|
||||||
|
@ -87,7 +87,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Content.CornerRadius = CORNER_RADIUS;
|
Content.CornerRadius = CORNER_RADIUS;
|
||||||
Content.BorderThickness = 2;
|
Content.BorderThickness = 2;
|
||||||
|
|
||||||
Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0);
|
Shear = new Vector2(OsuGame.SHEAR, 0);
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -128,10 +128,10 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold),
|
Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
Shear = new Vector2(-OsuGame.SHEAR, 0),
|
||||||
Margin = new MarginPadding
|
Margin = new MarginPadding
|
||||||
{
|
{
|
||||||
Left = -18 * ShearedOverlayContainer.SHEAR
|
Left = -18 * OsuGame.SHEAR
|
||||||
},
|
},
|
||||||
ShowTooltip = false, // Tooltip is handled by `IncompatibilityDisplayingModPanel`.
|
ShowTooltip = false, // Tooltip is handled by `IncompatibilityDisplayingModPanel`.
|
||||||
},
|
},
|
||||||
@ -139,7 +139,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
Font = OsuFont.Default.With(size: 12),
|
Font = OsuFont.Default.With(size: 12),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
Shear = new Vector2(-OsuGame.SHEAR, 0),
|
||||||
ShowTooltip = false, // Tooltip is handled by `IncompatibilityDisplayingModPanel`.
|
ShowTooltip = false, // Tooltip is handled by `IncompatibilityDisplayingModPanel`.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Anchor = Anchor.BottomRight,
|
Anchor = Anchor.BottomRight,
|
||||||
Origin = Anchor.BottomRight,
|
Origin = Anchor.BottomRight,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0),
|
Shear = new Vector2(OsuGame.SHEAR, 0),
|
||||||
CornerRadius = ShearedButton.CORNER_RADIUS,
|
CornerRadius = ShearedButton.CORNER_RADIUS,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
@ -79,7 +79,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
Shear = new Vector2(-OsuGame.SHEAR, 0),
|
||||||
Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold)
|
Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,7 +94,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Child = counter = new EffectCounter
|
Child = counter = new EffectCounter
|
||||||
{
|
{
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
Shear = new Vector2(-OsuGame.SHEAR, 0),
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Current = { BindTarget = ModMultiplier }
|
Current = { BindTarget = ModMultiplier }
|
||||||
|
@ -22,8 +22,6 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
protected const float PADDING = 14;
|
protected const float PADDING = 14;
|
||||||
|
|
||||||
public const float SHEAR = 0.2f;
|
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
protected readonly OverlayColourProvider ColourProvider;
|
protected readonly OverlayColourProvider ColourProvider;
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
|
|
||||||
private void updateState()
|
private void updateState()
|
||||||
{
|
{
|
||||||
CanRotateSelectionOrigin.Value = selectedItems.Count > 0;
|
CanRotateAroundSelectionOrigin.Value = selectedItems.Count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable[]? objectsInRotation;
|
private Drawable[]? objectsInRotation;
|
||||||
|
@ -43,7 +43,12 @@ namespace osu.Game.Rulesets
|
|||||||
|
|
||||||
// add all legacy rulesets first to ensure they have exclusive choice of primary key.
|
// add all legacy rulesets first to ensure they have exclusive choice of primary key.
|
||||||
foreach (var r in instances.Where(r => r is ILegacyRuleset))
|
foreach (var r in instances.Where(r => r is ILegacyRuleset))
|
||||||
availableRulesets.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID));
|
{
|
||||||
|
availableRulesets.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)
|
||||||
|
{
|
||||||
|
Available = true
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -24,13 +21,13 @@ namespace osu.Game.Rulesets.Judgements
|
|||||||
{
|
{
|
||||||
private const float judgement_size = 128;
|
private const float judgement_size = 128;
|
||||||
|
|
||||||
public JudgementResult Result { get; private set; }
|
public JudgementResult? Result { get; private set; }
|
||||||
|
|
||||||
public DrawableHitObject JudgedObject { get; private set; }
|
public DrawableHitObject? JudgedObject { get; private set; }
|
||||||
|
|
||||||
public override bool RemoveCompletedTransforms => false;
|
public override bool RemoveCompletedTransforms => false;
|
||||||
|
|
||||||
protected SkinnableDrawable JudgementBody { get; private set; }
|
protected SkinnableDrawable? JudgementBody { get; private set; }
|
||||||
|
|
||||||
private readonly Container aboveHitObjectsContent;
|
private readonly Container aboveHitObjectsContent;
|
||||||
|
|
||||||
@ -97,12 +94,19 @@ namespace osu.Game.Rulesets.Judgements
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="result">The applicable judgement.</param>
|
/// <param name="result">The applicable judgement.</param>
|
||||||
/// <param name="judgedObject">The drawable object.</param>
|
/// <param name="judgedObject">The drawable object.</param>
|
||||||
public void Apply([NotNull] JudgementResult result, [CanBeNull] DrawableHitObject judgedObject)
|
public void Apply(JudgementResult result, DrawableHitObject? judgedObject)
|
||||||
{
|
{
|
||||||
Result = result;
|
Result = result;
|
||||||
JudgedObject = judgedObject;
|
JudgedObject = judgedObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void FreeAfterUse()
|
||||||
|
{
|
||||||
|
base.FreeAfterUse();
|
||||||
|
|
||||||
|
JudgedObject = null;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void PrepareForUse()
|
protected override void PrepareForUse()
|
||||||
{
|
{
|
||||||
base.PrepareForUse();
|
base.PrepareForUse();
|
||||||
@ -121,6 +125,8 @@ namespace osu.Game.Rulesets.Judgements
|
|||||||
ApplyTransformsAt(double.MinValue, true);
|
ApplyTransformsAt(double.MinValue, true);
|
||||||
ClearTransforms(true);
|
ClearTransforms(true);
|
||||||
|
|
||||||
|
Debug.Assert(Result != null && JudgementBody != null);
|
||||||
|
|
||||||
LifetimeStart = Result.TimeAbsolute;
|
LifetimeStart = Result.TimeAbsolute;
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(Result.TimeAbsolute))
|
using (BeginAbsoluteSequence(Result.TimeAbsolute))
|
||||||
|
@ -330,6 +330,20 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
if (subControlPoints.Length != 3)
|
if (subControlPoints.Length != 3)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
CircularArcProperties circularArcProperties = new CircularArcProperties(subControlPoints);
|
||||||
|
|
||||||
|
// `PathApproximator` will already internally revert to B-spline if the arc isn't valid.
|
||||||
|
if (!circularArcProperties.IsValid)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// taken from https://github.com/ppy/osu-framework/blob/1201e641699a1d50d2f6f9295192dad6263d5820/osu.Framework/Utils/PathApproximator.cs#L181-L186
|
||||||
|
int subPoints = (2f * circularArcProperties.Radius <= 0.1f) ? 2 : Math.Max(2, (int)Math.Ceiling(circularArcProperties.ThetaRange / (2.0 * Math.Acos(1f - (0.1f / circularArcProperties.Radius)))));
|
||||||
|
|
||||||
|
// 1000 subpoints requires an arc length of at least ~120 thousand to occur
|
||||||
|
// See here for calculations https://www.desmos.com/calculator/umj6jvmcz7
|
||||||
|
if (subPoints >= 1000)
|
||||||
|
break;
|
||||||
|
|
||||||
List<Vector2> subPath = PathApproximator.CircularArcToPiecewiseLinear(subControlPoints);
|
List<Vector2> subPath = PathApproximator.CircularArcToPiecewiseLinear(subControlPoints);
|
||||||
|
|
||||||
// If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation.
|
// If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
@ -21,6 +21,7 @@ using osu.Game.Rulesets;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osuTK;
|
||||||
using SharpCompress.Compressors.LZMA;
|
using SharpCompress.Compressors.LZMA;
|
||||||
|
|
||||||
namespace osu.Game.Scoring.Legacy
|
namespace osu.Game.Scoring.Legacy
|
||||||
@ -262,7 +263,7 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
private void readLegacyReplay(Replay replay, StreamReader reader)
|
private void readLegacyReplay(Replay replay, StreamReader reader)
|
||||||
{
|
{
|
||||||
float lastTime = beatmapOffset;
|
float lastTime = beatmapOffset;
|
||||||
ReplayFrame currentFrame = null;
|
var legacyFrames = new List<LegacyReplayFrame>();
|
||||||
|
|
||||||
string[] frames = reader.ReadToEnd().Split(',');
|
string[] frames = reader.ReadToEnd().Split(',');
|
||||||
|
|
||||||
@ -285,23 +286,44 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
|
|
||||||
lastTime += diff;
|
lastTime += diff;
|
||||||
|
|
||||||
if (i < 2 && mouseX == 256 && mouseY == -500)
|
legacyFrames.Add(new LegacyReplayFrame(lastTime,
|
||||||
// at the start of the replay, stable places two replay frames, at time 0 and SkipBoundary - 1, respectively.
|
|
||||||
// both frames use a position of (256, -500).
|
|
||||||
// ignore these frames as they serve no real purpose (and can even mislead ruleset-specific handlers - see mania)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Todo: At some point we probably want to rewind and play back the negative-time frames
|
|
||||||
// but for now we'll achieve equal playback to stable by skipping negative frames
|
|
||||||
if (diff < 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
currentFrame = convertFrame(new LegacyReplayFrame(lastTime,
|
|
||||||
mouseX,
|
mouseX,
|
||||||
mouseY,
|
mouseY,
|
||||||
(ReplayButtonState)Parsing.ParseInt(split[3])), currentFrame);
|
(ReplayButtonState)Parsing.ParseInt(split[3])));
|
||||||
|
}
|
||||||
|
|
||||||
replay.Frames.Add(currentFrame);
|
// https://github.com/peppy/osu-stable-reference/blob/e53980dd76857ee899f66ce519ba1597e7874f28/osu!/GameModes/Play/ReplayWatcher.cs#L62-L67
|
||||||
|
if (legacyFrames.Count >= 2 && legacyFrames[1].Time < legacyFrames[0].Time)
|
||||||
|
{
|
||||||
|
legacyFrames[1].Time = legacyFrames[0].Time;
|
||||||
|
legacyFrames[0].Time = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/peppy/osu-stable-reference/blob/e53980dd76857ee899f66ce519ba1597e7874f28/osu!/GameModes/Play/ReplayWatcher.cs#L69-L71
|
||||||
|
if (legacyFrames.Count >= 3 && legacyFrames[0].Time > legacyFrames[2].Time)
|
||||||
|
legacyFrames[0].Time = legacyFrames[1].Time = legacyFrames[2].Time;
|
||||||
|
|
||||||
|
// at the start of the replay, stable places two replay frames, at time 0 and SkipBoundary - 1, respectively.
|
||||||
|
// both frames use a position of (256, -500).
|
||||||
|
// ignore these frames as they serve no real purpose (and can even mislead ruleset-specific handlers - see mania)
|
||||||
|
if (legacyFrames.Count >= 2 && legacyFrames[1].Position == new Vector2(256, -500))
|
||||||
|
legacyFrames.RemoveAt(1);
|
||||||
|
|
||||||
|
if (legacyFrames.Count >= 1 && legacyFrames[0].Position == new Vector2(256, -500))
|
||||||
|
legacyFrames.RemoveAt(0);
|
||||||
|
|
||||||
|
ReplayFrame currentFrame = null;
|
||||||
|
|
||||||
|
foreach (var legacyFrame in legacyFrames)
|
||||||
|
{
|
||||||
|
// never allow backwards time traversal in relation to the current frame.
|
||||||
|
// this handles frames with negative delta.
|
||||||
|
// this doesn't match stable 100% as stable will do something similar to adding an interpolated "intermediate frame"
|
||||||
|
// at the point wherein time flow changes from backwards to forwards, but it'll do for now.
|
||||||
|
if (currentFrame != null && legacyFrame.Time < currentFrame.Time)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
replay.Frames.Add(currentFrame = convertFrame(legacyFrame, currentFrame));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,6 +198,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// <param name="bankName">The name of the sample bank.</param>
|
/// <param name="bankName">The name of the sample bank.</param>
|
||||||
public void AddSampleBank(string bankName)
|
public void AddSampleBank(string bankName)
|
||||||
{
|
{
|
||||||
|
if (SelectedItems.All(h => h.Samples.All(s => s.Bank == bankName)))
|
||||||
|
return;
|
||||||
|
|
||||||
EditorBeatmap.PerformOnSelection(h =>
|
EditorBeatmap.PerformOnSelection(h =>
|
||||||
{
|
{
|
||||||
if (h.Samples.All(s => s.Bank == bankName))
|
if (h.Samples.All(s => s.Bank == bankName))
|
||||||
@ -214,6 +217,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// <param name="sampleName">The name of the hit sample.</param>
|
/// <param name="sampleName">The name of the hit sample.</param>
|
||||||
public void AddHitSample(string sampleName)
|
public void AddHitSample(string sampleName)
|
||||||
{
|
{
|
||||||
|
if (SelectedItems.All(h => h.Samples.Any(s => s.Name == sampleName)))
|
||||||
|
return;
|
||||||
|
|
||||||
EditorBeatmap.PerformOnSelection(h =>
|
EditorBeatmap.PerformOnSelection(h =>
|
||||||
{
|
{
|
||||||
// Make sure there isn't already an existing sample
|
// Make sure there isn't already an existing sample
|
||||||
@ -231,6 +237,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// <param name="sampleName">The name of the hit sample.</param>
|
/// <param name="sampleName">The name of the hit sample.</param>
|
||||||
public void RemoveHitSample(string sampleName)
|
public void RemoveHitSample(string sampleName)
|
||||||
{
|
{
|
||||||
|
if (SelectedItems.All(h => h.Samples.All(s => s.Name != sampleName)))
|
||||||
|
return;
|
||||||
|
|
||||||
EditorBeatmap.PerformOnSelection(h =>
|
EditorBeatmap.PerformOnSelection(h =>
|
||||||
{
|
{
|
||||||
h.SamplesBindable.RemoveAll(s => s.Name == sampleName);
|
h.SamplesBindable.RemoveAll(s => s.Name == sampleName);
|
||||||
@ -245,6 +254,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// <exception cref="InvalidOperationException">Throws if any selected object doesn't implement <see cref="IHasComboInformation"/></exception>
|
/// <exception cref="InvalidOperationException">Throws if any selected object doesn't implement <see cref="IHasComboInformation"/></exception>
|
||||||
public void SetNewCombo(bool state)
|
public void SetNewCombo(bool state)
|
||||||
{
|
{
|
||||||
|
if (SelectedItems.OfType<IHasComboInformation>().All(h => h.NewCombo == state))
|
||||||
|
return;
|
||||||
|
|
||||||
EditorBeatmap.PerformOnSelection(h =>
|
EditorBeatmap.PerformOnSelection(h =>
|
||||||
{
|
{
|
||||||
var comboInfo = h as IHasComboInformation;
|
var comboInfo = h as IHasComboInformation;
|
||||||
|
@ -0,0 +1,166 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Compose.Components
|
||||||
|
{
|
||||||
|
public abstract partial class LinedPositionSnapGrid : PositionSnapGrid
|
||||||
|
{
|
||||||
|
protected void GenerateGridLines(Vector2 step, Vector2 drawSize)
|
||||||
|
{
|
||||||
|
if (Precision.AlmostEquals(step, Vector2.Zero))
|
||||||
|
return;
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
// Make lines the same width independent of display resolution.
|
||||||
|
float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width;
|
||||||
|
float rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X));
|
||||||
|
|
||||||
|
List<Box> generatedLines = new List<Box>();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
Vector2 currentPosition = StartPosition.Value + index * step;
|
||||||
|
index++;
|
||||||
|
|
||||||
|
if (!lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize, out var p1, out var p2))
|
||||||
|
{
|
||||||
|
if (!isMovingTowardsBox(currentPosition, step, drawSize))
|
||||||
|
break;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var gridLine = new Box
|
||||||
|
{
|
||||||
|
Colour = Colour4.White,
|
||||||
|
Alpha = 0.1f,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.None,
|
||||||
|
Width = lineWidth,
|
||||||
|
Height = Vector2.Distance(p1, p2),
|
||||||
|
Position = (p1 + p2) / 2,
|
||||||
|
Rotation = rotation,
|
||||||
|
};
|
||||||
|
|
||||||
|
generatedLines.Add(gridLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (generatedLines.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
generatedLines.First().Alpha = 0.2f;
|
||||||
|
|
||||||
|
AddRangeInternal(generatedLines);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool isMovingTowardsBox(Vector2 currentPosition, Vector2 step, Vector2 box)
|
||||||
|
{
|
||||||
|
return (currentPosition + step).LengthSquared < currentPosition.LengthSquared ||
|
||||||
|
(currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if the line starting at <paramref name="lineStart"/> and going in the direction of <paramref name="lineDir"/>
|
||||||
|
/// definitely intersects the box on (0, 0) with the given width and height and returns the intersection points if it does.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lineStart">The start point of the line.</param>
|
||||||
|
/// <param name="lineDir">The direction of the line.</param>
|
||||||
|
/// <param name="box">The width and height of the box.</param>
|
||||||
|
/// <param name="p1">The first intersection point.</param>
|
||||||
|
/// <param name="p2">The second intersection point.</param>
|
||||||
|
/// <returns>Whether the line definitely intersects the box.</returns>
|
||||||
|
private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box, out Vector2 p1, out Vector2 p2)
|
||||||
|
{
|
||||||
|
p1 = Vector2.Zero;
|
||||||
|
p2 = Vector2.Zero;
|
||||||
|
|
||||||
|
if (Precision.AlmostEquals(lineDir.X, 0))
|
||||||
|
{
|
||||||
|
// If the line is vertical, we only need to check if the X coordinate of the line is within the box.
|
||||||
|
if (!Precision.DefinitelyBigger(lineStart.X, 0) || !Precision.DefinitelyBigger(box.X, lineStart.X))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
p1 = new Vector2(lineStart.X, 0);
|
||||||
|
p2 = new Vector2(lineStart.X, box.Y);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Precision.AlmostEquals(lineDir.Y, 0))
|
||||||
|
{
|
||||||
|
// If the line is horizontal, we only need to check if the Y coordinate of the line is within the box.
|
||||||
|
if (!Precision.DefinitelyBigger(lineStart.Y, 0) || !Precision.DefinitelyBigger(box.Y, lineStart.Y))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
p1 = new Vector2(0, lineStart.Y);
|
||||||
|
p2 = new Vector2(box.X, lineStart.Y);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
float m = lineDir.Y / lineDir.X;
|
||||||
|
float mInv = lineDir.X / lineDir.Y; // Use this to improve numerical stability if X is close to zero.
|
||||||
|
float b = lineStart.Y - m * lineStart.X;
|
||||||
|
|
||||||
|
// Calculate intersection points with the sides of the box.
|
||||||
|
var p = new List<Vector2>(4);
|
||||||
|
|
||||||
|
if (0 <= b && b <= box.Y)
|
||||||
|
p.Add(new Vector2(0, b));
|
||||||
|
if (0 <= (box.Y - b) * mInv && (box.Y - b) * mInv <= box.X)
|
||||||
|
p.Add(new Vector2((box.Y - b) * mInv, box.Y));
|
||||||
|
if (0 <= m * box.X + b && m * box.X + b <= box.Y)
|
||||||
|
p.Add(new Vector2(box.X, m * box.X + b));
|
||||||
|
if (0 <= -b * mInv && -b * mInv <= box.X)
|
||||||
|
p.Add(new Vector2(-b * mInv, 0));
|
||||||
|
|
||||||
|
switch (p.Count)
|
||||||
|
{
|
||||||
|
case 4:
|
||||||
|
// If there are 4 intersection points, the line is a diagonal of the box.
|
||||||
|
if (m > 0)
|
||||||
|
{
|
||||||
|
p1 = Vector2.Zero;
|
||||||
|
p2 = box;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
p1 = new Vector2(0, box.Y);
|
||||||
|
p2 = new Vector2(box.X, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
// If there are 3 intersection points, the line goes through a corner of the box.
|
||||||
|
if (p[0] == p[1])
|
||||||
|
{
|
||||||
|
p1 = p[0];
|
||||||
|
p2 = p[2];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
p1 = p[0];
|
||||||
|
p2 = p[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
p1 = p[0];
|
||||||
|
p2 = p[1];
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !Precision.AlmostEquals(p1, p2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
93
osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs
Normal file
93
osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Layout;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Compose.Components
|
||||||
|
{
|
||||||
|
public abstract partial class PositionSnapGrid : CompositeDrawable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The position of the origin of this <see cref="PositionSnapGrid"/> in local coordinates.
|
||||||
|
/// </summary>
|
||||||
|
public Bindable<Vector2> StartPosition { get; } = new Bindable<Vector2>(Vector2.Zero);
|
||||||
|
|
||||||
|
protected readonly LayoutValue GridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit);
|
||||||
|
|
||||||
|
protected PositionSnapGrid()
|
||||||
|
{
|
||||||
|
StartPosition.BindValueChanged(_ => GridCache.Invalidate());
|
||||||
|
|
||||||
|
AddLayout(GridCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (GridCache.IsValid) return;
|
||||||
|
|
||||||
|
ClearInternal();
|
||||||
|
|
||||||
|
if (DrawWidth > 0 && DrawHeight > 0)
|
||||||
|
CreateContent();
|
||||||
|
|
||||||
|
GridCache.Validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void CreateContent();
|
||||||
|
|
||||||
|
protected void GenerateOutline(Vector2 drawSize)
|
||||||
|
{
|
||||||
|
// Make lines the same width independent of display resolution.
|
||||||
|
float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width;
|
||||||
|
|
||||||
|
AddRangeInternal(new[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = Colour4.White,
|
||||||
|
Alpha = 0.3f,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = lineWidth,
|
||||||
|
Y = 0,
|
||||||
|
},
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = Colour4.White,
|
||||||
|
Alpha = 0.3f,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = lineWidth,
|
||||||
|
Y = drawSize.Y,
|
||||||
|
},
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = Colour4.White,
|
||||||
|
Alpha = 0.3f,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Width = lineWidth,
|
||||||
|
X = 0,
|
||||||
|
},
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = Colour4.White,
|
||||||
|
Alpha = 0.3f,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Width = lineWidth,
|
||||||
|
X = drawSize.X,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Vector2 GetSnappedPosition(Vector2 original);
|
||||||
|
}
|
||||||
|
}
|
@ -2,132 +2,51 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using osu.Framework.Bindables;
|
||||||
using System.Linq;
|
using osu.Game.Utils;
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Layout;
|
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components
|
namespace osu.Game.Screens.Edit.Compose.Components
|
||||||
{
|
{
|
||||||
public partial class RectangularPositionSnapGrid : CompositeDrawable
|
public partial class RectangularPositionSnapGrid : LinedPositionSnapGrid
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The position of the origin of this <see cref="RectangularPositionSnapGrid"/> in local coordinates.
|
|
||||||
/// </summary>
|
|
||||||
public Vector2 StartPosition { get; }
|
|
||||||
|
|
||||||
private Vector2 spacing = Vector2.One;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The spacing between grid lines of this <see cref="RectangularPositionSnapGrid"/>.
|
/// The spacing between grid lines of this <see cref="RectangularPositionSnapGrid"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Vector2 Spacing
|
public Bindable<Vector2> Spacing { get; } = new Bindable<Vector2>(Vector2.One);
|
||||||
{
|
|
||||||
get => spacing;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (spacing.X <= 0 || spacing.Y <= 0)
|
|
||||||
throw new ArgumentException("Grid spacing must be positive.");
|
|
||||||
|
|
||||||
spacing = value;
|
/// <summary>
|
||||||
gridCache.Invalidate();
|
/// The rotation in degrees of the grid lines of this <see cref="RectangularPositionSnapGrid"/>.
|
||||||
}
|
/// </summary>
|
||||||
|
public BindableFloat GridLineRotation { get; } = new BindableFloat();
|
||||||
|
|
||||||
|
public RectangularPositionSnapGrid()
|
||||||
|
{
|
||||||
|
Spacing.BindValueChanged(_ => GridCache.Invalidate());
|
||||||
|
GridLineRotation.BindValueChanged(_ => GridCache.Invalidate());
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit);
|
protected override void CreateContent()
|
||||||
|
|
||||||
public RectangularPositionSnapGrid(Vector2 startPosition)
|
|
||||||
{
|
|
||||||
StartPosition = startPosition;
|
|
||||||
|
|
||||||
AddLayout(gridCache);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
|
|
||||||
if (!gridCache.IsValid)
|
|
||||||
{
|
|
||||||
ClearInternal();
|
|
||||||
|
|
||||||
if (DrawWidth > 0 && DrawHeight > 0)
|
|
||||||
createContent();
|
|
||||||
|
|
||||||
gridCache.Validate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createContent()
|
|
||||||
{
|
{
|
||||||
var drawSize = DrawSize;
|
var drawSize = DrawSize;
|
||||||
|
var rot = Quaternion.FromAxisAngle(Vector3.UnitZ, MathHelper.DegreesToRadians(GridLineRotation.Value));
|
||||||
|
|
||||||
generateGridLines(Direction.Horizontal, StartPosition.Y, 0, -Spacing.Y);
|
GenerateGridLines(Vector2.Transform(new Vector2(0, -Spacing.Value.Y), rot), drawSize);
|
||||||
generateGridLines(Direction.Horizontal, StartPosition.Y, drawSize.Y, Spacing.Y);
|
GenerateGridLines(Vector2.Transform(new Vector2(0, Spacing.Value.Y), rot), drawSize);
|
||||||
|
|
||||||
generateGridLines(Direction.Vertical, StartPosition.X, 0, -Spacing.X);
|
GenerateGridLines(Vector2.Transform(new Vector2(-Spacing.Value.X, 0), rot), drawSize);
|
||||||
generateGridLines(Direction.Vertical, StartPosition.X, drawSize.X, Spacing.X);
|
GenerateGridLines(Vector2.Transform(new Vector2(Spacing.Value.X, 0), rot), drawSize);
|
||||||
|
|
||||||
|
GenerateOutline(drawSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateGridLines(Direction direction, float startPosition, float endPosition, float step)
|
public override Vector2 GetSnappedPosition(Vector2 original)
|
||||||
{
|
{
|
||||||
int index = 0;
|
Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition.Value, GridLineRotation.Value);
|
||||||
float currentPosition = startPosition;
|
Vector2 offset = Vector2.Divide(relativeToStart, Spacing.Value);
|
||||||
|
|
||||||
// Make lines the same width independent of display resolution.
|
|
||||||
float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width;
|
|
||||||
|
|
||||||
List<Box> generatedLines = new List<Box>();
|
|
||||||
|
|
||||||
while (Precision.AlmostBigger((endPosition - currentPosition) * Math.Sign(step), 0))
|
|
||||||
{
|
|
||||||
var gridLine = new Box
|
|
||||||
{
|
|
||||||
Colour = Colour4.White,
|
|
||||||
Alpha = 0.1f,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (direction == Direction.Horizontal)
|
|
||||||
{
|
|
||||||
gridLine.Origin = Anchor.CentreLeft;
|
|
||||||
gridLine.RelativeSizeAxes = Axes.X;
|
|
||||||
gridLine.Height = lineWidth;
|
|
||||||
gridLine.Y = currentPosition;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
gridLine.Origin = Anchor.TopCentre;
|
|
||||||
gridLine.RelativeSizeAxes = Axes.Y;
|
|
||||||
gridLine.Width = lineWidth;
|
|
||||||
gridLine.X = currentPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
generatedLines.Add(gridLine);
|
|
||||||
|
|
||||||
index += 1;
|
|
||||||
currentPosition = startPosition + index * step;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (generatedLines.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
generatedLines.First().Alpha = 0.3f;
|
|
||||||
generatedLines.Last().Alpha = 0.3f;
|
|
||||||
|
|
||||||
AddRangeInternal(generatedLines);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Vector2 GetSnappedPosition(Vector2 original)
|
|
||||||
{
|
|
||||||
Vector2 relativeToStart = original - StartPosition;
|
|
||||||
Vector2 offset = Vector2.Divide(relativeToStart, Spacing);
|
|
||||||
Vector2 roundedOffset = new Vector2(MathF.Round(offset.X), MathF.Round(offset.Y));
|
Vector2 roundedOffset = new Vector2(MathF.Round(offset.X), MathF.Round(offset.Y));
|
||||||
|
|
||||||
return StartPosition + Vector2.Multiply(roundedOffset, Spacing);
|
return StartPosition.Value + GeometryUtils.RotateVector(Vector2.Multiply(roundedOffset, Spacing.Value), -GridLineRotation.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
if (rotationHandler != null)
|
if (rotationHandler != null)
|
||||||
canRotate.BindTo(rotationHandler.CanRotateSelectionOrigin);
|
canRotate.BindTo(rotationHandler.CanRotateAroundSelectionOrigin);
|
||||||
|
|
||||||
if (scaleHandler != null)
|
if (scaleHandler != null)
|
||||||
{
|
{
|
||||||
|
@ -15,12 +15,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether rotation anchored by the selection origin can currently be performed.
|
/// Whether rotation anchored by the selection origin can currently be performed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Bindable<bool> CanRotateSelectionOrigin { get; private set; } = new BindableBool();
|
public Bindable<bool> CanRotateAroundSelectionOrigin { get; private set; } = new BindableBool();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether rotation anchored by the center of the playfield can currently be performed.
|
/// Whether rotation anchored by the center of the playfield can currently be performed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Bindable<bool> CanRotatePlayfieldOrigin { get; private set; } = new BindableBool();
|
public Bindable<bool> CanRotateAroundPlayfieldOrigin { get; private set; } = new BindableBool();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs a single, instant, atomic rotation operation.
|
/// Performs a single, instant, atomic rotation operation.
|
||||||
|
@ -198,6 +198,11 @@ namespace osu.Game.Screens.Edit
|
|||||||
/// Perform the provided action on every selected hitobject.
|
/// Perform the provided action on every selected hitobject.
|
||||||
/// Changes will be grouped as one history action.
|
/// Changes will be grouped as one history action.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Note that this incurs a full state save, and as such requires the entire beatmap to be encoded, etc.
|
||||||
|
/// Very frequent use of this method (e.g. once a frame) is most discouraged.
|
||||||
|
/// If there is need to do so, use local precondition checks to eliminate changes that are known to be no-ops.
|
||||||
|
/// </remarks>
|
||||||
/// <param name="action">The action to perform.</param>
|
/// <param name="action">The action to perform.</param>
|
||||||
public void PerformOnSelection(Action<HitObject> action)
|
public void PerformOnSelection(Action<HitObject> action)
|
||||||
{
|
{
|
||||||
|
64
osu.Game/Screens/Footer/ScreenBackButton.cs
Normal file
64
osu.Game/Screens/Footer/ScreenBackButton.cs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// 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.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Footer
|
||||||
|
{
|
||||||
|
public partial class ScreenBackButton : ShearedButton
|
||||||
|
{
|
||||||
|
// todo: see https://github.com/ppy/osu-framework/issues/3271
|
||||||
|
private const float torus_scale_factor = 1.2f;
|
||||||
|
|
||||||
|
public const float BUTTON_WIDTH = 240;
|
||||||
|
|
||||||
|
public ScreenBackButton()
|
||||||
|
: base(BUTTON_WIDTH, 70)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
ButtonContent.Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
X = -10f,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Spacing = new Vector2(20f, 0f),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(20f),
|
||||||
|
Icon = FontAwesome.Solid.ChevronLeft,
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Font = OsuFont.TorusAlternate.With(size: 20 * torus_scale_factor),
|
||||||
|
Text = CommonStrings.Back,
|
||||||
|
UseFullGlyphHeight = false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DarkerColour = Color4Extensions.FromHex("#DE31AE");
|
||||||
|
LighterColour = Color4Extensions.FromHex("#FF86DD");
|
||||||
|
TextColour = Color4.White;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,6 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -15,9 +14,8 @@ namespace osu.Game.Screens.Footer
|
|||||||
{
|
{
|
||||||
public partial class ScreenFooter : InputBlockingContainer
|
public partial class ScreenFooter : InputBlockingContainer
|
||||||
{
|
{
|
||||||
//Should be 60, setting to 50 for now for the sake of matching the current BackButton height.
|
private const int height = 60;
|
||||||
private const int height = 50;
|
private const int padding = 60;
|
||||||
private const int padding = 80;
|
|
||||||
|
|
||||||
private readonly List<OverlayContainer> overlays = new List<OverlayContainer>();
|
private readonly List<OverlayContainer> overlays = new List<OverlayContainer>();
|
||||||
|
|
||||||
@ -68,13 +66,20 @@ namespace osu.Game.Screens.Footer
|
|||||||
},
|
},
|
||||||
buttons = new FillFlowContainer<ScreenFooterButton>
|
buttons = new FillFlowContainer<ScreenFooterButton>
|
||||||
{
|
{
|
||||||
Position = new Vector2(TwoLayerButton.SIZE_EXTENDED.X + padding, 10),
|
Position = new Vector2(ScreenBackButton.BUTTON_WIDTH + padding, 10),
|
||||||
Anchor = Anchor.BottomLeft,
|
Anchor = Anchor.BottomLeft,
|
||||||
Origin = Anchor.BottomLeft,
|
Origin = Anchor.BottomLeft,
|
||||||
Direction = FillDirection.Horizontal,
|
Direction = FillDirection.Horizontal,
|
||||||
Spacing = new Vector2(7, 0),
|
Spacing = new Vector2(7, 0),
|
||||||
AutoSizeAxes = Axes.Both
|
AutoSizeAxes = Axes.Both
|
||||||
}
|
},
|
||||||
|
new ScreenBackButton
|
||||||
|
{
|
||||||
|
Margin = new MarginPadding { Bottom = 10f, Left = 12f },
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Action = () => { },
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,7 @@ namespace osu.Game.Screens.Footer
|
|||||||
{
|
{
|
||||||
public partial class ScreenFooterButton : OsuClickableContainer, IKeyBindingHandler<GlobalAction>
|
public partial class ScreenFooterButton : OsuClickableContainer, IKeyBindingHandler<GlobalAction>
|
||||||
{
|
{
|
||||||
// This should be 12 by design, but an extra allowance is added due to the corner radius specification.
|
private const float shear = OsuGame.SHEAR;
|
||||||
private const float shear_width = 13.5f;
|
|
||||||
|
|
||||||
protected const int CORNER_RADIUS = 10;
|
protected const int CORNER_RADIUS = 10;
|
||||||
protected const int BUTTON_HEIGHT = 90;
|
protected const int BUTTON_HEIGHT = 90;
|
||||||
@ -34,7 +33,7 @@ namespace osu.Game.Screens.Footer
|
|||||||
|
|
||||||
public Bindable<Visibility> OverlayState = new Bindable<Visibility>();
|
public Bindable<Visibility> OverlayState = new Bindable<Visibility>();
|
||||||
|
|
||||||
protected static readonly Vector2 BUTTON_SHEAR = new Vector2(shear_width / BUTTON_HEIGHT, 0);
|
protected static readonly Vector2 BUTTON_SHEAR = new Vector2(shear, 0);
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||||
|
@ -115,7 +115,6 @@ namespace osu.Game.Screens.Menu
|
|||||||
backgroundContent = CreateBackground(colour).With(bg =>
|
backgroundContent = CreateBackground(colour).With(bg =>
|
||||||
{
|
{
|
||||||
bg.RelativeSizeAxes = Axes.Y;
|
bg.RelativeSizeAxes = Axes.Y;
|
||||||
bg.X = -ButtonSystem.WEDGE_WIDTH;
|
|
||||||
bg.Anchor = Anchor.Centre;
|
bg.Anchor = Anchor.Centre;
|
||||||
bg.Origin = Anchor.Centre;
|
bg.Origin = Anchor.Centre;
|
||||||
}),
|
}),
|
||||||
|
@ -73,8 +73,19 @@ namespace osu.Game.Screens.Menu
|
|||||||
Task.Run(() => request.Perform())
|
Task.Run(() => request.Perform())
|
||||||
.ContinueWith(r =>
|
.ContinueWith(r =>
|
||||||
{
|
{
|
||||||
|
if (!FetchOnlineContent)
|
||||||
|
return;
|
||||||
|
|
||||||
if (r.IsCompletedSuccessfully)
|
if (r.IsCompletedSuccessfully)
|
||||||
Schedule(() => Current.Value = request.ResponseObject);
|
{
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
if (!FetchOnlineContent)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Current.Value = request.ResponseObject;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// if the request failed, "observe" the exception.
|
// if the request failed, "observe" the exception.
|
||||||
// it isn't very important why this failed, as it's only for display.
|
// it isn't very important why this failed, as it's only for display.
|
||||||
@ -170,6 +181,11 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
private Sprite flash = null!;
|
private Sprite flash = null!;
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// Overridden as a safety for <see cref="openUrlAction"/> functioning correctly.
|
||||||
|
/// </remarks>
|
||||||
|
public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
|
||||||
|
|
||||||
private ScheduledDelegate? openUrlAction;
|
private ScheduledDelegate? openUrlAction;
|
||||||
|
|
||||||
public MenuImage(APIMenuImage image)
|
public MenuImage(APIMenuImage image)
|
||||||
|
45
osu.Game/Screens/Play/HUD/DefaultRankDisplay.cs
Normal file
45
osu.Game/Screens/Play/HUD/DefaultRankDisplay.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// 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.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Online.Leaderboards;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Play.HUD
|
||||||
|
{
|
||||||
|
public partial class DefaultRankDisplay : Container, ISerialisableDrawable
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private ScoreProcessor scoreProcessor { get; set; } = null!;
|
||||||
|
|
||||||
|
public bool UsesFixedAnchor { get; set; }
|
||||||
|
|
||||||
|
private readonly UpdateableRank rank;
|
||||||
|
|
||||||
|
public DefaultRankDisplay()
|
||||||
|
{
|
||||||
|
Size = new Vector2(70, 35);
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
rank = new UpdateableRank(Scoring.ScoreRank.X)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
rank.Rank = scoreProcessor.Rank.Value;
|
||||||
|
|
||||||
|
scoreProcessor.Rank.BindValueChanged(v => rank.Rank = v.NewValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -402,9 +402,7 @@ namespace osu.Game.Screens.Select
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// this doesn't consider mods which apply variable rates, yet.
|
// this doesn't consider mods which apply variable rates, yet.
|
||||||
double rate = 1;
|
double rate = ModUtils.CalculateRateWithMods(mods.Value);
|
||||||
foreach (var mod in mods.Value.OfType<IApplicableToRate>())
|
|
||||||
rate = mod.ApplyToRate(0, rate);
|
|
||||||
|
|
||||||
int bpmMax = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMaximum, rate);
|
int bpmMax = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMaximum, rate);
|
||||||
int bpmMin = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMinimum, rate);
|
int bpmMin = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMinimum, rate);
|
||||||
|
@ -27,6 +27,7 @@ using osu.Game.Configuration;
|
|||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Overlays.Mods;
|
using osu.Game.Overlays.Mods;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Select.Details
|
namespace osu.Game.Screens.Select.Details
|
||||||
{
|
{
|
||||||
@ -179,9 +180,7 @@ namespace osu.Game.Screens.Select.Details
|
|||||||
|
|
||||||
if (Ruleset.Value != null)
|
if (Ruleset.Value != null)
|
||||||
{
|
{
|
||||||
double rate = 1;
|
double rate = ModUtils.CalculateRateWithMods(mods.Value);
|
||||||
foreach (var mod in mods.Value.OfType<IApplicableToRate>())
|
|
||||||
rate = mod.ApplyToRate(0, rate);
|
|
||||||
|
|
||||||
adjustedDifficulty = Ruleset.Value.CreateInstance().GetRateAdjustedDisplayDifficulty(originalDifficulty, rate);
|
adjustedDifficulty = Ruleset.Value.CreateInstance().GetRateAdjustedDisplayDifficulty(originalDifficulty, rate);
|
||||||
|
|
||||||
|
@ -33,12 +33,9 @@ namespace osu.Game.Screens.SelectV2.Footer
|
|||||||
{
|
{
|
||||||
// todo: see https://github.com/ppy/osu-framework/issues/3271
|
// todo: see https://github.com/ppy/osu-framework/issues/3271
|
||||||
private const float torus_scale_factor = 1.2f;
|
private const float torus_scale_factor = 1.2f;
|
||||||
private const float bar_shear_width = 7f;
|
|
||||||
private const float bar_height = 37f;
|
private const float bar_height = 37f;
|
||||||
private const float mod_display_portion = 0.65f;
|
private const float mod_display_portion = 0.65f;
|
||||||
|
|
||||||
private static readonly Vector2 bar_shear = new Vector2(bar_shear_width / bar_height, 0);
|
|
||||||
|
|
||||||
private readonly BindableWithCurrent<IReadOnlyList<Mod>> current = new BindableWithCurrent<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
private readonly BindableWithCurrent<IReadOnlyList<Mod>> current = new BindableWithCurrent<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||||
|
|
||||||
public Bindable<IReadOnlyList<Mod>> Current
|
public Bindable<IReadOnlyList<Mod>> Current
|
||||||
@ -77,7 +74,7 @@ namespace osu.Game.Screens.SelectV2.Footer
|
|||||||
Y = -5f,
|
Y = -5f,
|
||||||
Depth = float.MaxValue,
|
Depth = float.MaxValue,
|
||||||
Origin = Anchor.BottomLeft,
|
Origin = Anchor.BottomLeft,
|
||||||
Shear = bar_shear,
|
Shear = BUTTON_SHEAR,
|
||||||
CornerRadius = CORNER_RADIUS,
|
CornerRadius = CORNER_RADIUS,
|
||||||
Size = new Vector2(BUTTON_WIDTH, bar_height),
|
Size = new Vector2(BUTTON_WIDTH, bar_height),
|
||||||
Masking = true,
|
Masking = true,
|
||||||
@ -107,7 +104,7 @@ namespace osu.Game.Screens.SelectV2.Footer
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Shear = -bar_shear,
|
Shear = -BUTTON_SHEAR,
|
||||||
UseFullGlyphHeight = false,
|
UseFullGlyphHeight = false,
|
||||||
Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold)
|
Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold)
|
||||||
}
|
}
|
||||||
@ -129,7 +126,7 @@ namespace osu.Game.Screens.SelectV2.Footer
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Shear = -bar_shear,
|
Shear = -BUTTON_SHEAR,
|
||||||
Scale = new Vector2(0.6f),
|
Scale = new Vector2(0.6f),
|
||||||
Current = { BindTarget = Current },
|
Current = { BindTarget = Current },
|
||||||
ExpansionMode = ExpansionMode.AlwaysContracted,
|
ExpansionMode = ExpansionMode.AlwaysContracted,
|
||||||
@ -138,7 +135,7 @@ namespace osu.Game.Screens.SelectV2.Footer
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Shear = -bar_shear,
|
Shear = -BUTTON_SHEAR,
|
||||||
Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold),
|
Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold),
|
||||||
Mods = { BindTarget = Current },
|
Mods = { BindTarget = Current },
|
||||||
}
|
}
|
||||||
@ -304,7 +301,7 @@ namespace osu.Game.Screens.SelectV2.Footer
|
|||||||
Y = -5f;
|
Y = -5f;
|
||||||
Depth = float.MaxValue;
|
Depth = float.MaxValue;
|
||||||
Origin = Anchor.BottomLeft;
|
Origin = Anchor.BottomLeft;
|
||||||
Shear = bar_shear;
|
Shear = BUTTON_SHEAR;
|
||||||
CornerRadius = CORNER_RADIUS;
|
CornerRadius = CORNER_RADIUS;
|
||||||
AutoSizeAxes = Axes.X;
|
AutoSizeAxes = Axes.X;
|
||||||
Height = bar_height;
|
Height = bar_height;
|
||||||
@ -328,7 +325,7 @@ namespace osu.Game.Screens.SelectV2.Footer
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Shear = -bar_shear,
|
Shear = -BUTTON_SHEAR,
|
||||||
Text = ModSelectOverlayStrings.Unranked.ToUpper(),
|
Text = ModSelectOverlayStrings.Unranked.ToUpper(),
|
||||||
Margin = new MarginPadding { Horizontal = 15 },
|
Margin = new MarginPadding { Horizontal = 15 },
|
||||||
UseFullGlyphHeight = false,
|
UseFullGlyphHeight = false,
|
||||||
|
63
osu.Game/Skinning/LegacyRankDisplay.cs
Normal file
63
osu.Game/Skinning/LegacyRankDisplay.cs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// 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.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Skinning
|
||||||
|
{
|
||||||
|
public partial class LegacyRankDisplay : CompositeDrawable, ISerialisableDrawable
|
||||||
|
{
|
||||||
|
public bool UsesFixedAnchor { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private ScoreProcessor scoreProcessor { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private ISkinSource source { get; set; } = null!;
|
||||||
|
|
||||||
|
private readonly Sprite rank;
|
||||||
|
|
||||||
|
public LegacyRankDisplay()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
AddInternal(rank = new Sprite
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
scoreProcessor.Rank.BindValueChanged(v =>
|
||||||
|
{
|
||||||
|
var texture = source.GetTexture($"ranking-{v.NewValue}-small");
|
||||||
|
|
||||||
|
rank.Texture = texture;
|
||||||
|
|
||||||
|
if (texture != null)
|
||||||
|
{
|
||||||
|
var transientRank = new Sprite
|
||||||
|
{
|
||||||
|
Texture = texture,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
BypassAutoSizeAxes = Axes.Both,
|
||||||
|
};
|
||||||
|
AddInternal(transientRank);
|
||||||
|
transientRank.FadeOutFromOne(500, Easing.Out)
|
||||||
|
.ScaleTo(new Vector2(1.625f), 500, Easing.Out)
|
||||||
|
.Expire();
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
FinishTransforms(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,9 +26,7 @@ namespace osu.Game.Utils
|
|||||||
point.X -= origin.X;
|
point.X -= origin.X;
|
||||||
point.Y -= origin.Y;
|
point.Y -= origin.Y;
|
||||||
|
|
||||||
Vector2 ret;
|
Vector2 ret = RotateVector(point, angle);
|
||||||
ret.X = point.X * MathF.Cos(float.DegreesToRadians(angle)) + point.Y * MathF.Sin(float.DegreesToRadians(angle));
|
|
||||||
ret.Y = point.X * -MathF.Sin(float.DegreesToRadians(angle)) + point.Y * MathF.Cos(float.DegreesToRadians(angle));
|
|
||||||
|
|
||||||
ret.X += origin.X;
|
ret.X += origin.X;
|
||||||
ret.Y += origin.Y;
|
ret.Y += origin.Y;
|
||||||
@ -36,6 +34,19 @@ namespace osu.Game.Utils
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rotate a vector around the origin.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="vector">The vector.</param>
|
||||||
|
/// <param name="angle">The angle to rotate (in degrees).</param>
|
||||||
|
public static Vector2 RotateVector(Vector2 vector, float angle)
|
||||||
|
{
|
||||||
|
return new Vector2(
|
||||||
|
vector.X * MathF.Cos(float.DegreesToRadians(angle)) + vector.Y * MathF.Sin(float.DegreesToRadians(angle)),
|
||||||
|
vector.X * -MathF.Sin(float.DegreesToRadians(angle)) + vector.Y * MathF.Cos(float.DegreesToRadians(angle))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Given a flip direction, a surrounding quad for all selected objects, and a position,
|
/// Given a flip direction, a surrounding quad for all selected objects, and a position,
|
||||||
/// will return the flipped position in screen space coordinates.
|
/// will return the flipped position in screen space coordinates.
|
||||||
|
@ -276,5 +276,20 @@ namespace osu.Game.Utils
|
|||||||
|
|
||||||
return scoreMultiplier.ToLocalisableString("0.00x");
|
return scoreMultiplier.ToLocalisableString("0.00x");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate the rate for the song with the selected mods.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mods">The list of selected mods.</param>
|
||||||
|
/// <returns>The rate with mods.</returns>
|
||||||
|
public static double CalculateRateWithMods(IEnumerable<Mod> mods)
|
||||||
|
{
|
||||||
|
double rate = 1;
|
||||||
|
|
||||||
|
foreach (var mod in mods.OfType<IApplicableToRate>())
|
||||||
|
rate = mod.ApplyToRate(0, rate);
|
||||||
|
|
||||||
|
return rate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="11.5.0" />
|
<PackageReference Include="Realm" Version="11.5.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2024.523.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2024.528.1" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.517.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.517.0" />
|
||||||
<PackageReference Include="Sentry" Version="4.3.0" />
|
<PackageReference Include="Sentry" Version="4.3.0" />
|
||||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||||
|
@ -23,6 +23,6 @@
|
|||||||
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.523.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.528.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -82,7 +82,7 @@
|
|||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToConstant_002ELocal/@EntryIndexedValue">WARNING</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToConstant_002ELocal/@EntryIndexedValue">WARNING</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToLambdaExpression/@EntryIndexedValue">WARNING</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToLambdaExpression/@EntryIndexedValue">WARNING</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToLocalFunction/@EntryIndexedValue">HINT</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToLocalFunction/@EntryIndexedValue">HINT</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToPrimaryConstructor/@EntryIndexedValue">HINT</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToPrimaryConstructor/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToStaticClass/@EntryIndexedValue">WARNING</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToStaticClass/@EntryIndexedValue">WARNING</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToUsingDeclaration/@EntryIndexedValue">HINT</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToUsingDeclaration/@EntryIndexedValue">HINT</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertTypeCheckPatternToNullCheck/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertTypeCheckPatternToNullCheck/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||||
|
Loading…
Reference in New Issue
Block a user