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