diff --git a/osu.Framework.Tests/Visual/UserInterface/TestSceneTextBox.cs b/osu.Framework.Tests/Visual/UserInterface/TestSceneTextBox.cs index 1242f984f3..e209afa3aa 100644 --- a/osu.Framework.Tests/Visual/UserInterface/TestSceneTextBox.cs +++ b/osu.Framework.Tests/Visual/UserInterface/TestSceneTextBox.cs @@ -122,8 +122,9 @@ public void VariousTextBoxes() TabbableContentContainer = otherTextBoxes }); - otherTextBoxes.Add(new BasicPasswordTextBox + otherTextBoxes.Add(new BasicTextBox { + InputProperties = new TextInputProperties(TextInputType.Password), PlaceholderText = @"Password textbox", Text = "Secret ;)", Size = new Vector2(500, 30), @@ -169,12 +170,13 @@ public void VariousTextBoxes() [Test] public void TestNumbersOnly() { - NumberTextBox numbers = null; + BasicTextBox numbers = null; AddStep("add number textbox", () => { - textBoxes.Add(numbers = new NumberTextBox + textBoxes.Add(numbers = new BasicTextBox { + InputProperties = new TextInputProperties(TextInputType.Number), PlaceholderText = @"Only numbers", Size = new Vector2(500, 30), TabbableContentContainer = textBoxes @@ -1042,13 +1044,6 @@ public partial class InsertableTextBox : BasicTextBox public new void InsertString(string text) => base.InsertString(text); } - private partial class NumberTextBox : BasicTextBox - { - protected override bool CanAddCharacter(char character) => char.IsAsciiDigit(character); - - protected override bool AllowIme => false; - } - private partial class CustomTextBox : BasicTextBox { protected override Drawable GetDrawableCharacter(char c) => new ScalingText(c, FontSize); diff --git a/osu.Framework.Tests/Visual/UserInterface/TestSceneTextBoxEvents.cs b/osu.Framework.Tests/Visual/UserInterface/TestSceneTextBoxEvents.cs index 643924de69..d3211bfd6a 100644 --- a/osu.Framework.Tests/Visual/UserInterface/TestSceneTextBoxEvents.cs +++ b/osu.Framework.Tests/Visual/UserInterface/TestSceneTextBoxEvents.cs @@ -206,7 +206,7 @@ public void TestMovingOrExpandingSelectionInvokesEvent() AddAssert("text input not deactivated", () => textInput.DeactivationQueue.Count == 0); AddAssert("text input not activated again", () => textInput.ActivationQueue.Count == 0); - AddAssert("text input ensure activated", () => textInput.EnsureActivatedQueue.Dequeue() && textInput.EnsureActivatedQueue.Count == 0); + AddAssert("text input ensure activated", () => textInput.EnsureActivatedQueue.Dequeue() != default && textInput.EnsureActivatedQueue.Count == 0); AddStep("click deselection", () => { @@ -217,7 +217,7 @@ public void TestMovingOrExpandingSelectionInvokesEvent() AddAssert("text input not deactivated", () => textInput.DeactivationQueue.Count == 0); AddAssert("text input not activated again", () => textInput.ActivationQueue.Count == 0); - AddAssert("text input ensure activated", () => textInput.EnsureActivatedQueue.Dequeue() && textInput.EnsureActivatedQueue.Count == 0); + AddAssert("text input ensure activated", () => textInput.EnsureActivatedQueue.Dequeue() != default && textInput.EnsureActivatedQueue.Count == 0); AddStep("click-drag selection", () => { @@ -500,7 +500,7 @@ public void TestChangingFocusDoesNotReactivate(bool allowIme) AddStep("add second textbox", () => textInputContainer.Add(secondTextBox = new EventQueuesTextBox { - ImeAllowed = allowIme, + InputProperties = new TextInputProperties(TextInputType.Text, allowIme), Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, CommitOnFocusLost = true, @@ -517,7 +517,7 @@ public void TestChangingFocusDoesNotReactivate(bool allowIme) AddAssert("text input not deactivated", () => textInput.DeactivationQueue.Count == 0); AddAssert("text input not activated again", () => textInput.ActivationQueue.Count == 0); - AddAssert($"text input ensure activated {(allowIme ? "with" : "without")} IME", () => textInput.EnsureActivatedQueue.Dequeue() == allowIme && textInput.EnsureActivatedQueue.Count == 0); + AddAssert($"text input ensure activated {(allowIme ? "with" : "without")} IME", () => textInput.EnsureActivatedQueue.Dequeue().AllowIme == allowIme && textInput.EnsureActivatedQueue.Count == 0); AddStep("commit text", () => InputManager.Key(Key.Enter)); AddAssert("text input deactivated", () => textInput.DeactivationQueue.Dequeue()); @@ -574,10 +574,6 @@ private void testNormalTextInput() public partial class EventQueuesTextBox : TestSceneTextBox.InsertableTextBox { - public bool ImeAllowed { get; set; } = true; - - protected override bool AllowIme => ImeAllowed; - public readonly Queue InputErrorQueue = new Queue(); public readonly Queue UserConsumedTextQueue = new Queue(); public readonly Queue UserRemovedTextQueue = new Queue(); diff --git a/osu.Framework/Graphics/UserInterface/BasicPasswordTextBox.cs b/osu.Framework/Graphics/UserInterface/BasicPasswordTextBox.cs deleted file mode 100644 index b0791317b8..0000000000 --- a/osu.Framework/Graphics/UserInterface/BasicPasswordTextBox.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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.Input; - -namespace osu.Framework.Graphics.UserInterface -{ - public partial class BasicPasswordTextBox : BasicTextBox, ISuppressKeyEventLogging - { - protected virtual char MaskCharacter => '*'; - - protected override bool AllowClipboardExport => false; - - protected override bool AllowWordNavigation => false; - - protected override bool AllowIme => false; - - protected override Drawable AddCharacterToFlow(char c) => base.AddCharacterToFlow(MaskCharacter); - } -} diff --git a/osu.Framework/Graphics/UserInterface/DropdownSearchBar.cs b/osu.Framework/Graphics/UserInterface/DropdownSearchBar.cs index c0879d6ac5..2e1b33c995 100644 --- a/osu.Framework/Graphics/UserInterface/DropdownSearchBar.cs +++ b/osu.Framework/Graphics/UserInterface/DropdownSearchBar.cs @@ -197,20 +197,20 @@ public DropdownTextInputSource(TextInputSource platformSource, GameHost host) platformSource.OnImeResult += TriggerImeResult; } - protected override void ActivateTextInput(bool allowIme) + protected override void ActivateTextInput(TextInputProperties properties) { - base.ActivateTextInput(allowIme); + base.ActivateTextInput(properties); if (allowTextInput) - platformSource.Activate(allowIme, imeRectangle ?? RectangleF.Empty); + platformSource.Activate(properties, imeRectangle ?? RectangleF.Empty); } - protected override void EnsureTextInputActivated(bool allowIme) + protected override void EnsureTextInputActivated(TextInputProperties properties) { - base.EnsureTextInputActivated(allowIme); + base.EnsureTextInputActivated(properties); if (allowTextInput) - platformSource.EnsureActivated(allowIme, imeRectangle); + platformSource.EnsureActivated(properties, imeRectangle); } protected override void DeactivateTextInput() diff --git a/osu.Framework/Graphics/UserInterface/TextBox.cs b/osu.Framework/Graphics/UserInterface/TextBox.cs index e825299cd2..fb1ca73423 100644 --- a/osu.Framework/Graphics/UserInterface/TextBox.cs +++ b/osu.Framework/Graphics/UserInterface/TextBox.cs @@ -29,13 +29,18 @@ namespace osu.Framework.Graphics.UserInterface { - public abstract partial class TextBox : TabbableContainer, IHasCurrentValue, IKeyBindingHandler + public abstract partial class TextBox : TabbableContainer, IHasCurrentValue, IKeyBindingHandler, ICanSuppressKeyEventLogging { protected FillFlowContainer TextFlow { get; private set; } protected Container TextContainer { get; private set; } public override bool HandleNonPositionalInput => HasFocus; + /// + /// A character displayed whenever the type of text input set by is hidden. + /// + protected virtual char MaskCharacter => '*'; + /// /// Padding to be used within the TextContainer. Requires special handling due to the sideways scrolling of text content. /// @@ -50,12 +55,14 @@ public abstract partial class TextBox : TabbableContainer, IHasCurrentValue /// Whether clipboard copying functionality is allowed. /// - protected virtual bool AllowClipboardExport => true; + protected virtual bool AllowClipboardExport => !InputProperties.Type.IsPassword(); /// /// Whether seeking to word boundaries is allowed. /// - protected virtual bool AllowWordNavigation => true; + protected virtual bool AllowWordNavigation => !InputProperties.Type.IsPassword(); + + bool ICanSuppressKeyEventLogging.SuppressKeyEventLogging => InputProperties.Type.IsPassword(); /// /// Represents the left/right selection coordinates of the word double clicked on when dragging. @@ -67,17 +74,13 @@ public abstract partial class TextBox : TabbableContainer, IHasCurrentValue public virtual bool HandleLeftRightArrows => true; + [Obsolete($"Use {nameof(InputProperties)} instead.")] // can be removed 20250506 + protected virtual bool AllowIme => true; + /// - /// Whether to allow IME input when this text box has input focus. + /// A set of properties to consider when interacting with this . /// - /// - /// This is just a hint to the native implementation, some might respect this, - /// while others will ignore and always have the IME (dis)allowed. - /// - /// - /// Useful for situations where IME input is not wanted, such as for passwords, numbers, or romanised text. - /// - protected virtual bool AllowIme => true; + public TextInputProperties InputProperties { get; init; } /// /// Check if a character can be added to this TextBox. @@ -87,9 +90,14 @@ public abstract partial class TextBox : TabbableContainer, IHasCurrentValue true; /// - /// Private helper for , additionally requiring that the character is not a control character. + /// Private helper for , additionally requiring that the character is not a control character and obeys . /// - private bool canAddCharacter(char character) => !char.IsControl(character) && CanAddCharacter(character); + private bool canAddCharacter(char character) + { + return !char.IsControl(character) + && (!InputProperties.Type.IsNumerical() || char.IsAsciiDigit(character)) + && CanAddCharacter(character); + } private bool readOnly; @@ -158,6 +166,10 @@ public bool ReadOnly protected TextBox() { +#pragma warning disable CS0618 // Type or member is obsolete + InputProperties = new TextInputProperties(TextInputType.Text, AllowIme); +#pragma warning restore CS0618 // Type or member is obsolete + Masking = true; Children = new Drawable[] @@ -789,6 +801,9 @@ private string removeCharacters(int number = 1) protected virtual Drawable AddCharacterToFlow(char c) { + if (InputProperties.Type.IsPassword()) + c = MaskCharacter; + // Remove all characters to the right and store them in a local list, // such that their depth can be updated. List charsRight = new List(); @@ -1339,7 +1354,7 @@ protected override void OnFocusLost(FocusLostEvent e) protected override bool OnClick(ClickEvent e) { if (!ReadOnly && textInputBound) - textInput.EnsureActivated(AllowIme); + textInput.EnsureActivated(InputProperties); return !ReadOnly; } @@ -1366,7 +1381,7 @@ private void bindInput([CanBeNull] TextBox previous) if (textInputBound) { - textInput.EnsureActivated(AllowIme); + textInput.EnsureActivated(InputProperties); return; } @@ -1374,9 +1389,9 @@ private void bindInput([CanBeNull] TextBox previous) // We don't deactivate and activate, but instead keep text input active during the focus handoff, so that virtual keyboards on phones don't flicker. if (previous?.textInput == textInput) - textInput.EnsureActivated(AllowIme, ScreenSpaceDrawQuad.AABBFloat); + textInput.EnsureActivated(InputProperties, ScreenSpaceDrawQuad.AABBFloat); else - textInput.Activate(AllowIme, ScreenSpaceDrawQuad.AABBFloat); + textInput.Activate(InputProperties, ScreenSpaceDrawQuad.AABBFloat); textInput.OnTextInput += handleTextInput; textInput.OnImeComposition += handleImeComposition; diff --git a/osu.Framework/Input/ButtonEventManager.cs b/osu.Framework/Input/ButtonEventManager.cs index b10ef23a26..ab8b5e4923 100644 --- a/osu.Framework/Input/ButtonEventManager.cs +++ b/osu.Framework/Input/ButtonEventManager.cs @@ -133,9 +133,18 @@ private void handleButtonUp(InputState state) } if (handledBy != null) - Logger.Log($"{e} handled by {handledBy}.", LoggingTarget.Runtime, LogLevel.Debug); + { + Logger.Log(SuppressLoggingEventInformation(handledBy) + ? $"{e.GetType().Name} handled by {handledBy}." + : $"{e} handled by {handledBy}.", LoggingTarget.Runtime, LogLevel.Debug); + } return handledBy; } + + /// + /// Whether information about the event should be suppressed from logging for the given drawable. + /// + protected virtual bool SuppressLoggingEventInformation(Drawable drawable) => false; } } diff --git a/osu.Framework/Input/ISuppressKeyEventLogging.cs b/osu.Framework/Input/ICanSuppressKeyEventLogging.cs similarity index 51% rename from osu.Framework/Input/ISuppressKeyEventLogging.cs rename to osu.Framework/Input/ICanSuppressKeyEventLogging.cs index 686ab71a97..02f6f47644 100644 --- a/osu.Framework/Input/ISuppressKeyEventLogging.cs +++ b/osu.Framework/Input/ICanSuppressKeyEventLogging.cs @@ -4,10 +4,14 @@ namespace osu.Framework.Input { /// - /// Marker interface which suppresses logging of keyboard input events. + /// An interface which suppresses logging of keyboard input events. /// Useful for password fields, where user input should not be logged. /// - public interface ISuppressKeyEventLogging + public interface ICanSuppressKeyEventLogging { + /// + /// Whether key event logging should be suppressed for this drawable. + /// + bool SuppressKeyEventLogging { get; } } } diff --git a/osu.Framework/Input/InputManager.cs b/osu.Framework/Input/InputManager.cs index debc7cef14..934837af72 100644 --- a/osu.Framework/Input/InputManager.cs +++ b/osu.Framework/Input/InputManager.cs @@ -10,7 +10,6 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Extensions.ListExtensions; -using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; @@ -999,37 +998,13 @@ protected virtual bool PropagateBlockableEvent(SlimReadOnlyListWrapper { foreach (var d in drawables) { - if (!d.TriggerEvent(e)) continue; - - if (shouldLog(e)) - { - string detail = d is ISuppressKeyEventLogging ? e.GetType().ReadableName() : e.ToString(); - Logger.Log($"{detail} handled by {d}.", LoggingTarget.Runtime, LogLevel.Debug); - } - - return true; + if (d.TriggerEvent(e)) + return true; } return false; } - private bool shouldLog(UIEvent eventType) - { - switch (eventType) - { - case KeyDownEvent k: - return !k.Repeat; - - case DragEvent: - case ScrollEvent: - case MouseMoveEvent: - return false; - - default: - return true; - } - } - /// /// Unfocus the current focused drawable if it is no longer in a valid state. /// diff --git a/osu.Framework/Input/KeyEventManager.cs b/osu.Framework/Input/KeyEventManager.cs index b716e7b67c..d1434a3bf4 100644 --- a/osu.Framework/Input/KeyEventManager.cs +++ b/osu.Framework/Input/KeyEventManager.cs @@ -33,5 +33,7 @@ public void HandleRepeat(InputState state) protected override void HandleButtonUp(InputState state, List targets) => PropagateButtonEvent(targets, new KeyUpEvent(state, Button)); + + protected override bool SuppressLoggingEventInformation(Drawable drawable) => drawable is ICanSuppressKeyEventLogging canSuppress && canSuppress.SuppressKeyEventLogging; } } diff --git a/osu.Framework/Input/SDLWindowTextInput.cs b/osu.Framework/Input/SDLWindowTextInput.cs index c0fd539f44..79fa1d848f 100644 --- a/osu.Framework/Input/SDLWindowTextInput.cs +++ b/osu.Framework/Input/SDLWindowTextInput.cs @@ -37,16 +37,16 @@ private void handleTextEditing(string? text, int selectionStart, int selectionLe TriggerImeComposition(text, selectionStart, selectionLength); } - protected override void ActivateTextInput(bool allowIme) + protected override void ActivateTextInput(TextInputProperties properties) { window.TextInput += handleTextInput; window.TextEditing += handleTextEditing; - window.StartTextInput(allowIme); + window.StartTextInput(properties); } - protected override void EnsureTextInputActivated(bool allowIme) + protected override void EnsureTextInputActivated(TextInputProperties properties) { - window.StartTextInput(allowIme); + window.StartTextInput(properties); } protected override void DeactivateTextInput() diff --git a/osu.Framework/Input/TextInputProperties.cs b/osu.Framework/Input/TextInputProperties.cs new file mode 100644 index 0000000000..e44bb6dbd2 --- /dev/null +++ b/osu.Framework/Input/TextInputProperties.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Framework.Input +{ + /// + /// Represents a number of properties to consider during a text input session. + /// + /// The type of text being input. + /// + /// + /// Whether IME should be allowed during this text input session, if supported by the given text input type. + /// + /// + /// Note that this is just a hint to the native implementation, some might respect this, + /// while others will ignore and always have the IME (dis)allowed. + /// + /// + public record struct TextInputProperties(TextInputType Type, bool AllowIme = true); +} diff --git a/osu.Framework/Input/TextInputSource.cs b/osu.Framework/Input/TextInputSource.cs index 237d17f36d..9b77568e30 100644 --- a/osu.Framework/Input/TextInputSource.cs +++ b/osu.Framework/Input/TextInputSource.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 System; using System.Threading; using osu.Framework.Graphics.Primitives; @@ -29,7 +27,7 @@ public class TextInputSource /// Activates this . /// User text input can be acquired through , and . /// - /// Whether input using IME should be allowed. + /// A set of properties to consider during this text input session. /// /// Rough location of where the text will be input, so the native implementation /// can adjust virtual keyboards and IME popups. @@ -37,36 +35,36 @@ public class TextInputSource /// /// Each must be followed by a . /// - public void Activate(bool allowIme, RectangleF imeRectangle) + public void Activate(TextInputProperties properties, RectangleF imeRectangle) { if (Interlocked.Increment(ref activationCounter) == 1) { SetImeRectangle(imeRectangle); - ActivateTextInput(allowIme); + ActivateTextInput(properties); } else // the latest consumer that activated should always take precedence in (dis)allowing IME. - EnsureActivated(allowIme, imeRectangle); + EnsureActivated(properties, imeRectangle); } /// /// Ensures that the native implementation that retrieves user text input is activated /// and that the user can start entering text. /// - /// Whether input using IME should be allowed. + /// A set of properties to consider during this text input session. /// /// Rough location of where the text will be input, so the native implementation /// can adjust virtual keyboards and IME popups. Can be null to avoid changing /// the IME rectangle. /// - public void EnsureActivated(bool allowIme, RectangleF? imeRectangle = null) + public void EnsureActivated(TextInputProperties properties, RectangleF? imeRectangle = null) { if (activationCounter >= 1) { if (imeRectangle.HasValue) SetImeRectangle(imeRectangle.Value); - EnsureTextInputActivated(allowIme); + EnsureTextInputActivated(properties); } } @@ -103,29 +101,29 @@ public virtual void ResetIme() /// /// Invoked on text input. /// - public event Action OnTextInput; + public event Action? OnTextInput; /// /// Invoked when IME composition starts or changes. /// /// Empty string for text means that the composition has been cancelled. - public event ImeCompositionDelegate OnImeComposition; + public event ImeCompositionDelegate? OnImeComposition; /// /// Invoked when IME composition successfully completes. /// - public event Action OnImeResult; + public event Action? OnImeResult; /// /// Activates the native implementation that provides text input. /// Should be overriden per-platform. /// - /// Whether input using IME should be allowed. + /// A set of properties to consider during this text input session. /// /// An active native implementation should call on new text input /// and forward IME composition events through and . /// - protected virtual void ActivateTextInput(bool allowIme) + protected virtual void ActivateTextInput(TextInputProperties properties) { } @@ -134,7 +132,7 @@ protected virtual void ActivateTextInput(bool allowIme) /// /// Only called if the native implementation has been activated with . /// - protected virtual void EnsureTextInputActivated(bool allowIme) + protected virtual void EnsureTextInputActivated(TextInputProperties properties) { } diff --git a/osu.Framework/Input/TextInputType.cs b/osu.Framework/Input/TextInputType.cs new file mode 100644 index 0000000000..2a1645875f --- /dev/null +++ b/osu.Framework/Input/TextInputType.cs @@ -0,0 +1,74 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Framework.Input +{ + public enum TextInputType + { + /// + /// Plain text, default type of text input. + /// + Text, + + /// + /// The text input is a person's name. + /// + Name, + + /// + /// The text input is an email address. + /// + EmailAddress, + + /// + /// The text input is a username. + /// + Username, + + /// + /// The text input is numerical. + /// + Number, + + /// + /// The text input is a password hidden from the user. + /// + Password, + + /// + /// The text input is a numerical password hidden from the user. + /// + NumericalPassword, + } + + public static class TextInputTypeExtensions + { + public static bool IsPassword(this TextInputType type) + { + switch (type) + { + case TextInputType.Password: + case TextInputType.NumericalPassword: + return true; + + default: + return false; + } + } + + public static bool IsNumerical(this TextInputType type) + { + switch (type) + { + case TextInputType.Number: + case TextInputType.NumericalPassword: + return true; + + default: + return false; + } + } + + public static bool SupportsIme(this TextInputType type) => type == TextInputType.Name || type == TextInputType.Text; + } +} diff --git a/osu.Framework/Platform/ISDLWindow.cs b/osu.Framework/Platform/ISDLWindow.cs index 640cd8b099..b675c21b25 100644 --- a/osu.Framework/Platform/ISDLWindow.cs +++ b/osu.Framework/Platform/ISDLWindow.cs @@ -43,7 +43,7 @@ internal interface ISDLWindow : IWindow void UpdateMousePosition(Vector2 position); - void StartTextInput(bool allowIme); + void StartTextInput(TextInputProperties properties); void StopTextInput(); void SetTextInputRect(RectangleF rectangle); void ResetIme(); diff --git a/osu.Framework/Platform/SDL2/SDL2Window_Input.cs b/osu.Framework/Platform/SDL2/SDL2Window_Input.cs index 43a62dd8ba..5c0088392d 100644 --- a/osu.Framework/Platform/SDL2/SDL2Window_Input.cs +++ b/osu.Framework/Platform/SDL2/SDL2Window_Input.cs @@ -172,7 +172,7 @@ private void pollMouse() } } - public virtual void StartTextInput(bool allowIme) => ScheduleCommand(SDL_StartTextInput); + public virtual void StartTextInput(TextInputProperties properties) => ScheduleCommand(SDL_StartTextInput); public void StopTextInput() => ScheduleCommand(SDL_StopTextInput); diff --git a/osu.Framework/Platform/SDL3/SDL3Extensions.cs b/osu.Framework/Platform/SDL3/SDL3Extensions.cs index 711d57fe1b..880407b7eb 100644 --- a/osu.Framework/Platform/SDL3/SDL3Extensions.cs +++ b/osu.Framework/Platform/SDL3/SDL3Extensions.cs @@ -1010,6 +1010,34 @@ public static SDL_Rect ToSDLRect(this RectangleI rectangle) => w = rectangle.Width, }; + public static SDL_TextInputType ToSDLTextInputType(this TextInputType type) + { + switch (type) + { + default: + case TextInputType.Text: + return SDL_TextInputType.SDL_TEXTINPUT_TYPE_TEXT; + + case TextInputType.Name: + return SDL_TextInputType.SDL_TEXTINPUT_TYPE_TEXT_NAME; + + case TextInputType.EmailAddress: + return SDL_TextInputType.SDL_TEXTINPUT_TYPE_TEXT_EMAIL; + + case TextInputType.Username: + return SDL_TextInputType.SDL_TEXTINPUT_TYPE_TEXT_USERNAME; + + case TextInputType.Number: + return SDL_TextInputType.SDL_TEXTINPUT_TYPE_NUMBER; + + case TextInputType.Password: + return SDL_TextInputType.SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN; + + case TextInputType.NumericalPassword: + return SDL_TextInputType.SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN; + } + } + public static unsafe DisplayMode ToDisplayMode(this SDL_DisplayMode mode, int displayIndex) { int bpp; diff --git a/osu.Framework/Platform/SDL3/SDL3Window_Input.cs b/osu.Framework/Platform/SDL3/SDL3Window_Input.cs index 7709f531eb..f2f1870886 100644 --- a/osu.Framework/Platform/SDL3/SDL3Window_Input.cs +++ b/osu.Framework/Platform/SDL3/SDL3Window_Input.cs @@ -187,7 +187,16 @@ private void pollMouse() } } - public virtual void StartTextInput(bool allowIme) => ScheduleCommand(() => SDL_StartTextInput(SDLWindowHandle)); + private SDL_PropertiesID? currentTextInputProperties; + + public virtual void StartTextInput(TextInputProperties properties) => ScheduleCommand(() => + { + currentTextInputProperties ??= SDL_CreateProperties(); + + var props = currentTextInputProperties.Value; + SDL_SetNumberProperty(props, SDL_PROP_TEXTINPUT_TYPE_NUMBER, (long)properties.Type.ToSDLTextInputType()); + SDL_StartTextInputWithProperties(SDLWindowHandle, props); + }); public void StopTextInput() => ScheduleCommand(() => SDL_StopTextInput(SDLWindowHandle)); @@ -198,7 +207,11 @@ private void pollMouse() public virtual void ResetIme() => ScheduleCommand(() => { SDL_StopTextInput(SDLWindowHandle); - SDL_StartTextInput(SDLWindowHandle); + + if (currentTextInputProperties is SDL_PropertiesID props) + SDL_StartTextInputWithProperties(SDLWindowHandle, props); + else + SDL_StartTextInput(SDLWindowHandle); }); public void SetTextInputRect(RectangleF rect) => ScheduleCommand(() => diff --git a/osu.Framework/Platform/Windows/SDL2WindowsWindow.cs b/osu.Framework/Platform/Windows/SDL2WindowsWindow.cs index 4284cbf0e1..4a1d9dafee 100644 --- a/osu.Framework/Platform/Windows/SDL2WindowsWindow.cs +++ b/osu.Framework/Platform/Windows/SDL2WindowsWindow.cs @@ -5,6 +5,7 @@ using System.Drawing; using System.Runtime.InteropServices; using System.Runtime.Versioning; +using osu.Framework.Input; using osu.Framework.Input.Handlers.Mouse; using osu.Framework.Platform.SDL2; using osu.Framework.Platform.Windows.Native; @@ -137,10 +138,10 @@ private void warpCursorFromFocusLoss() #region IME handling - public override void StartTextInput(bool allowIme) + public override void StartTextInput(TextInputProperties properties) { - base.StartTextInput(allowIme); - ScheduleCommand(() => Imm.SetImeAllowed(WindowHandle, allowIme)); + base.StartTextInput(properties); + ScheduleCommand(() => Imm.SetImeAllowed(WindowHandle, properties.Type.SupportsIme() && properties.AllowIme)); } public override void ResetIme() => ScheduleCommand(() => Imm.CancelComposition(WindowHandle)); diff --git a/osu.Framework/Platform/Windows/SDL3WindowsWindow.cs b/osu.Framework/Platform/Windows/SDL3WindowsWindow.cs index d04ea28ba1..5147a787ab 100644 --- a/osu.Framework/Platform/Windows/SDL3WindowsWindow.cs +++ b/osu.Framework/Platform/Windows/SDL3WindowsWindow.cs @@ -5,6 +5,7 @@ using System.Drawing; using System.Runtime.InteropServices; using System.Runtime.Versioning; +using osu.Framework.Input; using osu.Framework.Input.Handlers.Mouse; using osu.Framework.Platform.SDL3; using osu.Framework.Platform.Windows.Native; @@ -92,10 +93,10 @@ private void warpCursorFromFocusLoss() } } - public override void StartTextInput(bool allowIme) + public override void StartTextInput(TextInputProperties properties) { - base.StartTextInput(allowIme); - ScheduleCommand(() => Imm.SetImeAllowed(WindowHandle, allowIme)); + base.StartTextInput(properties); + ScheduleCommand(() => Imm.SetImeAllowed(WindowHandle, properties.Type.SupportsIme() && properties.AllowIme)); } public override void ResetIme() => ScheduleCommand(() => Imm.CancelComposition(WindowHandle)); diff --git a/osu.Framework/Testing/Input/ManualTextInputSource.cs b/osu.Framework/Testing/Input/ManualTextInputSource.cs index def56cb23b..94b722da5e 100644 --- a/osu.Framework/Testing/Input/ManualTextInputSource.cs +++ b/osu.Framework/Testing/Input/ManualTextInputSource.cs @@ -8,8 +8,8 @@ namespace osu.Framework.Testing.Input { public class ManualTextInputSource : TextInputSource { - public readonly Queue ActivationQueue = new Queue(); - public readonly Queue EnsureActivatedQueue = new Queue(); + public readonly Queue ActivationQueue = new Queue(); + public readonly Queue EnsureActivatedQueue = new Queue(); public readonly Queue DeactivationQueue = new Queue(); public void Text(string text) => TriggerTextInput(text); @@ -32,16 +32,16 @@ public override void ResetIme() base.TriggerImeComposition(string.Empty, 0, 0); } - protected override void ActivateTextInput(bool allowIme) + protected override void ActivateTextInput(TextInputProperties properties) { - base.ActivateTextInput(allowIme); - ActivationQueue.Enqueue(allowIme); + base.ActivateTextInput(properties); + ActivationQueue.Enqueue(properties); } - protected override void EnsureTextInputActivated(bool allowIme) + protected override void EnsureTextInputActivated(TextInputProperties properties) { - base.EnsureTextInputActivated(allowIme); - EnsureActivatedQueue.Enqueue(allowIme); + base.EnsureTextInputActivated(properties); + EnsureActivatedQueue.Enqueue(properties); } protected override void DeactivateTextInput()