1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-22 16:32:59 +08:00

Merge branch 'master' into localisable-description

This commit is contained in:
Dean Herbert 2021-08-03 18:29:52 +09:00
commit 8dbcccc350
78 changed files with 1007 additions and 376 deletions

View File

@ -190,3 +190,5 @@ dotnet_diagnostic.CA2225.severity = none
# Banned APIs # Banned APIs
dotnet_diagnostic.RS0030.severity = error dotnet_diagnostic.RS0030.severity = error
dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text.

View File

@ -51,7 +51,7 @@
<Reference Include="Java.Interop" /> <Reference Include="Java.Interop" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.722.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.803.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.728.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2021.728.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Transitive Dependencies"> <ItemGroup Label="Transitive Dependencies">

View File

@ -64,7 +64,7 @@
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Xamarin.Essentials" Version="1.6.1" /> <PackageReference Include="Xamarin.Essentials" Version="1.7.0" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
</Project> </Project>

View File

@ -109,15 +109,23 @@ namespace osu.Game.Rulesets.Catch.UI
comboDisplay.X = Catcher.X; comboDisplay.X = Catcher.X;
if (!lastHyperDashState && Catcher.HyperDashing && Time.Elapsed > 0) if (Time.Elapsed <= 0)
catcherTrails.DisplayHyperDashAfterImage(Catcher.CurrentState, Catcher.X, Catcher.BodyScale); {
// This is probably a wrong value, but currently the true value is not recorded.
// Setting `true` will prevent generation of false-positive after-images (with more false-negatives).
lastHyperDashState = true;
return;
}
if (!lastHyperDashState && Catcher.HyperDashing)
displayCatcherTrail(CatcherTrailAnimation.HyperDashAfterImage);
if (Catcher.Dashing || Catcher.HyperDashing) if (Catcher.Dashing || Catcher.HyperDashing)
{ {
double generationInterval = Catcher.HyperDashing ? 25 : 50; double generationInterval = Catcher.HyperDashing ? 25 : 50;
if (Time.Current - catcherTrails.LastDashTrailTime >= generationInterval) if (Time.Current - catcherTrails.LastDashTrailTime >= generationInterval)
catcherTrails.DisplayDashTrail(Catcher.CurrentState, Catcher.X, Catcher.BodyScale, Catcher.HyperDashing); displayCatcherTrail(Catcher.HyperDashing ? CatcherTrailAnimation.HyperDashing : CatcherTrailAnimation.Dashing);
} }
lastHyperDashState = Catcher.HyperDashing; lastHyperDashState = Catcher.HyperDashing;
@ -173,5 +181,7 @@ namespace osu.Game.Rulesets.Catch.UI
break; break;
} }
} }
private void displayCatcherTrail(CatcherTrailAnimation animation) => catcherTrails.Add(new CatcherTrailEntry(Time.Current, Catcher.CurrentState, Catcher.X, Catcher.BodyScale, animation));
} }
} }

View File

@ -2,8 +2,8 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Rulesets.Objects.Pooling;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Catch.UI namespace osu.Game.Rulesets.Catch.UI
@ -12,13 +12,8 @@ namespace osu.Game.Rulesets.Catch.UI
/// A trail of the catcher. /// A trail of the catcher.
/// It also represents a hyper dash afterimage. /// It also represents a hyper dash afterimage.
/// </summary> /// </summary>
public class CatcherTrail : PoolableDrawable public class CatcherTrail : PoolableDrawableWithLifetime<CatcherTrailEntry>
{ {
public CatcherAnimationState AnimationState
{
set => body.AnimationState.Value = value;
}
private readonly SkinnableCatcher body; private readonly SkinnableCatcher body;
public CatcherTrail() public CatcherTrail()
@ -34,11 +29,40 @@ namespace osu.Game.Rulesets.Catch.UI
}; };
} }
protected override void FreeAfterUse() protected override void OnApply(CatcherTrailEntry entry)
{ {
Position = new Vector2(entry.Position, 0);
Scale = entry.Scale;
body.AnimationState.Value = entry.CatcherState;
using (BeginAbsoluteSequence(entry.LifetimeStart, false))
applyTransforms(entry.Animation);
}
protected override void OnFree(CatcherTrailEntry entry)
{
ApplyTransformsAt(double.MinValue);
ClearTransforms(); ClearTransforms();
Alpha = 1; }
base.FreeAfterUse();
private void applyTransforms(CatcherTrailAnimation animation)
{
switch (animation)
{
case CatcherTrailAnimation.Dashing:
case CatcherTrailAnimation.HyperDashing:
this.FadeTo(0.4f).FadeOut(800, Easing.OutQuint);
break;
case CatcherTrailAnimation.HyperDashAfterImage:
this.MoveToOffset(new Vector2(0, -10), 1200, Easing.In);
this.ScaleTo(Scale * 0.95f).ScaleTo(Scale * 1.2f, 1200, Easing.In);
this.FadeOut(1200);
break;
}
Expire();
} }
} }
} }

View File

@ -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.Catch.UI
{
public enum CatcherTrailAnimation
{
Dashing,
HyperDashing,
HyperDashAfterImage
}
}

View File

@ -2,12 +2,13 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Pooling;
using osu.Game.Rulesets.Catch.Skinning; using osu.Game.Rulesets.Catch.Skinning;
using osu.Game.Rulesets.Objects.Pooling;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.UI namespace osu.Game.Rulesets.Catch.UI
@ -16,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.UI
/// Represents a component responsible for displaying /// Represents a component responsible for displaying
/// the appropriate catcher trails when requested to. /// the appropriate catcher trails when requested to.
/// </summary> /// </summary>
public class CatcherTrailDisplay : SkinReloadableDrawable public class CatcherTrailDisplay : PooledDrawableWithLifetimeContainer<CatcherTrailEntry, CatcherTrail>
{ {
/// <summary> /// <summary>
/// The most recent time a dash trail was added to this container. /// The most recent time a dash trail was added to this container.
@ -29,12 +30,17 @@ namespace osu.Game.Rulesets.Catch.UI
public Color4 HyperDashAfterImageColour => hyperDashAfterImages.Colour; public Color4 HyperDashAfterImageColour => hyperDashAfterImages.Colour;
protected override bool RemoveRewoundEntry => true;
private readonly DrawablePool<CatcherTrail> trailPool; private readonly DrawablePool<CatcherTrail> trailPool;
private readonly Container<CatcherTrail> dashTrails; private readonly Container<CatcherTrail> dashTrails;
private readonly Container<CatcherTrail> hyperDashTrails; private readonly Container<CatcherTrail> hyperDashTrails;
private readonly Container<CatcherTrail> hyperDashAfterImages; private readonly Container<CatcherTrail> hyperDashAfterImages;
[Resolved]
private ISkinSource skin { get; set; }
public CatcherTrailDisplay() public CatcherTrailDisplay()
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
@ -48,50 +54,60 @@ namespace osu.Game.Rulesets.Catch.UI
}; };
} }
protected override void SkinChanged(ISkinSource skin) protected override void LoadComplete()
{ {
base.SkinChanged(skin); base.LoadComplete();
skin.SourceChanged += skinSourceChanged;
skinSourceChanged();
}
private void skinSourceChanged()
{
hyperDashTrails.Colour = skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value ?? Catcher.DEFAULT_HYPER_DASH_COLOUR; hyperDashTrails.Colour = skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value ?? Catcher.DEFAULT_HYPER_DASH_COLOUR;
hyperDashAfterImages.Colour = skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashAfterImage)?.Value ?? hyperDashTrails.Colour; hyperDashAfterImages.Colour = skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashAfterImage)?.Value ?? hyperDashTrails.Colour;
} }
/// <summary> protected override void AddDrawable(CatcherTrailEntry entry, CatcherTrail drawable)
/// Displays a hyper-dash after-image of the catcher.
/// </summary>
public void DisplayHyperDashAfterImage(CatcherAnimationState animationState, float x, Vector2 scale)
{ {
var trail = createTrail(animationState, x, scale); switch (entry.Animation)
{
case CatcherTrailAnimation.Dashing:
dashTrails.Add(drawable);
break;
hyperDashAfterImages.Add(trail); case CatcherTrailAnimation.HyperDashing:
hyperDashTrails.Add(drawable);
break;
trail.MoveToOffset(new Vector2(0, -10), 1200, Easing.In); case CatcherTrailAnimation.HyperDashAfterImage:
trail.ScaleTo(trail.Scale * 0.95f).ScaleTo(trail.Scale * 1.2f, 1200, Easing.In); hyperDashAfterImages.Add(drawable);
trail.FadeOut(1200); break;
trail.Expire(true); }
} }
public void DisplayDashTrail(CatcherAnimationState animationState, float x, Vector2 scale, bool hyperDashing) protected override void RemoveDrawable(CatcherTrailEntry entry, CatcherTrail drawable)
{ {
var trail = createTrail(animationState, x, scale); switch (entry.Animation)
{
case CatcherTrailAnimation.Dashing:
dashTrails.Remove(drawable);
break;
if (hyperDashing) case CatcherTrailAnimation.HyperDashing:
hyperDashTrails.Add(trail); hyperDashTrails.Remove(drawable);
else break;
dashTrails.Add(trail);
trail.FadeTo(0.4f).FadeOut(800, Easing.OutQuint); case CatcherTrailAnimation.HyperDashAfterImage:
trail.Expire(true); hyperDashAfterImages.Remove(drawable);
break;
}
} }
private CatcherTrail createTrail(CatcherAnimationState animationState, float x, Vector2 scale) protected override CatcherTrail GetDrawable(CatcherTrailEntry entry)
{ {
CatcherTrail trail = trailPool.Get(); CatcherTrail trail = trailPool.Get();
trail.Apply(entry);
trail.AnimationState = animationState;
trail.Scale = scale;
trail.Position = new Vector2(x, 0);
return trail; return trail;
} }
@ -107,5 +123,13 @@ namespace osu.Game.Rulesets.Catch.UI
return maxTime; return maxTime;
} }
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (skin != null)
skin.SourceChanged -= skinSourceChanged;
}
} }
} }

View File

@ -0,0 +1,31 @@
// 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.Performance;
using osuTK;
namespace osu.Game.Rulesets.Catch.UI
{
public class CatcherTrailEntry : LifetimeEntry
{
public readonly CatcherAnimationState CatcherState;
public readonly float Position;
/// <summary>
/// The scaling of the catcher body. It also represents a flipped catcher (negative x component).
/// </summary>
public readonly Vector2 Scale;
public readonly CatcherTrailAnimation Animation;
public CatcherTrailEntry(double startTime, CatcherAnimationState catcherState, float position, Vector2 scale, CatcherTrailAnimation animation)
{
LifetimeStart = startTime;
CatcherState = catcherState;
Position = position;
Scale = scale;
Animation = animation;
}
}
}

View File

