From 3b4dfd59031e8ef73fb2986bf828de507d8e51ad Mon Sep 17 00:00:00 2001 From: Krzysztof Gutkowski Date: Thu, 11 Jun 2026 12:39:25 +0200 Subject: [PATCH] Implement a V2 password textbox (#38028) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR adds a password input form control consistent with the V2 form controls. Default state: image Caps Lock active: image --------- Co-authored-by: Bartłomiej Dach --- .../UserInterface/TestSceneFormControls.cs | 5 + .../UserInterfaceV2/FormPasswordTextBox.cs | 106 ++++++++++++++++++ .../Graphics/UserInterfaceV2/FormTextBox.cs | 18 ++- osu.Game/Localisation/CommonStrings.cs | 9 +- 4 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Graphics/UserInterfaceV2/FormPasswordTextBox.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs index 2463ab6741..49a74f84c5 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs @@ -235,6 +235,11 @@ namespace osu.Game.Tests.Visual.UserInterface Padding = new MarginPadding(10), Children = new Drawable[] { + new FormPasswordTextBox + { + Caption = "Password", + TabbableContentContainer = this, + }, new FormNumberBox(allowDecimals: true) { Caption = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua", diff --git a/osu.Game/Graphics/UserInterfaceV2/FormPasswordTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/FormPasswordTextBox.cs new file mode 100644 index 0000000000..99590780d7 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/FormPasswordTextBox.cs @@ -0,0 +1,106 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; +using osu.Framework.Input.Events; +using osu.Framework.Platform; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Localisation; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public partial class FormPasswordTextBox : FormTextBox + { + private CapsWarning capsWarning = null!; + + [BackgroundDependencyLoader] + private void load() + { + CaptionContainer.Add(capsWarning = new CapsWarning + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Alpha = 0, + }); + } + + internal override InnerTextBox CreateTextBox() => new InnerPasswordBox + { + UpdateCapsWarning = visible => capsWarning.FadeTo(visible ? 1 : 0, 250, Easing.OutQuint), + }; + + internal partial class InnerPasswordBox : InnerTextBox + { + public Action? UpdateCapsWarning { get; init; } + + [Resolved] + private GameHost host { get; set; } = null!; + + protected override Drawable GetDrawableCharacter(char c) => new FallingDownContainer + { + AutoSizeAxes = Axes.Both, + Child = new OsuPasswordTextBox.PasswordMaskChar(FontSize), + }; + + protected override bool AllowUniqueCharacterSamples => false; + + public InnerPasswordBox() + { + InputProperties = new TextInputProperties(TextInputType.Password, false); + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + if (e.Key == Key.CapsLock) + UpdateCapsWarning?.Invoke(host.CapsLockEnabled); + + return base.OnKeyDown(e); + } + + protected override void OnFocus(FocusEvent e) + { + UpdateCapsWarning?.Invoke(host.CapsLockEnabled); + base.OnFocus(e); + } + + protected override void OnFocusLost(FocusLostEvent e) + { + UpdateCapsWarning?.Invoke(false); + base.OnFocusLost(e); + } + } + + private partial class CapsWarning : OsuTextFlowContainer + { + public CapsWarning() + : base(t => t.Font = OsuFont.Style.Caption1) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AutoSizeAxes = Axes.Both; + Colour = colours.YellowLight; + + AddArbitraryDrawable(new SpriteIcon + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Size = new Vector2(10), + Icon = FontAwesome.Solid.ExclamationTriangle, + Margin = new MarginPadding { Right = 2 }, + Y = 1f, + }); + AddText(CommonStrings.CapsLock); + } + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs index 842c034b79..67981bb615 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs @@ -84,6 +84,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 private FormFieldCaption caption = null!; private IFocusManager focusManager = null!; + protected Container CaptionContainer { get; private set; } = null!; + [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -104,12 +106,22 @@ namespace osu.Game.Graphics.UserInterfaceV2 Spacing = new Vector2(0, 4), Children = new Drawable[] { - caption = new FormFieldCaption + CaptionContainer = new Container { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, - Caption = Caption, - TooltipText = HintText, + Children = new Drawable[] + { + caption = new FormFieldCaption + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Caption = Caption, + TooltipText = HintText, + }, + }, }, textBox = CreateTextBox().With(t => { diff --git a/osu.Game/Localisation/CommonStrings.cs b/osu.Game/Localisation/CommonStrings.cs index 6264f35029..4152b1339b 100644 --- a/osu.Game/Localisation/CommonStrings.cs +++ b/osu.Game/Localisation/CommonStrings.cs @@ -170,9 +170,14 @@ namespace osu.Game.Localisation public static LocalisableString Exit => new TranslatableString(getKey(@"exit"), @"Exit"); /// - /// "Caps lock is active" + /// "Caps Lock" /// - public static LocalisableString CapsLockIsActive => new TranslatableString(getKey(@"caps_lock_is_active"), @"Caps lock is active"); + public static LocalisableString CapsLock => new TranslatableString(getKey(@"caps_lock"), @"Caps Lock"); + + /// + /// "Caps Lock is active" + /// + public static LocalisableString CapsLockIsActive => new TranslatableString(getKey(@"caps_lock_is_active"), @"Caps Lock is active"); /// /// "Revert to default"