TermOx
layout_span.hpp
1 #ifndef TERMOX_WIDGET_LAYOUTS_DETAIL_LAYOUT_SPAN_HPP
2 #define TERMOX_WIDGET_LAYOUTS_DETAIL_LAYOUT_SPAN_HPP
3 #include <algorithm>
4 #include <cstddef>
5 #include <iterator>
6 #include <numeric>
7 #include <utility>
8 #include <vector>
9 
10 #include <termox/widget/size_policy.hpp>
11 #include <termox/widget/widget.hpp>
12 
13 namespace ox::layout::detail {
14 
15 struct Dimension {
16  Widget* widget; // This is nullptr when limit is reached
17  int length;
18 };
19 
21 
24 template <typename Get_policy_t>
25 class Layout_span {
26  private:
27  using Container_t = std::vector<Dimension>;
28 
30  template <typename Get_limit_t>
31  class Iterator {
32  private:
33  using Underlying_t = Container_t::iterator;
34 
35  public:
36  using iterator_category = std::forward_iterator_tag;
37  using value_type = Underlying_t::value_type;
38  using difference_type = Underlying_t::difference_type;
39  using pointer = Underlying_t::pointer;
40  using reference = Underlying_t::reference;
41 
42  public:
44  Iterator(Underlying_t iter,
45  Underlying_t end,
46  Get_policy_t get_policy,
47  Get_limit_t get_limit)
48  : iter_{iter},
49  end_{end},
50  get_policy_{get_policy},
51  get_limit_{get_limit}
52  {
53  while (iter_ != end && iter_->widget == nullptr)
54  ++iter_;
55  }
56 
57  Iterator() = delete;
58  Iterator(Iterator const&) = delete;
59  Iterator(Iterator&&) = delete;
60  auto operator=(Iterator const&) -> Iterator& = delete;
61  auto operator=(Iterator&&) -> Iterator& = delete;
62  ~Iterator() = default;
63 
64  public:
65  auto operator++() -> Iterator&
66  {
67  if (iter_->length == get_limit_(get_policy_(*iter_->widget)))
68  iter_->widget = nullptr;
69  do {
70  ++iter_;
71  } while (iter_ != end_ && iter_->widget == nullptr);
72  return *this;
73  }
74 
75  [[nodiscard]] auto operator*() const -> reference { return *iter_; }
76 
77  [[nodiscard]] auto operator->() const -> pointer
78  {
79  return iter_.operator->();
80  }
81 
82  [[nodiscard]] auto operator==(Container_t::iterator other) const -> bool
83  {
84  return this->iter_ == other;
85  }
86 
87  [[nodiscard]] auto operator!=(Container_t::iterator other) const -> bool
88  {
89  return this->iter_ != other;
90  }
91 
92  [[nodiscard]] auto get_policy() const -> Size_policy const&
93  {
94  return get_policy_(*iter_->widget);
95  }
96 
97  private:
98  Underlying_t iter_;
99  Underlying_t end_;
100  Get_policy_t get_policy_;
101  Get_limit_t get_limit_;
102  };
103 
104  // Needed for clang, GCC finds Constructor without this, but class scope
105  // deduction guide breaks GCC.
106 #ifdef __clang__
107  template <typename Get_limit_t>
108  Iterator(typename Layout_span::Container_t::iterator iter,
109  typename Layout_span::Container_t::iterator end,
110  Get_policy_t get_policy,
111  Get_limit_t get_limit) -> Iterator<decltype(get_limit)>;
112 #endif
113 
114  public:
116  template <typename Iter>
117  Layout_span(Iter first,
118  Iter last,
119  int primary_length,
120  Get_policy_t&& get_policy)
121  : dimensions_{Layout_span::build_dimensions(first,
122  last,
123  primary_length,
124  get_policy)},
125  get_policy_{std::forward<Get_policy_t>(get_policy)}
126  {}
127 
128  public:
130 
131  [[nodiscard]] auto begin_max()
132  {
133  total_stretch_ = this->calculate_total_stretch();
134  return this->begin([](Size_policy const& p) { return p.max(); });
135  }
136 
138 
139  [[nodiscard]] auto begin_min()
140  {
141  total_inverse_stretch_ = this->calculate_total_inverse_stretch();
142  return this->begin([](Size_policy const& p) { return p.min(); });
143  }
144 
145  [[nodiscard]] auto end() -> Container_t::iterator
146  {
147  return dimensions_.end();
148  }
149 
150  [[nodiscard]] auto total_stretch() const -> double
151  {
152  return total_stretch_;
153  }
154 
155  [[nodiscard]] auto total_inverse_stretch() const -> double
156  {
157  return total_inverse_stretch_;
158  }
159 
160  [[nodiscard]] auto entire_length() const -> int
161  {
162  return std::accumulate(
163  dimensions_.begin(), dimensions_.end(), 0,
164  [](int total, Dimension const& d) { return total + d.length; });
165  }
166 
167  [[nodiscard]] auto size() const -> std::size_t
168  {
169  return std::count_if(dimensions_.begin(), dimensions_.end(),
170  [](auto const& d) { return d.widget != nullptr; });
171  }
172 
173  [[nodiscard]] auto get_results() const -> std::vector<int>
174  {
175  auto result = std::vector<int>{};
176  result.reserve(dimensions_.size());
177  std::transform(dimensions_.begin(), dimensions_.end(),
178  std::back_inserter(result),
179  [](auto const& d) { return d.length; });
180  return result;
181  }
182 
183  private:
184  Container_t dimensions_;
185  Get_policy_t get_policy_;
186  mutable double total_stretch_ = 0.;
187  mutable double total_inverse_stretch_ = 0.;
188 
189  private:
191  template <typename Iter>
192  [[nodiscard]] static auto generate_zero_init_dimensions(Iter first,
193  Iter last)
194  -> Container_t
195  {
196  auto result = Container_t{};
197  result.reserve(std::distance(first, last));
198  std::transform(first, last, std::back_inserter(result),
199  [](auto& child) -> Dimension {
200  return {&child, 0uL};
201  });
202  return result;
203  }
204 
205  template <typename Iter_t> // Dimension iterator
206  static void handle_edge(Iter_t first,
207  Iter_t last,
208  std::size_t leftover,
209  bool can_ignore_min)
210  {
211  // First iter is edge, can assume it is not last.
212  if (can_ignore_min)
213  first->length = leftover;
214  else
215  first->length = 0;
216  ++first;
217  while (first != last) {
218  first->length = 0;
219  ++first;
220  }
221  }
222 
223  static void invalidate_each(Container_t& dimensions)
224  {
225  for (auto& d : dimensions)
226  d.widget = nullptr;
227  }
228 
230 
234  static auto set_each_to_min(Container_t& dimensions,
235  std::size_t length,
236  Get_policy_t get_policy) -> bool
237  {
238  // Can assume all Dimension::widget pointers are valid.
239  auto min_sum = 0uL;
240  auto const end = std::end(dimensions);
241  for (auto iter = std::begin(dimensions); iter != end; ++iter) {
242  auto const& policy = get_policy(*(iter->widget));
243  auto const min = policy.min();
244  min_sum += min;
245  if (min_sum > length) { // Stop Condition
246  auto const leftover = length - (min_sum - min);
247  handle_edge(iter, end, leftover, policy.can_ignore_min());
248  invalidate_each(dimensions);
249  return true;
250  }
251  iter->length = min;
252  }
253  return false;
254  }
255 
257  static void set_each_to_hint(Container_t& dimensions,
258  Get_policy_t get_policy)
259  {
260  // Can assume all Dimension::widget pointers are valid.
261  std::for_each(std::begin(dimensions), std::end(dimensions),
262  [get_policy](auto& dimension) {
263  dimension.length =
264  get_policy(*dimension.widget).hint();
265  });
266  }
267 
268  template <typename Iter>
269  [[nodiscard]] static auto build_dimensions(Iter first,
270  Iter last,
271  std::size_t primary_length,
272  Get_policy_t get_policy)
273  -> Container_t
274  {
275  auto dimensions = generate_zero_init_dimensions(first, last);
276  if (!set_each_to_min(dimensions, primary_length, get_policy))
277  set_each_to_hint(dimensions, get_policy);
278  return dimensions;
279  }
280 
282 
283  template <typename Get_limit_t>
284  [[nodiscard]] auto begin(Get_limit_t get_limit)
285  {
286  auto const begin = dimensions_.begin();
287  auto const end = dimensions_.end();
288  auto temp = Iterator{begin, end, get_policy_, get_limit};
289  while (temp != end)
290  ++temp; // This call invalidates elements that are at limit.
291  return Iterator{begin, end, get_policy_, get_limit};
292  }
293 
294  [[nodiscard]] auto calculate_total_stretch() const -> double
295  {
296  auto sum = 0.;
297  auto const end = dimensions_.end();
298  for (auto iter = dimensions_.begin(); iter != end; ++iter) {
299  if (iter->widget != nullptr)
300  sum += get_policy_(*iter->widget).stretch();
301  }
302  return sum;
303  }
304 
305  [[nodiscard]] auto calculate_total_inverse_stretch() const -> double
306  {
307  auto sum = 0.;
308  auto const end = dimensions_.end();
309  for (auto iter = dimensions_.begin(); iter != end; ++iter) {
310  if (iter->widget != nullptr)
311  sum += (1. / get_policy_(*iter->widget).stretch());
312  }
313  return sum;
314  }
315 
316  [[nodiscard]] auto find_valid_begin() const
317  {
318  auto const end = dimensions_.end();
319  for (auto iter = dimensions_.begin(); iter != end; ++iter) {
320  if (iter->widget != nullptr)
321  return iter;
322  }
323  return end;
324  }
325 };
326 
327 } // namespace ox::layout::detail
328 #endif // TERMOX_WIDGET_LAYOUTS_DETAIL_LAYOUT_SPAN_HPP
Defines how a Layout should resize a Widget in one length Dimension.
Definition: size_policy.hpp:11
void max(int value)
Set the maximum length/height that the owning Widget can be.
Definition: size_policy.cpp:32
void min(int value)
Set the minimum length that the owning Widget should be.
Definition: size_policy.cpp:24
Definition: widget.hpp:31
Container view to iterate over a Widget's children, yielding layout info.
Definition: layout_span.hpp:25
Layout_span(Iter first, Iter last, int primary_length, Get_policy_t &&get_policy)
Construct, only considers children from first up to last.
Definition: layout_span.hpp:117
auto begin_min()
Return iterator to the first element, will skip when length == min.
Definition: layout_span.hpp:139
auto begin_max()
Return iterator to the first element, will skip when length == max.
Definition: layout_span.hpp:131
Definition: layout_span.hpp:15