diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index a92dd5ab7c..3514e9ecab 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -92,6 +92,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "apiwrap.h" #include "base/qthelp_regex.h" +#include "ui/chat/pinned_bar.h" #include "ui/widgets/popup_menu.h" #include "ui/item_text_options.h" #include "ui/unread_badge.h" @@ -5252,10 +5253,9 @@ void HistoryWidget::checkPinnedBarState() { messageId.type }; }); - _pinnedBar = std::make_unique( + _pinnedBar = std::make_unique( this, - &session(), - std::move(shown), + HistoryView::PinnedBarContent(&session(), std::move(shown)), true); _pinnedBar->closeClicks( diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 1be6ab3bc5..230570317c 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -66,7 +66,7 @@ class SilentToggle; class FlatButton; class LinkButton; class RoundButton; -class MessageBar; +class PinnedBar; namespace Toast { class Instance; } // namespace Toast @@ -95,7 +95,6 @@ class TopBarWidget; class ContactStatus; class Element; class PinnedTracker; -class PinnedBar; } // namespace HistoryView class DragArea; @@ -603,7 +602,7 @@ private: object_ptr _fieldBarCancel; std::unique_ptr _pinnedTracker; - std::unique_ptr _pinnedBar; + std::unique_ptr _pinnedBar; int _pinnedBarHeight = 0; mtpRequestId _saveEditMsgRequestId = 0; diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp index 4401bbb092..a31713bbc4 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp @@ -12,42 +12,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_changes.h" #include "data/data_poll.h" -#include "ui/widgets/shadow.h" -#include "ui/widgets/buttons.h" #include "history/view/history_view_pinned_tracker.h" #include "history/history_item.h" #include "history/history.h" #include "apiwrap.h" -#include "styles/style_chat.h" +#include "styles/style_history.h" namespace HistoryView { namespace { [[nodiscard]] Ui::MessageBarContent ContentWithoutPreview( - not_null item, - PinnedIdType type) { + not_null item) { const auto media = item->media(); const auto poll = media ? media->poll() : nullptr; return Ui::MessageBarContent{ .id = item->id, - .title = ((type == PinnedIdType::First) - ? tr::lng_pinned_previous(tr::now) // #TODO pinned first? - : (type == PinnedIdType::Middle) - ? tr::lng_pinned_previous(tr::now) - : !poll - ? tr::lng_pinned_message(tr::now) - : poll->quiz() - ? tr::lng_pinned_quiz(tr::now) - : tr::lng_pinned_poll(tr::now)), .text = item->inReplyText(), }; } [[nodiscard]] Ui::MessageBarContent ContentWithPreview( not_null item, - PinnedIdType type, Image *preview) { - auto result = ContentWithoutPreview(item, type); + auto result = ContentWithoutPreview(item); if (!preview) { static const auto kEmpty = [&] { const auto size = st::historyReplyHeight * cIntRetinaFactor(); @@ -66,15 +53,14 @@ namespace { } [[nodiscard]] rpl::producer ContentByItem( - not_null item, - PinnedIdType type) { + not_null item) { return item->history()->session().changes().messageFlagsValue( item, Data::MessageUpdate::Flag::Edited ) | rpl::map([=]() -> rpl::producer { const auto media = item->media(); if (!media || !media->hasReplyPreview()) { - return rpl::single(ContentWithoutPreview(item, type)); + return rpl::single(ContentWithoutPreview(item)); } constexpr auto kFullLoaded = 2; constexpr auto kSomeLoaded = 1; @@ -98,19 +84,19 @@ namespace { }) | rpl::then( rpl::single(kFullLoaded) ) | rpl::map([=] { - return ContentWithPreview(item, type, media->replyPreview()); + return ContentWithPreview(item, media->replyPreview()); }); }) | rpl::flatten_latest(); } [[nodiscard]] rpl::producer ContentByItemId( not_null session, - PinnedBarId id, + FullMsgId id, bool alreadyLoaded = false) { - if (!id.message) { + if (!id) { return rpl::single(Ui::MessageBarContent()); - } else if (const auto item = session->data().message(id.message)) { - return ContentByItem(item, id.type); + } else if (const auto item = session->data().message(id)) { + return ContentByItem(item); } else if (alreadyLoaded) { return rpl::single(Ui::MessageBarContent()); // Deleted message?.. } @@ -118,13 +104,13 @@ namespace { consumer.put_next(Ui::MessageBarContent{ .text = tr::lng_contacts_loading(tr::now), }); - const auto channel = id.message.channel - ? session->data().channel(id.message.channel).get() + const auto channel = id.channel + ? session->data().channel(id.channel).get() : nullptr; const auto callback = [=](ChannelData *channel, MsgId id) { consumer.put_done(); }; - session->api().requestMessageData(channel, id.message.msg, callback); + session->api().requestMessageData(channel, id.msg, callback); return rpl::lifetime(); }); return std::move( @@ -134,180 +120,47 @@ namespace { })); } +auto WithPinnedTitle(not_null session, PinnedBarId id) { + return [=](Ui::MessageBarContent &&content) { + const auto item = session->data().message(id.message); + if (!item) { + return std::move(content); + } + const auto media = item->media(); + const auto poll = media ? media->poll() : nullptr; + content.title = (id.type == PinnedIdType::First) + ? tr::lng_pinned_previous(tr::now) // #TODO pinned first? + : (id.type == PinnedIdType::Middle) + ? tr::lng_pinned_previous(tr::now) + : !poll + ? tr::lng_pinned_message(tr::now) + : poll->quiz() + ? tr::lng_pinned_quiz(tr::now) + : tr::lng_pinned_poll(tr::now); + return std::move(content); + }; +} + } // namespace -PinnedBar::PinnedBar( - not_null parent, - not_null session, - rpl::producer itemId, - bool withClose) -: _wrap(parent, object_ptr(parent)) -, _close(withClose - ? std::make_unique( - _wrap.entity(), - st::historyReplyCancel) - : nullptr) -, _shadow(std::make_unique(_wrap.parentWidget())) { - _wrap.hide(anim::type::instant); - _shadow->hide(); +rpl::producer MessageBarContentByItemId( + not_null session, + FullMsgId id) { + return ContentByItemId(session, id); +} - _wrap.entity()->paintRequest( - ) | rpl::start_with_next([=](QRect clip) { - QPainter(_wrap.entity()).fillRect(clip, st::historyPinnedBg); - }, lifetime()); - _wrap.setAttribute(Qt::WA_OpaquePaintEvent); - - rpl::duplicate( - itemId +rpl::producer PinnedBarContent( + not_null session, + rpl::producer id) { + return std::move( + id ) | rpl::distinct_until_changed( ) | rpl::map([=](PinnedBarId id) { - return ContentByItemId(session, id); - }) | rpl::flatten_latest( - ) | rpl::filter([=](const Ui::MessageBarContent &content) { - return !content.title.isEmpty() || !content.text.text.isEmpty(); - }) | rpl::start_with_next([=](Ui::MessageBarContent &&content) { - const auto creating = !_bar; - if (creating) { - createControls(); - } - _bar->set(std::move(content)); - if (creating) { - _bar->finishAnimating(); - } - }, lifetime()); - - std::move( - itemId - ) | rpl::map([=](PinnedBarId id) { - return !id.message; - }) | rpl::start_with_next_done([=](bool hidden) { - _shouldBeShown = !hidden; - if (!_forceHidden) { - _wrap.toggle(_shouldBeShown, anim::type::normal); - } else if (!_shouldBeShown) { - _bar = nullptr; - } - }, [=] { - _forceHidden = true; - _wrap.toggle(false, anim::type::normal); - }, lifetime()); -} - -void PinnedBar::createControls() { - Expects(!_bar); - - _bar = std::make_unique( - _wrap.entity(), - st::defaultMessageBar); - if (_close) { - _close->raise(); - } - - // Clicks. - _bar->widget()->setCursor(style::cur_pointer); - _bar->widget()->events( - ) | rpl::filter([=](not_null event) { - return (event->type() == QEvent::MouseButtonPress); - }) | rpl::map([=] { - return _bar->widget()->events( - ) | rpl::filter([=](not_null event) { - return (event->type() == QEvent::MouseButtonRelease); - }) | rpl::take(1) | rpl::filter([=](not_null event) { - return _bar->widget()->rect().contains( - static_cast(event.get())->pos()); - }); - }) | rpl::flatten_latest( - ) | rpl::map([] { - return rpl::empty_value(); - }) | rpl::start_to_stream(_barClicks, _bar->widget()->lifetime()); - - _bar->widget()->move(0, 0); - _bar->widget()->show(); - _wrap.entity()->resize(_wrap.entity()->width(), _bar->widget()->height()); - - _wrap.geometryValue( - ) | rpl::start_with_next([=](QRect rect) { - _shadow->setGeometry( - rect.x(), - rect.y() + rect.height(), - rect.width(), - st::lineWidth); - _bar->widget()->resizeToWidth( - rect.width() - (_close ? _close->width() : 0)); - const auto hidden = _wrap.isHidden() || !rect.height(); - if (_shadow->isHidden() != hidden) { - _shadow->setVisible(!hidden); - } - if (_close) { - _close->moveToRight(0, 0); - } - }, _bar->widget()->lifetime()); - - _wrap.shownValue( - ) | rpl::skip( - 1 - ) | rpl::filter([=](bool shown) { - return !shown && !_forceHidden; - }) | rpl::start_with_next([=] { - _bar = nullptr; - }, _bar->widget()->lifetime()); - - Ensures(_bar != nullptr); -} - -void PinnedBar::show() { - if (!_forceHidden) { - return; - } - _forceHidden = false; - if (_shouldBeShown) { - _wrap.show(anim::type::instant); - _shadow->show(); - } -} - -void PinnedBar::hide() { - if (_forceHidden) { - return; - } - _forceHidden = true; - _wrap.hide(anim::type::instant); - _shadow->hide(); -} - -void PinnedBar::raise() { - _wrap.raise(); - _shadow->raise(); -} - -void PinnedBar::move(int x, int y) { - _wrap.move(x, y); -} - -void PinnedBar::resizeToWidth(int width) { - _wrap.entity()->resizeToWidth(width); -} - -int PinnedBar::height() const { - return !_forceHidden - ? _wrap.height() - : _shouldBeShown - ? st::historyReplyHeight - : 0; -} - -rpl::producer PinnedBar::heightValue() const { - return _wrap.heightValue(); -} - -rpl::producer<> PinnedBar::closeClicks() const { - return !_close - ? (rpl::never<>() | rpl::type_erased()) - : (_close->clicks() | rpl::map([] { return rpl::empty_value(); })); -} - -rpl::producer<> PinnedBar::barClicks() const { - return _barClicks.events(); + return ContentByItemId( + session, + id.message + ) | rpl::map(WithPinnedTitle(session, id)); + }) | rpl::flatten_latest(); } } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_bar.h b/Telegram/SourceFiles/history/view/history_view_pinned_bar.h index 469e6f2a16..0995d1cb52 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_bar.h +++ b/Telegram/SourceFiles/history/view/history_view_pinned_bar.h @@ -7,9 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "ui/wrap/slide_wrap.h" #include "ui/chat/message_bar.h" +#include + namespace Main { class Session; } // namespace Main @@ -17,10 +18,15 @@ class Session; namespace Ui { class IconButton; class PlainShadow; +struct MessageBarContent; } // namespace Ui namespace HistoryView { +[[nodiscard]] rpl::producer MessageBarContentByItemId( + not_null session, + FullMsgId id); + enum class PinnedIdType; struct PinnedBarId { FullMsgId message; @@ -33,42 +39,8 @@ struct PinnedBarId { return std::tie(message, type) == std::tie(other.message, other.type); } }; - -class PinnedBar final { -public: - PinnedBar( - not_null parent, - not_null session, - rpl::producer itemId, - bool withClose = false); - - void show(); - void hide(); - void raise(); - - void move(int x, int y); - void resizeToWidth(int width); - [[nodiscard]] int height() const; - [[nodiscard]] rpl::producer heightValue() const; - [[nodiscard]] rpl::producer<> closeClicks() const; - [[nodiscard]] rpl::producer<> barClicks() const; - - [[nodiscard]] rpl::lifetime &lifetime() { - return _wrap.lifetime(); - } - -private: - void createControls(); - - Ui::SlideWrap<> _wrap; - std::unique_ptr _bar; - std::unique_ptr _close; - std::unique_ptr _shadow; - rpl::event_stream _content; - rpl::event_stream<> _barClicks; - bool _shouldBeShown = false; - bool _forceHidden = false; - -}; +[[nodiscard]] rpl::producer PinnedBarContent( + not_null session, + rpl::producer id); } // namespace HistoryView diff --git a/Telegram/SourceFiles/ui/chat/message_bar.h b/Telegram/SourceFiles/ui/chat/message_bar.h index 73bf973307..1c82d5672f 100644 --- a/Telegram/SourceFiles/ui/chat/message_bar.h +++ b/Telegram/SourceFiles/ui/chat/message_bar.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "ui/rp_widget.h" +#include "ui/effects/animations.h" class Painter; diff --git a/Telegram/SourceFiles/ui/chat/pinned_bar.cpp b/Telegram/SourceFiles/ui/chat/pinned_bar.cpp new file mode 100644 index 0000000000..b925b91d79 --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/pinned_bar.cpp @@ -0,0 +1,195 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/chat/pinned_bar.h" + +#include "ui/chat/message_bar.h" +#include "ui/widgets/shadow.h" +#include "ui/widgets/buttons.h" +#include "styles/style_chat.h" +#include "styles/palette.h" + +#include + +namespace Ui { + +PinnedBar::PinnedBar( + not_null parent, + rpl::producer content, + bool withClose) +: _wrap(parent, object_ptr(parent)) +, _close(withClose + ? std::make_unique( + _wrap.entity(), + st::historyReplyCancel) + : nullptr) +, _shadow(std::make_unique(_wrap.parentWidget())) { + _wrap.hide(anim::type::instant); + _shadow->hide(); + + _wrap.entity()->paintRequest( + ) | rpl::start_with_next([=](QRect clip) { + QPainter(_wrap.entity()).fillRect(clip, st::historyPinnedBg); + }, lifetime()); + _wrap.setAttribute(Qt::WA_OpaquePaintEvent); + + auto copy = std::move( + content + ) | rpl::start_spawning(_wrap.lifetime()); + + rpl::duplicate( + copy + ) | rpl::filter([=](const MessageBarContent &content) { + return !content.title.isEmpty() || !content.text.text.isEmpty(); + }) | rpl::start_with_next([=](MessageBarContent &&content) { + const auto creating = !_bar; + if (creating) { + createControls(); + } + _bar->set(std::move(content)); + if (creating) { + _bar->finishAnimating(); + } + }, lifetime()); + + std::move( + copy + ) | rpl::map([=](const MessageBarContent &content) { + return content.title.isEmpty() || content.text.text.isEmpty(); + }) | rpl::start_with_next_done([=](bool hidden) { + _shouldBeShown = !hidden; + if (!_forceHidden) { + _wrap.toggle(_shouldBeShown, anim::type::normal); + } else if (!_shouldBeShown) { + _bar = nullptr; + } + }, [=] { + _forceHidden = true; + _wrap.toggle(false, anim::type::normal); + }, lifetime()); +} + +PinnedBar::~PinnedBar() = default; + +void PinnedBar::createControls() { + Expects(!_bar); + + _bar = std::make_unique( + _wrap.entity(), + st::defaultMessageBar); + if (_close) { + _close->raise(); + } + + // Clicks. + _bar->widget()->setCursor(style::cur_pointer); + _bar->widget()->events( + ) | rpl::filter([=](not_null event) { + return (event->type() == QEvent::MouseButtonPress); + }) | rpl::map([=] { + return _bar->widget()->events( + ) | rpl::filter([=](not_null event) { + return (event->type() == QEvent::MouseButtonRelease); + }) | rpl::take(1) | rpl::filter([=](not_null event) { + return _bar->widget()->rect().contains( + static_cast(event.get())->pos()); + }); + }) | rpl::flatten_latest( + ) | rpl::map([] { + return rpl::empty_value(); + }) | rpl::start_to_stream(_barClicks, _bar->widget()->lifetime()); + + _bar->widget()->move(0, 0); + _bar->widget()->show(); + _wrap.entity()->resize(_wrap.entity()->width(), _bar->widget()->height()); + + _wrap.geometryValue( + ) | rpl::start_with_next([=](QRect rect) { + _shadow->setGeometry( + rect.x(), + rect.y() + rect.height(), + rect.width(), + st::lineWidth); + _bar->widget()->resizeToWidth( + rect.width() - (_close ? _close->width() : 0)); + const auto hidden = _wrap.isHidden() || !rect.height(); + if (_shadow->isHidden() != hidden) { + _shadow->setVisible(!hidden); + } + if (_close) { + _close->moveToRight(0, 0); + } + }, _bar->widget()->lifetime()); + + _wrap.shownValue( + ) | rpl::skip( + 1 + ) | rpl::filter([=](bool shown) { + return !shown && !_forceHidden; + }) | rpl::start_with_next([=] { + _bar = nullptr; + }, _bar->widget()->lifetime()); + + Ensures(_bar != nullptr); +} + +void PinnedBar::show() { + if (!_forceHidden) { + return; + } + _forceHidden = false; + if (_shouldBeShown) { + _wrap.show(anim::type::instant); + _shadow->show(); + } +} + +void PinnedBar::hide() { + if (_forceHidden) { + return; + } + _forceHidden = true; + _wrap.hide(anim::type::instant); + _shadow->hide(); +} + +void PinnedBar::raise() { + _wrap.raise(); + _shadow->raise(); +} + +void PinnedBar::move(int x, int y) { + _wrap.move(x, y); +} + +void PinnedBar::resizeToWidth(int width) { + _wrap.entity()->resizeToWidth(width); +} + +int PinnedBar::height() const { + return !_forceHidden + ? _wrap.height() + : _shouldBeShown + ? st::historyReplyHeight + : 0; +} + +rpl::producer PinnedBar::heightValue() const { + return _wrap.heightValue(); +} + +rpl::producer<> PinnedBar::closeClicks() const { + return !_close + ? (rpl::never<>() | rpl::type_erased()) + : (_close->clicks() | rpl::map([] { return rpl::empty_value(); })); +} + +rpl::producer<> PinnedBar::barClicks() const { + return _barClicks.events(); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/pinned_bar.h b/Telegram/SourceFiles/ui/chat/pinned_bar.h new file mode 100644 index 0000000000..dca2ee50ea --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/pinned_bar.h @@ -0,0 +1,54 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once +#include "ui/wrap/slide_wrap.h" + +namespace Ui { + +struct MessageBarContent; +class MessageBar; +class IconButton; +class PlainShadow; + +class PinnedBar final { +public: + PinnedBar( + not_null parent, + rpl::producer content, + bool withClose = false); + ~PinnedBar(); + + void show(); + void hide(); + void raise(); + + void move(int x, int y); + void resizeToWidth(int width); + [[nodiscard]] int height() const; + [[nodiscard]] rpl::producer heightValue() const; + [[nodiscard]] rpl::producer<> closeClicks() const; + [[nodiscard]] rpl::producer<> barClicks() const; + + [[nodiscard]] rpl::lifetime &lifetime() { + return _wrap.lifetime(); + } + +private: + void createControls(); + + Ui::SlideWrap<> _wrap; + std::unique_ptr _bar; + std::unique_ptr _close; + std::unique_ptr _shadow; + rpl::event_stream<> _barClicks; + bool _shouldBeShown = false; + bool _forceHidden = false; + +}; + +} // namespace Ui diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 086a78e73d..9c41becf5f 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -34,6 +34,8 @@ PRIVATE ui/ui_pch.h ui/chat/message_bar.cpp ui/chat/message_bar.h + ui/chat/pinned_bar.cpp + ui/chat/pinned_bar.h ui/text/format_values.cpp ui/text/format_values.h ui/text/text_options.cpp