Skip to content

Commit 3495215

Browse files
authored
Merge pull request #20045 from nekodex/textbox-invalid-sfx
Add audio feedback for invalid textbox input
2 parents 6af8143 + ba20044 commit 3495215

File tree

5 files changed

+73
-67
lines changed

5 files changed

+73
-67
lines changed

osu.Android.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
<Reference Include="Java.Interop" />
5252
</ItemGroup>
5353
<ItemGroup>
54-
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.819.0" />
54+
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.831.0" />
5555
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.825.0" />
5656
</ItemGroup>
5757
<ItemGroup Label="Transitive Dependencies">

osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ public void TestResumeViaNotification()
194194

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

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

199199
AddAssert("overlay shown", () => overlay.State.Value == Visibility.Visible);
200200
AddAssert("is resumed", () => overlay.CurrentScreen is ScreenUIScale);

osu.Game/Graphics/UserInterface/OsuTextBox.cs

+69-63
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
22
// See the LICENCE file in the repository root for full licence text.
33

4+
using System;
5+
using System.Collections.Generic;
46
using System.Linq;
57
using osu.Framework.Allocation;
68
using osu.Framework.Audio;
@@ -40,30 +42,27 @@ public class OsuTextBox : BasicTextBox
4042
Margin = new MarginPadding { Left = 2 },
4143
};
4244

43-
private readonly Sample?[] textAddedSamples = new Sample[4];
44-
private Sample? capsTextAddedSample;
45-
private Sample? textRemovedSample;
46-
private Sample? textCommittedSample;
47-
private Sample? caretMovedSample;
48-
49-
private Sample? selectCharSample;
50-
private Sample? selectWordSample;
51-
private Sample? selectAllSample;
52-
private Sample? deselectSample;
53-
5445
private OsuCaret? caret;
5546

5647
private bool selectionStarted;
5748
private double sampleLastPlaybackTime;
5849

59-
private enum SelectionSampleType
50+
private enum FeedbackSampleType
6051
{
61-
Character,
62-
Word,
63-
All,
52+
TextAdd,
53+
TextAddCaps,
54+
TextRemove,
55+
TextConfirm,
56+
TextInvalid,
57+
CaretMove,
58+
SelectCharacter,
59+
SelectWord,
60+
SelectAll,
6461
Deselect
6562
}
6663

