mirror of
				https://github.com/ArthurSonzogni/FTXUI.git
				synced 2025-11-04 21:48:15 +08:00 
			
		
		
		
	Improve mouse support for menu and toggle.
This commit is contained in:
		@@ -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 = []() {};
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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 = []() {};
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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 = []() {};
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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:
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user