A game framework written with osu! in mind.
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
4using System;
5using Foundation;
6using osu.Framework.Input.Handlers;
7using osu.Framework.Input.StateChanges;
8using osu.Framework.Platform;
9using osuTK.Input;
10using UIKit;
11
12namespace osu.Framework.iOS.Input
13{
14 public class IOSKeyboardHandler : InputHandler
15 {
16 private readonly IOSGameView view;
17
18 public IOSKeyboardHandler(IOSGameView view)
19 {
20 this.view = view;
21 view.KeyboardTextField.HandleShouldChangeCharacters += handleShouldChangeCharacters;
22 view.KeyboardTextField.HandleShouldReturn += handleShouldReturn;
23 view.KeyboardTextField.HandleKeyCommand += handleKeyCommand;
24 }
25
26 private void handleShouldChangeCharacters(NSRange range, string text)
27 {
28 if (!IsActive)
29 return;
30
31 if (text.Length == 0)
32 {
33 Key key = range.Location < IOSGameView.HiddenTextField.CURSOR_POSITION ? Key.BackSpace : Key.Delete;
34
35 // NOTE: this makes the assumption that Key.AltLeft triggers the WordPrevious platform action
36 if (range.Length > 1)
37 PendingInputs.Enqueue(new KeyboardKeyInput(Key.AltLeft, true));
38
39 if (range.Length > 0)
40 {
41 PendingInputs.Enqueue(new KeyboardKeyInput(key, true));
42 PendingInputs.Enqueue(new KeyboardKeyInput(key, false));
43 }
44
45 if (range.Length > 1)
46 PendingInputs.Enqueue(new KeyboardKeyInput(Key.AltLeft, false));
47
48 return;
49 }
50
51 foreach (char c in text)
52 {
53 Key? key = keyForString(char.ToString(c), out bool upper);
54
55 if (!key.HasValue) continue;
56
57 if (upper)
58 PendingInputs.Enqueue(new KeyboardKeyInput(Key.LShift, true));
59
60 PendingInputs.Enqueue(new KeyboardKeyInput(key.Value, true));
61 PendingInputs.Enqueue(new KeyboardKeyInput(key.Value, false));
62
63 if (upper)
64 PendingInputs.Enqueue(new KeyboardKeyInput(Key.LShift, false));
65 }
66 }
67
68 private void handleShouldReturn()
69 {
70 if (!IsActive)
71 return;
72
73 PendingInputs.Enqueue(new KeyboardKeyInput(Key.Enter, true));
74 PendingInputs.Enqueue(new KeyboardKeyInput(Key.Enter, false));
75 }
76
77 private void handleKeyCommand(UIKeyCommand cmd)
78 {
79 if (!IsActive)
80 return;
81
82 Key? key;
83 bool upper = false;
84
85 // UIKeyCommand constants are not actually constants, so we can't use a switch
86 if (cmd.Input == UIKeyCommand.LeftArrow)
87 key = Key.Left;
88 else if (cmd.Input == UIKeyCommand.RightArrow)
89 key = Key.Right;
90 else if (cmd.Input == UIKeyCommand.UpArrow)
91 key = Key.Up;
92 else if (cmd.Input == UIKeyCommand.DownArrow)
93 key = Key.Down;
94 else
95 key = keyForString(cmd.Input, out upper);
96
97 if (!key.HasValue) return;
98
99 bool shiftHeld = (cmd.ModifierFlags & UIKeyModifierFlags.Shift) > 0 || upper;
100 bool superHeld = (cmd.ModifierFlags & UIKeyModifierFlags.Command) > 0;
101 bool ctrlHeld = (cmd.ModifierFlags & UIKeyModifierFlags.Control) > 0;
102 bool optionHeld = (cmd.ModifierFlags & UIKeyModifierFlags.Alternate) > 0;
103
104 if (shiftHeld) PendingInputs.Enqueue(new KeyboardKeyInput(Key.LShift, true));
105 if (superHeld) PendingInputs.Enqueue(new KeyboardKeyInput(Key.LWin, true));
106 if (ctrlHeld) PendingInputs.Enqueue(new KeyboardKeyInput(Key.LControl, true));
107 if (optionHeld) PendingInputs.Enqueue(new KeyboardKeyInput(Key.LAlt, true));
108
109 PendingInputs.Enqueue(new KeyboardKeyInput(key.Value, true));
110 PendingInputs.Enqueue(new KeyboardKeyInput(key.Value, false));
111
112 if (optionHeld) PendingInputs.Enqueue(new KeyboardKeyInput(Key.LAlt, false));
113 if (ctrlHeld) PendingInputs.Enqueue(new KeyboardKeyInput(Key.LControl, false));
114 if (superHeld) PendingInputs.Enqueue(new KeyboardKeyInput(Key.LWin, false));
115 if (shiftHeld) PendingInputs.Enqueue(new KeyboardKeyInput(Key.LShift, false));
116 }
117
118 private Key? keyForString(string str, out bool upper)
119 {
120 upper = false;
121 if (str.Length == 0)
122 return null;
123
124 char c = str[0];
125
126 switch (c)
127 {
128 case ' ':
129 return Key.Space;
130
131 case '\t':
132 return Key.Tab;
133
134 case '1':
135 case '!':
136 upper = !char.IsDigit(c);
137 return Key.Number1;
138
139 case '2':
140 case '@':
141 upper = !char.IsDigit(c);
142 return Key.Number2;
143
144 case '3':
145 case '#':
146 upper = !char.IsDigit(c);
147 return Key.Number3;
148
149 case '4':
150 case '$':
151 upper = !char.IsDigit(c);
152 return Key.Number4;
153
154 case '5':
155 case '%':
156 upper = !char.IsDigit(c);
157 return Key.Number5;
158
159 case '6':
160 case '^':
161 upper = !char.IsDigit(c);
162 return Key.Number6;
163
164 case '7':
165 case '&':
166 upper = !char.IsDigit(c);
167 return Key.Number7;
168
169 case '8':
170 case '*':
171 upper = !char.IsDigit(c);
172 return Key.Number8;
173
174 case '9':
175 case '(':
176 upper = !char.IsDigit(c);
177 return Key.Number9;
178
179 case '0':
180 case ')':
181 upper = !char.IsDigit(c);
182 return Key.Number0;
183
184 case '-':
185 case '_':
186 upper = c == '_';
187 return Key.Minus;
188
189 case '=':
190 case '+':
191 upper = c == '+';
192 return Key.Plus;
193
194 case '`':
195 case '~':
196 upper = c == '~';
197 return Key.Tilde;
198
199 case '[':
200 case '{':
201 upper = c == '{';
202 return Key.BracketLeft;
203
204 case ']':
205 case '}':
206 upper = c == '}';
207 return Key.BracketRight;
208
209 case '\\':
210 case '|':
211 upper = c == '|';
212 return Key.BackSlash;
213
214 case ';':
215 case ':':
216 upper = c == ':';
217 return Key.Semicolon;
218
219 case '\'':
220 case '\"':
221 upper = c == '\"';
222 return Key.Quote;
223
224 case ',':
225 case '<':
226 upper = c == '<';
227 return Key.Comma;
228
229 case '.':
230 case '>':
231 upper = c == '>';
232 return Key.Period;
233
234 case '/':
235 case '?':
236 upper = c == '?';
237 return Key.Slash;
238
239 default:
240 if (char.IsLetter(c))
241 {
242 string keyName = c.ToString().ToUpper();
243 if (Enum.TryParse(keyName, out Key result))
244 return result;
245 }
246
247 return null;
248 }
249 }
250
251 internal bool KeyboardActive;
252 public override bool IsActive => KeyboardActive;
253
254 protected override void Dispose(bool disposing)
255 {
256 view.KeyboardTextField.HandleShouldChangeCharacters -= handleShouldChangeCharacters;
257 view.KeyboardTextField.HandleShouldReturn -= handleShouldReturn;
258 view.KeyboardTextField.HandleKeyCommand -= handleKeyCommand;
259 base.Dispose(disposing);
260 }
261
262 public override bool Initialize(GameHost host) => true;
263 }
264}