diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 5a095f698..5c559e8a5 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -962,6 +962,8 @@ PRIVATE media/player/media_player_volume_controller.h media/player/media_player_widget.cpp media/player/media_player_widget.h + media/stories/media_stories_caption_full_view.cpp + media/stories/media_stories_caption_full_view.h media/stories/media_stories_controller.cpp media/stories/media_stories_controller.h media/stories/media_stories_delegate.cpp diff --git a/Telegram/SourceFiles/media/stories/media_stories_caption_full_view.cpp b/Telegram/SourceFiles/media/stories/media_stories_caption_full_view.cpp new file mode 100644 index 000000000..b301ced92 --- /dev/null +++ b/Telegram/SourceFiles/media/stories/media_stories_caption_full_view.cpp @@ -0,0 +1,81 @@ +/* +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 "media/stories/media_stories_caption_full_view.h" + +#include "core/ui_integration.h" +#include "ui/widgets/scroll_area.h" +#include "ui/widgets/labels.h" +#include "styles/style_media_view.h" + +namespace Media::Stories { + +CaptionFullView::CaptionFullView( + not_null parent, + not_null session, + const TextWithEntities &text, + Fn close) +: RpWidget(parent) +, _scroll(std::make_unique((RpWidget*)this)) +, _text(_scroll->setOwnedWidget( + object_ptr>( + _scroll.get(), + object_ptr(_scroll.get(), st::storiesCaptionFull), + st::mediaviewCaptionPadding))->entity()) +, _close(std::move(close)) +, _background(st::storiesRadius, st::mediaviewCaptionBg) { + _text->setMarkedText(text, Core::MarkedTextContext{ + .session = session, + .customEmojiRepaint = [=] { _text->update(); }, + }); + + parent->sizeValue() | rpl::start_with_next([=](QSize size) { + setGeometry(QRect(QPoint(), size)); + }, lifetime()); + + show(); + setFocus(); +} + +CaptionFullView::~CaptionFullView() = default; + +void CaptionFullView::paintEvent(QPaintEvent *e) { + auto p = QPainter(this); + _background.paint(p, _scroll->geometry()); + _background.paint(p, _scroll->geometry()); +} + +void CaptionFullView::resizeEvent(QResizeEvent *e) { + const auto wanted = _text->naturalWidth(); + const auto padding = st::mediaviewCaptionPadding; + const auto margin = st::mediaviewCaptionMargin * 2; + const auto available = (rect() - padding).width() + - (margin.width() * 2); + const auto use = std::min(wanted, available); + _text->resizeToWidth(use); + const auto fullw = use + padding.left() + padding.right(); + const auto fullh = std::min( + _text->height() + padding.top() + padding.bottom(), + height() - (margin.height() * 2)); + const auto left = (width() - fullw) / 2; + const auto top = (height() - fullh) / 2; + _scroll->setGeometry(left, top, fullw, fullh); +} + +void CaptionFullView::keyPressEvent(QKeyEvent *e) { + if (e->key() == Qt::Key_Escape) { + _close(); + } +} + +void CaptionFullView::mousePressEvent(QMouseEvent *e) { + if (e->button() == Qt::LeftButton) { + _close(); + } +} + +} // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_caption_full_view.h b/Telegram/SourceFiles/media/stories/media_stories_caption_full_view.h new file mode 100644 index 000000000..94b6e2cff --- /dev/null +++ b/Telegram/SourceFiles/media/stories/media_stories_caption_full_view.h @@ -0,0 +1,46 @@ +/* +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/rp_widget.h" +#include "ui/round_rect.h" + +namespace Main { +class Session; +} // namespace Main + +namespace Ui { +class FlatLabel; +class ScrollArea; +} // namespace Ui + +namespace Media::Stories { + +class CaptionFullView final : private Ui::RpWidget { +public: + CaptionFullView( + not_null parent, + not_null session, + const TextWithEntities &text, + Fn close); + ~CaptionFullView(); + +private: + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + void keyPressEvent(QKeyEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + + std::unique_ptr _scroll; + const not_null _text; + Fn _close; + Ui::RoundRect _background; + +}; + +} // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 7bb7409f1..a0ff10a86 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -9,8 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/timer.h" #include "base/power_save_blocker.h" +#include "chat_helpers/compose/compose_show.h" #include "data/data_stories.h" #include "data/data_user.h" +#include "media/stories/media_stories_caption_full_view.h" #include "media/stories/media_stories_delegate.h" #include "media/stories/media_stories_header.h" #include "media/stories/media_stories_sibling.h" @@ -115,6 +117,9 @@ Controller::Controller(not_null delegate) _replyArea->focusedValue( ) | rpl::start_with_next([=](bool focused) { + if (focused) { + _captionFullView = nullptr; + } _contentFaded = focused; _contentFadeAnimation.start( [=] { _delegate->storiesRepaint(); }, @@ -292,6 +297,18 @@ TextWithEntities Controller::captionText() const { return _captionText; } +void Controller::showFullCaption() { + if (_captionText.empty()) { + return; + } + togglePaused(true); + _captionFullView = std::make_unique( + wrap(), + &_delegate->storiesShow()->session(), + _captionText, + [=] { togglePaused(false); }); +} + std::shared_ptr Controller::uiShow() const { return _delegate->storiesShow(); } @@ -334,10 +351,15 @@ void Controller::show( } _shown = id; _captionText = item.caption; + _captionFullView = nullptr; _header->show({ .user = list.user, .date = item.date }); _slider->show({ .index = _index, .total = list.total }); _replyArea->show({ .user = list.user }); + + if (_contentFaded) { + togglePaused(true); + } } void Controller::showSiblings( @@ -447,6 +469,9 @@ bool Controller::paused() const { } void Controller::togglePaused(bool paused) { + if (!paused) { + _captionFullView = nullptr; + } if (_photoPlayback) { _photoPlayback->togglePaused(paused); } else { diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index 3f9eee581..88a307dc0 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -41,6 +41,7 @@ class Delegate; struct SiblingView; enum class SiblingType; struct ContentLayout; +class CaptionFullView; enum class HeaderLayout { Normal, @@ -78,6 +79,7 @@ public: [[nodiscard]] rpl::producer layoutValue() const; [[nodiscard]] ContentLayout contentLayout() const; [[nodiscard]] TextWithEntities captionText() const; + void showFullCaption(); [[nodiscard]] std::shared_ptr uiShow() const; [[nodiscard]] auto stickerOrEmojiChosen() const @@ -130,6 +132,7 @@ private: const std::unique_ptr _slider; const std::unique_ptr _replyArea; std::unique_ptr _photoPlayback; + std::unique_ptr _captionFullView; Ui::Animations::Simple _contentFadeAnimation; bool _contentFaded = false; diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.cpp b/Telegram/SourceFiles/media/stories/media_stories_view.cpp index 0412a6d78..82c4388d2 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_view.cpp @@ -83,6 +83,10 @@ TextWithEntities View::captionText() const { return _controller->captionText(); } +void View::showFullCaption() { + _controller->showFullCaption(); +} + rpl::lifetime &View::lifetime() { return _controller->lifetime(); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.h b/Telegram/SourceFiles/media/stories/media_stories_view.h index b5d591e16..f36b65cf1 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.h +++ b/Telegram/SourceFiles/media/stories/media_stories_view.h @@ -63,6 +63,7 @@ public: [[nodiscard]] ContentLayout contentLayout() const; [[nodiscard]] SiblingView sibling(SiblingType type) const; [[nodiscard]] TextWithEntities captionText() const; + void showFullCaption(); void updatePlayback(const Player::TrackState &state); diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index a69039406..3cd675b90 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -448,3 +448,8 @@ storiesAttach: IconButton(defaultIconButton) { } } storiesSideSkip: 145px; +storiesCaptionFull: FlatLabel(defaultFlatLabel) { + style: mediaviewCaptionStyle; + textFg: mediaviewCaptionFg; + minWidth: 360px; +} diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index f5fd2623e..2d7da4d8b 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -1217,14 +1217,32 @@ void OverlayWidget::refreshCaptionGeometry() { - st::mediaviewCaptionPadding.left() - st::mediaviewCaptionPadding.right()), _caption.maxWidth()); - const auto maxHeight = (_stories ? (_h / 3) : (height() / 4)) + const auto maxExpandedOuterHeight = (_stories + ? (_h - st::storiesShadowTop.height()) + : height()); + const auto maxCollapsedOuterHeight = !_stories + ? (height() / 4) + : (_h / 3); + const auto maxExpandedHeight = maxExpandedOuterHeight - st::mediaviewCaptionPadding.top() - - st::mediaviewCaptionPadding.bottom() - - (_stories ? 0 : (2 * st::mediaviewCaptionMargin.height())); + - st::mediaviewCaptionPadding.bottom(); + const auto maxCollapsedHeight = maxCollapsedOuterHeight + - st::mediaviewCaptionPadding.top() + - st::mediaviewCaptionPadding.bottom(); const auto lineHeight = st::mediaviewCaptionStyle.font->height; + const auto wantedHeight = _caption.countHeight(captionWidth); + const auto maxHeight = _captionExpanded + ? maxExpandedHeight + : maxCollapsedHeight; const auto captionHeight = std::min( - _caption.countHeight(captionWidth), + wantedHeight, (maxHeight / lineHeight) * lineHeight); + _captionFitsIfExpanded = _stories + && (wantedHeight <= maxExpandedHeight); + _captionShownFull = (wantedHeight <= maxCollapsedHeight); + if (_captionShownFull) { + _captionExpanded = false; + } _captionRect = QRect( (width() - captionWidth) / 2, (captionBottom @@ -3000,6 +3018,7 @@ void OverlayWidget::show(OpenRequest request) { _streamingStartPaused = false; displayDocument( document, + anim::activation::normal, request.cloudTheme() ? *request.cloudTheme() : Data::CloudTheme(), @@ -3014,9 +3033,11 @@ void OverlayWidget::show(OpenRequest request) { } } -void OverlayWidget::displayPhoto(not_null photo) { +void OverlayWidget::displayPhoto( + not_null photo, + anim::activation activation) { if (photo->isNull()) { - displayDocument(nullptr); + displayDocument(nullptr, activation); return; } _touchbarDisplay.fire(TouchBarItemType::Photo); @@ -3055,7 +3076,7 @@ void OverlayWidget::displayPhoto(not_null photo) { } contentSizeChanged(); refreshFromLabel(); - displayFinished(); + displayFinished(activation); } void OverlayWidget::destroyThemePreview() { @@ -3071,15 +3092,16 @@ void OverlayWidget::redisplayContent() { if (isHidden() || !_session) { return; } else if (_photo) { - displayPhoto(_photo); + displayPhoto(_photo, anim::activation::background); } else { - displayDocument(_document); + displayDocument(_document, anim::activation::background); } } // Empty messages shown as docs: doc can be nullptr. void OverlayWidget::displayDocument( DocumentData *doc, + anim::activation activation, const Data::CloudTheme &cloud, const StartStreaming &startStreaming) { _fullScreenVideo = false; @@ -3209,7 +3231,7 @@ void OverlayWidget::displayDocument( if (_showAsPip && _streamed && _streamed->controls) { switchToPip(); } else { - displayFinished(); + displayFinished(activation); } } @@ -3236,7 +3258,8 @@ void OverlayWidget::updateThemePreviewGeometry() { } } -void OverlayWidget::displayFinished() { +void OverlayWidget::displayFinished(anim::activation activation) { + _captionExpanded = _captionFitsIfExpanded = _captionShownFull = false; updateControls(); if (isHidden()) { _helper->beforeShow(_fullscreen); @@ -3247,6 +3270,8 @@ void OverlayWidget::displayFinished() { //setAttribute(Qt::WA_DontShowOnScreen, false); //Ui::Platform::UpdateOverlayed(_window); showAndActivate(); + } else if (activation == anim::activation::background) { + return; } else if (isMinimized()) { _helper->beforeShow(_fullscreen); showAndActivate(); @@ -4015,10 +4040,11 @@ void OverlayWidget::storiesJumpTo(Data::FullStoryId id) { clearStreaming(); _streamingStartPaused = false; const auto &data = j->media.data; + const auto activation = anim::activation::background; if (const auto photo = std::get_if>(&data)) { - displayPhoto(*photo); + displayPhoto(*photo, activation); } else { - displayDocument(v::get>(data)); + displayDocument(v::get>(data), activation); } } @@ -5302,6 +5328,9 @@ void OverlayWidget::updateOver(QPoint pos) { } else if (_captionRect.contains(pos)) { auto textState = _caption.getState(pos - _captionRect.topLeft(), _captionRect.width()); lnk = textState.link; + if (_stories && !_captionShownFull && !lnk) { + lnk = ensureCaptionExpandLink(); + } lnkhost = this; } else if (_groupThumbs && _groupThumbsRect.contains(pos)) { const auto point = pos - QPoint(_groupThumbsLeft, _groupThumbsTop); @@ -5373,6 +5402,28 @@ void OverlayWidget::updateOver(QPoint pos) { } } +ClickHandlerPtr OverlayWidget::ensureCaptionExpandLink() { + if (!_captionExpandLink) { + const auto toggle = crl::guard(_widget, [=] { + if (!_stories) { + return; + } else if (_captionExpanded) { + _captionExpanded = false; + refreshCaptionGeometry(); + update(); + } else if (_captionFitsIfExpanded) { + _captionExpanded = true; + refreshCaptionGeometry(); + update(); + } else { + _stories->showFullCaption(); + } + }); + _captionExpandLink = std::make_shared(toggle); + } + return _captionExpandLink; +} + void OverlayWidget::handleMouseRelease( QPoint position, Qt::MouseButton button) { diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index fdb8997c6..6007e9fab 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -23,6 +23,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class History; +namespace anim { +enum class activation : uchar; +} // namespace anim + namespace Data { class PhotoMedia; class DocumentMedia; @@ -148,6 +152,7 @@ private: OverMore, OverIcon, OverVideo, + OverCaption, }; struct Entity { std::variant< @@ -356,12 +361,15 @@ private: void resizeContentByScreenSize(); void recountSkipTop(); - void displayPhoto(not_null photo); + void displayPhoto( + not_null photo, + anim::activation activation = anim::activation::normal); void displayDocument( DocumentData *document, + anim::activation activation = anim::activation::normal, const Data::CloudTheme &cloud = Data::CloudTheme(), const StartStreaming &startStreaming = StartStreaming()); - void displayFinished(); + void displayFinished(anim::activation activation); void redisplayContent(); void findCurrent(); @@ -496,6 +504,7 @@ private: [[nodiscard]] bool topShadowOnTheRight() const; void applyHideWindowWorkaround(); + [[nodiscard]] ClickHandlerPtr ensureCaptionExpandLink(); Window::SessionController *findWindow(bool switchTo = true) const; @@ -558,6 +567,10 @@ private: int _groupThumbsTop = 0; Ui::Text::String _caption; QRect _captionRect; + ClickHandlerPtr _captionExpandLink; + bool _captionShownFull = false; + bool _captionFitsIfExpanded = false; + bool _captionExpanded = false; int _width = 0; int _height = 0;