FTXUI  5.0.0
C++ functional terminal UI.
slider.cpp
Go to the documentation of this file.
1 // Copyright 2020 Arthur Sonzogni. All rights reserved.
2 // Use of this source code is governed by the MIT license that can be found in
3 // the LICENSE file.
4 #include <algorithm> // for max, min
5 #include <cstdint> // for uint8_t, uint16_t, uint32_t, uint64_t
6 #include <ftxui/component/component_options.hpp> // for SliderOption
7 #include <ftxui/dom/direction.hpp> // for Direction, Direction::Down, Direction::Left, Direction::Right, Direction::Up
8 #include <string> // for allocator
9 #include <utility> // for move
10 
11 #include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
12 #include "ftxui/component/component.hpp" // for Make, Slider
13 #include "ftxui/component/component_base.hpp" // for ComponentBase
14 #include "ftxui/component/event.hpp" // for Event, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp
15 #include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed, Mouse::Released
16 #include "ftxui/component/screen_interactive.hpp" // for Component
17 #include "ftxui/dom/elements.hpp" // for operator|, text, Element, xflex, hbox, color, underlined, reflect, Decorator, dim, vcenter, focus, nothing, select, yflex, gaugeDirection
18 #include "ftxui/screen/box.hpp" // for Box
19 #include "ftxui/screen/color.hpp" // for Color, Color::GrayDark, Color::White
20 #include "ftxui/screen/util.hpp" // for clamp
21 #include "ftxui/util/ref.hpp" // for ConstRef, Ref, ConstStringRef
22 
23 namespace ftxui {
24 
25 namespace {
26 Decorator flexDirection(Direction direction) {
27  switch (direction) {
28  case Direction::Up:
29  case Direction::Down:
30  return yflex;
31  case Direction::Left:
32  case Direction::Right:
33  return xflex;
34  }
35  return xflex; // NOT_REACHED()
36 }
37 
38 template <class T>
39 class SliderBase : public ComponentBase {
40  public:
41  explicit SliderBase(SliderOption<T> options)
42  : value_(options.value),
43  min_(options.min),
44  max_(options.max),
45  increment_(options.increment),
46  options_(options) {}
47 
48  Element Render() override {
49  auto gauge_color = Focused() ? color(options_.color_active)
50  : color(options_.color_inactive);
51  const float percent = float(value_() - min_()) / float(max_() - min_());
52  return gaugeDirection(percent, options_.direction) |
53  flexDirection(options_.direction) | reflect(gauge_box_) |
54  gauge_color;
55  }
56 
57  void OnLeft() {
58  switch (options_.direction) {
59  case Direction::Right:
60  value_() -= increment_();
61  break;
62  case Direction::Left:
63  value_() += increment_();
64  break;
65  case Direction::Up:
66  case Direction::Down:
67  break;
68  }
69  }
70 
71  void OnRight() {
72  switch (options_.direction) {
73  case Direction::Right:
74  value_() += increment_();
75  break;
76  case Direction::Left:
77  value_() -= increment_();
78  break;
79  case Direction::Up:
80  case Direction::Down:
81  break;
82  }
83  }
84 
85  void OnUp() {
86  switch (options_.direction) {
87  case Direction::Up:
88  value_() -= increment_();
89  break;
90  case Direction::Down:
91  value_() += increment_();
92  break;
93  case Direction::Left:
94  case Direction::Right:
95  break;
96  }
97  }
98 
99  void OnDown() {
100  switch (options_.direction) {
101  case Direction::Down:
102  value_() -= increment_();
103  break;
104  case Direction::Up:
105  value_() += increment_();
106  break;
107  case Direction::Left:
108  case Direction::Right:
109  break;
110  }
111  }
112 
113  bool OnEvent(Event event) final {
114  if (event.is_mouse()) {
115  return OnMouseEvent(event);
116  }
117 
118  T old_value = value_();
119  if (event == Event::ArrowLeft || event == Event::Character('h')) {
120  OnLeft();
121  }
122  if (event == Event::ArrowRight || event == Event::Character('l')) {
123  OnRight();
124  }
125  if (event == Event::ArrowUp || event == Event::Character('k')) {
126  OnDown();
127  }
128  if (event == Event::ArrowDown || event == Event::Character('j')) {
129  OnUp();
130  }
131 
132  value_() = util::clamp(value_(), min_(), max_());
133  if (old_value != value_()) {
134  return true;
135  }
136 
137  return ComponentBase::OnEvent(event);
138  }
139 
140  bool OnMouseEvent(Event event) {
141  if (captured_mouse_) {
142  if (event.mouse().motion == Mouse::Released) {
143  captured_mouse_ = nullptr;
144  return true;
145  }
146 
147  switch (options_.direction) {
148  case Direction::Right: {
149  value_() = min_() + (event.mouse().x - gauge_box_.x_min) *
150  (max_() - min_()) /
151  (gauge_box_.x_max - gauge_box_.x_min);
152  break;
153  }
154  case Direction::Left: {
155  value_() = max_() - (event.mouse().x - gauge_box_.x_min) *
156  (max_() - min_()) /
157  (gauge_box_.x_max - gauge_box_.x_min);
158  break;
159  }
160  case Direction::Down: {
161  value_() = min_() + (event.mouse().y - gauge_box_.y_min) *
162  (max_() - min_()) /
163  (gauge_box_.y_max - gauge_box_.y_min);
164  break;
165  }
166  case Direction::Up: {
167  value_() = max_() - (event.mouse().y - gauge_box_.y_min) *
168  (max_() - min_()) /
169  (gauge_box_.y_max - gauge_box_.y_min);
170  break;
171  }
172  }
173  value_() = std::max(min_(), std::min(max_(), value_()));
174  return true;
175  }
176 
177  if (event.mouse().button != Mouse::Left) {
178  return false;
179  }
180  if (event.mouse().motion != Mouse::Pressed) {
181  return false;
182  }
183 
184  if (!gauge_box_.Contain(event.mouse().x, event.mouse().y)) {
185  return false;
186  }
187 
188  captured_mouse_ = CaptureMouse(event);
189 
190  if (captured_mouse_) {
191  TakeFocus();
192  return true;
193  }
194 
195  return false;
196  }
197 
198  bool Focusable() const final { return true; }
199 
200  private:
201  Ref<T> value_;
202  ConstRef<T> min_;
203  ConstRef<T> max_;
204  ConstRef<T> increment_;
205  SliderOption<T> options_;
206  Box gauge_box_;
207  CapturedMouse captured_mouse_;
208 };
209 
210 class SliderWithLabel : public ComponentBase {
211  public:
212  SliderWithLabel(ConstStringRef label, Component inner)
213  : label_(std::move(label)) {
214  Add(std::move(inner));
215  SetActiveChild(ChildAt(0));
216  }
217 
218  private:
219  bool OnEvent(Event event) final {
220  if (ComponentBase::OnEvent(event)) {
221  return true;
222  }
223 
224  if (!event.is_mouse()) {
225  return false;
226  }
227 
228  mouse_hover_ = box_.Contain(event.mouse().x, event.mouse().y);
229 
230  if (!mouse_hover_) {
231  return false;
232  }
233 
234  if (!CaptureMouse(event)) {
235  return false;
236  }
237 
238  return true;
239  }
240 
241  Element Render() override {
242  auto focus_management = Focused() ? focus : Active() ? select : nothing;
243  auto gauge_color = (Focused() || mouse_hover_) ? color(Color::White)
245  return hbox({
246  text(label_()) | dim | vcenter,
247  hbox({
248  text("["),
250  text("]"),
251  }) | xflex,
252  }) |
253  gauge_color | xflex | reflect(box_) | focus_management;
254  }
255 
256  ConstStringRef label_;
257  Box box_;
258  bool mouse_hover_ = false;
259 };
260 } // namespace
261 
262 /// @brief An horizontal slider.
263 /// @param label The name of the slider.
264 /// @param value The current value of the slider.
265 /// @param min The minimum value.
266 /// @param max The maximum value.
267 /// @param increment The increment when used by the cursor.
268 /// @ingroup component
269 ///
270 /// ### Example
271 ///
272 /// ```cpp
273 /// auto screen = ScreenInteractive::TerminalOutput();
274 /// int value = 50;
275 /// auto slider = Slider("Value:", &value, 0, 100, 1);
276 /// screen.Loop(slider);
277 /// ```
278 ///
279 /// ### Output
280 ///
281 /// ```bash
282 /// Value:[██████████████████████████ ]
283 /// ```
285  Ref<int> value,
286  ConstRef<int> min,
287  ConstRef<int> max,
288  ConstRef<int> increment) {
289  SliderOption<int> option;
290  option.value = value;
291  option.min = min;
292  option.max = max;
293  option.increment = increment;
294  auto slider = Make<SliderBase<int>>(option);
295  return Make<SliderWithLabel>(std::move(label), slider);
296 }
297 
299  Ref<float> value,
300  ConstRef<float> min,
301  ConstRef<float> max,
302  ConstRef<float> increment) {
303  SliderOption<float> option;
304  option.value = value;
305  option.min = min;
306  option.max = max;
307  option.increment = increment;
308  auto slider = Make<SliderBase<float>>(option);
309  return Make<SliderWithLabel>(std::move(label), slider);
310 }
312  Ref<long> value,
313  ConstRef<long> min,
314  ConstRef<long> max,
315  ConstRef<long> increment) {
316  SliderOption<long> option;
317  option.value = value;
318  option.min = min;
319  option.max = max;
320  option.increment = increment;
321  auto slider = Make<SliderBase<long>>(option);
322  return Make<SliderWithLabel>(std::move(label), slider);
323 }
324 
325 /// @brief A slider in any direction.
326 /// @param options The options
327 /// ### Example
328 ///
329 /// ```cpp
330 /// auto screen = ScreenInteractive::TerminalOutput();
331 /// int value = 50;
332 /// auto slider = Slider({
333 /// .value = &value,
334 /// .min = 0,
335 /// .max = 100,
336 /// .increment= 20,
337 /// });
338 /// screen.Loop(slider);
339 /// ```
340 template <typename T>
342  return Make<SliderBase<T>>(options);
343 }
348 
353 
356 
357 } // namespace ftxui
virtual Element Render()
Draw the component. Build a ftxui::Element to be drawn on the ftxi::Screen representing this ftxui::C...
Definition: component.cpp:92
virtual bool OnEvent(Event)
Called in response to an event.
Definition: component.cpp:106
An adapter. Own or reference an immutable object.
Definition: ref.hpp:15
An adapter. Own or reference a constant string. For convenience, this class convert multiple immutabl...
Definition: ref.hpp:86
constexpr const T & clamp(const T &v, const T &lo, const T &hi)
Definition: util.hpp:12
Element xflex(Element)
Expand/Minimize if possible/needed on the X axis.
Definition: flex.cpp:129
Element gaugeDirection(float progress, Direction direction)
Draw a high definition progress bar progressing in specified direction.
Definition: gauge.cpp:169
std::function< Element(Element)> Decorator
Definition: elements.hpp:25
Element nothing(Element element)
A decoration doing absolutely nothing.
Definition: util.cpp:30
Direction
Definition: direction.hpp:8
std::unique_ptr< CapturedMouseInterface > CapturedMouse
std::shared_ptr< Node > Element
Definition: elements.hpp:23
std::shared_ptr< ComponentBase > Component
Element yflex(Element)
Expand/Minimize if possible/needed on the Y axis.
Definition: flex.cpp:135
Element hbox(Elements)
A container displaying elements horizontally one by one.
Definition: hbox.cpp:83
Element underlined(Element)
Make the underlined element to be underlined.
Definition: underlined.cpp:33
Element text(std::wstring text)
Display a piece of unicode text.
Definition: text.cpp:120
Element select(Element)
Set the child to be the one selected among its siblings.
Definition: frame.cpp:150
Element focus(Element)
Set the child to be the one in focus globally.
Definition: frame.cpp:157
Component Slider(SliderOption< T > options)
A slider in any direction.
Definition: slider.cpp:341
Decorator reflect(Box &box)
Definition: reflect.cpp:44
Element dim(Element)
Use a light font, for elements with less emphasis.
Definition: dim.cpp:33
void Render(Screen &screen, const Element &element)
Display an element on a ftxui::Screen.
Definition: node.cpp:47
Element vcenter(Element)
Center an element vertically.
Decorator color(Color)
Decorate using a foreground color.
Definition: color.cpp:91
static Event Character(std::string)
An event corresponding to a given typed character.
Definition: event.cpp:16
static const Event ArrowUp
Definition: event.hpp:42
static const Event ArrowDown
Definition: event.hpp:43
static const Event ArrowLeft
Definition: event.hpp:40
static const Event ArrowRight
Definition: event.hpp:41