diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs index 0df6605cdd..f24589ed35 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.Menus { typeof(ToolbarButton), typeof(ToolbarRulesetSelector), - typeof(ToolbarRulesetButton), + typeof(ToolbarRulesetTabButton), typeof(ToolbarNotificationButton), }; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneToolbarRulesetSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneToolbarRulesetSelector.cs new file mode 100644 index 0000000000..582303024b --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneToolbarRulesetSelector.cs @@ -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 osu.Framework.Graphics.Containers; +using osu.Game.Overlays.Toolbar; +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using System.Linq; +using osu.Framework.MathUtils; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneToolbarRulesetSelector : OsuTestScene + { + public override IReadOnlyList<Type> RequiredTypes => new[] + { + typeof(ToolbarRulesetSelector), + typeof(ToolbarRulesetTabButton), + }; + + public TestSceneToolbarRulesetSelector() + { + ToolbarRulesetSelector selector; + + Add(new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.X, + Height = Toolbar.HEIGHT, + Child = selector = new ToolbarRulesetSelector() + }); + + AddStep("Select random", () => + { + selector.Current.Value = selector.Items.ElementAt(RNG.Next(selector.Items.Count())); + }); + AddStep("Toggle disabled state", () => selector.Current.Disabled = !selector.Current.Disabled); + } + } +} diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 982fb26b6b..19038c3981 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Input.Events; +using osu.Game.Rulesets; namespace osu.Game.Overlays.Toolbar { @@ -23,6 +24,7 @@ namespace osu.Game.Overlays.Toolbar public Action OnHome; private ToolbarUserButton userButton; + private ToolbarRulesetSelector rulesetSelector; protected override bool BlockPositionalInput => false; @@ -40,7 +42,7 @@ namespace osu.Game.Overlays.Toolbar } [BackgroundDependencyLoader(true)] - private void load(OsuGame osuGame) + private void load(OsuGame osuGame, Bindable<RulesetInfo> parentRuleset) { Children = new Drawable[] { @@ -57,7 +59,7 @@ namespace osu.Game.Overlays.Toolbar { Action = () => OnHome?.Invoke() }, - new ToolbarRulesetSelector() + rulesetSelector = new ToolbarRulesetSelector() } }, new FillFlowContainer @@ -84,6 +86,9 @@ namespace osu.Game.Overlays.Toolbar } }; + // Bound after the selector is added to the hierarchy to give it a chance to load the available rulesets + rulesetSelector.Current.BindTo(parentRuleset); + State.ValueChanged += visibility => { if (overlayActivationMode.Value == OverlayActivation.Disabled) diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs deleted file mode 100644 index 87b18ba9f4..0000000000 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs +++ /dev/null @@ -1,58 +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 osu.Framework.Graphics.Effects; -using osu.Game.Rulesets; -using osuTK.Graphics; - -namespace osu.Game.Overlays.Toolbar -{ - public class ToolbarRulesetButton : ToolbarButton - { - private RulesetInfo ruleset; - - public RulesetInfo Ruleset - { - get => ruleset; - set - { - ruleset = value; - - var rInstance = ruleset.CreateInstance(); - - TooltipMain = rInstance.Description; - TooltipSub = $"Play some {rInstance.Description}"; - SetIcon(rInstance.CreateIcon()); - } - } - - public bool Active - { - set - { - if (value) - { - IconContainer.Colour = Color4.White; - IconContainer.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = new Color4(255, 194, 224, 100), - Radius = 15, - Roundness = 15, - }; - } - else - { - IconContainer.Colour = new Color4(255, 194, 224, 255); - IconContainer.EdgeEffect = new EdgeEffectParameters(); - } - } - } - - protected override void LoadComplete() - { - base.LoadComplete(); - IconContainer.Scale *= 1.4f; - } - } -} diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index 90412ec1d1..95d7d3f73a 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -1,49 +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.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osuTK; -using osuTK.Input; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; using osu.Game.Rulesets; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osuTK.Input; +using System.Linq; +using osu.Framework.Allocation; namespace osu.Game.Overlays.Toolbar { - public class ToolbarRulesetSelector : Container + public class ToolbarRulesetSelector : RulesetSelector { private const float padding = 10; - private readonly FillFlowContainer modeButtons; - private readonly Drawable modeButtonLine; - private ToolbarRulesetButton activeButton; - - private RulesetStore rulesets; - private readonly Bindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>(); + private Drawable modeButtonLine; public ToolbarRulesetSelector() { RelativeSizeAxes = Axes.Y; AutoSizeAxes = Axes.X; + } - Children = new[] + [BackgroundDependencyLoader] + private void load() + { + AddRangeInternal(new[] { - new OpaqueBackground(), - modeButtons = new FillFlowContainer + new OpaqueBackground { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Direction = FillDirection.Horizontal, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Padding = new MarginPadding { Left = padding, Right = padding }, + Depth = 1, }, modeButtonLine = new Container { @@ -58,36 +50,50 @@ namespace osu.Game.Overlays.Toolbar Radius = 15, Roundness = 15, }, - Children = new[] + Child = new Box { - new Box - { - RelativeSizeAxes = Axes.Both, - } + RelativeSizeAxes = Axes.Both, } } - }; + }); } - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets, Bindable<RulesetInfo> parentRuleset) + protected override void LoadComplete() { - this.rulesets = rulesets; + base.LoadComplete(); - foreach (var r in rulesets.AvailableRulesets) - { - modeButtons.Add(new ToolbarRulesetButton - { - Ruleset = r, - Action = delegate { ruleset.Value = r; } - }); - } - - ruleset.ValueChanged += rulesetChanged; - ruleset.DisabledChanged += disabledChanged; - ruleset.BindTo(parentRuleset); + Current.BindDisabledChanged(disabled => this.FadeColour(disabled ? Color4.Gray : Color4.White, 300), true); + Current.BindValueChanged(_ => moveLineToCurrent(), true); } + private void moveLineToCurrent() + { + foreach (TabItem<RulesetInfo> tabItem in TabContainer) + { + if (tabItem.Value == Current.Value) + { + modeButtonLine.MoveToX(tabItem.DrawPosition.X, 200, Easing.OutQuint); + break; + } + } + } + + public override bool HandleNonPositionalInput => !Current.Disabled && base.HandleNonPositionalInput; + + public override bool HandlePositionalInput => !Current.Disabled && base.HandlePositionalInput; + + public override bool PropagatePositionalInputSubTree => !Current.Disabled && base.PropagatePositionalInputSubTree; + + protected override TabItem<RulesetInfo> CreateTabItem(RulesetInfo value) => new ToolbarRulesetTabButton(value); + + protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding { Left = padding, Right = padding }, + }; + protected override bool OnKeyDown(KeyDownEvent e) { base.OnKeyDown(e); @@ -96,46 +102,13 @@ namespace osu.Game.Overlays.Toolbar { int requested = e.Key - Key.Number1; - RulesetInfo found = rulesets.AvailableRulesets.Skip(requested).FirstOrDefault(); + RulesetInfo found = Rulesets.AvailableRulesets.Skip(requested).FirstOrDefault(); if (found != null) - ruleset.Value = found; + Current.Value = found; return true; } return false; } - - public override bool HandleNonPositionalInput => !ruleset.Disabled && base.HandleNonPositionalInput; - public override bool HandlePositionalInput => !ruleset.Disabled && base.HandlePositionalInput; - - public override bool PropagatePositionalInputSubTree => !ruleset.Disabled && base.PropagatePositionalInputSubTree; - - private void disabledChanged(bool isDisabled) => this.FadeColour(isDisabled ? Color4.Gray : Color4.White, 300); - - private void rulesetChanged(ValueChangedEvent<RulesetInfo> e) - { - foreach (ToolbarRulesetButton m in modeButtons.Children.Cast<ToolbarRulesetButton>()) - { - bool isActive = m.Ruleset.ID == e.NewValue.ID; - m.Active = isActive; - if (isActive) - activeButton = m; - } - - activeMode.Invalidate(); - } - - private Cached activeMode = new Cached(); - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - if (!activeMode.IsValid) - { - modeButtonLine.MoveToX(activeButton.DrawPosition.X, 200, Easing.OutQuint); - activeMode.Validate(); - } - } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs new file mode 100644 index 0000000000..a5194ea752 --- /dev/null +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs @@ -0,0 +1,76 @@ +// 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.Effects; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Rulesets; +using osuTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; + +namespace osu.Game.Overlays.Toolbar +{ + public class ToolbarRulesetTabButton : TabItem<RulesetInfo> + { + private readonly RulesetButton ruleset; + + public ToolbarRulesetTabButton(RulesetInfo value) + : base(value) + { + AutoSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + Child = ruleset = new RulesetButton + { + Active = false, + }; + + var rInstance = value.CreateInstance(); + + ruleset.TooltipMain = rInstance.Description; + ruleset.TooltipSub = $"Play some {rInstance.Description}"; + ruleset.SetIcon(rInstance.CreateIcon()); + } + + protected override void OnActivated() => ruleset.Active = true; + + protected override void OnDeactivated() => ruleset.Active = false; + + private class RulesetButton : ToolbarButton + { + public bool Active + { + set + { + if (value) + { + IconContainer.Colour = Color4.White; + IconContainer.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = new Color4(255, 194, 224, 100), + Radius = 15, + Roundness = 15, + }; + } + else + { + IconContainer.Colour = new Color4(255, 194, 224, 255); + IconContainer.EdgeEffect = new EdgeEffectParameters(); + } + } + } + + protected override bool OnClick(ClickEvent e) + { + Parent.Click(); + return base.OnClick(e); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + IconContainer.Scale *= 1.4f; + } + } + } +} diff --git a/osu.Game/Rulesets/RulesetSelector.cs b/osu.Game/Rulesets/RulesetSelector.cs new file mode 100644 index 0000000000..8e6ec556d2 --- /dev/null +++ b/osu.Game/Rulesets/RulesetSelector.cs @@ -0,0 +1,23 @@ +// 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.UserInterface; +using osu.Framework.Allocation; + +namespace osu.Game.Rulesets +{ + public abstract class RulesetSelector : TabControl<RulesetInfo> + { + [Resolved] + protected RulesetStore Rulesets { get; private set; } + + protected override Dropdown<RulesetInfo> CreateDropdown() => null; + + [BackgroundDependencyLoader] + private void load() + { + foreach (var r in Rulesets.AvailableRulesets) + AddItem(r); + } + } +} diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index a80056d164..b7fb758298 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -195,6 +195,7 @@ namespace osu.Game.Screens.Multi.Match Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); Mods.Value = e.NewValue?.RequiredMods?.ToArray() ?? Array.Empty<Mod>(); + if (e.NewValue?.Ruleset != null) Ruleset.Value = e.NewValue.Ruleset; }