@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.UI
private void onJudgementLoaded(DrawableOsuJudgement judgement) private void onJudgementLoaded(DrawableOsuJudgement judgement)
{ {
judgementAboveHitObjectLayer.Add(judgement.GetProxyAboveHitObjectsContent()); judgementAboveHitObjectLayer.Add(judgement.ProxiedAboveHitObjectsContent);
} }
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
@ -150,6 +150,10 @@ namespace osu.Game.Rulesets.Osu.UI
DrawableOsuJudgement explosion = poolDictionary[result.Type].Get(doj => doj.Apply(result, judgedObject)); DrawableOsuJudgement explosion = poolDictionary[result.Type].Get(doj => doj.Apply(result, judgedObject));
judgementLayer.Add(explosion); judgementLayer.Add(explosion);
// the proxied content is added to judgementAboveHitObjectLayer once, on first load, and never removed from it.
// ensure that ordering is consistent with expectations (latest judgement should be front-most).
judgementAboveHitObjectLayer.ChangeChildDepth(explosion.ProxiedAboveHitObjectsContent, (float)-result.TimeAbsolute);
} }
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos);

View File

@ -2,10 +2,30 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Taiko.Mods namespace osu.Game.Rulesets.Taiko.Mods
{ {
public class TaikoModClassic : ModClassic public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset<TaikoHitObject>, IUpdatableByPlayfield
{ {
private DrawableTaikoRuleset drawableTaikoRuleset;
public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)
{
drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset;
}
public void Update(Playfield playfield)
{
// Classic taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened.
const float scroll_rate = 10;
// Since the time range will depend on a positional value, it is referenced to the x480 pixel space.
float ratio = drawableTaikoRuleset.DrawHeight / 480;
drawableTaikoRuleset.TimeRange.Value = (playfield.HitObjectContainer.DrawWidth / ratio) * scroll_rate;
}
} }
} }

View File

@ -12,23 +12,11 @@ using osu.Game.Rulesets.Taiko.Objects.Drawables;
namespace osu.Game.Rulesets.Taiko.Mods namespace osu.Game.Rulesets.Taiko.Mods
{ {
public class TaikoModHidden : ModHidden, IApplicableToDifficulty public class TaikoModHidden : ModHidden
{ {
public override string Description => @"Beats fade out before you hit them!"; public override string Description => @"Beats fade out before you hit them!";
public override double ScoreMultiplier => 1.06; public override double ScoreMultiplier => 1.06;
/// <summary>
/// In osu-stable, the hit position is 160, so the active playfield is essentially 160 pixels shorter
/// than the actual screen width. The normalized playfield height is 480, so on a 4:3 screen the
/// playfield ratio of the active area up to the hit position will actually be (640 - 160) / 480 = 1.
/// For custom resolutions/aspect ratios (x:y), the screen width given the normalized height becomes 480 * x / y instead,
/// and the playfield ratio becomes (480 * x / y - 160) / 480 = x / y - 1/3.
/// This constant is equal to the playfield ratio on 4:3 screens divided by the playfield ratio on 16:9 screens.
/// </summary>
private const double hd_sv_scale = (4.0 / 3.0 - 1.0 / 3.0) / (16.0 / 9.0 - 1.0 / 3.0);
private double originalSliderMultiplier;
private ControlPointInfo controlPointInfo; private ControlPointInfo controlPointInfo;
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
@ -41,7 +29,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
double beatLength = controlPointInfo.TimingPointAt(position).BeatLength; double beatLength = controlPointInfo.TimingPointAt(position).BeatLength;
double speedMultiplier = controlPointInfo.DifficultyPointAt(position).SpeedMultiplier; double speedMultiplier = controlPointInfo.DifficultyPointAt(position).SpeedMultiplier;
return originalSliderMultiplier * speedMultiplier * TimingControlPoint.DEFAULT_BEAT_LENGTH / beatLength; return speedMultiplier * TimingControlPoint.DEFAULT_BEAT_LENGTH / beatLength;
} }
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
@ -69,22 +57,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
} }
} }
public void ReadFromDifficulty(BeatmapDifficulty difficulty)
{
}
public void ApplyToDifficulty(BeatmapDifficulty difficulty)
{
// needs to be read after all processing has been run (TaikoBeatmapConverter applies an adjustment which would otherwise be omitted).
originalSliderMultiplier = difficulty.SliderMultiplier;
// osu-stable has an added playfield cover that essentially forces a 4:3 playfield ratio, by cutting off all objects past that size.
// This is not yet implemented; instead a playfield adjustment container is present which maintains a 16:9 ratio.
// For now, increase the slider multiplier proportionally so that the notes stay on the screen for the same amount of time as on stable.
// Note that this means that the notes will scroll faster as they have a longer distance to travel on the screen in that same amount of time.
difficulty.SliderMultiplier /= hd_sv_scale;
}
public override void ApplyToBeatmap(IBeatmap beatmap) public override void ApplyToBeatmap(IBeatmap beatmap)
{ {
controlPointInfo = beatmap.ControlPointInfo; controlPointInfo = beatmap.ControlPointInfo;

View File

@ -3,6 +3,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
@ -24,12 +25,14 @@ namespace osu.Game.Rulesets.Taiko.UI
{ {
public class DrawableTaikoRuleset : DrawableScrollingRuleset<TaikoHitObject> public class DrawableTaikoRuleset : DrawableScrollingRuleset<TaikoHitObject>
{ {
private SkinnableDrawable scroller; public new BindableDouble TimeRange => base.TimeRange;
protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping; protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping;
protected override bool UserScrollSpeedAdjustment => false; protected override bool UserScrollSpeedAdjustment => false;
private SkinnableDrawable scroller;
public DrawableTaikoRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null) public DrawableTaikoRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
: base(ruleset, beatmap, mods) : base(ruleset, beatmap, mods)
{ {

View File

@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Taiko.UI
/// <summary> /// <summary>
/// Default height of a <see cref="TaikoPlayfield"/> when inside a <see cref="DrawableTaikoRuleset"/>. /// Default height of a <see cref="TaikoPlayfield"/> when inside a <see cref="DrawableTaikoRuleset"/>.
/// </summary> /// </summary>
public const float DEFAULT_HEIGHT = 178; public const float DEFAULT_HEIGHT = 212;
private Container<HitExplosion> hitExplosionContainer; private Container<HitExplosion> hitExplosionContainer;
private Container<KiaiHitExplosion> kiaiExplosionContainer; private Container<KiaiHitExplosion> kiaiExplosionContainer;

View File

@ -0,0 +1,41 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using NUnit.Framework;
using osu.Game.Extensions;
namespace osu.Game.Tests.NonVisual
{
[TestFixture]
public class TimeDisplayExtensionTest
{
private static readonly object[][] editor_formatted_duration_tests =
{
new object[] { new TimeSpan(0, 0, 0, 0, 50), "00:00:050" },
new object[] { new TimeSpan(0, 0, 0, 10, 50), "00:10:050" },
new object[] { new TimeSpan(0, 0, 5, 10), "05:10:000" },
new object[] { new TimeSpan(0, 1, 5, 10), "65:10:000" },
};
[TestCaseSource(nameof(editor_formatted_duration_tests))]
public void TestEditorFormat(TimeSpan input, string expectedOutput)
{
Assert.AreEqual(expectedOutput, input.ToEditorFormattedString());
}
private static readonly object[][] formatted_duration_tests =
{
new object[] { new TimeSpan(0, 0, 10), "00:10" },
new object[] { new TimeSpan(0, 5, 10), "05:10" },
new object[] { new TimeSpan(1, 5, 10), "01:05:10" },
new object[] { new TimeSpan(1, 1, 5, 10), "01:01:05:10" },
};
[TestCaseSource(nameof(formatted_duration_tests))]
public void TestFormattedDuration(TimeSpan input, string expectedOutput)
{
Assert.AreEqual(expectedOutput, input.ToFormattedDuration().ToString());
}
}
}

View File

@ -168,8 +168,8 @@ namespace osu.Game.Tests.Online
public override async Task<BeatmapSetInfo> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) public override async Task<BeatmapSetInfo> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
{ {
await AllowImport.Task; await AllowImport.Task.ConfigureAwait(false);
return await (CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken)); return await (CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken)).ConfigureAwait(false);
} }
} }

View File

@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Online
[Test] [Test]
public void TestControl() public void TestControl()
{ {
AddAssert("Front page selected", () => header.Current.Value == "frontpage"); AddAssert("Front page selected", () => header.Current.Value == NewsHeader.FrontPageString);
AddAssert("1 tab total", () => header.TabCount == 1); AddAssert("1 tab total", () => header.TabCount == 1);
AddStep("Set article 1", () => header.SetArticle("1")); AddStep("Set article 1", () => header.SetArticle("1"));
@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.Online
AddAssert("2 tabs total", () => header.TabCount == 2); AddAssert("2 tabs total", () => header.TabCount == 2);
AddStep("Set front page", () => header.SetFrontPage()); AddStep("Set front page", () => header.SetFrontPage());
AddAssert("Front page selected", () => header.Current.Value == "frontpage"); AddAssert("Front page selected", () => header.Current.Value == NewsHeader.FrontPageString);
AddAssert("1 tab total", () => header.TabCount == 1); AddAssert("1 tab total", () => header.TabCount == 1);
} }

View File

@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Online
[Test] [Test]
public void TestWikiHeader() public void TestWikiHeader()
{ {
AddAssert("Current is index", () => checkCurrent("index")); AddAssert("Current is index", () => checkCurrent(WikiHeader.IndexPageString));
AddStep("Change wiki page data", () => wikiPageData.Value = new APIWikiPage AddStep("Change wiki page data", () => wikiPageData.Value = new APIWikiPage
{ {
@ -54,8 +54,8 @@ namespace osu.Game.Tests.Visual.Online
AddAssert("Current is welcome", () => checkCurrent("Welcome")); AddAssert("Current is welcome", () => checkCurrent("Welcome"));
AddAssert("Check breadcrumb", checkBreadcrumb); AddAssert("Check breadcrumb", checkBreadcrumb);
AddStep("Change current to index", () => header.Current.Value = "index"); AddStep("Change current to index", () => header.Current.Value = WikiHeader.IndexPageString);
AddAssert("Current is index", () => checkCurrent("index")); AddAssert("Current is index", () => checkCurrent(WikiHeader.IndexPageString));
AddStep("Change wiki page data", () => wikiPageData.Value = new APIWikiPage AddStep("Change wiki page data", () => wikiPageData.Value = new APIWikiPage
{ {
@ -71,7 +71,7 @@ namespace osu.Game.Tests.Visual.Online
AddAssert("Check breadcrumb", checkBreadcrumb); AddAssert("Check breadcrumb", checkBreadcrumb);
} }
private bool checkCurrent(string expectedCurrent) => header.Current.Value == expectedCurrent; private bool checkCurrent(LocalisableString expectedCurrent) => header.Current.Value == expectedCurrent;
private bool checkBreadcrumb() private bool checkBreadcrumb()
{ {

View File

@ -143,9 +143,9 @@ namespace osu.Game.Tests.Visual.SongSelect
public override async Task<StarDifficulty> GetDifficultyAsync(BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo = null, IEnumerable<Mod> mods = null, CancellationToken cancellationToken = default) public override async Task<StarDifficulty> GetDifficultyAsync(BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo = null, IEnumerable<Mod> mods = null, CancellationToken cancellationToken = default)
{ {
if (blockCalculation) if (blockCalculation)
await calculationBlocker.Task; await calculationBlocker.Task.ConfigureAwait(false);
return await base.GetDifficultyAsync(beatmapInfo, rulesetInfo, mods, cancellationToken); return await base.GetDifficultyAsync(beatmapInfo, rulesetInfo, mods, cancellationToken).ConfigureAwait(false);
} }
} }
} }

View File

@ -1,16 +1,21 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterfaceV2;
using osuTK.Graphics; using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface namespace osu.Game.Tests.Visual.UserInterface
{ {
public class TestSceneLabelledColourPalette : OsuTestScene public class TestSceneLabelledColourPalette : OsuManualInputManagerTestScene
{ {
private LabelledColourPalette component; private LabelledColourPalette component;
@ -30,21 +35,41 @@ namespace osu.Game.Tests.Visual.UserInterface
}, 8); }, 8);
} }
[Test]
public void TestUserInteractions()
{
createColourPalette();
assertColourCount(4);
clickAddColour();
assertColourCount(5);
deleteFirstColour();
assertColourCount(4);
clickFirstColour();
AddAssert("colour picker spawned", () => this.ChildrenOfType<OsuColourPicker>().Any());
}
private void createColourPalette(bool hasDescription = false) private void createColourPalette(bool hasDescription = false)
{ {
AddStep("create component", () => AddStep("create component", () =>
{ {
Child = new Container Child = new OsuContextMenuContainer
{ {
Anchor = Anchor.Centre, RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre, Child = new Container
Width = 500,
AutoSizeAxes = Axes.Y,
Child = component = new LabelledColourPalette
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
ColourNamePrefix = "My colour #" Width = 500,
AutoSizeAxes = Axes.Y,
Child = component = new LabelledColourPalette
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
ColourNamePrefix = "My colour #"
}
} }
}; };
@ -66,5 +91,36 @@ namespace osu.Game.Tests.Visual.UserInterface
RNG.NextSingle(), RNG.NextSingle(),
RNG.NextSingle(), RNG.NextSingle(),
1); 1);
private void assertColourCount(int count) => AddAssert($"colour count is {count}", () => component.Colours.Count == count);
private void clickAddColour() => AddStep("click new colour button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<ColourPalette.AddColourButton>().Single());
InputManager.Click(MouseButton.Left);
});
private void clickFirstColour() => AddStep("click first colour", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<ColourDisplay>().First());
InputManager.Click(MouseButton.Left);
});
private void deleteFirstColour()
{
AddStep("right-click first colour", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<ColourDisplay>().First());
InputManager.Click(MouseButton.Right);
});
AddUntilStep("wait for menu", () => this.ChildrenOfType<OsuContextMenu>().Any());
AddStep("click delete", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<DrawableOsuMenuItem>().Single());
InputManager.Click(MouseButton.Left);
});
}
} }
} }

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -11,6 +10,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Legacy;
using osu.Game.Extensions;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
@ -198,8 +198,8 @@ namespace osu.Game.Tournament.Components
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Children = new Drawable[] Children = new Drawable[]
{ {
new DiffPiece(("Length", TimeSpan.FromMilliseconds(length).ToString(@"mm\:ss"))), new DiffPiece(("Length", length.ToFormattedDuration().ToString())),
new DiffPiece(("BPM", $"{bpm:0.#}")) new DiffPiece(("BPM", $"{bpm:0.#}")),
} }
}, },
new Container new Container

