diff --git a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp index 386f41b56..34205a98b 100644 --- a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp +++ b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp @@ -41,138 +41,6 @@ namespace { constexpr auto kVisibleButtonsCount = 7; -void AddIcon( - not_null parent, - rpl::producer iconPositionValue, - int iconSize, - not_null session, - const Data::Reaction &reaction, - rpl::producer<> &&selects, - rpl::producer<> &&destroys, - not_null stateLifetime) { - - struct State { - struct Entry { - std::shared_ptr media; - std::shared_ptr icon; - }; - Entry appear; - Entry select; - bool appearAnimated = false; - rpl::lifetime loadingLifetime; - - base::unique_qptr widget; - - Ui::Animations::Simple finalAnimation; - }; - - const auto state = stateLifetime->make_state(); - state->widget = base::make_unique_q(parent); - - state->appear.media = reaction.appearAnimation->createMediaView(); - state->select.media = reaction.selectAnimation->createMediaView(); - state->appear.media->checkStickerLarge(); - state->select.media->checkStickerLarge(); - rpl::single() | rpl::then( - session->downloaderTaskFinished() - ) | rpl::start_with_next([=] { - const auto check = [&](State::Entry &entry) { - if (!entry.media) { - return true; - } else if (!entry.media->loaded()) { - return false; - } - entry.icon = HistoryView::Reactions::DefaultIconFactory( - entry.media.get(), - iconSize); - entry.media = nullptr; - return true; - }; - if (check(state->select) && check(state->appear)) { - state->loadingLifetime.destroy(); - } - }, state->loadingLifetime); - - const auto widget = state->widget.get(); - widget->resize(iconSize, iconSize); - widget->setAttribute(Qt::WA_TransparentForMouseEvents); - - std::move( - iconPositionValue - ) | rpl::start_with_next([=](const QPoint &point) { - widget->moveToLeft(point.x(), point.y()); - }, widget->lifetime()); - - const auto update = crl::guard(widget, [=] { widget->update(); }); - - widget->paintRequest( - ) | rpl::start_with_next([=, - frameSize = (iconSize / style::DevicePixelRatio())] { - Painter p(widget); - - if (state->finalAnimation.animating()) { - const auto progress = 1. - state->finalAnimation.value(0.); - const auto size = widget->size(); - const auto scaledSize = size * progress; - const auto scaledCenter = QPoint( - (size.width() - scaledSize.width()) / 2., - (size.height() - scaledSize.height()) / 2.); - p.setOpacity(progress); - p.translate(scaledCenter); - p.scale(progress, progress); - } - - const auto paintFrame = [&](not_null animation) { - const auto frame = animation->frame(); - p.drawImage( - QRect( - (widget->width() - frameSize) / 2, - (widget->height() - frameSize) / 2, - frameSize, - frameSize), - frame); - }; - - const auto appear = state->appear.icon.get(); - if (appear && !state->appearAnimated) { - state->appearAnimated = true; - appear->animate(update, 0, appear->framesCount() - 1); - } - if (appear && appear->animating()) { - paintFrame(appear); - } else if (const auto select = state->select.icon.get()) { - paintFrame(select); - } - }, widget->lifetime()); - - std::move( - selects - ) | rpl::start_with_next([=] { - const auto select = state->select.icon.get(); - if (select && !select->animating()) { - select->animate(update, 0, select->framesCount() - 1); - } - }, widget->lifetime()); - - std::move( - destroys - ) | rpl::take(1) | rpl::start_with_next([=, from = 0., to = 1.] { - state->finalAnimation.start( - [=](float64 value) { - update(); - if (value == to) { - stateLifetime->destroy(); - } - }, - from, - to, - st::defaultPopupMenu.showDuration); - }, widget->lifetime()); - - widget->raise(); - widget->show(); -} - PeerId GenerateUser(not_null history, const QString &name) { Expects(history->peer->isUser()); const auto peerId = Data::FakePeerIdForJustName(name); @@ -351,7 +219,7 @@ void AddMessage( } const auto index = state->icons.flag ? 1 : 0; state->icons.lifetimes[index] = rpl::lifetime(); - AddIcon( + AddReactionLottieIcon( box->verticalLayout(), widget->geometryValue( ) | rpl::map([=](const QRect &r) { @@ -375,6 +243,138 @@ void AddMessage( } // namespace +void AddReactionLottieIcon( + not_null parent, + rpl::producer iconPositionValue, + int iconSize, + not_null session, + const Data::Reaction &reaction, + rpl::producer<> &&selects, + rpl::producer<> &&destroys, + not_null stateLifetime) { + + struct State { + struct Entry { + std::shared_ptr media; + std::shared_ptr icon; + }; + Entry appear; + Entry select; + bool appearAnimated = false; + rpl::lifetime loadingLifetime; + + base::unique_qptr widget; + + Ui::Animations::Simple finalAnimation; + }; + + const auto state = stateLifetime->make_state(); + state->widget = base::make_unique_q(parent); + + state->appear.media = reaction.appearAnimation->createMediaView(); + state->select.media = reaction.selectAnimation->createMediaView(); + state->appear.media->checkStickerLarge(); + state->select.media->checkStickerLarge(); + rpl::single() | rpl::then( + session->downloaderTaskFinished() + ) | rpl::start_with_next([=] { + const auto check = [&](State::Entry &entry) { + if (!entry.media) { + return true; + } else if (!entry.media->loaded()) { + return false; + } + entry.icon = HistoryView::Reactions::DefaultIconFactory( + entry.media.get(), + iconSize); + entry.media = nullptr; + return true; + }; + if (check(state->select) && check(state->appear)) { + state->loadingLifetime.destroy(); + } + }, state->loadingLifetime); + + const auto widget = state->widget.get(); + widget->resize(iconSize, iconSize); + widget->setAttribute(Qt::WA_TransparentForMouseEvents); + + std::move( + iconPositionValue + ) | rpl::start_with_next([=](const QPoint &point) { + widget->moveToLeft(point.x(), point.y()); + }, widget->lifetime()); + + const auto update = crl::guard(widget, [=] { widget->update(); }); + + widget->paintRequest( + ) | rpl::start_with_next([=, + frameSize = (iconSize / style::DevicePixelRatio())] { + Painter p(widget); + + if (state->finalAnimation.animating()) { + const auto progress = 1. - state->finalAnimation.value(0.); + const auto size = widget->size(); + const auto scaledSize = size * progress; + const auto scaledCenter = QPoint( + (size.width() - scaledSize.width()) / 2., + (size.height() - scaledSize.height()) / 2.); + p.setOpacity(progress); + p.translate(scaledCenter); + p.scale(progress, progress); + } + + const auto paintFrame = [&](not_null animation) { + const auto frame = animation->frame(); + p.drawImage( + QRect( + (widget->width() - frameSize) / 2, + (widget->height() - frameSize) / 2, + frameSize, + frameSize), + frame); + }; + + const auto appear = state->appear.icon.get(); + if (appear && !state->appearAnimated) { + state->appearAnimated = true; + appear->animate(update, 0, appear->framesCount() - 1); + } + if (appear && appear->animating()) { + paintFrame(appear); + } else if (const auto select = state->select.icon.get()) { + paintFrame(select); + } + }, widget->lifetime()); + + std::move( + selects + ) | rpl::start_with_next([=] { + const auto select = state->select.icon.get(); + if (select && !select->animating()) { + select->animate(update, 0, select->framesCount() - 1); + } + }, widget->lifetime()); + + std::move( + destroys + ) | rpl::take(1) | rpl::start_with_next([=, from = 0., to = 1.] { + state->finalAnimation.start( + [=](float64 value) { + update(); + if (value == to) { + stateLifetime->destroy(); + } + }, + from, + to, + st::defaultPopupMenu.showDuration); + }, widget->lifetime()); + + widget->raise(); + widget->show(); +} + void ReactionsSettingsBox( not_null box, not_null controller) { @@ -436,7 +436,7 @@ void ReactionsSettingsBox( stButton); const auto iconSize = st::settingsReactionSize; - AddIcon( + AddReactionLottieIcon( button, button->sizeValue( ) | rpl::map([=, left = button->st().iconLeft](const QSize &s) { diff --git a/Telegram/SourceFiles/boxes/reactions_settings_box.h b/Telegram/SourceFiles/boxes/reactions_settings_box.h index db0d616da..33cbe5369 100644 --- a/Telegram/SourceFiles/boxes/reactions_settings_box.h +++ b/Telegram/SourceFiles/boxes/reactions_settings_box.h @@ -9,12 +9,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Ui { class GenericBox; +class RpWidget; } // namespace Ui namespace Window { class SessionController; } // namespace Window +namespace Main { +class Session; +} // namespace Main + +namespace Data { +struct Reaction; +} // namespace Data + +void AddReactionLottieIcon( + not_null parent, + rpl::producer iconPositionValue, + int iconSize, + not_null session, + const Data::Reaction &reaction, + rpl::producer<> &&selects, + rpl::producer<> &&destroys, + not_null stateLifetime); + void ReactionsSettingsBox( not_null box, not_null controller); diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index 7d661de74..958b85bcf 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -182,13 +182,11 @@ QImage Reactions::resolveImageFor( if (set.bottomInfo.isNull() && set.icon) { resolve(set.bottomInfo, st::reactionInfoImage); resolve(set.inlineList, st::reactionInlineImage); - resolve(set.settings, st::reactionSettingsImage); crl::async([icon = std::move(set.icon)]{}); } switch (size) { case ImageSize::BottomInfo: return set.bottomInfo; case ImageSize::InlineList: return set.inlineList; - case ImageSize::Settings: return set.settings; } Unexpected("ImageSize in Reactions::resolveImageFor."); } diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h index f428e1308..0a587ea15 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.h +++ b/Telegram/SourceFiles/data/data_message_reactions.h @@ -54,7 +54,6 @@ public: enum class ImageSize { BottomInfo, InlineList, - Settings, }; void preloadImageFor(const QString &emoji); void preloadAnimationsFor(const QString &emoji); @@ -78,7 +77,6 @@ private: struct ImageSet { QImage bottomInfo; QImage inlineList; - QImage settings; std::shared_ptr media; std::unique_ptr icon; bool fromAppearAnimation = false; diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 5c857d067..a2a0f192c 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -376,6 +376,8 @@ settingsReactionCornerSize: size(28px, 22px); settingsReactionCornerSkip: point(11px, -6px); settingsReactionMessageSize: 36px; +settingsReactionRightIcon: 40px; + notifyPreviewMargins: margins(40px, 20px, 40px, 58px); notifyPreviewUserpicSize: 36px; notifyPreviewUserpicPosition: point(14px, 11px); diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp index d9b0df664..7b8cfa692 100644 --- a/Telegram/SourceFiles/settings/settings_chat.cpp +++ b/Telegram/SourceFiles/settings/settings_chat.cpp @@ -857,48 +857,72 @@ void SetupMessages( const auto buttonRight = Ui::CreateChild( inner, st::stickersRemove); - const auto reactRight = Ui::CreateChild(inner); + const auto toggleButtonRight = [=](bool value) { + buttonRight->setAttribute(Qt::WA_TransparentForMouseEvents, !value); + }; + toggleButtonRight(false); struct State { - QString lastFavorite; - QImage cache; + struct { + std::vector lifetimes; + bool flag = false; + } icons; }; - const auto state = reactRight->lifetime().make_state(); - const auto updateState = [=] { - auto &reactions = controller->session().data().reactions(); - if (state->lastFavorite == reactions.favorite()) { - return; + const auto state = buttonRight->lifetime().make_state(); + state->icons.lifetimes = std::vector(2); + + const auto &reactions = controller->session().data().reactions(); + auto emojiValue = rpl::single( + reactions.favorite() + ) | rpl::then( + reactions.updates() | rpl::map([=] { + return controller->session().data().reactions().favorite(); + }) + ) | rpl::filter([](const QString &emoji) { + return !emoji.isEmpty(); + }); + rpl::duplicate( + emojiValue + ) | rpl::start_with_next([=, emojiValue = std::move(emojiValue)]( + const QString &emoji) { + const auto &reactions = controller->session().data().reactions(); + for (const auto &r : reactions.list(Data::Reactions::Type::All)) { + if (emoji != r.emoji) { + continue; + } + const auto index = state->icons.flag ? 1 : 0; + state->icons.lifetimes[index] = rpl::lifetime(); + const auto iconSize = st::settingsReactionRightIcon; + AddReactionLottieIcon( + inner, + buttonRight->geometryValue( + ) | rpl::map([=](const QRect &r) { + return QPoint( + r.left() + (r.width() - iconSize) / 2, + r.top() + (r.height() - iconSize) / 2); + }), + iconSize, + &controller->session(), + r, + buttonRight->events( + ) | rpl::filter([=](not_null event) { + return event->type() == QEvent::Enter; + }) | rpl::to_empty, + rpl::duplicate(emojiValue) | rpl::skip(1) | rpl::to_empty, + &state->icons.lifetimes[index]); + state->icons.flag = !state->icons.flag; + toggleButtonRight(true); + break; } - state->lastFavorite = reactions.favorite(); - state->cache = reactions.resolveImageFor( - reactions.favorite(), - Data::Reactions::ImageSize::Settings); - reactRight->resize(state->cache.size() / style::DevicePixelRatio()); - }; - updateState(); - controller->session().data().reactions().updates( - ) | rpl::start_with_next(updateState, reactRight->lifetime()); + }, buttonRight->lifetime()); - reactRight->setAttribute(Qt::WA_TransparentForMouseEvents); - reactRight->paintRequest( - ) | rpl::start_with_next([=] { - Painter p(reactRight); - - p.drawImage(0, 0, state->cache); - }, reactRight->lifetime()); - rpl::combine( - reactRight->sizeValue(), - react->geometryValue() - ) | rpl::start_with_next([=](const QSize &rightSize, const QRect &r) { - reactRight->moveToRight( + react->geometryValue( + ) | rpl::start_with_next([=](const QRect &r) { + const auto rightSize = buttonRight->size(); + buttonRight->moveToRight( st::settingsButtonRightSkip, r.y() + (r.height() - rightSize.height()) / 2); - buttonRight->moveToLeft( - reactRight->x() - + (rightSize.width() - buttonRight->width()) / 2, - reactRight->y() - + (rightSize.height() - buttonRight->height()) / 2); - }, reactRight->lifetime()); + }, buttonRight->lifetime()); groupQuick->setChangedCallback([=](Quick value) { Core::App().settings().setChatQuickAction(value); diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index d835af71f..e1467fdd5 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1007,8 +1007,6 @@ reactionInfoSkip: 3px; reactionInfoDigitSkip: 6px; reactionInfoBetween: 3px; -reactionSettingsImage: 40px; - reactionCornerSize: size(36px, 32px); reactionCornerCenter: point(7px, -9px); reactionCornerImage: 22px;