diff --git a/Telegram/Resources/icons/emoji/emoji_skin.png b/Telegram/Resources/icons/emoji/emoji_skin.png new file mode 100644 index 000000000..776617ebe Binary files /dev/null and b/Telegram/Resources/icons/emoji/emoji_skin.png differ diff --git a/Telegram/Resources/icons/emoji/emoji_skin@2x.png b/Telegram/Resources/icons/emoji/emoji_skin@2x.png new file mode 100644 index 000000000..a53f5c18e Binary files /dev/null and b/Telegram/Resources/icons/emoji/emoji_skin@2x.png differ diff --git a/Telegram/Resources/icons/emoji/emoji_skin@3x.png b/Telegram/Resources/icons/emoji/emoji_skin@3x.png new file mode 100644 index 000000000..fd756d0a9 Binary files /dev/null and b/Telegram/Resources/icons/emoji/emoji_skin@3x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 2703e2c71..4253a9792 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1807,6 +1807,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_emoji_set_active" = "Current set"; "lng_emoji_set_download" = "Download {size}"; "lng_emoji_set_loading" = "{percent}, {progress}"; +"lng_emoji_color_all" = "Choose color for all emoji"; "lng_recent_stickers" = "Frequently used"; "lng_faved_stickers_add" = "Add to Favorites"; diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 75fbfbbb0..fb85201eb 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -118,6 +118,8 @@ EmojiPan { tabs: SettingsSlider; search: TabbedSearch; searchMargin: margins; + colorAll: IconButton; + colorAllLabel: FlatLabel; removeSet: IconButton; boxLabel: FlatLabel; icons: ComposeIcons; @@ -448,6 +450,7 @@ inlineResultsMaxHeight: 640px; emojiPanHeaderFont: semiboldFont; emojiPanRemoveSkip: 10px; emojiPanRemoveTop: 10px; +emojiPanColorAllSkip: 9px; emojiColorsPadding: 5px; emojiColorsSep: 1px; @@ -490,6 +493,25 @@ stickerIconMove: 400; stickerPreviewDuration: 150; stickerPreviewMin: 0.1; +emojiPanColorAll: IconButton(stickerPanRemoveSet) { + width: 24px; + height: 24px; + rippleAreaSize: 24px; + icon: icon {{ "emoji/emoji_skin", smallCloseIconFg }}; + iconOver: icon {{ "emoji/emoji_skin", smallCloseIconFgOver }}; +} +emojiPanColorAllLabel: FlatLabel(defaultFlatLabel) { + textFg: windowSubTextFg; + align: align(top); + minWidth: 40px; + style: TextStyle(defaultTextStyle) { + font: font(12px); + linkFont: font(12px); + linkFontOver: font(12px); + } +} +emojiPanColorAllPadding: margins(10px, 6px, 10px, -1px); + stickerGroupCategorySize: 28px; stickerGroupCategoryAbout: defaultTextStyle; stickerGroupCategoryAddMargin: margins(0px, 10px, 0px, 5px); @@ -626,6 +648,8 @@ defaultEmojiPan: EmojiPan { tabs: emojiTabs; search: defaultTabbedSearch; searchMargin: margins(1px, 11px, 2px, 5px); + colorAll: emojiPanColorAll; + colorAllLabel: emojiPanColorAllLabel; removeSet: stickerPanRemoveSet; boxLabel: boxLabel; icons: defaultComposeIcons; diff --git a/Telegram/SourceFiles/chat_helpers/emoji_keywords.cpp b/Telegram/SourceFiles/chat_helpers/emoji_keywords.cpp index 684027624..500de20b6 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_keywords.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_keywords.cpp @@ -672,17 +672,9 @@ std::vector EmojiKeywords::PrioritizeRecent( } std::vector EmojiKeywords::ApplyVariants(std::vector list) { + auto &settings = Core::App().settings(); for (auto &item : list) { - item.emoji = [&] { - const auto result = item.emoji; - const auto &variants = Core::App().settings().emojiVariants(); - const auto i = result->hasVariants() - ? variants.find(result->nonColoredId()) - : end(variants); - return (i != end(variants)) - ? result->variant(i->second) - : result; - }(); + item.emoji = settings.lookupEmojiVariant(item.emoji); } return list; } diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index f41482c19..11fa6b234 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -58,7 +58,7 @@ class EmojiColorPicker final : public Ui::RpWidget { public: EmojiColorPicker(QWidget *parent, const style::EmojiPan &st); - void showEmoji(EmojiPtr emoji); + void showEmoji(EmojiPtr emoji, bool allLabel = false); void clearSelection(); void handleMouseMove(QPoint globalPos); @@ -79,8 +79,10 @@ protected: void mouseMoveEvent(QMouseEvent *e) override; private: + void createAllLabel(); void animationCallback(); void updateSize(); + [[nodiscard]] int topColorAllSkip() const; void drawVariant(QPainter &p, int variant); @@ -106,6 +108,8 @@ private: QPixmap _cache; Ui::Animations::Simple _a_opacity; + std::unique_ptr _allLabel; + rpl::event_stream _chosen; rpl::event_stream<> _hidden; @@ -131,10 +135,15 @@ EmojiColorPicker::EmojiColorPicker( setMouseTracking(true); } -void EmojiColorPicker::showEmoji(EmojiPtr emoji) { +void EmojiColorPicker::showEmoji(EmojiPtr emoji, bool allLabel) { if (!emoji || !emoji->hasVariants()) { return; } + if (!allLabel) { + _allLabel = nullptr; + } else if (!_allLabel) { + createAllLabel(); + } _ignoreShow = false; _variants.resize(emoji->variantsCount() + 1); @@ -144,10 +153,21 @@ void EmojiColorPicker::showEmoji(EmojiPtr emoji) { updateSize(); - if (!_cache.isNull()) _cache = QPixmap(); + if (!_cache.isNull()) { + _cache = QPixmap(); + } showAnimated(); } +void EmojiColorPicker::createAllLabel() { + _allLabel = std::make_unique( + this, + tr::lng_emoji_color_all(), + _st.colorAllLabel); + _allLabel->show(); + _allLabel->setAttribute(Qt::WA_TransparentForMouseEvents); +} + void EmojiColorPicker::updateSize() { auto width = st::emojiPanMargins.left() + _singleSize.width() * _variants.size() @@ -158,6 +178,17 @@ void EmojiColorPicker::updateSize() { + 2 * st::emojiColorsPadding + _singleSize.height() + st::emojiPanMargins.bottom(); + if (_allLabel) { + _allLabel->resizeToWidth(width + - st::emojiPanMargins.left() + - st::emojiPanMargins.right() + - st::emojiPanColorAllPadding.left() + - st::emojiPanColorAllPadding.right()); + _allLabel->move( + st::emojiPanMargins.left() + st::emojiPanColorAllPadding.left(), + st::emojiPanMargins.top() + st::emojiPanColorAllPadding.top()); + height += topColorAllSkip(); + } resize(width, height); update(); updateSelected(); @@ -186,11 +217,15 @@ void EmojiColorPicker::paintEvent(QPaintEvent *e) { Ui::Shadow::paint(p, inner, width(), _st.showAnimation.shadow); _backgroundRect.paint(p, inner); + const auto skip = topColorAllSkip(); auto x = st::emojiPanMargins.left() + 2 * st::emojiColorsPadding + _singleSize.width(); if (rtl()) x = width() - x - st::emojiColorsSep; - p.fillRect(x, st::emojiPanMargins.top() + st::emojiColorsPadding, st::emojiColorsSep, inner.height() - st::emojiColorsPadding * 2, st::emojiColorsSepColor); + p.fillRect(x, st::emojiPanMargins.top() + skip + st::emojiColorsPadding, st::emojiColorsSep, inner.height() - st::emojiColorsPadding * 2 - skip, st::emojiColorsSepColor); - if (_variants.isEmpty()) return; + if (_variants.isEmpty()) { + return; + } + p.translate(0, skip); for (auto i = 0, count = int(_variants.size()); i != count; ++i) { drawVariant(p, i); } @@ -248,6 +283,9 @@ void EmojiColorPicker::animationCallback() { update(); if (!_a_opacity.animating()) { _cache = QPixmap(); + if (_allLabel) { + _allLabel->show(); + } if (_hiding) { hide(); _hidden.fire({}); @@ -276,10 +314,16 @@ rpl::producer<> EmojiColorPicker::hidden() const { void EmojiColorPicker::hideAnimated() { if (_cache.isNull()) { + if (_allLabel) { + _allLabel->show(); + } _cache = Ui::GrabWidget(this); clearSelection(); } _hiding = true; + if (_allLabel) { + _allLabel->hide(); + } _a_opacity.start([this] { animationCallback(); }, 1., 0., st::emojiPanDuration); } @@ -291,10 +335,16 @@ void EmojiColorPicker::showAnimated() { } _hiding = false; if (_cache.isNull()) { + if (_allLabel) { + _allLabel->show(); + } _cache = Ui::GrabWidget(this); clearSelection(); } show(); + if (_allLabel) { + _allLabel->hide(); + } _a_opacity.start([this] { animationCallback(); }, 0., 1., st::emojiPanDuration); } @@ -304,10 +354,18 @@ void EmojiColorPicker::clearSelection() { _lastMousePos = mapToGlobal(QPoint(-10, -10)); } +int EmojiColorPicker::topColorAllSkip() const { + return _allLabel + ? (st::emojiPanColorAllPadding.top() + + _allLabel->height() + + st::emojiPanColorAllPadding.bottom()) + : 0; +} + void EmojiColorPicker::updateSelected() { auto newSelected = -1; auto p = mapFromGlobal(_lastMousePos); - auto sx = rtl() ? (width() - p.x()) : p.x(), y = p.y() - st::emojiPanMargins.top() - st::emojiColorsPadding; + auto sx = rtl() ? (width() - p.x()) : p.x(), y = p.y() - st::emojiPanMargins.top() - topColorAllSkip() - st::emojiColorsPadding; if (y >= 0 && y < _singleSize.height()) { auto x = sx - st::emojiPanMargins.left() - st::emojiColorsPadding; if (x >= 0 && x < _singleSize.width()) { @@ -327,7 +385,8 @@ void EmojiColorPicker::setSelected(int newSelected) { if (_selected == newSelected) { return; } - auto updateSelectedRect = [this] { + const auto skip = topColorAllSkip(); + const auto updateSelectedRect = [&] { if (_selected < 0) return; auto addedSkip = (_selected > 0) ? (2 * st::emojiColorsPadding + st::emojiColorsSep) @@ -338,7 +397,7 @@ void EmojiColorPicker::setSelected(int newSelected) { + addedSkip; rtlupdate( left, - st::emojiPanMargins.top() + st::emojiColorsPadding, + st::emojiPanMargins.top() + st::emojiColorsPadding + skip, _singleSize.width(), _singleSize.height()); }; @@ -851,6 +910,31 @@ void EmojiListWidget::setSingleSize(QSize size) { _picker->setSingleSize(_singleSize); } +void EmojiListWidget::setColorAllForceRippled(bool force) { + _colorAllRippleForced = force; + if (_colorAllRippleForced) { + _colorAllRippleForcedLifetime = style::PaletteChanged( + ) | rpl::filter([=] { + return _colorAllRipple != nullptr; + }) | rpl::start_with_next([=] { + _colorAllRipple->forceRepaint(); + }); + if (!_colorAllRipple) { + _colorAllRipple = createButtonRipple(int(Section::People)); + } + if (_colorAllRipple->empty()) { + _colorAllRipple->addFading(); + } else { + _colorAllRipple->lastUnstop(); + } + } else { + if (_colorAllRipple) { + _colorAllRipple->lastStop(); + } + _colorAllRippleForcedLifetime.destroy(); + } +} + int EmojiListWidget::countDesiredHeight(int newWidth) { const auto fullWidth = st().margin.left() + newWidth @@ -897,14 +981,9 @@ void EmojiListWidget::ensureLoaded(int section) { _emoji[section] = Ui::Emoji::GetSection(static_cast
(section)); _counts[section] = _emoji[section].size(); - const auto &variants = Core::App().settings().emojiVariants(); + const auto &settings = Core::App().settings(); for (auto &emoji : _emoji[section]) { - if (emoji->hasVariants()) { - const auto j = variants.find(emoji->nonColoredId()); - if (j != end(variants)) { - emoji = emoji->variant(j->second); - } - } + emoji = settings.lookupEmojiVariant(emoji); } } @@ -1366,8 +1445,7 @@ void EmojiListWidget::mousePressEvent(QMouseEvent *e) { if (emoji && emoji->hasVariants()) { _pickerSelected = _selected; setCursor(style::cur_default); - const auto &variants = Core::App().settings().emojiVariants(); - if (!variants.contains(emoji->nonColoredId())) { + if (!Core::App().settings().hasChosenEmojiVariant(emoji)) { showPicker(); } else { _showPickerTimer.callOnce(500); @@ -1385,12 +1463,11 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) { return _picker->handleMouseRelease(QCursor::pos()); } else if (const auto over = std::get_if(&_pickerSelected)) { const auto emoji = lookupOverEmoji(over); - if (emoji && emoji->hasVariants()) { - const auto &variants = Core::App().settings().emojiVariants(); - if (variants.contains(emoji->nonColoredId())) { - _picker->hideAnimated(); - _pickerSelected = v::null; - } + if (emoji + && emoji->hasVariants() + && Core::App().settings().hasChosenEmojiVariant(emoji)) { + _picker->hideAnimated(); + _pickerSelected = v::null; } } } @@ -1429,11 +1506,15 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) { && set->section < _staticCount + _custom.size()); displaySet(_custom[set->section - _staticCount].id); } else if (auto button = std::get_if(&pressed)) { - Assert(button->section >= _staticCount - && button->section < _staticCount + _custom.size()); - const auto id = _custom[button->section - _staticCount].id; + Assert(hasButton(button->section)); + const auto id = hasColorButton(button->section) + ? 0 + : _custom[button->section - _staticCount].id; const auto usage = ChatHelpers::WindowUsage::PremiumPromo; - if (hasRemoveButton(button->section)) { + if (hasColorButton(button->section)) { + _pickerSelected = pressed; + showPicker(); + } else if (hasRemoveButton(button->section)) { removeSet(id); } else if (hasAddButton(button->section)) { _localSetsManager->install(id); @@ -1496,23 +1577,35 @@ void EmojiListWidget::showPicker() { if (v::is_null(_pickerSelected)) { return; } - - const auto over = std::get_if(&_pickerSelected); - const auto emoji = lookupOverEmoji(over); - if (emoji && emoji->hasVariants()) { - _picker->showEmoji(emoji); - - auto y = emojiRect(over->section, over->index).y(); + const auto showAt = [&](float64 xCoef, int y, int height) { y -= _picker->height() - st::emojiPanRadius + getVisibleTop(); if (y < st().header) { - y += _picker->height() - st::emojiPanRadius + _singleSize.height() - st::emojiPanRadius; + y += _picker->height() + height; } auto xmax = width() - _picker->width(); - auto coef = float64(over->index % _columnCount) / float64(_columnCount - 1); - if (rtl()) coef = 1. - coef; - _picker->move(qRound(xmax * coef), y); + if (rtl()) xCoef = 1. - xCoef; + _picker->move(qRound(xmax * xCoef), y); disableScroll(true); + }; + if (const auto button = std::get_if(&_pickerSelected)) { + const auto hand = QString::fromUtf8("\xF0\x9F\x91\x8B"); + const auto emoji = Ui::Emoji::Find(hand); + Assert(emoji != nullptr && emoji->hasVariants()); + _picker->showEmoji(emoji, true); + setColorAllForceRippled(true); + const auto rect = buttonRect(button->section); + showAt(1., rect.y(), rect.height() - 2 * st::emojiPanRadius); + } else if (const auto over = std::get_if(&_pickerSelected)) { + const auto emoji = lookupOverEmoji(over); + if (emoji && emoji->hasVariants()) { + _picker->showEmoji(emoji); + + const auto coef = float64(over->index % _columnCount) + / float64(_columnCount - 1); + const auto h = _singleSize.height() - 2 * st::emojiPanRadius; + showAt(coef, emojiRect(over->section, over->index).y(), h); + } } } @@ -1520,11 +1613,34 @@ void EmojiListWidget::pickerHidden() { _pickerSelected = v::null; update(); disableScroll(false); + setColorAllForceRippled(false); _lastMousePos = QCursor::pos(); updateSelected(); } +bool EmojiListWidget::hasColorButton(int index) const { + return (_staticCount > int(Section::People)) + && (index == int(Section::People)); +} + +QRect EmojiListWidget::colorButtonRect(int index) const { + return colorButtonRect(sectionInfo(index)); +} + +QRect EmojiListWidget::colorButtonRect(const SectionInfo &info) const { + if (_mode != Mode::Full) { + return QRect(); + } + const auto &colorSt = st().colorAll; + const auto buttonw = colorSt.rippleAreaPosition.x() + + colorSt.rippleAreaSize; + const auto buttonh = colorSt.height; + const auto buttonx = emojiRight() - st::emojiPanColorAllSkip - buttonw; + const auto buttony = info.top + st::emojiPanRemoveTop; + return QRect(buttonx, buttony, buttonw, buttonh); +} + bool EmojiListWidget::hasRemoveButton(int index) const { if (index < _staticCount || index >= _staticCount + _custom.size()) { @@ -1581,15 +1697,18 @@ QRect EmojiListWidget::unlockButtonRect(int index) const { } bool EmojiListWidget::hasButton(int index) const { - if (index < _staticCount - || index >= _staticCount + _custom.size()) { - return false; + if (hasColorButton(index) + || (index >= _staticCount + && index < _staticCount + _custom.size())) { + return true; } - return true; + return false; } QRect EmojiListWidget::buttonRect(int index) const { - return hasRemoveButton(index) + return hasColorButton(index) + ? colorButtonRect(index) + : hasRemoveButton(index) ? removeButtonRect(index) : hasAddButton(index) ? addButtonRect(index) @@ -1637,19 +1756,33 @@ QRect EmojiListWidget::emojiRect(int section, int index) const { } void EmojiListWidget::colorChosen(EmojiChosen data) { + Expects(data.emoji != nullptr && data.emoji->hasVariants()); + const auto emoji = data.emoji; - if (emoji->hasVariants()) { - Core::App().settings().saveEmojiVariant(emoji); + auto &settings = Core::App().settings(); + if (const auto button = std::get_if(&_pickerSelected)) { + settings.saveAllEmojiVariants(emoji); + for (auto section = int(Section::People) + ; section < _staticCount + ; ++section) { + for (auto &emoji : _emoji[section]) { + emoji = settings.lookupEmojiVariant(emoji); + } + } + update(); + } else { + settings.saveEmojiVariant(emoji); + + const auto over = std::get_if(&_pickerSelected); + if (over + && over->section > int(Section::Recent) + && over->section < _staticCount + && over->index < _emoji[over->section].size()) { + _emoji[over->section][over->index] = emoji; + rtlupdate(emojiRect(over->section, over->index)); + } + selectEmoji(data); } - const auto over = std::get_if(&_pickerSelected); - if (over - && over->section > int(Section::Recent) - && over->section < _staticCount - && over->index < _emoji[over->section].size()) { - _emoji[over->section][over->index] = emoji; - rtlupdate(emojiRect(over->section, over->index)); - } - selectEmoji(data); _picker->hideAnimated(); } @@ -1967,47 +2100,54 @@ int EmojiListWidget::paintButtonGetWidth( const SectionInfo &info, bool selected, QRect clip) const { - if (info.section < _staticCount - || info.section >= _staticCount + _custom.size()) { + if (!hasButton(info.section)) { return 0; } - auto &custom = _custom[info.section - _staticCount]; - if (hasRemoveButton(info.section)) { - const auto remove = removeButtonRect(info); - if (remove.isEmpty()) { + auto &ripple = (info.section >= _staticCount) + ? _custom[info.section - _staticCount].ripple + : _colorAllRipple; + const auto colorAll = hasColorButton(info.section); + if (colorAll || hasRemoveButton(info.section)) { + const auto rect = colorAll + ? colorButtonRect(info) + : removeButtonRect(info); + if (rect.isEmpty()) { return 0; - } else if (remove.intersects(clip)) { - const auto &removeSt = st().removeSet; - if (custom.ripple) { - custom.ripple->paint( + } else if (rect.intersects(clip)) { + const auto &bst = colorAll ? st().colorAll : st().removeSet; + if (colorAll && _colorAllRippleForced) { + selected = true; + } + if (ripple) { + ripple->paint( p, - remove.x() + removeSt.rippleAreaPosition.x(), - remove.y() + removeSt.rippleAreaPosition.y(), + rect.x() + bst.rippleAreaPosition.x(), + rect.y() + bst.rippleAreaPosition.y(), width()); - if (custom.ripple->empty()) { - custom.ripple.reset(); + if (ripple->empty()) { + ripple.reset(); } } - const auto &icon = selected ? removeSt.iconOver : removeSt.icon; + const auto &icon = selected ? bst.iconOver : bst.icon; icon.paint( p, - (remove.topLeft() + (rect.topLeft() + QPoint( - remove.width() - icon.width(), - remove.height() - icon.height()) / 2), + rect.width() - icon.width(), + rect.height() - icon.height()) / 2), width()); } - return emojiRight() - remove.x(); + return emojiRight() - rect.x(); } const auto canAdd = hasAddButton(info.section); const auto &button = rightButton(info.section); const auto rect = buttonRect(info, button); p.drawImage(rect.topLeft(), selected ? button.backOver : button.back); - if (custom.ripple) { - const auto ripple = QColor(0, 0, 0, 36); - custom.ripple->paint(p, rect.x(), rect.y(), width(), &ripple); - if (custom.ripple->empty()) { - custom.ripple.reset(); + if (ripple) { + const auto color = QColor(0, 0, 0, 36); + ripple->paint(p, rect.x(), rect.y(), width(), &color); + if (ripple->empty()) { + ripple.reset(); } } p.setPen(!canAdd @@ -2108,22 +2248,28 @@ void EmojiListWidget::setSelected(OverState newSelected) { void EmojiListWidget::setPressed(OverState newPressed) { if (auto button = std::get_if(&_pressed)) { - Assert(button->section >= _staticCount - && button->section < _staticCount + _custom.size()); - auto &set = _custom[button->section - _staticCount]; - if (set.ripple) { - set.ripple->lastStop(); + Assert(hasColorButton(button->section) + || (button->section >= _staticCount + && button->section < _staticCount + _custom.size())); + auto &ripple = (button->section >= _staticCount) + ? _custom[button->section - _staticCount].ripple + : _colorAllRipple; + if (ripple) { + ripple->lastStop(); } } _pressed = newPressed; if (auto button = std::get_if(&_pressed)) { - Assert(button->section >= _staticCount - && button->section < _staticCount + _custom.size()); - auto &set = _custom[button->section - _staticCount]; - if (!set.ripple) { - set.ripple = createButtonRipple(button->section); + Assert(hasColorButton(button->section) + || (button->section >= _staticCount + && button->section < _staticCount + _custom.size())); + auto &ripple = (button->section >= _staticCount) + ? _custom[button->section - _staticCount].ripple + : _colorAllRipple; + if (!ripple) { + ripple = createButtonRipple(button->section); } - set.ripple->add(mapFromGlobal(QCursor::pos()) - buttonRippleTopLeft(button->section)); + ripple->add(mapFromGlobal(QCursor::pos()) - buttonRippleTopLeft(button->section)); } } @@ -2167,16 +2313,18 @@ void EmojiListWidget::initButton( std::unique_ptr EmojiListWidget::createButtonRipple( int section) { - Expects(section >= _staticCount - && section < _staticCount + _custom.size()); + Expects(hasButton(section)); + const auto colorAll = hasColorButton(section); const auto remove = hasRemoveButton(section); - const auto &removeSt = st().removeSet; - const auto &st = remove ? removeSt.ripple : st::emojiPanButton.ripple; - auto mask = remove + const auto &staticSt = colorAll ? st().colorAll : st().removeSet; + const auto &st = (colorAll || remove) + ? staticSt.ripple + : st::emojiPanButton.ripple; + auto mask = (colorAll || remove) ? Ui::RippleAnimation::EllipseMask(QSize( - removeSt.rippleAreaSize, - removeSt.rippleAreaSize)) + staticSt.rippleAreaSize, + staticSt.rippleAreaSize)) : rightButton(section).rippleMask; return std::make_unique( st, @@ -2185,11 +2333,12 @@ std::unique_ptr EmojiListWidget::createButtonRipple( } QPoint EmojiListWidget::buttonRippleTopLeft(int section) const { - Expects(section >= _staticCount - && section < _staticCount + _custom.size()); + Expects(hasButton(section)); return myrtlrect(buttonRect(section)).topLeft() - + (hasRemoveButton(section) + + (hasColorButton(section) + ? st().colorAll.rippleAreaPosition + : hasRemoveButton(section) ? st().removeSet.rippleAreaPosition : QPoint()); } diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h index 374b6e7b3..b042a568e 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h @@ -247,6 +247,7 @@ private: [[nodiscard]] SectionInfo sectionInfoByOffset(int yOffset) const; [[nodiscard]] int sectionsCount() const; void setSingleSize(QSize size); + void setColorAllForceRippled(bool force); void showPicker(); void pickerHidden(); @@ -297,6 +298,9 @@ private: int set, int index); void validateEmojiPaintContext(const ExpandingContext &context); + [[nodiscard]] bool hasColorButton(int index) const; + [[nodiscard]] QRect colorButtonRect(int index) const; + [[nodiscard]] QRect colorButtonRect(const SectionInfo &info) const; [[nodiscard]] bool hasRemoveButton(int index) const; [[nodiscard]] QRect removeButtonRect(int index) const; [[nodiscard]] QRect removeButtonRect(const SectionInfo &info) const; @@ -378,6 +382,10 @@ private: Ui::RoundRect _overBg; QImage _searchExpandCache; + mutable std::unique_ptr _colorAllRipple; + bool _colorAllRippleForced = false; + rpl::lifetime _colorAllRippleForcedLifetime; + std::vector _nextSearchQuery; std::vector _searchQuery; base::flat_set _searchEmoji; diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index cef0bd2e8..8abdd7496 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -1076,11 +1076,40 @@ void Settings::setLegacyRecentEmojiPreload( } } +EmojiPtr Settings::lookupEmojiVariant(EmojiPtr emoji) const { + if (emoji->hasVariants()) { + const auto i = _emojiVariants.find(emoji->nonColoredId()); + if (i != end(_emojiVariants)) { + return emoji->variant(i->second); + } + const auto j = _emojiVariants.find(QString()); + if (j != end(_emojiVariants)) { + return emoji->variant(j->second); + } + } + return emoji; +} + +bool Settings::hasChosenEmojiVariant(EmojiPtr emoji) const { + return _emojiVariants.contains(QString()) + || _emojiVariants.contains(emoji->nonColoredId()); +} + void Settings::saveEmojiVariant(EmojiPtr emoji) { + Expects(emoji->hasVariants()); + _emojiVariants[emoji->nonColoredId()] = emoji->variantIndex(emoji); _saveDelayed.fire({}); } +void Settings::saveAllEmojiVariants(EmojiPtr emoji) { + Expects(emoji->hasVariants()); + + _emojiVariants.clear(); + _emojiVariants[QString()] = emoji->variantIndex(emoji); + _saveDelayed.fire({}); +} + void Settings::setLegacyEmojiVariants(QMap data) { if (!_emojiVariants.empty() || data.isEmpty()) { return; diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index 72468e755..bc7b74282 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -668,7 +668,10 @@ public: [[nodiscard]] const base::flat_map &emojiVariants() const { return _emojiVariants; } + [[nodiscard]] EmojiPtr lookupEmojiVariant(EmojiPtr emoji) const; + [[nodiscard]] bool hasChosenEmojiVariant(EmojiPtr emoji) const; void saveEmojiVariant(EmojiPtr emoji); + void saveAllEmojiVariants(EmojiPtr emoji); void setLegacyEmojiVariants(QMap data); [[nodiscard]] bool disableOpenGL() const { diff --git a/Telegram/SourceFiles/core/ui_integration.cpp b/Telegram/SourceFiles/core/ui_integration.cpp index e7351e8fd..ead942262 100644 --- a/Telegram/SourceFiles/core/ui_integration.cpp +++ b/Telegram/SourceFiles/core/ui_integration.cpp @@ -282,15 +282,10 @@ rpl::producer<> UiIntegration::forcePopupMenuHideRequests() { const Ui::Emoji::One *UiIntegration::defaultEmojiVariant( const Ui::Emoji::One *emoji) { - if (!emoji || !emoji->hasVariants()) { + if (!emoji) { return emoji; } - const auto nonColored = emoji->nonColoredId(); - const auto &variants = Core::App().settings().emojiVariants(); - const auto i = variants.find(nonColored); - const auto result = (i != end(variants)) - ? emoji->variant(i->second) - : emoji; + const auto result = Core::App().settings().lookupEmojiVariant(emoji); Core::App().settings().incrementRecentEmoji({ result }); return result; } diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index a2426a990..67e684764 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -479,6 +479,14 @@ storiesRemoveSet: IconButton(stickerPanRemoveSet) { iconOver: icon {{ "simple_close", storiesComposeGrayIcon }}; ripple: storiesComposeRippleLight; } +storiesColorAll: IconButton(emojiPanColorAll) { + icon: icon {{ "emoji/emoji_skin", storiesComposeGrayIcon }}; + iconOver: icon {{ "emoji/emoji_skin", storiesComposeGrayIcon }}; + ripple: storiesComposeRippleLight; +} +storiesColorAllLabel: FlatLabel(emojiPanColorAllLabel) { + textFg: storiesComposeGrayText; +} storiesMenuSeparator: mediaviewMenuSeparator; storiesMenu: Menu(defaultMenu) { itemBg: groupCallMenuBg; @@ -588,6 +596,8 @@ storiesEmojiPan: EmojiPan(defaultEmojiPan) { rippleBgActive: storiesComposeBgOver; } search: storiesEmojiTabbedSearch; + colorAll: storiesColorAll; + colorAllLabel: storiesColorAllLabel; removeSet: storiesRemoveSet; boxLabel: storiesBoxLabel; icons: ComposeIcons {