Line data Source code
1 : /**
2 : Copyright (c) 2023-2024 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 "MaterialFlexibleLayout.h"
24 : #include "MaterialEasing.h"
25 : #include "MaterialAppBar.h"
26 : #include "MaterialButton.h"
27 : #include "XL2dSceneContent.h"
28 : #include "XLDirector.h"
29 : #include "XLView.h"
30 : #include "XLAction.h"
31 :
32 : namespace STAPPLER_VERSIONIZED stappler::xenolith::material2d {
33 :
34 144 : void FlexibleLayout::NodeParams::setPosition(float x, float y) {
35 144 : setPosition(Vec2(x, y));
36 144 : }
37 :
38 144 : void FlexibleLayout::NodeParams::setPosition(const Vec2 &pos) {
39 144 : position = pos;
40 144 : mask = Mask(mask | Mask::Position);
41 144 : }
42 :
43 144 : void FlexibleLayout::NodeParams::setAnchorPoint(const Vec2 &pt) {
44 144 : anchorPoint = pt;
45 144 : mask = Mask(mask | Mask::AnchorPoint);
46 144 : }
47 :
48 144 : void FlexibleLayout::NodeParams::setContentSize(const Size2 &size) {
49 144 : contentSize = size;
50 144 : mask = Mask(mask | Mask::ContentSize);
51 144 : }
52 :
53 144 : void FlexibleLayout::NodeParams::setVisible(bool value) {
54 144 : visible = value;
55 144 : mask = Mask(mask | Mask::Visibility);
56 144 : }
57 :
58 132 : void FlexibleLayout::NodeParams::apply(Node *node) const {
59 132 : if (mask & Mask::AnchorPoint) {
60 60 : node->setAnchorPoint(anchorPoint);
61 : }
62 132 : if (mask & Mask::Position) {
63 60 : node->setPosition(position);
64 : }
65 132 : if (mask & Mask::ContentSize) {
66 60 : node->setContentSize(contentSize);
67 : }
68 132 : if (mask & Mask::Visibility) {
69 102 : node->setVisible(visible);
70 : }
71 132 : }
72 :
73 52 : bool FlexibleLayout::init() {
74 52 : if (!DecoratedLayout::init()) {
75 0 : return false;
76 : }
77 :
78 52 : setCascadeOpacityEnabled(true);
79 :
80 52 : return true;
81 : }
82 :
83 52 : void FlexibleLayout::onContentSizeDirty() {
84 52 : DecoratedLayout::onContentSizeDirty();
85 :
86 52 : float realFlexMin = _targetFlexibleMinHeight;
87 52 : float realFlexMax = _targetFlexibleMaxHeight;
88 :
89 52 : if (_flexibleNode) {
90 10 : auto heightLimit = _flexibleNode->getHeightLimits(true);
91 10 : if (!isnan(heightLimit.first)) {
92 10 : realFlexMin = std::max(heightLimit.first, realFlexMin);
93 : }
94 :
95 10 : if (!isnan(heightLimit.second)) {
96 10 : realFlexMax = std::max(heightLimit.second, realFlexMax);
97 : }
98 : }
99 :
100 52 : if (_flexibleHeightFunction) {
101 0 : auto ret = _flexibleHeightFunction();
102 0 : realFlexMin = std::max(ret.first, realFlexMin);
103 0 : realFlexMax = std::min(ret.second, realFlexMax);
104 : }
105 :
106 52 : _realFlexibleMinHeight = realFlexMin;
107 52 : _realFlexibleMaxHeight = realFlexMax;
108 :
109 52 : _flexibleExtraSpace = 0.0f;
110 52 : updateFlexParams();
111 52 : }
112 :
113 10 : void FlexibleLayout::setBaseNode(ScrollView *node, ZOrder zOrder) {
114 10 : if (node != _baseNode) {
115 10 : if (_baseNode) {
116 0 : _baseNode->removeFromParent();
117 0 : _baseNode = nullptr;
118 : }
119 10 : _baseNode = node;
120 10 : if (_baseNode) {
121 10 : _baseNode->setScrollCallback(std::bind(&FlexibleLayout::onScroll, this,
122 : std::placeholders::_1, std::placeholders::_2));
123 10 : if (_baseNode->isVertical()) {
124 10 : _baseNode->setOverscrollFrontOffset(getCurrentFlexibleHeight());
125 : }
126 10 : if (!_baseNode->getParent()) {
127 10 : addChild(_baseNode, zOrder);
128 : }
129 : }
130 10 : _contentSizeDirty = true;
131 : }
132 10 : }
133 :
134 10 : void FlexibleLayout::setFlexibleNode(Surface *node, ZOrder zOrder) {
135 10 : if (node != _flexibleNode) {
136 10 : if (_flexibleNode) {
137 0 : _flexibleNode->removeFromParent();
138 0 : _flexibleNode = nullptr;
139 : }
140 10 : _flexibleNode = node;
141 10 : _appBar = nullptr;
142 10 : if (_flexibleNode) {
143 10 : if (auto bar = dynamic_cast<AppBar *>(_flexibleNode)) {
144 10 : _appBar = bar;
145 : }
146 10 : addChild(_flexibleNode, zOrder);
147 : }
148 10 : _contentSizeDirty = true;
149 : }
150 10 : }
151 :
152 0 : void FlexibleLayout::setFlexibleAutoComplete(bool value) {
153 0 : if (_flexibleAutoComplete != value) {
154 0 : _flexibleAutoComplete = value;
155 : }
156 0 : }
157 :
158 10 : void FlexibleLayout::setFlexibleMinHeight(float height) {
159 10 : if (_targetFlexibleMinHeight != height) {
160 0 : _targetFlexibleMinHeight = height;
161 0 : _contentSizeDirty = true;
162 : }
163 10 : }
164 :
165 10 : void FlexibleLayout::setFlexibleMaxHeight(float height) {
166 10 : if (_targetFlexibleMaxHeight != height) {
167 10 : _targetFlexibleMaxHeight = height;
168 10 : _contentSizeDirty = true;
169 : }
170 10 : }
171 :
172 0 : float FlexibleLayout::getFlexibleMinHeight() const {
173 0 : return _targetFlexibleMinHeight;
174 : }
175 :
176 0 : float FlexibleLayout::getFlexibleMaxHeight() const {
177 0 : return _targetFlexibleMaxHeight;
178 : }
179 :
180 0 : void FlexibleLayout::setFlexibleBaseNode(bool val) {
181 0 : if (_flexibleBaseNode != val) {
182 0 : _flexibleBaseNode = val;
183 0 : _contentSizeDirty = true;
184 : }
185 0 : }
186 :
187 0 : bool FlexibleLayout::isFlexibleBaseNode() const {
188 0 : return _flexibleBaseNode;
189 : }
190 :
191 0 : void FlexibleLayout::setFlexibleHeightFunction(const HeightFunction &cb) {
192 0 : _flexibleHeightFunction = cb;
193 0 : if (cb) {
194 0 : _contentSizeDirty = true;
195 0 : _flexibleLevel = 1.0f;
196 : }
197 0 : }
198 :
199 0 : const FlexibleLayout::HeightFunction &FlexibleLayout::getFlexibleHeightFunction() const {
200 0 : return _flexibleHeightFunction;
201 : }
202 :
203 72 : void FlexibleLayout::updateFlexParams() {
204 72 : NodeParams decorParams, flexibleNodeParams, baseNodeParams;
205 :
206 72 : bool tracked = (_viewDecoration & ViewDecorationFlags::Tracked) != ViewDecorationFlags::None;
207 72 : bool hasTopDecor = (_decorationMask & DecorationMask::Top) != DecorationMask::None;
208 72 : auto size = _contentSize;
209 72 : size.height -= _decorationPadding.bottom;
210 72 : float decor = tracked ? _decorationPadding.top : 0.0f;
211 72 : float flexSize = _realFlexibleMinHeight + (_realFlexibleMaxHeight + decor - _realFlexibleMinHeight) * _flexibleLevel;
212 :
213 72 : if (flexSize >= _realFlexibleMaxHeight && tracked) {
214 0 : float tmpDecor = (flexSize - _realFlexibleMaxHeight);
215 0 : decorParams.setContentSize(Size2(_contentSize.width - _decorationPadding.horizontal(), tmpDecor));
216 0 : size.height -= tmpDecor;
217 0 : flexSize = _realFlexibleMaxHeight;
218 0 : decorParams.setPosition(Vec2(_decorationPadding.left, _contentSize.height));
219 0 : decorParams.setVisible(true);
220 72 : } else if (tracked) {
221 0 : decorParams.setVisible(false);
222 : } else {
223 72 : decorParams.setVisible(hasTopDecor);
224 72 : if (hasTopDecor) {
225 72 : size.height -= _decorationPadding.top;
226 : }
227 : }
228 :
229 72 : flexibleNodeParams.setPosition(_decorationPadding.left, size.height + _decorationPadding.bottom);
230 72 : flexibleNodeParams.setAnchorPoint(Vec2(0, 1));
231 72 : flexibleNodeParams.setContentSize(Size2(size.width - _decorationPadding.horizontal(), flexSize + _flexibleExtraSpace));
232 72 : flexibleNodeParams.setVisible(flexSize > 0.0f);
233 :
234 72 : if (tracked && _sceneContent) {
235 0 : if (_flexibleLevel == 1.0f) {
236 0 : _sceneContent->showViewDecoration();
237 : } else {
238 0 : _sceneContent->hideViewDecoration();
239 : }
240 : }
241 :
242 72 : Padding padding;
243 72 : if (_baseNode) {
244 30 : padding = _baseNode->getPadding();
245 : }
246 :
247 72 : float baseNodeOffset = getCurrentFlexibleHeight();
248 72 : Padding baseNodePadding(padding.setTop(getCurrentFlexibleMax() + _baseNodePadding));
249 :
250 72 : baseNodeParams.setAnchorPoint(Vec2(0, 0));
251 72 : baseNodeParams.setPosition(_decorationPadding.left, _decorationPadding.bottom);
252 :
253 72 : if (_flexibleBaseNode) {
254 72 : baseNodeParams.setContentSize(Size2(size.width - _decorationPadding.horizontal(), size.height + decor));
255 : } else {
256 0 : baseNodeParams.setContentSize(Size2(size.width - _decorationPadding.horizontal(), size.height + decor - getCurrentFlexibleMax() - 0.0f));
257 0 : baseNodePadding = padding.setTop(4.0f);
258 0 : baseNodeOffset = 0.0f;
259 : }
260 :
261 72 : onDecorNode(decorParams);
262 72 : onFlexibleNode(flexibleNodeParams);
263 72 : onBaseNode(baseNodeParams, baseNodePadding, baseNodeOffset);
264 72 : }
265 :
266 220 : void FlexibleLayout::onScroll(float delta, bool finished) {
267 220 : auto size = _baseNode->getScrollableAreaSize();
268 220 : if (!isnan(size) && size < _contentSize.height) {
269 0 : clearFlexibleExpand(0.25f);
270 0 : setFlexibleLevel(1.0f);
271 0 : return;
272 : }
273 :
274 220 : clearFlexibleExpand(0.25f);
275 220 : if (!finished && delta != 0.0f) {
276 220 : const auto distanceFromStart = _baseNode->getDistanceFromStart();
277 220 : const auto trigger = _safeTrigger ? ( _realFlexibleMaxHeight - _realFlexibleMinHeight) : 8.0f;
278 220 : if (isnan(distanceFromStart) || distanceFromStart > trigger || delta < 0) {
279 210 : stopActionByTag(FlexibleLayout::AutoCompleteTag());
280 210 : float height = getCurrentFlexibleHeight();
281 : //float diff = delta.y / _baseNode->getGlobalScale().y;
282 210 : float newHeight = height - delta;
283 210 : if (delta < 0) {
284 0 : float max = getCurrentFlexibleMax();
285 0 : if (newHeight > max) {
286 0 : newHeight = max;
287 : }
288 : } else {
289 210 : if (newHeight < _realFlexibleMinHeight) {
290 200 : newHeight = _realFlexibleMinHeight;
291 : }
292 : }
293 210 : setFlexibleHeight(newHeight);
294 : }
295 220 : } else if (finished) {
296 0 : if (_flexibleAutoComplete) {
297 0 : if (_flexibleLevel < 1.0f && _flexibleLevel > 0.0f) {
298 0 : auto distanceFromStart = _baseNode->getDistanceFromStart();
299 0 : bool open = (_flexibleLevel > 0.5) || (!isnan(distanceFromStart) && distanceFromStart < (_realFlexibleMaxHeight - _realFlexibleMinHeight));
300 0 : auto a = Rc<ActionProgress>::create(progress(0.0f, 0.3f, open?_flexibleLevel:(1.0f - _flexibleLevel)), open?1.0f:0.0f,
301 0 : [this] (float p) {
302 0 : setFlexibleLevel(p);
303 0 : });
304 0 : a->setSourceProgress(_flexibleLevel);
305 0 : a->setTag(FlexibleLayout::AutoCompleteTag());
306 0 : if (open) {
307 0 : runAction(makeEasing(move(a), EasingType::StandardAccelerate));
308 : } else {
309 0 : runAction(makeEasing(move(a), EasingType::StandardDecelerate));
310 : }
311 0 : }
312 : }
313 : }
314 : }
315 :
316 0 : float FlexibleLayout::getFlexibleLevel() const {
317 0 : return _flexibleLevel;
318 : }
319 :
320 210 : void FlexibleLayout::setFlexibleLevel(float value) {
321 210 : if (value > 1.0f) {
322 0 : value = 1.0f;
323 210 : } else if (value < 0.0f) {
324 0 : value = 0.0f;
325 : }
326 :
327 210 : if (value == _flexibleLevel) {
328 190 : return;
329 : }
330 :
331 20 : _flexibleLevel = value;
332 20 : updateFlexParams();
333 : }
334 :
335 0 : void FlexibleLayout::setFlexibleLevelAnimated(float value, float duration) {
336 0 : stopActionByTag("FlexibleLevel"_tag);
337 0 : if (duration <= 0.0f) {
338 0 : setFlexibleLevel(value);
339 : } else {
340 0 : if (_flexibleLevel != value) {
341 0 : auto a = Rc<Sequence>::create(makeEasing(Rc<ActionProgress>::create(
342 0 : duration, _flexibleLevel, value,
343 0 : [this] (float progress) {
344 0 : setFlexibleLevel(progress);
345 0 : }), EasingType::Emphasized), [this, value] {
346 0 : setFlexibleLevel(value);
347 0 : });
348 0 : a->setTag("FlexibleLevel"_tag);
349 0 : runAction(a);
350 0 : }
351 : }
352 0 : }
353 :
354 210 : void FlexibleLayout::setFlexibleHeight(float height) {
355 210 : float size = getCurrentFlexibleMax() - _realFlexibleMinHeight;
356 210 : if (size > 0.0f) {
357 210 : float value = (height - _realFlexibleMinHeight) / (getCurrentFlexibleMax() - _realFlexibleMinHeight);
358 210 : setFlexibleLevel(value);
359 : } else {
360 0 : setFlexibleLevel(1.0f);
361 : }
362 210 : }
363 :
364 0 : void FlexibleLayout::setBaseNodePadding(float val) {
365 0 : if (_baseNodePadding != val) {
366 0 : _baseNodePadding = val;
367 0 : _contentSizeDirty = true;
368 : }
369 0 : }
370 0 : float FlexibleLayout::getBaseNodePadding() const {
371 0 : return _baseNodePadding;
372 : }
373 :
374 292 : float FlexibleLayout::getCurrentFlexibleHeight() const {
375 292 : return (getCurrentFlexibleMax() - _realFlexibleMinHeight) * _flexibleLevel + _realFlexibleMinHeight;
376 : }
377 :
378 784 : float FlexibleLayout::getCurrentFlexibleMax() const {
379 784 : return _realFlexibleMaxHeight + (((_viewDecoration & ViewDecorationFlags::Tracked) != ViewDecorationFlags::None)?_decorationPadding.top:0);
380 : }
381 :
382 52 : void FlexibleLayout::onPush(SceneContent2d *l, bool replace) {
383 52 : DecoratedLayout::onPush(l, replace);
384 :
385 52 : if (_appBar && !replace) {
386 10 : if (auto prev = l->getPrevLayout()) {
387 10 : auto nav = _appBar->getNavNode();
388 :
389 10 : if (auto prevL = dynamic_cast<FlexibleLayout *>(prev)) {
390 10 : if (auto prevBar = prevL->getAppBar()) {
391 0 : if (prevBar->getNavButtonIcon() == IconName::Dynamic_Nav && _appBar->getNavButtonIcon() == IconName::Dynamic_Nav) {
392 0 : auto p = prevBar->getNavNode()->getLeadingIconProgress();
393 0 : if (p >= 1.0f) {
394 0 : nav->setLeadingIconProgress(1.0f);
395 0 : } else if (p < 1.0f) {
396 0 : nav->setLeadingIconProgress(1.0f, 0.25f);
397 : }
398 0 : if (!_appBar->getNavCallback()) {
399 0 : _appBar->setNavCallback([this] {
400 0 : onBackButton();
401 0 : });
402 : }
403 : }
404 0 : return;
405 : }
406 : }
407 :
408 10 : if (_appBar->getNavButtonIcon() == IconName::Dynamic_Nav) {
409 0 : _appBar->getNavNode()->setLeadingIconProgress(1.0f, 0.25f);
410 0 : if (!_appBar->getNavCallback()) {
411 0 : _appBar->setNavCallback([this] {
412 0 : onBackButton();
413 0 : });
414 : }
415 : }
416 : }
417 : }
418 : }
419 :
420 0 : void FlexibleLayout::onForegroundTransitionBegan(SceneContent2d *l, SceneLayout2d *overlay) {
421 0 : DecoratedLayout::onForegroundTransitionBegan(l, overlay);
422 :
423 0 : if (_appBar) {
424 0 : if (auto overlayL = dynamic_cast<FlexibleLayout *>(overlay)) {
425 0 : if (auto overlayBar = overlayL->getAppBar()) {
426 0 : if (overlayBar->getNavButtonIcon() == IconName::Dynamic_Nav && _appBar->getNavButtonIcon() == IconName::Dynamic_Nav) {
427 0 : auto p = overlayBar->getNavNode()->getLeadingIconProgress();
428 0 : if (l->getPrevLayout() == nullptr) {
429 0 : auto nav = _appBar->getNavNode();
430 0 : nav->setLeadingIconProgress(p);
431 0 : nav->setLeadingIconProgress(0.0f, 0.25f);
432 : }
433 : }
434 : }
435 : }
436 : }
437 0 : }
438 :
439 72 : void FlexibleLayout::onDecorNode(const NodeParams &p) {
440 72 : p.apply(_decorationTop);
441 72 : }
442 :
443 72 : void FlexibleLayout::onFlexibleNode(const NodeParams &p) {
444 72 : if (_flexibleNode) {
445 30 : p.apply(_flexibleNode);
446 : }
447 72 : }
448 :
449 72 : void FlexibleLayout::onBaseNode(const NodeParams &p, const Padding &padding, float offset) {
450 72 : if (_baseNode) {
451 30 : p.apply(_baseNode);
452 30 : if (_baseNode->isVertical()) {
453 30 : _baseNode->setOverscrollFrontOffset(offset);
454 30 : _baseNode->setPadding(padding);
455 : }
456 : }
457 72 : }
458 :
459 0 : void FlexibleLayout::setSafeTrigger(bool value) {
460 0 : _safeTrigger = value;
461 0 : }
462 :
463 0 : bool FlexibleLayout::isSafeTrigger() const {
464 0 : return _safeTrigger;
465 : }
466 :
467 0 : void FlexibleLayout::expandFlexibleNode(float extraSpace, float duration) {
468 0 : stopActionByTag("FlexibleExtraSpace"_tag);
469 0 : stopActionByTag("FlexibleExtraClear"_tag);
470 0 : if (duration > 0.0f) {
471 0 : auto prevSpace = _flexibleExtraSpace;
472 0 : if (extraSpace > prevSpace) {
473 0 : auto a = makeEasing(Rc<ActionProgress>::create(
474 0 : duration, [this, extraSpace, prevSpace] (float p) {
475 0 : _flexibleExtraSpace = progress(prevSpace, extraSpace, p);
476 0 : updateFlexParams();
477 0 : }), EasingType::Emphasized);
478 0 : runAction(a, "FlexibleExtraSpace"_tag);
479 0 : } else {
480 0 : auto a = makeEasing(Rc<ActionProgress>::create(
481 0 : duration, [this, extraSpace, prevSpace] (float p) {
482 0 : _flexibleExtraSpace = progress(prevSpace, extraSpace, p);
483 0 : updateFlexParams();
484 0 : }), EasingType::Emphasized);
485 0 : runAction(a, "FlexibleExtraSpace"_tag);
486 0 : }
487 : } else {
488 0 : _flexibleExtraSpace = extraSpace;
489 0 : updateFlexParams();
490 : }
491 0 : }
492 :
493 220 : void FlexibleLayout::clearFlexibleExpand(float duration) {
494 220 : if (_flexibleExtraSpace == 0.0f) {
495 220 : return;
496 : }
497 :
498 0 : if (duration > 0.0f) {
499 0 : auto a = getActionByTag("FlexibleExtraClear"_tag);
500 0 : if (!a) {
501 0 : stopActionByTag("FlexibleExtraSpace"_tag);
502 0 : auto prevSpace = _flexibleExtraSpace;
503 0 : auto a = makeEasing(Rc<ActionProgress>::create(
504 0 : duration, [this, prevSpace] (float p) {
505 0 : _flexibleExtraSpace = progress(prevSpace, 0.0f, p);
506 0 : updateFlexParams();
507 0 : }), EasingType::Emphasized);
508 0 : runAction(a, "FlexibleExtraClear"_tag);
509 0 : }
510 : } else {
511 0 : _flexibleExtraSpace = 0.0f;
512 0 : updateFlexParams();
513 : }
514 : }
515 :
516 52 : DecorationStatus FlexibleLayout::getDecorationStatus() const {
517 52 : if ((_viewDecoration & ViewDecorationFlags::Visible) != ViewDecorationFlags::None) {
518 52 : if ((_viewDecoration & ViewDecorationFlags::Tracked) != ViewDecorationFlags::None) {
519 0 : return (_flexibleLevel == 1.0f) ? DecorationStatus::Visible : DecorationStatus::Hidden;
520 : }
521 52 : return DecorationStatus::Visible;
522 : } else {
523 0 : return DecorationStatus::DontCare;
524 : }
525 : }
526 :
527 : }
|