Skip to content

Commit

Permalink
Merge pull request #6408 from frenzibyte/text-input-properties
Browse files Browse the repository at this point in the history
Allow `TextBox` to specify type of text being input
  • Loading branch information
peppy authored Jan 14, 2025
2 parents c3a701a + 416718c commit ffac972
Show file tree
Hide file tree
Showing 20 changed files with 240 additions and 129 deletions.
15 changes: 5 additions & 10 deletions osu.Framework.Tests/Visual/UserInterface/TestSceneTextBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1076,13 +1078,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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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", () =>
{
Expand All @@ -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", () =>
{
Expand Down Expand Up @@ -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,
Expand All @@ -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());
Expand Down Expand Up @@ -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<bool> InputErrorQueue = new Queue<bool>();
public readonly Queue<string> UserConsumedTextQueue = new Queue<string>();
public readonly Queue<string> UserRemovedTextQueue = new Queue<string>();
Expand Down
20 changes: 0 additions & 20 deletions osu.Framework/Graphics/UserInterface/BasicPasswordTextBox.cs

This file was deleted.

12 changes: 6 additions & 6 deletions osu.Framework/Graphics/UserInterface/DropdownSearchBar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,20 +192,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()
Expand Down
51 changes: 33 additions & 18 deletions osu.Framework/Graphics/UserInterface/TextBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,18 @@

namespace osu.Framework.Graphics.UserInterface
{
public abstract partial class TextBox : TabbableContainer, IHasCurrentValue<string>, IKeyBindingHandler<PlatformAction>
public abstract partial class TextBox : TabbableContainer, IHasCurrentValue<string>, IKeyBindingHandler<PlatformAction>, ICanSuppressKeyEventLogging
{
protected FillFlowContainer TextFlow { get; private set; }
protected Container TextContainer { get; private set; }

public override bool HandleNonPositionalInput => HasFocus;

/// <summary>
/// A character displayed whenever the type of text input set by <see cref="TextInputProperties.Type"/> is hidden.
/// </summary>
protected virtual char MaskCharacter => '*';

/// <summary>
/// Padding to be used within the TextContainer. Requires special handling due to the sideways scrolling of text content.
/// </summary>
Expand All @@ -50,12 +55,14 @@ public abstract partial class TextBox : TabbableContainer, IHasCurrentValue<stri
/// <summary>
/// Whether clipboard copying functionality is allowed.
/// </summary>
protected virtual bool AllowClipboardExport => true;
protected virtual bool AllowClipboardExport => !InputProperties.Type.IsPassword();

/// <summary>
/// Whether seeking to word boundaries is allowed.
/// </summary>
protected virtual bool AllowWordNavigation => true;
protected virtual bool AllowWordNavigation => !InputProperties.Type.IsPassword();

bool ICanSuppressKeyEventLogging.SuppressKeyEventLogging => InputProperties.Type.IsPassword();

/// <summary>
/// Represents the left/right selection coordinates of the word double clicked on when dragging.
Expand All @@ -67,17 +74,13 @@ public abstract partial class TextBox : TabbableContainer, IHasCurrentValue<stri
/// </summary>
public virtual bool HandleLeftRightArrows => true;

[Obsolete($"Use {nameof(InputProperties)} instead.")] // can be removed 20250506
protected virtual bool AllowIme => true;

/// <summary>
/// Whether to allow IME input when this text box has input focus.
/// A set of properties to consider when interacting with this <see cref="TextBox"/>.
/// </summary>
/// <remarks>
/// This is just a hint to the native implementation, some might respect this,
/// while others will ignore and always have the IME (dis)allowed.
/// </remarks>
/// <example>
/// Useful for situations where IME input is not wanted, such as for passwords, numbers, or romanised text.
/// </example>
protected virtual bool AllowIme => true;
public TextInputProperties InputProperties { get; init; }

/// <summary>
/// Check if a character can be added to this TextBox.
Expand All @@ -87,9 +90,14 @@ public abstract partial class TextBox : TabbableContainer, IHasCurrentValue<stri
protected virtual bool CanAddCharacter(char character) => true;

/// <summary>
/// Private helper for <see cref="CanAddCharacter"/>, additionally requiring that the character is not a control character.
/// Private helper for <see cref="CanAddCharacter"/>, additionally requiring that the character is not a control character and obeys <see cref="TextInputProperties.Type"/>.
/// </summary>
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;

Expand Down Expand Up @@ -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[]
Expand Down Expand Up @@ -790,6 +802,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<Drawable> charsRight = new List<Drawable>();
Expand Down Expand Up @@ -1340,7 +1355,7 @@ protected override void OnFocusLost(FocusLostEvent e)
protected override bool OnClick(ClickEvent e)
{
if (!ReadOnly && textInputBound)
textInput.EnsureActivated(AllowIme);
textInput.EnsureActivated(InputProperties);

return !ReadOnly;
}
Expand All @@ -1367,17 +1382,17 @@ private void bindInput([CanBeNull] TextBox previous)

if (textInputBound)
{
textInput.EnsureActivated(AllowIme);
textInput.EnsureActivated(InputProperties);
return;
}

// TextBox has special handling of text input activation when focus is changed directly from one TextBox to another.
// 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;
Expand Down
11 changes: 10 additions & 1 deletion osu.Framework/Input/ButtonEventManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/// <summary>
/// Whether information about the event should be suppressed from logging for the given drawable.
/// </summary>
protected virtual bool SuppressLoggingEventInformation(Drawable drawable) => false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
namespace osu.Framework.Input
{
/// <summary>
/// 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.
/// </summary>
public interface ISuppressKeyEventLogging
public interface ICanSuppressKeyEventLogging
{
/// <summary>
/// Whether key event logging should be suppressed for this drawable.
/// </summary>
bool SuppressKeyEventLogging { get; }
}
}
29 changes: 2 additions & 27 deletions osu.Framework/Input/InputManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1002,37 +1001,13 @@ protected virtual bool PropagateBlockableEvent(SlimReadOnlyListWrapper<Drawable>
{
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;
}
}

/// <summary>
/// Unfocus the current focused drawable if it is no longer in a valid state.
/// </summary>
Expand Down
2 changes: 2 additions & 0 deletions osu.Framework/Input/KeyEventManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,7 @@ public void HandleRepeat(InputState state)

protected override void HandleButtonUp(InputState state, List<Drawable> targets) =>
PropagateButtonEvent(targets, new KeyUpEvent(state, Button));

protected override bool SuppressLoggingEventInformation(Drawable drawable) => drawable is ICanSuppressKeyEventLogging canSuppress && canSuppress.SuppressKeyEventLogging;
}
}
8 changes: 4 additions & 4 deletions osu.Framework/Input/SDLWindowTextInput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
20 changes: 20 additions & 0 deletions osu.Framework/Input/TextInputProperties.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

namespace osu.Framework.Input
{
/// <summary>
/// Represents a number of properties to consider during a text input session.
/// </summary>
/// <param name="Type">The type of text being input.</param>
/// <param name="AllowIme">
/// <para>
/// Whether IME should be allowed during this text input session, if supported by the given text input type.
/// </para>
/// <para>
/// 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.
/// </para>
/// </param>
public record struct TextInputProperties(TextInputType Type, bool AllowIme = true);
}
Loading

0 comments on commit ffac972

Please sign in to comment.