Expand emoji categories in the footer.

This commit is contained in:
John Preston 2022-07-12 19:10:40 +03:00
parent d3f62d971d
commit 0bd9d5f7ae
5 changed files with 229 additions and 84 deletions

View file

@ -179,6 +179,8 @@ emojiSwitchColor: windowActiveTextFg;
emojiSwitchStickers: icon {{ "emoji/emoji_switch", emojiSwitchColor }}; emojiSwitchStickers: icon {{ "emoji/emoji_switch", emojiSwitchColor }};
emojiSwitchEmoji: icon {{ "emoji/emoji_switch-flip_horizontal", emojiSwitchColor }}; emojiSwitchEmoji: icon {{ "emoji/emoji_switch-flip_horizontal", emojiSwitchColor }};
emojiIconSelectSkip: 2px;
hashtagClose: IconButton { hashtagClose: IconButton {
width: 30px; width: 30px;
height: 30px; height: 30px;

View file

@ -1211,11 +1211,10 @@ void EmojiListWidget::refreshCustom() {
std::vector<StickerIcon> EmojiListWidget::fillIcons() { std::vector<StickerIcon> EmojiListWidget::fillIcons() {
auto result = std::vector<StickerIcon>(); auto result = std::vector<StickerIcon>();
result.reserve(kEmojiSectionCount + _custom.size()); result.reserve(2 + _custom.size());
for (auto i = 0; i != kEmojiSectionCount; ++i) { result.emplace_back(EmojiSectionSetId(Ui::Emoji::Section::Recent));
result.emplace_back(EmojiSectionSetId(static_cast<Section>(i))); result.emplace_back(EmojiSectionSetId(Ui::Emoji::Section::People));
}
for (const auto &custom : _custom) { for (const auto &custom : _custom) {
const auto set = custom.set; const auto set = custom.set;
const auto s = custom.list[0].document; const auto s = custom.list[0].document;

View file

@ -41,6 +41,14 @@ uint64 EmojiSectionSetId(EmojiSection section) {
return kEmojiSectionSetIdBase + static_cast<uint64>(section); return kEmojiSectionSetIdBase + static_cast<uint64>(section);
} }
uint64 RecentEmojiSectionSetId() {
return EmojiSectionSetId(EmojiSection::Recent);
}
uint64 FirstEmojiSectionSetId() {
return EmojiSectionSetId(EmojiSection::People);
}
std::optional<EmojiSection> SetIdEmojiSection(uint64 id) { std::optional<EmojiSection> SetIdEmojiSection(uint64 id) {
const auto base = EmojiSectionSetId(EmojiSection::Recent); const auto base = EmojiSectionSetId(EmojiSection::Recent);
if (id < base) { if (id < base) {
@ -94,7 +102,10 @@ StickersListFooter::StickersListFooter(Descriptor &&descriptor)
, _settingsButtonVisible(descriptor.settingsButtonVisible) , _settingsButtonVisible(descriptor.settingsButtonVisible)
, _iconsAnimation([=](crl::time now) { , _iconsAnimation([=](crl::time now) {
return iconsAnimationCallback(now); return iconsAnimationCallback(now);
}) { })
, _selectionBg(st::roundRadiusSmall, st::windowBgRipple)
, _emojiIconWidth(st::stickerIconWidth)
, _barSelection(descriptor.barSelection) {
setMouseTracking(true); setMouseTracking(true);
_iconsLeft = st::emojiCategorySkip + (_searchButtonVisible _iconsLeft = st::emojiCategorySkip + (_searchButtonVisible
@ -152,6 +163,7 @@ void StickersListFooter::clearHeavyData() {
if (!info.visible) { if (!info.visible) {
icon.savedFrame = QPixmap(); icon.savedFrame = QPixmap();
} }
return true;
}); });
} }
@ -224,39 +236,54 @@ void StickersListFooter::returnFocus() {
} }
void StickersListFooter::enumerateVisibleIcons( void StickersListFooter::enumerateVisibleIcons(
Fn<void(const IconInfo &)> callback) { Fn<void(const IconInfo &)> callback) const {
auto iconsX = qRound(_iconsX.current()); enumerateIcons([&](const IconInfo &info) {
auto x = _iconsLeft - (iconsX % st::stickerIconWidth); if (info.visible) {
auto first = floorclamp(iconsX, st::stickerIconWidth, 0, _icons.size()); callback(info);
auto last = ceilclamp( } else if (info.left > 0) {
iconsX + width(), return false;
st::stickerIconWidth, }
0, return true;
_icons.size()); });
for (auto index = first; index != last; ++index) {
callback({ .index = index, .left = x, .visible = true });
x += st::stickerIconWidth;
}
} }
void StickersListFooter::enumerateIcons( void StickersListFooter::enumerateIcons(
Fn<void(const IconInfo &)> callback) { Fn<bool(const IconInfo &)> callback) const {
auto iconsX = qRound(_iconsX.current()); auto iconsX = int(base::SafeRound(_iconsX.current()));
auto x = _iconsLeft - (iconsX % st::stickerIconWidth); auto left = _iconsLeft - iconsX;
auto first = floorclamp(iconsX, st::stickerIconWidth, 0, _icons.size()); const auto emojiId = FirstEmojiSectionSetId();
auto last = ceilclamp( const auto right = width();
iconsX + width(),
st::stickerIconWidth,
0,
_icons.size());
x -= first * st::stickerIconWidth;
for (auto i = 0, count = int(_icons.size()); i != count; ++i) { for (auto i = 0, count = int(_icons.size()); i != count; ++i) {
const auto visible = (i >= first && i < last); auto &icon = _icons[i];
callback({ .index = i, .left = x, .visible = visible }); const auto width = (icon.setId == emojiId)
x += st::stickerIconWidth; ? _emojiIconWidthAnimation.value(_emojiIconWidth)
: st::stickerIconWidth;
const auto visible = (left + width > 0 && left < right);
const auto result = callback({
.index = i,
.left = left,
.width = int(base::SafeRound(width)),
.visible = visible,
});
if (!result) {
break;
}
left += width;
} }
} }
auto StickersListFooter::iconInfo(int index) const -> IconInfo {
auto result = IconInfo();
enumerateIcons([&](const IconInfo &info) {
if (info.index == index) {
result = info;
return false;
}
return true;
});
return result;
}
void StickersListFooter::preloadImages() { void StickersListFooter::preloadImages() {
enumerateVisibleIcons([&](const IconInfo &info) { enumerateVisibleIcons([&](const IconInfo &info) {
const auto &icon = _icons[info.index]; const auto &icon = _icons[info.index];
@ -276,8 +303,13 @@ void StickersListFooter::validateSelectedIcon(
ValidateIconAnimations animations) { ValidateIconAnimations animations) {
_activeByScrollId = setId; _activeByScrollId = setId;
using EmojiSection = Ui::Emoji::Section;
auto favedIconIndex = -1; auto favedIconIndex = -1;
auto newSelected = -1; auto newSelected = -1;
const auto emojiSection = SetIdEmojiSection(setId);
const auto isEmojiSection = emojiSection.has_value()
&& (emojiSection != EmojiSection::Recent);
const auto firstEmojiSetId = FirstEmojiSectionSetId();
for (auto i = 0, l = int(_icons.size()); i != l; ++i) { for (auto i = 0, l = int(_icons.size()); i != l; ++i) {
if (_icons[i].setId == setId if (_icons[i].setId == setId
|| (_icons[i].setId == Data::Stickers::FavedSetId || (_icons[i].setId == Data::Stickers::FavedSetId
@ -286,6 +318,8 @@ void StickersListFooter::validateSelectedIcon(
break; break;
} else if (_icons[i].setId == Data::Stickers::FavedSetId) { } else if (_icons[i].setId == Data::Stickers::FavedSetId) {
favedIconIndex = i; favedIconIndex = i;
} else if (isEmojiSection && _icons[i].setId == firstEmojiSetId) {
newSelected = i;
} }
} }
setSelectedIcon( setSelectedIcon(
@ -295,6 +329,39 @@ void StickersListFooter::validateSelectedIcon(
animations); animations);
} }
void StickersListFooter::updateEmojiSectionWidth() {
_emojiIconExpanded = (_iconSel >= 0)
&& (_iconSel < _icons.size())
&& (_icons[_iconSel].setId == FirstEmojiSectionSetId());
const auto desired = _emojiIconExpanded
? (9 * st::stickerIconWidth / 2)
: st::stickerIconWidth;
if (_emojiIconWidth == desired) {
return;
}
_emojiIconWidthAnimation.start(
[=] { updateEmojiWidthCallback(); },
_emojiIconWidth,
desired,
st::stickerIconMove);
_emojiIconWidth = desired;
}
void StickersListFooter::updateEmojiWidthCallback() {
update();
const auto info = iconInfo(_iconSel);
if (_iconSelX.from() != _iconSelX.to()) {
_iconSelX = anim::value(_iconSelX.from(), info.left);
} else {
_iconSelX = anim::value(info.left, info.left);
}
if (_iconSelWidth.from() != _iconSelWidth.to()) {
_iconSelWidth = anim::value(_iconSelWidth.from(), info.width);
} else {
_iconSelWidth = anim::value(info.width, info.width);
}
}
void StickersListFooter::setSelectedIcon( void StickersListFooter::setSelectedIcon(
int newSelected, int newSelected,
ValidateIconAnimations animations) { ValidateIconAnimations animations) {
@ -302,16 +369,18 @@ void StickersListFooter::setSelectedIcon(
return; return;
} }
_iconSel = newSelected; _iconSel = newSelected;
auto iconSelXFinal = _iconSel * st::stickerIconWidth; const auto info = iconInfo(_iconSel);
updateEmojiSectionWidth();
if (animations == ValidateIconAnimations::Full) { if (animations == ValidateIconAnimations::Full) {
_iconSelX.start(iconSelXFinal); _iconSelX.start(info.left);
_iconSelWidth.start(info.width);
} else { } else {
_iconSelX = anim::value(iconSelXFinal, iconSelXFinal); _iconSelX = anim::value(info.left, info.left);
_iconSelWidth = anim::value(info.width, info.width);
} }
auto iconsCountForCentering = (2 * _iconSel + 1); const auto relativeLeft = info.left - _iconsLeft;
auto iconsWidthForCentering = iconsCountForCentering const auto iconsWidthForCentering = 2 * relativeLeft + info.width;
* st::stickerIconWidth; const auto iconsXFinal = std::clamp(
auto iconsXFinal = std::clamp(
(_iconsLeft + iconsWidthForCentering + _iconsRight - width()) / 2, (_iconsLeft + iconsWidthForCentering + _iconsRight - width()) / 2,
0, 0,
_iconsMax); _iconsMax);
@ -333,6 +402,7 @@ void StickersListFooter::processHideFinished() {
_iconsAnimation.stop(); _iconsAnimation.stop();
_iconsX.finish(); _iconsX.finish();
_iconSelX.finish(); _iconSelX.finish();
_iconSelWidth.finish();
_horizontal = false; _horizontal = false;
} }
@ -371,6 +441,10 @@ void StickersListFooter::paintEvent(QPaintEvent *e) {
} }
p.setClipRect(clip); p.setClipRect(clip);
if (!_barSelection) {
paintSelectionBg(p);
}
const auto now = crl::now(); const auto now = crl::now();
const auto paused = _controller->isGifPausedAtLeastFor( const auto paused = _controller->isGifPausedAtLeastFor(
Window::GifPauseReason::SavedGifs); Window::GifPauseReason::SavedGifs);
@ -378,20 +452,39 @@ void StickersListFooter::paintEvent(QPaintEvent *e) {
paintSetIcon(p, info, now, paused); paintSetIcon(p, info, now, paused);
}); });
paintSelectionBar(p); if (_barSelection) {
paintSelectionBar(p);
}
paintLeftRightFading(p); paintLeftRightFading(p);
} }
void StickersListFooter::paintSelectionBar(Painter &p) const { void StickersListFooter::paintSelectionBg(Painter &p) const {
auto selxrel = _iconsLeft + qRound(_iconSelX.current()); auto selxrel = qRound(_iconSelX.current());
auto selx = selxrel - qRound(_iconsX.current()); auto selx = selxrel - qRound(_iconsX.current());
const auto selw = qRound(_iconSelWidth.current());
if (rtl()) { if (rtl()) {
selx = width() - selx - st::stickerIconWidth; selx = width() - selx - selw;
}
const auto skip = st::emojiIconSelectSkip;
const auto sely = _iconsTop
+ (st::emojiFooterHeight - st::stickerIconWidth) / 2;
const auto selh = st::stickerIconWidth;
const auto rect = QRect(selx, sely, selw, selh);
const auto fill = rect.marginsRemoved({ skip, skip, skip, skip });
_selectionBg.paint(p, fill);
}
void StickersListFooter::paintSelectionBar(Painter &p) const {
auto selxrel = qRound(_iconSelX.current());
auto selx = selxrel - qRound(_iconsX.current());
const auto selw = qRound(_iconSelWidth.current());
if (rtl()) {
selx = width() - selx - selw;
} }
p.fillRect( p.fillRect(
selx, selx,
_iconsTop + st::emojiFooterHeight - st::stickerIconPadding, _iconsTop + st::emojiFooterHeight - st::stickerIconPadding,
st::stickerIconWidth, selw,
st::stickerIconSel, st::stickerIconSel,
st::stickerIconSelColor); st::stickerIconSelColor);
} }
@ -509,9 +602,9 @@ void StickersListFooter::mouseReleaseEvent(QMouseEvent *e) {
updateSelected(); updateSelected();
if (wasDown == _iconOver) { if (wasDown == _iconOver) {
if (const auto index = std::get_if<int>(&_iconOver)) { if (const auto index = std::get_if<int>(&_iconOver)) {
_iconSelX = anim::value( const auto info = iconInfo(*index);
*index * st::stickerIconWidth, _iconSelX = anim::value(info.left, info.left);
*index * st::stickerIconWidth); _iconSelWidth = anim::value(info.width, info.width);
_setChosen.fire_copy(_icons[*index].setId); _setChosen.fire_copy(_icons[*index].setId);
} }
} }
@ -585,7 +678,7 @@ void StickersListFooter::clipCallback(
enumerateIcons([&](const IconInfo &info) { enumerateIcons([&](const IconInfo &info) {
auto &icon = _icons[info.index]; auto &icon = _icons[info.index];
if (icon.setId != setId || !icon.webm) { if (icon.setId != setId || !icon.webm) {
return; return true;
} else if (icon.webm->state() == State::Error) { } else if (icon.webm->state() == State::Error) {
icon.webm.setBad(); icon.webm.setBad();
} else if (!info.visible) { } else if (!info.visible) {
@ -597,6 +690,7 @@ void StickersListFooter::clipCallback(
}); });
} }
updateSetIconAt(info.left); updateSetIconAt(info.left);
return true;
}); });
} break; } break;
@ -636,10 +730,14 @@ void StickersListFooter::updateSelected() {
&& y < _iconsTop + st::emojiFooterHeight && y < _iconsTop + st::emojiFooterHeight
&& x >= _iconsLeft && x >= _iconsLeft
&& x < width() - _iconsRight) { && x < width() - _iconsRight) {
x += qRound(_iconsX.current()) - _iconsLeft; x += qRound(_iconsX.current());
if (x < _icons.size() * st::stickerIconWidth) { enumerateIcons([&](const IconInfo &info) {
newOver = qFloor(x / st::stickerIconWidth); if (x >= info.left && x < info.left + info.width) {
} newOver = info.index;
return false;
}
return true;
});
} }
} }
if (newOver != _iconOver) { if (newOver != _iconOver) {
@ -698,10 +796,12 @@ void StickersListFooter::refreshIconsGeometry(
_iconOver = _iconDown = SpecialOver::None; _iconOver = _iconDown = SpecialOver::None;
_iconsX.finish(); _iconsX.finish();
_iconSelX.finish(); _iconSelX.finish();
_iconSelWidth.finish();
_iconsStartAnim = 0; _iconsStartAnim = 0;
_iconsAnimation.stop(); _iconsAnimation.stop();
const auto &last = iconInfo(_icons.size() - 1);
_iconsMax = std::max( _iconsMax = std::max(
_iconsLeft + int(_icons.size()) * st::stickerIconWidth + _iconsRight - width(), last.left + last.width + _iconsRight - width(),
0); 0);
if (_iconsX.current() > _iconsMax) { if (_iconsX.current() > _iconsMax) {
_iconsX = anim::value(_iconsMax, _iconsMax); _iconsX = anim::value(_iconsMax, _iconsMax);
@ -880,35 +980,58 @@ void StickersListFooter::paintSetIcon(
_iconsTop + (st::emojiFooterHeight - size.height()) / 2, _iconsTop + (st::emojiFooterHeight - size.height()) / 2,
_premiumIcon); _premiumIcon);
} else { } else {
const auto paintedIcon = [&] { using Section = Ui::Emoji::Section;
if (icon.setId == Data::Stickers::FeaturedSetId) { const auto sectionIcon = [&](Section section) {
const auto session = &_controller->session(); switch (section) {
return session->data().stickers().featuredSetsUnreadCount() case Section::Recent: return &st::emojiRecent;
? &st::stickersTrendingUnread case Section::People: return &st::emojiPeople;
: &st::stickersTrending; case Section::Nature: return &st::emojiNature;
//} else if (setId == Stickers::FavedSetId) { case Section::Food: return &st::emojiFood;
// return &st::stickersFaved; case Section::Activity: return &st::emojiActivity;
} else if (const auto section = SetIdEmojiSection(icon.setId)) { case Section::Travel: return &st::emojiTravel;
using Section = Ui::Emoji::Section; case Section::Objects: return &st::emojiObjects;
switch (*section) { case Section::Symbols: return &st::emojiSymbols;
case Section::Recent: return &st::emojiRecent;
case Section::People: return &st::emojiPeople;
case Section::Nature: return &st::emojiNature;
case Section::Food: return &st::emojiFood;
case Section::Activity: return &st::emojiActivity;
case Section::Travel: return &st::emojiTravel;
case Section::Objects: return &st::emojiObjects;
case Section::Symbols: return &st::emojiSymbols;
}
Unexpected("Section in SetIdEmojiSection result.");
} }
return &st::emojiRecent; Unexpected("Section in SetIdEmojiSection result.");
}(); };
paintedIcon->paint( auto left = info.left;
p, const auto paintOne = [&](const style::icon *icon) {
info.left + (st::stickerIconWidth - paintedIcon->width()) / 2, icon->paint(
_iconsTop + (st::emojiFooterHeight - paintedIcon->height()) / 2, p,
width()); left + (st::stickerIconWidth - icon->width()) / 2,
_iconsTop + (st::emojiFooterHeight - icon->height()) / 2,
width());
};
if (_icons[info.index].setId == FirstEmojiSectionSetId()
&& info.width > st::stickerIconWidth) {
const auto skip = st::emojiIconSelectSkip;
p.save();
p.setClipRect(
left + skip,
_iconsTop,
info.width - 2 * skip,
st::emojiFooterHeight,
Qt::IntersectClip);
for (auto i = int(Section::People); i <= int(Section::Symbols); ++i) {
paintOne(sectionIcon(Section(i)));
left += st::stickerIconWidth;
}
p.restore();
} else {
paintOne([&] {
if (icon.setId == Data::Stickers::FeaturedSetId) {
const auto session = &_controller->session();
return session->data().stickers().featuredSetsUnreadCount()
? &st::stickersTrendingUnread
: &st::stickersTrending;
//} else if (setId == Stickers::FavedSetId) {
// return &st::stickersFaved;
} else if (const auto section = SetIdEmojiSection(icon.setId)) {
return sectionIcon(*section);
}
return &st::emojiRecent;
}());
}
} }
} }
@ -917,14 +1040,17 @@ bool StickersListFooter::iconsAnimationCallback(crl::time now) {
now += st::stickerIconMove; now += st::stickerIconMove;
} }
if (_iconsStartAnim) { if (_iconsStartAnim) {
const auto dt = (now - _iconsStartAnim) / float64(st::stickerIconMove); const auto dt = (now - _iconsStartAnim)
/ float64(st::stickerIconMove);
if (dt >= 1.) { if (dt >= 1.) {
_iconsStartAnim = 0; _iconsStartAnim = 0;
_iconsX.finish(); _iconsX.finish();
_iconSelX.finish(); _iconSelX.finish();
_iconSelWidth.finish();
} else { } else {
_iconsX.update(dt, anim::linear); _iconsX.update(dt, anim::linear);
_iconSelX.update(dt, anim::linear); _iconSelX.update(dt, anim::linear);
_iconSelWidth.update(dt, anim::linear);
} }
} }

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/clip/media_clip_reader.h" #include "media/clip/media_clip_reader.h"
#include "chat_helpers/tabbed_selector.h" #include "chat_helpers/tabbed_selector.h"
#include "ui/round_rect.h"
namespace Ui { namespace Ui {
class InputField; class InputField;
@ -40,6 +41,8 @@ enum class ValidateIconAnimations {
}; };
[[nodiscard]] uint64 EmojiSectionSetId(Ui::Emoji::Section section); [[nodiscard]] uint64 EmojiSectionSetId(Ui::Emoji::Section section);
[[nodiscard]] uint64 RecentEmojiSectionSetId();
[[nodiscard]] uint64 FirstEmojiSectionSetId();
[[nodiscard]] std::optional<Ui::Emoji::Section> SetIdEmojiSection(uint64 id); [[nodiscard]] std::optional<Ui::Emoji::Section> SetIdEmojiSection(uint64 id);
struct StickerIcon { struct StickerIcon {
@ -77,6 +80,7 @@ public:
not_null<RpWidget*> parent; not_null<RpWidget*> parent;
bool searchButtonVisible = false; bool searchButtonVisible = false;
bool settingsButtonVisible = false; bool settingsButtonVisible = false;
bool barSelection = false;
}; };
explicit StickersListFooter(Descriptor &&descriptor); explicit StickersListFooter(Descriptor &&descriptor);
@ -127,11 +131,13 @@ private:
struct IconInfo { struct IconInfo {
int index = 0; int index = 0;
int left = 0; int left = 0;
int width = 0;
bool visible = false; bool visible = false;
}; };
void enumerateVisibleIcons(Fn<void(const IconInfo &)> callback); void enumerateVisibleIcons(Fn<void(const IconInfo &)> callback) const;
void enumerateIcons(Fn<void(const IconInfo &)> callback); void enumerateIcons(Fn<bool(const IconInfo &)> callback) const;
[[nodiscard]] IconInfo iconInfo(int index) const;
[[nodiscard]] std::shared_ptr<Lottie::FrameRenderer> getLottieRenderer(); [[nodiscard]] std::shared_ptr<Lottie::FrameRenderer> getLottieRenderer();
bool iconsAnimationCallback(crl::time now); bool iconsAnimationCallback(crl::time now);
@ -154,10 +160,14 @@ private:
const IconInfo &info, const IconInfo &info,
crl::time now, crl::time now,
bool paused) const; bool paused) const;
void paintSelectionBg(Painter &p) const;
void paintSelectionBar(Painter &p) const; void paintSelectionBar(Painter &p) const;
void paintLeftRightFading(Painter &p) const; void paintLeftRightFading(Painter &p) const;
void validatePremiumIcon() const; void validatePremiumIcon() const;
void updateEmojiSectionWidth();
void updateEmojiWidthCallback();
void initSearch(); void initSearch();
void toggleSearch(bool visible); void toggleSearch(bool visible);
void resizeSearchControls(); void resizeSearchControls();
@ -189,8 +199,15 @@ private:
int _iconsMax = 0; int _iconsMax = 0;
anim::value _iconsX; anim::value _iconsX;
anim::value _iconSelX; anim::value _iconSelX;
anim::value _iconSelWidth;
crl::time _iconsStartAnim = 0; crl::time _iconsStartAnim = 0;
Ui::RoundRect _selectionBg;
Ui::Animations::Simple _emojiIconWidthAnimation;
int _emojiIconWidth = 0;
bool _emojiIconExpanded = false;
bool _barSelection = false;
bool _horizontal = false; bool _horizontal = false;
bool _searchShown = false; bool _searchShown = false;

View file

@ -252,6 +252,7 @@ object_ptr<TabbedSelector::InnerFooter> StickersListWidget::createFooter() {
.parent = this, .parent = this,
.searchButtonVisible = !_isMasks, .searchButtonVisible = !_isMasks,
.settingsButtonVisible = true, .settingsButtonVisible = true,
.barSelection = true,
}); });
_footer = result; _footer = result;
@ -303,7 +304,7 @@ void StickersListWidget::visibleTopBottomUpdated(
} }
if (_footer) { if (_footer) {
_footer->validateSelectedIcon( _footer->validateSelectedIcon(
currentSet(top), currentSet(visibleTop),
ValidateIconAnimations::Full); ValidateIconAnimations::Full);
} }
} }