diff --git a/examples/component/menu_style.cpp b/examples/component/menu_style.cpp index 839d263..da1fb68 100644 --- a/examples/component/menu_style.cpp +++ b/examples/component/menu_style.cpp @@ -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 on_enter = []() {}; diff --git a/examples/util/print_key_press.cpp b/examples/util/print_key_press.cpp index 3cfd57f..fbd6ff8 100644 --- a/examples/util/print_key_press.cpp +++ b/examples/util/print_key_press.cpp @@ -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); diff --git a/include/ftxui/component/event.hpp b/include/ftxui/component/event.hpp index 5c05578..742c75c 100644 --- a/include/ftxui/component/event.hpp +++ b/include/ftxui/component/event.hpp @@ -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 { diff --git a/include/ftxui/component/menu.hpp b/include/ftxui/component/menu.hpp index 51fb148..d621363 100644 --- a/include/ftxui/component/menu.hpp +++ b/include/ftxui/component/menu.hpp @@ -19,10 +19,12 @@ class Menu : public Component { // State. std::vector 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 on_change = []() {}; diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp index 3327969..8f5903a 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -52,6 +52,9 @@ class ScreenInteractive : public Screen { std::string reset_cursor_position; std::atomic quit_ = false; + + int cursor_x_ = 0; + int cursor_y_ = 0; }; } // namespace ftxui diff --git a/include/ftxui/component/toggle.hpp b/include/ftxui/component/toggle.hpp index 846d11b..8e9a3f6 100644 --- a/include/ftxui/component/toggle.hpp +++ b/include/ftxui/component/toggle.hpp @@ -16,12 +16,14 @@ class Toggle : public Component { ~Toggle() override = default; // State. - int selected = 0; std::vector 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 on_change = []() {}; diff --git a/src/ftxui/component/button.cpp b/src/ftxui/component/button.cpp index 8281268..85065c9 100644 --- a/src/ftxui/component/button.cpp +++ b/src/ftxui/component/button.cpp @@ -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) { diff --git a/src/ftxui/component/event.cpp b/src/ftxui/component/event.cpp index 607ea7f..dac8951 100644 --- a/src/ftxui/component/event.cpp +++ b/src/ftxui/component/event.cpp @@ -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: diff --git a/src/ftxui/component/menu.cpp b/src/ftxui/component/menu.cpp index a511493..c626f71 100644 --- a/src/ftxui/component/menu.cpp +++ b/src/ftxui/component/menu.cpp @@ -6,16 +6,21 @@ namespace ftxui { Element Menu::Render() { - std::vector 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; diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index 6b3365c..c149421 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -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> 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(); diff --git a/src/ftxui/component/terminal_input_parser.cpp b/src/ftxui/component/terminal_input_parser.cpp index f348aeb..c566154 100644 --- a/src/ftxui/component/terminal_input_parser.cpp +++ b/src/ftxui/component/terminal_input_parser.cpp @@ -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 arguments) { + if (arguments.size() != 2) + return SPECIAL; + return Output(CURSOR_REPORTING, arguments[0], arguments[1]); +} + } // namespace ftxui diff --git a/src/ftxui/component/terminal_input_parser.hpp b/src/ftxui/component/terminal_input_parser.hpp index db13d59..bb63ad8 100644 --- a/src/ftxui/component/terminal_input_parser.hpp +++ b/src/ftxui/component/terminal_input_parser.hpp @@ -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 arguments); + Output ParseCursorReporting(std::vector arguments); Sender out_; int position_ = -1; diff --git a/src/ftxui/component/terminal_input_parser_test.cpp b/src/ftxui/component/terminal_input_parser_test.cpp index 09bcfa4..80fbd29 100644 --- a/src/ftxui/component/terminal_input_parser_test.cpp +++ b/src/ftxui/component/terminal_input_parser_test.cpp @@ -66,6 +66,25 @@ TEST(Event, EscapeKeyEnoughWait) { EXPECT_FALSE(event_receiver->Receive(&received)); } +TEST(Event, GnomeTerminalMouse) { + auto event_receiver = MakeReceiver(); + { + 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. diff --git a/src/ftxui/component/toggle.cpp b/src/ftxui/component/toggle.cpp index 0437d00..525cded 100644 --- a/src/ftxui/component/toggle.cpp +++ b/src/ftxui/component/toggle.cpp @@ -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;