Line data Source code
1 : /**
2 : Copyright (c) 2023 Stappler LLC <admin@stappler.dev>
3 :
4 : Permission is hereby granted, free of charge, to any person obtaining a copy
5 : of this software and associated documentation files (the "Software"), to deal
6 : in the Software without restriction, including without limitation the rights
7 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 : copies of the Software, and to permit persons to whom the Software is
9 : furnished to do so, subject to the following conditions:
10 :
11 : The above copyright notice and this permission notice shall be included in
12 : all copies or substantial portions of the Software.
13 :
14 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 : THE SOFTWARE.
21 : **/
22 :
23 : #include "XL2dScrollView.h"
24 : #include "XL2dLayerRounded.h"
25 : #include "XLActionEase.h"
26 :
27 : namespace STAPPLER_VERSIONIZED stappler::xenolith::basic2d {
28 :
29 92 : bool ScrollView::Overscroll::init() {
30 92 : if (!VectorSprite::init(Size2(8.0f, 8.0f))) {
31 0 : return false;
32 : }
33 :
34 92 : return true;
35 : }
36 :
37 0 : bool ScrollView::Overscroll::init(Direction dir) {
38 0 : if (!VectorSprite::init(Size2(8.0f, 8.0f))) {
39 0 : return false;
40 : }
41 :
42 0 : _direction = dir;
43 0 : return true;
44 : }
45 :
46 68 : void ScrollView::Overscroll::onContentSizeDirty() {
47 68 : VectorSprite::onContentSizeDirty();
48 :
49 68 : if (_contentSize == Size2::ZERO) {
50 0 : _image->clear();
51 68 : } else if (_image->getImageSize() != _contentSize) {
52 68 : auto image = Rc<VectorImage>::create(_contentSize);
53 68 : updateProgress(image);
54 68 : setImage(move(image));
55 68 : } else if (_progressDirty) {
56 0 : updateProgress(_image);
57 : }
58 68 : }
59 :
60 26330 : void ScrollView::Overscroll::update(const UpdateTime &time) {
61 26330 : VectorSprite::update(time);
62 26330 : if (TimeInterval(time.global - _delayStart) > TimeInterval::microseconds(250000)) {
63 26330 : decrementProgress(time.dt);
64 : }
65 26330 : }
66 :
67 92 : void ScrollView::Overscroll::onEnter(Scene *scene) {
68 92 : VectorSprite::onEnter(scene);
69 92 : scheduleUpdate();
70 92 : }
71 :
72 92 : void ScrollView::Overscroll::onExit() {
73 92 : unscheduleUpdate();
74 92 : VectorSprite::onExit();
75 92 : }
76 :
77 116 : void ScrollView::Overscroll::setDirection(Direction dir) {
78 116 : if (_direction != dir) {
79 38 : _direction = dir;
80 38 : _progressDirty = _contentSizeDirty = true;
81 : }
82 116 : }
83 :
84 0 : ScrollView::Overscroll::Direction ScrollView::Overscroll::getDirection() const {
85 0 : return _direction;
86 : }
87 :
88 26330 : void ScrollView::Overscroll::setProgress(float p) {
89 26330 : p = math::clamp(p, 0.0f, 1.0f);
90 26330 : if (p != _progress) {
91 0 : _progress = p;
92 0 : _progressDirty = _contentSizeDirty = true;
93 : }
94 26330 : }
95 :
96 0 : void ScrollView::Overscroll::incrementProgress(float dt) {
97 0 : setProgress(_progress + (dt * ((1.0 - _progress) * (1.0 - _progress))));
98 0 : _delayStart = Time::now().toMicros();
99 0 : }
100 :
101 26330 : void ScrollView::Overscroll::decrementProgress(float dt) {
102 26330 : setProgress(_progress - (dt * 2.5f));
103 26330 : }
104 :
105 68 : void ScrollView::Overscroll::updateProgress(VectorImage *) {
106 :
107 68 : }
108 :
109 46 : bool ScrollView::init(Layout l) {
110 46 : if (!ScrollViewBase::init(l)) {
111 0 : return false;
112 : }
113 :
114 46 : _indicator = addChild(Rc<LayerRounded>::create(Color4F(1.0f, 1.0f, 1.0f, 0.0f), 2.0f), ZOrder(1));
115 46 : _indicator->setAnchorPoint(Vec2(1, 0));
116 :
117 46 : _overflowFront = addChild(Rc<Overscroll>::create());
118 46 : _overflowBack = addChild(Rc<Overscroll>::create());
119 :
120 46 : setOverscrollColor(Color4F(0.5f, 0.5f, 0.5f, 1.0f));
121 46 : setOverscrollVisible(!_bounce);
122 :
123 46 : return true;
124 : }
125 :
126 58 : void ScrollView::onContentSizeDirty() {
127 58 : ScrollViewBase::onContentSizeDirty();
128 58 : if (isVertical()) {
129 54 : _overflowFront->setAnchorPoint(Vec2(0, 1)); // top
130 54 : _overflowFront->setDirection(Overscroll::Direction::Top);
131 54 : _overflowFront->setPosition(Vec2(0, _contentSize.height - _overscrollFrontOffset));
132 54 : _overflowFront->setContentSize(Size2(_contentSize.width,
133 54 : std::min(_contentSize.width * Overscroll::OverscrollScale, Overscroll::OverscrollMaxHeight)));
134 :
135 54 : _overflowBack->setAnchorPoint(Vec2(0, 0)); // bottom
136 54 : _overflowBack->setDirection(Overscroll::Direction::Bottom);
137 54 : _overflowBack->setPosition(Vec2(0, _overscrollBackOffset));
138 54 : _overflowBack->setContentSize(Size2(_contentSize.width,
139 108 : std::min(_contentSize.width * Overscroll::OverscrollScale, Overscroll::OverscrollMaxHeight)));
140 :
141 : } else {
142 4 : _overflowFront->setAnchorPoint(Vec2(0, 0)); // left
143 4 : _overflowFront->setDirection(Overscroll::Direction::Left);
144 4 : _overflowFront->setPosition(Vec2(_overscrollFrontOffset, 0));
145 4 : _overflowFront->setContentSize(Size2(
146 4 : std::min(_contentSize.height * Overscroll::OverscrollScale, Overscroll::OverscrollMaxHeight),
147 : _contentSize.height));
148 :
149 4 : _overflowBack->setAnchorPoint(Vec2(1, 0)); // right
150 4 : _overflowBack->setDirection(Overscroll::Direction::Right);
151 4 : _overflowBack->setPosition(Vec2(_contentSize.width - _overscrollBackOffset, 0));
152 4 : _overflowBack->setContentSize(Size2(
153 8 : std::min(_contentSize.height * Overscroll::OverscrollScale, Overscroll::OverscrollMaxHeight),
154 : _contentSize.height));
155 : }
156 58 : updateIndicatorPosition();
157 58 : }
158 :
159 46 : void ScrollView::setOverscrollColor(const Color4F &val, bool withOpacity) {
160 46 : _overflowFront->setColor(val, withOpacity);
161 46 : _overflowBack->setColor(val, withOpacity);
162 46 : }
163 :
164 3 : Color4F ScrollView::getOverscrollColor() const {
165 3 : return _overflowFront->getColor();
166 : }
167 :
168 48 : void ScrollView::setOverscrollVisible(bool value) {
169 48 : _overflowFront->setVisible(value);
170 48 : _overflowBack->setVisible(value);
171 48 : }
172 :
173 3 : bool ScrollView::isOverscrollVisible() const {
174 3 : return _overflowFront->isVisible();
175 : }
176 :
177 10 : void ScrollView::setIndicatorColor(const Color4B &val, bool withOpacity) {
178 10 : _indicator->setPathColor(val, withOpacity);
179 10 : }
180 :
181 3 : Color4F ScrollView::getIndicatorColor() const {
182 3 : return _indicator->getColor();
183 : }
184 :
185 5 : void ScrollView::setIndicatorVisible(bool value) {
186 5 : _indicatorVisible = value;
187 5 : if (!isnan(getScrollLength())) {
188 5 : _indicator->setVisible(value);
189 : } else {
190 0 : _indicator->setVisible(false);
191 : }
192 5 : }
193 :
194 3 : bool ScrollView::isIndicatorVisible() const {
195 3 : return _indicatorVisible;
196 : }
197 :
198 299 : void ScrollView::doSetScrollPosition(float pos) {
199 299 : ScrollViewBase::doSetScrollPosition(pos);
200 299 : updateIndicatorPosition();
201 299 : }
202 :
203 0 : void ScrollView::onOverscroll(float delta) {
204 0 : ScrollViewBase::onOverscroll(delta);
205 0 : if (isOverscrollVisible()) {
206 0 : if (delta > 0.0f) {
207 0 : _overflowBack->incrementProgress(delta / 50.0f);
208 : } else {
209 0 : _overflowFront->incrementProgress(-delta / 50.0f);
210 : }
211 : }
212 0 : }
213 :
214 333 : void ScrollView::onScroll(float delta, bool finished) {
215 333 : ScrollViewBase::onScroll(delta, finished);
216 333 : if (!finished) {
217 333 : updateIndicatorPosition();
218 : }
219 333 : }
220 :
221 0 : void ScrollView::onTap(int count, const Vec2 &loc) {
222 0 : if (_tapCallback) {
223 0 : _tapCallback(count, loc);
224 : }
225 0 : }
226 :
227 38 : void ScrollView::onAnimationFinished() {
228 38 : ScrollViewBase::onAnimationFinished();
229 38 : if (_animationCallback) {
230 6 : _animationCallback();
231 : }
232 38 : updateIndicatorPosition();
233 38 : }
234 :
235 328 : void ScrollView::updateIndicatorPosition() {
236 328 : if (!_indicatorVisible) {
237 4 : return;
238 : }
239 :
240 324 : const float scrollWidth = _contentSize.width;
241 324 : const float scrollHeight = _contentSize.height;
242 324 : const float scrollLength = getScrollLength();
243 :
244 324 : updateIndicatorPosition(_indicator, (isVertical()?scrollHeight:scrollWidth) / scrollLength,
245 324 : (_scrollPosition - getScrollMinPosition()) / (getScrollMaxPosition() - getScrollMinPosition()), true, 20.0f);
246 : }
247 :
248 724 : void ScrollView::updateIndicatorPosition(Node *indicator, float size, float value, bool actions, float min) {
249 724 : if (!_indicatorVisible) {
250 0 : return;
251 : }
252 :
253 724 : float scrollWidth = _contentSize.width;
254 724 : float scrollHeight = _contentSize.height;
255 :
256 724 : float scrollLength = getScrollLength();
257 724 : if (isnan(scrollLength)) {
258 78 : indicator->setVisible(false);
259 : } else {
260 646 : indicator->setVisible(_indicatorVisible);
261 : }
262 :
263 724 : auto paddingLocal = _paddingGlobal;
264 724 : if (_indicatorIgnorePadding) {
265 127 : if (isVertical()) {
266 127 : paddingLocal.top = 0;
267 127 : paddingLocal.bottom = 0;
268 : } else {
269 0 : paddingLocal.left = 0;
270 0 : paddingLocal.right = 0;
271 : }
272 : }
273 :
274 724 : if (scrollLength > _scrollSize) {
275 602 : if (isVertical()) {
276 602 : float h = (scrollHeight - 4 - paddingLocal.top - paddingLocal.bottom) * size;
277 602 : if (h < min) {
278 0 : h = min;
279 : }
280 602 : float r = scrollHeight - h - 4 - paddingLocal.top - paddingLocal.bottom;
281 :
282 602 : indicator->setContentSize(Size2(3, h));
283 602 : indicator->setPosition(Vec2(scrollWidth - 2, paddingLocal.bottom + 2 + r * (1.0f - value)));
284 602 : indicator->setAnchorPoint(Vec2(1, 0));
285 : } else {
286 0 : float h = (scrollWidth - 4 - paddingLocal.left - paddingLocal.right) * size;
287 0 : if (h < min) {
288 0 : h = min;
289 : }
290 0 : float r = scrollWidth - h - 4 - paddingLocal.left - paddingLocal.right;
291 :
292 0 : indicator->setContentSize(Size2(h, 3));
293 0 : indicator->setPosition(Vec2(paddingLocal.left + 2 + r * (value), 2));
294 0 : indicator->setAnchorPoint(Vec2(0, 0));
295 : }
296 602 : if (actions) {
297 602 : if (indicator->getOpacity() != 1.0f) {
298 123 : Action* a = indicator->getActionByTag(19);
299 123 : if (!a) {
300 24 : indicator->runAction(Rc<FadeTo>::create(progress(0.1f, 0.0f, indicator->getOpacity()), 1.0f), 19);
301 : }
302 : }
303 :
304 602 : indicator->stopActionByTag(18);
305 602 : auto fade = Rc<Sequence>::create(2.0f, Rc<FadeTo>::create(0.25f, 0.0f));
306 602 : indicator->runAction(fade, 18);
307 602 : }
308 : } else {
309 122 : indicator->setVisible(false);
310 : }
311 : }
312 :
313 30 : void ScrollView::setPadding(const Padding &p) {
314 30 : if (p != _paddingGlobal) {
315 10 : float offset = (isVertical()?_paddingGlobal.top:_paddingGlobal.left);
316 10 : float newOffset = (isVertical()?p.top:p.left);
317 10 : ScrollViewBase::setPadding(p);
318 :
319 10 : if (offset != newOffset) {
320 10 : setScrollPosition(getScrollPosition() + (offset - newOffset));
321 : }
322 : }
323 30 : }
324 :
325 43 : void ScrollView::setOverscrollFrontOffset(float value) {
326 43 : if (_overscrollFrontOffset != value) {
327 33 : _overscrollFrontOffset = value;
328 33 : _contentSizeDirty = true;
329 : }
330 43 : }
331 3 : float ScrollView::getOverscrollFrontOffset() const {
332 3 : return _overscrollFrontOffset;
333 : }
334 :
335 3 : void ScrollView::setOverscrollBackOffset(float value) {
336 3 : if (_overscrollBackOffset != value) {
337 3 : _overscrollBackOffset = value;
338 3 : _contentSizeDirty = true;
339 : }
340 3 : }
341 3 : float ScrollView::getOverscrollBackOffset() const {
342 3 : return _overscrollBackOffset;
343 : }
344 :
345 3 : void ScrollView::setIndicatorIgnorePadding(bool value) {
346 3 : if (_indicatorIgnorePadding != value) {
347 3 : _indicatorIgnorePadding = value;
348 : }
349 3 : }
350 3 : bool ScrollView::isIndicatorIgnorePadding() const {
351 3 : return _indicatorIgnorePadding;
352 : }
353 :
354 3 : void ScrollView::setTapCallback(const TapCallback &cb) {
355 3 : _tapCallback = cb;
356 3 : }
357 :
358 3 : const ScrollView::TapCallback &ScrollView::getTapCallback() const {
359 3 : return _tapCallback;
360 : }
361 :
362 3 : void ScrollView::setAnimationCallback(const AnimationCallback &cb) {
363 3 : _animationCallback = cb;
364 3 : }
365 :
366 3 : const ScrollView::AnimationCallback &ScrollView::getAnimationCallback() const {
367 3 : return _animationCallback;
368 : }
369 :
370 0 : void ScrollView::update(const UpdateTime &time) {
371 0 : auto newpos = getScrollPosition();
372 0 : auto factor = std::min(64.0f, _adjustValue);
373 :
374 0 : switch (_adjust) {
375 0 : case Adjust::Front:
376 0 : newpos += (45.0f + progress(0.0f, 200.0f, factor / 32.0f)) * time.dt;
377 0 : break;
378 0 : case Adjust::Back:
379 0 : newpos -= (45.0f + progress(0.0f, 200.0f, factor / 32.0f)) * time.dt;
380 0 : break;
381 0 : default:
382 0 : break;
383 : }
384 :
385 0 : if (newpos != getScrollPosition()) {
386 0 : if (newpos < getScrollMinPosition()) {
387 0 : newpos = getScrollMinPosition();
388 0 : } else if (newpos > getScrollMaxPosition()) {
389 0 : newpos = getScrollMaxPosition();
390 : }
391 0 : _root->stopAllActionsByTag("ScrollViewAdjust"_tag);
392 0 : setScrollPosition(newpos);
393 : }
394 0 : }
395 :
396 0 : void ScrollView::runAdjustPosition(float newPos, float factor) {
397 0 : if (!isnan(newPos)) {
398 0 : if (newPos < getScrollMinPosition()) {
399 0 : newPos = getScrollMinPosition();
400 0 : } else if (newPos > getScrollMaxPosition()) {
401 0 : newPos = getScrollMaxPosition();
402 : }
403 0 : if (_adjustValue != newPos) {
404 0 : _adjustValue = newPos;
405 0 : auto dist = fabsf(newPos - getScrollPosition());
406 :
407 0 : auto t = 0.15f;
408 0 : if (dist < 20.0f) {
409 0 : t = 0.15f;
410 0 : } else if (dist > 220.0f) {
411 0 : t = 0.45f;
412 : } else {
413 0 : t = progress(0.15f, 0.45f, (dist - 20.0f) / 200.0f);
414 : }
415 0 : _root->stopAllActionsByTag("ScrollViewAdjust"_tag);
416 0 : auto a = Rc<Sequence>::create(Rc<EaseQuadraticActionInOut>::create(Rc<MoveTo>::create(t,
417 0 : isVertical()?Vec2(_root->getPosition().x, newPos + _scrollSize):Vec2(-newPos, _root->getPosition().y))),
418 0 : [this] { _adjustValue = nan(); });
419 0 : _root->runAction(a, "ScrollViewAdjust"_tag);
420 0 : }
421 : }
422 0 : }
423 0 : void ScrollView::runAdjust(float pos, float factor) {
424 0 : auto scrollPos = getScrollPosition();
425 0 : auto scrollSize = getScrollSize();
426 :
427 0 : float newPos = nan();
428 0 : if (scrollSize < 64.0f + 48.0f) {
429 0 : newPos = ((pos - 64.0f) + (pos - scrollSize + 48.0f)) / 2.0f;
430 0 : } else if (pos < scrollPos + 64.0f) {
431 0 : newPos = pos - 64.0f;
432 0 : } else if (pos > scrollPos + scrollSize - 48.0f) {
433 0 : newPos = pos - scrollSize + 48.0f;
434 : }
435 :
436 0 : runAdjustPosition(newPos, factor);
437 0 : }
438 :
439 0 : void ScrollView::scheduleAdjust(Adjust a, float val) {
440 0 : _adjustValue = val;
441 0 : if (a != _adjust) {
442 0 : _adjust = a;
443 0 : switch (_adjust) {
444 0 : case Adjust::None:
445 0 : unscheduleUpdate();
446 0 : _adjustValue = nan();
447 0 : break;
448 0 : default:
449 0 : scheduleUpdate();
450 0 : break;
451 : }
452 : }
453 0 : }
454 :
455 3 : Value ScrollView::save() const {
456 3 : Value ret;
457 3 : ret.setDouble(getScrollRelativePosition(), "value");
458 3 : return ret;
459 0 : }
460 :
461 3 : void ScrollView::load(const Value &d) {
462 3 : if (d.isDictionary()) {
463 0 : _savedRelativePosition = d.getDouble("value");
464 0 : if (_controller) {
465 0 : _controller->onScrollPosition(true);
466 : }
467 : }
468 3 : }
469 :
470 6 : ScrollController::Item * ScrollView::getItemForNode(Node *node) const {
471 6 : auto &items = _controller->getItems();
472 84 : for (auto &it : items) {
473 84 : if (it.node && it.node == node) {
474 6 : return ⁢
475 : }
476 : }
477 0 : return nullptr;
478 : }
479 :
480 3 : Rc<ActionProgress> ScrollView::resizeNode(Node *node, float newSize, float duration, Function<void()> &&cb) {
481 3 : return resizeNode(getItemForNode(node), newSize, duration, move(cb));
482 : }
483 :
484 6 : Rc<ActionProgress> ScrollView::resizeNode(ScrollController::Item *item, float newSize, float duration, Function<void()> &&cb) {
485 6 : if (!item) {
486 0 : return nullptr;
487 : }
488 :
489 6 : auto &items = _controller->getItems();
490 :
491 6 : float sourceSize = isVertical()?item->size.height:item->size.width;
492 6 : float tergetSize = newSize;
493 :
494 : struct ItemRects {
495 : float startPos;
496 : float startSize;
497 : float targetPos;
498 : float targetSize;
499 : ScrollController::Item *item;
500 : };
501 :
502 6 : Vector<ItemRects> vec;
503 :
504 6 : float offset = 0.0f;
505 462 : for (auto &it : items) {
506 456 : if (it.node && &it == item) {
507 6 : offset += sourceSize - tergetSize;
508 6 : vec.emplace_back(ItemRects{
509 6 : getNodeScrollPosition(it.pos),
510 6 : getNodeScrollSize(it.size),
511 6 : getNodeScrollPosition(it.pos),
512 : tergetSize,
513 : &it});
514 450 : } else if (offset != 0.0f) {
515 372 : vec.emplace_back(ItemRects{
516 372 : getNodeScrollPosition(it.pos),
517 372 : getNodeScrollSize(it.size),
518 372 : getNodeScrollPosition(it.pos) - offset,
519 372 : getNodeScrollSize(it.size),
520 : &it});
521 : }
522 : }
523 :
524 0 : auto ret = Rc<ActionProgress>::create(duration, [this, vec] (float p) {
525 0 : for (auto &it : vec) {
526 0 : if (isVertical()) {
527 0 : it.item->pos.y = progress(it.startPos, it.targetPos, p);
528 0 : it.item->size.height = progress(it.startSize, it.targetSize, p);
529 : } else {
530 0 : it.item->pos.x = progress(it.startPos, it.targetPos, p);
531 0 : it.item->size.width = progress(it.startSize, it.targetSize, p);
532 : }
533 0 : if (it.item->node) {
534 0 : updateScrollNode(it.item->node, it.item->pos, it.item->size, it.item->zIndex, it.item->name);
535 : }
536 : }
537 0 : _controller->onScrollPosition(true);
538 0 : }, [] () {
539 :
540 6 : }, [cb = move(cb)] () {
541 0 : if (cb) {
542 0 : cb();
543 : }
544 12 : });
545 6 : return ret;
546 6 : }
547 :
548 3 : Rc<ActionProgress> ScrollView::removeNode(Node *node, float duration, Function<void()> &&cb, bool disable) {
549 3 : return removeNode(getItemForNode(node), duration, move(cb), disable);
550 : }
551 :
552 3 : Rc<ActionProgress> ScrollView::removeNode(ScrollController::Item *item, float duration, Function<void()> &&cb, bool disable) {
553 3 : return resizeNode(item, 0.0f, duration, [item, cb = move(cb), disable] {
554 0 : if (item->node) {
555 0 : if (item->node->isRunning()) {
556 0 : item->node->removeFromParent();
557 : }
558 0 : item->node = nullptr;
559 0 : item->handle = nullptr;
560 0 : if (disable) {
561 0 : item->nodeFunction = nullptr;
562 : }
563 : }
564 0 : if (cb) {
565 0 : cb();
566 : }
567 3 : });
568 : }
569 :
570 : }
|