diff --git a/osu.Android.props b/osu.Android.props
index 8d79eb94a8..82dec74855 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,7 +52,7 @@
-
+
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneButtonSystemNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneButtonSystemNavigation.cs
new file mode 100644
index 0000000000..8c96ec699f
--- /dev/null
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneButtonSystemNavigation.cs
@@ -0,0 +1,46 @@
+// 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.Testing;
+using osu.Game.Screens.Menu;
+using osu.Game.Screens.Select;
+using osuTK.Input;
+
+namespace osu.Game.Tests.Visual.Navigation
+{
+ public class TestSceneButtonSystemNavigation : OsuGameTestScene
+ {
+ private ButtonSystem buttons => ((MainMenu)Game.ScreenStack.CurrentScreen).ChildrenOfType().Single();
+
+ [Test]
+ public void TestGlobalActionHasPriority()
+ {
+ AddAssert("state is initial", () => buttons.State == ButtonSystemState.Initial);
+
+ // triggering the cookie in the initial state with any key should only happen if no other action is bound to that key.
+ // here, F10 is bound to GlobalAction.ToggleGameplayMouseButtons.
+ AddStep("press F10", () => InputManager.Key(Key.F10));
+ AddAssert("state is initial", () => buttons.State == ButtonSystemState.Initial);
+
+ AddStep("press P", () => InputManager.Key(Key.P));
+ AddAssert("state is top level", () => buttons.State == ButtonSystemState.TopLevel);
+ }
+
+ [Test]
+ public void TestShortcutKeys()
+ {
+ AddAssert("state is initial", () => buttons.State == ButtonSystemState.Initial);
+
+ AddStep("press P", () => InputManager.Key(Key.P));
+ AddAssert("state is top level", () => buttons.State == ButtonSystemState.TopLevel);
+
+ AddStep("press P", () => InputManager.Key(Key.P));
+ AddAssert("state is play", () => buttons.State == ButtonSystemState.Play);
+
+ AddStep("press P", () => InputManager.Key(Key.P));
+ AddAssert("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs
index 1bb5cadc6a..1a879e2e70 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs
@@ -10,11 +10,12 @@ using osu.Framework.Graphics.Shapes;
using osu.Game.Screens.Menu;
using osuTK;
using osuTK.Graphics;
+using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface
{
[TestFixture]
- public class TestSceneButtonSystem : OsuTestScene
+ public class TestSceneButtonSystem : OsuManualInputManagerTestScene
{
private OsuLogo logo;
private ButtonSystem buttons;
@@ -64,6 +65,66 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("Enter mode", performEnterMode);
}
+ [TestCase(Key.P, true)]
+ [TestCase(Key.M, true)]
+ [TestCase(Key.L, true)]
+ [TestCase(Key.E, false)]
+ [TestCase(Key.D, false)]
+ [TestCase(Key.Q, false)]
+ [TestCase(Key.O, false)]
+ public void TestShortcutKeys(Key key, bool entersPlay)
+ {
+ int activationCount = -1;
+ AddStep("set up action", () =>
+ {
+ activationCount = 0;
+ void action() => activationCount++;
+
+ switch (key)
+ {
+ case Key.P:
+ buttons.OnSolo = action;
+ break;
+
+ case Key.M:
+ buttons.OnMultiplayer = action;
+ break;
+
+ case Key.L:
+ buttons.OnPlaylists = action;
+ break;
+
+ case Key.E:
+ buttons.OnEdit = action;
+ break;
+
+ case Key.D:
+ buttons.OnBeatmapListing = action;
+ break;
+
+ case Key.Q:
+ buttons.OnExit = action;
+ break;
+
+ case Key.O:
+ buttons.OnSettings = action;
+ break;
+ }
+ });
+
+ AddStep($"press {key}", () => InputManager.Key(key));
+ AddAssert("state is top level", () => buttons.State == ButtonSystemState.TopLevel);
+
+ if (entersPlay)
+ {
+ AddStep("press P", () => InputManager.Key(Key.P));
+ AddAssert("state is play", () => buttons.State == ButtonSystemState.Play);
+ }
+
+ AddStep($"press {key}", () => InputManager.Key(key));
+ AddAssert("action triggered", () => activationCount == 1);
+ }
+
private void performEnterMode()
{
buttons.State = ButtonSystemState.EnteringMode;
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs
index efce4f350b..31c4d66784 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs
@@ -66,6 +66,7 @@ namespace osu.Game.Tests.Visual.UserInterface
}
[Test]
+ [Ignore("Enable when first run setup is being displayed on first run.")]
public void TestDoesntOpenOnSecondRun()
{
AddStep("set first run", () => LocalConfig.SetValue(OsuSetting.ShowFirstRunSetup, true));
@@ -165,7 +166,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("click outside content", () =>
{
- InputManager.MoveMouseTo(overlay.ScreenSpaceDrawQuad.TopLeft - new Vector2(1));
+ InputManager.MoveMouseTo(new Vector2(overlay.ScreenSpaceDrawQuad.TopLeft.X, overlay.ScreenSpaceDrawQuad.Centre.Y));
InputManager.Click(MouseButton.Left);
});
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPanel.cs
index 95323e5dfa..f56d9c8a91 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModPanel.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPanel.cs
@@ -47,12 +47,22 @@ namespace osu.Game.Tests.Visual.UserInterface
{
IncompatibilityDisplayingModPanel panel = null;
- AddStep("create panel with DT", () => Child = panel = new IncompatibilityDisplayingModPanel(new OsuModDoubleTime())
+ AddStep("create panel with DT", () =>
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.None,
- Width = 300
+ Child = panel = new IncompatibilityDisplayingModPanel(new OsuModDoubleTime())
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.None,
+ Width = 300,
+ };
+
+ panel.Active.BindValueChanged(active =>
+ {
+ SelectedMods.Value = active.NewValue
+ ? Array.Empty()
+ : new[] { panel.Mod };
+ });
});
clickPanel();
@@ -63,11 +73,6 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("set incompatible mod", () => SelectedMods.Value = new[] { new OsuModHalfTime() });
- clickPanel();
- AddAssert("panel not active", () => !panel.Active.Value);
-
- AddStep("reset mods", () => SelectedMods.Value = Array.Empty());
-
clickPanel();
AddAssert("panel active", () => panel.Active.Value);
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs
index 4a738cb29d..514538161e 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.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.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@@ -89,6 +90,27 @@ namespace osu.Game.Tests.Visual.UserInterface
changeRuleset(3);
}
+ [Test]
+ public void TestIncompatibilityToggling()
+ {
+ createScreen();
+ changeRuleset(0);
+
+ AddStep("activate DT", () => getPanelForMod(typeof(OsuModDoubleTime)).TriggerClick());
+ AddAssert("DT active", () => SelectedMods.Value.Single().GetType() == typeof(OsuModDoubleTime));
+
+ AddStep("activate NC", () => getPanelForMod(typeof(OsuModNightcore)).TriggerClick());
+ AddAssert("only NC active", () => SelectedMods.Value.Single().GetType() == typeof(OsuModNightcore));
+
+ AddStep("activate HR", () => getPanelForMod(typeof(OsuModHardRock)).TriggerClick());
+ AddAssert("NC+HR active", () => SelectedMods.Value.Any(mod => mod.GetType() == typeof(OsuModNightcore))
+ && SelectedMods.Value.Any(mod => mod.GetType() == typeof(OsuModHardRock)));
+
+ AddStep("activate MR", () => getPanelForMod(typeof(OsuModMirror)).TriggerClick());
+ AddAssert("NC+MR active", () => SelectedMods.Value.Any(mod => mod.GetType() == typeof(OsuModNightcore))
+ && SelectedMods.Value.Any(mod => mod.GetType() == typeof(OsuModMirror)));
+ }
+
[Test]
public void TestCustomisationToggleState()
{
@@ -136,5 +158,8 @@ namespace osu.Game.Tests.Visual.UserInterface
AddAssert($"customisation toggle is {(disabled ? "" : "not ")}disabled", () => getToggle().Active.Disabled == disabled);
AddAssert($"customisation toggle is {(active ? "" : "not ")}active", () => getToggle().Active.Value == active);
}
+
+ private ModPanel getPanelForMod(Type modType)
+ => modSelectScreen.ChildrenOfType().Single(panel => panel.Mod.GetType() == modType);
}
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSectionsContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSectionsContainer.cs
index 2312c57af2..1f3736bd9b 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneSectionsContainer.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSectionsContainer.cs
@@ -3,45 +3,79 @@
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
+using osu.Framework.Utils;
+using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.Sprites;
using osuTK.Graphics;
+using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneSectionsContainer : OsuManualInputManagerTestScene
{
- private readonly SectionsContainer container;
+ private SectionsContainer container;
private float custom;
- private const float header_height = 100;
- public TestSceneSectionsContainer()
+ private const float header_expandable_height = 300;
+ private const float header_fixed_height = 100;
+
+ [SetUpSteps]
+ public void SetUpSteps()
{
- container = new SectionsContainer
+ AddStep("setup container", () =>
{
- RelativeSizeAxes = Axes.Y,
- Width = 300,
- Origin = Anchor.Centre,
- Anchor = Anchor.Centre,
- FixedHeader = new Box
+ container = new SectionsContainer
{
- Alpha = 0.5f,
+ RelativeSizeAxes = Axes.Y,
Width = 300,
- Height = header_height,
- Colour = Color4.Red
- }
- };
- container.SelectedSection.ValueChanged += section =>
- {
- if (section.OldValue != null)
- section.OldValue.Selected = false;
- if (section.NewValue != null)
- section.NewValue.Selected = true;
- };
- Add(container);
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ };
+
+ container.SelectedSection.ValueChanged += section =>
+ {
+ if (section.OldValue != null)
+ section.OldValue.Selected = false;
+ if (section.NewValue != null)
+ section.NewValue.Selected = true;
+ };
+
+ Child = container;
+ });
+
+ AddToggleStep("disable expandable header", v => container.ExpandableHeader = v
+ ? null
+ : new TestBox(@"Expandable Header")
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = header_expandable_height,
+ BackgroundColour = new OsuColour().GreySky,
+ });
+
+ AddToggleStep("disable fixed header", v => container.FixedHeader = v
+ ? null
+ : new TestBox(@"Fixed Header")
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = header_fixed_height,
+ BackgroundColour = new OsuColour().Red.Opacity(0.5f),
+ });
+
+ AddToggleStep("disable footer", v => container.Footer = v
+ ? null
+ : new TestBox("Footer")
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = 200,
+ BackgroundColour = new OsuColour().Green4,
+ });
}
[Test]
@@ -71,7 +105,6 @@ namespace osu.Game.Tests.Visual.UserInterface
{
const int sections_count = 11;
float[] alternating = { 0.07f, 0.33f, 0.16f, 0.33f };
- AddStep("clear", () => container.Clear());
AddStep("fill with sections", () =>
{
for (int i = 0; i < sections_count; i++)
@@ -84,9 +117,9 @@ namespace osu.Game.Tests.Visual.UserInterface
AddUntilStep("correct section selected", () => container.SelectedSection.Value == container.Children[scrollIndex]);
AddUntilStep("section top is visible", () =>
{
- float scrollPosition = container.ChildrenOfType().First().Current;
- float sectionTop = container.Children[scrollIndex].BoundingBox.Top;
- return scrollPosition < sectionTop;
+ var scrollContainer = container.ChildrenOfType().Single();
+ float sectionPosition = scrollContainer.GetChildPosInContent(container.Children[scrollIndex]);
+ return scrollContainer.Current < sectionPosition;
});
}
@@ -101,15 +134,56 @@ namespace osu.Game.Tests.Visual.UserInterface
AddUntilStep("correct section selected", () => container.SelectedSection.Value == container.Children[sections_count - 1]);
}
- private static readonly ColourInfo selected_colour = ColourInfo.GradientVertical(Color4.Yellow, Color4.Gold);
+ [Test]
+ public void TestNavigation()
+ {
+ AddRepeatStep("add sections", () => append(1f), 3);
+ AddUntilStep("wait for load", () => container.Children.Any());
+
+ AddStep("hover sections container", () => InputManager.MoveMouseTo(container));
+ AddStep("press page down", () => InputManager.Key(Key.PageDown));
+ AddUntilStep("scrolled one page down", () =>
+ {
+ var scroll = container.ChildrenOfType().First();
+ return Precision.AlmostEquals(scroll.Current, Content.DrawHeight - header_fixed_height, 1f);
+ });
+
+ AddStep("press page down", () => InputManager.Key(Key.PageDown));
+ AddUntilStep("scrolled two pages down", () =>
+ {
+ var scroll = container.ChildrenOfType().First();
+ return Precision.AlmostEquals(scroll.Current, (Content.DrawHeight - header_fixed_height) * 2, 1f);
+ });
+
+ AddStep("press page up", () => InputManager.Key(Key.PageUp));
+ AddUntilStep("scrolled one page up", () =>
+ {
+ var scroll = container.ChildrenOfType().First();
+ return Precision.AlmostEquals(scroll.Current, Content.DrawHeight - header_fixed_height, 1f);
+ });
+ }
+
+ private static readonly ColourInfo selected_colour = ColourInfo.GradientVertical(new OsuColour().Orange2, new OsuColour().Orange3);
private static readonly ColourInfo default_colour = ColourInfo.GradientVertical(Color4.White, Color4.DarkGray);
private void append(float multiplier)
{
- container.Add(new TestSection
+ float fixedHeaderHeight = container.FixedHeader?.Height ?? 0;
+ float expandableHeaderHeight = container.ExpandableHeader?.Height ?? 0;
+
+ float totalHeaderHeight = expandableHeaderHeight + fixedHeaderHeight;
+ float effectiveHeaderHeight = totalHeaderHeight;
+
+ // if we're in the "next page" of the sections container,
+ // height of the expandable header should not be accounted.
+ var scrollContent = container.ChildrenOfType().Single().ScrollContent;
+ if (totalHeaderHeight + scrollContent.Height >= Content.DrawHeight)
+ effectiveHeaderHeight -= expandableHeaderHeight;
+
+ container.Add(new TestSection($"Section #{container.Children.Count + 1}")
{
Width = 300,
- Height = (container.ChildSize.Y - header_height) * multiplier,
+ Height = (Content.DrawHeight - effectiveHeaderHeight) * multiplier,
Colour = default_colour
});
}
@@ -120,11 +194,50 @@ namespace osu.Game.Tests.Visual.UserInterface
InputManager.ScrollVerticalBy(direction);
}
- private class TestSection : Box
+ private class TestSection : TestBox
{
public bool Selected
{
- set => Colour = value ? selected_colour : default_colour;
+ set => BackgroundColour = value ? selected_colour : default_colour;
+ }
+
+ public TestSection(string label)
+ : base(label)
+ {
+ BackgroundColour = default_colour;
+ }
+ }
+
+ private class TestBox : Container
+ {
+ private readonly Box background;
+ private readonly OsuSpriteText text;
+
+ public ColourInfo BackgroundColour
+ {
+ set
+ {
+ background.Colour = value;
+ text.Colour = OsuColour.ForegroundTextColourFor(value.AverageColour);
+ }
+ }
+
+ public TestBox(string label)
+ {
+ Children = new Drawable[]
+ {
+ background = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ text = new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = label,
+ Font = OsuFont.Default.With(size: 36),
+ }
+ };
}
}
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneShearedOverlayContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneShearedOverlayContainer.cs
new file mode 100644
index 0000000000..5a9cafde27
--- /dev/null
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneShearedOverlayContainer.cs
@@ -0,0 +1,102 @@
+// 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.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Testing;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Overlays;
+using osu.Game.Overlays.Mods;
+using osuTK;
+using osuTK.Graphics;
+using osuTK.Input;
+
+namespace osu.Game.Tests.Visual.UserInterface
+{
+ [TestFixture]
+ public class TestSceneShearedOverlayContainer : OsuManualInputManagerTestScene
+ {
+ private TestShearedOverlayContainer overlay;
+
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("create overlay", () =>
+ {
+ Child = overlay = new TestShearedOverlayContainer
+ {
+ State = { Value = Visibility.Visible }
+ };
+ });
+ }
+
+ [Test]
+ public void TestClickAwayToExit()
+ {
+ AddStep("click inside header", () =>
+ {
+ InputManager.MoveMouseTo(overlay.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre);
+ InputManager.Click(MouseButton.Left);
+ });
+
+ AddAssert("overlay not dismissed", () => overlay.State.Value == Visibility.Visible);
+
+ AddStep("click inside content", () =>
+ {
+ InputManager.MoveMouseTo(overlay.ScreenSpaceDrawQuad.Centre);
+ InputManager.Click(MouseButton.Left);
+ });
+
+ AddAssert("overlay not dismissed", () => overlay.State.Value == Visibility.Visible);
+
+ AddStep("click outside header", () =>
+ {
+ InputManager.MoveMouseTo(new Vector2(overlay.ScreenSpaceDrawQuad.TopLeft.X, overlay.ScreenSpaceDrawQuad.Centre.Y));
+ InputManager.Click(MouseButton.Left);
+ });
+
+ AddAssert("overlay dismissed", () => overlay.State.Value == Visibility.Hidden);
+ }
+
+ public class TestShearedOverlayContainer : ShearedOverlayContainer
+ {
+ protected override OverlayColourScheme ColourScheme => OverlayColourScheme.Green;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Header.Title = "Sheared overlay header";
+ Header.Description = string.Join(" ", Enumerable.Repeat("This is a description.", 20));
+
+ MainAreaContent.Child = new InputBlockingContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(0.9f),
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.Blue,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new OsuSpriteText
+ {
+ Font = OsuFont.Default.With(size: 24),
+ Text = "Content",
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }
+ }
+ };
+ }
+ }
+ }
+}
diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs
index 823ba33216..6ad538959e 100644
--- a/osu.Game/Graphics/Containers/SectionsContainer.cs
+++ b/osu.Game/Graphics/Containers/SectionsContainer.cs
@@ -149,13 +149,11 @@ namespace osu.Game.Graphics.Containers
{
lastKnownScroll = null;
- float fixedHeaderSize = FixedHeader?.BoundingBox.Height ?? 0;
-
// implementation similar to ScrollIntoView but a bit more nuanced.
float top = scrollContainer.GetChildPosInContent(target);
- float bottomScrollExtent = scrollContainer.ScrollableExtent - fixedHeaderSize;
- float scrollTarget = top - fixedHeaderSize - scrollContainer.DisplayableContent * scroll_y_centre;
+ float bottomScrollExtent = scrollContainer.ScrollableExtent;
+ float scrollTarget = top - scrollContainer.DisplayableContent * scroll_y_centre;
if (scrollTarget > bottomScrollExtent)
scrollContainer.ScrollToEnd();
@@ -267,9 +265,13 @@ namespace osu.Game.Graphics.Containers
{
if (!Children.Any()) return;
- var newMargin = originalSectionsMargin;
+ // if a fixed header is present, apply top padding for it
+ // to make the scroll container aware of its displayable area.
+ // (i.e. for page up/down to work properly)
+ scrollContainer.Padding = new MarginPadding { Top = FixedHeader?.LayoutSize.Y ?? 0 };
- newMargin.Top += (headerHeight ?? 0);
+ var newMargin = originalSectionsMargin;
+ newMargin.Top += (ExpandableHeader?.LayoutSize.Y ?? 0);
newMargin.Bottom += (footerHeight ?? 0);
scrollContentContainer.Margin = newMargin;
diff --git a/osu.Game/Graphics/InputBlockingContainer.cs b/osu.Game/Graphics/InputBlockingContainer.cs
new file mode 100644
index 0000000000..d8387b1401
--- /dev/null
+++ b/osu.Game/Graphics/InputBlockingContainer.cs
@@ -0,0 +1,21 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+#nullable enable
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Input.Events;
+
+namespace osu.Game.Graphics
+{
+ ///
+ /// A simple container which blocks input events from travelling through it.
+ ///
+ public class InputBlockingContainer : Container
+ {
+ protected override bool OnHover(HoverEvent e) => true;
+
+ protected override bool OnMouseDown(MouseDownEvent e) => true;
+
+ protected override bool OnClick(ClickEvent e) => true;
+ }
+}
diff --git a/osu.Game/Graphics/UserInterface/ShearedOverlayHeader.cs b/osu.Game/Graphics/UserInterface/ShearedOverlayHeader.cs
index 9ed7bb35de..452a1dd394 100644
--- a/osu.Game/Graphics/UserInterface/ShearedOverlayHeader.cs
+++ b/osu.Game/Graphics/UserInterface/ShearedOverlayHeader.cs
@@ -66,7 +66,7 @@ namespace osu.Game.Graphics.UserInterface
},
Children = new Drawable[]
{
- underlayContainer = new Container
+ underlayContainer = new InputBlockingContainer
{
RelativeSizeAxes = Axes.X,
Height = HEIGHT,
diff --git a/osu.Game/Localisation/JoystickSettingsStrings.cs b/osu.Game/Localisation/JoystickSettingsStrings.cs
new file mode 100644
index 0000000000..410cd0a6f5
--- /dev/null
+++ b/osu.Game/Localisation/JoystickSettingsStrings.cs
@@ -0,0 +1,24 @@
+// Copyright (c) ppy Pty Ltd . 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 JoystickSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.JoystickSettings";
+
+ ///
+ /// "Joystick / Gamepad"
+ ///
+ public static LocalisableString JoystickGamepad => new TranslatableString(getKey(@"joystick_gamepad"), @"Joystick / Gamepad");
+
+ ///
+ /// "Deadzone Threshold"
+ ///
+ public static LocalisableString DeadzoneThreshold => new TranslatableString(getKey(@"deadzone_threshold"), @"Deadzone");
+
+ private static string getKey(string key) => $@"{prefix}:{key}";
+ }
+}
\ No newline at end of file
diff --git a/osu.Game/Overlays/FirstRunSetupOverlay.cs b/osu.Game/Overlays/FirstRunSetupOverlay.cs
index a12fec4507..dc1ae2be37 100644
--- a/osu.Game/Overlays/FirstRunSetupOverlay.cs
+++ b/osu.Game/Overlays/FirstRunSetupOverlay.cs
@@ -7,10 +7,8 @@ using System;
using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
-using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
@@ -18,25 +16,22 @@ using osu.Framework.Localisation;
using osu.Framework.Screens;
using osu.Game.Configuration;
using osu.Game.Graphics;
-using osu.Game.Graphics.Containers;
-using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Localisation;
using osu.Game.Overlays.FirstRunSetup;
+using osu.Game.Overlays.Mods;
using osu.Game.Overlays.Notifications;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osu.Game.Screens.OnlinePlay.Match.Components;
-using osuTK;
-using osuTK.Graphics;
namespace osu.Game.Overlays
{
[Cached]
- public class FirstRunSetupOverlay : OsuFocusedOverlayContainer
+ public class FirstRunSetupOverlay : ShearedOverlayContainer
{
- protected override bool StartHidden => true;
+ protected override OverlayColourScheme ColourScheme => OverlayColourScheme.Purple;
[Resolved]
private IPerformFromScreenRunner performer { get; set; } = null!;
@@ -52,15 +47,10 @@ namespace osu.Game.Overlays
public PurpleTriangleButton NextButton = null!;
public DangerousTriangleButton BackButton = null!;
- [Cached]
- private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
-
private readonly Bindable showFirstRunSetup = new Bindable();
private int? currentStepIndex;
- private const float scale_when_hidden = 0.9f;
-
///
/// The currently displayed screen, if any.
///
@@ -76,149 +66,89 @@ namespace osu.Game.Overlays
private Bindable? overlayActivationMode;
- public FirstRunSetupOverlay()
- {
- RelativeSizeAxes = Axes.Both;
- }
+ private Container content = null!;
[BackgroundDependencyLoader]
private void load()
{
- Anchor = Anchor.Centre;
- Origin = Anchor.Centre;
+ Header.Title = FirstRunSetupOverlayStrings.FirstRunSetupTitle;
+ Header.Description = FirstRunSetupOverlayStrings.FirstRunSetupDescription;
- RelativeSizeAxes = Axes.Both;
- Size = new Vector2(0.95f);
-
- EdgeEffect = new EdgeEffectParameters
+ MainAreaContent.AddRange(new Drawable[]
{
- Type = EdgeEffectType.Shadow,
- Radius = 5,
- Colour = Color4.Black.Opacity(0.2f),
- };
-
- Masking = true;
- CornerRadius = 10;
-
- Children = new Drawable[]
- {
- new Box
+ content = new Container
{
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
- Colour = colourProvider.Background6,
- },
- new GridContainer
- {
- RelativeSizeAxes = Axes.Both,
- RowDimensions = new[]
+ Padding = new MarginPadding { Horizontal = 50 },
+ Child = new InputBlockingContainer
{
- new Dimension(GridSizeMode.AutoSize),
- new Dimension(),
- new Dimension(GridSizeMode.AutoSize),
- },
- Content = new[]
- {
- new Drawable[]
+ Masking = true,
+ CornerRadius = 14,
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
{
- new Container
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Children = new Drawable[]
- {
- new Box
- {
- Colour = colourProvider.Background5,
- RelativeSizeAxes = Axes.Both,
- },
- new FillFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- Margin = new MarginPadding(10),
- AutoSizeAxes = Axes.Y,
- Direction = FillDirection.Vertical,
- Children = new Drawable[]
- {
- new OsuSpriteText
- {
- Text = FirstRunSetupOverlayStrings.FirstRunSetupTitle,
- Font = OsuFont.Default.With(size: 32),
- Colour = colourProvider.Content1,
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- },
- new OsuTextFlowContainer
- {
- Text = FirstRunSetupOverlayStrings.FirstRunSetupDescription,
- Colour = colourProvider.Content2,
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- AutoSizeAxes = Axes.Both,
- },
- }
- },
- }
- },
- },
- new Drawable[]
- {
- stackContainer = new Container
+ new Box
{
RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding(20),
+ Colour = ColourProvider.Background6,
},
- },
- new Drawable[]
- {
- new Container
+ stackContainer = new Container
{
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Padding = new MarginPadding(20)
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding
{
- Top = 0 // provided by the stack container above.
- },
- Child = new GridContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- ColumnDimensions = new[]
- {
- new Dimension(GridSizeMode.AutoSize),
- new Dimension(GridSizeMode.Absolute, 10),
- new Dimension(),
- },
- RowDimensions = new[]
- {
- new Dimension(GridSizeMode.AutoSize),
- },
- Content = new[]
- {
- new[]
- {
- BackButton = new DangerousTriangleButton
- {
- Width = 200,
- Text = CommonStrings.Back,
- Action = showPreviousStep,
- Enabled = { Value = false },
- },
- Empty(),
- NextButton = new PurpleTriangleButton
- {
- RelativeSizeAxes = Axes.X,
- Width = 1,
- Text = FirstRunSetupOverlayStrings.GetStarted,
- Action = showNextStep
- }
- },
- }
+ Vertical = 20,
+ Horizontal = 20,
},
}
- }
- }
+ },
+ },
},
- };
+ });
+
+ FooterContent.Add(new GridContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Width = 0.98f,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ ColumnDimensions = new[]
+ {
+ new Dimension(GridSizeMode.AutoSize),
+ new Dimension(GridSizeMode.Absolute, 10),
+ new Dimension(),
+ },
+ RowDimensions = new[]
+ {
+ new Dimension(GridSizeMode.AutoSize),
+ },
+ Content = new[]
+ {
+ new[]
+ {
+ BackButton = new DangerousTriangleButton
+ {
+ Width = 200,
+ Text = CommonStrings.Back,
+ Action = showPreviousStep,
+ Enabled = { Value = false },
+ },
+ Empty(),
+ NextButton = new PurpleTriangleButton
+ {
+ RelativeSizeAxes = Axes.X,
+ Width = 1,
+ Text = FirstRunSetupOverlayStrings.GetStarted,
+ Action = showNextStep
+ }
+ },
+ }
+ });
}
protected override void LoadComplete()
@@ -227,7 +157,8 @@ namespace osu.Game.Overlays
config.BindWith(OsuSetting.ShowFirstRunSetup, showFirstRunSetup);
- if (showFirstRunSetup.Value) Show();
+ // TODO: uncomment when happy with the whole flow.
+ // if (showFirstRunSetup.Value) Show();
}
public override bool OnPressed(KeyBindingPressEvent e)
@@ -280,10 +211,8 @@ namespace osu.Game.Overlays
{
base.PopIn();
- this.ScaleTo(scale_when_hidden)
- .ScaleTo(1, 400, Easing.OutElasticHalf);
-
- this.FadeIn(400, Easing.OutQuint);
+ content.ScaleTo(0.99f)
+ .ScaleTo(1, 400, Easing.OutQuint);
if (currentStepIndex == null)
showFirstStep();
@@ -291,6 +220,10 @@ namespace osu.Game.Overlays
protected override void PopOut()
{
+ base.PopOut();
+
+ content.ScaleTo(0.99f, 400, Easing.OutQuint);
+
if (overlayActivationMode != null)
{
// If this is non-null we are guaranteed to have come from the main menu.
@@ -316,11 +249,6 @@ namespace osu.Game.Overlays
stack?.FadeOut(100)
.Expire();
}
-
- base.PopOut();
-
- this.ScaleTo(0.96f, 400, Easing.OutQuint);
- this.FadeOut(200, Easing.OutQuint);
}
private void showFirstStep()
@@ -362,7 +290,8 @@ namespace osu.Game.Overlays
}
else
{
- showFirstRunSetup.Value = false;
+ // TODO: uncomment when happy with the whole flow.
+ // showFirstRunSetup.Value = false;
currentStepIndex = null;
Hide();
}
diff --git a/osu.Game/Overlays/Login/LoginForm.cs b/osu.Game/Overlays/Login/LoginForm.cs
index c31416e078..502f0cd22e 100644
--- a/osu.Game/Overlays/Login/LoginForm.cs
+++ b/osu.Game/Overlays/Login/LoginForm.cs
@@ -3,6 +3,7 @@
using System;
using osu.Framework.Allocation;
+using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
@@ -51,14 +52,14 @@ namespace osu.Game.Overlays.Login
{
username = new OsuTextBox
{
- PlaceholderText = UsersStrings.LoginUsername,
+ PlaceholderText = UsersStrings.LoginUsername.ToLower(),
RelativeSizeAxes = Axes.X,
Text = api?.ProvidedUsername ?? string.Empty,
TabbableContentContainer = this
},
password = new OsuPasswordTextBox
{
- PlaceholderText = UsersStrings.LoginPassword,
+ PlaceholderText = UsersStrings.LoginPassword.ToLower(),
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
},
diff --git a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs
index 66fd6a202d..1d848fe456 100644
--- a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs
+++ b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs
@@ -52,7 +52,7 @@ namespace osu.Game.Overlays.Mods
Height = HEIGHT;
AutoSizeAxes = Axes.X;
- InternalChild = new Container
+ InternalChild = new InputBlockingContainer
{
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
diff --git a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs
index 38781455fa..aeb983d352 100644
--- a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs
+++ b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs
@@ -1,15 +1,12 @@
// 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.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Cursor;
-using osu.Framework.Input.Events;
using osu.Game.Rulesets.Mods;
using osu.Game.Utils;
@@ -42,41 +39,13 @@ namespace osu.Game.Overlays.Mods
&& !ModUtils.CheckCompatibleSet(selectedMods.Value.Append(Mod));
}
+ protected override Colour4 BackgroundColour => incompatible.Value ? (Colour4)ColourProvider.Background6 : base.BackgroundColour;
+ protected override Colour4 ForegroundColour => incompatible.Value ? (Colour4)ColourProvider.Background5 : base.ForegroundColour;
+
protected override void UpdateState()
{
- Action = incompatible.Value ? () => { } : (Action)Active.Toggle;
-
- if (incompatible.Value)
- {
- Colour4 backgroundColour = ColourProvider.Background6;
- Colour4 textBackgroundColour = ColourProvider.Background5;
-
- Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(backgroundColour, textBackgroundColour), TRANSITION_DURATION, Easing.OutQuint);
- Background.FadeColour(backgroundColour, TRANSITION_DURATION, Easing.OutQuint);
-
- SwitchContainer.ResizeWidthTo(IDLE_SWITCH_WIDTH, TRANSITION_DURATION, Easing.OutQuint);
- SwitchContainer.FadeColour(Colour4.Gray, TRANSITION_DURATION, Easing.OutQuint);
- MainContentContainer.TransformTo(nameof(Padding), new MarginPadding
- {
- Left = IDLE_SWITCH_WIDTH,
- Right = CORNER_RADIUS
- }, TRANSITION_DURATION, Easing.OutQuint);
-
- TextBackground.FadeColour(textBackgroundColour, TRANSITION_DURATION, Easing.OutQuint);
- TextFlow.FadeColour(Colour4.White.Opacity(0.5f), TRANSITION_DURATION, Easing.OutQuint);
- return;
- }
-
- SwitchContainer.FadeColour(Colour4.White, TRANSITION_DURATION, Easing.OutQuint);
base.UpdateState();
- }
-
- protected override bool OnMouseDown(MouseDownEvent e)
- {
- if (incompatible.Value)
- return true; // bypasses base call purposely in order to not play out the intermediate state animation.
-
- return base.OnMouseDown(e);
+ SwitchContainer.FadeColour(incompatible.Value ? Colour4.Gray : Colour4.White, TRANSITION_DURATION, Easing.OutQuint);
}
#region IHasCustomTooltip
diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs
index 7ae325bde7..f2a97da3b2 100644
--- a/osu.Game/Overlays/Mods/ModPanel.cs
+++ b/osu.Game/Overlays/Mods/ModPanel.cs
@@ -203,20 +203,24 @@ namespace osu.Game.Overlays.Mods
base.OnMouseUp(e);
}
+ protected virtual Colour4 BackgroundColour => Active.Value ? activeColour.Darken(0.3f) : (Colour4)ColourProvider.Background3;
+ protected virtual Colour4 ForegroundColour => Active.Value ? activeColour : (Colour4)ColourProvider.Background2;
+ protected virtual Colour4 TextColour => Active.Value ? (Colour4)ColourProvider.Background6 : Colour4.White;
+
protected virtual void UpdateState()
{
float targetWidth = Active.Value ? EXPANDED_SWITCH_WIDTH : IDLE_SWITCH_WIDTH;
double transitionDuration = TRANSITION_DURATION;
- Colour4 textBackgroundColour = Active.Value ? activeColour : (Colour4)ColourProvider.Background2;
- Colour4 mainBackgroundColour = Active.Value ? activeColour.Darken(0.3f) : (Colour4)ColourProvider.Background3;
- Colour4 textColour = Active.Value ? (Colour4)ColourProvider.Background6 : Colour4.White;
+ Colour4 backgroundColour = BackgroundColour;
+ Colour4 foregroundColour = ForegroundColour;
+ Colour4 textColour = TextColour;
// Hover affects colour of button background
if (IsHovered)
{
- textBackgroundColour = textBackgroundColour.Lighten(0.1f);
- mainBackgroundColour = mainBackgroundColour.Lighten(0.1f);
+ backgroundColour = backgroundColour.Lighten(0.1f);
+ foregroundColour = foregroundColour.Lighten(0.1f);
}
// Mouse down adds a halfway tween of the movement
@@ -226,15 +230,15 @@ namespace osu.Game.Overlays.Mods
transitionDuration *= 4;
}
- Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(mainBackgroundColour, textBackgroundColour), transitionDuration, Easing.OutQuint);
- Background.FadeColour(mainBackgroundColour, transitionDuration, Easing.OutQuint);
+ Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(backgroundColour, foregroundColour), transitionDuration, Easing.OutQuint);
+ Background.FadeColour(backgroundColour, transitionDuration, Easing.OutQuint);
SwitchContainer.ResizeWidthTo(targetWidth, transitionDuration, Easing.OutQuint);
MainContentContainer.TransformTo(nameof(Padding), new MarginPadding
{
Left = targetWidth,
Right = CORNER_RADIUS
}, transitionDuration, Easing.OutQuint);
- TextBackground.FadeColour(textBackgroundColour, transitionDuration, Easing.OutQuint);
+ TextBackground.FadeColour(foregroundColour, transitionDuration, Easing.OutQuint);
TextFlow.FadeColour(textColour, transitionDuration, Easing.OutQuint);
}
diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs
index 693c85fafc..8a83071109 100644
--- a/osu.Game/Overlays/Mods/ModSelectScreen.cs
+++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs
@@ -168,7 +168,7 @@ namespace osu.Game.Overlays.Mods
foreach (var column in columnFlow)
{
- column.SelectedMods.BindValueChanged(_ => updateBindableFromSelection());
+ column.SelectedMods.BindValueChanged(updateBindableFromSelection);
}
customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true);
@@ -237,33 +237,36 @@ namespace osu.Game.Overlays.Mods
TopLevelContent.MoveToY(-modAreaHeight, transition_duration, Easing.InOutCubic);
}
- private bool selectionBindableSyncInProgress;
-
private void updateSelectionFromBindable()
{
- if (selectionBindableSyncInProgress)
- return;
-
- selectionBindableSyncInProgress = true;
-
+ // note that selectionBindableSyncInProgress is purposefully not checked here.
+ // this is because in the case of mod selection in solo gameplay, a user selection of a mod can actually lead to deselection of other incompatible mods.
+ // to synchronise state correctly, updateBindableFromSelection() computes the final mods (including incompatibility rules) and updates SelectedMods,
+ // and this method then runs unconditionally again to make sure the new visual selection accurately reflects the final set of selected mods.
+ // selectionBindableSyncInProgress ensures that mutual infinite recursion does not happen after that unconditional call.
foreach (var column in columnFlow)
column.SelectedMods.Value = SelectedMods.Value.Where(mod => mod.Type == column.ModType).ToArray();
-
- selectionBindableSyncInProgress = false;
}
- private void updateBindableFromSelection()
+ private bool selectionBindableSyncInProgress;
+
+ private void updateBindableFromSelection(ValueChangedEvent> modSelectionChange)
{
if (selectionBindableSyncInProgress)
return;
selectionBindableSyncInProgress = true;
- SelectedMods.Value = columnFlow.SelectMany(column => column.SelectedMods.Value).ToArray();
+ SelectedMods.Value = ComputeNewModsFromSelection(
+ modSelectionChange.NewValue.Except(modSelectionChange.OldValue),
+ modSelectionChange.OldValue.Except(modSelectionChange.NewValue));
selectionBindableSyncInProgress = false;
}
+ protected virtual IReadOnlyList ComputeNewModsFromSelection(IEnumerable addedMods, IEnumerable removedMods)
+ => columnFlow.SelectMany(column => column.SelectedMods.Value).ToArray();
+
protected override void PopIn()
{
const double fade_in_duration = 400;
diff --git a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs
index 62ed736dc2..eca192c8e5 100644
--- a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs
+++ b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs
@@ -5,6 +5,8 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input.Events;
+using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
@@ -88,7 +90,7 @@ namespace osu.Game.Overlays.Mods
Bottom = footer_height + PADDING,
}
},
- Footer = new Container
+ Footer = new InputBlockingContainer
{
RelativeSizeAxes = Axes.X,
Depth = float.MinValue,
@@ -113,6 +115,17 @@ namespace osu.Game.Overlays.Mods
};
}
+ protected override bool OnClick(ClickEvent e)
+ {
+ if (State.Value == Visibility.Visible)
+ {
+ Hide();
+ return true;
+ }
+
+ return base.OnClick(e);
+ }
+
protected override void PopIn()
{
const double fade_in_duration = 400;
diff --git a/osu.Game/Overlays/Mods/UserModSelectScreen.cs b/osu.Game/Overlays/Mods/UserModSelectScreen.cs
index 81943da514..ed0a07521b 100644
--- a/osu.Game/Overlays/Mods/UserModSelectScreen.cs
+++ b/osu.Game/Overlays/Mods/UserModSelectScreen.cs
@@ -1,8 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
+using System.Linq;
using JetBrains.Annotations;
using osu.Game.Rulesets.Mods;
+using osu.Game.Utils;
using osuTK.Input;
namespace osu.Game.Overlays.Mods
@@ -11,6 +14,24 @@ namespace osu.Game.Overlays.Mods
{
protected override ModColumn CreateModColumn(ModType modType, Key[] toggleKeys = null) => new UserModColumn(modType, false, toggleKeys);
+ protected override IReadOnlyList ComputeNewModsFromSelection(IEnumerable addedMods, IEnumerable removedMods)
+ {
+ IEnumerable modsAfterRemoval = SelectedMods.Value.Except(removedMods).ToList();
+
+ // the preference is that all new mods should override potential incompatible old mods.
+ // in general that's a bit difficult to compute if more than one mod is added at a time,
+ // so be conservative and just remove all mods that aren't compatible with any one added mod.
+ foreach (var addedMod in addedMods)
+ {
+ if (!ModUtils.CheckCompatibleSet(modsAfterRemoval.Append(addedMod), out var invalidMods))
+ modsAfterRemoval = modsAfterRemoval.Except(invalidMods);
+
+ modsAfterRemoval = modsAfterRemoval.Append(addedMod).ToList();
+ }
+
+ return modsAfterRemoval.ToList();
+ }
+
private class UserModColumn : ModColumn
{
public UserModColumn(ModType modType, bool allowBulkSelection, [CanBeNull] Key[] toggleKeys = null)
diff --git a/osu.Game/Overlays/Settings/Sections/Input/JoystickSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/JoystickSettings.cs
new file mode 100644
index 0000000000..c136ca6a19
--- /dev/null
+++ b/osu.Game/Overlays/Settings/Sections/Input/JoystickSettings.cs
@@ -0,0 +1,56 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Input.Handlers.Joystick;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
+
+namespace osu.Game.Overlays.Settings.Sections.Input
+{
+ public class JoystickSettings : SettingsSubsection
+ {
+ protected override LocalisableString Header => JoystickSettingsStrings.JoystickGamepad;
+
+ private readonly JoystickHandler joystickHandler;
+
+ private readonly Bindable enabled = new BindableBool(true);
+
+ private SettingsSlider deadzoneSlider;
+
+ public JoystickSettings(JoystickHandler joystickHandler)
+ {
+ this.joystickHandler = joystickHandler;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Children = new Drawable[]
+ {
+ new SettingsCheckbox
+ {
+ LabelText = CommonStrings.Enabled,
+ Current = enabled
+ },
+ deadzoneSlider = new SettingsSlider
+ {
+ LabelText = JoystickSettingsStrings.DeadzoneThreshold,
+ KeyboardStep = 0.01f,
+ DisplayAsPercentage = true,
+ Current = joystickHandler.DeadzoneThreshold,
+ },
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ enabled.BindTo(joystickHandler.Enabled);
+ enabled.BindValueChanged(e => deadzoneSlider.Current.Disabled = !e.NewValue, true);
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs
index d282ba5318..d2c5d2fcf7 100644
--- a/osu.Game/Overlays/Settings/Sections/InputSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs
@@ -68,7 +68,10 @@ namespace osu.Game.Overlays.Settings.Sections
break;
// whitelist the handlers which should be displayed to avoid any weird cases of users touching settings they shouldn't.
- case JoystickHandler _:
+ case JoystickHandler jh:
+ section = new JoystickSettings(jh);
+ break;
+
case MidiHandler _:
section = new HandlerSection(handler);
break;
diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs
index 885f4903b0..b48aef330a 100644
--- a/osu.Game/Screens/Menu/ButtonSystem.cs
+++ b/osu.Game/Screens/Menu/ButtonSystem.cs
@@ -196,11 +196,8 @@ namespace osu.Game.Screens.Menu
if (State == ButtonSystemState.Initial)
{
- if (buttonsTopLevel.Any(b => e.Key == b.TriggerKey))
- {
- logo?.TriggerClick();
- return true;
- }
+ logo?.TriggerClick();
+ return true;
}
return base.OnKeyDown(e);
diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs
index c6037d1bd6..9772b1feb3 100644
--- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs
+++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs
@@ -244,7 +244,7 @@ namespace osu.Game.Screens.Select.Carousel
}
if (hideRequested != null)
- items.Add(new OsuMenuItem(CommonStrings.ButtonsHide, MenuItemType.Destructive, () => hideRequested(beatmapInfo)));
+ items.Add(new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested(beatmapInfo)));
return items.ToArray();
}
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index f7d5581621..aed6026d69 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -390,7 +390,7 @@ namespace osu.Game.Skinning
return new LegacyJudgementPieceOld(resultComponent.Component, createDrawable);
}
- break;
+ return null;
}
return this.GetAnimation(component.LookupName, false, false);
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index c6c18f6061..325e834fa5 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -35,7 +35,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 64af0d70f3..8775442be2 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -61,7 +61,7 @@
-
+
@@ -84,7 +84,7 @@
-
+