From a7b678542074722022ca31553c7eb47fa0dc4936 Mon Sep 17 00:00:00 2001
From: Arthur Sonzogni <sonzogniarthur@gmail.com>
Date: Sun, 17 Dec 2023 10:35:21 +0100
Subject: [PATCH] Restore cursor shape on exit. (#793) (#794)

Fixed: https://github.com/ArthurSonzogni/FTXUI/issues/792
---
 CHANGELOG.md                                  | 28 +++++---------
 CMakeLists.txt                                |  1 -
 examples/component/print_key_press.cpp        |  3 ++
 include/ftxui/component/mouse.hpp             |  9 +----
 .../ftxui/component/screen_interactive.hpp    |  1 -
 src/ftxui/component/button.cpp                |  3 +-
 src/ftxui/component/checkbox.cpp              |  3 +-
 src/ftxui/component/input.cpp                 |  5 ++-
 src/ftxui/component/menu.cpp                  |  7 +++-
 src/ftxui/component/mouse.cpp                 | 36 ------------------
 src/ftxui/component/radiobox.cpp              |  3 +-
 src/ftxui/component/resizable_split.cpp       |  3 +-
 src/ftxui/component/screen_interactive.cpp    |  6 ---
 src/ftxui/component/slider.cpp                |  5 ++-
 src/ftxui/component/terminal_input_parser.cpp | 37 +++++++++++++++----
 .../component/terminal_input_parser_test.cpp  | 31 ++++++++++++++--
 src/ftxui/component/window.cpp                |  5 ++-
 17 files changed, 96 insertions(+), 90 deletions(-)
 delete mode 100644 src/ftxui/component/mouse.cpp

diff --git a/CHANGELOG.md b/CHANGELOG.md
index cc45103e..6a976af7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,26 +7,16 @@ current (development)
 ### Component
 - Feature: Add support for `Input`'s insert mode. Add `InputOption::insert`
   option. Added by @mingsheng13.
-- Feature/Bugfix/Breaking change: `Mouse transition`:
+- Bugfix/Breaking change: `Mouse transition`:
+  - Detect when the mouse move, as opposed to being pressed.
+    The Mouse::Moved motion was added.
+  - Dragging the mouse with the left button pressed now avoids activating
+    multiple checkboxes.
+  - A couple of components are now activated when the mouse is pressed,
+  as opposed to being released.
   This fixes: https://github.com/ArthurSonzogni/FTXUI/issues/773
-  Dragging the mouse with the left button pressed now avoids activating multiple
-  checkboxes.
-
-  Add support for detecting mouse press transition. Added:
-  ```cpp
-  // The previous mouse event.
-  Mouse Mouse::previous;
-
-  // Return whether the mouse transitionned from:
-  // released to pressed => IsPressed()
-  // pressed to pressed => IsHeld()
-  // pressed to released => IsReleased()
-  bool Mouse::IsPressed(Button button) const;
-  bool Mouse::IsHeld(Button button) const;
-  bool Mouse::IsReleased(Button button) const;
-  ```
-  A couple of components are now activated when the mouse is pressed,
-  as opposed to released.
+  This fixes: https://github.com/ArthurSonzogni/FTXUI/issues/792
+- Bugfix: mouse.control is now reported correctly.
 - Feature: Add `ScreenInteractive::FullscreenPrimaryScreen()`. This allows
   displaying a fullscreen component on the primary screen, as opposed to the
   alternate screen.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6dc246bf..583ed46a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -123,7 +123,6 @@ add_library(component
   src/ftxui/component/maybe.cpp
   src/ftxui/component/menu.cpp
   src/ftxui/component/modal.cpp
-  src/ftxui/component/mouse.cpp
   src/ftxui/component/radiobox.cpp
   src/ftxui/component/radiobox.cpp
   src/ftxui/component/renderer.cpp
diff --git a/examples/component/print_key_press.cpp b/examples/component/print_key_press.cpp
index c4812719..02b61331 100644
--- a/examples/component/print_key_press.cpp
+++ b/examples/component/print_key_press.cpp
@@ -55,6 +55,9 @@ std::string Stringify(Event event) {
       case Mouse::Released:
         out += "_released";
         break;
+      case Mouse::Moved:
+        out += "_moved";
+        break;
     }
     if (event.mouse().control)
       out += "_control";
diff --git a/include/ftxui/component/mouse.hpp b/include/ftxui/component/mouse.hpp
index 38bf1e15..adfeb7fa 100644
--- a/include/ftxui/component/mouse.hpp
+++ b/include/ftxui/component/mouse.hpp
@@ -21,13 +21,9 @@ struct Mouse {
   enum Motion {
     Released = 0,
     Pressed = 1,
+    Moved = 2,
   };
 
-  // Utility function to check the variations of the mouse state.
-  bool IsPressed(Button btn = Left) const;   // Released => Pressed.
-  bool IsHeld(Button btn = Left) const;      // Pressed => Pressed.
-  bool IsReleased(Button btn = Left) const;  // Pressed => Released.
-
   // Button
   Button button = Button::None;
 
@@ -42,9 +38,6 @@ struct Mouse {
   // Coordinates:
   int x = 0;
   int y = 0;
-
-  // Previous mouse event, if any.
-  Mouse* previous = nullptr;
 };
 
 }  // namespace ftxui
diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp
index 496e2c1d..cb4c189c 100644
--- a/include/ftxui/component/screen_interactive.hpp
+++ b/include/ftxui/component/screen_interactive.hpp
@@ -117,7 +117,6 @@ class ScreenInteractive : public Screen {
   // The style of the cursor to restore on exit.
   int cursor_reset_shape_ = 1;
 
-  Mouse latest_mouse_event_;
   friend class Loop;
 
  public:
diff --git a/src/ftxui/component/button.cpp b/src/ftxui/component/button.cpp
index 229c32ee..c983f31f 100644
--- a/src/ftxui/component/button.cpp
+++ b/src/ftxui/component/button.cpp
@@ -124,7 +124,8 @@ class ButtonBase : public ComponentBase, public ButtonOption {
       return false;
     }
 
-    if (event.mouse().IsPressed()) {
+    if (event.mouse().button == Mouse::Left &&
+        event.mouse().motion == Mouse::Pressed) {
       TakeFocus();
       OnClick();
       return true;
diff --git a/src/ftxui/component/checkbox.cpp b/src/ftxui/component/checkbox.cpp
index bf1324f0..ebfa46d8 100644
--- a/src/ftxui/component/checkbox.cpp
+++ b/src/ftxui/component/checkbox.cpp
@@ -69,7 +69,8 @@ class CheckboxBase : public ComponentBase, public CheckboxOption {
       return false;
     }
 
-    if (event.mouse().IsPressed()) {
+    if (event.mouse().button == Mouse::Left &&
+        event.mouse().motion == Mouse::Pressed) {
       *checked = !*checked;
       on_change();
       return true;
diff --git a/src/ftxui/component/input.cpp b/src/ftxui/component/input.cpp
index b41a9b4a..b0cf92d1 100644
--- a/src/ftxui/component/input.cpp
+++ b/src/ftxui/component/input.cpp
@@ -466,7 +466,10 @@ class InputBase : public ComponentBase, public InputOption {
       return false;
     }
 
-    if (!event.mouse().IsPressed()) {
+    if (event.mouse().button != Mouse::Left) {
+      return false;
+    }
+    if (event.mouse().motion != Mouse::Pressed) {
       return false;
     }
 
diff --git a/src/ftxui/component/menu.cpp b/src/ftxui/component/menu.cpp
index 890e500f..21a73d89 100644
--- a/src/ftxui/component/menu.cpp
+++ b/src/ftxui/component/menu.cpp
@@ -318,7 +318,9 @@ class MenuBase : public ComponentBase, public MenuOption {
 
       TakeFocus();
       focused_entry() = i;
-      if (event.mouse().IsPressed()) {
+
+      if (event.mouse().button == Mouse::Left &&
+          event.mouse().motion == Mouse::Pressed) {
         if (selected() != i) {
           selected() = i;
           selected_previous_ = selected();
@@ -682,7 +684,8 @@ Component MenuEntry(MenuEntryOption option) {
         return false;
       }
 
-      if (event.mouse().IsPressed()) {
+      if (event.mouse().button == Mouse::Left &&
+          event.mouse().motion == Mouse::Pressed) {
         TakeFocus();
         return true;
       }
diff --git a/src/ftxui/component/mouse.cpp b/src/ftxui/component/mouse.cpp
deleted file mode 100644
index 74e51d2e..00000000
--- a/src/ftxui/component/mouse.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2023 Arthur Sonzogni. All rights reserved.
-// Use of this source code is governed by the MIT license that can be found in
-// the LICENSE file.
-
-#include "ftxui/component/mouse.hpp"
-
-namespace ftxui {
-
-namespace {
-bool IsDown(const Mouse* mouse, Mouse::Button btn) {
-  return mouse->button == btn && mouse->motion == Mouse::Pressed;
-}
-}  // namespace
-
-/// Return whether the mouse transitionned from released to pressed.
-/// This is useful to detect a click.
-/// @arg btn The button to check.
-bool Mouse::IsPressed(Button btn) const {
-  return IsDown(this, btn) && (!previous || !IsDown(previous, btn));
-}
-
-/// Return whether the mouse is currently held.
-/// This is useful to detect a drag.
-/// @arg btn The button to check.
-bool Mouse::IsHeld(Button btn) const {
-  return IsDown(this, btn) && previous && IsDown(previous, btn);
-}
-
-/// Return whether the mouse transitionned from pressed to released.
-/// This is useful to detect a click.
-/// @arg btn The button to check.
-bool Mouse::IsReleased(Button btn) const {
-  return !IsDown(this, btn) && (previous && IsDown(previous, btn));
-}
-
-}  // namespace ftxui
diff --git a/src/ftxui/component/radiobox.cpp b/src/ftxui/component/radiobox.cpp
index 0d00f403..d64e2b88 100644
--- a/src/ftxui/component/radiobox.cpp
+++ b/src/ftxui/component/radiobox.cpp
@@ -123,7 +123,8 @@ class RadioboxBase : public ComponentBase, public RadioboxOption {
 
       TakeFocus();
       focused_entry() = i;
-      if (event.mouse().IsPressed()) {
+      if (event.mouse().button == Mouse::Left &&
+          event.mouse().motion == Mouse::Pressed) {
         if (selected() != i) {
           selected() = i;
           on_change();
diff --git a/src/ftxui/component/resizable_split.cpp b/src/ftxui/component/resizable_split.cpp
index 614fd417..9e0d62e3 100644
--- a/src/ftxui/component/resizable_split.cpp
+++ b/src/ftxui/component/resizable_split.cpp
@@ -42,7 +42,8 @@ class ResizableSplitBase : public ComponentBase {
       return true;
     }
 
-    if (event.mouse().IsPressed() &&
+    if (event.mouse().button == Mouse::Left &&
+        event.mouse().motion == Mouse::Pressed &&
         separator_box_.Contain(event.mouse().x, event.mouse().y) &&
         !captured_mouse_) {
       captured_mouse_ = CaptureMouse(event);
diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp
index 7be7f913..c446f2ee 100644
--- a/src/ftxui/component/screen_interactive.cpp
+++ b/src/ftxui/component/screen_interactive.cpp
@@ -734,17 +734,11 @@ void ScreenInteractive::HandleTask(Component component, Task& task) {
       if (arg.is_mouse()) {
         arg.mouse().x -= cursor_x_;
         arg.mouse().y -= cursor_y_;
-        arg.mouse().previous = &latest_mouse_event_;
       }
 
       arg.screen_ = this;
       component->OnEvent(arg);
       frame_valid_ = false;
-
-      if (arg.is_mouse()) {
-        latest_mouse_event_ = arg.mouse();
-        latest_mouse_event_.previous = nullptr;
-      }
       return;
     }
 
diff --git a/src/ftxui/component/slider.cpp b/src/ftxui/component/slider.cpp
index 80d29fdb..bc1a9d68 100644
--- a/src/ftxui/component/slider.cpp
+++ b/src/ftxui/component/slider.cpp
@@ -174,7 +174,10 @@ class SliderBase : public ComponentBase {
       return true;
     }
 
-    if (!event.mouse().IsPressed()) {
+    if (event.mouse().button != Mouse::Left) {
+      return false;
+    }
+    if (event.mouse().motion != Mouse::Pressed) {
       return false;
     }
 
diff --git a/src/ftxui/component/terminal_input_parser.cpp b/src/ftxui/component/terminal_input_parser.cpp
index 7ad3f704..cb48604a 100644
--- a/src/ftxui/component/terminal_input_parser.cpp
+++ b/src/ftxui/component/terminal_input_parser.cpp
@@ -410,13 +410,36 @@ TerminalInputParser::Output TerminalInputParser::ParseMouse(  // NOLINT
   (void)altered;
 
   Output output(MOUSE);
-  output.mouse.button = Mouse::Button((arguments[0] & 3) +          // NOLINT
-                                      ((arguments[0] & 64) >> 4));  // NOLINT
-  output.mouse.motion = Mouse::Motion(pressed);                     // NOLINT
-  output.mouse.shift = bool(arguments[0] & 4);                      // NOLINT
-  output.mouse.meta = bool(arguments[0] & 8);                       // NOLINT
-  output.mouse.x = arguments[1];                                    // NOLINT
-  output.mouse.y = arguments[2];                                    // NOLINT
+  output.mouse.motion = Mouse::Motion(pressed);  // NOLINT
+
+  // Bits value Modifer  Comment
+  // ---- ----- ------- ---------
+  // 0 1  1 2   button   0 = Left, 1 = Middle, 2 = Right, 3 = Release
+  // 2    4     Shift
+  // 3    8     Meta
+  // 4    16    Control
+  // 5    32    Move
+  // 6    64    Wheel
+
+  // clang-format off
+  const int button      = arguments[0] & (1 + 2); // NOLINT
+  const bool is_shift   = arguments[0] & 4;       // NOLINT
+  const bool is_meta    = arguments[0] & 8;       // NOLINT
+  const bool is_control = arguments[0] & 16;      // NOLINT
+  const bool is_move    = arguments[0] & 32;      // NOLINT
+  const bool is_wheel   = arguments[0] & 64;      // NOLINT
+  // clang-format on
+
+  output.mouse.motion = is_move ? Mouse::Moved : Mouse::Motion(pressed);
+  output.mouse.button = is_wheel ? Mouse::Button(Mouse::WheelUp + button)  //
+                                 : Mouse::Button(button);
+  output.mouse.shift = is_shift;
+  output.mouse.meta = is_meta;
+  output.mouse.control = is_control;
+  output.mouse.x = arguments[1];  // NOLINT
+  output.mouse.y = arguments[2];  // NOLINT
+
+  // Motion event.
   return output;
 }
 
diff --git a/src/ftxui/component/terminal_input_parser_test.cpp b/src/ftxui/component/terminal_input_parser_test.cpp
index 9e99a349..9210fdda 100644
--- a/src/ftxui/component/terminal_input_parser_test.cpp
+++ b/src/ftxui/component/terminal_input_parser_test.cpp
@@ -82,8 +82,7 @@ TEST(Event, MouseLeftClickPressed) {
     auto parser = TerminalInputParser(event_receiver->MakeSender());
     parser.Add('\x1B');
     parser.Add('[');
-    parser.Add('3');
-    parser.Add('2');
+    parser.Add('0');
     parser.Add(';');
     parser.Add('1');
     parser.Add('2');
@@ -103,7 +102,7 @@ TEST(Event, MouseLeftClickPressed) {
   EXPECT_FALSE(event_receiver->Receive(&received));
 }
 
-TEST(Event, MouseLeftClickReleased) {
+TEST(Event, MouseLeftMoved) {
   auto event_receiver = MakeReceiver<Task>();
   {
     auto parser = TerminalInputParser(event_receiver->MakeSender());
@@ -117,6 +116,32 @@ TEST(Event, MouseLeftClickReleased) {
     parser.Add(';');
     parser.Add('4');
     parser.Add('2');
+    parser.Add('M');
+  }
+
+  Task received;
+  EXPECT_TRUE(event_receiver->Receive(&received));
+  EXPECT_TRUE(std::get<Event>(received).is_mouse());
+  EXPECT_EQ(Mouse::Left, std::get<Event>(received).mouse().button);
+  EXPECT_EQ(12, std::get<Event>(received).mouse().x);
+  EXPECT_EQ(42, std::get<Event>(received).mouse().y);
+  EXPECT_EQ(std::get<Event>(received).mouse().motion, Mouse::Moved);
+  EXPECT_FALSE(event_receiver->Receive(&received));
+}
+
+TEST(Event, MouseLeftClickReleased) {
+  auto event_receiver = MakeReceiver<Task>();
+  {
+    auto parser = TerminalInputParser(event_receiver->MakeSender());
+    parser.Add('\x1B');
+    parser.Add('[');
+    parser.Add('0');
+    parser.Add(';');
+    parser.Add('1');
+    parser.Add('2');
+    parser.Add(';');
+    parser.Add('4');
+    parser.Add('2');
     parser.Add('m');
   }
 
diff --git a/src/ftxui/component/window.cpp b/src/ftxui/component/window.cpp
index cae72b45..b2b96ceb 100644
--- a/src/ftxui/component/window.cpp
+++ b/src/ftxui/component/window.cpp
@@ -225,7 +225,10 @@ class WindowImpl : public ComponentBase, public WindowOptions {
       return true;
     }
 
-    if (!event.mouse().IsPressed()) {
+    if (event.mouse().button != Mouse::Left) {
+      return true;
+    }
+    if (event.mouse().motion != Mouse::Pressed) {
       return true;
     }
 
-- 
GitLab