mirror of
				https://github.com/ArthurSonzogni/FTXUI.git
				synced 2025-11-04 05:28:15 +08:00 
			
		
		
		
	Improve mouse support
This commit is contained in:
		@@ -10,12 +10,11 @@ Element Button::Render() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Button::OnEvent(Event event) {
 | 
			
		||||
  if (event.is_mouse() && box_.Contain(event.mouse_x(), event.mouse_y())) {
 | 
			
		||||
    if (event.is_mouse_move()) {
 | 
			
		||||
      TakeFocus();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    if (event.is_mouse_left_down()) {
 | 
			
		||||
  if (event.is_mouse() && box_.Contain(event.mouse().x, event.mouse().y)) {
 | 
			
		||||
    TakeFocus();
 | 
			
		||||
 | 
			
		||||
    if (event.mouse().button == Mouse::Left &&
 | 
			
		||||
        event.mouse().motion == Mouse::Pressed) {
 | 
			
		||||
      on_click();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -26,15 +26,13 @@ bool CheckBox::OnEvent(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;
 | 
			
		||||
 | 
			
		||||
  if (event.is_mouse_move()) {
 | 
			
		||||
    TakeFocus();
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  TakeFocus();
 | 
			
		||||
 | 
			
		||||
  if (event.is_mouse_left_down()) {
 | 
			
		||||
  if (event.mouse().button == Mouse::Left &&
 | 
			
		||||
      event.mouse().motion == Mouse::Pressed) {
 | 
			
		||||
    state = !state;
 | 
			
		||||
    on_change();
 | 
			
		||||
    return true;
 | 
			
		||||
 
 | 
			
		||||
@@ -29,56 +29,11 @@ Event Event::Character(wchar_t c) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// static
 | 
			
		||||
Event Event::MouseMove(std::string input, int x, int y) {
 | 
			
		||||
Event Event::Mouse(std::string input, struct Mouse mouse) {
 | 
			
		||||
  Event event;
 | 
			
		||||
  event.input_ = std::move(input);
 | 
			
		||||
  event.type_ = Type::MouseMove;
 | 
			
		||||
  event.mouse_ = {x, y};
 | 
			
		||||
  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};
 | 
			
		||||
  event.type_ = Type::Mouse;
 | 
			
		||||
  event.mouse_ = mouse;
 | 
			
		||||
  return event;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -90,54 +45,15 @@ Event Event::Special(std::string input) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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;
 | 
			
		||||
  event.input_ = std::move(input);
 | 
			
		||||
  event.type_ = Type::CursorReporting;
 | 
			
		||||
  event.mouse_ = {x, y};
 | 
			
		||||
  event.cursor_.x = x;
 | 
			
		||||
  event.cursor_.y = 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:
 | 
			
		||||
    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 ---
 | 
			
		||||
const Event Event::ArrowLeft = Event::Special("\x1B[D");
 | 
			
		||||
const Event Event::ArrowRight = Event::Special("\x1B[C");
 | 
			
		||||
 
 | 
			
		||||
@@ -61,12 +61,13 @@ bool Menu::OnEvent(Event event) {
 | 
			
		||||
 | 
			
		||||
bool Menu::OnMouseEvent(Event event) {
 | 
			
		||||
  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;
 | 
			
		||||
 | 
			
		||||
    TakeFocus();
 | 
			
		||||
    focused = i;
 | 
			
		||||
    if (event.is_mouse_left_down()) {
 | 
			
		||||
      TakeFocus();
 | 
			
		||||
    if (event.mouse().button == Mouse::Left &&
 | 
			
		||||
        event.mouse().motion == Mouse::Released) {
 | 
			
		||||
      if (selected != i) {
 | 
			
		||||
        selected = i;
 | 
			
		||||
        on_change();
 | 
			
		||||
 
 | 
			
		||||
@@ -56,16 +56,14 @@ bool RadioBox::OnEvent(Event event) {
 | 
			
		||||
 | 
			
		||||
bool RadioBox::OnMouseEvent(Event event) {
 | 
			
		||||
  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;
 | 
			
		||||
 | 
			
		||||
    if (event.is_mouse_move()) {
 | 
			
		||||
      focused = i;
 | 
			
		||||
      TakeFocus();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    focused = i;
 | 
			
		||||
    TakeFocus();
 | 
			
		||||
 | 
			
		||||
    if (event.is_mouse_left_down()) {
 | 
			
		||||
    if (event.mouse().button == Mouse::Left &&
 | 
			
		||||
        event.mouse().motion == Mouse::Pressed) {
 | 
			
		||||
      cursor_position = i;
 | 
			
		||||
      TakeFocus();
 | 
			
		||||
      if (selected != i) {
 | 
			
		||||
 
 | 
			
		||||
@@ -146,19 +146,53 @@ void EventListener(std::atomic<bool>* quit, Sender<Event> out) {
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
static const char SHOW_CURSOR[] = "\x1B[?25h";
 | 
			
		||||
static const char HIDE_CURSOR[] = "\x1B[?25l";
 | 
			
		||||
const std::string CSI = "\x1b[";
 | 
			
		||||
 | 
			
		||||
static const char ENABLE_LINE_WRAP[] = "\x1B[7h";
 | 
			
		||||
static const char DISABLE_LINE_WRAP[] = "\x1B[7l";
 | 
			
		||||
// DEC: Digital Equipment Corporation
 | 
			
		||||
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";
 | 
			
		||||
static const char USE_NORMAL_SCREEN[] = "\x1B[?1049l";
 | 
			
		||||
// Device Status Report (DSR) {
 | 
			
		||||
enum class DSRMode {
 | 
			
		||||
  kCursor = 6,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const char ENABLE_MOUSE[] = "\x1B[?1000;1003;1006;1015h";
 | 
			
		||||
static const char DISABLE_MOUSE[] = "\x1B[?1000;1003;1006;1015l";
 | 
			
		||||
const std::string Serialize(std::vector<DECMode> parameters) {
 | 
			
		||||
  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);
 | 
			
		||||
std::stack<std::function<void()>> on_exit_functions;
 | 
			
		||||
@@ -279,24 +313,44 @@ void ScreenInteractive::Loop(Component* component) {
 | 
			
		||||
  install_signal_handler(SIGWINCH, OnResize);
 | 
			
		||||
#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_) {
 | 
			
		||||
    std::cout << USE_ALTERNATIVE_SCREEN;
 | 
			
		||||
    on_exit_functions.push([] { std::cout << USE_NORMAL_SCREEN; });
 | 
			
		||||
    enable({
 | 
			
		||||
        DECMode::kAlternateScreen,
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::cout << ENABLE_MOUSE;
 | 
			
		||||
  on_exit_functions.push([] { std::cout << DISABLE_MOUSE; });
 | 
			
		||||
  // On exit, reset cursor one line after the current drawing.
 | 
			
		||||
  on_exit_functions.push(
 | 
			
		||||
      [=] { std::cout << reset_cursor_position << std::endl; });
 | 
			
		||||
 | 
			
		||||
  // Hide the cursor and show it at exit.
 | 
			
		||||
  std::cout << HIDE_CURSOR;
 | 
			
		||||
  std::cout << DISABLE_LINE_WRAP;
 | 
			
		||||
  Flush();
 | 
			
		||||
  on_exit_functions.push([&] {
 | 
			
		||||
    std::cout << reset_cursor_position;
 | 
			
		||||
    std::cout << SHOW_CURSOR;
 | 
			
		||||
    std::cout << ENABLE_LINE_WRAP;
 | 
			
		||||
    std::cout << std::endl;
 | 
			
		||||
    Flush();
 | 
			
		||||
  disable({
 | 
			
		||||
      DECMode::kCursor,
 | 
			
		||||
      DECMode::kLineWrap,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  enable({
 | 
			
		||||
      //DECMode::kMouseVt200,
 | 
			
		||||
      DECMode::kMouseAnyEvent,
 | 
			
		||||
      DECMode::kMouseUtf8,
 | 
			
		||||
      DECMode::kMouseSgrExtMode,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  auto event_listener =
 | 
			
		||||
@@ -307,8 +361,8 @@ void ScreenInteractive::Loop(Component* component) {
 | 
			
		||||
    if (!event_receiver_->HasPending()) {
 | 
			
		||||
      std::cout << reset_cursor_position << ResetPosition();
 | 
			
		||||
      static int i = -2;
 | 
			
		||||
      if (i % 30 == 0)
 | 
			
		||||
        std::cout << REQUEST_CURSOR_LINE;
 | 
			
		||||
      if (i % 10 == 0)
 | 
			
		||||
        std::cout << DeviceStatusReport(DSRMode::kCursor);
 | 
			
		||||
      ++i;
 | 
			
		||||
      Draw(component);
 | 
			
		||||
      std::cout << ToString() << set_cursor_position;
 | 
			
		||||
@@ -321,13 +375,15 @@ void ScreenInteractive::Loop(Component* component) {
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    if (event.is_cursor_reporting()) {
 | 
			
		||||
      cursor_x_ = event.mouse_y();
 | 
			
		||||
      cursor_y_ = event.mouse_x();
 | 
			
		||||
      cursor_x_ = event.cursor_x();
 | 
			
		||||
      cursor_y_ = event.cursor_y();
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (event.is_mouse())
 | 
			
		||||
      event.MoveMouse(-cursor_x_, -cursor_y_);
 | 
			
		||||
    if (event.is_mouse()) {
 | 
			
		||||
      event.mouse().x -= cursor_x_;
 | 
			
		||||
      event.mouse().y -= cursor_y_;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    component->OnEvent(event);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -36,62 +36,28 @@ void TerminalInputParser::Send(TerminalInputParser::Output output) {
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
    case DROP:
 | 
			
		||||
      break;
 | 
			
		||||
      pending_.clear();
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
    case CHARACTER:
 | 
			
		||||
      out_->Send(Event::Character(std::move(pending_)));
 | 
			
		||||
      break;
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
    case SPECIAL:
 | 
			
		||||
      out_->Send(Event::Special(std::move(pending_)));
 | 
			
		||||
      break;
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
    case MOUSE_MOVE:
 | 
			
		||||
      out_->Send(
 | 
			
		||||
          Event::MouseMove(std::move(pending_), output.mouse.x, output.mouse.y));
 | 
			
		||||
      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 MOUSE:
 | 
			
		||||
      out_->Send(Event::Mouse(std::move(pending_), output.mouse));
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
    case CURSOR_REPORTING:
 | 
			
		||||
      out_->Send(Event::CursorReporting(std::move(pending_), output.mouse.x,
 | 
			
		||||
                                        output.mouse.y));
 | 
			
		||||
      break;
 | 
			
		||||
      out_->Send(Event::CursorReporting(std::move(pending_), output.cursor.x,
 | 
			
		||||
                                        output.cursor.y));
 | 
			
		||||
      return;
 | 
			
		||||
  }
 | 
			
		||||
  pending_.clear();
 | 
			
		||||
  // NOT_REACHED().
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TerminalInputParser::Output TerminalInputParser::Parse() {
 | 
			
		||||
@@ -166,12 +132,18 @@ TerminalInputParser::Output TerminalInputParser::ParseDCS() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TerminalInputParser::Output TerminalInputParser::ParseCSI() {
 | 
			
		||||
  bool altered = false;
 | 
			
		||||
  int argument;
 | 
			
		||||
  std::vector<int> arguments;
 | 
			
		||||
  while (true) {
 | 
			
		||||
    if (!Eat())
 | 
			
		||||
      return UNCOMPLETED;
 | 
			
		||||
 | 
			
		||||
    if (Current() == '<') {
 | 
			
		||||
      altered = true;
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (Current() >= '0' && Current() <= '9') {
 | 
			
		||||
      argument *= 10;
 | 
			
		||||
      argument += int(Current() - '0');
 | 
			
		||||
@@ -189,7 +161,9 @@ TerminalInputParser::Output TerminalInputParser::ParseCSI() {
 | 
			
		||||
      argument = 0;
 | 
			
		||||
      switch (Current()) {
 | 
			
		||||
        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':
 | 
			
		||||
          return ParseCursorReporting(std::move(arguments));
 | 
			
		||||
        default:
 | 
			
		||||
@@ -219,41 +193,34 @@ TerminalInputParser::Output TerminalInputParser::ParseOSC() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TerminalInputParser::Output TerminalInputParser::ParseMouse(
 | 
			
		||||
    bool altered,
 | 
			
		||||
    bool pressed,
 | 
			
		||||
    std::vector<int> arguments) {
 | 
			
		||||
  if (arguments.size() != 3)
 | 
			
		||||
    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:
 | 
			
		||||
      return Output(MOUSE_MIDDLE_DOWN, arguments[1], arguments[2]);
 | 
			
		||||
    case 65:
 | 
			
		||||
      return Output(MOUSE_MIDDLE_MOVE, arguments[1], arguments[2]);
 | 
			
		||||
  (void)altered;
 | 
			
		||||
 | 
			
		||||
    case 34:
 | 
			
		||||
      return Output(MOUSE_RIGHT_DOWN, arguments[1], arguments[2]);
 | 
			
		||||
    case 66:
 | 
			
		||||
      return Output(MOUSE_RIGHT_MOVE, arguments[1], arguments[2]);
 | 
			
		||||
 | 
			
		||||
    case 35:
 | 
			
		||||
      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;
 | 
			
		||||
  Output output(MOUSE);
 | 
			
		||||
  output.mouse.button = Mouse::Button((arguments[0] & 3) +  //
 | 
			
		||||
                                      ((arguments[0] & 64) >> 4));
 | 
			
		||||
  output.mouse.motion = Mouse::Motion(pressed);
 | 
			
		||||
  output.mouse.shift = arguments[0] & 4;
 | 
			
		||||
  output.mouse.meta = arguments[0] & 8;
 | 
			
		||||
  output.mouse.control = arguments[0] & 16;
 | 
			
		||||
  output.mouse.x = arguments[1];
 | 
			
		||||
  output.mouse.y = arguments[2];
 | 
			
		||||
  return output;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TerminalInputParser::Output TerminalInputParser::ParseCursorReporting(
 | 
			
		||||
    std::vector<int> arguments) {
 | 
			
		||||
  if (arguments.size() != 2)
 | 
			
		||||
    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
 | 
			
		||||
 
 | 
			
		||||
@@ -24,31 +24,23 @@ class TerminalInputParser {
 | 
			
		||||
    DROP,
 | 
			
		||||
    CHARACTER,
 | 
			
		||||
    SPECIAL,
 | 
			
		||||
    MOUSE_UP,
 | 
			
		||||
    MOUSE_MOVE,
 | 
			
		||||
    MOUSE_LEFT_DOWN,
 | 
			
		||||
    MOUSE_LEFT_MOVE,
 | 
			
		||||
    MOUSE_MIDDLE_DOWN,
 | 
			
		||||
    MOUSE_MIDDLE_MOVE,
 | 
			
		||||
    MOUSE_RIGHT_DOWN,
 | 
			
		||||
    MOUSE_RIGHT_MOVE,
 | 
			
		||||
    MOUSE,
 | 
			
		||||
    CURSOR_REPORTING,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  struct Mouse {
 | 
			
		||||
  struct CursorReporting {
 | 
			
		||||
    int x;
 | 
			
		||||
    int y;
 | 
			
		||||
    Mouse(int x, int y) : x(x), y(y) {}
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  struct Output {
 | 
			
		||||
    Type type;
 | 
			
		||||
    union {
 | 
			
		||||
      Mouse mouse;
 | 
			
		||||
      CursorReporting cursor;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Output(Type type) : type(type) {}
 | 
			
		||||
    Output(Type type, int x, int y) : type(type), mouse(x, y) {}
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  void Send(Output type);
 | 
			
		||||
@@ -58,7 +50,7 @@ class TerminalInputParser {
 | 
			
		||||
  Output ParseDCS();
 | 
			
		||||
  Output ParseCSI();
 | 
			
		||||
  Output ParseOSC();
 | 
			
		||||
  Output ParseMouse(std::vector<int> arguments);
 | 
			
		||||
  Output ParseMouse(bool altered, bool pressed, std::vector<int> arguments);
 | 
			
		||||
  Output ParseCursorReporting(std::vector<int> arguments);
 | 
			
		||||
 | 
			
		||||
  Sender<Event> out_;
 | 
			
		||||
 
 | 
			
		||||
@@ -66,22 +66,81 @@ TEST(Event, EscapeKeyEnoughWait) {
 | 
			
		||||
  EXPECT_FALSE(event_receiver->Receive(&received));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST(Event, GnomeTerminalMouse) {
 | 
			
		||||
TEST(Event, MouseLeftClick) {
 | 
			
		||||
  auto event_receiver = MakeReceiver<Event>();
 | 
			
		||||
  {
 | 
			
		||||
    auto parser = TerminalInputParser(event_receiver->MakeSender());
 | 
			
		||||
    parser.Add('\x1B');
 | 
			
		||||
    parser.Add('[');
 | 
			
		||||
    parser.Add('<');
 | 
			
		||||
    parser.Add('1');
 | 
			
		||||
    parser.Add('3');
 | 
			
		||||
    parser.Add('2');
 | 
			
		||||
    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::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));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -60,12 +60,13 @@ bool Toggle::OnEvent(Event event) {
 | 
			
		||||
 | 
			
		||||
bool Toggle::OnMouseEvent(Event event) {
 | 
			
		||||
  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;
 | 
			
		||||
 | 
			
		||||
    TakeFocus();
 | 
			
		||||
    focused = i;
 | 
			
		||||
    if (event.is_mouse_left_down()) {
 | 
			
		||||
    if (event.mouse().button == Mouse::Left &&
 | 
			
		||||
        event.mouse().motion == Mouse::Pressed) {
 | 
			
		||||
      TakeFocus();
 | 
			
		||||
      if (selected != i) {
 | 
			
		||||
        selected = i;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user