From 0331955ce7cfaf2731afdb58db43a1e4433c0c77 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 12 May 2023 13:41:25 +0400 Subject: [PATCH] Show captions with darkening over stories. --- .../stories/media_stories_controller.cpp | 15 +++- .../media/stories/media_stories_controller.h | 2 + .../media/stories/media_stories_header.cpp | 2 +- .../media/stories/media_stories_sibling.cpp | 2 +- .../media/stories/media_stories_view.cpp | 4 + .../media/stories/media_stories_view.h | 4 +- .../SourceFiles/media/view/media_view.style | 3 + .../media/view/media_view_overlay_opengl.cpp | 73 +++++++++------ .../media/view/media_view_overlay_opengl.h | 6 +- .../media/view/media_view_overlay_raster.cpp | 17 ++-- .../media/view/media_view_overlay_widget.cpp | 89 +++++++++++++------ .../media/view/media_view_overlay_widget.h | 6 +- 12 files changed, 151 insertions(+), 72 deletions(-) diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index d54ea6617..7bb7409f1 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -28,7 +28,7 @@ namespace { constexpr auto kPhotoProgressInterval = crl::time(100); constexpr auto kPhotoDuration = 5 * crl::time(1000); -constexpr auto kFullContentFade = 0.2; +constexpr auto kFullContentFade = 0.35; constexpr auto kSiblingMultiplier = 0.448; constexpr auto kSiblingOutsidePart = 0.24; constexpr auto kSiblingUserpicSize = 0.3; @@ -276,14 +276,22 @@ rpl::producer Controller::layoutValue() const { } ContentLayout Controller::contentLayout() const { + const auto ¤t = _layout.current(); + Assert(current.has_value()); + return { - .geometry = _layout.current()->content, + .geometry = current->content, .fade = (_contentFadeAnimation.value(_contentFaded ? 1. : 0.) * kFullContentFade), - .radius = float64(st::storiesRadius), + .radius = st::storiesRadius, + .headerOutside = (current->headerLayout == HeaderLayout::Outside), }; } +TextWithEntities Controller::captionText() const { + return _captionText; +} + std::shared_ptr Controller::uiShow() const { return _delegate->storiesShow(); } @@ -325,6 +333,7 @@ void Controller::show( return; } _shown = id; + _captionText = item.caption; _header->show({ .user = list.user, .date = item.date }); _slider->show({ .index = _index, .total = list.total }); diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index 60c25011d..3f9eee581 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -77,6 +77,7 @@ public: [[nodiscard]] Layout layout() const; [[nodiscard]] rpl::producer layoutValue() const; [[nodiscard]] ContentLayout contentLayout() const; + [[nodiscard]] TextWithEntities captionText() const; [[nodiscard]] std::shared_ptr uiShow() const; [[nodiscard]] auto stickerOrEmojiChosen() const @@ -134,6 +135,7 @@ private: bool _contentFaded = false; Data::FullStoryId _shown; + TextWithEntities _captionText; std::optional _list; int _index = 0; bool _started = false; diff --git a/Telegram/SourceFiles/media/stories/media_stories_header.cpp b/Telegram/SourceFiles/media/stories/media_stories_header.cpp index af11254a5..fca65c8ca 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_header.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_header.cpp @@ -21,7 +21,7 @@ namespace Media::Stories { namespace { constexpr auto kNameOpacity = 1.; -constexpr auto kDateOpacity = 0.6; +constexpr auto kDateOpacity = 0.8; } // namespace diff --git a/Telegram/SourceFiles/media/stories/media_stories_sibling.cpp b/Telegram/SourceFiles/media/stories/media_stories_sibling.cpp index 8558e7980..05e66297b 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_sibling.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_sibling.cpp @@ -256,7 +256,7 @@ SiblingView Sibling::view(const SiblingLayout &layout, float64 over) { .layout = { .geometry = layout.geometry, .fade = kSiblingFade * (1 - over) + kSiblingFadeOver * over, - .radius = float64(st::storiesRadius), + .radius = st::storiesRadius, }, .userpic = userpicImage(layout), .userpicPosition = layout.userpic.topLeft(), diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.cpp b/Telegram/SourceFiles/media/stories/media_stories_view.cpp index f5cfd628d..0412a6d78 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_view.cpp @@ -79,6 +79,10 @@ SiblingView View::sibling(SiblingType type) const { return _controller->sibling(type); } +TextWithEntities View::captionText() const { + return _controller->captionText(); +} + 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 09865c861..b5d591e16 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.h +++ b/Telegram/SourceFiles/media/stories/media_stories_view.h @@ -23,7 +23,8 @@ class Controller; struct ContentLayout { QRect geometry; float64 fade = 0.; - float64 radius = 0.; + int radius = 0; + bool headerOutside = false; }; enum class SiblingType; @@ -61,6 +62,7 @@ public: [[nodiscard]] rpl::producer finalShownGeometryValue() const; [[nodiscard]] ContentLayout contentLayout() const; [[nodiscard]] SiblingView sibling(SiblingType type) const; + [[nodiscard]] TextWithEntities captionText() const; 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 c896d253a..a69039406 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -196,6 +196,7 @@ mediaviewSaveMsgStyle: TextStyle(defaultTextStyle) { mediaviewTextPalette: TextPalette(defaultTextPalette) { linkFg: mediaviewTextLinkFg; monoFg: mediaviewCaptionFg; + spoilerFg: mediaviewCaptionFg; } mediaviewCaptionStyle: defaultTextStyle; @@ -429,6 +430,8 @@ storiesHeaderDate: FlatLabel(defaultFlatLabel) { textFg: mediaviewControlFg; } storiesHeaderDatePosition: point(50px, 17px); +storiesShadowTop: icon{{ "mediaview/shadow_bottom-flip_vertical", windowShadowFg }}; +storiesShadowBottom: mediaviewShadowBottom; storiesControlsMinWidth: 200px; storiesFieldMargin: margins(0px, 14px, 0px, 16px); storiesAttach: IconButton(defaultIconButton) { diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp index 8c1ea6cf9..e2f2bff04 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp @@ -36,13 +36,14 @@ constexpr auto kControlValues = 4 * 4 + 4 * 4; // over + icon .header = R"( uniform sampler2D f_texture; uniform vec4 shadowTopRect; -uniform vec3 shadowBottomOpacityFullFade; +uniform vec4 shadowBottomSkipOpacityFullFade; )", .body = R"( float topHeight = shadowTopRect.w; - float bottomHeight = shadowBottomOpacityFullFade.x; - float opacity = shadowBottomOpacityFullFade.y; - float fullFade = shadowBottomOpacityFullFade.z; + float bottomHeight = shadowBottomSkipOpacityFullFade.x; + float bottomSkip = shadowBottomSkipOpacityFullFade.y; + float opacity = shadowBottomSkipOpacityFullFade.z; + float fullFade = shadowBottomSkipOpacityFullFade.w; float viewportHeight = shadowTopRect.y + topHeight; float fullHeight = topHeight + bottomHeight; float topY = min( @@ -50,7 +51,8 @@ uniform vec3 shadowBottomOpacityFullFade; topHeight / fullHeight); float topX = (gl_FragCoord.x - shadowTopRect.x) / shadowTopRect.z; vec4 fadeTop = texture2D(f_texture, vec2(topX, topY)) * opacity; - float bottomY = max(fullHeight - gl_FragCoord.y, topHeight) / fullHeight; + float bottomY = max(bottomSkip + fullHeight - gl_FragCoord.y, topHeight) + / fullHeight; vec4 fadeBottom = texture2D(f_texture, vec2(0.5, bottomY)) * opacity; float fade = min((1. - fadeTop.a) * (1. - fadeBottom.a), fullFade); result.rgb = result.rgb * fade; @@ -496,16 +498,30 @@ void OverlayWidget::RendererGL::paintTransformedContent( _contentBuffer->write(0, coords, sizeof(coords)); program->setUniformValue("viewport", _uniformViewport); - const auto &top = st::mediaviewShadowTop.size(); - const auto point = QPoint( - _shadowTopFlip ? 0 : (_viewport.width() - top.width()), - 0); - program->setUniformValue( - "shadowTopRect", - Uniform(transformRect(QRect(point, top)))); - const auto &bottom = st::mediaviewShadowBottom; - program->setUniformValue("shadowBottomOpacityFullFade", QVector3D( + if (_owner->_stories) { + const auto &top = st::storiesShadowTop.size(); + const auto shadowTop = geometry.topShadowShown + ? geometry.rect.y() + : geometry.rect.y() - top.height(); + program->setUniformValue( + "shadowTopRect", + Uniform(transformRect( + QRect(QPoint(geometry.rect.x(), shadowTop), top)))); + } else { + const auto &top = st::mediaviewShadowTop.size(); + const auto point = QPoint( + _shadowTopFlip ? 0 : (_viewport.width() - top.width()), + 0); + program->setUniformValue( + "shadowTopRect", + Uniform(transformRect(QRect(point, top)))); + } + const auto &bottom = _owner->_stories + ? st::storiesShadowBottom + : st::mediaviewShadowBottom; + program->setUniformValue("shadowBottomSkipOpacityFullFade", QVector4D( bottom.height() * _factor, + geometry.bottomShadowSkip * _factor, geometry.controlsOpacity, 1.f - float(geometry.fade))); if (!fillTransparentBackground) { @@ -733,14 +749,23 @@ void OverlayWidget::RendererGL::invalidateControls() { void OverlayWidget::RendererGL::validateControlsFade() { const auto flip = !_owner->topShadowOnTheRight(); + const auto forStories = (_owner->_stories != nullptr); if (!_controlsFadeImage.image().isNull() - && _shadowTopFlip == flip) { + && _shadowTopFlip == flip + && _shadowsForStories == forStories) { return; } _shadowTopFlip = flip; - const auto width = st::mediaviewShadowTop.width(); - const auto bottomTop = st::mediaviewShadowTop.height(); - const auto height = bottomTop + st::mediaviewShadowBottom.height(); + _shadowsForStories = forStories; + const auto &top = _shadowsForStories + ? st::storiesShadowTop + : st::mediaviewShadowTop; + const auto &bottom = _shadowsForStories + ? st::storiesShadowBottom + : st::mediaviewShadowBottom; + const auto width = top.width(); + const auto bottomTop = top.height(); + const auto height = bottomTop + bottom.height(); auto image = QImage( QSize(width, height) * _factor, @@ -749,10 +774,10 @@ void OverlayWidget::RendererGL::validateControlsFade() { image.setDevicePixelRatio(_factor); auto p = QPainter(&image); - st::mediaviewShadowTop.paint(p, 0, 0, width); - st::mediaviewShadowBottom.fill( + top.paint(p, 0, 0, width); + bottom.fill( p, - QRect(0, bottomTop, width, st::mediaviewShadowBottom.height())); + QRect(0, bottomTop, width, bottom.height())); p.end(); if (flip) { @@ -760,12 +785,6 @@ void OverlayWidget::RendererGL::validateControlsFade() { } _controlsFadeImage.setImage(std::move(image)); - _shadowTopTexture = QRect( - QPoint(), - QSize(width, st::mediaviewShadowTop.height()) * _factor); - _shadowBottomTexture = QRect( - QPoint(0, bottomTop) * _factor, - QSize(width, st::mediaviewShadowBottom.height()) * _factor); } void OverlayWidget::RendererGL::paintFooter(QRect outer, float64 opacity) { diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_opengl.h b/Telegram/SourceFiles/media/view/media_view_overlay_opengl.h index b0ba15dc8..95d57ec87 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_opengl.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_opengl.h @@ -153,10 +153,8 @@ private: // Last one is for the over circle image. std::array _controlsTextures; - QRect _shadowTopTexture; - QRect _shadowBottomTexture; - - bool _shadowTopFlip; + bool _shadowTopFlip = false; + bool _shadowsForStories = false; bool _blendingEnabled = false; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_raster.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_raster.cpp index d6d24e1e8..13109e828 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_raster.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_raster.cpp @@ -113,11 +113,16 @@ void OverlayWidget::RendererSW::paintControlsFade( _p->setOpacity(opacity); _p->setClipRect(geometry); const auto width = _owner->width(); - const auto &top = st::mediaviewShadowTop; const auto flip = !_owner->topShadowOnTheRight(); - const auto topShadow = QRect( - QPoint(flip ? 0 : (width - top.width()), 0), - top.size()); + const auto stories = (_owner->_stories != nullptr); + const auto &top = stories + ? st::storiesShadowTop + : st::mediaviewShadowTop; + const auto topShadow = stories + ? QRect(geometry.topLeft(), QSize(geometry.width(), top.height())) + : QRect( + QPoint(flip ? 0 : (width - top.width()), 0), + top.size()); if (topShadow.intersected(geometry).intersects(_clipOuter)) { if (flip) { if (_topShadowCache.isNull() @@ -131,7 +136,9 @@ void OverlayWidget::RendererSW::paintControlsFade( top.paint(*_p, topShadow.topLeft(), width); } } - const auto &bottom = st::mediaviewShadowBottom; + const auto &bottom = stories + ? st::storiesShadowBottom + : st::mediaviewShadowBottom; const auto bottomShadow = QRect( QPoint(0, _owner->height() - bottom.height()), QSize(width, bottom.height())); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 33526046c..f5fd2623e 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -127,6 +127,7 @@ constexpr auto kIdsPreloadAfter = 28; constexpr auto kLeftSiblingTextureIndex = 1; constexpr auto kRightSiblingTextureIndex = 2; +constexpr auto kStoriesControlsOpacity = 1.; class PipDelegate final : public Pip::Delegate { public: @@ -1200,27 +1201,35 @@ void OverlayWidget::refreshCaptionGeometry() { _groupThumbs = nullptr; _groupThumbsRect = QRect(); } - const auto captionBottom = (_streamed && _streamed->controls) + const auto captionBottom = _stories + ? (_y + _h) + : (_streamed && _streamed->controls) ? (_streamed->controls->y() - st::mediaviewCaptionMargin.height()) : _groupThumbs ? _groupThumbsTop : height() - st::mediaviewCaptionMargin.height(); - const auto captionWidth = std::min( - _groupThumbsAvailableWidth - - st::mediaviewCaptionPadding.left() - - st::mediaviewCaptionPadding.right(), - _caption.maxWidth()); - const auto captionHeight = std::min( - _caption.countHeight(captionWidth), - height() / 4 + const auto captionWidth = _stories + ? (_w + - st::mediaviewCaptionPadding.left() + - st::mediaviewCaptionPadding.right()) + : std::min( + (_groupThumbsAvailableWidth + - st::mediaviewCaptionPadding.left() + - st::mediaviewCaptionPadding.right()), + _caption.maxWidth()); + const auto maxHeight = (_stories ? (_h / 3) : (height() / 4)) - st::mediaviewCaptionPadding.top() - st::mediaviewCaptionPadding.bottom() - - 2 * st::mediaviewCaptionMargin.height()); + - (_stories ? 0 : (2 * st::mediaviewCaptionMargin.height())); + const auto lineHeight = st::mediaviewCaptionStyle.font->height; + const auto captionHeight = std::min( + _caption.countHeight(captionWidth), + (maxHeight / lineHeight) * lineHeight); _captionRect = QRect( (width() - captionWidth) / 2, - captionBottom - - captionHeight - - st::mediaviewCaptionPadding.bottom(), + (captionBottom + - captionHeight + - st::mediaviewCaptionPadding.bottom()), captionWidth, captionHeight); } @@ -1497,7 +1506,14 @@ QRect OverlayWidget::finalContentRect() const { OverlayWidget::ContentGeometry OverlayWidget::contentGeometry() const { if (_stories) { - return storiesContentGeometry(_stories->contentLayout()); + auto result = storiesContentGeometry(_stories->contentLayout()); + if (!_caption.isEmpty()) { + result.bottomShadowSkip = _widget->height() + - _captionRect.y() + + st::mediaviewCaptionStyle.font->height + - st::storiesShadowBottom.height(); + } + return result; } const auto controlsOpacity = _controlsOpacity.current(); const auto toRotation = qreal(finalContentRotation()); @@ -1541,9 +1557,10 @@ OverlayWidget::ContentGeometry OverlayWidget::storiesContentGeometry( const Stories::ContentLayout &layout) const { return { .rect = QRectF(layout.geometry), - .controlsOpacity = 0., // #TODO stories ?.. + .controlsOpacity = kStoriesControlsOpacity, .fade = layout.fade, .roundRadius = layout.radius, + .topShadowShown = !layout.headerOutside, }; } @@ -2708,21 +2725,26 @@ void OverlayWidget::refreshFromLabel() { void OverlayWidget::refreshCaption() { _caption = Ui::Text::String(); - if (!_message) { - return; - } else if (const auto media = _message->media()) { - if (media->webpage()) { - return; + const auto caption = [&] { + if (_message) { + if (const auto media = _message->media()) { + if (media->webpage()) { + return TextWithEntities(); + } + } + return _message->translatedText(); + } else if (_stories) { + return _stories->captionText(); } - } - const auto caption = _message->translatedText(); + return TextWithEntities(); + }(); if (caption.text.isEmpty()) { return; } using namespace HistoryView; _caption = Ui::Text::String(st::msgMinWidth); - const auto duration = (_streamed && _document) + const auto duration = (_streamed && _document && _message) ? DurationForTimestampLinks(_document) : 0; const auto base = duration @@ -2735,7 +2757,9 @@ void OverlayWidget::refreshCaption() { update(captionGeometry()); }; const auto context = Core::MarkedTextContext{ - .session = &_message->history()->session(), + .session = (_stories + ? _storiesSession + : &_message->history()->session()), .customEmojiRepaint = captionRepaint, }; _caption.setMarkedText( @@ -2743,7 +2767,9 @@ void OverlayWidget::refreshCaption() { (base.isEmpty() ? caption : AddTimestampLinks(caption, duration, base)), - Ui::ItemTextOptions(_message), + (_message + ? Ui::ItemTextOptions(_message) + : Ui::ItemTextDefaultOptions()), context); if (_caption.hasSpoilers()) { const auto weak = Ui::MakeWeak(widget()); @@ -4627,10 +4653,15 @@ void OverlayWidget::paintCaptionContent( QRect clip, float64 opacity) { const auto inner = outer.marginsRemoved(st::mediaviewCaptionPadding); - p.setOpacity(opacity); - p.setBrush(st::mediaviewCaptionBg); - p.setPen(Qt::NoPen); - p.drawRoundedRect(outer, st::mediaviewCaptionRadius, st::mediaviewCaptionRadius); + if (!_stories) { + p.setOpacity(opacity); + p.setBrush(st::mediaviewCaptionBg); + p.setPen(Qt::NoPen); + p.drawRoundedRect( + outer, + st::mediaviewCaptionRadius, + st::mediaviewCaptionRadius); + } if (inner.intersects(clip)) { p.setPen(st::mediaviewCaptionFg); _caption.draw(p, { diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index 49c4a6df7..fdb8997c6 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -166,8 +166,12 @@ private: QRectF rect; qreal rotation = 0.; qreal controlsOpacity = 0.; + + // Stories. qreal fade = 0.; - qreal roundRadius = 0.; + int bottomShadowSkip = 0; + int roundRadius = 0; + bool topShadowShown = false; }; struct StartStreaming { StartStreaming() : continueStreaming(false), startTime(0) {