1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 13:37:25 +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
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" />
</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" />
</ItemGroup>
<ItemGroup Label="Transitive Dependencies">

View File

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

View File

@ -109,15 +109,23 @@ namespace osu.Game.Rulesets.Catch.UI
comboDisplay.X = Catcher.X;
if (!lastHyperDashState && Catcher.HyperDashing && Time.Elapsed > 0)
catcherTrails.DisplayHyperDashAfterImage(Catcher.CurrentState, Catcher.X, Catcher.BodyScale);
if (Time.Elapsed <= 0)
{
// 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)
{
double generationInterval = Catcher.HyperDashing ? 25 : 50;
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;
@ -173,5 +181,7 @@ namespace osu.Game.Rulesets.Catch.UI
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.
using osu.Framework.Graphics;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Timing;
using osu.Game.Rulesets.Objects.Pooling;
using osuTK;
namespace osu.Game.Rulesets.Catch.UI
@ -12,13 +12,8 @@ namespace osu.Game.Rulesets.Catch.UI
/// A trail of the catcher.
/// It also represents a hyper dash afterimage.
/// </summary>
public class CatcherTrail : PoolableDrawable
public class CatcherTrail : PoolableDrawableWithLifetime<CatcherTrailEntry>
{
public CatcherAnimationState AnimationState
{
set => body.AnimationState.Value = value;
}
private readonly SkinnableCatcher body;
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();
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.
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
using osu.Game.Rulesets.Catch.Skinning;
using osu.Game.Rulesets.Objects.Pooling;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.UI
@ -16,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.UI
/// Represents a component responsible for displaying
/// the appropriate catcher trails when requested to.
/// </summary>
public class CatcherTrailDisplay : SkinReloadableDrawable
public class CatcherTrailDisplay : PooledDrawableWithLifetimeContainer<CatcherTrailEntry, CatcherTrail>
{
/// <summary>
/// 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;
protected override bool RemoveRewoundEntry => true;
private readonly DrawablePool<CatcherTrail> trailPool;
private readonly Container<CatcherTrail> dashTrails;
private readonly Container<CatcherTrail> hyperDashTrails;
private readonly Container<CatcherTrail> hyperDashAfterImages;
[Resolved]
private ISkinSource skin { get; set; }
public CatcherTrailDisplay()
{
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;
hyperDashAfterImages.Colour = skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashAfterImage)?.Value ?? hyperDashTrails.Colour;
}
/// <summary>
/// Displays a hyper-dash after-image of the catcher.
/// </summary>
public void DisplayHyperDashAfterImage(CatcherAnimationState animationState, float x, Vector2 scale)
protected override void AddDrawable(CatcherTrailEntry entry, CatcherTrail drawable)
{
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);
trail.ScaleTo(trail.Scale * 0.95f).ScaleTo(trail.Scale * 1.2f, 1200, Easing.In);
trail.FadeOut(1200);
trail.Expire(true);
case CatcherTrailAnimation.HyperDashAfterImage:
hyperDashAfterImages.Add(drawable);
break;
}
}
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)
hyperDashTrails.Add(trail);
else
dashTrails.Add(trail);
case CatcherTrailAnimation.HyperDashing:
hyperDashTrails.Remove(drawable);
break;
trail.FadeTo(0.4f).FadeOut(800, Easing.OutQuint);
trail.Expire(true);
case CatcherTrailAnimation.HyperDashAfterImage:
hyperDashAfterImages.Remove(drawable);
break;
}
}
private CatcherTrail createTrail(CatcherAnimationState animationState, float x, Vector2 scale)
protected override CatcherTrail GetDrawable(CatcherTrailEntry entry)
{
CatcherTrail trail = trailPool.Get();
trail.AnimationState = animationState;
trail.Scale = scale;
trail.Position = new Vector2(x, 0);
trail.Apply(entry);
return trail;
}
@ -107,5 +123,13 @@ namespace osu.Game.Rulesets.Catch.UI
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)
{
judgementAboveHitObjectLayer.Add(judgement.GetProxyAboveHitObjectsContent());
judgementAboveHitObjectLayer.Add(judgement.ProxiedAboveHitObjectsContent);
}
[BackgroundDependencyLoader(true)]
@ -150,6 +150,10 @@ namespace osu.Game.Rulesets.Osu.UI
DrawableOsuJudgement explosion = poolDictionary[result.Type].Get(doj => doj.Apply(result, judgedObject));
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);

