From edcc607f4bb5d12cf2eb4c1ccd08f75537b711a6 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 10 Mar 2025 03:48:12 -0400 Subject: [PATCH] Bring back touch control under a setting --- .../TestSceneManiaTouchInput.cs | 81 +++++++ osu.Game.Rulesets.Mania/UI/Column.cs | 12 +- .../UI/DrawableManiaRuleset.cs | 2 + .../UI/ManiaTouchInputArea.cs | 223 ++++++++++++++++++ 4 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Mania/UI/ManiaTouchInputArea.cs diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaTouchInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaTouchInput.cs index dc95cd9ca0..364f7240e1 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaTouchInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaTouchInput.cs @@ -3,10 +3,13 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Testing; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; +using osuTK; namespace osu.Game.Rulesets.Mania.Tests { @@ -14,6 +17,11 @@ namespace osu.Game.Rulesets.Mania.Tests { protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + [SetUp] + public void SetUp() => Schedule(() => toggleTouchControls(false)); + + #region Without touch controls + [Test] public void TestTouchInput() { @@ -63,6 +71,79 @@ namespace osu.Game.Rulesets.Mania.Tests () => Does.Not.Contain(getColumn(0).Action.Value)); } + #endregion + + #region With touch controls + + [Test] + public void TestTouchAreaNotInitiallyVisible() + { + AddStep("enable touch controls", () => toggleTouchControls(true)); + AddAssert("touch area not visible", () => getTouchOverlay()?.State.Value == Visibility.Hidden); + } + + [Test] + public void TestPressReceptors() + { + AddStep("enable touch controls", () => toggleTouchControls(true)); + AddAssert("touch area not visible", () => getTouchOverlay()?.State.Value == Visibility.Hidden); + + for (int i = 0; i < 4; i++) + { + int index = i; + + AddStep($"touch receptor {index}", () => InputManager.BeginTouch(new Touch(TouchSource.Touch1, getReceptor(index).ScreenSpaceDrawQuad.Centre))); + + AddAssert("action sent", + () => this.ChildrenOfType().SelectMany(m => m.KeyBindingContainer.PressedActions), + () => Does.Contain(getReceptor(index).Action.Value)); + + AddStep($"release receptor {index}", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, getReceptor(index).ScreenSpaceDrawQuad.Centre))); + + AddAssert("touch area visible", () => getTouchOverlay()?.State.Value == Visibility.Visible); + } + } + + [Test] + public void TestColumnsNotTouchableWithTouchControls() + { + AddStep("enable touch controls", () => toggleTouchControls(true)); + + AddStep("touch receptor 0", () => InputManager.BeginTouch(new Touch(TouchSource.Touch1, getReceptor(0).ScreenSpaceDrawQuad.Centre))); + + AddAssert("action sent", + () => this.ChildrenOfType().SelectMany(m => m.KeyBindingContainer.PressedActions), + () => Does.Contain(getReceptor(0).Action.Value)); + + AddStep("release receptor 0", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, getReceptor(0).ScreenSpaceDrawQuad.Centre))); + + AddAssert("touch area visible", () => getTouchOverlay()?.State.Value == Visibility.Visible); + + AddStep("touch column 0", () => InputManager.BeginTouch(new Touch(TouchSource.Touch1, getColumn(0).ScreenSpaceDrawQuad.Centre + new Vector2(0f, -50f)))); + + AddAssert("action not sent", + () => this.ChildrenOfType().SelectMany(m => m.KeyBindingContainer.PressedActions), + () => Does.Not.Contain(getColumn(0).Action.Value)); + + AddStep("release column 0", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, getColumn(0).ScreenSpaceDrawQuad.Centre + new Vector2(0f, -50f)))); + + AddAssert("action not sent", + () => this.ChildrenOfType().SelectMany(m => m.KeyBindingContainer.PressedActions), + () => Does.Not.Contain(getColumn(0).Action.Value)); + } + + #endregion + + private void toggleTouchControls(bool enabled) + { + var maniaConfig = (ManiaRulesetConfigManager)RulesetConfigs.GetConfigFor(CreatePlayerRuleset())!; + maniaConfig.SetValue(ManiaRulesetSetting.TouchControls, enabled); + } + + private ManiaTouchInputArea? getTouchOverlay() => this.ChildrenOfType().SingleOrDefault(); + + private ManiaTouchInputArea.ColumnInputReceptor getReceptor(int index) => this.ChildrenOfType().ElementAt(index); + private Column getColumn(int index) => this.ChildrenOfType().ElementAt(index); } } diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 5425965897..f9f0c21f0d 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -12,6 +12,7 @@ using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Game.Extensions; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Skinning; @@ -57,6 +58,8 @@ namespace osu.Game.Rulesets.Mania.UI public readonly Bindable AccentColour = new Bindable(Color4.Black); + private IBindable touchControls = null!; + public Column(int index, bool isSpecial) { Index = index; @@ -77,7 +80,7 @@ namespace osu.Game.Rulesets.Mania.UI private ISkinSource skin { get; set; } = null!; [BackgroundDependencyLoader] - private void load(GameHost host) + private void load(GameHost host, ManiaRulesetConfigManager? rulesetConfig) { SkinnableDrawable keyArea; @@ -115,6 +118,9 @@ namespace osu.Game.Rulesets.Mania.UI RegisterPool(10, 50); RegisterPool(10, 50); RegisterPool(10, 50); + + if (rulesetConfig != null) + touchControls = rulesetConfig.GetBindable(ManiaRulesetSetting.TouchControls); } private void onSourceChanged() @@ -193,6 +199,10 @@ namespace osu.Game.Rulesets.Mania.UI protected override bool OnTouchDown(TouchDownEvent e) { + // if touch controls are enabled, disallow columns from handling touch directly. + if (touchControls.Value) + return false; + maniaInputManager?.KeyBindingContainer.TriggerPressed(Action.Value); touchActivationCount++; return true; diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 42e481a8dd..c53329599d 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -112,6 +112,8 @@ namespace osu.Game.Rulesets.Mania.UI configScrollSpeed.BindValueChanged(speed => TargetTimeRange = ComputeScrollTime(speed.NewValue)); TimeRange.Value = TargetTimeRange = currentTimeRange = ComputeScrollTime(configScrollSpeed.Value); + + KeyBindingInputManager.Add(new ManiaTouchInputArea(this)); } protected override void AdjustScrollSpeed(int amount) => configScrollSpeed.Value += amount; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaTouchInputArea.cs b/osu.Game.Rulesets.Mania/UI/ManiaTouchInputArea.cs new file mode 100644 index 0000000000..bfa8dcab34 --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/ManiaTouchInputArea.cs @@ -0,0 +1,223 @@ +// 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 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.Events; +using osu.Game.Configuration; +using osu.Game.Rulesets.Mania.Configuration; +using osuTK; + +namespace osu.Game.Rulesets.Mania.UI +{ + /// + /// An overlay that captures and displays osu!mania mouse and touch input. + /// + public partial class ManiaTouchInputArea : VisibilityContainer + { + private readonly DrawableManiaRuleset drawableRuleset; + + // visibility state affects our child. we always want to handle input. + public override bool PropagatePositionalInputSubTree => true; + public override bool PropagateNonPositionalInputSubTree => true; + + [SettingSource("Spacing", "The spacing between receptors.")] + public BindableFloat Spacing { get; } = new BindableFloat(10) + { + Precision = 1, + MinValue = 0, + MaxValue = 100, + }; + + [SettingSource("Opacity", "The receptor opacity.")] + public BindableFloat Opacity { get; } = new BindableFloat(1) + { + Precision = 0.1f, + MinValue = 0, + MaxValue = 1 + }; + + [Resolved] + private ManiaRulesetConfigManager rulesetConfig { get; set; } = null!; + + private GridContainer gridContainer = null!; + + private readonly BindableBool touchControls = new BindableBool(); + + public ManiaTouchInputArea(DrawableManiaRuleset drawableRuleset) + { + this.drawableRuleset = drawableRuleset; + + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; + + RelativeSizeAxes = Axes.Both; + Height = 0.5f; + } + + [BackgroundDependencyLoader] + private void load() + { + List receptorGridContent = new List(); + List receptorGridDimensions = new List(); + + bool first = true; + + foreach (var stage in drawableRuleset.Playfield.Stages) + { + foreach (var column in stage.Columns) + { + if (!first) + { + receptorGridContent.Add(new Gutter { Spacing = { BindTarget = Spacing } }); + receptorGridDimensions.Add(new Dimension(GridSizeMode.AutoSize)); + } + + receptorGridContent.Add(new ColumnInputReceptor + { + Action = { BindTarget = column.Action }, + Enabled = { BindTarget = touchControls }, + }); + receptorGridDimensions.Add(new Dimension()); + + first = false; + } + } + + InternalChild = gridContainer = new GridContainer + { + RelativeSizeAxes = Axes.Both, + AlwaysPresent = true, + Content = new[] { receptorGridContent.ToArray() }, + ColumnDimensions = receptorGridDimensions.ToArray() + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + rulesetConfig.BindWith(ManiaRulesetSetting.TouchControls, touchControls); + Opacity.BindValueChanged(o => Alpha = o.NewValue, true); + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + // Hide whenever the keyboard is used. + Hide(); + return false; + } + + protected override bool OnTouchDown(TouchDownEvent e) + { + if (touchControls.Value) + { + Show(); + return true; + } + + return false; + } + + protected override void PopIn() + { + gridContainer.FadeIn(500, Easing.OutQuint); + } + + protected override void PopOut() + { + gridContainer.FadeOut(300); + } + + public partial class ColumnInputReceptor : CompositeDrawable + { + public readonly IBindable Action = new Bindable(); + public readonly IBindable Enabled = new BindableBool(); + + private readonly Box highlightOverlay; + + [Resolved] + private ManiaInputManager? inputManager { get; set; } + + private bool isPressed; + + public ColumnInputReceptor() + { + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.15f, + }, + highlightOverlay = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Blending = BlendingParameters.Additive, + } + } + } + }; + } + + protected override bool OnTouchDown(TouchDownEvent e) + { + if (Enabled.Value) + { + updateButton(true); + return false; // handled by parent container to show overlay. + } + + return false; + } + + protected override void OnTouchUp(TouchUpEvent e) + { + updateButton(false); + } + + private void updateButton(bool press) + { + if (press == isPressed) + return; + + isPressed = press; + + if (press) + { + inputManager?.KeyBindingContainer.TriggerPressed(Action.Value); + highlightOverlay.FadeTo(0.1f, 80, Easing.OutQuint); + } + else + { + inputManager?.KeyBindingContainer.TriggerReleased(Action.Value); + highlightOverlay.FadeTo(0, 400, Easing.OutQuint); + } + } + } + + private partial class Gutter : Drawable + { + public readonly IBindable Spacing = new Bindable(); + + public Gutter() + { + Spacing.BindValueChanged(s => Size = new Vector2(s.NewValue)); + } + } + } +}