From c8ed8e0e5f3221f846cf008b82a465cb9a60aa6b Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 25 Oct 2022 20:40:26 +0400 Subject: [PATCH] Support nice empty topic view. --- Telegram/Resources/langs/lang.strings | 6 +- Telegram/SourceFiles/apiwrap.cpp | 4 + .../boxes/peers/edit_forum_topic_box.cpp | 17 +- .../view/history_view_context_menu.cpp | 2 +- .../history/view/history_view_element.cpp | 2 +- .../history/view/history_view_list_widget.cpp | 295 +++++++++--------- .../history/view/history_view_list_widget.h | 3 + .../view/history_view_pinned_section.cpp | 5 + .../view/history_view_pinned_section.h | 3 + .../view/history_view_replies_section.cpp | 45 ++- .../view/history_view_replies_section.h | 7 + .../view/history_view_scheduled_section.cpp | 5 + .../view/history_view_scheduled_section.h | 3 + .../view/history_view_service_message.cpp | 81 +++-- .../view/history_view_service_message.h | 18 ++ .../media/history_view_sticker_player.cpp | 1 - .../info/profile/info_profile_cover.cpp | 118 ++++--- .../info/profile/info_profile_cover.h | 40 ++- 18 files changed, 418 insertions(+), 237 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 9a13a54cc..6b3af9cb7 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1509,7 +1509,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_gift_received" = "{user} sent you a gift for {cost}"; "lng_action_gift_received_me" = "You sent to {user} a gift for {cost}"; "lng_action_topic_created_inside" = "Topic created"; -"lng_action_topic_closed_inside" = "Topics closed"; +"lng_action_topic_closed_inside" = "Topic closed"; "lng_action_topic_reopened_inside" = "Topic reopened"; "lng_action_topic_created" = "{topic} — was created"; "lng_action_topic_closed" = "{topic} — was closed"; @@ -3536,9 +3536,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_forum_topic_delete" = "Delete"; "lng_forum_topic_delete_sure" = "Are you sure you want to delete this topic?"; "lng_forum_topic_created_title_my" = "Almost done!"; -"lng_forum_topic_created_body_my" = "Send the first message to start this topic."; +"lng_forum_topic_created_body_my" = "Send the first message\nto start this topic."; "lng_forum_topic_created_title" = "Topic started!"; -"lng_forum_topic_created_body" = "Send a message to open the discussion"; +"lng_forum_topic_created_body" = "Send a message to open\nthe discussion."; "lng_forum_topics_switch" = "Topics"; "lng_forum_topics_not_enough#one" = "Only groups with more than **{count} member** can have topics enabled."; "lng_forum_topics_not_enough#other" = "Only groups with more than **{count} members** can have topics enabled."; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index cec2ec60f..d5920414b 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -2016,6 +2016,10 @@ void ApiWrap::saveCurrentDraftToCloud() { for (const auto &controller : _session->windows()) { controller->materializeLocalDrafts(); if (const auto thread = controller->activeChatCurrent().thread()) { + const auto topic = thread->asTopic(); + if (topic && topic->creating()) { + continue; + } const auto history = thread->owningHistory(); _session->local().writeDrafts(history); diff --git a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp index 179d4756b..87fa6ba59 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp @@ -468,7 +468,6 @@ void EditForumTopicBox( state->iconId = (iconId != kDefaultIconId) ? iconId : 0; }, box->lifetime()); - const auto requestId = std::make_shared(); const auto create = [=] { const auto channel = forum->peer->asChannel(); if (!channel || !channel->isForum()) { @@ -505,9 +504,11 @@ void EditForumTopicBox( topic->applyTitle(title->getLastText().trimmed()); topic->applyColorId(state->defaultIcon.current().colorId); topic->applyIconId(state->iconId.current()); + box->closeBox(); } else { using Flag = MTPchannels_EditForumTopic::Flag; const auto api = &forum->session().api(); + const auto weak = Ui::MakeWeak(box.get()); state->requestId = api->request(MTPchannels_EditForumTopic( MTP_flags(Flag::f_title | Flag::f_icon_emoji_id), topic->channel()->inputChannel, @@ -517,12 +518,16 @@ void EditForumTopicBox( MTPBool() // closed )).done([=](const MTPUpdates &result) { api->applyUpdates(result); - box->closeBox(); + if (const auto strong = weak.data()) { + strong->closeBox(); + } }).fail([=](const MTP::Error &error) { - if (error.type() == u"TOPIC_NOT_MODIFIED") { - box->closeBox(); - } else { - state->requestId = -1; + if (const auto strong = weak.data()) { + if (error.type() == u"TOPIC_NOT_MODIFIED") { + strong->closeBox(); + } else { + state->requestId = -1; + } } }).send(); } diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index d71b34a95..e36c400e6 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -589,7 +589,7 @@ bool AddReplyToMessageAction( const auto peer = item ? item->history()->peer.get() : nullptr; if (!item || !item->isRegular() - || (topic ? topic->canWrite() : !peer->canWrite()) + || (topic ? !topic->canWrite() : !peer->canWrite()) || (context != Context::History && context != Context::Replies)) { return false; } diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index f93aed046..6a0660de6 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -702,7 +702,7 @@ auto Element::contextDependentServiceText() -> TextWithLinks { const QString &title, std::optional iconId) { auto result = TextWithEntities{ title }; - auto full = iconId + auto full = (iconId && *iconId) ? wrapIcon(*iconId).append(' ').append(std::move(result)) : result; return Ui::Text::Link(std::move(full), topicUrl); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index a5971937a..4749e9e7c 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -1908,167 +1908,170 @@ void ListWidget::paintEvent(QPaintEvent *e) { return this->itemTop(elem) < bottom; }); - if (from != end(_items)) { - _reactionsManager->startEffectsCollection(); + auto context = preparePaintContext(clip); + if (from == end(_items)) { + _delegate->listPaintEmpty(p, context); + return; + } + _reactionsManager->startEffectsCollection(); - const auto session = &controller()->session(); - auto top = itemTop(from->get()); - auto context = preparePaintContext(clip).translated(0, -top); - p.translate(0, top); - const auto &sendingAnimation = _controller->sendingAnimation(); - for (auto i = from; i != to; ++i) { - const auto view = *i; - const auto item = view->data(); - const auto height = view->height(); - if (!sendingAnimation.hasAnimatedMessage(item)) { - context.reactionInfo - = _reactionsManager->currentReactionPaintInfo(); - context.outbg = view->hasOutLayout(); - context.selection = itemRenderSelection(view); - view->draw(p, context); - } - const auto isSponsored = item->isSponsored(); - const auto isUnread = _delegate->listElementShownUnread(view) - && item->isRegular(); - const auto withReaction = item->hasUnreadReaction(); - const auto yShown = [&](int y) { - return (_visibleBottom >= y && _visibleTop <= y); - }; - const auto markShown = isSponsored - ? view->markSponsoredViewed(_visibleBottom - top) - : withReaction - ? yShown(top + context.reactionInfo->position.y()) - : isUnread - ? yShown(top + height) - : yShown(top + height / 2); - if (markShown) { - if (isSponsored) { - session->data().sponsoredMessages().view( - item->fullId()); - } else if (isUnread) { - readTill = item; - } - if (item->hasViews()) { - session->api().views().scheduleIncrement(item); - } - if (withReaction) { - readContents.insert(item); - } else if (item->isUnreadMention() - && !item->isUnreadMedia()) { - readContents.insert(item); - _highlighter.enqueue(view); - } - } - session->data().reactions().poll(item, context.now); - if (item->hasExtendedMediaPreview()) { - session->api().views().pollExtendedMedia(item); - } - _reactionsManager->recordCurrentReactionEffect( - item->fullId(), - QPoint(0, top)); - top += height; - context.translate(0, -height); - p.translate(0, height); + const auto session = &controller()->session(); + auto top = itemTop(from->get()); + context = context.translated(0, -top); + p.translate(0, top); + const auto &sendingAnimation = _controller->sendingAnimation(); + for (auto i = from; i != to; ++i) { + const auto view = *i; + const auto item = view->data(); + const auto height = view->height(); + if (!sendingAnimation.hasAnimatedMessage(item)) { + context.reactionInfo + = _reactionsManager->currentReactionPaintInfo(); + context.outbg = view->hasOutLayout(); + context.selection = itemRenderSelection(view); + view->draw(p, context); } - context.translate(0, top); - p.translate(0, -top); - - enumerateUserpics([&](not_null view, int userpicTop) { - // stop the enumeration if the userpic is below the painted rect - if (userpicTop >= clip.top() + clip.height()) { - return false; + const auto isSponsored = item->isSponsored(); + const auto isUnread = _delegate->listElementShownUnread(view) + && item->isRegular(); + const auto withReaction = item->hasUnreadReaction(); + const auto yShown = [&](int y) { + return (_visibleBottom >= y && _visibleTop <= y); + }; + const auto markShown = isSponsored + ? view->markSponsoredViewed(_visibleBottom - top) + : withReaction + ? yShown(top + context.reactionInfo->position.y()) + : isUnread + ? yShown(top + height) + : yShown(top + height / 2); + if (markShown) { + if (isSponsored) { + session->data().sponsoredMessages().view( + item->fullId()); + } else if (isUnread) { + readTill = item; } + if (item->hasViews()) { + session->api().views().scheduleIncrement(item); + } + if (withReaction) { + readContents.insert(item); + } else if (item->isUnreadMention() + && !item->isUnreadMedia()) { + readContents.insert(item); + _highlighter.enqueue(view); + } + } + session->data().reactions().poll(item, context.now); + if (item->hasExtendedMediaPreview()) { + session->api().views().pollExtendedMedia(item); + } + _reactionsManager->recordCurrentReactionEffect( + item->fullId(), + QPoint(0, top)); + top += height; + context.translate(0, -height); + p.translate(0, height); + } + context.translate(0, top); + p.translate(0, -top); - // paint the userpic if it intersects the painted rect - if (userpicTop + st::msgPhotoSize > clip.top()) { - if (const auto from = view->data()->displayFrom()) { - from->paintUserpicLeft( + enumerateUserpics([&](not_null view, int userpicTop) { + // stop the enumeration if the userpic is below the painted rect + if (userpicTop >= clip.top() + clip.height()) { + return false; + } + + // paint the userpic if it intersects the painted rect + if (userpicTop + st::msgPhotoSize > clip.top()) { + if (const auto from = view->data()->displayFrom()) { + from->paintUserpicLeft( + p, + _userpics[from], + st::historyPhotoLeft, + userpicTop, + view->width(), + st::msgPhotoSize); + } else if (const auto info = view->data()->hiddenSenderInfo()) { + if (info->customUserpic.empty()) { + info->emptyUserpic.paint( p, - _userpics[from], st::historyPhotoLeft, userpicTop, view->width(), st::msgPhotoSize); - } else if (const auto info = view->data()->hiddenSenderInfo()) { - if (info->customUserpic.empty()) { - info->emptyUserpic.paint( - p, - st::historyPhotoLeft, - userpicTop, - view->width(), - st::msgPhotoSize); - } else { - const auto painted = info->paintCustomUserpic( - p, - st::historyPhotoLeft, - userpicTop, - view->width(), - st::msgPhotoSize); - if (!painted) { - const auto itemId = view->data()->fullId(); - auto &v = _sponsoredUserpics[itemId.msg]; - if (!info->customUserpic.isCurrentView(v)) { - v = info->customUserpic.createView(); - info->customUserpic.load(session, itemId); - } + } else { + const auto painted = info->paintCustomUserpic( + p, + st::historyPhotoLeft, + userpicTop, + view->width(), + st::msgPhotoSize); + if (!painted) { + const auto itemId = view->data()->fullId(); + auto &v = _sponsoredUserpics[itemId.msg]; + if (!info->customUserpic.isCurrentView(v)) { + v = info->customUserpic.createView(); + info->customUserpic.load(session, itemId); } } + } + } else { + Unexpected("Corrupt forwarded information in message."); + } + } + return true; + }); + + auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top(); + auto scrollDateOpacity = _scrollDateOpacity.value(_scrollDateShown ? 1. : 0.); + enumerateDates([&](not_null view, int itemtop, int dateTop) { + // stop the enumeration if the date is above the painted rect + if (dateTop + dateHeight <= clip.top()) { + return false; + } + + const auto displayDate = view->displayDate(); + auto dateInPlace = displayDate; + if (dateInPlace) { + const auto correctDateTop = itemtop + st::msgServiceMargin.top(); + dateInPlace = (dateTop < correctDateTop + dateHeight); + } + //bool noFloatingDate = (item->date.date() == lastDate && displayDate); + //if (noFloatingDate) { + // if (itemtop < showFloatingBefore) { + // noFloatingDate = false; + // } + //} + + // paint the date if it intersects the painted rect + if (dateTop < clip.top() + clip.height()) { + auto opacity = (dateInPlace/* || noFloatingDate*/) ? 1. : scrollDateOpacity; + if (opacity > 0.) { + p.setOpacity(opacity); + int dateY = /*noFloatingDate ? itemtop :*/ (dateTop - st::msgServiceMargin.top()); + int width = view->width(); + if (const auto date = view->Get()) { + date->paint(p, context.st, dateY, width, _isChatWide); } else { - Unexpected("Corrupt forwarded information in message."); + ServiceMessagePainter::PaintDate( + p, + context.st, + ItemDateText( + view->data(), + IsItemScheduledUntilOnline(view->data())), + dateY, + width, + _isChatWide); } } - return true; - }); + } + return true; + }); - auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top(); - auto scrollDateOpacity = _scrollDateOpacity.value(_scrollDateShown ? 1. : 0.); - enumerateDates([&](not_null view, int itemtop, int dateTop) { - // stop the enumeration if the date is above the painted rect - if (dateTop + dateHeight <= clip.top()) { - return false; - } - - const auto displayDate = view->displayDate(); - auto dateInPlace = displayDate; - if (dateInPlace) { - const auto correctDateTop = itemtop + st::msgServiceMargin.top(); - dateInPlace = (dateTop < correctDateTop + dateHeight); - } - //bool noFloatingDate = (item->date.date() == lastDate && displayDate); - //if (noFloatingDate) { - // if (itemtop < showFloatingBefore) { - // noFloatingDate = false; - // } - //} - - // paint the date if it intersects the painted rect - if (dateTop < clip.top() + clip.height()) { - auto opacity = (dateInPlace/* || noFloatingDate*/) ? 1. : scrollDateOpacity; - if (opacity > 0.) { - p.setOpacity(opacity); - int dateY = /*noFloatingDate ? itemtop :*/ (dateTop - st::msgServiceMargin.top()); - int width = view->width(); - if (const auto date = view->Get()) { - date->paint(p, context.st, dateY, width, _isChatWide); - } else { - ServiceMessagePainter::PaintDate( - p, - context.st, - ItemDateText( - view->data(), - IsItemScheduledUntilOnline(view->data())), - dateY, - width, - _isChatWide); - } - } - } - return true; - }); - - _reactionsManager->paint(p, context); - _emojiInteractions->paint(p); - } + _reactionsManager->paint(p, context); + _emojiInteractions->paint(p); } void ListWidget::maybeMarkReactionsRead(not_null item) { diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index b07f586da..fbd4ae0b1 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -132,6 +132,9 @@ public: not_null document, FullMsgId context, bool showInMediaView) = 0; + virtual void listPaintEmpty( + Painter &p, + const Ui::ChatPaintContext &context) = 0; }; struct SelectionData { diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp index 2ce5e65f1..cff87b6fc 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp @@ -603,6 +603,11 @@ void PinnedWidget::listOpenDocument( controller()->openDocument(document, context, MsgId(), showInMediaView); } +void PinnedWidget::listPaintEmpty( + Painter &p, + const Ui::ChatPaintContext &context) { +} + void PinnedWidget::confirmDeleteSelected() { ConfirmDeleteSelectedItems(_inner); } diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.h b/Telegram/SourceFiles/history/view/history_view_pinned_section.h index b5bc978a1..abf0e1628 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.h +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.h @@ -119,6 +119,9 @@ public: not_null document, FullMsgId context, bool showInMediaView) override; + void listPaintEmpty( + Painter &p, + const Ui::ChatPaintContext &context) override; // CornerButtonsDelegate delegate. void cornerButtonsShowAtPosition( diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index cc567076e..da6336e7c 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_sticker_toast.h" #include "history/view/history_view_cursor_state.h" #include "history/view/history_view_contact_status.h" +#include "history/view/history_view_service_message.h" #include "history/history.h" #include "history/history_drag_area.h" #include "history/history_item_components.h" @@ -355,16 +356,17 @@ RepliesWidget::RepliesWidget( } RepliesWidget::~RepliesWidget() { + base::take(_sendAction); + session().api().saveCurrentDraftToCloud(); + controller()->sendingAnimation().clear(); if (_topic && _topic->creating()) { + _emptyPainter = nullptr; _topic->discard(); _topic = nullptr; } - base::take(_sendAction); _history->owner().sendActionManager().repliesPainterRemoved( _history, _rootId); - session().api().saveCurrentDraftToCloud(); - controller()->sendingAnimation().clear(); } void RepliesWidget::orderWidgets() { @@ -518,6 +520,11 @@ void RepliesWidget::setTopic(Data::ForumTopic *topic) { } subscribeToTopic(); } + if (_topic && emptyShown()) { + setupEmptyPainter(); + } else { + _emptyPainter = nullptr; + } } HistoryItem *RepliesWidget::lookupRoot() const { @@ -1772,6 +1779,12 @@ void RepliesWidget::paintEvent(QPaintEvent *e) { SectionWidget::PaintBackground(controller(), _theme.get(), this, bg); } +bool RepliesWidget::emptyShown() const { + return _topic + && (_inner->isEmpty() + || (_topic->lastKnownServerMessageId() == _rootId)); +} + void RepliesWidget::onScroll() { if (_skipScrollEvent) { return; @@ -2065,6 +2078,32 @@ void RepliesWidget::listOpenDocument( controller()->openDocument(document, context, _rootId, showInMediaView); } +void RepliesWidget::listPaintEmpty( + Painter &p, + const Ui::ChatPaintContext &context) { + if (!emptyShown()) { + return; + } else if (!_emptyPainter) { + setupEmptyPainter(); + } + _emptyPainter->paint(p, context.st, width(), _scroll->height()); +} + +void RepliesWidget::setupEmptyPainter() { + Expects(_topic != nullptr); + + _emptyPainter = std::make_unique(_topic, [=] { + return controller()->isGifPausedAtLeastFor( + Window::GifPauseReason::Any); + }, [=] { + if (emptyShown()) { + update(); + } else { + _emptyPainter = nullptr; + } + }); +} + void RepliesWidget::confirmDeleteSelected() { ConfirmDeleteSelectedItems(_inner); } diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 362c27ac2..4ab5b651c 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -66,6 +66,7 @@ class ComposeControls; class SendActionPainter; class StickerToast; class TopicReopenBar; +class EmptyPainter; class RepliesWidget final : public Window::SectionWidget @@ -158,6 +159,9 @@ public: not_null document, FullMsgId context, bool showInMediaView) override; + void listPaintEmpty( + Painter &p, + const Ui::ChatPaintContext &context) override; // CornerButtonsDelegate delegate. void cornerButtonsShowAtPosition( @@ -275,7 +279,9 @@ private: Api::SendOptions options, std::optional localMessageId); + void setupEmptyPainter(); void refreshJoinGroupButton(); + [[nodiscard]] bool emptyShown() const; [[nodiscard]] bool showSlowmodeError(); [[nodiscard]] std::optional writeRestriction() const; @@ -296,6 +302,7 @@ private: std::unique_ptr _composeControls; std::unique_ptr _joinGroup; std::unique_ptr _topicReopenBar; + std::unique_ptr _emptyPainter; bool _skipScrollEvent = false; std::unique_ptr _rootView; diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 468e081e0..016960024 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -1282,6 +1282,11 @@ void ScheduledWidget::listOpenDocument( controller()->openDocument(document, context, MsgId(), showInMediaView); } +void ScheduledWidget::listPaintEmpty( + Painter &p, + const Ui::ChatPaintContext &context) { +} + void ScheduledWidget::confirmSendNowSelected() { ConfirmSendNowSelectedItems(_inner); } diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h index eee84054e..f52114e33 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h @@ -142,6 +142,9 @@ public: not_null document, FullMsgId context, bool showInMediaView) override; + void listPaintEmpty( + Painter &p, + const Ui::ChatPaintContext &context) override; // CornerButtonsDelegate delegate. void cornerButtonsShowAtPosition( diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.cpp b/Telegram/SourceFiles/history/view/history_view_service_message.cpp index 0df0ffaf2..9c59795c8 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_service_message.cpp @@ -15,18 +15,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_abstract_structure.h" #include "data/data_chat.h" #include "data/data_channel.h" +#include "info/profile/info_profile_cover.h" #include "ui/chat/chat_style.h" #include "ui/text/text_options.h" #include "ui/painter.h" #include "ui/ui_utility.h" #include "mainwidget.h" #include "menu/menu_ttl_validator.h" +#include "data/data_forum_topic.h" #include "lang/lang_keys.h" #include "styles/style_chat.h" +#include "styles/style_info.h" namespace HistoryView { namespace { +TextParseOptions EmptyLineOptions = { + TextParseMultiline, // flags + 4096, // maxw + 1, // maxh + Qt::LayoutDirectionAuto, // lang-dependent +}; + enum CircleMask { NormalMask = 0x00, InvertedMask = 0x01, @@ -181,7 +191,11 @@ bool NeedAboutGroup(not_null history) { return false; } -} // namepsace +void SetText(Ui::Text::String &text, const QString &content) { + text.setText(st::serviceTextStyle, content, EmptyLineOptions); +} + +} // namespace int WideChatWidth() { return st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left(); @@ -657,6 +671,21 @@ EmptyPainter::EmptyPainter(not_null history) } } +EmptyPainter::EmptyPainter( + not_null topic, + Fn paused, + Fn update) +: _history(topic->history()) +, _topic(topic) +, _icon( + std::make_unique(topic, paused, update)) +, _header(st::msgMinWidth) +, _text(st::msgMinWidth) { + fillAboutTopic(); +} + +EmptyPainter::~EmptyPainter() = default; + void EmptyPainter::fillAboutGroup() { const auto phrases = { tr::lng_group_about1(tr::now), @@ -664,34 +693,38 @@ void EmptyPainter::fillAboutGroup() { tr::lng_group_about3(tr::now), tr::lng_group_about4(tr::now), }; - const auto setText = [](Ui::Text::String &text, const QString &content) { - text.setText( - st::serviceTextStyle, - content, - Ui::NameTextOptions()); - }; - setText(_header, tr::lng_group_about_header(tr::now)); - setText(_text, tr::lng_group_about_text(tr::now)); + SetText(_header, tr::lng_group_about_header(tr::now)); + SetText(_text, tr::lng_group_about_text(tr::now)); for (const auto &text : phrases) { _phrases.emplace_back(st::msgMinWidth); - setText(_phrases.back(), text); + SetText(_phrases.back(), text); } } +void EmptyPainter::fillAboutTopic() { + SetText(_header, _topic->my() + ? tr::lng_forum_topic_created_title_my(tr::now) + : tr::lng_forum_topic_created_title(tr::now)); + SetText(_text, _topic->my() + ? tr::lng_forum_topic_created_body_my(tr::now) + : tr::lng_forum_topic_created_body(tr::now)); +} + void EmptyPainter::paint( Painter &p, not_null st, int width, int height) { - if (_phrases.empty()) { + if (_phrases.empty() && _text.isEmpty()) { return; } constexpr auto kMaxTextLines = 3; - const auto maxPhraseWidth = ranges::max_element( - _phrases, - ranges::less(), - &Ui::Text::String::maxWidth - )->maxWidth(); + const auto maxPhraseWidth = _phrases.empty() + ? 0 + : ranges::max_element( + _phrases, + ranges::less(), + &Ui::Text::String::maxWidth)->maxWidth(); const auto &font = st::serviceTextStyle.font; const auto maxBubbleWidth = width - 2 * st::historyGroupAboutMargin; @@ -708,13 +741,17 @@ void EmptyPainter::paint( text.countHeight(innerWidth), kMaxTextLines * font->height); }; + const auto iconHeight = _icon + ? st::infoTopicCover.photo.size.height() + : 0; const auto bubbleHeight = padding.top() + + (_icon ? (iconHeight + st::historyGroupAboutHeaderSkip) : 0) + textHeight(_header) + st::historyGroupAboutHeaderSkip + textHeight(_text) + st::historyGroupAboutTextSkip + ranges::accumulate(_phrases, 0, ranges::plus(), textHeight) - + st::historyGroupAboutSkip * int(_phrases.size() - 1) + + st::historyGroupAboutSkip * std::max(int(_phrases.size()) - 1, 0) + padding.bottom(); const auto bubbleLeft = (width - bubbleWidth) / 2; const auto bubbleTop = (height - bubbleHeight) / 2; @@ -731,6 +768,13 @@ void EmptyPainter::paint( const auto left = bubbleLeft + padding.left(); auto top = bubbleTop + padding.top(); + if (_icon) { + _icon->paintInRect( + p, + QRect(bubbleLeft, top, bubbleWidth, iconHeight)); + top += iconHeight + st::historyGroupAboutHeaderSkip; + } + _header.drawElided( p, left, @@ -745,7 +789,8 @@ void EmptyPainter::paint( left, top, innerWidth, - kMaxTextLines); + kMaxTextLines, + _topic ? style::al_top : style::al_topleft); top += textHeight(_text) + st::historyGroupAboutTextSkip; for (const auto &text : _phrases) { diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.h b/Telegram/SourceFiles/history/view/history_view_service_message.h index 1b36513d5..73a51a480 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.h +++ b/Telegram/SourceFiles/history/view/history_view_service_message.h @@ -16,6 +16,14 @@ class ChatStyle; struct CornersPixmaps; } // namespace Ui +namespace Data { +class ForumTopic; +} // namespace Data + +namespace Info::Profile { +class TopicIconView; +} // namespace Info::Profile + namespace HistoryView { class Service final : public Element { @@ -116,6 +124,11 @@ private: class EmptyPainter { public: explicit EmptyPainter(not_null history); + EmptyPainter( + not_null topic, + Fn paused, + Fn update); + ~EmptyPainter(); void paint( Painter &p, @@ -125,12 +138,17 @@ public: private: void fillAboutGroup(); + void fillAboutTopic(); not_null _history; + Data::ForumTopic *_topic = nullptr; + std::unique_ptr _icon; Ui::Text::String _header; Ui::Text::String _text; std::vector _phrases; + rpl::lifetime _lifetime; + }; } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker_player.cpp b/Telegram/SourceFiles/history/view/media/history_view_sticker_player.cpp index c79950c87..64f6a883b 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker_player.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker_player.cpp @@ -123,7 +123,6 @@ StaticStickerPlayer::StaticStickerPlayer( : _frame(Images::Read({ .path = location.name(), .content = data, - .forceOpaque = true, }).image) { if (!_frame.isNull()) { size = _frame.size().scaled(size, Qt::KeepAspectRatio); diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp index 4509e2d30..b59dfb519 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp @@ -118,54 +118,59 @@ Cover::Cover( } TopicIconView::TopicIconView( - QWidget *parent, - not_null controller, - not_null topic) -: AbstractButton(parent) { - setup(topic, [=] { - return controller->isGifPausedAtLeastFor( - Window::GifPauseReason::Layer); - }); + not_null topic, + Fn paused, + Fn update) +: _topic(topic) +, _paused(std::move(paused)) +, _update(std::move(update)) { + setup(topic); } -void TopicIconView::setup( - not_null topic, - Fn paused) { +void TopicIconView::paintInRect(QPainter &p, QRect rect) { + const auto paint = [&](const QImage &image) { + const auto size = image.size() / image.devicePixelRatio(); + p.drawImage( + rect.x() + (rect.width() - size.width()) / 2, + rect.y() + (rect.height() - size.height()) / 2, + image); + }; + if (_player && _player->ready()) { + paint(_player->frame( + st::infoTopicCover.photo.size, + QColor(0, 0, 0, 0), + false, + crl::now(), + _paused()).image); + _player->markFrameShown(); + } else if (!_topic->iconId() && !_image.isNull()) { + paint(_image); + } +} + +void TopicIconView::setup(not_null topic) { setupPlayer(topic); setupImage(topic); - - resize(st::infoTopicCover.photo.size); - paintRequest( - ) | rpl::start_with_next([=] { - auto p = QPainter(this); - const auto paint = [&](const QImage &image) { - const auto size = image.size() / image.devicePixelRatio(); - p.drawImage( - (st::infoTopicCover.photo.size.width() - size.width()) / 2, - (st::infoTopicCover.photo.size.height() - size.height()) / 2, - image); - }; - if (_player && _player->ready()) { - paint(_player->frame( - st::infoTopicCover.photo.size, - QColor(0, 0, 0, 0), - false, - crl::now(), - paused()).image); - _player->markFrameShown(); - } else if (!topic->iconId() && !_image.isNull()) { - paint(_image); - } - }, lifetime()); } void TopicIconView::setupPlayer(not_null topic) { IconIdValue( topic - ) | rpl::map([=](DocumentId id) { - return topic->owner().customEmojiManager().resolve(id); + ) | rpl::map([=](DocumentId id) -> rpl::producer { + if (!id) { + return rpl::single((DocumentData*)nullptr); + } + return topic->owner().customEmojiManager().resolve( + id + ) | rpl::map([=](not_null document) { + return document.get(); + }); }) | rpl::flatten_latest( - ) | rpl::map([=](not_null document) { + ) | rpl::map([=](DocumentData *document) + -> rpl::producer> { + if (!document) { + return rpl::single(std::shared_ptr()); + } const auto media = document->createMediaView(); media->checkStickerLarge(); media->goodThumbnailWanted(); @@ -195,13 +200,16 @@ void TopicIconView::setupPlayer(not_null topic) { media->bytes(), st::infoTopicCover.photo.size); } - result->setRepaintCallback([=] { update(); }); + result->setRepaintCallback(_update); return result; }); }) | rpl::flatten_latest( ) | rpl::start_with_next([=](std::shared_ptr player) { _player = std::move(player); - }, lifetime()); + if (!_player) { + _update(); + } + }, _lifetime); } void TopicIconView::setupImage(not_null topic) { @@ -213,7 +221,25 @@ void TopicIconView::setupImage(not_null topic) { return ForumTopicIconFrame(colorId, title, st::infoForumTopicIcon); }) | rpl::start_with_next([=](QImage &&image) { _image = std::move(image); - update(); + _update(); + }, _lifetime); +} + +TopicIconButton::TopicIconButton( + QWidget *parent, + not_null controller, + not_null topic) +: AbstractButton(parent) +, _view( + topic, + [=] { return controller->isGifPausedAtLeastFor( + Window::GifPauseReason::Layer); }, + [=] { update(); }) { + resize(st::infoTopicCover.photo.size); + paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(this); + _view.paintInRect(p, rect()); }, lifetime()); } @@ -248,8 +274,8 @@ Cover::Cover( _peer, Ui::UserpicButton::Role::OpenPhoto, _st.photo)) -, _iconView(topic - ? object_ptr(this, controller, topic) +, _iconButton(topic + ? object_ptr(this, controller, topic) : nullptr) , _name(this, _st.name) , _status(this, _st.status) @@ -285,7 +311,7 @@ Cover::Cover( _userpic->takeResultImage()); }, _userpic->lifetime()); } else if (topic->canEdit()) { - _iconView->setClickedCallback([=] { + _iconButton->setClickedCallback([=] { _controller->show(Box( EditForumTopicBox, _controller, @@ -293,7 +319,7 @@ Cover::Cover( topic->rootId())); }); } else { - _iconView->setAttribute(Qt::WA_TransparentForMouseEvents); + _iconButton->setAttribute(Qt::WA_TransparentForMouseEvents); } } @@ -303,7 +329,7 @@ void Cover::setupChildGeometry() { if (_userpic) { _userpic->moveToLeft(_st.photoLeft, _st.photoTop, newWidth); } else { - _iconView->moveToLeft(_st.photoLeft, _st.photoTop, newWidth); + _iconButton->moveToLeft(_st.photoLeft, _st.photoTop, newWidth); } refreshNameGeometry(newWidth); refreshStatusGeometry(newWidth); diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.h b/Telegram/SourceFiles/info/profile/info_profile_cover.h index 2bf12c676..b15d861eb 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.h +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.h @@ -44,24 +44,40 @@ namespace Info::Profile { class EmojiStatusPanel; class Badge; -class TopicIconView final : public Ui::AbstractButton { +class TopicIconView final { public: TopicIconView( + not_null topic, + Fn paused, + Fn update); + + void paintInRect(QPainter &p, QRect rect); + +private: + using StickerPlayer = HistoryView::StickerPlayer; + + void setup(not_null topic); + void setupPlayer(not_null topic); + void setupImage(not_null topic); + + const not_null _topic; + Fn _paused; + Fn _update; + std::shared_ptr _player; + QImage _image; + rpl::lifetime _lifetime; + +}; + +class TopicIconButton final : public Ui::AbstractButton { +public: + TopicIconButton( QWidget *parent, not_null controller, not_null topic); private: - using StickerPlayer = HistoryView::StickerPlayer; - - void setup( - not_null topic, - Fn paused); - void setupPlayer(not_null topic); - void setupImage(not_null topic); - - std::shared_ptr _player; - QImage _image; + TopicIconView _view; }; @@ -112,7 +128,7 @@ private: int _onlineCount = 0; object_ptr _userpic; - object_ptr _iconView; + object_ptr _iconButton; object_ptr _name = { nullptr }; object_ptr _status = { nullptr }; //object_ptr _dropArea = { nullptr };