View File

@ -1,2 +1,3 @@
[*.cs] [*.cs]
dotnet_diagnostic.OLOC001.prefix_namespace = osu.Game.Resources.Localisation dotnet_diagnostic.OLOC001.prefix_namespace = osu.Game.Resources.Localisation
dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text.

View File

@ -1,26 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
namespace osu.Game.Extensions
{
public static class EditorDisplayExtensions
{
/// <summary>
/// Get an editor formatted string (mm:ss:mss)
/// </summary>
/// <param name="milliseconds">A time value in milliseconds.</param>
/// <returns>An editor formatted display string.</returns>
public static string ToEditorFormattedString(this double milliseconds) =>
ToEditorFormattedString(TimeSpan.FromMilliseconds(milliseconds));
/// <summary>
/// Get an editor formatted string (mm:ss:mss)
/// </summary>
/// <param name="timeSpan">A time value.</param>
/// <returns>An editor formatted display string.</returns>
public static string ToEditorFormattedString(this TimeSpan timeSpan) =>
$"{(timeSpan < TimeSpan.Zero ? "-" : string.Empty)}{timeSpan:mm\\:ss\\:fff}";
}
}

View File

@ -0,0 +1,51 @@
// 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.Localisation;
namespace osu.Game.Extensions
{
public static class TimeDisplayExtensions
{
/// <summary>
/// Get an editor formatted string (mm:ss:mss)
/// </summary>
/// <param name="milliseconds">A time value in milliseconds.</param>
/// <returns>An editor formatted display string.</returns>
public static string ToEditorFormattedString(this double milliseconds) =>
ToEditorFormattedString(TimeSpan.FromMilliseconds(milliseconds));
/// <summary>
/// Get an editor formatted string (mm:ss:mss)
/// </summary>
/// <param name="timeSpan">A time value.</param>
/// <returns>An editor formatted display string.</returns>
public static string ToEditorFormattedString(this TimeSpan timeSpan) =>
$"{(timeSpan < TimeSpan.Zero ? "-" : string.Empty)}{(int)timeSpan.TotalMinutes:00}:{timeSpan:ss\\:fff}";
/// <summary>
/// Get a formatted duration (dd:hh:mm:ss with days/hours omitted if zero).
/// </summary>
/// <param name="milliseconds">A duration in milliseconds.</param>
/// <returns>A formatted duration string.</returns>
public static LocalisableString ToFormattedDuration(this double milliseconds) =>
ToFormattedDuration(TimeSpan.FromMilliseconds(milliseconds));
/// <summary>
/// Get a formatted duration (dd:hh:mm:ss with days/hours omitted if zero).
/// </summary>
/// <param name="timeSpan">A duration value.</param>
/// <returns>A formatted duration string.</returns>
public static LocalisableString ToFormattedDuration(this TimeSpan timeSpan)
{
if (timeSpan.TotalDays >= 1)
return new LocalisableFormattableString(timeSpan, @"dd\:hh\:mm\:ss");
if (timeSpan.TotalHours >= 1)
return new LocalisableFormattableString(timeSpan, @"hh\:mm\:ss");
return new LocalisableFormattableString(timeSpan, @"mm\:ss");
}
}
}

View File

@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -59,33 +60,37 @@ namespace osu.Game.Graphics.Containers
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
InternalChild = new GridContainer InternalChildren = new Drawable[]
{ {
RelativeSizeAxes = Axes.X, new GridContainer
AutoSizeAxes = Axes.Y,
Content = new[]
{ {
new[] RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Content = new[]
{ {
handleContainer = new Container new[]
{ {
Anchor = Anchor.Centre, handleContainer = new Container
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = 5 },
Child = handle = new PlaylistItemHandle
{ {
Size = new Vector2(12), Anchor = Anchor.Centre,
Colour = HandleColour, Origin = Anchor.Centre,
AlwaysPresent = true, AutoSizeAxes = Axes.Both,
Alpha = 0 Padding = new MarginPadding { Horizontal = 5 },
} Child = handle = new PlaylistItemHandle
}, {
CreateContent() Size = new Vector2(12),
} Colour = HandleColour,
AlwaysPresent = true,
Alpha = 0
}
},
CreateContent()
}
},
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
}, },
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, new HoverClickSounds()
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
}; };
} }

View File

@ -20,7 +20,7 @@ namespace osu.Game.Graphics.UserInterface
{ {
Size = TwoLayerButton.SIZE_EXTENDED; Size = TwoLayerButton.SIZE_EXTENDED;
Child = button = new TwoLayerButton Child = button = new TwoLayerButton(HoverSampleSet.Submit)
{ {
Anchor = Anchor.TopLeft, Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft, Origin = Anchor.TopLeft,

View File

@ -56,6 +56,7 @@ namespace osu.Game.Graphics.UserInterface
private Vector2 hoverSpacing => new Vector2(3f, 0f); private Vector2 hoverSpacing => new Vector2(3f, 0f);
public DialogButton() public DialogButton()
: base(HoverSampleSet.Submit)
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;

View File

@ -23,14 +23,20 @@ namespace osu.Game.Graphics.UserInterface
[Resolved] [Resolved]
private GameHost host { get; set; } private GameHost host { get; set; }
private readonly SpriteIcon linkIcon;
public ExternalLinkButton(string link = null) public ExternalLinkButton(string link = null)
{ {
Link = link; Link = link;
Size = new Vector2(12); Size = new Vector2(12);
InternalChild = new SpriteIcon InternalChildren = new Drawable[]
{ {
Icon = FontAwesome.Solid.ExternalLinkAlt, linkIcon = new SpriteIcon
RelativeSizeAxes = Axes.Both {
Icon = FontAwesome.Solid.ExternalLinkAlt,
RelativeSizeAxes = Axes.Both
},
new HoverClickSounds(HoverSampleSet.Submit)
}; };
} }
@ -42,13 +48,13 @@ namespace osu.Game.Graphics.UserInterface
protected override bool OnHover(HoverEvent e) protected override bool OnHover(HoverEvent e)
{ {
InternalChild.FadeColour(hoverColour, 500, Easing.OutQuint); linkIcon.FadeColour(hoverColour, 500, Easing.OutQuint);
return base.OnHover(e); return base.OnHover(e);
} }
protected override void OnHoverLost(HoverLostEvent e) protected override void OnHoverLost(HoverLostEvent e)
{ {
InternalChild.FadeColour(Color4.White, 500, Easing.OutQuint); linkIcon.FadeColour(Color4.White, 500, Easing.OutQuint);
base.OnHoverLost(e); base.OnHoverLost(e);
} }

View File

@ -10,8 +10,8 @@ namespace osu.Game.Graphics.UserInterface
[Description("default")] [Description("default")]
Default, Default,
[Description("soft")] [Description("submit")]
Soft, Submit,
[Description("button")] [Description("button")]
Button, Button,

View File

@ -71,7 +71,8 @@ namespace osu.Game.Graphics.UserInterface
} }
} }
public TwoLayerButton() public TwoLayerButton(HoverSampleSet sampleSet = HoverSampleSet.Default)
: base(sampleSet)
{ {
Size = SIZE_RETRACTED; Size = SIZE_RETRACTED;
Shear = shear; Shear = shear;

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions; using osu.Framework.Extensions;
@ -12,6 +13,7 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osuTK; using osuTK;
namespace osu.Game.Graphics.UserInterfaceV2 namespace osu.Game.Graphics.UserInterfaceV2
@ -19,12 +21,16 @@ namespace osu.Game.Graphics.UserInterfaceV2
/// <summary> /// <summary>
/// A component which displays a colour along with related description text. /// A component which displays a colour along with related description text.
/// </summary> /// </summary>
public class ColourDisplay : CompositeDrawable, IHasCurrentValue<Colour4>, IHasPopover public class ColourDisplay : CompositeDrawable, IHasCurrentValue<Colour4>
{ {
/// <summary>
/// Invoked when the user has requested the colour corresponding to this <see cref="ColourDisplay"/>
/// to be removed from its palette.
/// </summary>
public event Action<ColourDisplay> DeleteRequested;
private readonly BindableWithCurrent<Colour4> current = new BindableWithCurrent<Colour4>(); private readonly BindableWithCurrent<Colour4> current = new BindableWithCurrent<Colour4>();
private Box fill;
private OsuSpriteText colourHexCode;
private OsuSpriteText colourName; private OsuSpriteText colourName;
public Bindable<Colour4> Current public Bindable<Colour4> Current
@ -63,26 +69,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
Spacing = new Vector2(0, 10), Spacing = new Vector2(0, 10),
Children = new Drawable[] Children = new Drawable[]
{ {
new OsuClickableContainer new ColourCircle
{ {
RelativeSizeAxes = Axes.X, Current = { BindTarget = Current },
Height = 100, DeleteRequested = () => DeleteRequested?.Invoke(this)
CornerRadius = 50,
Masking = true,
Children = new Drawable[]
{
fill = new Box
{
RelativeSizeAxes = Axes.Both
},
colourHexCode = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.Default.With(size: 12)
}
},
Action = this.ShowPopover
}, },
colourName = new OsuSpriteText colourName = new OsuSpriteText
{ {
@ -93,26 +83,64 @@ namespace osu.Game.Graphics.UserInterfaceV2
}; };
} }
protected override void LoadComplete() private class ColourCircle : OsuClickableContainer, IHasPopover, IHasContextMenu
{ {
base.LoadComplete(); public Bindable<Colour4> Current { get; } = new Bindable<Colour4>();
current.BindValueChanged(_ => updateColour(), true); public Action DeleteRequested { get; set; }
}
private void updateColour() private readonly Box fill;
{ private readonly OsuSpriteText colourHexCode;
fill.Colour = current.Value;
colourHexCode.Text = current.Value.ToHex();
colourHexCode.Colour = OsuColour.ForegroundTextColourFor(current.Value);
}
public Popover GetPopover() => new OsuPopover(false) public ColourCircle()
{
Child = new OsuColourPicker
{ {
Current = { BindTarget = Current } RelativeSizeAxes = Axes.X;
Height = 100;
CornerRadius = 50;
Masking = true;
Action = this.ShowPopover;
Children = new Drawable[]
{
fill = new Box
{
RelativeSizeAxes = Axes.Both
},
colourHexCode = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.Default.With(size: 12)
}
};
} }
};
protected override void LoadComplete()
{
base.LoadComplete();
Current.BindValueChanged(_ => updateColour(), true);
}
private void updateColour()
{
fill.Colour = Current.Value;
colourHexCode.Text = Current.Value.ToHex();
colourHexCode.Colour = OsuColour.ForegroundTextColourFor(Current.Value);
}
public Popover GetPopover() => new OsuPopover(false)
{
Child = new OsuColourPicker
{
Current = { BindTarget = Current }
}
};
public MenuItem[] ContextMenuItems => new MenuItem[]
{
new OsuMenuItem("Delete", MenuItemType.Destructive, () => DeleteRequested?.Invoke())
};
}
} }
} }

