mirror of
				https://github.com/ArthurSonzogni/FTXUI.git
				synced 2025-11-01 02:58:12 +08:00 
			
		
		
		
	Add UTF8 support and a better xterm parsing.
This fixes: https://github.com/ArthurSonzogni/FTXUI/issues/2
This commit is contained in:
		| @@ -15,19 +15,12 @@ class DrawKey : public Component { | ||||
|   Element Render() override { | ||||
|     Elements children; | ||||
|     for (size_t i = std::max(0, (int)keys.size() - 10); i < keys.size(); ++i) { | ||||
|       std::string code = ""; | ||||
|       for (size_t j = 0; j < 5; ++j) | ||||
|         code += " " + std::to_string(keys[i].values[j]); | ||||
|       std::wstring code; | ||||
|       for(auto& it : keys[i].input()) | ||||
|         code += L" " + std::to_wstring((int)it); | ||||
|  | ||||
|       try { | ||||
|         std::string line = code + " -> " + std::to_string(keys[i].values[0]) + | ||||
|                            " (" + char(keys[i].values[0]) + ")"; | ||||
|         children.push_back(text(to_wstring(line))); | ||||
|       } catch (...) { | ||||
|         std::string line = | ||||
|             code + " -> " + std::to_string(keys[i].values[0]) + " (undefined)"; | ||||
|         children.push_back(text(to_wstring(line))); | ||||
|       } | ||||
|       code = L"(" + code + L" ) -> " + keys[i].character() + L")"; | ||||
|       children.push_back(text(code)); | ||||
|     } | ||||
|     return vbox(std::move(children)); | ||||
|   } | ||||
|   | ||||
| @@ -1,22 +1,32 @@ | ||||
| #ifndef FTXUI_COMPONENT_EVENT_HPP | ||||
| #define FTXUI_COMPONENT_EVENT_HPP | ||||
|  | ||||
| #include <vector> | ||||
| #include <array> | ||||
| #include <vector> | ||||
| #include <functional> | ||||
|  | ||||
| namespace ftxui { | ||||
|  | ||||
| struct Event{ | ||||
| // Documentation: | ||||
| // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html | ||||
| // | ||||
| struct Event { | ||||
|  public: | ||||
|   // --- Character --- | ||||
|   static Event Character(int); | ||||
|   // --- Constructor section --------------------------------------------------- | ||||
|   static Event Character(char); | ||||
|   static Event Character(wchar_t); | ||||
|  | ||||
|   static Event Character(const std::string&); | ||||
|   static Event Special(const std::string&); | ||||
|  | ||||
|   static Event GetEvent(std::function<char()> getchar); | ||||
|  | ||||
|   // --- Arrow --- | ||||
|   static Event ArrowLeft; | ||||
|   static Event ArrowRight; | ||||
|   static Event ArrowUp; | ||||
|   static Event ArrowDown; | ||||
|    | ||||
|  | ||||
|   // --- Other --- | ||||
|   static Event Backspace; | ||||
|   static Event Delete; | ||||
| @@ -27,15 +37,20 @@ struct Event{ | ||||
|   // --- Custom --- | ||||
|   static Event Custom; | ||||
|  | ||||
|   bool operator==(const Event& other) { return values == other.values; } | ||||
|   //--- Method section --------------------------------------------------------- | ||||
|   bool is_character() { return is_character_; } | ||||
|   wchar_t character() { return character_; } | ||||
|   const std::string& input() { return input_; } | ||||
|  | ||||
|   // Internal representation. | ||||
|   std::array<int, 5> values = {0, 0, 0, 0, 0}; | ||||
|    | ||||
|   bool operator==(const Event& other) { return input_ == other.input_; } | ||||
|  | ||||
|   //--- State section ---------------------------------------------------------- | ||||
|  private: | ||||
|   std::string input_; | ||||
|   bool is_character_ = false; | ||||
|   wchar_t character_ = '?'; | ||||
| }; | ||||
|  | ||||
|  | ||||
| } // namespace ftxui | ||||
|  | ||||
| }  // namespace ftxui | ||||
|  | ||||
| #endif /* end of include guard: FTXUI_COMPONENT_EVENT_HPP */ | ||||
|   | ||||
| @@ -36,7 +36,7 @@ Decorator bgcolor(Color); | ||||
| Element color(Color, Element); | ||||
| Element bgcolor(Color, Element); | ||||
|  | ||||
| // --- Layout --- | ||||
| // --- Layout is | ||||
| // Horizontal, Vertical or stacked set of elements. | ||||
| Element hbox(Elements); | ||||
| Element vbox(Elements); | ||||
|   | ||||
| @@ -1,39 +1,160 @@ | ||||
| #include "ftxui/component/event.hpp" | ||||
| #include "ftxui/screen/string.hpp" | ||||
|  | ||||
| namespace ftxui { | ||||
|  | ||||
| constexpr int ESC = int(27); | ||||
| // static | ||||
| Event Event::Character(const std::string& input) { | ||||
|   Event event; | ||||
|   event.input_ = input; | ||||
|   event.is_character_ = true; | ||||
|   event.character_ = to_wstring(input)[0]; | ||||
|   return event; | ||||
| } | ||||
|  | ||||
| // --- Character --- | ||||
| Event Event::Character(int c) { | ||||
|   return Event{c}; | ||||
| // static | ||||
| Event Event::Character(char c) { | ||||
|   return Character(wchar_t(c)); | ||||
| } | ||||
|  | ||||
| // static | ||||
| Event Event::Character(wchar_t c) { | ||||
|   Event event; | ||||
|   event.input_ = {(char)c}; | ||||
|   event.is_character_ = true; | ||||
|   event.character_ = c; | ||||
|   return event; | ||||
| } | ||||
|  | ||||
| // static | ||||
| Event Event::Special(const std::string& input) { | ||||
|   Event event; | ||||
|   event.input_ = std::move(input); | ||||
|   return event; | ||||
| } | ||||
|  | ||||
| Event ParseUTF8(std::function<char()>& getchar, std::string& input) { | ||||
|   if ((input[0] & 0b11000000) == 0b11000000) | ||||
|     input += getchar(); | ||||
|   if ((input[0] & 0b11100000) == 0b11100000) | ||||
|     input += getchar(); | ||||
|   if ((input[0] & 0b11110000) == 0b11110000) | ||||
|     input += getchar(); | ||||
|   return Event::Character(input); | ||||
| } | ||||
|  | ||||
| void ParsePs(std::function<char()> getchar, std::string input) { | ||||
|   while (1) { | ||||
|     char key = getchar(); | ||||
|     input += key; | ||||
|     if ('0' <= key && key <= '9') | ||||
|       continue; | ||||
|     return; | ||||
|   } | ||||
| } | ||||
|  | ||||
| Event ParseCSI(std::function<char()> getchar, std::string& input) { | ||||
|   while (1) { | ||||
|     char c = getchar(); | ||||
|     input += c; | ||||
|  | ||||
|     if (c >= ' ' && c <= '/') | ||||
|       return Event::Special(input); | ||||
|  | ||||
|     // Invalid ESC in CSI. | ||||
|     if (c == '\e') | ||||
|       return Event::Special(input); | ||||
|  | ||||
|     if (c >= '@' && c <= 'v') | ||||
|       return Event::Special(input); | ||||
|   } | ||||
| } | ||||
|  | ||||
| Event ParseDCS(std::function<char()> getchar, std::string& input) { | ||||
|   // Parse until the string terminator ST. | ||||
|   while (1) { | ||||
|     input += getchar(); | ||||
|     if (input.back() != '\e') | ||||
|       continue; | ||||
|     input += getchar(); | ||||
|     if (input.back() != '\\') | ||||
|       continue; | ||||
|     return Event::Special(input); | ||||
|   } | ||||
| } | ||||
|  | ||||
| Event ParseOSC(std::function<char()> getchar, std::string& input) { | ||||
|   // Parse until the string terminator ST. | ||||
|   while (1) { | ||||
|     input += getchar(); | ||||
|     if (input.back() != '\e') | ||||
|       continue; | ||||
|     input += getchar(); | ||||
|     if (input.back() != '\\') | ||||
|       continue; | ||||
|     return Event::Special(input); | ||||
|   } | ||||
| } | ||||
|  | ||||
| Event ParseESC(std::function<char()> getchar, std::string& input) { | ||||
|   input += getchar(); | ||||
|   switch (input.back()) { | ||||
|     case 'P': | ||||
|       return ParseDCS(getchar, input); | ||||
|     case '[': | ||||
|       return ParseCSI(getchar, input); | ||||
|     case ']': | ||||
|       return ParseOSC(getchar, input); | ||||
|     default: | ||||
|       input += getchar(); | ||||
|       return Event::Special(input); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // static | ||||
| Event Event::GetEvent(std::function<char()> getchar) { | ||||
|   std::string input; | ||||
|   input += getchar(); | ||||
|  | ||||
|   switch (input[0]) { | ||||
|     case 24:           // CAN | ||||
|     case 26:           // SUB | ||||
|       return Event();  // Ignored. | ||||
|  | ||||
|     case 'P': | ||||
|       return ParseDCS(getchar, input); | ||||
|  | ||||
|     case '\e': | ||||
|       return ParseESC(getchar, input); | ||||
|   } | ||||
|  | ||||
|   if (input[0] < 32)  // C0 | ||||
|     return Event::Special(input); | ||||
|  | ||||
|   return ParseUTF8(getchar, input); | ||||
| } | ||||
|  | ||||
| // --- Arrow --- | ||||
| Event Event::ArrowLeft{ESC, '[', 'D'}; | ||||
| Event Event::ArrowRight{ESC, '[', 'C'}; | ||||
| Event Event::ArrowUp{ESC, '[', 'A'}; | ||||
| Event Event::ArrowDown{ESC, '[', 'B'}; | ||||
| Event Event::ArrowLeft = Event::Special("\e[D"); | ||||
| Event Event::ArrowRight = Event::Special("\e[C"); | ||||
| Event Event::ArrowUp = Event::Special("\e[A"); | ||||
| Event Event::ArrowDown = Event::Special("\e[B"); | ||||
| Event Event::Backspace = Event::Special({127}); | ||||
| Event Event::Delete = Event::Special("\e[3~"); | ||||
| Event Event::Escape = Event::Special("\e"); | ||||
| Event Event::Return = Event::Special({10}); | ||||
| Event Event::F1 = Event::Special("\e[OP"); | ||||
| Event Event::F2 = Event::Special("\e[OQ"); | ||||
| Event Event::F3 = Event::Special("\e[OR"); | ||||
| Event Event::F4 = Event::Special("\e[OS"); | ||||
| Event Event::F5 = Event::Special("\e[15~"); | ||||
| Event Event::F6 = Event::Special("\e[17~"); | ||||
| Event Event::F7 = Event::Special("\e[18~"); | ||||
| Event Event::F8 = Event::Special("\e[19~"); | ||||
| Event Event::F9 = Event::Special("\e[20~"); | ||||
| Event Event::F10 = Event::Special("\e[21~"); | ||||
| Event Event::F11 = Event::Special("\e[21~");  // Doesn't exist | ||||
| Event Event::F12 = Event::Special("\e[24~"); | ||||
| Event Event::Custom = Event::Special({0}); | ||||
|  | ||||
| // --- Other --- | ||||
| Event Event::Backspace{127}; | ||||
| Event Event::Delete{ESC, '[', '3', '~'}; | ||||
| Event Event::Escape{ESC}; | ||||
| Event Event::Return{10}; | ||||
|  | ||||
| Event Event::F1{ESC, '[', 'O', 'P'}; | ||||
| Event Event::F2{ESC, '[', 'O', 'Q'}; | ||||
| Event Event::F3{ESC, '[', 'O', 'R'}; | ||||
| Event Event::F4{ESC, '[', 'O', 'S'}; | ||||
| Event Event::F5{ESC, '[', '1', '5', '~'}; | ||||
| Event Event::F6{ESC, '[', '1', '7', '~'}; | ||||
| Event Event::F7{ESC, '[', '1', '8', '~'}; | ||||
| Event Event::F8{ESC, '[', '1', '9', '~'}; | ||||
| Event Event::F9{ESC, '[', '2', '0', '~'}; | ||||
| Event Event::F10{ESC, '[', '2', '1', '~'}; | ||||
| Event Event::F11{ESC, '[', '2', '1', '~'};  // Same as F10 ? | ||||
| Event Event::F12{ESC, '[', '2', '4', '~'}; | ||||
|  | ||||
| Event Event::Custom{0, 0, 0, 0, 0}; | ||||
|  | ||||
| } // namespace ftxui | ||||
| }  // namespace ftxui | ||||
|   | ||||
| @@ -73,10 +73,8 @@ bool Input::OnEvent(Event event) { | ||||
|   } | ||||
|  | ||||
|   // Content | ||||
|   constexpr char ESC = char(27); | ||||
|   if (event.values[0] != ESC) { | ||||
|     wchar_t v = (char)event.values[0]; | ||||
|     content.insert(cursor_position, 1, v); | ||||
|   if (event.is_character()) { | ||||
|     content.insert(cursor_position, 1, event.character()); | ||||
|     cursor_position++; | ||||
|     return true; | ||||
|   } | ||||
|   | ||||
| @@ -4,47 +4,13 @@ | ||||
| #include <termios.h> | ||||
| #include <unistd.h> | ||||
| #include <iostream> | ||||
| #include "ftxui/component/component.hpp" | ||||
| #include "ftxui/screen/terminal.hpp" | ||||
| #include <thread> | ||||
| #include "ftxui/component/component.hpp" | ||||
| #include "ftxui/screen/string.hpp" | ||||
| #include "ftxui/screen/terminal.hpp" | ||||
|  | ||||
| namespace ftxui { | ||||
|  | ||||
| namespace { | ||||
| constexpr int ESC = 27; | ||||
| constexpr int WAT = 195; | ||||
| constexpr int WAT2 = 194; | ||||
| constexpr int WATWAIT = 91; | ||||
|  | ||||
| Event GetEvent() { | ||||
|   int v1 = getchar(); | ||||
|   if (v1 == ESC) { | ||||
|     int v2 = getchar(); | ||||
|     int v3 = getchar(); | ||||
|  | ||||
|     // if (v2 == WATWAIT) { | ||||
|     // int v4 = getchar(); | ||||
|     // int v5 = getchar(); | ||||
|     // return Event{v1, v2, v3, v4, v5}; | ||||
|     //} | ||||
|     return Event{v1, v2, v3}; | ||||
|   } | ||||
|  | ||||
|   if (v1 == WAT) { | ||||
|     int v2 = getchar(); | ||||
|     return Event{v1, v2}; | ||||
|   } | ||||
|  | ||||
|   if (v1 == WAT2) { | ||||
|     int v2 = getchar(); | ||||
|     return Event{v1, v2}; | ||||
|   } | ||||
|  | ||||
|   return Event{v1}; | ||||
| }; | ||||
|  | ||||
| };  // namespace | ||||
|  | ||||
| ScreenInteractive::ScreenInteractive(int dimx, | ||||
|                                      int dimy, | ||||
|                                      Dimension dimension) | ||||
| @@ -109,8 +75,10 @@ void ScreenInteractive::Loop(Component* component) { | ||||
|   tcsetattr(STDIN_FILENO, TCSANOW, &terminal_configuration_new); | ||||
|  | ||||
|   std::thread read_char([this]() { | ||||
|     while (!quit_) | ||||
|       PostEvent(GetEvent()); | ||||
|     while (!quit_) { | ||||
|       auto event = Event::GetEvent([] { return (char)getchar(); }); | ||||
|       PostEvent(std::move(event)); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   std::string reset_position; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| #include "ftxui/dom/node.hpp" | ||||
| #include "ftxui/dom/elements.hpp" | ||||
| #include "ftxui/dom/node.hpp" | ||||
|  | ||||
| namespace ftxui { | ||||
|  | ||||
|   | ||||
| @@ -9,7 +9,7 @@ namespace ftxui { | ||||
|  | ||||
| namespace { | ||||
| static const wchar_t* BOLD_SET = L"\e[1m"; | ||||
| static const wchar_t* BOLD_RESET = L"\e[22m"; // Can't use 21 here. | ||||
| static const wchar_t* BOLD_RESET = L"\e[22m";  // Can't use 21 here. | ||||
|  | ||||
| static const wchar_t* DIM_SET = L"\e[2m"; | ||||
| static const wchar_t* DIM_RESET = L"\e[22m"; | ||||
| @@ -34,7 +34,7 @@ bool In(const Box& stencil, int x, int y) { | ||||
|  | ||||
| Pixel dev_null_pixel; | ||||
|  | ||||
| } // namespace | ||||
| }  // namespace | ||||
|  | ||||
| Dimension Dimension::Fixed(int v) { | ||||
|   return Dimension{v, v}; | ||||
| @@ -86,8 +86,11 @@ void UpdatePixelStyle(std::wstringstream& ss, Pixel& previous, Pixel& next) { | ||||
|  | ||||
|   if (next.foreground_color != previous.foreground_color || | ||||
|       next.background_color != previous.background_color) { | ||||
|     ss << L"\e[" + to_wstring(std::to_string((uint8_t)next.foreground_color)) + L"m"; | ||||
|     ss << L"\e[" + to_wstring(std::to_string(10 + (uint8_t)next.background_color)) + L"m"; | ||||
|     ss << L"\e[" + to_wstring(std::to_string((uint8_t)next.foreground_color)) + | ||||
|               L"m"; | ||||
|     ss << L"\e[" + | ||||
|               to_wstring(std::to_string(10 + (uint8_t)next.background_color)) + | ||||
|               L"m"; | ||||
|   } | ||||
|  | ||||
|   previous = next; | ||||
| @@ -105,7 +108,6 @@ std::string Screen::ToString() { | ||||
|       UpdatePixelStyle(ss, previous_pixel, pixels_[y][x]); | ||||
|       ss << pixels_[y][x].character; | ||||
|     } | ||||
|  | ||||
|   } | ||||
|  | ||||
|   Pixel final_pixel; | ||||
| @@ -115,7 +117,7 @@ std::string Screen::ToString() { | ||||
| } | ||||
|  | ||||
| wchar_t& Screen::at(int x, int y) { | ||||
|   return PixelAt(x,y).character; | ||||
|   return PixelAt(x, y).character; | ||||
| } | ||||
|  | ||||
| Pixel& Screen::PixelAt(int x, int y) { | ||||
| @@ -137,36 +139,34 @@ void Screen::Clear() { | ||||
| } | ||||
|  | ||||
| void Screen::ApplyShader() { | ||||
|  | ||||
|   // Merge box characters togethers. | ||||
|   for(int y = 1; y<dimy_; ++y) { | ||||
|     for(int x = 1; x<dimx_; ++x) { | ||||
|   for (int y = 1; y < dimy_; ++y) { | ||||
|     for (int x = 1; x < dimx_; ++x) { | ||||
|       wchar_t& left = at(x - 1, y); | ||||
|       wchar_t& top = at(x, y - 1); | ||||
|       wchar_t& cur = at(x, y); | ||||
|  | ||||
|       // Left vs current | ||||
|       if (cur== U'│' && left == U'─') | ||||
|         cur= U'┤'; | ||||
|       if (cur== U'─' && left == U'│') | ||||
|       if (cur == U'│' && left == U'─') | ||||
|         cur = U'┤'; | ||||
|       if (cur == U'─' && left == U'│') | ||||
|         left = U'├'; | ||||
|       if (cur== U'├' && left == U'─') | ||||
|         cur= U'┼'; | ||||
|       if (cur== U'─' && left == U'┤') | ||||
|       if (cur == U'├' && left == U'─') | ||||
|         cur = U'┼'; | ||||
|       if (cur == U'─' && left == U'┤') | ||||
|         left = U'┼'; | ||||
|  | ||||
|       // Top vs current | ||||
|       if (cur== U'─' && top == U'│') | ||||
|         cur= U'┴'; | ||||
|       if (cur== U'│' && top == U'─') | ||||
|       if (cur == U'─' && top == U'│') | ||||
|         cur = U'┴'; | ||||
|       if (cur == U'│' && top == U'─') | ||||
|         top = U'┬'; | ||||
|       if (cur== U'┬' && top == U'│') | ||||
|         cur= U'┼'; | ||||
|       if (cur== U'│' && top == U'┴') | ||||
|       if (cur == U'┬' && top == U'│') | ||||
|         cur = U'┼'; | ||||
|       if (cur == U'│' && top == U'┴') | ||||
|         top = U'┼'; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
|  | ||||
| };  // namespace ftxui | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| #include <iostream> | ||||
| #include <sys/ioctl.h> | ||||
| #include <stdio.h> | ||||
| #include <sys/ioctl.h> | ||||
| #include <unistd.h> | ||||
| #include <iostream> | ||||
|  | ||||
| #include "ftxui/screen/terminal.hpp" | ||||
|  | ||||
| @@ -9,7 +9,7 @@ namespace ftxui { | ||||
|  | ||||
| Terminal::Dimensions Terminal::Size() { | ||||
| #ifdef __EMSCRIPTEN__ | ||||
|   return Dimensions{80,43}; | ||||
|   return Dimensions{80, 43}; | ||||
| #else | ||||
|   winsize w; | ||||
|   ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); | ||||
| @@ -17,4 +17,4 @@ Terminal::Dimensions Terminal::Size() { | ||||
| #endif | ||||
| } | ||||
|  | ||||
| } // namespace ftxui | ||||
| }  // namespace ftxui | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 ArthurSonzogni
					ArthurSonzogni