QmlMaterial 0.1.0
Loading...
Searching...
No Matches
linear_layout.hpp
1#pragma once
2
3#include <algorithm>
4#include <cmath>
5#include <set>
6#include <vector>
7
8namespace qml_material
9{
10
11class LinearLayout {
12public:
13 bool reset(int count, double estimate, double spacing) {
14 if (count < 0 || ! validExtent(estimate) || ! validExtent(spacing) ||
15 ! std::isfinite((estimate + spacing) * count))
16 return false;
17 m_spacing = spacing;
18 m_extents.assign(count, estimate);
19 m_positive.clear();
20 if (estimate > 0)
21 for (int i = 0; i < count; ++i) m_positive.insert(m_positive.end(), i);
22 m_tree.assign(size_t(count) + 1, 0);
23 for (size_t i = 1; i < m_tree.size(); ++i) {
24 m_tree[i] += estimate + spacing;
25 const size_t parent = i + lowBit(i);
26 if (parent < m_tree.size()) m_tree[parent] += m_tree[i];
27 }
28 return true;
29 }
30
31 int count() const { return int(m_extents.size()); }
32 double extent(int index) const { return m_extents.at(index); }
33 double spacing() const { return m_spacing; }
34
35 bool setExtent(int index, double value) {
36 if (index < 0 || index >= count() || ! validExtent(value)) return false;
37 if (! std::isfinite(offset(count()) - m_extents[index] + value)) return false;
38 const double delta = value - m_extents[index];
39 m_extents[index] = value;
40 if (value > 0)
41 m_positive.insert(index);
42 else
43 m_positive.erase(index);
44 for (size_t i = size_t(index) + 1; i < m_tree.size(); i += lowBit(i)) m_tree[i] += delta;
45 return true;
46 }
47
48 double offset(int index) const {
49 double result = 0;
50 for (size_t i = size_t(std::clamp(index, 0, count())); i; i -= lowBit(i))
51 result += m_tree[i];
52 return result;
53 }
54
55 double totalExtent() const { return count() ? std::max(0.0, offset(count()) - m_spacing) : 0; }
56
57 // Gaps belong to the preceding row; equal starts select the last zero-sized row.
58 int indexAt(double position) const {
59 if (! count() || ! std::isfinite(position)) return -1;
60 if (position < 0) return 0;
61 size_t index = 0;
62 size_t bit = 1;
63 while (bit <= size_t(count()) / 2) bit <<= 1;
64 double prefix = 0;
65 for (; bit; bit >>= 1) {
66 const size_t next = index + bit;
67 if (next < m_tree.size() && prefix + m_tree[next] <= position) {
68 prefix += m_tree[next];
69 index = next;
70 }
71 }
72 return std::min(int(index), count() - 1);
73 }
74
75 struct Range {
76 int first = -1;
77 int last = -1;
78 };
79
80 // Positive-area intersections with the half-open viewport, excluding spacing.
81 Range visibleRange(double begin, double end) const {
82 if (! std::isfinite(begin) || ! std::isfinite(end) || begin >= end || m_positive.empty())
83 return {};
84 int first = indexAt(begin);
85 if (offset(first) + extent(first) <= begin) ++first;
86 auto it = m_positive.lower_bound(first);
87 if (it == m_positive.end() || offset(*it) >= end) return {};
88 first = *it;
89 int last = indexAt(end);
90 if (offset(last) >= end) --last;
91 auto past = m_positive.upper_bound(last);
92 if (past == m_positive.begin()) return {};
93 --past;
94 return { first, *past };
95 }
96
97private:
98 static size_t lowBit(size_t value) { return value & (~value + 1); }
99 static bool validExtent(double value) { return std::isfinite(value) && value >= 0; }
100 double m_spacing { 0 };
101 std::vector<double> m_extents;
102 std::vector<double> m_tree;
103 std::set<int> m_positive;
104};
105
106} // namespace qml_material