-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathKeyboard.cs
253 lines (229 loc) · 9 KB
/
Keyboard.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
using UdonSharp;
using UnityEngine;
using UnityEngine.UI;
using VRC.SDKBase;
using VRC.Udon;
using System.Text.RegularExpressions;
public class Keyboard : UdonSharpBehaviour {
public InputField inputField;
public Text chatLog;
public Toggle lShift;
public Toggle rShift;
public Toggle capsLock;
[Range(1, 1000)] public int maxLength = 80;
[Range(1, 1000)] public int maxLines = 14;
[Range(0f, 1000f)] public float maxTypingDist = 5f;
public Color pressedColor = Color.blue;
public Color normalColor = Color.white;
public AudioSource clickSound;
public AudioSource messageSound;
private string allowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`~!@#$%^&*()-_=+[]{}\\|;:'\",<.>/? \t";
[UdonSynced] public string delivery = "0@";
private string lastDelivery = "0@";
private int lastSeq = 0;
private string queuedMessage = "";
private int queuedSeq = 0;
private VRCPlayerApi localPlayer;
private string prefix = "@Unknown: ";
private string lastInput = "";
// Use Upper-case.
private bool shift = false;
void Start() {
lastDelivery = delivery;
lastSeq = ParseSequenceNumber(delivery);
inputField.characterLimit = maxLength;
inputField.DeactivateInputField();
localPlayer = Networking.LocalPlayer;
if (localPlayer != null) prefix = "@" + localPlayer.displayName + ": ";
}
// Check for enter key.
void Update() {
if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter)) SendMessage();
}
// User pressed key.
public void AppendChar(char c) {
if (localPlayer != null && Vector3.Distance(localPlayer.GetPosition(), transform.position) > maxTypingDist) {
inputField.DeactivateInputField();
return;
}
inputField.text += c;
lShift.isOn = false;
rShift.isOn = false;
}
// Add line to chat log.
public void AppendLine(string msg) {
chatLog.text += "\n" + msg;
string[] split = chatLog.text.Split('\n');
int splitLen = split.Length;
if (splitLen > maxLines) {
chatLog.text = System.String.Join("\n", split, splitLen - maxLines, maxLines);
}
}
// Delete last character. Caret doesn't seem to work, so we fall back to removing last character.
public void Backspace() {
if (inputField.caretPosition > 0) {
int width = inputField.caretWidth;
if (width == 0) width = 1;
// Debug.Log("Backspacing: " + width + ", ending: " + inputField.caretPosition);
inputField.text = inputField.text.Remove(inputField.caretPosition - width, width);
} else if (inputField.text.Length > 0) {
// Debug.Log("Backspacing last");
inputField.text = inputField.text.Remove(inputField.text.Length - 1);
}
}
// Live sanitize input text. (CustomEvent)
public void InputUpdated() {
if (localPlayer != null && Vector3.Distance(localPlayer.GetPosition(), transform.position) > maxTypingDist) {
// Player too far to type, prevent typing accidentally.
if (inputField.text != lastInput) inputField.text = lastInput;
lastInput = inputField.text;
} else if (inputField.text.Length > maxLength) {
// Max line length exceeded, truncate end.
inputField.text = inputField.text.Substring(0, maxLength);
} else {
// Normal case, character typed or deleted.
clickSound.PlayOneShot(clickSound.clip);
}
}
// Shift or capslock was changed.
public void ShiftUpdated() {
bool shifting = lShift.isOn || rShift.isOn;
shift = shifting != capsLock.isOn;
SetPressed(lShift, shift);
SetPressed(rShift, shift);
SetPressed(capsLock, capsLock.isOn);
clickSound.PlayOneShot(clickSound.clip);
}
// Set a key as visibly pressed.
private void SetPressed(Toggle t, bool set) {
ColorBlock cb = t.colors;
cb.normalColor = set ? pressedColor : normalColor;
cb.highlightedColor = set ? pressedColor : normalColor;
cb.pressedColor = set ? pressedColor : normalColor;
t.colors = cb;
}
// Check for commands typed. False means no command was detected.
private bool ProcessCommands(string msg) {
switch (msg) {
case "/clear":
chatLog.text = "<i><color=white>Cleared Log</color></i>";
return true;
default:
return false;
}
}
// Get sequence number of delivered message.
private int ParseSequenceNumber(string input) {
return System.Convert.ToInt32(input.Substring(0, input.IndexOf('@')));
}
private string ParseMessage(string input) {
return input.Substring(input.IndexOf('@')); // Keeps @
}
// Sanitize user-input of malicious intent.
public string SanitizeInput(string input) {
input = input.Trim();
for (int i = 0; i < input.Length; i++) {
if (allowedChars.IndexOf(input[i]) < 0) {
input = input.Remove(i, 1);
i--;
}
}
if (input.Length > maxLength) {
input = input.Substring(0, maxLength);
}
return input.Replace("<", "<\u200B").Replace(">", "\u200B>");
}
// Send the message currently in the text input box.
public void SendMessage() {
// Debug.Log("Sending Message: " + inputField.text);
if (inputField.text.Length == 0) return;
if (ProcessCommands(inputField.text)) {
inputField.text = "";
return;
}
int seq = ParseSequenceNumber(delivery) + 1;
// int seq = lastSeq + 1;
string sanitized = SanitizeInput(inputField.text);
if (sanitized.Length == 0) return;
if (localPlayer != null) Networking.SetOwner(localPlayer, gameObject);
queuedMessage = prefix + sanitized;
delivery = seq + queuedMessage;
queuedSeq = seq;
inputField.text = "";
RequestSerialization();
OnDeserialization();
}
// Receive a new message.
public override void OnDeserialization() {
// Debug.Log("OnDeserialization: " + delivery);
if (delivery == lastDelivery) return;
lastDelivery = delivery;
int seq = ParseSequenceNumber(delivery);
if (seq < lastSeq) return;
// if (seq == 0) return;
// if (seq != lastSeq + 1) {
// AppendLine("<i><color=white>Desynced...</color></i>");
// }
lastSeq = seq;
string newChatline = ParseMessage(delivery);
AppendLine(newChatline);
messageSound.PlayOneShot(messageSound.clip);
// Resend our message because we were too late.
/* if (seq == queuedSeq && queuedMessage != newChatline) {
string tmp = inputField.text;
inputField.text = queuedMessage;
SendMessage();
inputField.text = tmp;
} */
}
// Key Press Handlers.
public void Tilde() { AppendChar(shift ? '~' : '`'); }
public void One() { AppendChar(shift ? '!' : '1'); }
public void Two() { AppendChar(shift ? '@' : '2'); }
public void Three() { AppendChar(shift ? '#' : '3'); }
public void Four() { AppendChar(shift ? '$' : '4'); }
public void Five() { AppendChar(shift ? '%' : '5'); }
public void Six() { AppendChar(shift ? '^' : '6'); }
public void Seven() { AppendChar(shift ? '&' : '7'); }
public void Eight() { AppendChar(shift ? '*' : '8'); }
public void Nine() { AppendChar(shift ? '(' : '9'); }
public void Zero() { AppendChar(shift ? ')' : '0'); }
public void Minus() { AppendChar(shift ? '_' : '-'); }
public void Equals() { AppendChar(shift ? '+' : '='); }
public void Tab() { AppendChar('\t'); }
public void Q() { AppendChar(shift ? 'Q' : 'q'); }
public void W() { AppendChar(shift ? 'W' : 'w'); }
public void E() { AppendChar(shift ? 'E' : 'e'); }
public void R() { AppendChar(shift ? 'R' : 'r'); }
public void T() { AppendChar(shift ? 'T' : 't'); }
public void Y() { AppendChar(shift ? 'Y' : 'y'); }
public void U() { AppendChar(shift ? 'U' : 'u'); }
public void I() { AppendChar(shift ? 'I' : 'i'); }
public void O() { AppendChar(shift ? 'O' : 'o'); }
public void P() { AppendChar(shift ? 'P' : 'p'); }
public void LBracket() { AppendChar(shift ? '{' : '['); }
public void RBracket() { AppendChar(shift ? '}' : ']'); }
public void BackSlash() { AppendChar(shift ? '|' : '\\'); }
public void A() { AppendChar(shift ? 'A' : 'a'); }
public void S() { AppendChar(shift ? 'S' : 's'); }
public void D() { AppendChar(shift ? 'D' : 'd'); }
public void F() { AppendChar(shift ? 'F' : 'f'); }
public void G() { AppendChar(shift ? 'G' : 'g'); }
public void H() { AppendChar(shift ? 'H' : 'h'); }
public void J() { AppendChar(shift ? 'J' : 'j'); }
public void K() { AppendChar(shift ? 'K' : 'k'); }
public void L() { AppendChar(shift ? 'L' : 'l'); }
public void Semicolon() { AppendChar(shift ? ':' : ';'); }
public void Quote() { AppendChar(shift ? '"' : '\''); }
public void Z() { AppendChar(shift ? 'Z' : 'z'); }
public void X() { AppendChar(shift ? 'X' : 'x'); }
public void C() { AppendChar(shift ? 'C' : 'c'); }
public void V() { AppendChar(shift ? 'V' : 'v'); }
public void B() { AppendChar(shift ? 'B' : 'b'); }
public void N() { AppendChar(shift ? 'N' : 'n'); }
public void M() { AppendChar(shift ? 'M' : 'm'); }
public void Comma() { AppendChar(shift ? '<' : ','); }
public void Period() { AppendChar(shift ? '>' : '.'); }
public void Slash() { AppendChar(shift ? '?' : '/'); }
public void Space() { AppendChar(' '); }
}