diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index e9f70180e1..194e4301c2 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Input.Handlers; using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Testing; using osu.Framework.Utils; @@ -69,7 +70,7 @@ namespace osu.Game.Tests.Visual.Settings { AddStep("Test with wide tablet", () => tabletHandler.SetTabletSize(new Vector2(160, 100))); - AddStep("Reset to full area", () => settings.ChildrenOfType().First().TriggerClick()); + AddStep("Reset to full area", () => settings.ChildrenOfType().First().TriggerClick()); ensureValid(); AddStep("rotate 10", () => tabletHandler.Rotation.Value = 10); @@ -129,7 +130,7 @@ namespace osu.Game.Tests.Visual.Settings private void ensureInvalid() => AddAssert("area invalid", () => !settings.AreaSelection.IsWithinBounds); - public class TestTabletHandler : ITabletHandler + public class TestTabletHandler : InputHandler, ITabletHandler { public Bindable AreaOffset { get; } = new Bindable(); public Bindable AreaSize { get; } = new Bindable(); @@ -149,7 +150,7 @@ namespace osu.Game.Tests.Visual.Settings private readonly Bindable tablet = new Bindable(); - public BindableBool Enabled { get; } = new BindableBool(true); + public override bool IsActive => true; public void SetTabletSize(Vector2 size) { diff --git a/osu.Game/Graphics/UserInterfaceV2/SwitchButton.cs b/osu.Game/Graphics/UserInterfaceV2/SwitchButton.cs index 5bb6aca7f4..f2d2f9c618 100644 --- a/osu.Game/Graphics/UserInterfaceV2/SwitchButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/SwitchButton.cs @@ -112,7 +112,11 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected override void OnUserChange(bool value) { base.OnUserChange(value); + PlaySample(value); + } + public void PlaySample(bool value) + { if (value) sampleChecked?.Play(); else diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 222427cb60..a2f3871cf3 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -625,7 +625,7 @@ namespace osu.Game return new TouchSettings(th); case MidiHandler: - return new InputSection.HandlerSection(handler); + return new InputSubsection(handler); // return null for handlers that shouldn't have settings. default: diff --git a/osu.Game/Overlays/Settings/Sections/Input/JoystickSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/JoystickSettings.cs index 8455c09633..d5d7540f79 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/JoystickSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/JoystickSettings.cs @@ -8,25 +8,23 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Handlers.Joystick; using osu.Framework.Localisation; +using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; namespace osu.Game.Overlays.Settings.Sections.Input { - public partial class JoystickSettings : SettingsSubsection + public partial class JoystickSettings : InputSubsection { protected override LocalisableString Header => JoystickSettingsStrings.JoystickGamepad; private readonly JoystickHandler joystickHandler; - private readonly Bindable enabled = new BindableBool(true); - - private SettingsSlider deadzoneSlider; - private Bindable handlerDeadzone; private Bindable localDeadzone; public JoystickSettings(JoystickHandler joystickHandler) + : base(joystickHandler) { this.joystickHandler = joystickHandler; } @@ -38,30 +36,22 @@ namespace osu.Game.Overlays.Settings.Sections.Input handlerDeadzone = joystickHandler.DeadzoneThreshold.GetBoundCopy(); localDeadzone = handlerDeadzone.GetUnboundCopy(); - Children = new Drawable[] + AddRange(new Drawable[] { - new SettingsCheckbox + new SettingsItemV2(new FormSliderBar { - LabelText = CommonStrings.Enabled, - Current = enabled - }, - deadzoneSlider = new SettingsSlider - { - LabelText = JoystickSettingsStrings.DeadzoneThreshold, + Caption = JoystickSettingsStrings.DeadzoneThreshold, KeyboardStep = 0.01f, DisplayAsPercentage = true, Current = localDeadzone, - }, - }; + }) + }); } protected override void LoadComplete() { base.LoadComplete(); - enabled.BindTo(joystickHandler.Enabled); - enabled.BindValueChanged(e => deadzoneSlider.Current.Disabled = !e.NewValue, true); - handlerDeadzone.BindValueChanged(val => { bool disabled = localDeadzone.Disabled; diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 3fb4016498..1d4538baef 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -11,30 +9,33 @@ using osu.Framework.Graphics; using osu.Framework.Input.Handlers.Mouse; using osu.Framework.Localisation; using osu.Game.Configuration; -using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Input; using osu.Game.Localisation; namespace osu.Game.Overlays.Settings.Sections.Input { - public partial class MouseSettings : SettingsSubsection + public partial class MouseSettings : InputSubsection { private readonly MouseHandler mouseHandler; protected override LocalisableString Header => MouseSettingsStrings.Mouse; - private Bindable handlerSensitivity; + private Bindable handlerSensitivity = null!; + private Bindable localSensitivity = null!; + private Bindable windowMode = null!; + private Bindable minimiseOnFocusLoss = null!; + private FormEnumDropdown confineMouseModeSetting = null!; + private Bindable relativeMode = null!; - private Bindable localSensitivity; + private FormCheckBox highPrecisionMouse = null!; - private Bindable windowMode; - private Bindable minimiseOnFocusLoss; - private SettingsEnumDropdown confineMouseModeSetting; - private Bindable relativeMode; + private readonly Bindable highPrecisionMouseNote = new Bindable(); - private SettingsCheckbox highPrecisionMouse; + protected override bool IsToggleable => false; public MouseSettings(MouseHandler mouseHandler) + : base(mouseHandler) { this.mouseHandler = mouseHandler; } @@ -50,38 +51,47 @@ namespace osu.Game.Overlays.Settings.Sections.Input windowMode = config.GetBindable(FrameworkSetting.WindowMode); minimiseOnFocusLoss = config.GetBindable(FrameworkSetting.MinimiseOnFocusLossInFullscreen); - Children = new Drawable[] + AddRange(new Drawable[] { - highPrecisionMouse = new SettingsCheckbox + new SettingsItemV2(highPrecisionMouse = new FormCheckBox { - LabelText = MouseSettingsStrings.HighPrecisionMouse, - TooltipText = MouseSettingsStrings.HighPrecisionMouseTooltip, + Caption = MouseSettingsStrings.HighPrecisionMouse, + HintText = MouseSettingsStrings.HighPrecisionMouseTooltip, Current = relativeMode, + }) + { Keywords = new[] { @"raw", @"input", @"relative", @"cursor", "sensitivity", "speed", "velocity" }, + Note = { BindTarget = highPrecisionMouseNote }, }, - new SensitivitySetting + new SettingsItemV2(new FormSliderBar + { + Caption = MouseSettingsStrings.CursorSensitivity, + Current = localSensitivity, + KeyboardStep = 0.01f, + TransferValueOnCommit = true, + LabelFormat = v => $@"{v:0.##}x", + TooltipFormat = v => localSensitivity.Disabled ? MouseSettingsStrings.EnableHighPrecisionForSensitivityAdjust : $@"{v:0.##}x", + }) { Keywords = new[] { "speed", "velocity" }, - LabelText = MouseSettingsStrings.CursorSensitivity, - Current = localSensitivity }, - confineMouseModeSetting = new SettingsEnumDropdown + new SettingsItemV2(confineMouseModeSetting = new FormEnumDropdown { - LabelText = MouseSettingsStrings.ConfineMouseMode, + Caption = MouseSettingsStrings.ConfineMouseMode, Current = osuConfig.GetBindable(OsuSetting.ConfineMouseMode) - }, - new SettingsCheckbox + }), + new SettingsItemV2(new FormCheckBox { - LabelText = MouseSettingsStrings.DisableMouseWheelVolumeAdjust, - TooltipText = MouseSettingsStrings.DisableMouseWheelVolumeAdjustTooltip, + Caption = MouseSettingsStrings.DisableMouseWheelVolumeAdjust, + HintText = MouseSettingsStrings.DisableMouseWheelVolumeAdjustTooltip, Current = osuConfig.GetBindable(OsuSetting.MouseDisableWheel) - }, - new SettingsCheckbox + }), + new SettingsItemV2(new FormCheckBox { - LabelText = MouseSettingsStrings.DisableClicksDuringGameplay, + Caption = MouseSettingsStrings.DisableClicksDuringGameplay, Current = osuConfig.GetBindable(OsuSetting.MouseDisableButtons) - }, - }; + }), + }); } protected override void LoadComplete() @@ -112,9 +122,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input case RuntimeInfo.Platform.macOS: case RuntimeInfo.Platform.iOS: if (highPrecision.NewValue) - highPrecisionMouse.SetNoticeText(MouseSettingsStrings.HighPrecisionPlatformWarning, true); + highPrecisionMouseNote.Value = new SettingsNote.Data(MouseSettingsStrings.HighPrecisionPlatformWarning, SettingsNote.Type.Warning); else - highPrecisionMouse.ClearNoticeText(); + highPrecisionMouseNote.Value = null; break; } @@ -131,27 +141,13 @@ namespace osu.Game.Overlays.Settings.Sections.Input if (confineModeOverriden) { confineMouseModeSetting.Current.Disabled = true; - confineMouseModeSetting.TooltipText = MouseSettingsStrings.NotApplicableFullscreen; + confineMouseModeSetting.HintText = MouseSettingsStrings.NotApplicableFullscreen; } else { confineMouseModeSetting.Current.Disabled = false; - confineMouseModeSetting.TooltipText = string.Empty; + confineMouseModeSetting.HintText = default; } } - - public partial class SensitivitySetting : SettingsSlider - { - public SensitivitySetting() - { - KeyboardStep = 0.01f; - TransferValueOnCommit = true; - } - } - - public partial class SensitivitySlider : RoundedSliderBar - { - public override LocalisableString TooltipText => Current.Disabled ? MouseSettingsStrings.EnableHighPrecisionForSensitivityAdjust : $"{base.TooltipText}x"; - } } } diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index b3fdd53466..bdc7c9bb35 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -45,7 +45,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input { this.handler = handler; - Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS }; + Padding = SettingsPanel.ContentPaddingV2; } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 2a3710c67b..00ddbf17de 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -9,6 +9,8 @@ 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.Input.Handlers; using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Localisation; using osu.Framework.Platform; @@ -17,13 +19,14 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; using osuTK; using osu.Game.Localisation; using osu.Game.Online.Chat; namespace osu.Game.Overlays.Settings.Sections.Input { - public partial class TabletSettings : SettingsSubsection + public partial class TabletSettings : InputSubsection { public override IEnumerable FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "area" }); @@ -74,70 +77,92 @@ namespace osu.Game.Overlays.Settings.Sections.Input private FillFlowContainer mainSettings; - private FillFlowContainer noTabletMessage; + private Container noTabletMessage; protected override LocalisableString Header => TabletSettingsStrings.Tablet; public TabletSettings(ITabletHandler tabletHandler) + : base((InputHandler)tabletHandler) { this.tabletHandler = tabletHandler; } [BackgroundDependencyLoader] - private void load(OsuColour colours, LocalisationManager localisation, OsuConfigManager osuConfig) + private void load(OsuColour colours, LocalisationManager localisation, OsuConfigManager osuConfig, OverlayColourProvider colourProvider) { scalingMode = osuConfig.GetBindable(OsuSetting.Scaling); scalingSizeX = osuConfig.GetBindable(OsuSetting.ScalingSizeX); scalingSizeY = osuConfig.GetBindable(OsuSetting.ScalingSizeY); - Children = new Drawable[] + AddRange(new Drawable[] { - new SettingsCheckbox - { - LabelText = CommonStrings.Enabled, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Current = enabled, - }, - noTabletMessage = new FillFlowContainer + noTabletMessage = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS }, - Spacing = new Vector2(5f), - Children = new Drawable[] + Padding = SettingsPanel.ContentPaddingV2, + Child = new Container { - new OsuSpriteText + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Masking = true, + CornerRadius = 5, + CornerExponent = 2.5f, + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = TabletSettingsStrings.NoTabletDetected, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Dark2, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(5f), + Padding = new MarginPadding { Horizontal = 8, Vertical = 10 }, + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = TabletSettingsStrings.NoTabletDetected, + Font = OsuFont.Style.Caption1.With(weight: FontWeight.SemiBold), + Colour = colourProvider.Content2, + }, + new LinkFlowContainer(cp => + { + cp.Colour = colours.Orange1; + cp.Font = OsuFont.Style.Caption1.With(weight: FontWeight.SemiBold); + }) + { + TextAnchor = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }.With(t => + { + t.NewLine(); + + const string url = @"https://opentabletdriver.net/Wiki/FAQ/General"; + var formattedSource = MessageFormatter.FormatText(localisation.GetLocalisedString(TabletSettingsStrings.NoTabletDetectedDescription(url))); + + t.AddLinks(formattedSource.Text, formattedSource.Links); + }), + } + }, }, - new LinkFlowContainer(cp => cp.Colour = colours.Yellow) - { - TextAnchor = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }.With(t => - { - t.NewLine(); - - const string url = @"https://opentabletdriver.net/Wiki/FAQ/General"; - var formattedSource = MessageFormatter.FormatText(localisation.GetLocalisedString(TabletSettingsStrings.NoTabletDetectedDescription(url))); - - t.AddLinks(formattedSource.Text, formattedSource.Links); - }), - } + }, }, mainSettings = new FillFlowContainer { Alpha = 0, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0, SettingsSection.ITEM_SPACING), + Spacing = new Vector2(0, SettingsSection.ITEM_SPACING_V2), Direction = FillDirection.Vertical, Children = new Drawable[] { @@ -146,7 +171,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input RelativeSizeAxes = Axes.X, Height = 300, }, - new DangerousSettingsButton + new DangerousSettingsButtonV2 { Text = TabletSettingsStrings.ResetToFullArea, Action = () => @@ -156,9 +181,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input areaOffset.SetDefault(); areaSize.SetDefault(); }, - CanBeShown = { BindTarget = enabled } }, - new SettingsButton + new SettingsButtonV2 { Text = TabletSettingsStrings.ConformToCurrentGameAspectRatio, Action = () => @@ -174,73 +198,62 @@ namespace osu.Game.Overlays.Settings.Sections.Input forceAspectRatio(gameplayWidth / gameplayHeight); }, - CanBeShown = { BindTarget = enabled } }, - new SettingsSlider + new SettingsItemV2(new FormSliderBar { TransferValueOnCommit = true, - LabelText = TabletSettingsStrings.XOffset, + Caption = TabletSettingsStrings.XOffset, Current = offsetX, - CanBeShown = { BindTarget = enabled } - }, - new SettingsSlider + }), + new SettingsItemV2(new FormSliderBar { TransferValueOnCommit = true, - LabelText = TabletSettingsStrings.YOffset, + Caption = TabletSettingsStrings.YOffset, Current = offsetY, - CanBeShown = { BindTarget = enabled } - }, - new SettingsSlider + }), + new SettingsItemV2(new FormSliderBar { TransferValueOnCommit = true, - LabelText = TabletSettingsStrings.Rotation, + Caption = TabletSettingsStrings.Rotation, Current = rotation, - CanBeShown = { BindTarget = enabled } - }, + }), new RotationPresetButtons(tabletHandler) { - Padding = new MarginPadding - { - Horizontal = SettingsPanel.CONTENT_MARGINS - } + Padding = SettingsPanel.ContentPaddingV2, }, - new SettingsSlider + new SettingsItemV2(new FormSliderBar { TransferValueOnCommit = true, - LabelText = TabletSettingsStrings.AspectRatio, + Caption = TabletSettingsStrings.AspectRatio, Current = aspectRatio, - CanBeShown = { BindTarget = enabled } - }, - new SettingsCheckbox + }), + new SettingsItemV2(new FormCheckBox { - LabelText = TabletSettingsStrings.LockAspectRatio, + Caption = TabletSettingsStrings.LockAspectRatio, Current = aspectLock, - CanBeShown = { BindTarget = enabled } - }, - new SettingsSlider + }), + new SettingsItemV2(new FormSliderBar { TransferValueOnCommit = true, - LabelText = CommonStrings.Width, + Caption = CommonStrings.Width, Current = sizeX, - CanBeShown = { BindTarget = enabled } - }, - new SettingsSlider + }), + new SettingsItemV2(new FormSliderBar { TransferValueOnCommit = true, - LabelText = CommonStrings.Height, + Caption = CommonStrings.Height, Current = sizeY, - CanBeShown = { BindTarget = enabled } - }, - new SettingsPercentageSlider + }), + new SettingsItemV2(new FormSliderBar { TransferValueOnCommit = true, - LabelText = TabletSettingsStrings.TipPressureForClick, + Caption = TabletSettingsStrings.TipPressureForClick, Current = pressureThreshold, - CanBeShown = { BindTarget = enabled } - }, + DisplayAsPercentage = true, + }), } }, - }; + }); } protected override void LoadComplete() diff --git a/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs index 0056de6674..5499d49648 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Input.Handlers; using osu.Framework.Localisation; using osu.Game.Configuration; +using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; namespace osu.Game.Overlays.Settings.Sections.Input @@ -15,34 +16,25 @@ namespace osu.Game.Overlays.Settings.Sections.Input /// /// Touch input settings subsection common to all touch handlers (even on different platforms). /// - public partial class TouchSettings : SettingsSubsection + public partial class TouchSettings : InputSubsection { - private readonly InputHandler handler; - protected override LocalisableString Header => TouchSettingsStrings.Touch; + protected override bool IsToggleable => !RuntimeInfo.IsMobile; + public TouchSettings(InputHandler handler) + : base(handler) { - this.handler = handler; } [BackgroundDependencyLoader] private void load(OsuConfigManager osuConfig) { - if (!RuntimeInfo.IsMobile) // don't allow disabling the only input method (touch) on mobile. + Add(new SettingsItemV2(new FormCheckBox { - Add(new SettingsCheckbox - { - LabelText = CommonStrings.Enabled, - Current = handler.Enabled - }); - } - - Add(new SettingsCheckbox - { - LabelText = TouchSettingsStrings.DisableTapsDuringGameplay, + Caption = TouchSettingsStrings.DisableTapsDuringGameplay, Current = osuConfig.GetBindable(OsuSetting.TouchDisableGameplayTaps) - }); + })); } public override IEnumerable FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { @"touchscreen" }); diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs index 0204aa5e64..0b22022362 100644 --- a/osu.Game/Overlays/Settings/Sections/InputSection.cs +++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Handlers; using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Game.Graphics; @@ -45,30 +44,5 @@ namespace osu.Game.Overlays.Settings.Sections Add(handlerSection); } } - - public partial class HandlerSection : SettingsSubsection - { - private readonly InputHandler handler; - - public HandlerSection(InputHandler handler) - { - this.handler = handler; - } - - [BackgroundDependencyLoader] - private void load() - { - Children = new Drawable[] - { - new SettingsCheckbox - { - LabelText = CommonStrings.Enabled, - Current = handler.Enabled - }, - }; - } - - protected override LocalisableString Header => handler.Description; - } } } diff --git a/osu.Game/Overlays/Settings/Sections/InputSubsection.cs b/osu.Game/Overlays/Settings/Sections/InputSubsection.cs new file mode 100644 index 0000000000..985c4fb4f0 --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/InputSubsection.cs @@ -0,0 +1,179 @@ +// 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.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Framework.Input.Handlers; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; +using osuTK; + +namespace osu.Game.Overlays.Settings.Sections +{ + public partial class InputSubsection : SettingsSubsection + { + private readonly InputHandler handler; + + protected override LocalisableString Header => handler.Description; + + /// + /// Whether the input handler can be toggled on/off by the user. + /// + protected virtual bool IsToggleable => true; + + private readonly BindableBool handlerEnabled = new BindableBool(); + + public InputSubsection(InputHandler handler) + { + this.handler = handler; + + FlowContent.AlwaysPresent = true; + } + + [BackgroundDependencyLoader] + private void load() + { + HeaderContainer.Child = new ToggleableHeader(Header, IsToggleable) + { + Current = { BindTarget = handlerEnabled }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + handlerEnabled.BindTo(handler.Enabled); + handlerEnabled.BindValueChanged(v => updateEnabledState(), true); + } + + private void updateEnabledState() + { + // set negative bottom margin to not have too much vertical gap between disabled input subsections. + bool negativeBottomMargin = !handlerEnabled.Value || FlowContent.Count == 0; + HeaderContainer.TransformTo(nameof(Margin), new MarginPadding { Bottom = negativeBottomMargin ? -15 : 0 }, 300, Easing.OutQuint); + + FlowContent.ClearTransforms(); + + if (!handlerEnabled.Value) + { + FlowContent.AutoSizeAxes = Axes.None; + FlowContent.ResizeHeightTo(0, 300, Easing.OutQuint); + + FlowContent.FadeOut(200, Easing.OutQuint); + } + else + { + // enable auto size transform momentarily for smooth pop in animation, and disable it right after the transform is added. + // we don't want this specification to apply when a dropdown in the input settings is being open, it causes too slow animation. + // (try removing the schedule below then watch a settings dropdown menu opening animation). + FlowContent.AutoSizeDuration = 300; + FlowContent.AutoSizeEasing = Easing.OutQuint; + FlowContent.AutoSizeAxes = Axes.Y; + ScheduleAfterChildren(() => FlowContent.AutoSizeDuration = 0); + + FlowContent.FadeIn(300, Easing.OutQuint); + } + } + + private partial class ToggleableHeader : CompositeDrawable + { + private readonly LocalisableString header; + private readonly bool toggleable; + + public readonly BindableBool Current = new BindableBool(true); + + public ToggleableHeader(LocalisableString header, bool toggleable) + { + this.header = header; + this.toggleable = toggleable; + } + + private SwitchButton switchButton = null!; + private OsuSpriteText headerText = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + switchButton = new SwitchButton + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Scale = new Vector2(0.6f), + Position = new Vector2(12, 8), + Rotation = 90, + }, + headerText = new OsuSpriteText + { + Text = header, + Font = OsuFont.GetFont(size: 20), + Margin = new MarginPadding { Vertical = 12 }, + X = 20, + }, + new HoverSounds(), + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + switchButton.Current.ValueChanged += v => Current.Value = v.NewValue; + + Current.BindValueChanged(v => + { + switchButton.Current.Disabled = false; + switchButton.Current.Value = v.NewValue; + switchButton.Current.Disabled = !toggleable; + + updateDisplay(); + }, true); + } + + protected override bool OnHover(HoverEvent e) + { + updateDisplay(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateDisplay(); + base.OnHoverLost(e); + } + + protected override bool OnClick(ClickEvent e) + { + if (toggleable) + { + Current.Toggle(); + switchButton.PlaySample(Current.Value); + } + + updateDisplay(); + return true; + } + + private void updateDisplay() + { + if (toggleable && IsHovered) + headerText.FadeColour(colourProvider.Light1, 300, Easing.OutQuint); + else + headerText.FadeColour(Current.Value ? colourProvider.Content1 : colourProvider.Foreground1, 300, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Overlays/Settings/SettingsSubsection.cs b/osu.Game/Overlays/Settings/SettingsSubsection.cs index 34809f3bbb..d614311bab 100644 --- a/osu.Game/Overlays/Settings/SettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/SettingsSubsection.cs @@ -18,6 +18,8 @@ namespace osu.Game.Overlays.Settings protected readonly FillFlowContainer FlowContent; + protected Container HeaderContainer { get; private set; } = null!; + protected abstract LocalisableString Header { get; } public virtual IEnumerable FilterTerms => new[] { Header }; @@ -53,11 +55,20 @@ namespace osu.Game.Overlays.Settings { AddRangeInternal(new Drawable[] { - new OsuSpriteText + HeaderContainer = new Container { - Text = Header, - Margin = new MarginPadding { Vertical = (header_height - header_font_size) * 0.5f, Horizontal = SettingsPanel.CONTENT_MARGINS }, - Font = OsuFont.GetFont(size: header_font_size), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = SettingsPanel.ContentPaddingV2, + Children = new[] + { + new OsuSpriteText + { + Text = Header, + Font = OsuFont.GetFont(size: header_font_size), + Margin = new MarginPadding { Vertical = (header_height - header_font_size) * 0.5f }, + }, + }, }, FlowContent });