diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj
index 582c856a47..b2599535ae 100644
--- a/osu.Android/osu.Android.csproj
+++ b/osu.Android/osu.Android.csproj
@@ -64,7 +64,7 @@
-
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index 8993a9b18a..c1c2ea2299 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -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);
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs
index d7af47a835..6fafb8f87a 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs
@@ -1,16 +1,21 @@
// Copyright (c) ppy Pty Ltd . 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().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().Single());
+ InputManager.Click(MouseButton.Left);
+ });
+
+ private void clickFirstColour() => AddStep("click first colour", () =>
+ {
+ InputManager.MoveMouseTo(this.ChildrenOfType().First());
+ InputManager.Click(MouseButton.Left);
+ });
+
+ private void deleteFirstColour()
+ {
+ AddStep("right-click first colour", () =>
+ {
+ InputManager.MoveMouseTo(this.ChildrenOfType().First());
+ InputManager.Click(MouseButton.Right);
+ });
+
+ AddUntilStep("wait for menu", () => this.ChildrenOfType().Any());
+
+ AddStep("click delete", () =>
+ {
+ InputManager.MoveMouseTo(this.ChildrenOfType().Single());
+ InputManager.Click(MouseButton.Left);
+ });
+ }
}
}
diff --git a/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs b/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs
index 25f89fdcaa..5240df74a2 100644
--- a/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs
+++ b/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . 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
///
/// A component which displays a colour along with related description text.
///
- public class ColourDisplay : CompositeDrawable, IHasCurrentValue, IHasPopover
+ public class ColourDisplay : CompositeDrawable, IHasCurrentValue
{
+ ///
+ /// Invoked when the user has requested the colour corresponding to this
+ /// to be removed from its palette.
+ ///
+ public event Action DeleteRequested;
+
private readonly BindableWithCurrent current = new BindableWithCurrent();
- private Box fill;
- private OsuSpriteText colourHexCode;
private OsuSpriteText colourName;
public Bindable 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 Current { get; } = new Bindable();
- 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())
+ };
+ }
}
}
diff --git a/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs b/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs
index d8edd00c16..a966f61b74 100644
--- a/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs
+++ b/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs
@@ -1,12 +1,17 @@
// Copyright (c) ppy Pty Ltd . 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 palette;
- private Container placeholder;
+ private FillFlowContainer palette;
+
+ private IEnumerable colourDisplays => palette.OfType();
[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
- {
- 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;
+ }
+ }
}
}
diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs
index 63305d004c..4e4a665a60 100644
--- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs
+++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs
@@ -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%"))
}
}
};
diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs
index 0f22d35bb5..d25d46c6e2 100644
--- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs
+++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . 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 proxiedAboveHitObjectsContent;
+ public Drawable ProxiedAboveHitObjectsContent => proxiedAboveHitObjectsContent.Value;
+
///
/// Creates a drawable which visualises a .
///
@@ -52,6 +56,8 @@ namespace osu.Game.Rulesets.Judgements
Depth = float.MinValue,
RelativeSizeAxes = Axes.Both
});
+
+ proxiedAboveHitObjectsContent = new Lazy(() => aboveHitObjectsContent.CreateProxy());
}
[BackgroundDependencyLoader]
@@ -60,8 +66,6 @@ namespace osu.Game.Rulesets.Judgements
prepareDrawables();
}
- public Drawable GetProxyAboveHitObjectsContent() => aboveHitObjectsContent.CreateProxy();
-
///
/// Apply top-level animations to the current judgement when successfully hit.
/// If displaying components which require lifetime extensions, manually adjusting is required.
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index db6ec1f4a9..09eaf1c543 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -297,6 +297,8 @@ namespace osu.Game.Screens.Play
ScoreProcessor.HasCompleted.BindValueChanged(scoreCompletionChanged);
HealthProcessor.Failed += onFail;
+ // 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())
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 936fe8db94..7e32f1e9fd 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -22,7 +22,7 @@
-
+
@@ -38,7 +38,7 @@
-
+
diff --git a/osu.iOS/osu.iOS.csproj b/osu.iOS/osu.iOS.csproj
index 1cbe4422cc..1203c3659b 100644
--- a/osu.iOS/osu.iOS.csproj
+++ b/osu.iOS/osu.iOS.csproj
@@ -117,7 +117,7 @@
-
+