Improve mouse support for menu and toggle.

This commit is contained in:
ArthurSonzogni 2021-04-24 18:16:13 +02:00
parent 890a41a64c
commit 8037a5fa5f
No known key found for this signature in database
GPG Key ID: 41D98248C074CD6C
14 changed files with 124 additions and 36 deletions

View File

@ -13,7 +13,7 @@ class MyComponent : public Component {
MyComponent() { MyComponent() {
Add(&container); Add(&container);
for (Menu* menu : {&menu_1, &menu_2, &menu_3, &menu_4, &menu_5, &menu_6}) { for (Menu* menu : {&menu_1, &menu_2, &menu_3, &menu_4, &menu_5, &menu_6,}) {
container.Add(menu); container.Add(menu);
menu->entries = { menu->entries = {
L"Monkey", L"Dog", L"Cat", L"Bird", L"Elephant", L"Monkey", L"Dog", L"Cat", L"Bird", L"Elephant",
@ -21,22 +21,27 @@ class MyComponent : public Component {
menu->on_enter = [this]() { on_enter(); }; menu->on_enter = [this]() { on_enter(); };
} }
menu_2.selected_style = color(Color::Blue);
menu_2.focused_style = bold | color(Color::Blue); menu_2.focused_style = bold | color(Color::Blue);
menu_2.selected_style = color(Color::Blue);
menu_2.selected_focused_style = bold | color(Color::Blue);
menu_3.selected_style = color(Color::Blue); menu_3.selected_style = color(Color::Blue);
menu_3.focused_style = bgcolor(Color::Blue); menu_3.focused_style = bgcolor(Color::Blue);
menu_3.selected_focused_style = bgcolor(Color::Blue);
menu_4.selected_style = bgcolor(Color::Blue); menu_4.selected_style = bgcolor(Color::Blue);
menu_4.focused_style = bgcolor(Color::BlueLight); menu_4.focused_style = bgcolor(Color::BlueLight);
menu_4.selected_focused_style = bgcolor(Color::BlueLight);
menu_5.normal_style = bgcolor(Color::Blue); menu_5.normal_style = bgcolor(Color::Blue);
menu_5.selected_style = bgcolor(Color::Yellow); menu_5.selected_style = bgcolor(Color::Yellow);
menu_5.focused_style = bgcolor(Color::Red); menu_5.focused_style = bgcolor(Color::Red);
menu_5.selected_focused_style = bgcolor(Color::Red);
menu_6.normal_style = dim | color(Color::Blue); menu_6.normal_style = dim | color(Color::Blue);
menu_6.selected_style = color(Color::Blue); menu_6.selected_style = color(Color::Blue);
menu_6.focused_style = bold | color(Color::Blue); menu_6.focused_style = bold | color(Color::Blue);
menu_6.selected_focused_style = bold | color(Color::Blue);
} }
std::function<void()> on_enter = []() {}; std::function<void()> on_enter = []() {};

View File

@ -17,7 +17,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() - 10); i < keys.size(); ++i) { for (size_t i = std::max(0, (int)keys.size() - 20); i < keys.size(); ++i) {
std::wstring code; std::wstring code;
for (auto& it : keys[i].input()) for (auto& it : keys[i].input())
code += L" " + std::to_wstring((unsigned int)it); code += L" " + std::to_wstring((unsigned int)it);

View File

@ -36,6 +36,7 @@ struct Event {
static Event MouseMiddleDown(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 MouseRightMove(std::string, int x, int y);
static Event MouseRightDown(std::string, int x, int y); static Event MouseRightDown(std::string, int x, int y);
static Event CursorReporting(std::string, int x, int y);
// --- Arrow --- // --- Arrow ---
static const Event ArrowLeft; static const Event ArrowLeft;
@ -68,6 +69,7 @@ struct Event {
bool is_mouse_right_move() const { return type_ == Type::MouseRightMove; } bool is_mouse_right_move() const { return type_ == Type::MouseRightMove; }
bool is_mouse_up() const { return type_ == Type::MouseUp; } bool is_mouse_up() const { return type_ == Type::MouseUp; }
bool is_mouse_move() const { return type_ == Type::MouseMove; } bool is_mouse_move() const { return type_ == Type::MouseMove; }
bool is_cursor_reporting() const { return type_ == Type::CursorReporting; }
int mouse_x() const { return mouse_.x; } int mouse_x() const { return mouse_.x; }
int mouse_y() const { return mouse_.y; } int mouse_y() const { return mouse_.y; }
@ -90,6 +92,7 @@ struct Event {
MouseMiddleMove, MouseMiddleMove,
MouseRightDown, MouseRightDown,
MouseRightMove, MouseRightMove,
CursorReporting,
}; };
struct Mouse { struct Mouse {

View File

@ -19,10 +19,12 @@ class Menu : public Component {
// State. // State.
std::vector<std::wstring> entries = {}; std::vector<std::wstring> entries = {};
int selected = 0; int selected = 0;
int focused = 0;
Decorator normal_style = nothing;
Decorator focused_style = inverted; Decorator focused_style = inverted;
Decorator selected_style = bold; Decorator selected_style = bold;
Decorator normal_style = nothing; Decorator selected_focused_style = focused_style | selected_style;
// State update callback. // State update callback.
std::function<void()> on_change = []() {}; std::function<void()> on_change = []() {};

View File

@ -52,6 +52,9 @@ class ScreenInteractive : public Screen {
std::string reset_cursor_position; std::string reset_cursor_position;
std::atomic<bool> quit_ = false; std::atomic<bool> quit_ = false;
int cursor_x_ = 0;
int cursor_y_ = 0;
}; };
} // namespace ftxui } // namespace ftxui

View File

@ -16,12 +16,14 @@ class Toggle : public Component {
~Toggle() override = default; ~Toggle() override = default;
// State. // State.
int selected = 0;
std::vector<std::wstring> entries = {L"On", L"Off"}; std::vector<std::wstring> entries = {L"On", L"Off"};
int selected = 0;
int focused = 0;
Decorator normal_style = dim;
Decorator focused_style = inverted; Decorator focused_style = inverted;
Decorator selected_style = bold; Decorator selected_style = bold;
Decorator normal_style = dim; Decorator selected_focused_style = focused_style | selected_style;
// Callback. // Callback.
std::function<void()> on_change = []() {}; std::function<void()> on_change = []() {};

View File

@ -5,10 +5,8 @@
namespace ftxui { namespace ftxui {
Element Button::Render() { Element Button::Render() {
return text(label) | // auto style = Focused() ? inverted : nothing;
border | // return text(label) | border | style | reflect(box_);
(Focused() ? inverted : nothing) | //
reflect(box_);
} }
bool Button::OnEvent(Event event) { bool Button::OnEvent(Event event) {

View File

@ -107,10 +107,19 @@ Event Event::MouseMiddleDown(std::string input, int x, int y) {
return event; 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};
return event;
}
bool Event::is_mouse() const { bool Event::is_mouse() const {
switch (type_) { switch (type_) {
case Type::Unknown: case Type::Unknown:
case Type::Character: case Type::Character:
case Type::CursorReporting:
return false; return false;
case Type::MouseMove: case Type::MouseMove:
case Type::MouseUp: case Type::MouseUp:

View File

@ -6,16 +6,21 @@
namespace ftxui { namespace ftxui {
Element Menu::Render() { Element Menu::Render() {
std::vector<Element> elements; Elements elements;
bool is_focused = Focused(); bool is_menu_focused = Focused();
boxes_.resize(entries.size()); boxes_.resize(entries.size());
for (size_t i = 0; i < entries.size(); ++i) { for (size_t i = 0; i < entries.size(); ++i) {
auto style = (selected != int(i)) bool is_focused = (focused == int(i)) && is_menu_focused;
? normal_style bool is_selected = (selected == int(i));
: is_focused ? focused_style : selected_style;
auto focused = (selected != int(i)) ? nothing : is_focused ? focus : select; auto style = is_selected
auto icon = (selected != int(i)) ? L" " : L"> "; ? (is_focused ? selected_focused_style : selected_style)
elements.push_back(text(icon + entries[i]) | style | focused | : (is_focused ? focused_style : normal_style);
auto focus_management = !is_selected ? nothing
: is_menu_focused ? focus
: select;
auto icon = is_selected ? L"> " : L" ";
elements.push_back(text(icon + entries[i]) | style | focus_management |
reflect(boxes_[i])); reflect(boxes_[i]));
} }
return vbox(std::move(elements)); return vbox(std::move(elements));
@ -41,6 +46,7 @@ bool Menu::OnEvent(Event event) {
selected = std::max(0, std::min(int(entries.size()) - 1, selected)); selected = std::max(0, std::min(int(entries.size()) - 1, selected));
if (selected != old_selected) { if (selected != old_selected) {
focused = selected;
on_change(); on_change();
return true; return true;
} }
@ -58,10 +64,11 @@ bool Menu::OnMouseEvent(Event event) {
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()) { if (event.is_mouse_left_down()) {
TakeFocus();
if (selected != i) { if (selected != i) {
selected = i; selected = i;
TakeFocus();
on_change(); on_change();
} }
return true; return true;

View File

@ -156,7 +156,9 @@ static const char USE_ALTERNATIVE_SCREEN[] = "\x1B[?1049h";
static const char USE_NORMAL_SCREEN[] = "\x1B[?1049l"; static const char USE_NORMAL_SCREEN[] = "\x1B[?1049l";
static const char ENABLE_MOUSE[] = "\x1B[?1000;1003;1006;1015h"; static const char ENABLE_MOUSE[] = "\x1B[?1000;1003;1006;1015h";
static const char DISABLE_MOUSE[] = "\x1B[?1000;1003;10006;1015l"; static const char DISABLE_MOUSE[] = "\x1B[?1000;1003;1006;1015l";
static const char REQUEST_CURSOR_LINE[] = "\x1b[6n";
using SignalHandler = void(int); using SignalHandler = void(int);
std::stack<std::function<void()>> on_exit_functions; std::stack<std::function<void()>> on_exit_functions;
@ -304,17 +306,30 @@ void ScreenInteractive::Loop(Component* component) {
while (!quit_) { while (!quit_) {
if (!event_receiver_->HasPending()) { if (!event_receiver_->HasPending()) {
std::cout << reset_cursor_position << ResetPosition(); std::cout << reset_cursor_position << ResetPosition();
static int i = -2;
if (i % 30 == 0)
std::cout << REQUEST_CURSOR_LINE;
++i;
Draw(component); Draw(component);
std::cout << ToString() << set_cursor_position; std::cout << ToString() << set_cursor_position;
Flush(); Flush();
Clear(); Clear();
} }
Event event; Event event;
if (event_receiver_->Receive(&event)) { if (!event_receiver_->Receive(&event))
if (event.is_mouse()) break;
event.MoveMouse(-1, -1);
component->OnEvent(event); if (event.is_cursor_reporting()) {
cursor_x_ = event.mouse_y();
cursor_y_ = event.mouse_x();
continue;
} }
if (event.is_mouse())
event.MoveMouse(-cursor_x_, -cursor_y_);
component->OnEvent(event);
} }
event_listener.join(); event_listener.join();

View File

@ -85,6 +85,11 @@ void TerminalInputParser::Send(TerminalInputParser::Output output) {
out_->Send(Event::MouseRightMove(std::move(pending_), output.mouse.x, out_->Send(Event::MouseRightMove(std::move(pending_), output.mouse.x,
output.mouse.y)); output.mouse.y));
break; break;
case CURSOR_REPORTING:
out_->Send(Event::CursorReporting(std::move(pending_), output.mouse.x,
output.mouse.y));
break;
} }
pending_.clear(); pending_.clear();
} }
@ -179,12 +184,14 @@ TerminalInputParser::Output TerminalInputParser::ParseCSI() {
continue; continue;
} }
if (Current() >= ' ' && Current() <= '~') { if (Current() >= ' ' && Current() <= '~' && Current() != '<') {
arguments.push_back(argument); arguments.push_back(argument);
argument = 0; argument = 0;
switch (Current()) { switch (Current()) {
case 'M': case 'M':
return ParseMouse(std::move(arguments)); return ParseMouse(std::move(arguments));
case 'R':
return ParseCursorReporting(std::move(arguments));
default: default:
return SPECIAL; return SPECIAL;
} }
@ -235,8 +242,18 @@ TerminalInputParser::Output TerminalInputParser::ParseMouse(
return Output(MOUSE_UP, arguments[1], arguments[2]); return Output(MOUSE_UP, arguments[1], arguments[2]);
case 67: case 67:
return Output(MOUSE_MOVE, arguments[1], arguments[2]); return Output(MOUSE_MOVE, arguments[1], arguments[2]);
default:
return Output(MOUSE_MOVE, arguments[1], arguments[2]);
} }
return SPECIAL; return SPECIAL;
} }
TerminalInputParser::Output TerminalInputParser::ParseCursorReporting(
std::vector<int> arguments) {
if (arguments.size() != 2)
return SPECIAL;
return Output(CURSOR_REPORTING, arguments[0], arguments[1]);
}
} // namespace ftxui } // namespace ftxui

View File

@ -32,6 +32,7 @@ class TerminalInputParser {
MOUSE_MIDDLE_MOVE, MOUSE_MIDDLE_MOVE,
MOUSE_RIGHT_DOWN, MOUSE_RIGHT_DOWN,
MOUSE_RIGHT_MOVE, MOUSE_RIGHT_MOVE,
CURSOR_REPORTING,
}; };
struct Mouse { struct Mouse {
@ -58,6 +59,7 @@ class TerminalInputParser {
Output ParseCSI(); Output ParseCSI();
Output ParseOSC(); Output ParseOSC();
Output ParseMouse(std::vector<int> arguments); Output ParseMouse(std::vector<int> arguments);
Output ParseCursorReporting(std::vector<int> arguments);
Sender<Event> out_; Sender<Event> out_;
int position_ = -1; int position_ = -1;

View File

@ -66,6 +66,25 @@ TEST(Event, EscapeKeyEnoughWait) {
EXPECT_FALSE(event_receiver->Receive(&received)); EXPECT_FALSE(event_receiver->Receive(&received));
} }
TEST(Event, GnomeTerminalMouse) {
auto event_receiver = MakeReceiver<Event>();
{
auto parser = TerminalInputParser(event_receiver->MakeSender());
parser.Add('\x1B');
parser.Add('[');
parser.Add('<');
parser.Add('1');
parser.Add(';');
parser.Add('1');
parser.Add('M');
}
Event received;
EXPECT_TRUE(event_receiver->Receive(&received));
EXPECT_TRUE(received.is_mouse());
EXPECT_FALSE(event_receiver->Receive(&received));
}
// Copyright 2020 Arthur Sonzogni. All rights reserved. // Copyright 2020 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in // Use of this source code is governed by the MIT license that can be found in
// the LICENSE file. // the LICENSE file.

View File

@ -5,22 +5,25 @@
namespace ftxui { namespace ftxui {
Element Toggle::Render() { Element Toggle::Render() {
bool is_focused = Focused();
boxes_.resize(entries.size());
Elements children; Elements children;
bool is_toggle_focused = Focused();
boxes_.resize(entries.size());
for (size_t i = 0; i < entries.size(); ++i) { for (size_t i = 0; i < entries.size(); ++i) {
// Separator. // Separator.
if (i != 0) if (i != 0)
children.push_back(separator()); children.push_back(separator());
// Entry. bool is_focused = (focused == int(i)) && is_toggle_focused;
auto style = (selected != int(i)) ? normal_style bool is_selected = (selected == int(i));
: is_focused ? focused_style
: selected_style; auto style = is_selected
auto focused = (selected != int(i)) ? nothing : is_focused ? focus : select; ? (is_focused ? selected_focused_style : selected_style)
children.push_back(text(entries[i]) | style | focused | reflect(boxes_[i])); : (is_focused ? focused_style : normal_style);
auto focus_management = !is_selected ? nothing
: is_toggle_focused ? focus
: select;
children.push_back(text(entries[i]) | style | focus_management |
reflect(boxes_[i]));
} }
return hbox(std::move(children)); return hbox(std::move(children));
} }
@ -42,6 +45,7 @@ bool Toggle::OnEvent(Event event) {
selected = std::max(0, std::min(int(entries.size()) - 1, selected)); selected = std::max(0, std::min(int(entries.size()) - 1, selected));
if (old_selected != selected) { if (old_selected != selected) {
focused = selected;
on_change(); on_change();
return true; return true;
} }
@ -59,10 +63,12 @@ bool Toggle::OnMouseEvent(Event event) {
if (!boxes_[i].Contain(event.mouse_x(), event.mouse_y())) if (!boxes_[i].Contain(event.mouse_x(), event.mouse_y()))
continue; continue;
TakeFocus();
focused = i;
if (event.is_mouse_left_down()) { if (event.is_mouse_left_down()) {
TakeFocus();
if (selected != i) { if (selected != i) {
selected = i; selected = i;
TakeFocus();
on_change(); on_change();
} }
return true; return true;