mirror of
				https://github.com/ArthurSonzogni/FTXUI.git
				synced 2025-10-31 18:48:11 +08:00 
			
		
		
		
	Add SliderWithCallback component (#938)
				
					
				
			Add SliderOption::on_change. Useful to observe a change to the value. Signed-off-by: Mikołaj Lubiak <lubiak@proton.me> Co-authored-by: ArthurSonzogni <sonzogniarthur@gmail.com>
This commit is contained in:
		 Mikołaj Lubiak
					Mikołaj Lubiak
				
			
				
					committed by
					
						 ArthurSonzogni
						ArthurSonzogni
					
				
			
			
				
	
			
			
			 ArthurSonzogni
						ArthurSonzogni
					
				
			
						parent
						
							d5099afa18
						
					
				
				
					commit
					024ce3908e
				
			| @@ -35,6 +35,8 @@ current (development) | ||||
| - Bugfix: Fix cursor position in when in the last column. See #831. | ||||
| - Bugfix: Fix `ResizeableSplit` keyboard navigation. Fixed by #842. | ||||
| - Bugfix: Fix `Menu` focus. See #841 | ||||
| - 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 | ||||
|   | ||||
| @@ -227,6 +227,7 @@ struct SliderOption { | ||||
|   Direction direction = Direction::Right; | ||||
|   Color color_active = Color::White; | ||||
|   Color color_inactive = Color::GrayDark; | ||||
|   std::function<void()> on_change;  ///> Called when `value` is updated. | ||||
| }; | ||||
|  | ||||
| // Parameter pack used by `WindowOptions::render`. | ||||
|   | ||||
| @@ -35,31 +35,26 @@ Decorator flexDirection(Direction direction) { | ||||
| } | ||||
|  | ||||
| template <class T> | ||||
| class SliderBase : public ComponentBase { | ||||
| class SliderBase : public SliderOption<T>, public ComponentBase { | ||||
|  public: | ||||
|   explicit SliderBase(SliderOption<T> options) | ||||
|       : value_(options.value), | ||||
|         min_(options.min), | ||||
|         max_(options.max), | ||||
|         increment_(options.increment), | ||||
|         options_(options) {} | ||||
|   explicit SliderBase(SliderOption<T> options) : SliderOption<T>(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<T> value_; | ||||
|   ConstRef<T> min_; | ||||
|   ConstRef<T> max_; | ||||
|   ConstRef<T> increment_; | ||||
|   SliderOption<T> 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 <typename T> | ||||
| Component Slider(SliderOption<T> options) { | ||||
|   return Make<SliderBase<T>>(options); | ||||
| } | ||||
|  | ||||
| template Component Slider(SliderOption<int8_t>); | ||||
| template Component Slider(SliderOption<int16_t>); | ||||
| template Component Slider(SliderOption<int32_t>); | ||||
|   | ||||
| @@ -45,6 +45,7 @@ Event MouseReleased(int x, int y) { | ||||
| }  // namespace | ||||
|  | ||||
| TEST(SliderTest, Right) { | ||||
|   int updated = 0; | ||||
|   int value = 50; | ||||
|   auto slider = Slider<int>({ | ||||
|       .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<int>({ | ||||
|       .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<int>({ | ||||
|       .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<int>({ | ||||
|       .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) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user