From 99df1ac8bacf09d5d3aedd862d4ac76138c6469a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Lubiak?= Date: Tue, 29 Oct 2024 08:03:59 +0100 Subject: [PATCH] Add `SliderWithCallback` component (#938) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add SliderOption::on_change. Useful to observe a change to the value. Signed-off-by: MikoĊ‚aj Lubiak Co-authored-by: ArthurSonzogni --- CHANGELOG.md | 2 + include/ftxui/component/component_options.hpp | 1 + src/ftxui/component/slider.cpp | 101 ++++++++++-------- src/ftxui/component/slider_test.cpp | 33 ++++++ 4 files changed, 90 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 611187d..476b633 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,8 @@ current (development) component in its parent. See #932 - Feature: Add `EntryState::index`. This allows to get the index of a menu entry. See #932 +- Feature: Add `SliderOption::on_change`. This allows to set a callback when the + slider value changes. See #938. ### Dom - Feature: Add `hscroll_indicator`. It display an horizontal indicator diff --git a/include/ftxui/component/component_options.hpp b/include/ftxui/component/component_options.hpp index a55a4df..e9dd084 100644 --- a/include/ftxui/component/component_options.hpp +++ b/include/ftxui/component/component_options.hpp @@ -228,6 +228,7 @@ struct SliderOption { Direction direction = Direction::Right; Color color_active = Color::White; Color color_inactive = Color::GrayDark; + std::function on_change; ///> Called when `value` is updated. }; // Parameter pack used by `WindowOptions::render`. diff --git a/src/ftxui/component/slider.cpp b/src/ftxui/component/slider.cpp index b107580..e8efbb8 100644 --- a/src/ftxui/component/slider.cpp +++ b/src/ftxui/component/slider.cpp @@ -35,31 +35,26 @@ Decorator flexDirection(Direction direction) { } template -class SliderBase : public ComponentBase { +class SliderBase : public SliderOption, public ComponentBase { public: - explicit SliderBase(SliderOption options) - : value_(options.value), - min_(options.min), - max_(options.max), - increment_(options.increment), - options_(options) {} + explicit SliderBase(SliderOption options) : SliderOption(options) {} 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; + auto gauge_color = + Focused() ? color(this->color_active) : color(this->color_inactive); + const float percent = + float(this->value() - this->min()) / float(this->max() - this->min()); + return gaugeDirection(percent, this->direction) | + flexDirection(this->direction) | reflect(gauge_box_) | gauge_color; } void OnLeft() { - switch (options_.direction) { + switch (this->direction) { case Direction::Right: - value_() -= increment_(); + this->value() -= this->increment(); break; case Direction::Left: - value_() += increment_(); + this->value() += this->increment(); break; case Direction::Up: case Direction::Down: @@ -68,12 +63,12 @@ class SliderBase : public ComponentBase { } void OnRight() { - switch (options_.direction) { + switch (this->direction) { case Direction::Right: - value_() += increment_(); + this->value() += this->increment(); break; case Direction::Left: - value_() -= increment_(); + this->value() -= this->increment(); break; case Direction::Up: case Direction::Down: @@ -82,12 +77,12 @@ class SliderBase : public ComponentBase { } void OnUp() { - switch (options_.direction) { + switch (this->direction) { case Direction::Up: - value_() -= increment_(); + this->value() -= this->increment(); break; case Direction::Down: - value_() += increment_(); + this->value() += this->increment(); break; case Direction::Left: case Direction::Right: @@ -96,12 +91,12 @@ class SliderBase : public ComponentBase { } void OnDown() { - switch (options_.direction) { + switch (this->direction) { case Direction::Down: - value_() -= increment_(); + this->value() += this->increment(); break; case Direction::Up: - value_() += increment_(); + this->value() -= this->increment(); break; case Direction::Left: case Direction::Right: @@ -114,7 +109,7 @@ class SliderBase : public ComponentBase { return OnMouseEvent(event); } - T old_value = value_(); + T old_value = this->value(); if (event == Event::ArrowLeft || event == Event::Character('h')) { OnLeft(); } @@ -128,8 +123,11 @@ class SliderBase : public ComponentBase { OnUp(); } - value_() = util::clamp(value_(), min_(), max_()); - if (old_value != value_()) { + this->value() = std::max(this->min(), std::min(this->max(), this->value())); + if (old_value != this->value()) { + if (this->on_change) { + this->on_change(); + } return true; } @@ -143,33 +141,45 @@ class SliderBase : public ComponentBase { return true; } - switch (options_.direction) { + T old_value = this->value(); + switch (this->direction) { case Direction::Right: { - value_() = min_() + (event.mouse().x - gauge_box_.x_min) * - (max_() - min_()) / - (gauge_box_.x_max - gauge_box_.x_min); + this->value() = + this->min() + (event.mouse().x - gauge_box_.x_min) * + (this->max() - this->min()) / + (gauge_box_.x_max - gauge_box_.x_min); + break; } case Direction::Left: { - value_() = max_() - (event.mouse().x - gauge_box_.x_min) * - (max_() - min_()) / - (gauge_box_.x_max - gauge_box_.x_min); + this->value() = + this->max() - (event.mouse().x - gauge_box_.x_min) * + (this->max() - this->min()) / + (gauge_box_.x_max - gauge_box_.x_min); break; } case Direction::Down: { - value_() = min_() + (event.mouse().y - gauge_box_.y_min) * - (max_() - min_()) / - (gauge_box_.y_max - gauge_box_.y_min); + this->value() = + this->min() + (event.mouse().y - gauge_box_.y_min) * + (this->max() - this->min()) / + (gauge_box_.y_max - gauge_box_.y_min); break; } case Direction::Up: { - value_() = max_() - (event.mouse().y - gauge_box_.y_min) * - (max_() - min_()) / - (gauge_box_.y_max - gauge_box_.y_min); + this->value() = + this->max() - (event.mouse().y - gauge_box_.y_min) * + (this->max() - this->min()) / + (gauge_box_.y_max - gauge_box_.y_min); break; } } - value_() = std::max(min_(), std::min(max_(), value_())); + + this->value() = + std::max(this->min(), std::min(this->max(), this->value())); + + if (old_value != this->value() && this->on_change) { + this->on_change(); + } return true; } @@ -197,11 +207,6 @@ class SliderBase : public ComponentBase { bool Focusable() const final { return true; } private: - Ref value_; - ConstRef min_; - ConstRef max_; - ConstRef increment_; - SliderOption options_; Box gauge_box_; CapturedMouse captured_mouse_; }; @@ -256,6 +261,7 @@ class SliderWithLabel : public ComponentBase { Box box_; bool mouse_hover_ = false; }; + } // namespace /// @brief An horizontal slider. @@ -340,6 +346,7 @@ template Component Slider(SliderOption options) { return Make>(options); } + template Component Slider(SliderOption); template Component Slider(SliderOption); template Component Slider(SliderOption); diff --git a/src/ftxui/component/slider_test.cpp b/src/ftxui/component/slider_test.cpp index c205755..21b9656 100644 --- a/src/ftxui/component/slider_test.cpp +++ b/src/ftxui/component/slider_test.cpp @@ -45,6 +45,7 @@ Event MouseReleased(int x, int y) { } // namespace TEST(SliderTest, Right) { + int updated = 0; int value = 50; auto slider = Slider({ .value = &value, @@ -52,23 +53,31 @@ TEST(SliderTest, Right) { .max = 100, .increment = 10, .direction = Direction::Right, + .on_change = [&]() { updated++; }, }); Screen screen(11, 1); Render(screen, slider->Render()); EXPECT_EQ(value, 50); + EXPECT_EQ(updated, 0); EXPECT_TRUE(slider->OnEvent(MousePressed(3, 0))); EXPECT_EQ(value, 50); + EXPECT_EQ(updated, 0); EXPECT_TRUE(slider->OnEvent(MousePressed(9, 0))); EXPECT_EQ(value, 90); + EXPECT_EQ(updated, 1); EXPECT_TRUE(slider->OnEvent(MousePressed(9, 2))); EXPECT_EQ(value, 90); + EXPECT_EQ(updated, 1); EXPECT_TRUE(slider->OnEvent(MousePressed(5, 2))); EXPECT_EQ(value, 50); + EXPECT_EQ(updated, 2); EXPECT_TRUE(slider->OnEvent(MouseReleased(5, 2))); EXPECT_FALSE(slider->OnEvent(MousePressed(5, 2))); + EXPECT_EQ(value, 50); } TEST(SliderTest, Left) { + int updated = 0; int value = 50; auto slider = Slider({ .value = &value, @@ -76,23 +85,31 @@ TEST(SliderTest, Left) { .max = 100, .increment = 10, .direction = Direction::Left, + .on_change = [&]() { updated++; }, }); Screen screen(11, 1); Render(screen, slider->Render()); EXPECT_EQ(value, 50); + EXPECT_EQ(updated, 0); EXPECT_TRUE(slider->OnEvent(MousePressed(3, 0))); EXPECT_EQ(value, 50); + EXPECT_EQ(updated, 0); EXPECT_TRUE(slider->OnEvent(MousePressed(9, 0))); EXPECT_EQ(value, 10); + EXPECT_EQ(updated, 1); EXPECT_TRUE(slider->OnEvent(MousePressed(9, 2))); EXPECT_EQ(value, 10); + EXPECT_EQ(updated, 1); EXPECT_TRUE(slider->OnEvent(MousePressed(5, 2))); EXPECT_EQ(value, 50); + EXPECT_EQ(updated, 2); EXPECT_TRUE(slider->OnEvent(MouseReleased(5, 2))); EXPECT_FALSE(slider->OnEvent(MousePressed(5, 2))); + EXPECT_EQ(value, 50); } TEST(SliderTest, Down) { + int updated = 0; int value = 50; auto slider = Slider({ .value = &value, @@ -100,23 +117,32 @@ TEST(SliderTest, Down) { .max = 100, .increment = 10, .direction = Direction::Down, + .on_change = [&]() { updated++; }, }); Screen screen(1, 11); Render(screen, slider->Render()); EXPECT_EQ(value, 50); + EXPECT_EQ(updated, 0); EXPECT_TRUE(slider->OnEvent(MousePressed(0, 3))); EXPECT_EQ(value, 50); + EXPECT_EQ(updated, 0); EXPECT_TRUE(slider->OnEvent(MousePressed(0, 9))); EXPECT_EQ(value, 90); + EXPECT_EQ(updated, 1); EXPECT_TRUE(slider->OnEvent(MousePressed(2, 9))); EXPECT_EQ(value, 90); + EXPECT_EQ(updated, 1); EXPECT_TRUE(slider->OnEvent(MousePressed(2, 5))); EXPECT_EQ(value, 50); + EXPECT_EQ(updated, 2); EXPECT_TRUE(slider->OnEvent(MouseReleased(2, 5))); EXPECT_FALSE(slider->OnEvent(MousePressed(2, 5))); + EXPECT_EQ(value, 50); + EXPECT_EQ(updated, 2); } TEST(SliderTest, Up) { + int updated = 0; int value = 50; auto slider = Slider({ .value = &value, @@ -124,20 +150,27 @@ TEST(SliderTest, Up) { .max = 100, .increment = 10, .direction = Direction::Up, + .on_change = [&]() { updated++; }, }); Screen screen(1, 11); Render(screen, slider->Render()); EXPECT_EQ(value, 50); + EXPECT_EQ(updated, 0); EXPECT_TRUE(slider->OnEvent(MousePressed(0, 3))); EXPECT_EQ(value, 50); + EXPECT_EQ(updated, 0); EXPECT_TRUE(slider->OnEvent(MousePressed(0, 9))); EXPECT_EQ(value, 10); + EXPECT_EQ(updated, 1); EXPECT_TRUE(slider->OnEvent(MousePressed(2, 9))); EXPECT_EQ(value, 10); + EXPECT_EQ(updated, 1); EXPECT_TRUE(slider->OnEvent(MousePressed(2, 5))); EXPECT_EQ(value, 50); + EXPECT_EQ(updated, 2); EXPECT_TRUE(slider->OnEvent(MouseReleased(2, 5))); EXPECT_FALSE(slider->OnEvent(MousePressed(2, 5))); + EXPECT_EQ(value, 50); } TEST(SliderTest, Focus) {