diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b79204..c6182a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,6 +77,7 @@ add_library(dom add_library(component include/ftxui/component/button.hpp + include/ftxui/component/captured_mouse.hpp include/ftxui/component/checkbox.hpp include/ftxui/component/component.hpp include/ftxui/component/container.hpp diff --git a/examples/component/gallery.cpp b/examples/component/gallery.cpp index 54f4d6c..9bd5c86 100644 --- a/examples/component/gallery.cpp +++ b/examples/component/gallery.cpp @@ -5,6 +5,7 @@ #include "ftxui/component/menu.hpp" #include "ftxui/component/radiobox.hpp" #include "ftxui/component/screen_interactive.hpp" +#include "ftxui/component/slider.hpp" #include "ftxui/component/toggle.hpp" using namespace ftxui; @@ -20,6 +21,13 @@ class MyComponent : public Component { Input input; Button button; + int slider_value_1_ = 12; + int slider_value_2_ = 56; + int slider_value_3_ = 128; + ComponentPtr slider_1_ = Slider(L"R:", &slider_value_1_, 0, 256, 1); + ComponentPtr slider_2_ = Slider(L"G:", &slider_value_2_, 0, 256, 1); + ComponentPtr slider_3_ = Slider(L"B:", &slider_value_3_, 0, 256, 1); + public: MyComponent() { Add(&container); @@ -54,17 +62,25 @@ class MyComponent : public Component { input.placeholder = L"Input placeholder"; container.Add(&input); + container.Add(slider_1_.get()); + container.Add(slider_2_.get()); + container.Add(slider_3_.get()); + button.label = L"Quit"; button.on_click = [&] { on_quit(); }; container.Add(&button); } - Element Render(std::wstring name, Component& component) { + Element Render(std::wstring name, Element element) { return hbox({ text(name) | size(WIDTH, EQUAL, 8), separator(), - component.Render(), - }); + element | xflex, + }) | xflex; + } + + Element Render(std::wstring name, Component& component) { + return Render(name, component.Render()); } Element Render() override { @@ -78,11 +94,18 @@ class MyComponent : public Component { separator(), Render(L"radiobox", radiobox), separator(), - Render(L"input", input) | size(WIDTH, LESS_THAN, 30), + Render(L"input", input) | size(WIDTH, LESS_THAN, 50), + separator(), + Render(L"slider", // + vbox({ + slider_1_->Render(), + slider_2_->Render(), + slider_3_->Render(), + })), separator(), Render(L"button", button), }) | - border; + xflex | size(WIDTH, GREATER_THAN, 40) | border; } std::function on_quit = [] {}; diff --git a/include/ftxui/component/captured_mouse.hpp b/include/ftxui/component/captured_mouse.hpp new file mode 100644 index 0000000..b76ac10 --- /dev/null +++ b/include/ftxui/component/captured_mouse.hpp @@ -0,0 +1,14 @@ +#ifndef FTXUI_CAPTURED_MOUSE_HPP +#define FTXUI_CAPTURED_MOUSE_HPP + +#include + +namespace ftxui { +class CapturedMouseInterface { + public: + virtual ~CapturedMouseInterface() {} +}; +using CapturedMouse = std::unique_ptr; +} // namespace ftxui + +#endif /* end of include guard: FTXUI_CAPTURED_MOUSE_HPP */ diff --git a/include/ftxui/component/component.hpp b/include/ftxui/component/component.hpp index fcaf3f9..5a42216 100644 --- a/include/ftxui/component/component.hpp +++ b/include/ftxui/component/component.hpp @@ -52,13 +52,13 @@ class Component { // Configure all the ancestors to give focus to this component. void TakeFocus(); + protected: + std::vector children_; + private: Component* parent_ = nullptr; void Detach(); void Attach(Component* parent); - - protected: - std::vector children_; }; using ComponentPtr = std::unique_ptr; diff --git a/include/ftxui/component/event.hpp b/include/ftxui/component/event.hpp index bb87c6a..6295c91 100644 --- a/include/ftxui/component/event.hpp +++ b/include/ftxui/component/event.hpp @@ -10,6 +10,8 @@ namespace ftxui { +class ScreenInteractive; + /// @brief Represent an event. It can be key press event, a terminal resize, or /// more ... /// @@ -65,6 +67,9 @@ struct Event { const std::string& input() const { return input_; } + ScreenInteractive* screen() { return screen_; } + void SetScreen(ScreenInteractive* screen) { screen_ = screen; } + bool operator==(const Event& other) const { return input_ == other.input_; } //--- State section ---------------------------------------------------------- @@ -88,6 +93,8 @@ struct Event { struct Cursor cursor_; }; std::string input_; + + ScreenInteractive* screen_; }; diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp index 8f5903a..c61132c 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -9,6 +9,7 @@ #include #include +#include "ftxui/component/captured_mouse.hpp" #include "ftxui/component/event.hpp" #include "ftxui/screen/screen.hpp" @@ -27,6 +28,7 @@ class ScreenInteractive : public Screen { std::function ExitLoopClosure(); void PostEvent(Event event); + CapturedMouse CaptureMouse(); private: void Draw(Component* component); @@ -55,6 +57,8 @@ class ScreenInteractive : public Screen { int cursor_x_ = 0; int cursor_y_ = 0; + + bool mouse_captured = false; }; } // namespace ftxui diff --git a/include/ftxui/component/slider.hpp b/include/ftxui/component/slider.hpp index 35a139d..f9bff71 100644 --- a/include/ftxui/component/slider.hpp +++ b/include/ftxui/component/slider.hpp @@ -11,11 +11,14 @@ namespace ftxui { // float max = 100.f, // float increment = (max - min) * 0.05f); +template // T = {int, float} ComponentPtr Slider(std::wstring label, - int* value, - int min, - int max, - int increment); + T* value, + T min, + T max, + T increment); + + } // namespace ftxui #endif /* end of include guard: FTXUI_COMPONENT_SLIDER_HPP */ diff --git a/src/ftxui/component/button.cpp b/src/ftxui/component/button.cpp index 4da42dd..3ea1b77 100644 --- a/src/ftxui/component/button.cpp +++ b/src/ftxui/component/button.cpp @@ -1,4 +1,5 @@ #include "ftxui/component/button.hpp" +#include "ftxui/component/screen_interactive.hpp" #include @@ -11,6 +12,9 @@ Element Button::Render() { bool Button::OnEvent(Event event) { if (event.is_mouse() && box_.Contain(event.mouse().x, event.mouse().y)) { + if (!event.screen()->CaptureMouse()) + return false; + TakeFocus(); if (event.mouse().button == Mouse::Left && diff --git a/src/ftxui/component/checkbox.cpp b/src/ftxui/component/checkbox.cpp index 5fe251a..8c04a08 100644 --- a/src/ftxui/component/checkbox.cpp +++ b/src/ftxui/component/checkbox.cpp @@ -1,4 +1,5 @@ #include "ftxui/component/checkbox.hpp" +#include "ftxui/component/screen_interactive.hpp" #include @@ -26,6 +27,8 @@ bool CheckBox::OnEvent(Event event) { } bool CheckBox::OnMouseEvent(Event event) { + if (!event.screen()->CaptureMouse()) + return false; if (!box_.Contain(event.mouse().x, event.mouse().y)) return false; diff --git a/src/ftxui/component/input.cpp b/src/ftxui/component/input.cpp index 9ca7bcd..d39ec1b 100644 --- a/src/ftxui/component/input.cpp +++ b/src/ftxui/component/input.cpp @@ -1,4 +1,5 @@ #include "ftxui/component/input.hpp" +#include "ftxui/component/screen_interactive.hpp" #include @@ -102,6 +103,8 @@ bool Input::OnEvent(Event event) { } bool Input::OnMouseEvent(Event event) { + if (!event.screen()->CaptureMouse()) + return false; if (!input_box_.Contain(event.mouse().x, event.mouse().y)) return false; diff --git a/src/ftxui/component/menu.cpp b/src/ftxui/component/menu.cpp index 4eccfed..44f5f36 100644 --- a/src/ftxui/component/menu.cpp +++ b/src/ftxui/component/menu.cpp @@ -1,4 +1,5 @@ #include "ftxui/component/menu.hpp" +#include "ftxui/component/screen_interactive.hpp" #include #include @@ -27,6 +28,8 @@ Element Menu::Render() { } bool Menu::OnEvent(Event event) { + if (!event.screen()->CaptureMouse()) + return false; if (event.is_mouse()) return OnMouseEvent(event); @@ -60,6 +63,8 @@ bool Menu::OnEvent(Event event) { } bool Menu::OnMouseEvent(Event event) { + if (!event.screen()->CaptureMouse()) + return false; for (int i = 0; i < boxes_.size(); ++i) { if (!boxes_[i].Contain(event.mouse().x, event.mouse().y)) continue; diff --git a/src/ftxui/component/radiobox.cpp b/src/ftxui/component/radiobox.cpp index d3ab38f..7c50655 100644 --- a/src/ftxui/component/radiobox.cpp +++ b/src/ftxui/component/radiobox.cpp @@ -1,4 +1,5 @@ #include "ftxui/component/radiobox.hpp" +#include "ftxui/component/screen_interactive.hpp" #include #include @@ -23,6 +24,8 @@ Element RadioBox::Render() { } bool RadioBox::OnEvent(Event event) { + if (!event.screen()->CaptureMouse()) + return false; if (event.is_mouse()) return OnMouseEvent(event); @@ -55,6 +58,8 @@ bool RadioBox::OnEvent(Event event) { } bool RadioBox::OnMouseEvent(Event event) { + if (!event.screen()->CaptureMouse()) + return false; for (int i = 0; i < boxes_.size(); ++i) { if (!boxes_[i].Contain(event.mouse().x, event.mouse().y)) continue; diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index 385271a..95cc26c 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -9,6 +9,7 @@ #include #include +#include "ftxui/component/captured_mouse.hpp" #include "ftxui/component/component.hpp" #include "ftxui/component/terminal_input_parser.hpp" #include "ftxui/screen/string.hpp" @@ -216,6 +217,16 @@ void OnResize(int /* signal */) { on_resize(); } +class CapturedMouseImpl : public CapturedMouseInterface { + public: + CapturedMouseImpl(std::function callback) + : callback_(callback) {} + ~CapturedMouseImpl() override { callback_(); } + + private: + std::function callback_; +}; + } // namespace ScreenInteractive::ScreenInteractive(int dimx, @@ -256,6 +267,14 @@ void ScreenInteractive::PostEvent(Event event) { event_sender_->Send(event); } +CapturedMouse ScreenInteractive::CaptureMouse() { + if (mouse_captured) + return nullptr; + mouse_captured = true; + return std::make_unique( + [this] { mouse_captured = false; }); +} + void ScreenInteractive::Loop(Component* component) { // Install a SIGINT handler and restore the old handler on exit. auto old_sigint_handler = std::signal(SIGINT, OnExit); @@ -387,6 +406,7 @@ void ScreenInteractive::Loop(Component* component) { event.mouse().y -= cursor_y_; } + event.SetScreen(this); component->OnEvent(event); } diff --git a/src/ftxui/component/slider.cpp b/src/ftxui/component/slider.cpp index 894f9ee..8ddb4fb 100644 --- a/src/ftxui/component/slider.cpp +++ b/src/ftxui/component/slider.cpp @@ -1,9 +1,13 @@ #include "ftxui/component/slider.hpp" +#include "ftxui/component/captured_mouse.hpp" +#include "ftxui/component/screen_interactive.hpp" namespace ftxui { -class SliderInt : public Component { + +template +class SliderImpl : public Component { public: - SliderInt(std::wstring label, int* value, int min, int max, int increment) + SliderImpl(std::wstring label, T* value, T min, T max, T increment) : label_(label), value_(value), min_(min), @@ -45,36 +49,59 @@ class SliderInt : public Component { } bool OnMouseEvent(Event event) { - if (!box_.Contain(event.mouse().x, event.mouse().y)) - return false; - TakeFocus(); - if (!gauge_box_.Contain(event.mouse().x, event.mouse().y)) - return false; - if (event.mouse().button == Mouse::Left && - event.mouse().motion == Mouse::Pressed) { - *value_ = min_ + (event.mouse().x - gauge_box_.x_min) * (max_ - min_) / - (gauge_box_.x_max - gauge_box_.x_min); + if (captured_mouse_ && event.mouse().motion == Mouse::Released) { + captured_mouse_ = nullptr; + return true; } - return true; + + if (box_.Contain(event.mouse().x, event.mouse().y) && + event.screen()->CaptureMouse()) { + TakeFocus(); + } + + if (event.mouse().button == Mouse::Left && + event.mouse().motion == Mouse::Pressed && + gauge_box_.Contain(event.mouse().x, event.mouse().y) && + !captured_mouse_) { + captured_mouse_ = event.screen()->CaptureMouse(); + } + + if (captured_mouse_) { + *value_ = min_ + (event.mouse().x - gauge_box_.x_min) * (max_ - min_) / + (gauge_box_.x_max - gauge_box_.x_min); + *value_ = std::max(min_, std::min(max_, *value_)); + return true; + } + return false; } private: std::wstring label_; - int* value_; - int min_; - int max_; - int increment_ = 1; + T* value_; + T min_; + T max_; + T increment_ = 1; Box box_; Box gauge_box_; + CapturedMouse captured_mouse_; }; -ComponentPtr Slider(std::wstring label, - int* value, - int min, - int max, - int increment) { - return std::make_unique(std::move(label), value, min, max, - increment); +template +ComponentPtr Slider(std::wstring label, T* value, T min, T max, T increment) { + return std::make_unique>(std::move(label), value, min, max, + increment); } +template ComponentPtr Slider(std::wstring label, + int* value, + int min, + int max, + int increment); + +template ComponentPtr Slider(std::wstring label, + float* value, + float min, + float max, + float increment); + } // namespace ftxui diff --git a/src/ftxui/component/toggle.cpp b/src/ftxui/component/toggle.cpp index 5c8b32e..8936096 100644 --- a/src/ftxui/component/toggle.cpp +++ b/src/ftxui/component/toggle.cpp @@ -1,4 +1,5 @@ #include "ftxui/component/toggle.hpp" +#include "ftxui/component/screen_interactive.hpp" #include @@ -59,6 +60,8 @@ bool Toggle::OnEvent(Event event) { } bool Toggle::OnMouseEvent(Event event) { + if (!event.screen()->CaptureMouse()) + return false; for (int i = 0; i < boxes_.size(); ++i) { if (!boxes_[i].Contain(event.mouse().x, event.mouse().y)) continue;