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 "MaterialSidebar.h"
24 : #include "MaterialEasing.h"
25 : #include "MaterialStyleMonitor.h"
26 : #include "XLInputListener.h"
27 : #include "XLAction.h"
28 :
29 : namespace STAPPLER_VERSIONIZED stappler::xenolith::material2d {
30 :
31 : static constexpr uint32_t SHOW_ACTION_TAG = 154;
32 : static constexpr uint32_t HIDE_ACTION_TAG = 154;
33 :
34 10 : Sidebar::~Sidebar() { }
35 :
36 10 : bool Sidebar::init(Position pos) {
37 10 : if (!Node::init()) {
38 0 : return false;
39 : }
40 :
41 10 : _position = pos;
42 :
43 10 : _listener = addInputListener(Rc<InputListener>::create());
44 10 : _listener->setTouchFilter([this] (const InputEvent &ev, const InputListener::DefaultEventFilter &) -> bool {
45 0 : if (!_node) {
46 0 : return false;
47 : }
48 0 : if (isNodeEnabled() || (isNodeVisible() && _swallowTouches)) {
49 0 : return true;
50 : } else {
51 0 : auto pos = convertToNodeSpace(ev.currentLocation);
52 0 : if (_node->isTouched(ev.currentLocation)) {
53 0 : return true;
54 : }
55 0 : if (_edgeSwipeEnabled) {
56 0 : if ((_position == Left && pos.x < 16.0f) || (_position == Right && pos.x > _contentSize.width - 16.0f)) {
57 0 : return true;
58 : }
59 : }
60 0 : return false;
61 : }
62 : });
63 10 : _listener->addPressRecognizer([this] (const GesturePress &p) -> bool {
64 0 : if (isNodeEnabled() && !_node->isTouched(p.location())) {
65 0 : if (p.event == GestureEvent::Ended) {
66 0 : hide();
67 : }
68 0 : return true;
69 : }
70 0 : return false;
71 : });
72 10 : _listener->addSwipeRecognizer([this] (const GestureSwipe &s) -> bool {
73 0 : if (!isNodeVisible() && !_edgeSwipeEnabled) {
74 0 : return false;
75 : }
76 :
77 0 : if (s.event == GestureEvent::Began) {
78 0 : if (std::abs(s.delta.y) < std::abs(s.delta.x) && !_node->isTouched(s.location())) {
79 0 : stopNodeActions();
80 0 : onSwipeDelta(s.delta.x / s.density);
81 0 : return true;
82 : }
83 0 : return false;
84 0 : } else if (s.event == GestureEvent::Activated) {
85 0 : onSwipeDelta(s.delta.x / s.density);
86 0 : return true;
87 : } else {
88 0 : onSwipeFinished(s.velocity.x / s.density);
89 0 : return true;
90 : }
91 :
92 : return true;
93 : });
94 10 : _listener->setSwallowEvents(InputListener::EventMaskTouch);
95 :
96 10 : addComponent(Rc<StyleMonitor>::create([this] (const ColorScheme *scheme, const SurfaceStyleData &) {
97 10 : _background->setColor(scheme->get(ColorRole::Scrim));
98 10 : }));
99 :
100 10 : _background = addChild(Rc<Layer>::create(Color::Grey_500), ZOrder(-1));
101 10 : _background->setAnchorPoint(Vec2(0.0f, 0.0f));
102 10 : _background->setVisible(false);
103 10 : _background->setOpacity(_backgroundPassiveOpacity);
104 :
105 10 : return true;
106 : }
107 :
108 10 : void Sidebar::onContentSizeDirty() {
109 10 : Node::onContentSizeDirty();
110 :
111 10 : _background->setContentSize(_contentSize);
112 :
113 10 : stopNodeActions();
114 :
115 10 : if (_widthCallback) {
116 10 : _nodeWidth = _widthCallback(_contentSize);
117 : }
118 :
119 10 : if (_node) {
120 10 : _node->setContentSize(Size2(_nodeWidth, _contentSize.height));
121 10 : if (_position == Left) {
122 10 : _node->setPosition(Vec2(0.0f, 0.0f));
123 : } else {
124 0 : _node->setPosition(Vec2(_contentSize.width, 0.0f));
125 : }
126 : }
127 :
128 10 : setProgress(0.0f);
129 10 : }
130 :
131 10 : void Sidebar::setBaseNode(Node *n, ZOrder zOrder) {
132 10 : if (_node) {
133 0 : _node->removeFromParent();
134 0 : _node = nullptr;
135 : }
136 10 : _node = n;
137 10 : if (getProgress() == 0) {
138 0 : _node->setVisible(false);
139 : }
140 10 : addChildNode(_node, zOrder);
141 10 : _contentSizeDirty = true;
142 10 : }
143 :
144 0 : Node *Sidebar::getNode() const {
145 0 : return _node;
146 : }
147 :
148 0 : void Sidebar::setNodeWidth(float value) {
149 0 : _nodeWidth = value;
150 0 : }
151 0 : float Sidebar::getNodeWidth() const {
152 0 : return _nodeWidth;
153 : }
154 :
155 10 : void Sidebar::setNodeWidthCallback(const WidthCallback &cb) {
156 10 : _widthCallback = cb;
157 10 : }
158 0 : const Sidebar::WidthCallback &Sidebar::getNodeWidthCallback() const {
159 0 : return _widthCallback;
160 : }
161 :
162 0 : void Sidebar::setSwallowTouches(bool value) {
163 0 : _swallowTouches = value;
164 0 : }
165 0 : bool Sidebar::isSwallowTouches() const {
166 0 : return _swallowTouches;
167 : }
168 :
169 0 : void Sidebar::setEdgeSwipeEnabled(bool value) {
170 0 : _edgeSwipeEnabled = value;
171 0 : if (isNodeVisible()) {
172 0 : if (value) {
173 0 : _listener->setSwallowEvents(InputListener::EventMaskTouch);
174 : } else {
175 0 : _listener->clearSwallowEvents(InputListener::EventMaskTouch);
176 : }
177 : }
178 0 : }
179 :
180 0 : bool Sidebar::isEdgeSwipeEnabled() const {
181 0 : return _edgeSwipeEnabled;
182 : }
183 :
184 10 : void Sidebar::setBackgroundActiveOpacity(float value) {
185 10 : _backgroundActiveOpacity = value;
186 10 : _background->setOpacity(progress(_backgroundPassiveOpacity, _backgroundActiveOpacity, getProgress()));
187 10 : }
188 10 : void Sidebar::setBackgroundPassiveOpacity(float value) {
189 10 : _backgroundPassiveOpacity = value;
190 10 : _background->setOpacity(progress(_backgroundPassiveOpacity, _backgroundActiveOpacity, getProgress()));
191 10 : }
192 :
193 0 : void Sidebar::show() {
194 0 : stopActionByTag(HIDE_ACTION_TAG);
195 0 : if (getActionByTag(SHOW_ACTION_TAG) == nullptr) {
196 0 : auto a = makeEasing(Rc<ActionProgress>::create(
197 0 : progress(0.35f, 0.0f, getProgress()), getProgress(), 1.0f,
198 0 : [this] (float progress) {
199 0 : setProgress(progress);
200 0 : }), EasingType::Standard);
201 0 : a->setTag(SHOW_ACTION_TAG);
202 0 : runAction(a);
203 0 : }
204 0 : }
205 :
206 0 : void Sidebar::hide(float factor) {
207 0 : stopActionByTag(SHOW_ACTION_TAG);
208 0 : if (getActionByTag(HIDE_ACTION_TAG) == nullptr) {
209 0 : if (factor <= 1.0f) {
210 0 : auto a = makeEasing(Rc<ActionProgress>::create(
211 0 : progress(0.0f, 0.35f / factor, getProgress()), getProgress(), 0.0f,
212 0 : [this] (float progress) {
213 0 : setProgress(progress);
214 0 : }), EasingType::Standard);
215 0 : a->setTag(HIDE_ACTION_TAG);
216 0 : runAction(a);
217 0 : } else {
218 0 : auto a = makeEasing(Rc<ActionProgress>::create(
219 0 : progress(0.0f, 0.35f / factor, getProgress()), getProgress(), 0.0f,
220 0 : [this] (float progress) {
221 0 : setProgress(progress);
222 0 : }), EasingType::Standard);
223 0 : a->setTag(HIDE_ACTION_TAG);
224 0 : runAction(a);
225 0 : }
226 : }
227 0 : }
228 :
229 60 : float Sidebar::getProgress() const {
230 60 : if (_node) {
231 60 : return (_position == Left)?(1.0f - _node->getAnchorPoint().x):(_node->getAnchorPoint().x);
232 : }
233 0 : return 0.0f;
234 : }
235 :
236 10 : void Sidebar::setProgress(float value) {
237 10 : auto prev = getProgress();
238 10 : if (_node && value != getProgress()) {
239 10 : _node->setAnchorPoint(Vec2((_position == Left)?(1.0f - value):(value), 0.0f));
240 10 : if (value == 0.0f) {
241 10 : if (_node->isVisible()) {
242 10 : _node->setVisible(false);
243 10 : onNodeVisible(false);
244 : }
245 : } else {
246 0 : if (!_node->isVisible()) {
247 0 : _node->setVisible(true);
248 0 : onNodeVisible(true);
249 : }
250 :
251 0 : if (value == 1.0f && prev != 1.0f) {
252 0 : onNodeEnabled(true);
253 0 : } else if (value != 1.0f && prev == 1.0f) {
254 0 : onNodeEnabled(false);
255 : }
256 : }
257 :
258 10 : _background->setOpacity(progress(_backgroundPassiveOpacity, _backgroundActiveOpacity, value));
259 10 : if (!_background->isVisible() && _background->getOpacity() > 0) {
260 0 : _background->setVisible(true);
261 : }
262 : }
263 10 : }
264 :
265 0 : void Sidebar::onSwipeDelta(float value) {
266 0 : float d = value / _nodeWidth;
267 :
268 0 : float progress = getProgress() - d * (_position==Left ? -1 : 1);
269 0 : if (progress >= 1.0f) {
270 0 : progress = 1.0f;
271 0 : } else if (progress <= 0.0f) {
272 0 : progress = 0.0f;
273 : }
274 :
275 0 : setProgress(progress);
276 0 : }
277 :
278 0 : void Sidebar::onSwipeFinished(float value) {
279 0 : float v = value / _nodeWidth;
280 0 : auto t = fabsf(v) / (5000 / _nodeWidth);
281 0 : auto d = v * t - (5000.0f * t * t / _nodeWidth) / 2.0f;
282 :
283 0 : float progress = getProgress() - d * (_position==Left ? -1 : 1);
284 :
285 0 : if (progress > 0.5) {
286 0 : show();
287 : } else {
288 0 : hide(1.0f + fabsf(d) * 2.0f);
289 : }
290 0 : }
291 :
292 10 : bool Sidebar::isNodeVisible() const {
293 10 : return getProgress() > 0.0f;
294 : }
295 0 : bool Sidebar::isNodeEnabled() const {
296 0 : return getProgress() == 1.0f;
297 : }
298 :
299 0 : void Sidebar::setEnabled(bool value) {
300 0 : _listener->setEnabled(value);
301 0 : }
302 0 : bool Sidebar::isEnabled() const {
303 0 : return _listener->isEnabled();
304 : }
305 :
306 0 : void Sidebar::setNodeVisibleCallback(BoolCallback &&cb) {
307 0 : _visibleCallback = move(cb);
308 0 : }
309 0 : void Sidebar::setNodeEnabledCallback(BoolCallback &&cb) {
310 0 : _enabledCallback = move(cb);
311 0 : }
312 :
313 0 : void Sidebar::onNodeEnabled(bool value) {
314 0 : if (_enabledCallback) {
315 0 : _enabledCallback(value);
316 : }
317 0 : }
318 10 : void Sidebar::onNodeVisible(bool value) {
319 10 : if (value) {
320 0 : if (_swallowTouches) {
321 0 : _listener->setSwallowEvents(InputListener::EventMaskTouch);
322 : }
323 0 : if (_backgroundPassiveOpacity == 0) {
324 0 : _background->setVisible(false);
325 : }
326 : } else {
327 10 : _background->setVisible(true);
328 10 : _listener->clearSwallowEvents(InputListener::EventMaskTouch);
329 : }
330 :
331 10 : if (_visibleCallback) {
332 0 : _visibleCallback(value);
333 : }
334 10 : }
335 :
336 10 : void Sidebar::stopNodeActions() {
337 10 : stopActionByTag(SHOW_ACTION_TAG);
338 10 : stopActionByTag(HIDE_ACTION_TAG);
339 10 : }
340 :
341 : }
|