From c32bb5dd6d7293abad8b2b6a884356c4b0a6e729 Mon Sep 17 00:00:00 2001 From: Pk11 Date: Sat, 28 Aug 2021 20:54:11 -0500 Subject: [PATCH] Very WIP: Add keyboard --- examples/keyboard/english-us.json | 205 +++++++++++++++++++ include/gui.hpp | 5 + include/keyboard.hpp | 131 ++++++++++++ include/structs.hpp | 6 + include/textUtils.hpp | 70 +++++++ source/gui.cpp | 4 + source/keyboard.cpp | 326 ++++++++++++++++++++++++++++++ source/textUtils.cpp | 114 +++++++++++ 8 files changed, 861 insertions(+) create mode 100644 examples/keyboard/english-us.json create mode 100644 include/keyboard.hpp create mode 100644 include/textUtils.hpp create mode 100644 source/keyboard.cpp create mode 100644 source/textUtils.cpp diff --git a/examples/keyboard/english-us.json b/examples/keyboard/english-us.json new file mode 100644 index 0000000..3786499 --- /dev/null +++ b/examples/keyboard/english-us.json @@ -0,0 +1,205 @@ +{ + "info": { + "name": "English (US)", + "x": 0, + "y": 132 + }, + "layout": { + "!main": { + "keys": { + "`": [0, 0, 20, 20], + "1": [22, 0, 20, 20], + "2": [44, 0, 20, 20], + "3": [66, 0, 20, 20], + "4": [88, 0, 20, 20], + "5": [110, 0, 20, 20], + "6": [132, 0, 20, 20], + "7": [154, 0, 20, 20], + "8": [176, 0, 20, 20], + "9": [198, 0, 20, 20], + "0": [220, 0, 20, 20], + "-": [242, 0, 20, 20], + "=": [264, 0, 20, 20], + "Bksp": [286, 0, 34, 20, {"action": "backspace", "key": "B"}], + "Tab": [0, 22, 31, 20, {"value": "\t"}], + "q": [33, 22, 20, 20], + "w": [55, 22, 20, 20], + "e": [77, 22, 20, 20], + "r": [99, 22, 20, 20], + "t": [121, 22, 20, 20], + "y": [143, 22, 20, 20], + "u": [165, 22, 20, 20], + "i": [187, 22, 20, 20], + "o": [209, 22, 20, 20], + "p": [231, 22, 20, 20], + "[": [253, 22, 20, 20], + "]": [275, 22, 20, 20], + "\\": [297, 22, 23, 20], + "Caps": [0, 44, 38, 20, {"mode": "caps"}], + "a": [40, 44, 20, 20], + "s": [62, 44, 20, 20], + "d": [84, 44, 20, 20], + "f": [106, 44, 20, 20], + "g": [128, 44, 20, 20], + "h": [150, 44, 20, 20], + "j": [172, 44, 20, 20], + "k": [194, 44, 20, 20], + "l": [216, 44, 20, 20], + ";": [238, 44, 20, 20], + "'": [260, 44, 20, 20], + "Enter": [282, 44, 38, 20, {"action": "newline"}], + "Shift": [0, 66, 49, 20, {"mode": "shift", "key": "Y"}], + "z": [51, 66, 20, 20], + "x": [73, 66, 20, 20], + "c": [95, 66, 20, 20], + "v": [117, 66, 20, 20], + "b": [139, 66, 20, 20], + "n": [161, 66, 20, 20], + "m": [183, 66, 20, 20], + ",": [205, 66, 20, 20], + ".": [227, 66, 20, 20], + "/": [249, 66, 20, 20], + "Shift (R)": [271, 66, 49, 20, {"mode": "shift", "label": "Shift"}], + "Close": [0, 88, 64, 20, {"action": "exit", "key": ["START", "SELECT"]}], + "(=)": [66, 88, 27, 20, {"action": "layout"}], + " ": [95, 88, 108, 20], + "(!)": [205, 88, 27, 20, {"action": "phrases"}], + "←": [234, 88, 20, 20, {"action": "left", "key": "LEFT"}], + "→": [256, 88, 20, 20, {"action": "right", "key": "RIGHT"}], + "↓": [278, 88, 20, 20, {"action": "down", "key": "DOWN"}], + "↑": [300, 88, 20, 20, {"action": "up", "key": "UP"}] + } + }, + "shift": { + "return": true, + "keys": { + "~": [0, 0, 20, 20], + "!": [22, 0, 20, 20], + "@": [44, 0, 20, 20], + "#": [66, 0, 20, 20], + "$": [88, 0, 20, 20], + "%": [110, 0, 20, 20], + "^": [132, 0, 20, 20], + "&": [154, 0, 20, 20], + "*": [176, 0, 20, 20], + "(": [198, 0, 20, 20], + ")": [220, 0, 20, 20], + "_": [242, 0, 20, 20], + "+": [264, 0, 20, 20], + "Bksp": [286, 0, 34, 20, {"action": "backspace", "key": "B"}], + "Tab": [0, 22, 31, 20, {"value": "\t"}], + "Q": [33, 22, 20, 20], + "W": [55, 22, 20, 20], + "E": [77, 22, 20, 20], + "R": [99, 22, 20, 20], + "T": [121, 22, 20, 20], + "Y": [143, 22, 20, 20], + "U": [165, 22, 20, 20], + "I": [187, 22, 20, 20], + "O": [209, 22, 20, 20], + "P": [231, 22, 20, 20], + "{": [253, 22, 20, 20], + "}": [275, 22, 20, 20], + "|": [297, 22, 23, 20], + "Caps": [0, 44, 38, 20, {"mode": "caps"}], + "A": [40, 44, 20, 20], + "S": [62, 44, 20, 20], + "D": [84, 44, 20, 20], + "F": [106, 44, 20, 20], + "G": [128, 44, 20, 20], + "H": [150, 44, 20, 20], + "J": [172, 44, 20, 20], + "K": [194, 44, 20, 20], + "L": [216, 44, 20, 20], + ":": [238, 44, 20, 20], + "\"": [260, 44, 20, 20], + "Enter": [282, 44, 38, 20, {"action": "newline"}], + "Shift": [0, 66, 49, 20, {"value": "", "active": true, "key": "Y"}], + "Z": [51, 66, 20, 20], + "X": [73, 66, 20, 20], + "C": [95, 66, 20, 20], + "V": [117, 66, 20, 20], + "B": [139, 66, 20, 20], + "N": [161, 66, 20, 20], + "M": [183, 66, 20, 20], + "<": [205, 66, 20, 20], + ">": [227, 66, 20, 20], + "?": [249, 66, 20, 20], + "Shift (R)": [271, 66, 49, 20, {"value": "", "active": true, "label": "Shift"}], + "Close": [0, 88, 64, 20, {"action": "exit", "key": ["START", "SELECT"]}], + "(=)": [66, 88, 27, 20, {"action": "layout"}], + " ": [95, 88, 108, 20], + "(!)": [205, 88, 27, 20, {"action": "phrases"}], + "←": [234, 88, 20, 20, {"action": "left", "key": "LEFT"}], + "→": [256, 88, 20, 20, {"action": "right", "key": "RIGHT"}], + "↑": [278, 88, 20, 20, {"action": "down", "key": "DOWN"}], + "↓": [300, 88, 20, 20, {"action": "up", "key": "UP"}] + } + }, + "caps": { + "keys": { + "`": [0, 0, 20, 20], + "1": [22, 0, 20, 20], + "2": [44, 0, 20, 20], + "3": [66, 0, 20, 20], + "4": [88, 0, 20, 20], + "5": [110, 0, 20, 20], + "6": [132, 0, 20, 20], + "7": [154, 0, 20, 20], + "8": [176, 0, 20, 20], + "9": [198, 0, 20, 20], + "0": [220, 0, 20, 20], + "-": [242, 0, 20, 20], + "=": [264, 0, 20, 20], + "Bksp": [286, 0, 34, 20, {"action": "backspace", "key": "B"}], + "Tab": [0, 22, 31, 20, {"value": "\t"}], + "Q": [33, 22, 20, 20], + "W": [55, 22, 20, 20], + "E": [77, 22, 20, 20], + "R": [99, 22, 20, 20], + "T": [121, 22, 20, 20], + "Y": [143, 22, 20, 20], + "U": [165, 22, 20, 20], + "I": [187, 22, 20, 20], + "O": [209, 22, 20, 20], + "P": [231, 22, 20, 20], + "[": [253, 22, 20, 20], + "]": [275, 22, 20, 20], + "\\": [297, 22, 23, 20], + "Caps": [0, 44, 38, 20, {"mode": "!main", "active": true}], + "A": [40, 44, 20, 20], + "S": [62, 44, 20, 20], + "D": [84, 44, 20, 20], + "F": [106, 44, 20, 20], + "G": [128, 44, 20, 20], + "H": [150, 44, 20, 20], + "J": [172, 44, 20, 20], + "K": [194, 44, 20, 20], + "L": [216, 44, 20, 20], + ";": [238, 44, 20, 20], + "'": [260, 44, 20, 20], + "Enter": [282, 44, 38, 20, {"action": "newline"}], + "Shift": [0, 66, 49, 20, {"mode": "shift", "key": "Y"}], + "Z": [51, 66, 20, 20], + "X": [73, 66, 20, 20], + "C": [95, 66, 20, 20], + "V": [117, 66, 20, 20], + "B": [139, 66, 20, 20], + "N": [161, 66, 20, 20], + "M": [183, 66, 20, 20], + ",": [205, 66, 20, 20], + ".": [227, 66, 20, 20], + "/": [249, 66, 20, 20], + "Shift (R)": [271, 66, 49, 20, {"mode": "shift", "label": "Shift"}], + "Close": [0, 88, 64, 20, {"action": "exit", "key": ["START", "SELECT"]}], + "(=)": [66, 88, 27, 20, {"action": "layout"}], + " ": [95, 88, 108, 20], + "(!)": [205, 88, 27, 20, {"action": "phrases"}], + "←": [234, 88, 20, 20, {"action": "left", "key": "LEFT"}], + "→": [256, 88, 20, 20, {"action": "right", "key": "RIGHT"}], + "↑": [278, 88, 20, 20, {"action": "down", "key": "DOWN"}], + "↓": [300, 88, 20, 20, {"action": "up", "key": "UP"}] + } + } + } +} diff --git a/include/gui.hpp b/include/gui.hpp index 0e9f063..14e5dbc 100644 --- a/include/gui.hpp +++ b/include/gui.hpp @@ -60,6 +60,11 @@ namespace Gui { */ void clearTextBufs(void); + /* + Updates the Text Buffer to the screen. + */ + void updateTextBufs(bool top); + /* Clear the screen. */ diff --git a/include/keyboard.hpp b/include/keyboard.hpp new file mode 100644 index 0000000..ee92a79 --- /dev/null +++ b/include/keyboard.hpp @@ -0,0 +1,131 @@ +/* + * This file is part of Universal-Core + * Copyright (C) 2021 Universal-Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Additional Terms 7.b and 7.c of GPLv3 apply to this file: + * * Requiring preservation of specified reasonable legal notices or + * author attributions in that material or in the Appropriate Legal + * Notices displayed by works containing it. + * * Prohibiting misrepresentation of the origin of that material, + * or requiring that modified versions of such material be marked in + * reasonable ways as different from the original version. + */ + +#ifndef _UNIVERSAL_CORE_KEYBOARD_HPP +#define _UNIVERSAL_CORE_KEYBOARD_HPP + +#include "structs.hpp" + +#include +#include +#include + +class UCKeyboard { +private: + struct Key { + enum class Property : uint8_t { Invalid, Action, Mode, Value }; + + Structs::ButtonPos Pos; + std::string Label; + std::map Properties = { }; + bool Active = false; + uint32_t Button = 0; + + Key(Structs::ButtonPos Pos, const std::string &Label) : Pos(Pos), Label(Label) { }; + }; + + struct Mode { + std::vector Keys = { }; + bool Ret = false; + }; + + uint8_t BgColor, BarColor, OutlineColor, KeyColor, KeyColorPressed, KeyColorActive, TextColor, HintColor; + + bool Loaded = false; + + std::vector CurrentMode = { "!main" }; + int KbdX = 0, KbdY = 0; + std::map Kbd; + + std::string CurrentString = ""; + int Cursor = 0; + uint MaxSize = 0; + bool IsDone = false; + + uint8_t GetCharSize(void) const; + uint8_t GetPrevCharSize(void) const; + + void HandleKeyPress(const Key &Key); + void SwitchLayout() const; +public: + /** + * @brief UCKeyboard user input class. + * @param KeyboardJSON The path to the layout JSON. + * @param BgColor The background color. + * @param BgColor The bar color. + * @param BgColor The outline color. + * @param KeyColor The key color. + * @param KeyColorPressed The pressed key color. + * @param KeyColorActive The active key color. + * @param TextColor The text color. + * @param HintColor The hint text color. + */ + UCKeyboard(const std::string &KeyboardJSON, uint8_t BgColor, uint8_t BarColor, uint8_t OutlineColor, uint8_t KeyColor, uint8_t KeyColorPressed, uint8_t KeyColorActive, uint8_t TextColor, uint8_t HintColor); + + ~UCKeyboard(void) { }; + + /** + * @brief Draws the keyboard, use with Handler() for live input. + * @param Held The value from keysHeld(). + * @param Repeat The value from keysDownRepeat(). + * @param T The value from touchRead(). + */ + void Draw(uint32_t Held, uint32_t Repeat, touchPosition T) const; + + /** + * @brief Handles keyboard actions, use with Draw() for live input. + * @param Held The value from keysHeld(). + * @param Repeat The value from keysDownRepeat(). + * @param T The value from touchRead(). + */ + void Handler(uint32_t Held, uint32_t Repeat, touchPosition T); + + /** + * @brief Gets the current string for use in live input mode. + */ + std::string String(void) const { return CurrentString; }; + + /** + * @brief Gets if the user is done inputting. + */ + bool Done(void) const { return IsDone; }; + + /** + * @brief Gets a string from user input. + * @param maxSize The maximum size *in bytes*, set to 0 for no limit. + * @param Hint The hint text. + */ + std::string GetString(uint MaxSize, const std::string &Hint); + + /** + * @brief Gets an int from user input. + * @param maxSize The maximum size of the number, set to 0 for no limit. + * @param Hint The hint text. + */ + uint GetInt(uint Max, const std::string &Hint); +}; + +#endif diff --git a/include/structs.hpp b/include/structs.hpp index 36d4790..9983e25 100644 --- a/include/structs.hpp +++ b/include/structs.hpp @@ -28,6 +28,8 @@ #define _UNIVERSAL_CORE_STRUCTS_HPP #include +#include +#include class Structs { public: @@ -36,6 +38,10 @@ public: int y; int w; int h; + + bool Touched(const touchPosition &T) const { + return (T.px >= this->x && T.px <= (this->x + this->w)) && (T.py >= this->y && T.py <= (this->y + this->h)); + }; }; struct Key { diff --git a/include/textUtils.hpp b/include/textUtils.hpp new file mode 100644 index 0000000..1227323 --- /dev/null +++ b/include/textUtils.hpp @@ -0,0 +1,70 @@ +/* + * This file is part of Universal-Core + * Copyright (C) 2021 Universal-Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Additional Terms 7.b and 7.c of GPLv3 apply to this file: + * * Requiring preservation of specified reasonable legal notices or + * author attributions in that material or in the Appropriate Legal + * Notices displayed by works containing it. + * * Prohibiting misrepresentation of the origin of that material, + * or requiring that modified versions of such material be marked in + * reasonable ways as different from the original version. + */ + +#ifndef _UNIVERSAL_CORE_TEXTUTILS_HPP +#define _UNIVERSAL_CORE_TEXTUTILS_HPP + +#include +#include +#include +#include + +class TextUtils { +private: + static constexpr std::array Dakutenable = { + u'か', u'き', u'く', u'け', u'こ', + u'さ', u'し', u'す', u'せ', u'そ', + u'た', u'ち', u'つ', u'て', u'と', + u'は', u'ひ', u'ふ', u'へ', u'ほ' + }; + + static constexpr std::array Handakutenable = { + u'は', u'ひ', u'ふ', u'へ', u'ほ' + }; + + static constexpr std::array, 13> KeyNames = {{ + { "A", KEY_A }, + { "B", KEY_B }, + { "SELECT", KEY_SELECT }, + { "START", KEY_START }, + { "R", KEY_R }, + { "L", KEY_L }, + { "X", KEY_X }, + { "Y", KEY_Y }, + { "TOUCH", KEY_TOUCH }, + { "UP", KEY_UP }, + { "DOWN", KEY_DOWN }, + { "LEFT", KEY_LEFT }, + { "RIGHT", KEY_RIGHT } + }}; + +public: + static char32_t GetCodepoint(const char *Str); + static std::string Dakutenify(std::string Str, bool Handakuten); + static uint32_t StrToKey(const std::string &Str); +}; + +#endif diff --git a/source/gui.cpp b/source/gui.cpp index 71a848f..dcde84c 100644 --- a/source/gui.cpp +++ b/source/gui.cpp @@ -87,6 +87,10 @@ void Gui::clearTextBufs(void) { DefaultFont->clear(); } +void Gui::updateTextBufs(bool top) { + DefaultFont->update(top); +} + void Gui::DrawSprite(Spritesheet &sheet, size_t imgindex, int x, int y, float ScaleX, float ScaleY) { sheet[imgindex].draw(x, y, 0x20, ScaleX, ScaleY); } diff --git a/source/keyboard.cpp b/source/keyboard.cpp new file mode 100644 index 0000000..ed0559e --- /dev/null +++ b/source/keyboard.cpp @@ -0,0 +1,326 @@ +/* + * This file is part of Universal-Core + * Copyright (C) 2021 Universal-Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Additional Terms 7.b and 7.c of GPLv3 apply to this file: + * * Requiring preservation of specified reasonable legal notices or + * author attributions in that material or in the Appropriate Legal + * Notices displayed by works containing it. + * * Prohibiting misrepresentation of the origin of that material, + * or requiring that modified versions of such material be marked in + * reasonable ways as different from the original version. + */ + +#include "keyboard.hpp" + +#include "gui.hpp" +#include "JSON.hpp" +#include "textUtils.hpp" + +#include + +UCKeyboard::UCKeyboard(const std::string &KeyboardJSON, uint8_t BgColor, uint8_t BarColor, uint8_t OutlineColor, uint8_t KeyColor, uint8_t KeyColorPressed, uint8_t KeyColorActive, uint8_t TextColor, uint8_t HintColor) : BgColor(BgColor), BarColor(BarColor), OutlineColor(OutlineColor), KeyColor(KeyColor), KeyColorPressed(KeyColorPressed), KeyColorActive(KeyColorActive), TextColor(TextColor), HintColor(HintColor) { + FILE *File = fopen(KeyboardJSON.c_str(), "rt"); + + if (File) { + nlohmann::json Json = nlohmann::json::parse(File, nullptr, false); + fclose(File); + + /* Clear. */ + this->Kbd.clear(); + this->CurrentMode.clear(); + this->CurrentMode.push_back("!main"); + + if (Json.contains("info") && Json["info"].is_object()) { + /* UCKeyboard global X/Y offset */ + if (Json["info"].contains("x") && Json["info"]["x"].is_number()) this->KbdX = Json["info"]["x"]; + if (Json["info"].contains("y") && Json["info"]["y"].is_number()) this->KbdY = Json["info"]["y"]; + }; + + if (Json.contains("layout") && Json["layout"].is_object()) { + /* Loop through each mode and parse a struct out of the JSON. */ + for (const auto &Mode : Json["layout"].items()) { + if (Mode.value().is_object() && Mode.value().contains("keys") && Mode.value()["keys"].is_object()) { + this->Kbd[Mode.key()] = { }; + + /* Add all the keys*/ + for (const auto &Key : Mode.value()["keys"].items()) { + /* Check that the positions are good. */ + if (Key.value().is_array() && Key.value().size() >= 4) { + bool Good = true; + + for (uint8_t Idx = 0; Idx < 4; Idx++) { + if (!Key.value()[Idx].is_number()) { + Good = false; + break; + }; + }; + + if (Good) { + this->Kbd[Mode.key()].Keys.emplace_back(Structs::ButtonPos({ this->KbdX + Key.value()[0].get(), this->KbdY + Key.value()[1].get(), Key.value()[2], Key.value()[3] }), Key.key()); + + /* Check for any special properties. */ + if (Key.value().size() >= 5 && Key.value()[4].is_object()) { + for (const auto &Property : Key.value()[4].items()) { + if (Property.key() == "key") { + if (Property.value().is_string()) { + this->Kbd[Mode.key()].Keys.back().Button = TextUtils::StrToKey(Property.value()); + + } else if (Property.value().is_array()) { + for (const auto &Button : Property.value()) { + if (Button.is_string()) { + this->Kbd[Mode.key()].Keys.back().Button |= TextUtils::StrToKey(Button); + }; + }; + }; + + } else if (Property.value().is_string()) { + if (Property.key() == "label") { + this->Kbd[Mode.key()].Keys.back().Label = Property.value(); + + } else { + Key::Property Prop = Key::Property::Invalid; + if (Property.key() == "action") Prop = Key::Property::Action; + else if (Property.key() == "mode") Prop = Key::Property::Mode; + else if (Property.key() == "value") Prop = Key::Property::Value; + + this->Kbd[Mode.key()].Keys.back().Properties[Prop] = Property.value(); + }; + + } else if (Property.value().is_boolean()) { + if (Property.key() == "active") this->Kbd[Mode.key()].Keys.back().Active = Property.value(); + }; + }; + }; + }; + }; + }; + + /* Check if this should return on key press. */ + if (Mode.value().contains("return") && Mode.value()["return"].is_boolean()) { + this->Kbd[Mode.key()].Ret = Mode.value()["return"]; + }; + }; + }; + }; + + this->Loaded = true; + + } else { + this->Loaded = false; + }; +}; + +uint8_t UCKeyboard::GetCharSize(void) const { + const char *Str = this->CurrentString.c_str() + Cursor; + do { + Str++; + } while ((*Str & 0xC0) == 0x80); + + return Str - this->CurrentString.c_str(); +} + +uint8_t UCKeyboard::GetPrevCharSize(void) const { + const char *Str = this->CurrentString.c_str() + Cursor; + do { + Str--; + } while ((*Str & 0xC0) == 0x80); + + return this->CurrentString.c_str() - Str; +} + +void UCKeyboard::Draw(uint32_t Held, uint32_t Repeat, touchPosition T) const { + Gui::clearTextBufs(); + + /* A sub menu or so? */ + if (!this->Loaded) { + Gui::Draw_Rect(48, 0, 320, 20, this->BarColor); + Gui::Draw_Rect(48, 20, 320, 1, this->OutlineColor); + Gui::DrawStringCentered(24, 2, 1.0f, this->TextColor, "Invalid keyboard layout", 310); + + } else { + Gui::Draw_Rect(0, 0, 320, 240, this->BgColor); + Gui::DrawStringCentered(24, 2, 1.0f, this->TextColor, this->CurrentString, 310); + + if (this->Kbd.contains(this->CurrentMode.back())) { + for (const auto &Key : this->Kbd.at(this->CurrentMode.back()).Keys) { + Gui::Draw_Rect(Key.Pos.x, Key.Pos.y, Key.Pos.w, Key.Pos.h, (Key.Active || Key.Pos.Touched(T) || Held & Key.Button) ? this->KeyColorPressed : this->KeyColor); + Gui::DrawStringCentered(Key.Pos.x + (Key.Pos.w / 2) - 160, Key.Pos.y + (Key.Pos.h / 10), 1.0f, this->TextColor, Key.Label); + }; + + } else { + Gui::Draw_Rect(48, 0, 320, 20, this->BarColor); + Gui::Draw_Rect(48, 20, 320, 1, this->OutlineColor); + Gui::DrawStringCentered(24, 2, 1.0f, this->TextColor, "Invalid keyboard layout", 310); + }; + }; + + Gui::updateTextBufs(false); +}; + + +void UCKeyboard::SwitchLayout() const { + // TODO? +}; + + +void UCKeyboard::Handler(uint32_t Held, uint32_t Repeat, touchPosition T) { + /* Handle Load. */ + if (!this->Loaded) + return; + + if (Repeat & KEY_TOUCH) { + /* Check if any key is being touched. */ + for (const auto &Key : this->Kbd[this->CurrentMode.back()].Keys) { + if (Key.Pos.Touched(T)) { + this->HandleKeyPress(Key); + break; + }; + }; + + } else if(Repeat) { + /* If not touching, then check all keys for button values. */ + for (const auto &Key : this->Kbd[this->CurrentMode.back()].Keys) { + if (Repeat & Key.Button) { + this->HandleKeyPress(Key); + }; + }; + }; +}; + +void UCKeyboard::HandleKeyPress(const Key &Key) { + /* Return to last non-returning layout. */ + while (this->Kbd.at(this->CurrentMode.back()).Ret) this->CurrentMode.pop_back(); + + /* If the key has any special properties, then apply them. */ + if (Key.Properties.size() > 0) { + for (const auto &[Prop, Value] : Key.Properties) { + switch (Prop) { + /* Special action, such as modifying other characters. */ + case Key::Property::Action: + if (Value == "backspace") { + if (this->Cursor > 0) { + this->Cursor -= GetPrevCharSize(); + this->CurrentString = this->CurrentString.substr(0, this->Cursor) + this->CurrentString.substr(this->Cursor + GetCharSize(), this->CurrentString.size()); + }; + } else if (Value == "delete") { + // TextEditor::Remove(); + + } else if (Value == "up") { + // TextEditor::CursorUp(); + + } else if (Value == "down") { + // TextEditor::CursorDown(); + + } else if (Value == "left") { + // TextEditor::CursorLeft(); + + } else if (Value == "right") { + // TextEditor::CursorRight(); + + } else if (Value == "dakuten" || Value == "handakuten") { + // bool Handakuten = Value == "handakuten"; + + // if (TextEditor::CursorPos > 0) { + // TextEditor::CursorLeft(); + // const std::string Char = UniversalEdit::UE->CurrentFile->GetCharacter(TextEditor::CurrentLine, TextEditor::CursorPos); + // const std::string Out = TextUtils::Dakutenify(Char, Handakuten); + + // UniversalEdit::UE->CurrentFile->EraseContent(TextEditor::CurrentLine, TextEditor::CursorPos, Char.size()); + // if (UniversalEdit::UE->CurrentFile->InsertContent(TextEditor::CurrentLine, TextEditor::CursorPos, Out)) { + // TextEditor::CursorRight(); + + // if (Out.size() > Char.size()) TextEditor::CursorRight(); + // }; + + // } else { + // if (UniversalEdit::UE->CurrentFile->InsertContent(TextEditor::CurrentLine, TextEditor::CursorPos, Handakuten ? "゜" : "゛")) { + // TextEditor::CursorPos += 3; + // }; + // }; + + } else if (Value == "newline") { + // TextEditor::InsertLine(); + + } else if (Value == "exit") { + IsDone = true; + + } else if (Value == "layout") { + this->SwitchLayout(); + return; // Return to not mess up. + + } else if (Value == "phrases") { + // UniversalEdit::UE->ActiveTab = UniversalEdit::Tabs::Phrases; + return; + }; + break; + + /* Changes mode, such as to Shift mode. */ + case Key::Property::Mode: + if (this->Kbd.contains(Value)) this->CurrentMode.push_back(Value); + else if (Value == "!return" && this->CurrentMode.size() > 1) this->CurrentMode.pop_back(); + break; + + /* Output a value that's not the label. */ + case Key::Property::Value: + if(this->CurrentString.size() + Value.size() < this->MaxSize) { + this->CurrentString += Value; + this->Cursor += Value.size(); + }; + break; + + case Key::Property::Invalid: + break; + }; + }; + + } else { + nocashMessage(Key.Label.c_str()); + /* Otherwise, just output the label */ + if(this->CurrentString.size() + Key.Label.size() < this->MaxSize) { + nocashMessage("adding"); + this->CurrentString += Key.Label; + this->Cursor += Key.Label.size(); + }; + }; +}; + +std::string UCKeyboard::GetString(uint MaxSize, const std::string &Hint) { + this->MaxSize = MaxSize == 0 ? 0xFFFFFFFF : 0; + + u32 Held = 0, Repeat = 0; + touchPosition T; + while(1) { + this->Draw(Held, Repeat, T); + this->Handler(Held, Repeat, T); + + do { + swiWaitForVBlank(); + scanKeys(); + Held = keysHeld(); + Repeat = keysDownRepeat(); + touchRead(&T); + SCALE_3DS(T.px); + SCALE_3DS(T.py); + } while (!Held); + + if (Held & KEY_START) { + break; + }; + }; + + return this->CurrentString; +}; diff --git a/source/textUtils.cpp b/source/textUtils.cpp new file mode 100644 index 0000000..a3769bf --- /dev/null +++ b/source/textUtils.cpp @@ -0,0 +1,114 @@ +/* + * This file is part of Universal-Core + * Copyright (C) 2021 Universal-Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Additional Terms 7.b and 7.c of GPLv3 apply to this file: + * * Requiring preservation of specified reasonable legal notices or + * author attributions in that material or in the Appropriate Legal + * Notices displayed by works containing it. + * * Prohibiting misrepresentation of the origin of that material, + * or requiring that modified versions of such material be marked in + * reasonable ways as different from the original version. + */ + +#include "TextUtils.hpp" + +#include + +/* Get first codepoint from a UTF-8 string. */ +char32_t TextUtils::GetCodepoint(const char *Str) { + /* Return 0 if nullptr or empty string. */ + if (!Str || !*Str) return 0; + + size_t Len = strlen(Str); + char32_t Codepoint = 0xFFFD; + + if (!(*Str & 0x80)) { + Codepoint = *Str; + + } else if ((*Str & 0xE0) == 0xC0 && Len >= 2) { + Codepoint = (*(Str++) & 0x1F) << 6; + Codepoint |= *(Str++) & 0x3F; + + } else if ((*Str & 0xF0) == 0xE0 && Len >= 3) { + Codepoint = (*(Str++) & 0x0F) << 12; + Codepoint |= (*(Str++) & 0x3F) << 6; + Codepoint |= *(Str++) & 0x3F; + + } else if ((*Str & 0xF8) == 0xF0 && Len >= 4) { + Codepoint = (*(Str++) & 0x07) << 18; + Codepoint |= (*(Str++) & 0x3F) << 12; + Codepoint |= (*(Str++) & 0x3F) << 6; + Codepoint |= *(Str++) & 0x3F; + }; + + return Codepoint; +}; + +/* Try to make the first given character have a dakuten. */ +std::string TextUtils::Dakutenify(std::string Str, bool Handakuten) { + char32_t Char = GetCodepoint(Str.c_str()); + if (Char >= u'ァ' && Char <= u'ヶ') Char -= 0x60; // Katakana, convert to Hiragana + + int Change = 0; + + if (Handakuten) { + for (const char16_t Item : Handakutenable) { + if (Char == Item) { + Change = 2; + break; + }; + }; + + } else { + if (Char == u'う') { + Change = 0x4E; + + } else { + for (const char16_t Item : Dakutenable) { + if (Char == Item) { + Change = 1; + break; + }; + }; + }; + }; + + if (Change) { + if ((Str[2] & 0x3F) + Change < 0x3F) { + Str[2] += Change; + + } else { + Str[2] = 0x80 | ((Str[2] + Change) & 0x3F); + Str[1]++; + }; + + } else { + int I = 1; + while((Str[I] & 0xC0) == 0x80) I++; + Str.insert(I, Handakuten ? "゜" : "゛"); + }; + + return Str; +}; + +uint32_t TextUtils::StrToKey(const std::string &Str) { + for (const auto &Key : KeyNames) { + if (strcmp(Str.c_str(), Key.first) == 0) return Key.second; + }; + + return 0; +};