TermOx
number_edit.hpp
1 #ifndef TERMOX_WIDGET_WIDGETS_NUMBER_EDIT_HPP
2 #define TERMOX_WIDGET_WIDGETS_NUMBER_EDIT_HPP
3 #include <algorithm>
4 #include <cctype>
5 #include <limits>
6 #include <sstream>
7 #include <string>
8 #include <type_traits>
9 
10 #include <signals_light/signal.hpp>
11 
12 #include <termox/system/key.hpp>
13 #include <termox/system/mouse.hpp>
14 #include <termox/widget/align.hpp>
15 #include <termox/widget/pipe.hpp>
16 #include <termox/widget/widgets/detail/textline_base.hpp>
17 
18 namespace ox {
19 
20 template <typename Number_t>
22  public:
23  struct Range {
24  Number_t min = Min_num;
25  Number_t max = Max_num;
26  };
27 
28  struct Parameters {
29  Number_t initial = 0;
30  Range range = {};
31  Align alignment = Align::Left;
32  Action focus_in_action = Action::None;
33  Number_t increment = 1;
34  };
35 
36  public:
38  sl::Signal<void(Number_t)> submitted;
39 
41  sl::Signal<void(Number_t)> value_modified;
42 
43  public:
44  explicit Number_edit(Number_t initial = 0,
45  Range range = {},
46  Align alignment = Align::Left,
47  Action focus_in_action = Action::None,
48  Number_t increment = 1)
49  : Textline_base{std::to_string(
50  std::clamp(initial, range.min, range.max)),
52  value_{std::clamp(initial, range.min, range.max)},
53  range_{range},
54  increment_{increment >= 0 ? increment : 0}
55  {}
56 
57  explicit Number_edit(Parameters p)
58  : Number_edit{p.initial, p.range, p.alignment, p.focus_in_action,
59  p.increment}
60  {}
61 
62  public:
64 
65  void set_value(Number_t x)
66  {
67  value_ = std::clamp(x, range_.min, range_.max);
68  core_.set_text(std::to_string(value_));
69  this->update();
70  }
71 
73  [[nodiscard]] auto value() const -> Number_t { return value_; }
74 
76  void set_range(Range x)
77  {
78  range_ = x;
79  this->set_value(value_); // clamp to new range
80  }
81 
83  [[nodiscard]] auto range() const -> Range { return range_; }
84 
86  void set_increment(Number_t x) { increment_ = x; }
87 
89  [[nodiscard]] auto get_increment() const -> Number_t { return increment_; }
90 
92  void increment()
93  {
94  this->set_value(
95  Max_num - increment_ < value_ // Overflow Check
96  ? range_.max
97  : std::clamp(value_ + increment_, range_.min, range_.max));
98  value_modified.emit(value_);
99  submitted.emit(value_);
100  }
101 
103  void decrement()
104  {
105  this->set_value(
106  Min_num + increment_ > value_ // Underflow Check
107  ? range_.min
108  : std::clamp(value_ - increment_, range_.min, range_.max));
109  value_modified.emit(value_);
110  submitted.emit(value_);
111  }
112 
113  protected:
114  auto key_press_event(Key k) -> bool override
115  {
116  switch (k) {
117  case Key::Enter: this->validate_and_submit(); break;
118  case Key::Arrow_up: this->increment(); break;
119  case Key::Arrow_down: this->decrement(); break;
120  default: break;
121  }
122  auto const c = key_to_char(k);
123  if (is_valid_input(c)) {
124  core_.insert_at_cursor(c);
125  this->update();
126  value_ = string_to_value(this->text().str());
127  value_modified.emit(value_);
128  }
129  return Textline_base::key_press_event(k);
130  }
131 
132  auto mouse_wheel_event(Mouse const& m) -> bool override
133  {
134  switch (m.button) {
135  case Mouse::Button::ScrollUp: this->increment(); break;
136  case Mouse::Button::ScrollDown: this->decrement(); break;
137  default: break;
138  }
139  return Textline_base::mouse_wheel_event(m);
140  }
141 
142  auto focus_out_event() -> bool override
143  {
144  this->validate_and_submit();
145  return Textline_base::focus_out_event();
146  }
147 
148  private:
149  Number_t value_;
150  Range range_;
151  Number_t increment_;
152 
153  static auto constexpr Min_num = std::numeric_limits<Number_t>::lowest();
154  static auto constexpr Max_num = std::numeric_limits<Number_t>::max();
155 
156  private:
158  [[nodiscard]] static auto is_valid_input(char c) -> bool
159  {
160  bool is_separator = (c == ',');
161  if constexpr (std::is_floating_point<Number_t>::value)
162  is_separator |= (c == '.');
163  bool is_sign = (c == '+');
164  if constexpr (!std::is_unsigned<Number_t>::value)
165  is_sign |= (c == '-');
166  return std::isdigit(c) || is_separator || is_sign;
167  }
168 
170  [[nodiscard]] auto string_to_value(std::string x) -> Number_t
171  {
172  sanitize(x);
173  auto ss = std::istringstream{x};
174  auto result = Number_t(0);
175  ss >> result;
176  return result;
177  }
178 
180  static void sanitize(std::string& s)
181  {
182  s.erase(std::remove(std::begin(s), std::end(s), ','), std::end(s));
183  }
184 
186  void validate_and_submit()
187  {
188  auto const new_value = string_to_value(this->text().str());
189  value_ = std::clamp(new_value, range_.min, range_.max);
190  if (value_ != new_value) {
191  core_.set_text(std::to_string(value_));
192  this->update();
193  }
194  submitted.emit(value_);
195  }
196 };
197 
199 template <typename Number_t>
200 [[nodiscard]] auto number_edit(
201  Number_t initial = 0,
202  typename Number_edit<Number_t>::Range range = {},
203  Align alignment = Align::Left,
204  typename Number_edit<Number_t>::Action focus_in_action =
205  Number_edit<Number_t>::Action::None,
206  Number_t increment = 1) -> std::unique_ptr<Number_edit<Number_t>>
207 {
208  return std::make_unique<Number_edit<Number_t>>(initial, range, alignment,
209  focus_in_action, increment);
210 }
211 
213 template <typename Number_t>
214 [[nodiscard]] auto number_edit(typename Number_edit<Number_t>::Parameters p)
215  -> std::unique_ptr<Number_edit<Number_t>>
216 {
217  return std::make_unique<Number_edit<Number_t>>(std::move(p));
218 }
219 
220 using Int_edit = Number_edit<int>;
221 
223 [[nodiscard]] inline auto int_edit(
224  int initial = 0,
225  Int_edit::Range range = {},
226  Align alignment = Align::Left,
227  Int_edit::Action focus_in_action = Int_edit::Action::None,
228  int increment = 1) -> std::unique_ptr<Int_edit>
229 {
230  return std::make_unique<Int_edit>(initial, range, alignment,
231  focus_in_action, increment);
232 }
233 
235 [[nodiscard]] inline auto int_edit(Int_edit::Parameters p)
236  -> std::unique_ptr<Int_edit>
237 {
238  return std::make_unique<Int_edit>(std::move(p));
239 }
240 
241 using Unsigned_edit = Number_edit<unsigned int>;
242 
244 [[nodiscard]] inline auto unsigned_edit(
245  unsigned int initial = 0,
246  Unsigned_edit::Range range = {},
247  Align alignment = Align::Left,
248  Unsigned_edit::Action focus_in_action = Unsigned_edit::Action::None,
249  unsigned int increment = 1) -> std::unique_ptr<Unsigned_edit>
250 {
251  return std::make_unique<Unsigned_edit>(initial, range, alignment,
252  focus_in_action, increment);
253 }
254 
256 [[nodiscard]] inline auto unsigned_edit(Unsigned_edit::Parameters p)
257  -> std::unique_ptr<Unsigned_edit>
258 {
259  return std::make_unique<Unsigned_edit>(std::move(p));
260 }
261 
262 using Double_edit = Number_edit<double>;
263 
265 [[nodiscard]] inline auto double_edit(
266  double initial = 0,
267  Double_edit::Range range = {},
268  Align alignment = Align::Left,
269  Double_edit::Action focus_in_action = Double_edit::Action::None,
270  double increment = 1) -> std::unique_ptr<Double_edit>
271 {
272  return std::make_unique<Double_edit>(initial, range, alignment,
273  focus_in_action, increment);
274 }
275 
277 [[nodiscard]] inline auto double_edit(Double_edit::Parameters p)
278  -> std::unique_ptr<Double_edit>
279 {
280  return std::make_unique<Double_edit>(std::move(p));
281 }
282 
283 using Float_edit = Number_edit<float>;
284 
286 [[nodiscard]] inline auto float_edit(
287  float initial = 0,
288  Float_edit::Range range = {},
289  Align alignment = Align::Left,
290  Float_edit::Action focus_in_action = Float_edit::Action::None,
291  float increment = 1) -> std::unique_ptr<Float_edit>
292 {
293  return std::make_unique<Float_edit>(initial, range, alignment,
294  focus_in_action, increment);
295 }
296 
298 [[nodiscard]] inline auto float_edit(Float_edit::Parameters p)
299  -> std::unique_ptr<Float_edit>
300 {
301  return std::make_unique<Float_edit>(std::move(p));
302 }
303 
304 } // namespace ox
305 #endif // TERMOX_WIDGET_WIDGETS_NUMBER_EDIT_HPP
Definition: number_edit.hpp:21
auto get_increment() const -> Number_t
Return the current increment/decrement value.
Definition: number_edit.hpp:89
void set_increment(Number_t x)
Set the value to increment/decrement by on arrow key/mouse wheel input.
Definition: number_edit.hpp:86
auto range() const -> Range
Get the currently set Range.
Definition: number_edit.hpp:83
sl::Signal< void(Number_t)> submitted
Emitted on Enter key press; sends along the current value.
Definition: number_edit.hpp:38
auto value() const -> Number_t
Get the current value of the Number_edit, defaults to zero if invalid.
Definition: number_edit.hpp:73
sl::Signal< void(Number_t)> value_modified
Emitted on key press and inc/decrement; sends along the current value.
Definition: number_edit.hpp:41
auto key_press_event(Key k) -> bool override
Handles Key_press_event objects.
Definition: number_edit.hpp:114
void set_range(Range x)
Set the accepted range of values.
Definition: number_edit.hpp:76
auto focus_out_event() -> bool override
Handles Focus_out_event objects.
Definition: number_edit.hpp:142
void increment()
Increment the current value by the set increment.
Definition: number_edit.hpp:92
auto mouse_wheel_event(Mouse const &m) -> bool override
Handles Mouse_wheel_event objects.
Definition: number_edit.hpp:132
void decrement()
Decrement the current value by the set increment.
Definition: number_edit.hpp:103
void set_value(Number_t x)
Set a new value to be displayed.
Definition: number_edit.hpp:65
virtual void update()
Post a paint event to this Widget.
Definition: widget.cpp:110
Implements single line text Cursor navigation and deletion, no text input.
Definition: textline_base.hpp:14
auto text() const noexcept -> ox::Glyph_string const &
Return the full text contents.
Definition: textline_base.cpp:35
auto alignment() const noexcept -> ox::Align
Return the current alignment.
Definition: textline_base.cpp:46
auto focus_in_action() const noexcept -> Action
Return the currently used focus in event Action.
Definition: textline_base.cpp:57
void set_text(ox::Glyph_string x)
Clear and reset the text.
Definition: textline_core.cpp:24
void insert_at_cursor(ox::Glyph x)
Inserts x at the current cursor index.
Definition: textline_core.cpp:126
Definition: number_edit.hpp:28
Definition: number_edit.hpp:23
Holds [begin, end) half-open range of iterators and provides access.
Definition: range.hpp:7