mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 10:52:53 +08:00
Merge remote-tracking branch 'origin/bubble_mod_implementation_clean' into bubble_mod_implementation_clean
This commit is contained in:
commit
d413b69f55
@ -17,7 +17,7 @@
|
||||
<EmbeddedResource Include="Resources\**\*.*" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Code Analysis">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" PrivateAssets="All" />
|
||||
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Code Analysis">
|
||||
|
@ -9,9 +9,9 @@
|
||||
<GenerateProgramFile>false</GenerateProgramFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\osu.Game.Rulesets.EmptyFreeform\osu.Game.Rulesets.EmptyFreeform.csproj" />
|
||||
|
@ -9,9 +9,9 @@
|
||||
<GenerateProgramFile>false</GenerateProgramFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />
|
||||
|
@ -9,9 +9,9 @@
|
||||
<GenerateProgramFile>false</GenerateProgramFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\osu.Game.Rulesets.EmptyScrolling\osu.Game.Rulesets.EmptyScrolling.csproj" />
|
||||
|
@ -9,9 +9,9 @@
|
||||
<GenerateProgramFile>false</GenerateProgramFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />
|
||||
|
@ -98,7 +98,7 @@ namespace osu.Desktop
|
||||
|
||||
if (status.Value is UserStatusOnline && activity.Value != null)
|
||||
{
|
||||
presence.State = truncate(activity.Value.Status);
|
||||
presence.State = truncate(activity.Value.GetStatus(privacyMode.Value == DiscordRichPresenceMode.Limited));
|
||||
presence.Details = truncate(getDetails(activity.Value));
|
||||
|
||||
if (getBeatmap(activity.Value) is IBeatmapInfo beatmap && beatmap.OnlineID > 0)
|
||||
@ -169,7 +169,7 @@ namespace osu.Desktop
|
||||
case UserActivity.InGame game:
|
||||
return game.BeatmapInfo;
|
||||
|
||||
case UserActivity.Editing edit:
|
||||
case UserActivity.EditingBeatmap edit:
|
||||
return edit.BeatmapInfo;
|
||||
}
|
||||
|
||||
@ -183,9 +183,12 @@ namespace osu.Desktop
|
||||
case UserActivity.InGame game:
|
||||
return game.BeatmapInfo.ToString() ?? string.Empty;
|
||||
|
||||
case UserActivity.Editing edit:
|
||||
case UserActivity.EditingBeatmap edit:
|
||||
return edit.BeatmapInfo.ToString() ?? string.Empty;
|
||||
|
||||
case UserActivity.WatchingReplay watching:
|
||||
return watching.BeatmapInfo.ToString();
|
||||
|
||||
case UserActivity.InLobby lobby:
|
||||
return privacyMode.Value == DiscordRichPresenceMode.Limited ? string.Empty : lobby.Room.Name.Value;
|
||||
}
|
||||
|
@ -26,8 +26,8 @@
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Clowd.Squirrel" Version="2.9.42" />
|
||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||
<PackageReference Include="System.IO.Packaging" Version="6.0.0" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.1.1.14" />
|
||||
<PackageReference Include="System.IO.Packaging" Version="7.0.0" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.1.3.18" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Resources">
|
||||
<EmbeddedResource Include="lazer.ico" />
|
||||
|
@ -7,9 +7,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.4" />
|
||||
<PackageReference Include="nunit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -1,9 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Project">
|
||||
<OutputType>WinExe</OutputType>
|
||||
|
@ -1,9 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Project">
|
||||
<OutputType>WinExe</OutputType>
|
||||
|
@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
};
|
||||
}
|
||||
|
||||
private partial class ManiaScrollSlider : OsuSliderBar<double>
|
||||
private partial class ManiaScrollSlider : RoundedSliderBar<double>
|
||||
{
|
||||
public override LocalisableString TooltipText => RulesetSettingsStrings.ScrollSpeedTooltip(Current.Value, (int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / Current.Value));
|
||||
}
|
||||
|
@ -2,12 +2,15 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Animations;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
@ -34,6 +37,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
private Drawable? lightContainer;
|
||||
|
||||
private Drawable? light;
|
||||
private LegacyNoteBodyStyle? bodyStyle;
|
||||
|
||||
public LegacyBodyPiece()
|
||||
{
|
||||
@ -54,9 +58,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
float lightScale = GetColumnSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.HoldNoteLightScale)?.Value
|
||||
?? 1;
|
||||
|
||||
float minimumColumnWidth = GetColumnSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.MinimumColumnWidth)?.Value
|
||||
?? 1;
|
||||
|
||||
// Create a temporary animation to retrieve the number of frames, in an effort to calculate the intended frame length.
|
||||
// This animation is discarded and re-queried with the appropriate frame length afterwards.
|
||||
var tmp = skin.GetAnimation(lightImage, true, false);
|
||||
@ -83,7 +84,14 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
};
|
||||
}
|
||||
|
||||
bodySprite = skin.GetAnimation(imageName, WrapMode.ClampToEdge, WrapMode.ClampToEdge, true, true).With(d =>
|
||||
bodyStyle = skin.GetConfig<ManiaSkinConfigurationLookup, LegacyNoteBodyStyle>(new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.NoteBodyStyle))?.Value;
|
||||
|
||||
var wrapMode = bodyStyle == LegacyNoteBodyStyle.Stretch ? WrapMode.ClampToEdge : WrapMode.Repeat;
|
||||
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
isHitting.BindTo(holdNote.IsHitting);
|
||||
|
||||
bodySprite = skin.GetAnimation(imageName, wrapMode, wrapMode, true, true).With(d =>
|
||||
{
|
||||
if (d == null)
|
||||
return;
|
||||
@ -94,16 +102,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
d.Anchor = Anchor.TopCentre;
|
||||
d.RelativeSizeAxes = Axes.Both;
|
||||
d.Size = Vector2.One;
|
||||
d.FillMode = FillMode.Stretch;
|
||||
d.Height = minimumColumnWidth / d.DrawWidth * 1.6f; // constant matching stable.
|
||||
// Todo: Wrap?
|
||||
});
|
||||
|
||||
if (bodySprite != null)
|
||||
InternalChild = bodySprite;
|
||||
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
isHitting.BindTo(holdNote.IsHitting);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -165,7 +168,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
if (bodySprite != null)
|
||||
{
|
||||
bodySprite.Origin = Anchor.BottomCentre;
|
||||
bodySprite.Scale = new Vector2(1, -1);
|
||||
bodySprite.Scale = new Vector2(bodySprite.Scale.X, Math.Abs(bodySprite.Scale.Y) * -1);
|
||||
}
|
||||
|
||||
if (light != null)
|
||||
@ -176,7 +179,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
if (bodySprite != null)
|
||||
{
|
||||
bodySprite.Origin = Anchor.TopCentre;
|
||||
bodySprite.Scale = Vector2.One;
|
||||
bodySprite.Scale = new Vector2(bodySprite.Scale.X, Math.Abs(bodySprite.Scale.Y));
|
||||
}
|
||||
|
||||
if (light != null)
|
||||
@ -207,6 +210,29 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
{
|
||||
base.Update();
|
||||
missFadeTime.Value ??= holdNote.HoldBrokenTime;
|
||||
|
||||
// here we go...
|
||||
switch (bodyStyle)
|
||||
{
|
||||
case LegacyNoteBodyStyle.Stretch:
|
||||
// this is how lazer works by default. nothing required.
|
||||
break;
|
||||
|
||||
default:
|
||||
// this is where things get fucked up.
|
||||
// honestly there's three modes to handle here but they seem really pointless?
|
||||
// let's wait to see if anyone actually uses them in skins.
|
||||
if (bodySprite != null)
|
||||
{
|
||||
var sprite = bodySprite as Sprite ?? bodySprite.ChildrenOfType<Sprite>().Single();
|
||||
|
||||
bodySprite.FillMode = FillMode.Stretch;
|
||||
// i dunno this looks about right??
|
||||
bodySprite.Scale = new Vector2(1, 32800 / sprite.DrawHeight);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
|
@ -1,10 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="Moq" Version="4.18.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
<PackageReference Include="Moq" Version="4.18.4" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Project">
|
||||
<OutputType>WinExe</OutputType>
|
||||
|
@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
[TestCase("slider-conversion-v6")]
|
||||
[TestCase("slider-conversion-v14")]
|
||||
[TestCase("slider-generating-drumroll-2")]
|
||||
[TestCase("file-hitsamples")]
|
||||
public void Test(string name) => base.Test(name);
|
||||
|
||||
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
||||
|
@ -1,9 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Project">
|
||||
<OutputType>WinExe</OutputType>
|
||||
|
@ -2,13 +2,15 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset<TaikoHitObject>
|
||||
public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset<TaikoHitObject>, IApplicableToDrawableHitObject
|
||||
{
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)
|
||||
{
|
||||
@ -18,5 +20,11 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
var playfield = (TaikoPlayfield)drawableRuleset.Playfield;
|
||||
playfield.ClassicHitTargetPosition.Value = true;
|
||||
}
|
||||
|
||||
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
|
||||
{
|
||||
if (drawable is DrawableTaikoHitObject hit)
|
||||
hit.SnapJudgementLocation = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -207,6 +207,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
const float gravity_time = 300;
|
||||
const float gravity_travel_height = 200;
|
||||
|
||||
if (SnapJudgementLocation)
|
||||
MainPiece.MoveToX(-X);
|
||||
|
||||
this.ScaleTo(0.8f, gravity_time * 2, Easing.OutQuad);
|
||||
|
||||
this.MoveToY(-gravity_travel_height, gravity_time, Easing.Out)
|
||||
|
@ -25,6 +25,15 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
|
||||
private readonly Container nonProxiedContent;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the location of the hit should be snapped to the hit target before animating.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is how osu-stable worked, but notably is not how TnT works.
|
||||
/// Not snapping results in less visual feedback on hit accuracy.
|
||||
/// </remarks>
|
||||
public bool SnapJudgementLocation { get; set; }
|
||||
|
||||
protected DrawableTaikoHitObject([CanBeNull] TaikoHitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
|
@ -0,0 +1 @@
|
||||
{"Mappings":[{"StartTime":500.0,"Objects":[{"StartTime":500.0,"EndTime":500.0,"IsRim":false,"IsCentre":true,"IsDrumRoll":false,"IsSwell":false,"IsStrong":false}]},{"StartTime":1000.0,"Objects":[{"StartTime":1000.0,"EndTime":1000.0,"IsRim":true,"IsCentre":false,"IsDrumRoll":false,"IsSwell":false,"IsStrong":false}]},{"StartTime":1500.0,"Objects":[{"StartTime":1500.0,"EndTime":1500.0,"IsRim":true,"IsCentre":false,"IsDrumRoll":false,"IsSwell":false,"IsStrong":false}]},{"StartTime":2000.0,"Objects":[{"StartTime":2000.0,"EndTime":2000.0,"IsRim":true,"IsCentre":false,"IsDrumRoll":false,"IsSwell":false,"IsStrong":false}]},{"StartTime":2500.0,"Objects":[{"StartTime":2500.0,"EndTime":2500.0,"IsRim":false,"IsCentre":true,"IsDrumRoll":false,"IsSwell":false,"IsStrong":true}]},{"StartTime":3000.0,"Objects":[{"StartTime":3000.0,"EndTime":3000.0,"IsRim":true,"IsCentre":false,"IsDrumRoll":false,"IsSwell":false,"IsStrong":true}]},{"StartTime":3500.0,"Objects":[{"StartTime":3500.0,"EndTime":3500.0,"IsRim":true,"IsCentre":false,"IsDrumRoll":false,"IsSwell":false,"IsStrong":true}]},{"StartTime":4000.0,"Objects":[{"StartTime":4000.0,"EndTime":4000.0,"IsRim":true,"IsCentre":false,"IsDrumRoll":false,"IsSwell":false,"IsStrong":true}]}]}
|
@ -0,0 +1,22 @@
|
||||
osu file format v14
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:5
|
||||
CircleSize:7
|
||||
OverallDifficulty:6.5
|
||||
ApproachRate:10
|
||||
SliderMultiplier:1.9
|
||||
SliderTickRate:1
|
||||
|
||||
[TimingPoints]
|
||||
500,500,4,2,1,50,1,0
|
||||
|
||||
[HitObjects]
|
||||
256,192,500,1,0,0:0:0:0:sample.ogg
|
||||
256,192,1000,1,8,0:0:0:0:sample.ogg
|
||||
256,192,1500,1,2,0:0:0:0:sample.ogg
|
||||
256,192,2000,1,10,0:0:0:0:sample.ogg
|
||||
256,192,2500,1,4,0:0:0:0:sample.ogg
|
||||
256,192,3000,1,12,0:0:0:0:sample.ogg
|
||||
256,192,3500,1,6,0:0:0:0:sample.ogg
|
||||
256,192,4000,1,14,0:0:0:0:sample.ogg
|
@ -209,9 +209,8 @@ namespace osu.Game.Rulesets.Taiko
|
||||
HitResult.Great,
|
||||
HitResult.Ok,
|
||||
|
||||
HitResult.SmallTickHit,
|
||||
|
||||
HitResult.SmallBonus,
|
||||
HitResult.LargeBonus,
|
||||
};
|
||||
}
|
||||
|
||||
@ -220,6 +219,9 @@ namespace osu.Game.Rulesets.Taiko
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.SmallBonus:
|
||||
return "drum tick";
|
||||
|
||||
case HitResult.LargeBonus:
|
||||
return "bonus";
|
||||
}
|
||||
|
||||
|
@ -214,7 +214,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.That(oneTime.EndTime, Is.EqualTo(4000 + loop_duration));
|
||||
|
||||
StoryboardSprite manyTimes = background.Elements.OfType<StoryboardSprite>().Single(s => s.Path == "many-times.png");
|
||||
Assert.That(manyTimes.EndTime, Is.EqualTo(9000 + 40 * loop_duration));
|
||||
// It is intentional that we don't consider the loop count (40) as part of the end time calculation to match stable's handling.
|
||||
// If we were to include the loop count, storyboards which loop for stupid long loop counts would continue playing the outro forever.
|
||||
Assert.That(manyTimes.EndTime, Is.EqualTo(9000 + loop_duration));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
@ -12,7 +10,7 @@ using osu.Game.Screens.Edit;
|
||||
namespace osu.Game.Tests.Editing
|
||||
{
|
||||
[TestFixture]
|
||||
public class EditorChangeHandlerTest
|
||||
public class BeatmapEditorChangeHandlerTest
|
||||
{
|
||||
private int stateChangedFired;
|
||||
|
||||
@ -23,18 +21,23 @@ namespace osu.Game.Tests.Editing
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSaveRestoreState()
|
||||
public void TestSaveRestoreStateUsingTransaction()
|
||||
{
|
||||
var (handler, beatmap) = createChangeHandler();
|
||||
|
||||
Assert.That(handler.CanUndo.Value, Is.False);
|
||||
Assert.That(handler.CanRedo.Value, Is.False);
|
||||
|
||||
addArbitraryChange(beatmap);
|
||||
handler.SaveState();
|
||||
handler.BeginChange();
|
||||
|
||||
// Initial state will be saved on BeginChange
|
||||
Assert.That(stateChangedFired, Is.EqualTo(1));
|
||||
|
||||
addArbitraryChange(beatmap);
|
||||
handler.EndChange();
|
||||
|
||||
Assert.That(stateChangedFired, Is.EqualTo(2));
|
||||
|
||||
Assert.That(handler.CanUndo.Value, Is.True);
|
||||
Assert.That(handler.CanRedo.Value, Is.False);
|
||||
|
||||
@ -43,7 +46,35 @@ namespace osu.Game.Tests.Editing
|
||||
Assert.That(handler.CanUndo.Value, Is.False);
|
||||
Assert.That(handler.CanRedo.Value, Is.True);
|
||||
|
||||
Assert.That(stateChangedFired, Is.EqualTo(3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSaveRestoreState()
|
||||
{
|
||||
var (handler, beatmap) = createChangeHandler();
|
||||
|
||||
Assert.That(handler.CanUndo.Value, Is.False);
|
||||
Assert.That(handler.CanRedo.Value, Is.False);
|
||||
|
||||
// Save initial state
|
||||
handler.SaveState();
|
||||
Assert.That(stateChangedFired, Is.EqualTo(1));
|
||||
|
||||
addArbitraryChange(beatmap);
|
||||
handler.SaveState();
|
||||
|
||||
Assert.That(stateChangedFired, Is.EqualTo(2));
|
||||
|
||||
Assert.That(handler.CanUndo.Value, Is.True);
|
||||
Assert.That(handler.CanRedo.Value, Is.False);
|
||||
|
||||
handler.RestoreState(-1);
|
||||
|
||||
Assert.That(handler.CanUndo.Value, Is.False);
|
||||
Assert.That(handler.CanRedo.Value, Is.True);
|
||||
|
||||
Assert.That(stateChangedFired, Is.EqualTo(3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -54,6 +85,10 @@ namespace osu.Game.Tests.Editing
|
||||
Assert.That(handler.CanUndo.Value, Is.False);
|
||||
Assert.That(handler.CanRedo.Value, Is.False);
|
||||
|
||||
// Save initial state
|
||||
handler.SaveState();
|
||||
Assert.That(stateChangedFired, Is.EqualTo(1));
|
||||
|
||||
string originalHash = handler.CurrentStateHash;
|
||||
|
||||
addArbitraryChange(beatmap);
|
||||
@ -61,7 +96,7 @@ namespace osu.Game.Tests.Editing
|
||||
|
||||
Assert.That(handler.CanUndo.Value, Is.True);
|
||||
Assert.That(handler.CanRedo.Value, Is.False);
|
||||
Assert.That(stateChangedFired, Is.EqualTo(1));
|
||||
Assert.That(stateChangedFired, Is.EqualTo(2));
|
||||
|
||||
string hash = handler.CurrentStateHash;
|
||||
|
||||
@ -69,7 +104,7 @@ namespace osu.Game.Tests.Editing
|
||||
handler.RestoreState(-1);
|
||||
|
||||
Assert.That(originalHash, Is.EqualTo(handler.CurrentStateHash));
|
||||
Assert.That(stateChangedFired, Is.EqualTo(2));
|
||||
Assert.That(stateChangedFired, Is.EqualTo(3));
|
||||
|
||||
addArbitraryChange(beatmap);
|
||||
handler.SaveState();
|
||||
@ -84,12 +119,16 @@ namespace osu.Game.Tests.Editing
|
||||
Assert.That(handler.CanUndo.Value, Is.False);
|
||||
Assert.That(handler.CanRedo.Value, Is.False);
|
||||
|
||||
// Save initial state
|
||||
handler.SaveState();
|
||||
Assert.That(stateChangedFired, Is.EqualTo(1));
|
||||
|
||||
addArbitraryChange(beatmap);
|
||||
handler.SaveState();
|
||||
|
||||
Assert.That(handler.CanUndo.Value, Is.True);
|
||||
Assert.That(handler.CanRedo.Value, Is.False);
|
||||
Assert.That(stateChangedFired, Is.EqualTo(1));
|
||||
Assert.That(stateChangedFired, Is.EqualTo(2));
|
||||
|
||||
string hash = handler.CurrentStateHash;
|
||||
|
||||
@ -97,7 +136,7 @@ namespace osu.Game.Tests.Editing
|
||||
handler.SaveState();
|
||||
|
||||
Assert.That(hash, Is.EqualTo(handler.CurrentStateHash));
|
||||
Assert.That(stateChangedFired, Is.EqualTo(1));
|
||||
Assert.That(stateChangedFired, Is.EqualTo(2));
|
||||
|
||||
handler.RestoreState(-1);
|
||||
|
||||
@ -106,7 +145,7 @@ namespace osu.Game.Tests.Editing
|
||||
// we should only be able to restore once even though we saved twice.
|
||||
Assert.That(handler.CanUndo.Value, Is.False);
|
||||
Assert.That(handler.CanRedo.Value, Is.True);
|
||||
Assert.That(stateChangedFired, Is.EqualTo(2));
|
||||
Assert.That(stateChangedFired, Is.EqualTo(3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -114,11 +153,15 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var (handler, beatmap) = createChangeHandler();
|
||||
|
||||
// Save initial state
|
||||
handler.SaveState();
|
||||
Assert.That(stateChangedFired, Is.EqualTo(1));
|
||||
|
||||
Assert.That(handler.CanUndo.Value, Is.False);
|
||||
|
||||
for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++)
|
||||
{
|
||||
Assert.That(stateChangedFired, Is.EqualTo(i));
|
||||
Assert.That(stateChangedFired, Is.EqualTo(i + 1));
|
||||
|
||||
addArbitraryChange(beatmap);
|
||||
handler.SaveState();
|
||||
@ -169,7 +212,7 @@ namespace osu.Game.Tests.Editing
|
||||
},
|
||||
});
|
||||
|
||||
var changeHandler = new EditorChangeHandler(beatmap);
|
||||
var changeHandler = new BeatmapEditorChangeHandler(beatmap);
|
||||
|
||||
changeHandler.OnStateChange += () => stateChangedFired++;
|
||||
return (changeHandler, beatmap);
|
@ -31,8 +31,8 @@ namespace osu.Game.Tests.Visual.Audio
|
||||
|
||||
private WaveformTestBeatmap beatmap;
|
||||
|
||||
private OsuSliderBar<int> lowPassSlider;
|
||||
private OsuSliderBar<int> highPassSlider;
|
||||
private RoundedSliderBar<int> lowPassSlider;
|
||||
private RoundedSliderBar<int> highPassSlider;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Audio
|
||||
Text = $"Low Pass: {lowPassFilter.Cutoff}hz",
|
||||
Font = new FontUsage(size: 40)
|
||||
},
|
||||
lowPassSlider = new OsuSliderBar<int>
|
||||
lowPassSlider = new RoundedSliderBar<int>
|
||||
{
|
||||
Width = 500,
|
||||
Height = 50,
|
||||
@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Audio
|
||||
Text = $"High Pass: {highPassFilter.Cutoff}hz",
|
||||
Font = new FontUsage(size: 40)
|
||||
},
|
||||
highPassSlider = new OsuSliderBar<int>
|
||||
highPassSlider = new RoundedSliderBar<int>
|
||||
{
|
||||
Width = 500,
|
||||
Height = 50,
|
||||
|
24
osu.Game.Tests/Visual/Gameplay/TestSceneLetterboxOverlay.cs
Normal file
24
osu.Game.Tests/Visual/Gameplay/TestSceneLetterboxOverlay.cs
Normal file
@ -0,0 +1,24 @@
|
||||
// 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.Shapes;
|
||||
using osu.Game.Screens.Play.Break;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public partial class TestSceneLetterboxOverlay : OsuTestScene
|
||||
{
|
||||
public TestSceneLetterboxOverlay()
|
||||
{
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
new LetterboxOverlay()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,32 +1,64 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public partial class TestSceneReplayPlayer : RateAdjustedBeatmapTestScene
|
||||
{
|
||||
protected TestReplayPlayer Player;
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("Initialise player", () => Player = CreatePlayer(new OsuRuleset()));
|
||||
AddStep("Load player", () => LoadScreen(Player));
|
||||
AddUntilStep("player loaded", () => Player.IsLoaded);
|
||||
}
|
||||
protected TestReplayPlayer Player = null!;
|
||||
|
||||
[Test]
|
||||
public void TestPauseViaSpace()
|
||||
{
|
||||
loadPlayerWithBeatmap();
|
||||
|
||||
double? lastTime = null;
|
||||
|
||||
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
||||
|
||||
AddStep("Pause playback with space", () => InputManager.Key(Key.Space));
|
||||
|
||||
AddAssert("player not exited", () => Player.IsCurrentScreen());
|
||||
|
||||
AddUntilStep("Time stopped progressing", () =>
|
||||
{
|
||||
double current = Player.GameplayClockContainer.CurrentTime;
|
||||
bool changed = lastTime != current;
|
||||
lastTime = current;
|
||||
|
||||
return !changed;
|
||||
});
|
||||
|
||||
AddWaitStep("wait some", 10);
|
||||
|
||||
AddAssert("Time still stopped", () => lastTime == Player.GameplayClockContainer.CurrentTime);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPauseViaSpaceWithSkip()
|
||||
{
|
||||
loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo)
|
||||
{
|
||||
BeatmapInfo = { AudioLeadIn = 60000 }
|
||||
});
|
||||
|
||||
AddUntilStep("wait for skip overlay", () => Player.ChildrenOfType<SkipOverlay>().First().IsButtonVisible);
|
||||
|
||||
AddStep("Skip with space", () => InputManager.Key(Key.Space));
|
||||
|
||||
AddAssert("Player not paused", () => !Player.DrawableRuleset.IsPaused.Value);
|
||||
|
||||
double? lastTime = null;
|
||||
|
||||
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
||||
@ -52,6 +84,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestPauseViaMiddleMouse()
|
||||
{
|
||||
loadPlayerWithBeatmap();
|
||||
|
||||
double? lastTime = null;
|
||||
|
||||
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
||||
@ -77,6 +111,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestSeekBackwards()
|
||||
{
|
||||
loadPlayerWithBeatmap();
|
||||
|
||||
double? lastTime = null;
|
||||
|
||||
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
||||
@ -93,6 +129,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestSeekForwards()
|
||||
{
|
||||
loadPlayerWithBeatmap();
|
||||
|
||||
double? lastTime = null;
|
||||
|
||||
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
||||
@ -106,12 +144,26 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("Jumped forwards", () => Player.GameplayClockContainer.CurrentTime - lastTime > 500);
|
||||
}
|
||||
|
||||
protected TestReplayPlayer CreatePlayer(Ruleset ruleset)
|
||||
private void loadPlayerWithBeatmap(IBeatmap? beatmap = null)
|
||||
{
|
||||
Beatmap.Value = CreateWorkingBeatmap(ruleset.RulesetInfo);
|
||||
AddStep("create player", () =>
|
||||
{
|
||||
CreatePlayer(new OsuRuleset(), beatmap);
|
||||
});
|
||||
|
||||
AddStep("Load player", () => LoadScreen(Player));
|
||||
AddUntilStep("player loaded", () => Player.IsLoaded);
|
||||
}
|
||||
|
||||
protected void CreatePlayer(Ruleset ruleset, IBeatmap? beatmap = null)
|
||||
{
|
||||
Beatmap.Value = beatmap != null
|
||||
? CreateWorkingBeatmap(beatmap)
|
||||
: CreateWorkingBeatmap(ruleset.RulesetInfo);
|
||||
|
||||
SelectedMods.Value = new[] { ruleset.GetAutoplayMod() };
|
||||
|
||||
return new TestReplayPlayer(false);
|
||||
Player = new TestReplayPlayer(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
92
osu.Game.Tests/Visual/Online/TestSceneGroupBadges.cs
Normal file
92
osu.Game.Tests/Visual/Online/TestSceneGroupBadges.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 System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays.Profile.Header.Components;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneGroupBadges : OsuTestScene
|
||||
{
|
||||
public TestSceneGroupBadges()
|
||||
{
|
||||
var groups = new[]
|
||||
{
|
||||
new APIUser(),
|
||||
new APIUser
|
||||
{
|
||||
Groups = new[]
|
||||
{
|
||||
new APIUserGroup { Colour = "#EB47D0", ShortName = "DEV", Name = "Developers" },
|
||||
}
|
||||
},
|
||||
new APIUser
|
||||
{
|
||||
Groups = new[]
|
||||
{
|
||||
new APIUserGroup { Colour = "#EB47D0", ShortName = "DEV", Name = "Developers" },
|
||||
new APIUserGroup { Colour = "#A347EB", ShortName = "BN", Name = "Beatmap Nominators", Playmodes = new[] { "osu", "taiko" } }
|
||||
}
|
||||
},
|
||||
new APIUser
|
||||
{
|
||||
Groups = new[]
|
||||
{
|
||||
new APIUserGroup { Colour = "#0066FF", ShortName = "PPY", Name = "peppy" },
|
||||
new APIUserGroup { Colour = "#EB47D0", ShortName = "DEV", Name = "Developers" },
|
||||
new APIUserGroup { Colour = "#A347EB", ShortName = "BN", Name = "Beatmap Nominators", Playmodes = new[] { "osu", "taiko" } }
|
||||
}
|
||||
},
|
||||
new APIUser
|
||||
{
|
||||
Groups = new[]
|
||||
{
|
||||
new APIUserGroup { Colour = "#0066FF", ShortName = "PPY", Name = "peppy" },
|
||||
new APIUserGroup { Colour = "#EB47D0", ShortName = "DEV", Name = "Developers" },
|
||||
new APIUserGroup { Colour = "#999999", ShortName = "ALM", Name = "osu! Alumni" },
|
||||
new APIUserGroup { Colour = "#A347EB", ShortName = "BN", Name = "Beatmap Nominators", Playmodes = new[] { "osu", "taiko" } },
|
||||
new APIUserGroup { Colour = "#A347EB", ShortName = "BN", Name = "Beatmap Nominators (Probationary)", Playmodes = new[] { "osu", "taiko" }, IsProbationary = true }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Colour4.DarkGray
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(40),
|
||||
Children = new[]
|
||||
{
|
||||
new FillFlowContainer<GroupBadgeFlow>
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(5),
|
||||
ChildrenEnumerable = groups.Select(g => new GroupBadgeFlow { User = { Value = g } })
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[Test]
|
||||
public void TestEditActivity()
|
||||
{
|
||||
AddStep("Set activity", () => api.Activity.Value = new UserActivity.Editing(new BeatmapInfo()));
|
||||
AddStep("Set activity", () => api.Activity.Value = new UserActivity.EditingBeatmap(new BeatmapInfo()));
|
||||
|
||||
AddStep("Run command", () => Add(new NowPlayingCommand(new Channel())));
|
||||
|
||||
|
@ -11,6 +11,8 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Users;
|
||||
using osuTK;
|
||||
|
||||
@ -107,14 +109,16 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddStep("set online status", () => status.Value = new UserStatusOnline());
|
||||
|
||||
AddStep("idle", () => activity.Value = null);
|
||||
AddStep("spectating", () => activity.Value = new UserActivity.Spectating());
|
||||
AddStep("watching replay", () => activity.Value = new UserActivity.WatchingReplay(createScore(@"nats")));
|
||||
AddStep("spectating user", () => activity.Value = new UserActivity.SpectatingUser(createScore(@"mrekk")));
|
||||
AddStep("solo (osu!)", () => activity.Value = soloGameStatusForRuleset(0));
|
||||
AddStep("solo (osu!taiko)", () => activity.Value = soloGameStatusForRuleset(1));
|
||||
AddStep("solo (osu!catch)", () => activity.Value = soloGameStatusForRuleset(2));
|
||||
AddStep("solo (osu!mania)", () => activity.Value = soloGameStatusForRuleset(3));
|
||||
AddStep("choosing", () => activity.Value = new UserActivity.ChoosingBeatmap());
|
||||
AddStep("editing", () => activity.Value = new UserActivity.Editing(null));
|
||||
AddStep("modding", () => activity.Value = new UserActivity.Modding());
|
||||
AddStep("editing beatmap", () => activity.Value = new UserActivity.EditingBeatmap(null));
|
||||
AddStep("modding beatmap", () => activity.Value = new UserActivity.ModdingBeatmap(null));
|
||||
AddStep("testing beatmap", () => activity.Value = new UserActivity.TestingBeatmap(null, null));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -132,6 +136,14 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
private UserActivity soloGameStatusForRuleset(int rulesetId) => new UserActivity.InSoloGame(null, rulesetStore.GetRuleset(rulesetId));
|
||||
|
||||
private ScoreInfo createScore(string name) => new ScoreInfo(new TestBeatmap(Ruleset.Value).BeatmapInfo)
|
||||
{
|
||||
User = new APIUser
|
||||
{
|
||||
Username = name,
|
||||
}
|
||||
};
|
||||
|
||||
private partial class TestUserListPanel : UserListPanel
|
||||
{
|
||||
public TestUserListPanel(APIUser user)
|
||||
|
@ -90,7 +90,9 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
new APIUserGroup { Colour = "#EB47D0", ShortName = "DEV", Name = "Developers" },
|
||||
new APIUserGroup { Colour = "#A347EB", ShortName = "BN", Name = "Beatmap Nominators", Playmodes = new[] { "mania" } },
|
||||
new APIUserGroup { Colour = "#A347EB", ShortName = "BN", Name = "Beatmap Nominators", Playmodes = new[] { "osu", "taiko" } }
|
||||
new APIUserGroup { Colour = "#A347EB", ShortName = "BN", Name = "Beatmap Nominators", Playmodes = new[] { "osu", "taiko" } },
|
||||
new APIUserGroup { Colour = "#A347EB", ShortName = "BN", Name = "Beatmap Nominators", Playmodes = new[] { "osu", "taiko", "fruits", "mania" } },
|
||||
new APIUserGroup { Colour = "#A347EB", ShortName = "BN", Name = "Beatmap Nominators (Probationary)", Playmodes = new[] { "osu", "taiko", "fruits", "mania" }, IsProbationary = true }
|
||||
},
|
||||
ProfileOrder = new[]
|
||||
{
|
||||
@ -119,6 +121,12 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray()
|
||||
},
|
||||
},
|
||||
TournamentBanner = new TournamentBanner
|
||||
{
|
||||
Id = 13926,
|
||||
TournamentId = 35,
|
||||
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2022/profile/winner_US.jpg",
|
||||
},
|
||||
Badges = new[]
|
||||
{
|
||||
new Badge
|
||||
|
@ -24,17 +24,26 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
public partial class TestSceneAccuracyCircle : OsuTestScene
|
||||
{
|
||||
[TestCase(0.2, ScoreRank.D)]
|
||||
[TestCase(0.5, ScoreRank.D)]
|
||||
[TestCase(0.75, ScoreRank.C)]
|
||||
[TestCase(0.85, ScoreRank.B)]
|
||||
[TestCase(0.925, ScoreRank.A)]
|
||||
[TestCase(0.975, ScoreRank.S)]
|
||||
[TestCase(0.9999, ScoreRank.S)]
|
||||
[TestCase(1, ScoreRank.X)]
|
||||
public void TestRank(double accuracy, ScoreRank rank)
|
||||
[TestCase(0)]
|
||||
[TestCase(0.2)]
|
||||
[TestCase(0.5)]
|
||||
[TestCase(0.6999)]
|
||||
[TestCase(0.7)]
|
||||
[TestCase(0.75)]
|
||||
[TestCase(0.7999)]
|
||||
[TestCase(0.8)]
|
||||
[TestCase(0.85)]
|
||||
[TestCase(0.8999)]
|
||||
[TestCase(0.9)]
|
||||
[TestCase(0.925)]
|
||||
[TestCase(0.9499)]
|
||||
[TestCase(0.95)]
|
||||
[TestCase(0.975)]
|
||||
[TestCase(0.9999)]
|
||||
[TestCase(1)]
|
||||
public void TestRank(double accuracy)
|
||||
{
|
||||
var score = createScore(accuracy, rank);
|
||||
var score = createScore(accuracy, ScoreProcessor.RankFromAccuracy(accuracy));
|
||||
|
||||
addCircleStep(score);
|
||||
}
|
||||
|
146
osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs
Normal file
146
osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs
Normal file
@ -0,0 +1,146 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Screens.Select.FooterV2;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
public partial class TestSceneSongSelectFooterV2 : OsuManualInputManagerTestScene
|
||||
{
|
||||
private FooterButtonRandomV2 randomButton = null!;
|
||||
private FooterButtonModsV2 modsButton = null!;
|
||||
|
||||
private bool nextRandomCalled;
|
||||
private bool previousRandomCalled;
|
||||
|
||||
private DummyOverlay overlay = null!;
|
||||
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider { get; set; } = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
nextRandomCalled = false;
|
||||
previousRandomCalled = false;
|
||||
|
||||
FooterV2 footer;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
footer = new FooterV2
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre
|
||||
},
|
||||
overlay = new DummyOverlay()
|
||||
};
|
||||
|
||||
footer.AddButton(modsButton = new FooterButtonModsV2(), overlay);
|
||||
footer.AddButton(randomButton = new FooterButtonRandomV2
|
||||
{
|
||||
NextRandom = () => nextRandomCalled = true,
|
||||
PreviousRandom = () => previousRandomCalled = true
|
||||
});
|
||||
footer.AddButton(new FooterButtonOptionsV2());
|
||||
|
||||
overlay.Hide();
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestState()
|
||||
{
|
||||
AddToggleStep("set options enabled state", state => this.ChildrenOfType<FooterButtonV2>().Last().Enabled.Value = state);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFooterRandom()
|
||||
{
|
||||
AddStep("press F2", () => InputManager.Key(Key.F2));
|
||||
AddAssert("next random invoked", () => nextRandomCalled && !previousRandomCalled);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFooterRandomViaMouse()
|
||||
{
|
||||
AddStep("click button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(randomButton);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("next random invoked", () => nextRandomCalled && !previousRandomCalled);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFooterRewind()
|
||||
{
|
||||
AddStep("press Shift+F2", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.LShift);
|
||||
InputManager.PressKey(Key.F2);
|
||||
InputManager.ReleaseKey(Key.F2);
|
||||
InputManager.ReleaseKey(Key.LShift);
|
||||
});
|
||||
AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFooterRewindViaShiftMouseLeft()
|
||||
{
|
||||
AddStep("shift + click button", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.LShift);
|
||||
InputManager.MoveMouseTo(randomButton);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
InputManager.ReleaseKey(Key.LShift);
|
||||
});
|
||||
AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFooterRewindViaMouseRight()
|
||||
{
|
||||
AddStep("right click button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(randomButton);
|
||||
InputManager.Click(MouseButton.Right);
|
||||
});
|
||||
AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOverlayPresent()
|
||||
{
|
||||
AddStep("Press F1", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(modsButton);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("Overlay visible", () => overlay.State.Value == Visibility.Visible);
|
||||
AddStep("Hide", () => overlay.Hide());
|
||||
}
|
||||
|
||||
private partial class DummyOverlay : ShearedOverlayContainer
|
||||
{
|
||||
public DummyOverlay()
|
||||
: base(OverlayColourScheme.Green)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Header.Title = "An overlay";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -261,7 +261,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
AddStep($"Set {name} slider to {value}", () =>
|
||||
this.ChildrenOfType<DifficultyAdjustSettingsControl>().First(c => c.LabelText == name)
|
||||
.ChildrenOfType<OsuSliderBar<float>>().First().Current.Value = value);
|
||||
.ChildrenOfType<RoundedSliderBar<float>>().First().Current.Value = value);
|
||||
}
|
||||
|
||||
private void checkBindableAtValue(string name, float? expectedValue)
|
||||
@ -275,7 +275,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
AddAssert($"Slider {name} at {expectedValue}", () =>
|
||||
this.ChildrenOfType<DifficultyAdjustSettingsControl>().First(c => c.LabelText == name)
|
||||
.ChildrenOfType<OsuSliderBar<float>>().First().Current.Value == expectedValue);
|
||||
.ChildrenOfType<RoundedSliderBar<float>>().First().Current.Value == expectedValue);
|
||||
}
|
||||
|
||||
private void setBeatmapWithDifficultyParameters(float value)
|
||||
|
@ -270,7 +270,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
createScreen();
|
||||
AddStep("select difficulty adjust via panel", () => getPanelForMod(typeof(OsuModDifficultyAdjust)).TriggerClick());
|
||||
|
||||
AddStep("set setting", () => modSelectOverlay.ChildrenOfType<OsuSliderBar<float>>().First().Current.Value = 8);
|
||||
AddStep("set setting", () => modSelectOverlay.ChildrenOfType<RoundedSliderBar<float>>().First().Current.Value = 8);
|
||||
|
||||
AddAssert("ensure setting is propagated", () => SelectedMods.Value.OfType<OsuModDifficultyAdjust>().Single().CircleSize.Value == 8);
|
||||
|
||||
|
@ -0,0 +1,37 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneShearedSliderBar : OsuTestScene
|
||||
{
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider { get; set; } = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||
|
||||
private readonly BindableDouble current = new BindableDouble(5)
|
||||
{
|
||||
Precision = 0.1f,
|
||||
MinValue = 0,
|
||||
MaxValue = 15
|
||||
};
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Child = new ShearedSliderBar<double>
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Current = current,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.4f
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -2,11 +2,11 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="DeepEqual" Version="4.2.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
||||
<PackageReference Include="Moq" Version="4.18.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||
<PackageReference Include="Moq" Version="4.18.4" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Project">
|
||||
<OutputType>WinExe</OutputType>
|
||||
|
@ -4,9 +4,9 @@
|
||||
<StartupObject>osu.Game.Tournament.Tests.TournamentTestRunner</StartupObject>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Project">
|
||||
<OutputType>WinExe</OutputType>
|
||||
|
@ -98,6 +98,9 @@ namespace osu.Game.Audio
|
||||
|
||||
Track.Stop();
|
||||
|
||||
// Ensure the track is reset immediately on stopping, so the next time it is started it has a correct time value.
|
||||
Track.Seek(0);
|
||||
|
||||
Stopped?.Invoke();
|
||||
}
|
||||
|
||||
|
@ -66,10 +66,16 @@ namespace osu.Game.Extensions
|
||||
|
||||
foreach (var (_, property) in component.GetSettingsSourceProperties())
|
||||
{
|
||||
if (!info.Settings.TryGetValue(property.Name.ToSnakeCase(), out object? settingValue))
|
||||
continue;
|
||||
var bindable = ((IBindable)property.GetValue(component)!);
|
||||
|
||||
skinnable.CopyAdjustedSetting(((IBindable)property.GetValue(component)!), settingValue);
|
||||
if (!info.Settings.TryGetValue(property.Name.ToSnakeCase(), out object? settingValue))
|
||||
{
|
||||
// TODO: We probably want to restore default if not included in serialisation information.
|
||||
// This is not simple to do as SetDefault() is only found in the typed Bindable<T> interface right now.
|
||||
continue;
|
||||
}
|
||||
|
||||
skinnable.CopyAdjustedSetting(bindable, settingValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,9 +100,9 @@ namespace osu.Game.Graphics.Containers
|
||||
/// <summary>
|
||||
/// Abort any ongoing confirmation. Should be called when the container's interaction is no longer valid (ie. the user releases a key).
|
||||
/// </summary>
|
||||
protected void AbortConfirm()
|
||||
protected virtual void AbortConfirm()
|
||||
{
|
||||
if (!AllowMultipleFires && Fired) return;
|
||||
if (!confirming || (!AllowMultipleFires && Fired)) return;
|
||||
|
||||
confirming = false;
|
||||
Fired = false;
|
||||
|
@ -46,8 +46,8 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
CreateHoverSounds(sampleSet),
|
||||
content,
|
||||
CreateHoverSounds(sampleSet)
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// </summary>
|
||||
public partial class ExpandableSlider<T, TSlider> : CompositeDrawable, IExpandable, IHasCurrentValue<T>
|
||||
where T : struct, IEquatable<T>, IComparable<T>, IConvertible
|
||||
where TSlider : OsuSliderBar<T>, new()
|
||||
where TSlider : RoundedSliderBar<T>, new()
|
||||
{
|
||||
private readonly OsuSpriteText label;
|
||||
private readonly TSlider slider;
|
||||
@ -130,7 +130,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// <summary>
|
||||
/// An <see cref="IExpandable"/> implementation for the UI slider bar control.
|
||||
/// </summary>
|
||||
public partial class ExpandableSlider<T> : ExpandableSlider<T, OsuSliderBar<T>>
|
||||
public partial class ExpandableSlider<T> : ExpandableSlider<T, RoundedSliderBar<T>>
|
||||
where T : struct, IEquatable<T>, IComparable<T>, IConvertible
|
||||
{
|
||||
}
|
||||
|
@ -1,10 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
@ -58,7 +55,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load([CanBeNull] OverlayColourProvider colourProvider, OsuColour colours)
|
||||
private void load(OverlayColourProvider? colourProvider, OsuColour colours)
|
||||
{
|
||||
AccentColour = colourProvider?.Highlight1 ?? colours.Pink;
|
||||
GlowingAccentColour = colourProvider?.Highlight1.Lighten(0.2f) ?? colours.PinkLighter;
|
||||
|
@ -1,195 +1,59 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using JetBrains.Annotations;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public partial class OsuSliderBar<T> : SliderBar<T>, IHasTooltip, IHasAccentColour
|
||||
public abstract partial class OsuSliderBar<T> : SliderBar<T>, IHasTooltip
|
||||
where T : struct, IEquatable<T>, IComparable<T>, IConvertible
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum number of decimal digits to be displayed in the tooltip.
|
||||
/// </summary>
|
||||
private const int max_decimal_digits = 5;
|
||||
|
||||
private Sample sample;
|
||||
private double lastSampleTime;
|
||||
private T lastSampleValue;
|
||||
|
||||
protected readonly Nub Nub;
|
||||
protected readonly Box LeftBox;
|
||||
protected readonly Box RightBox;
|
||||
private readonly Container nubContainer;
|
||||
|
||||
public virtual LocalisableString TooltipText { get; private set; }
|
||||
|
||||
public bool PlaySamplesOnAdjust { get; set; } = true;
|
||||
|
||||
private readonly HoverClickSounds hoverClickSounds;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to format the tooltip as a percentage or the actual value.
|
||||
/// </summary>
|
||||
public bool DisplayAsPercentage { get; set; }
|
||||
|
||||
private Color4 accentColour;
|
||||
public virtual LocalisableString TooltipText { get; private set; }
|
||||
|
||||
public Color4 AccentColour
|
||||
{
|
||||
get => accentColour;
|
||||
set
|
||||
{
|
||||
accentColour = value;
|
||||
LeftBox.Colour = value;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Maximum number of decimal digits to be displayed in the tooltip.
|
||||
/// </summary>
|
||||
private const int max_decimal_digits = 5;
|
||||
|
||||
private Colour4 backgroundColour;
|
||||
private Sample sample = null!;
|
||||
|
||||
public Color4 BackgroundColour
|
||||
{
|
||||
get => backgroundColour;
|
||||
set
|
||||
{
|
||||
backgroundColour = value;
|
||||
RightBox.Colour = value;
|
||||
}
|
||||
}
|
||||
private double lastSampleTime;
|
||||
private T lastSampleValue;
|
||||
|
||||
public OsuSliderBar()
|
||||
{
|
||||
Height = Nub.HEIGHT;
|
||||
RangePadding = Nub.EXPANDED_SIZE / 2;
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Padding = new MarginPadding { Horizontal = 2 },
|
||||
Child = new CircularContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Masking = true,
|
||||
CornerRadius = 5f,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
LeftBox = new Box
|
||||
{
|
||||
Height = 5,
|
||||
EdgeSmoothness = new Vector2(0, 0.5f),
|
||||
RelativeSizeAxes = Axes.None,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
RightBox = new Box
|
||||
{
|
||||
Height = 5,
|
||||
EdgeSmoothness = new Vector2(0, 0.5f),
|
||||
RelativeSizeAxes = Axes.None,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
nubContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = Nub = new Nub
|
||||
{
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativePositionAxes = Axes.X,
|
||||
Current = { Value = true }
|
||||
},
|
||||
},
|
||||
hoverClickSounds = new HoverClickSounds()
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(AudioManager audio, [CanBeNull] OverlayColourProvider colourProvider, OsuColour colours)
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
sample = audio.Samples.Get(@"UI/notch-tick");
|
||||
AccentColour = colourProvider?.Highlight1 ?? colours.Pink;
|
||||
BackgroundColour = colourProvider?.Background5 ?? colours.PinkDarker.Darken(1);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
nubContainer.Padding = new MarginPadding { Horizontal = RangePadding };
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
CurrentNumber.BindValueChanged(current => TooltipText = getTooltipText(current.NewValue), true);
|
||||
|
||||
Current.BindDisabledChanged(disabled =>
|
||||
{
|
||||
Alpha = disabled ? 0.3f : 1;
|
||||
hoverClickSounds.Enabled.Value = !disabled;
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
updateGlow();
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
updateGlow();
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
protected override bool ShouldHandleAsRelativeDrag(MouseDownEvent e)
|
||||
=> Nub.ReceivePositionalInputAt(e.ScreenSpaceMouseDownPosition);
|
||||
|
||||
protected override void OnDragEnd(DragEndEvent e)
|
||||
{
|
||||
updateGlow();
|
||||
base.OnDragEnd(e);
|
||||
}
|
||||
|
||||
private void updateGlow()
|
||||
{
|
||||
Nub.Glowing = !Current.Disabled && (IsHovered || IsDragged);
|
||||
}
|
||||
|
||||
protected override void OnUserChange(T value)
|
||||
{
|
||||
base.OnUserChange(value);
|
||||
|
||||
playSample(value);
|
||||
|
||||
TooltipText = getTooltipText(value);
|
||||
}
|
||||
|
||||
@ -236,18 +100,6 @@ namespace osu.Game.Graphics.UserInterface
|
||||
return floatValue.ToString($"N{significantDigits}");
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
LeftBox.Scale = new Vector2(Math.Clamp(RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, Math.Max(0, DrawWidth)), 1);
|
||||
RightBox.Scale = new Vector2(Math.Clamp(DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2, 0, Math.Max(0, DrawWidth)), 1);
|
||||
}
|
||||
|
||||
protected override void UpdateValue(float value)
|
||||
{
|
||||
Nub.MoveToX(value, 250, Easing.OutQuint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all non-significant digits, keeping at most a requested number of decimal digits.
|
||||
/// </summary>
|
||||
|
@ -158,7 +158,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
&& screenSpacePos.X >= Nub.ScreenSpaceDrawQuad.TopLeft.X;
|
||||
}
|
||||
|
||||
protected partial class BoundSlider : OsuSliderBar<double>
|
||||
protected partial class BoundSlider : RoundedSliderBar<double>
|
||||
{
|
||||
public string? DefaultString;
|
||||
public LocalisableString? DefaultTooltip;
|
||||
|
170
osu.Game/Graphics/UserInterface/RoundedSliderBar.cs
Normal file
170
osu.Game/Graphics/UserInterface/RoundedSliderBar.cs
Normal file
@ -0,0 +1,170 @@
|
||||
// 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 osuTK;
|
||||
using osuTK.Graphics;
|
||||
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.Events;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public partial class RoundedSliderBar<T> : OsuSliderBar<T>
|
||||
where T : struct, IEquatable<T>, IComparable<T>, IConvertible
|
||||
{
|
||||
protected readonly Nub Nub;
|
||||
protected readonly Box LeftBox;
|
||||
protected readonly Box RightBox;
|
||||
private readonly Container nubContainer;
|
||||
|
||||
private readonly HoverClickSounds hoverClickSounds;
|
||||
|
||||
private Color4 accentColour;
|
||||
|
||||
public Color4 AccentColour
|
||||
{
|
||||
get => accentColour;
|
||||
set
|
||||
{
|
||||
accentColour = value;
|
||||
LeftBox.Colour = value;
|
||||
}
|
||||
}
|
||||
|
||||
private Colour4 backgroundColour;
|
||||
|
||||
public Color4 BackgroundColour
|
||||
{
|
||||
get => backgroundColour;
|
||||
set
|
||||
{
|
||||
backgroundColour = value;
|
||||
RightBox.Colour = value;
|
||||
}
|
||||
}
|
||||
|
||||
public RoundedSliderBar()
|
||||
{
|
||||
Height = Nub.HEIGHT;
|
||||
RangePadding = Nub.EXPANDED_SIZE / 2;
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Padding = new MarginPadding { Horizontal = 2 },
|
||||
Child = new CircularContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Masking = true,
|
||||
CornerRadius = 5f,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
LeftBox = new Box
|
||||
{
|
||||
Height = 5,
|
||||
EdgeSmoothness = new Vector2(0, 0.5f),
|
||||
RelativeSizeAxes = Axes.None,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
RightBox = new Box
|
||||
{
|
||||
Height = 5,
|
||||
EdgeSmoothness = new Vector2(0, 0.5f),
|
||||
RelativeSizeAxes = Axes.None,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
nubContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = Nub = new Nub
|
||||
{
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativePositionAxes = Axes.X,
|
||||
Current = { Value = true }
|
||||
},
|
||||
},
|
||||
hoverClickSounds = new HoverClickSounds()
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OverlayColourProvider? colourProvider, OsuColour colours)
|
||||
{
|
||||
AccentColour = colourProvider?.Highlight1 ?? colours.Pink;
|
||||
BackgroundColour = colourProvider?.Background5 ?? colours.PinkDarker.Darken(1);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
nubContainer.Padding = new MarginPadding { Horizontal = RangePadding };
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Current.BindDisabledChanged(disabled =>
|
||||
{
|
||||
Alpha = disabled ? 0.3f : 1;
|
||||
hoverClickSounds.Enabled.Value = !disabled;
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
updateGlow();
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
updateGlow();
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
protected override bool ShouldHandleAsRelativeDrag(MouseDownEvent e)
|
||||
=> Nub.ReceivePositionalInputAt(e.ScreenSpaceMouseDownPosition);
|
||||
|
||||
protected override void OnDragEnd(DragEndEvent e)
|
||||
{
|
||||
updateGlow();
|
||||
base.OnDragEnd(e);
|
||||
}
|
||||
|
||||
private void updateGlow()
|
||||
{
|
||||
Nub.Glowing = !Current.Disabled && (IsHovered || IsDragged);
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
LeftBox.Scale = new Vector2(Math.Clamp(RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, Math.Max(0, DrawWidth)), 1);
|
||||
RightBox.Scale = new Vector2(Math.Clamp(DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2, 0, Math.Max(0, DrawWidth)), 1);
|
||||
}
|
||||
|
||||
protected override void UpdateValue(float value)
|
||||
{
|
||||
Nub.MoveToX(value, 250, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
183
osu.Game/Graphics/UserInterface/ShearedNub.cs
Normal file
183
osu.Game/Graphics/UserInterface/ShearedNub.cs
Normal file
@ -0,0 +1,183 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public partial class ShearedNub : Container, IHasCurrentValue<bool>, IHasAccentColour
|
||||
{
|
||||
protected const float BORDER_WIDTH = 3;
|
||||
|
||||
public const int HEIGHT = 30;
|
||||
public const float EXPANDED_SIZE = 50;
|
||||
|
||||
public static readonly Vector2 SHEAR = new Vector2(0.15f, 0);
|
||||
|
||||
private readonly Box fill;
|
||||
private readonly Container main;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the shape for the nub, allowing for any type of container to be used.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ShearedNub()
|
||||
{
|
||||
Size = new Vector2(EXPANDED_SIZE, HEIGHT);
|
||||
InternalChild = main = new Container
|
||||
{
|
||||
Shear = SHEAR,
|
||||
BorderColour = Colour4.White,
|
||||
BorderThickness = BORDER_WIDTH,
|
||||
Masking = true,
|
||||
CornerRadius = 5,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Child = fill = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OverlayColourProvider? colourProvider, OsuColour colours)
|
||||
{
|
||||
AccentColour = colourProvider?.Highlight1 ?? colours.Pink;
|
||||
GlowingAccentColour = colourProvider?.Highlight1.Lighten(0.4f) ?? colours.PinkLighter;
|
||||
GlowColour = colourProvider?.Highlight1 ?? colours.PinkLighter;
|
||||
|
||||
main.EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Colour = GlowColour.Opacity(0),
|
||||
Type = EdgeEffectType.Glow,
|
||||
Radius = 8,
|
||||
Roundness = 4,
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Current.BindValueChanged(onCurrentValueChanged, true);
|
||||
}
|
||||
|
||||
private bool glowing;
|
||||
|
||||
public bool Glowing
|
||||
{
|
||||
get => glowing;
|
||||
set
|
||||
{
|
||||
if (glowing == value)
|
||||
return;
|
||||
|
||||
glowing = value;
|
||||
|
||||
if (value)
|
||||
{
|
||||
main.FadeColour(GlowingAccentColour.Lighten(0.1f), 40, Easing.OutQuint)
|
||||
.Then()
|
||||
.FadeColour(GlowingAccentColour, 800, Easing.OutQuint);
|
||||
|
||||
main.FadeEdgeEffectTo(Color4.White.Opacity(0.1f), 40, Easing.OutQuint)
|
||||
.Then()
|
||||
.FadeEdgeEffectTo(GlowColour.Opacity(0.1f), 800, Easing.OutQuint);
|
||||
}
|
||||
else
|
||||
{
|
||||
main.FadeEdgeEffectTo(GlowColour.Opacity(0), 800, Easing.OutQuint);
|
||||
main.FadeColour(AccentColour, 800, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Bindable<bool> current = new Bindable<bool>();
|
||||
|
||||
public Bindable<bool> Current
|
||||
{
|
||||
get => current;
|
||||
set
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
|
||||
current.UnbindBindings();
|
||||
current.BindTo(value);
|
||||
}
|
||||
}
|
||||
|
||||
private Color4 accentColour;
|
||||
|
||||
public Color4 AccentColour
|
||||
{
|
||||
get => accentColour;
|
||||
set
|
||||
{
|
||||
accentColour = value;
|
||||
if (!Glowing)
|
||||
main.Colour = value;
|
||||
}
|
||||
}
|
||||
|
||||
private Color4 glowingAccentColour;
|
||||
|
||||
public Color4 GlowingAccentColour
|
||||
{
|
||||
get => glowingAccentColour;
|
||||
set
|
||||
{
|
||||
glowingAccentColour = value;
|
||||
if (Glowing)
|
||||
main.Colour = value;
|
||||
}
|
||||
}
|
||||
|
||||
private Color4 glowColour;
|
||||
|
||||
public Color4 GlowColour
|
||||
{
|
||||
get => glowColour;
|
||||
set
|
||||
{
|
||||
glowColour = value;
|
||||
|
||||
var effect = main.EdgeEffect;
|
||||
effect.Colour = Glowing ? value : value.Opacity(0);
|
||||
main.EdgeEffect = effect;
|
||||
}
|
||||
}
|
||||
|
||||
private void onCurrentValueChanged(ValueChangedEvent<bool> filled)
|
||||
{
|
||||
const double duration = 200;
|
||||
|
||||
fill.FadeTo(filled.NewValue ? 1 : 0, duration, Easing.OutQuint);
|
||||
|
||||
if (filled.NewValue)
|
||||
{
|
||||
main.ResizeWidthTo(1, duration, Easing.OutElasticHalf);
|
||||
main.TransformTo(nameof(BorderThickness), 8.5f, duration, Easing.OutElasticHalf);
|
||||
}
|
||||
else
|
||||
{
|
||||
main.ResizeWidthTo(0.75f, duration, Easing.OutQuint);
|
||||
main.TransformTo(nameof(BorderThickness), BORDER_WIDTH, duration, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
173
osu.Game/Graphics/UserInterface/ShearedSliderBar.cs
Normal file
173
osu.Game/Graphics/UserInterface/ShearedSliderBar.cs
Normal file
@ -0,0 +1,173 @@
|
||||
// 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 osuTK;
|
||||
using osuTK.Graphics;
|
||||
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.Events;
|
||||
using osu.Game.Overlays;
|
||||
using static osu.Game.Graphics.UserInterface.ShearedNub;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public partial class ShearedSliderBar<T> : OsuSliderBar<T>
|
||||
where T : struct, IEquatable<T>, IComparable<T>, IConvertible
|
||||
{
|
||||
protected readonly ShearedNub Nub;
|
||||
protected readonly Box LeftBox;
|
||||
protected readonly Box RightBox;
|
||||
private readonly Container nubContainer;
|
||||
|
||||
private readonly HoverClickSounds hoverClickSounds;
|
||||
|
||||
private Color4 accentColour;
|
||||
|
||||
public Color4 AccentColour
|
||||
{
|
||||
get => accentColour;
|
||||
set
|
||||
{
|
||||
accentColour = value;
|
||||
|
||||
// We want to slightly darken the colour for the box because the sheared slider has the boxes at the same height as the nub,
|
||||
// making the nub invisible when not hovered.
|
||||
LeftBox.Colour = value.Darken(0.1f);
|
||||
}
|
||||
}
|
||||
|
||||
private Colour4 backgroundColour;
|
||||
|
||||
public Color4 BackgroundColour
|
||||
{
|
||||
get => backgroundColour;
|
||||
set
|
||||
{
|
||||
backgroundColour = value;
|
||||
RightBox.Colour = value;
|
||||
}
|
||||
}
|
||||
|
||||
public ShearedSliderBar()
|
||||
{
|
||||
Shear = SHEAR;
|
||||
Height = HEIGHT;
|
||||
RangePadding = EXPANDED_SIZE / 2;
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Padding = new MarginPadding { Horizontal = 2 },
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Masking = true,
|
||||
CornerRadius = 5,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
LeftBox = new Box
|
||||
{
|
||||
EdgeSmoothness = new Vector2(0, 0.5f),
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
RightBox = new Box
|
||||
{
|
||||
EdgeSmoothness = new Vector2(0, 0.5f),
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
nubContainer = new Container
|
||||
{
|
||||
Shear = -SHEAR,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = Nub = new ShearedNub
|
||||
{
|
||||
X = -SHEAR.X * HEIGHT / 2f,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativePositionAxes = Axes.X,
|
||||
Current = { Value = true }
|
||||
},
|
||||
},
|
||||
hoverClickSounds = new HoverClickSounds()
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OverlayColourProvider? colourProvider, OsuColour colours)
|
||||
{
|
||||
AccentColour = colourProvider?.Highlight1 ?? colours.Pink;
|
||||
BackgroundColour = colourProvider?.Background5 ?? colours.PinkDarker.Darken(1);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
nubContainer.Padding = new MarginPadding { Horizontal = RangePadding };
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Current.BindDisabledChanged(disabled =>
|
||||
{
|
||||
Alpha = disabled ? 0.3f : 1;
|
||||
hoverClickSounds.Enabled.Value = !disabled;
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
updateGlow();
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
updateGlow();
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
protected override bool ShouldHandleAsRelativeDrag(MouseDownEvent e)
|
||||
=> Nub.ReceivePositionalInputAt(e.ScreenSpaceMouseDownPosition);
|
||||
|
||||
protected override void OnDragEnd(DragEndEvent e)
|
||||
{
|
||||
updateGlow();
|
||||
base.OnDragEnd(e);
|
||||
}
|
||||
|
||||
private void updateGlow()
|
||||
{
|
||||
Nub.Glowing = !Current.Disabled && (IsHovered || IsDragged);
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
LeftBox.Scale = new Vector2(Math.Clamp(RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2.15f, 0, Math.Max(0, DrawWidth)), 1);
|
||||
RightBox.Scale = new Vector2(Math.Clamp(DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2.15f, 0, Math.Max(0, DrawWidth)), 1);
|
||||
}
|
||||
|
||||
protected override void UpdateValue(float value)
|
||||
{
|
||||
Nub.MoveToX(value, 250, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// <summary>
|
||||
/// A slider bar which displays a millisecond time value.
|
||||
/// </summary>
|
||||
public partial class TimeSlider : OsuSliderBar<double>
|
||||
public partial class TimeSlider : RoundedSliderBar<double>
|
||||
{
|
||||
public override LocalisableString TooltipText => $"{Current.Value:N0} ms";
|
||||
}
|
||||
|
@ -35,8 +35,8 @@ namespace osu.Game.Input.Bindings
|
||||
// It is used to decide the order of precedence, with the earlier items having higher precedence.
|
||||
public override IEnumerable<IKeyBinding> DefaultKeyBindings => GlobalKeyBindings
|
||||
.Concat(EditorKeyBindings)
|
||||
.Concat(ReplayKeyBindings)
|
||||
.Concat(InGameKeyBindings)
|
||||
.Concat(ReplayKeyBindings)
|
||||
.Concat(SongSelectKeyBindings)
|
||||
.Concat(AudioControlKeyBindings)
|
||||
// Overlay bindings may conflict with more local cases like the editor so they are checked last.
|
||||
|
@ -234,6 +234,10 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
set => Statistics.RankHistory = value;
|
||||
}
|
||||
|
||||
[JsonProperty(@"active_tournament_banner")]
|
||||
[CanBeNull]
|
||||
public TournamentBanner TournamentBanner;
|
||||
|
||||
[JsonProperty("badges")]
|
||||
public Badge[] Badges;
|
||||
|
||||
|
@ -95,7 +95,7 @@ namespace osu.Game.Online.Chat
|
||||
{
|
||||
connector.ChannelJoined += ch => Schedule(() => joinChannel(ch));
|
||||
|
||||
connector.ChannelParted += ch => Schedule(() => LeaveChannel(getChannel(ch)));
|
||||
connector.ChannelParted += ch => Schedule(() => leaveChannel(getChannel(ch), false));
|
||||
|
||||
connector.NewMessages += msgs => Schedule(() => addMessages(msgs));
|
||||
|
||||
@ -558,7 +558,9 @@ namespace osu.Game.Online.Chat
|
||||
/// Leave the specified channel. Can be called from any thread.
|
||||
/// </summary>
|
||||
/// <param name="channel">The channel to leave.</param>
|
||||
public void LeaveChannel(Channel channel) => Schedule(() =>
|
||||
public void LeaveChannel(Channel channel) => Schedule(() => leaveChannel(channel, true));
|
||||
|
||||
private void leaveChannel(Channel channel, bool sendLeaveRequest)
|
||||
{
|
||||
if (channel == null) return;
|
||||
|
||||
@ -581,10 +583,11 @@ namespace osu.Game.Online.Chat
|
||||
|
||||
if (channel.Joined.Value)
|
||||
{
|
||||
api.Queue(new LeaveChannelRequest(channel));
|
||||
if (sendLeaveRequest)
|
||||
api.Queue(new LeaveChannelRequest(channel));
|
||||
channel.Joined.Value = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the most recently closed channel that has not already been reopened,
|
||||
|
@ -61,7 +61,7 @@ namespace osu.Game.Online.Chat
|
||||
beatmapInfo = game.BeatmapInfo;
|
||||
break;
|
||||
|
||||
case UserActivity.Editing edit:
|
||||
case UserActivity.EditingBeatmap edit:
|
||||
verb = "editing";
|
||||
beatmapInfo = edit.BeatmapInfo;
|
||||
break;
|
||||
|
@ -60,6 +60,7 @@ using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Updater;
|
||||
using osu.Game.Users;
|
||||
using osu.Game.Utils;
|
||||
@ -501,6 +502,23 @@ namespace osu.Game
|
||||
/// <param name="version">The build version of the update stream</param>
|
||||
public void ShowChangelogBuild(string updateStream, string version) => waitForReady(() => changelogOverlay, _ => changelogOverlay.ShowBuild(updateStream, version));
|
||||
|
||||
/// <summary>
|
||||
/// Present a skin select immediately.
|
||||
/// </summary>
|
||||
/// <param name="skin">The skin to select.</param>
|
||||
public void PresentSkin(SkinInfo skin)
|
||||
{
|
||||
var databasedSkin = SkinManager.Query(s => s.ID == skin.ID);
|
||||
|
||||
if (databasedSkin == null)
|
||||
{
|
||||
Logger.Log("The requested skin could not be loaded.", LoggingTarget.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
SkinManager.CurrentSkinInfo.Value = databasedSkin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Present a beatmap at song select immediately.
|
||||
/// The user should have already requested this interactively.
|
||||
@ -777,6 +795,7 @@ namespace osu.Game
|
||||
|
||||
// todo: all archive managers should be able to be looped here.
|
||||
SkinManager.PostNotification = n => Notifications.Post(n);
|
||||
SkinManager.PresentImport = items => PresentSkin(items.First().Value);
|
||||
|
||||
BeatmapManager.PostNotification = n => Notifications.Post(n);
|
||||
BeatmapManager.PresentImport = items => PresentBeatmap(items.First().Value);
|
||||
|
@ -315,10 +315,10 @@ namespace osu.Game.Overlays
|
||||
channelListing.Hide();
|
||||
textBar.ShowSearch.Value = false;
|
||||
|
||||
if (loadedChannels.ContainsKey(newChannel))
|
||||
if (loadedChannels.TryGetValue(newChannel, out var loadedChannel))
|
||||
{
|
||||
currentChannelContainer.Clear(false);
|
||||
currentChannelContainer.Add(loadedChannels[newChannel]);
|
||||
currentChannelContainer.Add(loadedChannel);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -76,6 +76,7 @@ namespace osu.Game.Overlays.Comments
|
||||
private GridContainer content = null!;
|
||||
private VotePill votePill = null!;
|
||||
private Container<CommentEditor> replyEditorContainer = null!;
|
||||
private Container repliesButtonContainer = null!;
|
||||
|
||||
[Resolved]
|
||||
private IDialogOverlay? dialogOverlay { get; set; }
|
||||
@ -239,10 +240,12 @@ namespace osu.Game.Overlays.Comments
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Padding = new MarginPadding { Top = 10 },
|
||||
Alpha = 0,
|
||||
},
|
||||
new Container
|
||||
repliesButtonContainer = new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
showRepliesButton = new ShowRepliesButton(Comment.RepliesCount)
|
||||
@ -449,6 +452,7 @@ namespace osu.Game.Overlays.Comments
|
||||
{
|
||||
if (replyEditorContainer.Count == 0)
|
||||
{
|
||||
replyEditorContainer.Show();
|
||||
replyEditorContainer.Add(new ReplyCommentEditor(Comment)
|
||||
{
|
||||
OnPost = comments =>
|
||||
@ -456,12 +460,14 @@ namespace osu.Game.Overlays.Comments
|
||||
Comment.RepliesCount += comments.Length;
|
||||
showRepliesButton.Count = Comment.RepliesCount;
|
||||
Replies.AddRange(comments);
|
||||
}
|
||||
},
|
||||
OnCancel = toggleReply
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
replyEditorContainer.Clear(true);
|
||||
replyEditorContainer.ForEach(e => e.Expire());
|
||||
replyEditorContainer.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
@ -513,9 +519,11 @@ namespace osu.Game.Overlays.Comments
|
||||
int loadedRepliesCount = loadedReplies.Count;
|
||||
bool hasUnloadedReplies = loadedRepliesCount != Comment.RepliesCount;
|
||||
|
||||
loadRepliesButton.FadeTo(hasUnloadedReplies && loadedRepliesCount == 0 ? 1 : 0);
|
||||
showMoreButton.FadeTo(hasUnloadedReplies && loadedRepliesCount > 0 ? 1 : 0);
|
||||
showRepliesButton.FadeTo(loadedRepliesCount != 0 ? 1 : 0);
|
||||
loadRepliesButton.FadeTo(hasUnloadedReplies && loadedRepliesCount == 0 ? 1 : 0);
|
||||
repliesButtonContainer.FadeTo(repliesButtonContainer.Any(child => child.Alpha > 0) ? 1 : 0);
|
||||
|
||||
showMoreButton.FadeTo(hasUnloadedReplies && loadedRepliesCount > 0 ? 1 : 0);
|
||||
|
||||
if (Comment.IsTopLevel)
|
||||
chevronButton.FadeTo(loadedRepliesCount != 0 ? 1 : 0);
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Online.API;
|
||||
@ -33,7 +32,6 @@ namespace osu.Game.Overlays.Comments
|
||||
public ReplyCommentEditor(Comment parent)
|
||||
{
|
||||
parentComment = parent;
|
||||
OnCancel = () => this.FadeOut(200).Expire();
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
@ -57,6 +57,7 @@ namespace osu.Game.Overlays.Dialog
|
||||
private Sample confirmSample;
|
||||
private double lastTickPlaybackTime;
|
||||
private AudioFilter lowPassFilter = null!;
|
||||
private bool mouseDown;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
@ -73,6 +74,12 @@ namespace osu.Game.Overlays.Dialog
|
||||
Progress.BindValueChanged(progressChanged);
|
||||
}
|
||||
|
||||
protected override void AbortConfirm()
|
||||
{
|
||||
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF);
|
||||
base.AbortConfirm();
|
||||
}
|
||||
|
||||
protected override void Confirm()
|
||||
{
|
||||
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF);
|
||||
@ -83,6 +90,7 @@ namespace osu.Game.Overlays.Dialog
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
BeginConfirm();
|
||||
mouseDown = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -90,11 +98,28 @@ namespace osu.Game.Overlays.Dialog
|
||||
{
|
||||
if (!e.HasAnyButtonPressed)
|
||||
{
|
||||
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF);
|
||||
AbortConfirm();
|
||||
mouseDown = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
if (mouseDown)
|
||||
BeginConfirm();
|
||||
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
base.OnHoverLost(e);
|
||||
|
||||
if (!mouseDown) return;
|
||||
|
||||
AbortConfirm();
|
||||
}
|
||||
|
||||
private void progressChanged(ValueChangedEvent<double> progress)
|
||||
{
|
||||
if (progress.NewValue < progress.OldValue) return;
|
||||
|
@ -107,7 +107,7 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
public override bool? AllowTrackAdjustments => false;
|
||||
}
|
||||
|
||||
private partial class UIScaleSlider : OsuSliderBar<float>
|
||||
private partial class UIScaleSlider : RoundedSliderBar<float>
|
||||
{
|
||||
public override LocalisableString TooltipText => base.TooltipText + "x";
|
||||
}
|
||||
|
63
osu.Game/Overlays/Profile/Header/BannerHeaderContainer.cs
Normal file
63
osu.Game/Overlays/Profile/Header/BannerHeaderContainer.cs
Normal file
@ -0,0 +1,63 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays.Profile.Header.Components;
|
||||
|
||||
namespace osu.Game.Overlays.Profile.Header
|
||||
{
|
||||
public partial class BannerHeaderContainer : CompositeDrawable
|
||||
{
|
||||
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Alpha = 0;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
FillMode = FillMode.Fit;
|
||||
FillAspectRatio = 1000 / 60f;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
User.BindValueChanged(u => updateDisplay(u.NewValue?.User), true);
|
||||
}
|
||||
|
||||
private CancellationTokenSource? cancellationTokenSource;
|
||||
|
||||
private void updateDisplay(APIUser? user)
|
||||
{
|
||||
cancellationTokenSource?.Cancel();
|
||||
cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
ClearInternal();
|
||||
|
||||
var banner = user?.TournamentBanner;
|
||||
|
||||
if (banner != null)
|
||||
{
|
||||
Show();
|
||||
|
||||
LoadComponentAsync(new DrawableTournamentBanner(banner), AddInternal, cancellationTokenSource.Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
cancellationTokenSource?.Cancel();
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Overlays.Profile.Header.Components
|
||||
{
|
||||
[LongRunningLoad]
|
||||
public partial class DrawableTournamentBanner : OsuClickableContainer
|
||||
{
|
||||
private readonly TournamentBanner banner;
|
||||
|
||||
public DrawableTournamentBanner(TournamentBanner banner)
|
||||
{
|
||||
this.banner = banner;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(LargeTextureStore textures, OsuGame? game, IAPIProvider api)
|
||||
{
|
||||
Child = new Sprite
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Texture = textures.Get(banner.Image),
|
||||
};
|
||||
|
||||
Action = () => game?.OpenUrlExternally($@"{api.WebsiteRootUrl}/community/tournaments/{banner.TournamentId}");
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
this.FadeInFromZero(200);
|
||||
}
|
||||
|
||||
public override LocalisableString TooltipText => "view in browser";
|
||||
}
|
||||
}
|
@ -35,6 +35,11 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
CornerRadius = 8;
|
||||
|
||||
TooltipText = group.Name;
|
||||
|
||||
if (group.IsProbationary)
|
||||
{
|
||||
Alpha = 0.6f;
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -47,7 +52,11 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider?.Background6 ?? Colour4.Black
|
||||
Colour = colourProvider?.Background6 ?? Colour4.Black,
|
||||
// Normal badges background opacity is 75%, probationary is full opacity as the whole badge gets a bit transparent
|
||||
// Goal is to match osu-web so this is the most accurate it can be, its a bit scuffed but it is what it is
|
||||
// Source: https://github.com/ppy/osu-web/blob/master/resources/css/bem/user-group-badge.less#L50
|
||||
Alpha = group.IsProbationary ? 1 : 0.75f,
|
||||
},
|
||||
innerContainer = new FillFlowContainer
|
||||
{
|
||||
|
@ -66,10 +66,12 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
{
|
||||
int days = ranked_days - index + 1;
|
||||
|
||||
return new UserGraphTooltipContent(
|
||||
UsersStrings.ShowRankGlobalSimple,
|
||||
rank.ToLocalisableString("\\##,##0"),
|
||||
days == 0 ? "now" : $"{"day".ToQuantity(days)} ago");
|
||||
return new UserGraphTooltipContent
|
||||
{
|
||||
Name = UsersStrings.ShowRankGlobalSimple,
|
||||
Count = rank.ToLocalisableString("\\##,##0"),
|
||||
Time = days == 0 ? "now" : $"{"day".ToQuantity(days)} ago",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Overlays.Profile.Header.Components;
|
||||
@ -104,76 +103,69 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
Colour = Colour4.Black.Opacity(0.25f),
|
||||
}
|
||||
},
|
||||
new OsuContextMenuContainer
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Child = new FillFlowContainer
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Children = new Drawable[]
|
||||
new FillFlowContainer
|
||||
{
|
||||
new FillFlowContainer
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5, 0),
|
||||
Children = new Drawable[]
|
||||
usernameText = new OsuSpriteText
|
||||
{
|
||||
usernameText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 24, weight: FontWeight.Regular)
|
||||
},
|
||||
supporterTag = new SupporterIcon
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Height = 15,
|
||||
},
|
||||
openUserExternally = new ExternalLinkButton
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
groupBadgeFlow = new GroupBadgeFlow
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
}
|
||||
},
|
||||
titleText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular),
|
||||
Margin = new MarginPadding { Bottom = 5 }
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new Drawable[]
|
||||
Font = OsuFont.GetFont(size: 24, weight: FontWeight.Regular)
|
||||
},
|
||||
supporterTag = new SupporterIcon
|
||||
{
|
||||
userFlag = new UpdateableFlag
|
||||
{
|
||||
Size = new Vector2(28, 20),
|
||||
ShowPlaceholderOnUnknown = false,
|
||||
},
|
||||
userCountryText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 14f, weight: FontWeight.Regular),
|
||||
Margin = new MarginPadding { Left = 5 },
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
}
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Height = 15,
|
||||
},
|
||||
openUserExternally = new ExternalLinkButton
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
groupBadgeFlow = new GroupBadgeFlow
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
}
|
||||
},
|
||||
titleText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular),
|
||||
Margin = new MarginPadding { Bottom = 5 }
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
userFlag = new UpdateableFlag
|
||||
{
|
||||
Size = new Vector2(28, 20),
|
||||
ShowPlaceholderOnUnknown = false,
|
||||
},
|
||||
userCountryText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 14f, weight: FontWeight.Regular),
|
||||
Margin = new MarginPadding { Left = 5 },
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
@ -47,6 +47,10 @@ namespace osu.Game.Overlays.Profile
|
||||
RelativeSizeAxes = Axes.X,
|
||||
User = { BindTarget = User },
|
||||
},
|
||||
new BannerHeaderContainer
|
||||
{
|
||||
User = { BindTarget = User },
|
||||
},
|
||||
new BadgeHeaderContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
|
@ -27,9 +27,11 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
|
||||
protected override float GetDataPointHeight(long playCount) => playCount;
|
||||
|
||||
protected override UserGraphTooltipContent GetTooltipContent(DateTime date, long playCount) =>
|
||||
new UserGraphTooltipContent(
|
||||
tooltipCounterName,
|
||||
playCount.ToLocalisableString("N0"),
|
||||
date.ToLocalisableString("MMMM yyyy"));
|
||||
new UserGraphTooltipContent
|
||||
{
|
||||
Name = tooltipCounterName,
|
||||
Count = playCount.ToLocalisableString("N0"),
|
||||
Time = date.ToLocalisableString("MMMM yyyy")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -298,16 +298,8 @@ namespace osu.Game.Overlays.Profile
|
||||
|
||||
public class UserGraphTooltipContent
|
||||
{
|
||||
// todo: could use init-only properties on C# 9 which read better than a constructor.
|
||||
public LocalisableString Name { get; }
|
||||
public LocalisableString Count { get; }
|
||||
public LocalisableString Time { get; }
|
||||
|
||||
public UserGraphTooltipContent(LocalisableString name, LocalisableString count, LocalisableString time)
|
||||
{
|
||||
Name = name;
|
||||
Count = count;
|
||||
Time = time;
|
||||
}
|
||||
public LocalisableString Name { get; init; }
|
||||
public LocalisableString Count { get; init; }
|
||||
public LocalisableString Time { get; init; }
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
|
||||
{
|
||||
protected override Drawable CreateControl()
|
||||
{
|
||||
var sliderBar = (OsuSliderBar<double>)base.CreateControl();
|
||||
var sliderBar = (RoundedSliderBar<double>)base.CreateControl();
|
||||
sliderBar.PlaySamplesOnAdjust = false;
|
||||
return sliderBar;
|
||||
}
|
||||
|
@ -329,7 +329,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
private partial class UIScaleSlider : OsuSliderBar<float>
|
||||
private partial class UIScaleSlider : RoundedSliderBar<float>
|
||||
{
|
||||
public override LocalisableString TooltipText => base.TooltipText + "x";
|
||||
}
|
||||
|
@ -135,7 +135,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
}
|
||||
}
|
||||
|
||||
public partial class SensitivitySlider : OsuSliderBar<double>
|
||||
public partial class SensitivitySlider : RoundedSliderBar<double>
|
||||
{
|
||||
public override LocalisableString TooltipText => Current.Disabled ? MouseSettingsStrings.EnableHighPrecisionForSensitivityAdjust : $"{base.TooltipText}x";
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
/// <summary>
|
||||
/// A slider intended to show a "size" multiplier number, where 1x is 1.0.
|
||||
/// </summary>
|
||||
public partial class SizeSlider<T> : OsuSliderBar<T>
|
||||
public partial class SizeSlider<T> : RoundedSliderBar<T>
|
||||
where T : struct, IEquatable<T>, IComparable<T>, IConvertible, IFormattable
|
||||
{
|
||||
public override LocalisableString TooltipText => Current.Value.ToString(@"0.##x", NumberFormatInfo.CurrentInfo);
|
||||
|
@ -10,14 +10,14 @@ using osu.Game.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Overlays.Settings
|
||||
{
|
||||
public partial class SettingsSlider<T> : SettingsSlider<T, OsuSliderBar<T>>
|
||||
public partial class SettingsSlider<T> : SettingsSlider<T, RoundedSliderBar<T>>
|
||||
where T : struct, IEquatable<T>, IComparable<T>, IConvertible
|
||||
{
|
||||
}
|
||||
|
||||
public partial class SettingsSlider<TValue, TSlider> : SettingsItem<TValue>
|
||||
where TValue : struct, IEquatable<TValue>, IComparable<TValue>, IConvertible
|
||||
where TSlider : OsuSliderBar<TValue>, new()
|
||||
where TSlider : RoundedSliderBar<TValue>, new()
|
||||
{
|
||||
protected override Drawable CreateControl() => new TSlider
|
||||
{
|
||||
|
@ -148,9 +148,9 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
component.Origin = Anchor.Centre;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
base.Update();
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
if (component.DrawSize != Vector2.Zero)
|
||||
{
|
||||
|
@ -24,6 +24,7 @@ using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.OSD;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Components;
|
||||
using osu.Game.Screens.Edit.Components.Menus;
|
||||
using osu.Game.Skinning;
|
||||
@ -31,7 +32,7 @@ using osu.Game.Skinning;
|
||||
namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
[Cached(typeof(SkinEditor))]
|
||||
public partial class SkinEditor : VisibilityContainer, ICanAcceptFiles, IKeyBindingHandler<PlatformAction>
|
||||
public partial class SkinEditor : VisibilityContainer, ICanAcceptFiles, IKeyBindingHandler<PlatformAction>, IEditorChangeHandler
|
||||
{
|
||||
public const double TRANSITION_DURATION = 300;
|
||||
|
||||
@ -72,6 +73,11 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
private EditorSidebar componentsSidebar = null!;
|
||||
private EditorSidebar settingsSidebar = null!;
|
||||
|
||||
private SkinEditorChangeHandler? changeHandler;
|
||||
|
||||
private EditorMenuItem undoMenuItem = null!;
|
||||
private EditorMenuItem redoMenuItem = null!;
|
||||
|
||||
[Resolved]
|
||||
private OnScreenDisplay? onScreenDisplay { get; set; }
|
||||
|
||||
@ -131,6 +137,14 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, () => skinEditorOverlay?.Hide()),
|
||||
},
|
||||
},
|
||||
new MenuItem(CommonStrings.MenuBarEdit)
|
||||
{
|
||||
Items = new[]
|
||||
{
|
||||
undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo),
|
||||
redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo),
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
headerText = new OsuTextFlowContainer
|
||||
@ -210,6 +224,14 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case PlatformAction.Undo:
|
||||
Undo();
|
||||
return true;
|
||||
|
||||
case PlatformAction.Redo:
|
||||
Redo();
|
||||
return true;
|
||||
|
||||
case PlatformAction.Save:
|
||||
if (e.Repeat)
|
||||
return false;
|
||||
@ -229,6 +251,8 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
this.targetScreen = targetScreen;
|
||||
|
||||
changeHandler?.Dispose();
|
||||
|
||||
SelectedComponents.Clear();
|
||||
|
||||
// Immediately clear the previous blueprint container to ensure it doesn't try to interact with the old target.
|
||||
@ -241,6 +265,10 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
Debug.Assert(content != null);
|
||||
|
||||
changeHandler = new SkinEditorChangeHandler(targetScreen);
|
||||
changeHandler.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true);
|
||||
changeHandler.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true);
|
||||
|
||||
content.Child = new SkinBlueprintContainer(targetScreen);
|
||||
|
||||
componentsSidebar.Child = new SkinComponentToolbox(getFirstTarget() as CompositeDrawable)
|
||||
@ -333,6 +361,10 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
}
|
||||
}
|
||||
|
||||
protected void Undo() => changeHandler?.RestoreState(-1);
|
||||
|
||||
protected void Redo() => changeHandler?.RestoreState(1);
|
||||
|
||||
public void Save(bool userTriggered = true)
|
||||
{
|
||||
if (!hasBegunMutating)
|
||||
@ -436,5 +468,27 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
#region Delegation of IEditorChangeHandler
|
||||
|
||||
public event Action? OnStateChange
|
||||
{
|
||||
add => throw new NotImplementedException();
|
||||
remove => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private IEditorChangeHandler? beginChangeHandler;
|
||||
|
||||
public void BeginChange()
|
||||
{
|
||||
// Change handler may change between begin and end, which can cause unbalanced operations.
|
||||
// Let's track the one that was used when beginning the change so we can call EndChange on it specifically.
|
||||
(beginChangeHandler = changeHandler)?.BeginChange();
|
||||
}
|
||||
|
||||
public void EndChange() => beginChangeHandler?.EndChange();
|
||||
public void SaveState() => changeHandler?.SaveState();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
78
osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs
Normal file
78
osu.Game/Overlays/SkinEditor/SkinEditorChangeHandler.cs
Normal file
@ -0,0 +1,78 @@
|
||||
// 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.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
public partial class SkinEditorChangeHandler : EditorChangeHandler
|
||||
{
|
||||
private readonly ISkinnableTarget? firstTarget;
|
||||
|
||||
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
|
||||
private readonly BindableList<ISkinnableDrawable>? components;
|
||||
|
||||
public SkinEditorChangeHandler(Drawable targetScreen)
|
||||
{
|
||||
// To keep things simple, we are currently only handling the current target screen for undo / redo.
|
||||
// In the future we'll want this to cover all changes, even to skin's `InstantiationInfo`.
|
||||
// We'll also need to consider cases where multiple targets are on screen at the same time.
|
||||
|
||||
firstTarget = targetScreen.ChildrenOfType<ISkinnableTarget>().FirstOrDefault();
|
||||
|
||||
if (firstTarget == null)
|
||||
return;
|
||||
|
||||
components = new BindableList<ISkinnableDrawable> { BindTarget = firstTarget.Components };
|
||||
components.BindCollectionChanged((_, _) => SaveState());
|
||||
}
|
||||
|
||||
protected override void WriteCurrentStateToStream(MemoryStream stream)
|
||||
{
|
||||
if (firstTarget == null)
|
||||
return;
|
||||
|
||||
var skinnableInfos = firstTarget.CreateSkinnableInfo().ToArray();
|
||||
string json = JsonConvert.SerializeObject(skinnableInfos, new JsonSerializerSettings { Formatting = Formatting.Indented });
|
||||
stream.Write(Encoding.UTF8.GetBytes(json));
|
||||
}
|
||||
|
||||
protected override void ApplyStateChange(byte[] previousState, byte[] newState)
|
||||
{
|
||||
if (firstTarget == null)
|
||||
return;
|
||||
|
||||
var deserializedContent = JsonConvert.DeserializeObject<IEnumerable<SkinnableInfo>>(Encoding.UTF8.GetString(newState));
|
||||
|
||||
if (deserializedContent == null)
|
||||
return;
|
||||
|
||||
SkinnableInfo[] skinnableInfo = deserializedContent.ToArray();
|
||||
Drawable[] targetComponents = firstTarget.Components.OfType<Drawable>().ToArray();
|
||||
|
||||
if (!skinnableInfo.Select(s => s.Type).SequenceEqual(targetComponents.Select(d => d.GetType())))
|
||||
{
|
||||
// Perform a naive full reload for now.
|
||||
firstTarget.Reload(skinnableInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
foreach (var drawable in targetComponents)
|
||||
drawable.ApplySkinnableInfo(skinnableInfo[i++]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -241,6 +241,8 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
|
||||
private void applyOrigins(Anchor origin)
|
||||
{
|
||||
OnOperationBegan();
|
||||
|
||||
foreach (var item in SelectedItems)
|
||||
{
|
||||
var drawable = (Drawable)item;
|
||||
@ -255,6 +257,8 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
|
||||
ApplyClosestAnchor(drawable);
|
||||
}
|
||||
|
||||
OnOperationEnded();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -266,6 +270,8 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
|
||||
private void applyFixedAnchors(Anchor anchor)
|
||||
{
|
||||
OnOperationBegan();
|
||||
|
||||
foreach (var item in SelectedItems)
|
||||
{
|
||||
var drawable = (Drawable)item;
|
||||
@ -273,15 +279,21 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
item.UsesFixedAnchor = true;
|
||||
applyAnchor(drawable, anchor);
|
||||
}
|
||||
|
||||
OnOperationEnded();
|
||||
}
|
||||
|
||||
private void applyClosestAnchors()
|
||||
{
|
||||
OnOperationBegan();
|
||||
|
||||
foreach (var item in SelectedItems)
|
||||
{
|
||||
item.UsesFixedAnchor = false;
|
||||
ApplyClosestAnchor((Drawable)item);
|
||||
}
|
||||
|
||||
OnOperationEnded();
|
||||
}
|
||||
|
||||
private static Anchor getClosestAnchor(Drawable drawable)
|
||||
|
@ -2,10 +2,13 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Components;
|
||||
using osuTK;
|
||||
|
||||
@ -13,19 +16,41 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
internal partial class SkinSettingsToolbox : EditorSidebarSection
|
||||
{
|
||||
[Resolved]
|
||||
private IEditorChangeHandler? changeHandler { get; set; }
|
||||
|
||||
protected override Container<Drawable> Content { get; }
|
||||
|
||||
private readonly Drawable component;
|
||||
|
||||
public SkinSettingsToolbox(Drawable component)
|
||||
: base(SkinEditorStrings.Settings(component.GetType().Name))
|
||||
{
|
||||
this.component = component;
|
||||
|
||||
base.Content.Add(Content = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(10),
|
||||
Children = component.CreateSettingsControls().ToArray()
|
||||
});
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
var controls = component.CreateSettingsControls().ToArray();
|
||||
|
||||
Content.AddRange(controls);
|
||||
|
||||
// track any changes to update undo states.
|
||||
foreach (var c in controls.OfType<ISettingsItem>())
|
||||
{
|
||||
// TODO: SettingChanged is called too often for cases like SettingsTextBox and SettingsSlider.
|
||||
// We will want to expose a SettingCommitted or similar to make this work better.
|
||||
c.SettingChanged += () => changeHandler?.SaveState();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ using osu.Framework.Input.Events;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online;
|
||||
@ -100,17 +101,22 @@ namespace osu.Game.Overlays
|
||||
Origin = Anchor.TopCentre,
|
||||
};
|
||||
|
||||
Add(sectionsContainer = new ProfileSectionsContainer
|
||||
Add(new OsuContextMenuContainer
|
||||
{
|
||||
ExpandableHeader = Header,
|
||||
FixedHeader = tabs,
|
||||
HeaderBackground = new Box
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = sectionsContainer = new ProfileSectionsContainer
|
||||
{
|
||||
// this is only visible as the ProfileTabControl background
|
||||
Colour = ColourProvider.Background5,
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
ExpandableHeader = Header,
|
||||
FixedHeader = tabs,
|
||||
HeaderBackground = new Box
|
||||
{
|
||||
// this is only visible as the ProfileTabControl background
|
||||
Colour = ColourProvider.Background5,
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
sectionsContainer.SelectedSection.ValueChanged += section =>
|
||||
{
|
||||
if (lastSection != section.NewValue)
|
||||
|
@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new OsuSliderBar<float>
|
||||
new RoundedSliderBar<float>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Current = currentNumber,
|
||||
|
@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
}
|
||||
}
|
||||
|
||||
public partial class PercentSlider : OsuSliderBar<double>
|
||||
public partial class PercentSlider : RoundedSliderBar<double>
|
||||
{
|
||||
public PercentSlider()
|
||||
{
|
||||
|
@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
|
||||
}
|
||||
|
||||
public partial class MuteComboSlider : OsuSliderBar<int>
|
||||
public partial class MuteComboSlider : RoundedSliderBar<int>
|
||||
{
|
||||
public override LocalisableString TooltipText => Current.Value == 0 ? "always muted" : base.TooltipText;
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
}
|
||||
}
|
||||
|
||||
public partial class HiddenComboSlider : OsuSliderBar<int>
|
||||
public partial class HiddenComboSlider : RoundedSliderBar<int>
|
||||
{
|
||||
public override LocalisableString TooltipText => Current.Value == 0 ? "always hidden" : base.TooltipText;
|
||||
}
|
||||
|
@ -439,19 +439,20 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
|
||||
private List<HitSampleInfo> convertSoundType(LegacyHitSoundType type, SampleBankInfo bankInfo)
|
||||
{
|
||||
// Todo: This should return the normal SampleInfos if the specified sample file isn't found, but that's a pretty edge-case scenario
|
||||
if (!string.IsNullOrEmpty(bankInfo.Filename))
|
||||
{
|
||||
return new List<HitSampleInfo> { new FileHitSampleInfo(bankInfo.Filename, bankInfo.Volume) };
|
||||
}
|
||||
var soundTypes = new List<HitSampleInfo>();
|
||||
|
||||
var soundTypes = new List<HitSampleInfo>
|
||||
if (string.IsNullOrEmpty(bankInfo.Filename))
|
||||
{
|
||||
new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.BankForNormal, bankInfo.Volume, bankInfo.CustomSampleBank,
|
||||
// if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample.
|
||||
// None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds
|
||||
type != LegacyHitSoundType.None && !type.HasFlagFast(LegacyHitSoundType.Normal))
|
||||
};
|
||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.BankForNormal, bankInfo.Volume, bankInfo.CustomSampleBank,
|
||||
// if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample.
|
||||
// None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds
|
||||
type != LegacyHitSoundType.None && !type.HasFlagFast(LegacyHitSoundType.Normal)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Todo: This should set the normal SampleInfo if the specified sample file isn't found, but that's a pretty edge-case scenario
|
||||
soundTypes.Add(new FileHitSampleInfo(bankInfo.Filename, bankInfo.Volume));
|
||||
}
|
||||
|
||||
if (type.HasFlagFast(LegacyHitSoundType.Finish))
|
||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_FINISH, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank));
|
||||
|
@ -7,21 +7,28 @@ using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Localisation;
|
||||
|
||||
namespace osu.Game.Rulesets.Scoring
|
||||
{
|
||||
public partial class ScoreProcessor : JudgementProcessor
|
||||
{
|
||||
private const double accuracy_cutoff_x = 1;
|
||||
private const double accuracy_cutoff_s = 0.95;
|
||||
private const double accuracy_cutoff_a = 0.9;
|
||||
private const double accuracy_cutoff_b = 0.8;
|
||||
private const double accuracy_cutoff_c = 0.7;
|
||||
private const double accuracy_cutoff_d = 0;
|
||||
|
||||
private const double max_score = 1000000;
|
||||
|
||||
/// <summary>
|
||||
@ -160,7 +167,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
Combo.ValueChanged += combo => HighestCombo.Value = Math.Max(HighestCombo.Value, combo.NewValue);
|
||||
Accuracy.ValueChanged += accuracy =>
|
||||
{
|
||||
Rank.Value = rankFrom(accuracy.NewValue);
|
||||
Rank.Value = RankFromAccuracy(accuracy.NewValue);
|
||||
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
|
||||
Rank.Value = mod.AdjustRank(Rank.Value, accuracy.NewValue);
|
||||
};
|
||||
@ -369,22 +376,6 @@ namespace osu.Game.Rulesets.Scoring
|
||||
}
|
||||
}
|
||||
|
||||
private ScoreRank rankFrom(double acc)
|
||||
{
|
||||
if (acc == 1)
|
||||
return ScoreRank.X;
|
||||
if (acc >= 0.95)
|
||||
return ScoreRank.S;
|
||||
if (acc >= 0.9)
|
||||
return ScoreRank.A;
|
||||
if (acc >= 0.8)
|
||||
return ScoreRank.B;
|
||||
if (acc >= 0.7)
|
||||
return ScoreRank.C;
|
||||
|
||||
return ScoreRank.D;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets this ScoreProcessor to a default state.
|
||||
/// </summary>
|
||||
@ -583,6 +574,62 @@ namespace osu.Game.Rulesets.Scoring
|
||||
hitEvents.Clear();
|
||||
}
|
||||
|
||||
#region Static helper methods
|
||||
|
||||
/// <summary>
|
||||
/// Given an accuracy (0..1), return the correct <see cref="ScoreRank"/>.
|
||||
/// </summary>
|
||||
public static ScoreRank RankFromAccuracy(double accuracy)
|
||||
{
|
||||
if (accuracy == accuracy_cutoff_x)
|
||||
return ScoreRank.X;
|
||||
if (accuracy >= accuracy_cutoff_s)
|
||||
return ScoreRank.S;
|
||||
if (accuracy >= accuracy_cutoff_a)
|
||||
return ScoreRank.A;
|
||||
if (accuracy >= accuracy_cutoff_b)
|
||||
return ScoreRank.B;
|
||||
if (accuracy >= accuracy_cutoff_c)
|
||||
return ScoreRank.C;
|
||||
|
||||
return ScoreRank.D;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a <see cref="ScoreRank"/>, return the cutoff accuracy (0..1).
|
||||
/// Accuracy must be greater than or equal to the cutoff to qualify for the provided rank.
|
||||
/// </summary>
|
||||
public static double AccuracyCutoffFromRank(ScoreRank rank)
|
||||
{
|
||||
switch (rank)
|
||||
{
|
||||
case ScoreRank.X:
|
||||
case ScoreRank.XH:
|
||||
return accuracy_cutoff_x;
|
||||
|
||||
case ScoreRank.S:
|
||||
case ScoreRank.SH:
|
||||
return accuracy_cutoff_s;
|
||||
|
||||
case ScoreRank.A:
|
||||
return accuracy_cutoff_a;
|
||||
|
||||
case ScoreRank.B:
|
||||
return accuracy_cutoff_b;
|
||||
|
||||
case ScoreRank.C:
|
||||
return accuracy_cutoff_c;
|
||||
|
||||
case ScoreRank.D:
|
||||
return accuracy_cutoff_d;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(rank), rank, null);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Stores the required scoring data that fulfils the minimum requirements for a <see cref="ScoreProcessor"/> to calculate score.
|
||||
/// </summary>
|
||||
|
40
osu.Game/Screens/Edit/BeatmapEditorChangeHandler.cs
Normal file
40
osu.Game/Screens/Edit/BeatmapEditorChangeHandler.cs
Normal file
@ -0,0 +1,40 @@
|
||||
// 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.IO;
|
||||
using System.Text;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Screens.Edit
|
||||
{
|
||||
public partial class BeatmapEditorChangeHandler : EditorChangeHandler
|
||||
{
|
||||
private readonly LegacyEditorBeatmapPatcher patcher;
|
||||
private readonly EditorBeatmap editorBeatmap;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="EditorChangeHandler"/>.
|
||||
/// </summary>
|
||||
/// <param name="editorBeatmap">The <see cref="EditorBeatmap"/> to track the <see cref="HitObject"/>s of.</param>
|
||||
public BeatmapEditorChangeHandler(EditorBeatmap editorBeatmap)
|
||||
{
|
||||
this.editorBeatmap = editorBeatmap;
|
||||
|
||||
editorBeatmap.TransactionBegan += BeginChange;
|
||||
editorBeatmap.TransactionEnded += EndChange;
|
||||
editorBeatmap.SaveStateTriggered += SaveState;
|
||||
|
||||
patcher = new LegacyEditorBeatmapPatcher(editorBeatmap);
|
||||
}
|
||||
|
||||
protected override void WriteCurrentStateToStream(MemoryStream stream)
|
||||
{
|
||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||
new LegacyBeatmapEncoder(editorBeatmap, editorBeatmap.BeatmapSkin).Encode(sw);
|
||||
}
|
||||
|
||||
protected override void ApplyStateChange(byte[] previousState, byte[] newState) =>
|
||||
patcher.Patch(previousState, newState);
|
||||
}
|
||||
}
|
@ -157,7 +157,16 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
private bool isNewBeatmap;
|
||||
|
||||
protected override UserActivity InitialActivity => new UserActivity.Editing(Beatmap.Value.BeatmapInfo);
|
||||
protected override UserActivity InitialActivity
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Beatmap.Value.Metadata.Author.OnlineID == api.LocalUser.Value.OnlineID)
|
||||
return new UserActivity.EditingBeatmap(Beatmap.Value.BeatmapInfo);
|
||||
|
||||
return new UserActivity.ModdingBeatmap(Beatmap.Value.BeatmapInfo);
|
||||
}
|
||||
}
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
@ -240,7 +249,7 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
if (canSave)
|
||||
{
|
||||
changeHandler = new EditorChangeHandler(editorBeatmap);
|
||||
changeHandler = new BeatmapEditorChangeHandler(editorBeatmap);
|
||||
dependencies.CacheAs<IEditorChangeHandler>(changeHandler);
|
||||
}
|
||||
|
||||
|
@ -1,31 +1,25 @@
|
||||
// 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.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Screens.Edit
|
||||
{
|
||||
/// <summary>
|
||||
/// Tracks changes to the <see cref="Editor"/>.
|
||||
/// </summary>
|
||||
public partial class EditorChangeHandler : TransactionalCommitComponent, IEditorChangeHandler
|
||||
public abstract partial class EditorChangeHandler : TransactionalCommitComponent, IEditorChangeHandler
|
||||
{
|
||||
public readonly Bindable<bool> CanUndo = new Bindable<bool>();
|
||||
public readonly Bindable<bool> CanRedo = new Bindable<bool>();
|
||||
|
||||
public event Action OnStateChange;
|
||||
public event Action? OnStateChange;
|
||||
|
||||
private readonly LegacyEditorBeatmapPatcher patcher;
|
||||
private readonly List<byte[]> savedStates = new List<byte[]>();
|
||||
|
||||
private int currentState = -1;
|
||||
@ -37,32 +31,28 @@ namespace osu.Game.Screens.Edit
|
||||
{
|
||||
get
|
||||
{
|
||||
ensureStateSaved();
|
||||
|
||||
using (var stream = new MemoryStream(savedStates[currentState]))
|
||||
return stream.ComputeSHA2Hash();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly EditorBeatmap editorBeatmap;
|
||||
private bool isRestoring;
|
||||
|
||||
public const int MAX_SAVED_STATES = 50;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="EditorChangeHandler"/>.
|
||||
/// </summary>
|
||||
/// <param name="editorBeatmap">The <see cref="EditorBeatmap"/> to track the <see cref="HitObject"/>s of.</param>
|
||||
public EditorChangeHandler(EditorBeatmap editorBeatmap)
|
||||
public override void BeginChange()
|
||||
{
|
||||
this.editorBeatmap = editorBeatmap;
|
||||
ensureStateSaved();
|
||||
|
||||
editorBeatmap.TransactionBegan += BeginChange;
|
||||
editorBeatmap.TransactionEnded += EndChange;
|
||||
editorBeatmap.SaveStateTriggered += SaveState;
|
||||
base.BeginChange();
|
||||
}
|
||||
|
||||
patcher = new LegacyEditorBeatmapPatcher(editorBeatmap);
|
||||
|
||||
// Initial state.
|
||||
SaveState();
|
||||
private void ensureStateSaved()
|
||||
{
|
||||
if (savedStates.Count == 0)
|
||||
SaveState();
|
||||
}
|
||||
|
||||
protected override void UpdateState()
|
||||
@ -72,9 +62,7 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||
new LegacyBeatmapEncoder(editorBeatmap, editorBeatmap.BeatmapSkin).Encode(sw);
|
||||
|
||||
WriteCurrentStateToStream(stream);
|
||||
byte[] newState = stream.ToArray();
|
||||
|
||||
// if the previous state is binary equal we don't need to push a new one, unless this is the initial state.
|
||||
@ -113,7 +101,8 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
isRestoring = true;
|
||||
|
||||
patcher.Patch(savedStates[currentState], savedStates[newState]);
|
||||
ApplyStateChange(savedStates[currentState], savedStates[newState]);
|
||||
|
||||
currentState = newState;
|
||||
|
||||
isRestoring = false;
|
||||
@ -122,6 +111,20 @@ namespace osu.Game.Screens.Edit
|
||||
updateBindables();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a serialised copy of the currently tracked state to the provided stream.
|
||||
/// This will be stored as a state which can be restored in the future.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream which the state should be written to.</param>
|
||||
protected abstract void WriteCurrentStateToStream(MemoryStream stream);
|
||||
|
||||
/// <summary>
|
||||
/// Given a previous and new state, apply any changes required to bring the current state in line with the new state.
|
||||
/// </summary>
|
||||
/// <param name="previousState">The previous (current before this call) serialised state.</param>
|
||||
/// <param name="newState">The new state to be applied.</param>
|
||||
protected abstract void ApplyStateChange(byte[] previousState, byte[] newState);
|
||||
|
||||
private void updateBindables()
|
||||
{
|
||||
CanUndo.Value = savedStates.Count > 0 && currentState > 0;
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Screens;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Screens.Edit.GameplayTest
|
||||
{
|
||||
@ -15,6 +16,8 @@ namespace osu.Game.Screens.Edit.GameplayTest
|
||||
private readonly Editor editor;
|
||||
private readonly EditorState editorState;
|
||||
|
||||
protected override UserActivity InitialActivity => new UserActivity.TestingBeatmap(Beatmap.Value.BeatmapInfo, Ruleset.Value);
|
||||
|
||||
[Resolved]
|
||||
private MusicController musicController { get; set; } = null!;
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Screens.Edit
|
||||
@ -11,12 +10,13 @@ namespace osu.Game.Screens.Edit
|
||||
/// <summary>
|
||||
/// Interface for a component that manages changes in the <see cref="Editor"/>.
|
||||
/// </summary>
|
||||
[Cached]
|
||||
public interface IEditorChangeHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Fired whenever a state change occurs.
|
||||
/// </summary>
|
||||
event Action OnStateChange;
|
||||
event Action? OnStateChange;
|
||||
|
||||
/// <summary>
|
||||
/// Begins a bulk state change event. <see cref="EndChange"/> should be invoked soon after.
|
||||
|
209
osu.Game/Screens/Edit/Timing/ControlPointList.cs
Normal file
209
osu.Game/Screens/Edit/Timing/ControlPointList.cs
Normal file
@ -0,0 +1,209 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
public partial class ControlPointList : CompositeDrawable
|
||||
{
|
||||
private OsuButton deleteButton = null!;
|
||||
private ControlPointTable table = null!;
|
||||
private OsuScrollContainer scroll = null!;
|
||||
private RoundedButton addButton = null!;
|
||||
|
||||
private readonly IBindableList<ControlPointGroup> controlPointGroups = new BindableList<ControlPointGroup>();
|
||||
|
||||
[Resolved]
|
||||
private EditorClock clock { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
protected EditorBeatmap Beatmap { get; private set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private Bindable<ControlPointGroup?> selectedGroup { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IEditorChangeHandler? changeHandler { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colours)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
const float margins = 10;
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = colours.Background4,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new Box
|
||||
{
|
||||
Colour = colours.Background3,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = ControlPointTable.TIMING_COLUMN_WIDTH + margins,
|
||||
},
|
||||
scroll = new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = table = new ControlPointTable(),
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Margin = new MarginPadding(margins),
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
deleteButton = new RoundedButton
|
||||
{
|
||||
Text = "-",
|
||||
Size = new Vector2(30, 30),
|
||||
Action = delete,
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
},
|
||||
addButton = new RoundedButton
|
||||
{
|
||||
Action = addNew,
|
||||
Size = new Vector2(160, 30),
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
selectedGroup.BindValueChanged(selected =>
|
||||
{
|
||||
deleteButton.Enabled.Value = selected.NewValue != null;
|
||||
|
||||
addButton.Text = selected.NewValue != null
|
||||
? "+ Clone to current time"
|
||||
: "+ Add at current time";
|
||||
}, true);
|
||||
|
||||
controlPointGroups.BindTo(Beatmap.ControlPointInfo.Groups);
|
||||
controlPointGroups.BindCollectionChanged((_, _) =>
|
||||
{
|
||||
table.ControlGroups = controlPointGroups;
|
||||
changeHandler?.SaveState();
|
||||
}, true);
|
||||
|
||||
table.OnRowSelected += drawable => scroll.ScrollIntoView(drawable);
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
selectedGroup.Value = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
trackActivePoint();
|
||||
|
||||
addButton.Enabled.Value = clock.CurrentTimeAccurate != selectedGroup.Value?.Time;
|
||||
}
|
||||
|
||||
private Type? trackedType;
|
||||
|
||||
/// <summary>
|
||||
/// Given the user has selected a control point group, we want to track any group which is
|
||||
/// active at the current point in time which matches the type the user has selected.
|
||||
///
|
||||
/// So if the user is currently looking at a timing point and seeks into the future, a
|
||||
/// future timing point would be automatically selected if it is now the new "current" point.
|
||||
/// </summary>
|
||||
private void trackActivePoint()
|
||||
{
|
||||
// For simplicity only match on the first type of the active control point.
|
||||
if (selectedGroup.Value == null)
|
||||
trackedType = null;
|
||||
else
|
||||
{
|
||||
// If the selected group only has one control point, update the tracking type.
|
||||
if (selectedGroup.Value.ControlPoints.Count == 1)
|
||||
trackedType = selectedGroup.Value?.ControlPoints.Single().GetType();
|
||||
// If the selected group has more than one control point, choose the first as the tracking type
|
||||
// if we don't already have a singular tracked type.
|
||||
else if (trackedType == null)
|
||||
trackedType = selectedGroup.Value?.ControlPoints.FirstOrDefault()?.GetType();
|
||||
}
|
||||
|
||||
if (trackedType != null)
|
||||
{
|
||||
// We don't have an efficient way of looking up groups currently, only individual point types.
|
||||
// To improve the efficiency of this in the future, we should reconsider the overall structure of ControlPointInfo.
|
||||
|
||||
// Find the next group which has the same type as the selected one.
|
||||
var found = Beatmap.ControlPointInfo.Groups
|
||||
.Where(g => g.ControlPoints.Any(cp => cp.GetType() == trackedType))
|
||||
.LastOrDefault(g => g.Time <= clock.CurrentTimeAccurate);
|
||||
|
||||
if (found != null)
|
||||
selectedGroup.Value = found;
|
||||
}
|
||||
}
|
||||
|
||||
private void delete()
|
||||
{
|
||||
if (selectedGroup.Value == null)
|
||||
return;
|
||||
|
||||
Beatmap.ControlPointInfo.RemoveGroup(selectedGroup.Value);
|
||||
|
||||
selectedGroup.Value = Beatmap.ControlPointInfo.Groups.FirstOrDefault(g => g.Time >= clock.CurrentTime);
|
||||
}
|
||||
|
||||
private void addNew()
|
||||
{
|
||||
bool isFirstControlPoint = !Beatmap.ControlPointInfo.TimingPoints.Any();
|
||||
|
||||
var group = Beatmap.ControlPointInfo.GroupAt(clock.CurrentTime, true);
|
||||
|
||||
if (isFirstControlPoint)
|
||||
group.Add(new TimingControlPoint());
|
||||
else
|
||||
{
|
||||
// Try and create matching types from the currently selected control point.
|
||||
var selected = selectedGroup.Value;
|
||||
|
||||
if (selected != null && !ReferenceEquals(selected, group))
|
||||
{
|
||||
foreach (var controlPoint in selected.ControlPoints)
|
||||
{
|
||||
group.Add(controlPoint.DeepClone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectedGroup.Value = group;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +1,11 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
@ -23,6 +14,9 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
[Cached]
|
||||
public readonly Bindable<ControlPointGroup> SelectedGroup = new Bindable<ControlPointGroup>();
|
||||
|
||||
[Resolved]
|
||||
private EditorClock? editorClock { get; set; }
|
||||
|
||||
public TimingScreen()
|
||||
: base(EditorScreenMode.Timing)
|
||||
{
|
||||
@ -46,192 +40,17 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
}
|
||||
};
|
||||
|
||||
public partial class ControlPointList : CompositeDrawable
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
private OsuButton deleteButton = null!;
|
||||
private ControlPointTable table = null!;
|
||||
private OsuScrollContainer scroll = null!;
|
||||
private RoundedButton addButton = null!;
|
||||
base.LoadComplete();
|
||||
|
||||
private readonly IBindableList<ControlPointGroup> controlPointGroups = new BindableList<ControlPointGroup>();
|
||||
|
||||
[Resolved]
|
||||
private EditorClock clock { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
protected EditorBeatmap Beatmap { get; private set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private Bindable<ControlPointGroup?> selectedGroup { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IEditorChangeHandler? changeHandler { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colours)
|
||||
if (editorClock != null)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
const float margins = 10;
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = colours.Background4,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new Box
|
||||
{
|
||||
Colour = colours.Background3,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = ControlPointTable.TIMING_COLUMN_WIDTH + margins,
|
||||
},
|
||||
scroll = new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = table = new ControlPointTable(),
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Margin = new MarginPadding(margins),
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
deleteButton = new RoundedButton
|
||||
{
|
||||
Text = "-",
|
||||
Size = new Vector2(30, 30),
|
||||
Action = delete,
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
},
|
||||
addButton = new RoundedButton
|
||||
{
|
||||
Action = addNew,
|
||||
Size = new Vector2(160, 30),
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
selectedGroup.BindValueChanged(selected =>
|
||||
{
|
||||
deleteButton.Enabled.Value = selected.NewValue != null;
|
||||
|
||||
addButton.Text = selected.NewValue != null
|
||||
? "+ Clone to current time"
|
||||
: "+ Add at current time";
|
||||
}, true);
|
||||
|
||||
controlPointGroups.BindTo(Beatmap.ControlPointInfo.Groups);
|
||||
controlPointGroups.BindCollectionChanged((_, _) =>
|
||||
{
|
||||
table.ControlGroups = controlPointGroups;
|
||||
changeHandler?.SaveState();
|
||||
}, true);
|
||||
|
||||
table.OnRowSelected += drawable => scroll.ScrollIntoView(drawable);
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
selectedGroup.Value = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
trackActivePoint();
|
||||
|
||||
addButton.Enabled.Value = clock.CurrentTimeAccurate != selectedGroup.Value?.Time;
|
||||
}
|
||||
|
||||
private Type? trackedType;
|
||||
|
||||
/// <summary>
|
||||
/// Given the user has selected a control point group, we want to track any group which is
|
||||
/// active at the current point in time which matches the type the user has selected.
|
||||
///
|
||||
/// So if the user is currently looking at a timing point and seeks into the future, a
|
||||
/// future timing point would be automatically selected if it is now the new "current" point.
|
||||
/// </summary>
|
||||
private void trackActivePoint()
|
||||
{
|
||||
// For simplicity only match on the first type of the active control point.
|
||||
if (selectedGroup.Value == null)
|
||||
trackedType = null;
|
||||
else
|
||||
{
|
||||
// If the selected group only has one control point, update the tracking type.
|
||||
if (selectedGroup.Value.ControlPoints.Count == 1)
|
||||
trackedType = selectedGroup.Value?.ControlPoints.Single().GetType();
|
||||
// If the selected group has more than one control point, choose the first as the tracking type
|
||||
// if we don't already have a singular tracked type.
|
||||
else if (trackedType == null)
|
||||
trackedType = selectedGroup.Value?.ControlPoints.FirstOrDefault()?.GetType();
|
||||
}
|
||||
|
||||
if (trackedType != null)
|
||||
{
|
||||
// We don't have an efficient way of looking up groups currently, only individual point types.
|
||||
// To improve the efficiency of this in the future, we should reconsider the overall structure of ControlPointInfo.
|
||||
|
||||
// Find the next group which has the same type as the selected one.
|
||||
var found = Beatmap.ControlPointInfo.Groups
|
||||
.Where(g => g.ControlPoints.Any(cp => cp.GetType() == trackedType))
|
||||
.LastOrDefault(g => g.Time <= clock.CurrentTimeAccurate);
|
||||
|
||||
if (found != null)
|
||||
selectedGroup.Value = found;
|
||||
}
|
||||
}
|
||||
|
||||
private void delete()
|
||||
{
|
||||
if (selectedGroup.Value == null)
|
||||
return;
|
||||
|
||||
Beatmap.ControlPointInfo.RemoveGroup(selectedGroup.Value);
|
||||
|
||||
selectedGroup.Value = Beatmap.ControlPointInfo.Groups.FirstOrDefault(g => g.Time >= clock.CurrentTime);
|
||||
}
|
||||
|
||||
private void addNew()
|
||||
{
|
||||
bool isFirstControlPoint = !Beatmap.ControlPointInfo.TimingPoints.Any();
|
||||
|
||||
var group = Beatmap.ControlPointInfo.GroupAt(clock.CurrentTime, true);
|
||||
|
||||
if (isFirstControlPoint)
|
||||
group.Add(new TimingControlPoint());
|
||||
else
|
||||
{
|
||||
// Try and create matching types from the currently selected control point.
|
||||
var selected = selectedGroup.Value;
|
||||
|
||||
if (selected != null && !ReferenceEquals(selected, group))
|
||||
{
|
||||
foreach (var controlPoint in selected.ControlPoints)
|
||||
{
|
||||
group.Add(controlPoint.DeepClone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectedGroup.Value = group;
|
||||
// When entering the timing screen, let's choose the closest valid timing point.
|
||||
// This will emulate the osu-stable behaviour where a metronome and timing information
|
||||
// are presented on entering the screen.
|
||||
var nearestTimingPoint = EditorBeatmap.ControlPointInfo.TimingPointAt(editorClock.CurrentTime);
|
||||
SelectedGroup.Value = EditorBeatmap.ControlPointInfo.GroupAt(nearestTimingPoint.Time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics;
|
||||
|
||||
@ -16,17 +14,17 @@ namespace osu.Game.Screens.Edit
|
||||
/// <summary>
|
||||
/// Fires whenever a transaction begins. Will not fire on nested transactions.
|
||||
/// </summary>
|
||||
public event Action TransactionBegan;
|
||||
public event Action? TransactionBegan;
|
||||
|
||||
/// <summary>
|
||||
/// Fires when the last transaction completes.
|
||||
/// </summary>
|
||||
public event Action TransactionEnded;
|
||||
public event Action? TransactionEnded;
|
||||
|
||||
/// <summary>
|
||||
/// Fires when <see cref="SaveState"/> is called and results in a non-transactional state save.
|
||||
/// </summary>
|
||||
public event Action SaveStateTriggered;
|
||||
public event Action? SaveStateTriggered;
|
||||
|
||||
public bool TransactionActive => bulkChangesStarted > 0;
|
||||
|
||||
@ -35,7 +33,7 @@ namespace osu.Game.Screens.Edit
|
||||
/// <summary>
|
||||
/// Signal the beginning of a change.
|
||||
/// </summary>
|
||||
public void BeginChange()
|
||||
public virtual void BeginChange()
|
||||
{
|
||||
if (bulkChangesStarted++ == 0)
|
||||
TransactionBegan?.Invoke();
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -22,29 +20,21 @@ namespace osu.Game.Screens.Play.Break
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
new Box
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = height,
|
||||
Child = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientVertical(Color4.Black, transparent_black),
|
||||
}
|
||||
Colour = ColourInfo.GradientVertical(Color4.Black, transparent_black),
|
||||
},
|
||||
new Container
|
||||
new Box
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = height,
|
||||
Child = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientVertical(transparent_black, Color4.Black),
|
||||
}
|
||||
Colour = ColourInfo.GradientVertical(transparent_black, Color4.Black),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -69,8 +69,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
var bindable = (IBindable)property.GetValue(component)!;
|
||||
|
||||
if (!bindable.IsDefault)
|
||||
Settings.Add(property.Name.ToSnakeCase(), bindable.GetUnderlyingSettingValue());
|
||||
Settings.Add(property.Name.ToSnakeCase(), bindable.GetUnderlyingSettingValue());
|
||||
}
|
||||
|
||||
if (component is Container<Drawable> container)
|
||||
|
@ -121,8 +121,8 @@ namespace osu.Game.Screens.Play.HUD
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Child = new UprightAspectMaintainingContainer
|
||||
{
|
||||
Origin = Anchor.CentreRight,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Scaling = ScaleMode.Vertical,
|
||||
ScalingFactor = 0.5f,
|
||||
|
@ -44,6 +44,14 @@ namespace osu.Game.Screens.Play
|
||||
});
|
||||
}
|
||||
|
||||
public void StopAllSamples()
|
||||
{
|
||||
if (!IsLoaded)
|
||||
return;
|
||||
|
||||
pauseLoop.Stop();
|
||||
}
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
base.PopIn();
|
||||
|
@ -1073,7 +1073,10 @@ namespace osu.Game.Screens.Play
|
||||
public override bool OnExiting(ScreenExitEvent e)
|
||||
{
|
||||
screenSuspension?.RemoveAndDisposeImmediately();
|
||||
|
||||
// Eagerly clean these up as disposal of child components is asynchronous and may leave sounds playing beyond user expectations.
|
||||
failAnimationLayer?.Stop();
|
||||
PauseOverlay?.StopAllSamples();
|
||||
|
||||
if (LoadedBeatmapSuccessfully)
|
||||
{
|
||||
|
@ -15,11 +15,11 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
||||
public partial class PlayerSliderBar<T> : SettingsSlider<T>
|
||||
where T : struct, IEquatable<T>, IComparable<T>, IConvertible
|
||||
{
|
||||
public OsuSliderBar<T> Bar => (OsuSliderBar<T>)Control;
|
||||
public RoundedSliderBar<T> Bar => (RoundedSliderBar<T>)Control;
|
||||
|
||||
protected override Drawable CreateControl() => new SliderBar();
|
||||
|
||||
protected partial class SliderBar : OsuSliderBar<T>
|
||||
protected partial class SliderBar : RoundedSliderBar<T>
|
||||
{
|
||||
public SliderBar()
|
||||
{
|
||||
|
@ -15,6 +15,7 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Screens.Play
|
||||
{
|
||||
@ -24,6 +25,8 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private readonly bool replayIsFailedScore;
|
||||
|
||||
protected override UserActivity InitialActivity => new UserActivity.WatchingReplay(Score.ScoreInfo);
|
||||
|
||||
// Disallow replays from failing. (see https://github.com/ppy/osu/issues/6108)
|
||||
protected override bool CheckModsAllowFailure()
|
||||
{
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Online.Spectator;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Screens.Play
|
||||
{
|
||||
@ -14,6 +15,8 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
private readonly Score score;
|
||||
|
||||
protected override UserActivity InitialActivity => new UserActivity.SpectatingUser(Score.ScoreInfo);
|
||||
|
||||
public SoloSpectatorPlayer(Score score, PlayerConfiguration configuration = null)
|
||||
: base(score, configuration)
|
||||
{
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user