mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 19:42:55 +08:00
Merge branch 'master' into remove-nullable-disable-in-the-mods-for-catch-ruleset
This commit is contained in:
commit
88db835e76
@ -51,8 +51,8 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.716.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.722.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.720.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.722.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||||
|
@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
|
|
||||||
private float halfCatcherWidth;
|
private float halfCatcherWidth;
|
||||||
|
|
||||||
|
public override int Version => 20220701;
|
||||||
|
|
||||||
public CatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
public CatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||||
: base(ruleset, beatmap)
|
: base(ruleset, beatmap)
|
||||||
{
|
{
|
||||||
|
@ -31,6 +31,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
private readonly bool isForCurrentRuleset;
|
private readonly bool isForCurrentRuleset;
|
||||||
private readonly double originalOverallDifficulty;
|
private readonly double originalOverallDifficulty;
|
||||||
|
|
||||||
|
public override int Version => 20220701;
|
||||||
|
|
||||||
public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||||
: base(ruleset, beatmap)
|
: base(ruleset, beatmap)
|
||||||
{
|
{
|
||||||
|
@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
private const double difficulty_multiplier = 0.0675;
|
private const double difficulty_multiplier = 0.0675;
|
||||||
private double hitWindowGreat;
|
private double hitWindowGreat;
|
||||||
|
|
||||||
|
public override int Version => 20220701;
|
||||||
|
|
||||||
public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||||
: base(ruleset, beatmap)
|
: base(ruleset, beatmap)
|
||||||
{
|
{
|
||||||
|
@ -23,13 +23,29 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
protected DrawableTaikoRuleset DrawableRuleset { get; private set; }
|
protected DrawableTaikoRuleset DrawableRuleset { get; private set; }
|
||||||
protected Container PlayfieldContainer { get; private set; }
|
protected Container PlayfieldContainer { get; private set; }
|
||||||
|
|
||||||
|
private ControlPointInfo controlPointInfo { get; set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
var controlPointInfo = new ControlPointInfo();
|
controlPointInfo = new ControlPointInfo();
|
||||||
controlPointInfo.Add(0, new TimingControlPoint());
|
controlPointInfo.Add(0, new TimingControlPoint());
|
||||||
|
|
||||||
IWorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
|
IWorkingBeatmap beatmap = CreateWorkingBeatmap(CreateBeatmap(new TaikoRuleset().RulesetInfo));
|
||||||
|
|
||||||
|
Add(PlayfieldContainer = new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = DEFAULT_PLAYFIELD_CONTAINER_HEIGHT,
|
||||||
|
Children = new[] { DrawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset)) }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||||
|
{
|
||||||
|
return new Beatmap
|
||||||
{
|
{
|
||||||
HitObjects = new List<HitObject> { new Hit { Type = HitType.Centre } },
|
HitObjects = new List<HitObject> { new Hit { Type = HitType.Centre } },
|
||||||
BeatmapInfo = new BeatmapInfo
|
BeatmapInfo = new BeatmapInfo
|
||||||
@ -41,19 +57,10 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
Title = @"Sample Beatmap",
|
Title = @"Sample Beatmap",
|
||||||
Author = { Username = @"peppy" },
|
Author = { Username = @"peppy" },
|
||||||
},
|
},
|
||||||
Ruleset = new TaikoRuleset().RulesetInfo
|
Ruleset = ruleset
|
||||||
},
|
},
|
||||||
ControlPointInfo = controlPointInfo
|
ControlPointInfo = controlPointInfo
|
||||||
});
|
};
|
||||||
|
|
||||||
Add(PlayfieldContainer = new Container
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = DEFAULT_PLAYFIELD_CONTAINER_HEIGHT,
|
|
||||||
Children = new[] { DrawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(new TaikoRuleset().RulesetInfo)) }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(200),
|
Size = new Vector2(200),
|
||||||
Child = new InputDrum(playfield.HitObjectContainer)
|
Child = new InputDrum()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
49
osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs
Normal file
49
osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// 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.Graphics;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Rulesets.Taiko.UI;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneDrumTouchInputArea : OsuTestScene
|
||||||
|
{
|
||||||
|
private DrumTouchInputArea drumTouchInputArea = null!;
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("create drum", () =>
|
||||||
|
{
|
||||||
|
Child = new TaikoInputManager(new TaikoRuleset().RulesetInfo)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new InputDrum
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Height = 0.2f,
|
||||||
|
},
|
||||||
|
drumTouchInputArea = new DrumTouchInputArea
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDrum()
|
||||||
|
{
|
||||||
|
AddStep("show drum", () => drumTouchInputArea.Show());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
private const double colour_skill_multiplier = 0.01;
|
private const double colour_skill_multiplier = 0.01;
|
||||||
private const double stamina_skill_multiplier = 0.021;
|
private const double stamina_skill_multiplier = 0.021;
|
||||||
|
|
||||||
|
public override int Version => 20220701;
|
||||||
|
|
||||||
public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||||
: base(ruleset, beatmap)
|
: base(ruleset, beatmap)
|
||||||
{
|
{
|
||||||
|
@ -10,8 +10,6 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
|
||||||
using osu.Game.Rulesets.Taiko.UI;
|
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -115,9 +113,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
public readonly Sprite Rim;
|
public readonly Sprite Rim;
|
||||||
public readonly Sprite Centre;
|
public readonly Sprite Centre;
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private DrumSampleTriggerSource sampleTriggerSource { get; set; }
|
|
||||||
|
|
||||||
public LegacyHalfDrum(bool flipped)
|
public LegacyHalfDrum(bool flipped)
|
||||||
{
|
{
|
||||||
Masking = true;
|
Masking = true;
|
||||||
@ -152,12 +147,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
if (e.Action == CentreAction)
|
if (e.Action == CentreAction)
|
||||||
{
|
{
|
||||||
target = Centre;
|
target = Centre;
|
||||||
sampleTriggerSource.Play(HitType.Centre);
|
|
||||||
}
|
}
|
||||||
else if (e.Action == RimAction)
|
else if (e.Action == RimAction)
|
||||||
{
|
{
|
||||||
target = Rim;
|
target = Rim;
|
||||||
sampleTriggerSource.Play(HitType.Rim);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target != null)
|
if (target != null)
|
||||||
|
@ -4,11 +4,13 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko
|
namespace osu.Game.Rulesets.Taiko
|
||||||
{
|
{
|
||||||
|
[Cached] // Used for touch input, see DrumTouchInputArea.
|
||||||
public class TaikoInputManager : RulesetInputManager<TaikoAction>
|
public class TaikoInputManager : RulesetInputManager<TaikoAction>
|
||||||
{
|
{
|
||||||
public TaikoInputManager(RulesetInfo ruleset)
|
public TaikoInputManager(RulesetInfo ruleset)
|
||||||
|
@ -8,18 +8,18 @@ using System.Collections.Generic;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
|
||||||
using osu.Game.Rulesets.UI;
|
|
||||||
using osu.Game.Rulesets.Taiko.Replays;
|
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Input.Handlers;
|
using osu.Game.Input.Handlers;
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Rulesets.Taiko.Replays;
|
||||||
using osu.Game.Rulesets.Timing;
|
using osu.Game.Rulesets.Timing;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -56,6 +56,8 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Depth = float.MaxValue
|
Depth = float.MaxValue
|
||||||
});
|
});
|
||||||
|
|
||||||
|
KeyBindingInputManager.Add(new DrumTouchInputArea());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateAfterChildren()
|
protected override void UpdateAfterChildren()
|
||||||
|
59
osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs
Normal file
59
osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// 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.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.UI
|
||||||
|
{
|
||||||
|
internal class DrumSamplePlayer : CompositeDrawable, IKeyBindingHandler<TaikoAction>
|
||||||
|
{
|
||||||
|
private readonly DrumSampleTriggerSource leftRimSampleTriggerSource;
|
||||||
|
private readonly DrumSampleTriggerSource leftCentreSampleTriggerSource;
|
||||||
|
private readonly DrumSampleTriggerSource rightCentreSampleTriggerSource;
|
||||||
|
private readonly DrumSampleTriggerSource rightRimSampleTriggerSource;
|
||||||
|
|
||||||
|
public DrumSamplePlayer(HitObjectContainer hitObjectContainer)
|
||||||
|
{
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
leftRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer),
|
||||||
|
leftCentreSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer),
|
||||||
|
rightCentreSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer),
|
||||||
|
rightRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnPressed(KeyBindingPressEvent<TaikoAction> e)
|
||||||
|
{
|
||||||
|
switch (e.Action)
|
||||||
|
{
|
||||||
|
case TaikoAction.LeftRim:
|
||||||
|
leftRimSampleTriggerSource.Play(HitType.Rim);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TaikoAction.LeftCentre:
|
||||||
|
leftCentreSampleTriggerSource.Play(HitType.Centre);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TaikoAction.RightCentre:
|
||||||
|
rightCentreSampleTriggerSource.Play(HitType.Centre);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TaikoAction.RightRim:
|
||||||
|
rightRimSampleTriggerSource.Play(HitType.Rim);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnReleased(KeyBindingReleaseEvent<TaikoAction> e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
243
osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs
Normal file
243
osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An overlay that captures and displays osu!taiko mouse and touch input.
|
||||||
|
/// </summary>
|
||||||
|
public class DrumTouchInputArea : VisibilityContainer
|
||||||
|
{
|
||||||
|
// visibility state affects our child. we always want to handle input.
|
||||||
|
public override bool PropagatePositionalInputSubTree => true;
|
||||||
|
public override bool PropagateNonPositionalInputSubTree => true;
|
||||||
|
|
||||||
|
private KeyBindingContainer<TaikoAction> keyBindingContainer = null!;
|
||||||
|
|
||||||
|
private readonly Dictionary<object, TaikoAction> trackedActions = new Dictionary<object, TaikoAction>();
|
||||||
|
|
||||||
|
private Container mainContent = null!;
|
||||||
|
|
||||||
|
private QuarterCircle leftCentre = null!;
|
||||||
|
private QuarterCircle rightCentre = null!;
|
||||||
|
private QuarterCircle leftRim = null!;
|
||||||
|
private QuarterCircle rightRim = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(TaikoInputManager taikoInputManager, OsuColour colours)
|
||||||
|
{
|
||||||
|
Debug.Assert(taikoInputManager.KeyBindingContainer != null);
|
||||||
|
keyBindingContainer = taikoInputManager.KeyBindingContainer;
|
||||||
|
|
||||||
|
// Container should handle input everywhere.
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
const float centre_region = 0.80f;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 350,
|
||||||
|
Y = 20,
|
||||||
|
Masking = true,
|
||||||
|
FillMode = FillMode.Fit,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
mainContent = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
leftRim = new QuarterCircle(TaikoAction.LeftRim, colours.Blue)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
X = -2,
|
||||||
|
},
|
||||||
|
rightRim = new QuarterCircle(TaikoAction.RightRim, colours.Blue)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
X = 2,
|
||||||
|
Rotation = 90,
|
||||||
|
},
|
||||||
|
leftCentre = new QuarterCircle(TaikoAction.LeftCentre, colours.Pink)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
X = -2,
|
||||||
|
Scale = new Vector2(centre_region),
|
||||||
|
},
|
||||||
|
rightCentre = new QuarterCircle(TaikoAction.RightCentre, colours.Pink)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
X = 2,
|
||||||
|
Scale = new Vector2(centre_region),
|
||||||
|
Rotation = 90,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
|
{
|
||||||
|
// Hide whenever the keyboard is used.
|
||||||
|
Hide();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
{
|
||||||
|
if (!validMouse(e))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
handleDown(e.Button, e.ScreenSpaceMousePosition);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseUp(MouseUpEvent e)
|
||||||
|
{
|
||||||
|
if (!validMouse(e))
|
||||||
|
return;
|
||||||
|
|
||||||
|
handleUp(e.Button);
|
||||||
|
base.OnMouseUp(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnTouchDown(TouchDownEvent e)
|
||||||
|
{
|
||||||
|
handleDown(e.Touch.Source, e.ScreenSpaceTouchDownPosition);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnTouchUp(TouchUpEvent e)
|
||||||
|
{
|
||||||
|
handleUp(e.Touch.Source);
|
||||||
|
base.OnTouchUp(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleDown(object source, Vector2 position)
|
||||||
|
{
|
||||||
|
Show();
|
||||||
|
|
||||||
|
TaikoAction taikoAction = getTaikoActionFromInput(position);
|
||||||
|
|
||||||
|
// Not too sure how this can happen, but let's avoid throwing.
|
||||||
|
if (trackedActions.ContainsKey(source))
|
||||||
|
return;
|
||||||
|
|
||||||
|
trackedActions.Add(source, taikoAction);
|
||||||
|
keyBindingContainer.TriggerPressed(taikoAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleUp(object source)
|
||||||
|
{
|
||||||
|
keyBindingContainer.TriggerReleased(trackedActions[source]);
|
||||||
|
trackedActions.Remove(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool validMouse(MouseButtonEvent e) =>
|
||||||
|
leftRim.Contains(e.ScreenSpaceMouseDownPosition) || rightRim.Contains(e.ScreenSpaceMouseDownPosition);
|
||||||
|
|
||||||
|
private TaikoAction getTaikoActionFromInput(Vector2 inputPosition)
|
||||||
|
{
|
||||||
|
bool centreHit = leftCentre.Contains(inputPosition) || rightCentre.Contains(inputPosition);
|
||||||
|
bool leftSide = ToLocalSpace(inputPosition).X < DrawWidth / 2;
|
||||||
|
|
||||||
|
if (leftSide)
|
||||||
|
return centreHit ? TaikoAction.LeftCentre : TaikoAction.LeftRim;
|
||||||
|
|
||||||
|
return centreHit ? TaikoAction.RightCentre : TaikoAction.RightRim;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopIn()
|
||||||
|
{
|
||||||
|
mainContent.FadeIn(500, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopOut()
|
||||||
|
{
|
||||||
|
mainContent.FadeOut(300);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class QuarterCircle : CompositeDrawable, IKeyBindingHandler<TaikoAction>
|
||||||
|
{
|
||||||
|
private readonly Circle overlay;
|
||||||
|
|
||||||
|
private readonly TaikoAction handledAction;
|
||||||
|
|
||||||
|
private readonly Circle circle;
|
||||||
|
|
||||||
|
public override bool Contains(Vector2 screenSpacePos) => circle.Contains(screenSpacePos);
|
||||||
|
|
||||||
|
public QuarterCircle(TaikoAction handledAction, Color4 colour)
|
||||||
|
{
|
||||||
|
this.handledAction = handledAction;
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
FillMode = FillMode.Fit;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Masking = true,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
circle = new Circle
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colour.Multiply(1.4f).Darken(2.8f),
|
||||||
|
Alpha = 0.8f,
|
||||||
|
Scale = new Vector2(2),
|
||||||
|
},
|
||||||
|
overlay = new Circle
|
||||||
|
{
|
||||||
|
Alpha = 0,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Colour = colour,
|
||||||
|
Scale = new Vector2(2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnPressed(KeyBindingPressEvent<TaikoAction> e)
|
||||||
|
{
|
||||||
|
if (e.Action == handledAction)
|
||||||
|
overlay.FadeTo(1f, 80, Easing.OutQuint);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnReleased(KeyBindingReleaseEvent<TaikoAction> e)
|
||||||
|
{
|
||||||
|
if (e.Action == handledAction)
|
||||||
|
overlay.FadeOut(1000, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,8 +12,6 @@ using osu.Framework.Graphics.Textures;
|
|||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
|
||||||
using osu.Game.Rulesets.UI;
|
|
||||||
using osu.Game.Screens.Ranking;
|
using osu.Game.Screens.Ranking;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -27,13 +25,8 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
{
|
{
|
||||||
private const float middle_split = 0.025f;
|
private const float middle_split = 0.025f;
|
||||||
|
|
||||||
[Cached]
|
public InputDrum()
|
||||||
private DrumSampleTriggerSource sampleTriggerSource;
|
|
||||||
|
|
||||||
public InputDrum(HitObjectContainer hitObjectContainer)
|
|
||||||
{
|
{
|
||||||
sampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer);
|
|
||||||
|
|
||||||
AutoSizeAxes = Axes.X;
|
AutoSizeAxes = Axes.X;
|
||||||
RelativeSizeAxes = Axes.Y;
|
RelativeSizeAxes = Axes.Y;
|
||||||
}
|
}
|
||||||
@ -48,7 +41,6 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
AutoSizeAxes = Axes.X,
|
AutoSizeAxes = Axes.X,
|
||||||
},
|
},
|
||||||
sampleTriggerSource
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,9 +108,6 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
private readonly Sprite centre;
|
private readonly Sprite centre;
|
||||||
private readonly Sprite centreHit;
|
private readonly Sprite centreHit;
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private DrumSampleTriggerSource sampleTriggerSource { get; set; }
|
|
||||||
|
|
||||||
public TaikoHalfDrum(bool flipped)
|
public TaikoHalfDrum(bool flipped)
|
||||||
{
|
{
|
||||||
Masking = true;
|
Masking = true;
|
||||||
@ -179,15 +168,11 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
{
|
{
|
||||||
target = centreHit;
|
target = centreHit;
|
||||||
back = centre;
|
back = centre;
|
||||||
|
|
||||||
sampleTriggerSource.Play(HitType.Centre);
|
|
||||||
}
|
}
|
||||||
else if (e.Action == RimAction)
|
else if (e.Action == RimAction)
|
||||||
{
|
{
|
||||||
target = rimHit;
|
target = rimHit;
|
||||||
back = rim;
|
back = rim;
|
||||||
|
|
||||||
sampleTriggerSource.Play(HitType.Rim);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target != null)
|
if (target != null)
|
||||||
|
@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
inputDrum = new InputDrum(HitObjectContainer)
|
inputDrum = new InputDrum
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
@ -164,6 +164,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
drumRollHitContainer.CreateProxy(),
|
drumRollHitContainer.CreateProxy(),
|
||||||
|
new DrumSamplePlayer(HitObjectContainer),
|
||||||
// this is added at the end of the hierarchy to receive input before taiko objects.
|
// this is added at the end of the hierarchy to receive input before taiko objects.
|
||||||
// but is proxied below everything to not cover visual effects such as hit explosions.
|
// but is proxied below everything to not cover visual effects such as hit explosions.
|
||||||
inputDrum,
|
inputDrum,
|
||||||
|
100
osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs
Normal file
100
osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Beatmaps
|
||||||
|
{
|
||||||
|
[HeadlessTest]
|
||||||
|
public class WorkingBeatmapManagerTest : OsuTestScene
|
||||||
|
{
|
||||||
|
private BeatmapManager beatmaps = null!;
|
||||||
|
|
||||||
|
private BeatmapSetInfo importedSet = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(GameHost host, AudioManager audio, RulesetStore rulesets)
|
||||||
|
{
|
||||||
|
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("import beatmap", () =>
|
||||||
|
{
|
||||||
|
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||||
|
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestGetWorkingBeatmap() => AddStep("run test", () =>
|
||||||
|
{
|
||||||
|
Assert.That(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()), Is.Not.Null);
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCachedRetrievalNoFiles() => AddStep("run test", () =>
|
||||||
|
{
|
||||||
|
var beatmap = importedSet.Beatmaps.First();
|
||||||
|
|
||||||
|
Assert.That(beatmap.BeatmapSet?.Files, Is.Empty);
|
||||||
|
|
||||||
|
var first = beatmaps.GetWorkingBeatmap(beatmap);
|
||||||
|
var second = beatmaps.GetWorkingBeatmap(beatmap);
|
||||||
|
|
||||||
|
Assert.That(first, Is.SameAs(second));
|
||||||
|
Assert.That(first.BeatmapInfo.BeatmapSet?.Files, Has.Count.GreaterThan(0));
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCachedRetrievalWithFiles() => AddStep("run test", () =>
|
||||||
|
{
|
||||||
|
var beatmap = Realm.Run(r => r.Find<BeatmapInfo>(importedSet.Beatmaps.First().ID).Detach());
|
||||||
|
|
||||||
|
Assert.That(beatmap.BeatmapSet?.Files, Has.Count.GreaterThan(0));
|
||||||
|
|
||||||
|
var first = beatmaps.GetWorkingBeatmap(beatmap);
|
||||||
|
var second = beatmaps.GetWorkingBeatmap(beatmap);
|
||||||
|
|
||||||
|
Assert.That(first, Is.SameAs(second));
|
||||||
|
Assert.That(first.BeatmapInfo.BeatmapSet?.Files, Has.Count.GreaterThan(0));
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestForcedRefetchRetrievalNoFiles() => AddStep("run test", () =>
|
||||||
|
{
|
||||||
|
var beatmap = importedSet.Beatmaps.First();
|
||||||
|
|
||||||
|
Assert.That(beatmap.BeatmapSet?.Files, Is.Empty);
|
||||||
|
|
||||||
|
var first = beatmaps.GetWorkingBeatmap(beatmap);
|
||||||
|
var second = beatmaps.GetWorkingBeatmap(beatmap, true);
|
||||||
|
Assert.That(first, Is.Not.SameAs(second));
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestForcedRefetchRetrievalWithFiles() => AddStep("run test", () =>
|
||||||
|
{
|
||||||
|
var beatmap = Realm.Run(r => r.Find<BeatmapInfo>(importedSet.Beatmaps.First().ID).Detach());
|
||||||
|
|
||||||
|
Assert.That(beatmap.BeatmapSet?.Files, Has.Count.GreaterThan(0));
|
||||||
|
|
||||||
|
var first = beatmaps.GetWorkingBeatmap(beatmap);
|
||||||
|
var second = beatmaps.GetWorkingBeatmap(beatmap, true);
|
||||||
|
Assert.That(first, Is.Not.SameAs(second));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
132
osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs
Normal file
132
osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Tests.Beatmaps.IO;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Database
|
||||||
|
{
|
||||||
|
[HeadlessTest]
|
||||||
|
public class BackgroundBeatmapProcessorTests : OsuTestScene, ILocalUserPlayInfo
|
||||||
|
{
|
||||||
|
public IBindable<bool> IsPlaying => isPlaying;
|
||||||
|
|
||||||
|
private readonly Bindable<bool> isPlaying = new Bindable<bool>();
|
||||||
|
|
||||||
|
private BeatmapSetInfo importedSet = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuGameBase osu)
|
||||||
|
{
|
||||||
|
importedSet = BeatmapImportHelper.LoadQuickOszIntoOsu(osu).GetResultSafely();
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("Set not playing", () => isPlaying.Value = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDifficultyProcessing()
|
||||||
|
{
|
||||||
|
AddAssert("Difficulty is initially set", () =>
|
||||||
|
{
|
||||||
|
return Realm.Run(r =>
|
||||||
|
{
|
||||||
|
var beatmapSetInfo = r.Find<BeatmapSetInfo>(importedSet.ID);
|
||||||
|
return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Reset difficulty", () =>
|
||||||
|
{
|
||||||
|
Realm.Write(r =>
|
||||||
|
{
|
||||||
|
var beatmapSetInfo = r.Find<BeatmapSetInfo>(importedSet.ID);
|
||||||
|
foreach (var b in beatmapSetInfo.Beatmaps)
|
||||||
|
b.StarRating = -1;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Run background processor", () =>
|
||||||
|
{
|
||||||
|
Add(new TestBackgroundBeatmapProcessor());
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for difficulties repopulated", () =>
|
||||||
|
{
|
||||||
|
return Realm.Run(r =>
|
||||||
|
{
|
||||||
|
var beatmapSetInfo = r.Find<BeatmapSetInfo>(importedSet.ID);
|
||||||
|
return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDifficultyProcessingWhilePlaying()
|
||||||
|
{
|
||||||
|
AddAssert("Difficulty is initially set", () =>
|
||||||
|
{
|
||||||
|
return Realm.Run(r =>
|
||||||
|
{
|
||||||
|
var beatmapSetInfo = r.Find<BeatmapSetInfo>(importedSet.ID);
|
||||||
|
return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Set playing", () => isPlaying.Value = true);
|
||||||
|
|
||||||
|
AddStep("Reset difficulty", () =>
|
||||||
|
{
|
||||||
|
Realm.Write(r =>
|
||||||
|
{
|
||||||
|
var beatmapSetInfo = r.Find<BeatmapSetInfo>(importedSet.ID);
|
||||||
|
foreach (var b in beatmapSetInfo.Beatmaps)
|
||||||
|
b.StarRating = -1;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Run background processor", () =>
|
||||||
|
{
|
||||||
|
Add(new TestBackgroundBeatmapProcessor());
|
||||||
|
});
|
||||||
|
|
||||||
|
AddWaitStep("wait some", 500);
|
||||||
|
|
||||||
|
AddAssert("Difficulty still not populated", () =>
|
||||||
|
{
|
||||||
|
return Realm.Run(r =>
|
||||||
|
{
|
||||||
|
var beatmapSetInfo = r.Find<BeatmapSetInfo>(importedSet.ID);
|
||||||
|
return beatmapSetInfo.Beatmaps.All(b => b.StarRating == -1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Set not playing", () => isPlaying.Value = false);
|
||||||
|
|
||||||
|
AddUntilStep("wait for difficulties repopulated", () =>
|
||||||
|
{
|
||||||
|
return Realm.Run(r =>
|
||||||
|
{
|
||||||
|
var beatmapSetInfo = r.Find<BeatmapSetInfo>(importedSet.ID);
|
||||||
|
return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestBackgroundBeatmapProcessor : BackgroundBeatmapProcessor
|
||||||
|
{
|
||||||
|
protected override int TimeToSleepDuringGameplay => 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -142,7 +142,6 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
// ReSharper disable once AccessToDisposedClosure
|
|
||||||
var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
|
var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
|
||||||
|
|
||||||
Assert.NotNull(beatmapSet);
|
Assert.NotNull(beatmapSet);
|
||||||
@ -311,6 +310,7 @@ namespace osu.Game.Tests.Database
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
File.Delete(temp);
|
||||||
Directory.Delete(extractedFolder, true);
|
Directory.Delete(extractedFolder, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -32,31 +32,29 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestAccessAfterStorageMigrate()
|
public void TestAccessAfterStorageMigrate()
|
||||||
{
|
{
|
||||||
RunTestWithRealm((realm, storage) =>
|
using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target"))
|
||||||
{
|
{
|
||||||
var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
|
RunTestWithRealm((realm, storage) =>
|
||||||
|
|
||||||
Live<BeatmapInfo>? liveBeatmap = null;
|
|
||||||
|
|
||||||
realm.Run(r =>
|
|
||||||
{
|
{
|
||||||
r.Write(_ => r.Add(beatmap));
|
var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
|
||||||
|
|
||||||
liveBeatmap = beatmap.ToLive(realm);
|
Live<BeatmapInfo>? liveBeatmap = null;
|
||||||
});
|
|
||||||
|
realm.Run(r =>
|
||||||
|
{
|
||||||
|
r.Write(_ => r.Add(beatmap));
|
||||||
|
|
||||||
|
liveBeatmap = beatmap.ToLive(realm);
|
||||||
|
});
|
||||||
|
|
||||||
using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target"))
|
|
||||||
{
|
|
||||||
migratedStorage.DeleteDirectory(string.Empty);
|
migratedStorage.DeleteDirectory(string.Empty);
|
||||||
|
|
||||||
using (realm.BlockAllOperations("testing"))
|
using (realm.BlockAllOperations("testing"))
|
||||||
{
|
|
||||||
storage.Migrate(migratedStorage);
|
storage.Migrate(migratedStorage);
|
||||||
}
|
|
||||||
|
|
||||||
Assert.IsFalse(liveBeatmap?.PerformRead(l => l.Hidden));
|
Assert.IsFalse(liveBeatmap?.PerformRead(l => l.Hidden));
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -341,14 +339,12 @@ namespace osu.Game.Tests.Database
|
|||||||
liveBeatmap.PerformRead(resolved =>
|
liveBeatmap.PerformRead(resolved =>
|
||||||
{
|
{
|
||||||
// retrieval causes an implicit refresh. even changes that aren't related to the retrieval are fired at this point.
|
// retrieval causes an implicit refresh. even changes that aren't related to the retrieval are fired at this point.
|
||||||
// ReSharper disable once AccessToDisposedClosure
|
|
||||||
Assert.AreEqual(2, outerRealm.All<BeatmapInfo>().Count());
|
Assert.AreEqual(2, outerRealm.All<BeatmapInfo>().Count());
|
||||||
Assert.AreEqual(1, changesTriggered);
|
Assert.AreEqual(1, changesTriggered);
|
||||||
|
|
||||||
// can access properties without a crash.
|
// can access properties without a crash.
|
||||||
Assert.IsFalse(resolved.Hidden);
|
Assert.IsFalse(resolved.Hidden);
|
||||||
|
|
||||||
// ReSharper disable once AccessToDisposedClosure
|
|
||||||
outerRealm.Write(r =>
|
outerRealm.Write(r =>
|
||||||
{
|
{
|
||||||
// can use with the main context.
|
// can use with the main context.
|
||||||
|
@ -4,11 +4,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Testing;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
@ -20,22 +20,15 @@ namespace osu.Game.Tests.Database
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public abstract class RealmTest
|
public abstract class RealmTest
|
||||||
{
|
{
|
||||||
private static readonly TemporaryNativeStorage storage;
|
protected void RunTestWithRealm([InstantHandle] Action<RealmAccess, OsuStorage> testAction, [CallerMemberName] string caller = "")
|
||||||
|
|
||||||
static RealmTest()
|
|
||||||
{
|
|
||||||
storage = new TemporaryNativeStorage("realm-test");
|
|
||||||
storage.DeleteDirectory(string.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void RunTestWithRealm(Action<RealmAccess, OsuStorage> testAction, [CallerMemberName] string caller = "")
|
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller))
|
||||||
{
|
{
|
||||||
host.Run(new RealmTestGame(() =>
|
host.Run(new RealmTestGame(() =>
|
||||||
{
|
{
|
||||||
// ReSharper disable once AccessToDisposedClosure
|
var defaultStorage = host.Storage;
|
||||||
var testStorage = new OsuStorage(host, storage.GetStorageForDirectory(caller));
|
|
||||||
|
var testStorage = new OsuStorage(host, defaultStorage);
|
||||||
|
|
||||||
using (var realm = new RealmAccess(testStorage, OsuGameBase.CLIENT_DATABASE_FILENAME))
|
using (var realm = new RealmAccess(testStorage, OsuGameBase.CLIENT_DATABASE_FILENAME))
|
||||||
{
|
{
|
||||||
@ -58,7 +51,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
host.Run(new RealmTestGame(async () =>
|
host.Run(new RealmTestGame(async () =>
|
||||||
{
|
{
|
||||||
var testStorage = storage.GetStorageForDirectory(caller);
|
var testStorage = host.Storage;
|
||||||
|
|
||||||
using (var realm = new RealmAccess(testStorage, OsuGameBase.CLIENT_DATABASE_FILENAME))
|
using (var realm = new RealmAccess(testStorage, OsuGameBase.CLIENT_DATABASE_FILENAME))
|
||||||
{
|
{
|
||||||
@ -116,7 +109,7 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
private class RealmTestGame : Framework.Game
|
private class RealmTestGame : Framework.Game
|
||||||
{
|
{
|
||||||
public RealmTestGame(Func<Task> work)
|
public RealmTestGame([InstantHandle] Func<Task> work)
|
||||||
{
|
{
|
||||||
// ReSharper disable once AsyncVoidLambda
|
// ReSharper disable once AsyncVoidLambda
|
||||||
Scheduler.Add(async () =>
|
Scheduler.Add(async () =>
|
||||||
@ -126,7 +119,7 @@ namespace osu.Game.Tests.Database
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public RealmTestGame(Action work)
|
public RealmTestGame([InstantHandle] Action work)
|
||||||
{
|
{
|
||||||
Scheduler.Add(() =>
|
Scheduler.Add(() =>
|
||||||
{
|
{
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -15,6 +16,7 @@ using osu.Game.Rulesets.Osu;
|
|||||||
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.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Gameplay
|
namespace osu.Game.Tests.Gameplay
|
||||||
@ -91,6 +93,47 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0));
|
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFailScore()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new TestHitObject(),
|
||||||
|
new TestHitObject(HitResult.LargeTickHit),
|
||||||
|
new TestHitObject(HitResult.SmallTickHit),
|
||||||
|
new TestHitObject(HitResult.SmallBonus),
|
||||||
|
new TestHitObject(),
|
||||||
|
new TestHitObject(HitResult.LargeTickHit),
|
||||||
|
new TestHitObject(HitResult.SmallTickHit),
|
||||||
|
new TestHitObject(HitResult.LargeBonus),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var scoreProcessor = new ScoreProcessor(new OsuRuleset());
|
||||||
|
scoreProcessor.ApplyBeatmap(beatmap);
|
||||||
|
|
||||||
|
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Ok });
|
||||||
|
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.LargeTickHit });
|
||||||
|
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[2], beatmap.HitObjects[2].CreateJudgement()) { Type = HitResult.SmallTickMiss });
|
||||||
|
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[3], beatmap.HitObjects[3].CreateJudgement()) { Type = HitResult.SmallBonus });
|
||||||
|
|
||||||
|
var score = new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo };
|
||||||
|
scoreProcessor.FailScore(score);
|
||||||
|
|
||||||
|
Assert.That(score.Rank, Is.EqualTo(ScoreRank.F));
|
||||||
|
Assert.That(score.Passed, Is.False);
|
||||||
|
Assert.That(score.Statistics.Count(kvp => kvp.Value > 0), Is.EqualTo(7));
|
||||||
|
Assert.That(score.Statistics[HitResult.Ok], Is.EqualTo(1));
|
||||||
|
Assert.That(score.Statistics[HitResult.Miss], Is.EqualTo(1));
|
||||||
|
Assert.That(score.Statistics[HitResult.LargeTickHit], Is.EqualTo(1));
|
||||||
|
Assert.That(score.Statistics[HitResult.LargeTickMiss], Is.EqualTo(1));
|
||||||
|
Assert.That(score.Statistics[HitResult.SmallTickMiss], Is.EqualTo(2));
|
||||||
|
Assert.That(score.Statistics[HitResult.SmallBonus], Is.EqualTo(1));
|
||||||
|
Assert.That(score.Statistics[HitResult.IgnoreMiss], Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
private class TestJudgement : Judgement
|
private class TestJudgement : Judgement
|
||||||
{
|
{
|
||||||
public override HitResult MaxResult { get; }
|
public override HitResult MaxResult { get; }
|
||||||
@ -100,5 +143,17 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
MaxResult = maxResult;
|
MaxResult = maxResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class TestHitObject : HitObject
|
||||||
|
{
|
||||||
|
private readonly HitResult maxResult;
|
||||||
|
|
||||||
|
public TestHitObject(HitResult maxResult = HitResult.Perfect)
|
||||||
|
{
|
||||||
|
this.maxResult = maxResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Judgement CreateJudgement() => new TestJudgement(maxResult);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Solo;
|
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -110,30 +109,30 @@ namespace osu.Game.Tests.Online
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestDeserialiseSubmittableScoreWithEmptyMods()
|
public void TestDeserialiseSoloScoreWithEmptyMods()
|
||||||
{
|
{
|
||||||
var score = new SubmittableScore(new ScoreInfo
|
var score = SoloScoreInfo.ForSubmission(new ScoreInfo
|
||||||
{
|
{
|
||||||
User = new APIUser(),
|
User = new APIUser(),
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
});
|
});
|
||||||
|
|
||||||
var deserialised = JsonConvert.DeserializeObject<SubmittableScore>(JsonConvert.SerializeObject(score));
|
var deserialised = JsonConvert.DeserializeObject<SoloScoreInfo>(JsonConvert.SerializeObject(score));
|
||||||
|
|
||||||
Assert.That(deserialised?.Mods.Length, Is.Zero);
|
Assert.That(deserialised?.Mods.Length, Is.Zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestDeserialiseSubmittableScoreWithCustomModSetting()
|
public void TestDeserialiseSoloScoreWithCustomModSetting()
|
||||||
{
|
{
|
||||||
var score = new SubmittableScore(new ScoreInfo
|
var score = SoloScoreInfo.ForSubmission(new ScoreInfo
|
||||||
{
|
{
|
||||||
Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } },
|
Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } },
|
||||||
User = new APIUser(),
|
User = new APIUser(),
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
});
|
});
|
||||||
|
|
||||||
var deserialised = JsonConvert.DeserializeObject<SubmittableScore>(JsonConvert.SerializeObject(score));
|
var deserialised = JsonConvert.DeserializeObject<SoloScoreInfo>(JsonConvert.SerializeObject(score));
|
||||||
|
|
||||||
Assert.That((deserialised?.Mods[0])?.Settings["speed_change"], Is.EqualTo(2));
|
Assert.That((deserialised?.Mods[0])?.Settings["speed_change"], Is.EqualTo(2));
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.IO.Serialization;
|
using osu.Game.IO.Serialization;
|
||||||
using osu.Game.Online.Solo;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Online
|
namespace osu.Game.Tests.Online
|
||||||
@ -15,12 +15,12 @@ namespace osu.Game.Tests.Online
|
|||||||
/// Basic testing to ensure our attribute-based naming is correctly working.
|
/// Basic testing to ensure our attribute-based naming is correctly working.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSubmittableScoreJsonSerialization
|
public class TestSoloScoreInfoJsonSerialization
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
public void TestScoreSerialisationViaExtensionMethod()
|
public void TestScoreSerialisationViaExtensionMethod()
|
||||||
{
|
{
|
||||||
var score = new SubmittableScore(TestResources.CreateTestScoreInfo());
|
var score = SoloScoreInfo.ForSubmission(TestResources.CreateTestScoreInfo());
|
||||||
|
|
||||||
string serialised = score.Serialize();
|
string serialised = score.Serialize();
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ namespace osu.Game.Tests.Online
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestScoreSerialisationWithoutSettings()
|
public void TestScoreSerialisationWithoutSettings()
|
||||||
{
|
{
|
||||||
var score = new SubmittableScore(TestResources.CreateTestScoreInfo());
|
var score = SoloScoreInfo.ForSubmission(TestResources.CreateTestScoreInfo());
|
||||||
|
|
||||||
string serialised = JsonConvert.SerializeObject(score);
|
string serialised = JsonConvert.SerializeObject(score);
|
||||||
|
|
@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Utils;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Editing
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
{
|
{
|
||||||
@ -14,30 +13,22 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
public override Drawable CreateTestComponent() => Empty();
|
public override Drawable CreateTestComponent() => Empty();
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
[FlakyTest]
|
|
||||||
/*
|
|
||||||
* Fail rate around 0.3%
|
|
||||||
*
|
|
||||||
* TearDown : osu.Framework.Testing.Drawables.Steps.AssertButton+TracedException : range halved
|
|
||||||
* --TearDown
|
|
||||||
* at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal()
|
|
||||||
* at osu.Framework.Threading.Scheduler.Update()
|
|
||||||
* at osu.Framework.Graphics.Drawable.UpdateSubTree()
|
|
||||||
*/
|
|
||||||
public void TestVisibleRangeUpdatesOnZoomChange()
|
public void TestVisibleRangeUpdatesOnZoomChange()
|
||||||
{
|
{
|
||||||
double initialVisibleRange = 0;
|
double initialVisibleRange = 0;
|
||||||
|
|
||||||
|
AddUntilStep("wait for load", () => MusicController.TrackLoaded);
|
||||||
|
|
||||||
AddStep("reset zoom", () => TimelineArea.Timeline.Zoom = 100);
|
AddStep("reset zoom", () => TimelineArea.Timeline.Zoom = 100);
|
||||||
AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange);
|
AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange);
|
||||||
|
|
||||||
AddStep("scale zoom", () => TimelineArea.Timeline.Zoom = 200);
|
AddStep("scale zoom", () => TimelineArea.Timeline.Zoom = 200);
|
||||||
AddAssert("range halved", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange / 2, 1));
|
AddStep("range halved", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange / 2).Within(1)));
|
||||||
AddStep("descale zoom", () => TimelineArea.Timeline.Zoom = 50);
|
AddStep("descale zoom", () => TimelineArea.Timeline.Zoom = 50);
|
||||||
AddAssert("range doubled", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange * 2, 1));
|
AddStep("range doubled", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange * 2).Within(1)));
|
||||||
|
|
||||||
AddStep("restore zoom", () => TimelineArea.Timeline.Zoom = 100);
|
AddStep("restore zoom", () => TimelineArea.Timeline.Zoom = 100);
|
||||||
AddAssert("range restored", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange, 1));
|
AddStep("range restored", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange).Within(1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -45,6 +36,8 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
{
|
{
|
||||||
double initialVisibleRange = 0;
|
double initialVisibleRange = 0;
|
||||||
|
|
||||||
|
AddUntilStep("wait for load", () => MusicController.TrackLoaded);
|
||||||
|
|
||||||
AddStep("reset timeline size", () => TimelineArea.Timeline.Width = 1);
|
AddStep("reset timeline size", () => TimelineArea.Timeline.Width = 1);
|
||||||
AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange);
|
AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange);
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = OsuColour.Gray(30)
|
Colour = OsuColour.Gray(30)
|
||||||
},
|
},
|
||||||
scrollContainer = new ZoomableScrollContainer
|
scrollContainer = new ZoomableScrollContainer(1, 60, 1)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -80,21 +80,6 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddAssert("Inner container width matches scroll container", () => innerBox.DrawWidth == scrollContainer.DrawWidth);
|
AddAssert("Inner container width matches scroll container", () => innerBox.DrawWidth == scrollContainer.DrawWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestZoomRangeUpdate()
|
|
||||||
{
|
|
||||||
AddStep("set zoom to 2", () => scrollContainer.Zoom = 2);
|
|
||||||
AddStep("set min zoom to 5", () => scrollContainer.MinZoom = 5);
|
|
||||||
AddAssert("zoom = 5", () => scrollContainer.Zoom == 5);
|
|
||||||
|
|
||||||
AddStep("set max zoom to 10", () => scrollContainer.MaxZoom = 10);
|
|
||||||
AddAssert("zoom = 5", () => scrollContainer.Zoom == 5);
|
|
||||||
|
|
||||||
AddStep("set min zoom to 20", () => scrollContainer.MinZoom = 20);
|
|
||||||
AddStep("set max zoom to 40", () => scrollContainer.MaxZoom = 40);
|
|
||||||
AddAssert("zoom = 20", () => scrollContainer.Zoom == 20);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestZoom0()
|
public void TestZoom0()
|
||||||
{
|
{
|
||||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
public TestScenePause()
|
public TestScenePause()
|
||||||
{
|
{
|
||||||
base.Content.Add(content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both });
|
base.Content.Add(content = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both });
|
||||||
}
|
}
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
|
@ -7,7 +7,6 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Online;
|
using osu.Game.Online;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
@ -15,7 +14,6 @@ using osu.Framework.Testing;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Rulesets;
|
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Ranking;
|
using osu.Game.Screens.Ranking;
|
||||||
@ -30,9 +28,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
private const long online_score_id = 2553163309;
|
private const long online_score_id = 2553163309;
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private RulesetStore rulesets { get; set; }
|
|
||||||
|
|
||||||
private TestReplayDownloadButton downloadButton;
|
private TestReplayDownloadButton downloadButton;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
@ -211,21 +206,18 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType<DownloadButton>().First().Enabled.Value);
|
AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType<DownloadButton>().First().Enabled.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScoreInfo getScoreInfo(bool replayAvailable, bool hasOnlineId = true)
|
private ScoreInfo getScoreInfo(bool replayAvailable, bool hasOnlineId = true) => new ScoreInfo
|
||||||
{
|
{
|
||||||
return new APIScore
|
OnlineID = hasOnlineId ? online_score_id : 0,
|
||||||
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
BeatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(),
|
||||||
|
Hash = replayAvailable ? "online" : string.Empty,
|
||||||
|
User = new APIUser
|
||||||
{
|
{
|
||||||
OnlineID = hasOnlineId ? online_score_id : 0,
|
Id = 39828,
|
||||||
RulesetID = 0,
|
Username = @"WubWoofWolf",
|
||||||
Beatmap = CreateAPIBeatmapSet(new OsuRuleset().RulesetInfo).Beatmaps.First(),
|
}
|
||||||
HasReplay = replayAvailable,
|
};
|
||||||
User = new APIUser
|
|
||||||
{
|
|
||||||
Id = 39828,
|
|
||||||
Username = @"WubWoofWolf",
|
|
||||||
}
|
|
||||||
}.CreateScoreInfo(rulesets, beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First());
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestReplayDownloadButton : ReplayDownloadButton
|
private class TestReplayDownloadButton : ReplayDownloadButton
|
||||||
{
|
{
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
@ -40,8 +40,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
private TestReplayRecorder recorder;
|
private TestReplayRecorder recorder;
|
||||||
|
|
||||||
[Cached]
|
private GameplayState gameplayState;
|
||||||
private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
|
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public void SetUpSteps()
|
public void SetUpSteps()
|
||||||
@ -52,81 +51,15 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
replay = new Replay();
|
replay = new Replay();
|
||||||
|
|
||||||
Add(new GridContainer
|
gameplayState = TestGameplayState.Create(new OsuRuleset());
|
||||||
|
gameplayState.Score.Replay = replay;
|
||||||
|
|
||||||
|
Child = new DependencyProvidingContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Content = new[]
|
CachedDependencies = new (Type, object)[] { (typeof(GameplayState), gameplayState) },
|
||||||
{
|
Child = createContent(),
|
||||||
new Drawable[]
|
};
|
||||||
{
|
|
||||||
recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
|
|
||||||
{
|
|
||||||
Recorder = recorder = new TestReplayRecorder(new Score
|
|
||||||
{
|
|
||||||
Replay = replay,
|
|
||||||
ScoreInfo =
|
|
||||||
{
|
|
||||||
BeatmapInfo = gameplayState.Beatmap.BeatmapInfo,
|
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
{
|
|
||||||
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
|
|
||||||
},
|
|
||||||
Child = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
Colour = Color4.Brown,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Text = "Recording",
|
|
||||||
Scale = new Vector2(3),
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
},
|
|
||||||
new TestInputConsumer()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new Drawable[]
|
|
||||||
{
|
|
||||||
playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
|
|
||||||
{
|
|
||||||
ReplayInputHandler = new TestFramedReplayInputHandler(replay)
|
|
||||||
{
|
|
||||||
GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
|
|
||||||
},
|
|
||||||
Child = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
Colour = Color4.DarkBlue,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Text = "Playback",
|
|
||||||
Scale = new Vector2(3),
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
},
|
|
||||||
new TestInputConsumer()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,6 +136,74 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
recorder = null;
|
recorder = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Drawable createContent() => new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
|
||||||
|
{
|
||||||
|
Recorder = recorder = new TestReplayRecorder(gameplayState.Score)
|
||||||
|
{
|
||||||
|
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
|
||||||
|
},
|
||||||
|
Child = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.Brown,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = "Recording",
|
||||||
|
Scale = new Vector2(3),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
new TestInputConsumer()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
|
||||||
|
{
|
||||||
|
ReplayInputHandler = new TestFramedReplayInputHandler(replay)
|
||||||
|
{
|
||||||
|
GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
|
||||||
|
},
|
||||||
|
Child = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.DarkBlue,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = "Playback",
|
||||||
|
Scale = new Vector2(3),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
new TestInputConsumer()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public class TestFramedReplayInputHandler : FramedReplayInputHandler<TestReplayFrame>
|
public class TestFramedReplayInputHandler : FramedReplayInputHandler<TestReplayFrame>
|
||||||
{
|
{
|
||||||
public TestFramedReplayInputHandler(Replay replay)
|
public TestFramedReplayInputHandler(Replay replay)
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Spectator;
|
using osu.Game.Online.Spectator;
|
||||||
@ -43,6 +44,21 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("spectator client sent correct ruleset", () => spectatorClient.WatchedUserStates[dummy_user_id].RulesetID == Ruleset.Value.OnlineID);
|
AddAssert("spectator client sent correct ruleset", () => spectatorClient.WatchedUserStates[dummy_user_id].RulesetID == Ruleset.Value.OnlineID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRestart()
|
||||||
|
{
|
||||||
|
AddAssert("spectator client sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing);
|
||||||
|
|
||||||
|
AddStep("exit player", () => Player.Exit());
|
||||||
|
AddStep("reload player", LoadPlayer);
|
||||||
|
AddUntilStep("wait for player load", () => Player.IsLoaded && Player.Alpha == 1);
|
||||||
|
|
||||||
|
AddAssert("spectator client sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing);
|
||||||
|
|
||||||
|
AddWaitStep("wait", 5);
|
||||||
|
AddUntilStep("spectator client still sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing);
|
||||||
|
}
|
||||||
|
|
||||||
public override void TearDownSteps()
|
public override void TearDownSteps()
|
||||||
{
|
{
|
||||||
base.TearDownSteps();
|
base.TearDownSteps();
|
||||||
|
@ -24,7 +24,6 @@ using osu.Game.Online.API;
|
|||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Overlays.Mods;
|
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Catch;
|
using osu.Game.Rulesets.Catch;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -633,7 +632,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
AddStep("invoke on back button", () => multiplayerComponents.OnBackButton());
|
AddStep("invoke on back button", () => multiplayerComponents.OnBackButton());
|
||||||
|
|
||||||
AddAssert("mod overlay is hidden", () => this.ChildrenOfType<UserModSelectOverlay>().Single().State.Value == Visibility.Hidden);
|
AddAssert("mod overlay is hidden", () => this.ChildrenOfType<RoomSubScreen>().Single().UserModsSelectOverlay.State.Value == Visibility.Hidden);
|
||||||
|
|
||||||
AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden);
|
AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden);
|
||||||
|
|
||||||
|
@ -16,6 +16,10 @@ using osu.Framework.Testing;
|
|||||||
using osu.Game.Beatmaps.Drawables;
|
using osu.Game.Beatmaps.Drawables;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays.BeatmapSet.Scores;
|
using osu.Game.Overlays.BeatmapSet.Scores;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Screens.Select.Details;
|
||||||
using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
|
using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Online
|
namespace osu.Game.Tests.Visual.Online
|
||||||
@ -34,6 +38,9 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private IRulesetStore rulesets { get; set; }
|
private IRulesetStore rulesets { get; set; }
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() => SelectedMods.Value = Array.Empty<Mod>());
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestLoading()
|
public void TestLoading()
|
||||||
{
|
{
|
||||||
@ -205,6 +212,21 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSelectedModsDontAffectStatistics()
|
||||||
|
{
|
||||||
|
AddStep("show map", () => overlay.ShowBeatmapSet(getBeatmapSet()));
|
||||||
|
AddAssert("AR displayed as 0", () => overlay.ChildrenOfType<AdvancedStats.StatisticRow>().Single(s => s.Title == BeatmapsetsStrings.ShowStatsAr).Value == (0, null));
|
||||||
|
AddStep("set AR10 diff adjust", () => SelectedMods.Value = new[]
|
||||||
|
{
|
||||||
|
new OsuModDifficultyAdjust
|
||||||
|
{
|
||||||
|
ApproachRate = { Value = 10 }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
AddAssert("AR still displayed as 0", () => overlay.ChildrenOfType<AdvancedStats.StatisticRow>().Single(s => s.Title == BeatmapsetsStrings.ShowStatsAr).Value == (0, null));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHide()
|
public void TestHide()
|
||||||
{
|
{
|
||||||
|
@ -141,6 +141,19 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddUntilStep("best score not displayed", () => scoresContainer.ChildrenOfType<DrawableTopScore>().Count() == 1);
|
AddUntilStep("best score not displayed", () => scoresContainer.ChildrenOfType<DrawableTopScore>().Count() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUnprocessedPP()
|
||||||
|
{
|
||||||
|
AddStep("Load scores with unprocessed PP", () =>
|
||||||
|
{
|
||||||
|
var allScores = createScores();
|
||||||
|
allScores.Scores[0].PP = null;
|
||||||
|
allScores.UserScore = createUserBest();
|
||||||
|
allScores.UserScore.Score.PP = null;
|
||||||
|
scoresContainer.Scores = allScores;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private int onlineID = 1;
|
private int onlineID = 1;
|
||||||
|
|
||||||
private APIScoresCollection createScores()
|
private APIScoresCollection createScores()
|
||||||
|
@ -7,6 +7,7 @@ using System;
|
|||||||
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;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
@ -99,6 +100,23 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
Accuracy = 0.55879
|
Accuracy = 0.55879
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var unprocessedPPScore = new SoloScoreInfo
|
||||||
|
{
|
||||||
|
Rank = ScoreRank.B,
|
||||||
|
Beatmap = new APIBeatmap
|
||||||
|
{
|
||||||
|
BeatmapSet = new APIBeatmapSet
|
||||||
|
{
|
||||||
|
Title = "C18H27NO3(extend)",
|
||||||
|
Artist = "Team Grimoire",
|
||||||
|
},
|
||||||
|
DifficultyName = "[4K] Cataclysmic Hypernova",
|
||||||
|
Status = BeatmapOnlineStatus.Ranked,
|
||||||
|
},
|
||||||
|
EndedAt = DateTimeOffset.Now,
|
||||||
|
Accuracy = 0.55879
|
||||||
|
};
|
||||||
|
|
||||||
Add(new FillFlowContainer
|
Add(new FillFlowContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -112,6 +130,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(firstScore)),
|
new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(firstScore)),
|
||||||
new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(secondScore)),
|
new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(secondScore)),
|
||||||
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(noPPScore)),
|
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(noPPScore)),
|
||||||
|
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(unprocessedPPScore)),
|
||||||
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(firstScore, 0.97)),
|
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(firstScore, 0.97)),
|
||||||
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(secondScore, 0.85)),
|
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(secondScore, 0.85)),
|
||||||
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(thirdScore, 0.66)),
|
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(thirdScore, 0.66)),
|
||||||
|
@ -21,12 +21,12 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneCursors : OsuManualInputManagerTestScene
|
public class TestSceneCursors : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
private readonly MenuCursorContainer menuCursorContainer;
|
private readonly GlobalCursorDisplay globalCursorDisplay;
|
||||||
private readonly CustomCursorBox[] cursorBoxes = new CustomCursorBox[6];
|
private readonly CustomCursorBox[] cursorBoxes = new CustomCursorBox[6];
|
||||||
|
|
||||||
public TestSceneCursors()
|
public TestSceneCursors()
|
||||||
{
|
{
|
||||||
Child = menuCursorContainer = new MenuCursorContainer
|
Child = globalCursorDisplay = new GlobalCursorDisplay
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new[]
|
Children = new[]
|
||||||
@ -96,11 +96,11 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
private void testUserCursor()
|
private void testUserCursor()
|
||||||
{
|
{
|
||||||
AddStep("Move to green area", () => InputManager.MoveMouseTo(cursorBoxes[0]));
|
AddStep("Move to green area", () => InputManager.MoveMouseTo(cursorBoxes[0]));
|
||||||
AddAssert("Check green cursor visible", () => checkVisible(cursorBoxes[0].Cursor));
|
AddAssert("Check green cursor visible", () => checkVisible(cursorBoxes[0].MenuCursor));
|
||||||
AddAssert("Check green cursor at mouse", () => checkAtMouse(cursorBoxes[0].Cursor));
|
AddAssert("Check green cursor at mouse", () => checkAtMouse(cursorBoxes[0].MenuCursor));
|
||||||
AddStep("Move out", moveOut);
|
AddStep("Move out", moveOut);
|
||||||
AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].Cursor));
|
AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].MenuCursor));
|
||||||
AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor));
|
AddAssert("Check global cursor visible", () => checkVisible(globalCursorDisplay.MenuCursor));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -111,13 +111,13 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
private void testLocalCursor()
|
private void testLocalCursor()
|
||||||
{
|
{
|
||||||
AddStep("Move to purple area", () => InputManager.MoveMouseTo(cursorBoxes[3]));
|
AddStep("Move to purple area", () => InputManager.MoveMouseTo(cursorBoxes[3]));
|
||||||
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor));
|
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor));
|
||||||
AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor));
|
AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].MenuCursor));
|
||||||
AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor));
|
AddAssert("Check global cursor visible", () => checkVisible(globalCursorDisplay.MenuCursor));
|
||||||
AddAssert("Check global cursor at mouse", () => checkAtMouse(menuCursorContainer.Cursor));
|
AddAssert("Check global cursor at mouse", () => checkAtMouse(globalCursorDisplay.MenuCursor));
|
||||||
AddStep("Move out", moveOut);
|
AddStep("Move out", moveOut);
|
||||||
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor));
|
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor));
|
||||||
AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor));
|
AddAssert("Check global cursor visible", () => checkVisible(globalCursorDisplay.MenuCursor));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -128,12 +128,12 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
private void testUserCursorOverride()
|
private void testUserCursorOverride()
|
||||||
{
|
{
|
||||||
AddStep("Move to blue-green boundary", () => InputManager.MoveMouseTo(cursorBoxes[1].ScreenSpaceDrawQuad.BottomRight - new Vector2(10)));
|
AddStep("Move to blue-green boundary", () => InputManager.MoveMouseTo(cursorBoxes[1].ScreenSpaceDrawQuad.BottomRight - new Vector2(10)));
|
||||||
AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].Cursor));
|
AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].MenuCursor));
|
||||||
AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].Cursor));
|
AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].MenuCursor));
|
||||||
AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].Cursor));
|
AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].MenuCursor));
|
||||||
AddStep("Move out", moveOut);
|
AddStep("Move out", moveOut);
|
||||||
AddAssert("Check blue cursor not visible", () => !checkVisible(cursorBoxes[1].Cursor));
|
AddAssert("Check blue cursor not visible", () => !checkVisible(cursorBoxes[1].MenuCursor));
|
||||||
AddAssert("Check green cursor not visible", () => !checkVisible(cursorBoxes[0].Cursor));
|
AddAssert("Check green cursor not visible", () => !checkVisible(cursorBoxes[0].MenuCursor));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -143,13 +143,13 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
private void testMultipleLocalCursors()
|
private void testMultipleLocalCursors()
|
||||||
{
|
{
|
||||||
AddStep("Move to yellow-purple boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.BottomRight - new Vector2(10)));
|
AddStep("Move to yellow-purple boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.BottomRight - new Vector2(10)));
|
||||||
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor));
|
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor));
|
||||||
AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor));
|
AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].MenuCursor));
|
||||||
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor));
|
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor));
|
||||||
AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].Cursor));
|
AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].MenuCursor));
|
||||||
AddStep("Move out", moveOut);
|
AddStep("Move out", moveOut);
|
||||||
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor));
|
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor));
|
||||||
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor));
|
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -159,13 +159,13 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
private void testUserOverrideWithLocal()
|
private void testUserOverrideWithLocal()
|
||||||
{
|
{
|
||||||
AddStep("Move to yellow-blue boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.TopRight - new Vector2(10)));
|
AddStep("Move to yellow-blue boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.TopRight - new Vector2(10)));
|
||||||
AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].Cursor));
|
AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].MenuCursor));
|
||||||
AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].Cursor));
|
AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].MenuCursor));
|
||||||
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor));
|
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor));
|
||||||
AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].Cursor));
|
AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].MenuCursor));
|
||||||
AddStep("Move out", moveOut);
|
AddStep("Move out", moveOut);
|
||||||
AddAssert("Check blue cursor invisible", () => !checkVisible(cursorBoxes[1].Cursor));
|
AddAssert("Check blue cursor invisible", () => !checkVisible(cursorBoxes[1].MenuCursor));
|
||||||
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor));
|
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -191,7 +191,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
public bool SmoothTransition;
|
public bool SmoothTransition;
|
||||||
|
|
||||||
public CursorContainer Cursor { get; }
|
public CursorContainer MenuCursor { get; }
|
||||||
public bool ProvidingUserCursor { get; }
|
public bool ProvidingUserCursor { get; }
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || (SmoothTransition && !ProvidingUserCursor);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || (SmoothTransition && !ProvidingUserCursor);
|
||||||
@ -218,7 +218,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Text = providesUserCursor ? "User cursor" : "Local cursor"
|
Text = providesUserCursor ? "User cursor" : "Local cursor"
|
||||||
},
|
},
|
||||||
Cursor = new TestCursorContainer
|
MenuCursor = new TestCursorContainer
|
||||||
{
|
{
|
||||||
State = { Value = providesUserCursor ? Visibility.Hidden : Visibility.Visible },
|
State = { Value = providesUserCursor ? Visibility.Hidden : Visibility.Visible },
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,10 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
@ -17,11 +19,26 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
public class TestSceneLabelledSliderBar : OsuTestScene
|
public class TestSceneLabelledSliderBar : OsuTestScene
|
||||||
{
|
{
|
||||||
[TestCase(false)]
|
[Test]
|
||||||
[TestCase(true)]
|
public void TestBasic() => createSliderBar();
|
||||||
public void TestSliderBar(bool hasDescription) => createSliderBar(hasDescription);
|
|
||||||
|
|
||||||
private void createSliderBar(bool hasDescription = false)
|
[Test]
|
||||||
|
public void TestDescription()
|
||||||
|
{
|
||||||
|
createSliderBar();
|
||||||
|
AddStep("set description", () => this.ChildrenOfType<LabelledSliderBar<double>>().ForEach(l => l.Description = "this text describes the component"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSize()
|
||||||
|
{
|
||||||
|
createSliderBar();
|
||||||
|
AddStep("set zero width", () => this.ChildrenOfType<LabelledSliderBar<double>>().ForEach(l => l.ResizeWidthTo(0, 200, Easing.OutQuint)));
|
||||||
|
AddStep("set negative width", () => this.ChildrenOfType<LabelledSliderBar<double>>().ForEach(l => l.ResizeWidthTo(-1, 200, Easing.OutQuint)));
|
||||||
|
AddStep("revert back", () => this.ChildrenOfType<LabelledSliderBar<double>>().ForEach(l => l.ResizeWidthTo(1, 200, Easing.OutQuint)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createSliderBar()
|
||||||
{
|
{
|
||||||
AddStep("create component", () =>
|
AddStep("create component", () =>
|
||||||
{
|
{
|
||||||
@ -38,6 +55,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
new LabelledSliderBar<double>
|
new LabelledSliderBar<double>
|
||||||
{
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
Current = new BindableDouble(5)
|
Current = new BindableDouble(5)
|
||||||
{
|
{
|
||||||
MinValue = 0,
|
MinValue = 0,
|
||||||
@ -45,7 +64,6 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
Precision = 1,
|
Precision = 1,
|
||||||
},
|
},
|
||||||
Label = "a sample component",
|
Label = "a sample component",
|
||||||
Description = hasDescription ? "this text describes the component" : string.Empty,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -54,10 +72,14 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
flow.Add(new OverlayColourContainer(colour)
|
flow.Add(new OverlayColourContainer(colour)
|
||||||
{
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Child = new LabelledSliderBar<double>
|
Child = new LabelledSliderBar<double>
|
||||||
{
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
Current = new BindableDouble(5)
|
Current = new BindableDouble(5)
|
||||||
{
|
{
|
||||||
MinValue = 0,
|
MinValue = 0,
|
||||||
@ -65,7 +87,6 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
Precision = 1,
|
Precision = 1,
|
||||||
},
|
},
|
||||||
Label = "a sample component",
|
Label = "a sample component",
|
||||||
Description = hasDescription ? "this text describes the component" : string.Empty,
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Mods;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
public class TestSceneModPresetColumn : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBasicAppearance()
|
||||||
|
{
|
||||||
|
ModPresetColumn modPresetColumn = null!;
|
||||||
|
|
||||||
|
AddStep("create content", () => Child = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding(30),
|
||||||
|
Child = modPresetColumn = new ModPresetColumn
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Presets = createTestPresets().ToArray()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
AddStep("change presets", () => modPresetColumn.Presets = createTestPresets().Skip(1).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<ModPreset> createTestPresets() => new[]
|
||||||
|
{
|
||||||
|
new ModPreset
|
||||||
|
{
|
||||||
|
Name = "First preset",
|
||||||
|
Description = "Please ignore",
|
||||||
|
Mods = new Mod[]
|
||||||
|
{
|
||||||
|
new OsuModHardRock(),
|
||||||
|
new OsuModDoubleTime()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new ModPreset
|
||||||
|
{
|
||||||
|
Name = "AR0",
|
||||||
|
Description = "For good readers",
|
||||||
|
Mods = new Mod[]
|
||||||
|
{
|
||||||
|
new OsuModDifficultyAdjust
|
||||||
|
{
|
||||||
|
ApproachRate = { Value = 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new ModPreset
|
||||||
|
{
|
||||||
|
Name = "This preset is going to have an extraordinarily long name",
|
||||||
|
Description = "This is done so that the capability to truncate overlong texts may be demonstrated",
|
||||||
|
Mods = new Mod[]
|
||||||
|
{
|
||||||
|
new OsuModFlashlight(),
|
||||||
|
new OsuModSpinIn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Mods;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneModPresetPanel : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestVariousModPresets()
|
||||||
|
{
|
||||||
|
AddStep("create content", () => Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Width = 300,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Spacing = new Vector2(0, 5),
|
||||||
|
ChildrenEnumerable = createTestPresets().Select(preset => new ModPresetPanel(preset))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<ModPreset> createTestPresets() => new[]
|
||||||
|
{
|
||||||
|
new ModPreset
|
||||||
|
{
|
||||||
|
Name = "First preset",
|
||||||
|
Description = "Please ignore",
|
||||||
|
Mods = new Mod[]
|
||||||
|
{
|
||||||
|
new OsuModHardRock(),
|
||||||
|
new OsuModDoubleTime()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new ModPreset
|
||||||
|
{
|
||||||
|
Name = "AR0",
|
||||||
|
Description = "For good readers",
|
||||||
|
Mods = new Mod[]
|
||||||
|
{
|
||||||
|
new OsuModDifficultyAdjust
|
||||||
|
{
|
||||||
|
ApproachRate = { Value = 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new ModPreset
|
||||||
|
{
|
||||||
|
Name = "This preset is going to have an extraordinarily long name",
|
||||||
|
Description = "This is done so that the capability to truncate overlong texts may be demonstrated",
|
||||||
|
Mods = new Mod[]
|
||||||
|
{
|
||||||
|
new OsuModFlashlight(),
|
||||||
|
new OsuModSpinIn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
Spacing = new Vector2(2f),
|
Spacing = new Vector2(2f),
|
||||||
Direction = FillDirection.Horizontal,
|
Direction = FillDirection.Horizontal,
|
||||||
ChildrenEnumerable = Enumerable.Range(0, 15).Select(i => new FillFlowContainer
|
ChildrenEnumerable = Enumerable.Range(-1, 15).Select(i => new FillFlowContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -27,7 +28,6 @@ namespace osu.Game.Tournament.Tests.NonVisual
|
|||||||
{
|
{
|
||||||
var osu = new TestTournament(runOnLoadComplete: () =>
|
var osu = new TestTournament(runOnLoadComplete: () =>
|
||||||
{
|
{
|
||||||
// ReSharper disable once AccessToDisposedClosure
|
|
||||||
var storage = host.Storage.GetStorageForDirectory(Path.Combine("tournaments", "default"));
|
var storage = host.Storage.GetStorageForDirectory(Path.Combine("tournaments", "default"));
|
||||||
|
|
||||||
using (var stream = storage.CreateFileSafely("bracket.json"))
|
using (var stream = storage.CreateFileSafely("bracket.json"))
|
||||||
@ -85,7 +85,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
|
|||||||
|
|
||||||
public new Task BracketLoadTask => base.BracketLoadTask;
|
public new Task BracketLoadTask => base.BracketLoadTask;
|
||||||
|
|
||||||
public TestTournament(bool resetRuleset = false, Action runOnLoadComplete = null)
|
public TestTournament(bool resetRuleset = false, [InstantHandle] Action runOnLoadComplete = null)
|
||||||
{
|
{
|
||||||
this.resetRuleset = resetRuleset;
|
this.resetRuleset = resetRuleset;
|
||||||
this.runOnLoadComplete = runOnLoadComplete;
|
this.runOnLoadComplete = runOnLoadComplete;
|
||||||
|
@ -70,10 +70,10 @@ namespace osu.Game.Tournament
|
|||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
MenuCursorContainer.Cursor.AlwaysPresent = true; // required for tooltip display
|
GlobalCursorDisplay.MenuCursor.AlwaysPresent = true; // required for tooltip display
|
||||||
|
|
||||||
// we don't want to show the menu cursor as it would appear on stream output.
|
// we don't want to show the menu cursor as it would appear on stream output.
|
||||||
MenuCursorContainer.Cursor.Alpha = 0;
|
GlobalCursorDisplay.MenuCursor.Alpha = 0;
|
||||||
|
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
|
144
osu.Game/BackgroundBeatmapProcessor.cs
Normal file
144
osu.Game/BackgroundBeatmapProcessor.cs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
// 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.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
|
||||||
|
namespace osu.Game
|
||||||
|
{
|
||||||
|
public class BackgroundBeatmapProcessor : Component
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private RulesetStore rulesetStore { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private RealmAccess realmAccess { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private BeatmapUpdater beatmapUpdater { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IBindable<WorkingBeatmap> gameBeatmap { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private ILocalUserPlayInfo? localUserPlayInfo { get; set; }
|
||||||
|
|
||||||
|
protected virtual int TimeToSleepDuringGameplay => 30000;
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
Logger.Log("Beginning background beatmap processing..");
|
||||||
|
checkForOutdatedStarRatings();
|
||||||
|
processBeatmapSetsWithMissingMetrics();
|
||||||
|
}).ContinueWith(t =>
|
||||||
|
{
|
||||||
|
if (t.Exception?.InnerException is ObjectDisposedException)
|
||||||
|
{
|
||||||
|
Logger.Log("Finished background aborted during shutdown");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Log("Finished background beatmap processing!");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check whether the databased difficulty calculation version matches the latest ruleset provided version.
|
||||||
|
/// If it doesn't, clear out any existing difficulties so they can be incrementally recalculated.
|
||||||
|
/// </summary>
|
||||||
|
private void checkForOutdatedStarRatings()
|
||||||
|
{
|
||||||
|
foreach (var ruleset in rulesetStore.AvailableRulesets)
|
||||||
|
{
|
||||||
|
// beatmap being passed in is arbitrary here. just needs to be non-null.
|
||||||
|
int currentVersion = ruleset.CreateInstance().CreateDifficultyCalculator(gameBeatmap.Value).Version;
|
||||||
|
|
||||||
|
if (ruleset.LastAppliedDifficultyVersion < currentVersion)
|
||||||
|
{
|
||||||
|
Logger.Log($"Resetting star ratings for {ruleset.Name} (difficulty calculation version updated from {ruleset.LastAppliedDifficultyVersion} to {currentVersion})");
|
||||||
|
|
||||||
|
int countReset = 0;
|
||||||
|
|
||||||
|
realmAccess.Write(r =>
|
||||||
|
{
|
||||||
|
foreach (var b in r.All<BeatmapInfo>())
|
||||||
|
{
|
||||||
|
if (b.Ruleset.ShortName == ruleset.ShortName)
|
||||||
|
{
|
||||||
|
b.StarRating = -1;
|
||||||
|
countReset++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Find<RulesetInfo>(ruleset.ShortName).LastAppliedDifficultyVersion = currentVersion;
|
||||||
|
});
|
||||||
|
|
||||||
|
Logger.Log($"Finished resetting {countReset} beatmap sets for {ruleset.Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processBeatmapSetsWithMissingMetrics()
|
||||||
|
{
|
||||||
|
HashSet<Guid> beatmapSetIds = new HashSet<Guid>();
|
||||||
|
|
||||||
|
Logger.Log("Querying for beatmap sets to reprocess...");
|
||||||
|
|
||||||
|
realmAccess.Run(r =>
|
||||||
|
{
|
||||||
|
foreach (var b in r.All<BeatmapInfo>().Where(b => b.StarRating < 0 || (b.OnlineID > 0 && b.LastOnlineUpdate == null)))
|
||||||
|
{
|
||||||
|
Debug.Assert(b.BeatmapSet != null);
|
||||||
|
beatmapSetIds.Add(b.BeatmapSet.ID);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Logger.Log($"Found {beatmapSetIds.Count} beatmap sets which require reprocessing.");
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
foreach (var id in beatmapSetIds)
|
||||||
|
{
|
||||||
|
while (localUserPlayInfo?.IsPlaying.Value == true)
|
||||||
|
{
|
||||||
|
Logger.Log("Background processing sleeping due to active gameplay...");
|
||||||
|
Thread.Sleep(TimeToSleepDuringGameplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
realmAccess.Run(r =>
|
||||||
|
{
|
||||||
|
var set = r.Find<BeatmapSetInfo>(id);
|
||||||
|
|
||||||
|
if (set != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logger.Log($"Background processing {set} ({++i} / {beatmapSetIds.Count})");
|
||||||
|
beatmapUpdater.Process(set);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Log($"Background processing failed on {set}: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -87,7 +87,11 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public string Hash { get; set; } = string.Empty;
|
public string Hash { get; set; } = string.Empty;
|
||||||
|
|
||||||
public double StarRating { get; set; }
|
/// <summary>
|
||||||
|
/// Defaults to -1 (meaning not-yet-calculated).
|
||||||
|
/// Will likely be superseded with a better storage considering ruleset/mods.
|
||||||
|
/// </summary>
|
||||||
|
public double StarRating { get; set; } = -1;
|
||||||
|
|
||||||
[Indexed]
|
[Indexed]
|
||||||
public string MD5Hash { get; set; } = string.Empty;
|
public string MD5Hash { get; set; } = string.Empty;
|
||||||
|
@ -439,12 +439,15 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
if (beatmapInfo != null)
|
if (beatmapInfo != null)
|
||||||
{
|
{
|
||||||
// Detached sets don't come with files.
|
if (refetch)
|
||||||
// If we seem to be missing files, now is a good time to re-fetch.
|
|
||||||
if (refetch || beatmapInfo.IsManaged || beatmapInfo.BeatmapSet?.Files.Count == 0)
|
|
||||||
{
|
|
||||||
workingBeatmapCache.Invalidate(beatmapInfo);
|
workingBeatmapCache.Invalidate(beatmapInfo);
|
||||||
|
|
||||||
|
// Detached beatmapsets don't come with files as an optimisation (see `RealmObjectExtensions.beatmap_set_mapper`).
|
||||||
|
// If we seem to be missing files, now is a good time to re-fetch.
|
||||||
|
bool missingFiles = beatmapInfo.BeatmapSet?.Files.Count == 0;
|
||||||
|
|
||||||
|
if (refetch || beatmapInfo.IsManaged || missingFiles)
|
||||||
|
{
|
||||||
Guid id = beatmapInfo.ID;
|
Guid id = beatmapInfo.ID;
|
||||||
beatmapInfo = Realm.Run(r => r.Find<BeatmapInfo>(id)?.Detach()) ?? beatmapInfo;
|
beatmapInfo = Realm.Run(r => r.Find<BeatmapInfo>(id)?.Detach()) ?? beatmapInfo;
|
||||||
}
|
}
|
||||||
|
@ -151,7 +151,7 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
|
|
||||||
displayedStars.BindValueChanged(s =>
|
displayedStars.BindValueChanged(s =>
|
||||||
{
|
{
|
||||||
starsText.Text = s.NewValue.ToLocalisableString("0.00");
|
starsText.Text = s.NewValue < 0 ? "-" : s.NewValue.ToLocalisableString("0.00");
|
||||||
|
|
||||||
background.Colour = colours.ForStarDifficulty(s.NewValue);
|
background.Colour = colours.ForStarDifficulty(s.NewValue);
|
||||||
|
|
||||||
|
@ -91,6 +91,7 @@ namespace osu.Game.Configuration
|
|||||||
// Input
|
// Input
|
||||||
SetDefault(OsuSetting.MenuCursorSize, 1.0f, 0.5f, 2f, 0.01f);
|
SetDefault(OsuSetting.MenuCursorSize, 1.0f, 0.5f, 2f, 0.01f);
|
||||||
SetDefault(OsuSetting.GameplayCursorSize, 1.0f, 0.1f, 2f, 0.01f);
|
SetDefault(OsuSetting.GameplayCursorSize, 1.0f, 0.1f, 2f, 0.01f);
|
||||||
|
SetDefault(OsuSetting.GameplayCursorDuringTouch, false);
|
||||||
SetDefault(OsuSetting.AutoCursorSize, false);
|
SetDefault(OsuSetting.AutoCursorSize, false);
|
||||||
|
|
||||||
SetDefault(OsuSetting.MouseDisableButtons, false);
|
SetDefault(OsuSetting.MouseDisableButtons, false);
|
||||||
@ -292,6 +293,7 @@ namespace osu.Game.Configuration
|
|||||||
MenuCursorSize,
|
MenuCursorSize,
|
||||||
GameplayCursorSize,
|
GameplayCursorSize,
|
||||||
AutoCursorSize,
|
AutoCursorSize,
|
||||||
|
GameplayCursorDuringTouch,
|
||||||
DimLevel,
|
DimLevel,
|
||||||
BlurLevel,
|
BlurLevel,
|
||||||
LightenDuringBreaks,
|
LightenDuringBreaks,
|
||||||
|
@ -63,8 +63,9 @@ namespace osu.Game.Database
|
|||||||
/// 17 2022-07-16 Added CountryCode to RealmUser.
|
/// 17 2022-07-16 Added CountryCode to RealmUser.
|
||||||
/// 18 2022-07-19 Added OnlineMD5Hash and LastOnlineUpdate to BeatmapInfo.
|
/// 18 2022-07-19 Added OnlineMD5Hash and LastOnlineUpdate to BeatmapInfo.
|
||||||
/// 19 2022-07-19 Added DateSubmitted and DateRanked to BeatmapSetInfo.
|
/// 19 2022-07-19 Added DateSubmitted and DateRanked to BeatmapSetInfo.
|
||||||
|
/// 20 2022-07-21 Added LastAppliedDifficultyVersion to RulesetInfo, changed default value of BeatmapInfo.StarRating to -1.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const int schema_version = 19;
|
private const int schema_version = 20;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
||||||
@ -780,6 +781,15 @@ namespace osu.Game.Database
|
|||||||
case 14:
|
case 14:
|
||||||
foreach (var beatmap in migration.NewRealm.All<BeatmapInfo>())
|
foreach (var beatmap in migration.NewRealm.All<BeatmapInfo>())
|
||||||
beatmap.UserSettings = new BeatmapUserSettings();
|
beatmap.UserSettings = new BeatmapUserSettings();
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 20:
|
||||||
|
// As we now have versioned difficulty calculations, let's reset
|
||||||
|
// all star ratings and have `BackgroundBeatmapProcessor` recalculate them.
|
||||||
|
foreach (var beatmap in migration.NewRealm.All<BeatmapInfo>())
|
||||||
|
beatmap.StarRating = -1;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
92
osu.Game/Graphics/Cursor/GlobalCursorDisplay.cs
Normal file
92
osu.Game/Graphics/Cursor/GlobalCursorDisplay.cs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Input;
|
||||||
|
using osu.Framework.Input.StateChanges;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.Cursor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A container which provides the main <see cref="Cursor.MenuCursor"/>.
|
||||||
|
/// Also handles cases where a more localised cursor is provided by another component (via <see cref="IProvideCursor"/>).
|
||||||
|
/// </summary>
|
||||||
|
public class GlobalCursorDisplay : Container, IProvideCursor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Control whether any cursor should be displayed.
|
||||||
|
/// </summary>
|
||||||
|
internal bool ShowCursor = true;
|
||||||
|
|
||||||
|
public CursorContainer MenuCursor { get; }
|
||||||
|
|
||||||
|
public bool ProvidingUserCursor => true;
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content { get; } = new Container { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
|
private Bindable<bool> showDuringTouch = null!;
|
||||||
|
|
||||||
|
private InputManager inputManager = null!;
|
||||||
|
|
||||||
|
private IProvideCursor? currentOverrideProvider;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuConfigManager config { get; set; } = null!;
|
||||||
|
|
||||||
|
public GlobalCursorDisplay()
|
||||||
|
{
|
||||||
|
AddRangeInternal(new Drawable[]
|
||||||
|
{
|
||||||
|
MenuCursor = new MenuCursor { State = { Value = Visibility.Hidden } },
|
||||||
|
Content = new Container { RelativeSizeAxes = Axes.Both }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
inputManager = GetContainingInputManager();
|
||||||
|
showDuringTouch = config.GetBindable<bool>(OsuSetting.GameplayCursorDuringTouch);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
var lastMouseSource = inputManager.CurrentState.Mouse.LastSource;
|
||||||
|
bool hasValidInput = lastMouseSource != null && (showDuringTouch.Value || lastMouseSource is not ISourcedFromTouch);
|
||||||
|
|
||||||
|
if (!hasValidInput || !ShowCursor)
|
||||||
|
{
|
||||||
|
currentOverrideProvider?.MenuCursor?.Hide();
|
||||||
|
currentOverrideProvider = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IProvideCursor newOverrideProvider = this;
|
||||||
|
|
||||||
|
foreach (var d in inputManager.HoveredDrawables)
|
||||||
|
{
|
||||||
|
if (d is IProvideCursor p && p.ProvidingUserCursor)
|
||||||
|
{
|
||||||
|
newOverrideProvider = p;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentOverrideProvider == newOverrideProvider)
|
||||||
|
return;
|
||||||
|
|
||||||
|
currentOverrideProvider?.MenuCursor?.Hide();
|
||||||
|
newOverrideProvider.MenuCursor?.Show();
|
||||||
|
|
||||||
|
currentOverrideProvider = newOverrideProvider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,10 +17,10 @@ namespace osu.Game.Graphics.Cursor
|
|||||||
/// The cursor provided by this <see cref="IDrawable"/>.
|
/// The cursor provided by this <see cref="IDrawable"/>.
|
||||||
/// May be null if no cursor should be visible.
|
/// May be null if no cursor should be visible.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CursorContainer Cursor { get; }
|
CursorContainer MenuCursor { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether <see cref="Cursor"/> should be displayed as the singular user cursor. This will temporarily hide any other user cursor.
|
/// Whether <see cref="MenuCursor"/> should be displayed as the singular user cursor. This will temporarily hide any other user cursor.
|
||||||
/// This value is checked every frame and may be used to control whether multiple cursors are displayed (e.g. watching replays).
|
/// This value is checked every frame and may be used to control whether multiple cursors are displayed (e.g. watching replays).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool ProvidingUserCursor { get; }
|
bool ProvidingUserCursor { get; }
|
||||||
|
@ -1,83 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Cursor;
|
|
||||||
using osu.Framework.Input;
|
|
||||||
using osu.Framework.Input.StateChanges;
|
|
||||||
|
|
||||||
namespace osu.Game.Graphics.Cursor
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A container which provides a <see cref="MenuCursor"/> which can be overridden by hovered <see cref="Drawable"/>s.
|
|
||||||
/// </summary>
|
|
||||||
public class MenuCursorContainer : Container, IProvideCursor
|
|
||||||
{
|
|
||||||
protected override Container<Drawable> Content => content;
|
|
||||||
private readonly Container content;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether any cursors can be displayed.
|
|
||||||
/// </summary>
|
|
||||||
internal bool CanShowCursor = true;
|
|
||||||
|
|
||||||
public CursorContainer Cursor { get; }
|
|
||||||
public bool ProvidingUserCursor => true;
|
|
||||||
|
|
||||||
public MenuCursorContainer()
|
|
||||||
{
|
|
||||||
AddRangeInternal(new Drawable[]
|
|
||||||
{
|
|
||||||
Cursor = new MenuCursor { State = { Value = Visibility.Hidden } },
|
|
||||||
content = new Container { RelativeSizeAxes = Axes.Both }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private InputManager inputManager;
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
inputManager = GetContainingInputManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
private IProvideCursor currentTarget;
|
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
|
|
||||||
var lastMouseSource = inputManager.CurrentState.Mouse.LastSource;
|
|
||||||
bool hasValidInput = lastMouseSource != null && !(lastMouseSource is ISourcedFromTouch);
|
|
||||||
|
|
||||||
if (!hasValidInput || !CanShowCursor)
|
|
||||||
{
|
|
||||||
currentTarget?.Cursor?.Hide();
|
|
||||||
currentTarget = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IProvideCursor newTarget = this;
|
|
||||||
|
|
||||||
foreach (var d in inputManager.HoveredDrawables)
|
|
||||||
{
|
|
||||||
if (d is IProvideCursor p && p.ProvidingUserCursor)
|
|
||||||
{
|
|
||||||
newTarget = p;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentTarget == newTarget)
|
|
||||||
return;
|
|
||||||
|
|
||||||
currentTarget?.Cursor?.Hide();
|
|
||||||
newTarget.Cursor?.Show();
|
|
||||||
|
|
||||||
currentTarget = newTarget;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,9 +10,8 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Localisation;
|
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Timing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -22,8 +21,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
public class FPSCounter : VisibilityContainer, IHasCustomTooltip
|
public class FPSCounter : VisibilityContainer, IHasCustomTooltip
|
||||||
{
|
{
|
||||||
private RollingCounter<double> counterUpdateFrameTime = null!;
|
private OsuSpriteText counterUpdateFrameTime = null!;
|
||||||
private RollingCounter<double> counterDrawFPS = null!;
|
private OsuSpriteText counterDrawFPS = null!;
|
||||||
|
|
||||||
private Container mainContent = null!;
|
private Container mainContent = null!;
|
||||||
|
|
||||||
@ -31,10 +30,32 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
private Container counters = null!;
|
private Container counters = null!;
|
||||||
|
|
||||||
|
private const double min_time_between_updates = 10;
|
||||||
|
|
||||||
|
private const double spike_time_ms = 20;
|
||||||
|
|
||||||
private const float idle_background_alpha = 0.4f;
|
private const float idle_background_alpha = 0.4f;
|
||||||
|
|
||||||
private readonly BindableBool showFpsDisplay = new BindableBool(true);
|
private readonly BindableBool showFpsDisplay = new BindableBool(true);
|
||||||
|
|
||||||
|
private double displayedFpsCount;
|
||||||
|
private double displayedFrameTime;
|
||||||
|
|
||||||
|
private bool isDisplayed;
|
||||||
|
|
||||||
|
private double aimDrawFPS;
|
||||||
|
private double aimUpdateFPS;
|
||||||
|
|
||||||
|
private double lastUpdate;
|
||||||
|
private ThrottledFrameClock drawClock = null!;
|
||||||
|
private ThrottledFrameClock updateClock = null!;
|
||||||
|
private ThrottledFrameClock inputClock = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The last time value where the display was required (due to a significant change or hovering).
|
||||||
|
/// </summary>
|
||||||
|
private double lastDisplayRequiredTime;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; } = null!;
|
private OsuColour colours { get; set; } = null!;
|
||||||
|
|
||||||
@ -44,7 +65,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuConfigManager config)
|
private void load(OsuConfigManager config, GameHost gameHost)
|
||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -77,20 +98,23 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
counterUpdateFrameTime = new FrameTimeCounter
|
counterUpdateFrameTime = new OsuSpriteText
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopRight,
|
Anchor = Anchor.TopRight,
|
||||||
Origin = Anchor.TopRight,
|
Origin = Anchor.TopRight,
|
||||||
Margin = new MarginPadding(1),
|
Margin = new MarginPadding(1),
|
||||||
|
Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold),
|
||||||
|
Spacing = new Vector2(-1),
|
||||||
Y = -2,
|
Y = -2,
|
||||||
},
|
},
|
||||||
counterDrawFPS = new FramesPerSecondCounter
|
counterDrawFPS = new OsuSpriteText
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopRight,
|
Anchor = Anchor.TopRight,
|
||||||
Origin = Anchor.TopRight,
|
Origin = Anchor.TopRight,
|
||||||
Margin = new MarginPadding(2),
|
Margin = new MarginPadding(2),
|
||||||
|
Font = OsuFont.Default.With(fixedWidth: true, size: 13, weight: FontWeight.SemiBold),
|
||||||
|
Spacing = new Vector2(-2),
|
||||||
Y = 10,
|
Y = 10,
|
||||||
Scale = new Vector2(0.8f),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -99,19 +123,23 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
};
|
};
|
||||||
|
|
||||||
config.BindWith(OsuSetting.ShowFpsDisplay, showFpsDisplay);
|
config.BindWith(OsuSetting.ShowFpsDisplay, showFpsDisplay);
|
||||||
|
|
||||||
|
drawClock = gameHost.DrawThread.Clock;
|
||||||
|
updateClock = gameHost.UpdateThread.Clock;
|
||||||
|
inputClock = gameHost.InputThread.Clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
displayTemporarily();
|
requestDisplay();
|
||||||
|
|
||||||
showFpsDisplay.BindValueChanged(showFps =>
|
showFpsDisplay.BindValueChanged(showFps =>
|
||||||
{
|
{
|
||||||
State.Value = showFps.NewValue ? Visibility.Visible : Visibility.Hidden;
|
State.Value = showFps.NewValue ? Visibility.Visible : Visibility.Hidden;
|
||||||
if (showFps.NewValue)
|
if (showFps.NewValue)
|
||||||
displayTemporarily();
|
requestDisplay();
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
State.BindValueChanged(state => showFpsDisplay.Value = state.NewValue == Visibility.Visible);
|
State.BindValueChanged(state => showFpsDisplay.Value = state.NewValue == Visibility.Visible);
|
||||||
@ -124,48 +152,17 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
background.FadeTo(1, 200);
|
background.FadeTo(1, 200);
|
||||||
displayTemporarily();
|
requestDisplay();
|
||||||
return base.OnHover(e);
|
return base.OnHover(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnHoverLost(HoverLostEvent e)
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
{
|
{
|
||||||
background.FadeTo(idle_background_alpha, 200);
|
background.FadeTo(idle_background_alpha, 200);
|
||||||
displayTemporarily();
|
requestDisplay();
|
||||||
base.OnHoverLost(e);
|
base.OnHoverLost(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool isDisplayed;
|
|
||||||
|
|
||||||
private ScheduledDelegate? fadeOutDelegate;
|
|
||||||
|
|
||||||
private double aimDrawFPS;
|
|
||||||
private double aimUpdateFPS;
|
|
||||||
|
|
||||||
private void displayTemporarily()
|
|
||||||
{
|
|
||||||
if (!isDisplayed)
|
|
||||||
{
|
|
||||||
mainContent.FadeTo(1, 300, Easing.OutQuint);
|
|
||||||
isDisplayed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fadeOutDelegate?.Cancel();
|
|
||||||
fadeOutDelegate = null;
|
|
||||||
|
|
||||||
if (!IsHovered)
|
|
||||||
{
|
|
||||||
fadeOutDelegate = Scheduler.AddDelayed(() =>
|
|
||||||
{
|
|
||||||
mainContent.FadeTo(0, 300, Easing.OutQuint);
|
|
||||||
isDisplayed = false;
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private GameHost gameHost { get; set; } = null!;
|
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
@ -176,50 +173,75 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
// frame limiter (we want to show the FPS as it's changing, even if it isn't an outlier).
|
// frame limiter (we want to show the FPS as it's changing, even if it isn't an outlier).
|
||||||
bool aimRatesChanged = updateAimFPS();
|
bool aimRatesChanged = updateAimFPS();
|
||||||
|
|
||||||
// TODO: this is wrong (elapsed clock time, not actual run time).
|
bool hasUpdateSpike = displayedFrameTime < spike_time_ms && updateClock.ElapsedFrameTime > spike_time_ms;
|
||||||
double newUpdateFrameTime = gameHost.UpdateThread.Clock.ElapsedFrameTime;
|
|
||||||
double newDrawFrameTime = gameHost.DrawThread.Clock.ElapsedFrameTime;
|
|
||||||
double newDrawFps = gameHost.DrawThread.Clock.FramesPerSecond;
|
|
||||||
|
|
||||||
const double spike_time_ms = 20;
|
|
||||||
|
|
||||||
bool hasUpdateSpike = counterUpdateFrameTime.Current.Value < spike_time_ms && newUpdateFrameTime > spike_time_ms;
|
|
||||||
// use elapsed frame time rather then FramesPerSecond to better catch stutter frames.
|
// use elapsed frame time rather then FramesPerSecond to better catch stutter frames.
|
||||||
bool hasDrawSpike = counterDrawFPS.Current.Value > (1000 / spike_time_ms) && newDrawFrameTime > spike_time_ms;
|
bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && drawClock.ElapsedFrameTime > spike_time_ms;
|
||||||
|
|
||||||
// If the frame time spikes up, make sure it shows immediately on the counter.
|
// note that we use an elapsed time here of 1 intentionally.
|
||||||
if (hasUpdateSpike)
|
// this weights all updates equally. if we passed in the elapsed time, longer frames would be weighted incorrectly lower.
|
||||||
counterUpdateFrameTime.SetCountWithoutRolling(newUpdateFrameTime);
|
displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, updateClock.ElapsedFrameTime, hasUpdateSpike ? 0 : 100, 1);
|
||||||
else
|
|
||||||
counterUpdateFrameTime.Current.Value = newUpdateFrameTime;
|
|
||||||
|
|
||||||
if (hasDrawSpike)
|
if (hasDrawSpike)
|
||||||
// show spike time using raw elapsed value, to account for `FramesPerSecond` being so averaged spike frames don't show.
|
// show spike time using raw elapsed value, to account for `FramesPerSecond` being so averaged spike frames don't show.
|
||||||
counterDrawFPS.SetCountWithoutRolling(1000 / newDrawFrameTime);
|
displayedFpsCount = 1000 / drawClock.ElapsedFrameTime;
|
||||||
else
|
else
|
||||||
counterDrawFPS.Current.Value = newDrawFps;
|
displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, drawClock.FramesPerSecond, 100, Time.Elapsed);
|
||||||
|
|
||||||
counterDrawFPS.Colour = getColour(counterDrawFPS.DisplayedCount / aimDrawFPS);
|
if (Time.Current - lastUpdate > min_time_between_updates)
|
||||||
|
{
|
||||||
|
updateFpsDisplay();
|
||||||
|
updateFrameTimeDisplay();
|
||||||
|
|
||||||
double displayedUpdateFPS = 1000 / counterUpdateFrameTime.DisplayedCount;
|
lastUpdate = Time.Current;
|
||||||
counterUpdateFrameTime.Colour = getColour(displayedUpdateFPS / aimUpdateFPS);
|
}
|
||||||
|
|
||||||
bool hasSignificantChanges = aimRatesChanged
|
bool hasSignificantChanges = aimRatesChanged
|
||||||
|| hasDrawSpike
|
|| hasDrawSpike
|
||||||
|| hasUpdateSpike
|
|| hasUpdateSpike
|
||||||
|| counterDrawFPS.DisplayedCount < aimDrawFPS * 0.8
|
|| displayedFpsCount < aimDrawFPS * 0.8
|
||||||
|| displayedUpdateFPS < aimUpdateFPS * 0.8;
|
|| 1000 / displayedFrameTime < aimUpdateFPS * 0.8;
|
||||||
|
|
||||||
if (hasSignificantChanges)
|
if (hasSignificantChanges)
|
||||||
displayTemporarily();
|
requestDisplay();
|
||||||
|
else if (isDisplayed && Time.Current - lastDisplayRequiredTime > 2000)
|
||||||
|
{
|
||||||
|
mainContent.FadeTo(0, 300, Easing.OutQuint);
|
||||||
|
isDisplayed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestDisplay()
|
||||||
|
{
|
||||||
|
lastDisplayRequiredTime = Time.Current;
|
||||||
|
|
||||||
|
if (!isDisplayed)
|
||||||
|
{
|
||||||
|
mainContent.FadeTo(1, 300, Easing.OutQuint);
|
||||||
|
isDisplayed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateFpsDisplay()
|
||||||
|
{
|
||||||
|
counterDrawFPS.Colour = getColour(displayedFpsCount / aimDrawFPS);
|
||||||
|
counterDrawFPS.Text = $"{displayedFpsCount:#,0}fps";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateFrameTimeDisplay()
|
||||||
|
{
|
||||||
|
counterUpdateFrameTime.Text = displayedFrameTime < 5
|
||||||
|
? $"{displayedFrameTime:N1}ms"
|
||||||
|
: $"{displayedFrameTime:N0}ms";
|
||||||
|
|
||||||
|
counterUpdateFrameTime.Colour = getColour((1000 / displayedFrameTime) / aimUpdateFPS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool updateAimFPS()
|
private bool updateAimFPS()
|
||||||
{
|
{
|
||||||
if (gameHost.UpdateThread.Clock.Throttling)
|
if (updateClock.Throttling)
|
||||||
{
|
{
|
||||||
double newAimDrawFPS = gameHost.DrawThread.Clock.MaximumUpdateHz;
|
double newAimDrawFPS = drawClock.MaximumUpdateHz;
|
||||||
double newAimUpdateFPS = gameHost.UpdateThread.Clock.MaximumUpdateHz;
|
double newAimUpdateFPS = updateClock.MaximumUpdateHz;
|
||||||
|
|
||||||
if (aimDrawFPS != newAimDrawFPS || aimUpdateFPS != newAimUpdateFPS)
|
if (aimDrawFPS != newAimDrawFPS || aimUpdateFPS != newAimUpdateFPS)
|
||||||
{
|
{
|
||||||
@ -230,7 +252,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
double newAimFPS = gameHost.InputThread.Clock.MaximumUpdateHz;
|
double newAimFPS = inputClock.MaximumUpdateHz;
|
||||||
|
|
||||||
if (aimDrawFPS != newAimFPS || aimUpdateFPS != newAimFPS)
|
if (aimDrawFPS != newAimFPS || aimUpdateFPS != newAimFPS)
|
||||||
{
|
{
|
||||||
@ -253,50 +275,5 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
public ITooltip GetCustomTooltip() => new FPSCounterTooltip();
|
public ITooltip GetCustomTooltip() => new FPSCounterTooltip();
|
||||||
|
|
||||||
public object TooltipContent => this;
|
public object TooltipContent => this;
|
||||||
|
|
||||||
public class FramesPerSecondCounter : RollingCounter<double>
|
|
||||||
{
|
|
||||||
protected override double RollingDuration => 1000;
|
|
||||||
|
|
||||||
protected override OsuSpriteText CreateSpriteText()
|
|
||||||
{
|
|
||||||
return new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopRight,
|
|
||||||
Origin = Anchor.TopRight,
|
|
||||||
Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold),
|
|
||||||
Spacing = new Vector2(-2),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override LocalisableString FormatCount(double count)
|
|
||||||
{
|
|
||||||
return $"{count:#,0}fps";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FrameTimeCounter : RollingCounter<double>
|
|
||||||
{
|
|
||||||
protected override double RollingDuration => 1000;
|
|
||||||
|
|
||||||
protected override OsuSpriteText CreateSpriteText()
|
|
||||||
{
|
|
||||||
return new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopRight,
|
|
||||||
Origin = Anchor.TopRight,
|
|
||||||
Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold),
|
|
||||||
Spacing = new Vector2(-1),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override LocalisableString FormatCount(double count)
|
|
||||||
{
|
|
||||||
if (count < 1)
|
|
||||||
return $"{count:N1}ms";
|
|
||||||
|
|
||||||
return $"{count:N0}ms";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -228,10 +228,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
protected override void UpdateAfterChildren()
|
protected override void UpdateAfterChildren()
|
||||||
{
|
{
|
||||||
base.UpdateAfterChildren();
|
base.UpdateAfterChildren();
|
||||||
LeftBox.Scale = new Vector2(Math.Clamp(
|
LeftBox.Scale = new Vector2(Math.Clamp(RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, Math.Max(0, DrawWidth)), 1);
|
||||||
RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1);
|
RightBox.Scale = new Vector2(Math.Clamp(DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2, 0, Math.Max(0, DrawWidth)), 1);
|
||||||
RightBox.Scale = new Vector2(Math.Clamp(
|
|
||||||
DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2, 0, DrawWidth), 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateValue(float value)
|
protected override void UpdateValue(float value)
|
||||||
|
@ -24,6 +24,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString ModCustomisation => new TranslatableString(getKey(@"mod_customisation"), @"Mod Customisation");
|
public static LocalisableString ModCustomisation => new TranslatableString(getKey(@"mod_customisation"), @"Mod Customisation");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Personal Presets"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString PersonalPresets => new TranslatableString(getKey(@"personal_presets"), @"Personal Presets");
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString AutoCursorSize => new TranslatableString(getKey(@"auto_cursor_size"), @"Adjust gameplay cursor size based on current beatmap");
|
public static LocalisableString AutoCursorSize => new TranslatableString(getKey(@"auto_cursor_size"), @"Adjust gameplay cursor size based on current beatmap");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Show gameplay cursor during touch input"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString GameplayCursorDuringTouch => new TranslatableString(getKey(@"gameplay_cursor_during_touch"), @"Show gameplay cursor during touch input");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Beatmap skins"
|
/// "Beatmap skins"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1,162 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Converters;
|
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Database;
|
|
||||||
using osu.Game.Rulesets;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Scoring;
|
|
||||||
using osu.Game.Scoring.Legacy;
|
|
||||||
using osu.Game.Users;
|
|
||||||
|
|
||||||
namespace osu.Game.Online.API.Requests.Responses
|
|
||||||
{
|
|
||||||
public class APIScore : IScoreInfo
|
|
||||||
{
|
|
||||||
[JsonProperty(@"score")]
|
|
||||||
public long TotalScore { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(@"max_combo")]
|
|
||||||
public int MaxCombo { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(@"user")]
|
|
||||||
public APIUser User { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(@"id")]
|
|
||||||
public long OnlineID { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(@"replay")]
|
|
||||||
public bool HasReplay { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(@"created_at")]
|
|
||||||
public DateTimeOffset Date { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(@"beatmap")]
|
|
||||||
[CanBeNull]
|
|
||||||
public APIBeatmap Beatmap { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("accuracy")]
|
|
||||||
public double Accuracy { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(@"pp")]
|
|
||||||
public double? PP { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(@"beatmapset")]
|
|
||||||
[CanBeNull]
|
|
||||||
public APIBeatmapSet BeatmapSet
|
|
||||||
{
|
|
||||||
set
|
|
||||||
{
|
|
||||||
// in the deserialisation case we need to ferry this data across.
|
|
||||||
// the order of properties returned by the API guarantees that the beatmap is populated by this point.
|
|
||||||
if (!(Beatmap is APIBeatmap apiBeatmap))
|
|
||||||
throw new InvalidOperationException("Beatmap set metadata arrived before beatmap metadata in response");
|
|
||||||
|
|
||||||
apiBeatmap.BeatmapSet = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonProperty("statistics")]
|
|
||||||
public Dictionary<string, int> Statistics { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(@"mode_int")]
|
|
||||||
public int RulesetID { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(@"mods")]
|
|
||||||
private string[] mods { set => Mods = value.Select(acronym => new APIMod { Acronym = acronym }); }
|
|
||||||
|
|
||||||
[NotNull]
|
|
||||||
public IEnumerable<APIMod> Mods { get; set; } = Array.Empty<APIMod>();
|
|
||||||
|
|
||||||
[JsonProperty("rank")]
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
|
||||||
public ScoreRank Rank { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a <see cref="ScoreInfo"/> from an API score instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="rulesets">A ruleset store, used to populate a ruleset instance in the returned score.</param>
|
|
||||||
/// <param name="beatmap">An optional beatmap, copied into the returned score (for cases where the API does not populate the beatmap).</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null)
|
|
||||||
{
|
|
||||||
var ruleset = rulesets.GetRuleset(RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {RulesetID} not found locally");
|
|
||||||
|
|
||||||
var rulesetInstance = ruleset.CreateInstance();
|
|
||||||
|
|
||||||
var modInstances = Mods.Select(apiMod => rulesetInstance.CreateModFromAcronym(apiMod.Acronym)).Where(m => m != null).ToArray();
|
|
||||||
|
|
||||||
// all API scores provided by this class are considered to be legacy.
|
|
||||||
modInstances = modInstances.Append(rulesetInstance.CreateMod<ModClassic>()).ToArray();
|
|
||||||
|
|
||||||
var scoreInfo = new ScoreInfo
|
|
||||||
{
|
|
||||||
TotalScore = TotalScore,
|
|
||||||
MaxCombo = MaxCombo,
|
|
||||||
BeatmapInfo = beatmap ?? new BeatmapInfo(),
|
|
||||||
User = User,
|
|
||||||
Accuracy = Accuracy,
|
|
||||||
OnlineID = OnlineID,
|
|
||||||
Date = Date,
|
|
||||||
PP = PP,
|
|
||||||
Hash = HasReplay ? "online" : string.Empty, // todo: temporary?
|
|
||||||
Rank = Rank,
|
|
||||||
Ruleset = ruleset,
|
|
||||||
Mods = modInstances,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (Statistics != null)
|
|
||||||
{
|
|
||||||
foreach (var kvp in Statistics)
|
|
||||||
{
|
|
||||||
switch (kvp.Key)
|
|
||||||
{
|
|
||||||
case @"count_geki":
|
|
||||||
scoreInfo.SetCountGeki(kvp.Value);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case @"count_300":
|
|
||||||
scoreInfo.SetCount300(kvp.Value);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case @"count_katu":
|
|
||||||
scoreInfo.SetCountKatu(kvp.Value);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case @"count_100":
|
|
||||||
scoreInfo.SetCount100(kvp.Value);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case @"count_50":
|
|
||||||
scoreInfo.SetCount50(kvp.Value);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case @"count_miss":
|
|
||||||
scoreInfo.SetCountMiss(kvp.Value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return scoreInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IRulesetInfo Ruleset => new RulesetInfo { OnlineID = RulesetID };
|
|
||||||
IEnumerable<INamedFileUsage> IHasNamedFiles.Files => throw new NotImplementedException();
|
|
||||||
|
|
||||||
#region Implementation of IScoreInfo
|
|
||||||
|
|
||||||
IBeatmapInfo IScoreInfo.Beatmap => Beatmap;
|
|
||||||
IUser IScoreInfo.User => User;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
@ -151,6 +151,23 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
PP = PP,
|
PP = PP,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a <see cref="SoloScoreInfo"/> from a local score for score submission.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="score">The local score.</param>
|
||||||
|
public static SoloScoreInfo ForSubmission(ScoreInfo score) => new SoloScoreInfo
|
||||||
|
{
|
||||||
|
Rank = score.Rank,
|
||||||
|
TotalScore = (int)score.TotalScore,
|
||||||
|
Accuracy = score.Accuracy,
|
||||||
|
PP = score.PP,
|
||||||
|
MaxCombo = score.MaxCombo,
|
||||||
|
RulesetID = score.RulesetID,
|
||||||
|
Passed = score.Passed,
|
||||||
|
Mods = score.APIMods,
|
||||||
|
Statistics = score.Statistics,
|
||||||
|
};
|
||||||
|
|
||||||
public long OnlineID => ID ?? -1;
|
public long OnlineID => ID ?? -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,20 +7,20 @@ using System.Net.Http;
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using osu.Framework.IO.Network;
|
using osu.Framework.IO.Network;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.Solo;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Online.Rooms
|
namespace osu.Game.Online.Rooms
|
||||||
{
|
{
|
||||||
public abstract class SubmitScoreRequest : APIRequest<MultiplayerScore>
|
public abstract class SubmitScoreRequest : APIRequest<MultiplayerScore>
|
||||||
{
|
{
|
||||||
public readonly SubmittableScore Score;
|
public readonly SoloScoreInfo Score;
|
||||||
|
|
||||||
protected readonly long ScoreId;
|
protected readonly long ScoreId;
|
||||||
|
|
||||||
protected SubmitScoreRequest(ScoreInfo scoreInfo, long scoreId)
|
protected SubmitScoreRequest(ScoreInfo scoreInfo, long scoreId)
|
||||||
{
|
{
|
||||||
Score = new SubmittableScore(scoreInfo);
|
Score = SoloScoreInfo.ForSubmission(scoreInfo);
|
||||||
ScoreId = scoreId;
|
ScoreId = scoreId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Converters;
|
|
||||||
using osu.Game.Online.API;
|
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
using osu.Game.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Online.Solo
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A class specifically for sending scores to the API during score submission.
|
|
||||||
/// This is used instead of <see cref="APIScore"/> due to marginally different serialisation naming requirements.
|
|
||||||
/// </summary>
|
|
||||||
[Serializable]
|
|
||||||
public class SubmittableScore
|
|
||||||
{
|
|
||||||
[JsonProperty("rank")]
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
|
||||||
public ScoreRank Rank { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("total_score")]
|
|
||||||
public long TotalScore { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("accuracy")]
|
|
||||||
public double Accuracy { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(@"pp")]
|
|
||||||
public double? PP { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("max_combo")]
|
|
||||||
public int MaxCombo { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("ruleset_id")]
|
|
||||||
public int RulesetID { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("passed")]
|
|
||||||
public bool Passed { get; set; }
|
|
||||||
|
|
||||||
// Used for API serialisation/deserialisation.
|
|
||||||
[JsonProperty("mods")]
|
|
||||||
public APIMod[] Mods { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("statistics")]
|
|
||||||
public Dictionary<HitResult, int> Statistics { get; set; }
|
|
||||||
|
|
||||||
[UsedImplicitly]
|
|
||||||
public SubmittableScore()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public SubmittableScore(ScoreInfo score)
|
|
||||||
{
|
|
||||||
Rank = score.Rank;
|
|
||||||
TotalScore = score.TotalScore;
|
|
||||||
Accuracy = score.Accuracy;
|
|
||||||
PP = score.PP;
|
|
||||||
MaxCombo = score.MaxCombo;
|
|
||||||
RulesetID = score.RulesetID;
|
|
||||||
Passed = score.Passed;
|
|
||||||
Mods = score.APIMods;
|
|
||||||
Statistics = score.Statistics;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -206,6 +206,11 @@ namespace osu.Game.Online.Spectator
|
|||||||
if (!IsPlaying)
|
if (!IsPlaying)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Disposal can take some time, leading to EndPlaying potentially being called after a future play session.
|
||||||
|
// Account for this by ensuring the score of the current play matches the one in the provided state.
|
||||||
|
if (currentScore != state.Score)
|
||||||
|
return;
|
||||||
|
|
||||||
if (pendingFrames.Count > 0)
|
if (pendingFrames.Count > 0)
|
||||||
purgePendingFrames();
|
purgePendingFrames();
|
||||||
|
|
||||||
|
@ -716,7 +716,7 @@ namespace osu.Game
|
|||||||
// The next time this is updated is in UpdateAfterChildren, which occurs too late and results
|
// The next time this is updated is in UpdateAfterChildren, which occurs too late and results
|
||||||
// in the cursor being shown for a few frames during the intro.
|
// in the cursor being shown for a few frames during the intro.
|
||||||
// This prevents the cursor from showing until we have a screen with CursorVisible = true
|
// This prevents the cursor from showing until we have a screen with CursorVisible = true
|
||||||
MenuCursorContainer.CanShowCursor = menuScreen?.CursorVisible ?? false;
|
GlobalCursorDisplay.ShowCursor = menuScreen?.CursorVisible ?? false;
|
||||||
|
|
||||||
// todo: all archive managers should be able to be looped here.
|
// todo: all archive managers should be able to be looped here.
|
||||||
SkinManager.PostNotification = n => Notifications.Post(n);
|
SkinManager.PostNotification = n => Notifications.Post(n);
|
||||||
@ -904,6 +904,8 @@ namespace osu.Game
|
|||||||
|
|
||||||
loadComponentSingleFile(CreateHighPerformanceSession(), Add);
|
loadComponentSingleFile(CreateHighPerformanceSession(), Add);
|
||||||
|
|
||||||
|
loadComponentSingleFile(new BackgroundBeatmapProcessor(), Add);
|
||||||
|
|
||||||
chatOverlay.State.BindValueChanged(_ => updateChatPollRate());
|
chatOverlay.State.BindValueChanged(_ => updateChatPollRate());
|
||||||
// Multiplayer modes need to increase poll rate temporarily.
|
// Multiplayer modes need to increase poll rate temporarily.
|
||||||
API.Activity.BindValueChanged(_ => updateChatPollRate(), true);
|
API.Activity.BindValueChanged(_ => updateChatPollRate(), true);
|
||||||
@ -1229,7 +1231,7 @@ namespace osu.Game
|
|||||||
ScreenOffsetContainer.X = horizontalOffset;
|
ScreenOffsetContainer.X = horizontalOffset;
|
||||||
overlayContent.X = horizontalOffset * 1.2f;
|
overlayContent.X = horizontalOffset * 1.2f;
|
||||||
|
|
||||||
MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false;
|
GlobalCursorDisplay.ShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void screenChanged(IScreen current, IScreen newScreen)
|
private void screenChanged(IScreen current, IScreen newScreen)
|
||||||
|
@ -138,7 +138,7 @@ namespace osu.Game
|
|||||||
|
|
||||||
protected RealmKeyBindingStore KeyBindingStore { get; private set; }
|
protected RealmKeyBindingStore KeyBindingStore { get; private set; }
|
||||||
|
|
||||||
protected MenuCursorContainer MenuCursorContainer { get; private set; }
|
protected GlobalCursorDisplay GlobalCursorDisplay { get; private set; }
|
||||||
|
|
||||||
protected MusicController MusicController { get; private set; }
|
protected MusicController MusicController { get; private set; }
|
||||||
|
|
||||||
@ -280,8 +280,7 @@ namespace osu.Game
|
|||||||
AddInternal(difficultyCache);
|
AddInternal(difficultyCache);
|
||||||
|
|
||||||
// TODO: OsuGame or OsuGameBase?
|
// TODO: OsuGame or OsuGameBase?
|
||||||
beatmapUpdater = new BeatmapUpdater(BeatmapManager, difficultyCache, API, Storage);
|
dependencies.CacheAs(beatmapUpdater = new BeatmapUpdater(BeatmapManager, difficultyCache, API, Storage));
|
||||||
|
|
||||||
dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints));
|
dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints));
|
||||||
dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints));
|
dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints));
|
||||||
dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints));
|
dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints));
|
||||||
@ -341,10 +340,10 @@ namespace osu.Game
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = CreateScalingContainer().WithChildren(new Drawable[]
|
Child = CreateScalingContainer().WithChildren(new Drawable[]
|
||||||
{
|
{
|
||||||
(MenuCursorContainer = new MenuCursorContainer
|
(GlobalCursorDisplay = new GlobalCursorDisplay
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
}).WithChild(content = new OsuTooltipContainer(MenuCursorContainer.Cursor)
|
}).WithChild(content = new OsuTooltipContainer(GlobalCursorDisplay.MenuCursor)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
}),
|
}),
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
@ -25,6 +24,7 @@ using osu.Framework.Localisation;
|
|||||||
using osu.Framework.Extensions.LocalisationExtensions;
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
using osu.Game.Scoring.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.BeatmapSet.Scores
|
namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||||
{
|
{
|
||||||
@ -179,8 +179,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
|
|
||||||
if (showPerformancePoints)
|
if (showPerformancePoints)
|
||||||
{
|
{
|
||||||
Debug.Assert(score.PP != null);
|
if (score.PP != null)
|
||||||
content.Add(new StatisticText(score.PP.Value, format: @"N0"));
|
content.Add(new StatisticText(score.PP, format: @"N0"));
|
||||||
|
else
|
||||||
|
content.Add(new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(text_size) });
|
||||||
}
|
}
|
||||||
|
|
||||||
content.Add(new ScoreboardTime(score.Date, text_size)
|
content.Add(new ScoreboardTime(score.Date, text_size)
|
||||||
@ -222,19 +224,19 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
|
|
||||||
private class StatisticText : OsuSpriteText, IHasTooltip
|
private class StatisticText : OsuSpriteText, IHasTooltip
|
||||||
{
|
{
|
||||||
private readonly double count;
|
private readonly double? count;
|
||||||
private readonly double? maxCount;
|
private readonly double? maxCount;
|
||||||
private readonly bool showTooltip;
|
private readonly bool showTooltip;
|
||||||
|
|
||||||
public LocalisableString TooltipText => maxCount == null || !showTooltip ? string.Empty : $"{count}/{maxCount}";
|
public LocalisableString TooltipText => maxCount == null || !showTooltip ? string.Empty : $"{count}/{maxCount}";
|
||||||
|
|
||||||
public StatisticText(double count, double? maxCount = null, string format = null, bool showTooltip = true)
|
public StatisticText(double? count, double? maxCount = null, string format = null, bool showTooltip = true)
|
||||||
{
|
{
|
||||||
this.count = count;
|
this.count = count;
|
||||||
this.maxCount = maxCount;
|
this.maxCount = maxCount;
|
||||||
this.showTooltip = showTooltip;
|
this.showTooltip = showTooltip;
|
||||||
|
|
||||||
Text = count.ToLocalisableString(format);
|
Text = count?.ToLocalisableString(format) ?? default;
|
||||||
Font = OsuFont.GetFont(size: text_size);
|
Font = OsuFont.GetFont(size: text_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,14 +12,17 @@ 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.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Scoring.Drawables;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.BeatmapSet.Scores
|
namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||||
@ -121,7 +124,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
maxComboColumn.Text = value.MaxCombo.ToLocalisableString(@"0\x");
|
maxComboColumn.Text = value.MaxCombo.ToLocalisableString(@"0\x");
|
||||||
|
|
||||||
ppColumn.Alpha = value.BeatmapInfo.Status.GrantsPerformancePoints() ? 1 : 0;
|
ppColumn.Alpha = value.BeatmapInfo.Status.GrantsPerformancePoints() ? 1 : 0;
|
||||||
ppColumn.Text = value.PP?.ToLocalisableString(@"N0") ?? default;
|
|
||||||
|
if (value.PP is double pp)
|
||||||
|
ppColumn.Text = pp.ToLocalisableString(@"N0");
|
||||||
|
else
|
||||||
|
ppColumn.Drawable = new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(smallFont.Size) };
|
||||||
|
|
||||||
statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn);
|
statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn);
|
||||||
modsColumn.Mods = value.Mods;
|
modsColumn.Mods = value.Mods;
|
||||||
@ -197,30 +204,48 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TextColumn : InfoColumn
|
private class TextColumn : InfoColumn, IHasCurrentValue<string>
|
||||||
{
|
{
|
||||||
private readonly SpriteText text;
|
private readonly OsuTextFlowContainer text;
|
||||||
|
|
||||||
public TextColumn(LocalisableString title, FontUsage font, float? minWidth = null)
|
|
||||||
: this(title, new OsuSpriteText { Font = font }, minWidth)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private TextColumn(LocalisableString title, SpriteText text, float? minWidth = null)
|
|
||||||
: base(title, text, minWidth)
|
|
||||||
{
|
|
||||||
this.text = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LocalisableString Text
|
public LocalisableString Text
|
||||||
{
|
{
|
||||||
set => text.Text = value;
|
set => text.Text = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Drawable Drawable
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
text.Clear();
|
||||||
|
text.AddArbitraryDrawable(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bindable<string> current;
|
||||||
|
|
||||||
public Bindable<string> Current
|
public Bindable<string> Current
|
||||||
{
|
{
|
||||||
get => text.Current;
|
get => current;
|
||||||
set => text.Current = value;
|
set
|
||||||
|
{
|
||||||
|
text.Clear();
|
||||||
|
text.AddText(value.Value, t => t.Current = current = value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextColumn(LocalisableString title, FontUsage font, float? minWidth = null)
|
||||||
|
: this(title, new OsuTextFlowContainer(t => t.Font = font)
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both
|
||||||
|
}, minWidth)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextColumn(LocalisableString title, OsuTextFlowContainer text, float? minWidth = null)
|
||||||
|
: base(title, text, minWidth)
|
||||||
|
{
|
||||||
|
this.text = text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,10 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -12,6 +15,8 @@ using osu.Game.Online.API.Requests.Responses;
|
|||||||
using osu.Game.Overlays.BeatmapSet;
|
using osu.Game.Overlays.BeatmapSet;
|
||||||
using osu.Game.Overlays.BeatmapSet.Scores;
|
using osu.Game.Overlays.BeatmapSet.Scores;
|
||||||
using osu.Game.Overlays.Comments;
|
using osu.Game.Overlays.Comments;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Screens.Select.Details;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -25,6 +30,14 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
private readonly Bindable<APIBeatmapSet> beatmapSet = new Bindable<APIBeatmapSet>();
|
private readonly Bindable<APIBeatmapSet> beatmapSet = new Bindable<APIBeatmapSet>();
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// Isolates the beatmap set overlay from the game-wide selected mods bindable
|
||||||
|
/// to avoid affecting the beatmap details section (i.e. <see cref="AdvancedStats.StatisticRow"/>).
|
||||||
|
/// </remarks>
|
||||||
|
[Cached]
|
||||||
|
[Cached(typeof(IBindable<IReadOnlyList<Mod>>))]
|
||||||
|
protected readonly Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||||
|
|
||||||
public BeatmapSetOverlay()
|
public BeatmapSetOverlay()
|
||||||
: base(OverlayColourScheme.Blue)
|
: base(OverlayColourScheme.Blue)
|
||||||
{
|
{
|
||||||
|
@ -60,7 +60,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
AutoSizeAxes = Axes.X,
|
AutoSizeAxes = Axes.X,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
CornerRadius = ModPanel.CORNER_RADIUS,
|
CornerRadius = ModSelectPanel.CORNER_RADIUS,
|
||||||
Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0),
|
Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0),
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -69,7 +69,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
Width = multiplier_value_area_width + ModPanel.CORNER_RADIUS
|
Width = multiplier_value_area_width + ModSelectPanel.CORNER_RADIUS
|
||||||
},
|
},
|
||||||
new GridContainer
|
new GridContainer
|
||||||
{
|
{
|
||||||
@ -89,7 +89,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
AutoSizeAxes = Axes.X,
|
AutoSizeAxes = Axes.X,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
CornerRadius = ModPanel.CORNER_RADIUS,
|
CornerRadius = ModSelectPanel.CORNER_RADIUS,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
contentBackground = new Box
|
contentBackground = new Box
|
||||||
|
@ -12,14 +12,10 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Overlays.Mods.Input;
|
using osu.Game.Overlays.Mods.Input;
|
||||||
@ -29,10 +25,8 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Overlays.Mods
|
namespace osu.Game.Overlays.Mods
|
||||||
{
|
{
|
||||||
public class ModColumn : CompositeDrawable
|
public class ModColumn : ModSelectColumn
|
||||||
{
|
{
|
||||||
public readonly Container TopLevelContent;
|
|
||||||
|
|
||||||
public readonly ModType ModType;
|
public readonly ModType ModType;
|
||||||
|
|
||||||
private IReadOnlyList<ModState> availableMods = Array.Empty<ModState>();
|
private IReadOnlyList<ModState> availableMods = Array.Empty<ModState>();
|
||||||
@ -62,149 +56,29 @@ namespace osu.Game.Overlays.Mods
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether this column should accept user input.
|
|
||||||
/// </summary>
|
|
||||||
public Bindable<bool> Active = new BindableBool(true);
|
|
||||||
|
|
||||||
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && Active.Value;
|
|
||||||
|
|
||||||
protected virtual ModPanel CreateModPanel(ModState mod) => new ModPanel(mod);
|
protected virtual ModPanel CreateModPanel(ModState mod) => new ModPanel(mod);
|
||||||
|
|
||||||
private readonly bool allowIncompatibleSelection;
|
private readonly bool allowIncompatibleSelection;
|
||||||
|
|
||||||
private readonly TextFlowContainer headerText;
|
|
||||||
private readonly Box headerBackground;
|
|
||||||
private readonly Container contentContainer;
|
|
||||||
private readonly Box contentBackground;
|
|
||||||
private readonly FillFlowContainer<ModPanel> panelFlow;
|
|
||||||
private readonly ToggleAllCheckbox? toggleAllCheckbox;
|
private readonly ToggleAllCheckbox? toggleAllCheckbox;
|
||||||
|
|
||||||
private Colour4 accentColour;
|
|
||||||
|
|
||||||
private Bindable<ModSelectHotkeyStyle> hotkeyStyle = null!;
|
private Bindable<ModSelectHotkeyStyle> hotkeyStyle = null!;
|
||||||
private IModHotkeyHandler hotkeyHandler = null!;
|
private IModHotkeyHandler hotkeyHandler = null!;
|
||||||
|
|
||||||
private Task? latestLoadTask;
|
private Task? latestLoadTask;
|
||||||
internal bool ItemsLoaded => latestLoadTask == null;
|
internal bool ItemsLoaded => latestLoadTask == null;
|
||||||
|
|
||||||
private const float header_height = 42;
|
|
||||||
|
|
||||||
public ModColumn(ModType modType, bool allowIncompatibleSelection)
|
public ModColumn(ModType modType, bool allowIncompatibleSelection)
|
||||||
{
|
{
|
||||||
ModType = modType;
|
ModType = modType;
|
||||||
this.allowIncompatibleSelection = allowIncompatibleSelection;
|
this.allowIncompatibleSelection = allowIncompatibleSelection;
|
||||||
|
|
||||||
Width = 320;
|
HeaderText = ModType.Humanize(LetterCasing.Title);
|
||||||
RelativeSizeAxes = Axes.Y;
|
|
||||||
Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0);
|
|
||||||
|
|
||||||
Container controlContainer;
|
|
||||||
InternalChildren = new Drawable[]
|
|
||||||
{
|
|
||||||
TopLevelContent = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
CornerRadius = ModPanel.CORNER_RADIUS,
|
|
||||||
Masking = true,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = header_height + ModPanel.CORNER_RADIUS,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
headerBackground = new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = header_height + ModPanel.CORNER_RADIUS
|
|
||||||
},
|
|
||||||
headerText = new OsuTextFlowContainer(t =>
|
|
||||||
{
|
|
||||||
t.Font = OsuFont.TorusAlternate.With(size: 17);
|
|
||||||
t.Shadow = false;
|
|
||||||
t.Colour = Colour4.Black;
|
|
||||||
})
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
|
||||||
Padding = new MarginPadding
|
|
||||||
{
|
|
||||||
Horizontal = 17,
|
|
||||||
Bottom = ModPanel.CORNER_RADIUS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Padding = new MarginPadding { Top = header_height },
|
|
||||||
Child = contentContainer = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
CornerRadius = ModPanel.CORNER_RADIUS,
|
|
||||||
BorderThickness = 3,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
contentBackground = new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both
|
|
||||||
},
|
|
||||||
new GridContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
RowDimensions = new[]
|
|
||||||
{
|
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
|
||||||
new Dimension()
|
|
||||||
},
|
|
||||||
Content = new[]
|
|
||||||
{
|
|
||||||
new Drawable[]
|
|
||||||
{
|
|
||||||
controlContainer = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Padding = new MarginPadding { Horizontal = 14 }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new Drawable[]
|
|
||||||
{
|
|
||||||
new OsuScrollContainer(Direction.Vertical)
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
ClampExtension = 100,
|
|
||||||
ScrollbarOverlapsContent = false,
|
|
||||||
Child = panelFlow = new FillFlowContainer<ModPanel>
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Spacing = new Vector2(0, 7),
|
|
||||||
Padding = new MarginPadding(7)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
createHeaderText();
|
|
||||||
|
|
||||||
if (allowIncompatibleSelection)
|
if (allowIncompatibleSelection)
|
||||||
{
|
{
|
||||||
controlContainer.Height = 35;
|
ControlContainer.Height = 35;
|
||||||
controlContainer.Add(toggleAllCheckbox = new ToggleAllCheckbox(this)
|
ControlContainer.Add(toggleAllCheckbox = new ToggleAllCheckbox(this)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
@ -212,7 +86,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0)
|
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0)
|
||||||
});
|
});
|
||||||
panelFlow.Padding = new MarginPadding
|
ItemsFlow.Padding = new MarginPadding
|
||||||
{
|
{
|
||||||
Top = 0,
|
Top = 0,
|
||||||
Bottom = 7,
|
Bottom = 7,
|
||||||
@ -221,33 +95,17 @@ namespace osu.Game.Overlays.Mods
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createHeaderText()
|
|
||||||
{
|
|
||||||
IEnumerable<string> headerTextWords = ModType.Humanize(LetterCasing.Title).Split(' ');
|
|
||||||
|
|
||||||
if (headerTextWords.Count() > 1)
|
|
||||||
{
|
|
||||||
headerText.AddText($"{headerTextWords.First()} ", t => t.Font = t.Font.With(weight: FontWeight.SemiBold));
|
|
||||||
headerTextWords = headerTextWords.Skip(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
headerText.AddText(string.Join(' ', headerTextWords));
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OverlayColourProvider colourProvider, OsuColour colours, OsuConfigManager configManager)
|
private void load(OsuColour colours, OsuConfigManager configManager)
|
||||||
{
|
{
|
||||||
headerBackground.Colour = accentColour = colours.ForModType(ModType);
|
AccentColour = colours.ForModType(ModType);
|
||||||
|
|
||||||
if (toggleAllCheckbox != null)
|
if (toggleAllCheckbox != null)
|
||||||
{
|
{
|
||||||
toggleAllCheckbox.AccentColour = accentColour;
|
toggleAllCheckbox.AccentColour = AccentColour;
|
||||||
toggleAllCheckbox.AccentHoverColour = accentColour.Lighten(0.3f);
|
toggleAllCheckbox.AccentHoverColour = AccentColour.Lighten(0.3f);
|
||||||
}
|
}
|
||||||
|
|
||||||
contentContainer.BorderColour = ColourInfo.GradientVertical(colourProvider.Background4, colourProvider.Background3);
|
|
||||||
contentBackground.Colour = colourProvider.Background4;
|
|
||||||
|
|
||||||
hotkeyStyle = configManager.GetBindable<ModSelectHotkeyStyle>(OsuSetting.ModSelectHotkeyStyle);
|
hotkeyStyle = configManager.GetBindable<ModSelectHotkeyStyle>(OsuSetting.ModSelectHotkeyStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,7 +136,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded =>
|
latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded =>
|
||||||
{
|
{
|
||||||
panelFlow.ChildrenEnumerable = loaded;
|
ItemsFlow.ChildrenEnumerable = loaded;
|
||||||
updateState();
|
updateState();
|
||||||
}, (cancellationTokenSource = new CancellationTokenSource()).Token);
|
}, (cancellationTokenSource = new CancellationTokenSource()).Token);
|
||||||
loadTask.ContinueWith(_ =>
|
loadTask.ContinueWith(_ =>
|
||||||
|
@ -1,144 +1,42 @@
|
|||||||
// 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;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
|
||||||
using osu.Framework.Audio.Sample;
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Audio;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osu.Game.Graphics.Sprites;
|
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Mods
|
namespace osu.Game.Overlays.Mods
|
||||||
{
|
{
|
||||||
public class ModPanel : OsuClickableContainer
|
public class ModPanel : ModSelectPanel
|
||||||
{
|
{
|
||||||
public Mod Mod => modState.Mod;
|
public Mod Mod => modState.Mod;
|
||||||
public BindableBool Active => modState.Active;
|
public override BindableBool Active => modState.Active;
|
||||||
public BindableBool Filtered => modState.Filtered;
|
public BindableBool Filtered => modState.Filtered;
|
||||||
|
|
||||||
|
protected override float IdleSwitchWidth => 54;
|
||||||
|
protected override float ExpandedSwitchWidth => 70;
|
||||||
|
|
||||||
private readonly ModState modState;
|
private readonly ModState modState;
|
||||||
|
|
||||||
protected readonly Box Background;
|
|
||||||
protected readonly Container SwitchContainer;
|
|
||||||
protected readonly Container MainContentContainer;
|
|
||||||
protected readonly Box TextBackground;
|
|
||||||
protected readonly FillFlowContainer TextFlow;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
protected OverlayColourProvider ColourProvider { get; private set; } = null!;
|
|
||||||
|
|
||||||
protected const double TRANSITION_DURATION = 150;
|
|
||||||
|
|
||||||
public const float CORNER_RADIUS = 7;
|
|
||||||
|
|
||||||
protected const float HEIGHT = 42;
|
|
||||||
protected const float IDLE_SWITCH_WIDTH = 54;
|
|
||||||
protected const float EXPANDED_SWITCH_WIDTH = 70;
|
|
||||||
|
|
||||||
private Colour4 activeColour;
|
|
||||||
|
|
||||||
private readonly Bindable<bool> samplePlaybackDisabled = new BindableBool();
|
|
||||||
private Sample? sampleOff;
|
|
||||||
private Sample? sampleOn;
|
|
||||||
|
|
||||||
public ModPanel(ModState modState)
|
public ModPanel(ModState modState)
|
||||||
{
|
{
|
||||||
this.modState = modState;
|
this.modState = modState;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
Title = Mod.Name;
|
||||||
Height = 42;
|
Description = Mod.Description;
|
||||||
|
|
||||||
// all below properties are applied to `Content` rather than the `ModPanel` in its entirety
|
SwitchContainer.Child = new ModSwitchSmall(Mod)
|
||||||
// to allow external components to set these properties on the panel without affecting
|
|
||||||
// its "internal" appearance.
|
|
||||||
Content.Masking = true;
|
|
||||||
Content.CornerRadius = CORNER_RADIUS;
|
|
||||||
Content.BorderThickness = 2;
|
|
||||||
Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0);
|
|
||||||
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
Background = new Box
|
Anchor = Anchor.Centre,
|
||||||
{
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Both
|
Active = { BindTarget = Active },
|
||||||
},
|
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
||||||
SwitchContainer = new Container
|
Scale = new Vector2(HEIGHT / ModSwitchSmall.DEFAULT_SIZE)
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Y,
|
|
||||||
Child = new ModSwitchSmall(Mod)
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Active = { BindTarget = Active },
|
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
|
||||||
Scale = new Vector2(HEIGHT / ModSwitchSmall.DEFAULT_SIZE)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
MainContentContainer = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Child = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
CornerRadius = CORNER_RADIUS,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
TextBackground = new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both
|
|
||||||
},
|
|
||||||
TextFlow = new FillFlowContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Padding = new MarginPadding
|
|
||||||
{
|
|
||||||
Horizontal = 17.5f,
|
|
||||||
Vertical = 4
|
|
||||||
},
|
|
||||||
Direction = FillDirection.Vertical,
|
|
||||||
Children = new[]
|
|
||||||
{
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Text = Mod.Name,
|
|
||||||
Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold),
|
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
|
||||||
Margin = new MarginPadding
|
|
||||||
{
|
|
||||||
Left = -18 * ShearedOverlayContainer.SHEAR
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Text = Mod.Description,
|
|
||||||
Font = OsuFont.Default.With(size: 12),
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Truncate = true,
|
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Action = Active.Toggle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModPanel(Mod mod)
|
public ModPanel(Mod mod)
|
||||||
@ -146,122 +44,21 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio, OsuColour colours, ISamplePlaybackDisabler? samplePlaybackDisabler)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
sampleOn = audio.Samples.Get(@"UI/check-on");
|
AccentColour = colours.ForModType(Mod.Type);
|
||||||
sampleOff = audio.Samples.Get(@"UI/check-off");
|
|
||||||
|
|
||||||
activeColour = colours.ForModType(Mod.Type);
|
|
||||||
|
|
||||||
if (samplePlaybackDisabler != null)
|
|
||||||
((IBindable<bool>)samplePlaybackDisabled).BindTo(samplePlaybackDisabler.SamplePlaybackDisabled);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet);
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
Active.BindValueChanged(_ =>
|
|
||||||
{
|
|
||||||
playStateChangeSamples();
|
|
||||||
UpdateState();
|
|
||||||
});
|
|
||||||
Filtered.BindValueChanged(_ => updateFilterState(), true);
|
Filtered.BindValueChanged(_ => updateFilterState(), true);
|
||||||
|
|
||||||
UpdateState();
|
|
||||||
FinishTransforms(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void playStateChangeSamples()
|
|
||||||
{
|
|
||||||
if (samplePlaybackDisabled.Value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (Active.Value)
|
|
||||||
sampleOn?.Play();
|
|
||||||
else
|
|
||||||
sampleOff?.Play();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
|
||||||
{
|
|
||||||
UpdateState();
|
|
||||||
return base.OnHover(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnHoverLost(HoverLostEvent e)
|
|
||||||
{
|
|
||||||
UpdateState();
|
|
||||||
base.OnHoverLost(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool mouseDown;
|
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
|
||||||
{
|
|
||||||
if (e.Button == MouseButton.Left)
|
|
||||||
mouseDown = true;
|
|
||||||
|
|
||||||
UpdateState();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnMouseUp(MouseUpEvent e)
|
|
||||||
{
|
|
||||||
mouseDown = false;
|
|
||||||
|
|
||||||
UpdateState();
|
|
||||||
base.OnMouseUp(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual Colour4 BackgroundColour => Active.Value ? activeColour.Darken(0.3f) : ColourProvider.Background3;
|
|
||||||
protected virtual Colour4 ForegroundColour => Active.Value ? activeColour : ColourProvider.Background2;
|
|
||||||
protected virtual Colour4 TextColour => Active.Value ? ColourProvider.Background6 : Colour4.White;
|
|
||||||
|
|
||||||
protected virtual void UpdateState()
|
|
||||||
{
|
|
||||||
float targetWidth = Active.Value ? EXPANDED_SWITCH_WIDTH : IDLE_SWITCH_WIDTH;
|
|
||||||
double transitionDuration = TRANSITION_DURATION;
|
|
||||||
|
|
||||||
Colour4 backgroundColour = BackgroundColour;
|
|
||||||
Colour4 foregroundColour = ForegroundColour;
|
|
||||||
Colour4 textColour = TextColour;
|
|
||||||
|
|
||||||
// Hover affects colour of button background
|
|
||||||
if (IsHovered)
|
|
||||||
{
|
|
||||||
backgroundColour = backgroundColour.Lighten(0.1f);
|
|
||||||
foregroundColour = foregroundColour.Lighten(0.1f);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mouse down adds a halfway tween of the movement
|
|
||||||
if (mouseDown)
|
|
||||||
{
|
|
||||||
targetWidth = (float)Interpolation.Lerp(IDLE_SWITCH_WIDTH, EXPANDED_SWITCH_WIDTH, 0.5f);
|
|
||||||
transitionDuration *= 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(backgroundColour, foregroundColour), transitionDuration, Easing.OutQuint);
|
|
||||||
Background.FadeColour(backgroundColour, transitionDuration, Easing.OutQuint);
|
|
||||||
SwitchContainer.ResizeWidthTo(targetWidth, transitionDuration, Easing.OutQuint);
|
|
||||||
MainContentContainer.TransformTo(nameof(Padding), new MarginPadding
|
|
||||||
{
|
|
||||||
Left = targetWidth,
|
|
||||||
Right = CORNER_RADIUS
|
|
||||||
}, transitionDuration, Easing.OutQuint);
|
|
||||||
TextBackground.FadeColour(foregroundColour, transitionDuration, Easing.OutQuint);
|
|
||||||
TextFlow.FadeColour(textColour, transitionDuration, Easing.OutQuint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Filtering support
|
#region Filtering support
|
||||||
|
|
||||||
public void ApplyFilter(Func<Mod, bool>? filter)
|
|
||||||
{
|
|
||||||
Filtered.Value = filter != null && !filter.Invoke(Mod);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateFilterState()
|
private void updateFilterState()
|
||||||
{
|
{
|
||||||
this.FadeTo(Filtered.Value ? 0 : 1);
|
this.FadeTo(Filtered.Value ? 0 : 1);
|
||||||
|
77
osu.Game/Overlays/Mods/ModPresetColumn.cs
Normal file
77
osu.Game/Overlays/Mods/ModPresetColumn.cs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Mods
|
||||||
|
{
|
||||||
|
public class ModPresetColumn : ModSelectColumn
|
||||||
|
{
|
||||||
|
private IReadOnlyList<ModPreset> presets = Array.Empty<ModPreset>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the collection of available mod presets.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<ModPreset> Presets
|
||||||
|
{
|
||||||
|
get => presets;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
presets = value;
|
||||||
|
|
||||||
|
if (IsLoaded)
|
||||||
|
asyncLoadPanels();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
AccentColour = colours.Orange1;
|
||||||
|
HeaderText = ModSelectOverlayStrings.PersonalPresets;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
asyncLoadPanels();
|
||||||
|
}
|
||||||
|
|
||||||
|
private CancellationTokenSource? cancellationTokenSource;
|
||||||
|
|
||||||
|
private Task? latestLoadTask;
|
||||||
|
internal bool ItemsLoaded => latestLoadTask == null;
|
||||||
|
|
||||||
|
private void asyncLoadPanels()
|
||||||
|
{
|
||||||
|
cancellationTokenSource?.Cancel();
|
||||||
|
|
||||||
|
var panels = presets.Select(preset => new ModPresetPanel(preset)
|
||||||
|
{
|
||||||
|
Shear = Vector2.Zero
|
||||||
|
});
|
||||||
|
|
||||||
|
Task? loadTask;
|
||||||
|
|
||||||
|
latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded =>
|
||||||
|
{
|
||||||
|
ItemsFlow.ChildrenEnumerable = loaded;
|
||||||
|
}, (cancellationTokenSource = new CancellationTokenSource()).Token);
|
||||||
|
loadTask.ContinueWith(_ =>
|
||||||
|
{
|
||||||
|
if (loadTask == latestLoadTask)
|
||||||
|
latestLoadTask = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
osu.Game/Overlays/Mods/ModPresetPanel.cs
Normal file
35
osu.Game/Overlays/Mods/ModPresetPanel.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// 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.Cursor;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Mods
|
||||||
|
{
|
||||||
|
public class ModPresetPanel : ModSelectPanel, IHasCustomTooltip<ModPreset>
|
||||||
|
{
|
||||||
|
public readonly ModPreset Preset;
|
||||||
|
|
||||||
|
public override BindableBool Active { get; } = new BindableBool();
|
||||||
|
|
||||||
|
public ModPresetPanel(ModPreset preset)
|
||||||
|
{
|
||||||
|
Preset = preset;
|
||||||
|
|
||||||
|
Title = preset.Name;
|
||||||
|
Description = preset.Description;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
AccentColour = colours.Orange1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModPreset TooltipContent => Preset;
|
||||||
|
public ITooltip<ModPreset> GetCustomTooltip() => new ModPresetTooltip(ColourProvider);
|
||||||
|
}
|
||||||
|
}
|
115
osu.Game/Overlays/Mods/ModPresetTooltip.cs
Normal file
115
osu.Game/Overlays/Mods/ModPresetTooltip.cs
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Mods
|
||||||
|
{
|
||||||
|
public class ModPresetTooltip : VisibilityContainer, ITooltip<ModPreset>
|
||||||
|
{
|
||||||
|
protected override Container<Drawable> Content { get; }
|
||||||
|
|
||||||
|
private const double transition_duration = 200;
|
||||||
|
|
||||||
|
public ModPresetTooltip(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
Width = 250;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
Masking = true;
|
||||||
|
CornerRadius = 7;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background6
|
||||||
|
},
|
||||||
|
Content = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Padding = new MarginPadding(7),
|
||||||
|
Spacing = new Vector2(7)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private ModPreset? lastPreset;
|
||||||
|
|
||||||
|
public void SetContent(ModPreset preset)
|
||||||
|
{
|
||||||
|
if (preset == lastPreset)
|
||||||
|
return;
|
||||||
|
|
||||||
|
lastPreset = preset;
|
||||||
|
Content.ChildrenEnumerable = preset.Mods.Select(mod => new ModPresetRow(mod));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopIn() => this.FadeIn(transition_duration, Easing.OutQuint);
|
||||||
|
protected override void PopOut() => this.FadeOut(transition_duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
public void Move(Vector2 pos) => Position = pos;
|
||||||
|
|
||||||
|
private class ModPresetRow : FillFlowContainer
|
||||||
|
{
|
||||||
|
public ModPresetRow(Mod mod)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
Direction = FillDirection.Vertical;
|
||||||
|
Spacing = new Vector2(4);
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Spacing = new Vector2(7),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new ModSwitchTiny(mod)
|
||||||
|
{
|
||||||
|
Active = { Value = true },
|
||||||
|
Scale = new Vector2(0.6f),
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = mod.Name,
|
||||||
|
Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold),
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Margin = new MarginPadding { Bottom = 2 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(mod.SettingDescription))
|
||||||
|
{
|
||||||
|
AddInternal(new OsuTextFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Padding = new MarginPadding { Left = 14 },
|
||||||
|
Text = mod.SettingDescription
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
177
osu.Game/Overlays/Mods/ModSelectColumn.cs
Normal file
177
osu.Game/Overlays/Mods/ModSelectColumn.cs
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Mods
|
||||||
|
{
|
||||||
|
public abstract class ModSelectColumn : CompositeDrawable, IHasAccentColour
|
||||||
|
{
|
||||||
|
public readonly Container TopLevelContent;
|
||||||
|
|
||||||
|
public LocalisableString HeaderText
|
||||||
|
{
|
||||||
|
set => createHeaderText(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color4 AccentColour
|
||||||
|
{
|
||||||
|
get => headerBackground.Colour;
|
||||||
|
set => headerBackground.Colour = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether this column should accept user input.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Bindable<bool> Active = new BindableBool(true);
|
||||||
|
|
||||||
|
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && Active.Value;
|
||||||
|
|
||||||
|
protected readonly Container ControlContainer;
|
||||||
|
protected readonly FillFlowContainer ItemsFlow;
|
||||||
|
|
||||||
|
private readonly TextFlowContainer headerText;
|
||||||
|
private readonly Box headerBackground;
|
||||||
|
private readonly Container contentContainer;
|
||||||
|
private readonly Box contentBackground;
|
||||||
|
|
||||||
|
private const float header_height = 42;
|
||||||
|
|
||||||
|
protected ModSelectColumn()
|
||||||
|
{
|
||||||
|
Width = 320;
|
||||||
|
RelativeSizeAxes = Axes.Y;
|
||||||
|
Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0);
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
TopLevelContent = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
CornerRadius = ModSelectPanel.CORNER_RADIUS,
|
||||||
|
Masking = true,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = header_height + ModSelectPanel.CORNER_RADIUS,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
headerBackground = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = header_height + ModSelectPanel.CORNER_RADIUS
|
||||||
|
},
|
||||||
|
headerText = new OsuTextFlowContainer(t =>
|
||||||
|
{
|
||||||
|
t.Font = OsuFont.TorusAlternate.With(size: 17);
|
||||||
|
t.Shadow = false;
|
||||||
|
t.Colour = Colour4.Black;
|
||||||
|
})
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Horizontal = 17,
|
||||||
|
Bottom = ModSelectPanel.CORNER_RADIUS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding { Top = header_height },
|
||||||
|
Child = contentContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = ModSelectPanel.CORNER_RADIUS,
|
||||||
|
BorderThickness = 3,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
contentBackground = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
|
new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
RowDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
new Dimension()
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
ControlContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Padding = new MarginPadding { Horizontal = 14 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuScrollContainer(Direction.Vertical)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
ClampExtension = 100,
|
||||||
|
ScrollbarOverlapsContent = false,
|
||||||
|
Child = ItemsFlow = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(0, 7),
|
||||||
|
Padding = new MarginPadding(7)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createHeaderText(LocalisableString text)
|
||||||
|
{
|
||||||
|
headerText.Clear();
|
||||||
|
|
||||||
|
int wordIndex = 0;
|
||||||
|
|
||||||
|
headerText.AddText(text, t =>
|
||||||
|
{
|
||||||
|
if (wordIndex == 0)
|
||||||
|
t.Font = t.Font.With(weight: FontWeight.SemiBold);
|
||||||
|
wordIndex += 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
contentContainer.BorderColour = ColourInfo.GradientVertical(colourProvider.Background4, colourProvider.Background3);
|
||||||
|
contentBackground.Colour = colourProvider.Background4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
252
osu.Game/Overlays/Mods/ModSelectPanel.cs
Normal file
252
osu.Game/Overlays/Mods/ModSelectPanel.cs
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
// 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.Audio;
|
||||||
|
using osu.Framework.Audio.Sample;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Mods
|
||||||
|
{
|
||||||
|
public abstract class ModSelectPanel : OsuClickableContainer, IHasAccentColour
|
||||||
|
{
|
||||||
|
public abstract BindableBool Active { get; }
|
||||||
|
|
||||||
|
public Color4 AccentColour { get; set; }
|
||||||
|
|
||||||
|
public LocalisableString Title
|
||||||
|
{
|
||||||
|
get => titleText.Text;
|
||||||
|
set => titleText.Text = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalisableString Description
|
||||||
|
{
|
||||||
|
get => descriptionText.Text;
|
||||||
|
set => descriptionText.Text = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public const float CORNER_RADIUS = 7;
|
||||||
|
|
||||||
|
protected const float HEIGHT = 42;
|
||||||
|
|
||||||
|
protected virtual float IdleSwitchWidth => 14;
|
||||||
|
protected virtual float ExpandedSwitchWidth => 30;
|
||||||
|
protected virtual Colour4 BackgroundColour => Active.Value ? AccentColour.Darken(0.3f) : ColourProvider.Background3;
|
||||||
|
protected virtual Colour4 ForegroundColour => Active.Value ? AccentColour : ColourProvider.Background2;
|
||||||
|
protected virtual Colour4 TextColour => Active.Value ? ColourProvider.Background6 : Colour4.White;
|
||||||
|
|
||||||
|
protected const double TRANSITION_DURATION = 150;
|
||||||
|
|
||||||
|
protected readonly Box Background;
|
||||||
|
protected readonly Container SwitchContainer;
|
||||||
|
protected readonly Container MainContentContainer;
|
||||||
|
protected readonly Box TextBackground;
|
||||||
|
protected readonly FillFlowContainer TextFlow;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
protected OverlayColourProvider ColourProvider { get; private set; } = null!;
|
||||||
|
|
||||||
|
private readonly OsuSpriteText titleText;
|
||||||
|
private readonly OsuSpriteText descriptionText;
|
||||||
|
|
||||||
|
private readonly Bindable<bool> samplePlaybackDisabled = new BindableBool();
|
||||||
|
private Sample? sampleOff;
|
||||||
|
private Sample? sampleOn;
|
||||||
|
|
||||||
|
protected ModSelectPanel()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = HEIGHT;
|
||||||
|
|
||||||
|
// all below properties are applied to `Content` rather than the `ModPanel` in its entirety
|
||||||
|
// to allow external components to set these properties on the panel without affecting
|
||||||
|
// its "internal" appearance.
|
||||||
|
Content.Masking = true;
|
||||||
|
Content.CornerRadius = CORNER_RADIUS;
|
||||||
|
Content.BorderThickness = 2;
|
||||||
|
|
||||||
|
Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0);
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
Background = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
|
SwitchContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
},
|
||||||
|
MainContentContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = CORNER_RADIUS,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
TextBackground = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
|
TextFlow = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Horizontal = 17.5f,
|
||||||
|
Vertical = 4
|
||||||
|
},
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
titleText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold),
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Truncate = true,
|
||||||
|
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
||||||
|
Margin = new MarginPadding
|
||||||
|
{
|
||||||
|
Left = -18 * ShearedOverlayContainer.SHEAR
|
||||||
|
}
|
||||||
|
},
|
||||||
|
descriptionText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Font = OsuFont.Default.With(size: 12),
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Truncate = true,
|
||||||
|
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Action = () => Active.Toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(AudioManager audio, ISamplePlaybackDisabler? samplePlaybackDisabler)
|
||||||
|
{
|
||||||
|
sampleOn = audio.Samples.Get(@"UI/check-on");
|
||||||
|
sampleOff = audio.Samples.Get(@"UI/check-off");
|
||||||
|
|
||||||
|
if (samplePlaybackDisabler != null)
|
||||||
|
((IBindable<bool>)samplePlaybackDisabled).BindTo(samplePlaybackDisabler.SamplePlaybackDisabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected sealed override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet);
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
Active.BindValueChanged(_ =>
|
||||||
|
{
|
||||||
|
playStateChangeSamples();
|
||||||
|
UpdateState();
|
||||||
|
});
|
||||||
|
|
||||||
|
UpdateState();
|
||||||
|
FinishTransforms(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void playStateChangeSamples()
|
||||||
|
{
|
||||||
|
if (samplePlaybackDisabled.Value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Active.Value)
|
||||||
|
sampleOn?.Play();
|
||||||
|
else
|
||||||
|
sampleOff?.Play();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
UpdateState();
|
||||||
|
return base.OnHover(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
UpdateState();
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool mouseDown;
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
{
|
||||||
|
if (e.Button == MouseButton.Left)
|
||||||
|
mouseDown = true;
|
||||||
|
|
||||||
|
UpdateState();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseUp(MouseUpEvent e)
|
||||||
|
{
|
||||||
|
mouseDown = false;
|
||||||
|
|
||||||
|
UpdateState();
|
||||||
|
base.OnMouseUp(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void UpdateState()
|
||||||
|
{
|
||||||
|
float targetWidth = Active.Value ? ExpandedSwitchWidth : IdleSwitchWidth;
|
||||||
|
double transitionDuration = TRANSITION_DURATION;
|
||||||
|
|
||||||
|
Colour4 backgroundColour = BackgroundColour;
|
||||||
|
Colour4 foregroundColour = ForegroundColour;
|
||||||
|
Colour4 textColour = TextColour;
|
||||||
|
|
||||||
|
// Hover affects colour of button background
|
||||||
|
if (IsHovered)
|
||||||
|
{
|
||||||
|
backgroundColour = backgroundColour.Lighten(0.1f);
|
||||||
|
foregroundColour = foregroundColour.Lighten(0.1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mouse down adds a halfway tween of the movement
|
||||||
|
if (mouseDown)
|
||||||
|
{
|
||||||
|
targetWidth = (float)Interpolation.Lerp(IdleSwitchWidth, ExpandedSwitchWidth, 0.5f);
|
||||||
|
transitionDuration *= 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(backgroundColour, foregroundColour), transitionDuration, Easing.OutQuint);
|
||||||
|
Background.FadeColour(backgroundColour, transitionDuration, Easing.OutQuint);
|
||||||
|
SwitchContainer.ResizeWidthTo(targetWidth, transitionDuration, Easing.OutQuint);
|
||||||
|
MainContentContainer.TransformTo(nameof(Padding), new MarginPadding
|
||||||
|
{
|
||||||
|
Left = targetWidth,
|
||||||
|
Right = CORNER_RADIUS
|
||||||
|
}, transitionDuration, Easing.OutQuint);
|
||||||
|
TextBackground.FadeColour(foregroundColour, transitionDuration, Easing.OutQuint);
|
||||||
|
TextFlow.FadeColour(textColour, transitionDuration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@ using osu.Game.Online.API.Requests.Responses;
|
|||||||
using osu.Game.Online.Leaderboards;
|
using osu.Game.Online.Leaderboards;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Scoring.Drawables;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -218,39 +219,42 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
|
|||||||
|
|
||||||
private Drawable createDrawablePerformance()
|
private Drawable createDrawablePerformance()
|
||||||
{
|
{
|
||||||
if (Score.PP.HasValue)
|
if (!Score.PP.HasValue)
|
||||||
{
|
{
|
||||||
return new FillFlowContainer
|
if (Score.Beatmap?.Status.GrantsPerformancePoints() == true)
|
||||||
|
return new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(16), Colour = colourProvider.Highlight1 };
|
||||||
|
|
||||||
|
return new OsuSpriteText
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both,
|
Font = OsuFont.GetFont(weight: FontWeight.Bold),
|
||||||
Direction = FillDirection.Horizontal,
|
Text = "-",
|
||||||
Children = new[]
|
Colour = colourProvider.Highlight1
|
||||||
{
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomLeft,
|
|
||||||
Origin = Anchor.BottomLeft,
|
|
||||||
Font = OsuFont.GetFont(weight: FontWeight.Bold),
|
|
||||||
Text = $"{Score.PP:0}",
|
|
||||||
Colour = colourProvider.Highlight1
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomLeft,
|
|
||||||
Origin = Anchor.BottomLeft,
|
|
||||||
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
|
|
||||||
Text = "pp",
|
|
||||||
Colour = colourProvider.Light3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OsuSpriteText
|
return new FillFlowContainer
|
||||||
{
|
{
|
||||||
Font = OsuFont.GetFont(weight: FontWeight.Bold),
|
AutoSizeAxes = Axes.Both,
|
||||||
Text = "-",
|
Direction = FillDirection.Horizontal,
|
||||||
Colour = colourProvider.Highlight1
|
Children = new[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Font = OsuFont.GetFont(weight: FontWeight.Bold),
|
||||||
|
Text = $"{Score.PP:0}",
|
||||||
|
Colour = colourProvider.Highlight1
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
|
||||||
|
Text = "pp",
|
||||||
|
Colour = colourProvider.Light3
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,12 +42,11 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
|
|||||||
CreateDrawableAccuracy(),
|
CreateDrawableAccuracy(),
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Y,
|
Size = new Vector2(50, 14),
|
||||||
Width = 50,
|
|
||||||
Child = new OsuSpriteText
|
Child = new OsuSpriteText
|
||||||
{
|
{
|
||||||
Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true),
|
Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true),
|
||||||
Text = $"{Score.PP * weight:0}pp",
|
Text = Score.PP.HasValue ? $"{Score.PP * weight:0}pp" : string.Empty,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
|
|||||||
LabelText = SkinSettingsStrings.AutoCursorSize,
|
LabelText = SkinSettingsStrings.AutoCursorSize,
|
||||||
Current = config.GetBindable<bool>(OsuSetting.AutoCursorSize)
|
Current = config.GetBindable<bool>(OsuSetting.AutoCursorSize)
|
||||||
},
|
},
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = SkinSettingsStrings.GameplayCursorDuringTouch,
|
||||||
|
Current = config.GetBindable<bool>(OsuSetting.GameplayCursorDuringTouch)
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows)
|
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows)
|
||||||
|
@ -34,6 +34,11 @@ namespace osu.Game.Rulesets.Difficulty
|
|||||||
private readonly IRulesetInfo ruleset;
|
private readonly IRulesetInfo ruleset;
|
||||||
private readonly IWorkingBeatmap beatmap;
|
private readonly IWorkingBeatmap beatmap;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A yymmdd version which is used to discern when reprocessing is required.
|
||||||
|
/// </summary>
|
||||||
|
public virtual int Version => 0;
|
||||||
|
|
||||||
protected DifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
protected DifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||||
{
|
{
|
||||||
this.ruleset = ruleset;
|
this.ruleset = ruleset;
|
||||||
|
35
osu.Game/Rulesets/Mods/ModPreset.cs
Normal file
35
osu.Game/Rulesets/Mods/ModPreset.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mods
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A mod preset is a named collection of configured mods.
|
||||||
|
/// Presets are presented to the user in the mod select overlay for convenience.
|
||||||
|
/// </summary>
|
||||||
|
public class ModPreset
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The ruleset that the preset is valid for.
|
||||||
|
/// </summary>
|
||||||
|
public RulesetInfo RulesetInfo { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the mod preset.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The description of the mod preset.
|
||||||
|
/// </summary>
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The set of configured mods that are part of the preset.
|
||||||
|
/// </summary>
|
||||||
|
public ICollection<Mod> Mods { get; set; } = Array.Empty<Mod>();
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Rulesets.Difficulty;
|
||||||
using Realms;
|
using Realms;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets
|
namespace osu.Game.Rulesets
|
||||||
@ -22,6 +23,11 @@ namespace osu.Game.Rulesets
|
|||||||
|
|
||||||
public string InstantiationInfo { get; set; } = string.Empty;
|
public string InstantiationInfo { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores the last applied <see cref="DifficultyCalculator.Version"/>
|
||||||
|
/// </summary>
|
||||||
|
public int LastAppliedDifficultyVersion { get; set; }
|
||||||
|
|
||||||
public RulesetInfo(string shortName, string name, string instantiationInfo, int onlineID)
|
public RulesetInfo(string shortName, string name, string instantiationInfo, int onlineID)
|
||||||
{
|
{
|
||||||
ShortName = shortName;
|
ShortName = shortName;
|
||||||
@ -86,7 +92,8 @@ namespace osu.Game.Rulesets
|
|||||||
Name = Name,
|
Name = Name,
|
||||||
ShortName = ShortName,
|
ShortName = ShortName,
|
||||||
InstantiationInfo = InstantiationInfo,
|
InstantiationInfo = InstantiationInfo,
|
||||||
Available = Available
|
Available = Available,
|
||||||
|
LastAppliedDifficultyVersion = LastAppliedDifficultyVersion,
|
||||||
};
|
};
|
||||||
|
|
||||||
public Ruleset CreateInstance()
|
public Ruleset CreateInstance()
|
||||||
|
@ -126,6 +126,9 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
private bool beatmapApplied;
|
private bool beatmapApplied;
|
||||||
|
|
||||||
private readonly Dictionary<HitResult, int> scoreResultCounts = new Dictionary<HitResult, int>();
|
private readonly Dictionary<HitResult, int> scoreResultCounts = new Dictionary<HitResult, int>();
|
||||||
|
|
||||||
|
private Dictionary<HitResult, int>? maximumResultCounts;
|
||||||
|
|
||||||
private readonly List<HitEvent> hitEvents = new List<HitEvent>();
|
private readonly List<HitEvent> hitEvents = new List<HitEvent>();
|
||||||
private HitObject? lastHitObject;
|
private HitObject? lastHitObject;
|
||||||
|
|
||||||
@ -410,12 +413,16 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
{
|
{
|
||||||
base.Reset(storeResults);
|
base.Reset(storeResults);
|
||||||
|
|
||||||
scoreResultCounts.Clear();
|
|
||||||
hitEvents.Clear();
|
hitEvents.Clear();
|
||||||
lastHitObject = null;
|
lastHitObject = null;
|
||||||
|
|
||||||
if (storeResults)
|
if (storeResults)
|
||||||
|
{
|
||||||
maximumScoringValues = currentScoringValues;
|
maximumScoringValues = currentScoringValues;
|
||||||
|
maximumResultCounts = new Dictionary<HitResult, int>(scoreResultCounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
scoreResultCounts.Clear();
|
||||||
|
|
||||||
currentScoringValues = default;
|
currentScoringValues = default;
|
||||||
currentMaximumScoringValues = default;
|
currentMaximumScoringValues = default;
|
||||||
@ -423,6 +430,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
TotalScore.Value = 0;
|
TotalScore.Value = 0;
|
||||||
Accuracy.Value = 1;
|
Accuracy.Value = 1;
|
||||||
Combo.Value = 0;
|
Combo.Value = 0;
|
||||||
|
Rank.Disabled = false;
|
||||||
Rank.Value = ScoreRank.X;
|
Rank.Value = ScoreRank.X;
|
||||||
HighestCombo.Value = 0;
|
HighestCombo.Value = 0;
|
||||||
}
|
}
|
||||||
@ -445,6 +453,36 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
score.TotalScore = (long)Math.Round(ComputeFinalScore(ScoringMode.Standardised, score));
|
score.TotalScore = (long)Math.Round(ComputeFinalScore(ScoringMode.Standardised, score));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Populates the given score with remaining statistics as "missed" and marks it with <see cref="ScoreRank.F"/> rank.
|
||||||
|
/// </summary>
|
||||||
|
public void FailScore(ScoreInfo score)
|
||||||
|
{
|
||||||
|
if (Rank.Value == ScoreRank.F)
|
||||||
|
return;
|
||||||
|
|
||||||
|
score.Passed = false;
|
||||||
|
Rank.Value = ScoreRank.F;
|
||||||
|
|
||||||
|
Debug.Assert(maximumResultCounts != null);
|
||||||
|
|
||||||
|
if (maximumResultCounts.TryGetValue(HitResult.LargeTickHit, out int maximumLargeTick))
|
||||||
|
scoreResultCounts[HitResult.LargeTickMiss] = maximumLargeTick - scoreResultCounts.GetValueOrDefault(HitResult.LargeTickHit);
|
||||||
|
|
||||||
|
if (maximumResultCounts.TryGetValue(HitResult.SmallTickHit, out int maximumSmallTick))
|
||||||
|
scoreResultCounts[HitResult.SmallTickMiss] = maximumSmallTick - scoreResultCounts.GetValueOrDefault(HitResult.SmallTickHit);
|
||||||
|
|
||||||
|
int maximumBonusOrIgnore = maximumResultCounts.Where(kvp => kvp.Key.IsBonus() || kvp.Key == HitResult.IgnoreHit).Sum(kvp => kvp.Value);
|
||||||
|
int currentBonusOrIgnore = scoreResultCounts.Where(kvp => kvp.Key.IsBonus() || kvp.Key == HitResult.IgnoreHit).Sum(kvp => kvp.Value);
|
||||||
|
scoreResultCounts[HitResult.IgnoreMiss] = maximumBonusOrIgnore - currentBonusOrIgnore;
|
||||||
|
|
||||||
|
int maximumBasic = maximumResultCounts.SingleOrDefault(kvp => kvp.Key.IsBasic()).Value;
|
||||||
|
int currentBasic = scoreResultCounts.Where(kvp => kvp.Key.IsBasic() && kvp.Key != HitResult.Miss).Sum(kvp => kvp.Value);
|
||||||
|
scoreResultCounts[HitResult.Miss] = maximumBasic - currentBasic;
|
||||||
|
|
||||||
|
PopulateScore(score);
|
||||||
|
}
|
||||||
|
|
||||||
public override void ResetFromReplayFrame(ReplayFrame frame)
|
public override void ResetFromReplayFrame(ReplayFrame frame)
|
||||||
{
|
{
|
||||||
base.ResetFromReplayFrame(frame);
|
base.ResetFromReplayFrame(frame);
|
||||||
|
@ -380,7 +380,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
// only show the cursor when within the playfield, by default.
|
// only show the cursor when within the playfield, by default.
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Playfield.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Playfield.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
CursorContainer IProvideCursor.Cursor => Playfield.Cursor;
|
CursorContainer IProvideCursor.MenuCursor => Playfield.Cursor;
|
||||||
|
|
||||||
public override GameplayCursorContainer Cursor => Playfield.Cursor;
|
public override GameplayCursorContainer Cursor => Playfield.Cursor;
|
||||||
|
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
|
||||||
|
namespace osu.Game.Scoring.Drawables
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A placeholder used in PP columns for scores with unprocessed PP value.
|
||||||
|
/// </summary>
|
||||||
|
public class UnprocessedPerformancePointsPlaceholder : SpriteIcon, IHasTooltip
|
||||||
|
{
|
||||||
|
public LocalisableString TooltipText => ScoresStrings.StatusProcessing;
|
||||||
|
|
||||||
|
public UnprocessedPerformancePointsPlaceholder()
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
Icon = FontAwesome.Solid.ExclamationTriangle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@ using System.Threading.Tasks;
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -172,6 +173,10 @@ namespace osu.Game.Scoring
|
|||||||
|
|
||||||
// We can compute the max combo locally after the async beatmap difficulty computation.
|
// We can compute the max combo locally after the async beatmap difficulty computation.
|
||||||
var difficulty = await difficultyCache.GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false);
|
var difficulty = await difficultyCache.GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (difficulty == null)
|
||||||
|
Logger.Log($"Couldn't get beatmap difficulty for beatmap {score.BeatmapInfo.OnlineID}");
|
||||||
|
|
||||||
return difficulty?.MaxCombo;
|
return difficulty?.MaxCombo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +41,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private EditorClock editorClock { get; set; }
|
private EditorClock editorClock { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorBeatmap editorBeatmap { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The timeline's scroll position in the last frame.
|
/// The timeline's scroll position in the last frame.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -68,8 +71,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private float defaultTimelineZoom;
|
private float defaultTimelineZoom;
|
||||||
|
|
||||||
private readonly Bindable<double> timelineZoomScale = new BindableDouble(1.0);
|
|
||||||
|
|
||||||
public Timeline(Drawable userContent)
|
public Timeline(Drawable userContent)
|
||||||
{
|
{
|
||||||
this.userContent = userContent;
|
this.userContent = userContent;
|
||||||
@ -93,7 +94,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
private Bindable<float> waveformOpacity;
|
private Bindable<float> waveformOpacity;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(IBindable<WorkingBeatmap> beatmap, EditorBeatmap editorBeatmap, OsuColour colours, OsuConfigManager config)
|
private void load(IBindable<WorkingBeatmap> beatmap, OsuColour colours, OsuConfigManager config)
|
||||||
{
|
{
|
||||||
CentreMarker centreMarker;
|
CentreMarker centreMarker;
|
||||||
|
|
||||||
@ -145,21 +146,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
waveform.Waveform = b.NewValue.Waveform;
|
waveform.Waveform = b.NewValue.Waveform;
|
||||||
track = b.NewValue.Track;
|
track = b.NewValue.Track;
|
||||||
|
|
||||||
// todo: i don't think this is safe, the track may not be loaded yet.
|
setupTimelineZoom();
|
||||||
if (track.Length > 0)
|
|
||||||
{
|
|
||||||
MaxZoom = getZoomLevelForVisibleMilliseconds(500);
|
|
||||||
MinZoom = getZoomLevelForVisibleMilliseconds(10000);
|
|
||||||
defaultTimelineZoom = getZoomLevelForVisibleMilliseconds(6000);
|
|
||||||
}
|
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
timelineZoomScale.Value = editorBeatmap.BeatmapInfo.TimelineZoom;
|
Zoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom);
|
||||||
timelineZoomScale.BindValueChanged(scale =>
|
|
||||||
{
|
|
||||||
Zoom = (float)(defaultTimelineZoom * scale.NewValue);
|
|
||||||
editorBeatmap.BeatmapInfo.TimelineZoom = scale.NewValue;
|
|
||||||
}, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -209,6 +199,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
scrollToTrackTime();
|
scrollToTrackTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setupTimelineZoom()
|
||||||
|
{
|
||||||
|
if (!track.IsLoaded)
|
||||||
|
{
|
||||||
|
Scheduler.AddOnce(setupTimelineZoom);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultTimelineZoom = getZoomLevelForVisibleMilliseconds(6000);
|
||||||
|
|
||||||
|
float initialZoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom);
|
||||||
|
SetupZoom(initialZoom, getZoomLevelForVisibleMilliseconds(10000), getZoomLevelForVisibleMilliseconds(500));
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnScroll(ScrollEvent e)
|
protected override bool OnScroll(ScrollEvent e)
|
||||||
{
|
{
|
||||||
// if this is not a precision scroll event, let the editor handle the seek itself (for snapping support)
|
// if this is not a precision scroll event, let the editor handle the seek itself (for snapping support)
|
||||||
@ -221,7 +225,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
protected override void OnZoomChanged()
|
protected override void OnZoomChanged()
|
||||||
{
|
{
|
||||||
base.OnZoomChanged();
|
base.OnZoomChanged();
|
||||||
timelineZoomScale.Value = Zoom / defaultTimelineZoom;
|
editorBeatmap.BeatmapInfo.TimelineZoom = Zoom / defaultTimelineZoom;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateAfterChildren()
|
protected override void UpdateAfterChildren()
|
||||||
|
@ -118,7 +118,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
Height = 0.5f,
|
Height = 0.5f,
|
||||||
Icon = FontAwesome.Solid.SearchPlus,
|
Icon = FontAwesome.Solid.SearchPlus,
|
||||||
Action = () => changeZoom(1)
|
Action = () => Timeline.AdjustZoomRelatively(1)
|
||||||
},
|
},
|
||||||
new TimelineButton
|
new TimelineButton
|
||||||
{
|
{
|
||||||
@ -127,7 +127,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
Height = 0.5f,
|
Height = 0.5f,
|
||||||
Icon = FontAwesome.Solid.SearchMinus,
|
Icon = FontAwesome.Solid.SearchMinus,
|
||||||
Action = () => changeZoom(-1)
|
Action = () => Timeline.AdjustZoomRelatively(-1)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -153,7 +153,5 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
Timeline.ControlPointsVisible.BindTo(controlPointsCheckbox.Current);
|
Timeline.ControlPointsVisible.BindTo(controlPointsCheckbox.Current);
|
||||||
Timeline.TicksVisible.BindTo(ticksCheckbox.Current);
|
Timeline.TicksVisible.BindTo(ticksCheckbox.Current);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void changeZoom(float change) => Timeline.Zoom += change;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,20 +32,28 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
private readonly Container zoomedContent;
|
private readonly Container zoomedContent;
|
||||||
protected override Container<Drawable> Content => zoomedContent;
|
protected override Container<Drawable> Content => zoomedContent;
|
||||||
private float currentZoom = 1;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current zoom level of <see cref="ZoomableScrollContainer" />.
|
/// The current zoom level of <see cref="ZoomableScrollContainer"/>.
|
||||||
/// It may differ from <see cref="Zoom" /> during transitions.
|
/// It may differ from <see cref="Zoom"/> during transitions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float CurrentZoom => currentZoom;
|
public float CurrentZoom { get; private set; } = 1;
|
||||||
|
|
||||||
|
private bool isZoomSetUp;
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved(canBeNull: true)]
|
||||||
private IFrameBasedClock editorClock { get; set; }
|
private IFrameBasedClock editorClock { get; set; }
|
||||||
|
|
||||||
private readonly LayoutValue zoomedContentWidthCache = new LayoutValue(Invalidation.DrawSize);
|
private readonly LayoutValue zoomedContentWidthCache = new LayoutValue(Invalidation.DrawSize);
|
||||||
|
|
||||||
public ZoomableScrollContainer()
|
private float minZoom;
|
||||||
|
private float maxZoom;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a <see cref="ZoomableScrollContainer"/> with no zoom range.
|
||||||
|
/// Functionality will be disabled until zoom is set up via <see cref="SetupZoom"/>.
|
||||||
|
/// </summary>
|
||||||
|
protected ZoomableScrollContainer()
|
||||||
: base(Direction.Horizontal)
|
: base(Direction.Horizontal)
|
||||||
{
|
{
|
||||||
base.Content.Add(zoomedContent = new Container { RelativeSizeAxes = Axes.Y });
|
base.Content.Add(zoomedContent = new Container { RelativeSizeAxes = Axes.Y });
|
||||||
@ -53,46 +61,36 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
AddLayout(zoomedContentWidthCache);
|
AddLayout(zoomedContentWidthCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
private float minZoom = 1;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The minimum zoom level allowed.
|
/// Creates a <see cref="ZoomableScrollContainer"/> with a defined zoom range.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float MinZoom
|
public ZoomableScrollContainer(float minimum, float maximum, float initial)
|
||||||
|
: this()
|
||||||
{
|
{
|
||||||
get => minZoom;
|
SetupZoom(initial, minimum, maximum);
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value < 1)
|
|
||||||
throw new ArgumentException($"{nameof(MinZoom)} must be >= 1.", nameof(value));
|
|
||||||
|
|
||||||
minZoom = value;
|
|
||||||
|
|
||||||
// ensure zoom range is in valid state before updating zoom.
|
|
||||||
if (MinZoom < MaxZoom)
|
|
||||||
updateZoom();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private float maxZoom = 60;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The maximum zoom level allowed.
|
/// Sets up the minimum and maximum range of this zoomable scroll container, along with the initial zoom value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float MaxZoom
|
/// <param name="initial">The initial zoom value, applied immediately.</param>
|
||||||
|
/// <param name="minimum">The minimum zoom value.</param>
|
||||||
|
/// <param name="maximum">The maximum zoom value.</param>
|
||||||
|
protected void SetupZoom(float initial, float minimum, float maximum)
|
||||||
{
|
{
|
||||||
get => maxZoom;
|
if (minimum < 1)
|
||||||
set
|
throw new ArgumentException($"{nameof(minimum)} ({minimum}) must be >= 1.", nameof(maximum));
|
||||||
{
|
|
||||||
if (value < 1)
|
|
||||||
throw new ArgumentException($"{nameof(MaxZoom)} must be >= 1.", nameof(value));
|
|
||||||
|
|
||||||
maxZoom = value;
|
if (maximum < 1)
|
||||||
|
throw new ArgumentException($"{nameof(maximum)} ({maximum}) must be >= 1.", nameof(maximum));
|
||||||
|
|
||||||
// ensure zoom range is in valid state before updating zoom.
|
if (minimum > maximum)
|
||||||
if (MaxZoom > MinZoom)
|
throw new ArgumentException($"{nameof(minimum)} ({minimum}) must be less than {nameof(maximum)} ({maximum})");
|
||||||
updateZoom();
|
|
||||||
}
|
minZoom = minimum;
|
||||||
|
maxZoom = maximum;
|
||||||
|
CurrentZoom = zoomTarget = initial;
|
||||||
|
isZoomSetUp = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -104,14 +102,17 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
set => updateZoom(value);
|
set => updateZoom(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateZoom(float? value = null)
|
private void updateZoom(float value)
|
||||||
{
|
{
|
||||||
float newZoom = Math.Clamp(value ?? Zoom, MinZoom, MaxZoom);
|
if (!isZoomSetUp)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float newZoom = Math.Clamp(value, minZoom, maxZoom);
|
||||||
|
|
||||||
if (IsLoaded)
|
if (IsLoaded)
|
||||||
setZoomTarget(newZoom, ToSpaceOfOtherDrawable(new Vector2(DrawWidth / 2, 0), zoomedContent).X);
|
setZoomTarget(newZoom, ToSpaceOfOtherDrawable(new Vector2(DrawWidth / 2, 0), zoomedContent).X);
|
||||||
else
|
else
|
||||||
currentZoom = zoomTarget = newZoom;
|
CurrentZoom = zoomTarget = newZoom;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
@ -127,7 +128,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
if (e.AltPressed)
|
if (e.AltPressed)
|
||||||
{
|
{
|
||||||
// zoom when holding alt.
|
// zoom when holding alt.
|
||||||
setZoomTarget(zoomTarget + e.ScrollDelta.Y, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X);
|
AdjustZoomRelatively(e.ScrollDelta.Y, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,16 +142,28 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
private void updateZoomedContentWidth()
|
private void updateZoomedContentWidth()
|
||||||
{
|
{
|
||||||
zoomedContent.Width = DrawWidth * currentZoom;
|
zoomedContent.Width = DrawWidth * CurrentZoom;
|
||||||
zoomedContentWidthCache.Validate();
|
zoomedContentWidthCache.Validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AdjustZoomRelatively(float change, float? focusPoint = null)
|
||||||
|
{
|
||||||
|
if (!isZoomSetUp)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const float zoom_change_sensitivity = 0.02f;
|
||||||
|
|
||||||
|
setZoomTarget(zoomTarget + change * (maxZoom - minZoom) * zoom_change_sensitivity, focusPoint);
|
||||||
|
}
|
||||||
|
|
||||||
private float zoomTarget = 1;
|
private float zoomTarget = 1;
|
||||||
|
|
||||||
private void setZoomTarget(float newZoom, float focusPoint)
|
private void setZoomTarget(float newZoom, float? focusPoint = null)
|
||||||
{
|
{
|
||||||
zoomTarget = Math.Clamp(newZoom, MinZoom, MaxZoom);
|
zoomTarget = Math.Clamp(newZoom, minZoom, maxZoom);
|
||||||
transformZoomTo(zoomTarget, focusPoint, ZoomDuration, ZoomEasing);
|
focusPoint ??= zoomedContent.ToLocalSpace(ToScreenSpace(new Vector2(DrawWidth / 2, 0))).X;
|
||||||
|
|
||||||
|
transformZoomTo(zoomTarget, focusPoint.Value, ZoomDuration, ZoomEasing);
|
||||||
|
|
||||||
OnZoomChanged();
|
OnZoomChanged();
|
||||||
}
|
}
|
||||||
@ -183,7 +196,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
private readonly float scrollOffset;
|
private readonly float scrollOffset;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Transforms <see cref="ZoomableScrollContainer.currentZoom"/> to a new value.
|
/// Transforms <see cref="ZoomableScrollContainer.CurrentZoom"/> to a new value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="focusPoint">The focus point in absolute coordinates local to the content.</param>
|
/// <param name="focusPoint">The focus point in absolute coordinates local to the content.</param>
|
||||||
/// <param name="contentSize">The size of the content.</param>
|
/// <param name="contentSize">The size of the content.</param>
|
||||||
@ -195,7 +208,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
this.scrollOffset = scrollOffset;
|
this.scrollOffset = scrollOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string TargetMember => nameof(currentZoom);
|
public override string TargetMember => nameof(CurrentZoom);
|
||||||
|
|
||||||
private float valueAt(double time)
|
private float valueAt(double time)
|
||||||
{
|
{
|
||||||
@ -213,7 +226,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
float expectedWidth = d.DrawWidth * newZoom;
|
float expectedWidth = d.DrawWidth * newZoom;
|
||||||
float targetOffset = expectedWidth * (focusPoint / contentSize) - focusOffset;
|
float targetOffset = expectedWidth * (focusPoint / contentSize) - focusOffset;
|
||||||
|
|
||||||
d.currentZoom = newZoom;
|
d.CurrentZoom = newZoom;
|
||||||
d.updateZoomedContentWidth();
|
d.updateZoomedContentWidth();
|
||||||
|
|
||||||
// Temporarily here to make sure ScrollTo gets the correct DrawSize for scrollable area.
|
// Temporarily here to make sure ScrollTo gets the correct DrawSize for scrollable area.
|
||||||
@ -222,7 +235,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
d.ScrollTo(targetOffset, false);
|
d.ScrollTo(targetOffset, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ReadIntoStartValue(ZoomableScrollContainer d) => StartValue = d.currentZoom;
|
protected override void ReadIntoStartValue(ZoomableScrollContainer d) => StartValue = d.CurrentZoom;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,7 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
ScoreInfo =
|
ScoreInfo =
|
||||||
{
|
{
|
||||||
|
BeatmapInfo = beatmap.BeatmapInfo,
|
||||||
Ruleset = ruleset.RulesetInfo
|
Ruleset = ruleset.RulesetInfo
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -267,12 +267,7 @@ namespace osu.Game.Screens.Play
|
|||||||
},
|
},
|
||||||
FailOverlay = new FailOverlay
|
FailOverlay = new FailOverlay
|
||||||
{
|
{
|
||||||
SaveReplay = () =>
|
SaveReplay = prepareAndImportScore,
|
||||||
{
|
|
||||||
Score.ScoreInfo.Passed = false;
|
|
||||||
Score.ScoreInfo.Rank = ScoreRank.F;
|
|
||||||
return prepareAndImportScore();
|
|
||||||
},
|
|
||||||
OnRetry = Restart,
|
OnRetry = Restart,
|
||||||
OnQuit = () => PerformExit(true),
|
OnQuit = () => PerformExit(true),
|
||||||
},
|
},
|
||||||
@ -831,7 +826,6 @@ namespace osu.Game.Screens.Play
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
GameplayState.HasFailed = true;
|
GameplayState.HasFailed = true;
|
||||||
Score.ScoreInfo.Passed = false;
|
|
||||||
|
|
||||||
updateGameplayState();
|
updateGameplayState();
|
||||||
|
|
||||||
@ -849,9 +843,16 @@ namespace osu.Game.Screens.Play
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called back when the transform finishes
|
/// <summary>
|
||||||
|
/// Invoked when the fail animation has finished.
|
||||||
|
/// </summary>
|
||||||
private void onFailComplete()
|
private void onFailComplete()
|
||||||
{
|
{
|
||||||
|
// fail completion is a good point to mark a score as failed,
|
||||||
|
// since the last judgement that caused the fail only applies to score processor after onFail.
|
||||||
|
// todo: this should probably be handled better.
|
||||||
|
ScoreProcessor.FailScore(Score.ScoreInfo);
|
||||||
|
|
||||||
GameplayClockContainer.Stop();
|
GameplayClockContainer.Stop();
|
||||||
|
|
||||||
FailOverlay.Retries = RestartCount;
|
FailOverlay.Retries = RestartCount;
|
||||||
@ -1028,10 +1029,7 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
// if arriving here and the results screen preparation task hasn't run, it's safe to say the user has not completed the beatmap.
|
// if arriving here and the results screen preparation task hasn't run, it's safe to say the user has not completed the beatmap.
|
||||||
if (prepareScoreForDisplayTask == null)
|
if (prepareScoreForDisplayTask == null)
|
||||||
{
|
ScoreProcessor.FailScore(Score.ScoreInfo);
|
||||||
Score.ScoreInfo.Passed = false;
|
|
||||||
Score.ScoreInfo.Rank = ScoreRank.F;
|
|
||||||
}
|
|
||||||
|
|
||||||
// EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous.
|
// EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous.
|
||||||
// To resolve test failures, forcefully end playing synchronously when this screen exits.
|
// To resolve test failures, forcefully end playing synchronously when this screen exits.
|
||||||
|
@ -133,7 +133,7 @@ namespace osu.Game.Screens.Ranking.Expanded
|
|||||||
FillMode = FillMode.Fit,
|
FillMode = FillMode.Fit,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scoreCounter = new TotalScoreCounter
|
scoreCounter = new TotalScoreCounter(!withFlair)
|
||||||
{
|
{
|
||||||
Margin = new MarginPadding { Top = 0, Bottom = 5 },
|
Margin = new MarginPadding { Top = 0, Bottom = 5 },
|
||||||
Current = { Value = 0 },
|
Current = { Value = 0 },
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
@ -21,13 +23,19 @@ namespace osu.Game.Screens.Ranking.Expanded
|
|||||||
{
|
{
|
||||||
private readonly APIUser user;
|
private readonly APIUser user;
|
||||||
|
|
||||||
|
private Sample appearanceSample;
|
||||||
|
|
||||||
|
private readonly bool playAppearanceSound;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="ExpandedPanelTopContent"/>.
|
/// Creates a new <see cref="ExpandedPanelTopContent"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="user">The <see cref="APIUser"/> to display.</param>
|
/// <param name="user">The <see cref="APIUser"/> to display.</param>
|
||||||
public ExpandedPanelTopContent(APIUser user)
|
/// <param name="playAppearanceSound">Whether the appearance sample should play</param>
|
||||||
|
public ExpandedPanelTopContent(APIUser user, bool playAppearanceSound = false)
|
||||||
{
|
{
|
||||||
this.user = user;
|
this.user = user;
|
||||||
|
this.playAppearanceSound = playAppearanceSound;
|
||||||
Anchor = Anchor.TopCentre;
|
Anchor = Anchor.TopCentre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
@ -35,8 +43,10 @@ namespace osu.Game.Screens.Ranking.Expanded
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(AudioManager audio)
|
||||||
{
|
{
|
||||||
|
appearanceSample = audio.Samples.Get(@"Results/score-panel-top-appear");
|
||||||
|
|
||||||
InternalChild = new FillFlowContainer
|
InternalChild = new FillFlowContainer
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
@ -62,5 +72,13 @@ namespace osu.Game.Screens.Ranking.Expanded
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
if (playAppearanceSound)
|
||||||
|
appearanceSample?.Play();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,11 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Audio;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -22,11 +26,35 @@ namespace osu.Game.Screens.Ranking.Expanded
|
|||||||
|
|
||||||
protected override Easing RollingEasing => AccuracyCircle.ACCURACY_TRANSFORM_EASING;
|
protected override Easing RollingEasing => AccuracyCircle.ACCURACY_TRANSFORM_EASING;
|
||||||
|
|
||||||
public TotalScoreCounter()
|
private readonly bool playSamples;
|
||||||
|
|
||||||
|
private readonly Bindable<double> tickPlaybackRate = new Bindable<double>();
|
||||||
|
|
||||||
|
private double lastSampleTime;
|
||||||
|
|
||||||
|
private DrawableSample sampleTick;
|
||||||
|
|
||||||
|
public TotalScoreCounter(bool playSamples = false)
|
||||||
{
|
{
|
||||||
// Todo: AutoSize X removed here due to https://github.com/ppy/osu-framework/issues/3369
|
// Todo: AutoSize X removed here due to https://github.com/ppy/osu-framework/issues/3369
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
|
|
||||||
|
this.playSamples = playSamples;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(AudioManager audio)
|
||||||
|
{
|
||||||
|
AddInternal(sampleTick = new DrawableSample(audio.Samples.Get(@"Results/score-tick-lesser")));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
if (playSamples)
|
||||||
|
Current.BindValueChanged(_ => startTicking());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override LocalisableString FormatCount(long count) => count.ToString("N0");
|
protected override LocalisableString FormatCount(long count) => count.ToString("N0");
|
||||||
@ -39,5 +67,35 @@ namespace osu.Game.Screens.Ranking.Expanded
|
|||||||
s.Font = OsuFont.Torus.With(size: 60, weight: FontWeight.Light, fixedWidth: true);
|
s.Font = OsuFont.Torus.With(size: 60, weight: FontWeight.Light, fixedWidth: true);
|
||||||
s.Spacing = new Vector2(-5, 0);
|
s.Spacing = new Vector2(-5, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
public override long DisplayedCount
|
||||||
|
{
|
||||||
|
get => base.DisplayedCount;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (base.DisplayedCount == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
base.DisplayedCount = value;
|
||||||
|
|
||||||
|
if (playSamples && Time.Current > lastSampleTime + tickPlaybackRate.Value)
|
||||||
|
{
|
||||||
|
sampleTick?.Play();
|
||||||
|
lastSampleTime = Time.Current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startTicking()
|
||||||
|
{
|
||||||
|
const double tick_debounce_rate_start = 10f;
|
||||||
|
const double tick_debounce_rate_end = 100f;
|
||||||
|
const double tick_volume_start = 0.5f;
|
||||||
|
const double tick_volume_end = 1.0f;
|
||||||
|
|
||||||
|
this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_start);
|
||||||
|
this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_end, RollingDuration, Easing.OutSine);
|
||||||
|
sampleTick.VolumeTo(tick_volume_start).Then().VolumeTo(tick_volume_end, RollingDuration, Easing.OutSine);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -60,6 +62,8 @@ namespace osu.Game.Screens.Ranking
|
|||||||
private readonly bool allowRetry;
|
private readonly bool allowRetry;
|
||||||
private readonly bool allowWatchingReplay;
|
private readonly bool allowWatchingReplay;
|
||||||
|
|
||||||
|
private Sample popInSample;
|
||||||
|
|
||||||
protected ResultsScreen(ScoreInfo score, bool allowRetry, bool allowWatchingReplay = true)
|
protected ResultsScreen(ScoreInfo score, bool allowRetry, bool allowWatchingReplay = true)
|
||||||
{
|
{
|
||||||
Score = score;
|
Score = score;
|
||||||
@ -70,10 +74,12 @@ namespace osu.Game.Screens.Ranking
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(AudioManager audio)
|
||||||
{
|
{
|
||||||
FillFlowContainer buttons;
|
FillFlowContainer buttons;
|
||||||
|
|
||||||
|
popInSample = audio.Samples.Get(@"UI/overlay-pop-in");
|
||||||
|
|
||||||
InternalChild = new GridContainer
|
InternalChild = new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
@ -244,6 +250,8 @@ namespace osu.Game.Screens.Ranking
|
|||||||
});
|
});
|
||||||
|
|
||||||
bottomPanel.FadeTo(1, 250);
|
bottomPanel.FadeTo(1, 250);
|
||||||
|
|
||||||
|
popInSample?.Play();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool OnExiting(ScreenExitEvent e)
|
public override bool OnExiting(ScreenExitEvent e)
|
||||||
|
@ -6,13 +6,16 @@
|
|||||||
using System;
|
using System;
|
||||||
using osu.Framework;
|
using osu.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Audio;
|
||||||
using osu.Framework.Graphics.Colour;
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Ranking.Contracted;
|
using osu.Game.Screens.Ranking.Contracted;
|
||||||
using osu.Game.Screens.Ranking.Expanded;
|
using osu.Game.Screens.Ranking.Expanded;
|
||||||
@ -93,9 +96,12 @@ namespace osu.Game.Screens.Ranking
|
|||||||
|
|
||||||
public readonly ScoreInfo Score;
|
public readonly ScoreInfo Score;
|
||||||
|
|
||||||
private bool displayWithFlair;
|
[Resolved]
|
||||||
|
private OsuGameBase game { get; set; }
|
||||||
|
|
||||||
private Container content;
|
private DrawableAudioMixer mixer;
|
||||||
|
|
||||||
|
private bool displayWithFlair;
|
||||||
|
|
||||||
private Container topLayerContainer;
|
private Container topLayerContainer;
|
||||||
private Drawable topLayerBackground;
|
private Drawable topLayerBackground;
|
||||||
@ -107,6 +113,8 @@ namespace osu.Game.Screens.Ranking
|
|||||||
private Container middleLayerContentContainer;
|
private Container middleLayerContentContainer;
|
||||||
private Drawable middleLayerContent;
|
private Drawable middleLayerContent;
|
||||||
|
|
||||||
|
private DrawableSample samplePanelFocus;
|
||||||
|
|
||||||
public ScorePanel(ScoreInfo score, bool isNewLocalScore = false)
|
public ScorePanel(ScoreInfo score, bool isNewLocalScore = false)
|
||||||
{
|
{
|
||||||
Score = score;
|
Score = score;
|
||||||
@ -116,13 +124,13 @@ namespace osu.Game.Screens.Ranking
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(AudioManager audio)
|
||||||
{
|
{
|
||||||
// ScorePanel doesn't include the top extruding area in its own size.
|
// ScorePanel doesn't include the top extruding area in its own size.
|
||||||
// Adding a manual offset here allows the expanded version to take on an "acceptable" vertical centre when at 100% UI scale.
|
// Adding a manual offset here allows the expanded version to take on an "acceptable" vertical centre when at 100% UI scale.
|
||||||
const float vertical_fudge = 20;
|
const float vertical_fudge = 20;
|
||||||
|
|
||||||
InternalChild = content = new Container
|
InternalChild = mixer = new DrawableAudioMixer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -174,7 +182,8 @@ namespace osu.Game.Screens.Ranking
|
|||||||
},
|
},
|
||||||
middleLayerContentContainer = new Container { RelativeSizeAxes = Axes.Both }
|
middleLayerContentContainer = new Container { RelativeSizeAxes = Axes.Both }
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
samplePanelFocus = new DrawableSample(audio.Samples.Get(@"Results/score-panel-focus"))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -202,12 +211,32 @@ namespace osu.Game.Screens.Ranking
|
|||||||
state = value;
|
state = value;
|
||||||
|
|
||||||
if (IsLoaded)
|
if (IsLoaded)
|
||||||
|
{
|
||||||
updateState();
|
updateState();
|
||||||
|
|
||||||
|
if (value == PanelState.Expanded)
|
||||||
|
playAppearSample();
|
||||||
|
}
|
||||||
|
|
||||||
StateChanged?.Invoke(value);
|
StateChanged?.Invoke(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
mixer.Balance.Value = (ScreenSpaceDrawQuad.Centre.X / game.ScreenSpaceDrawQuad.Width) * 2 - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void playAppearSample()
|
||||||
|
{
|
||||||
|
var channel = samplePanelFocus?.GetChannel();
|
||||||
|
if (channel == null) return;
|
||||||
|
|
||||||
|
channel.Frequency.Value = 0.99 + RNG.NextDouble(0.2);
|
||||||
|
channel.Play();
|
||||||
|
}
|
||||||
|
|
||||||
private void updateState()
|
private void updateState()
|
||||||
{
|
{
|
||||||
topLayerContent?.FadeOut(content_fade_duration).Expire();
|
topLayerContent?.FadeOut(content_fade_duration).Expire();
|
||||||
@ -221,7 +250,8 @@ namespace osu.Game.Screens.Ranking
|
|||||||
topLayerBackground.FadeColour(expanded_top_layer_colour, RESIZE_DURATION, Easing.OutQuint);
|
topLayerBackground.FadeColour(expanded_top_layer_colour, RESIZE_DURATION, Easing.OutQuint);
|
||||||
middleLayerBackground.FadeColour(expanded_middle_layer_colour, RESIZE_DURATION, Easing.OutQuint);
|
middleLayerBackground.FadeColour(expanded_middle_layer_colour, RESIZE_DURATION, Easing.OutQuint);
|
||||||
|
|
||||||
topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User) { Alpha = 0 });
|
bool firstLoad = topLayerContent == null;
|
||||||
|
topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User, firstLoad) { Alpha = 0 });
|
||||||
middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, displayWithFlair) { Alpha = 0 });
|
middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, displayWithFlair) { Alpha = 0 });
|
||||||
|
|
||||||
// only the first expanded display should happen with flair.
|
// only the first expanded display should happen with flair.
|
||||||
@ -244,7 +274,7 @@ namespace osu.Game.Screens.Ranking
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
content.ResizeTo(Size, RESIZE_DURATION, Easing.OutQuint);
|
mixer.ResizeTo(Size, RESIZE_DURATION, Easing.OutQuint);
|
||||||
|
|
||||||
bool topLayerExpanded = topLayerContainer.Y < 0;
|
bool topLayerExpanded = topLayerContainer.Y < 0;
|
||||||
|
|
||||||
|
@ -8,7 +8,10 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
@ -35,6 +38,10 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
private readonly Container content;
|
private readonly Container content;
|
||||||
private readonly LoadingSpinner spinner;
|
private readonly LoadingSpinner spinner;
|
||||||
|
|
||||||
|
private bool wasOpened;
|
||||||
|
private Sample popInSample;
|
||||||
|
private Sample popOutSample;
|
||||||
|
|
||||||
public StatisticsPanel()
|
public StatisticsPanel()
|
||||||
{
|
{
|
||||||
InternalChild = new Container
|
InternalChild = new Container
|
||||||
@ -56,9 +63,12 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(AudioManager audio)
|
||||||
{
|
{
|
||||||
Score.BindValueChanged(populateStatistics, true);
|
Score.BindValueChanged(populateStatistics, true);
|
||||||
|
|
||||||
|
popInSample = audio.Samples.Get(@"Results/statistics-panel-pop-in");
|
||||||
|
popOutSample = audio.Samples.Get(@"Results/statistics-panel-pop-out");
|
||||||
}
|
}
|
||||||
|
|
||||||
private CancellationTokenSource loadCancellation;
|
private CancellationTokenSource loadCancellation;
|
||||||
@ -81,18 +91,16 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
spinner.Show();
|
spinner.Show();
|
||||||
|
|
||||||
var localCancellationSource = loadCancellation = new CancellationTokenSource();
|
var localCancellationSource = loadCancellation = new CancellationTokenSource();
|
||||||
IBeatmap playableBeatmap = null;
|
|
||||||
|
var workingBeatmap = beatmapManager.GetWorkingBeatmap(newScore.BeatmapInfo);
|
||||||
|
|
||||||
// Todo: The placement of this is temporary. Eventually we'll both generate the playable beatmap _and_ run through it in a background task to generate the hit events.
|
// Todo: The placement of this is temporary. Eventually we'll both generate the playable beatmap _and_ run through it in a background task to generate the hit events.
|
||||||
Task.Run(() =>
|
Task.Run(() => workingBeatmap.GetPlayableBeatmap(newScore.Ruleset, newScore.Mods), loadCancellation.Token).ContinueWith(task => Schedule(() =>
|
||||||
{
|
|
||||||
playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.BeatmapInfo).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods);
|
|
||||||
}, loadCancellation.Token).ContinueWith(_ => Schedule(() =>
|
|
||||||
{
|
{
|
||||||
bool hitEventsAvailable = newScore.HitEvents.Count != 0;
|
bool hitEventsAvailable = newScore.HitEvents.Count != 0;
|
||||||
Container<Drawable> container;
|
Container<Drawable> container;
|
||||||
|
|
||||||
var statisticRows = newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap);
|
var statisticRows = newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, task.GetResultSafely());
|
||||||
|
|
||||||
if (!hitEventsAvailable && statisticRows.SelectMany(r => r.Columns).All(c => c.RequiresHitEvents))
|
if (!hitEventsAvailable && statisticRows.SelectMany(r => r.Columns).All(c => c.RequiresHitEvents))
|
||||||
{
|
{
|
||||||
@ -216,9 +224,21 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void PopIn() => this.FadeIn(150, Easing.OutQuint);
|
protected override void PopIn()
|
||||||
|
{
|
||||||
|
this.FadeIn(150, Easing.OutQuint);
|
||||||
|
|
||||||
protected override void PopOut() => this.FadeOut(150, Easing.OutQuint);
|
popInSample?.Play();
|
||||||
|
wasOpened = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopOut()
|
||||||
|
{
|
||||||
|
this.FadeOut(150, Easing.OutQuint);
|
||||||
|
|
||||||
|
if (wasOpened)
|
||||||
|
popOutSample?.Play();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
|
@ -115,42 +115,53 @@ namespace osu.Game.Screens.Select
|
|||||||
Origin = Anchor.BottomLeft,
|
Origin = Anchor.BottomLeft,
|
||||||
Anchor = Anchor.BottomLeft,
|
Anchor = Anchor.BottomLeft,
|
||||||
},
|
},
|
||||||
new FillFlowContainer
|
new GridContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.BottomRight,
|
Anchor = Anchor.BottomRight,
|
||||||
Origin = Anchor.BottomRight,
|
Origin = Anchor.BottomRight,
|
||||||
Direction = FillDirection.Horizontal,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Spacing = new Vector2(OsuTabControl<SortMode>.HORIZONTAL_SPACING, 0),
|
ColumnDimensions = new[]
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
new OsuTabControlCheckbox
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
new Dimension(GridSizeMode.Absolute, OsuTabControl<SortMode>.HORIZONTAL_SPACING),
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.Absolute, OsuTabControl<SortMode>.HORIZONTAL_SPACING),
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
},
|
||||||
|
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new[]
|
||||||
{
|
{
|
||||||
Text = "Show converted",
|
new OsuSpriteText
|
||||||
Current = config.GetBindable<bool>(OsuSetting.ShowConvertedBeatmaps),
|
{
|
||||||
Anchor = Anchor.BottomRight,
|
Text = SortStrings.Default,
|
||||||
Origin = Anchor.BottomRight,
|
Font = OsuFont.GetFont(size: 14),
|
||||||
},
|
Margin = new MarginPadding(5),
|
||||||
sortTabs = new OsuTabControl<SortMode>
|
Anchor = Anchor.BottomRight,
|
||||||
{
|
Origin = Anchor.BottomRight,
|
||||||
RelativeSizeAxes = Axes.X,
|
},
|
||||||
Width = 0.5f,
|
Empty(),
|
||||||
Height = 24,
|
sortTabs = new OsuTabControl<SortMode>
|
||||||
AutoSort = true,
|
{
|
||||||
Anchor = Anchor.BottomRight,
|
RelativeSizeAxes = Axes.X,
|
||||||
Origin = Anchor.BottomRight,
|
Height = 24,
|
||||||
AccentColour = colours.GreenLight,
|
AutoSort = true,
|
||||||
Current = { BindTarget = sortMode }
|
Anchor = Anchor.BottomRight,
|
||||||
},
|
Origin = Anchor.BottomRight,
|
||||||
new OsuSpriteText
|
AccentColour = colours.GreenLight,
|
||||||
{
|
Current = { BindTarget = sortMode }
|
||||||
Text = SortStrings.Default,
|
},
|
||||||
Font = OsuFont.GetFont(size: 14),
|
Empty(),
|
||||||
Margin = new MarginPadding(5),
|
new OsuTabControlCheckbox
|
||||||
Anchor = Anchor.BottomRight,
|
{
|
||||||
Origin = Anchor.BottomRight,
|
Text = "Show converted",
|
||||||
},
|
Current = config.GetBindable<bool>(OsuSetting.ShowConvertedBeatmaps),
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Screens.Utility
|
|||||||
|
|
||||||
public readonly Bindable<LatencyVisualMode> VisualMode = new Bindable<LatencyVisualMode>();
|
public readonly Bindable<LatencyVisualMode> VisualMode = new Bindable<LatencyVisualMode>();
|
||||||
|
|
||||||
public CursorContainer? Cursor { get; private set; }
|
public CursorContainer? MenuCursor { get; private set; }
|
||||||
|
|
||||||
public bool ProvidingUserCursor => IsActiveArea.Value;
|
public bool ProvidingUserCursor => IsActiveArea.Value;
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ namespace osu.Game.Screens.Utility
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
Cursor = new LatencyCursorContainer
|
MenuCursor = new LatencyCursorContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
@ -105,7 +105,7 @@ namespace osu.Game.Screens.Utility
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
Cursor = new LatencyCursorContainer
|
MenuCursor = new LatencyCursorContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
@ -119,7 +119,7 @@ namespace osu.Game.Screens.Utility
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
Cursor = new LatencyCursorContainer
|
MenuCursor = new LatencyCursorContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
|
@ -171,6 +171,11 @@ namespace osu.Game.Tests.Visual
|
|||||||
API.Login("Rhythm Champion", "osu!");
|
API.Login("Rhythm Champion", "osu!");
|
||||||
|
|
||||||
Dependencies.Get<SessionStatics>().SetValue(Static.MutedAudioNotificationShownOnce, true);
|
Dependencies.Get<SessionStatics>().SetValue(Static.MutedAudioNotificationShownOnce, true);
|
||||||
|
|
||||||
|
// set applied version to latest so that the BackgroundBeatmapProcessor doesn't consider
|
||||||
|
// beatmap star ratings as outdated and reset them throughout the test.
|
||||||
|
foreach (var ruleset in RulesetStore.AvailableRulesets)
|
||||||
|
ruleset.LastAppliedDifficultyVersion = ruleset.CreateInstance().CreateDifficultyCalculator(Beatmap.Default).Version;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
|
@ -38,11 +38,11 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
protected OsuManualInputManagerTestScene()
|
protected OsuManualInputManagerTestScene()
|
||||||
{
|
{
|
||||||
MenuCursorContainer cursorContainer;
|
GlobalCursorDisplay cursorDisplay;
|
||||||
|
|
||||||
CompositeDrawable mainContent = cursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both };
|
CompositeDrawable mainContent = cursorDisplay = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
cursorContainer.Child = content = new OsuTooltipContainer(cursorContainer.Cursor)
|
cursorDisplay.Child = content = new OsuTooltipContainer(cursorDisplay.MenuCursor)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
};
|
};
|
||||||
|
@ -36,8 +36,8 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="10.14.0" />
|
<PackageReference Include="Realm" Version="10.14.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2022.720.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2022.722.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.716.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.722.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.19.0" />
|
<PackageReference Include="Sentry" Version="3.19.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.32.1" />
|
<PackageReference Include="SharpCompress" Version="0.32.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
|
@ -61,8 +61,8 @@
|
|||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.720.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.722.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.716.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.722.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
|
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
@ -84,7 +84,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2022.720.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2022.722.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.32.1" />
|
<PackageReference Include="SharpCompress" Version="0.32.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||||
|
Loading…
Reference in New Issue
Block a user