mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 05:32:54 +08:00
Merge branch 'master' into footer_V2_implementation
This commit is contained in:
commit
51d4ae5241
@ -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;
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ namespace osu.Desktop
|
||||
internal partial class OsuGameDesktop : OsuGame
|
||||
{
|
||||
private OsuSchemeLinkIPCChannel? osuSchemeLinkIPCChannel;
|
||||
private ArchiveImportIPCChannel? archiveImportIPCChannel;
|
||||
|
||||
public OsuGameDesktop(string[]? args = null)
|
||||
: base(args)
|
||||
@ -123,6 +124,7 @@ namespace osu.Desktop
|
||||
LoadComponentAsync(new ElevatedPrivilegesChecker(), Add);
|
||||
|
||||
osuSchemeLinkIPCChannel = new OsuSchemeLinkIPCChannel(Host, this);
|
||||
archiveImportIPCChannel = new ArchiveImportIPCChannel(Host, this);
|
||||
}
|
||||
|
||||
public override void SetHost(GameHost host)
|
||||
@ -181,6 +183,7 @@ namespace osu.Desktop
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
osuSchemeLinkIPCChannel?.Dispose();
|
||||
archiveImportIPCChannel?.Dispose();
|
||||
}
|
||||
|
||||
private class SDL2BatteryInfo : BatteryInfo
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -187,28 +187,19 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
if (b.IsSelected)
|
||||
continue;
|
||||
|
||||
var hitObject = (OsuHitObject)b.Item;
|
||||
var snapPositions = b.ScreenSpaceSnapPoints;
|
||||
|
||||
Vector2? snap = checkSnap(hitObject.Position);
|
||||
if (snap == null && hitObject.Position != hitObject.EndPosition)
|
||||
snap = checkSnap(hitObject.EndPosition);
|
||||
if (!snapPositions.Any())
|
||||
continue;
|
||||
|
||||
if (snap != null)
|
||||
var closestSnapPosition = snapPositions.MinBy(p => Vector2.Distance(p, screenSpacePosition));
|
||||
|
||||
if (Vector2.Distance(closestSnapPosition, screenSpacePosition) < snapRadius)
|
||||
{
|
||||
// only return distance portion, since time is not really valid
|
||||
snapResult = new SnapResult(snap.Value, null, playfield);
|
||||
snapResult = new SnapResult(closestSnapPosition, null, playfield);
|
||||
return true;
|
||||
}
|
||||
|
||||
Vector2? checkSnap(Vector2 checkPos)
|
||||
{
|
||||
Vector2 checkScreenPos = playfield.GamefieldToScreenSpace(checkPos);
|
||||
|
||||
if (Vector2.Distance(checkScreenPos, screenSpacePosition) < snapRadius)
|
||||
return checkScreenPos;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
snapResult = null;
|
||||
|
@ -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)
|
||||
|
@ -2,8 +2,11 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Taiko.Configuration;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
@ -14,36 +17,48 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
private DrumTouchInputArea drumTouchInputArea = null!;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
private readonly Bindable<TaikoTouchControlScheme> controlScheme = new Bindable<TaikoTouchControlScheme>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddStep("create drum", () =>
|
||||
var config = (TaikoRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull();
|
||||
config.BindWith(TaikoRulesetSetting.TouchControlScheme, controlScheme);
|
||||
}
|
||||
|
||||
private void createDrum()
|
||||
{
|
||||
Child = new TaikoInputManager(new TaikoRuleset().RulesetInfo)
|
||||
{
|
||||
Child = new TaikoInputManager(new TaikoRuleset().RulesetInfo)
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
new InputDrum
|
||||
{
|
||||
new InputDrum
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Height = 0.2f,
|
||||
},
|
||||
drumTouchInputArea = new DrumTouchInputArea
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
},
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Height = 0.2f,
|
||||
},
|
||||
};
|
||||
});
|
||||
drumTouchInputArea = new DrumTouchInputArea
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDrum()
|
||||
{
|
||||
AddStep("create drum", createDrum);
|
||||
AddStep("show drum", () => drumTouchInputArea.Show());
|
||||
|
||||
AddStep("change scheme (kddk)", () => controlScheme.Value = TaikoTouchControlScheme.KDDK);
|
||||
AddStep("change scheme (kkdd)", () => controlScheme.Value = TaikoTouchControlScheme.KKDD);
|
||||
AddStep("change scheme (ddkk)", () => controlScheme.Value = TaikoTouchControlScheme.DDKK);
|
||||
}
|
||||
|
||||
protected override Ruleset CreateRuleset() => new TaikoRuleset();
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -0,0 +1,28 @@
|
||||
// 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.Game.Configuration;
|
||||
using osu.Game.Rulesets.Configuration;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Configuration
|
||||
{
|
||||
public class TaikoRulesetConfigManager : RulesetConfigManager<TaikoRulesetSetting>
|
||||
{
|
||||
public TaikoRulesetConfigManager(SettingsStore? settings, RulesetInfo ruleset, int? variant = null)
|
||||
: base(settings, ruleset, variant)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void InitialiseDefaults()
|
||||
{
|
||||
base.InitialiseDefaults();
|
||||
|
||||
SetDefault(TaikoRulesetSetting.TouchControlScheme, TaikoTouchControlScheme.KDDK);
|
||||
}
|
||||
}
|
||||
|
||||
public enum TaikoRulesetSetting
|
||||
{
|
||||
TouchControlScheme
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
// 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.
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Configuration
|
||||
{
|
||||
public enum TaikoTouchControlScheme
|
||||
{
|
||||
KDDK,
|
||||
DDKK,
|
||||
KKDD
|
||||
}
|
||||
}
|
@ -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
|
@ -28,9 +28,13 @@ using osu.Game.Rulesets.Taiko.Skinning.Argon;
|
||||
using osu.Game.Rulesets.Taiko.Skinning.Legacy;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking.Statistics;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Rulesets.Configuration;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Taiko.Configuration;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko
|
||||
{
|
||||
@ -194,6 +198,10 @@ namespace osu.Game.Rulesets.Taiko
|
||||
|
||||
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame();
|
||||
|
||||
public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new TaikoRulesetConfigManager(settings, RulesetInfo);
|
||||
|
||||
public override RulesetSettingsSubsection CreateSettings() => new TaikoSettingsSubsection(this);
|
||||
|
||||
protected override IEnumerable<HitResult> GetValidHitResults()
|
||||
{
|
||||
return new[]
|
||||
@ -201,9 +209,8 @@ namespace osu.Game.Rulesets.Taiko
|
||||
HitResult.Great,
|
||||
HitResult.Ok,
|
||||
|
||||
HitResult.SmallTickHit,
|
||||
|
||||
HitResult.SmallBonus,
|
||||
HitResult.LargeBonus,
|
||||
};
|
||||
}
|
||||
|
||||
@ -212,6 +219,9 @@ namespace osu.Game.Rulesets.Taiko
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.SmallBonus:
|
||||
return "drum tick";
|
||||
|
||||
case HitResult.LargeBonus:
|
||||
return "bonus";
|
||||
}
|
||||
|
||||
|
36
osu.Game.Rulesets.Taiko/TaikoSettingsSubsection.cs
Normal file
36
osu.Game.Rulesets.Taiko/TaikoSettingsSubsection.cs
Normal file
@ -0,0 +1,36 @@
|
||||
// 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.Localisation;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Taiko.Configuration;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko
|
||||
{
|
||||
public partial class TaikoSettingsSubsection : RulesetSettingsSubsection
|
||||
{
|
||||
protected override LocalisableString Header => "osu!taiko";
|
||||
|
||||
public TaikoSettingsSubsection(TaikoRuleset ruleset)
|
||||
: base(ruleset)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
var config = (TaikoRulesetConfigManager)Config;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SettingsEnumDropdown<TaikoTouchControlScheme>
|
||||
{
|
||||
LabelText = "Touch control scheme",
|
||||
Current = config.GetBindable<TaikoTouchControlScheme>(TaikoRulesetSetting.TouchControlScheme)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +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.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -11,6 +13,7 @@ using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Taiko.Configuration;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -31,15 +34,18 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
|
||||
private Container mainContent = null!;
|
||||
|
||||
private QuarterCircle leftCentre = null!;
|
||||
private QuarterCircle rightCentre = null!;
|
||||
private QuarterCircle leftRim = null!;
|
||||
private QuarterCircle rightRim = null!;
|
||||
private DrumSegment leftCentre = null!;
|
||||
private DrumSegment rightCentre = null!;
|
||||
private DrumSegment leftRim = null!;
|
||||
private DrumSegment rightRim = null!;
|
||||
|
||||
private readonly Bindable<TaikoTouchControlScheme> configTouchControlScheme = new Bindable<TaikoTouchControlScheme>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(TaikoInputManager taikoInputManager, OsuColour colours)
|
||||
private void load(TaikoInputManager taikoInputManager, TaikoRulesetConfigManager config)
|
||||
{
|
||||
Debug.Assert(taikoInputManager.KeyBindingContainer != null);
|
||||
|
||||
keyBindingContainer = taikoInputManager.KeyBindingContainer;
|
||||
|
||||
// Container should handle input everywhere.
|
||||
@ -65,27 +71,27 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
leftRim = new QuarterCircle(TaikoAction.LeftRim, colours.Blue)
|
||||
leftRim = new DrumSegment
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomRight,
|
||||
X = -2,
|
||||
},
|
||||
rightRim = new QuarterCircle(TaikoAction.RightRim, colours.Blue)
|
||||
rightRim = new DrumSegment
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomRight,
|
||||
X = 2,
|
||||
Rotation = 90,
|
||||
},
|
||||
leftCentre = new QuarterCircle(TaikoAction.LeftCentre, colours.Pink)
|
||||
leftCentre = new DrumSegment
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomRight,
|
||||
X = -2,
|
||||
Scale = new Vector2(centre_region),
|
||||
},
|
||||
rightCentre = new QuarterCircle(TaikoAction.RightCentre, colours.Pink)
|
||||
rightCentre = new DrumSegment
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomRight,
|
||||
@ -98,6 +104,17 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
config.BindWith(TaikoRulesetSetting.TouchControlScheme, configTouchControlScheme);
|
||||
configTouchControlScheme.BindValueChanged(scheme =>
|
||||
{
|
||||
var actions = getOrderedActionsForScheme(scheme.NewValue);
|
||||
|
||||
leftRim.Action = actions[0];
|
||||
leftCentre.Action = actions[1];
|
||||
rightCentre.Action = actions[2];
|
||||
rightRim.Action = actions[3];
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
@ -119,11 +136,47 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
base.OnTouchUp(e);
|
||||
}
|
||||
|
||||
private static TaikoAction[] getOrderedActionsForScheme(TaikoTouchControlScheme scheme)
|
||||
{
|
||||
switch (scheme)
|
||||
{
|
||||
case TaikoTouchControlScheme.KDDK:
|
||||
return new[]
|
||||
{
|
||||
TaikoAction.LeftRim,
|
||||
TaikoAction.LeftCentre,
|
||||
TaikoAction.RightCentre,
|
||||
TaikoAction.RightRim
|
||||
};
|
||||
|
||||
case TaikoTouchControlScheme.DDKK:
|
||||
return new[]
|
||||
{
|
||||
TaikoAction.LeftCentre,
|
||||
TaikoAction.RightCentre,
|
||||
TaikoAction.LeftRim,
|
||||
TaikoAction.RightRim
|
||||
};
|
||||
|
||||
case TaikoTouchControlScheme.KKDD:
|
||||
return new[]
|
||||
{
|
||||
TaikoAction.LeftRim,
|
||||
TaikoAction.RightRim,
|
||||
TaikoAction.LeftCentre,
|
||||
TaikoAction.RightCentre
|
||||
};
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(scheme), scheme, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDown(object source, Vector2 position)
|
||||
{
|
||||
Show();
|
||||
|
||||
TaikoAction taikoAction = getTaikoActionFromInput(position);
|
||||
TaikoAction taikoAction = getTaikoActionFromPosition(position);
|
||||
|
||||
// Not too sure how this can happen, but let's avoid throwing.
|
||||
if (trackedActions.ContainsKey(source))
|
||||
@ -139,18 +192,15 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
trackedActions.Remove(source);
|
||||
}
|
||||
|
||||
private bool validMouse(MouseButtonEvent e) =>
|
||||
leftRim.Contains(e.ScreenSpaceMouseDownPosition) || rightRim.Contains(e.ScreenSpaceMouseDownPosition);
|
||||
|
||||
private TaikoAction getTaikoActionFromInput(Vector2 inputPosition)
|
||||
private TaikoAction getTaikoActionFromPosition(Vector2 inputPosition)
|
||||
{
|
||||
bool centreHit = leftCentre.Contains(inputPosition) || rightCentre.Contains(inputPosition);
|
||||
bool leftSide = ToLocalSpace(inputPosition).X < DrawWidth / 2;
|
||||
|
||||
if (leftSide)
|
||||
return centreHit ? TaikoAction.LeftCentre : TaikoAction.LeftRim;
|
||||
return centreHit ? leftCentre.Action : leftRim.Action;
|
||||
|
||||
return centreHit ? TaikoAction.RightCentre : TaikoAction.RightRim;
|
||||
return centreHit ? rightCentre.Action : rightRim.Action;
|
||||
}
|
||||
|
||||
protected override void PopIn()
|
||||
@ -163,23 +213,42 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
mainContent.FadeOut(300);
|
||||
}
|
||||
|
||||
private partial class QuarterCircle : CompositeDrawable, IKeyBindingHandler<TaikoAction>
|
||||
private partial class DrumSegment : CompositeDrawable, IKeyBindingHandler<TaikoAction>
|
||||
{
|
||||
private readonly Circle overlay;
|
||||
private TaikoAction action;
|
||||
|
||||
private readonly TaikoAction handledAction;
|
||||
public TaikoAction Action
|
||||
{
|
||||
get => action;
|
||||
set
|
||||
{
|
||||
if (action == value)
|
||||
return;
|
||||
|
||||
private readonly Circle circle;
|
||||
action = value;
|
||||
updateColoursFromAction();
|
||||
}
|
||||
}
|
||||
|
||||
private Circle overlay = null!;
|
||||
|
||||
private Circle circle = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
public override bool Contains(Vector2 screenSpacePos) => circle.Contains(screenSpacePos);
|
||||
|
||||
public QuarterCircle(TaikoAction handledAction, Color4 colour)
|
||||
public DrumSegment()
|
||||
{
|
||||
this.handledAction = handledAction;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
FillMode = FillMode.Fit;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
@ -191,7 +260,6 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
circle = new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colour.Multiply(1.4f).Darken(2.8f),
|
||||
Alpha = 0.8f,
|
||||
Scale = new Vector2(2),
|
||||
},
|
||||
@ -200,7 +268,6 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
Alpha = 0,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Colour = colour,
|
||||
Scale = new Vector2(2),
|
||||
}
|
||||
}
|
||||
@ -208,18 +275,52 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
updateColoursFromAction();
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<TaikoAction> e)
|
||||
{
|
||||
if (e.Action == handledAction)
|
||||
if (e.Action == Action)
|
||||
overlay.FadeTo(1f, 80, Easing.OutQuint);
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<TaikoAction> e)
|
||||
{
|
||||
if (e.Action == handledAction)
|
||||
if (e.Action == Action)
|
||||
overlay.FadeOut(1000, Easing.OutQuint);
|
||||
}
|
||||
|
||||
private void updateColoursFromAction()
|
||||
{
|
||||
if (!IsLoaded)
|
||||
return;
|
||||
|
||||
var colour = getColourFromTaikoAction(Action);
|
||||
|
||||
circle.Colour = colour.Multiply(1.4f).Darken(2.8f);
|
||||
overlay.Colour = colour;
|
||||
}
|
||||
|
||||
private Color4 getColourFromTaikoAction(TaikoAction handledAction)
|
||||
{
|
||||
switch (handledAction)
|
||||
{
|
||||
case TaikoAction.LeftRim:
|
||||
case TaikoAction.RightRim:
|
||||
return colours.Blue;
|
||||
|
||||
case TaikoAction.LeftCentre:
|
||||
case TaikoAction.RightCentre:
|
||||
return colours.Pink;
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework;
|
||||
@ -14,6 +15,8 @@ using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
@ -23,11 +26,13 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
private HeadlessGameHost ipcSenderHost = null!;
|
||||
|
||||
private OsuSchemeLinkIPCChannel osuSchemeLinkIPCReceiver = null!;
|
||||
private OsuSchemeLinkIPCChannel osuSchemeLinkIPCSender = null!;
|
||||
private ArchiveImportIPCChannel archiveImportIPCSender = null!;
|
||||
|
||||
private const int requested_beatmap_set_id = 1;
|
||||
|
||||
protected override TestOsuGame CreateTestGame() => new IpcGame(LocalStorage, API);
|
||||
|
||||
[Resolved]
|
||||
private GameHost gameHost { get; set; } = null!;
|
||||
|
||||
@ -56,11 +61,11 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
return false;
|
||||
};
|
||||
});
|
||||
AddStep("create IPC receiver channel", () => osuSchemeLinkIPCReceiver = new OsuSchemeLinkIPCChannel(gameHost, Game));
|
||||
AddStep("create IPC sender channel", () =>
|
||||
AddStep("create IPC sender channels", () =>
|
||||
{
|
||||
ipcSenderHost = new HeadlessGameHost(gameHost.Name, new HostOptions { BindIPC = true });
|
||||
osuSchemeLinkIPCSender = new OsuSchemeLinkIPCChannel(ipcSenderHost);
|
||||
archiveImportIPCSender = new ArchiveImportIPCChannel(ipcSenderHost);
|
||||
});
|
||||
}
|
||||
|
||||
@ -72,15 +77,50 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddUntilStep("beatmap overlay showing content", () => Game.ChildrenOfType<BeatmapSetOverlay>().FirstOrDefault()?.Header.BeatmapSet.Value.OnlineID == requested_beatmap_set_id);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestArchiveImportLinkIPCChannel()
|
||||
{
|
||||
string? beatmapFilepath = null;
|
||||
|
||||
AddStep("import beatmap via IPC", () => archiveImportIPCSender.ImportAsync(beatmapFilepath = TestResources.GetQuickTestBeatmapForImport()).WaitSafely());
|
||||
AddUntilStep("import complete notification was presented", () => Game.Notifications.ChildrenOfType<ProgressCompletionNotification>().Count(), () => Is.EqualTo(1));
|
||||
AddAssert("original file deleted", () => File.Exists(beatmapFilepath), () => Is.False);
|
||||
}
|
||||
|
||||
public override void TearDownSteps()
|
||||
{
|
||||
AddStep("dispose IPC receiver", () => osuSchemeLinkIPCReceiver.Dispose());
|
||||
AddStep("dispose IPC sender", () =>
|
||||
AddStep("dispose IPC senders", () =>
|
||||
{
|
||||
osuSchemeLinkIPCSender.Dispose();
|
||||
archiveImportIPCSender.Dispose();
|
||||
ipcSenderHost.Dispose();
|
||||
});
|
||||
base.TearDownSteps();
|
||||
}
|
||||
|
||||
private partial class IpcGame : TestOsuGame
|
||||
{
|
||||
private OsuSchemeLinkIPCChannel? osuSchemeLinkIPCChannel;
|
||||
private ArchiveImportIPCChannel? archiveImportIPCChannel;
|
||||
|
||||
public IpcGame(Storage storage, IAPIProvider api, string[]? args = null)
|
||||
: base(storage, api, args)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
osuSchemeLinkIPCChannel = new OsuSchemeLinkIPCChannel(Host, this);
|
||||
archiveImportIPCChannel = new ArchiveImportIPCChannel(Host, this);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
osuSchemeLinkIPCChannel?.Dispose();
|
||||
archiveImportIPCChannel?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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);
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -294,8 +294,14 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
vertexBatch = renderer.CreateQuadBatch<TexturedVertex2D>(Source.AimCount, 1);
|
||||
}
|
||||
|
||||
// Due to triangles having various sizes we would need to set a different "texelSize" value for each of them, which is insanely expensive, thus we should use one single value.
|
||||
// texelSize computed for an average triangle (size 100) will result in big triangles becoming blurry, so we may just use 0 for all of them.
|
||||
// But we still need to specify at least something, because otherwise other shader usages will override this value.
|
||||
float texelSize = 0f;
|
||||
|
||||
shader.Bind();
|
||||
shader.GetUniform<float>("thickness").UpdateValue(ref fill);
|
||||
shader.GetUniform<float>("texelSize").UpdateValue(ref texelSize);
|
||||
|
||||
foreach (TriangleParticle particle in parts)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -44,8 +44,11 @@ namespace osu.Game.Graphics.Containers
|
||||
content.AutoSizeAxes = AutoSizeAxes;
|
||||
}
|
||||
|
||||
AddInternal(content);
|
||||
Add(CreateHoverSounds(sampleSet));
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
CreateHoverSounds(sampleSet),
|
||||
content,
|
||||
});
|
||||
}
|
||||
|
||||
protected override void ClearInternal(bool disposeChildren = true) =>
|
||||
|
@ -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
|
||||
{
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
if (buttons.Contains(e.Button) && Contains(e.ScreenSpaceMousePosition))
|
||||
if (buttons.Contains(e.Button))
|
||||
{
|
||||
var channel = Enabled.Value ? sampleClick?.GetChannel() : sampleClickDisabled?.GetChannel();
|
||||
|
||||
|
@ -5,19 +5,22 @@
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Configuration;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles debouncing hover sounds at a global level to ensure the effects are not overwhelming.
|
||||
/// </summary>
|
||||
public abstract partial class HoverSampleDebounceComponent : CompositeDrawable
|
||||
public abstract partial class HoverSampleDebounceComponent : Component
|
||||
{
|
||||
private Bindable<double?> lastPlaybackTime;
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.ReceivePositionalInputAt(screenSpacePos) == true;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(SessionStatics statics)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -116,7 +116,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
});
|
||||
|
||||
if (hoverSounds.HasValue)
|
||||
Add(new HoverClickSounds(hoverSounds.Value) { Enabled = { BindTarget = Enabled } });
|
||||
AddInternal(new HoverClickSounds(hoverSounds.Value) { Enabled = { BindTarget = Enabled } });
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
@ -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.
|
||||
|
89
osu.Game/Localisation/HUD/BarHitErrorMeterStrings.cs
Normal file
89
osu.Game/Localisation/HUD/BarHitErrorMeterStrings.cs
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Localisation;
|
||||
|
||||
namespace osu.Game.Localisation.HUD
|
||||
{
|
||||
public static class BarHitErrorMeterStrings
|
||||
{
|
||||
private const string prefix = @"osu.Game.Resources.Localisation.HUD.BarHitErrorMeter";
|
||||
|
||||
/// <summary>
|
||||
/// "Judgement line thickness"
|
||||
/// </summary>
|
||||
public static LocalisableString JudgementLineThickness => new TranslatableString(getKey(@"judgement_line_thickness"), "Judgement line thickness");
|
||||
|
||||
/// <summary>
|
||||
/// "How thick the individual lines should be."
|
||||
/// </summary>
|
||||
public static LocalisableString JudgementLineThicknessDescription => new TranslatableString(getKey(@"judgement_line_thickness_description"), "How thick the individual lines should be.");
|
||||
|
||||
/// <summary>
|
||||
/// "Show colour bars"
|
||||
/// </summary>
|
||||
public static LocalisableString ColourBarVisibility => new TranslatableString(getKey(@"colour_bar_visibility"), "Show colour bars");
|
||||
|
||||
/// <summary>
|
||||
/// "Show moving average arrow"
|
||||
/// </summary>
|
||||
public static LocalisableString ShowMovingAverage => new TranslatableString(getKey(@"show_moving_average"), "Show moving average arrow");
|
||||
|
||||
/// <summary>
|
||||
/// "Whether an arrow should move beneath the bar showing the average error."
|
||||
/// </summary>
|
||||
public static LocalisableString ShowMovingAverageDescription => new TranslatableString(getKey(@"show_moving_average_description"), "Whether an arrow should move beneath the bar showing the average error.");
|
||||
|
||||
/// <summary>
|
||||
/// "Centre marker style"
|
||||
/// </summary>
|
||||
public static LocalisableString CentreMarkerStyle => new TranslatableString(getKey(@"centre_marker_style"), "Centre marker style");
|
||||
|
||||
/// <summary>
|
||||
/// "How to signify the centre of the display"
|
||||
/// </summary>
|
||||
public static LocalisableString CentreMarkerStyleDescription => new TranslatableString(getKey(@"centre_marker_style_description"), "How to signify the centre of the display");
|
||||
|
||||
/// <summary>
|
||||
/// "None"
|
||||
/// </summary>
|
||||
public static LocalisableString CentreMarkerStylesNone => new TranslatableString(getKey(@"centre_marker_styles_none"), "None");
|
||||
|
||||
/// <summary>
|
||||
/// "Circle"
|
||||
/// </summary>
|
||||
public static LocalisableString CentreMarkerStylesCircle => new TranslatableString(getKey(@"centre_marker_styles_circle"), "Circle");
|
||||
|
||||
/// <summary>
|
||||
/// "Line"
|
||||
/// </summary>
|
||||
public static LocalisableString CentreMarkerStylesLine => new TranslatableString(getKey(@"centre_marker_styles_line"), "Line");
|
||||
|
||||
/// <summary>
|
||||
/// "Label style"
|
||||
/// </summary>
|
||||
public static LocalisableString LabelStyle => new TranslatableString(getKey(@"label_style"), "Label style");
|
||||
|
||||
/// <summary>
|
||||
/// "How to show early/late extremities"
|
||||
/// </summary>
|
||||
public static LocalisableString LabelStyleDescription => new TranslatableString(getKey(@"label_style_description"), "How to show early/late extremities");
|
||||
|
||||
/// <summary>
|
||||
/// "None"
|
||||
/// </summary>
|
||||
public static LocalisableString LabelStylesNone => new TranslatableString(getKey(@"label_styles_none"), "None");
|
||||
|
||||
/// <summary>
|
||||
/// "Icons"
|
||||
/// </summary>
|
||||
public static LocalisableString LabelStylesIcons => new TranslatableString(getKey(@"label_styles_icons"), "Icons");
|
||||
|
||||
/// <summary>
|
||||
/// "Text"
|
||||
/// </summary>
|
||||
public static LocalisableString LabelStylesText => new TranslatableString(getKey(@"label_styles_text"), "Text");
|
||||
|
||||
private static string getKey(string key) => $"{prefix}:{key}";
|
||||
}
|
||||
}
|
54
osu.Game/Localisation/HUD/ColourHitErrorMeterStrings.cs
Normal file
54
osu.Game/Localisation/HUD/ColourHitErrorMeterStrings.cs
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Localisation;
|
||||
|
||||
namespace osu.Game.Localisation.HUD
|
||||
{
|
||||
public static class ColourHitErrorMeterStrings
|
||||
{
|
||||
private const string prefix = @"osu.Game.Resources.Localisation.HUD.ColourHitError";
|
||||
|
||||
/// <summary>
|
||||
/// "Judgement count"
|
||||
/// </summary>
|
||||
public static LocalisableString JudgementCount => new TranslatableString(getKey(@"judgement_count"), "Judgement count");
|
||||
|
||||
/// <summary>
|
||||
/// "The number of displayed judgements"
|
||||
/// </summary>
|
||||
public static LocalisableString JudgementCountDescription => new TranslatableString(getKey(@"judgement_count_description"), "The number of displayed judgements");
|
||||
|
||||
/// <summary>
|
||||
/// "Judgement spacing"
|
||||
/// </summary>
|
||||
public static LocalisableString JudgementSpacing => new TranslatableString(getKey(@"judgement_spacing"), "Judgement spacing");
|
||||
|
||||
/// <summary>
|
||||
/// "The space between each displayed judgement"
|
||||
/// </summary>
|
||||
public static LocalisableString JudgementSpacingDescription => new TranslatableString(getKey(@"judgement_spacing_description"), "The space between each displayed judgement");
|
||||
|
||||
/// <summary>
|
||||
/// "Judgement shape"
|
||||
/// </summary>
|
||||
public static LocalisableString JudgementShape => new TranslatableString(getKey(@"judgement_shape"), "Judgement shape");
|
||||
|
||||
/// <summary>
|
||||
/// "The shape of each displayed judgement"
|
||||
/// </summary>
|
||||
public static LocalisableString JudgementShapeDescription => new TranslatableString(getKey(@"judgement_shape_description"), "The shape of each displayed judgement");
|
||||
|
||||
/// <summary>
|
||||
/// "Circle"
|
||||
/// </summary>
|
||||
public static LocalisableString ShapeStyleCircle => new TranslatableString(getKey(@"shape_style_cricle"), "Circle");
|
||||
|
||||
/// <summary>
|
||||
/// "Square"
|
||||
/// </summary>
|
||||
public static LocalisableString ShapeStyleSquare => new TranslatableString(getKey(@"shape_style_square"), "Square");
|
||||
|
||||
private static string getKey(string key) => $"{prefix}:{key}";
|
||||
}
|
||||
}
|
39
osu.Game/Localisation/HUD/GameplayAccuracyCounterStrings.cs
Normal file
39
osu.Game/Localisation/HUD/GameplayAccuracyCounterStrings.cs
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Localisation;
|
||||
|
||||
namespace osu.Game.Localisation.HUD
|
||||
{
|
||||
public static class GameplayAccuracyCounterStrings
|
||||
{
|
||||
private const string prefix = @"osu.Game.Resources.Localisation.HUD.GameplayAccuracyCounter";
|
||||
|
||||
/// <summary>
|
||||
/// "Accuracy display mode"
|
||||
/// </summary>
|
||||
public static LocalisableString AccuracyDisplay => new TranslatableString(getKey(@"accuracy_display"), "Accuracy display mode");
|
||||
|
||||
/// <summary>
|
||||
/// "Which accuracy mode should be displayed."
|
||||
/// </summary>
|
||||
public static LocalisableString AccuracyDisplayDescription => new TranslatableString(getKey(@"accuracy_display_description"), "Which accuracy mode should be displayed.");
|
||||
|
||||
/// <summary>
|
||||
/// "Standard"
|
||||
/// </summary>
|
||||
public static LocalisableString AccuracyDisplayModeStandard => new TranslatableString(getKey(@"accuracy_display_mode_standard"), "Standard");
|
||||
|
||||
/// <summary>
|
||||
/// "Maximum achievable"
|
||||
/// </summary>
|
||||
public static LocalisableString AccuracyDisplayModeMax => new TranslatableString(getKey(@"accuracy_display_mode_max"), "Maximum achievable");
|
||||
|
||||
/// <summary>
|
||||
/// "Minimum achievable"
|
||||
/// </summary>
|
||||
public static LocalisableString AccuracyDisplayModeMin => new TranslatableString(getKey(@"accuracy_display_mode_min"), "Minimum achievable");
|
||||
|
||||
private static string getKey(string key) => $"{prefix}:{key}";
|
||||
}
|
||||
}
|
49
osu.Game/Localisation/HUD/JudgementCounterDisplayStrings.cs
Normal file
49
osu.Game/Localisation/HUD/JudgementCounterDisplayStrings.cs
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Localisation;
|
||||
|
||||
namespace osu.Game.Localisation.HUD
|
||||
{
|
||||
public static class JudgementCounterDisplayStrings
|
||||
{
|
||||
private const string prefix = @"osu.Game.Resources.Localisation.HUD.JudgementCounterDisplay";
|
||||
|
||||
/// <summary>
|
||||
/// "Display mode"
|
||||
/// </summary>
|
||||
public static LocalisableString JudgementDisplayMode => new TranslatableString(getKey(@"judgement_display_mode"), "Display mode");
|
||||
|
||||
/// <summary>
|
||||
/// "Counter direction"
|
||||
/// </summary>
|
||||
public static LocalisableString FlowDirection => new TranslatableString(getKey(@"flow_direction"), "Counter direction");
|
||||
|
||||
/// <summary>
|
||||
/// "Show judgement names"
|
||||
/// </summary>
|
||||
public static LocalisableString ShowJudgementNames => new TranslatableString(getKey(@"show_judgement_names"), "Show judgement names");
|
||||
|
||||
/// <summary>
|
||||
/// "Show max judgement"
|
||||
/// </summary>
|
||||
public static LocalisableString ShowMaxJudgement => new TranslatableString(getKey(@"show_max_judgement"), "Show max judgement");
|
||||
|
||||
/// <summary>
|
||||
/// "Simple"
|
||||
/// </summary>
|
||||
public static LocalisableString JudgementDisplayModeSimple => new TranslatableString(getKey(@"judgement_display_mode_simple"), "Simple");
|
||||
|
||||
/// <summary>
|
||||
/// "Normal"
|
||||
/// </summary>
|
||||
public static LocalisableString JudgementDisplayModeNormal => new TranslatableString(getKey(@"judgement_display_mode_normal"), "Normal");
|
||||
|
||||
/// <summary>
|
||||
/// "All"
|
||||
/// </summary>
|
||||
public static LocalisableString JudgementDisplayModeAll => new TranslatableString(getKey(@"judgement_display_mode_all"), "All");
|
||||
|
||||
private static string getKey(string key) => $"{prefix}:{key}";
|
||||
}
|
||||
}
|
24
osu.Game/Localisation/HUD/SongProgressStrings.cs
Normal file
24
osu.Game/Localisation/HUD/SongProgressStrings.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.Localisation;
|
||||
|
||||
namespace osu.Game.Localisation.HUD
|
||||
{
|
||||
public static class SongProgressStrings
|
||||
{
|
||||
private const string prefix = @"osu.Game.Resources.Localisation.HUD.SongProgress";
|
||||
|
||||
/// <summary>
|
||||
/// "Show difficulty graph"
|
||||
/// </summary>
|
||||
public static LocalisableString ShowGraph => new TranslatableString(getKey(@"show_graph"), "Show difficulty graph");
|
||||
|
||||
/// <summary>
|
||||
/// "Whether a graph displaying difficulty throughout the beatmap should be shown"
|
||||
/// </summary>
|
||||
public static LocalisableString ShowGraphDescription => new TranslatableString(getKey(@"show_graph_description"), "Whether a graph displaying difficulty throughout the beatmap should be shown");
|
||||
|
||||
private static string getKey(string key) => $"{prefix}:{key}";
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Localisation;
|
||||
|
||||
namespace osu.Game.Localisation.SkinComponents
|
||||
{
|
||||
public static class BeatmapAttributeTextStrings
|
||||
{
|
||||
private const string prefix = @"osu.Game.Resources.Localisation.SkinComponents.BeatmapAttributeText";
|
||||
|
||||
/// <summary>
|
||||
/// "Attribute"
|
||||
/// </summary>
|
||||
public static LocalisableString Attribute => new TranslatableString(getKey(@"attribute"), "Attribute");
|
||||
|
||||
/// <summary>
|
||||
/// "The attribute to be displayed."
|
||||
/// </summary>
|
||||
public static LocalisableString AttributeDescription => new TranslatableString(getKey(@"attribute_description"), "The attribute to be displayed.");
|
||||
|
||||
/// <summary>
|
||||
/// "Template"
|
||||
/// </summary>
|
||||
public static LocalisableString Template => new TranslatableString(getKey(@"template"), "Template");
|
||||
|
||||
/// <summary>
|
||||
/// "Supports {{Label}} and {{Value}}, but also including arbitrary attributes like {{StarRating}} (see attribute list for supported values)."
|
||||
/// </summary>
|
||||
public static LocalisableString TemplateDescription => new TranslatableString(getKey(@"template_description"), @"Supports {{Label}} and {{Value}}, but also including arbitrary attributes like {{StarRating}} (see attribute list for supported values).");
|
||||
|
||||
private static string getKey(string key) => $"{prefix}:{key}";
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Localisation;
|
||||
|
||||
namespace osu.Game.Localisation.SkinComponents
|
||||
{
|
||||
public static class SkinnableComponentStrings
|
||||
{
|
||||
private const string prefix = @"osu.Game.Resources.Localisation.SkinComponents.SkinnableComponentStrings";
|
||||
|
||||
/// <summary>
|
||||
/// "Sprite name"
|
||||
/// </summary>
|
||||
public static LocalisableString SpriteName => new TranslatableString(getKey(@"sprite_name"), "Sprite name");
|
||||
|
||||
/// <summary>
|
||||
/// "The filename of the sprite"
|
||||
/// </summary>
|
||||
public static LocalisableString SpriteNameDescription => new TranslatableString(getKey(@"sprite_name_description"), "The filename of the sprite");
|
||||
|
||||
/// <summary>
|
||||
/// "Font"
|
||||
/// </summary>
|
||||
public static LocalisableString Font => new TranslatableString(getKey(@"font"), "Font");
|
||||
|
||||
/// <summary>
|
||||
/// "The font to use."
|
||||
/// </summary>
|
||||
public static LocalisableString FontDescription => new TranslatableString(getKey(@"font_description"), "The font to use.");
|
||||
|
||||
/// <summary>
|
||||
/// "Text"
|
||||
/// </summary>
|
||||
public static LocalisableString TextElementText => new TranslatableString(getKey(@"text_element_text"), "Text");
|
||||
|
||||
/// <summary>
|
||||
/// "The text to be displayed."
|
||||
/// </summary>
|
||||
public static LocalisableString TextElementTextDescription => new TranslatableString(getKey(@"text_element_text_description"), "The text to be displayed.");
|
||||
|
||||
private static string getKey(string key) => $"{prefix}:{key}";
|
||||
}
|
||||
}
|
@ -39,6 +39,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString Settings(string arg0) => new TranslatableString(getKey(@"settings"), @"Settings ({0})", arg0);
|
||||
|
||||
/// <summary>
|
||||
/// "Currently editing"
|
||||
/// </summary>
|
||||
public static LocalisableString CurrentlyEditing => new TranslatableString(getKey(@"currently_editing"), "Currently editing");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -36,21 +36,24 @@ namespace osu.Game.Overlays.Profile
|
||||
AutoSizeAxes = Axes.Y;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 10;
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Offset = new Vector2(0, 1),
|
||||
Radius = 3,
|
||||
Colour = Colour4.Black.Opacity(0.25f)
|
||||
};
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
background = new Box
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 10,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Offset = new Vector2(0, 1),
|
||||
Radius = 3,
|
||||
Colour = Colour4.Black.Opacity(0.25f)
|
||||
},
|
||||
Child = background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -40,7 +40,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(2)
|
||||
Spacing = new Vector2(EditorSidebar.PADDING)
|
||||
};
|
||||
|
||||
reloadComponents();
|
||||
@ -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; }
|
||||
|
||||
@ -125,12 +131,20 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
Items = new[]
|
||||
{
|
||||
new EditorMenuItem(Resources.Localisation.Web.CommonStrings.ButtonsSave, MenuItemType.Standard, Save),
|
||||
new EditorMenuItem(Resources.Localisation.Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()),
|
||||
new EditorMenuItem(CommonStrings.RevertToDefault, MenuItemType.Destructive, revert),
|
||||
new EditorMenuItemSpacer(),
|
||||
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)
|
||||
@ -256,13 +284,13 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
|
||||
headerText.AddParagraph(SkinEditorStrings.SkinEditor, cp => cp.Font = OsuFont.Default.With(size: 16));
|
||||
headerText.NewParagraph();
|
||||
headerText.AddText("Currently editing ", cp =>
|
||||
headerText.AddText(SkinEditorStrings.CurrentlyEditing, cp =>
|
||||
{
|
||||
cp.Font = OsuFont.Default.With(size: 12);
|
||||
cp.Colour = colours.Yellow;
|
||||
});
|
||||
|
||||
headerText.AddText($"{currentSkin.Value.SkinInfo}", cp =>
|
||||
headerText.AddText($" {currentSkin.Value.SkinInfo}", cp =>
|
||||
{
|
||||
cp.Font = OsuFont.Default.With(size: 12, weight: FontWeight.Bold);
|
||||
cp.Colour = colours.Yellow;
|
||||
@ -333,7 +361,11 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
protected void Undo() => changeHandler?.RestoreState(-1);
|
||||
|
||||
protected void Redo() => changeHandler?.RestoreState(1);
|
||||
|
||||
public void Save(bool userTriggered = true)
|
||||
{
|
||||
if (!hasBegunMutating)
|
||||
return;
|
||||
@ -343,8 +375,9 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
foreach (var t in targetContainers)
|
||||
currentSkin.Value.UpdateDrawableTarget(t);
|
||||
|
||||
skins.Save(skins.CurrentSkin.Value);
|
||||
onScreenDisplay?.Display(new SkinEditorToast(ToastStrings.SkinSaved, currentSkin.Value.SkinInfo.ToString() ?? "Unknown"));
|
||||
// In the case the save was user triggered, always show the save message to make them feel confident.
|
||||
if (skins.Save(skins.CurrentSkin.Value) || userTriggered)
|
||||
onScreenDisplay?.Display(new SkinEditorToast(ToastStrings.SkinSaved, currentSkin.Value.SkinInfo.ToString() ?? "Unknown"));
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e) => true;
|
||||
@ -435,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++]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -147,7 +147,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
|
||||
if (skinEditor == null) return;
|
||||
|
||||
skinEditor.Save();
|
||||
skinEditor.Save(userTriggered: false);
|
||||
|
||||
// ensure the toolbar is re-hidden even if a new screen decides to try and show it.
|
||||
updateComponentVisibility();
|
||||
|
@ -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)
|
||||
|
@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
/// </summary>
|
||||
protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.ReceivePositionalInputAt(screenSpacePos) ?? false;
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.ReceivePositionalInputAt(screenSpacePos) == true;
|
||||
|
||||
protected override bool Handle(UIEvent e)
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user