Line data Source code
1 : /**
2 : Copyright (c) 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 "MaterialSceneContent.h"
24 : #include "MaterialLabel.h"
25 : #include "MaterialButton.h"
26 : #include "MaterialEasing.h"
27 : #include "MaterialNavigationDrawer.h"
28 : #include "XLInputListener.h"
29 : #include "XLAction.h"
30 :
31 : namespace STAPPLER_VERSIONIZED stappler::xenolith::material2d {
32 :
33 : class Snackbar : public Node {
34 : public:
35 20 : virtual ~Snackbar() { }
36 :
37 : virtual bool init() override;
38 : virtual void onContentSizeDirty() override;
39 :
40 : virtual void setSnackbarData(SnackbarData &&);
41 :
42 : const SnackbarData &getData() const;
43 :
44 : void clear();
45 :
46 : void hide(Function<void()> &&cb);
47 : void show(SnackbarData &&);
48 :
49 : void onHidden();
50 : void onButton();
51 :
52 : protected:
53 : SnackbarData _data;
54 : Surface *_surface = nullptr;
55 : TypescaleLabel *_label = nullptr;
56 : Button *_button = nullptr;
57 : InputListener *_listener = nullptr;
58 : bool _scheduledUpdate = false;
59 : };
60 :
61 10 : bool Snackbar::init() {
62 10 : if (!Node::init()) {
63 0 : return false;
64 : }
65 :
66 10 : setAnchorPoint(Anchor::MiddleBottom);
67 :
68 10 : _listener = addInputListener(Rc<InputListener>::create());
69 10 : _listener->addTouchRecognizer([this] (const GestureData &data) -> bool {
70 0 : if (data.event == GestureEvent::Began) {
71 0 : stopAllActions();
72 0 : runAction(Rc<Sequence>::create(_data.delayTime, std::bind(&Snackbar::hide, this, nullptr)));
73 : }
74 0 : return true;
75 : });
76 10 : _listener->setSwallowEvents(InputListener::EventMaskTouch);
77 :
78 10 : _surface = addChild(Rc<Surface>::create(SurfaceStyle(NodeStyle::Filled, Elevation::Level5, ColorRole::OnSurfaceVariant)));
79 :
80 10 : _label = _surface->addChild(Rc<TypescaleLabel>::create(TypescaleRole::BodyLarge), ZOrder(1));
81 10 : _label->setLocaleEnabled(true);
82 10 : _label->setAnchorPoint(Anchor::MiddleLeft);
83 :
84 10 : _button = _surface->addChild(Rc<Button>::create(NodeStyle::Text), ZOrder(1));
85 10 : _button->setTapCallback([this] {
86 0 : onButton();
87 0 : });
88 10 : _button->setAnchorPoint(Anchor::MiddleRight);
89 10 : _button->setVisible(false);
90 10 : _button->setSwallowEvents(true);
91 :
92 10 : return true;
93 : }
94 :
95 0 : void Snackbar::onContentSizeDirty() {
96 0 : Node::onContentSizeDirty();
97 :
98 0 : _surface->setContentSize(_contentSize);
99 :
100 0 : _button->setPosition(Vec2(_contentSize.width - 8.0f, _contentSize.height / 2.0f));
101 0 : _button->setContentSize(Size2(_button->getContentSize().width, _contentSize.height));
102 :
103 0 : _label->setPosition(Vec2(24.0f, _contentSize.height / 2.0f));
104 0 : }
105 :
106 10 : void Snackbar::setSnackbarData(SnackbarData &&data) {
107 10 : _data = move(data);
108 :
109 10 : if (!_data.buttonText.empty() && _data.buttonCallback) {
110 10 : _button->setVisible(true);
111 10 : _button->setLeadingIconName(_data.buttonIcon);
112 10 : _button->setText(string::toupper<Interface>(_data.buttonText));
113 10 : _button->setBlendColor(_data.buttonColor, _data.buttonBlendValue);
114 10 : _label->setWidth(_contentSize.width - 48.0f - _button->getContentSize().width);
115 : } else {
116 0 : _button->setVisible(false);
117 0 : _label->setWidth(_contentSize.width - 48.0f);
118 : }
119 :
120 10 : _label->setString(_data.text);
121 10 : _label->setBlendColor(_data.textColor, _data.textBlendValue);
122 10 : _label->tryUpdateLabel();
123 :
124 10 : setContentSize(Size2(_contentSize.width, _label->getContentSize().height + 32.0f));
125 10 : setPosition(Vec2(_position.x, -_contentSize.height));
126 10 : if (!_data.text.empty() || !_data.buttonText.empty()) {
127 10 : setVisible(true);
128 10 : setOpacity(1.0f);
129 10 : runAction(Rc<Sequence>::create(makeEasing(Rc<MoveTo>::create(0.25f, Vec2(_position.x, 0)), EasingType::Standard), _data.delayTime,
130 20 : std::bind(&Snackbar::hide, this, nullptr)));
131 : }
132 10 : }
133 :
134 0 : const SnackbarData &Snackbar::getData() const {
135 0 : return _data;
136 : }
137 :
138 0 : void Snackbar::clear() {
139 0 : setSnackbarData(SnackbarData(""));
140 0 : }
141 :
142 0 : void Snackbar::hide(Function<void()> &&cb) {
143 0 : if (!cb) {
144 0 : runAction(Rc<Sequence>::create(
145 0 : makeEasing(Rc<MoveTo>::create(0.25f, Vec2(_position.x, -_contentSize.height)), EasingType::Standard),
146 0 : std::bind(&Snackbar::onHidden, this)));
147 : } else {
148 0 : runAction(Rc<Sequence>::create(
149 0 : makeEasing(Rc<MoveTo>::create(0.25f, Vec2(_position.x, -_contentSize.height)), EasingType::Standard),
150 0 : move(cb)));
151 : }
152 0 : }
153 :
154 10 : void Snackbar::show(SnackbarData &&data) {
155 10 : stopAllActions();
156 10 : if (!isVisible()) {
157 10 : setSnackbarData(move(data));
158 : } else {
159 0 : _scheduledUpdate = true;
160 0 : hide([this, data = move(data)] {
161 0 : _scheduledUpdate = false;
162 0 : setSnackbarData(move(const_cast<SnackbarData &>(data)));
163 0 : });
164 : }
165 10 : }
166 :
167 20 : void Snackbar::onHidden() {
168 20 : stopAllActions();
169 20 : setVisible(false);
170 20 : setPosition(Vec2(_position.x, -_contentSize.height));
171 20 : _button->setVisible(false);
172 20 : _label->setString("");
173 20 : }
174 :
175 0 : void Snackbar::onButton() {
176 0 : if (_data.buttonCallback) {
177 0 : _data.buttonCallback();
178 0 : _data.buttonCallback = nullptr;
179 : }
180 0 : if (!_scheduledUpdate) {
181 0 : stopAllActions();
182 0 : runAction(Rc<Sequence>::create(0.35f, std::bind(&Snackbar::hide, this, nullptr)));
183 : }
184 0 : }
185 :
186 :
187 20 : SceneContent::~SceneContent() { }
188 :
189 10 : bool SceneContent::init() {
190 10 : if (!SceneContent2d::init()) {
191 0 : return false;
192 : }
193 :
194 10 : _snackbarRoot = addChild(Rc<Node>::create());
195 :
196 10 : _snackbar = _snackbarRoot->addChild(Rc<Snackbar>::create(), ZOrderMax - ZOrder(2));
197 10 : _snackbar->setVisible(false);
198 :
199 10 : _navigation = addChild(Rc<NavigationDrawer>::create(), ZOrderMax - ZOrder(3));
200 :
201 10 : return true;
202 : }
203 :
204 20 : void SceneContent::onContentSizeDirty() {
205 20 : SceneContent2d::onContentSizeDirty();
206 :
207 20 : _snackbarRoot->setPosition(Vec2::ZERO);
208 20 : _snackbarRoot->setContentSize(_contentSize);
209 :
210 20 : _snackbar->onHidden();
211 20 : _snackbar->setContentSize(Size2(std::min(_contentSize.width, 536.0f), 48.0f));
212 20 : _snackbar->setPosition(Vec2(_contentSize.width / 2, -48.0f));
213 :
214 20 : _navigation->setPosition(Vec2::ZERO);
215 20 : _navigation->setContentSize(_contentSize);
216 20 : }
217 :
218 4650 : bool SceneContent::visitDraw(FrameInfo &frame, NodeFlags parentFlags) {
219 4650 : if (!_visible) {
220 0 : return false;
221 : }
222 :
223 4650 : auto maxDepth = getMaxDepthIndex();
224 :
225 4650 : _snackbar->setDepthIndex(maxDepth);
226 4650 : _navigation->setDepthIndex(maxDepth);
227 :
228 4650 : return SceneContent2d::visitDraw(frame, parentFlags);
229 : }
230 :
231 10 : void SceneContent::showSnackbar(SnackbarData &&data) {
232 10 : _snackbar->show(move(data));
233 10 : }
234 0 : const String &SceneContent::getSnackbarString() const {
235 0 : return _snackbar->getData().text;
236 : }
237 0 : void SceneContent::clearSnackbar() {
238 0 : _snackbar->clear();
239 0 : }
240 :
241 0 : bool SceneContent::isNavigationAvailable() const {
242 0 : return _navigation->isEnabled();
243 : }
244 :
245 0 : void SceneContent::setNavigationEnabled(bool value) {
246 0 : _navigation->setEnabled(value);
247 0 : }
248 :
249 0 : void SceneContent::setNavigationMenuSource(MenuSource *source) {
250 0 : _navigation->setMenuSource(source);
251 0 : }
252 :
253 0 : void SceneContent::setNavigationStyle(const SurfaceStyle &style) {
254 0 : _navigation->setStyle(style);
255 0 : }
256 :
257 0 : void SceneContent::openNavigation() {
258 0 : _navigation->show();
259 0 : }
260 :
261 0 : void SceneContent::closeNavigation() {
262 0 : _navigation->hide();
263 0 : }
264 :
265 4650 : float SceneContent::getMaxDepthIndex() const {
266 4650 : float maxIndex = _depthIndex;
267 65523 : for (auto &it : _children) {
268 60873 : if (it != _snackbar && it != _snackbar && it->isVisible()) {
269 18600 : maxIndex = std::max(it->getMaxDepthIndex(), maxIndex);
270 : }
271 : }
272 4650 : return maxIndex;
273 : }
274 :
275 10 : bool SceneContent::onBackButton() {
276 10 : if (_navigation->isNodeVisible()) {
277 0 : _navigation->hide();
278 0 : return true;
279 : }
280 10 : return SceneContent2d::onBackButton();
281 : }
282 :
283 146 : void SceneContent::updateNodesVisibility() {
284 146 : SceneContent2d::updateNodesVisibility();
285 :
286 146 : if (!_layouts.empty()) {
287 146 : _snackbarRoot->setLocalZOrder(_layouts.back()->getLocalZOrder());
288 : }
289 146 : }
290 :
291 20 : void SceneContent::handleBackgroundTransition(bool value) {
292 20 : SceneContent2d::handleBackgroundTransition(value);
293 :
294 20 : _contentSizeDirty = true;
295 20 : }
296 :
297 : }
|