TermOx
selecting.hpp
1 #ifndef TERMOX_WIDGET_LAYOUTS_SELECTING_HPP
2 #define TERMOX_WIDGET_LAYOUTS_SELECTING_HPP
3 #include <algorithm>
4 #include <cstddef>
5 #include <memory>
6 #include <stdexcept>
7 #include <type_traits>
8 #include <utility>
9 #include <vector>
10 
11 #include <termox/system/key.hpp>
12 #include <termox/system/system.hpp>
13 #include <termox/widget/pipe.hpp>
14 
15 namespace ox::layout {
16 
18 
23 template <typename Layout_t, bool unselect_on_focus_out = true>
24 class Selecting : public Layout_t {
25  private:
26  using Key_codes = std::vector<Key>;
27 
28  template <typename Unary_predicate>
29  using Enable_if_invocable_with_child_t = std::enable_if_t<
30  std::is_invocable_v<Unary_predicate,
31  std::add_const_t<std::add_lvalue_reference_t<
32  typename Layout_t::Child_t>>>,
33  int>;
34 
35  public:
36  Selecting() { *this | pipe::strong_focus(); }
37 
38  Selecting(Key_codes increment_selection_keys,
39  Key_codes decrement_selection_keys,
40  Key_codes increment_scroll_keys,
41  Key_codes decrement_scroll_keys)
42  : increment_selection_keys_{increment_selection_keys},
43  decrement_selection_keys_{decrement_selection_keys},
44  increment_scroll_keys_{increment_scroll_keys},
45  decrement_scroll_keys_{decrement_scroll_keys}
46  {
47  *this | pipe::strong_focus();
48  }
49 
50  public:
51  void set_increment_selection_keys(Key_codes keys)
52  {
53  increment_selection_keys_ = std::move(keys);
54  }
55 
56  void set_decrement_selection_keys(Key_codes keys)
57  {
58  decrement_selection_keys_ = std::move(keys);
59  }
60 
61  void set_increment_scroll_keys(Key_codes keys)
62  {
63  increment_scroll_keys_ = std::move(keys);
64  }
65 
66  void set_decrement_scroll_keys(Key_codes keys)
67  {
68  decrement_scroll_keys_ = std::move(keys);
69  }
70 
71  public:
73 
74  [[nodiscard]] auto is_child_selected() const -> bool
75  {
76  return selected_ != nullptr;
77  }
78 
80  [[nodiscard]] auto selected_child() const ->
81  typename Layout_t::Child_t const&
82  {
83  if (selected_ != nullptr)
84  return *selected_;
85  throw std::out_of_range{"Selecting::select_child(): No Child Selected"};
86  }
87 
89  auto selected_child() -> typename Layout_t::Child_t&
90  {
91  if (selected_ != nullptr)
92  return *selected_;
93  throw std::out_of_range{"Selecting::select_child(): No Child Selected"};
94  }
95 
97 
98  auto selected_index() const -> std::size_t
99  {
100  return this->find_child_position(selected_);
101  }
102 
105  {
106  if (this->child_count() > 0)
107  this->set_selected_by_index(this->get_child_offset());
108  }
109 
110  protected:
111  auto key_press_event(Key k) -> bool override
112  {
113  if (contains(k, increment_selection_keys_))
114  this->increment_selected_and_scroll_if_necessary();
115  else if (contains(k, decrement_selection_keys_))
116  this->decrement_selected_and_scroll_if_necessary();
117  else if (contains(k, increment_scroll_keys_))
118  this->increment_offset_and_increment_selected();
119  else if (contains(k, decrement_scroll_keys_))
120  this->decrement_offset_and_decrement_selected();
121  return Layout_t::key_press_event(k);
122  }
123 
124  auto mouse_wheel_event_filter(Widget&, Mouse const& m) -> bool override
125  {
126  switch (m.button) {
127  case Mouse::Button::ScrollUp:
128  this->decrement_selected_and_scroll_if_necessary();
129  break;
130  case Mouse::Button::ScrollDown:
131  this->increment_selected_and_scroll_if_necessary();
132  break;
133  default: break;
134  }
135  return true;
136  }
137 
138  auto mouse_press_event_filter(Widget& w, Mouse const& m) -> bool override
139  {
140  if (!this->contains_child(&w))
141  return false;
142  switch (m.button) {
143  case Mouse::Button::Left:
144  this->set_selected_by_pointer(
145  static_cast<typename Layout_t::Child_t*>(&w));
146  break;
147  default: break;
148  }
149  return true;
150  }
151 
153  auto resize_event(ox::Area new_size, ox::Area old_size) -> bool override
154  {
155  auto const base_result = Layout_t::resize_event(new_size, old_size);
156  this->reset_selected_if_necessary();
157  return base_result;
158  }
159 
160  // This has a focus in event..
161  auto focus_in_event() -> bool override
162  {
163  this->reset_selected_if_necessary();
164  if (this->is_child_selected())
165  this->selected_child().select();
166  else if (this->child_count() > 0)
167  this->select_first_child();
168  return Layout_t::focus_in_event();
169  }
170 
171  auto focus_out_event() -> bool override
172  {
173  if constexpr (unselect_on_focus_out) {
174  if (this->is_child_selected())
175  this->selected_child().unselect();
176  }
177  return Layout_t::focus_out_event();
178  }
179 
180  auto enable_event() -> bool override
181  {
182  if (this->is_child_selected() && System::focus_widget() == this)
183  this->selected_child().select();
184  return Layout_t::enable_event();
185  }
186 
187  auto disable_event() -> bool override
188  {
189  if (this->is_child_selected())
190  this->selected_child().unselect();
191  return Layout_t::disable_event();
192  }
193 
194  auto child_added_event(Widget& child) -> bool override
195  {
196  if (this->child_count() == 1)
197  this->select_first_child();
198  child.install_event_filter(*this);
199  return Layout_t::child_added_event(child);
200  }
201 
202  auto child_removed_event(Widget& child) -> bool override
203  {
204  if (&child == selected_) {
205  if (this->child_count() > 0)
206  this->set_selected_by_index(this->get_child_offset());
207  else
208  selected_ = nullptr;
209  }
210  return Layout_t::child_removed_event(child);
211  }
212 
213  private:
214  typename Layout_t::Child_t* selected_{nullptr};
215  Key_codes increment_selection_keys_;
216  Key_codes decrement_selection_keys_;
217  Key_codes increment_scroll_keys_;
218  Key_codes decrement_scroll_keys_;
219 
220  private:
221  void increment_selected()
222  {
223  auto const next = this->selected_index() + 1;
224  if (next >= this->child_count())
225  return;
226  this->set_selected_by_index(next);
227  }
228 
229  void increment_selected_and_scroll_if_necessary()
230  {
231  if (!this->is_child_selected())
232  return;
233  this->increment_selected();
234  if (!this->selected_child().is_enabled())
235  this->increment_offset();
236  }
237 
238  void decrement_selected()
239  {
240  auto const current = this->selected_index();
241  if (current == 0)
242  return;
243  this->set_selected_by_index(current - 1);
244  }
245 
246  void decrement_selected_and_scroll_if_necessary()
247  {
248  if (!this->is_child_selected())
249  return;
250  this->decrement_selected();
251  if (!this->selected_child().is_enabled())
252  this->decrement_offset();
253  }
254 
255  void increment_offset_and_increment_selected()
256  {
257  this->increment_offset();
258  this->increment_selected();
259  }
260 
261  void decrement_offset_and_decrement_selected()
262  {
263  if (this->get_child_offset() == 0)
264  return;
265  this->decrement_offset();
266  this->decrement_selected();
267  }
268 
270 
271  void set_selected_by_index(std::size_t index)
272  {
273  if (index < this->child_count())
274  this->set_selected_by_pointer(&(this->get_children()[index]));
275  else
276  throw std::out_of_range{"Selecting::set_selected_by_index"};
277  }
278 
280  void set_selected_by_pointer(typename Layout_t::Child_t* child)
281  {
282  if (this->is_child_selected())
283  selected_->unselect();
284  selected_ = child;
285  selected_->select();
286  }
287 
289 
290  auto find_bottom_row_index() const -> std::size_t
291  {
292  auto const children = this->Widget::get_children();
293  auto const count = this->child_count();
294  auto const offset = this->get_child_offset();
295  for (auto i = offset + 1; i < count; ++i) {
296  if (children[i].is_enabled())
297  continue;
298  else
299  return i - 1;
300  }
301  return offset;
302  }
303 
305  void reset_selected_if_necessary()
306  {
307  if (this->is_child_selected()) {
308  if (this->selected_child().is_enabled())
309  return;
310  else
311  this->set_selected_by_index(this->find_bottom_row_index());
312  }
313  }
314 
316  [[nodiscard]] static auto contains(Key k, Key_codes const& codes) -> bool
317  {
318  return std::any_of(std::begin(codes), std::end(codes),
319  [=](auto code) { return code == k; });
320  }
321 };
322 
324 template <typename Layout_t,
325  bool unselect_on_focus_out = true,
326  typename... Args>
327 [[nodiscard]] auto selecting(Args&&... args)
328  -> std::unique_ptr<Selecting<Layout_t, unselect_on_focus_out>>
329 {
330  return std::make_unique<Selecting<Layout_t, unselect_on_focus_out>>(
331  std::forward<Args>(args)...);
332 }
333 
334 } // namespace ox::layout
335 #endif // TERMOX_WIDGET_LAYOUTS_SELECTING_HPP
static auto focus_widget() -> Widget *
Return a pointer to the currently focused Widget.
Definition: system.cpp:37
Definition: widget.hpp:31
auto get_children()
Get a range containing Widget& to each child.
Definition: widget.hpp:214
Adds a 'Selected Child' concept to Layout_t.
Definition: selecting.hpp:24
auto selected_index() const -> std::size_t
Return the index into get_children() corresponding to the selected child.
Definition: selecting.hpp:98
auto selected_child() const -> typename Layout_t::Child_t const &
Return the currently selected child, UB if no children in Layout.
Definition: selecting.hpp:80
auto selected_child() -> typename Layout_t::Child_t &
Return the currently selected child, UB if no children in Layout.
Definition: selecting.hpp:89
void select_first_child()
Set the first visible child as the selected child.
Definition: selecting.hpp:104
auto is_child_selected() const -> bool
Return whether or not a child widget is currently selected.
Definition: selecting.hpp:74
auto resize_event(ox::Area new_size, ox::Area old_size) -> bool override
Reset the selected child if needed.
Definition: selecting.hpp:153