Skip to content

Commit

Permalink
Merge pull request #20045 from nekodex/textbox-invalid-sfx
Browse files Browse the repository at this point in the history
Add audio feedback for invalid textbox input
  • Loading branch information
peppy authored Aug 31, 2022
2 parents 6af8143 + ba20044 commit 3495215
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 67 deletions.
2 changes: 1 addition & 1 deletion osu.Android.props
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
<Reference Include="Java.Interop" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.819.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.831.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.825.0" />
</ItemGroup>
<ItemGroup Label="Transitive Dependencies">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ public void TestResumeViaNotification()

AddStep("notification arrived", () => notificationOverlay.Verify(n => n.Post(It.IsAny<Notification>()), Times.Once));

AddStep("run notification action", () => lastNotification.Activated());
AddStep("run notification action", () => lastNotification.Activated?.Invoke());

AddAssert("overlay shown", () => overlay.State.Value == Visibility.Visible);
AddAssert("is resumed", () => overlay.CurrentScreen is ScreenUIScale);
Expand Down
132 changes: 69 additions & 63 deletions osu.Game/Graphics/UserInterface/OsuTextBox.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Audio;
Expand Down Expand Up @@ -40,30 +42,27 @@ public class OsuTextBox : BasicTextBox
Margin = new MarginPadding { Left = 2 },
};

private readonly Sample?[] textAddedSamples = new Sample[4];
private Sample? capsTextAddedSample;
private Sample? textRemovedSample;
private Sample? textCommittedSample;
private Sample? caretMovedSample;

private Sample? selectCharSample;
private Sample? selectWordSample;
private Sample? selectAllSample;
private Sample? deselectSample;

private OsuCaret? caret;

private bool selectionStarted;
private double sampleLastPlaybackTime;

private enum SelectionSampleType
private enum FeedbackSampleType
{
Character,
Word,
All,
TextAdd,
TextAddCaps,
TextRemove,
TextConfirm,
TextInvalid,
CaretMove,
SelectCharacter,
SelectWord,
SelectAll,
Deselect
}

private Dictionary<FeedbackSampleType, Sample?[]> sampleMap = new Dictionary<FeedbackSampleType, Sample?[]>();

public OsuTextBox()
{
Height = 40;
Expand All @@ -87,18 +86,23 @@ private void load(OverlayColourProvider? colourProvider, OsuColour colour, Audio

Placeholder.Colour = colourProvider?.Foreground1 ?? new Color4(180, 180, 180, 255);

var textAddedSamples = new Sample?[4];
for (int i = 0; i < textAddedSamples.Length; i++)
textAddedSamples[i] = audio.Samples.Get($@"Keyboard/key-press-{1 + i}");

capsTextAddedSample = audio.Samples.Get(@"Keyboard/key-caps");
textRemovedSample = audio.Samples.Get(@"Keyboard/key-delete");
textCommittedSample = audio.Samples.Get(@"Keyboard/key-confirm");
caretMovedSample = audio.Samples.Get(@"Keyboard/key-movement");

selectCharSample = audio.Samples.Get(@"Keyboard/select-char");
selectWordSample = audio.Samples.Get(@"Keyboard/select-word");
selectAllSample = audio.Samples.Get(@"Keyboard/select-all");
deselectSample = audio.Samples.Get(@"Keyboard/deselect");
sampleMap = new Dictionary<FeedbackSampleType, Sample?[]>
{
{ FeedbackSampleType.TextAdd, textAddedSamples },
{ FeedbackSampleType.TextAddCaps, new[] { audio.Samples.Get(@"Keyboard/key-caps") } },
{ FeedbackSampleType.TextRemove, new[] { audio.Samples.Get(@"Keyboard/key-delete") } },
{ FeedbackSampleType.TextConfirm, new[] { audio.Samples.Get(@"Keyboard/key-confirm") } },
{ FeedbackSampleType.TextInvalid, new[] { audio.Samples.Get(@"Keyboard/key-invalid") } },
{ FeedbackSampleType.CaretMove, new[] { audio.Samples.Get(@"Keyboard/key-movement") } },
{ FeedbackSampleType.SelectCharacter, new[] { audio.Samples.Get(@"Keyboard/select-char") } },
{ FeedbackSampleType.SelectWord, new[] { audio.Samples.Get(@"Keyboard/select-word") } },
{ FeedbackSampleType.SelectAll, new[] { audio.Samples.Get(@"Keyboard/select-all") } },
{ FeedbackSampleType.Deselect, new[] { audio.Samples.Get(@"Keyboard/deselect") } }
};
}

private Color4 selectionColour;
Expand All @@ -109,32 +113,42 @@ protected override void OnUserTextAdded(string added)
{
base.OnUserTextAdded(added);

if (!added.Any(CanAddCharacter))
return;

if (added.Any(char.IsUpper) && AllowUniqueCharacterSamples)
capsTextAddedSample?.Play();
playSample(FeedbackSampleType.TextAddCaps);
else
playTextAddedSample();
playSample(FeedbackSampleType.TextAdd);
}

protected override void OnUserTextRemoved(string removed)
{
base.OnUserTextRemoved(removed);

textRemovedSample?.Play();
playSample(FeedbackSampleType.TextRemove);
}

protected override void NotifyInputError()
{
base.NotifyInputError();

playSample(FeedbackSampleType.TextInvalid);
}

protected override void OnTextCommitted(bool textChanged)
{
base.OnTextCommitted(textChanged);

textCommittedSample?.Play();
playSample(FeedbackSampleType.TextConfirm);
}

protected override void OnCaretMoved(bool selecting)
{
base.OnCaretMoved(selecting);

if (!selecting)
caretMovedSample?.Play();
playSample(FeedbackSampleType.CaretMove);
}

protected override void OnTextSelectionChanged(TextSelectionType selectionType)
Expand All @@ -144,15 +158,15 @@ protected override void OnTextSelectionChanged(TextSelectionType selectionType)
switch (selectionType)
{
case TextSelectionType.Character:
playSelectSample(SelectionSampleType.Character);
playSample(FeedbackSampleType.SelectCharacter);
break;

case TextSelectionType.Word:
playSelectSample(selectionStarted ? SelectionSampleType.Character : SelectionSampleType.Word);
playSample(selectionStarted ? FeedbackSampleType.SelectCharacter : FeedbackSampleType.SelectWord);
break;

case TextSelectionType.All:
playSelectSample(SelectionSampleType.All);
playSample(FeedbackSampleType.SelectAll);
break;
}

Expand All @@ -165,7 +179,7 @@ protected override void OnTextDeselected()

if (!selectionStarted) return;

playSelectSample(SelectionSampleType.Deselect);
playSample(FeedbackSampleType.Deselect);

selectionStarted = false;
}
Expand All @@ -184,36 +198,36 @@ protected override void OnImeComposition(string newComposition, int removedTextL

case 1:
// composition probably ended by pressing backspace, or was cancelled.
textRemovedSample?.Play();
playSample(FeedbackSampleType.TextRemove);
return;

default:
// longer text removed, composition ended because it was cancelled.
// could be a different sample if desired.
textRemovedSample?.Play();
playSample(FeedbackSampleType.TextRemove);
return;
}
}