View File

@ -1,12 +1,17 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osuTK; using osuTK;
@ -36,36 +41,24 @@ namespace osu.Game.Graphics.UserInterfaceV2
} }
} }
private FillFlowContainer<ColourDisplay> palette; private FillFlowContainer palette;
private Container placeholder;
private IEnumerable<ColourDisplay> colourDisplays => palette.OfType<ColourDisplay>();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y; AutoSizeAxes = Axes.Y;
AutoSizeDuration = fade_duration;
AutoSizeEasing = Easing.OutQuint;
InternalChildren = new Drawable[] InternalChild = palette = new FillFlowContainer
{ {
palette = new FillFlowContainer<ColourDisplay> RelativeSizeAxes = Axes.X,
{ AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X, Spacing = new Vector2(10),
AutoSizeAxes = Axes.Y, Direction = FillDirection.Full
Spacing = new Vector2(10),
Direction = FillDirection.Full
},
placeholder = new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Child = new OsuSpriteText
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Text = "(none)",
Font = OsuFont.Default.With(weight: FontWeight.Bold)
}
}
}; };
} }
@ -73,30 +66,20 @@ namespace osu.Game.Graphics.UserInterfaceV2
{ {
base.LoadComplete(); base.LoadComplete();
Colours.BindCollectionChanged((_, args) => updatePalette(args), true); Colours.BindCollectionChanged((_, args) =>
{
if (args.Action != NotifyCollectionChangedAction.Replace)
updatePalette();
}, true);
FinishTransforms(true); FinishTransforms(true);
} }
private const int fade_duration = 200; private const int fade_duration = 200;
private void updatePalette(NotifyCollectionChangedEventArgs args) private void updatePalette()
{ {
if (args.Action == NotifyCollectionChangedAction.Replace)
return;
palette.Clear(); palette.Clear();
if (Colours.Any())
{
palette.FadeIn(fade_duration, Easing.OutQuint);
placeholder.FadeOut(fade_duration, Easing.OutQuint);
}
else
{
palette.FadeOut(fade_duration, Easing.OutQuint);
placeholder.FadeIn(fade_duration, Easing.OutQuint);
}
for (int i = 0; i < Colours.Count; ++i) for (int i = 0; i < Colours.Count; ++i)
{ {
// copy to avoid accesses to modified closure. // copy to avoid accesses to modified closure.
@ -109,20 +92,91 @@ namespace osu.Game.Graphics.UserInterfaceV2
}); });
display.Current.BindValueChanged(colour => Colours[colourIndex] = colour.NewValue); display.Current.BindValueChanged(colour => Colours[colourIndex] = colour.NewValue);
display.DeleteRequested += colourDeletionRequested;
} }
palette.Add(new AddColourButton
{
Action = () => Colours.Add(Colour4.White)
});
reindexItems(); reindexItems();
} }
private void colourDeletionRequested(ColourDisplay display) => Colours.RemoveAt(palette.IndexOf(display));
private void reindexItems() private void reindexItems()
{ {
int index = 1; int index = 1;
foreach (var colour in palette) foreach (var colourDisplay in colourDisplays)
{ {
colour.ColourName = $"{colourNamePrefix} {index}"; colourDisplay.ColourName = $"{colourNamePrefix} {index}";
index += 1; index += 1;
} }
} }
internal class AddColourButton : CompositeDrawable
{
public Action Action
{
set => circularButton.Action = value;
}
private readonly OsuClickableContainer circularButton;
public AddColourButton()
{
AutoSizeAxes = Axes.Y;
Width = 100;
InternalChild = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 10),
Children = new Drawable[]
{
circularButton = new OsuClickableContainer
{
RelativeSizeAxes = Axes.X,
Height = 100,
CornerRadius = 50,
Masking = true,
BorderThickness = 5,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Colour4.Transparent,
AlwaysPresent = true
},
new SpriteIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(20),
Icon = FontAwesome.Solid.Plus
}
}
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = "New"
}
}
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
circularButton.BorderColour = colours.BlueDarker;
}
}
} }
} }

View File

@ -32,7 +32,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
{ {
Depth = 1 Depth = 1
}, },
new HoverClickSounds(HoverSampleSet.Soft) new HoverClickSounds()
}); });
} }

View File

@ -57,7 +57,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
{ {
Depth = 1 Depth = 1
}, },
new HoverClickSounds(HoverSampleSet.Soft) new HoverClickSounds()
}); });
} }

View File

@ -0,0 +1,44 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Localisation;
namespace osu.Game.Localisation
{
public static class NamedOverlayComponentStrings
{
private const string prefix = @"osu.Game.Resources.Localisation.NamedOverlayComponent";
/// <summary>
/// "browse for new beatmaps"
/// </summary>
public static LocalisableString BeatmapListingDescription => new TranslatableString(getKey(@"beatmap_listing_description"), @"browse for new beatmaps");
/// <summary>
/// "track recent dev updates in the osu! ecosystem"
/// </summary>
public static LocalisableString ChangelogDescription => new TranslatableString(getKey(@"changelog_description"), @"track recent dev updates in the osu! ecosystem");
/// <summary>
/// "view your friends and other information"
/// </summary>
public static LocalisableString DashboardDescription => new TranslatableString(getKey(@"dashboard_description"), @"view your friends and other information");
/// <summary>
/// "find out who's the best right now"
/// </summary>
public static LocalisableString RankingsDescription => new TranslatableString(getKey(@"rankings_description"), @"find out who's the best right now");
/// <summary>
/// "get up-to-date on community happenings"
/// </summary>
public static LocalisableString NewsDescription => new TranslatableString(getKey(@"news_description"), @"get up-to-date on community happenings");
/// <summary>
/// "knowledge base"
/// </summary>
public static LocalisableString WikiDescription => new TranslatableString(getKey(@"wiki_description"), @"knowledge base");
private static string getKey(string key) => $"{prefix}:{key}";
}
}

View File

