diff --git a/CHANGELOG.md b/CHANGELOG.md index 611187dc..476b6336 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 b9718c08..e9dd0845 100644 --- a/include/ftxui/component/component_options.hpp +++ b/include/ftxui/component/component_options.hpp @@ -222,13 +222,13 @@ struct ResizableSplitOption { template struct SliderOption { Ref value; - std::function callback = nullptr; 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; + 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 15c99e85..e8efbb85 100644 --- a/src/ftxui/component/slider.cpp +++ b/src/ftxui/component/slider.cpp @@ -35,76 +35,72 @@ Decorator flexDirection(Direction direction) { } template -class SliderBase : public ComponentBase { -public: - explicit SliderBase(SliderOption options) - : callback_(options.callback), min_(options.min), max_(options.max), - increment_(options.increment), options_(options) { - SetValue(options.value()); - } +class SliderBase : public SliderOption, public ComponentBase { + public: + 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) { - case Direction::Right: - SetValue(value_() - increment_()); - break; - case Direction::Left: - SetValue(value_() + increment_()); - break; - case Direction::Up: - case Direction::Down: - break; + switch (this->direction) { + case Direction::Right: + this->value() -= this->increment(); + break; + case Direction::Left: + this->value() += this->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; + switch (this->direction) { + case Direction::Right: + this->value() += this->increment(); + break; + case Direction::Left: + this->value() -= this->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; + switch (this->direction) { + case Direction::Up: + this->value() -= this->increment(); + break; + case Direction::Down: + this->value() += this->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; + switch (this->direction) { + case Direction::Down: + this->value() += this->increment(); + break; + case Direction::Up: + this->value() -= this->increment(); + break; + case Direction::Left: + case Direction::Right: + break; } } @@ -113,7 +109,7 @@ public: return OnMouseEvent(event); } - T old_value = value_(); + T old_value = this->value(); if (event == Event::ArrowLeft || event == Event::Character('h')) { OnLeft(); } @@ -127,8 +123,11 @@ public: OnUp(); } - SetValue(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; } @@ -142,35 +141,45 @@ public: 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)); + T old_value = this->value(); + switch (this->direction) { + case Direction::Right: { + 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: { - 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; - } + break; + } + case Direction::Left: { + 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: { + 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: { + this->value() = + this->max() - (event.mouse().y - gauge_box_.y_min) * + (this->max() - this->min()) / + (gauge_box_.y_max - gauge_box_.y_min); + break; + } } - SetValue(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,25 +206,11 @@ public: bool Focusable() const final { return true; } - void SetValue(Ref val) { - value_() = util::clamp(val(), min_(), max_()); - if (callback_ != nullptr) { - callback_(value_()); - } - } - -private: - std::function callback_; - Ref value_; - ConstRef min_; - ConstRef max_; - ConstRef increment_; - SliderOption options_; + private: Box gauge_box_; CapturedMouse captured_mouse_; }; - class SliderWithLabel : public ComponentBase { public: SliderWithLabel(ConstStringRef label, Component inner) diff --git a/src/ftxui/component/slider_test.cpp b/src/ftxui/component/slider_test.cpp index c2057555..21b96569 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) {