diff --git a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp index 1e6ab6206b..45ad6b1931 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp @@ -93,11 +93,12 @@ void DefaultIconEmoji::paint(QPainter &p, const Context &context) { const auto &st = (_tag == Data::CustomEmojiSizeTag::Normal) ? st::normalForumTopicIcon : st::defaultForumTopicIcon; + const auto general = Data::IsForumGeneralIconTitle(_icon.title); if (_image.isNull()) { - _image = Data::IsForumGeneralIconTitle(_icon.title) + _image = general ? Data::ForumTopicGeneralIconFrame( st.size, - Data::ParseForumGeneralIconColor(_icon.colorId)) + QColor(255, 255, 255)) : Data::ForumTopicIconFrame(_icon.colorId, _icon.title, st); } const auto full = (_tag == Data::CustomEmojiSizeTag::Normal) @@ -106,7 +107,9 @@ void DefaultIconEmoji::paint(QPainter &p, const Context &context) { const auto esize = full / style::DevicePixelRatio(); const auto customSize = Ui::Text::AdjustCustomEmojiSize(esize); const auto skip = (customSize - st.size) / 2; - p.drawImage(context.position + QPoint(skip, skip), _image); + p.drawImage(context.position + QPoint(skip, skip), general + ? style::colorizeImage(_image, context.textColor) + : _image); } void DefaultIconEmoji::unload() { diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 51f77db85e..6684631180 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -408,6 +408,11 @@ void ChannelData::setPendingRequestsCount( } } +bool ChannelData::useSubsectionTabs() const { + return isForum() + && ((flags() & ChannelDataFlag::ForumTabs) || true); AssertIsDebug(); +} + ChatRestrictionsInfo ChannelData::KickedRestrictedRights( not_null participant) { using Flag = ChatRestriction; diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index b0362f1d8c..776edb0aa5 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -279,6 +279,7 @@ public: [[nodiscard]] bool paidMessagesAvailable() const { return flags() & Flag::PaidMessagesAvailable; } + [[nodiscard]] bool useSubsectionTabs() const; [[nodiscard]] static ChatRestrictionsInfo KickedRestrictedRights( not_null participant); diff --git a/Telegram/SourceFiles/data/data_forum_topic.cpp b/Telegram/SourceFiles/data/data_forum_topic.cpp index 12232d8538..799190714d 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.cpp +++ b/Telegram/SourceFiles/data/data_forum_topic.cpp @@ -152,10 +152,10 @@ QImage ForumTopicGeneralIconFrame(int size, const QColor &color) { result.setDevicePixelRatio(ratio); result.fill(Qt::transparent); - const auto use = size * 0.8; - const auto skip = size * 0.1; + const auto use = size * 1.; + const auto skip = size * 0.; auto p = QPainter(&result); - svg.render(&p, QRectF(skip, 0, use, use)); + svg.render(&p, QRectF(skip, skip, use, use)); p.end(); return style::colorizeImage(result, color); diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index a5c63d5e35..babbf4027a 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -500,8 +500,8 @@ dialogsLoadMoreLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) } dialogsSearchInHeight: 38px; -dialogsSearchInPhotoSize: 26px; -dialogsSearchInPhotoPadding: 12px; +dialogsSearchInPhotoSize: 28px; +dialogsSearchInPhotoPadding: 10px; dialogsSearchInSkip: 10px; dialogsSearchInNameTop: 9px; dialogsSearchInDownTop: 15px; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 84b340b7bd..9892c01598 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -4218,12 +4218,20 @@ void InnerWidget::updateSearchIn() { : _openedForum ? _openedForum->channel().get() : nullptr; + const auto paused = [window = _controller] { + return window->isGifPausedAtLeastFor(Window::GifPauseReason::Any); + }; + const auto textFg = [] { + return st::windowSubTextFg->c; + }; const auto topicIcon = !topic ? nullptr : topic->iconId() ? Ui::MakeEmojiThumbnail( &topic->owner(), - Data::SerializeCustomEmojiId(topic->iconId())) + Data::SerializeCustomEmojiId(topic->iconId()), + paused, + textFg) : Ui::MakeEmojiThumbnail( &topic->owner(), Data::TopicIconEmojiEntity({ @@ -4233,7 +4241,9 @@ void InnerWidget::updateSearchIn() { .colorId = (topic->isGeneral() ? Data::ForumGeneralIconColor(st::windowSubTextFg->c) : topic->colorId()), - })); + }), + paused, + textFg); const auto peerIcon = peer ? Ui::MakeUserpicThumbnail(peer) : sublist diff --git a/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp b/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp index cdc024c3d5..a4b4e64700 100644 --- a/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp +++ b/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "lang/lang_keys.h" #include "main/main_session.h" +#include "ui/controls/subsection_tabs_slider.h" #include "ui/effects/ripple_animation.h" #include "ui/text/text_utilities.h" #include "ui/widgets/buttons.h" @@ -36,284 +37,6 @@ namespace HistoryView { namespace { constexpr auto kDefaultLimit = 5; AssertIsDebug()// 10; -constexpr auto kMaxNameLines = 3; - -class VerticalSlider final : public Ui::RpWidget { -public: - explicit VerticalSlider(not_null parent); - - struct Section { - std::shared_ptr userpic; - QString text; - }; - - void setSections(std::vector
sections, Fn paused); - void setActiveSectionFast(int active); - - void fitHeightToSections(); - - [[nodiscard]] rpl::producer sectionActivated() const { - return _sectionActivated.events(); - } - - [[nodiscard]] int sectionsCount() const; - [[nodiscard]] int lookupSectionTop(int index) const; - -private: - struct Tab { - std::shared_ptr userpic; - Ui::Text::String text; - std::unique_ptr ripple; - int top = 0; - int height = 0; - bool subscribed = false; - }; - struct Range { - int top = 0; - int height = 0; - }; - - void paintEvent(QPaintEvent *e) override; - void timerEvent(QTimerEvent *e) override; - void mousePressEvent(QMouseEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; - - void startRipple(int index); - [[nodiscard]] int getIndexFromPosition(QPoint position) const; - [[nodiscard]] QImage prepareRippleMask(int index, const Tab &tab); - - void activateCallback(); - [[nodiscard]] Range getFinalActiveRange() const; - - const style::ChatTabsVertical &_st; - Ui::RoundRect _bar; - std::vector _tabs; - int _active = -1; - int _pressed = -1; - Ui::Animations::Simple _activeTop; - Ui::Animations::Simple _activeHeight; - - int _timerId = -1; - crl::time _callbackAfterMs = 0; - - rpl::event_stream _sectionActivated; - Fn _paused; - -}; - -VerticalSlider::VerticalSlider(not_null parent) -: RpWidget(parent) -, _st(st::chatTabsVertical) -, _bar(_st.barRadius, _st.barFg) { - setCursor(style::cur_pointer); -} - -void VerticalSlider::setSections( - std::vector
sections, - Fn paused) { - auto old = base::take(_tabs); - _tabs.reserve(sections.size()); - - for (auto §ion : sections) { - const auto i = ranges::find(old, section.userpic, &Tab::userpic); - if (i != end(old)) { - _tabs.push_back(std::move(*i)); - old.erase(i); - } else { - _tabs.push_back({ .userpic = std::move(section.userpic), }); - } - _tabs.back().text = Ui::Text::String( - _st.nameStyle, - section.text, - kDefaultTextOptions, - _st.nameWidth); - } - for (const auto &was : old) { - if (was.subscribed) { - was.userpic->subscribeToUpdates(nullptr); - } - } -} - -void VerticalSlider::setActiveSectionFast(int active) { - _active = active; - _activeTop.stop(); - _activeHeight.stop(); -} - -void VerticalSlider::fitHeightToSections() { - auto top = 0; - for (auto &tab : _tabs) { - tab.top = top; - tab.height = _st.baseHeight + std::min( - _st.nameStyle.font->height * kMaxNameLines, - tab.text.countHeight(_st.nameWidth, true)); - top += tab.height; - } - resize(_st.width, top); -} - -int VerticalSlider::sectionsCount() const { - return int(_tabs.size()); -} - -int VerticalSlider::lookupSectionTop(int index) const { - Expects(index >= 0 && index < _tabs.size()); - - return _tabs[index].top; -} - -VerticalSlider::Range VerticalSlider::getFinalActiveRange() const { - return (_active >= 0) - ? Range{ _tabs[_active].top, _tabs[_active].height } - : Range(); -} - -void VerticalSlider::paintEvent(QPaintEvent *e) { - const auto finalRange = getFinalActiveRange(); - const auto range = Range{ - int(base::SafeRound(_activeTop.value(finalRange.top))), - int(base::SafeRound(_activeHeight.value(finalRange.height))), - }; - - auto p = QPainter(this); - auto clip = e->rect(); - const auto drawRect = [&](QRect rect) { - _bar.paint(p, rect); - }; - const auto nameLeft = (_st.width - _st.nameWidth) / 2; - for (auto &tab : _tabs) { - if (!clip.intersects(QRect(0, tab.top, width(), tab.height))) { - continue; - } - const auto divider = std::max(std::min(tab.height, range.height), 1); - const auto active = 1. - - std::clamp( - std::abs(range.top - tab.top) / float64(divider), - 0., - 1.); - if (tab.ripple) { - const auto color = anim::color( - _st.rippleBg, - _st.rippleBgActive, - active); - tab.ripple->paint(p, 0, tab.top, width(), &color); - if (tab.ripple->empty()) { - tab.ripple.reset(); - } - } - - if (!tab.subscribed) { - tab.subscribed = true; - tab.userpic->subscribeToUpdates([=] { update(); }); - } - const auto &image = tab.userpic->image(_st.userpicSize); - const auto userpicLeft = (width() - _st.userpicSize) / 2; - p.drawImage(userpicLeft, tab.top + _st.userpicTop, image); - p.setPen(anim::pen(_st.nameFg, _st.nameFgActive, active)); - tab.text.draw(p, { - .position = QPoint(nameLeft, tab.top + _st.nameTop), - .outerWidth = width(), - .availableWidth = _st.nameWidth, - .align = style::al_top, - .paused = _paused && _paused(), - }); - } - if (range.height > 0) { - const auto add = _st.barStroke / 2; - drawRect(myrtlrect(-add, range.top, _st.barStroke, range.height)); - } -} - -void VerticalSlider::timerEvent(QTimerEvent *e) { - activateCallback(); -} - -void VerticalSlider::startRipple(int index) { - if (!_st.ripple.showDuration) { - return; - } - auto &tab = _tabs[index]; - if (!tab.ripple) { - auto mask = prepareRippleMask(index, tab); - tab.ripple = std::make_unique( - _st.ripple, - std::move(mask), - [this] { update(); }); - } - const auto point = mapFromGlobal(QCursor::pos()); - tab.ripple->add(point - QPoint(0, tab.top)); -} - -QImage VerticalSlider::prepareRippleMask(int index, const Tab &tab) { - return Ui::RippleAnimation::RectMask(QSize(width(), tab.height)); -} - -int VerticalSlider::getIndexFromPosition(QPoint position) const { - const auto count = int(_tabs.size()); - for (auto i = 0; i != count; ++i) { - const auto &tab = _tabs[i]; - if (position.y() < tab.top + tab.height) { - return i; - } - } - return count - 1; -} - -void VerticalSlider::mousePressEvent(QMouseEvent *e) { - for (auto i = 0, count = int(_tabs.size()); i != count; ++i) { - auto &tab = _tabs[i]; - if (tab.top <= e->y() && e->y() < tab.top + tab.height) { - startRipple(i); - _pressed = i; - break; - } - } -} - -void VerticalSlider::mouseReleaseEvent(QMouseEvent *e) { - const auto pressed = std::exchange(_pressed, -1); - if (pressed < 0) { - return; - } - - const auto index = getIndexFromPosition(e->pos()); - if (pressed < _tabs.size()) { - if (_tabs[pressed].ripple) { - _tabs[pressed].ripple->lastStop(); - } - } - if (index == pressed) { - if (_active != index) { - _callbackAfterMs = crl::now() + _st.duration; - activateCallback(); - - const auto from = getFinalActiveRange(); - _active = index; - const auto to = getFinalActiveRange(); - const auto updater = [this] { update(); }; - _activeTop.start(updater, from.top, to.top, _st.duration); - _activeHeight.start( - updater, - from.height, - to.height, - _st.duration); - } - } -} - -void VerticalSlider::activateCallback() { - if (_timerId >= 0) { - killTimer(_timerId); - _timerId = -1; - } - auto ms = crl::now(); - if (ms >= _callbackAfterMs) { - _sectionActivated.fire_copy(_active); - } else { - _timerId = startTimer(_callbackAfterMs - ms, Qt::PreciseTimer); - } -} } // namespace @@ -370,18 +93,13 @@ void SubsectionTabs::setupHorizontal(not_null parent) { st::chatTabsScroll, true); scroll->show(); - const auto tabs = scroll->setOwnedWidget( - object_ptr(scroll, st::chatTabsSlider)); - tabs->sectionActivated() | rpl::start_with_next([=](int active) { - if (active >= 0 - && active < _slice.size() - && _active != _slice[active]) { - auto params = Window::SectionShow(); - params.way = Window::SectionShow::Way::ClearStack; - params.animated = anim::type::instant; - _controller->showThread(_slice[active], {}, params); - } - }, tabs->lifetime()); + const auto slider = scroll->setOwnedWidget( + object_ptr(scroll)); + setupSlider(scroll, slider, false); + + _horizontal->resize( + _horizontal->width(), + std::max(toggle->height(), slider->height())); scroll->setCustomWheelProcess([=](not_null e) { const auto pixelDelta = e->pixelDelta(); @@ -394,36 +112,6 @@ void SubsectionTabs::setupHorizontal(not_null parent) { return true; }); - rpl::merge( - scroll->scrolls(), - _scrollCheckRequests.events(), - scroll->widthValue() | rpl::skip(1) | rpl::map_to(rpl::empty) - ) | rpl::start_with_next([=] { - const auto width = scroll->width(); - const auto left = scroll->scrollLeft(); - const auto max = scroll->scrollLeftMax(); - const auto availableLeft = left; - const auto availableRight = (max - left); - if (max <= 2 * width && _afterAvailable > 0) { - _beforeLimit *= 2; - _afterLimit *= 2; - } - if (availableLeft < width - && _beforeSkipped.value_or(0) > 0 - && !_slice.empty()) { - _around = _slice.front(); - refreshSlice(); - } else if (availableRight < width) { - if (_afterAvailable > 0) { - _around = _slice.back(); - refreshSlice(); - } else if (!_afterSkipped.has_value()) { - _loading = true; - loadMore(); - } - } - }, _horizontal->lifetime()); - _horizontal->sizeValue( ) | rpl::start_with_next([=](QSize size) { const auto togglew = toggle->width(); @@ -438,89 +126,6 @@ void SubsectionTabs::setupHorizontal(not_null parent) { { 0, 0, 0, st::lineWidth })), st::windowBg); }, _horizontal->lifetime()); - - _refreshed.events_starting_with_copy( - rpl::empty - ) | rpl::start_with_next([=] { - auto sections = std::vector(); - const auto manager = &_history->owner().customEmojiManager(); - auto activeIndex = -1; - for (const auto &thread : _slice) { - if (thread == _active) { - activeIndex = int(sections.size()); - } - if (const auto topic = thread->asTopic()) { - sections.push_back(topic->titleWithIcon()); - } else if (const auto sublist = thread->asSublist()) { - const auto peer = sublist->sublistPeer(); - sections.push_back(TextWithEntities().append( - Ui::Text::SingleCustomEmoji( - manager->peerUserpicEmojiData(peer), - u"@"_q) - ).append(' ').append(peer->shortName())); - } else { - sections.push_back(tr::lng_filters_all_short( - tr::now, - Ui::Text::WithEntities)); - } - } - const auto paused = [=] { - return _controller->isGifPausedAtLeastFor( - Window::GifPauseReason::Any); - }; - - auto scrollSavingThread = (Data::Thread*)nullptr; - auto scrollSavingShift = 0; - auto scrollSavingIndex = -1; - if (const auto count = tabs->sectionsCount()) { - const auto scrollLeft = scroll->scrollLeft(); - auto indexLeft = tabs->lookupSectionLeft(0); - for (auto index = 0; index != count; ++index) { - const auto nextLeft = (index + 1 != count) - ? tabs->lookupSectionLeft(index + 1) - : (indexLeft + scrollLeft + 1); - if (indexLeft <= scrollLeft && nextLeft > scrollLeft) { - scrollSavingThread = _sectionsSlice[index]; - scrollSavingShift = scrollLeft - indexLeft; - break; - } - indexLeft = nextLeft; - } - scrollSavingIndex = scrollSavingThread - ? int(ranges::find(_slice, not_null(scrollSavingThread)) - - begin(_slice)) - : -1; - if (scrollSavingIndex == _slice.size()) { - scrollSavingIndex = -1; - for (auto index = 0; index != count; ++index) { - const auto thread = _sectionsSlice[index]; - if (ranges::contains(_slice, thread)) { - scrollSavingThread = thread; - scrollSavingShift = scrollLeft - - tabs->lookupSectionLeft(index); - scrollSavingIndex = index; - break; - } - } - } - } - - tabs->setSections(sections, Core::TextContext({ - .session = &_history->session(), - }), paused); - tabs->fitWidthToSections(); - tabs->setActiveSectionFast(activeIndex); - _sectionsSlice = _slice; - _horizontal->resize( - _horizontal->width(), - std::max(toggle->height(), tabs->height())); - if (scrollSavingIndex >= 0) { - scroll->scrollToX(tabs->lookupSectionLeft(scrollSavingIndex) - + scrollSavingShift); - } - - _scrollCheckRequests.fire({}); - }, _horizontal->lifetime()); } void SubsectionTabs::setupVertical(not_null parent) { @@ -547,48 +152,14 @@ void SubsectionTabs::setupVertical(not_null parent) { _vertical, st::chatTabsScroll); scroll->show(); - const auto tabs = scroll->setOwnedWidget( - object_ptr(scroll)); - tabs->sectionActivated() | rpl::start_with_next([=](int active) { - if (active >= 0 - && active < _slice.size() - && _active != _slice[active]) { - auto params = Window::SectionShow(); - params.way = Window::SectionShow::Way::ClearStack; - params.animated = anim::type::instant; - _controller->showThread(_slice[active], {}, params); - } - }, tabs->lifetime()); - rpl::merge( - scroll->scrolls(), - _scrollCheckRequests.events(), - scroll->heightValue() | rpl::skip(1) | rpl::map_to(rpl::empty) - ) | rpl::start_with_next([=] { - const auto height = scroll->height(); - const auto top = scroll->scrollTop(); - const auto max = scroll->scrollTopMax(); - const auto availableTop = top; - const auto availableBottom = (max - top); - if (max <= 2 * height && _afterAvailable > 0) { - _beforeLimit *= 2; - _afterLimit *= 2; - } - if (availableTop < height - && _beforeSkipped.value_or(0) > 0 - && !_slice.empty()) { - _around = _slice.front(); - refreshSlice(); - } else if (availableBottom < height) { - if (_afterAvailable > 0) { - _around = _slice.back(); - refreshSlice(); - } else if (!_afterSkipped.has_value()) { - _loading = true; - loadMore(); - } - } - }, _vertical->lifetime()); + const auto slider = scroll->setOwnedWidget( + object_ptr(scroll)); + setupSlider(scroll, slider, true); + + _vertical->resize( + std::max(toggle->width(), slider->width()), + _vertical->height()); _vertical->sizeValue( ) | rpl::start_with_next([=](QSize size) { @@ -600,61 +171,149 @@ void SubsectionTabs::setupVertical(not_null parent) { _vertical->paintRequest() | rpl::start_with_next([=](QRect clip) { QPainter(_vertical).fillRect(clip, st::windowBg); }, _vertical->lifetime()); +} + +void SubsectionTabs::setupSlider( + not_null scroll, + not_null slider, + bool vertical) { + slider->sectionActivated() | rpl::start_with_next([=](int active) { + if (active >= 0 + && active < _slice.size() + && _active != _slice[active]) { + auto params = Window::SectionShow(); + params.way = Window::SectionShow::Way::ClearStack; + params.animated = anim::type::instant; + _controller->showThread(_slice[active], {}, params); + } + }, slider->lifetime()); + + rpl::merge( + scroll->scrolls(), + _scrollCheckRequests.events(), + scroll->heightValue() | rpl::skip(1) | rpl::map_to(rpl::empty) + ) | rpl::start_with_next([=] { + const auto full = vertical ? scroll->height() : scroll->width(); + const auto scrollValue = vertical + ? scroll->scrollTop() + : scroll->scrollLeft(); + const auto scrollMax = vertical + ? scroll->scrollTopMax() + : scroll->scrollLeftMax(); + const auto availableFrom = scrollValue; + const auto availableTill = (scrollMax - scrollValue); + if (scrollMax <= 2 * full && _afterAvailable > 0) { + _beforeLimit *= 2; + _afterLimit *= 2; + } + if (availableFrom < full + && _beforeSkipped.value_or(0) > 0 + && !_slice.empty()) { + _around = _slice.front(); + refreshSlice(); + } else if (availableTill < full) { + if (_afterAvailable > 0) { + _around = _slice.back(); + refreshSlice(); + } else if (!_afterSkipped.has_value()) { + _loading = true; + loadMore(); + } + } + }, scroll->lifetime()); _refreshed.events_starting_with_copy( rpl::empty ) | rpl::start_with_next([=] { - auto sections = std::vector(); - auto activeIndex = -1; - for (const auto &thread : _slice) { - if (thread == _active) { - activeIndex = int(sections.size()); - } - if (const auto topic = thread->asTopic()) { - sections.push_back({ - .userpic = (topic->iconId() - ? Ui::MakeEmojiThumbnail( - &topic->owner(), - Data::SerializeCustomEmojiId(topic->iconId())) - : Ui::MakeUserpicThumbnail( - _controller->session().user())), - .text = topic->title(), - }); - } else if (const auto sublist = thread->asSublist()) { - const auto peer = sublist->sublistPeer(); - sections.push_back({ - .userpic = Ui::MakeUserpicThumbnail(peer), - .text = peer->shortName(), - }); - } else { - sections.push_back({ - .userpic = Ui::MakeUserpicThumbnail( - _controller->session().user()), - .text = tr::lng_filters_all_short(tr::now), - }); - } - } + const auto manager = &_history->owner().customEmojiManager(); const auto paused = [=] { return _controller->isGifPausedAtLeastFor( Window::GifPauseReason::Any); }; + auto sections = std::vector(); + auto activeIndex = -1; + for (const auto &thread : _slice) { + const auto index = int(sections.size()); + if (thread == _active) { + activeIndex = index; + } + const auto textFg = [=] { + return anim::color( + st::windowSubTextFg, + st::windowActiveTextFg, + slider->buttonActive(slider->buttonAt(index))); + }; + if (const auto topic = thread->asTopic()) { + if (vertical) { + sections.push_back({ + .text = { topic->title() }, + .userpic = (topic->iconId() + ? Ui::MakeEmojiThumbnail( + &topic->owner(), + Data::SerializeCustomEmojiId(topic->iconId()), + paused, + textFg) + : Ui::MakeEmojiThumbnail( + &topic->owner(), + Data::TopicIconEmojiEntity({ + .title = (topic->isGeneral() + ? Data::ForumGeneralIconTitle() + : topic->title()), + .colorId = (topic->isGeneral() + ? Data::ForumGeneralIconColor( + st::windowSubTextFg->c) + : topic->colorId()), + }), + paused, + textFg)), + }); + } else { + sections.push_back({ + .text = topic->titleWithIcon(), + }); + } + } else if (const auto sublist = thread->asSublist()) { + const auto peer = sublist->sublistPeer(); + if (vertical) { + sections.push_back({ + .text = peer->shortName(), + .userpic = Ui::MakeUserpicThumbnail(peer), + }); + } else { + sections.push_back({ + .text = TextWithEntities().append( + Ui::Text::SingleCustomEmoji( + manager->peerUserpicEmojiData(peer), + u"@"_q) + ).append(' ').append(peer->shortName()), + }); + } + } else { + sections.push_back({ + .text = tr::lng_filters_all_short(tr::now), + .userpic = Ui::MakeAllSubsectionsThumbnail(textFg), + }); + } + } auto scrollSavingThread = (Data::Thread*)nullptr; auto scrollSavingShift = 0; auto scrollSavingIndex = -1; - if (const auto count = tabs->sectionsCount()) { - const auto scrollTop = scroll->scrollTop(); - auto indexTop = tabs->lookupSectionTop(0); + if (const auto count = slider->sectionsCount()) { + const auto scrollValue = vertical + ? scroll->scrollTop() + : scroll->scrollLeft(); + auto indexPosition = slider->lookupSectionPosition(0); for (auto index = 0; index != count; ++index) { - const auto nextTop = (index + 1 != count) - ? tabs->lookupSectionTop(index + 1) - : (indexTop + scrollTop + 1); - if (indexTop <= scrollTop && nextTop > scrollTop) { + const auto nextPosition = (index + 1 != count) + ? slider->lookupSectionPosition(index + 1) + : (indexPosition + scrollValue + 1); + if (indexPosition <= scrollValue && nextPosition > scrollValue) { scrollSavingThread = _sectionsSlice[index]; - scrollSavingShift = scrollTop - indexTop; + scrollSavingShift = scrollValue - indexPosition; break; } - indexTop = nextTop; + indexPosition = nextPosition; } scrollSavingIndex = scrollSavingThread ? int(ranges::find(_slice, not_null(scrollSavingThread)) @@ -666,8 +325,8 @@ void SubsectionTabs::setupVertical(not_null parent) { const auto thread = _sectionsSlice[index]; if (ranges::contains(_slice, thread)) { scrollSavingThread = thread; - scrollSavingShift = scrollTop - - tabs->lookupSectionTop(index); + scrollSavingShift = scrollValue + - slider->lookupSectionPosition(index); scrollSavingIndex = index; break; } @@ -675,20 +334,27 @@ void SubsectionTabs::setupVertical(not_null parent) { } } - tabs->setSections(sections, paused); - tabs->fitHeightToSections(); - tabs->setActiveSectionFast(activeIndex); + slider->setSections({ + .tabs = std::move(sections), + .context = Core::TextContext({ + .session = &_history->session(), + }), + }, paused); + slider->setActiveSectionFast(activeIndex); + _sectionsSlice = _slice; - _vertical->resize( - std::max(toggle->width(), tabs->width()), - _vertical->height()); if (scrollSavingIndex >= 0) { - scroll->scrollToY(tabs->lookupSectionTop(scrollSavingIndex) - + scrollSavingShift); + const auto position = scrollSavingShift + + slider->lookupSectionPosition(scrollSavingIndex); + if (vertical) { + scroll->scrollToY(position); + } else { + scroll->scrollToX(position); + } } _scrollCheckRequests.fire({}); - }, _vertical->lifetime()); + }, scroll->lifetime()); } void SubsectionTabs::loadMore() { @@ -910,9 +576,7 @@ bool SubsectionTabs::UsedFor(not_null thread) { return true; } const auto channel = history->peer->asChannel(); - return channel - && channel->isForum() - && ((channel->flags() & ChannelDataFlag::ForumTabs) || true); AssertIsDebug(); + return channel && channel->useSubsectionTabs(); } } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_subsection_tabs.h b/Telegram/SourceFiles/history/view/history_view_subsection_tabs.h index fe5054dbe5..65da19a8b4 100644 --- a/Telegram/SourceFiles/history/view/history_view_subsection_tabs.h +++ b/Telegram/SourceFiles/history/view/history_view_subsection_tabs.h @@ -19,6 +19,8 @@ class SessionController; namespace Ui { class RpWidget; +class ScrollArea; +class SubsectionSlider; } // namespace Ui namespace HistoryView { @@ -60,6 +62,11 @@ private: void loadMore(); [[nodiscard]] rpl::producer<> dataChanged() const; + void setupSlider( + not_null scroll, + not_null slider, + bool vertical); + const not_null _controller; const not_null _history; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 6de023c332..0bf7da504d 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1255,7 +1255,7 @@ newPeerWidth: 320px; swipeBackSize: 150px; chatTabsToggle: IconButton(defaultIconButton) { - width: 56px; + width: 64px; height: 36px; icon: icon {{ "top_bar_profile-flip_horizontal", menuIconFg }}; iconOver: icon {{ "top_bar_profile-flip_horizontal", menuIconFgOver }}; @@ -1285,6 +1285,13 @@ chatTabsSlider: SettingsSlider(defaultSettingsSlider) { ripple: defaultRippleAnimation; } +ChatTabsOutline { + radius: pixels; + stroke: pixels; + fg: color; + skip: pixels; +} + ChatTabsVertical { barStroke: pixels; barRadius: pixels; @@ -1311,16 +1318,26 @@ chatTabsVertical: ChatTabsVertical { nameStyle: TextStyle(defaultTextStyle) { font: font(10px); } - nameWidth: 46px; - nameTop: 46px; + nameWidth: 54px; + nameTop: 42px; nameFg: windowSubTextFg; nameFgActive: lightButtonFg; userpicTop: 8px; - userpicSize: 36px; - baseHeight: 56px; - width: 56px; + userpicSize: 28px; + baseHeight: 50px; + width: 64px; ripple: defaultRippleAnimation; rippleBg: windowBgOver; rippleBgActive: lightButtonBgOver; duration: 150; } + +chatTabsOutlineHorizontal: ChatTabsOutline { + stroke: 8px; + radius: 4px; + fg: sliderBgActive; + skip: 8px; +} + +chatTabsOutlineVertical: ChatTabsOutline(chatTabsOutlineHorizontal) { +} diff --git a/Telegram/SourceFiles/ui/controls/subsection_tabs_slider.cpp b/Telegram/SourceFiles/ui/controls/subsection_tabs_slider.cpp new file mode 100644 index 0000000000..d6bf6b1ca7 --- /dev/null +++ b/Telegram/SourceFiles/ui/controls/subsection_tabs_slider.cpp @@ -0,0 +1,487 @@ +/* +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/controls/subsection_tabs_slider.h" + +#include "base/call_delayed.h" +#include "ui/effects/ripple_animation.h" +#include "ui/dynamic_image.h" +#include "styles/style_chat.h" +#include "styles/style_filter_icons.h" + +namespace Ui { +namespace { + +constexpr auto kMaxNameLines = 3; + +class VerticalButton final : public SubsectionButton { +public: + VerticalButton( + not_null parent, + not_null delegate, + SubsectionTab &&data); + +private: + void paintEvent(QPaintEvent *e) override; + + void dataUpdatedHook() override; + + void updateSize(); + + const style::ChatTabsVertical &_st; + Text::String _text; + bool _subscribed = false; + +}; + +class HorizontalButton final : public SubsectionButton { +public: + HorizontalButton( + not_null parent, + const style::SettingsSlider &st, + not_null delegate, + SubsectionTab &&data); + +private: + void paintEvent(QPaintEvent *e) override; + + void dataUpdatedHook() override; + void updateSize(); + + const style::SettingsSlider &_st; + Text::String _text; + +}; + +VerticalButton::VerticalButton( + not_null parent, + not_null delegate, + SubsectionTab &&data) +: SubsectionButton(parent, delegate, std::move(data)) +, _st(st::chatTabsVertical) +, _text(_st.nameStyle, _data.text, kDefaultTextOptions, _st.nameWidth) { + updateSize(); +} + +void VerticalButton::dataUpdatedHook() { + _text.setMarkedText(_st.nameStyle, _data.text, kDefaultTextOptions); + updateSize(); +} + +void VerticalButton::updateSize() { + resize(_st.width, _st.baseHeight + std::min( + _st.nameStyle.font->height * kMaxNameLines, + _text.countHeight(_st.nameWidth, true))); +} + +void VerticalButton::paintEvent(QPaintEvent *e) { + auto p = QPainter(this); + + const auto active = _delegate->buttonActive(this); + const auto color = anim::color( + _st.rippleBg, + _st.rippleBgActive, + active); + paintRipple(p, QPoint(0, 0), &color); + + if (!_subscribed) { + _subscribed = true; + _data.userpic->subscribeToUpdates([=] { update(); }); + } + const auto &image = _data.userpic->image(_st.userpicSize); + const auto userpicLeft = (width() - _st.userpicSize) / 2; + p.drawImage(userpicLeft, _st.userpicTop, image); + p.setPen(anim::pen(_st.nameFg, _st.nameFgActive, active)); + + const auto textLeft = (width() - _st.nameWidth) / 2; + _text.draw(p, { + .position = QPoint(textLeft, _st.nameTop), + .outerWidth = width(), + .availableWidth = _st.nameWidth, + .align = style::al_top, + .paused = _delegate->buttonPaused(), + }); +} + +HorizontalButton::HorizontalButton( + not_null parent, + const style::SettingsSlider &st, + not_null delegate, + SubsectionTab &&data) +: SubsectionButton(parent, delegate, std::move(data)) +, _st(st) { + dataUpdatedHook(); +} + +void HorizontalButton::updateSize() { + resize(_st.strictSkip + _text.maxWidth(), _st.height); +} + +void HorizontalButton::dataUpdatedHook() { + auto context = _delegate->buttonContext(); + context.repaint = [=] { update(); }; + _text.setMarkedText( + _st.labelStyle, + _data.text, + kDefaultTextOptions, + context); + updateSize(); +} + +void HorizontalButton::paintEvent(QPaintEvent *e) { + auto p = QPainter(this); + const auto active = _delegate->buttonActive(this); + + const auto color = anim::color( + _st.rippleBg, + _st.rippleBgActive, + active); + paintRipple(p, QPoint(0, 0), &color); + + p.setPen(anim::pen(_st.labelFg, _st.labelFgActive, active)); + _text.draw(p, { + .position = QPoint(_st.strictSkip / 2, _st.labelTop), + .outerWidth = width(), + .availableWidth = _text.maxWidth(), + .paused = _delegate->buttonPaused(), + }); +} + +} // namespace + +SubsectionButton::SubsectionButton( + not_null parent, + not_null delegate, + SubsectionTab &&data) +: RippleButton(parent, st::defaultRippleAnimationBgOver) +, _delegate(delegate) +, _data(std::move(data)) { +} + +SubsectionButton::~SubsectionButton() = default; + +void SubsectionButton::setData(SubsectionTab &&data) { + _data = std::move(data); + dataUpdatedHook(); + update(); +} + +DynamicImage *SubsectionButton::userpic() const { + return _data.userpic.get(); +} + +void SubsectionButton::setActiveShown(float64 activeShown) { + if (_activeShown != activeShown) { + _activeShown = activeShown; + update(); + } +} + +SubsectionSlider::SubsectionSlider(not_null parent, bool vertical) +: RpWidget(parent) +, _vertical(vertical) +, _barSt(vertical + ? st::chatTabsOutlineVertical + : st::chatTabsOutlineHorizontal) +, _bar(CreateChild(this)) +, _barRect(_barSt.radius, _barSt.fg) { + setupBar(); +} + +SubsectionSlider::~SubsectionSlider() = default; + +void SubsectionSlider::setupBar() { + _bar->setAttribute(Qt::WA_TransparentForMouseEvents); + sizeValue() | rpl::start_with_next([=](QSize size) { + const auto thickness = _barSt.stroke - (_barSt.stroke / 2); + _bar->setGeometry( + 0, + _vertical ? 0 : (size.height() - thickness), + _vertical ? thickness : size.width(), + _vertical ? size.height() : thickness); + }, _bar->lifetime()); + _bar->paintRequest() | rpl::start_with_next([=](QRect clip) { + const auto start = -_barSt.stroke / 2; + const auto finalRange = getFinalActiveRange(); + const auto currentRange = getCurrentActiveRange(); + const auto from = currentRange.from + _barSt.skip; + const auto size = currentRange.size - 2 * _barSt.skip; + if (size <= 0) { + return; + } + const auto rect = myrtlrect( + _vertical ? start : from, + _vertical ? from : 0, + _vertical ? _barSt.stroke : size, + _vertical ? size : _barSt.stroke); + if (rect.intersects(clip)) { + auto p = QPainter(_bar); + _barRect.paint(p, rect); + } + }, _bar->lifetime()); +} + +void SubsectionSlider::setSections( + SubsectionTabs sections, + Fn paused) { + Expects(!sections.tabs.empty()); + + _context = sections.context; + _paused = std::move(paused); + _fixedCount = sections.fixed; + _pinnedCount = sections.pinned; + _reorderAllowed = sections.reorder; + + auto old = base::take(_tabs); + _tabs.reserve(sections.tabs.size()); + + auto size = 0; + for (auto &data : sections.tabs) { + const auto i = data.userpic + ? ranges::find( + old, + data.userpic.get(), + &SubsectionButton::userpic) + : old.empty() + ? end(old) + : (end(old) - 1); + if (i != end(old)) { + _tabs.push_back(std::move(*i)); + old.erase(i); + _tabs.back()->setData(std::move(data)); + } else { + _tabs.push_back(makeButton(std::move(data))); + _tabs.back()->show(); + } + _tabs.back()->move(_vertical ? 0 : size, _vertical ? size : 0); + + const auto index = int(_tabs.size()) - 1; + _tabs.back()->setClickedCallback([=] { + activate(index); + }); + size += _vertical ? _tabs.back()->height() : _tabs.back()->width(); + } + + if (!_tabs.empty()) { + resize( + _vertical ? _tabs.front()->width() : size, + _vertical ? size : _tabs.front()->height()); + } + + _bar->raise(); +} + +void SubsectionSlider::activate(int index) { + if (_active == index) { + return; + } + const auto old = _active; + const auto was = getFinalActiveRange(); + _active = index; + const auto now = getFinalActiveRange(); + const auto callback = [=] { + _bar->update(); + for (auto i = std::min(old, index); i != std::max(old, index); ++i) { + if (i >= 0 && i < int(_tabs.size())) { + _tabs[i]->update(); + } + } + }; + const auto duration = st::chatTabsSlider.duration; + _activeFrom.start(callback, was.from, now.from, duration); + _activeSize.start(callback, was.size, now.size, duration); + base::call_delayed(duration, this, [=] { + if (_active == index) { + _sectionActivated.fire_copy(index); + } + }); +} + +void SubsectionSlider::setActiveSectionFast(int active) { + Expects(active < int(_tabs.size())); + + _active = active; + _activeFrom.stop(); + _activeSize.stop(); + _bar->update(); +} + +int SubsectionSlider::sectionsCount() const { + return int(_tabs.size()); +} + +rpl::producer SubsectionSlider::sectionActivated() const { + return _sectionActivated.events(); +} + +int SubsectionSlider::lookupSectionPosition(int index) const { + Expects(index >= 0 && index < _tabs.size()); + + return _vertical ? _tabs[index]->y() : _tabs[index]->x(); +} + +void SubsectionSlider::paintEvent(QPaintEvent *e) { +} + +int SubsectionSlider::lookupSectionIndex(QPoint position) const { + Expects(!_tabs.empty()); + + const auto count = sectionsCount(); + if (_vertical) { + for (auto i = 0; i != count; ++i) { + const auto tab = _tabs[i].get(); + if (position.y() < tab->y() + tab->height()) { + return i; + } + } + } else { + for (auto i = 0; i != count; ++i) { + const auto tab = _tabs[i].get(); + if (position.x() < tab->x() + tab->width()) { + return i; + } + } + } + return count - 1; +} + +SubsectionSlider::Range SubsectionSlider::getFinalActiveRange() const { + if (_active < 0 || _active >= _tabs.size()) { + return {}; + } + const auto tab = _tabs[_active].get(); + return Range{ + .from = _vertical ? tab->y() : tab->x(), + .size = _vertical ? tab->height() : tab->width(), + }; +} + +SubsectionSlider::Range SubsectionSlider::getCurrentActiveRange() const { + const auto finalRange = getFinalActiveRange(); + return { + .from = int(base::SafeRound(_activeFrom.value(finalRange.from))), + .size = int(base::SafeRound(_activeSize.value(finalRange.size))), + }; +} + +bool SubsectionSlider::buttonPaused() { + return _paused && _paused(); +} + +float64 SubsectionSlider::buttonActive(not_null button) { + const auto finalRange = getFinalActiveRange(); + const auto currentRange = getCurrentActiveRange(); + const auto from = _vertical ? button->y() : button->x(); + const auto size = _vertical ? button->height() : button->width(); + const auto checkSize = std::min(size, currentRange.size); + return (checkSize > 0) + ? (1. - (std::abs(currentRange.from - from) / float64(checkSize))) + : 0.; +} + +Text::MarkedContext SubsectionSlider::buttonContext() { + return _context; +} + +not_null SubsectionSlider::buttonAt(int index) { + Expects(index >= 0 && index < _tabs.size()); + + return _tabs[index].get(); +} + +VerticalSlider::VerticalSlider(not_null parent) +: SubsectionSlider(parent, true) +, _st(st::chatTabsVertical) { +} + +VerticalSlider::~VerticalSlider() = default; + +std::unique_ptr VerticalSlider::makeButton( + SubsectionTab &&data) { + return std::make_unique( + this, + static_cast(this), + std::move(data)); +} + +HorizontalSlider::HorizontalSlider(not_null parent) +: SubsectionSlider(parent, false) +, _st(st::chatTabsSlider) { +} + +HorizontalSlider::~HorizontalSlider() = default; + +std::unique_ptr HorizontalSlider::makeButton( + SubsectionTab &&data) { + return std::make_unique( + this, + _st, + static_cast(this), + std::move(data)); +} + +std::shared_ptr MakeAllSubsectionsThumbnail( + Fn textColor) { + class Image final : public DynamicImage { + public: + Image(Fn textColor) : _textColor(std::move(textColor)) { + Expects(_textColor != nullptr); + } + + std::shared_ptr clone() { + return std::make_shared(_textColor); + } + + QImage image(int size) { + const auto ratio = style::DevicePixelRatio(); + const auto full = size * ratio; + const auto color = _textColor(); + if (_cache.size() != QSize(full, full)) { + _cache = QImage( + QSize(full, full), + QImage::Format_ARGB32_Premultiplied); + _cache.fill(Qt::TransparentMode); + } else if (_color == color) { + return _cache; + } + _color = color; + if (_mask.isNull()) { + _mask = st::foldersAll.instance(QColor(255, 255, 255)); + } + const auto position = ratio * QPoint( + (size - (_mask.width() / ratio)) / 2, + (size - (_mask.height() / ratio)) / 2); + if (_mask.width() <= full && _mask.height() <= full) { + style::colorizeImage(_mask, color, &_cache, QRect(), position); + } else { + _cache = style::colorizeImage(_mask, color).scaled( + full, + full, + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + _cache.setDevicePixelRatio(ratio); + } + return _cache; + } + void subscribeToUpdates(Fn callback) { + if (!callback) { + _cache = QImage(); + _mask = QImage(); + } + } + + private: + Fn _textColor; + QImage _mask; + QImage _cache; + QColor _color; + + }; + return std::make_shared(std::move(textColor)); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/controls/subsection_tabs_slider.h b/Telegram/SourceFiles/ui/controls/subsection_tabs_slider.h new file mode 100644 index 0000000000..80842d4169 --- /dev/null +++ b/Telegram/SourceFiles/ui/controls/subsection_tabs_slider.h @@ -0,0 +1,163 @@ +/* +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/round_rect.h" +#include "ui/rp_widget.h" +#include "ui/widgets/buttons.h" + +namespace style { +struct ChatTabsVertical; +struct ChatTabsOutline; +} // namespace style + +namespace Ui { + +class DynamicImage; +class RippleAnimation; +class SubsectionButton; + +struct SubsectionTab { + TextWithEntities text; + std::shared_ptr userpic; + int counter = 0; + bool muted = false; + bool mention = false; + bool reaciton = false; +}; + +struct SubsectionTabs { + std::vector tabs; + Text::MarkedContext context; + int fixed = 0; + int pinned = 0; + bool reorder = false; +}; + +class SubsectionButtonDelegate { +public: + virtual bool buttonPaused() = 0; + virtual float64 buttonActive(not_null button) = 0; + virtual Text::MarkedContext buttonContext() = 0; +}; + +class SubsectionButton : public RippleButton { +public: + SubsectionButton( + not_null parent, + not_null delegate, + SubsectionTab &&data); + ~SubsectionButton(); + + void setData(SubsectionTab &&data); + [[nodiscard]] DynamicImage *userpic() const; + + void setActiveShown(float64 activeShown); + +protected: + virtual void dataUpdatedHook() = 0; + + const not_null _delegate; + SubsectionTab _data; + float64 _activeShown = 0.; + +}; + +class SubsectionSlider + : public RpWidget + , public SubsectionButtonDelegate { +public: + ~SubsectionSlider(); + + void setSections( + SubsectionTabs sections, + Fn paused); + void setActiveSectionFast(int active); + + [[nodiscard]] int sectionsCount() const; + [[nodiscard]] rpl::producer sectionActivated() const; + [[nodiscard]] int lookupSectionPosition(int index) const; + + bool buttonPaused() override; + float64 buttonActive(not_null button) override; + Text::MarkedContext buttonContext() override; + [[nodiscard]] not_null buttonAt(int index); + +protected: + struct Range { + int from = 0; + int size = 0; + }; + + SubsectionSlider(not_null parent, bool vertical); + void setupBar(); + + void paintEvent(QPaintEvent *e) override; + + [[nodiscard]] int lookupSectionIndex(QPoint position) const; + [[nodiscard]] Range getFinalActiveRange() const; + [[nodiscard]] Range getCurrentActiveRange() const; + void activate(int index); + + [[nodiscard]] virtual std::unique_ptr makeButton( + SubsectionTab &&data) = 0; + + const bool _vertical = false; + + const style::ChatTabsOutline &_barSt; + RpWidget *_bar = nullptr; + RoundRect _barRect; + + std::vector> _tabs; + int _active = -1; + int _pressed = -1; + Animations::Simple _activeFrom; + Animations::Simple _activeSize; + + //int _buttonIndexHint = 0; + + Text::MarkedContext _context; + int _fixedCount = 0; + int _pinnedCount = 0; + bool _reorderAllowed = false; + + rpl::event_stream _sectionActivated; + Fn _paused; + +}; + +class VerticalSlider final : public SubsectionSlider { +public: + explicit VerticalSlider(not_null parent); + ~VerticalSlider(); + +private: + std::unique_ptr makeButton( + SubsectionTab &&data) override; + + const style::ChatTabsVertical &_st; + +}; + +class HorizontalSlider final : public SubsectionSlider { +public: + explicit HorizontalSlider(not_null parent); + ~HorizontalSlider(); + +private: + std::unique_ptr makeButton( + SubsectionTab &&data) override; + + const style::SettingsSlider &_st; + +}; + +[[nodiscard]] std::shared_ptr MakeAllSubsectionsThumbnail( + Fn textColor); + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp b/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp index f6538867a9..42dec878d3 100644 --- a/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp +++ b/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp @@ -196,7 +196,11 @@ private: class EmojiThumbnail final : public DynamicImage { public: - EmojiThumbnail(not_null owner, const QString &data); + EmojiThumbnail( + not_null owner, + const QString &data, + Fn paused, + Fn textColor); std::shared_ptr clone() override; @@ -207,6 +211,8 @@ private: const not_null _owner; const QString _data; std::unique_ptr _emoji; + Fn _paused; + Fn _textColor; QImage _frame; }; @@ -581,9 +587,13 @@ void IconThumbnail::subscribeToUpdates(Fn callback) { EmojiThumbnail::EmojiThumbnail( not_null owner, - const QString &data) + const QString &data, + Fn paused, + Fn textColor) : _owner(owner) -, _data(data) { +, _data(data) +, _paused(std::move(paused)) +, _textColor(std::move(textColor)) { } void EmojiThumbnail::subscribeToUpdates(Fn callback) { @@ -598,7 +608,11 @@ void EmojiThumbnail::subscribeToUpdates(Fn callback) { } std::shared_ptr EmojiThumbnail::clone() { - return std::make_shared(_owner, _data); + return std::make_shared( + _owner, + _data, + _paused, + _textColor); } QImage EmojiThumbnail::image(int size) { @@ -614,12 +628,16 @@ QImage EmojiThumbnail::image(int size) { } _frame.fill(Qt::transparent); + const auto esize = Text::AdjustCustomEmojiSize( + Emoji::GetSizeLarge() / style::DevicePixelRatio()); + const auto eskip = (size - esize) / 2; + auto p = Painter(&_frame); _emoji->paint(p, { - .textColor = st::windowBoldFg->c, + .textColor = _textColor ? _textColor() : st::windowBoldFg->c, .now = crl::now(), - .position = QPoint(0, 0), - .paused = false, + .position = QPoint(eskip, eskip), + .paused = _paused && _paused(), }); p.end(); @@ -665,8 +683,14 @@ std::shared_ptr MakeIconThumbnail(const style::icon &icon) { std::shared_ptr MakeEmojiThumbnail( not_null owner, - const QString &data) { - return std::make_shared(owner, data); + const QString &data, + Fn paused, + Fn textColor) { + return std::make_shared( + owner, + data, + std::move(paused), + std::move(textColor)); } std::shared_ptr MakePhotoThumbnail( diff --git a/Telegram/SourceFiles/ui/dynamic_thumbnails.h b/Telegram/SourceFiles/ui/dynamic_thumbnails.h index 08ae74052a..6e003bbe35 100644 --- a/Telegram/SourceFiles/ui/dynamic_thumbnails.h +++ b/Telegram/SourceFiles/ui/dynamic_thumbnails.h @@ -33,7 +33,9 @@ class DynamicImage; const style::icon &icon); [[nodiscard]] std::shared_ptr MakeEmojiThumbnail( not_null owner, - const QString &data); + const QString &data, + Fn paused = false, + Fn textColor = nullptr); [[nodiscard]] std::shared_ptr MakePhotoThumbnail( not_null photo, FullMsgId fullId); diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index c6de18b209..f39e17f834 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -386,6 +386,8 @@ PRIVATE ui/controls/send_as_button.h ui/controls/send_button.cpp ui/controls/send_button.h + ui/controls/subsection_tabs_slider.cpp + ui/controls/subsection_tabs_slider.h ui/controls/swipe_handler.cpp ui/controls/swipe_handler.h ui/controls/swipe_handler_data.h