diff --git a/Telegram/Resources/icons/mediaview/mini_close_friends.png b/Telegram/Resources/icons/mediaview/mini_close_friends.png new file mode 100644 index 000000000..5c3072447 Binary files /dev/null and b/Telegram/Resources/icons/mediaview/mini_close_friends.png differ diff --git a/Telegram/Resources/icons/mediaview/mini_close_friends@2x.png b/Telegram/Resources/icons/mediaview/mini_close_friends@2x.png new file mode 100644 index 000000000..82dd44af9 Binary files /dev/null and b/Telegram/Resources/icons/mediaview/mini_close_friends@2x.png differ diff --git a/Telegram/Resources/icons/mediaview/mini_close_friends@3x.png b/Telegram/Resources/icons/mediaview/mini_close_friends@3x.png new file mode 100644 index 000000000..1f024ce73 Binary files /dev/null and b/Telegram/Resources/icons/mediaview/mini_close_friends@3x.png differ diff --git a/Telegram/Resources/icons/mediaview/mini_contacts.png b/Telegram/Resources/icons/mediaview/mini_contacts.png new file mode 100644 index 000000000..bc716cab4 Binary files /dev/null and b/Telegram/Resources/icons/mediaview/mini_contacts.png differ diff --git a/Telegram/Resources/icons/mediaview/mini_contacts@2x.png b/Telegram/Resources/icons/mediaview/mini_contacts@2x.png new file mode 100644 index 000000000..a7df593e8 Binary files /dev/null and b/Telegram/Resources/icons/mediaview/mini_contacts@2x.png differ diff --git a/Telegram/Resources/icons/mediaview/mini_contacts@3x.png b/Telegram/Resources/icons/mediaview/mini_contacts@3x.png new file mode 100644 index 000000000..50c9ff55f Binary files /dev/null and b/Telegram/Resources/icons/mediaview/mini_contacts@3x.png differ diff --git a/Telegram/Resources/icons/mediaview/mini_selected_contacts.png b/Telegram/Resources/icons/mediaview/mini_selected_contacts.png new file mode 100644 index 000000000..c3ca5b06d Binary files /dev/null and b/Telegram/Resources/icons/mediaview/mini_selected_contacts.png differ diff --git a/Telegram/Resources/icons/mediaview/mini_selected_contacts@2x.png b/Telegram/Resources/icons/mediaview/mini_selected_contacts@2x.png new file mode 100644 index 000000000..d12f95496 Binary files /dev/null and b/Telegram/Resources/icons/mediaview/mini_selected_contacts@2x.png differ diff --git a/Telegram/Resources/icons/mediaview/mini_selected_contacts@3x.png b/Telegram/Resources/icons/mediaview/mini_selected_contacts@3x.png new file mode 100644 index 000000000..90860348b Binary files /dev/null and b/Telegram/Resources/icons/mediaview/mini_selected_contacts@3x.png differ diff --git a/Telegram/SourceFiles/media/stories/media_stories_header.cpp b/Telegram/SourceFiles/media/stories/media_stories_header.cpp index ab2a842e9..03f40701f 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_header.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_header.cpp @@ -16,7 +16,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/layers/box_content.h" #include "ui/text/format_values.h" #include "ui/text/text_utilities.h" +#include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" +#include "ui/wrap/fade_wrap.h" #include "ui/painter.h" #include "ui/rp_widget.h" #include "styles/style_media_view.h" @@ -32,6 +34,155 @@ struct Timestamp { TimeId changes = 0; }; +struct PrivacyBadge { + const style::icon *icon = nullptr; + const style::color *bg1 = nullptr; + const style::color *bg2 = nullptr; +}; + +class UserpicBadge final : public Ui::RpWidget { +public: + UserpicBadge( + not_null userpic, + PrivacyBadge badge, + Fn clicked); + +private: + bool eventFilter(QObject *o, QEvent *e) override; + void paintEvent(QPaintEvent *e) override; + + void updateGeometry(); + + const not_null _userpic; + const PrivacyBadge _badgeData; + const std::unique_ptr _clickable; + QRect _badge; + QImage _layer; + bool _grabbing = false; + +}; + +[[nodiscard]] PrivacyBadge LookupPrivacyBadge(Data::StoryPrivacy privacy) { + using namespace Data; + static const auto badges = base::flat_map{ + { StoryPrivacy::CloseFriends, PrivacyBadge{ + &st::storiesBadgeCloseFriends, + &st::historyPeer2UserpicBg, + &st::historyPeer2UserpicBg2, + } }, + { StoryPrivacy::Contacts, PrivacyBadge{ + &st::storiesBadgeContacts, + &st::historyPeer5UserpicBg, + &st::historyPeer5UserpicBg2, + } }, + { StoryPrivacy::SelectedContacts, PrivacyBadge{ + &st::storiesBadgeSelectedContacts, + &st::historyPeer8UserpicBg, + &st::historyPeer8UserpicBg2, + } }, + }; + if (const auto i = badges.find(privacy); i != end(badges)) { + return i->second; + } + return {}; +} + +UserpicBadge::UserpicBadge( + not_null userpic, + PrivacyBadge badge, + Fn clicked) +: RpWidget(userpic->parentWidget()) +, _userpic(userpic) +, _badgeData(badge) +, _clickable(std::make_unique(parentWidget())) { + _clickable->setClickedCallback(std::move(clicked)); + userpic->installEventFilter(this); + updateGeometry(); + setAttribute(Qt::WA_TransparentForMouseEvents); + Ui::PostponeCall(this, [=] { + _userpic->raise(); + }); + show(); +} + +bool UserpicBadge::eventFilter(QObject *o, QEvent *e) { + if (o != _userpic) { + return false; + } + const auto type = e->type(); + switch (type) { + case QEvent::Move: + case QEvent::Resize: + updateGeometry(); + return false; + case QEvent::Paint: + return !_grabbing; + } + return false; +} + +void UserpicBadge::paintEvent(QPaintEvent *e) { + const auto ratio = style::DevicePixelRatio(); + const auto layerSize = size() * ratio; + if (_layer.size() != layerSize) { + _layer = QImage(layerSize, QImage::Format_ARGB32_Premultiplied); + _layer.setDevicePixelRatio(ratio); + } + _layer.fill(Qt::transparent); + auto q = QPainter(&_layer); + + _grabbing = true; + Ui::RenderWidget(q, _userpic); + _grabbing = false; + + auto hq = PainterHighQualityEnabler(q); + auto pen = st::transparent->p; + pen.setWidthF(st::storiesBadgeOutline); + const auto half = st::storiesBadgeOutline / 2.; + auto outer = QRectF(_badge).marginsAdded({ half, half, half, half }); + auto gradient = QLinearGradient(outer.topLeft(), outer.bottomLeft()); + gradient.setStops({ + { 0., (*_badgeData.bg1)->c }, + { 1., (*_badgeData.bg2)->c }, + }); + q.setPen(pen); + q.setBrush(gradient); + q.setCompositionMode(QPainter::CompositionMode_Source); + q.drawEllipse(outer); + q.setCompositionMode(QPainter::CompositionMode_SourceOver); + _badgeData.icon->paintInCenter(q, _badge); + q.end(); + + QPainter(this).drawImage(0, 0, _layer); +} + +void UserpicBadge::updateGeometry() { + const auto width = _userpic->width() + st::storiesBadgeShift.x(); + const auto height = _userpic->height() + st::storiesBadgeShift.y(); + setGeometry(QRect(_userpic->pos(), QSize{ width, height })); + const auto inner = QRect(QPoint(), _badgeData.icon->size()); + const auto badge = inner.marginsAdded(st::storiesBadgePadding).size(); + _badge = QRect( + QPoint(width - badge.width(), height - badge.height()), + badge); + _clickable->setGeometry(_badge.translated(pos())); + update(); +} + +[[nodiscard]] std::unique_ptr MakePrivacyBadge( + not_null userpic, + Data::StoryPrivacy privacy, + Fn clicked) { + const auto badge = LookupPrivacyBadge(privacy); + if (!badge.icon) { + return nullptr; + } + return std::make_unique( + userpic, + badge, + std::move(clicked)); +} + [[nodiscard]] Timestamp ComposeTimestamp(TimeId when, TimeId now) { const auto minutes = (now - when) / 60; if (!minutes) { @@ -102,42 +253,41 @@ Header::Header(not_null controller) , _dateUpdateTimer([=] { updateDateText(); }) { } -Header::~Header() { -} +Header::~Header() = default; void Header::show(HeaderData data) { if (_data == data) { return; } - const auto nameDataChanged = !_data - || (_data->user != data.user) + const auto userChanged = !_data + || (_data->user != data.user); + const auto nameDataChanged = userChanged + || !_name || (_data->fullCount != data.fullCount) || (data.fullCount && _data->fullIndex != data.fullIndex); _data = data; - if (nameDataChanged) { + if (userChanged) { _date = nullptr; + _name = nullptr; + _userpic = nullptr; + _info = nullptr; + _privacy = nullptr; const auto parent = _controller->wrap(); - auto widget = std::make_unique(parent); + auto widget = std::make_unique(parent); const auto raw = widget.get(); - raw->setClickedCallback([=] { + _info = std::make_unique(raw); + _info->setClickedCallback([=] { _controller->uiShow()->show(PrepareShortInfoBox(_data->user)); }); - const auto userpic = Ui::CreateChild( + _userpic = std::make_unique( raw, data.user, st::storiesHeaderPhoto); - userpic->setAttribute(Qt::WA_TransparentForMouseEvents); - userpic->show(); - userpic->move( + _userpic->setAttribute(Qt::WA_TransparentForMouseEvents); + _userpic->show(); + _userpic->move( st::storiesHeaderMargin.left(), st::storiesHeaderMargin.top()); - const auto name = Ui::CreateChild( - raw, - rpl::single(ComposeName(data)), - st::storiesHeaderName); - name->setAttribute(Qt::WA_TransparentForMouseEvents); - name->setOpacity(kNameOpacity); - name->move(st::storiesHeaderNamePosition); raw->show(); _widget = std::move(widget); @@ -146,6 +296,26 @@ void Header::show(HeaderData data) { raw->setGeometry(layout.header); }, raw->lifetime()); } + if (nameDataChanged) { + _name = std::make_unique( + _widget.get(), + rpl::single(ComposeName(data)), + st::storiesHeaderName); + _name->setAttribute(Qt::WA_TransparentForMouseEvents); + _name->setOpacity(kNameOpacity); + _name->move(st::storiesHeaderNamePosition); + _name->show(); + + rpl::combine( + _name->widthValue(), + _widget->heightValue() + ) | rpl::start_with_next([=](int width, int height) { + if (_date) { + _info->setGeometry( + { 0, 0, std::max(width, _date->width()), height }); + } + }, _name->lifetime()); + } auto timestamp = ComposeDetails(data, base::unixtime::now()); _date = std::make_unique( _widget.get(), @@ -156,6 +326,16 @@ void Header::show(HeaderData data) { _date->show(); _date->move(st::storiesHeaderDatePosition); + _date->widthValue( + ) | rpl::start_with_next([=](int width) { + _info->setGeometry( + { 0, 0, std::max(width, _name->width()), _widget->height() }); + }, _name->lifetime()); + + _privacy = MakePrivacyBadge(_userpic.get(), data.privacy, [=] { + + }); + if (timestamp.changes > 0) { _dateUpdateTimer.callOnce(timestamp.changes * crl::time(1000)); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_header.h b/Telegram/SourceFiles/media/stories/media_stories_header.h index 2b8f6e61f..ae584ed8c 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_header.h +++ b/Telegram/SourceFiles/media/stories/media_stories_header.h @@ -17,6 +17,11 @@ enum class StoryPrivacy : uchar; namespace Ui { class RpWidget; class FlatLabel; +class IconButton; +class AbstractButton; +class UserpicButton; +template +class FadeWrap; } // namespace Ui namespace Media::Stories { @@ -51,7 +56,14 @@ private: const not_null _controller; std::unique_ptr _widget; + std::unique_ptr _info; + std::unique_ptr _userpic; + std::unique_ptr _name; std::unique_ptr _date; + std::unique_ptr _playPause; + std::unique_ptr _volumeToggle; + std::unique_ptr> _volume; + std::unique_ptr _privacy; std::optional _data; base::Timer _dateUpdateTimer; diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index ac5d9e062..097b498cb 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -844,3 +844,10 @@ storiesReportBox: ReportBox(defaultReportBox) { storiesActionToast: Toast(defaultToast) { maxWidth: 320px; } + +storiesBadgeCloseFriends: icon{{ "mediaview/mini_close_friends", historyPeerUserpicFg }}; +storiesBadgeContacts: icon{{ "mediaview/mini_contacts", historyPeerUserpicFg }}; +storiesBadgeSelectedContacts: icon{{ "mediaview/mini_selected_contacts", historyPeerUserpicFg }}; +storiesBadgePadding: margins(1px, 1px, 1px, 1px); +storiesBadgeOutline: 2px; +storiesBadgeShift: point(5px, 4px);