diff --git a/CHANGELOG.md b/CHANGELOG.md index cc45103..6a976af 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 6dc246b..583ed46 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 c481271..02b6133 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 38bf1e1..adfeb7f 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 496e2c1..cb4c189 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 229c32e..c983f31 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 bf1324f..ebfa46d 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 b41a9b4..b0cf92d 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 890e500..21a73d8 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 74e51d2..0000000 --- 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 0d00f40..d64e2b8 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 614fd41..9e0d62e 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 7be7f91..c446f2e 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 80d29fd..bc1a9d6 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 7ad3f70..cb48604 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 9e99a34..9210fdd 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(); { 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(received).is_mouse()); + EXPECT_EQ(Mouse::Left, std::get(received).mouse().button); + EXPECT_EQ(12, std::get(received).mouse().x); + EXPECT_EQ(42, std::get(received).mouse().y); + EXPECT_EQ(std::get(received).mouse().motion, Mouse::Moved); + EXPECT_FALSE(event_receiver->Receive(&received)); +} + +TEST(Event, MouseLeftClickReleased) { + auto event_receiver = MakeReceiver(); + { + 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 cae72b4..b2b96ce 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; }