LCOV - code coverage report
Current view: top level - xenolith/renderer/material2d/components/button - MaterialButton.cc (source / functions) Hit Total Coverage
Test: coverage.info Lines: 207 314 65.9 %
Date: 2024-05-12 00:16:13 Functions: 32 52 61.5 %

          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 "MaterialButton.h"
      24             : #include "MaterialLabel.h"
      25             : #include "XLScene.h"
      26             : #include "MaterialSceneContent.h"
      27             : #include "MaterialFloatingMenu.h"
      28             : #include "XLInputListener.h"
      29             : 
      30             : namespace STAPPLER_VERSIONIZED stappler::xenolith::material2d {
      31             : 
      32         146 : static SurfaceStyle Button_getSurfaceStyle(NodeStyle style, ColorRole role, uint32_t schemeTag) {
      33         292 :         return SurfaceStyle(style, Elevation::Level1, ShapeStyle::Full, role, schemeTag);
      34             : }
      35             : 
      36         146 : bool Button::init(NodeStyle style, ColorRole role, uint32_t schemeTag) {
      37         146 :         return init(Button_getSurfaceStyle(style, role, schemeTag));
      38             : }
      39             : 
      40         186 : bool Button::init(const SurfaceStyle &style) {
      41         186 :         if (!Surface::init(style)) {
      42           0 :                 return false;
      43             :         }
      44             : 
      45         186 :         _labelText = addChild(Rc<TypescaleLabel>::create(TypescaleRole::LabelLarge), ZOrder(1));
      46         186 :         _labelText->setAnchorPoint(Anchor::MiddleLeft);
      47         186 :         _labelText->setLocaleEnabled(true);
      48         186 :         _labelText->setOnContentSizeDirtyCallback([this] {
      49          76 :                 updateSizeFromContent();
      50          76 :         });
      51             : 
      52         186 :         _labelValue = addChild(Rc<TypescaleLabel>::create(TypescaleRole::LabelLarge), ZOrder(1));
      53         186 :         _labelValue->setAnchorPoint(Anchor::MiddleLeft);
      54         186 :         _labelValue->setLocaleEnabled(true);
      55         186 :         _labelValue->setOnContentSizeDirtyCallback([this] {
      56          40 :                 updateSizeFromContent();
      57          40 :         });
      58             : 
      59         186 :         _leadingIcon = addChild(Rc<IconSprite>::create(IconName::None), ZOrder(1));
      60         186 :         _leadingIcon->setAnchorPoint(Anchor::MiddleLeft);
      61         186 :         _leadingIcon->setContentSize(Size2(18.0f, 18.0f));
      62             : 
      63         186 :         _trailingIcon = addChild(Rc<IconSprite>::create(IconName::None), ZOrder(1));
      64         186 :         _trailingIcon->setAnchorPoint(Anchor::MiddleLeft);
      65         186 :         _trailingIcon->setContentSize(Size2(18.0f, 18.0f));
      66             : 
      67         186 :         _inputListener = addInputListener(Rc<InputListener>::create());
      68         186 :         _inputListener->addMouseOverRecognizer([this] (const GestureData &data) {
      69           1 :                 _mouseOver = (data.event == GestureEvent::Began);
      70           1 :                 updateActivityState();
      71           1 :                 return true;
      72             :         });
      73         186 :         _inputListener->addPressRecognizer([this] (const GesturePress &press) {
      74          32 :                 if (!_enabled || (_menuButtonListener->getSubscription() && !isMenuSourceButtonEnabled())) {
      75           0 :                         return false;
      76             :                 }
      77             : 
      78          32 :                 if (press.event == GestureEvent::Began) {
      79          16 :                         _touchLocation = press.location();
      80          16 :                         _longPressInit = false;
      81          16 :                         _pressed = true;
      82          16 :                         updateActivityState();
      83          16 :                 } else if (press.event == GestureEvent::Activated) {
      84           0 :                         _longPressInit = true;
      85           0 :                         handleLongPress();
      86           0 :                         _inputListener->setExclusiveForTouch(press.getId());
      87          16 :                 } else if (press.event == GestureEvent::Ended || press.event == GestureEvent::Cancelled) {
      88          16 :                         _pressed = false;
      89          16 :                         updateActivityState();
      90          16 :                         if (press.event == GestureEvent::Ended) {
      91          16 :                                 _inputListener->setExclusiveForTouch(press.getId());
      92          16 :                                 if (_longPressInit) {
      93             :                                 } else {
      94          16 :                                         handleTap();
      95             :                                 }
      96             :                         }
      97             :                 }
      98          32 :                 return true;
      99             :         }, LongPressInterval);
     100             : 
     101         186 :         _inputListener->addTapRecognizer([this] (const GestureTap &tap) {
     102           6 :                 if (!_enabled) {
     103           0 :                         return false;
     104             :                 }
     105           6 :                 if (tap.count == 2) {
     106           0 :                         _inputListener->setExclusiveForTouch(tap.getId());
     107           0 :                         handleDoubleTap();
     108             :                 }
     109           6 :                 return true;
     110             :         });
     111             : 
     112         186 :         _menuButtonListener = addComponent(Rc<DataListener<MenuSourceButton>>::create([this] (SubscriptionFlags flags) {
     113         178 :                 updateMenuButtonSource();
     114         178 :         }));
     115             : 
     116         186 :         return true;
     117             : }
     118             : 
     119         320 : void Button::onContentSizeDirty() {
     120         320 :         Surface::onContentSizeDirty();
     121             : 
     122         320 :         if (_followContentSize && hasContent()) {
     123           0 :                 updateSizeFromContent();
     124             :         }
     125             : 
     126         320 :         layoutContent();
     127         320 : }
     128             : 
     129         170 : void Button::setFollowContentSize(bool value) {
     130         170 :         if (value != _followContentSize) {
     131         170 :                 _followContentSize = value;
     132         170 :                 _contentSizeDirty = true;
     133         170 :                 if (_followContentSize) {
     134           0 :                         updateSizeFromContent();
     135             :                 }
     136             :         }
     137         170 : }
     138             : 
     139           0 : bool Button::isFollowContentSize() const {
     140           0 :         return _followContentSize;
     141             : }
     142             : 
     143         146 : void Button::setSwallowEvents(bool value) {
     144         146 :         if (value) {
     145         146 :                 _inputListener->setSwallowEvents(InputListener::EventMaskTouch);
     146             :         } else {
     147           0 :                 _inputListener->clearSwallowEvents(InputListener::EventMaskTouch);
     148             :         }
     149         146 : }
     150             : 
     151           0 : bool Button::isSwallowEvents() const {
     152           0 :         return _inputListener->isSwallowAllEvents(InputListener::EventMaskTouch);
     153             : }
     154             : 
     155           0 : void Button::setEnabled(bool value) {
     156           0 :         if (value != _enabled) {
     157           0 :                 _enabled = value;
     158           0 :                 _inputListener->setEnabled(_enabled);
     159           0 :                 updateActivityState();
     160             :         }
     161           0 : }
     162             : 
     163           0 : void Button::setSelected(bool val) {
     164           0 :         if (val != _selected) {
     165           0 :                 _selected = val;
     166           0 :                 updateActivityState();
     167             :         }
     168           0 : }
     169             : 
     170           0 : bool Button::isSelected() const {
     171           0 :         return _selected;
     172             : }
     173             : 
     174         409 : bool Button::isMenuSourceButtonEnabled() const {
     175         409 :         if (!_menuButtonListener) {
     176           0 :                 return false;
     177             :         }
     178             : 
     179         409 :         return _menuButtonListener->getSubscription()->getCallback() != nullptr || _menuButtonListener->getSubscription()->getNextMenu();
     180             : }
     181             : 
     182         130 : void Button::setNodeMask(NodeMask mask) {
     183         130 :         if (_nodeMask != mask) {
     184         130 :                 _nodeMask = mask;
     185         130 :                 updateSizeFromContent();
     186         130 :                 _contentSizeDirty = true;
     187             :         }
     188         130 : }
     189             : 
     190         354 : void Button::setText(StringView text) {
     191         354 :         _labelText->setString(text);
     192         354 :         updateSizeFromContent();
     193         354 :         _contentSizeDirty = true;
     194         354 : }
     195             : 
     196           0 : StringView Button::getText() const {
     197           0 :         return _labelText->getString8();
     198             : }
     199             : 
     200         344 : void Button::setTextValue(StringView text) {
     201         344 :         _labelValue->setString(text);
     202         344 :         updateSizeFromContent();
     203         344 :         _contentSizeDirty = true;
     204         344 : }
     205             : 
     206           0 : StringView Button::getTextValue() const {
     207           0 :         return _labelValue->getString8();
     208             : }
     209             : 
     210         130 : void Button::setIconSize(float value) {
     211         130 :         if (value != getIconSize()) {
     212         130 :                 _leadingIcon->setContentSize(Size2(value, value));
     213         130 :                 _trailingIcon->setContentSize(Size2(value, value));
     214         130 :                 updateSizeFromContent();
     215             :         }
     216         130 : }
     217             : 
     218         696 : float Button::getIconSize() const {
     219         696 :         return _leadingIcon->getContentSize().width;
     220             : }
     221             : 
     222         374 : void Button::setLeadingIconName(IconName name, float progress) {
     223         374 :         if (name != getLeadingIconName()) {
     224         176 :                 _leadingIcon->setIconName(name);
     225         176 :                 _leadingIcon->setProgress(progress);
     226         176 :                 updateSizeFromContent();
     227             :         }
     228         374 : }
     229             : 
     230        1614 : IconName Button::getLeadingIconName() const {
     231        1614 :         return _leadingIcon->getIconName();
     232             : }
     233             : 
     234           0 : void Button::setLeadingIconProgress(float progress, float animation) {
     235           0 :         if (animation > 0.0f) {
     236           0 :                 _leadingIcon->animate(progress, animation);
     237             :         } else {
     238           0 :                 _leadingIcon->setProgress(progress);
     239             :         }
     240           0 : }
     241             : 
     242           0 : float Button::getLeadingIconProgress() const {
     243           0 :         return _leadingIcon->getProgress();
     244             : }
     245             : 
     246         364 : void Button::setTrailingIconName(IconName name) {
     247         364 :         if (name != getTrailingIconName()) {
     248          50 :                 _trailingIcon->setIconName(name);
     249          50 :                 updateSizeFromContent();
     250             :         }
     251         364 : }
     252             : 
     253        1392 : IconName Button::getTrailingIconName() const {
     254        1392 :         return _trailingIcon->getIconName();
     255             : }
     256             : 
     257           0 : void Button::setTrailingIconProgress(float progress, float animation) {
     258           0 :         if (animation > 0.0f) {
     259           0 :                 _trailingIcon->animate(progress, animation);
     260             :         } else {
     261           0 :                 _trailingIcon->setProgress(progress);
     262             :         }
     263           0 : }
     264             : 
     265           0 : float Button::getTrailingIconProgress() const {
     266           0 :         return _trailingIcon->getProgress();
     267             : }
     268             : 
     269          66 : void Button::setTapCallback(Function<void()> &&cb) {
     270          66 :         _callbackTap = move(cb);
     271          66 : }
     272             : 
     273           0 : void Button::setLongPressCallback(Function<void()> &&cb) {
     274           0 :         _callbackLongPress = move(cb);
     275           0 : }
     276             : 
     277           0 : void Button::setDoubleTapCallback(Function<void()> &&cb) {
     278           0 :         _callbackDoubleTap = move(cb);
     279           0 : }
     280             : 
     281         166 : void Button::setMenuSourceButton(Rc<MenuSourceButton> &&button) {
     282         166 :         if (button != _menuButtonListener->getSubscription()) {
     283         166 :                 if (auto b = _menuButtonListener->getSubscription()) {
     284           0 :                         b->handleNodeDetached(this);
     285             :                 }
     286         166 :                 _menuButtonListener->setSubscription(button.get());
     287         166 :                 updateMenuButtonSource();
     288         166 :                 if (button) {
     289         166 :                         button->handleNodeAttached(this);
     290             :                 }
     291             :         }
     292         166 : }
     293             : 
     294           0 : MenuSourceButton *Button::getMenuSourceButton() const {
     295           0 :         return _menuButtonListener->getSubscription();
     296             : }
     297             : 
     298           0 : void Button::setBlendColor(ColorRole rule, float value) {
     299           0 :         _labelText->setBlendColor(rule, value);
     300           0 :         _labelValue->setBlendColor(rule, value);
     301           0 :         _leadingIcon->setBlendColor(rule, value);
     302           0 :         _trailingIcon->setBlendColor(rule, value);
     303           0 : }
     304             : 
     305          10 : void Button::setBlendColor(const Color4F &color, float value) {
     306          10 :         _labelText->setBlendColor(color, value);
     307          10 :         _labelValue->setBlendColor(color, value);
     308          10 :         _leadingIcon->setBlendColor(color, value);
     309          10 :         _trailingIcon->setBlendColor(color, value);
     310          10 : }
     311             : 
     312           0 : ColorRole Button::getBlendColorRule() const {
     313           0 :         return _labelText->getBlendColorRule();
     314             : }
     315             : 
     316           0 : const Color4F &Button::getBlendColor() const {
     317           0 :         return _labelText->getBlendColor();
     318             : }
     319             : 
     320           0 : float Button::getBlendColorValue() const {
     321           0 :         return _labelText->getBlendColorValue();
     322             : }
     323             : 
     324         678 : bool Button::hasContent() const {
     325        1244 :         return !_labelText->empty() || !_labelValue->empty()
     326         566 :                         || _leadingIcon->getIconName() != IconName::None
     327        1244 :                         || _trailingIcon->getIconName() != IconName::None;
     328             : }
     329             : 
     330        1300 : void Button::updateSizeFromContent() {
     331        1300 :         if (!_followContentSize || !hasContent()) {
     332         622 :                 _contentSizeDirty = true;
     333         622 :                 return;
     334             :         }
     335             : 
     336         678 :         Size2 targetSize;
     337         678 :         if (!_labelText->empty() && (_nodeMask & NodeMask::LabelText) != NodeMask::None) {
     338         112 :                 targetSize = _labelText->getContentSize();
     339             :         } else {
     340         566 :                 targetSize.height = getIconSize();
     341             :         }
     342         678 :         targetSize.width = getWidthForContent();
     343         678 :         targetSize.height += 24.0f;
     344             : 
     345         678 :         setContentSize(targetSize);
     346             : }
     347             : 
     348         377 : void Button::updateActivityState() {
     349         377 :         auto style = getStyleTarget();
     350         377 :         if (!_enabled || (_menuButtonListener->getSubscription() && !isMenuSourceButtonEnabled())) {
     351          20 :                 style.activityState = ActivityState::Disabled;
     352         357 :         } else if (_pressed || _selected) {
     353          26 :                 style.activityState = ActivityState::Pressed;
     354         331 :         } else if (_mouseOver) {
     355           1 :                 style.activityState = ActivityState::Hovered;
     356         330 :         } else if (_focused) {
     357           0 :                 style.activityState = ActivityState::Focused;
     358             :         } else {
     359         330 :                 style.activityState = ActivityState::Enabled;
     360             :         }
     361         377 :         setStyle(style, _activityAnimationDuration);
     362         377 : }
     363             : 
     364          16 : void Button::handleTap() {
     365          16 :         if (_callbackTap) {
     366          16 :                 auto id = retain();
     367          16 :                 _callbackTap();
     368          16 :                 release(id);
     369           0 :         } else if (auto btn = _menuButtonListener->getSubscription()) {
     370           0 :                 auto &cb = btn->getCallback();
     371           0 :                 if (cb) {
     372           0 :                         cb(this, btn);
     373           0 :                 } else if (_floatingMenuSource) {
     374           0 :                         if (auto content = dynamic_cast<SceneContent2d *>(_scene->getContent())) {
     375           0 :                                 auto posRight = content->convertToNodeSpace(convertToWorldSpace(Vec2(_contentSize.width, _contentSize.height)));
     376             : 
     377           0 :                                 FloatingMenu::push(content, _floatingMenuSource, posRight,
     378             :                                         FloatingMenu::Binding::OriginRight, nullptr);
     379             :                         }
     380             :                 }
     381             :         }
     382          16 : }
     383             : 
     384           0 : void Button::handleLongPress() {
     385           0 :         if (_callbackLongPress) {
     386           0 :                 auto id = retain();
     387           0 :                 _callbackLongPress();
     388           0 :                 release(id);
     389             :         }
     390           0 : }
     391             : 
     392           0 : void Button::handleDoubleTap() {
     393           0 :         if (_callbackDoubleTap) {
     394           0 :                 auto id = retain();
     395           0 :                 _callbackDoubleTap();
     396           0 :                 release(id);
     397             :         }
     398           0 : }
     399             : 
     400         678 : float Button::getWidthForContent() const {
     401         678 :         float contentWidth = 0.0f;
     402         678 :         if (!_labelText->empty() && (_nodeMask & NodeMask::LabelText) != NodeMask::None) {
     403         112 :                 contentWidth = ((_styleTarget.nodeStyle == NodeStyle::Text) ? 24.0f : 48.0f) + _labelText->getContentSize().width;
     404         112 :                 if (_styleTarget.nodeStyle == NodeStyle::Text && (getLeadingIconName() != IconName::None || getTrailingIconName() != IconName::None)) {
     405          92 :                         contentWidth += 16.0f;
     406             :                 }
     407             :         } else {
     408         566 :                 contentWidth = 24.0f;
     409             :         }
     410             : 
     411         678 :         if (!_labelValue->empty() && (_nodeMask & NodeMask::LabelValue) != NodeMask::None) {
     412          30 :                 contentWidth += _labelValue->getContentSize().width + 8.0f;
     413             :         }
     414             : 
     415         678 :         if (getLeadingIconName() != IconName::None && (_nodeMask & NodeMask::LeadingIcon) != NodeMask::None) {
     416         628 :                 contentWidth += _leadingIcon->getContentSize().width;
     417             :         }
     418         678 :         if (getTrailingIconName() != IconName::None && (_nodeMask & NodeMask::TrailingIcon) != NodeMask::None) {
     419          70 :                 contentWidth += _trailingIcon->getContentSize().width;
     420             :         }
     421         678 :         return contentWidth;
     422             : }
     423             : 
     424         344 : void Button::updateMenuButtonSource() {
     425         344 :         if (auto btn = _menuButtonListener->getSubscription()) {
     426         344 :                 _selected = btn->isSelected();
     427         344 :                 _floatingMenuSource = btn->getNextMenu();
     428             : 
     429         344 :                 setLeadingIconName(btn->getNameIcon());
     430         344 :                 setTrailingIconName(btn->getValueIcon());
     431         344 :                 setText(btn->getName());
     432         344 :                 setTextValue(btn->getValue());
     433             : 
     434         344 :                 if (btn->getNextMenu()) {
     435          20 :                         if (getTrailingIconName() == IconName::None) {
     436          20 :                                 setTrailingIconName(IconName::Navigation_arrow_right_solid);
     437             :                         }
     438             :                 }
     439             :         } else {
     440           0 :                 _selected = false;
     441           0 :                 _floatingMenuSource = nullptr;
     442             :         }
     443         344 :         updateActivityState();
     444         344 : }
     445             : 
     446         170 : void Button::layoutContent() {
     447         170 :         auto leadingIconName = getLeadingIconName();
     448         170 :         auto trailingIconName = getTrailingIconName();
     449         170 :         auto hasLabelText = !_labelText->empty();
     450         170 :         auto hasLabelValue = !_labelValue->empty();
     451             : 
     452         170 :         _labelText->setVisible((_nodeMask & NodeMask::LabelText) != NodeMask::None);
     453         170 :         _labelValue->setVisible((_nodeMask & NodeMask::LabelValue) != NodeMask::None);
     454         170 :         _leadingIcon->setVisible((_nodeMask & NodeMask::LeadingIcon) != NodeMask::None);
     455         170 :         _trailingIcon->setVisible((_nodeMask & NodeMask::TrailingIcon) != NodeMask::None);
     456             : 
     457         170 :         if ((_nodeMask & NodeMask::LabelText) == NodeMask::None) {
     458         170 :                 hasLabelText = false;
     459             :         }
     460             : 
     461         170 :         if ((_nodeMask & NodeMask::LabelValue) == NodeMask::None) {
     462         170 :                 hasLabelValue = false;
     463             :         }
     464             : 
     465         170 :         if ((_nodeMask & NodeMask::LeadingIcon) == NodeMask::None) {
     466           0 :                 leadingIconName = IconName::None;
     467             :         }
     468             : 
     469         170 :         if ((_nodeMask & NodeMask::TrailingIcon) == NodeMask::None) {
     470         170 :                 trailingIconName = IconName::None;
     471             :         }
     472             : 
     473         170 :         if (leadingIconName != IconName::None && trailingIconName == IconName::None && !hasLabelText && !hasLabelValue) {
     474         170 :                 _leadingIcon->setAnchorPoint(Anchor::Middle);
     475         170 :                 _leadingIcon->setPosition(_contentSize / 2.0f);
     476             :         } else {
     477           0 :                 _leadingIcon->setAnchorPoint(Anchor::MiddleLeft);
     478             : 
     479           0 :                 float contentWidth = getWidthForContent();
     480           0 :                 float offset = (_contentSize.width - contentWidth) / 2.0f;
     481             : 
     482           0 :                 Vec2 target(offset + (_styleTarget.nodeStyle == NodeStyle::Text ? 12.0f : 16.0f), _contentSize.height / 2.0f);
     483             : 
     484           0 :                 if (leadingIconName != IconName::None) {
     485           0 :                         _leadingIcon->setPosition(target);
     486           0 :                         target.x += 8.0f + _leadingIcon->getContentSize().width;
     487           0 :                 } else if (_styleTarget.nodeStyle != NodeStyle::Text) {
     488           0 :                         target.x += 8.0f;
     489             :                 }
     490             : 
     491           0 :                 _labelText->setPosition(target);
     492           0 :                 target.x += _labelText->getContentSize().width + 8.0f;
     493             : 
     494           0 :                 if (hasLabelValue) {
     495           0 :                         _labelValue->setPosition(target);
     496           0 :                         target.x += _labelValue->getContentSize().width + 8.0f;
     497             :                 }
     498             : 
     499           0 :                 _trailingIcon->setPosition(target);
     500             :         }
     501         170 : }
     502             : 
     503             : }

Generated by: LCOV version 1.14