diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index cb98565ea..5e7f85bc6 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3841,6 +3841,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stories_about_close_friends_my" = "Only your list of **Close Friends** can view this story."; "lng_stories_about_contacts_my" = "Only your contacts can view this story."; "lng_stories_about_selected_contacts_my" = "Only some contacts you selected can view this story."; +"lng_stories_click_to_view_mine" = "Click here to view your story."; "lng_stories_click_to_view" = "Click here to view updates from {users}."; "lng_stories_click_to_view_and_one" = "{accumulated}, {user}"; "lng_stories_click_to_view_and_last" = "{accumulated} and {user}"; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index c8f287328..2791724dd 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -1338,13 +1338,7 @@ groupCallNiceTooltip: ImportantTooltip(defaultImportantTooltip) { radius: 4px; arrow: 4px; } -groupCallNiceTooltipLabel: FlatLabel(defaultImportantTooltipLabel) { - style: TextStyle(defaultTextStyle) { - font: font(11px); - linkFont: font(11px); - linkFontOver: font(11px underline); - } -} +groupCallNiceTooltipLabel: defaultImportantTooltipLabel; groupCallStickedTooltip: ImportantTooltip(groupCallNiceTooltip) { padding: margins(10px, 1px, 6px, 3px); } diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index d1e9bfb5a..cef0bd2e8 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -200,7 +200,8 @@ QByteArray Settings::serialize() const { + sizeof(qint32) * 3 + Serialize::bytearraySize(mediaViewPosition) + sizeof(qint32) - + sizeof(quint64); + + sizeof(quint64) + + sizeof(qint32); auto result = QByteArray(); result.reserve(size); @@ -334,7 +335,8 @@ QByteArray Settings::serialize() const { << qint32(_windowTitleContent.current().hideTotalUnread ? 1 : 0) << mediaViewPosition << qint32(_ignoreBatterySaving.current() ? 1 : 0) - << quint64(_macRoundIconDigest.value_or(0)); + << quint64(_macRoundIconDigest.value_or(0)) + << qint32(_storiesClickTooltipHidden.current() ? 1 : 0); } return result; } @@ -440,6 +442,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { QByteArray mediaViewPosition; qint32 ignoreBatterySaving = _ignoreBatterySaving.current() ? 1 : 0; quint64 macRoundIconDigest = _macRoundIconDigest.value_or(0); + qint32 storiesClickTooltipHidden = _storiesClickTooltipHidden.current() ? 1 : 0; stream >> themesAccentColors; if (!stream.atEnd()) { @@ -674,6 +677,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) { if (!stream.atEnd()) { stream >> macRoundIconDigest; } + if (!stream.atEnd()) { + stream >> storiesClickTooltipHidden; + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for Core::Settings::constructFromSerialized()")); @@ -865,6 +871,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { } _ignoreBatterySaving = (ignoreBatterySaving == 1); _macRoundIconDigest = macRoundIconDigest ? macRoundIconDigest : std::optional(); + _storiesClickTooltipHidden = (storiesClickTooltipHidden == 1); } QString Settings::getSoundPath(const QString &key) const { @@ -1155,6 +1162,7 @@ void Settings::resetOnLastLogout() { _tabbedReplacedWithInfo = false; // per-window _systemDarkModeEnabled = false; _hiddenGroupCallTooltips = 0; + _storiesClickTooltipHidden = 0; _recentEmojiPreload.clear(); _recentEmoji.clear(); diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index 111bf6c80..aec34560c 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -802,6 +802,12 @@ public: [[nodiscard]] std::optional macRoundIconDigest() const { return _macRoundIconDigest; } + [[nodiscard]] rpl::producer storiesClickTooltipHiddenValue() const { + return _storiesClickTooltipHidden.value(); + } + void setStoriesClickTooltipHidden(bool value) { + _storiesClickTooltipHidden = value; + } [[nodiscard]] static bool ThirdColumnByDefault(); [[nodiscard]] static float64 DefaultDialogsWidthRatio(); @@ -923,6 +929,7 @@ private: WindowPosition _mediaViewPosition = { .maximized = 2 }; rpl::variable _ignoreBatterySaving = false; std::optional _macRoundIconDigest; + rpl::variable _storiesClickTooltipHidden = false; bool _tabbedReplacedWithInfo = false; // per-window rpl::event_stream _tabbedReplacedWithInfoValue; // per-window diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index c824b8219..0e877db81 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -562,3 +562,6 @@ dialogsStoriesListInfo: DialogsStoriesList(dialogsStoriesList) { dialogsStoriesListMine: DialogsStoriesList(dialogsStoriesListInfo) { readOpacity: 1.; } +dialogsStoriesTooltip: defaultImportantTooltip; +dialogsStoriesTooltipLabel: defaultImportantTooltipLabel; +dialogsStoriesTooltipMaxWidth: 200px; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 1b696ae8d..e2fb50534 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -798,6 +798,15 @@ void Widget::setupStories() { _scroll->viewportEvent(e); }, _stories->lifetime()); + const auto hideTooltip = [=] { + Core::App().settings().setStoriesClickTooltipHidden(true); + Core::App().saveSettingsDelayed(); + }; + _stories->setShowTooltip( + Core::App().settings().storiesClickTooltipHiddenValue( + ) | rpl::map(!rpl::mappers::_1), + hideTooltip); + _storiesContents.fire(Stories::ContentForSession( &controller()->session(), Data::StorySourcesList::NotHidden)); diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp index c3a9ddda3..da710848b 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp @@ -7,14 +7,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "dialogs/ui/dialogs_stories_list.h" +#include "base/event_filter.h" +#include "base/qt_signal_producer.h" #include "lang/lang_keys.h" #include "ui/effects/outline_segments.h" +#include "ui/text/text_utilities.h" #include "ui/widgets/menu/menu_add_action_callback_factory.h" +#include "ui/widgets/labels.h" #include "ui/widgets/popup_menu.h" +#include "ui/widgets/tooltip.h" #include "ui/painter.h" #include "styles/style_dialogs.h" #include +#include #include "base/debug_log.h" @@ -27,6 +33,7 @@ constexpr auto kExpandAfterRatio = 0.72; constexpr auto kCollapseAfterRatio = 0.68; constexpr auto kFrictionRatio = 0.15; constexpr auto kExpandCatchUpDuration = crl::time(200); +constexpr auto kMaxTooltipNames = 3; [[nodiscard]] int AvailableNameWidth(const style::DialogsStoriesList &st) { const auto &full = st.full; @@ -112,6 +119,7 @@ void List::showContent(Content &&content) { _data.items.emplace_back(Item{ .element = element }); } } + _lastCollapsedGeometry = {}; if (int(_data.items.size()) != wasCount) { updateGeometry(); } @@ -120,6 +128,8 @@ void List::showContent(Content &&content) { if (!wasCount) { _empty = false; } + _tooltipText = computeTooltipText(); + updateTooltipGeometry(); } void List::updateScrollMax() { @@ -162,10 +172,16 @@ void List::requestExpanded(bool expanded) { const auto from = _expanded ? 0. : 1.; const auto till = _expanded ? 2. : 0.; const auto duration = (_expanded ? 2 : 1) * st::slideWrapDuration; + if (!isHidden() && _expanded) { + toggleTooltip(false); + } _expandedAnimation.start([=] { checkForFullState(); update(); _collapsedGeometryChanged.fire({}); + if (!isHidden() && !_expandedAnimation.animating()) { + toggleTooltip(false); + } }, from, till, duration, anim::sineInOut); } _toggleExpandedRequests.fire_copy(_expanded); @@ -179,6 +195,12 @@ void List::resizeEvent(QResizeEvent *e) { updateScrollMax(); } +void List::updateExpanding() { + updateExpanding( + _lastExpandedHeight * _expandCatchUpAnimation.value(1.), + _st.full.height); +} + void List::updateExpanding(int expandingHeight, int expandedHeight) { Expects(!expandingHeight || expandedHeight > 0); @@ -196,12 +218,10 @@ void List::updateExpanding(int expandingHeight, int expandedHeight) { if (change) { requestExpanded(!_expanded); } + updateTooltipGeometry(); } List::Layout List::computeLayout() { - updateExpanding( - _lastExpandedHeight * _expandCatchUpAnimation.value(1.), - _st.full.height); return computeLayout(_expandedAnimation.value(_expanded ? 2. : 0.)); } @@ -678,6 +698,9 @@ void List::mousePressEvent(QMouseEvent *e) { return; } else if (_state == State::Small) { requestExpanded(true); + if (const auto onstack = _tooltipHide) { + onstack(); + } return; } else if (_state != State::Full) { return; @@ -757,6 +780,7 @@ void List::setExpandedHeight(int height, bool momentum) { } else if (!momentum && _expandIgnored && height > 0) { _expandIgnored = false; _expandCatchUpAnimation.start([=] { + updateExpanding(); update(); checkForFullState(); }, 0., 1., kExpandCatchUpDuration); @@ -764,6 +788,7 @@ void List::setExpandedHeight(int height, bool momentum) { _expandCatchUpAnimation.stop(); } _lastExpandedHeight = height; + updateExpanding(); if (!checkForFullState()) { setState(!height ? State::Small : State::Changing); } @@ -784,17 +809,177 @@ void List::setLayoutConstraints( QPoint positionSmall, style::align alignSmall, QRect geometryFull) { + if (_positionSmall == positionSmall + && _alignSmall == alignSmall + && _geometryFull == geometryFull) { + return; + } _positionSmall = positionSmall; _alignSmall = alignSmall; _geometryFull = geometryFull; + _lastCollapsedGeometry = {}; updateGeometry(); update(); } +TextWithEntities List::computeTooltipText() const { + const auto &list = _data.items; + if (list.empty()) { + return {}; + } else if (list.size() == 1 && list.front().element.skipSmall) { + return { tr::lng_stories_click_to_view_mine(tr::now) }; + } + auto names = QStringList(); + for (const auto &item : list) { + if (item.element.skipSmall) { + continue; + } + names.append(item.element.name); + if (names.size() >= kMaxTooltipNames) { + break; + } + } + auto sequence = Ui::Text::Bold(names.front()); + if (names.size() > 1) { + for (auto i = 1; i + 1 != names.size(); ++i) { + sequence = tr::lng_stories_click_to_view_and_one( + tr::now, + lt_accumulated, + sequence, + lt_user, + Ui::Text::Bold(names[i]), + Ui::Text::WithEntities); + } + sequence = tr::lng_stories_click_to_view_and_last( + tr::now, + lt_accumulated, + sequence, + lt_user, + Ui::Text::Bold(names.back()), + Ui::Text::WithEntities); + } + return tr::lng_stories_click_to_view( + tr::now, + lt_users, + sequence, + Ui::Text::WithEntities); +} + +void List::setShowTooltip(rpl::producer shown, Fn hide) { + _tooltip = nullptr; + _tooltipHide = std::move(hide); + _tooltipNotHidden = std::move(shown); + _tooltipText = computeTooltipText(); + const auto window = this->window(); + const auto notEmpty = [](const TextWithEntities &text) { + return !text.empty(); + }; + _tooltip = std::make_unique( + window, + Ui::MakeNiceTooltipLabel( + window, + _tooltipText.value() | rpl::filter(notEmpty), + st::dialogsStoriesTooltipMaxWidth, + st::dialogsStoriesTooltipLabel), + st::dialogsStoriesTooltip); + const auto tooltip = _tooltip.get(); + const auto weak = QPointer(tooltip); + tooltip->setAttribute(Qt::WA_TransparentForMouseEvents); + tooltip->toggleFast(false); + updateTooltipGeometry(); + + const auto handle = window->windowHandle(); + auto windowActive = rpl::single( + handle->isActive() + ) | rpl::then(base::qt_signal_producer( + handle, + &QWindow::activeChanged + ) | rpl::map([=] { + return handle->isActive(); + })) | rpl::distinct_until_changed(); + + for (auto parent = parentWidget() + ; parent != window + ; parent = parent->parentWidget()) { + using namespace base; + install_event_filter(parent, tooltip, [=](not_null e) { + if (e->type() == QEvent::Move) { + updateTooltipGeometry(); + } + return EventFilterResult::Continue; + }); + } + + rpl::combine( + _tooltipNotHidden.value(), + _tooltipText.value() | rpl::map( + notEmpty + ) | rpl::distinct_until_changed(), + std::move(windowActive) + ) | rpl::start_with_next([=](bool, bool, bool active) { + _tooltipWindowActive = active; + if (!isHidden()) { + toggleTooltip(false); + } + }, tooltip->lifetime()); + + shownValue( + ) | rpl::skip(1) | rpl::start_with_next([=](bool shown) { + toggleTooltip(true); + }, tooltip->lifetime()); +} + +void List::toggleTooltip(bool fast) { + const auto shown = !_expanded + && !_expandedAnimation.animating() + && !isHidden() + && _tooltipNotHidden.current() + && !_tooltipText.current().empty() + && window()->windowHandle()->isActive(); + if (shown) { + updateTooltipGeometry(); + } + if (fast) { + _tooltip->toggleFast(shown); + } else { + _tooltip->toggleAnimated(shown); + } +} + +void List::updateTooltipGeometry() { + if (!_tooltip || _expanded || _expandedAnimation.animating()) { + return; + } + const auto collapsed = collapsedGeometryCurrent(); + if (collapsed.geometry.isEmpty()) { + int a = 0; + } + const auto geometry = Ui::MapFrom( + window(), + parentWidget(), + QRect( + collapsed.geometry.x(), + collapsed.geometry.y(), + int(std::ceil(collapsed.singleWidth)), + collapsed.geometry.height())); + const auto weak = QPointer(_tooltip.get()); + const auto countPosition = [=](QSize size) { + return QPoint( + geometry.x() + (geometry.width() - size.width()) / 2, + geometry.y() + geometry.height()); + }; + _tooltip->pointAt(geometry, RectPart::Bottom, countPosition); +} + List::CollapsedGeometry List::collapsedGeometryCurrent() const { const auto expanded = _expandedAnimation.value(_expanded ? 2. : 0.); if (expanded >= 1.) { - return { QRect(), 1. }; + const auto single = 2 * _st.full.photoLeft + _st.full.photo; + return { QRect(), 1., float64(single) }; + } else if (_lastCollapsedRatio == _lastRatio + && _lastCollapsedGeometry.expanded == expanded + && !_lastCollapsedGeometry.geometry.isEmpty()) { + return _lastCollapsedGeometry; } const auto layout = computeLayout(0.); const auto small = countSmallGeometry(); @@ -803,10 +988,21 @@ List::CollapsedGeometry List::collapsedGeometryCurrent() const { const auto left = int(base::SafeRound( shift + layout.left + layout.single * index)); const auto width = small.x() + small.width() - left; - return { - QRect(left, small.y(), width, small.height()), + const auto photoTopSmall = _st.small.photoTop; + const auto photoTop = photoTopSmall + + (_st.full.photoTop - photoTopSmall) * layout.expandedRatio; + const auto ySmall = photoTopSmall + + ((photoTop - photoTopSmall) * kSmallThumbsShown / 0.5); + const auto photo = _st.small.photo + + (_st.full.photo - _st.small.photo) * layout.ratio; + const auto top = y() + layout.geometryShift.y(); + _lastCollapsedRatio = _lastRatio; + _lastCollapsedGeometry = { + QRect(left, top, width, ySmall + photo + _st.full.photoTop), expanded, + layout.photoLeft * 2 + photo, }; + return _lastCollapsedGeometry; } rpl::producer<> List::collapsedGeometryChanged() const { @@ -822,6 +1018,7 @@ void List::updateGeometry() { } break; case State::Full: setGeometry(_geometryFull); } + updateTooltipGeometry(); update(); } diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h index 3182d7229..9ecf28394 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h @@ -23,6 +23,7 @@ struct DialogsStoriesList; namespace Ui { class PopupMenu; struct OutlineSegment; +class ImportantTooltip; } // namespace Ui namespace Dialogs::Stories { @@ -72,9 +73,11 @@ public: QPoint positionSmall, style::align alignSmall, QRect geometryFull = QRect()); + void setShowTooltip(rpl::producer shown, Fn hide); struct CollapsedGeometry { QRect geometry; float64 expanded = 0.; + float64 singleWidth = 0.; }; [[nodiscard]] CollapsedGeometry collapsedGeometryCurrent() const; [[nodiscard]] rpl::producer<> collapsedGeometryChanged() const; @@ -142,10 +145,15 @@ private: void checkLoadMore(); void requestExpanded(bool expanded); + void updateTooltipGeometry(); + [[nodiscard]] TextWithEntities computeTooltipText() const; + void toggleTooltip(bool fast); + bool checkForFullState(); void setState(State state); void updateGeometry(); [[nodiscard]] QRect countSmallGeometry() const; + void updateExpanding(); void updateExpanding(int expandingHeight, int expandedHeight); void validateSegments( not_null item, @@ -189,11 +197,20 @@ private: bool _expandIgnored : 1 = false; bool _expanded : 1 = false; + mutable CollapsedGeometry _lastCollapsedGeometry; + mutable float64 _lastCollapsedRatio = 0.; + int _selected = -1; int _pressed = -1; rpl::event_stream> _verticalScrollEvents; + rpl::variable _tooltipText; + rpl::variable _tooltipNotHidden; + Fn _tooltipHide; + std::unique_ptr _tooltip; + bool _tooltipWindowActive = false; + base::unique_qptr _menu; base::has_weak_ptr _menuGuard; diff --git a/Telegram/SourceFiles/media/stories/media_stories_header.cpp b/Telegram/SourceFiles/media/stories/media_stories_header.cpp index 8da578b9b..ac85729ee 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_header.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_header.cpp @@ -315,7 +315,7 @@ void Header::show(HeaderData data) { raw, rpl::single(data.user->isSelf() ? tr::lng_stories_my_name(tr::now) - : data.user->shortName()), + : data.user->name()), st::storiesHeaderName); _name->setAttribute(Qt::WA_TransparentForMouseEvents); _name->setOpacity(kNameOpacity); diff --git a/Telegram/SourceFiles/media/stories/media_stories_header.h b/Telegram/SourceFiles/media/stories/media_stories_header.h index 4c67526e2..c10df3ec2 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_header.h +++ b/Telegram/SourceFiles/media/stories/media_stories_header.h @@ -93,7 +93,7 @@ private: std::unique_ptr _privacy; QRect _privacyBadgeGeometry; std::optional _data; - std::unique_ptr _tooltip = { nullptr }; + std::unique_ptr _tooltip; rpl::variable _tooltipShown = false; QRect _contentGeometry; Tooltip _tooltipType = {}; diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index 2a855721c..49eefe127 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -891,18 +891,6 @@ storiesVolumeSlider: MediaSlider { seekSize: size(12px, 12px); duration: mediaviewOverDuration; } -storiesInfoTooltipLabel: FlatLabel(defaultImportantTooltipLabel) { - style: TextStyle(defaultTextStyle) { - font: font(11px); - linkFont: font(11px); - linkFontOver: font(11px underline); - } - minWidth: 36px; -} -storiesInfoTooltip: ImportantTooltip(defaultImportantTooltip) { - bg: importantTooltipBg; - padding: margins(10px, 3px, 10px, 5px); - radius: 4px; - arrow: 4px; -} +storiesInfoTooltipLabel: defaultImportantTooltipLabel; +storiesInfoTooltip: defaultImportantTooltip; storiesInfoTooltipMaxWidth: 360px; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index bd1e8f7c4..8314fc9b3 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit bd1e8f7c47c3e99493adf9653d684c86a0a51941 +Subproject commit 8314fc9b3f292dd9921ef2bfa1c30bcfd2ed05ed