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() {
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);
menu->entries = {
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_2.selected_style = 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.focused_style = bgcolor(Color::Blue);
menu_3.selected_focused_style = bgcolor(Color::Blue);
menu_4.selected_style = bgcolor(Color::Blue);
menu_4.focused_style = bgcolor(Color::BlueLight);
menu_4.selected_focused_style = bgcolor(Color::BlueLight);
menu_5.normal_style = bgcolor(Color::Blue);
menu_5.selected_style = bgcolor(Color::Yellow);
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.selected_style = 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 = []() {};

View File

@ -17,7 +17,7 @@ class DrawKey : public Component {
Element Render() override {
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;
for (auto& it : keys[i].input())
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 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);
// --- Arrow ---
static const Event ArrowLeft;
@ -68,6 +69,7 @@ struct Event {
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; }
int mouse_x() const { return mouse_.x; }
int mouse_y() const { return mouse_.y; }
@ -90,6 +92,7 @@ struct Event {
MouseMiddleMove,
MouseRightDown,
MouseRightMove,
CursorReporting,
};
struct Mouse {

View File

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

View File

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

View File

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

View File

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

View File

@ -107,10 +107,19 @@ Event Event::MouseMiddleDown(std::string input, int x, int 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};
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:

View File

@ -6,16 +6,21 @@
namespace ftxui {
Element Menu::Render() {
std::vector<Element> elements;
bool is_focused = Focused();
Elements elements;
bool is_menu_focused = Focused();
boxes_.resize(entries.size());
for (size_t i = 0; i < entries.size(); ++i) {
auto style = (selected != int(i))
? normal_style
: is_focused ? focused_style : selected_style;
auto focused = (selected != int(i)) ? nothing : is_focused ? focus : select;
auto icon = (selected != int(i)) ? L" " : L"> ";
elements.push_back(text(icon + entries[i]) | style | focused |
bool is_focused = (focused == int(i)) && is_menu_focused;
bool is_selected = (selected == int(i));
auto style = is_selected
? (is_focused ? selected_focused_style : selected_style)
: (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]));
}
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));
if (selected != old_selected) {
focused = selected;
on_change();
return true;
}
@ -58,10 +64,11 @@ bool Menu::OnMouseEvent(Event event) {
if (!boxes_[i].Contain(event.mouse_x(), event.mouse_y()))
continue;
focused = i;
if (event.is_mouse_left_down()) {
TakeFocus();
if (selected != i) {
selected = i;
TakeFocus();
on_change();
}
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 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);
std::stack<std::function<void()>> on_exit_functions;
@ -304,17 +306,30 @@ void ScreenInteractive::Loop(Component* component) {
while (!quit_) {
if (!event_receiver_->HasPending()) {
std::cout << reset_cursor_position << ResetPosition();
static int i = -2;
if (i % 30 == 0)
std::cout << REQUEST_CURSOR_LINE;
++i;
Draw(component);
std::cout << ToString() << set_cursor_position;
Flush();
Clear();
}
Event event;
if (event_receiver_->Receive(&event)) {
if (event.is_mouse())
event.MoveMouse(-1, -1);
component->OnEvent(event);
if (!event_receiver_->Receive(&event))
break;
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();

View File

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

View File

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

View File

@ -66,6 +66,25 @@ TEST(Event, EscapeKeyEnoughWait) {
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.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.

View File

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