LCOV - code coverage report
Current view: top level - xenolith/renderer/material2d/components/appbar - MaterialAppBar.cc (source / functions) Hit Total Coverage
Test: coverage.info Lines: 140 257 54.5 %
Date: 2024-05-12 00:16:13 Functions: 15 32 46.9 %

          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 "MaterialAppBar.h"
      24             : #include "MaterialButton.h"
      25             : #include "XLInputListener.h"
      26             : #include "XLAction.h"
      27             : 
      28             : namespace STAPPLER_VERSIONIZED stappler::xenolith::material2d {
      29             : 
      30          10 : bool AppBar::init(AppBarLayout layout, const SurfaceStyle & style) {
      31          10 :         if (!Surface::init(style)) {
      32           0 :                 return false;
      33             :         }
      34             : 
      35          10 :         _layout = layout;
      36             : 
      37          10 :         _inputListener = addInputListener(Rc<InputListener>::create());
      38          10 :         _inputListener->addTouchRecognizer([this] (const GestureData &) -> bool {
      39           0 :                 return isSwallowTouches();
      40             :         });
      41          10 :         _inputListener->addPressRecognizer([this] (const GesturePress &press) -> bool {
      42           0 :                 if (_barCallback) {
      43           0 :                         if (press.event  == GestureEvent::Ended) {
      44           0 :                                 _barCallback();
      45             :                         }
      46           0 :                         return true;
      47             :                 }
      48           0 :                 return false;
      49          10 :         }, TimeInterval::milliseconds(425), true);
      50          10 :         _inputListener->setSwallowEvents(InputListener::EventMaskTouch);
      51             : 
      52          10 :         _actionMenuSourceListener = addComponent(Rc<DataListener<MenuSource>>::create(std::bind(&AppBar::layoutSubviews, this)));
      53             : 
      54          10 :         _navButton = addChild(Rc<Button>::create(NodeStyle::Text), ZOrder(1));
      55          10 :         _navButton->setTapCallback(std::bind(&AppBar::handleNavTapped, this));
      56          10 :         _navButton->setLeadingIconName(IconName::Navigation_menu_solid);
      57          10 :         _navButton->setIconSize(24.0f);
      58          10 :         _navButton->setNodeMask(Button::LeadingIcon);
      59          10 :         _navButton->setFollowContentSize(false);
      60          10 :         _navButton->setSwallowEvents(true);
      61             : 
      62          10 :         _label = addChild(Rc<TypescaleLabel>::create(TypescaleRole::TitleLarge));
      63          10 :         _label->setAnchorPoint(Anchor::MiddleLeft);
      64             : 
      65          10 :         _scissorNode = addChild(Rc<DynamicStateNode>::create());
      66          10 :         _scissorNode->setPosition(Vec2(0, 0));
      67          10 :         _scissorNode->setAnchorPoint(Anchor::BottomLeft);
      68             : 
      69          10 :         _iconsComposer = addChild(Rc<Node>::create(), ZOrder(1));
      70          10 :         _iconsComposer->setPosition(Vec2(0, 0));
      71          10 :         _iconsComposer->setAnchorPoint(Anchor::BottomLeft);
      72          10 :         _iconsComposer->setCascadeOpacityEnabled(true);
      73             : 
      74          10 :         updateDefaultHeight();
      75             : 
      76          10 :         setStateApplyMode(StateApplyMode::ApplyForAll);
      77          10 :         enableScissor();
      78             : 
      79          10 :         return true;
      80             : }
      81             : 
      82          20 : void AppBar::onContentSizeDirty() {
      83          20 :         Surface::onContentSizeDirty();
      84          20 :         updateDefaultHeight();
      85          20 :         layoutSubviews();
      86          20 : }
      87             : 
      88           0 : void AppBar::setLayout(AppBarLayout layout) {
      89           0 :         if (_layout != layout) {
      90           0 :                 _layout = layout;
      91           0 :                 _contentSizeDirty = true;
      92             :         }
      93           0 : }
      94             : 
      95          10 : void AppBar::setTitle(StringView str) {
      96          10 :         _label->setString(str);
      97          10 : }
      98             : 
      99           0 : StringView AppBar::getTitle() const {
     100           0 :         return _label->getString8();
     101             : }
     102             : 
     103          10 : void AppBar::setNavButtonIcon(IconName name, float progress) {
     104          10 :         _navButton->setLeadingIconName(name, progress);
     105          10 :         _contentSizeDirty = true;
     106          10 : }
     107             : 
     108          70 : IconName AppBar::getNavButtonIcon() const {
     109          70 :         return _navButton->getLeadingIconName();
     110             : }
     111             : 
     112          10 : void AppBar::setMaxActionIcons(size_t value) {
     113          10 :         _maxActionIcons = value;
     114          10 :         _contentSizeDirty = true;
     115          10 : }
     116           0 : size_t AppBar::getMaxActionIcons() const {
     117           0 :         return _maxActionIcons;
     118             : }
     119             : 
     120          10 : void AppBar::setActionMenuSource(MenuSource *source) {
     121          10 :         if (_actionMenuSourceListener->getSubscription() != source) {
     122          10 :                 _actionMenuSourceListener->setSubscription(source);
     123             :         }
     124          10 : }
     125             : 
     126           0 : void AppBar::replaceActionMenuSource(MenuSource *source, size_t maxIcons) {
     127           0 :         if (source == _actionMenuSourceListener->getSubscription()) {
     128           0 :                 return;
     129             :         }
     130             : 
     131           0 :         if (maxIcons == maxOf<size_t>()) {
     132           0 :                 maxIcons = source->getHintCount();
     133             :         }
     134             : 
     135           0 :         stopAllActionsByTag("replaceActionMenuSource"_tag);
     136           0 :         if (_prevComposer) {
     137           0 :                 _prevComposer->removeFromParent();
     138           0 :                 _prevComposer = nullptr;
     139             :         }
     140             : 
     141           0 :         _actionMenuSourceListener->setSubscription(source);
     142           0 :         _maxActionIcons = maxIcons;
     143             : 
     144           0 :         _prevComposer = _iconsComposer;
     145           0 :         float pos = -_prevComposer->getContentSize().height;
     146           0 :         _iconsComposer = _scissorNode->addChild(Rc<Node>::create(), ZOrder(1));
     147           0 :         _iconsComposer->setPosition(Vec2(0, pos));
     148           0 :         _iconsComposer->setAnchorPoint(Anchor::BottomLeft);
     149           0 :         _iconsComposer->setCascadeOpacityEnabled(true);
     150             : 
     151           0 :         float iconWidth = updateMenu(_iconsComposer, source, _maxActionIcons);
     152           0 :         if (iconWidth > _iconWidth) {
     153           0 :                 _iconWidth = iconWidth;
     154           0 :                 _contentSizeDirty = true;
     155             :         }
     156             : 
     157           0 :         _replaceProgress = 0.0f;
     158           0 :         updateProgress();
     159             : 
     160           0 :         runAction(Rc<ActionProgress>::create(0.15f, [this] (float p) {
     161           0 :                 _replaceProgress = p;
     162           0 :                 updateProgress();
     163           0 :         }, nullptr, [this] () {
     164           0 :                 _replaceProgress = 1.0f;
     165           0 :                 updateProgress();
     166           0 :                 _contentSizeDirty = true;
     167           0 :         }), "replaceActionMenuSource"_tag);
     168             : }
     169             : 
     170           0 : MenuSource * AppBar::getActionMenuSource() const {
     171           0 :         return _actionMenuSourceListener->getSubscription();
     172             : }
     173             : 
     174           0 : void AppBar::setBasicHeight(float value) {
     175           0 :         if (isnan(value)) {
     176           0 :                 _basicHeight = value;
     177           0 :                 _contentSizeDirty = true;
     178             :         } else {
     179           0 :                 if (_basicHeight != value) {
     180           0 :                         _basicHeight = value;
     181           0 :                         _contentSizeDirty = true;
     182             :                 }
     183             :         }
     184           0 : }
     185             : 
     186           0 : float AppBar::getBasicHeight() const {
     187           0 :         return _basicHeight;
     188             : }
     189             : 
     190          10 : void AppBar::setNavCallback(Function<void()> &&cb) {
     191          10 :         _navCallback = move(cb);
     192          10 : }
     193           0 : const Function<void()> & AppBar::getNavCallback() const {
     194           0 :         return _navCallback;
     195             : }
     196             : 
     197           0 : void AppBar::setSwallowTouches(bool value) {
     198           0 :         if (value) {
     199           0 :                 _inputListener->setSwallowEvents(InputListener::EventMaskTouch);
     200             :         } else {
     201           0 :                 _inputListener->clearSwallowEvents(InputListener::EventMaskTouch);
     202             :         }
     203           0 : }
     204             : 
     205           0 : bool AppBar::isSwallowTouches() const {
     206           0 :         return _inputListener->isSwallowAllEvents(InputListener::EventMaskTouch);
     207             : }
     208             : 
     209          10 : Button *AppBar::getNavNode() const {
     210          10 :         return _navButton;
     211             : }
     212             : 
     213           0 : void AppBar::setBarCallback(Function<void()> &&cb) {
     214           0 :         _barCallback = move(cb);
     215           0 : }
     216             : 
     217           0 : const Function<void()> & AppBar::getBarCallback() const {
     218           0 :         return _barCallback;
     219             : }
     220             : 
     221           0 : void AppBar::handleNavTapped() {
     222           0 :         if (_navCallback) {
     223           0 :                 _navCallback();
     224             :         }
     225           0 : }
     226             : 
     227          30 : void AppBar::updateProgress() {
     228          30 :         if (_replaceProgress == 1.0f) {
     229          30 :                 if (_prevComposer) {
     230           0 :                         _prevComposer->removeFromParent();
     231           0 :                         _prevComposer = nullptr;
     232             :                 }
     233             :         }
     234             : 
     235          30 :         if (_iconsComposer) {
     236          30 :                 _iconsComposer->setPositionY(progress(_iconsComposer->getContentSize().height, 0.0f, _replaceProgress));
     237             :         }
     238          30 :         if (_prevComposer) {
     239           0 :                 _prevComposer->setPositionY(progress(0.0f, -_prevComposer->getContentSize().height, _replaceProgress));
     240           0 :                 _prevComposer->setOpacity(1.0f - _replaceProgress);
     241             :         }
     242          30 : }
     243             : 
     244          30 : float AppBar::updateMenu(Node *composer, MenuSource *source, size_t maxIcons) {
     245          30 :         composer->removeAllChildren();
     246          30 :         composer->setContentSize(_contentSize);
     247             : 
     248          30 :         float baseline = getBaseLine();
     249          30 :         size_t iconsCount = 0;
     250          30 :         auto extMenuSource = Rc<MenuSource>::create();
     251          30 :         Vector<Button *> icons;
     252          30 :         bool hasExtMenu = false;
     253             : 
     254          30 :         if (source) {
     255          30 :                 auto &menuItems = source->getItems();
     256         150 :                 for (auto &item : menuItems) {
     257         120 :                         if (item->getType() == MenuSourceItem::Type::Button) {
     258         120 :                                 auto btnSrc = dynamic_cast<MenuSourceButton *>(item.get());
     259         120 :                                 if (btnSrc->getNameIcon() != IconName::None) {
     260         120 :                                         if (iconsCount < maxIcons) {
     261         120 :                                                 auto btn = composer->addChild(Rc<Button>::create(NodeStyle::Text), ZOrder(0), iconsCount);
     262         120 :                                                 btn->setMenuSourceButton(btnSrc);
     263         120 :                                                 btn->setIconSize(24.0f);
     264         120 :                                                 btn->setFollowContentSize(false);
     265         120 :                                                 btn->setSwallowEvents(true);
     266         120 :                                                 btn->setNodeMask(Button::LeadingIcon);
     267         120 :                                                 icons.push_back(btn);
     268         120 :                                                 iconsCount ++;
     269             :                                         } else {
     270           0 :                                                 extMenuSource->addItem(item);
     271             :                                         }
     272             :                                 }
     273             :                         }
     274             :                 }
     275             :         }
     276             : 
     277          30 :         if (extMenuSource->count() > 0) {
     278           0 :                 auto btn = Rc<Button>::create();
     279           0 :                 auto source = Rc<MenuSourceButton>::create(StringView(), IconName::Navigation_more_vert_solid, move(extMenuSource));
     280           0 :                 btn->setMenuSourceButton(move(source));
     281           0 :                 btn->setFollowContentSize(false);
     282           0 :                 btn->setSwallowEvents(true);
     283           0 :                 btn->setNodeMask(Button::LeadingIcon);
     284           0 :                 icons.push_back(btn);
     285           0 :                 composer->addChild(move(btn), ZOrder(0), iconsCount);
     286           0 :                 hasExtMenu = true;
     287           0 :         } else {
     288          30 :                 hasExtMenu = false;
     289             :         }
     290             : 
     291          30 :         if (icons.size() > 0) {
     292          30 :                 if (icons.back()->getLeadingIconName() == IconName::Navigation_more_vert_solid) {
     293           0 :                         hasExtMenu = true;
     294             :                 }
     295          30 :                 auto pos = composer->getContentSize().width - 56 * (icons.size() - 1) - (hasExtMenu?8:36);
     296         150 :                 for (auto &it : icons) {
     297         120 :                         it->setContentSize(Size2(48, std::min(48.0f, getRealHeight())));
     298         120 :                         it->setAnchorPoint(Vec2(0.5, 0.5));
     299         120 :                         it->setPosition(Vec2(pos, baseline));
     300         120 :                         pos += 56;
     301             :                 }
     302          30 :                 if (hasExtMenu) {
     303           0 :                         icons.back()->setContentSize(Size2(24, std::min(48.0f, getRealHeight())));
     304           0 :                         icons.back()->setPosition(Vec2(composer->getContentSize().width - 24, baseline));
     305             :                 }
     306             :         }
     307             : 
     308          30 :         if (icons.size() > 0) {
     309          30 :                 return (56 * (icons.size()) - (hasExtMenu?24:0));
     310             :         }
     311           0 :         return 0;
     312          30 : }
     313             : 
     314          30 : void AppBar::layoutSubviews() {
     315          30 :         _scissorNode->setContentSize(_contentSize);
     316             : 
     317          30 :         updateProgress();
     318             : 
     319          30 :         _iconsComposer->setContentSize(_scissorNode->getContentSize());
     320          30 :         auto iconWidth = updateMenu(_iconsComposer, _actionMenuSourceListener->getSubscription(), _maxActionIcons);
     321          30 :         if (_replaceProgress != 1.0f && _iconWidth != 0.0f) {
     322           0 :                 _iconWidth = std::max(iconWidth, _iconWidth);
     323             :         } else {
     324          30 :                 _iconWidth = iconWidth;
     325             :         }
     326             : 
     327          30 :         float baseline = getBaseLine();
     328          30 :         if (_navButton->getLeadingIconName() != IconName::Empty && _navButton->getLeadingIconName() != IconName::None) {
     329          30 :                 _navButton->setContentSize(Size2(48.0f, 48.0f));
     330          30 :                 _navButton->setAnchorPoint(Anchor::Middle);
     331          30 :                 _navButton->setPosition(Vec2(32.0f, baseline));
     332          30 :                 _navButton->setVisible(true);
     333             :         } else {
     334           0 :                 _navButton->setVisible(false);
     335             :         }
     336             : 
     337          30 :         float labelStart = (getNavButtonIcon() == IconName::None) ? 16.0f : 64.0f;
     338          30 :         float labelEnd = _contentSize.width - _iconWidth - 16.0f;
     339             : 
     340          30 :         _label->setMaxWidth(labelEnd - labelStart);
     341             : 
     342          30 :         switch (_layout) {
     343           0 :         case AppBarLayout::CenterAligned:
     344           0 :                 _label->setAnchorPoint(Anchor::Middle);
     345           0 :                 _label->setAlignment(Label::TextAlign::Center);
     346           0 :                 _label->setPosition(Vec2((labelStart + labelEnd) / 2.0f, baseline));
     347           0 :                 break;
     348          30 :         case AppBarLayout::Small:
     349          30 :                 _label->setAnchorPoint(Anchor::MiddleLeft);
     350          30 :                 _label->setAlignment(Label::TextAlign::Left);
     351          30 :                 _label->setPosition((getNavButtonIcon() == IconName::None) ? Vec2(16.0f, baseline) : Vec2(64.0f, baseline));
     352          30 :                 break;
     353           0 :         case AppBarLayout::Minified:
     354           0 :                 _label->setRole(TypescaleRole::TitleMedium);
     355           0 :                 _label->setAnchorPoint(Anchor::MiddleLeft);
     356           0 :                 _label->setAlignment(Label::TextAlign::Left);
     357           0 :                 _label->setPosition((getNavButtonIcon() == IconName::None) ? Vec2(16.0f, baseline) : Vec2(64.0f, baseline));
     358           0 :                 break;
     359           0 :         default:
     360           0 :                 break;
     361             :         }
     362          30 : }
     363             : 
     364          60 : float AppBar::getBaseLine() const {
     365          60 :         auto v = _defaultHeight;
     366          60 :         if (!isnan(_basicHeight)) {
     367           0 :                 v = std::max(v, _basicHeight);
     368             :         }
     369          60 :         if (_contentSize.height > _basicHeight) {
     370           0 :                 return _contentSize.height - v / 2;
     371             :         } else {
     372          60 :                 return v / 2;
     373             :         }
     374             : }
     375             : 
     376         150 : float AppBar::getRealHeight() const {
     377         150 :         auto v = _defaultHeight;
     378         150 :         if (!isnan(_basicHeight)) {
     379           0 :                 v = std::max(v, _basicHeight);
     380             :         }
     381         150 :         return v;
     382             : }
     383             : 
     384          30 : void AppBar::updateDefaultHeight() {
     385          30 :         switch (_layout) {
     386          30 :         case AppBarLayout::CenterAligned:
     387             :         case AppBarLayout::Small:
     388             :         case AppBarLayout::Medium:
     389             :         case AppBarLayout::Large:
     390          30 :                 _defaultHeight = 56.0f;
     391          30 :                 break;
     392           0 :         case AppBarLayout::Minified:
     393           0 :                 _defaultHeight = 32.0f;
     394           0 :                 break;
     395             :         }
     396             : 
     397          30 :         _minHeight = 0.0f;
     398          30 :         _maxHeight = getRealHeight();
     399          30 : }
     400             : 
     401             : }

Generated by: LCOV version 1.14