@ -150,7 +150,7 @@ namespace osu.Game.Online.API
userReq.Failure += ex => userReq.Failure += ex =>
{ {
if (ex.InnerException is WebException webException && webException.Message == @"Unauthorized") if (ex is WebException webException && webException.Message == @"Unauthorized")
{ {
log.Add(@"Login no longer valid"); log.Add(@"Login no longer valid");
Logout(); Logout();

View File

@ -31,6 +31,7 @@ namespace osu.Game.Online.Chat
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts); protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts);
public DrawableLinkCompiler(IEnumerable<Drawable> parts) public DrawableLinkCompiler(IEnumerable<Drawable> parts)
: base(HoverSampleSet.Submit)
{ {
Parts = parts.ToList(); Parts = parts.ToList();
} }

View File

@ -1,6 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Game.Localisation;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.BeatmapListing namespace osu.Game.Overlays.BeatmapListing
{ {
public class BeatmapListingHeader : OverlayHeader public class BeatmapListingHeader : OverlayHeader
@ -11,8 +14,8 @@ namespace osu.Game.Overlays.BeatmapListing
{ {
public BeatmapListingTitle() public BeatmapListingTitle()
{ {
Title = "beatmap listing"; Title = PageTitleStrings.MainBeatmapsetsControllerIndex;
Description = "browse for new beatmaps"; Description = NamedOverlayComponentStrings.BeatmapListingDescription;
IconTexture = "Icons/Hexacons/beatmap"; IconTexture = "Icons/Hexacons/beatmap";
} }
} }

View File

@ -50,6 +50,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
protected Action ViewBeatmap; protected Action ViewBeatmap;
protected BeatmapPanel(BeatmapSetInfo setInfo) protected BeatmapPanel(BeatmapSetInfo setInfo)
: base(HoverSampleSet.Submit)
{ {
Debug.Assert(setInfo.OnlineBeatmapSetID != null); Debug.Assert(setInfo.OnlineBeatmapSetID != null);

View File

@ -10,6 +10,7 @@ using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Extensions;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osuTK; using osuTK;
@ -62,7 +63,7 @@ namespace osu.Game.Overlays.BeatmapSet
} }
else else
{ {
length.Value = TimeSpan.FromMilliseconds(beatmap.Length).ToString(@"m\:ss"); length.Value = TimeSpan.FromMilliseconds(beatmap.Length).ToFormattedDuration();
circleCount.Value = beatmap.OnlineInfo.CircleCount.ToString(); circleCount.Value = beatmap.OnlineInfo.CircleCount.ToString();
sliderCount.Value = beatmap.OnlineInfo.SliderCount.ToString(); sliderCount.Value = beatmap.OnlineInfo.SliderCount.ToString();
} }

View File

@ -7,6 +7,7 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Effects;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -54,7 +55,7 @@ namespace osu.Game.Overlays.BeatmapSet
{ {
public BeatmapHeaderTitle() public BeatmapHeaderTitle()
{ {
Title = "beatmap info"; Title = PageTitleStrings.MainBeatmapsetsControllerShow;
IconTexture = "Icons/Hexacons/beatmap"; IconTexture = "Icons/Hexacons/beatmap";
} }
} }

View File

@ -9,7 +9,10 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Game.Localisation;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.Changelog namespace osu.Game.Overlays.Changelog
{ {
@ -21,16 +24,16 @@ namespace osu.Game.Overlays.Changelog
public ChangelogUpdateStreamControl Streams; public ChangelogUpdateStreamControl Streams;
private const string listing_string = "listing"; public static LocalisableString ListingString => LayoutStrings.HeaderChangelogIndex;
private Box streamsBackground; private Box streamsBackground;
public ChangelogHeader() public ChangelogHeader()
{ {
TabControl.AddItem(listing_string); TabControl.AddItem(ListingString);
Current.ValueChanged += e => Current.ValueChanged += e =>
{ {
if (e.NewValue == listing_string) if (e.NewValue == ListingString)
ListingSelected?.Invoke(); ListingSelected?.Invoke();
}; };
@ -63,7 +66,7 @@ namespace osu.Game.Overlays.Changelog
} }
else else
{ {
Current.Value = listing_string; Current.Value = ListingString;
Streams.Current.Value = null; Streams.Current.Value = null;
} }
} }
@ -114,8 +117,8 @@ namespace osu.Game.Overlays.Changelog
{ {
public ChangelogHeaderTitle() public ChangelogHeaderTitle()
{ {
Title = "changelog"; Title = PageTitleStrings.MainChangelogControllerDefault;
Description = "track recent dev updates in the osu! ecosystem"; Description = NamedOverlayComponentStrings.ChangelogDescription;
IconTexture = "Icons/Hexacons/devtools"; IconTexture = "Icons/Hexacons/devtools";
} }
} }

View File

@ -3,6 +3,9 @@
using System; using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Extensions;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -13,6 +16,7 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Chat; using osu.Game.Online.Chat;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -39,6 +43,8 @@ namespace osu.Game.Overlays.Chat.Tabs
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
private Sample sampleTabSwitched;
public ChannelTabItem(Channel value) public ChannelTabItem(Channel value)
: base(value) : base(value)
{ {
@ -112,6 +118,7 @@ namespace osu.Game.Overlays.Chat.Tabs
}, },
}, },
}, },
new HoverSounds()
}; };
} }
@ -152,11 +159,12 @@ namespace osu.Game.Overlays.Chat.Tabs
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours, AudioManager audio)
{ {
BackgroundActive = colours.ChatBlue; BackgroundActive = colours.ChatBlue;
BackgroundInactive = colours.Gray4; BackgroundInactive = colours.Gray4;
backgroundHover = colours.Gray7; backgroundHover = colours.Gray7;
sampleTabSwitched = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select");
highlightBox.Colour = colours.Yellow; highlightBox.Colour = colours.Yellow;
} }
@ -217,7 +225,14 @@ namespace osu.Game.Overlays.Chat.Tabs
Text.Font = Text.Font.With(weight: FontWeight.Medium); Text.Font = Text.Font.With(weight: FontWeight.Medium);
} }
protected override void OnActivated() => updateState(); protected override void OnActivated()
{
if (IsLoaded)
sampleTabSwitched?.Play();
updateState();
}
protected override void OnDeactivated() => updateState(); protected override void OnDeactivated() => updateState();
} }
} }

View File

@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Chat.Tabs
if (value.Type != ChannelType.PM) if (value.Type != ChannelType.PM)
throw new ArgumentException("Argument value needs to have the targettype user!"); throw new ArgumentException("Argument value needs to have the targettype user!");
ClickableAvatar avatar; DrawableAvatar avatar;
AddRange(new Drawable[] AddRange(new Drawable[]
{ {
@ -48,10 +48,9 @@ namespace osu.Game.Overlays.Chat.Tabs
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Masking = true, Masking = true,
Child = new DelayedLoadWrapper(avatar = new ClickableAvatar(value.Users.First()) Child = new DelayedLoadWrapper(avatar = new DrawableAvatar(value.Users.First())
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both
OpenOnClick = false,
}) })
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,

View File

@ -3,6 +3,7 @@
using System.ComponentModel; using System.ComponentModel;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Localisation;
using osu.Game.Resources.Localisation.Web; using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.Dashboard namespace osu.Game.Overlays.Dashboard
@ -15,8 +16,8 @@ namespace osu.Game.Overlays.Dashboard
{ {
public DashboardTitle() public DashboardTitle()
{ {
Title = HomeStrings.UserTitle; Title = PageTitleStrings.MainHomeControllerIndex;
Description = "view your friends and other information"; Description = NamedOverlayComponentStrings.DashboardDescription;
IconTexture = "Icons/Hexacons/social"; IconTexture = "Icons/Hexacons/social";
} }
} }

View File

@ -14,6 +14,7 @@ using osu.Framework.Platform;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Overlays.News namespace osu.Game.Overlays.News
@ -28,6 +29,7 @@ namespace osu.Game.Overlays.News
private TextFlowContainer main; private TextFlowContainer main;
public NewsCard(APINewsPost post) public NewsCard(APINewsPost post)
: base(HoverSampleSet.Submit)
{ {
this.post = post; this.post = post;

View File

@ -4,12 +4,15 @@
using System; using System;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Localisation;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.News namespace osu.Game.Overlays.News
{ {
public class NewsHeader : BreadcrumbControlOverlayHeader public class NewsHeader : BreadcrumbControlOverlayHeader
{ {
private const string front_page_string = "frontpage"; public static LocalisableString FrontPageString => NewsStrings.IndexTitleInfo;
public Action ShowFrontPage; public Action ShowFrontPage;
@ -17,7 +20,7 @@ namespace osu.Game.Overlays.News
public NewsHeader() public NewsHeader()
{ {
TabControl.AddItem(front_page_string); TabControl.AddItem(FrontPageString);
article.BindValueChanged(onArticleChanged, true); article.BindValueChanged(onArticleChanged, true);
} }
@ -28,7 +31,7 @@ namespace osu.Game.Overlays.News
Current.BindValueChanged(e => Current.BindValueChanged(e =>
{ {
if (e.NewValue == front_page_string) if (e.NewValue == FrontPageString)
ShowFrontPage?.Invoke(); ShowFrontPage?.Invoke();
}); });
} }
@ -49,7 +52,7 @@ namespace osu.Game.Overlays.News
} }
else else
{ {
Current.Value = front_page_string; Current.Value = FrontPageString;
} }
} }
@ -61,8 +64,8 @@ namespace osu.Game.Overlays.News
{ {
public NewsHeaderTitle() public NewsHeaderTitle()
{ {
Title = "news"; Title = PageTitleStrings.MainNewsControllerDefault;
Description = "get up-to-date on community happenings"; Description = NamedOverlayComponentStrings.NewsDescription;
IconTexture = "Icons/Hexacons/news"; IconTexture = "Icons/Hexacons/news";
} }
} }

View File

@ -15,13 +15,18 @@ using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using System.Diagnostics; using System.Diagnostics;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.News.Sidebar namespace osu.Game.Overlays.News.Sidebar
{ {
public class MonthSection : CompositeDrawable public class MonthSection : CompositeDrawable
{ {
private const int animation_duration = 250; private const int animation_duration = 250;
private Sample sampleOpen;
private Sample sampleClose;
public readonly BindableBool Expanded = new BindableBool(); public readonly BindableBool Expanded = new BindableBool();
@ -51,6 +56,21 @@ namespace osu.Game.Overlays.News.Sidebar
} }
} }
}; };
Expanded.ValueChanged += expanded =>
{
if (expanded.NewValue)
sampleOpen?.Play();
else
sampleClose?.Play();
};
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
sampleOpen = audio.Samples.Get(@"UI/dropdown-open");
sampleClose = audio.Samples.Get(@"UI/dropdown-close");
} }
private class DropdownHeader : OsuClickableContainer private class DropdownHeader : OsuClickableContainer
@ -59,6 +79,8 @@ namespace osu.Game.Overlays.News.Sidebar
private readonly SpriteIcon icon; private readonly SpriteIcon icon;
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
public DropdownHeader(int month, int year) public DropdownHeader(int month, int year)
{ {
var date = new DateTime(year, month, 1); var date = new DateTime(year, month, 1);
@ -104,6 +126,7 @@ namespace osu.Game.Overlays.News.Sidebar
private readonly APINewsPost post; private readonly APINewsPost post;
public PostButton(APINewsPost post) public PostButton(APINewsPost post)
: base(HoverSampleSet.Submit)
{ {
this.post = post; this.post = post;

View File

@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Profile.Sections namespace osu.Game.Overlays.Profile.Sections
{ {
@ -17,6 +18,7 @@ namespace osu.Game.Overlays.Profile.Sections
private readonly BeatmapInfo beatmap; private readonly BeatmapInfo beatmap;
protected BeatmapMetadataContainer(BeatmapInfo beatmap) protected BeatmapMetadataContainer(BeatmapInfo beatmap)
: base(HoverSampleSet.Submit)
{ {
this.beatmap = beatmap; this.beatmap = beatmap;

View File

@ -3,6 +3,7 @@
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Resources.Localisation.Web; using osu.Game.Resources.Localisation.Web;
@ -52,7 +53,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
new OsuSpriteText new OsuSpriteText
{ {
Font = OsuFont.GetFont(size: 12), Font = OsuFont.GetFont(size: 12),
Text = UsersStrings.ShowExtraTopRanksPpWeight(weight.ToString("0%")) Text = UsersStrings.ShowExtraTopRanksPpWeight(weight.ToLocalisableString("0%"))
} }
} }
}; };

View File

@ -3,6 +3,8 @@
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Game.Localisation;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Users; using osu.Game.Users;
@ -29,18 +31,10 @@ namespace osu.Game.Overlays.Rankings
{ {
public RankingsTitle() public RankingsTitle()
{ {
Title = "ranking"; Title = PageTitleStrings.MainRankingControllerDefault;
Description = "find out who's the best right now"; Description = NamedOverlayComponentStrings.RankingsDescription;
IconTexture = "Icons/Hexacons/rankings"; IconTexture = "Icons/Hexacons/rankings";
} }
} }
} }
public enum RankingsScope
{
Performance,
Spotlights,
Score,
Country
}
} }

View File

@ -0,0 +1,42 @@
// 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.Localisation;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.Rankings
{
[LocalisableEnum(typeof(RankingsScopeEnumLocalisationMapper))]
public enum RankingsScope
{
Performance,
Spotlights,
Score,
Country
}
public class RankingsScopeEnumLocalisationMapper : EnumLocalisationMapper<RankingsScope>
{
public override LocalisableString Map(RankingsScope value)
{
switch (value)
{
case RankingsScope.Performance:
return RankingsStrings.TypePerformance;
case RankingsScope.Spotlights:
return RankingsStrings.TypeCharts;
case RankingsScope.Score:
return RankingsStrings.TypeScore;
case RankingsScope.Country:
return RankingsStrings.TypeCountry;
default:
throw new ArgumentOutOfRangeException(nameof(value), value, null);
}
}
}
}

