From 0b9b6c692aa7527d95bae1c4c832c7c82b1f98a6 Mon Sep 17 00:00:00 2001 From: ArthurSonzogni Date: Sun, 25 Apr 2021 15:22:38 +0200 Subject: [PATCH] Improve mouse support --- CMakeLists.txt | 1 + examples/util/print_key_press.cpp | 98 ++++++++------- include/ftxui/component/event.hpp | 47 +++----- include/ftxui/component/mouse.hpp | 41 +++++++ src/ftxui/component/button.cpp | 11 +- src/ftxui/component/checkbox.cpp | 10 +- src/ftxui/component/event.cpp | 94 +-------------- src/ftxui/component/menu.cpp | 7 +- src/ftxui/component/radiobox.cpp | 12 +- src/ftxui/component/screen_interactive.cpp | 114 +++++++++++++----- src/ftxui/component/terminal_input_parser.cpp | 109 ++++++----------- src/ftxui/component/terminal_input_parser.hpp | 16 +-- .../component/terminal_input_parser_test.cpp | 65 +++++++++- src/ftxui/component/toggle.cpp | 5 +- 14 files changed, 326 insertions(+), 304 deletions(-) create mode 100644 include/ftxui/component/mouse.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 92814d7..a65cfd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,6 +83,7 @@ add_library(component include/ftxui/component/event.hpp include/ftxui/component/input.hpp include/ftxui/component/menu.hpp + include/ftxui/component/mouse.hpp include/ftxui/component/radiobox.hpp include/ftxui/component/receiver.hpp include/ftxui/component/screen_interactive.hpp diff --git a/examples/util/print_key_press.cpp b/examples/util/print_key_press.cpp index fbd6ff8..3ba9848 100644 --- a/examples/util/print_key_press.cpp +++ b/examples/util/print_key_press.cpp @@ -11,6 +11,60 @@ using namespace ftxui; +std::wstring Stringify(Event event) { + std::wstring out; + for (auto& it : event.input()) + out += L" " + std::to_wstring((unsigned int)it); + + out = L"(" + out + L" ) -> "; + if (event.is_character()) { + out += std::wstring(L"character(") + event.character() + L")"; + } else if (event.is_mouse()) { + out += L"mouse"; + switch (event.mouse().button) { + case Mouse::Left: + out += L"_left"; + break; + case Mouse::Middle: + out += L"_middle"; + break; + case Mouse::Right: + out += L"_right"; + break; + case Mouse::None: + out += L"_none"; + break; + case Mouse::WheelUp: + out += L"_wheel_up"; + break; + case Mouse::WheelDown: + out += L"_wheel_down"; + break; + } + switch (event.mouse().motion) { + case Mouse::Pressed: + out += L"_pressed"; + break; + case Mouse::Released: + out += L"_released"; + break; + } + if (event.mouse().control) + out += L"_control"; + if (event.mouse().shift) + out += L"_shift"; + if (event.mouse().meta) + out += L"_meta"; + + out += L"(" + // + std::to_wstring(event.mouse().x) + L"," + + std::to_wstring(event.mouse().y) + L")"; + } else { + out += L"(special)"; + } + return out; +} + class DrawKey : public Component { public: ~DrawKey() override = default; @@ -18,49 +72,7 @@ class DrawKey : public Component { Element Render() override { Elements children; for (size_t i = std::max(0, (int)keys.size() - 20); i < keys.size(); ++i) { - std::wstring code; - for (auto& it : keys[i].input()) - code += L" " + std::to_wstring((unsigned int)it); - - code = L"(" + code + L" ) -> "; - if (keys[i].is_character()) { - code += std::wstring(L"character(") + keys[i].character() + L")"; - } else if (keys[i].is_mouse_move()) { - code += L"mouse_move(" + // - std::to_wstring(keys[i].mouse_x()) + L"," + - std::to_wstring(keys[i].mouse_y()) + L")"; - } else if (keys[i].is_mouse_up()) { - code += L"mouse_up(" + // - std::to_wstring(keys[i].mouse_x()) + L"," + - std::to_wstring(keys[i].mouse_y()) + L")"; - } else if (keys[i].is_mouse_left_down()) { - code += L"mouse_left_down(" + // - std::to_wstring(keys[i].mouse_x()) + L"," + - std::to_wstring(keys[i].mouse_y()) + L")"; - } else if (keys[i].is_mouse_left_move()) { - code += L"mouse_left_move(" + // - std::to_wstring(keys[i].mouse_x()) + L"," + - std::to_wstring(keys[i].mouse_y()) + L")"; - } else if (keys[i].is_mouse_middle_down()) { - code += L"mouse_middle_down(" + // - std::to_wstring(keys[i].mouse_x()) + L"," + - std::to_wstring(keys[i].mouse_y()) + L")"; - } else if (keys[i].is_mouse_middle_move()) { - code += L"mouse_middle_move(" + // - std::to_wstring(keys[i].mouse_x()) + L"," + - std::to_wstring(keys[i].mouse_y()) + L")"; - } else if (keys[i].is_mouse_right_down()) { - code += L"mouse_right_down(" + // - std::to_wstring(keys[i].mouse_x()) + L"," + - std::to_wstring(keys[i].mouse_y()) + L")"; - } else if (keys[i].is_mouse_right_move()) { - code += L"mouse_right_move(" + // - std::to_wstring(keys[i].mouse_x()) + L"," + - std::to_wstring(keys[i].mouse_y()) + L")"; - } else { - code += L"(special)"; - } - children.push_back(text(code)); + children.push_back(text(Stringify(keys[i]))); } return window(text(L"keys"), vbox(std::move(children))); } diff --git a/include/ftxui/component/event.hpp b/include/ftxui/component/event.hpp index 742c75c..bb87c6a 100644 --- a/include/ftxui/component/event.hpp +++ b/include/ftxui/component/event.hpp @@ -2,6 +2,7 @@ #define FTXUI_COMPONENT_EVENT_HPP #include +#include #include #include #include @@ -28,14 +29,7 @@ struct Event { static Event Character(std::string); static Event Special(std::string); - static Event MouseMove(std::string, int x, int y); - static Event MouseUp(std::string, int x, int y); - static Event MouseLeftMove(std::string, int x, int y); - static Event MouseLeftDown(std::string, int x, int y); - static Event MouseMiddleMove(std::string, int x, int y); - static Event MouseMiddleDown(std::string, int x, int y); - static Event MouseRightMove(std::string, int x, int y); - static Event MouseRightDown(std::string, int x, int y); + static Event Mouse(std::string, Mouse mouse); static Event CursorReporting(std::string, int x, int y); // --- Arrow --- @@ -60,51 +54,38 @@ struct Event { bool is_character() const { return type_ == Type::Character;} wchar_t character() const { return character_; } - bool is_mouse() const; - bool is_mouse_left_down() const { return type_ == Type::MouseLeftDown; } - bool is_mouse_left_move() const { return type_ == Type::MouseLeftMove; } - bool is_mouse_middle_down() const { return type_ == Type::MouseMiddleDown; } - bool is_mouse_middle_move() const { return type_ == Type::MouseMiddleMove; } - bool is_mouse_right_down() const { return type_ == Type::MouseRightDown; } - bool is_mouse_right_move() const { return type_ == Type::MouseRightMove; } - bool is_mouse_up() const { return type_ == Type::MouseUp; } - bool is_mouse_move() const { return type_ == Type::MouseMove; } + bool is_mouse() const { return type_ == Type::Mouse; } + struct Mouse& mouse() { + return mouse_; + } + bool is_cursor_reporting() const { return type_ == Type::CursorReporting; } - int mouse_x() const { return mouse_.x; } - int mouse_y() const { return mouse_.y; } + int cursor_x() const { return cursor_.x; } + int cursor_y() const { return cursor_.y; } const std::string& input() const { return input_; } bool operator==(const Event& other) const { return input_ == other.input_; } - void MoveMouse(int dx, int dy); - //--- State section ---------------------------------------------------------- private: enum class Type { Unknown, Character, - MouseMove, - MouseUp, - MouseLeftDown, - MouseLeftMove, - MouseMiddleDown, - MouseMiddleMove, - MouseRightDown, - MouseRightMove, + Mouse, CursorReporting, }; + Type type_ = Type::Unknown; - struct Mouse { + struct Cursor { int x; int y; }; - Type type_ = Type::Unknown; - union { wchar_t character_ = U'?'; - Mouse mouse_; + struct Mouse mouse_; + struct Cursor cursor_; }; std::string input_; }; diff --git a/include/ftxui/component/mouse.hpp b/include/ftxui/component/mouse.hpp new file mode 100644 index 0000000..54e80d4 --- /dev/null +++ b/include/ftxui/component/mouse.hpp @@ -0,0 +1,41 @@ +namespace ftxui { + +/// @brief A mouse event. It contains the coordinate of the mouse, the button +/// pressed and the modifier (shift, ctrl, meta). +/// @ingroup component +struct Mouse { + enum Button { + Left = 0, + Middle = 1, + Right = 2, + None = 3, + WheelUp = 4, + WheelDown = 5, + }; + + enum Motion { + Released = 0, + Pressed = 1, + }; + + // Button + Button button; + + // Motion + Motion motion; + + // Modifiers: + bool shift; + bool meta; + bool control; + + // Coordinates: + int x; + int y; +}; + +} // namespace ftxui + +// Copyright 2020 Arthur Sonzogni. All rights reserved. +// Use of this source code is governed by the MIT license that can be found in +// the LICENSE file. diff --git a/src/ftxui/component/button.cpp b/src/ftxui/component/button.cpp index 85065c9..4da42dd 100644 --- a/src/ftxui/component/button.cpp +++ b/src/ftxui/component/button.cpp @@ -10,12 +10,11 @@ Element Button::Render() { } bool Button::OnEvent(Event event) { - if (event.is_mouse() && box_.Contain(event.mouse_x(), event.mouse_y())) { - if (event.is_mouse_move()) { - TakeFocus(); - return true; - } - if (event.is_mouse_left_down()) { + if (event.is_mouse() && box_.Contain(event.mouse().x, event.mouse().y)) { + TakeFocus(); + + if (event.mouse().button == Mouse::Left && + event.mouse().motion == Mouse::Pressed) { on_click(); return true; } diff --git a/src/ftxui/component/checkbox.cpp b/src/ftxui/component/checkbox.cpp index ae3dc0c..5fe251a 100644 --- a/src/ftxui/component/checkbox.cpp +++ b/src/ftxui/component/checkbox.cpp @@ -26,15 +26,13 @@ bool CheckBox::OnEvent(Event event) { } bool CheckBox::OnMouseEvent(Event event) { - if (!box_.Contain(event.mouse_x(), event.mouse_y())) + if (!box_.Contain(event.mouse().x, event.mouse().y)) return false; - if (event.is_mouse_move()) { - TakeFocus(); - return true; - } + TakeFocus(); - if (event.is_mouse_left_down()) { + if (event.mouse().button == Mouse::Left && + event.mouse().motion == Mouse::Pressed) { state = !state; on_change(); return true; diff --git a/src/ftxui/component/event.cpp b/src/ftxui/component/event.cpp index dac8951..2fa0cf0 100644 --- a/src/ftxui/component/event.cpp +++ b/src/ftxui/component/event.cpp @@ -29,56 +29,11 @@ Event Event::Character(wchar_t c) { } // static -Event Event::MouseMove(std::string input, int x, int y) { +Event Event::Mouse(std::string input, struct Mouse mouse) { Event event; event.input_ = std::move(input); - event.type_ = Type::MouseMove; - event.mouse_ = {x, y}; - return event; -} - -// static -Event Event::MouseUp(std::string input, int x, int y) { - Event event; - event.input_ = std::move(input); - event.type_ = Type::MouseUp; - event.mouse_ = {x, y}; - return event; -} - -// static -Event Event::MouseLeftDown(std::string input, int x, int y) { - Event event; - event.input_ = std::move(input); - event.type_ = Type::MouseLeftDown; - event.mouse_ = {x, y}; - return event; -} - -// static -Event Event::MouseLeftMove(std::string input, int x, int y) { - Event event; - event.input_ = std::move(input); - event.type_ = Type::MouseLeftMove; - event.mouse_ = {x, y}; - return event; -} - -// static -Event Event::MouseRightDown(std::string input, int x, int y) { - Event event; - event.input_ = std::move(input); - event.type_ = Type::MouseRightDown; - event.mouse_ = {x, y}; - return event; -} - -// static -Event Event::MouseMiddleMove(std::string input, int x, int y) { - Event event; - event.input_ = std::move(input); - event.type_ = Type::MouseMiddleMove; - event.mouse_ = {x, y}; + event.type_ = Type::Mouse; + event.mouse_ = mouse; return event; } @@ -90,54 +45,15 @@ Event Event::Special(std::string input) { } // static -Event Event::MouseRightMove(std::string input, int x, int y) { - Event event; - event.input_ = std::move(input); - event.type_ = Type::MouseRightMove; - event.mouse_ = {x, y}; - return event; -} - -// static -Event Event::MouseMiddleDown(std::string input, int x, int y) { - Event event; - event.input_ = std::move(input); - event.type_ = Type::MouseMiddleDown; - event.mouse_ = {x, y}; - return event; -} - Event Event::CursorReporting(std::string input, int x, int y) { Event event; event.input_ = std::move(input); event.type_ = Type::CursorReporting; - event.mouse_ = {x, y}; + event.cursor_.x = x; + event.cursor_.y = y; return event; } -bool Event::is_mouse() const { - switch (type_) { - case Type::Unknown: - case Type::Character: - case Type::CursorReporting: - return false; - case Type::MouseMove: - case Type::MouseUp: - case Type::MouseLeftDown: - case Type::MouseLeftMove: - case Type::MouseMiddleDown: - case Type::MouseMiddleMove: - case Type::MouseRightDown: - case Type::MouseRightMove: - return true; - }; -} - -void Event::MoveMouse(int dx, int dy) { - mouse_.x += dx; - mouse_.y += dy; -} - // --- Arrow --- const Event Event::ArrowLeft = Event::Special("\x1B[D"); const Event Event::ArrowRight = Event::Special("\x1B[C"); diff --git a/src/ftxui/component/menu.cpp b/src/ftxui/component/menu.cpp index c626f71..4eccfed 100644 --- a/src/ftxui/component/menu.cpp +++ b/src/ftxui/component/menu.cpp @@ -61,12 +61,13 @@ bool Menu::OnEvent(Event event) { bool Menu::OnMouseEvent(Event event) { for (int i = 0; i < boxes_.size(); ++i) { - if (!boxes_[i].Contain(event.mouse_x(), event.mouse_y())) + if (!boxes_[i].Contain(event.mouse().x, event.mouse().y)) continue; + TakeFocus(); focused = i; - if (event.is_mouse_left_down()) { - TakeFocus(); + if (event.mouse().button == Mouse::Left && + event.mouse().motion == Mouse::Released) { if (selected != i) { selected = i; on_change(); diff --git a/src/ftxui/component/radiobox.cpp b/src/ftxui/component/radiobox.cpp index 7147c86..d3ab38f 100644 --- a/src/ftxui/component/radiobox.cpp +++ b/src/ftxui/component/radiobox.cpp @@ -56,16 +56,14 @@ bool RadioBox::OnEvent(Event event) { bool RadioBox::OnMouseEvent(Event event) { for (int i = 0; i < boxes_.size(); ++i) { - if (!boxes_[i].Contain(event.mouse_x(), event.mouse_y())) + if (!boxes_[i].Contain(event.mouse().x, event.mouse().y)) continue; - if (event.is_mouse_move()) { - focused = i; - TakeFocus(); - return true; - } + focused = i; + TakeFocus(); - if (event.is_mouse_left_down()) { + if (event.mouse().button == Mouse::Left && + event.mouse().motion == Mouse::Pressed) { cursor_position = i; TakeFocus(); if (selected != i) { diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index c149421..d95af0d 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -146,19 +146,53 @@ void EventListener(std::atomic* quit, Sender out) { #endif -static const char SHOW_CURSOR[] = "\x1B[?25h"; -static const char HIDE_CURSOR[] = "\x1B[?25l"; +const std::string CSI = "\x1b["; -static const char ENABLE_LINE_WRAP[] = "\x1B[7h"; -static const char DISABLE_LINE_WRAP[] = "\x1B[7l"; +// DEC: Digital Equipment Corporation +enum class DECMode { + kLineWrap = 7, + kMouseX10 = 9, + kCursor = 25, + kMouseVt200 = 1000, + kMouseAnyEvent = 1003, + kMouseUtf8 = 1005, + kMouseSgrExtMode = 1006, + kMouseUrxvtMode = 1015, + kMouseSgrPixelsMode = 1016, + kAlternateScreen = 1049, +}; -static const char USE_ALTERNATIVE_SCREEN[] = "\x1B[?1049h"; -static const char USE_NORMAL_SCREEN[] = "\x1B[?1049l"; +// Device Status Report (DSR) { +enum class DSRMode { + kCursor = 6, +}; -static const char ENABLE_MOUSE[] = "\x1B[?1000;1003;1006;1015h"; -static const char DISABLE_MOUSE[] = "\x1B[?1000;1003;1006;1015l"; +const std::string Serialize(std::vector parameters) { + bool first = true; + std::string out; + for (DECMode parameter : parameters) { + if (!first) + out += ";"; + out += std::to_string(int(parameter)); + first = false; + } + return out; +} -static const char REQUEST_CURSOR_LINE[] = "\x1b[6n"; +// DEC Private Mode Set (DECSET) +const std::string Set(std::vector parameters) { + return CSI + "?" + Serialize(parameters) + "h"; +} + +// DEC Private Mode Reset (DECRST) +const std::string Reset(std::vector parameters) { + return CSI + "?" + Serialize(parameters) + "l"; +} + +// Device Status Report (DSR) +const std::string DeviceStatusReport(DSRMode ps) { + return CSI + std::to_string(int(ps)) + "n"; +} using SignalHandler = void(int); std::stack> on_exit_functions; @@ -279,24 +313,44 @@ void ScreenInteractive::Loop(Component* component) { install_signal_handler(SIGWINCH, OnResize); #endif + // Commit state: + auto flush = [&] { + Flush(); + on_exit_functions.push([] { Flush(); }); + }; + + auto enable = [&](std::vector parameters) { + std::cout << Set(parameters); + on_exit_functions.push([=] { std::cout << Reset(parameters); }); + }; + + auto disable = [&](std::vector parameters) { + std::cout << Reset(parameters); + on_exit_functions.push([=] { std::cout << Set(parameters); }); + }; + + flush(); + if (use_alternative_screen_) { - std::cout << USE_ALTERNATIVE_SCREEN; - on_exit_functions.push([] { std::cout << USE_NORMAL_SCREEN; }); + enable({ + DECMode::kAlternateScreen, + }); } - std::cout << ENABLE_MOUSE; - on_exit_functions.push([] { std::cout << DISABLE_MOUSE; }); + // On exit, reset cursor one line after the current drawing. + on_exit_functions.push( + [=] { std::cout << reset_cursor_position << std::endl; }); - // Hide the cursor and show it at exit. - std::cout << HIDE_CURSOR; - std::cout << DISABLE_LINE_WRAP; - Flush(); - on_exit_functions.push([&] { - std::cout << reset_cursor_position; - std::cout << SHOW_CURSOR; - std::cout << ENABLE_LINE_WRAP; - std::cout << std::endl; - Flush(); + disable({ + DECMode::kCursor, + DECMode::kLineWrap, + }); + + enable({ + //DECMode::kMouseVt200, + DECMode::kMouseAnyEvent, + DECMode::kMouseUtf8, + DECMode::kMouseSgrExtMode, }); auto event_listener = @@ -307,8 +361,8 @@ void ScreenInteractive::Loop(Component* component) { if (!event_receiver_->HasPending()) { std::cout << reset_cursor_position << ResetPosition(); static int i = -2; - if (i % 30 == 0) - std::cout << REQUEST_CURSOR_LINE; + if (i % 10 == 0) + std::cout << DeviceStatusReport(DSRMode::kCursor); ++i; Draw(component); std::cout << ToString() << set_cursor_position; @@ -321,13 +375,15 @@ void ScreenInteractive::Loop(Component* component) { break; if (event.is_cursor_reporting()) { - cursor_x_ = event.mouse_y(); - cursor_y_ = event.mouse_x(); + cursor_x_ = event.cursor_x(); + cursor_y_ = event.cursor_y(); continue; } - if (event.is_mouse()) - event.MoveMouse(-cursor_x_, -cursor_y_); + if (event.is_mouse()) { + event.mouse().x -= cursor_x_; + event.mouse().y -= cursor_y_; + } component->OnEvent(event); } diff --git a/src/ftxui/component/terminal_input_parser.cpp b/src/ftxui/component/terminal_input_parser.cpp index c566154..0864d74 100644 --- a/src/ftxui/component/terminal_input_parser.cpp +++ b/src/ftxui/component/terminal_input_parser.cpp @@ -36,62 +36,28 @@ void TerminalInputParser::Send(TerminalInputParser::Output output) { return; case DROP: - break; + pending_.clear(); + return; case CHARACTER: out_->Send(Event::Character(std::move(pending_))); - break; + return; case SPECIAL: out_->Send(Event::Special(std::move(pending_))); - break; + return; - case MOUSE_MOVE: - out_->Send( - Event::MouseMove(std::move(pending_), output.mouse.x, output.mouse.y)); - break; - - case MOUSE_UP: - out_->Send( - Event::MouseUp(std::move(pending_), output.mouse.x, output.mouse.y)); - break; - - case MOUSE_LEFT_DOWN: - out_->Send(Event::MouseLeftDown(std::move(pending_), output.mouse.x, - output.mouse.y)); - break; - - case MOUSE_LEFT_MOVE: - out_->Send(Event::MouseLeftMove(std::move(pending_), output.mouse.x, - output.mouse.y)); - break; - - case MOUSE_MIDDLE_DOWN: - out_->Send(Event::MouseMiddleDown(std::move(pending_), output.mouse.x, - output.mouse.y)); - break; - - case MOUSE_MIDDLE_MOVE: - out_->Send(Event::MouseMiddleMove(std::move(pending_), output.mouse.x, - output.mouse.y)); - break; - - case MOUSE_RIGHT_DOWN: - out_->Send(Event::MouseRightDown(std::move(pending_), output.mouse.x, - output.mouse.y)); - break; - - case MOUSE_RIGHT_MOVE: - out_->Send(Event::MouseRightMove(std::move(pending_), output.mouse.x, - output.mouse.y)); - break; + case MOUSE: + out_->Send(Event::Mouse(std::move(pending_), output.mouse)); + return; case CURSOR_REPORTING: - out_->Send(Event::CursorReporting(std::move(pending_), output.mouse.x, - output.mouse.y)); - break; + out_->Send(Event::CursorReporting(std::move(pending_), output.cursor.x, + output.cursor.y)); + return; } - pending_.clear(); + // NOT_REACHED(). + } TerminalInputParser::Output TerminalInputParser::Parse() { @@ -166,12 +132,18 @@ TerminalInputParser::Output TerminalInputParser::ParseDCS() { } TerminalInputParser::Output TerminalInputParser::ParseCSI() { + bool altered = false; int argument; std::vector arguments; while (true) { if (!Eat()) return UNCOMPLETED; + if (Current() == '<') { + altered = true; + continue; + } + if (Current() >= '0' && Current() <= '9') { argument *= 10; argument += int(Current() - '0'); @@ -189,7 +161,9 @@ TerminalInputParser::Output TerminalInputParser::ParseCSI() { argument = 0; switch (Current()) { case 'M': - return ParseMouse(std::move(arguments)); + return ParseMouse(altered, true, std::move(arguments)); + case 'm': + return ParseMouse(altered, false, std::move(arguments)); case 'R': return ParseCursorReporting(std::move(arguments)); default: @@ -219,41 +193,34 @@ TerminalInputParser::Output TerminalInputParser::ParseOSC() { } TerminalInputParser::Output TerminalInputParser::ParseMouse( + bool altered, + bool pressed, std::vector arguments) { if (arguments.size() != 3) return SPECIAL; - switch(arguments[0]) { - case 32: - return Output(MOUSE_LEFT_DOWN, arguments[1], arguments[2]); - case 64: - return Output(MOUSE_LEFT_MOVE, arguments[1], arguments[2]); - case 33: - return Output(MOUSE_MIDDLE_DOWN, arguments[1], arguments[2]); - case 65: - return Output(MOUSE_MIDDLE_MOVE, arguments[1], arguments[2]); + (void)altered; - case 34: - return Output(MOUSE_RIGHT_DOWN, arguments[1], arguments[2]); - case 66: - return Output(MOUSE_RIGHT_MOVE, arguments[1], arguments[2]); - - case 35: - return Output(MOUSE_UP, arguments[1], arguments[2]); - case 67: - return Output(MOUSE_MOVE, arguments[1], arguments[2]); - - default: - return Output(MOUSE_MOVE, arguments[1], arguments[2]); - } - return SPECIAL; + Output output(MOUSE); + output.mouse.button = Mouse::Button((arguments[0] & 3) + // + ((arguments[0] & 64) >> 4)); + output.mouse.motion = Mouse::Motion(pressed); + output.mouse.shift = arguments[0] & 4; + output.mouse.meta = arguments[0] & 8; + output.mouse.control = arguments[0] & 16; + output.mouse.x = arguments[1]; + output.mouse.y = arguments[2]; + return output; } TerminalInputParser::Output TerminalInputParser::ParseCursorReporting( std::vector arguments) { if (arguments.size() != 2) return SPECIAL; - return Output(CURSOR_REPORTING, arguments[0], arguments[1]); + Output output(CURSOR_REPORTING); + output.cursor.y = arguments[0]; + output.cursor.x = arguments[1]; + return output; } } // namespace ftxui diff --git a/src/ftxui/component/terminal_input_parser.hpp b/src/ftxui/component/terminal_input_parser.hpp index bb63ad8..d378694 100644 --- a/src/ftxui/component/terminal_input_parser.hpp +++ b/src/ftxui/component/terminal_input_parser.hpp @@ -24,31 +24,23 @@ class TerminalInputParser { DROP, CHARACTER, SPECIAL, - MOUSE_UP, - MOUSE_MOVE, - MOUSE_LEFT_DOWN, - MOUSE_LEFT_MOVE, - MOUSE_MIDDLE_DOWN, - MOUSE_MIDDLE_MOVE, - MOUSE_RIGHT_DOWN, - MOUSE_RIGHT_MOVE, + MOUSE, CURSOR_REPORTING, }; - struct Mouse { + struct CursorReporting { int x; int y; - Mouse(int x, int y) : x(x), y(y) {} }; struct Output { Type type; union { Mouse mouse; + CursorReporting cursor; }; Output(Type type) : type(type) {} - Output(Type type, int x, int y) : type(type), mouse(x, y) {} }; void Send(Output type); @@ -58,7 +50,7 @@ class TerminalInputParser { Output ParseDCS(); Output ParseCSI(); Output ParseOSC(); - Output ParseMouse(std::vector arguments); + Output ParseMouse(bool altered, bool pressed, std::vector arguments); Output ParseCursorReporting(std::vector arguments); Sender out_; diff --git a/src/ftxui/component/terminal_input_parser_test.cpp b/src/ftxui/component/terminal_input_parser_test.cpp index 80fbd29..6212a1c 100644 --- a/src/ftxui/component/terminal_input_parser_test.cpp +++ b/src/ftxui/component/terminal_input_parser_test.cpp @@ -66,22 +66,81 @@ TEST(Event, EscapeKeyEnoughWait) { EXPECT_FALSE(event_receiver->Receive(&received)); } -TEST(Event, GnomeTerminalMouse) { +TEST(Event, MouseLeftClick) { auto event_receiver = MakeReceiver(); { auto parser = TerminalInputParser(event_receiver->MakeSender()); parser.Add('\x1B'); parser.Add('['); - parser.Add('<'); - parser.Add('1'); + parser.Add('3'); + parser.Add('2'); parser.Add(';'); parser.Add('1'); + parser.Add('2'); + parser.Add(';'); + parser.Add('4'); + parser.Add('2'); parser.Add('M'); } Event received; EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_TRUE(received.is_mouse()); + EXPECT_EQ(Mouse::Left, received.mouse().button); + EXPECT_EQ(12, received.mouse().x); + EXPECT_EQ(42, received.mouse().y); + EXPECT_FALSE(event_receiver->Receive(&received)); +} + +TEST(Event, MouseMiddleClick) { + auto event_receiver = MakeReceiver(); + { + auto parser = TerminalInputParser(event_receiver->MakeSender()); + parser.Add('\x1B'); + parser.Add('['); + parser.Add('3'); + parser.Add('3'); + parser.Add(';'); + parser.Add('1'); + parser.Add('2'); + parser.Add(';'); + parser.Add('4'); + parser.Add('2'); + parser.Add('M'); + } + + Event received; + EXPECT_TRUE(event_receiver->Receive(&received)); + EXPECT_TRUE(received.is_mouse()); + EXPECT_EQ(Mouse::Middle, received.mouse().button); + EXPECT_EQ(12, received.mouse().x); + EXPECT_EQ(42, received.mouse().y); + EXPECT_FALSE(event_receiver->Receive(&received)); +} + +TEST(Event, MouseRightClick) { + auto event_receiver = MakeReceiver(); + { + auto parser = TerminalInputParser(event_receiver->MakeSender()); + parser.Add('\x1B'); + parser.Add('['); + parser.Add('3'); + parser.Add('4'); + parser.Add(';'); + parser.Add('1'); + parser.Add('2'); + parser.Add(';'); + parser.Add('4'); + parser.Add('2'); + parser.Add('M'); + } + + Event received; + EXPECT_TRUE(event_receiver->Receive(&received)); + EXPECT_TRUE(received.is_mouse()); + EXPECT_EQ(Mouse::Right, received.mouse().button); + EXPECT_EQ(12, received.mouse().x); + EXPECT_EQ(42, received.mouse().y); EXPECT_FALSE(event_receiver->Receive(&received)); } diff --git a/src/ftxui/component/toggle.cpp b/src/ftxui/component/toggle.cpp index 525cded..5c8b32e 100644 --- a/src/ftxui/component/toggle.cpp +++ b/src/ftxui/component/toggle.cpp @@ -60,12 +60,13 @@ bool Toggle::OnEvent(Event event) { bool Toggle::OnMouseEvent(Event event) { for (int i = 0; i < boxes_.size(); ++i) { - if (!boxes_[i].Contain(event.mouse_x(), event.mouse_y())) + if (!boxes_[i].Contain(event.mouse().x, event.mouse().y)) continue; TakeFocus(); focused = i; - if (event.is_mouse_left_down()) { + if (event.mouse().button == Mouse::Left && + event.mouse().motion == Mouse::Pressed) { TakeFocus(); if (selected != i) { selected = i;