Improve mouse support

This commit is contained in:
ArthurSonzogni 2021-04-25 15:22:38 +02:00
parent 8037a5fa5f
commit 0b9b6c692a
No known key found for this signature in database
GPG Key ID: 41D98248C074CD6C
14 changed files with 326 additions and 304 deletions

View File

@ -83,6 +83,7 @@ add_library(component
include/ftxui/component/event.hpp include/ftxui/component/event.hpp
include/ftxui/component/input.hpp include/ftxui/component/input.hpp
include/ftxui/component/menu.hpp include/ftxui/component/menu.hpp
include/ftxui/component/mouse.hpp
include/ftxui/component/radiobox.hpp include/ftxui/component/radiobox.hpp
include/ftxui/component/receiver.hpp include/ftxui/component/receiver.hpp
include/ftxui/component/screen_interactive.hpp include/ftxui/component/screen_interactive.hpp

View File

@ -11,6 +11,60 @@
using namespace ftxui; 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 { class DrawKey : public Component {
public: public:
~DrawKey() override = default; ~DrawKey() override = default;
@ -18,49 +72,7 @@ class DrawKey : public Component {
Element Render() override { Element Render() override {
Elements children; Elements children;
for (size_t i = std::max(0, (int)keys.size() - 20); i < keys.size(); ++i) { for (size_t i = std::max(0, (int)keys.size() - 20); i < keys.size(); ++i) {
std::wstring code; children.push_back(text(Stringify(keys[i])));
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));
} }
return window(text(L"keys"), vbox(std::move(children))); return window(text(L"keys"), vbox(std::move(children)));
} }

View File

@ -2,6 +2,7 @@
#define FTXUI_COMPONENT_EVENT_HPP #define FTXUI_COMPONENT_EVENT_HPP
#include <array> #include <array>
#include <ftxui/component/mouse.hpp>
#include <ftxui/component/receiver.hpp> #include <ftxui/component/receiver.hpp>
#include <functional> #include <functional>
#include <string> #include <string>
@ -28,14 +29,7 @@ struct Event {
static Event Character(std::string); static Event Character(std::string);
static Event Special(std::string); static Event Special(std::string);
static Event MouseMove(std::string, int x, int y); static Event Mouse(std::string, Mouse mouse);
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 CursorReporting(std::string, int x, int y); static Event CursorReporting(std::string, int x, int y);
// --- Arrow --- // --- Arrow ---
@ -60,51 +54,38 @@ struct Event {
bool is_character() const { return type_ == Type::Character;} bool is_character() const { return type_ == Type::Character;}
wchar_t character() const { return character_; } wchar_t character() const { return character_; }
bool is_mouse() const; bool is_mouse() const { return type_ == Type::Mouse; }
bool is_mouse_left_down() const { return type_ == Type::MouseLeftDown; } struct Mouse& mouse() {
bool is_mouse_left_move() const { return type_ == Type::MouseLeftMove; } return mouse_;
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_cursor_reporting() const { return type_ == Type::CursorReporting; } bool is_cursor_reporting() const { return type_ == Type::CursorReporting; }
int mouse_x() const { return mouse_.x; } int cursor_x() const { return cursor_.x; }
int mouse_y() const { return mouse_.y; } int cursor_y() const { return cursor_.y; }
const std::string& input() const { return input_; } const std::string& input() const { return input_; }
bool operator==(const Event& other) const { return input_ == other.input_; } bool operator==(const Event& other) const { return input_ == other.input_; }
void MoveMouse(int dx, int dy);
//--- State section ---------------------------------------------------------- //--- State section ----------------------------------------------------------
private: private:
enum class Type { enum class Type {
Unknown, Unknown,
Character, Character,
MouseMove, Mouse,
MouseUp,
MouseLeftDown,
MouseLeftMove,
MouseMiddleDown,
MouseMiddleMove,
MouseRightDown,
MouseRightMove,
CursorReporting, CursorReporting,
}; };
Type type_ = Type::Unknown;
struct Mouse { struct Cursor {
int x; int x;
int y; int y;
}; };
Type type_ = Type::Unknown;
union { union {
wchar_t character_ = U'?'; wchar_t character_ = U'?';
Mouse mouse_; struct Mouse mouse_;
struct Cursor cursor_;
}; };
std::string input_; std::string input_;
}; };

View File

@ -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.

View File

@ -10,12 +10,11 @@ Element Button::Render() {
} }
bool Button::OnEvent(Event event) { bool Button::OnEvent(Event event) {
if (event.is_mouse() && box_.Contain(event.mouse_x(), event.mouse_y())) { if (event.is_mouse() && box_.Contain(event.mouse().x, event.mouse().y)) {
if (event.is_mouse_move()) {
TakeFocus(); TakeFocus();
return true;
} if (event.mouse().button == Mouse::Left &&
if (event.is_mouse_left_down()) { event.mouse().motion == Mouse::Pressed) {
on_click(); on_click();
return true; return true;
} }

View File

@ -26,15 +26,13 @@ bool CheckBox::OnEvent(Event event) {
} }
bool CheckBox::OnMouseEvent(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; return false;
if (event.is_mouse_move()) {
TakeFocus(); TakeFocus();
return true;
}
if (event.is_mouse_left_down()) { if (event.mouse().button == Mouse::Left &&
event.mouse().motion == Mouse::Pressed) {
state = !state; state = !state;
on_change(); on_change();
return true; return true;

View File

@ -29,56 +29,11 @@ Event Event::Character(wchar_t c) {
} }
// static // static
Event Event::MouseMove(std::string input, int x, int y) { Event Event::Mouse(std::string input, struct Mouse mouse) {
Event event; Event event;
event.input_ = std::move(input); event.input_ = std::move(input);
event.type_ = Type::MouseMove; event.type_ = Type::Mouse;
event.mouse_ = {x, y}; event.mouse_ = mouse;
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};
return event; return event;
} }
@ -90,54 +45,15 @@ Event Event::Special(std::string input) {
} }
// static // 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::CursorReporting(std::string input, int x, int y) {
Event event; Event event;
event.input_ = std::move(input); event.input_ = std::move(input);
event.type_ = Type::CursorReporting; event.type_ = Type::CursorReporting;
event.mouse_ = {x, y}; event.cursor_.x = x;
event.cursor_.y = y;
return event; 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 --- // --- Arrow ---
const Event Event::ArrowLeft = Event::Special("\x1B[D"); const Event Event::ArrowLeft = Event::Special("\x1B[D");
const Event Event::ArrowRight = Event::Special("\x1B[C"); const Event Event::ArrowRight = Event::Special("\x1B[C");

View File

@ -61,12 +61,13 @@ bool Menu::OnEvent(Event event) {
bool Menu::OnMouseEvent(Event event) { bool Menu::OnMouseEvent(Event event) {
for (int i = 0; i < boxes_.size(); ++i) { 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; continue;
focused = i;
if (event.is_mouse_left_down()) {
TakeFocus(); TakeFocus();
focused = i;
if (event.mouse().button == Mouse::Left &&
event.mouse().motion == Mouse::Released) {
if (selected != i) { if (selected != i) {
selected = i; selected = i;
on_change(); on_change();

View File

@ -56,16 +56,14 @@ bool RadioBox::OnEvent(Event event) {
bool RadioBox::OnMouseEvent(Event event) { bool RadioBox::OnMouseEvent(Event event) {
for (int i = 0; i < boxes_.size(); ++i) { 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; continue;
if (event.is_mouse_move()) {
focused = i; focused = i;
TakeFocus(); TakeFocus();
return true;
}
if (event.is_mouse_left_down()) { if (event.mouse().button == Mouse::Left &&
event.mouse().motion == Mouse::Pressed) {
cursor_position = i; cursor_position = i;
TakeFocus(); TakeFocus();
if (selected != i) { if (selected != i) {

View File

@ -146,19 +146,53 @@ void EventListener(std::atomic<bool>* quit, Sender<Event> out) {
#endif #endif
static const char SHOW_CURSOR[] = "\x1B[?25h"; const std::string CSI = "\x1b[";
static const char HIDE_CURSOR[] = "\x1B[?25l";
static const char ENABLE_LINE_WRAP[] = "\x1B[7h"; // DEC: Digital Equipment Corporation
static const char DISABLE_LINE_WRAP[] = "\x1B[7l"; 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"; // Device Status Report (DSR) {
static const char USE_NORMAL_SCREEN[] = "\x1B[?1049l"; enum class DSRMode {
kCursor = 6,
};
static const char ENABLE_MOUSE[] = "\x1B[?1000;1003;1006;1015h"; const std::string Serialize(std::vector<DECMode> parameters) {
static const char DISABLE_MOUSE[] = "\x1B[?1000;1003;1006;1015l"; 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<DECMode> parameters) {
return CSI + "?" + Serialize(parameters) + "h";
}
// DEC Private Mode Reset (DECRST)
const std::string Reset(std::vector<DECMode> 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); using SignalHandler = void(int);
std::stack<std::function<void()>> on_exit_functions; std::stack<std::function<void()>> on_exit_functions;
@ -279,24 +313,44 @@ void ScreenInteractive::Loop(Component* component) {
install_signal_handler(SIGWINCH, OnResize); install_signal_handler(SIGWINCH, OnResize);
#endif #endif
// Commit state:
auto flush = [&] {
Flush();
on_exit_functions.push([] { Flush(); });
};
auto enable = [&](std::vector<DECMode> parameters) {
std::cout << Set(parameters);
on_exit_functions.push([=] { std::cout << Reset(parameters); });
};
auto disable = [&](std::vector<DECMode> parameters) {
std::cout << Reset(parameters);
on_exit_functions.push([=] { std::cout << Set(parameters); });
};
flush();
if (use_alternative_screen_) { if (use_alternative_screen_) {
std::cout << USE_ALTERNATIVE_SCREEN; enable({
on_exit_functions.push([] { std::cout << USE_NORMAL_SCREEN; }); DECMode::kAlternateScreen,
});
} }
std::cout << ENABLE_MOUSE; // On exit, reset cursor one line after the current drawing.
on_exit_functions.push([] { std::cout << DISABLE_MOUSE; }); on_exit_functions.push(
[=] { std::cout << reset_cursor_position << std::endl; });
// Hide the cursor and show it at exit. disable({
std::cout << HIDE_CURSOR; DECMode::kCursor,
std::cout << DISABLE_LINE_WRAP; DECMode::kLineWrap,
Flush(); });
on_exit_functions.push([&] {
std::cout << reset_cursor_position; enable({
std::cout << SHOW_CURSOR; //DECMode::kMouseVt200,
std::cout << ENABLE_LINE_WRAP; DECMode::kMouseAnyEvent,
std::cout << std::endl; DECMode::kMouseUtf8,
Flush(); DECMode::kMouseSgrExtMode,
}); });
auto event_listener = auto event_listener =
@ -307,8 +361,8 @@ void ScreenInteractive::Loop(Component* component) {
if (!event_receiver_->HasPending()) { if (!event_receiver_->HasPending()) {
std::cout << reset_cursor_position << ResetPosition(); std::cout << reset_cursor_position << ResetPosition();
static int i = -2; static int i = -2;
if (i % 30 == 0) if (i % 10 == 0)
std::cout << REQUEST_CURSOR_LINE; std::cout << DeviceStatusReport(DSRMode::kCursor);
++i; ++i;
Draw(component); Draw(component);
std::cout << ToString() << set_cursor_position; std::cout << ToString() << set_cursor_position;
@ -321,13 +375,15 @@ void ScreenInteractive::Loop(Component* component) {
break; break;
if (event.is_cursor_reporting()) { if (event.is_cursor_reporting()) {
cursor_x_ = event.mouse_y(); cursor_x_ = event.cursor_x();
cursor_y_ = event.mouse_x(); cursor_y_ = event.cursor_y();
continue; continue;
} }
if (event.is_mouse()) if (event.is_mouse()) {
event.MoveMouse(-cursor_x_, -cursor_y_); event.mouse().x -= cursor_x_;
event.mouse().y -= cursor_y_;
}
component->OnEvent(event); component->OnEvent(event);
} }

View File

@ -36,62 +36,28 @@ void TerminalInputParser::Send(TerminalInputParser::Output output) {
return; return;
case DROP: case DROP:
break; pending_.clear();
return;
case CHARACTER: case CHARACTER:
out_->Send(Event::Character(std::move(pending_))); out_->Send(Event::Character(std::move(pending_)));
break; return;
case SPECIAL: case SPECIAL:
out_->Send(Event::Special(std::move(pending_))); out_->Send(Event::Special(std::move(pending_)));
break; return;
case MOUSE_MOVE: case MOUSE:
out_->Send( out_->Send(Event::Mouse(std::move(pending_), output.mouse));
Event::MouseMove(std::move(pending_), output.mouse.x, output.mouse.y)); return;
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 CURSOR_REPORTING: case CURSOR_REPORTING:
out_->Send(Event::CursorReporting(std::move(pending_), output.mouse.x, out_->Send(Event::CursorReporting(std::move(pending_), output.cursor.x,
output.mouse.y)); output.cursor.y));
break; return;
} }
pending_.clear(); // NOT_REACHED().
} }
TerminalInputParser::Output TerminalInputParser::Parse() { TerminalInputParser::Output TerminalInputParser::Parse() {
@ -166,12 +132,18 @@ TerminalInputParser::Output TerminalInputParser::ParseDCS() {
} }
TerminalInputParser::Output TerminalInputParser::ParseCSI() { TerminalInputParser::Output TerminalInputParser::ParseCSI() {
bool altered = false;
int argument; int argument;
std::vector<int> arguments; std::vector<int> arguments;
while (true) { while (true) {
if (!Eat()) if (!Eat())
return UNCOMPLETED; return UNCOMPLETED;
if (Current() == '<') {
altered = true;
continue;
}
if (Current() >= '0' && Current() <= '9') { if (Current() >= '0' && Current() <= '9') {
argument *= 10; argument *= 10;
argument += int(Current() - '0'); argument += int(Current() - '0');
@ -189,7 +161,9 @@ TerminalInputParser::Output TerminalInputParser::ParseCSI() {
argument = 0; argument = 0;
switch (Current()) { switch (Current()) {
case 'M': 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': case 'R':
return ParseCursorReporting(std::move(arguments)); return ParseCursorReporting(std::move(arguments));
default: default:
@ -219,41 +193,34 @@ TerminalInputParser::Output TerminalInputParser::ParseOSC() {
} }
TerminalInputParser::Output TerminalInputParser::ParseMouse( TerminalInputParser::Output TerminalInputParser::ParseMouse(
bool altered,
bool pressed,
std::vector<int> arguments) { std::vector<int> arguments) {
if (arguments.size() != 3) if (arguments.size() != 3)
return SPECIAL; 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: (void)altered;
return Output(MOUSE_MIDDLE_DOWN, arguments[1], arguments[2]);
case 65:
return Output(MOUSE_MIDDLE_MOVE, arguments[1], arguments[2]);
case 34: Output output(MOUSE);
return Output(MOUSE_RIGHT_DOWN, arguments[1], arguments[2]); output.mouse.button = Mouse::Button((arguments[0] & 3) + //
case 66: ((arguments[0] & 64) >> 4));
return Output(MOUSE_RIGHT_MOVE, arguments[1], arguments[2]); output.mouse.motion = Mouse::Motion(pressed);
output.mouse.shift = arguments[0] & 4;
case 35: output.mouse.meta = arguments[0] & 8;
return Output(MOUSE_UP, arguments[1], arguments[2]); output.mouse.control = arguments[0] & 16;
case 67: output.mouse.x = arguments[1];
return Output(MOUSE_MOVE, arguments[1], arguments[2]); output.mouse.y = arguments[2];
return output;
default:
return Output(MOUSE_MOVE, arguments[1], arguments[2]);
}
return SPECIAL;
} }
TerminalInputParser::Output TerminalInputParser::ParseCursorReporting( TerminalInputParser::Output TerminalInputParser::ParseCursorReporting(
std::vector<int> arguments) { std::vector<int> arguments) {
if (arguments.size() != 2) if (arguments.size() != 2)
return SPECIAL; 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 } // namespace ftxui

View File

@ -24,31 +24,23 @@ class TerminalInputParser {
DROP, DROP,
CHARACTER, CHARACTER,
SPECIAL, SPECIAL,
MOUSE_UP, MOUSE,
MOUSE_MOVE,
MOUSE_LEFT_DOWN,
MOUSE_LEFT_MOVE,
MOUSE_MIDDLE_DOWN,
MOUSE_MIDDLE_MOVE,
MOUSE_RIGHT_DOWN,
MOUSE_RIGHT_MOVE,
CURSOR_REPORTING, CURSOR_REPORTING,
}; };
struct Mouse { struct CursorReporting {
int x; int x;
int y; int y;
Mouse(int x, int y) : x(x), y(y) {}
}; };
struct Output { struct Output {
Type type; Type type;
union { union {
Mouse mouse; Mouse mouse;
CursorReporting cursor;
}; };
Output(Type type) : type(type) {} Output(Type type) : type(type) {}
Output(Type type, int x, int y) : type(type), mouse(x, y) {}
}; };
void Send(Output type); void Send(Output type);
@ -58,7 +50,7 @@ class TerminalInputParser {
Output ParseDCS(); Output ParseDCS();
Output ParseCSI(); Output ParseCSI();
Output ParseOSC(); Output ParseOSC();
Output ParseMouse(std::vector<int> arguments); Output ParseMouse(bool altered, bool pressed, std::vector<int> arguments);
Output ParseCursorReporting(std::vector<int> arguments); Output ParseCursorReporting(std::vector<int> arguments);
Sender<Event> out_; Sender<Event> out_;

View File

@ -66,22 +66,81 @@ TEST(Event, EscapeKeyEnoughWait) {
EXPECT_FALSE(event_receiver->Receive(&received)); EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, GnomeTerminalMouse) { TEST(Event, MouseLeftClick) {
auto event_receiver = MakeReceiver<Event>(); auto event_receiver = MakeReceiver<Event>();
{ {
auto parser = TerminalInputParser(event_receiver->MakeSender()); auto parser = TerminalInputParser(event_receiver->MakeSender());
parser.Add('\x1B'); parser.Add('\x1B');
parser.Add('['); parser.Add('[');
parser.Add('<'); parser.Add('3');
parser.Add('1'); parser.Add('2');
parser.Add(';'); parser.Add(';');
parser.Add('1'); parser.Add('1');
parser.Add('2');
parser.Add(';');
parser.Add('4');
parser.Add('2');
parser.Add('M'); parser.Add('M');
} }
Event received; Event received;
EXPECT_TRUE(event_receiver->Receive(&received)); EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_TRUE(received.is_mouse()); 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<Event>();
{
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<Event>();
{
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)); EXPECT_FALSE(event_receiver->Receive(&received));
} }

View File

@ -60,12 +60,13 @@ bool Toggle::OnEvent(Event event) {
bool Toggle::OnMouseEvent(Event event) { bool Toggle::OnMouseEvent(Event event) {
for (int i = 0; i < boxes_.size(); ++i) { 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; continue;
TakeFocus(); TakeFocus();
focused = i; focused = i;
if (event.is_mouse_left_down()) { if (event.mouse().button == Mouse::Left &&
event.mouse().motion == Mouse::Pressed) {
TakeFocus(); TakeFocus();
if (selected != i) { if (selected != i) {
selected = i; selected = i;