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..810e2f89db 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,8 @@ public bool ReadOnly protected TextBox() { + InputProperties = new TextInputProperties(TextInputType.Text, true); + Masking = true; Children = new Drawable[] @@ -789,6 +799,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 +1352,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 +1379,7 @@ private void bindInput([CanBeNull] TextBox previous) if (textInputBound) { - textInput.EnsureActivated(AllowIme); + textInput.EnsureActivated(InputProperties); return; } @@ -1374,9 +1387,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/ISuppressKeyEventLogging.cs b/osu.Framework/Input/ISuppressKeyEventLogging.cs index 686ab71a97..02f6f47644 100644 --- a/osu.Framework/Input/ISuppressKeyEventLogging.cs +++ b/osu.Framework/Input/ISuppressKeyEventLogging.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..4235dc2ad0 100644 --- a/osu.Framework/Input/InputManager.cs +++ b/osu.Framework/Input/InputManager.cs @@ -1003,7 +1003,10 @@ protected virtual bool PropagateBlockableEvent(SlimReadOnlyListWrapper if (shouldLog(e)) { - string detail = d is ISuppressKeyEventLogging ? e.GetType().ReadableName() : e.ToString(); + string detail = d is ICanSuppressKeyEventLogging kd && kd.SuppressKeyEventLogging + ? e.GetType().ReadableName() + : e.ToString(); + Logger.Log($"{detail} handled by {d}.", LoggingTarget.Runtime, LogLevel.Debug); } 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..66d7f6638e --- /dev/null +++ b/osu.Framework/Input/TextInputProperties.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. + +namespace osu.Framework.Input +{ + /// + /// Represents a number of properties to consider during a text input session. + /// + /// The type of text being inputted. + /// + /// + /// Whether IME should be allowed during this text input session. + /// Useful for situations where IME input is not wanted, such as for passwords, numbers, or romanised text. + /// + /// + /// 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); +} 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..42da0afffe --- /dev/null +++ b/osu.Framework/Input/TextInputType.cs @@ -0,0 +1,72 @@ +// 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; + } + } + } +} diff --git a/osu.Framework/Platform/ISDLWindow.cs b/osu.Framework/Platform/ISDLWindow.cs index 66f627e28e..fde3545b25 100644 --- a/osu.Framework/Platform/ISDLWindow.cs +++ b/osu.Framework/Platform/ISDLWindow.cs @@ -42,7 +42,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 5c340f8e37..130d3dc50d 100644 --- a/osu.Framework/Platform/SDL3/SDL3Window_Input.cs +++ b/osu.Framework/Platform/SDL3/SDL3Window_Input.cs @@ -187,7 +187,13 @@ private void pollMouse() } } - public virtual void StartTextInput(bool allowIme) => ScheduleCommand(() => SDL_StartTextInput(SDLWindowHandle)); + public virtual void StartTextInput(TextInputProperties properties) => ScheduleCommand(() => + { + var props = SDL_CreateProperties(); + SDL_SetNumberProperty(props, SDL_PROP_TEXTINPUT_TYPE_NUMBER, (long)properties.Type.ToSDLTextInputType()); + SDL_StartTextInputWithProperties(SDLWindowHandle, props); + SDL_DestroyProperties(props); + }); public void StopTextInput() => ScheduleCommand(() => SDL_StopTextInput(SDLWindowHandle)); diff --git a/osu.Framework/Platform/Windows/SDL2WindowsWindow.cs b/osu.Framework/Platform/Windows/SDL2WindowsWindow.cs index 4284cbf0e1..f04bcd4c75 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.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 7c065b98f2..e431ef4f83 100644 --- a/osu.Framework/Platform/Windows/SDL3WindowsWindow.cs +++ b/osu.Framework/Platform/Windows/SDL3WindowsWindow.cs @@ -8,6 +8,7 @@ using System.Runtime.InteropServices; using System.Runtime.Versioning; using osu.Framework.Allocation; +using osu.Framework.Input; using osu.Framework.Input.Handlers.Mouse; using osu.Framework.Platform.SDL3; using osu.Framework.Platform.Windows.Native; @@ -127,10 +128,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.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()