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 "MaterialInputField.h"
24 : #include "MaterialInputTextContainer.h"
25 : #include "MaterialEasing.h"
26 : #include "XLDirector.h"
27 : #include "XL2dLayer.h"
28 : #include "XLInputListener.h"
29 :
30 : namespace STAPPLER_VERSIONIZED stappler::xenolith::material2d {
31 :
32 20 : InputField::~InputField() { }
33 :
34 10 : bool InputField::init(InputFieldStyle fieldStyle) {
35 10 : SurfaceStyle style;
36 10 : switch (fieldStyle) {
37 10 : case InputFieldStyle::Filled:
38 10 : style.nodeStyle = NodeStyle::Filled;
39 10 : style.colorRole = ColorRole::SurfaceVariant;
40 10 : break;
41 0 : case InputFieldStyle::Outlined:
42 0 : style.nodeStyle = NodeStyle::Outlined;
43 0 : style.shapeStyle = ShapeStyle::ExtraSmall;
44 0 : break;
45 : }
46 :
47 20 : return init(fieldStyle, style);
48 : }
49 :
50 10 : bool InputField::init(InputFieldStyle fieldStyle, const SurfaceStyle &surfaceStyle) {
51 10 : if (!Surface::init(surfaceStyle)) {
52 0 : return false;
53 : }
54 :
55 10 : _container = addChild(Rc<InputTextContainer>::create(), ZOrder(1));
56 10 : _container->setAnchorPoint(Anchor::BottomLeft);
57 :
58 10 : _labelText = addChild(Rc<TypescaleLabel>::create(TypescaleRole::BodyLarge), ZOrder(1));
59 10 : _labelText->setAnchorPoint(Anchor::MiddleLeft);
60 :
61 10 : _supportingText = addChild(Rc<TypescaleLabel>::create(TypescaleRole::BodySmall), ZOrder(1));
62 10 : _supportingText->setAnchorPoint(Anchor::TopLeft);
63 :
64 10 : _leadingIcon = addChild(Rc<IconSprite>::create(IconName::None), ZOrder(1));
65 10 : _leadingIcon->setAnchorPoint(Anchor::MiddleLeft);
66 10 : _leadingIcon->setContentSize(Size2(24.0f, 24.0f));
67 :
68 10 : _trailingIcon = addChild(Rc<IconSprite>::create(IconName::None), ZOrder(1));
69 10 : _trailingIcon->setAnchorPoint(Anchor::MiddleRight);
70 10 : _trailingIcon->setContentSize(Size2(24.0f, 24.0f));
71 :
72 10 : _indicator = addChild(Rc<Surface>::create(SurfaceStyle(ColorRole::OnSurfaceVariant, NodeStyle::Filled)), ZOrder(1));
73 10 : _indicator->setAnchorPoint(Anchor::BottomLeft);
74 :
75 10 : _inputListener = addInputListener(Rc<InputListener>::create());
76 :
77 10 : _inputListener->setTouchFilter([this] (const InputEvent &event, const InputListener::DefaultEventFilter &cb) {
78 51 : if (cb(event)) {
79 51 : return true;
80 : }
81 :
82 0 : if (_container->getTouchedCursor(event.currentLocation)) {
83 0 : return true;
84 : }
85 :
86 0 : return false;
87 : });
88 :
89 10 : _inputListener->addMouseOverRecognizer([this] (const GestureData &data) {
90 0 : _mouseOver = (data.event == GestureEvent::Began);
91 0 : updateActivityState();
92 0 : return true;
93 : });
94 10 : _inputListener->addTapRecognizer([this] (const GestureTap &tap) {
95 10 : return handleTap(tap.input->currentLocation);
96 10 : }, InputListener::makeButtonMask({InputMouseButton::Touch}), 1);
97 :
98 10 : _inputListener->addPressRecognizer([this] (const GesturePress &press) {
99 20 : switch (press.event) {
100 10 : case GestureEvent::Began:
101 10 : return handlePressBegin(press.location());
102 : break;
103 0 : case GestureEvent::Activated:
104 0 : return handleLongPress(press.location(), press.tickCount);
105 : break;
106 10 : case GestureEvent::Ended:
107 10 : return handlePressEnd(press.location());
108 : break;
109 0 : case GestureEvent::Cancelled:
110 0 : return handlePressCancel(press.location());
111 : break;
112 : }
113 0 : return false;
114 10 : }, TimeInterval::milliseconds(425), true);
115 :
116 10 : _inputListener->addSwipeRecognizer([this] (const GestureSwipe &swipe) {
117 0 : switch (swipe.event) {
118 0 : case GestureEvent::Began:
119 0 : if (handleSwipeBegin(swipe.input->originalLocation, swipe.delta / swipe.density)) {
120 0 : return handleSwipe(swipe.input->originalLocation, swipe.delta / swipe.density, swipe.velocity / swipe.density);
121 : }
122 0 : return false;
123 : break;
124 0 : case GestureEvent::Activated:
125 0 : return handleSwipe(swipe.location(), swipe.delta / swipe.density, swipe.velocity / swipe.density);
126 : break;
127 0 : case GestureEvent::Ended:
128 : case GestureEvent::Cancelled:
129 0 : return handleSwipeEnd(swipe.velocity / swipe.density);
130 : break;
131 : }
132 0 : return false;
133 : });
134 :
135 10 : _focusInputListener = addInputListener(Rc<InputListener>::create());
136 10 : _focusInputListener->setPriority(1);
137 10 : _focusInputListener->addTapRecognizer([this] (const GestureTap &tap) {
138 0 : if (_handler.isActive()) {
139 0 : _handler.cancel();
140 : }
141 0 : _focusInputListener->setEnabled(false);
142 0 : return true;
143 10 : }, InputListener::makeButtonMask({InputMouseButton::Touch}), 1);
144 10 : _focusInputListener->setTouchFilter([this] (const InputEvent &event, const InputListener::DefaultEventFilter &cb) {
145 0 : if (!_container->isTouched(event.currentLocation, 8.0f)) {
146 0 : return true;
147 : }
148 0 : return false;
149 : });
150 10 : _focusInputListener->setEnabled(false);
151 :
152 10 : _handler.onText = std::bind(&InputField::handleTextInput, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
153 10 : _handler.onKeyboard = std::bind(&InputField::handleKeyboardEnabled, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
154 10 : _handler.onInput = std::bind(&InputField::handleInputEnabled, this, std::placeholders::_1);
155 :
156 10 : return true;
157 : }
158 :
159 10 : void InputField::onEnter(Scene *scene) {
160 10 : Surface::onEnter(scene);
161 10 : }
162 :
163 10 : void InputField::onExit() {
164 10 : Surface::onExit();
165 10 : _container->setCursorCallback(nullptr);
166 10 : }
167 :
168 10 : void InputField::onContentSizeDirty() {
169 10 : Surface::onContentSizeDirty();
170 :
171 10 : _supportingText->setPosition(Vec2(16.0f, -4.0f));
172 10 : _supportingText->setWidth(_contentSize.width - 32.0f);
173 :
174 10 : _leadingIcon->setPosition(Vec2(12.0f, _contentSize.height / 2.0f));
175 10 : _trailingIcon->setPosition(Vec2(_contentSize.width - 12.0f, _contentSize.height / 2.0f));
176 :
177 10 : float xOffset = 16.0f;
178 10 : float containerWidth = _contentSize.width - 16.0f * 2.0f;
179 :
180 10 : if (getLeadingIconName() != IconName::None) {
181 10 : xOffset += _leadingIcon->getContentSize().width + 12.0f;
182 10 : containerWidth -= _leadingIcon->getContentSize().width + 12.0f;
183 : }
184 :
185 10 : if (getTrailingIconName() != IconName::None) {
186 10 : containerWidth -= _trailingIcon->getContentSize().width + 12.0f;
187 : }
188 :
189 10 : _container->setContentSize(Size2(containerWidth, _contentSize.height - 32.0f));
190 10 : _container->setPosition(Vec2(xOffset, 10.0f));
191 :
192 10 : if (_focused) {
193 0 : _labelText->setAnchorPoint(Anchor::TopLeft);
194 0 : _labelText->setPosition(Vec2(xOffset, _contentSize.height - 9.0f));
195 0 : _indicator->setContentSize(Size2(_contentSize.width, 2.0f));
196 : } else {
197 10 : if (!_inputString.empty()) {
198 0 : _labelText->setAnchorPoint(Anchor::TopLeft);
199 0 : _labelText->setPosition(Vec2(xOffset, _contentSize.height - 9.0f));
200 : } else {
201 10 : _labelText->setAnchorPoint(Anchor::MiddleLeft);
202 10 : _labelText->setPosition(Vec2(xOffset, _contentSize.height / 2.0f));
203 : }
204 10 : _indicator->setContentSize(Size2(_contentSize.width, 1.0f));
205 : }
206 :
207 10 : stopAllActionsByTag(InputEnabledActionTag);
208 10 : }
209 :
210 10 : void InputField::setLabelText(StringView text) {
211 10 : _labelText->setString(text);
212 10 : }
213 :
214 0 : StringView InputField::getLabelText() const {
215 0 : return _labelText->getString8();
216 : }
217 :
218 10 : void InputField::setSupportingText(StringView text) {
219 10 : _supportingText->setString(text);
220 10 : }
221 :
222 0 : StringView InputField::getSupportingText() const {
223 0 : return _supportingText->getString8();
224 : }
225 :
226 10 : void InputField::setLeadingIconName(IconName name) {
227 10 : if (name != getLeadingIconName()) {
228 10 : _leadingIcon->setIconName(name);
229 10 : _contentSizeDirty = true;
230 : }
231 10 : }
232 :
233 30 : IconName InputField::getLeadingIconName() const {
234 30 : return _leadingIcon->getIconName();
235 : }
236 :
237 10 : void InputField::setTrailingIconName(IconName name) {
238 10 : if (name != getTrailingIconName()) {
239 10 : _trailingIcon->setIconName(name);
240 10 : _contentSizeDirty = true;
241 : }
242 10 : }
243 :
244 30 : IconName InputField::getTrailingIconName() const {
245 30 : return _trailingIcon->getIconName();
246 : }
247 :
248 0 : void InputField::setEnabled(bool value) {
249 0 : if (_enabled != value) {
250 0 : _enabled = value;
251 0 : updateActivityState();
252 : }
253 0 : }
254 :
255 20 : bool InputField::isEnabled() const {
256 20 : return _enabled;
257 : }
258 :
259 0 : void InputField::setInputType(TextInputType type) {
260 0 : _inputType = type;
261 0 : }
262 :
263 0 : void InputField::setPasswordMode(InputFieldPasswordMode mode) {
264 0 : _passwordMode = mode;
265 0 : }
266 :
267 20 : void InputField::updateActivityState() {
268 20 : auto style = getStyleTarget();
269 20 : if (!_enabled) {
270 0 : style.activityState = ActivityState::Disabled;
271 20 : } else if (_focused) {
272 10 : style.activityState = ActivityState::Enabled;
273 10 : } else if (_mouseOver) {
274 0 : style.activityState = ActivityState::Hovered;
275 : } else {
276 10 : style.activityState = ActivityState::Enabled;
277 : }
278 20 : setStyle(style, _activityAnimationDuration);
279 20 : }
280 :
281 10 : bool InputField::handleTap(const Vec2 &pt) {
282 10 : if (!isEnabled()) {
283 0 : return false;
284 : }
285 :
286 10 : return true;
287 : }
288 :
289 10 : bool InputField::handlePressBegin(const Vec2 &pt) {
290 10 : if (!isEnabled()) {
291 0 : return false;
292 : }
293 :
294 10 : if (_leadingIcon && getLeadingIconName() != IconName::None && _leadingIcon->isTouched(pt, 12)) {
295 0 : return false;
296 : }
297 :
298 10 : if (_trailingIcon && getTrailingIconName() != IconName::None && _trailingIcon->isTouched(pt, 12)) {
299 0 : return false;
300 : }
301 :
302 10 : _inputListener->setExclusive();
303 10 : _isLongPress = false;
304 10 : return true;
305 : }
306 :
307 0 : bool InputField::handleLongPress(const Vec2 &pt, uint32_t tickCount) {
308 0 : if (!isEnabled() || !_rangeSelectionAllowed) {
309 0 : return false;
310 : }
311 :
312 0 : if (_container->handleLongPress(pt, tickCount)) {
313 0 : _isLongPress = true;
314 0 : if (!_focused) {
315 0 : acquireInputFromContainer();
316 : }
317 0 : return true;
318 : }
319 0 : return false;
320 : }
321 :
322 10 : bool InputField::handlePressEnd(const Vec2 &pt) {
323 10 : if (_container->isTouched(pt, 8.0f)) {
324 10 : if (!_isLongPress) {
325 10 : if (!_focused) {
326 10 : acquireInput(pt);
327 : } else {
328 0 : updateCursorForLocation(pt);
329 : }
330 : }
331 : }
332 10 : _isLongPress = false;
333 10 : return true;
334 : }
335 :
336 0 : bool InputField::handlePressCancel(const Vec2 &) {
337 0 : _isLongPress = false;
338 0 : return false;
339 : }
340 :
341 0 : bool InputField::handleSwipeBegin(const Vec2 &pt, const Vec2 &delta) {
342 0 : if (!isEnabled()) {
343 0 : return false;
344 : }
345 :
346 0 : if (_focused) {
347 0 : if (_container->handleSwipeBegin(pt)) {
348 0 : _pointerSwipeCaptured = true;
349 0 : return true;
350 : }
351 : }
352 :
353 0 : if (_container->hasHorizontalOverflow() && _container->isTouched(pt, 8.0f)) {
354 0 : _inputListener->setExclusive();
355 0 : _containerSwipeCaptured = true;
356 0 : return true;
357 : }
358 :
359 0 : return false;
360 : }
361 :
362 0 : bool InputField::handleSwipe(const Vec2 &pt, const Vec2 &delta, const Vec2 &v) {
363 0 : if (_pointerSwipeCaptured) {
364 0 : return _container->handleSwipe(pt, delta);
365 : }
366 :
367 0 : if (_containerSwipeCaptured) {
368 0 : _container->moveHorizontalOverflow(delta.x);
369 0 : return true;
370 : }
371 :
372 0 : return false;
373 : }
374 :
375 0 : bool InputField::handleSwipeEnd(const Vec2 &pt) {
376 0 : if (_pointerSwipeCaptured) {
377 0 : auto ret = _container->handleSwipeEnd(pt);
378 0 : _pointerSwipeCaptured = false;
379 0 : return ret;
380 : }
381 :
382 0 : if (_containerSwipeCaptured) {
383 0 : _containerSwipeCaptured = false;
384 0 : return true;
385 : }
386 0 : return false;
387 : }
388 :
389 20 : void InputField::updateInputEnabled() {
390 20 : if (!_running) {
391 10 : _contentSizeDirty = true;
392 10 : return;
393 : }
394 :
395 10 : stopAllActionsByTag(InputEnabledActionTag);
396 :
397 10 : bool populated = !_inputString.empty();
398 :
399 10 : auto labelAnchor = _labelText->getAnchorPoint();
400 10 : auto labelPos = _labelText->getPosition();
401 10 : auto indicatorSize = _indicator->getContentSize();
402 :
403 10 : auto targetLabelAnchor = Anchor::MiddleLeft;
404 10 : auto targetLabelPos = Vec3(labelPos.x, _contentSize.height / 2.0f, 0.0f);
405 10 : auto targetIndicatorSize = Size2(indicatorSize.width, _focused ? 2.0f : 1.0f);
406 :
407 10 : auto sourceLabelSize = _labelText->getFontSize();
408 10 : auto targetLabelSize = font::FontSize(16);
409 :
410 10 : auto sourceBlendValue = _labelText->getBlendColorValue();
411 10 : auto targetBlendValue = _focused ? 1.0f : 0.0f;
412 :
413 10 : if (populated || _focused) {
414 10 : targetLabelAnchor = Anchor::TopLeft;
415 10 : targetLabelPos = Vec3(labelPos.x, _contentSize.height - 9.0f, 0.0f);
416 10 : targetLabelSize = font::FontSize(12);
417 : }
418 :
419 10 : runAction(makeEasing(Rc<ActionProgress>::create(_activityAnimationDuration, [=, this] (float p) {
420 160 : _labelText->setAnchorPoint(progress(labelAnchor, targetLabelAnchor, p));
421 160 : _labelText->setPosition(progress(labelPos, targetLabelPos, p));
422 160 : _labelText->setFontSize(progress(sourceLabelSize, targetLabelSize, p));
423 160 : _indicator->setContentSize(progress(indicatorSize, targetIndicatorSize, p));
424 160 : }), EasingType::Standard), InputEnabledActionTag);
425 :
426 10 : runAction(makeEasing(Rc<ActionProgress>::create(_activityAnimationDuration, [=, this] (float p) {
427 160 : _labelText->setBlendColor(ColorRole::Primary, progress(sourceBlendValue, targetBlendValue, p));
428 160 : }), EasingType::Standard), InputEnabledLabelActionTag);
429 :
430 10 : auto indicatorStyle = _indicator->getStyleTarget();
431 10 : indicatorStyle.colorRole = _focused ? ColorRole::Primary : ColorRole::OnSurfaceVariant;
432 :
433 10 : _indicator->setStyle(indicatorStyle, _activityAnimationDuration);
434 : }
435 :
436 0 : void InputField::acquireInputFromContainer() {
437 0 : _cursor = _container->getCursor();
438 0 : _markedRegion = TextCursor::InvalidCursor;
439 0 : _handler.run(_director->getTextInputManager(), _inputString, _cursor, _markedRegion, _inputType);
440 0 : _focusInputListener->setEnabled(true);
441 0 : }
442 :
443 10 : void InputField::acquireInput(const Vec2 &targetLocation) {
444 10 : auto cursor = _container->getCursorForPosition(targetLocation);
445 10 : if (cursor != TextCursor::InvalidCursor) {
446 0 : _cursor = cursor;
447 : } else {
448 10 : _cursor = TextCursor(_inputString.size(), 0);
449 : }
450 :
451 10 : _container->setCursor(_cursor);
452 10 : _container->touchPointers();
453 10 : _markedRegion = TextCursor::InvalidCursor;
454 10 : _handler.run(_director->getTextInputManager(), _inputString, _cursor, _markedRegion, _inputType);
455 10 : _focusInputListener->setEnabled(true);
456 10 : }
457 :
458 0 : void InputField::updateCursorForLocation(const Vec2 &targetLocation) {
459 0 : auto cursor = _container->getCursorForPosition(targetLocation);
460 0 : if (cursor != TextCursor::InvalidCursor && cursor != _cursor) {
461 0 : _cursor = cursor;
462 0 : if (_handler.isActive()) {
463 0 : _handler.setCursor(cursor);
464 0 : _container->setCursor(cursor);
465 0 : _container->touchPointers();
466 0 : _markedRegion = TextCursor::InvalidCursor;
467 : }
468 : }
469 0 : }
470 :
471 330 : void InputField::handleTextInput(WideStringView str, TextCursor cursor, TextCursor marked) {
472 330 : auto label = _container->getLabel();
473 :
474 330 : auto maxChars = label->getMaxChars();
475 330 : if (maxChars > 0) {
476 0 : if (maxChars < str.size()) {
477 0 : auto tmpString = WideStringView(str, 0, maxChars);
478 0 : _handler.setString(tmpString, cursor);
479 0 : handleTextInput(tmpString, _cursor, _markedRegion);
480 0 : handleError(InputFieldError::Overflow);
481 0 : return;
482 : }
483 : }
484 :
485 3675 : for (auto &it : str) {
486 3345 : if (!handleInputChar(it)) {
487 0 : _handler.setString(getInputString(), _cursor);
488 0 : handleError(InputFieldError::Overflow);
489 0 : return;
490 : }
491 : }
492 :
493 330 : _container->setCursor(cursor);
494 :
495 330 : _inputString = str.str<Interface>();
496 330 : _cursor = cursor;
497 :
498 330 : switch (_passwordMode) {
499 330 : case InputFieldPasswordMode::NotPassword:
500 : case InputFieldPasswordMode::ShowAll:
501 330 : label->setString(_inputString);
502 330 : break;
503 0 : case InputFieldPasswordMode::ShowNone:
504 : case InputFieldPasswordMode::ShowChar: {
505 0 : WideString str; str.resize(_inputString.length(), u'*');
506 0 : label->setString(str);
507 : /*if (isInsert) {
508 : showLastChar();
509 : }*/
510 0 : break;
511 0 : }
512 : }
513 :
514 330 : label->tryUpdateLabel();
515 :
516 330 : _container->handleLabelChanged();
517 : }
518 :
519 0 : void InputField::handleKeyboardEnabled(bool, const Rect &, float) {
520 :
521 0 : }
522 :
523 20 : void InputField::handleInputEnabled(bool enabled) {
524 20 : if (_focused != enabled) {
525 20 : _focused = enabled;
526 20 : updateActivityState();
527 20 : updateInputEnabled();
528 20 : if (_enabled) {
529 20 : _container->setCursorCallback([this] (TextCursor cursor) {
530 0 : _cursor = cursor;
531 0 : _handler.setCursor(cursor);
532 0 : });
533 : } else {
534 0 : _container->setCursorCallback(nullptr);
535 : }
536 : }
537 20 : _container->setEnabled(enabled);
538 20 : }
539 :
540 3345 : bool InputField::handleInputChar(char16_t ch) {
541 3345 : return true;
542 : }
543 :
544 0 : void InputField::handleError(InputFieldError err) {
545 :
546 0 : }
547 :
548 : }
|