if (addedTextLength > 0)
{
// some text was added, probably due to typing new text or by changing the candidate.
playTextAddedSample();
playSample(FeedbackSampleType.TextAdd);
return;
}

if (removedTextLength > 0)
{
// text was probably removed by backspacing.
// it's also possible that a candidate that only removed text was changed to.
textRemovedSample?.Play();
playSample(FeedbackSampleType.TextRemove);
return;
}

if (caretMoved)
{
// only the caret/selection was moved.
caretMovedSample?.Play();
playSample(FeedbackSampleType.CaretMove);
}
}

Expand All @@ -224,13 +238,13 @@ protected override void OnImeResult(string result, bool successful)
if (successful)
{
// composition was successfully completed, usually by pressing the enter key.
textCommittedSample?.Play();
playSample(FeedbackSampleType.TextConfirm);
}
else
{
// composition was prematurely ended, eg. by clicking inside the textbox.
// could be a different sample if desired.
textCommittedSample?.Play();
playSample(FeedbackSampleType.TextConfirm);
}
}

Expand Down Expand Up @@ -259,43 +273,35 @@ protected override void OnFocusLost(FocusLostEvent e)
SelectionColour = SelectionColour,
};

private void playSelectSample(SelectionSampleType selectionType)
private SampleChannel? getSampleChannel(FeedbackSampleType feedbackSampleType)
{
if (Time.Current < sampleLastPlaybackTime + 15) return;

SampleChannel? channel;
double pitch = 0.98 + RNG.NextDouble(0.04);
var samples = sampleMap[feedbackSampleType];

switch (selectionType)
{
case SelectionSampleType.All:
channel = selectAllSample?.GetChannel();
break;
if (samples == null || samples.Length == 0)
return null;

case SelectionSampleType.Word:
channel = selectWordSample?.GetChannel();
break;
return samples[RNG.Next(0, samples.Length)]?.GetChannel();
}

case SelectionSampleType.Deselect:
channel = deselectSample?.GetChannel();
break;
private void playSample(FeedbackSampleType feedbackSample)
{
if (Time.Current < sampleLastPlaybackTime + 15) return;

default:
channel = selectCharSample?.GetChannel();
pitch += (SelectedText.Length / (double)Text.Length) * 0.15f;
break;
}
SampleChannel? channel = getSampleChannel(feedbackSample);

if (channel == null) return;

double pitch = 0.98 + RNG.NextDouble(0.04);

if (feedbackSample == FeedbackSampleType.SelectCharacter)
pitch += ((double)SelectedText.Length / Math.Max(1, Text.Length)) * 0.15f;

channel.Frequency.Value = pitch;
channel.Play();

sampleLastPlaybackTime = Time.Current;
}

private void playTextAddedSample() => textAddedSamples[RNG.Next(0, textAddedSamples.Length)]?.Play();

private class OsuCaret : Caret
{
private const float caret_move_time = 60;
Expand Down
2 changes: 1 addition & 1 deletion osu.Game/osu.Game.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
</PackageReference>
<PackageReference Include="Realm" Version="10.15.1" />
<PackageReference Include="ppy.osu.Framework" Version="2022.825.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.819.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.831.0" />
<PackageReference Include="Sentry" Version="3.20.1" />
<PackageReference Include="SharpCompress" Version="0.32.2" />
<PackageReference Include="NUnit" Version="3.13.3" />
Expand Down
2 changes: 1 addition & 1 deletion osu.iOS.props
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.825.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.819.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.831.0" />
</ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
<PropertyGroup>
Expand Down

0 comments on commit 3495215

Please sign in to comment.