View File

@ -2,10 +2,30 @@
// See the LICENCE file in the repository root for full licence text.
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
{
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
{
public class TaikoModHidden : ModHidden, IApplicableToDifficulty
public class TaikoModHidden : ModHidden
{
public override string Description => @"Beats fade out before you hit them!";
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;
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 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)
@ -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)
{
controlPointInfo = beatmap.ControlPointInfo;

View File

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

View File

@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Taiko.UI
/// <summary>
/// Default height of a <see cref="TaikoPlayfield"/> when inside a <see cref="DrawableTaikoRuleset"/>.
/// </summary>
public const float DEFAULT_HEIGHT = 178;
public const float DEFAULT_HEIGHT = 212;
private Container<HitExplosion> hitExplosionContainer;
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)
{
await AllowImport.Task;
return await (CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken));
await AllowImport.Task.ConfigureAwait(false);
return await (CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken)).ConfigureAwait(false);
}
}

View File

@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Online
[Test]
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);
AddStep("Set article 1", () => header.SetArticle("1"));
@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.Online
AddAssert("2 tabs total", () => header.TabCount == 2);
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);
}

View File

@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Online
[Test]
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
{
@ -54,8 +54,8 @@ namespace osu.Game.Tests.Visual.Online
AddAssert("Current is welcome", () => checkCurrent("Welcome"));
AddAssert("Check breadcrumb", checkBreadcrumb);
AddStep("Change current to index", () => header.Current.Value = "index");
AddAssert("Current is index", () => checkCurrent("index"));
AddStep("Change current to index", () => header.Current.Value = WikiHeader.IndexPageString);
AddAssert("Current is index", () => checkCurrent(WikiHeader.IndexPageString));
AddStep("Change wiki page data", () => wikiPageData.Value = new APIWikiPage
{
@ -71,7 +71,7 @@ namespace osu.Game.Tests.Visual.Online
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()
{

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)
{
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.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneLabelledColourPalette : OsuTestScene
public class TestSceneLabelledColourPalette : OsuManualInputManagerTestScene
{
private LabelledColourPalette component;
@ -30,21 +35,41 @@ namespace osu.Game.Tests.Visual.UserInterface
}, 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)
{
AddStep("create component", () =>
{
Child = new Container
Child = new OsuContextMenuContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 500,
AutoSizeAxes = Axes.Y,
Child = component = new LabelledColourPalette
RelativeSizeAxes = Axes.Both,
Child = new Container
{
Anchor = 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(),
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.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -11,6 +10,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Extensions;
using osu.Game.Graphics;
using osu.Game.Rulesets;
using osu.Game.Screens.Menu;
@ -198,8 +198,8 @@ namespace osu.Game.Tournament.Components
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new DiffPiece(("Length", TimeSpan.FromMilliseconds(length).ToString(@"mm\:ss"))),
new DiffPiece(("BPM", $"{bpm:0.#}"))
new DiffPiece(("Length", length.ToFormattedDuration().ToString())),
new DiffPiece(("BPM", $"{bpm:0.#}")),
}
},
new Container

View File

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

View File

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

View File

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

View File

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

View File

@ -10,8 +10,8 @@ namespace osu.Game.Graphics.UserInterface
[Description("default")]
Default,
[Description("soft")]
Soft,
[Description("submit")]
Submit,
[Description("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;
Shear = shear;

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
@ -12,6 +13,7 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Localisation;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osuTK;
namespace osu.Game.Graphics.UserInterfaceV2
@ -19,12 +21,16 @@ namespace osu.Game.Graphics.UserInterfaceV2
/// <summary>
/// A component which displays a colour along with related description text.
/// </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 Box fill;
private OsuSpriteText colourHexCode;
private OsuSpriteText colourName;
public Bindable<Colour4> Current
@ -63,26 +69,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
Spacing = new Vector2(0, 10),
Children = new Drawable[]
{
new OsuClickableContainer
new ColourCircle
{
RelativeSizeAxes = Axes.X,
Height = 100,
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
Current = { BindTarget = Current },
DeleteRequested = () => DeleteRequested?.Invoke(this)
},
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()
{
fill.Colour = current.Value;
colourHexCode.Text = current.Value.ToHex();
colourHexCode.Colour = OsuColour.ForegroundTextColourFor(current.Value);
}
private readonly Box fill;
private readonly OsuSpriteText colourHexCode;
public Popover GetPopover() => new OsuPopover(false)
{
Child = new OsuColourPicker
public ColourCircle()
{
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.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osuTK;
@ -36,36 +41,24 @@ namespace osu.Game.Graphics.UserInterfaceV2
}
}
private FillFlowContainer<ColourDisplay> palette;
private Container placeholder;
private FillFlowContainer palette;
private IEnumerable<ColourDisplay> colourDisplays => palette.OfType<ColourDisplay>();
[BackgroundDependencyLoader]
private void load()
{
RelativeSizeAxes = Axes.X;
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,
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)
}
}
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(10),
Direction = FillDirection.Full
};
}
@ -73,30 +66,20 @@ namespace osu.Game.Graphics.UserInterfaceV2
{
base.LoadComplete();
Colours.BindCollectionChanged((_, args) => updatePalette(args), true);
Colours.BindCollectionChanged((_, args) =>
{
if (args.Action != NotifyCollectionChangedAction.Replace)
updatePalette();
}, true);
FinishTransforms(true);
}
private const int fade_duration = 200;
private void updatePalette(NotifyCollectionChangedEventArgs args)
private void updatePalette()
{
if (args.Action == NotifyCollectionChangedAction.Replace)
return;
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)
{
// 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.DeleteRequested += colourDeletionRequested;
}
palette.Add(new AddColourButton
{
Action = () => Colours.Add(Colour4.White)
});
reindexItems();
}
private void colourDeletionRequested(ColourDisplay display) => Colours.RemoveAt(palette.IndexOf(display));
private void reindexItems()
{
int index = 1;
foreach (var colour in palette)
foreach (var colourDisplay in colourDisplays)
{
colour.ColourName = $"{colourNamePrefix} {index}";
colourDisplay.ColourName = $"{colourNamePrefix} {index}";
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
},
new HoverClickSounds(HoverSampleSet.Soft)
new HoverClickSounds()
});
}

View File

@ -57,7 +57,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
{
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 =>
{
if (ex.InnerException is WebException webException && webException.Message == @"Unauthorized")
if (ex is WebException webException && webException.Message == @"Unauthorized")
{
log.Add(@"Login no longer valid");
Logout();

View File

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

View File

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

View File

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

View File

@ -10,6 +10,7 @@ using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Extensions;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK;
@ -62,7 +63,7 @@ namespace osu.Game.Overlays.BeatmapSet
}
else
{
length.Value = TimeSpan.FromMilliseconds(beatmap.Length).ToString(@"m\:ss");
length.Value = TimeSpan.FromMilliseconds(beatmap.Length).ToFormattedDuration();
circleCount.Value = beatmap.OnlineInfo.CircleCount.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.Effects;
using osu.Game.Beatmaps;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets;
using osuTK;
using osuTK.Graphics;
@ -54,7 +55,7 @@ namespace osu.Game.Overlays.BeatmapSet
{
public BeatmapHeaderTitle()
{
Title = "beatmap info";
Title = PageTitleStrings.MainBeatmapsetsControllerShow;
IconTexture = "Icons/Hexacons/beatmap";
}
}

View File

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

View File

@ -3,6 +3,9 @@
using System;
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.Graphics;
using osu.Framework.Graphics.Containers;
@ -13,6 +16,7 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Chat;
using osuTK;
using osuTK.Graphics;
@ -39,6 +43,8 @@ namespace osu.Game.Overlays.Chat.Tabs
protected override Container<Drawable> Content => content;
private Sample sampleTabSwitched;
public ChannelTabItem(Channel 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]
private void load(OsuColour colours)
private void load(OsuColour colours, AudioManager audio)
{
BackgroundActive = colours.ChatBlue;
BackgroundInactive = colours.Gray4;
backgroundHover = colours.Gray7;
sampleTabSwitched = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select");
highlightBox.Colour = colours.Yellow;
}
@ -217,7 +225,14 @@ namespace osu.Game.Overlays.Chat.Tabs
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();
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -15,13 +15,18 @@ using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites;
using System.Diagnostics;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Platform;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.News.Sidebar
{
public class MonthSection : CompositeDrawable
{
private const int animation_duration = 250;
private Sample sampleOpen;
private Sample sampleClose;
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
@ -59,6 +79,8 @@ namespace osu.Game.Overlays.News.Sidebar
private readonly SpriteIcon icon;
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
public DropdownHeader(int month, int year)
{
var date = new DateTime(year, month, 1);
@ -104,6 +126,7 @@ namespace osu.Game.Overlays.News.Sidebar
private readonly APINewsPost post;
public PostButton(APINewsPost post)
: base(HoverSampleSet.Submit)
{
this.post = post;

View File

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

View File

@ -3,6 +3,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Resources.Localisation.Web;
@ -52,7 +53,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
new OsuSpriteText
{
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.Bindables;
using osu.Game.Localisation;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets;
using osu.Game.Users;
@ -29,18 +31,10 @@ namespace osu.Game.Overlays.Rankings
{
public RankingsTitle()
{
Title = "ranking";
Description = "find out who's the best right now";
Title = PageTitleStrings.MainRankingControllerDefault;
Description = NamedOverlayComponentStrings.RankingsDescription;
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.
// 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
{
public class RankingsSortTabControl : OverlaySortTabControl<RankingsSortCriteria>
{
public RankingsSortTabControl()
{
Title = "Show";
Title = RankingsStrings.FilterTitle.ToUpper();
}
}
[LocalisableEnum(typeof(RankingsSortCriteriaEnumLocalisationMapper))]
public enum RankingsSortCriteria
{
All,
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.Game.Online.API.Requests;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.Rankings
{
@ -92,10 +94,10 @@ namespace osu.Game.Overlays.Rankings
Margin = new MarginPadding { Bottom = 5 },
Children = new Drawable[]
{
startDateColumn = new InfoColumn(@"Start Date"),
endDateColumn = new InfoColumn(@"End Date"),
mapCountColumn = new InfoColumn(@"Map Count"),
participantsColumn = new InfoColumn(@"Participants")
startDateColumn = new InfoColumn(RankingsStrings.SpotlightStartDate),
endDateColumn = new InfoColumn(RankingsStrings.SpotlightEndDate),
mapCountColumn = new InfoColumn(RankingsStrings.SpotlightMapCount),
participantsColumn = new InfoColumn(RankingsStrings.SpotlightParticipants)
}
},
new RankingsSortTabControl
@ -122,22 +124,22 @@ namespace osu.Game.Overlays.Rankings
{
startDateColumn.Value = dateToString(response.Spotlight.StartDate);
endDateColumn.Value = dateToString(response.Spotlight.EndDate);
mapCountColumn.Value = response.BeatmapSets.Count.ToString();
participantsColumn.Value = response.Spotlight.Participants?.ToString("N0");
mapCountColumn.Value = response.BeatmapSets.Count.ToLocalisableString(@"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
{
public string Value
public LocalisableString Value
{
set => valueText.Text = value;
}
private readonly OsuSpriteText valueText;
public InfoColumn(string name)
public InfoColumn(LocalisableString name)
{
AutoSizeAxes = Axes.Both;
Direction = FillDirection.Vertical;

View File

@ -9,6 +9,8 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Game.Resources.Localisation.Web;
using osu.Framework.Localisation;
namespace osu.Game.Overlays.Rankings.Tables
{
@ -21,12 +23,12 @@ namespace osu.Game.Overlays.Rankings.Tables
protected override RankingsTableColumn[] CreateAdditionalHeaders() => new[]
{
new RankingsTableColumn("Active Users", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn("Ranked Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn("Avg. Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn("Performance", Anchor.Centre, new Dimension(GridSizeMode.AutoSize), true),
new RankingsTableColumn("Avg. Perf.", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn(RankingsStrings.StatActiveUsers, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn(RankingsStrings.StatPlayCount, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn(RankingsStrings.StatRankedScore, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn(RankingsStrings.StatAverageScore, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn(RankingsStrings.StatPerformance, Anchor.Centre, new Dimension(GridSizeMode.AutoSize), true),
new RankingsTableColumn(RankingsStrings.StatAveragePerformance, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
};
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[]
{
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
{
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 osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Users;
namespace osu.Game.Overlays.Rankings.Tables
@ -17,12 +19,12 @@ namespace osu.Game.Overlays.Rankings.Tables
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[]
{
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
{
Text = $"#{index + 1}",
Text = (index + 1).ToLocalisableString(@"\##"),
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]
private void load(OverlayColourProvider colourProvider)

View File

@ -4,6 +4,8 @@
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Users;
namespace osu.Game.Overlays.Rankings.Tables
@ -17,19 +19,19 @@ namespace osu.Game.Overlays.Rankings.Tables
protected override RankingsTableColumn[] CreateUniqueHeaders() => new[]
{
new RankingsTableColumn("Total Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn("Ranked Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize), true)
new RankingsTableColumn(RankingsStrings.StatTotalScore, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn(RankingsStrings.StatRankedScore, Anchor.Centre, new Dimension(GridSizeMode.AutoSize), true)
};
protected override Drawable[] CreateUniqueContent(UserStatistics item) => new Drawable[]
{
new ColoredRowText
new ColouredRowText
{
Text = $@"{item.TotalScore:N0}",
Text = item.TotalScore.ToLocalisableString(@"N0"),
},
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.Scoring;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
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[]
{
new RankingsTableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn(RankingsStrings.StatAccuracy, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn(RankingsStrings.StatPlayCount, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
}.Concat(CreateUniqueHeaders())
.Concat(GradeColumns.Select(grade => new GradeTableColumn(grade, Anchor.Centre, new Dimension(GridSizeMode.AutoSize))))
.ToArray();
@ -46,13 +47,13 @@ namespace osu.Game.Overlays.Rankings.Tables
protected sealed override Drawable[] CreateAdditionalContent(UserStatistics item) => new[]
{
new ColoredRowText { Text = item.DisplayAccuracy, },
new ColoredRowText { Text = $@"{item.PlayCount:N0}", },
new ColouredRowText { Text = item.DisplayAccuracy, },
new ColouredRowText { Text = item.PlayCount.ToLocalisableString(@"N0") },
}.Concat(CreateUniqueContent(item)).Concat(new[]
{
new ColoredRowText { Text = $@"{item.GradesCount[ScoreRank.XH] + item.GradesCount[ScoreRank.X]:N0}", },
new ColoredRowText { Text = $@"{item.GradesCount[ScoreRank.SH] + item.GradesCount[ScoreRank.S]:N0}", },
new ColoredRowText { Text = $@"{item.GradesCount[ScoreRank.A]:N0}", }
new ColouredRowText { Text = (item.GradesCount[ScoreRank.XH] + item.GradesCount[ScoreRank.X]).ToLocalisableString(@"N0"), },
new ColouredRowText { Text = (item.GradesCount[ScoreRank.SH] + item.GradesCount[ScoreRank.S]).ToLocalisableString(@"N0"), },
new ColouredRowText { Text = item.GradesCount[ScoreRank.A].ToLocalisableString(@"N0"), }
}).ToArray();
protected abstract RankingsTableColumn[] CreateUniqueHeaders();

View File

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

View File

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

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Diagnostics;
using JetBrains.Annotations;
using osu.Framework.Allocation;
@ -31,6 +32,9 @@ namespace osu.Game.Rulesets.Judgements
private readonly Container aboveHitObjectsContent;
private readonly Lazy<Drawable> proxiedAboveHitObjectsContent;
public Drawable ProxiedAboveHitObjectsContent => proxiedAboveHitObjectsContent.Value;
/// <summary>
/// Creates a drawable which visualises a <see cref="Judgements.Judgement"/>.
/// </summary>
@ -52,6 +56,8 @@ namespace osu.Game.Rulesets.Judgements
Depth = float.MinValue,
RelativeSizeAxes = Axes.Both
});
proxiedAboveHitObjectsContent = new Lazy<Drawable>(() => aboveHitObjectsContent.CreateProxy());
}
[BackgroundDependencyLoader]
@ -60,8 +66,6 @@ namespace osu.Game.Rulesets.Judgements
prepareDrawables();
}
public Drawable GetProxyAboveHitObjectsContent() => aboveHitObjectsContent.CreateProxy();
/// <summary>
/// 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.

View File

@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Mods
public interface IApplicableToHealthProcessor : IApplicableMod
{
/// <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>
void ApplyToHealthProcessor(HealthProcessor healthProcessor);
}

View File

@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mods
public interface IApplicableToScoreProcessor : IApplicableMod
{
/// <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>
void ApplyToScoreProcessor(ScoreProcessor scoreProcessor);

View File

@ -1,9 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics;
using osu.Framework.Bindables;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.Containers;
@ -11,11 +11,11 @@ using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mods
{
public class Metronome : BeatSyncedContainer
public class Metronome : BeatSyncedContainer, IAdjustableAudioComponent
{
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>
public Metronome(double firstHitTime)
@ -23,15 +23,8 @@ namespace osu.Game.Rulesets.Mods
this.firstHitTime = firstHitTime;
AllowMistimedEventFiring = false;
Divisor = 1;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChildren = new Drawable[]
{
sample = new PausableSkinnableSound(new SampleInfo("Gameplay/catch-banana"))
};
InternalChild = sample = new PausableSkinnableSound(new SampleInfo("Gameplay/catch-banana"));
}
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.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.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Configuration;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
namespace osu.Game.Rulesets.Mods
{
@ -22,27 +26,79 @@ namespace osu.Game.Rulesets.Mods
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
{
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
{
Default = 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)
{
track.AddAdjustment(AdjustableProperty.Volume, volumeAdjust);
track.AddAdjustment(AdjustableProperty.Volume, mainVolumeAdjust);
}
public void ApplyToDrawableRuleset(DrawableRuleset<TObject> drawableRuleset)
{
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.
// 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.Collections.Generic;
using System.Linq;
using System.Threading;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics.Cursor;
using osu.Game.Input.Handlers;
using osu.Game.Overlays;
using osu.Game.Replays;
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.Scoring;
using osu.Game.Screens.Play;
@ -98,6 +99,14 @@ namespace osu.Game.Rulesets.UI
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>
/// Creates a ruleset visualisation for the provided ruleset and beatmap.
/// </summary>
@ -155,23 +164,28 @@ namespace osu.Game.Rulesets.UI
[BackgroundDependencyLoader]
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,
Children = new Drawable[]
FrameStableComponents,
audioContainer = new AudioContainer
{
FrameStableComponents,
KeyBindingInputManager
RelativeSizeAxes = Axes.Both,
Child = KeyBindingInputManager
.WithChild(CreatePlayfieldAdjustmentContainer()
.WithChild(Playfield)
),
Overlays,
}
},
},
Overlays,
}
};
Audio = audioContainer;
if ((ResumeOverlay = CreateResumeOverlay()) != null)
{
AddInternal(CreateInputManager()

View File

@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
/// <summary>
/// The maximum span of time that may be visible by the length of the scrolling axes.
/// </summary>
private const double time_span_max = 10000;
private const double time_span_max = 20000;
/// <summary>
/// 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.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osuTK.Graphics;
@ -67,7 +66,6 @@ namespace osu.Game.Screens.Edit
private EditorClock clock { get; set; }
public RowBackground(object item)
: base(HoverSampleSet.Soft)
{
Item = item;

View File

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

View File

@ -178,7 +178,7 @@ namespace osu.Game.Screens.Play
float barHeight = bottom_bar_height + handle_size.Y;
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();
}

View File

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

View File

@ -14,6 +14,7 @@ using osu.Game.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
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 BeatmapOptionsButton()
: base(HoverSampleSet.Submit)
{
Width = width;
RelativeSizeAxes = Axes.Y;

View File

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

View File

@ -5,6 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
namespace osu.Game.Users.Drawables
@ -32,9 +33,17 @@ namespace osu.Game.Users.Drawables
if (country == null && !ShowPlaceholderOnNull)
return null;
return new DrawableFlag(country)
return new Container
{
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 UserPanel(User user)
: base(HoverSampleSet.Submit)
{
if (user == null)
throw new ArgumentNullException(nameof(user));

View File

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

View File

@ -71,7 +71,7 @@
</ItemGroup>
<ItemGroup Label="Package References">
<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>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
<PropertyGroup>

View File

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