Skip to content

Commit ffac972

Browse files
authored
Merge pull request #6408 from frenzibyte/text-input-properties
Allow `TextBox` to specify type of text being input
2 parents c3a701a + 416718c commit ffac972

20 files changed

+240
-129
lines changed

osu.Framework.Tests/Visual/UserInterface/TestSceneTextBox.cs

+5-10
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,9 @@ public void VariousTextBoxes()
122122
TabbableContentContainer = otherTextBoxes
123123
});
124124

125-
otherTextBoxes.Add(new BasicPasswordTextBox
125+
otherTextBoxes.Add(new BasicTextBox
126126
{
127+
InputProperties = new TextInputProperties(TextInputType.Password),
127128
PlaceholderText = @"Password textbox",
128129
Text = "Secret ;)",
129130
Size = new Vector2(500, 30),
@@ -169,12 +170,13 @@ public void VariousTextBoxes()
169170
[Test]
170171
public void TestNumbersOnly()
171172
{
172-
NumberTextBox numbers = null;
173+
BasicTextBox numbers = null;
173174

174175
AddStep("add number textbox", () =>
175176
{
176-
textBoxes.Add(numbers = new NumberTextBox
177+
textBoxes.Add(numbers = new BasicTextBox
177178
{
179+
InputProperties = new TextInputProperties(TextInputType.Number),
178180
PlaceholderText = @"Only numbers",
179181
Size = new Vector2(500, 30),
180182
TabbableContentContainer = textBoxes
@@ -1076,13 +1078,6 @@ public partial class InsertableTextBox : BasicTextBox
10761078
public new void InsertString(string text) => base.InsertString(text);
10771079
}
10781080

1079-
private partial class NumberTextBox : BasicTextBox
1080-
{
1081-
protected override bool CanAddCharacter(char character) => char.IsAsciiDigit(character);
1082-
1083-
protected override bool AllowIme => false;
1084-
}
1085-
10861081
private partial class CustomTextBox : BasicTextBox
10871082
{
10881083
protected override Drawable GetDrawableCharacter(char c) => new ScalingText(c, FontSize);

osu.Framework.Tests/Visual/UserInterface/TestSceneTextBoxEvents.cs

+4-8
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ public void TestMovingOrExpandingSelectionInvokesEvent()
206206

207207
AddAssert("text input not deactivated", () => textInput.DeactivationQueue.Count == 0);
208208
AddAssert("text input not activated again", () => textInput.ActivationQueue.Count == 0);
209-
AddAssert("text input ensure activated", () => textInput.EnsureActivatedQueue.Dequeue() && textInput.EnsureActivatedQueue.Count == 0);
209+
AddAssert("text input ensure activated", () => textInput.EnsureActivatedQueue.Dequeue() != default && textInput.EnsureActivatedQueue.Count == 0);
210210

211211
AddStep("click deselection", () =>
212212
{
@@ -217,7 +217,7 @@ public void TestMovingOrExpandingSelectionInvokesEvent()
217217

218218
AddAssert("text input not deactivated", () => textInput.DeactivationQueue.Count == 0);
219219
AddAssert("text input not activated again", () => textInput.ActivationQueue.Count == 0);
220-
AddAssert("text input ensure activated", () => textInput.EnsureActivatedQueue.Dequeue() && textInput.EnsureActivatedQueue.Count == 0);
220+
AddAssert("text input ensure activated", () => textInput.EnsureActivatedQueue.Dequeue() != default && textInput.EnsureActivatedQueue.Count == 0);
221221

222222
AddStep("click-drag selection", () =>
223223
{
@@ -500,7 +500,7 @@ public void TestChangingFocusDoesNotReactivate(bool allowIme)
500500

501501
AddStep("add second textbox", () => textInputContainer.Add(secondTextBox = new EventQueuesTextBox
502502
{
503-
ImeAllowed = allowIme,
503+
InputProperties = new TextInputProperties(TextInputType.Text, allowIme),
504504
Anchor = Anchor.CentreLeft,
505505
Origin = Anchor.CentreLeft,
506506
CommitOnFocusLost = true,
@@ -517,7 +517,7 @@ public void TestChangingFocusDoesNotReactivate(bool allowIme)
517517

518518
AddAssert("text input not deactivated", () => textInput.DeactivationQueue.Count == 0);
519519
AddAssert("text input not activated again", () => textInput.ActivationQueue.Count == 0);
520-
AddAssert($"text input ensure activated {(allowIme ? "with" : "without")} IME", () => textInput.EnsureActivatedQueue.Dequeue() == allowIme && textInput.EnsureActivatedQueue.Count == 0);
520+
AddAssert($"text input ensure activated {(allowIme ? "with" : "without")} IME", () => textInput.EnsureActivatedQueue.Dequeue().AllowIme == allowIme && textInput.EnsureActivatedQueue.Count == 0);
521521

522522
AddStep("commit text", () => InputManager.Key(Key.Enter));
523523
AddAssert("text input deactivated", () => textInput.DeactivationQueue.Dequeue());
@@ -574,10 +574,6 @@ private void testNormalTextInput()
574574

575575
public partial class EventQueuesTextBox : TestSceneTextBox.InsertableTextBox
576576
{
577-
public bool ImeAllowed { get; set; } = true;
578-
579-
protected override bool AllowIme => ImeAllowed;
580-
581577
public readonly Queue<bool> InputErrorQueue = new Queue<bool>();
582578
public readonly Queue<string> UserConsumedTextQueue = new Queue<string>();
583579
public readonly Queue<string> UserRemovedTextQueue = new Queue<string>();

osu.Framework/Graphics/UserInterface/BasicPasswordTextBox.cs

-20
This file was deleted.

osu.Framework/Graphics/UserInterface/DropdownSearchBar.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -192,20 +192,20 @@ public DropdownTextInputSource(TextInputSource platformSource, GameHost host)
192192
platformSource.OnImeResult += TriggerImeResult;
193193
}
194194

195-
protected override void ActivateTextInput(bool allowIme)
195+
protected override void ActivateTextInput(TextInputProperties properties)
196196
{
197-
base.ActivateTextInput(allowIme);
197+
base.ActivateTextInput(properties);
198198

199199
if (allowTextInput)
200-
platformSource.Activate(allowIme, imeRectangle ?? RectangleF.Empty);
200+
platformSource.Activate(properties, imeRectangle ?? RectangleF.Empty);
201201
}
202202

203-
protected override void EnsureTextInputActivated(bool allowIme)
203+
protected override void EnsureTextInputActivated(TextInputProperties properties)
204204
{
205-
base.EnsureTextInputActivated(allowIme);
205+
base.EnsureTextInputActivated(properties);
206206

207207
if (allowTextInput)
208-
platformSource.EnsureActivated(allowIme, imeRectangle);
208+
platformSource.EnsureActivated(properties, imeRectangle);
209209
}
210210

211211
protected override void DeactivateTextInput()

osu.Framework/Graphics/UserInterface/TextBox.cs

+33-18
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,18 @@
2929

3030
namespace osu.Framework.Graphics.UserInterface
3131
{
32-
public abstract partial class TextBox : TabbableContainer, IHasCurrentValue<string>, IKeyBindingHandler<PlatformAction>
32+
public abstract partial class TextBox : TabbableContainer, IHasCurrentValue<string>, IKeyBindingHandler<PlatformAction>, ICanSuppressKeyEventLogging
3333
{
3434
protected FillFlowContainer TextFlow { get; private set; }
3535
protected Container TextContainer { get; private set; }
3636

3737
public override bool HandleNonPositionalInput => HasFocus;
3838

39+
/// <summary>
40+
/// A character displayed whenever the type of text input set by <see cref="TextInputProperties.Type"/> is hidden.
41+
/// </summary>
42+
protected virtual char MaskCharacter => '*';
43+
3944
/// <summary>
4045
/// Padding to be used within the TextContainer. Requires special handling due to the sideways scrolling of text content.
4146
/// </summary>
@@ -50,12 +55,14 @@ public abstract partial class TextBox : TabbableContainer, IHasCurrentValue<stri
5055
/// <summary>
5156
/// Whether clipboard copying functionality is allowed.
5257
/// </summary>
53-
protected virtual bool AllowClipboardExport => true;
58+
protected virtual bool AllowClipboardExport => !InputProperties.Type.IsPassword();
5459

5560
/// <summary>
5661
/// Whether seeking to word boundaries is allowed.
5762
/// </summary>
58-
protected virtual bool AllowWordNavigation => true;
63+
protected virtual bool AllowWordNavigation => !InputProperties.Type.IsPassword();
64+
65+
bool ICanSuppressKeyEventLogging.SuppressKeyEventLogging => InputProperties.Type.IsPassword();
5966

6067
/// <summary>
6168
/// 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<stri
6774
/// </summary>
6875
public virtual bool HandleLeftRightArrows => true;
6976

77+
[Obsolete($"Use {nameof(InputProperties)} instead.")] // can be removed 20250506
78+
protected virtual bool AllowIme => true;
79+
7080
/// <summary>
71-
/// Whether to allow IME input when this text box has input focus.
81+
/// A set of properties to consider when interacting with this <see cref="TextBox"/>.
7282
/// </summary>
73-
/// <remarks>
74-
/// This is just a hint to the native implementation, some might respect this,
75-
/// while others will ignore and always have the IME (dis)allowed.
76-
/// </remarks>
77-
/// <example>
78-
/// Useful for situations where IME input is not wanted, such as for passwords, numbers, or romanised text.
79-
/// </example>
80-
protected virtual bool AllowIme => true;
83+
public TextInputProperties InputProperties { get; init; }
8184

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

8992
/// <summary>
90-
/// Private helper for <see cref="CanAddCharacter"/>, additionally requiring that the character is not a control character.
93+
/// Private helper for <see cref="CanAddCharacter"/>, additionally requiring that the character is not a control character and obeys <see cref="TextInputProperties.Type"/>.
9194
/// </summary>
92-
private bool canAddCharacter(char character) => !char.IsControl(character) && CanAddCharacter(character);
95+
private bool canAddCharacter(char character)
96+
{
97+
return !char.IsControl(character)
98+
&& (!InputProperties.Type.IsNumerical() || char.IsAsciiDigit(character))
99+
&& CanAddCharacter(character);
100+
}
93101

94102
private bool readOnly;
95103

@@ -158,6 +166,10 @@ public bool ReadOnly
158166

159167
protected TextBox()
160168
{
169+
#pragma warning disable CS0618 // Type or member is obsolete
170+
InputProperties = new TextInputProperties(TextInputType.Text, AllowIme);
171+
#pragma warning restore CS0618 // Type or member is obsolete
172+
161173
Masking = true;
162174

163175
Children = new Drawable[]
@@ -790,6 +802,9 @@ private string removeCharacters(int number = 1)
790802

791803
protected virtual Drawable AddCharacterToFlow(char c)
792804
{
805+
if (InputProperties.Type.IsPassword())
806+
c = MaskCharacter;
807+
793808
// Remove all characters to the right and store them in a local list,
794809
// such that their depth can be updated.
795810
List<Drawable> charsRight = new List<Drawable>();
@@ -1340,7 +1355,7 @@ protected override void OnFocusLost(FocusLostEvent e)
13401355
protected override bool OnClick(ClickEvent e)
13411356
{
13421357
if (!ReadOnly && textInputBound)
1343-
textInput.EnsureActivated(AllowIme);
1358+
textInput.EnsureActivated(InputProperties);
13441359

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

13681383
if (textInputBound)
13691384
{
1370-
textInput.EnsureActivated(AllowIme);
1385+
textInput.EnsureActivated(InputProperties);
13711386
return;
13721387
}
13731388

13741389
// TextBox has special handling of text input activation when focus is changed directly from one TextBox to another.
13751390
// 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.
13761391

13771392
if (previous?.textInput == textInput)
1378-
textInput.EnsureActivated(AllowIme, ScreenSpaceDrawQuad.AABBFloat);
1393+
textInput.EnsureActivated(InputProperties, ScreenSpaceDrawQuad.AABBFloat);
13791394
else
1380-
textInput.Activate(AllowIme, ScreenSpaceDrawQuad.AABBFloat);
1395+
textInput.Activate(InputProperties, ScreenSpaceDrawQuad.AABBFloat);
13811396

13821397
textInput.OnTextInput += handleTextInput;
13831398
textInput.OnImeComposition += handleImeComposition;

osu.Framework/Input/ButtonEventManager.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,18 @@ private void handleButtonUp(InputState state)
133133
}
134134

135135
if (handledBy != null)
136-
Logger.Log($"{e} handled by {handledBy}.", LoggingTarget.Runtime, LogLevel.Debug);
136+
{
137+
Logger.Log(SuppressLoggingEventInformation(handledBy)
138+
? $"{e.GetType().Name} handled by {handledBy}."
139+
: $"{e} handled by {handledBy}.", LoggingTarget.Runtime, LogLevel.Debug);
140+
}
137141

138142
return handledBy;
139143
}
144+
145+
/// <summary>
146+
/// Whether information about the event should be suppressed from logging for the given drawable.
147+
/// </summary>
148+
protected virtual bool SuppressLoggingEventInformation(Drawable drawable) => false;
140149
}
141150
}

osu.Framework/Input/ISuppressKeyEventLogging.cs osu.Framework/Input/ICanSuppressKeyEventLogging.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44
namespace osu.Framework.Input
55
{
66
/// <summary>
7-
/// Marker interface which suppresses logging of keyboard input events.
7+
/// An interface which suppresses logging of keyboard input events.
88
/// Useful for password fields, where user input should not be logged.
99
/// </summary>
10-
public interface ISuppressKeyEventLogging
10+
public interface ICanSuppressKeyEventLogging
1111
{
12+
/// <summary>
13+
/// Whether key event logging should be suppressed for this drawable.
14+
/// </summary>
15+
bool SuppressKeyEventLogging { get; }
1216
}
1317
}

osu.Framework/Input/InputManager.cs

+2-27
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
using JetBrains.Annotations;
1111
using osu.Framework.Allocation;
1212
using osu.Framework.Extensions.ListExtensions;
13-
using osu.Framework.Extensions.TypeExtensions;
1413
using osu.Framework.Graphics;
1514
using osu.Framework.Graphics.Containers;
1615
using osu.Framework.Input.Events;
@@ -1002,37 +1001,13 @@ protected virtual bool PropagateBlockableEvent(SlimReadOnlyListWrapper<Drawable>
10021001
{
10031002
foreach (var d in drawables)
10041003
{
1005-
if (!d.TriggerEvent(e)) continue;
1006-
1007-
if (shouldLog(e))
1008-
{
1009-
string detail = d is ISuppressKeyEventLogging ? e.GetType().ReadableName() : e.ToString();
1010-
Logger.Log($"{detail} handled by {d}.", LoggingTarget.Runtime, LogLevel.Debug);
1011-
}
1012-
1013-
return true;
1004+
if (d.TriggerEvent(e))
1005+
return true;
10141006
}
10151007

10161008
return false;
10171009
}
10181010

1019-
private bool shouldLog(UIEvent eventType)
1020-
{
1021-
switch (eventType)
1022-
{
1023-
case KeyDownEvent k:
1024-
return !k.Repeat;
1025-
1026-
case DragEvent:
1027-
case ScrollEvent:
1028-
case MouseMoveEvent:
1029-
return false;
1030-
1031-
default:
1032-
return true;
1033-
}
1034-
}
1035-
10361011
/// <summary>
10371012
/// Unfocus the current focused drawable if it is no longer in a valid state.
10381013
/// </summary>

osu.Framework/Input/KeyEventManager.cs

+2
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,7 @@ public void HandleRepeat(InputState state)
3333

3434
protected override void HandleButtonUp(InputState state, List<Drawable> targets) =>
3535
PropagateButtonEvent(targets, new KeyUpEvent(state, Button));
36+
37+
protected override bool SuppressLoggingEventInformation(Drawable drawable) => drawable is ICanSuppressKeyEventLogging canSuppress && canSuppress.SuppressKeyEventLogging;
3638
}
3739
}

osu.Framework/Input/SDLWindowTextInput.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,16 @@ private void handleTextEditing(string? text, int selectionStart, int selectionLe
3737
TriggerImeComposition(text, selectionStart, selectionLength);
3838
}
3939

40-
protected override void ActivateTextInput(bool allowIme)
40+
protected override void ActivateTextInput(TextInputProperties properties)
4141
{
4242
window.TextInput += handleTextInput;
4343
window.TextEditing += handleTextEditing;
44-
window.StartTextInput(allowIme);
44+
window.StartTextInput(properties);
4545
}
4646

47-
protected override void EnsureTextInputActivated(bool allowIme)
47+
protected override void EnsureTextInputActivated(TextInputProperties properties)
4848
{
49-
window.StartTextInput(allowIme);
49+
window.StartTextInput(properties);
5050
}
5151

5252
protected override void DeactivateTextInput()
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2+
// See the LICENCE file in the repository root for full licence text.
3+
4+
namespace osu.Framework.Input
5+
{
6+
/// <summary>
7+
/// Represents a number of properties to consider during a text input session.
8+
/// </summary>
9+
/// <param name="Type">The type of text being input.</param>
10+
/// <param name="AllowIme">
11+
/// <para>
12+
/// Whether IME should be allowed during this text input session, if supported by the given text input type.
13+
/// </para>
14+
/// <para>
15+
/// Note that this is just a hint to the native implementation, some might respect this,
16+
/// while others will ignore and always have the IME (dis)allowed.
17+
/// </para>
18+
/// </param>
19+
public record struct TextInputProperties(TextInputType Type, bool AllowIme = true);
20+
}

0 commit comments

Comments
 (0)