64+
private Dictionary<FeedbackSampleType, Sample?[]> sampleMap = new Dictionary<FeedbackSampleType, Sample?[]>();
65+
6766
public OsuTextBox()
6867
{
6968
Height = 40;
@@ -87,18 +86,23 @@ private void load(OverlayColourProvider? colourProvider, OsuColour colour, Audio
8786

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

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

93-
capsTextAddedSample = audio.Samples.Get(@"Keyboard/key-caps");
94-
textRemovedSample = audio.Samples.Get(@"Keyboard/key-delete");
95-
textCommittedSample = audio.Samples.Get(@"Keyboard/key-confirm");
96-
caretMovedSample = audio.Samples.Get(@"Keyboard/key-movement");
97-
98-
selectCharSample = audio.Samples.Get(@"Keyboard/select-char");
99-
selectWordSample = audio.Samples.Get(@"Keyboard/select-word");
100-
selectAllSample = audio.Samples.Get(@"Keyboard/select-all");
101-
deselectSample = audio.Samples.Get(@"Keyboard/deselect");
93+
sampleMap = new Dictionary<FeedbackSampleType, Sample?[]>
94+
{
95+
{ FeedbackSampleType.TextAdd, textAddedSamples },
96+
{ FeedbackSampleType.TextAddCaps, new[] { audio.Samples.Get(@"Keyboard/key-caps") } },
97+
{ FeedbackSampleType.TextRemove, new[] { audio.Samples.Get(@"Keyboard/key-delete") } },
98+
{ FeedbackSampleType.TextConfirm, new[] { audio.Samples.Get(@"Keyboard/key-confirm") } },
99+
{ FeedbackSampleType.TextInvalid, new[] { audio.Samples.Get(@"Keyboard/key-invalid") } },
100+
{ FeedbackSampleType.CaretMove, new[] { audio.Samples.Get(@"Keyboard/key-movement") } },
101+
{ FeedbackSampleType.SelectCharacter, new[] { audio.Samples.Get(@"Keyboard/select-char") } },
102+
{ FeedbackSampleType.SelectWord, new[] { audio.Samples.Get(@"Keyboard/select-word") } },
103+
{ FeedbackSampleType.SelectAll, new[] { audio.Samples.Get(@"Keyboard/select-all") } },
104+
{ FeedbackSampleType.Deselect, new[] { audio.Samples.Get(@"Keyboard/deselect") } }
105+
};
102106
}
103107

104108
private Color4 selectionColour;
@@ -109,32 +113,42 @@ protected override void OnUserTextAdded(string added)
109113
{
110114
base.OnUserTextAdded(added);
111115

116+
if (!added.Any(CanAddCharacter))
117+
return;
118+
112119
if (added.Any(char.IsUpper) && AllowUniqueCharacterSamples)
113-
capsTextAddedSample?.Play();
120+
playSample(FeedbackSampleType.TextAddCaps);
114121
else
115-
playTextAddedSample();
122+
playSample(FeedbackSampleType.TextAdd);
116123
}
117124

118125
protected override void OnUserTextRemoved(string removed)
119126
{
120127
base.OnUserTextRemoved(removed);
121128

122-
textRemovedSample?.Play();
129+
playSample(FeedbackSampleType.TextRemove);
130+
}
131+
132+
protected override void NotifyInputError()
133+
{
134+
base.NotifyInputError();
135+
136+
playSample(FeedbackSampleType.TextInvalid);
123137
}
124138

125139
protected override void OnTextCommitted(bool textChanged)
126140
{
127141
base.OnTextCommitted(textChanged);
128142

129-
textCommittedSample?.Play();
143+
playSample(FeedbackSampleType.TextConfirm);
130144
}
131145

132146
protected override void OnCaretMoved(bool selecting)
133147
{
134148
base.OnCaretMoved(selecting);
135149

136150
if (!selecting)
137-
caretMovedSample?.Play();
151+
playSample(FeedbackSampleType.CaretMove);
138152
}
139153

140154
protected override void OnTextSelectionChanged(TextSelectionType selectionType)
@@ -144,15 +158,15 @@ protected override void OnTextSelectionChanged(TextSelectionType selectionType)
144158
switch (selectionType)
145159
{
146160
case TextSelectionType.Character:
147-
playSelectSample(SelectionSampleType.Character);
161+
playSample(FeedbackSampleType.SelectCharacter);
148162
break;
149163

150164
case TextSelectionType.Word:
151-
playSelectSample(selectionStarted ? SelectionSampleType.Character : SelectionSampleType.Word);
165+
playSample(selectionStarted ? FeedbackSampleType.SelectCharacter : FeedbackSampleType.SelectWord);
152166
break;
153167

154168
case TextSelectionType.All:
155-
playSelectSample(SelectionSampleType.All);
169+
playSample(FeedbackSampleType.SelectAll);
156170
break;
157171
}
158172

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

166180
if (!selectionStarted) return;
167181

168-
playSelectSample(SelectionSampleType.Deselect);
182+
playSample(FeedbackSampleType.Deselect);
169183

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

185199
case 1:
186200
// composition probably ended by pressing backspace, or was cancelled.
187-
textRemovedSample?.Play();
201+
playSample(FeedbackSampleType.TextRemove);
188202
return;
189203

190204
default:
191205
// longer text removed, composition ended because it was cancelled.
192206
// could be a different sample if desired.
193-
textRemovedSample?.Play();
207+
playSample(FeedbackSampleType.TextRemove);
194208
return;
195209
}
196210
}
197211

198212
if (addedTextLength > 0)
199213
{
200214
// some text was added, probably due to typing new text or by changing the candidate.
201-
playTextAddedSample();
215+
playSample(FeedbackSampleType.TextAdd);
202216
return;
203217
}
204218

