diff --git a/CHANGELOG.md b/CHANGELOG.md index 847e6bca53d1c313ace064ce6c951ef88a10cef5..d3cefa5aa0dcecb15beb8f114c0da0193b508b83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,26 @@ current (development) ### Component - Feature: Add support for `Input`'s insert mode. Add `InputOption::insert` option. Added by @mingsheng13. +- Feature/Bugfix/Breaking change: `Mouse transition`: + 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. - Bugfix: `Input` `onchange` was not called on backspace or delete key. Fixed by @chrysante in chrysante in PR #776. diff --git a/CMakeLists.txt b/CMakeLists.txt index 583ed46a2723070e9ce959e152c6322a3c81d6e4..6dc246bf144530aeca432fd9e5f00416966c573f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,6 +123,7 @@ 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/include/ftxui/component/mouse.hpp b/include/ftxui/component/mouse.hpp index 3c61008dc3f3044ced0664bbe8ec69e7f5507137..38bf1e1516a5f1332a8e1280264cb9cb911607ee 100644 --- a/include/ftxui/component/mouse.hpp +++ b/include/ftxui/component/mouse.hpp @@ -23,6 +23,11 @@ struct Mouse { Pressed = 1, }; + // 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; @@ -37,6 +42,9 @@ 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 10d022c670d08bf48f73f192250d7a3f5df9ea7c..fd3a629f180da97a3db9f6ab59754ceabe51caa9 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -112,6 +112,7 @@ class ScreenInteractive : public Screen { bool frame_valid_ = false; + Mouse latest_mouse_event_; friend class Loop; public: diff --git a/src/ftxui/component/button.cpp b/src/ftxui/component/button.cpp index c983f31f2acbe79fd5c13b096c36b8aea2ef9f37..229c32ee3b51f1192bac48cde65e9ba85a283487 100644 --- a/src/ftxui/component/button.cpp +++ b/src/ftxui/component/button.cpp @@ -124,8 +124,7 @@ class ButtonBase : public ComponentBase, public ButtonOption { return false; } - if (event.mouse().button == Mouse::Left && - event.mouse().motion == Mouse::Pressed) { + if (event.mouse().IsPressed()) { TakeFocus(); OnClick(); return true; diff --git a/src/ftxui/component/checkbox.cpp b/src/ftxui/component/checkbox.cpp index ebfa46d8b298a44c0e242998178cc9109683645c..bf1324f07119d20d89a18e430037f60884cf77ee 100644 --- a/src/ftxui/component/checkbox.cpp +++ b/src/ftxui/component/checkbox.cpp @@ -69,8 +69,7 @@ class CheckboxBase : public ComponentBase, public CheckboxOption { return false; } - if (event.mouse().button == Mouse::Left && - event.mouse().motion == Mouse::Pressed) { + if (event.mouse().IsPressed()) { *checked = !*checked; on_change(); return true; diff --git a/src/ftxui/component/input.cpp b/src/ftxui/component/input.cpp index 7228f1310c5a038ea600a3443201e7483c4f9d4e..b41a9b4ac12b37582e02a5e077bf1b32ec108998 100644 --- a/src/ftxui/component/input.cpp +++ b/src/ftxui/component/input.cpp @@ -466,8 +466,7 @@ class InputBase : public ComponentBase, public InputOption { return false; } - if (event.mouse().button != Mouse::Left || - event.mouse().motion != Mouse::Pressed) { + if (!event.mouse().IsPressed()) { return false; } diff --git a/src/ftxui/component/menu.cpp b/src/ftxui/component/menu.cpp index 7f6b92d1d1a6c656e2b293d5040e4891db8a6ca6..890e500f2900637ca29ccaad395c1a018ce38e75 100644 --- a/src/ftxui/component/menu.cpp +++ b/src/ftxui/component/menu.cpp @@ -318,8 +318,7 @@ class MenuBase : public ComponentBase, public MenuOption { TakeFocus(); focused_entry() = i; - if (event.mouse().button == Mouse::Left && - event.mouse().motion == Mouse::Released) { + if (event.mouse().IsPressed()) { if (selected() != i) { selected() = i; selected_previous_ = selected(); @@ -683,8 +682,7 @@ Component MenuEntry(MenuEntryOption option) { return false; } - if (event.mouse().button == Mouse::Left && - event.mouse().motion == Mouse::Released) { + if (event.mouse().IsPressed()) { TakeFocus(); return true; } diff --git a/src/ftxui/component/mouse.cpp b/src/ftxui/component/mouse.cpp new file mode 100644 index 0000000000000000000000000000000000000000..74e51d2e436625b556bc77f4ecb0cd52951361d8 --- /dev/null +++ b/src/ftxui/component/mouse.cpp @@ -0,0 +1,36 @@ +// 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 004a2428983f102a0ed3dc17463ade0bcdca64b1..0d00f403d3b0017fc427395dfed432329017b472 100644 --- a/src/ftxui/component/radiobox.cpp +++ b/src/ftxui/component/radiobox.cpp @@ -123,8 +123,7 @@ class RadioboxBase : public ComponentBase, public RadioboxOption { TakeFocus(); focused_entry() = i; - if (event.mouse().button == Mouse::Left && - event.mouse().motion == Mouse::Released) { + if (event.mouse().IsPressed()) { if (selected() != i) { selected() = i; on_change(); diff --git a/src/ftxui/component/resizable_split.cpp b/src/ftxui/component/resizable_split.cpp index 9e0d62e35d1f797341c65dc7d925dcb76f032d50..614fd41714bf9a1e65ee221c1ea9a6f51cfbc192 100644 --- a/src/ftxui/component/resizable_split.cpp +++ b/src/ftxui/component/resizable_split.cpp @@ -42,8 +42,7 @@ class ResizableSplitBase : public ComponentBase { return true; } - if (event.mouse().button == Mouse::Left && - event.mouse().motion == Mouse::Pressed && + if (event.mouse().IsPressed() && 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 ca908609be0d61781f0c2f2c54390837ba4aa379..33660514a1bd98780ca9863e7b9ed45ed7e0f460 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -689,11 +689,17 @@ 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 d30b87b79005ff188cd29aecbbe2f1d4b1159ddd..80d29fdb8c3caf608f01c61f1d6b09f79602dd97 100644 --- a/src/ftxui/component/slider.cpp +++ b/src/ftxui/component/slider.cpp @@ -174,8 +174,7 @@ class SliderBase : public ComponentBase { return true; } - if (event.mouse().button != Mouse::Left || - event.mouse().motion != Mouse::Pressed) { + if (!event.mouse().IsPressed()) { return false; } diff --git a/src/ftxui/component/window.cpp b/src/ftxui/component/window.cpp index daed7e10eee04d0e82874f07b08e0ba697ccf143..cae72b45637afccda0338a2af38639a00aa8808a 100644 --- a/src/ftxui/component/window.cpp +++ b/src/ftxui/component/window.cpp @@ -225,8 +225,7 @@ class WindowImpl : public ComponentBase, public WindowOptions { return true; } - if (event.mouse().button != Mouse::Left || - event.mouse().motion != Mouse::Pressed) { + if (!event.mouse().IsPressed()) { return true; }