View File

@ -1,19 +1,43 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.Rankings namespace osu.Game.Overlays.Rankings
{ {
public class RankingsSortTabControl : OverlaySortTabControl<RankingsSortCriteria> public class RankingsSortTabControl : OverlaySortTabControl<RankingsSortCriteria>
{ {
public RankingsSortTabControl() public RankingsSortTabControl()
{ {
Title = "Show"; Title = RankingsStrings.FilterTitle.ToUpper();
} }
} }
[LocalisableEnum(typeof(RankingsSortCriteriaEnumLocalisationMapper))]
public enum RankingsSortCriteria public enum RankingsSortCriteria
{ {
All, All,
Friends Friends
} }
public class RankingsSortCriteriaEnumLocalisationMapper : EnumLocalisationMapper<RankingsSortCriteria>
{
public override LocalisableString Map(RankingsSortCriteria value)
{
switch (value)
{
case RankingsSortCriteria.All:
return SortStrings.All;
case RankingsSortCriteria.Friends:
return SortStrings.Friends;
default:
throw new ArgumentOutOfRangeException(nameof(value), value, null);
}
}
}
} }

View File

@ -16,6 +16,8 @@ using System.Collections.Generic;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.Rankings namespace osu.Game.Overlays.Rankings
{ {
@ -92,10 +94,10 @@ namespace osu.Game.Overlays.Rankings
Margin = new MarginPadding { Bottom = 5 }, Margin = new MarginPadding { Bottom = 5 },
Children = new Drawable[] Children = new Drawable[]
{ {
startDateColumn = new InfoColumn(@"Start Date"), startDateColumn = new InfoColumn(RankingsStrings.SpotlightStartDate),
endDateColumn = new InfoColumn(@"End Date"), endDateColumn = new InfoColumn(RankingsStrings.SpotlightEndDate),
mapCountColumn = new InfoColumn(@"Map Count"), mapCountColumn = new InfoColumn(RankingsStrings.SpotlightMapCount),
participantsColumn = new InfoColumn(@"Participants") participantsColumn = new InfoColumn(RankingsStrings.SpotlightParticipants)
} }
}, },
new RankingsSortTabControl new RankingsSortTabControl
@ -122,22 +124,22 @@ namespace osu.Game.Overlays.Rankings
{ {
startDateColumn.Value = dateToString(response.Spotlight.StartDate); startDateColumn.Value = dateToString(response.Spotlight.StartDate);
endDateColumn.Value = dateToString(response.Spotlight.EndDate); endDateColumn.Value = dateToString(response.Spotlight.EndDate);
mapCountColumn.Value = response.BeatmapSets.Count.ToString(); mapCountColumn.Value = response.BeatmapSets.Count.ToLocalisableString(@"N0");
participantsColumn.Value = response.Spotlight.Participants?.ToString("N0"); participantsColumn.Value = response.Spotlight.Participants?.ToLocalisableString(@"N0");
} }
private string dateToString(DateTimeOffset date) => date.ToString("yyyy-MM-dd"); private LocalisableString dateToString(DateTimeOffset date) => date.ToLocalisableString(@"yyyy-MM-dd");
private class InfoColumn : FillFlowContainer private class InfoColumn : FillFlowContainer
{ {
public string Value public LocalisableString Value
{ {
set => valueText.Text = value; set => valueText.Text = value;
} }
private readonly OsuSpriteText valueText; private readonly OsuSpriteText valueText;
public InfoColumn(string name) public InfoColumn(LocalisableString name)
{ {
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
Direction = FillDirection.Vertical; Direction = FillDirection.Vertical;

View File

@ -9,6 +9,8 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Resources.Localisation.Web;
using osu.Framework.Localisation;
namespace osu.Game.Overlays.Rankings.Tables namespace osu.Game.Overlays.Rankings.Tables
{ {
@ -21,12 +23,12 @@ namespace osu.Game.Overlays.Rankings.Tables
protected override RankingsTableColumn[] CreateAdditionalHeaders() => new[] protected override RankingsTableColumn[] CreateAdditionalHeaders() => new[]
{ {
new RankingsTableColumn("Active Users", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new RankingsTableColumn(RankingsStrings.StatActiveUsers, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new RankingsTableColumn(RankingsStrings.StatPlayCount, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn("Ranked Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new RankingsTableColumn(RankingsStrings.StatRankedScore, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn("Avg. Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new RankingsTableColumn(RankingsStrings.StatAverageScore, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn("Performance", Anchor.Centre, new Dimension(GridSizeMode.AutoSize), true), new RankingsTableColumn(RankingsStrings.StatPerformance, Anchor.Centre, new Dimension(GridSizeMode.AutoSize), true),
new RankingsTableColumn("Avg. Perf.", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new RankingsTableColumn(RankingsStrings.StatAveragePerformance, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
}; };
protected override Country GetCountry(CountryStatistics item) => item.Country; protected override Country GetCountry(CountryStatistics item) => item.Country;
@ -35,29 +37,29 @@ namespace osu.Game.Overlays.Rankings.Tables
protected override Drawable[] CreateAdditionalContent(CountryStatistics item) => new Drawable[] protected override Drawable[] CreateAdditionalContent(CountryStatistics item) => new Drawable[]
{ {
new ColoredRowText new ColouredRowText
{ {
Text = $@"{item.ActiveUsers:N0}", Text = item.ActiveUsers.ToLocalisableString(@"N0")
}, },
new ColoredRowText new ColouredRowText
{ {
Text = $@"{item.PlayCount:N0}", Text = item.PlayCount.ToLocalisableString(@"N0")
}, },
new ColoredRowText new ColouredRowText
{ {
Text = $@"{item.RankedScore:N0}", Text = item.RankedScore.ToLocalisableString(@"N0")
}, },
new ColoredRowText new ColouredRowText
{ {
Text = $@"{item.RankedScore / Math.Max(item.ActiveUsers, 1):N0}", Text = (item.RankedScore / Math.Max(item.ActiveUsers, 1)).ToLocalisableString(@"N0")
}, },
new RowText new RowText
{ {
Text = $@"{item.Performance:N0}", Text = item.Performance.ToLocalisableString(@"N0")
}, },
new ColoredRowText new ColouredRowText
{ {
Text = $@"{item.Performance / Math.Max(item.ActiveUsers, 1):N0}", Text = (item.Performance / Math.Max(item.ActiveUsers, 1)).ToLocalisableString(@"N0")
} }
}; };

View File

@ -4,6 +4,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Users; using osu.Game.Users;
namespace osu.Game.Overlays.Rankings.Tables namespace osu.Game.Overlays.Rankings.Tables
@ -17,12 +19,12 @@ namespace osu.Game.Overlays.Rankings.Tables
protected override RankingsTableColumn[] CreateUniqueHeaders() => new[] protected override RankingsTableColumn[] CreateUniqueHeaders() => new[]
{ {
new RankingsTableColumn("Performance", Anchor.Centre, new Dimension(GridSizeMode.AutoSize), true), new RankingsTableColumn(RankingsStrings.StatPerformance, Anchor.Centre, new Dimension(GridSizeMode.AutoSize), true),
}; };
protected override Drawable[] CreateUniqueContent(UserStatistics item) => new Drawable[] protected override Drawable[] CreateUniqueContent(UserStatistics item) => new Drawable[]
{ {
new RowText { Text = $@"{item.PP:N0}", } new RowText { Text = item.PP.ToLocalisableString(@"N0"), }
}; };
} }
} }

View File

@ -80,7 +80,7 @@ namespace osu.Game.Overlays.Rankings.Tables
private OsuSpriteText createIndexDrawable(int index) => new RowText private OsuSpriteText createIndexDrawable(int index) => new RowText
{ {
Text = $"#{index + 1}", Text = (index + 1).ToLocalisableString(@"\##"),
Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.SemiBold) Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.SemiBold)
}; };
@ -144,7 +144,7 @@ namespace osu.Game.Overlays.Rankings.Tables
} }
} }
protected class ColoredRowText : RowText protected class ColouredRowText : RowText
{ {
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider) private void load(OverlayColourProvider colourProvider)

View File

@ -4,6 +4,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Users; using osu.Game.Users;
namespace osu.Game.Overlays.Rankings.Tables namespace osu.Game.Overlays.Rankings.Tables
@ -17,19 +19,19 @@ namespace osu.Game.Overlays.Rankings.Tables
protected override RankingsTableColumn[] CreateUniqueHeaders() => new[] protected override RankingsTableColumn[] CreateUniqueHeaders() => new[]
{ {
new RankingsTableColumn("Total Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new RankingsTableColumn(RankingsStrings.StatTotalScore, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn("Ranked Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize), true) new RankingsTableColumn(RankingsStrings.StatRankedScore, Anchor.Centre, new Dimension(GridSizeMode.AutoSize), true)
}; };
protected override Drawable[] CreateUniqueContent(UserStatistics item) => new Drawable[] protected override Drawable[] CreateUniqueContent(UserStatistics item) => new Drawable[]
{ {
new ColoredRowText new ColouredRowText
{ {
Text = $@"{item.TotalScore:N0}", Text = item.TotalScore.ToLocalisableString(@"N0"),
}, },
new RowText new RowText
{ {
Text = $@"{item.RankedScore:N0}", Text = item.RankedScore.ToLocalisableString(@"N0")
} }
}; };
} }

View File

@ -10,6 +10,7 @@ using osu.Game.Graphics.Containers;
using osu.Game.Users; using osu.Game.Users;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.Rankings.Tables namespace osu.Game.Overlays.Rankings.Tables
{ {
@ -20,12 +21,12 @@ namespace osu.Game.Overlays.Rankings.Tables
{ {
} }
protected virtual IEnumerable<string> GradeColumns => new List<string> { "SS", "S", "A" }; protected virtual IEnumerable<LocalisableString> GradeColumns => new List<LocalisableString> { RankingsStrings.Statss, RankingsStrings.Stats, RankingsStrings.Stata };
protected override RankingsTableColumn[] CreateAdditionalHeaders() => new[] protected override RankingsTableColumn[] CreateAdditionalHeaders() => new[]
{ {
new RankingsTableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new RankingsTableColumn(RankingsStrings.StatAccuracy, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new RankingsTableColumn(RankingsStrings.StatPlayCount, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
}.Concat(CreateUniqueHeaders()) }.Concat(CreateUniqueHeaders())
.Concat(GradeColumns.Select(grade => new GradeTableColumn(grade, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)))) .Concat(GradeColumns.Select(grade => new GradeTableColumn(grade, Anchor.Centre, new Dimension(GridSizeMode.AutoSize))))
.ToArray(); .ToArray();
@ -46,13 +47,13 @@ namespace osu.Game.Overlays.Rankings.Tables
protected sealed override Drawable[] CreateAdditionalContent(UserStatistics item) => new[] protected sealed override Drawable[] CreateAdditionalContent(UserStatistics item) => new[]
{ {
new ColoredRowText { Text = item.DisplayAccuracy, }, new ColouredRowText { Text = item.DisplayAccuracy, },
new ColoredRowText { Text = $@"{item.PlayCount:N0}", }, new ColouredRowText { Text = item.PlayCount.ToLocalisableString(@"N0") },
}.Concat(CreateUniqueContent(item)).Concat(new[] }.Concat(CreateUniqueContent(item)).Concat(new[]
{ {
new ColoredRowText { Text = $@"{item.GradesCount[ScoreRank.XH] + item.GradesCount[ScoreRank.X]:N0}", }, new ColouredRowText { Text = (item.GradesCount[ScoreRank.XH] + item.GradesCount[ScoreRank.X]).ToLocalisableString(@"N0"), },
new ColoredRowText { Text = $@"{item.GradesCount[ScoreRank.SH] + item.GradesCount[ScoreRank.S]:N0}", }, new ColouredRowText { Text = (item.GradesCount[ScoreRank.SH] + item.GradesCount[ScoreRank.S]).ToLocalisableString(@"N0"), },
new ColoredRowText { Text = $@"{item.GradesCount[ScoreRank.A]:N0}", } new ColouredRowText { Text = item.GradesCount[ScoreRank.A].ToLocalisableString(@"N0"), }
}).ToArray(); }).ToArray();
protected abstract RankingsTableColumn[] CreateUniqueHeaders(); protected abstract RankingsTableColumn[] CreateUniqueHeaders();

View File

@ -138,7 +138,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
}, },
} }
} }
} },
new HoverClickSounds()
}; };
foreach (var b in bindings) foreach (var b in bindings)
@ -458,6 +459,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Origin = Anchor.Centre, Origin = Anchor.Centre,
Text = keyBinding.KeyCombination.ReadableString(), Text = keyBinding.KeyCombination.ReadableString(),
}, },
new HoverSounds()
}; };
} }