205219
if (removedTextLength > 0)
206220
{
207221
// text was probably removed by backspacing.
208222
// it's also possible that a candidate that only removed text was changed to.
209-
textRemovedSample?.Play();
223+
playSample(FeedbackSampleType.TextRemove);
210224
return;
211225
}
212226

213227
if (caretMoved)
214228
{
215229
// only the caret/selection was moved.
216-
caretMovedSample?.Play();
230+
playSample(FeedbackSampleType.CaretMove);
217231
}
218232
}
219233

@@ -224,13 +238,13 @@ protected override void OnImeResult(string result, bool successful)
224238
if (successful)
225239
{
226240
// composition was successfully completed, usually by pressing the enter key.
227-
textCommittedSample?.Play();
241+
playSample(FeedbackSampleType.TextConfirm);
228242
}
229243
else
230244
{
231245
// composition was prematurely ended, eg. by clicking inside the textbox.
232246
// could be a different sample if desired.
233-
textCommittedSample?.Play();
247+
playSample(FeedbackSampleType.TextConfirm);
234248
}
235249
}
236250

@@ -259,43 +273,35 @@ protected override void OnFocusLost(FocusLostEvent e)
259273
SelectionColour = SelectionColour,
260274
};
261275

262-
private void playSelectSample(SelectionSampleType selectionType)
276+
private SampleChannel? getSampleChannel(FeedbackSampleType feedbackSampleType)
263277
{
264-
if (Time.Current < sampleLastPlaybackTime + 15) return;
265-
266-
SampleChannel? channel;
267-
double pitch = 0.98 + RNG.NextDouble(0.04);
278+
var samples = sampleMap[feedbackSampleType];
268279

269-
switch (selectionType)
270-
{
271-
case SelectionSampleType.All:
272-
channel = selectAllSample?.GetChannel();
273-
break;
280+
if (samples == null || samples.Length == 0)
281+
return null;
274282

275-
case SelectionSampleType.Word:
276-
channel = selectWordSample?.GetChannel();
277-
break;
283+
return samples[RNG.Next(0, samples.Length)]?.GetChannel();
284+
}
278285

279-
case SelectionSampleType.Deselect:
280-
channel = deselectSample?.GetChannel();
281-
break;
286+
private void playSample(FeedbackSampleType feedbackSample)
287+
{
288+
if (Time.Current < sampleLastPlaybackTime + 15) return;
282289

283-
default:
284-
channel = selectCharSample?.GetChannel();
285-
pitch += (SelectedText.Length / (double)Text.Length) * 0.15f;
286-
break;
287-
}
290+
SampleChannel? channel = getSampleChannel(feedbackSample);
288291

289292
if (channel == null) return;
290293

294+
double pitch = 0.98 + RNG.NextDouble(0.04);
295+
296+
if (feedbackSample == FeedbackSampleType.SelectCharacter)
297+
pitch += ((double)SelectedText.Length / Math.Max(1, Text.Length)) * 0.15f;
298+
291299
channel.Frequency.Value = pitch;
292300
channel.Play();
293301

294302
sampleLastPlaybackTime = Time.Current;
295303
}
296304

297-
private void playTextAddedSample() => textAddedSamples[RNG.Next(0, textAddedSamples.Length)]?.Play();
298-
299305
private class OsuCaret : Caret
300306
{
301307
private const float caret_move_time = 60;

osu.Game/osu.Game.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
</PackageReference>
3838
<PackageReference Include="Realm" Version="10.15.1" />
3939
<PackageReference Include="ppy.osu.Framework" Version="2022.825.0" />
40-
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.819.0" />
40+
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.831.0" />
4141
<PackageReference Include="Sentry" Version="3.20.1" />
4242
<PackageReference Include="SharpCompress" Version="0.32.2" />
4343
<PackageReference Include="NUnit" Version="3.13.3" />

osu.iOS.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
</ItemGroup>
6363
<ItemGroup Label="Package References">
6464
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.825.0" />
65-
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.819.0" />
65+
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.831.0" />
6666
</ItemGroup>
6767
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
6868
<PropertyGroup>

0 commit comments

Comments
 (0)