Chesto 0.9
A declarative and element-based library for creating GUIs on homebrew'd consoles
EKeyboard.cpp
1#include "EKeyboard.hpp"
2
3namespace Chesto {
4
5using namespace std;
6
7EKeyboard::EKeyboard() : EKeyboard::EKeyboard(NULL)
8{
9 storeOwnText = true;
10}
11
12EKeyboard::EKeyboard(std::function<void(char)> typeAction)
13{
14 this->x = 30;
15 this->y = SCREEN_HEIGHT - 420;
16
17 this->width = SCREEN_WIDTH - 380;
18
19 this->typeAction = typeAction;
20
21 curRow = index = -1;
22 this->isAbsolute = true;
23
24 // position the EKeyboard based on this x and y
25 updateSize();
26}
27
28void EKeyboard::render(Element*)
29{
30 if (hidden || immersiveMode)
31 return;
32
33 CST_Rect dimens = { this->x, this->y, this->width + 305, this->height + 140 };
34
35 auto renderer = getRenderer();
36
37 CST_SetDrawColor(renderer, { 0xf9, 0xf9, 0xf9, 0xFF });
38 if (hasRoundedKeys) {
39 CST_SetDrawColor(renderer, { 0xdd, 0xdd, 0xdd, 0xFF });
40
41 // rounded keys keyboard is full screen width
42 dimens.x = 0;
43 dimens.y -= 15;
44 dimens.w = RootDisplay::screenWidth;
45 }
46
47 CST_FillRect(renderer, &dimens);
48
49 for (int y = 0; y < rowCount(); y++)
50 for (int x = 0; x < rowLength(y) + 1; x++)
51 {
52 CST_Rect dimens2 = { this->x + kXPad + x * kXOff + y * yYOff, this->y + kYPad + y * ySpacing, keyWidth, keyWidth };
53 if (this->hasRoundedKeys) {
54 CST_roundedBoxRGBA(renderer, dimens2.x, dimens2.y, dimens2.x + dimens2.w, dimens2.y + dimens2.h, 20, 0xee, 0xee, 0xee, 0xff);
55 } else {
56 CST_SetDrawColor(renderer, { 0xf4, 0xf4, 0xf4, 0xff });
57 CST_FillRect(renderer, &dimens2);
58 }
59
60 // draw the letters with fontcache, for rounded keys
61 if (hasRoundedKeys) {
62 char curChar = rows[y]->at(x*2);
63 auto curCharStr = std::string(1, curChar);
64 int fHeight = CST_GetFontHeight(roundKeyFont, curCharStr.c_str());
65 int fWidth = CST_GetFontWidth(roundKeyFont, curCharStr.c_str());
66 CST_DrawFont(
67 roundKeyFont,
68 renderer,
69 dimens2.x + dimens2.w/2 - fWidth/2,
70 dimens2.y + dimens2.h/2 - fHeight/2,
71 curCharStr.c_str()
72 );
73 }
74 }
75
76 CST_Rect dimensSpace = { this->x + sPos, this->y + dHeight, sWidth, textSize };
77 CST_Rect dimensEnter = { this->x + enterPos, this->y + enterHeight, enterWidth, (int)(1.5 * textSize) };
78 CST_Rect dimensTab = { this->x + dPos, this->y + enterHeight, enterWidth, (int)(1.5 * textSize) };
79
80 // if there's a highlighted piece set, color it in
81 if (curRow >= 0 || index >= 0)
82 {
83 CST_Rect dimens2 = { this->x + kXPad + index * kXOff + curRow * yYOff, this->y + kYPad + curRow * ySpacing, keyWidth, keyWidth };
84
85 if (curRow >= rowCount())
86 {
87 switch (index)
88 {
89 case 0:
90 dimens2 = dimensTab;
91 break;
92 case 1:
93 // if we're on SPACE, expand the dimens width of the highlighted button
94 dimens2 = dimensSpace;
95 break;
96 case 2:
97 // if we're on ENTER, expand the dimens width of the highlighted button
98 dimens2 = dimensEnter;
99 break;
100 default:
101 break;
102 }
103 }
104
105 // draw the actual enter/space/tab keys if needed
106 if (hasRoundedKeys) {
107 CST_roundedBoxRGBA(renderer, dimensSpace.x, dimensSpace.y, dimensSpace.x + dimensSpace.w, dimensSpace.y + dimensSpace.h, 20, 0xee, 0xee, 0xee, 0xff);
108 CST_roundedBoxRGBA(renderer, dimensEnter.x, dimensEnter.y, dimensEnter.x + dimensEnter.w, dimensEnter.y + dimensEnter.h, 20, 0xee, 0xee, 0xee, 0xff);
109 CST_roundedBoxRGBA(renderer, dimensTab.x, dimensTab.y, dimensTab.x + dimensTab.w, dimensTab.y + dimensTab.h, 20, 0xee, 0xee, 0xee, 0xff);
110 } else {
111 CST_SetDrawColor(renderer, { 0xf4, 0xf4, 0xf4, 0xff });
112 CST_FillRect(renderer, &dimensSpace);
113
114 if (!preventEnterAndTab) {
115 CST_FillRect(renderer, &dimensEnter);
116 CST_FillRect(renderer, &dimensTab);
117 }
118 }
119
120 // draw the currently selected tile if these index things are set
121 if (touchMode && !isTouchDrag)
122 {
123 if (hasRoundedKeys) {
124 CST_roundedBoxRGBA(renderer, dimens2.x, dimens2.y, dimens2.x + dimens2.w, dimens2.y + dimens2.h, 20, 0xad, 0xd8, 0xe6, 0x90);
125 CST_roundedRectangleRGBA(renderer, dimens2.x, dimens2.y, dimens2.x + dimens2.w, dimens2.y + dimens2.h, 20, 0x66, 0x7c, 0x89, 0xff);
126 } else {
127 CST_SetDrawColor(renderer, { 0xad, 0xd8, 0xe6, 0x90 }); // TODO: matches the DEEP_HIGHLIGHT color
128 CST_FillRect(renderer, &dimens2);
129 }
130 }
131 else if (hasRoundedKeys) {
132 CST_roundedBoxRGBA(renderer, dimens2.x, dimens2.y, dimens2.x + dimens2.w, dimens2.y + dimens2.h, 20, 0xad, 0xd8, 0xe6, 0x90);
133 CST_roundedRectangleRGBA(renderer, dimens2.x, dimens2.y, dimens2.x + dimens2.w, dimens2.y + dimens2.h, 20, 0x66, 0x7c, 0x89, 0xff);
134 }
135 else {
136 CST_SetDrawColor(renderer, { 0xff, 0xff, 0xff, 0xff }); // TODO: matches the DEEP_HIGHLIGHT color
137 CST_FillRect(renderer, &dimens2);
138
139 auto d = dimens2;
140 // draw a rectangle that is inlet a little
141 for (int x=-2; x<3; x++) {
142 CST_rectangleRGBA(renderer, d.x + x, d.y + x, d.x + d.w - x, d.y + d.h - x, 0x10, 0xD9, 0xD9, 0xff);
143 }
144 }
145 }
146
147 super::render(this);
148}
149
150bool EKeyboard::process(InputEvents* event)
151{
152 InputEvents::bypassKeyEvents = false;
153
154 // don't do anything if we're hidden, or there's a sidebar and it's active
155 if (hidden)
156 return false;
157
158 // our keyboard will be processing its own key events (not button events)
159 InputEvents::bypassKeyEvents = true;
160
161 if (event->type == SDL_KEYDOWN)
162 return listenForPhysicalKeys(event);
163 if (event->type == SDL_KEYUP && (event->keyCode == SDLK_LSHIFT ||
164 event->keyCode == SDLK_RSHIFT ||
165 event->keyCode == SDLK_CAPSLOCK)) {
166 shiftOn = false;
167 updateSize();
168 return true;
169 }
170
171 // immersive view doesn't use any keyboard touch events
172 if (immersiveMode) return false;
173
174 if (event->isTouchDown())
175 {
176 curRow = index = -1;
177 touchMode = true;
178 isTouchDrag = false;
179 }
180
181 if (event->isKeyDown()) {
182 touchMode = false;
183 isTouchDrag = false;
184 }
185
186 bool ret = false;
187
188 // only set touch drag if a selection hasn't been made
189 auto prevTouchDrag = isTouchDrag;
190 isTouchDrag = (event->isTouchDrag() && (!touchMode || (curRow < 0 && index < 0))) || isTouchDrag;
191 if (prevTouchDrag != isTouchDrag && isTouchDrag) {
192 // started a drag event, play vibrate
193 CST_LowRumble(event, 200);
194 ret = true;
195 }
196
197 if (!touchMode && !isTouchDrag)
198 {
199 if (curRow < 0 && index < 0)
200 {
201 // switched into EKeyboard, set to 0 and return
202 curRow = 1;
203 index = 0;
204 return true;
205 }
206
207 if (event->isKeyDown())
208 {
209 int lastRow = curRow;
210 curRow += (event->held(DOWN_BUTTON) - event->held(UP_BUTTON));
211 index += (event->held(RIGHT_BUTTON) - event->held(LEFT_BUTTON));
212
213 if (curRow < 0) curRow = 0;
214 if (index < 0) index = 0;
215 if (curRow >= rowCount() + 1) curRow = rowCount(); // +1 for bottom "row" (tab, space, enter)
216 if (curRow == rowCount())
217 {
218 // go to space key if last index is in the middle of row
219 if (lastRow < curRow && index > 0 && index < rowLength(lastRow))
220 {
221 index = 1;
222 }
223
224 // tab key
225 if (index < 0) index = 0;
226
227 // enter key
228 if (index > 2) index = 2;
229 }
230 else
231 {
232 if (index > rowLength(curRow))
233 index = rowLength(curRow);
234
235 if (lastRow == rowCount()) {
236 switch (index)
237 {
238 case 0: //tab
239 index = 0;
240 break;
241 case 1: // space
242 // go to middle of current row
243 index = rowLength(curRow) / 2;
244 break;
245 case 2: // enter
246 // go to end of current row
247 index = rowLength(curRow);
248 break;
249 default:
250 break;
251 }
252 }
253 }
254
255 if (event->held(A_BUTTON))
256 {
257 // space bar and enter key
258 if (curRow >= rowCount())
259 {
260 switch (index)
261 {
262 case 0:
263 just_type('\t');
264 break;
265 case 1:
266 just_type(' ');
267 break;
268 case 2:
269 just_type('\n');
270 break;
271 default:
272 break;
273 }
274 return true;
275 }
276
277 type(curRow, index);
278 }
279
280 // updateView();
281 return true;
282 }
283
284 return false;
285 }
286
287 int extWidth = width + 305;
288
289 if ((event->isTouchDown() || event->isTouchDrag()) && event->touchIn(this->x, this->y, extWidth, height + 200))
290 {
291 for (int y = 0; y < rowCount(); y++)
292 for (int x = 0; x < rowLength(y) + 1; x++) {
293 auto xStart = this->x + kXPad + x * kXOff + y * yYOff;
294 auto yStart = this->y + kYPad + y * ySpacing;
295 if (event->touchIn(xStart, yStart, keyWidth, keyWidth))
296 {
297 ret |= true;
298 curRow = y;
299 index = x;
300 }
301 }
302 return true;
303 }
304
305 if (event->isTouchUp())
306 {
307 // only proceed if we've been touchdown'd
308 // reset current row and info
309 curRow = -1;
310 index = -1;
311
312 if (event->touchIn(this->x, this->y, extWidth, height + 200))
313 {
314
315 for (int y = 0; y < rowCount(); y++)
316 for (int x = 0; x < rowLength(y) + 1; x++)
317 if (event->touchIn(this->x + kXPad + x * kXOff + y * yYOff, this->y + kYPad + y * ySpacing, keyWidth, keyWidth))
318 {
319 ret |= true;
320 type(y, x);
321 }
322
323 if (event->touchIn(this->x + dPos, this->y + enterHeight, enterWidth, textSize))
324 {
325 ret |= true;
326 just_type('\t');
327 }
328
329 if (event->touchIn(this->x + sPos, this->y + dHeight, sWidth, textSize))
330 {
331 ret |= true;
332 just_type(' ');
333 }
334
335 if (event->touchIn(this->x + enterPos, this->y + enterHeight, enterWidth, textSize))
336 {
337 ret |= true;
338 just_type('\n');
339 }
340
341 // if (ret)
342 // updateView();
343
344 return ret;
345 }
346
347 return false;
348 }
349
350 return false;
351}
352
353bool EKeyboard::listenForPhysicalKeys(InputEvents* e)
354{
355 int curBreak = 0;
356 int offset = 0;
357
358 auto keyCode = e->keyCode;
359 // auto mod = e->mod;
360
361 // special keys
362 if (keyCode == SDLK_LSHIFT || keyCode == SDLK_RSHIFT || keyCode == SDLK_CAPSLOCK) {
363 shiftOn = true;
364 updateSize();
365 return true;
366 }
367
368 if (keyCode == SDLK_TAB) {
369 just_type('\t');
370 return true;
371 }
372 if (keyCode == SDLK_SPACE) {
373 just_type(' ');
374 return true;
375 }
376 if (keyCode == SDLK_RETURN && !preventEnterAndTab) {
377 just_type('\n');
378 return true;
379 }
380
381 // alt key will toggle the keyboard, but still allow inputs
382 if (keyCode == SDLK_LALT || keyCode == SDLK_RALT) {
383 immersiveMode = !immersiveMode;
384 return true;
385 }
386
387 // primary typing loop, handles all the keycodes on our keyboard
388 auto roundedOffset = hasRoundedKeys ? 1 : 0;
389 for (int x=roundedOffset; x<KEYCODE_COUNT; x++)
390 {
391 int xx = x - offset;
392 int topRowOffset = curBreak == 0 ? roundedOffset : 0;
393
394 if (keyCode == usbKeys[x])
395 {
396 // we got a key down for this code, type our current position
397 // and update cursor
398 type(curBreak, xx - topRowOffset);
399 curRow = curBreak;
400 index = xx - topRowOffset;
401 return true;
402 }
403
404 if (xx + 1 >= breaks[curBreak]) {
405 curBreak++;
406 offset = x + 1;
407 }
408 }
409
410 // below are progammatic invocations of certain features we expect to be there
411 // in vgedit
412 // TODO: either make these callbacks or internal calls rather than going through SDL event engine
413
414 if (keyCode == SDLK_BACKSPACE) {
415 // programatically invoke the B button event
416 backspace();
417 return true;
418 }
419
420 if (keyCode == SDLK_RETURN && preventEnterAndTab) {
421 // if don't allow enter, let's consider RETURN as submitting the type action (hardcoded to X button)
422 // TODO: make it its own callback
423 SDL_Event sdlevent;
424 sdlevent.type = SDL_JOYBUTTONDOWN;
425 sdlevent.jbutton.button = SDL_X;
426 SDL_PushEvent(&sdlevent);
427 return true;
428 }
429
430 if (keyCode == SDLK_ESCAPE) {
431 // dismiss keyboard, also programmatically
432 SDL_Event sdlevent;
433 sdlevent.type = SDL_JOYBUTTONDOWN;
434 sdlevent.jbutton.button = SDL_Y;
435 SDL_PushEvent(&sdlevent);
436 return true;
437 }
438
439 return false;
440}
441
442void EKeyboard::updateSize()
443{
444 this->elements.clear();
445 rows.clear();
446
447 this->height = (304 / 900.0) * width;
448
449 // set up lots of scaling variables based on the width/height
450
451 this->keyWidth = (int)(0.08 * width);
452 this->padding = keyWidth / 2.0;
453
454 // these field variables are for displaying the QWERTY keys (touching and displaying)
455 kXPad = (int)((23 / 400.0) * width);
456 kXOff = (int)((36.5 / 400.0) * width) + (900.0 / width) * ( 900.0 != width); // when scaling, adjust our key x offset TODO: probably a guesstimate
457 yYOff = (int)((22 / 400.0) * width);
458 kYPad = (int)((17 / 135.0) * height);
459 ySpacing = (int)((33 / 400.0) * width);
460
461 if (hasRoundedKeys) {
462 // different positioning for round keyboard
463 // TODO: something else
464 kXOff = (int)((38 / 400.0) * width) + (900.0 / width) * ( 900.0 != width); // when scaling, adjust our key x offset TODO: probably a guesstimate
465 yYOff = (int)((10 / 400.0) * width);
466 kYPad = (int)((0 / 135.0) * height);
467 ySpacing = (int)((37 / 400.0) * width);
468 }
469
470 // these local variables position only the text, and has nothing to do with the
471 // touch. They should likely be based on the above field variables so those
472 // can change quickly
473 int kXPad = (int)((30 / 400.0) * width);
474 int kXOff = (int)((22 / 400.0) * width);
475#ifdef __WIIU__
476 // cheeky positioning hack because wiiu screen dimensions are different
477 // to properly fix, the above comments would need to be addressed
478 int kYPad = (int)((14 / 400.0) * width);
479#endif
480 int kYOff = (int)((33 / 400.0) * width);
481
482 this->textSize = 0.9375 * keyWidth;
483
484 // delete, space and enter key dimensions
485 dPos = (int)((13 / 400.0) * width);
486 dHeight = (int)((85 / 135.0) * height) + 145;
487 sPos = (int)((150 / 400.0) * width);
488 enterPos = dPos + 1000;
489 enterHeight = dHeight - 34;
490
491 dWidth = (int)(1.4125 * textSize);
492 sWidth = (int)(7.5 * textSize);
493 enterWidth = (int)(2.25 * textSize);
494
495 // set up the keys vector based on the current EKeyboard selection
496 generateEKeyboard();
497
498 CST_Color gray = { 0x52, 0x52, 0x52, 0xff };
499
500 int targetHeight = -1;
501
502 // go through and draw each of the three rows at the right position
503 if (!hasRoundedKeys) {
504 for (int x = 0; x < rowCount(); x++)
505 {
506 TextElement* rowText = new TextElement(rows[x]->c_str(), textSize, &gray, ICON);
507 // rowText->customFontPath = RAMFS "res/lightsans.ttf";
508 if (targetHeight < 0) {
509 targetHeight = rowText->height;
510 }
511 rowText->update(true);
512 rowText->position(kXPad + x * kXOff, kYPad + x * kYOff + targetHeight/2 - rowText->height/2);
513 this->addNode(std::unique_ptr<TextElement>(rowText));
514 }
515} // text for space, enter, and symbols
516 CST_Color grayish = { 0x55, 0x55, 0x55, 0xff };
517 TextElement* spaceText = new TextElement("space", 30, &grayish);
518 CST_Rect d4 = { this->x + sPos, this->y + dHeight, sWidth, textSize }; // todo: extract out hardcoded rects like this
519 spaceText->position(d4.x + d4.w / 2 - spaceText->width / 2 - 15, 345);
520 this->addNode(std::unique_ptr<TextElement>(spaceText));
521
522 if (!preventEnterAndTab)
523 {
524 TextElement* enterText = new TextElement("enter", 30, &grayish);
525 CST_Rect d3 = { this->x + enterPos, this->y + enterHeight, enterWidth, textSize }; // todo: extract out hardcoded rects like this
526 enterText->position(d3.x + d3.w / 2 - enterText->width / 2 - 30, 327);
527 this->addNode(std::unique_ptr<TextElement>(enterText));
528
529 TextElement* symText = new TextElement(hasRoundedKeys ? "shift" : "tab", 30, &grayish);
530 CST_Rect d5 = { this->x + dPos, this->y + enterHeight, enterWidth, textSize }; // todo: extract out hardcoded rects like this
531 symText->position(d5.x + d5.w / 2 - symText->width / 2 - 30, 327);
532 this->addNode(std::unique_ptr<TextElement>(symText));
533 }
534}
535
536// given a (y, x) pair in the keyboard layout, type the corresponding letter
537void EKeyboard::type(int y, int x)
538{
539 const char input = (*(rows[y]))[x * 2];
540 just_type(input);
541}
542
543// call type action w/ some checks
544void EKeyboard::just_type(const char input)
545{
546 if (preventEnterAndTab && (input == '\n' || input == '\t'))
547 return;
548
549 // store our own string if we need to
550 if (storeOwnText)
551 textInput.push_back(input);
552
553 // call the underlying type action, if present
554 if (typeAction != NULL) {
555 typeAction(input);
556 return;
557 }
558}
559
560void EKeyboard::generateEKeyboard()
561{
562 int count = 0;
563 string keys;
564
565 if (mode == 0)
566 keys = string((shiftOn || capsOn) ? upper_keys : lower_keys);
567 else
568 {
569 // depending on the mode, grab a bunch of characters from unicode starting from
570 // upside exclamation mark (U+00A1) and onward https://unicode-table.com/en/
571 // TODO: don't hardcode amount here, or hardcode a better one
572 int offset = 47 * (mode - 1) + 0x00a1;
573 char chars[47 + (mode == 2)];
574 for (int x = 0; x < 47 + (mode == 2); x++)
575 {
576 chars[x] = offset + x;
577 }
578
579 keys = string(chars);
580 }
581
582 breaks[3] += (mode == 2); // one more key for bottom row of mode 2
583
584 for (int& end : breaks)
585 {
586 auto roundedOffset = (hasRoundedKeys && count == 0) ? 1 : 0;
587 string* row = new string(keys.substr(count + roundedOffset, end - roundedOffset));
588 for (int x = 1; x < (int)row->size(); x += 2)
589 {
590 row->insert(row->begin() + x, ' ');
591 }
592
593 rows.push_back(row);
594 count += end;
595 }
596
597 if (hasRoundedKeys && roundKeyFont == NULL) {
598 // load the round key font
599 roundKeyFont = CST_CreateFont();
600 auto fontPath = RAMFS "res/lightsans.ttf";
601 auto renderer = getRenderer();
602 CST_LoadFont(roundKeyFont, renderer, fontPath, 40, CST_MakeColor(0,0,0,255), TTF_STYLE_NORMAL);
603 }
604}
605
606void EKeyboard::backspace()
607{
608 if (storeOwnText) {
609 // manage our own internal string
610 if (!textInput.empty())
611 textInput.pop_back();
612 // call out to the typing callback TODO: use a separate callback (see below)
613 typeAction('\b'); // not great
614 return;
615 }
616
617 // TODO: use a backspace callback instead of hardcoding a B button event
618 // (B is used by vgedit to manage external backspaces)
619 SDL_Event sdlevent;
620 sdlevent.type = SDL_JOYBUTTONDOWN;
621 sdlevent.jbutton.button = SDL_B;
622 SDL_PushEvent(&sdlevent);
623}
624
625const std::string& EKeyboard::getTextInput()
626{
627 return textInput;
628}
629
630EKeyboard::~EKeyboard()
631{
632 InputEvents::bypassKeyEvents = false;
633}
634
635} // namespace Chesto
bool touchIn(int x, int width, int y, int height)
whether or not a touch is detected within the specified rect in this cycle
bool held(int buttons)
whether or not a button is pressed during this cycle