View File

@ -6,15 +6,18 @@ using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Localisation;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.Wiki namespace osu.Game.Overlays.Wiki
{ {
public class WikiHeader : BreadcrumbControlOverlayHeader public class WikiHeader : BreadcrumbControlOverlayHeader
{ {
private const string index_page_string = "index";
private const string index_path = "Main_Page"; private const string index_path = "Main_Page";
public static LocalisableString IndexPageString => LayoutStrings.HeaderHelpIndex;
public readonly Bindable<APIWikiPage> WikiPageData = new Bindable<APIWikiPage>(); public readonly Bindable<APIWikiPage> WikiPageData = new Bindable<APIWikiPage>();
public Action ShowIndexPage; public Action ShowIndexPage;
@ -22,8 +25,8 @@ namespace osu.Game.Overlays.Wiki
public WikiHeader() public WikiHeader()
{ {
TabControl.AddItem(index_page_string); TabControl.AddItem(IndexPageString);
Current.Value = index_page_string; Current.Value = IndexPageString;
WikiPageData.BindValueChanged(onWikiPageChange); WikiPageData.BindValueChanged(onWikiPageChange);
Current.BindValueChanged(onCurrentChange); Current.BindValueChanged(onCurrentChange);
@ -37,11 +40,11 @@ namespace osu.Game.Overlays.Wiki
TabControl.Clear(); TabControl.Clear();
Current.Value = null; Current.Value = null;
TabControl.AddItem(index_page_string); TabControl.AddItem(IndexPageString);
if (e.NewValue.Path == index_path) if (e.NewValue.Path == index_path)
{ {
Current.Value = index_page_string; Current.Value = IndexPageString;
return; return;
} }
@ -57,7 +60,7 @@ namespace osu.Game.Overlays.Wiki
if (e.NewValue == TabControl.Items.LastOrDefault()) if (e.NewValue == TabControl.Items.LastOrDefault())
return; return;
if (e.NewValue == index_page_string) if (e.NewValue == IndexPageString)
{ {
ShowIndexPage?.Invoke(); ShowIndexPage?.Invoke();
return; return;
@ -74,8 +77,8 @@ namespace osu.Game.Overlays.Wiki
{ {
public WikiHeaderTitle() public WikiHeaderTitle()
{ {
Title = "wiki"; Title = PageTitleStrings.MainWikiControllerDefault;
Description = "knowledge base"; Description = NamedOverlayComponentStrings.WikiDescription;
IconTexture = "Icons/Hexacons/wiki"; IconTexture = "Icons/Hexacons/wiki";
} }
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Diagnostics; using System.Diagnostics;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -31,6 +32,9 @@ namespace osu.Game.Rulesets.Judgements
private readonly Container aboveHitObjectsContent; private readonly Container aboveHitObjectsContent;
private readonly Lazy<Drawable> proxiedAboveHitObjectsContent;
public Drawable ProxiedAboveHitObjectsContent => proxiedAboveHitObjectsContent.Value;
/// <summary> /// <summary>
/// Creates a drawable which visualises a <see cref="Judgements.Judgement"/>. /// Creates a drawable which visualises a <see cref="Judgements.Judgement"/>.
/// </summary> /// </summary>
@ -52,6 +56,8 @@ namespace osu.Game.Rulesets.Judgements
Depth = float.MinValue, Depth = float.MinValue,
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both
}); });
proxiedAboveHitObjectsContent = new Lazy<Drawable>(() => aboveHitObjectsContent.CreateProxy());
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -60,8 +66,6 @@ namespace osu.Game.Rulesets.Judgements
prepareDrawables(); prepareDrawables();
} }
public Drawable GetProxyAboveHitObjectsContent() => aboveHitObjectsContent.CreateProxy();
/// <summary> /// <summary>
/// Apply top-level animations to the current judgement when successfully hit. /// Apply top-level animations to the current judgement when successfully hit.
/// If displaying components which require lifetime extensions, manually adjusting <see cref="Drawable.LifetimeEnd"/> is required. /// If displaying components which require lifetime extensions, manually adjusting <see cref="Drawable.LifetimeEnd"/> is required.

View File

@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Mods
public interface IApplicableToHealthProcessor : IApplicableMod public interface IApplicableToHealthProcessor : IApplicableMod
{ {
/// <summary> /// <summary>
/// Provide a <see cref="HealthProcessor"/> to a mod. Called once on initialisation of a play instance. /// Provides a loaded <see cref="HealthProcessor"/> to a mod. Called once on initialisation of a play instance.
/// </summary> /// </summary>
void ApplyToHealthProcessor(HealthProcessor healthProcessor); void ApplyToHealthProcessor(HealthProcessor healthProcessor);
} }

View File

@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mods
public interface IApplicableToScoreProcessor : IApplicableMod public interface IApplicableToScoreProcessor : IApplicableMod
{ {
/// <summary> /// <summary>
/// Provide a <see cref="ScoreProcessor"/> to a mod. Called once on initialisation of a play instance. /// Provides a loaded <see cref="ScoreProcessor"/> to a mod. Called once on initialisation of a play instance.
/// </summary> /// </summary>
void ApplyToScoreProcessor(ScoreProcessor scoreProcessor); void ApplyToScoreProcessor(ScoreProcessor scoreProcessor);

View File

@ -1,9 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation; using osu.Framework.Audio;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Graphics; using osu.Framework.Bindables;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
@ -11,11 +11,11 @@ using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mods namespace osu.Game.Rulesets.Mods
{ {
public class Metronome : BeatSyncedContainer public class Metronome : BeatSyncedContainer, IAdjustableAudioComponent
{ {
private readonly double firstHitTime; private readonly double firstHitTime;
private PausableSkinnableSound sample; private readonly PausableSkinnableSound sample;
/// <param name="firstHitTime">Start time of the first hit object, used for providing a count down.</param> /// <param name="firstHitTime">Start time of the first hit object, used for providing a count down.</param>
public Metronome(double firstHitTime) public Metronome(double firstHitTime)
@ -23,15 +23,8 @@ namespace osu.Game.Rulesets.Mods
this.firstHitTime = firstHitTime; this.firstHitTime = firstHitTime;
AllowMistimedEventFiring = false; AllowMistimedEventFiring = false;
Divisor = 1; Divisor = 1;
}
[BackgroundDependencyLoader] InternalChild = sample = new PausableSkinnableSound(new SampleInfo("Gameplay/catch-banana"));
private void load()
{
InternalChildren = new Drawable[]
{
sample = new PausableSkinnableSound(new SampleInfo("Gameplay/catch-banana"))
};
} }
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
@ -49,5 +42,50 @@ namespace osu.Game.Rulesets.Mods
sample.Frequency.Value = beatIndex % timeSignature == 0 ? 1 : 0.5f; sample.Frequency.Value = beatIndex % timeSignature == 0 ? 1 : 0.5f;
sample.Play(); sample.Play();
} }
#region IAdjustableAudioComponent
public IBindable<double> AggregateVolume => sample.AggregateVolume;
public IBindable<double> AggregateBalance => sample.AggregateBalance;
public IBindable<double> AggregateFrequency => sample.AggregateFrequency;
public IBindable<double> AggregateTempo => sample.AggregateTempo;
public BindableNumber<double> Volume => sample.Volume;
public BindableNumber<double> Balance => sample.Balance;
public BindableNumber<double> Frequency => sample.Frequency;
public BindableNumber<double> Tempo => sample.Tempo;
public void BindAdjustments(IAggregateAudioAdjustment component)
{
sample.BindAdjustments(component);
}
public void UnbindAdjustments(IAggregateAudioAdjustment component)
{
sample.UnbindAdjustments(component);
}
public void AddAdjustment(AdjustableProperty type, IBindable<double> adjustBindable)
{
sample.AddAdjustment(type, adjustBindable);
}
public void RemoveAdjustment(AdjustableProperty type, IBindable<double> adjustBindable)
{
sample.RemoveAdjustment(type, adjustBindable);
}
public void RemoveAllAdjustments(AdjustableProperty type)
{
sample.RemoveAllAdjustments(type);
}
#endregion
} }
} }

View File

@ -1,14 +1,18 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq; using System.Linq;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
namespace osu.Game.Rulesets.Mods namespace osu.Game.Rulesets.Mods
{ {
@ -22,27 +26,79 @@ namespace osu.Game.Rulesets.Mods
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
} }
public abstract class ModMuted<TObject> : ModMuted, IApplicableToDrawableRuleset<TObject>, IApplicableToTrack public abstract class ModMuted<TObject> : ModMuted, IApplicableToDrawableRuleset<TObject>, IApplicableToTrack, IApplicableToScoreProcessor
where TObject : HitObject where TObject : HitObject
{ {
private readonly BindableNumber<double> volumeAdjust = new BindableDouble(); private readonly BindableNumber<double> mainVolumeAdjust = new BindableDouble(0.5);
private readonly BindableNumber<double> metronomeVolumeAdjust = new BindableDouble(0.5);
[SettingSource("Enable metronome", "Add a metronome to help you keep track of the rhythm.")] private BindableNumber<int> currentCombo;
[SettingSource("Enable metronome", "Add a metronome beat to help you keep track of the rhythm.")]
public BindableBool EnableMetronome { get; } = new BindableBool public BindableBool EnableMetronome { get; } = new BindableBool
{ {
Default = true, Default = true,
Value = true Value = true
}; };
[SettingSource("Final volume at combo", "The combo count at which point the track reaches its final volume.")]
public BindableInt MuteComboCount { get; } = new BindableInt
{
Default = 100,
Value = 100,
MinValue = 0,
MaxValue = 500,
};
[SettingSource("Start muted", "Increase volume as combo builds.")]
public BindableBool InverseMuting { get; } = new BindableBool
{
Default = false,
Value = false
};
[SettingSource("Mute hit sounds", "Hit sounds are also muted alongside the track.")]
public BindableBool AffectsHitSounds { get; } = new BindableBool
{
Default = true,
Value = true
};
public void ApplyToTrack(ITrack track) public void ApplyToTrack(ITrack track)
{ {
track.AddAdjustment(AdjustableProperty.Volume, volumeAdjust); track.AddAdjustment(AdjustableProperty.Volume, mainVolumeAdjust);
} }
public void ApplyToDrawableRuleset(DrawableRuleset<TObject> drawableRuleset) public void ApplyToDrawableRuleset(DrawableRuleset<TObject> drawableRuleset)
{ {
if (EnableMetronome.Value) if (EnableMetronome.Value)
drawableRuleset.Overlays.Add(new Metronome(drawableRuleset.Beatmap.HitObjects.First().StartTime)); {
Metronome metronome;
drawableRuleset.Overlays.Add(metronome = new Metronome(drawableRuleset.Beatmap.HitObjects.First().StartTime));
metronome.AddAdjustment(AdjustableProperty.Volume, metronomeVolumeAdjust);
}
if (AffectsHitSounds.Value)
drawableRuleset.Audio.AddAdjustment(AdjustableProperty.Volume, mainVolumeAdjust);
} }
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
{
currentCombo = scoreProcessor.Combo.GetBoundCopy();
currentCombo.BindValueChanged(combo =>
{
double dimFactor = Math.Min(1, (double)combo.NewValue / MuteComboCount.Value);
if (InverseMuting.Value)
dimFactor = 1 - dimFactor;
scoreProcessor.TransformBindableTo(metronomeVolumeAdjust, dimFactor, 500, Easing.OutQuint);
scoreProcessor.TransformBindableTo(mainVolumeAdjust, 1 - dimFactor, 500, Easing.OutQuint);
}, true);
}
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
} }
} }

