├── LICENSE ├── README.md ├── component.mk ├── examples ├── nouseTextbox │ └── nouseTextbox.ino └── simple │ └── simple.ino ├── library.json ├── library.properties └── src ├── M5ButtonDrawer.cpp ├── M5ButtonDrawer.h ├── M5FACESEncoder.cpp ├── M5FACESEncoder.h ├── M5JoyStick.cpp ├── M5JoyStick.h ├── M5OnScreenKeyboard.cpp ├── M5OnScreenKeyboard.h ├── M5PLUSEncoder.cpp └── M5PLUSEncoder.h /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 lovyan03 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | M5Stack OnScreenKeyboard library. (for ASCII code) 2 | === 3 | 4 | Add Keyboard for your M5Stack project. 5 | 6 | あなたのM5Stackプロジェクトにキーボードを! 7 | 8 | ## Description 9 | 10 | OnScreenKeyboard which can be operated with 3 button. 11 | 12 | M5Stack本体の3ボタンで操作できるオンスクリーンキーボード。 13 | 簡単な文字入力にお使いいただけます。 14 | 15 | Support FACES Keyboard and GameBoy and Encoder unit. 16 | Support PLUS Encoder unit. 17 | Support JoyStick unit. 18 | Support CardKB unit. 19 | Support morse code input. 20 | 21 | M5Stackの各種ユニットでの操作にも対応。 22 | A+C 2ボタン同時押しでモールス入力モードに切替可能。 23 | 24 | ![image](https://user-images.githubusercontent.com/42724151/52710266-45ab4280-2fd2-11e9-9897-f0b001cb0edf.png) 25 | 26 | Common operation: 27 | `BtnA click` : キーボードパネル切替 Keyboard panel switches. 28 | `BtnA and B hold and BtnC click` : 全消去 Clear all strings. 29 | `BtnA hold and BtnC click` : モールス入力/フォーカス入力 モード切替 Switch between Morse code mode and focus selection mode. 30 | 31 | in focus mode: 32 | `BtnA hold` : 左(上)に移動 The focus moves to the left (or up). 33 | `BtnB click(or hold)` : 選択決定、行/列選択切替 The focused target is entered. Then switch the row/column selection. 34 | `BtnC click(or hold)` : 右(下)に移動 The focus moves to the right (or down). 35 | `BtnA hold and BtnB click` : 入力完了(または列選択に戻る) Finish keyboard input. (or back to column selection.) 36 | 37 | ![image](https://user-images.githubusercontent.com/42724151/51086670-c0dbc780-178c-11e9-8c97-bc415042c09c.png) 38 | in morse code mode: 39 | `BtnB click` : モールス短音入力 Input a short pulse. 40 | `BtnC click` : モールス長音入力 Input a long pulse. 41 | `Release BtnB and C for 700 msec` : 入力確定 Fix input. 42 | `BtnA hold and BtnB click` : 入力完了 Finish keyboard input. 43 | 44 | 45 | Morse code is GBoard morse compliant. 46 | [GBoard morse code list](https://gist.github.com/natevw/0fce6b56c606632f8ee780b5d493f94e) 47 | 48 | ## Usage 49 | 50 | ``` 51 | #include 52 | 53 | M5OnScreenKeyboard m5osk; 54 | 55 | m5osk.useFACES = true; // FACES unit support. 56 | m5osk.useCardKB = true; // CARDKB unit support. 57 | m5osk.useJoyStick = true; // JoyStick unit support. 58 | m5osk.usePLUSEncoder = true; // PLUS Encoder unit support. 59 | m5osk.useFACESEncoder = true;// FACES Encoder unit support. 60 | // m5osk.swapBtnBC = true; // BtnB/BtnC KeyAssign swap. 61 | 62 | m5osk.setup(); 63 | //m5osk.setup("Hello World."); // You can also set default text 64 | 65 | while (m5osk.loop()) { 66 | // You can write your code here. 67 | delay(1); 68 | } 69 | String text = m5osk.getString(); 70 | m5osk.close(); 71 | ``` 72 | 73 | ## Licence 74 | 75 | [MIT](https://github.com/lovyan03/M5Stack_OnScreenKeyboard/blob/master/LICENSE) 76 | 77 | ## Author 78 | 79 | [lovyan03](https://twitter.com/lovyan03) 80 | -------------------------------------------------------------------------------- /component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # Main Makefile. This is basically the same as a component makefile. 3 | # 4 | # (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) 5 | 6 | COMPONENT_SRCDIRS := src 7 | COMPONENT_ADD_INCLUDEDIRS := src 8 | -------------------------------------------------------------------------------- /examples/nouseTextbox/nouseTextbox.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | 5 | M5OnScreenKeyboard m5osk; 6 | 7 | void setup() { 8 | M5.begin(); 9 | Wire.begin(); 10 | 11 | // textbox disabled. 12 | m5osk.useTextbox = false; 13 | 14 | m5osk.useFACES = true; // FACES unit support. 15 | m5osk.useCardKB = true; // CARDKB unit support. 16 | m5osk.useJoyStick = true; // JoyStick unit support. 17 | m5osk.usePLUSEncoder = true; // PLUS Encoder unit support. 18 | m5osk.useFACESEncoder = true;// FACES Encoder unit support. 19 | 20 | } 21 | void loop() { 22 | String text; 23 | 24 | m5osk.setup(); 25 | 26 | while (m5osk.loop()) { 27 | 28 | M5.Lcd.setCursor(0,0); 29 | M5.Lcd.setTextColor(0xfff0, 0); 30 | M5.Lcd.setTextFont(4); 31 | M5.Lcd.setTextSize(1); 32 | M5.Lcd.printf("msec:%09d", millis()); 33 | 34 | // get pressed character code. 35 | char key = m5osk.getKeyCode(); 36 | 37 | switch (key) { 38 | case 0: // no input. 39 | case 0x7f: // DEL. 40 | case 0x1d: // LEFT. 41 | case 0x1c: // RIGHT. 42 | break; 43 | 44 | case 0x08: // backspace 45 | text = text.substring(0,text.length() - 1); 46 | M5.Lcd.clear(0); 47 | M5.Lcd.setCursor(0, 40); 48 | M5.Lcd.print(text); 49 | m5osk.draw(); // force redraw. 50 | break; 51 | 52 | default: 53 | text += key; 54 | M5.Lcd.setCursor(0, 40); 55 | M5.Lcd.print(text); 56 | m5osk.draw(); // force redraw. 57 | break; 58 | } 59 | } 60 | 61 | m5osk.close(); 62 | 63 | delay(2000); 64 | M5.Lcd.clear(0); 65 | } -------------------------------------------------------------------------------- /examples/simple/simple.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | //#include 4 | #include 5 | 6 | M5OnScreenKeyboard m5osk; 7 | 8 | void setup() { 9 | M5.begin(); 10 | Wire.begin(); 11 | 12 | m5osk.useFACES = true; // FACES unit support. 13 | m5osk.useCardKB = true; // CardKB unit support. 14 | m5osk.useJoyStick = true; // JoyStick unit support. 15 | m5osk.usePLUSEncoder = true; // PLUS Encoder unit support. 16 | m5osk.useFACESEncoder = true;// FACES Encoder unit support. 17 | 18 | /* 19 | // style change example. 20 | m5osk.fontColor[0] = 0x0000; 21 | m5osk.fontColor[1] = 0xFFFF; 22 | m5osk.backColor[0] = 0xF79E; 23 | m5osk.backColor[1] = 0x8410; 24 | m5osk.frameColor[0] = 0x4208; 25 | m5osk.frameColor[1] = 0xFFFF; 26 | m5osk.textboxFontColor = 0xFFFF; 27 | m5osk.textboxBackColor = 0x8410; 28 | m5osk.keyHeight = 20; 29 | m5osk.setTextFont(2); 30 | 31 | //m5osk.keyHeight = 24; 32 | //m5osk.setFreeFont(&FreeMono9pt7b); 33 | //m5osk.setFreeFont(&FreeSans9pt7b); 34 | 35 | M5ButtonDrawer::fontColor[0] = 0xFFFF; 36 | M5ButtonDrawer::fontColor[1] = 0x0000; 37 | M5ButtonDrawer::backColor[0] = 0x0010; 38 | M5ButtonDrawer::backColor[1] = 0xF79E; 39 | M5ButtonDrawer::width = 100; 40 | M5ButtonDrawer::height = 20; 41 | M5ButtonDrawer::setTextFont(2); 42 | //*/ 43 | 44 | /* // response speed change example. 45 | m5osk.msecHold = 200; 46 | m5osk.msecRepeat= 100; 47 | m5osk.msecMorseInput = 500; 48 | //*/ 49 | 50 | /* // JoyStick unit rotation setting 51 | // need #include 52 | JoyStick.setRotate(0); // 0~3 rotation setting. 53 | //*/ 54 | 55 | /* // use non-latin-chars (tentative) 56 | m5osk.useOver0x80Chars = true; 57 | //*/ 58 | 59 | m5osk.setup(); 60 | //m5osk.setup("Hello World."); // You can also set default text 61 | } 62 | void loop() { 63 | 64 | while (m5osk.loop()) { 65 | // You can write your code here. 66 | delay(1); 67 | } 68 | 69 | // Get input string. 70 | String text = m5osk.getString(); 71 | m5osk.close(); 72 | 73 | M5.Lcd.setCursor(1,100); 74 | M5.Lcd.print(text); 75 | 76 | delay(2000); 77 | M5.Lcd.clear(0); 78 | 79 | m5osk.setup(text); 80 | } -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "M5Stack_OnScreenKeyboard", 3 | "description": "OnScreenKeyboard for M5Stack", 4 | "keywords": "onscreenkeyboard, m5stack, esp32", 5 | "authors": { 6 | "name": "lovyan03", 7 | "url": "https://github.com/lovyan03", 8 | "maintainer": true 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/lovyan03/M5Stack_OnScreenKeyboard" 13 | }, 14 | "dependencies": { 15 | "name": "M5Stack" 16 | }, 17 | "version": "0.3.4", 18 | "framework": "arduino", 19 | "platforms": "espressif32", 20 | "build": { 21 | "libArchive": false 22 | } 23 | } -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=M5Stack_OnScreenKeyboard 2 | version=0.3.4 3 | author=lovyan03 4 | maintainer=Lovyan <42724151+lovyan03@users.noreply.github.com> 5 | sentence=OnScreenKeyboard for M5Stack 6 | paragraph=OnScreenKeyboard which can be operated with 3 button 7 | category=Uncategorized 8 | url=https://github.com/lovyan03/M5Stack_OnScreenKeyboard 9 | architectures=esp32 10 | includes=M5OnScreenKeyboard.h 11 | depends=M5Stack 12 | -------------------------------------------------------------------------------- /src/M5ButtonDrawer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | uint16_t M5ButtonDrawer::frameColor[2] = { 0xA514,0xffff }; 4 | uint16_t M5ButtonDrawer::backColor[2] = { 0x0000,0x0000 }; 5 | uint16_t M5ButtonDrawer::fontColor[2] = { 0xffff,0xffff }; 6 | int16_t M5ButtonDrawer::width = 72; 7 | int16_t M5ButtonDrawer::height = 14; 8 | int16_t M5ButtonDrawer::font = 1; 9 | const GFXfont* M5ButtonDrawer::gfxFont = NULL; 10 | 11 | void M5ButtonDrawer::setText(const String& btnA, const String& btnB, const String& btnC) { 12 | setText(0, btnA); 13 | setText(1, btnB); 14 | setText(2, btnC); 15 | } 16 | void M5ButtonDrawer::setText(uint8_t idx, const String& str) { 17 | if (idx < 3 && _titles[idx] != str) { 18 | _titles[idx] = str; 19 | _mod[idx] = true; 20 | } 21 | } 22 | 23 | void M5ButtonDrawer::draw(bool force) 24 | { 25 | if (_mod[0] || force || M5.BtnA.wasPressed() || M5.BtnA.wasReleased()) draw(0, M5.BtnA.isPressed()); 26 | if (_mod[1] || force || M5.BtnB.wasPressed() || M5.BtnB.wasReleased()) draw(1, M5.BtnB.isPressed()); 27 | if (_mod[2] || force || M5.BtnC.wasPressed() || M5.BtnC.wasReleased()) draw(2, M5.BtnC.isPressed()); 28 | } 29 | 30 | void M5ButtonDrawer::draw(uint8_t idx, bool pressed) 31 | { 32 | _mod[idx] = false; 33 | drawButton((idx - 1) * (96 + (84 < width ? (width - 84) / 2 : 0)) + 160, pressed, _titles[idx]); 34 | } 35 | 36 | void M5ButtonDrawer::drawButton(int x, bool pressed, const String& title) const 37 | { 38 | M5.Lcd.setTextSize(1); 39 | if (gfxFont) { 40 | M5.Lcd.setFreeFont(gfxFont); 41 | } else { 42 | M5.Lcd.setTextFont(0); 43 | M5.Lcd.setTextFont(font); 44 | } 45 | M5.Lcd.setTextColor(fontColor[pressed]); 46 | int16_t fh = M5.Lcd.fontHeight(font); 47 | if (gfxFont && 12 < fh) fh = fh * 9 / 10; 48 | 49 | int rx = x - width / 2; 50 | int ry = M5.Lcd.height() - height; 51 | int rw = width; 52 | int rh = height; 53 | int fy = ry + (rh - fh)/2; 54 | uint16_t color = frameColor[pressed]; 55 | M5.Lcd.drawRect(rx+1,ry ,rw-2,rh ,color); 56 | M5.Lcd.drawRect(rx ,ry+1,rw ,rh-2 ,color); 57 | 58 | M5.Lcd.fillRect(rx+2, ry+2, rw-4, rh-4, backColor[pressed]); 59 | M5.Lcd.drawCentreString(title, x, fy, font); 60 | } 61 | 62 | -------------------------------------------------------------------------------- /src/M5ButtonDrawer.h: -------------------------------------------------------------------------------- 1 | #ifndef _M5BUTTONDRAWER_H_ 2 | #define _M5BUTTONDRAWER_H_ 3 | 4 | #include 5 | 6 | class M5ButtonDrawer { 7 | public: 8 | static uint16_t frameColor[2]; 9 | static uint16_t backColor[2]; 10 | static uint16_t fontColor[2]; 11 | static int16_t width; 12 | static int16_t height; 13 | static void setTextFont(int f) { gfxFont = NULL; font = f; } 14 | static void setFreeFont(const GFXfont* f) { gfxFont = f; font = 1; } 15 | M5ButtonDrawer(){}; 16 | M5ButtonDrawer(const String& btnA, const String& btnB, const String& btnC) 17 | : _titles{btnA,btnB,btnC} 18 | { 19 | }; 20 | void setText(const String& btnA, const String& btnB, const String& btnC); 21 | void setText(uint8_t idx, const String& str); 22 | void draw(bool force = false); 23 | void draw(uint8_t idx, bool pressed); 24 | 25 | private: 26 | static int16_t font; 27 | static const GFXfont* gfxFont; 28 | String _titles[3]; 29 | bool _mod[3]; 30 | void drawButton(int x, bool pressed, const String& title) const; 31 | }; 32 | 33 | #endif 34 | 35 | -------------------------------------------------------------------------------- /src/M5FACESEncoder.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | M5FACESEncoder FACESEncoder; 4 | 5 | bool M5FACESEncoder::update() 6 | { 7 | if (!Wire.requestFrom(_addr, 2)) return false; 8 | _time = millis(); 9 | _oldUpDown = _upDown; 10 | _oldPress = _press; 11 | bool press = false; 12 | while (Wire.available()){ 13 | _raw = Wire.read(); 14 | _rawsum += _raw; 15 | press = (Wire.read() == 0); 16 | } 17 | _upDown = _rawsum; 18 | if (_upDown != 0) _rawsum = 0; 19 | 20 | if (press != (0 != _oldPress)) _lastChange = _time; 21 | if (press) { 22 | if (!_oldPress) { 23 | _press = 1; 24 | } else 25 | if (1 == _oldPress && (_time - _lastChange >= msecHold)) { 26 | _press = 2; 27 | } 28 | } else { 29 | _press = 0; 30 | } 31 | 32 | return true; 33 | } 34 | 35 | void M5FACESEncoder::led(int led_index, int r, int g, int b) 36 | { 37 | Wire.beginTransmission(_addr); 38 | Wire.write(led_index); 39 | Wire.write(r); 40 | Wire.write(g); 41 | Wire.write(b); 42 | Wire.endTransmission(); 43 | } 44 | -------------------------------------------------------------------------------- /src/M5FACESEncoder.h: -------------------------------------------------------------------------------- 1 | #ifndef _M5FACESENCODER_H_ 2 | #define _M5FACESENCODER_H_ 3 | 4 | #include 5 | 6 | class M5FACESEncoder 7 | { 8 | public: 9 | uint16_t msecHold = 300; 10 | 11 | void setAddr(int8_t addr) { _addr = addr; } 12 | 13 | bool update(); 14 | 15 | int8_t rawValue() const { return _raw; } 16 | bool wasUp() const { return _upDown > 0; } 17 | bool wasDown() const { return _upDown < 0; } 18 | 19 | bool wasClicked() const { return _oldPress == 1 && _press == 0; } 20 | bool wasHold() const { return _oldPress == 1 && _press == 2; } 21 | bool isHolding() const { return _oldPress == 2 && _press == 2; } 22 | 23 | bool isPressed() const { return _press; } 24 | bool isReleased() const { return !_press; } 25 | bool wasPressed() const { return !_oldPress && _press; } 26 | bool wasReleased() const { return _oldPress && !_press; } 27 | bool pressedFor(uint32_t ms) const { return (_press && _time - _lastChange >= ms); } 28 | bool releasedFor(uint32_t ms) const { return (!_press && _time - _lastChange >= ms); } 29 | 30 | void led(int led_index, int r, int g, int b); 31 | 32 | private: 33 | int8_t _ledpos = 0; 34 | int8_t _addr = 0x5E; 35 | int8_t _raw = 0; 36 | int8_t _rawsum = 0; 37 | int8_t _upDown = 0; 38 | int8_t _oldUpDown = 0; 39 | uint8_t _press = 0; // 0:release 1:click 2:holding 40 | uint8_t _oldPress = 0; 41 | uint32_t _time = 0; 42 | uint32_t _lastChange = 0; 43 | }; 44 | 45 | #endif 46 | 47 | extern M5FACESEncoder FACESEncoder; 48 | 49 | -------------------------------------------------------------------------------- /src/M5JoyStick.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | M5JoyStick JoyStick; 4 | 5 | static int ValueConv(int v) { return (v < 48) ? -2 : (v < 64) ? -1 : (v > 207) ? 2 : (v > 191) ? 1 : 0; } 6 | 7 | bool M5JoyStick::update() 8 | { 9 | if (!Wire.requestFrom(_addr,3)) return false; 10 | _time = millis(); 11 | _oldPress = _press; 12 | _oldUpDown = _upDown; 13 | _oldLeftRight = _leftRight; 14 | bool press = false; 15 | while (Wire.available()) { 16 | _x = Wire.read(); 17 | _y = Wire.read(); 18 | press = Wire.read(); 19 | switch (_rotate) { 20 | default: 21 | _upDown = ValueConv(_y); 22 | _leftRight = ValueConv(_x); 23 | break; 24 | case 1: 25 | _upDown = -ValueConv(_x); 26 | _leftRight = ValueConv(_y); 27 | break; 28 | case 2: 29 | _upDown = -ValueConv(_y); 30 | _leftRight = -ValueConv(_x); 31 | break; 32 | case 3: 33 | _upDown = ValueConv(_x); 34 | _leftRight = -ValueConv(_y); 35 | break; 36 | } 37 | if (_oldUpDown == (_upDown * 2)) _upDown = _oldUpDown; 38 | if (_oldLeftRight == (_leftRight * 2)) _leftRight = _oldLeftRight; 39 | } 40 | if (press != _oldPress) _lastPressedChange = _time; 41 | if (press) { 42 | if (!_oldPress) { 43 | _press = 1; 44 | } else 45 | if (1 == _oldPress && (_time - _lastPressedChange >= msecHold)) { 46 | _press = 2; 47 | } 48 | } else { 49 | _press = 0; 50 | } 51 | 52 | if (_oldUpDown != _upDown || _oldLeftRight != _leftRight) _lastDirectionChange = _time; 53 | 54 | return true; 55 | } 56 | -------------------------------------------------------------------------------- /src/M5JoyStick.h: -------------------------------------------------------------------------------- 1 | #ifndef _M5JOYSTICK_H_ 2 | #define _M5JOYSTICK_H_ 3 | 4 | #include 5 | 6 | class M5JoyStick 7 | { 8 | public: 9 | uint16_t msecHold = 300; 10 | 11 | void setAddr(int8_t addr) { _addr = addr; } 12 | void setRotate(int rotate) { _rotate = rotate; } 13 | 14 | bool update(); 15 | 16 | uint8_t rawX() const { return _x; } 17 | uint8_t rawY() const { return _y; } 18 | bool isNeutral() const { return -2 < _upDown && _upDown < 2 && -2 < _leftRight && _leftRight < 2; } 19 | bool isUp() const { return -2 == _upDown; } 20 | bool isDown() const { return 2 == _upDown; } 21 | bool isLeft() const { return 2 == _leftRight; } 22 | bool isRight() const { return -2 == _leftRight; } 23 | bool wasUp() const { return -2 == _upDown && _oldUpDown != _upDown; } 24 | bool wasDown() const { return 2 == _upDown && _oldUpDown != _upDown; } 25 | bool wasLeft() const { return 2 == _leftRight && _oldLeftRight != _leftRight; } 26 | bool wasRight() const { return -2 == _leftRight && _oldLeftRight != _leftRight; } 27 | 28 | bool wasClicked() const { return _oldPress == 1 && _press == 0; } 29 | bool wasHold() const { return _oldPress == 1 && _press == 2; } 30 | bool isHolding() const { return _oldPress == 2 && _press == 2; } 31 | 32 | bool isPressed() const { return _press; } 33 | bool isReleased() const { return !_press; } 34 | bool wasPressed() const { return !_oldPress && _press; } 35 | bool wasReleased() const { return _oldPress && !_press; } 36 | bool pressedFor(uint32_t ms) const { return (_press && _time - _lastPressedChange >= ms); } 37 | bool releasedFor(uint32_t ms) const { return (!_press && _time - _lastPressedChange >= ms); } 38 | bool directionChangedFor(uint32_t ms) const { return (_time - _lastDirectionChange >= ms); } 39 | 40 | private: 41 | int8_t _addr = 0x52; 42 | uint8_t _x = 0; 43 | uint8_t _y = 0; 44 | uint8_t _rotate = 0; 45 | int8_t _upDown = 0; 46 | int8_t _leftRight = 0; 47 | int8_t _oldUpDown = 0; 48 | int8_t _oldLeftRight = 0; 49 | uint8_t _press = 0; // 0:release 1:click 2:holding 50 | uint8_t _oldPress = 0; 51 | uint32_t _time = 0; 52 | uint32_t _lastPressedChange = 0; 53 | uint32_t _lastDirectionChange = 0; 54 | }; 55 | 56 | #endif 57 | 58 | extern M5JoyStick JoyStick; 59 | 60 | -------------------------------------------------------------------------------- /src/M5OnScreenKeyboard.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | bool M5OnScreenKeyboard::useTextbox = true; 8 | bool M5OnScreenKeyboard::useOver0x80Chars = false; 9 | bool M5OnScreenKeyboard::useFACES = false; 10 | bool M5OnScreenKeyboard::useCardKB = false; 11 | bool M5OnScreenKeyboard::useJoyStick = false; 12 | bool M5OnScreenKeyboard::usePLUSEncoder = false; 13 | bool M5OnScreenKeyboard::useFACESEncoder = false; 14 | bool M5OnScreenKeyboard::swapBtnBC = false; 15 | 16 | uint16_t M5OnScreenKeyboard::fontColor[2] = {0xFFFF, 0xFFFF}; 17 | uint16_t M5OnScreenKeyboard::backColor[2] = {0x630C, 0x421F}; 18 | uint16_t M5OnScreenKeyboard::frameColor[2] = {0x0208, 0xFFFF}; 19 | uint16_t M5OnScreenKeyboard::textboxFontColor = 0x0000; 20 | uint16_t M5OnScreenKeyboard::textboxBackColor = 0xFFFF; 21 | uint8_t M5OnScreenKeyboard::keyHeight = 14; 22 | int16_t M5OnScreenKeyboard::font = 1; 23 | const GFXfont* M5OnScreenKeyboard::gfxFont = NULL; 24 | 25 | uint16_t M5OnScreenKeyboard::msecHold = 300; 26 | uint16_t M5OnScreenKeyboard::msecRepeat= 150; 27 | uint16_t M5OnScreenKeyboard::msecMorseInput = 700; 28 | uint8_t M5OnScreenKeyboard::maxlength = 52; 29 | 30 | 31 | enum 32 | { TABLECOUNT = 4 33 | , ROWCOUNT = 4 34 | , COLUMNCOUNT =11 35 | , KEYWIDTH = 29 36 | }; 37 | 38 | static const char BS = 0x08; 39 | static const char DEL = 0x7f; 40 | static const char LEFT = 0x11; 41 | static const char RIGH = 0x13; 42 | 43 | static const PROGMEM char _chartbl[TABLECOUNT][ROWCOUNT][COLUMNCOUNT] 44 | = {{{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', BS } 45 | , {'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', DEL} 46 | , {'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', '/',LEFT} 47 | , {'z', 'x', 'c', 'v', 'b', 'n', 'm', ' ', '.', '@',RIGH} 48 | } 49 | , {{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', BS } 50 | , {'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', DEL} 51 | , {'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', '/',LEFT} 52 | , {'Z', 'X', 'C', 'V', 'B', 'N', 'M', ' ', '.', '@',RIGH} 53 | } 54 | , {{'!', '"', '#', '$', '%', '&','\'', '`', '^', '~', BS } 55 | , {'\t','<', '>', '[', ']', '{', '}', '(', ')', '\\', DEL} 56 | , {'\r','|', ';', ':', '_', '=', '+', '-', '*', '/',LEFT} 57 | , {'\n','.', '.', '.', '.', '?', ',', ' ', '.', '@',RIGH} 58 | } 59 | 60 | , {{0x80, 0x84, 0x88, 0x8c, 0x90, 0x94, 0x98, 0x9c, 0xa0, 0xa4, BS } 61 | , {0x81, 0x85, 0x89, 0x8d, 0x91, 0x95, 0x99, 0x9d, 0xa1, 0xa5, DEL} 62 | , {0x82, 0x86, 0x8a, 0x8e, 0x92, 0x96, 0x9a, 0x9e, 0xa2, 0xa6, LEFT} 63 | , {0x83, 0x87, 0x8b, 0x8f, 0x93, 0x97, 0x9b, 0x9f, 0xa3, 0xa7, RIGH} 64 | }}; 65 | 66 | static const PROGMEM uint8_t _morsetbl[TABLECOUNT][ROWCOUNT][COLUMNCOUNT] 67 | = {{{0x30, 0x38, 0x3c, 0x3e, 0x3f, 0x2f, 0x27, 0x23, 0x21, 0x20, 0x10 } 68 | , {0x12, 0x0c, 0x03, 0x0d, 0x02, 0x14, 0x0e, 0x07, 0x08, 0x19, 0} 69 | , {0x06, 0x0f, 0x0b, 0x1d, 0x09, 0x1f, 0x18, 0x0a, 0x1b, 0x2d, 0} 70 | , {0x13, 0x16, 0x15, 0x1e, 0x17, 0x05, 0x04, 0x1c, 0x6a, 0x65, 0} 71 | } 72 | , {{0x54, 0x6d, 0x25, 0x7b, 0x22, 0x37, 0x61, 0x5a, 0x7f, 0x63, 0x10} 73 | , {0 , 0x28, 0x50, 0x33, 0x66, 0x32, 0x64, 0x29, 0x52, 0x2a, 0} 74 | , {0x1a, 0x4a, 0x55, 0x47, 0x72, 0x2e, 0x35, 0x5e, 0x3d, 0x2d, 0} 75 | , {0 , 0 , 0 , 0 , 0 , 0x73, 0x4c, 0x1c, 0x6a, 0x65, 0} 76 | } 77 | 78 | , {{0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0x10} 79 | , {0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0} 80 | , {0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0} 81 | , {0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0} 82 | }}; 83 | 84 | static const PROGMEM uint8_t _morsepanel[TABLECOUNT] 85 | = {0,0,1,2}; 86 | 87 | static uint8_t calcMorse(uint8_t m) 88 | { 89 | uint8_t res = -2; 90 | bool startbit = false; 91 | bool flg; 92 | for (int i = 0; i < 8; ++i) { 93 | flg = (m & (0x80 >> i)); 94 | if (!startbit) startbit = flg; 95 | else res += flg ? 3:5; 96 | } 97 | return res; 98 | } 99 | 100 | static void drawCodeSymbol(uint8_t code, int x, int y, uint16_t color) 101 | { 102 | switch (code) { 103 | case '\t': 104 | M5.Lcd.drawFastHLine(x , y + 3, 5, color); 105 | M5.Lcd.drawFastVLine(x + 3, y + 2, 3, color); 106 | break; 107 | case '\n': 108 | M5.Lcd.drawFastVLine(x + 2, y + 1, 5, color); 109 | M5.Lcd.drawFastHLine(x + 1, y + 4, 3, color); 110 | break; 111 | case '\r': 112 | M5.Lcd.drawFastHLine(x , y + 3, 5, color); 113 | M5.Lcd.drawFastVLine(x + 1, y + 2, 3, color); 114 | break; 115 | default: 116 | M5.Lcd.drawRect(x + 1, y + 2, 4, 4, color); 117 | break; 118 | } 119 | M5.Lcd.setCursor(x + 6, y); 120 | } 121 | 122 | void M5OnScreenKeyboard::setString(const String& value) { 123 | _string = value; 124 | _cursorPos = _string.length(); 125 | } 126 | 127 | void M5OnScreenKeyboard::clearString() { 128 | _string = ""; 129 | _cursorPos = 0; 130 | } 131 | 132 | void M5OnScreenKeyboard::setup(const String& value) { 133 | #ifdef ARDUINO_ODROID_ESP32 134 | _btnHeight = 0; 135 | #else 136 | _btnHeight = M5ButtonDrawer::height; 137 | _btnDrawer.setText("","",""); 138 | _btnDrawer.draw(true); 139 | #endif 140 | setString(value); 141 | _tbl = 0; 142 | _col = 0; 143 | _row = 0; 144 | _state = APPEAR; 145 | _msecLast = 0; 146 | _msec = millis(); 147 | } 148 | 149 | bool M5OnScreenKeyboard::loop() { 150 | applyFont(); 151 | _keyCode = 0; 152 | M5.Lcd.setTextSize(1); 153 | if (_state == APPEAR && !appear()) return true; 154 | 155 | _msec = millis(); 156 | M5.update(); 157 | 158 | eState oldState = _state; 159 | int8_t oldTbl = _tbl; 160 | int8_t oldCol = _col; 161 | int8_t oldRow = _row; 162 | int oldRepeat = _repeat; 163 | bool canRepeat = _repeat == 0 || (_msec - _msecLast) >= (1 < _repeat ? msecRepeat : msecHold); 164 | 165 | bool press = false; 166 | 167 | #ifdef ARDUINO_ODROID_ESP32 168 | if (M5.BtnStart .wasPressed()) { return false; } 169 | if (M5.BtnSelect.wasPressed()) { press = true; switchTable(); } 170 | if (M5.BtnA .isPressed()) { press = true; if (canRepeat) { ++_repeat; pressKey(); } } 171 | if (M5.BtnB .isPressed()) { press = true; if (canRepeat) { ++_repeat; pressKey(BS); } } 172 | if (M5.JOY_X.isAxisPressed() != DPAD_V_NONE) { 173 | press = true; 174 | if (canRepeat) { 175 | ++_repeat; 176 | _col += (M5.JOY_X.isAxisPressed() == DPAD_V_HALF) ? 1 : -1; 177 | } 178 | } 179 | if (M5.JOY_Y.isAxisPressed() != DPAD_V_NONE) { 180 | press = true; 181 | if (canRepeat) { 182 | ++_repeat; 183 | _row += (M5.JOY_Y.isAxisPressed() == DPAD_V_HALF) ? 1 : -1; 184 | } 185 | } 186 | #else 187 | Button& btnB(swapBtnBC ? M5.BtnC : M5.BtnB); 188 | Button& btnC(swapBtnBC ? M5.BtnB : M5.BtnC); 189 | 190 | if (M5.BtnA.isPressed() || _fn) 191 | { // BtnA Pressing Fn _state 192 | if (_fn < 3 && btnC.isPressed() && btnB.isPressed()) 193 | { // 3button simultaneously: clear string. 194 | _fn = 3; 195 | clearString(); 196 | drawTextbox(); 197 | } else if (_fn < 3 && btnB.isReleased() && btnC.wasReleased()) 198 | { // A + B simultaneously: switchs morse mode. 199 | _fn = 3; 200 | clearMorse(); 201 | _state = (_state == MORSE) 202 | ? LEFTRIGHT 203 | : MORSE; 204 | } else if (_fn < 3 && btnC.isReleased() && btnB.wasReleased()) { 205 | _fn = 3; 206 | if (_state == LEFTRIGHT || _state == MORSE) 207 | { // A + C simultaneously: finish input. 208 | while (M5.BtnA.isPressed()) M5.update(); 209 | return false; 210 | } 211 | _state = LEFTRIGHT; 212 | } else if (btnC.isReleased() && btnB.isReleased()) { 213 | if (M5.BtnA.isReleased()) { // AllBtnReleased clear fn mode 214 | _fn = 0; 215 | } else if (_fn == 3) { 216 | _fn = 2; 217 | } else if (_fn < 2 && M5.BtnA.pressedFor(msecHold)) { 218 | _fn = 1; 219 | press = true; 220 | if (canRepeat) { 221 | switch (_state) { 222 | case LEFTRIGHT: if (++_repeat < COLUMNCOUNT) --_col; break; 223 | case UPDOWN: if (++_repeat < ROWCOUNT) --_row; break; 224 | default: break; 225 | } 226 | } 227 | } 228 | } else if (!_fn) { 229 | // simultaneously: fn mode. 230 | _fn = (btnC.isPressed() || btnB.isPressed()) ? 1 : 0; 231 | } 232 | } else { 233 | if (M5.BtnA.wasReleased()) { switchTable(); } 234 | bool bC = btnC.isPressed(); 235 | bool bB = btnB.isPressed(); 236 | press |= bC || bB; 237 | switch (_state) { 238 | case LEFTRIGHT: // left right moving. 239 | if (bC && canRepeat) { if (++_repeat < COLUMNCOUNT) ++_col; } 240 | if (btnB.wasReleased()) { _state = UPDOWN; _repeat = 0; } 241 | break; 242 | case UPDOWN: // up down moving. 243 | if (bC && canRepeat) { if (++_repeat < ROWCOUNT) ++_row; } 244 | if (bB && canRepeat) { ++_repeat; pressKey(); } 245 | if (btnB.wasReleased() && 0 < _repeat) { _state = LEFTRIGHT; } 246 | break; 247 | case MORSE: // morse input mode. 248 | if (M5.BtnB.wasPressed()) { pressMorse(false); } 249 | if (M5.BtnC.wasPressed()) { pressMorse(true); } 250 | if (btnB.releasedFor(msecMorseInput) 251 | && btnC.releasedFor(msecMorseInput) 252 | && _morseInputBuf) { ++_repeat; inputMorse(); } 253 | break; 254 | default: break; 255 | } 256 | } 257 | if (useFACES && Wire.requestFrom(0x08, 1)) { 258 | while (Wire.available()){ 259 | char key = Wire.read(); 260 | if (key == 0xff) { _flgFACESKB = false; continue; } 261 | else if (key == 0) { _flgFACESKB = true; continue; } 262 | else press = true; 263 | if (canRepeat) { 264 | ++_repeat; 265 | if (_flgFACESKB) { 266 | if (!inputKB(key)) return false; 267 | } else { 268 | if (!(key & 0x80)) { return false; } // FACES GameBoy Start: Finish. 269 | if (!(key & 0x40)) { switchTable(); } // FACES GameBoy Select: Panel Switch. 270 | if (!(key & 0x10)) { pressKey(); } // FACES GameBoy A btn: Input. 271 | if (!(key & 0x20)) { pressKey(BS); } // FACES GameBoy B btn: BS key. 272 | _col += (0 == (key & 0x08)) ? 1 : (0 == (key & 0x04)) ? -1 : 0; 273 | _row += (0 == (key & 0x02)) ? 1 : (0 == (key & 0x01)) ? -1 : 0; 274 | } 275 | } 276 | } 277 | } 278 | if (useCardKB && Wire.requestFrom(0x5F, 1)) { 279 | while (Wire.available()){ 280 | char key = Wire.read(); 281 | if (key == 0) { continue; } 282 | press = true; 283 | if (canRepeat) { 284 | ++_repeat; 285 | if (!inputKB(key)) return false; 286 | } 287 | } 288 | } 289 | #ifdef _M5PLUSENCODER_H_ 290 | if (usePLUSEncoder && PLUSEncoder.update()) { 291 | switch (_state) { 292 | case LEFTRIGHT: // left right moving 293 | if (PLUSEncoder.wasUp()) { ++_col; } 294 | if (PLUSEncoder.wasDown()) { --_col; } 295 | if (PLUSEncoder.wasHold()) { switchTable(); break; } 296 | if (PLUSEncoder.wasClicked()) { _state = UPDOWN; } 297 | break; 298 | case UPDOWN: // up down moving 299 | if (PLUSEncoder.wasUp()) { --_row; } 300 | if (PLUSEncoder.wasDown()) { ++_row; } 301 | if (PLUSEncoder.wasHold()) { _state = LEFTRIGHT; } 302 | if (PLUSEncoder.wasClicked()) { ++_repeat; pressKey(); _state = LEFTRIGHT; } 303 | break; 304 | default: break; 305 | } 306 | } 307 | #endif 308 | #ifdef _M5FACESENCODER_H_ 309 | if (useFACESEncoder && FACESEncoder.update()) { 310 | switch (_state) { 311 | case LEFTRIGHT: // left right moving 312 | if (FACESEncoder.wasUp()) { ++_col; } 313 | if (FACESEncoder.wasDown()) { --_col; } 314 | if (FACESEncoder.wasHold()) { switchTable(); break; } 315 | if (FACESEncoder.wasClicked()) { _state = UPDOWN; } 316 | break; 317 | case UPDOWN: // up down moving 318 | if (FACESEncoder.wasUp()) { --_row; } 319 | if (FACESEncoder.wasDown()) { ++_row; } 320 | if (FACESEncoder.wasHold()) { _state = LEFTRIGHT; } 321 | if (FACESEncoder.wasClicked()) { ++_repeat; pressKey(); _state = LEFTRIGHT; } 322 | break; 323 | default: break; 324 | } 325 | } 326 | #endif 327 | #ifdef _M5JOYSTICK_H_ 328 | if (useJoyStick && JoyStick.update()) { 329 | if (!JoyStick.isNeutral()) { 330 | press = true; 331 | if (canRepeat) { 332 | ++_repeat; 333 | if (JoyStick.isLeft() ) { --_col; } 334 | if (JoyStick.isRight()) { ++_col; } 335 | if (JoyStick.isUp() ) { --_row; } 336 | if (JoyStick.isDown() ) { ++_row; } 337 | } 338 | } 339 | if (JoyStick.wasClicked()) { ++_repeat; pressKey(); } 340 | if (JoyStick.wasHold()) { switchTable(); } 341 | } 342 | #endif 343 | #endif 344 | if (oldCol != _col 345 | || oldRow != _row 346 | || oldTbl != _tbl 347 | || oldState != _state 348 | || oldRepeat != _repeat 349 | ) { 350 | _col = (_col + COLUMNCOUNT) % COLUMNCOUNT; 351 | _row = (_row + ROWCOUNT ) % ROWCOUNT; 352 | if (oldTbl != _tbl || oldState != _state) { 353 | drawKeyboard(); 354 | } else { 355 | drawColumn(_col); 356 | if (oldCol != _col) drawColumn(oldCol); 357 | } 358 | _msecLast = _msec; 359 | } else { 360 | if (!press) { 361 | _repeat = 0; 362 | if (_pressed != 0 && (_msec - _msecLast) >= msecHold) { 363 | _pressed = 0; 364 | drawColumn(_col); 365 | } 366 | } 367 | } 368 | // draw blink cursor. 369 | if (useTextbox) { 370 | M5.Lcd.drawFastVLine( _cursorX 371 | , getY(-1) + (keyHeight - M5.Lcd.fontHeight(font)) / 2 372 | , M5.Lcd.fontHeight(font) 373 | , (_msec / 150) % 2 ? textboxBackColor : textboxFontColor); 374 | } 375 | #ifndef ARDUINO_ODROID_ESP32 376 | updateButton(); 377 | _btnDrawer.draw(); 378 | #endif 379 | return true; 380 | } 381 | void M5OnScreenKeyboard::close() { 382 | int y = getY(-1); 383 | M5.Lcd.fillRect(0, y, M5.Lcd.width(), M5.Lcd.height() - y, 0); 384 | clearString(); 385 | } 386 | 387 | int M5OnScreenKeyboard::getX(int col) const { return col * KEYWIDTH; } 388 | int M5OnScreenKeyboard::getY(int row) const { return M5.Lcd.height() - _btnHeight - (ROWCOUNT - row) * keyHeight; } 389 | 390 | void M5OnScreenKeyboard::updateButton() { 391 | if (M5.BtnA.isPressed() || _fn) { 392 | _btnDrawer.setText(swapBtnBC?2:1, (M5.BtnC.isPressed()) ? "AllClear" : _state == UPDOWN ? "Column" : "Finish"); 393 | _btnDrawer.setText(swapBtnBC?1:2, (M5.BtnB.isPressed()) ? "AllClear" : _state == MORSE ? "Focus" : "Morse" ); 394 | _btnDrawer.setText(0, (_fn==1&&_state==LEFTRIGHT) ? "Left" 395 | : (_fn==1&&_state==UPDOWN ) ? "Up" 396 | : (_fn ||_state==MORSE ) ? "Fn" 397 | : "Panel"); 398 | } else { 399 | switch (_state) { 400 | case LEFTRIGHT: _btnDrawer.setText("Panel/Left" , swapBtnBC?"Right":"Row", swapBtnBC?"Row":"Right"); break; 401 | case UPDOWN: _btnDrawer.setText("Panel/Up" , swapBtnBC?"Down" :"Ok" , swapBtnBC?"Ok" :"Down" ); break; 402 | case MORSE: _btnDrawer.setText("Panel/Fn" , "." , "_" ); break; 403 | default: break; 404 | } 405 | } 406 | } 407 | void M5OnScreenKeyboard::switchTable() { 408 | _tbl = (_tbl + 1) % (TABLECOUNT - (useOver0x80Chars ? 0 : 1)); 409 | } 410 | 411 | bool M5OnScreenKeyboard::inputKB(char key) 412 | { 413 | // FACES cursor:0x80~0x83 414 | // CardKB cursor:0xB4~0xB7 415 | switch (key) { 416 | case 0x0D: return false; 417 | case 0x81: case 0xB4: pressKey(LEFT); break; 418 | case 0x83: case 0xB7: pressKey(RIGH); break; 419 | case '\t': 420 | case BS: pressKey(key); break; 421 | default: 422 | if (0x20 <= key && key < 0x80) { 423 | pressKey(key); 424 | } 425 | break; 426 | } 427 | return true; 428 | } 429 | void M5OnScreenKeyboard::pressKey() { 430 | pressKey(_chartbl[_tbl][_row][_col]); 431 | } 432 | void M5OnScreenKeyboard::pressKey(char keycode) { 433 | _pressed = keycode; 434 | _keyCode = keycode; 435 | if (!useTextbox) return; 436 | 437 | switch (_keyCode) { 438 | case BS: 439 | if (0 < _cursorPos) { 440 | _string = _string.substring(0, _cursorPos-1) + _string.substring(_cursorPos); 441 | --_cursorPos; 442 | } 443 | break; 444 | 445 | case DEL: 446 | if (_cursorPos < _string.length()) { 447 | _string = _string.substring(0, _cursorPos) + _string.substring(_cursorPos+1); 448 | } 449 | break; 450 | 451 | case LEFT: 452 | _cursorPos -= (0 < _cursorPos ? 1 : 0); 453 | break; 454 | 455 | case RIGH: 456 | _cursorPos += (_string.length() > _cursorPos ? 1 : 0); 457 | break; 458 | 459 | default: 460 | if (_string.length() < maxlength) { 461 | _string = _string.substring(0, _cursorPos) + _keyCode + _string.substring(_cursorPos); 462 | ++_cursorPos; 463 | } 464 | break; 465 | } 466 | drawTextbox(); 467 | } 468 | 469 | void M5OnScreenKeyboard::clearMorse() { 470 | _morseInputBuf = 0; 471 | } 472 | 473 | void M5OnScreenKeyboard::pressMorse(bool longTone) { 474 | _morseInputBuf = (0 == _morseInputBuf ? 2 : (_morseInputBuf << 1)) | (longTone ? 0 : 1); 475 | if (_morseInputBuf & 0x80) { 476 | inputMorse(); 477 | } else { 478 | int mp = _morsepanel[_tbl]; 479 | for (int r = 0; r < ROWCOUNT; ++r) { 480 | for (int c = 0; c < COLUMNCOUNT; ++c) { 481 | if (_morseInputBuf != _morsetbl[mp][r][c]) continue; 482 | _row = r; 483 | _col = c; 484 | return; 485 | } 486 | } 487 | _row = ROWCOUNT-1; 488 | _col = COLUMNCOUNT-1; 489 | } 490 | } 491 | 492 | void M5OnScreenKeyboard::inputMorse() { 493 | if (!_morseInputBuf) return; 494 | 495 | uint16_t morse = _morseInputBuf; 496 | clearMorse(); 497 | int mp = _morsepanel[_tbl]; 498 | for (int c = 0; c < COLUMNCOUNT; ++c) { 499 | for (int r = 0; r < ROWCOUNT; ++r) { 500 | if (morse != _morsetbl[mp][r][c]) continue; 501 | pressKey(_chartbl[_tbl][r][c]); 502 | return; 503 | } 504 | } 505 | for (int c = 0; c < COLUMNCOUNT; ++c) { 506 | for (int r = 0; r < ROWCOUNT; ++r) { 507 | int m = -1; 508 | for (int t = 0; t < TABLECOUNT; ++t) { 509 | if (m == _morsepanel[t] || mp == _morsepanel[t]) continue; 510 | m = _morsepanel[t]; 511 | if (morse != _morsetbl[m][r][c]) continue; 512 | pressKey(_chartbl[t][r][c]); 513 | return; 514 | } 515 | } 516 | } 517 | _row = ROWCOUNT-1; 518 | _col = COLUMNCOUNT-1; 519 | } 520 | 521 | void M5OnScreenKeyboard::drawKeyTop(int c, int r, int x, int y, int kh) 522 | { 523 | int fh = M5.Lcd.fontHeight(font); 524 | int moffset = _state == MORSE ? 2 : 0; 525 | 526 | char tbl[2] = {_chartbl[_tbl][r][c],0}; 527 | char* str = tbl; 528 | char code = _chartbl[_tbl][r][c]; 529 | switch (code) { 530 | case '\t': str = (char*)PROGMEM "TAB"; break; 531 | case '\r': str = (char*)PROGMEM "CR"; break; 532 | case '\n': str = (char*)PROGMEM "LF"; break; 533 | case BS : str = (char*)PROGMEM "BS"; break; 534 | case DEL : str = (char*)PROGMEM "DEL"; break; 535 | case LEFT: str = (char*)PROGMEM "<<"; break; 536 | case RIGH: str = (char*)PROGMEM ">>"; break; 537 | } 538 | uint16_t color = fontColor[_col == c && _row == r ? 1 : 0]; 539 | int fy = min(y + (kh - fh + 1) / 2 + moffset, M5.Lcd.height() - _btnHeight - fh); 540 | M5.Lcd.setTextColor(color); 541 | M5.Lcd.drawCentreString(str, x + 16, fy, font); 542 | if (_state == MORSE) { 543 | int morse = _morsetbl[_morsepanel[_tbl]][r][c]; 544 | if (morse != 0) { 545 | drawMorse(morse, x + 15 - calcMorse(morse) / 2, y + moffset, color); 546 | } 547 | } 548 | if (_pressed == code) { 549 | M5.Lcd.drawRect(x+1, y+1, KEYWIDTH - 2, keyHeight - 1, frameColor[1]); 550 | _pressed = -1; 551 | } 552 | } 553 | 554 | void M5OnScreenKeyboard::drawMorse(uint8_t m, int x, int y, uint16_t color) 555 | { 556 | bool startbit = false; 557 | bool flg; 558 | for (int i = 0; i < 8; ++i) { 559 | flg = (m & (0x80 >> i)); 560 | if (!startbit) startbit = flg; 561 | else { 562 | if (flg) M5.Lcd.drawFastVLine(x, y-1, 3, color); 563 | else M5.Lcd.drawFastHLine(x, y , 3, color); 564 | x += flg ? 3 : 5; 565 | } 566 | } 567 | } 568 | 569 | void M5OnScreenKeyboard::draw() { 570 | M5.Lcd.setTextSize(1); 571 | drawKeyboard(); 572 | if (useTextbox) drawTextbox(); 573 | } 574 | 575 | void M5OnScreenKeyboard::drawTextbox() { 576 | int y = getY(-1); 577 | int ty = y + (keyHeight - M5.Lcd.fontHeight(font)) / 2; 578 | int oldX = M5.Lcd.getCursorX(); 579 | int oldY = M5.Lcd.getCursorY(); 580 | M5.Lcd.setTextColor(textboxFontColor); 581 | M5.Lcd.drawFastHLine(0, y, M5.Lcd.width(), frameColor[0]); 582 | M5.Lcd.fillRect(0, y + 1, M5.Lcd.width(), keyHeight - 1, textboxBackColor); 583 | M5.Lcd.setCursor(1, ty, font); 584 | _cursorX = 0; 585 | for (int i = 0; i < _string.length(); ++i) { 586 | if (_string[i] < 0x20) { 587 | drawCodeSymbol(_string[i], M5.Lcd.getCursorX(), y + (keyHeight - 8) / 2, textboxFontColor); 588 | } else { 589 | M5.Lcd.print(_string[i]); 590 | } 591 | if (_cursorPos == i + 1) _cursorX = M5.Lcd.getCursorX() - 1; 592 | } 593 | M5.Lcd.setCursor(oldX, oldY); 594 | } 595 | 596 | void M5OnScreenKeyboard::drawKeyboard(int h) { 597 | if (h < 0) h = keyHeight * ROWCOUNT; 598 | int y = M5.Lcd.height() - _btnHeight - h; 599 | for (int c = 0; c < COLUMNCOUNT; ++c) { 600 | int x = getX(c); 601 | drawColumn(c, x, y, h); 602 | M5.Lcd.drawFastVLine(x, y, h, frameColor[0]); 603 | } 604 | M5.Lcd.drawFastVLine(getX(COLUMNCOUNT), y, h, frameColor[0]); 605 | } 606 | 607 | void M5OnScreenKeyboard::drawColumn(int col) { 608 | drawColumn(col, getX(col), getY(0), ROWCOUNT * keyHeight); 609 | } 610 | 611 | void M5OnScreenKeyboard::drawColumn(int col, int x, int y, int h) { 612 | M5.Lcd.fillRect(x+1, y, KEYWIDTH-1, h, backColor[0]); 613 | int kh = h / ROWCOUNT; 614 | if (_col == col) { 615 | M5.Lcd.fillRect(x+1, y + _row * kh + 1, KEYWIDTH - 1, kh - 1, backColor[1]); 616 | if (_state == LEFTRIGHT) { 617 | M5.Lcd.drawRect(x+1, y+1, KEYWIDTH - 1, h - 1, backColor[1]); 618 | M5.Lcd.drawRect(x+2, y+2, KEYWIDTH - 3, h - 3, backColor[1]); 619 | } 620 | } 621 | for (int r = 0; r < ROWCOUNT; ++r) { 622 | int tmpy = y + r * kh; 623 | drawKeyTop(col, r, x, tmpy, kh); 624 | M5.Lcd.drawFastHLine(x, tmpy, KEYWIDTH, frameColor[0]); 625 | } 626 | } 627 | 628 | void M5OnScreenKeyboard::applyFont() 629 | { 630 | if (gfxFont) { 631 | M5.Lcd.setFreeFont(gfxFont); 632 | } else { 633 | M5.Lcd.setTextFont(0); 634 | M5.Lcd.setTextFont(font); 635 | } 636 | } 637 | 638 | bool M5OnScreenKeyboard::appear() { 639 | int tmp = (millis() - _msec) / 2; 640 | if (tmp < keyHeight * ROWCOUNT) { 641 | drawKeyboard(tmp); 642 | return false; 643 | } 644 | 645 | _state = LEFTRIGHT; 646 | draw(); 647 | return true; 648 | } 649 | -------------------------------------------------------------------------------- /src/M5OnScreenKeyboard.h: -------------------------------------------------------------------------------- 1 | #ifndef _M5ONSCREENKEYBOARD_H_ 2 | #define _M5ONSCREENKEYBOARD_H_ 3 | 4 | #include 5 | #include 6 | 7 | class M5OnScreenKeyboard 8 | { 9 | public: 10 | static bool useTextbox; 11 | static bool useOver0x80Chars; 12 | static bool useFACES; 13 | static bool useCardKB; 14 | static bool useJoyStick; 15 | static bool usePLUSEncoder; 16 | static bool useFACESEncoder; 17 | static bool swapBtnBC; 18 | 19 | static uint16_t fontColor[2]; 20 | static uint16_t backColor[2]; 21 | static uint16_t frameColor[2]; 22 | static uint16_t textboxFontColor; 23 | static uint16_t textboxBackColor; 24 | static uint8_t keyHeight; 25 | 26 | static uint16_t msecHold; 27 | static uint16_t msecRepeat; 28 | static uint16_t msecMorseInput; 29 | static uint8_t maxlength; 30 | 31 | static void setTextFont(int f) { gfxFont = NULL; font = f; } 32 | static void setFreeFont(const GFXfont* f) { gfxFont = f; font = 1; } 33 | 34 | void setup(const String& value = ""); 35 | bool loop(); 36 | void close(); 37 | 38 | void draw(); 39 | void clearString(); 40 | String getString() const { return _string; } 41 | void setString(const String& value = ""); 42 | char getKeyCode() const { return _keyCode; } 43 | private: 44 | static int16_t font; 45 | static const GFXfont* gfxFont; 46 | 47 | enum eState 48 | { APPEAR 49 | , LEFTRIGHT 50 | , UPDOWN 51 | , MORSE 52 | }; 53 | eState _state; 54 | int8_t _fn = 0; 55 | int8_t _tbl = 0; 56 | int8_t _col = 0; 57 | int8_t _row = 0; 58 | int8_t _cursorPos = 0; 59 | int16_t _cursorX = 0; 60 | uint32_t _msec = 0; 61 | uint32_t _msecLast = 0; 62 | int _repeat; 63 | char _keyCode; 64 | char _pressed; 65 | String _string; 66 | uint8_t _morseInputBuf; 67 | bool _flgFACESKB; 68 | M5ButtonDrawer _btnDrawer; 69 | uint8_t _btnHeight; 70 | 71 | int getX(int col) const; 72 | int getY(int row) const; 73 | void updateButton(); 74 | void switchTable(); 75 | bool inputKB(char key); 76 | void pressKey(); 77 | void pressKey(char keycode); 78 | void clearMorse(); 79 | void pressMorse(bool longTone); 80 | void inputMorse(); 81 | void drawKeyTop(int c, int r, int x, int y, int keyh); 82 | void drawMorse(uint8_t m, int x, int y, uint16_t color); 83 | void drawTextbox(); 84 | void drawKeyboard(int h = -1); 85 | void drawColumn(int col); 86 | void drawColumn(int col, int x, int y, int h); 87 | void applyFont(); 88 | bool appear(); 89 | }; 90 | 91 | #endif 92 | -------------------------------------------------------------------------------- /src/M5PLUSEncoder.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | M5PLUSEncoder PLUSEncoder; 4 | 5 | bool M5PLUSEncoder::update() 6 | { 7 | if (!Wire.requestFrom(_addr, 2)) return false; 8 | _time = millis(); 9 | _oldUpDown = _upDown; 10 | _oldPress = _press; 11 | bool press = false; 12 | while (Wire.available()){ 13 | _raw = Wire.read(); 14 | _rawsum += _raw; 15 | press = (Wire.read() != 0xff); 16 | } 17 | _upDown = _rawsum / 4; 18 | if (_upDown != 0) _rawsum = 0; 19 | 20 | if (press != (0 != _oldPress)) _lastChange = _time; 21 | if (press) { 22 | if (!_oldPress) { 23 | _press = 1; 24 | } else 25 | if (1 == _oldPress && (_time - _lastChange >= msecHold)) { 26 | _press = 2; 27 | } 28 | } else { 29 | _press = 0; 30 | } 31 | 32 | return true; 33 | } 34 | -------------------------------------------------------------------------------- /src/M5PLUSEncoder.h: -------------------------------------------------------------------------------- 1 | #ifndef _M5PLUSENCODER_H_ 2 | #define _M5PLUSENCODER_H_ 3 | 4 | #include 5 | 6 | class M5PLUSEncoder 7 | { 8 | public: 9 | uint16_t msecHold = 300; 10 | 11 | void setAddr(int8_t addr) { _addr = addr; } 12 | 13 | bool update(); 14 | 15 | int8_t rawValue() const { return _raw; } 16 | bool wasUp() const { return _upDown > 0; } 17 | bool wasDown() const { return _upDown < 0; } 18 | 19 | bool wasClicked() const { return _oldPress == 1 && _press == 0; } 20 | bool wasHold() const { return _oldPress == 1 && _press == 2; } 21 | bool isHolding() const { return _oldPress == 2 && _press == 2; } 22 | 23 | bool isPressed() const { return _press; } 24 | bool isReleased() const { return !_press; } 25 | bool wasPressed() const { return !_oldPress && _press; } 26 | bool wasReleased() const { return _oldPress && !_press; } 27 | bool pressedFor(uint32_t ms) const { return (_press && _time - _lastChange >= ms); } 28 | bool releasedFor(uint32_t ms) const { return (!_press && _time - _lastChange >= ms); } 29 | 30 | private: 31 | int8_t _addr = 0x62; 32 | int8_t _raw = 0; 33 | int8_t _rawsum = 0; 34 | int8_t _upDown = 0; 35 | int8_t _oldUpDown = 0; 36 | uint8_t _press = 0; // 0:release 1:click 2:holding 37 | uint8_t _oldPress = 0; 38 | uint32_t _time = 0; 39 | uint32_t _lastChange = 0; 40 | }; 41 | 42 | #endif 43 | 44 | extern M5PLUSEncoder PLUSEncoder; 45 | 46 | --------------------------------------------------------------------------------