From 2887beff5be0b11f5f4dc4644c3e53ff3f2ee035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Lubiak?= Date: Fri, 18 Oct 2024 14:14:07 +0200 Subject: [PATCH 1/2] Add `SliderWithCallback` component I wanted to do some actions in real time when the slider value changes, not only update the reference value, so I made a little `SliderWithCallback` component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: MikoĊ‚aj Lubiak --- include/ftxui/component/component_options.hpp | 14 ++ src/ftxui/component/slider.cpp | 185 ++++++++++++++++++ 2 files changed, 199 insertions(+) diff --git a/include/ftxui/component/component_options.hpp b/include/ftxui/component/component_options.hpp index a55a4df..4aa50d5 100644 --- a/include/ftxui/component/component_options.hpp +++ b/include/ftxui/component/component_options.hpp @@ -230,6 +230,20 @@ struct SliderOption { Color color_inactive = Color::GrayDark; }; +// @brief Option for the `SliderWithCallback` component. +// @ingroup component +template +struct SliderWithCallbackOption { + std::function callback; + Ref value; + ConstRef min = T(0); + ConstRef max = T(100); + ConstRef increment = (max() - min()) / 20; + Direction direction = Direction::Right; + Color color_active = Color::White; + Color color_inactive = Color::GrayDark; +}; + // Parameter pack used by `WindowOptions::render`. struct WindowRenderState { Element inner; ///< The element wrapped inside this window. diff --git a/src/ftxui/component/slider.cpp b/src/ftxui/component/slider.cpp index b107580..08858a9 100644 --- a/src/ftxui/component/slider.cpp +++ b/src/ftxui/component/slider.cpp @@ -256,6 +256,185 @@ class SliderWithLabel : public ComponentBase { Box box_; bool mouse_hover_ = false; }; + +template +class SliderWithCallback : public ComponentBase { +public: + explicit SliderWithCallback(SliderWithCallbackOption options) + : callback_(options.callback), min_(options.min), max_(options.max), + increment_(options.increment), options_(options) { + SetValue(options.value()); + } + + Element Render() override { + auto gauge_color = Focused() ? color(options_.color_active) + : color(options_.color_inactive); + const float percent = float(value_() - min_()) / float(max_() - min_()); + return gaugeDirection(percent, options_.direction) | + flexDirection(options_.direction) | reflect(gauge_box_) | + gauge_color; + } + + void OnLeft() { + switch (options_.direction) { + case Direction::Right: + SetValue(value_() - increment_()); + break; + case Direction::Left: + SetValue(value_() + increment_()); + break; + case Direction::Up: + case Direction::Down: + break; + } + } + + void OnRight() { + switch (options_.direction) { + case Direction::Right: + SetValue(value_() + increment_()); + break; + case Direction::Left: + SetValue(value_() - increment_()); + break; + case Direction::Up: + case Direction::Down: + break; + } + } + + void OnUp() { + switch (options_.direction) { + case Direction::Up: + SetValue(value_() - increment_()); + break; + case Direction::Down: + SetValue(value_() + increment_()); + break; + case Direction::Left: + case Direction::Right: + break; + } + } + + void OnDown() { + switch (options_.direction) { + case Direction::Down: + SetValue(value_() - increment_()); + break; + case Direction::Up: + SetValue(value_() + increment_()); + break; + case Direction::Left: + case Direction::Right: + break; + } + } + + bool OnEvent(Event event) final { + if (event.is_mouse()) { + return OnMouseEvent(event); + } + + T old_value = value_(); + if (event == Event::ArrowLeft || event == Event::Character('h')) { + OnLeft(); + } + if (event == Event::ArrowRight || event == Event::Character('l')) { + OnRight(); + } + if (event == Event::ArrowUp || event == Event::Character('k')) { + OnDown(); + } + if (event == Event::ArrowDown || event == Event::Character('j')) { + OnUp(); + } + + SetValue(util::clamp(value_(), min_(), max_())); + if (old_value != value_()) { + return true; + } + + return ComponentBase::OnEvent(event); + } + + bool OnMouseEvent(Event event) { + if (captured_mouse_) { + if (event.mouse().motion == Mouse::Released) { + captured_mouse_ = nullptr; + return true; + } + + switch (options_.direction) { + case Direction::Right: { + SetValue(min_() + (event.mouse().x - gauge_box_.x_min) * + (max_() - min_()) / + (gauge_box_.x_max - gauge_box_.x_min)); + + break; + } + case Direction::Left: { + SetValue(max_() - (event.mouse().x - gauge_box_.x_min) * + (max_() - min_()) / + (gauge_box_.x_max - gauge_box_.x_min)); + break; + } + case Direction::Down: { + SetValue(min_() + (event.mouse().y - gauge_box_.y_min) * + (max_() - min_()) / + (gauge_box_.y_max - gauge_box_.y_min)); + break; + } + case Direction::Up: { + SetValue(max_() - (event.mouse().y - gauge_box_.y_min) * + (max_() - min_()) / + (gauge_box_.y_max - gauge_box_.y_min)); + break; + } + } + + SetValue(std::max(min_(), std::min(max_(), value_()))); + return true; + } + + if (event.mouse().button != Mouse::Left) { + return false; + } + if (event.mouse().motion != Mouse::Pressed) { + return false; + } + + if (!gauge_box_.Contain(event.mouse().x, event.mouse().y)) { + return false; + } + + captured_mouse_ = CaptureMouse(event); + + if (captured_mouse_) { + TakeFocus(); + return true; + } + + return false; + } + + bool Focusable() const final { return true; } + + void SetValue(Ref val) { + value_() = val(); + callback_(value_()); + } + +private: + std::function callback_; + Ref value_; + ConstRef min_; + ConstRef max_; + ConstRef increment_; + SliderWithCallbackOption options_; + Box gauge_box_; + CapturedMouse captured_mouse_; +}; } // namespace /// @brief An horizontal slider. @@ -340,6 +519,12 @@ template Component Slider(SliderOption options) { return Make>(options); } + +template +Component Slider(SliderWithCallbackOption options) { + return Make>(options); +}; + template Component Slider(SliderOption); template Component Slider(SliderOption); template Component Slider(SliderOption); From 528c508584ae4ff7d2d4f71d975e9ff326b440de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Lubiak?= Date: Sat, 19 Oct 2024 18:01:27 +0200 Subject: [PATCH 2/2] Fix `SliderWithCallback` not clamping the value before calling the callback --- src/ftxui/component/slider.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ftxui/component/slider.cpp b/src/ftxui/component/slider.cpp index 08858a9..9a2052f 100644 --- a/src/ftxui/component/slider.cpp +++ b/src/ftxui/component/slider.cpp @@ -421,7 +421,7 @@ public: bool Focusable() const final { return true; } void SetValue(Ref val) { - value_() = val(); + value_() = util::clamp(val(), min_(), max_()); callback_(value_()); }