View File

@ -1,29 +1,30 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Cursor;
using osu.Game.Input.Handlers; using osu.Game.Input.Handlers;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Replays; using osu.Game.Replays;
using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
@ -98,6 +99,14 @@ namespace osu.Game.Rulesets.UI
private DrawableRulesetDependencies dependencies; private DrawableRulesetDependencies dependencies;
/// <summary>
/// Audio adjustments which are applied to the playfield.
/// </summary>
/// <remarks>
/// Does not affect <see cref="Overlays"/>.
/// </remarks>
public IAdjustableAudioComponent Audio { get; private set; }
/// <summary> /// <summary>
/// Creates a ruleset visualisation for the provided ruleset and beatmap. /// Creates a ruleset visualisation for the provided ruleset and beatmap.
/// </summary> /// </summary>
@ -155,23 +164,28 @@ namespace osu.Game.Rulesets.UI
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(CancellationToken? cancellationToken) private void load(CancellationToken? cancellationToken)
{ {
InternalChildren = new Drawable[] AudioContainer audioContainer;
InternalChild = frameStabilityContainer = new FrameStabilityContainer(GameplayStartTime)
{ {
frameStabilityContainer = new FrameStabilityContainer(GameplayStartTime) FrameStablePlayback = FrameStablePlayback,
Children = new Drawable[]
{ {
FrameStablePlayback = FrameStablePlayback, FrameStableComponents,
Children = new Drawable[] audioContainer = new AudioContainer
{ {
FrameStableComponents, RelativeSizeAxes = Axes.Both,
KeyBindingInputManager Child = KeyBindingInputManager
.WithChild(CreatePlayfieldAdjustmentContainer() .WithChild(CreatePlayfieldAdjustmentContainer()
.WithChild(Playfield) .WithChild(Playfield)
), ),
Overlays, },
} Overlays,
}, }
}; };
Audio = audioContainer;
if ((ResumeOverlay = CreateResumeOverlay()) != null) if ((ResumeOverlay = CreateResumeOverlay()) != null)
{ {
AddInternal(CreateInputManager() AddInternal(CreateInputManager()

View File

@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
/// <summary> /// <summary>
/// The maximum span of time that may be visible by the length of the scrolling axes. /// The maximum span of time that may be visible by the length of the scrolling axes.
/// </summary> /// </summary>
private const double time_span_max = 10000; private const double time_span_max = 20000;
/// <summary> /// <summary>
/// The step increase/decrease of the span of time visible by the length of the scrolling axes. /// The step increase/decrease of the span of time visible by the length of the scrolling axes.

View File

@ -11,7 +11,6 @@ using osu.Framework.Localisation;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays; using osu.Game.Overlays;
using osuTK.Graphics; using osuTK.Graphics;
@ -67,7 +66,6 @@ namespace osu.Game.Screens.Edit
private EditorClock clock { get; set; } private EditorClock clock { get; set; }
public RowBackground(object item) public RowBackground(object item)
: base(HoverSampleSet.Soft)
{ {
Item = item; Item = item;

View File

@ -297,11 +297,19 @@ namespace osu.Game.Screens.Play
ScoreProcessor.HasCompleted.BindValueChanged(scoreCompletionChanged); ScoreProcessor.HasCompleted.BindValueChanged(scoreCompletionChanged);
HealthProcessor.Failed += onFail; HealthProcessor.Failed += onFail;
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>()) // Provide judgement processors to mods after they're loaded so that they're on the gameplay clock,
mod.ApplyToScoreProcessor(ScoreProcessor); // this is required for mods that apply transforms to these processors.
ScoreProcessor.OnLoadComplete += _ =>
{
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
mod.ApplyToScoreProcessor(ScoreProcessor);
};
foreach (var mod in Mods.Value.OfType<IApplicableToHealthProcessor>()) HealthProcessor.OnLoadComplete += _ =>
mod.ApplyToHealthProcessor(HealthProcessor); {
foreach (var mod in Mods.Value.OfType<IApplicableToHealthProcessor>())
mod.ApplyToHealthProcessor(HealthProcessor);
};
IsBreakTime.BindTo(breakTracker.IsBreakTime); IsBreakTime.BindTo(breakTracker.IsBreakTime);
IsBreakTime.BindValueChanged(onBreakTimeChanged, true); IsBreakTime.BindValueChanged(onBreakTimeChanged, true);

View File

@ -178,7 +178,7 @@ namespace osu.Game.Screens.Play
float barHeight = bottom_bar_height + handle_size.Y; float barHeight = bottom_bar_height + handle_size.Y;
bar.ResizeHeightTo(ShowGraph.Value ? barHeight + graph_height : barHeight, transition_duration, Easing.In); bar.ResizeHeightTo(ShowGraph.Value ? barHeight + graph_height : barHeight, transition_duration, Easing.In);
graph.MoveToY(ShowGraph.Value ? 0 : bottom_bar_height + graph_height, transition_duration, Easing.In); graph.FadeTo(ShowGraph.Value ? 1 : 0, transition_duration, Easing.In);
updateInfoMargin(); updateInfoMargin();
} }

View File

@ -24,6 +24,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Extensions;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -333,7 +334,7 @@ namespace osu.Game.Screens.Select
{ {
Name = "Length", Name = "Length",
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length), CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length),
Content = TimeSpan.FromMilliseconds(beatmap.BeatmapInfo.Length).ToString(@"m\:ss"), Content = beatmap.BeatmapInfo.Length.ToFormattedDuration().ToString(),
}), }),
bpmLabelContainer = new Container bpmLabelContainer = new Container
{ {

View File

@ -14,6 +14,7 @@ using osu.Game.Graphics.Sprites;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Screens.Select.Options namespace osu.Game.Screens.Select.Options
{ {
@ -76,6 +77,7 @@ namespace osu.Game.Screens.Select.Options
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos);
public BeatmapOptionsButton() public BeatmapOptionsButton()
: base(HoverSampleSet.Submit)
{ {
Width = width; Width = width;
RelativeSizeAxes = Axes.Y; RelativeSizeAxes = Axes.Y;

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Users.Drawables namespace osu.Game.Users.Drawables
{ {
@ -71,6 +72,11 @@ namespace osu.Game.Users.Drawables
{ {
private LocalisableString tooltip = default_tooltip_text; private LocalisableString tooltip = default_tooltip_text;
public ClickableArea()
: base(HoverSampleSet.Submit)
{
}
public override LocalisableString TooltipText public override LocalisableString TooltipText
{ {
get => Enabled.Value ? tooltip : default; get => Enabled.Value ? tooltip : default;

View File

@ -5,6 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays; using osu.Game.Overlays;
namespace osu.Game.Users.Drawables namespace osu.Game.Users.Drawables
@ -32,9 +33,17 @@ namespace osu.Game.Users.Drawables
if (country == null && !ShowPlaceholderOnNull) if (country == null && !ShowPlaceholderOnNull)
return null; return null;
return new DrawableFlag(country) return new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new DrawableFlag(country)
{
RelativeSizeAxes = Axes.Both
},
new HoverClickSounds(HoverSampleSet.Submit)
}
}; };
} }

View File

@ -31,6 +31,7 @@ namespace osu.Game.Users
protected Drawable Background { get; private set; } protected Drawable Background { get; private set; }
protected UserPanel(User user) protected UserPanel(User user)
: base(HoverSampleSet.Submit)
{ {
if (user == null) if (user == null)
throw new ArgumentNullException(nameof(user)); throw new ArgumentNullException(nameof(user));

View File

@ -22,7 +22,7 @@
<PackageReference Include="DiffPlex" Version="1.7.0" /> <PackageReference Include="DiffPlex" Version="1.7.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.34" /> <PackageReference Include="HtmlAgilityPack" Version="1.11.34" />
<PackageReference Include="Humanizer" Version="2.11.10" /> <PackageReference Include="Humanizer" Version="2.11.10" />
<PackageReference Include="MessagePack" Version="2.2.113" /> <PackageReference Include="MessagePack" Version="2.3.75" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="5.0.8" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="5.0.8" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="5.0.8" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="5.0.8" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="5.0.8" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="5.0.8" />
@ -37,8 +37,8 @@
</PackageReference> </PackageReference>
<PackageReference Include="Realm" Version="10.3.0" /> <PackageReference Include="Realm" Version="10.3.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.728.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.728.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.722.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.803.0" />
<PackageReference Include="Sentry" Version="3.8.2" /> <PackageReference Include="Sentry" Version="3.8.3" />
<PackageReference Include="SharpCompress" Version="0.28.3" /> <PackageReference Include="SharpCompress" Version="0.28.3" />
<PackageReference Include="NUnit" Version="3.13.2" /> <PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />

View File

@ -71,7 +71,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.728.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2021.728.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.722.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.803.0" />
</ItemGroup> </ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) --> <!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
<PropertyGroup> <PropertyGroup>

View File

@ -117,7 +117,7 @@
</ImageAsset> </ImageAsset>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Xamarin.Essentials" Version="1.6.1" /> <PackageReference Include="Xamarin.Essentials" Version="1.7.0" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
</Project> </Project>