Sparkle 0.0.1
Loading...
Searching...
No Matches
spk_scrollable_area.hpp
1#pragma once
2
3#include <algorithm>
4#include <cmath>
5#include <cstdint>
6#include <limits>
7#include <stdexcept>
8#include <string>
9#include <utility>
10
11#include "structure/design_pattern/spk_contract_provider.hpp"
12#include "structure/system/event/spk_mouse_event.hpp"
13#include "structure/widget/spk_container_widget.hpp"
14#include "structure/widget/spk_scroll_bar.hpp"
15#include "structure/widget/spk_widget.hpp"
16
17namespace spk
18{
19 template <typename TContent>
30 class ScrolableArea : public Widget
31 {
32 private:
33 struct ScrollLayout
34 {
35 bool showV = false;
36 bool showH = false;
37
38 Vector2UInt viewportSize = Vector2UInt::Zero;
39
40 Extend2D vBar{};
41 Extend2D hBar{};
42
43 float vThumbRatio = 1.0f;
44 float hThumbRatio = 1.0f;
45
46 Vector2Int contentAnchor = {0, 0};
47 Vector2UInt contentSize = Vector2UInt::Zero;
48 };
49
50 ContainerWidget _contentContainer;
51 TContent _content;
52
53 ScrollBar _horizontalScrollBar;
54 ScrollBar _verticalScrollBar;
55
56 Vector2UInt _lastViewportSize = Vector2UInt::Zero;
57
58 spk::ObservableValue<float>::Contract _horizontalRatioContract;
59 spk::ObservableValue<float>::Contract _verticalRatioContract;
60
61 private:
62 static Vector2UInt::value_type _subtractOrZero(Vector2UInt::value_type p_value, Vector2UInt::value_type p_amount)
63 {
64 return (p_amount >= p_value ? 0 : p_value - p_amount);
65 }
66
67 static Vector2UInt _resolveContentSize(const Vector2UInt &p_minimal, const Vector2UInt &p_desired, const Vector2UInt &p_maximal)
68 {
69 constexpr Vector2UInt::value_type MaxValue = std::numeric_limits<Vector2UInt::value_type>::max();
70 const Vector2UInt fallback = Vector2UInt::max(p_minimal, p_desired);
71 Vector2UInt result = p_maximal;
72
73 if (result.x == MaxValue)
74 {
75 result.x = fallback.x;
76 }
77 if (result.y == MaxValue)
78 {
79 result.y = fallback.y;
80 }
81
82 return result;
83 }
84
85 static Vector2UInt _availableSize(const Vector2UInt &p_base, bool p_showVertical, bool p_showHorizontal, Vector2UInt::value_type p_scrollbarThickness)
86 {
87 Vector2UInt available = p_base;
88 if (p_showVertical)
89 {
90 available.x = _subtractOrZero(available.x, p_scrollbarThickness);
91 }
92 if (p_showHorizontal)
93 {
94 available.y = _subtractOrZero(available.y, p_scrollbarThickness);
95 }
96 return (available);
97 }
98
99 static std::pair<bool, bool> _resolveVisibility(const Vector2UInt &p_containerSize, const Vector2UInt &p_contentSize, Vector2UInt::value_type p_scrollbarThickness)
100 {
101 bool showV = false;
102 bool showH = false;
103
104 bool dirty = true;
105 while (dirty)
106 {
107 const Vector2UInt avail = _availableSize(p_containerSize, showV, showH, p_scrollbarThickness);
108
109 const bool needV = (p_contentSize.y > avail.y);
110 const bool needH = (p_contentSize.x > avail.x);
111
112 dirty = (needV != showV) || (needH != showH);
113 showV = needV;
114 showH = needH;
115 }
116
117 return {showV, showH};
118 }
119
120 ScrollLayout _computeLayout() const
121 {
122 ScrollLayout out;
123
124 const Extend2D g = geometry();
125 const Vector2UInt containerSize = g.size;
126 const Vector2UInt::value_type thickness = scrollBarWidth();
127
128 out.contentSize = _resolveContentSize(
129 _content.sizeHint().minimal(),
130 _content.sizeHint().desired(),
131 _content.sizeHint().maximal());
132
133 std::tie(out.showV, out.showH) = _resolveVisibility(containerSize, out.contentSize, thickness);
134 out.viewportSize = _availableSize(containerSize, out.showV, out.showH, thickness);
135
136 if (out.showV)
137 {
138 out.vBar = Extend2D{
139 {static_cast<int>(containerSize.x) - static_cast<int>(thickness), 0},
140 {thickness, _subtractOrZero(containerSize.y, out.showH ? thickness : 0)}};
141 }
142
143 if (out.showH)
144 {
145 out.hBar = Extend2D{
146 {0, static_cast<int>(containerSize.y) - static_cast<int>(thickness)},
147 {_subtractOrZero(containerSize.x, out.showV ? thickness : 0), thickness}};
148 }
149
150 out.hThumbRatio = (out.contentSize.x == 0) ? 1.0f : std::clamp(static_cast<float>(out.viewportSize.x) / static_cast<float>(out.contentSize.x), 0.0f, 1.0f);
151
152 out.vThumbRatio = (out.contentSize.y == 0) ? 1.0f : std::clamp(static_cast<float>(out.viewportSize.y) / static_cast<float>(out.contentSize.y), 0.0f, 1.0f);
153
154 const float hRatio = out.showH ? _horizontalScrollBar.slider().ratioValue() : 0.0f;
155 const float vRatio = out.showV ? _verticalScrollBar.slider().ratioValue() : 0.0f;
156
157 const float hRange = (out.contentSize.x > out.viewportSize.x) ? float(out.contentSize.x - out.viewportSize.x) : 0.0f;
158 const float vRange = (out.contentSize.y > out.viewportSize.y) ? float(out.contentSize.y - out.viewportSize.y) : 0.0f;
159
160 const int32_t hOffset = static_cast<int32_t>(std::lround(hRange * hRatio));
161 const int32_t vOffset = static_cast<int32_t>(std::lround(vRange * vRatio));
162
163 out.contentAnchor = spk::Vector2Int::Zero;
164 out.contentAnchor.x -= hOffset;
165 out.contentAnchor.y -= vOffset;
166
167 return (out);
168 }
169
170 void _applyLayout(const ScrollLayout &p_layout)
171 {
172 _lastViewportSize = p_layout.viewportSize;
173
174 _contentContainer.setGeometry({{0, 0}, p_layout.viewportSize});
175
176 _horizontalScrollBar.slider().setThumbSizeRatio(p_layout.hThumbRatio);
177 _verticalScrollBar.slider().setThumbSizeRatio(p_layout.vThumbRatio);
178
179 if (p_layout.showV)
180 {
181 _verticalScrollBar.setGeometry(p_layout.vBar);
182 _verticalScrollBar.activate();
183 }
184 else
185 {
186 auto block = _verticalScrollBar.slider().ratio().onEdition().block();
187 _verticalScrollBar.slider().setRatio(0.0f);
188 _verticalScrollBar.deactivate();
189 }
190
191 if (p_layout.showH)
192 {
193 _horizontalScrollBar.setGeometry(p_layout.hBar);
194 _horizontalScrollBar.activate();
195 }
196 else
197 {
198 auto block = _horizontalScrollBar.slider().ratio().onEdition().block();
199 _horizontalScrollBar.slider().setRatio(0.0f);
200 _horizontalScrollBar.deactivate();
201 }
202
203 _content.setGeometry({p_layout.contentAnchor, p_layout.contentSize});
204 }
205
206 protected:
210 void _onGeometryChange() override
211 {
212 _applyLayout(_computeLayout());
213 }
214
219 void _onMouseWheelEvent(MouseWheelEvent &p_event) override
220 {
221 const ScrollLayout layout = _computeLayout();
222 if (layout.showV == false || layout.viewportSize.y == 0)
223 {
224 return;
225 }
226
227 const float contentHeight = static_cast<float>(layout.contentSize.y);
228 const float viewportHeight = static_cast<float>(layout.viewportSize.y);
229 const float verticalRange = contentHeight > viewportHeight ? contentHeight - viewportHeight : 0.0f;
230 if (verticalRange <= 0.0f)
231 {
232 return;
233 }
234
235 constexpr float ScrollFactor = 0.25f;
236 const float scrollPixels = std::max(1.0f, viewportHeight * ScrollFactor);
237 const float ratioDelta = -p_event.wheel * (scrollPixels / verticalRange);
238 if (ratioDelta == 0.0f)
239 {
240 return;
241 }
242
243 _verticalScrollBar.slider().adjustRatio(ratioDelta);
244 p_event.isConsumed = true;
245 }
246
247 public:
248 template <typename... TContentArguments>
256 ScrolableArea(const std::wstring &p_name, Widget *p_parent, TContentArguments &&...p_contentArgs) :
257 Widget(p_name, p_parent),
258 _contentContainer(p_name + L"/ScrolableArea_ContentContainer", this),
259 _horizontalScrollBar(p_name + L"/ScrolableArea_HorizontalScrollBar", this),
260 _verticalScrollBar(p_name + L"/ScrolableArea_VerticalScrollBar", this),
261 _content(p_name + L"/ScrolableArea_ContentContainer/Content", &_contentContainer, std::forward<TContentArguments>(p_contentArgs)...)
262 {
263 _horizontalScrollBar.setMinimalWidth(32);
264 _verticalScrollBar.setMinimalWidth(32);
265
266 _contentContainer.setLayer(0);
267 _contentContainer.activate();
268 _content.setLayer(0);
269 _content.activate();
270
271 _horizontalScrollBar.setOrientation(Orientation::Horizontal);
272 _horizontalScrollBar.setButtonVisibility(Enablement::Disable);
273
274 _verticalScrollBar.setOrientation(Orientation::Vertical);
275 _verticalScrollBar.setButtonVisibility(Enablement::Disable);
276
278 const Vector2UInt viewport = _content.sizeHint().minimal();
279 const Vector2UInt extent = _resolveContentSize(
280 _content.sizeHint().minimal(),
281 _content.sizeHint().desired(),
282 _content.sizeHint().maximal());
283 const Vector2UInt::value_type thickness = scrollBarWidth();
284
285 bool showV = false;
286 bool showH = false;
287 std::tie(showV, showH) = _resolveVisibility(viewport, extent, thickness);
288
289 Vector2UInt result = viewport;
290 if (showV == true)
291 {
292 result.x += thickness;
293 }
294 if (showH == true)
295 {
296 result.y += thickness;
297 }
298 return (result);
299 });
300
302 return _resolveContentSize(
303 _content.sizeHint().minimal(),
304 _content.sizeHint().desired(),
305 _content.sizeHint().maximal());
306 });
307
308 _horizontalRatioContract = _horizontalScrollBar.slider().ratio().onEdition().subscribe([this]() {
310 });
311 _verticalRatioContract = _verticalScrollBar.slider().ratio().onEdition().subscribe([this]() {
313 });
314
315 activate();
316 }
317
322 void setScrollBarWidth(const size_t p_scrollBarWidth)
323 {
324 _verticalScrollBar.setMinimalWidth(p_scrollBarWidth);
325 _horizontalScrollBar.setMinimalWidth(p_scrollBarWidth);
326 sizeHint().release();
327 }
328
333 size_t scrollBarWidth() const
334 {
335 return (std::max(_verticalScrollBar.minimalWidth(), _horizontalScrollBar.minimalWidth()));
336 }
337
342 TContent &content()
343 {
344 return (_content);
345 }
346
351 const TContent &content() const
352 {
353 return (_content);
354 }
355 };
356}
void activate()
Sets the state to Activated.
Definition spk_activable_object.hpp:54
Widget that activates itself as a generic container.
Definition spk_container_widget.hpp:18
spk::TContractProvider< TType >::Contract Contract
Contract type for subscriptions.
Definition spk_observable_value.hpp:27
void configureMaximalGenerator(Generator p_generator)
Configures the maximal size generator.
Definition spk_resizable_element.cpp:31
void release()
Releases all cached size hint values.
Definition spk_resizable_element.cpp:36
void configureMinimalGenerator(Generator p_generator)
Configures the minimal size generator.
Definition spk_resizable_element.cpp:21
SizeHint & sizeHint()
Returns mutable size hint data.
Definition spk_resizable_element.cpp:86
ScrolableArea(const std::wstring &p_name, Widget *p_parent, TContentArguments &&...p_contentArgs)
Builds a scrollable area with the given content arguments.
Definition spk_scrollable_area.hpp:256
const TContent & content() const
Returns the content widget.
Definition spk_scrollable_area.hpp:351
size_t scrollBarWidth() const
Returns the scrollbar width.
Definition spk_scrollable_area.hpp:333
void _onMouseWheelEvent(MouseWheelEvent &p_event) override
Handles mouse wheel scrolling.
Definition spk_scrollable_area.hpp:219
TContent & content()
Returns the content widget.
Definition spk_scrollable_area.hpp:342
void setScrollBarWidth(const size_t p_scrollBarWidth)
Sets the scrollbar width for both orientations.
Definition spk_scrollable_area.hpp:322
void _onGeometryChange() override
Updates layout when geometry changes.
Definition spk_scrollable_area.hpp:210
Scroll bar widget with buttons and a slider.
Definition spk_scroll_bar.hpp:24
const spk::Extend2D & geometry() const
Returns the current widget geometry.
Definition spk_widget.cpp:195
Widget(const std::wstring &p_name, Widget *p_parent)
Builds a widget optionally attached to a parent.
Definition spk_widget.cpp:103
void requireGeometryChange()
Marks the widget view region as needing recomputation.
Definition spk_widget.cpp:215
STL namespace.
Axis-aligned rectangle defined by an anchor point and size.
Definition spk_extend_2d.hpp:24
bool isConsumed
Indicates whether the event has been handled.
Definition spk_ievent.hpp:17
static IVector2 max(const IVector2 &p_a, const IVector2 &p_b)
Definition spk_vector2.hpp:635
TType x
X component.
Definition spk_vector2.hpp:44
uint32_t value_type
Definition spk_vector2.hpp:39
static const IVector2 Zero
Definition spk_vector2.hpp:96
TType y
Y component.
Definition spk_vector2.hpp:48
Mouse wheel scroll event.
Definition spk_mouse_event.hpp:62
float wheel
Wheel delta expressed in scroll units.
Definition spk_mouse_event.hpp:64