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 "XL2dSceneContent.h"
24 : #include "XL2dScene.h"
25 : #include "XL2dFrameContext.h"
26 : #include "XL2dSceneLayout.h"
27 : #include "XL2dSceneLight.h"
28 : #include "XLDirector.h"
29 : #include "XLView.h"
30 : #include "XLFrameInfo.h"
31 :
32 : namespace STAPPLER_VERSIONIZED stappler::xenolith::basic2d {
33 :
34 10 : SceneContent2d::~SceneContent2d() { }
35 :
36 10 : bool SceneContent2d::init() {
37 10 : if (!SceneContent::init()) {
38 0 : return false;
39 : }
40 :
41 10 : _2dContext = Rc<FrameContext2d>::create();
42 10 : _frameContext = _2dContext;
43 :
44 10 : return true;
45 : }
46 :
47 10 : void SceneContent2d::onEnter(Scene *scene) {
48 10 : SceneContent::onEnter(scene);
49 :
50 30 : for (auto &it : _lights) {
51 20 : it->onEnter(scene);
52 : }
53 10 : }
54 :
55 10 : void SceneContent2d::onExit() {
56 30 : for (auto &it : _lights) {
57 20 : it->onExit();
58 : }
59 :
60 10 : SceneContent::onExit();
61 10 : }
62 :
63 20 : void SceneContent2d::onContentSizeDirty() {
64 20 : SceneContent::onContentSizeDirty();
65 :
66 96 : for (auto &node : _layouts) {
67 76 : updateLayoutNode(node);
68 : }
69 :
70 20 : for (auto &overlay : _overlays) {
71 0 : updateLayoutNode(overlay);
72 : }
73 20 : }
74 :
75 0 : void SceneContent2d::replaceLayout(SceneLayout2d *node) {
76 0 : if (!node || node->isRunning()) {
77 0 : return;
78 : }
79 :
80 0 : if (_layouts.empty()) {
81 0 : pushLayout(node);
82 0 : return;
83 : }
84 :
85 0 : updateLayoutNode(node);
86 :
87 0 : ZOrder zIndex = -ZOrder(_layouts.size()) - ZOrder(2);
88 0 : for (auto n : _layouts) {
89 0 : n->setLocalZOrder(zIndex);
90 0 : zIndex ++;
91 0 : }
92 :
93 0 : _layouts.push_back(node);
94 0 : addChild(node, zIndex);
95 :
96 0 : for (auto &it : _layouts) {
97 0 : if (it != node) {
98 0 : it->onPopTransitionBegan(this, true);
99 : } else {
100 0 : it->onPush(this, true);
101 : }
102 : }
103 :
104 0 : auto fn = [this, node] {
105 0 : for (auto &it : _layouts) {
106 0 : if (it != node) {
107 0 : it->onPop(this, true);
108 : } else {
109 0 : it->onPushTransitionEnded(this, true);
110 : }
111 : }
112 0 : replaceNodes();
113 0 : updateBackButtonStatus();
114 0 : };
115 :
116 0 : if (auto enter = node->makeEnterTransition(this)) {
117 0 : node->runAction(Rc<Sequence>::create(enter, fn));
118 : } else {
119 0 : fn();
120 0 : }
121 : }
122 :
123 136 : void SceneContent2d::pushLayout(SceneLayout2d *node) {
124 136 : if (!node || node->isRunning()) {
125 0 : return;
126 : }
127 :
128 136 : updateLayoutNode(node);
129 136 : pushNodeInternal(node, nullptr);
130 : }
131 :
132 0 : void SceneContent2d::replaceTopLayout(SceneLayout2d *node) {
133 0 : if (!node || node->isRunning()) {
134 0 : return;
135 : }
136 :
137 0 : if (!_layouts.empty()) {
138 0 : auto back = _layouts.back();
139 0 : _layouts.pop_back();
140 0 : back->onPopTransitionBegan(this, false);
141 :
142 : // just push node, then silently remove previous
143 :
144 0 : pushNodeInternal(node, [this, back] {
145 0 : eraseLayout(back);
146 0 : back->onPop(this, false);
147 0 : });
148 0 : }
149 : }
150 :
151 10 : void SceneContent2d::popLayout(SceneLayout2d *node) {
152 10 : auto it = std::find(_layouts.begin(), _layouts.end(), node);
153 10 : if (it == _layouts.end()) {
154 0 : return;
155 : }
156 :
157 10 : auto linkId = node->retain();
158 10 : _layouts.erase(it);
159 :
160 10 : node->onPopTransitionBegan(this, false);
161 10 : if (!_layouts.empty()) {
162 10 : _layouts.back()->onForegroundTransitionBegan(this, node);
163 : }
164 :
165 60 : auto fn = [this, node, linkId] {
166 10 : eraseLayout(node);
167 10 : node->onPop(this, false);
168 10 : if (!_layouts.empty()) {
169 10 : _layouts.back()->onForeground(this, node);
170 : }
171 10 : node->release(linkId);
172 20 : };
173 :
174 10 : if (auto exit = node->makeExitTransition(this)) {
175 0 : node->runAction(Rc<Sequence>::create(move(exit), fn));
176 : } else {
177 10 : fn();
178 10 : }
179 : }
180 :
181 136 : void SceneContent2d::pushNodeInternal(SceneLayout2d *node, Function<void()> &&cb) {
182 136 : if (!_layouts.empty()) {
183 126 : ZOrder zIndex = -ZOrder(_layouts.size()) - ZOrder(2);
184 916 : for (auto &n : _layouts) {
185 790 : n->setLocalZOrder(zIndex);
186 790 : zIndex ++;
187 : }
188 : }
189 :
190 136 : _layouts.push_back(node);
191 :
192 136 : updateLayoutNode(node);
193 136 : addChild(node, ZOrder(-1));
194 :
195 136 : if (_layouts.size() > 1) {
196 126 : _layouts.at(_layouts.size() - 2)->onBackground(this, node);
197 : }
198 136 : node->onPush(this, false);
199 :
200 932 : auto fn = [this, node, cb = move(cb)] {
201 136 : updateNodesVisibility();
202 136 : updateBackButtonStatus();
203 136 : if (_layouts.size() > 1) {
204 126 : _layouts.at(_layouts.size() - 2)->onBackgroundTransitionEnded(this, node);
205 : }
206 136 : node->onPushTransitionEnded(this, false);
207 136 : if (cb) {
208 0 : cb();
209 : }
210 136 : };
211 :
212 136 : if (auto enter = node->makeEnterTransition(this)) {
213 0 : node->runAction(Rc<Sequence>::create(move(enter), move(fn)));
214 : } else {
215 136 : fn();
216 136 : }
217 136 : }
218 :
219 10 : void SceneContent2d::eraseLayout(SceneLayout2d *node) {
220 10 : node->removeFromParent();
221 10 : if (!_layouts.empty()) {
222 10 : ZOrder zIndex = -ZOrder(_layouts.size()) - ZOrder(2);
223 66 : for (auto n : _layouts) {
224 56 : n->setLocalZOrder(zIndex);
225 56 : n->setVisible(false);
226 56 : zIndex ++;
227 56 : }
228 10 : updateNodesVisibility();
229 : }
230 10 : updateBackButtonStatus();
231 10 : }
232 :
233 0 : void SceneContent2d::eraseOverlay(SceneLayout2d *l) {
234 0 : l->removeFromParent();
235 0 : if (!_overlays.empty()) {
236 0 : ZOrder zIndex = ZOrder(1);
237 0 : for (auto n : _overlays) {
238 0 : n->setLocalZOrder(zIndex);
239 0 : zIndex ++;
240 0 : }
241 0 : updateNodesVisibility();
242 : }
243 0 : updateBackButtonStatus();
244 0 : }
245 :
246 0 : void SceneContent2d::replaceNodes() {
247 0 : if (!_layouts.empty()) {
248 0 : auto vec = _layouts;
249 0 : for (auto &node : vec) {
250 0 : if (node != _layouts.back()) {
251 0 : node->removeFromParent();
252 : }
253 : }
254 :
255 0 : _layouts.erase(_layouts.begin(), _layouts.begin() + _layouts.size() - 1);
256 0 : }
257 0 : }
258 :
259 146 : void SceneContent2d::updateNodesVisibility() {
260 1128 : for (size_t i = 0; i < _layouts.size(); i++) {
261 982 : if (i == _layouts.size() - 1) {
262 146 : _layouts.at(i)->setVisible(true);
263 : } else {
264 836 : _layouts.at(i)->setVisible(false);
265 : }
266 : }
267 :
268 146 : if (!_layouts.empty()) {
269 146 : auto status = _layouts.back()->getDecorationStatus();
270 146 : switch (status) {
271 94 : case DecorationStatus::DontCare:
272 94 : break;
273 52 : case DecorationStatus::Visible:
274 52 : showViewDecoration();
275 52 : break;
276 0 : case DecorationStatus::Hidden:
277 0 : hideViewDecoration();
278 0 : break;
279 : }
280 : }
281 146 : }
282 :
283 146 : void SceneContent2d::updateBackButtonStatus() {
284 146 : if (!_overlays.empty() || _layouts.size() > 1 || _layouts.back()->hasBackButtonAction()) {
285 136 : if (!_retainBackButton) {
286 10 : _retainBackButton = true;
287 10 : if (_director && !_backButtonRetained) {
288 10 : _director->getView()->retainBackButton();
289 10 : _backButtonRetained = true;
290 : }
291 : }
292 : } else {
293 10 : _retainBackButton = false;
294 10 : if (_director && _backButtonRetained) {
295 0 : _director->getView()->releaseBackButton();
296 0 : _backButtonRetained = false;
297 : }
298 : }
299 146 : }
300 :
301 10 : SceneLayout2d *SceneContent2d::getTopLayout() {
302 10 : if (!_layouts.empty()) {
303 10 : return _layouts.back();
304 : } else {
305 0 : return nullptr;
306 : }
307 : }
308 :
309 10 : SceneLayout2d *SceneContent2d::getPrevLayout() {
310 10 : if (_layouts.size() > 1) {
311 10 : return *(_layouts.end() - 2);
312 : }
313 0 : return nullptr;
314 : }
315 :
316 0 : bool SceneContent2d::popTopLayout() {
317 0 : if (!_overlays.empty()) {
318 0 : popOverlay(_overlays.back());
319 0 : return true;
320 : }
321 0 : if (_layouts.size() > 1) {
322 0 : popLayout(_layouts.back());
323 0 : return true;
324 : }
325 0 : return false;
326 : }
327 :
328 0 : bool SceneContent2d::isActive() const {
329 0 : return !_layouts.empty();
330 : }
331 :
332 10 : bool SceneContent2d::onBackButton() {
333 10 : if (_layouts.empty()) {
334 0 : return false;
335 : } else {
336 10 : if (!_overlays.empty()) {
337 0 : if (!_overlays.back()->onBackButton()) {
338 0 : if (!popTopLayout()) {
339 0 : return false;
340 : } else {
341 0 : return true;
342 : }
343 : } else {
344 0 : return true;
345 : }
346 : }
347 10 : if (!_layouts.back()->onBackButton()) {
348 0 : if (!popTopLayout()) {
349 0 : return false;
350 : }
351 : }
352 10 : return true;
353 : }
354 : }
355 :
356 10 : size_t SceneContent2d::getLayoutsCount() const {
357 10 : return _layouts.size();
358 : }
359 :
360 0 : const Vector<Rc<SceneLayout2d>> &SceneContent2d::getLayouts() const {
361 0 : return _layouts;
362 : }
363 :
364 0 : const Vector<Rc<SceneLayout2d>> &SceneContent2d::getOverlays() const {
365 0 : return _overlays;
366 : }
367 :
368 0 : bool SceneContent2d::pushOverlay(SceneLayout2d *l) {
369 0 : if (!l || l->isRunning()) {
370 0 : return false;
371 : }
372 :
373 0 : updateLayoutNode(l);
374 :
375 0 : ZOrder zIndex = ZOrder(_overlays.size() + 1);
376 :
377 0 : _overlays.push_back(l);
378 :
379 0 : addChild(l, zIndex);
380 :
381 0 : l->onPush(this, false);
382 :
383 0 : auto fn = [this, l] {
384 0 : l->onPushTransitionEnded(this, false);
385 0 : updateBackButtonStatus();
386 0 : };
387 :
388 0 : if (auto enter = l->makeEnterTransition(this)) {
389 0 : l->runAction(Rc<Sequence>::create(move(enter), move(fn)), "ContentLayer.Transition"_tag);
390 : } else {
391 0 : fn();
392 0 : }
393 :
394 0 : return true;
395 : }
396 :
397 0 : bool SceneContent2d::popOverlay(SceneLayout2d *l) {
398 0 : auto it = std::find(_overlays.begin(), _overlays.end(), l);
399 0 : if (it == _overlays.end()) {
400 0 : return false;
401 : }
402 :
403 0 : auto linkId = l->retain();
404 0 : _overlays.erase(it);
405 0 : l->onPopTransitionBegan(this, false);
406 :
407 0 : auto fn = [this, l, linkId] {
408 0 : eraseOverlay(l);
409 0 : l->onPop(this, false);
410 0 : l->release(linkId);
411 0 : updateBackButtonStatus();
412 0 : };
413 :
414 0 : if (auto exit = l->makeExitTransition(this)) {
415 0 : l->runAction(Rc<Sequence>::create(move(exit), move(fn)));
416 : } else {
417 0 : fn();
418 0 : }
419 :
420 0 : return true;
421 : }
422 :
423 348 : void SceneContent2d::updateLayoutNode(SceneLayout2d *node) {
424 348 : auto mask = node->getDecodationMask();
425 :
426 348 : Vec2 pos = Vec2::ZERO;
427 348 : Size2 size = _contentSize;
428 348 : Padding effectiveDecorations;
429 :
430 348 : if (node->getTargetContentSize() != Size2::ZERO) {
431 0 : size.width = std::min(size.width, node->getTargetContentSize().width);
432 0 : size.height = std::min(size.height, node->getTargetContentSize().height);
433 : }
434 :
435 348 : if ((mask & DecorationMask::Top) != DecorationMask::None) {
436 116 : size.height += _decorationPadding.top;
437 116 : effectiveDecorations.top = _decorationPadding.top;
438 : }
439 348 : if ((mask & DecorationMask::Right) != DecorationMask::None) {
440 116 : size.width += _decorationPadding.right;
441 116 : effectiveDecorations.right = _decorationPadding.right;
442 : }
443 348 : if ((mask & DecorationMask::Left) != DecorationMask::None) {
444 116 : size.width += _decorationPadding.left;
445 116 : pos.x -= _decorationPadding.left;
446 116 : effectiveDecorations.left = _decorationPadding.left;
447 : }
448 348 : if ((mask & DecorationMask::Bottom) != DecorationMask::None) {
449 116 : size.height += _decorationPadding.bottom;
450 116 : pos.y -= _decorationPadding.bottom;
451 116 : effectiveDecorations.bottom = _decorationPadding.bottom;
452 : }
453 :
454 348 : node->setAnchorPoint(Anchor::BottomLeft);
455 348 : node->setDecorationPadding(effectiveDecorations);
456 348 : node->setPosition(pos);
457 348 : node->setContentSize(size);
458 348 : }
459 :
460 20 : bool SceneContent2d::addLight(SceneLight *light, uint64_t tag, StringView name) {
461 20 : if (tag != InvalidTag) {
462 0 : if (_lightsByTag.find(tag) != _lightsByTag.end()) {
463 0 : log::warn("Scene", "Light with tag ", tag, " is already defined");
464 0 : return false;
465 : }
466 : }
467 20 : if (!name.empty()) {
468 0 : if (_lightsByName.find(name) != _lightsByName.end()) {
469 0 : log::warn("Scene", "Light with name ", name, " is already defined");
470 0 : return false;
471 : }
472 : }
473 :
474 20 : if (light->getScene()) {
475 0 : log::warn("Scene", "Light is already on scene");
476 0 : return false;
477 : }
478 :
479 20 : switch (light->getType()) {
480 20 : case SceneLightType::Ambient:
481 20 : if (_lightsAmbientCount >= config::MaxAmbientLights) {
482 0 : log::warn("Scene", "Too many ambient lights");
483 0 : return false;
484 : }
485 20 : break;
486 0 : case SceneLightType::Direct:
487 0 : if (_lightsDirectCount >= config::MaxDirectLights) {
488 0 : log::warn("Scene", "Too many direct lights");
489 0 : return false;
490 : }
491 0 : break;
492 : }
493 :
494 20 : _lights.emplace_back(light);
495 :
496 20 : switch (light->getType()) {
497 20 : case SceneLightType::Ambient: ++ _lightsAmbientCount; break;
498 0 : case SceneLightType::Direct: ++ _lightsDirectCount; break;
499 : }
500 :
501 20 : if (tag != InvalidTag) {
502 0 : light->setTag(tag);
503 0 : _lightsByTag.emplace(tag, light);
504 : }
505 :
506 20 : if (!name.empty()) {
507 0 : light->setName(name);
508 0 : _lightsByName.emplace(light->getName(), light);
509 : }
510 :
511 20 : if (_running) {
512 0 : light->onEnter(_scene);
513 : } else {
514 20 : light->_scene = _scene;
515 : }
516 :
517 20 : return true;
518 : }
519 :
520 0 : SceneLight *SceneContent2d::getLightByTag(uint64_t tag) const {
521 0 : auto it = _lightsByTag.find(tag);
522 0 : if (it != _lightsByTag.end()) {
523 0 : return it->second;
524 : }
525 0 : return nullptr;
526 : }
527 :
528 0 : SceneLight *SceneContent2d::getLightByName(StringView name) const {
529 0 : auto it = _lightsByName.find(name);
530 0 : if (it != _lightsByName.end()) {
531 0 : return it->second;
532 : }
533 0 : return nullptr;
534 : }
535 :
536 0 : void SceneContent2d::removeLight(SceneLight *light) {
537 0 : if (light->getScene() != _scene) {
538 0 : return;
539 : }
540 :
541 0 : auto it = _lights.begin();
542 0 : while (it != _lights.end()) {
543 0 : if (*it == light) {
544 0 : removeLight(it);
545 0 : return;
546 : }
547 0 : ++ it;
548 : }
549 : }
550 :
551 0 : void SceneContent2d::removeLightByTag(uint64_t tag) {
552 0 : if (auto l = getLightByTag(tag)) {
553 0 : removeLight(l);
554 : }
555 0 : }
556 :
557 0 : void SceneContent2d::removeLightByName(StringView name) {
558 0 : if (auto l = getLightByName(name)) {
559 0 : removeLight(l);
560 : }
561 0 : }
562 :
563 10 : void SceneContent2d::removeAllLights() {
564 10 : auto it = _lights.begin();
565 10 : while (it != _lights.end()) {
566 0 : it = removeLight(it);
567 : }
568 10 : }
569 :
570 0 : void SceneContent2d::removeAllLightsByType(SceneLightType type) {
571 0 : auto it = _lights.begin();
572 0 : while (it != _lights.end()) {
573 0 : if ((*it)->getType() == type) {
574 0 : it = removeLight(it);
575 : } else {
576 0 : ++ it;
577 : }
578 : }
579 0 : }
580 :
581 10 : void SceneContent2d::setGlobalLight(const Color4F &color) {
582 10 : _globalLight = color;
583 10 : }
584 :
585 0 : const Color4F & SceneContent2d::getGlobalLight() const {
586 0 : return _globalLight;
587 : }
588 :
589 4650 : void SceneContent2d::draw(FrameInfo &info, NodeFlags flags) {
590 4650 : SceneContent::draw(info, flags);
591 :
592 4650 : FrameContextHandle2d *ctx = reinterpret_cast<FrameContextHandle2d *>(info.currentContext);
593 :
594 4650 : auto &constraints = _scene->getFrameConstraints();
595 :
596 4650 : Size2 scaledExtent(constraints.extent.width / constraints.density, constraints.extent.height / constraints.density);
597 :
598 4650 : ctx->lights.sceneDensity = constraints.density;
599 4650 : ctx->lights.shadowDensity = _shadowDensity;
600 4650 : ctx->lights.globalColor = _globalLight;
601 :
602 13950 : for (auto &it : _lights) {
603 9300 : switch (it->getType()) {
604 9300 : case SceneLightType::Ambient:
605 18600 : ctx->lights.addAmbientLight(Vec4(it->getNormal().x / scaledExtent.width, -it->getNormal().y / scaledExtent.height,
606 18600 : it->getNormal().z, it->getNormal().w), it->getColor(), it->isSoftShadow());
607 9300 : break;
608 0 : case SceneLightType::Direct:
609 0 : ctx->lights.addDirectLight(Vec4(it->getNormal().x / scaledExtent.width, -it->getNormal().y / scaledExtent.height,
610 0 : it->getNormal().z, it->getNormal().w), it->getColor(), it->getData());
611 0 : break;
612 : }
613 : }
614 4650 : }
615 :
616 0 : Vector<Rc<SceneLight>>::iterator SceneContent2d::removeLight(Vector<Rc<SceneLight>>::iterator itVec) {
617 0 : if ((*itVec)->isRunning()) {
618 0 : (*itVec)->onExit();
619 : }
620 :
621 0 : if (!(*itVec)->getName().empty()) {
622 0 : auto it = _lightsByName.find((*itVec)->getName());
623 0 : if (it != _lightsByName.end()) {
624 0 : _lightsByName.erase(it);
625 : }
626 : }
627 :
628 0 : if ((*itVec)->getTag() != InvalidTag) {
629 0 : auto it = _lightsByTag.find((*itVec)->getTag());
630 0 : if (it != _lightsByTag.end()) {
631 0 : _lightsByTag.erase(it);
632 : }
633 : }
634 :
635 0 : switch ((*itVec)->getType()) {
636 0 : case SceneLightType::Ambient: -- _lightsAmbientCount; break;
637 0 : case SceneLightType::Direct: -- _lightsDirectCount; break;
638 : }
639 :
640 0 : return _lights.erase(itVec);
641 : }
642 :
643 : }
|