From c31aecf2edde81d20f4f4bc8c97af053026483a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Roblot?= Date: Sat, 11 Nov 2023 23:33:50 +0700 Subject: [PATCH] Checkbox button debounce (#774) 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. Co-authored-by: ArthurSonzogni --- CHANGELOG.md | 20 +++++++++++ CMakeLists.txt | 1 + include/ftxui/component/mouse.hpp | 8 +++++ .../ftxui/component/screen_interactive.hpp | 1 + src/ftxui/component/button.cpp | 3 +- src/ftxui/component/checkbox.cpp | 3 +- src/ftxui/component/input.cpp | 3 +- src/ftxui/component/menu.cpp | 6 ++-- 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 | 3 +- src/ftxui/component/window.cpp | 3 +- 14 files changed, 81 insertions(+), 18 deletions(-) create mode 100644 src/ftxui/component/mouse.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 847e6bc..d3cefa5 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 583ed46..6dc246b 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 3c61008..38bf1e1 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 10d022c..fd3a629 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 c983f31..229c32e 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 ebfa46d..bf1324f 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 7228f13..b41a9b4 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 7f6b92d..890e500 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 0000000..74e51d2 --- /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 004a242..0d00f40 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 9e0d62e..614fd41 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 ca90860..3366051 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 d30b87b..80d29fd 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 daed7e1..cae72b4 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; }