From 30871ed1167a0ef8c6168650204cfdcce6cee9b8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 11 May 2023 18:36:59 +0400 Subject: [PATCH] Show userpic / name on sibling stories. --- .../stories/media_stories_controller.cpp | 92 +++++++++++---- .../media/stories/media_stories_controller.h | 18 ++- .../media/stories/media_stories_delegate.h | 6 + .../media/stories/media_stories_sibling.cpp | 111 +++++++++++++++++- .../media/stories/media_stories_sibling.h | 28 ++++- .../media/stories/media_stories_view.cpp | 16 +-- .../media/stories/media_stories_view.h | 24 +++- .../SourceFiles/media/view/media_view.style | 1 + .../media/view/media_view_overlay_opengl.cpp | 71 +++++++++-- .../media/view/media_view_overlay_opengl.h | 15 ++- .../media/view/media_view_overlay_raster.cpp | 73 +++++++----- .../media/view/media_view_overlay_raster.h | 8 +- .../media/view/media_view_overlay_renderer.h | 13 +- .../media/view/media_view_overlay_widget.cpp | 109 ++++++++++++----- .../media/view/media_view_overlay_widget.h | 4 + 15 files changed, 469 insertions(+), 120 deletions(-) diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 344b41738..d54ea6617 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -28,8 +28,11 @@ namespace { constexpr auto kPhotoProgressInterval = crl::time(100); constexpr auto kPhotoDuration = 5 * crl::time(1000); -constexpr auto kSiblingMultiplier = 0.448; constexpr auto kFullContentFade = 0.2; +constexpr auto kSiblingMultiplier = 0.448; +constexpr auto kSiblingOutsidePart = 0.24; +constexpr auto kSiblingUserpicSize = 0.3; +constexpr auto kInnerHeightMultiplier = 1.6; } // namespace @@ -210,14 +213,49 @@ void Controller::initLayout() { layout.controlsBottomPosition.y()); const auto siblingSize = layout.content.size() * kSiblingMultiplier; - const auto siblingTop = layout.content.y() - + (layout.content.height() - siblingSize.height()) / 2; - layout.siblingLeft = QRect( - { -siblingSize.width() / 3, siblingTop }, - siblingSize); - layout.siblingRight = QRect( - { size.width() - (2 * siblingSize.width() / 3), siblingTop }, - siblingSize); + const auto siblingTop = (size.height() - siblingSize.height()) / 2; + const auto outside = int(base::SafeRound( + siblingSize.width() * kSiblingOutsidePart)); + const auto xLeft = -outside; + const auto xRight = size.width() - siblingSize.width() + outside; + const auto userpicSize = int(base::SafeRound( + siblingSize.width() * kSiblingUserpicSize)); + const auto innerHeight = userpicSize * kInnerHeightMultiplier; + const auto userpic = [&](QRect geometry) { + return QRect( + (geometry.width() - userpicSize) / 2, + (geometry.height() - innerHeight) / 2, + userpicSize, + userpicSize + ).translated(geometry.topLeft()); + }; + const auto nameFontSize = st::storiesMaxNameFontSize * contentHeight + / st::storiesMaxSize.height(); + const auto nameBoundingRect = [&](QRect geometry, bool left) { + const auto skipSmall = nameFontSize; + const auto skipBig = skipSmall + outside; + const auto top = userpic(geometry).y() + innerHeight; + return QRect( + left ? skipBig : skipSmall, + (geometry.height() - innerHeight) / 2, + geometry.width() - skipSmall - skipBig, + innerHeight + ).translated(geometry.topLeft()); + }; + const auto left = QRect({ xLeft, siblingTop }, siblingSize); + const auto right = QRect({ xRight, siblingTop }, siblingSize); + layout.siblingLeft = { + .geometry = left, + .userpic = userpic(left), + .nameBoundingRect = nameBoundingRect(left, true), + .nameFontSize = nameFontSize, + }; + layout.siblingRight = { + .geometry = right, + .userpic = userpic(right), + .nameBoundingRect = nameBoundingRect(right, false), + .nameFontSize = nameFontSize, + }; return layout; }); @@ -237,9 +275,13 @@ rpl::producer Controller::layoutValue() const { return _layout.value() | rpl::filter_optional(); } -float64 Controller::contentFade() const { - return _contentFadeAnimation.value(_contentFaded ? 1. : 0.) - * kFullContentFade; +ContentLayout Controller::contentLayout() const { + return { + .geometry = _layout.current()->content, + .fade = (_contentFadeAnimation.value(_contentFaded ? 1. : 0.) + * kFullContentFade), + .radius = float64(st::storiesRadius), + }; } std::shared_ptr Controller::uiShow() const { @@ -349,7 +391,7 @@ bool Controller::subjumpAvailable(int delta) const { bool Controller::subjumpFor(int delta) { const auto index = _index + delta; if (index < 0) { - if (_siblingLeft->shownId().valid()) { + if (_siblingLeft && _siblingLeft->shownId().valid()) { return jumpFor(-1); } else if (!_list || _list->items.empty()) { return false; @@ -360,7 +402,9 @@ bool Controller::subjumpFor(int delta) { }); return true; } else if (index >= _list->total) { - return _siblingRight->shownId().valid() && jumpFor(1); + return _siblingRight + && _siblingRight->shownId().valid() + && jumpFor(1); } else if (index < _list->items.size()) { // #TODO stories load more _delegate->storiesJumpTo({ @@ -411,16 +455,16 @@ void Controller::repaintSibling(not_null sibling) { } } -SiblingView Controller::siblingLeft() const { - if (const auto value = _siblingLeft.get()) { - return { value->image(), _layout.current()->siblingLeft }; - } - return {}; -} - -SiblingView Controller::siblingRight() const { - if (const auto value = _siblingRight.get()) { - return { value->image(), _layout.current()->siblingRight }; +SiblingView Controller::sibling(SiblingType type) const { + const auto &pointer = (type == SiblingType::Left) + ? _siblingLeft + : _siblingRight; + if (const auto value = pointer.get()) { + const auto over = _delegate->storiesSiblingOver(type); + const auto layout = (type == SiblingType::Left) + ? _layout.current()->siblingLeft + : _layout.current()->siblingRight; + return value->view(layout, over); } return {}; } diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index 9e3791442..60c25011d 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -39,12 +39,21 @@ class ReplyArea; class Sibling; class Delegate; struct SiblingView; +enum class SiblingType; +struct ContentLayout; enum class HeaderLayout { Normal, Outside, }; +struct SiblingLayout { + QRect geometry; + QRect userpic; + QRect nameBoundingRect; + int nameFontSize = 0; +}; + struct Layout { QRect content; QRect header; @@ -53,8 +62,8 @@ struct Layout { QPoint controlsBottomPosition; QRect autocompleteRect; HeaderLayout headerLayout = HeaderLayout::Normal; - QRect siblingLeft; - QRect siblingRight; + SiblingLayout siblingLeft; + SiblingLayout siblingRight; friend inline bool operator==(Layout, Layout) = default; }; @@ -67,7 +76,7 @@ public: [[nodiscard]] not_null wrap() const; [[nodiscard]] Layout layout() const; [[nodiscard]] rpl::producer layoutValue() const; - [[nodiscard]] float64 contentFade() const; + [[nodiscard]] ContentLayout contentLayout() const; [[nodiscard]] std::shared_ptr uiShow() const; [[nodiscard]] auto stickerOrEmojiChosen() const @@ -90,8 +99,7 @@ public: [[nodiscard]] bool canDownload() const; void repaintSibling(not_null sibling); - [[nodiscard]] SiblingView siblingLeft() const; - [[nodiscard]] SiblingView siblingRight() const; + [[nodiscard]] SiblingView sibling(SiblingType type) const; void unfocusReply(); diff --git a/Telegram/SourceFiles/media/stories/media_stories_delegate.h b/Telegram/SourceFiles/media/stories/media_stories_delegate.h index 256aff2f2..8a34c47bd 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_delegate.h +++ b/Telegram/SourceFiles/media/stories/media_stories_delegate.h @@ -27,6 +27,11 @@ enum class JumpReason { User, }; +enum class SiblingType { + Left, + Right, +}; + class Delegate { public: [[nodiscard]] virtual not_null storiesWrap() = 0; @@ -36,6 +41,7 @@ public: -> rpl::producer = 0; virtual void storiesJumpTo(Data::FullStoryId id) = 0; [[nodiscard]] virtual bool storiesPaused() = 0; + [[nodiscard]] virtual float64 storiesSiblingOver(SiblingType type) = 0; virtual void storiesTogglePaused(bool paused) = 0; virtual void storiesRepaint() = 0; }; diff --git a/Telegram/SourceFiles/media/stories/media_stories_sibling.cpp b/Telegram/SourceFiles/media/stories/media_stories_sibling.cpp index 006485088..8558e7980 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_sibling.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_sibling.cpp @@ -14,15 +14,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_photo.h" #include "data/data_photo_media.h" #include "data/data_session.h" +#include "data/data_user.h" #include "main/main_session.h" #include "media/stories/media_stories_controller.h" +#include "media/stories/media_stories_view.h" #include "media/streaming/media_streaming_instance.h" #include "media/streaming/media_streaming_player.h" +#include "ui/painter.h" +#include "styles/style_media_view.h" namespace Media::Stories { namespace { constexpr auto kGoodFadeDuration = crl::time(200); +constexpr auto kSiblingFade = 0.5; +constexpr auto kSiblingFadeOver = 0.4; +constexpr auto kSiblingNameOpacity = 0.8; +constexpr auto kSiblingNameOpacityOver = 1.; } // namespace @@ -241,8 +249,107 @@ bool Sibling::shows(const Data::StoriesList &list) const { return _id == Data::FullStoryId{ list.user, list.items.front().id }; } -QImage Sibling::image() const { - return _good.isNull() ? _blurred : _good; +SiblingView Sibling::view(const SiblingLayout &layout, float64 over) { + const auto name = nameImage(layout); + return { + .image = _good.isNull() ? _blurred : _good, + .layout = { + .geometry = layout.geometry, + .fade = kSiblingFade * (1 - over) + kSiblingFadeOver * over, + .radius = float64(st::storiesRadius), + }, + .userpic = userpicImage(layout), + .userpicPosition = layout.userpic.topLeft(), + .name = name, + .namePosition = namePosition(layout, name), + .nameOpacity = (kSiblingNameOpacity * (1 - over) + + kSiblingNameOpacityOver * over), + }; +} + +QImage Sibling::userpicImage(const SiblingLayout &layout) { + Expects(_id.user != nullptr); + + const auto ratio = style::DevicePixelRatio(); + const auto size = layout.userpic.width() * ratio; + const auto key = _id.user->userpicUniqueKey(_userpicView); + if (_userpicImage.width() != size || _userpicKey != key) { + _userpicKey = key; + _userpicImage = _id.user->generateUserpicImage(_userpicView, size); + _userpicImage.setDevicePixelRatio(ratio); + } + return _userpicImage; +} + +QImage Sibling::nameImage(const SiblingLayout &layout) { + Expects(_id.user != nullptr); + + if (_nameFontSize != layout.nameFontSize) { + _nameFontSize = layout.nameFontSize; + + const auto family = 0; // Default font family. + const auto font = style::font( + _nameFontSize, + style::internal::FontSemibold, + family); + _name.reset(); + _nameStyle = std::make_unique(style::TextStyle{ + .font = font, + .linkFont = font, + .linkFontOver = font, + }); + }; + const auto text = _id.user->shortName(); + if (_nameText != text) { + _name.reset(); + _nameText = text; + } + if (!_name) { + _nameAvailableWidth = 0; + _name.emplace(*_nameStyle, _nameText); + } + const auto available = layout.nameBoundingRect.width(); + const auto wasCut = (_nameAvailableWidth < _name->maxWidth()); + const auto nowCut = (available < _name->maxWidth()); + if (_nameImage.isNull() + || _nameAvailableWidth != layout.nameBoundingRect.width()) { + _nameAvailableWidth = layout.nameBoundingRect.width(); + if (_nameImage.isNull() || nowCut || wasCut) { + const auto w = std::min(_nameAvailableWidth, _name->maxWidth()); + const auto h = _nameStyle->font->height; + const auto ratio = style::DevicePixelRatio(); + _nameImage = QImage( + QSize(w, h) * ratio, + QImage::Format_ARGB32_Premultiplied); + _nameImage.setDevicePixelRatio(ratio); + _nameImage.fill(Qt::transparent); + auto p = Painter(&_nameImage); + auto hq = PainterHighQualityEnabler(p); + p.setFont(_nameStyle->font); + p.setPen(Qt::white); + _name->drawLeftElided(p, 0, 0, w, w); + } + } + return _nameImage; +} + +QPoint Sibling::namePosition( + const SiblingLayout &layout, + const QImage &image) const { + const auto size = image.size() / image.devicePixelRatio(); + const auto width = size.width(); + const auto left = layout.geometry.x() + + (layout.geometry.width() - width) / 2; + if (left < layout.nameBoundingRect.x()) { + return layout.nameBoundingRect.topLeft(); + } else if (left + width > layout.nameBoundingRect.x() + layout.nameBoundingRect.width()) { + return layout.nameBoundingRect.topLeft() + + QPoint(layout.nameBoundingRect.width() - width, 0); + } + const auto top = layout.nameBoundingRect.y() + + layout.nameBoundingRect.height() + - size.height(); + return QPoint(left, top); } void Sibling::check() { diff --git a/Telegram/SourceFiles/media/stories/media_stories_sibling.h b/Telegram/SourceFiles/media/stories/media_stories_sibling.h index ad3b961d9..ecbd89de7 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_sibling.h +++ b/Telegram/SourceFiles/media/stories/media_stories_sibling.h @@ -10,10 +10,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_stories.h" #include "ui/effects/animations.h" +#include "ui/userpic_view.h" + +namespace style { +struct TextStyle; +} // namespace style namespace Media::Stories { class Controller; +struct SiblingView; +struct SiblingLayout; class Sibling final { public: @@ -25,7 +32,9 @@ public: [[nodiscard]] Data::FullStoryId shownId() const; [[nodiscard]] bool shows(const Data::StoriesList &list) const; - [[nodiscard]] QImage image() const; + [[nodiscard]] SiblingView view( + const SiblingLayout &layout, + float64 over); private: class Loader; @@ -34,6 +43,12 @@ private: void check(); + [[nodiscard]] QImage userpicImage(const SiblingLayout &layout); + [[nodiscard]] QImage nameImage(const SiblingLayout &layout); + [[nodiscard]] QPoint namePosition( + const SiblingLayout &layout, + const QImage &image) const; + const not_null _controller; Data::FullStoryId _id; @@ -41,6 +56,17 @@ private: QImage _good; Ui::Animations::Simple _goodShown; + QImage _userpicImage; + InMemoryKey _userpicKey = {}; + Ui::PeerUserpicView _userpicView; + + QImage _nameImage; + std::unique_ptr _nameStyle; + std::optional _name; + QString _nameText; + int _nameAvailableWidth = 0; + int _nameFontSize = 0; + std::unique_ptr _loader; }; diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.cpp b/Telegram/SourceFiles/media/stories/media_stories_view.cpp index dbcf9cdf4..f5cfd628d 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_view.cpp @@ -36,19 +36,19 @@ bool View::canDownload() const { return _controller->canDownload(); } -QRect View::contentGeometry() const { +QRect View::finalShownGeometry() const { return _controller->layout().content; } -rpl::producer View::contentGeometryValue() const { +rpl::producer View::finalShownGeometryValue() const { return _controller->layoutValue( ) | rpl::map([=](const Layout &layout) { return layout.content; }) | rpl::distinct_until_changed(); } -float64 View::contentFade() const { - return _controller->contentFade(); +ContentLayout View::contentLayout() const { + return _controller->contentLayout(); } void View::updatePlayback(const Player::TrackState &state) { @@ -75,12 +75,8 @@ void View::togglePaused(bool paused) { _controller->togglePaused(paused); } -SiblingView View::siblingLeft() const { - return _controller->siblingLeft(); -} - -SiblingView View::siblingRight() const { - return _controller->siblingRight(); +SiblingView View::sibling(SiblingType type) const { + return _controller->sibling(type); } rpl::lifetime &View::lifetime() { diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.h b/Telegram/SourceFiles/media/stories/media_stories_view.h index a11f49436..09865c861 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.h +++ b/Telegram/SourceFiles/media/stories/media_stories_view.h @@ -20,9 +20,22 @@ namespace Media::Stories { class Delegate; class Controller; +struct ContentLayout { + QRect geometry; + float64 fade = 0.; + float64 radius = 0.; +}; + +enum class SiblingType; + struct SiblingView { QImage image; - QRect geometry; + ContentLayout layout; + QImage userpic; + QPoint userpicPosition; + QImage name; + QPoint namePosition; + float64 nameOpacity = 0.; [[nodiscard]] bool valid() const { return !image.isNull(); @@ -44,11 +57,10 @@ public: void ready(); [[nodiscard]] bool canDownload() const; - [[nodiscard]] QRect contentGeometry() const; - [[nodiscard]] rpl::producer contentGeometryValue() const; - [[nodiscard]] float64 contentFade() const; - [[nodiscard]] SiblingView siblingLeft() const; - [[nodiscard]] SiblingView siblingRight() const; + [[nodiscard]] QRect finalShownGeometry() const; + [[nodiscard]] rpl::producer finalShownGeometryValue() const; + [[nodiscard]] ContentLayout contentLayout() const; + [[nodiscard]] SiblingView sibling(SiblingType type) 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 5e7a30bb8..c896d253a 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -406,6 +406,7 @@ pipVolumeIcon2Over: icon {{ "player/player_volume_on", mediaviewPipControlsFgOve speedSliderDividerSize: size(2px, 8px); storiesMaxSize: size(405px, 720px); +storiesMaxNameFontSize: 17px; storiesRadius: 8px; storiesControlSize: 64px; storiesLeft: icon {{ "mediaview/stories_next-flip_horizontal", mediaviewControlFg }}; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp index 0ea2f2ee6..8c1ea6cf9 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/gl/gl_shader.h" #include "ui/painter.h" +#include "media/stories/media_stories_view.h" #include "media/streaming/media_streaming_common.h" #include "platform/platform_overlay_widget.h" #include "base/platform/base_platform_info.h" @@ -133,7 +134,11 @@ void OverlayWidget::RendererGL::init( constexpr auto kRoundingQuads = 4; constexpr auto kRoundingVertices = kRoundingQuads * 6; constexpr auto kRoundingValues = kRoundingVertices * 2; - constexpr auto kValues = kQuadValues + kControlsValues + kRoundingValues; + constexpr auto kStoriesSiblingValues = kStoriesSiblingPartsCount * 16; + constexpr auto kValues = kQuadValues + + kControlsValues + + kRoundingValues + + kStoriesSiblingValues; _contentBuffer.emplace(); _contentBuffer->setUsagePattern(QOpenGLBuffer::DynamicDraw); @@ -315,7 +320,7 @@ void OverlayWidget::RendererGL::paintTransformedVideoFrame( _streamedIndex = _owner->streamedIndex(); _f->glActiveTexture(GL_TEXTURE0); - _textures.bind(*_f, 1); + _textures.bind(*_f, 3); if (upload) { _f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1); uploadTexture( @@ -328,7 +333,7 @@ void OverlayWidget::RendererGL::paintTransformedVideoFrame( _lumaSize = yuv->size; } _f->glActiveTexture(GL_TEXTURE1); - _textures.bind(*_f, 2); + _textures.bind(*_f, 4); if (upload) { uploadTexture( nv12 ? GL_RG : GL_ALPHA, @@ -350,7 +355,7 @@ void OverlayWidget::RendererGL::paintTransformedVideoFrame( _controlsFadeImage.bind(*_f); } else { _f->glActiveTexture(GL_TEXTURE2); - _textures.bind(*_f, 3); + _textures.bind(*_f, 5); if (upload) { uploadTexture( GL_ALPHA, @@ -383,7 +388,9 @@ void OverlayWidget::RendererGL::paintTransformedStaticContent( const QImage &image, ContentGeometry geometry, bool semiTransparent, - bool fillTransparentBackground) { + bool fillTransparentBackground, + int index) { + Expects(index >= 0 && index < 3); Expects(image.isNull() || image.format() == QImage::Format_RGB32 || image.format() == QImage::Format_ARGB32_Premultiplied); @@ -409,11 +416,11 @@ void OverlayWidget::RendererGL::paintTransformedStaticContent( } _f->glActiveTexture(GL_TEXTURE0); - _textures.bind(*_f, 0); + _textures.bind(*_f, index); const auto cacheKey = image.isNull() ? qint64(-1) : image.cacheKey(); - const auto upload = (_cacheKey != cacheKey); + const auto upload = (_cacheKeys[index] != cacheKey); if (upload) { - _cacheKey = cacheKey; + _cacheKeys[index] = cacheKey; if (image.isNull()) { // Upload transparent 2x2 texture. const auto stride = 2; @@ -853,6 +860,54 @@ void OverlayWidget::RendererGL::paintRoundedCorners(int radius) { _f->glDisableVertexAttribArray(position); } + +void OverlayWidget::RendererGL::paintStoriesSiblingPart( + int index, + const QImage &image, + QRect rect, + float64 opacity) { + Expects(index >= 0 && index < kStoriesSiblingPartsCount); + + _f->glActiveTexture(GL_TEXTURE0); + + auto &part = _storiesSiblingParts[index]; + part.setImage(image); + part.bind(*_f); + + const auto textured = part.texturedRect( + rect, + QRect(QPoint(), image.size())); + const auto geometry = transformRect(textured.geometry); + const GLfloat coords[] = { + geometry.left(), geometry.top(), + textured.texture.left(), textured.texture.bottom(), + + geometry.right(), geometry.top(), + textured.texture.right(), textured.texture.bottom(), + + geometry.right(), geometry.bottom(), + textured.texture.right(), textured.texture.top(), + + geometry.left(), geometry.bottom(), + textured.texture.left(), textured.texture.top(), + }; + const auto offset = kControlsOffset + + (kControlsCount * kControlValues) / 4 + + (6 * 2 * 4) / 4 // rounding + + (index * 4); + const auto byteOffset = offset * 4 * sizeof(GLfloat); + _contentBuffer->write(byteOffset, coords, sizeof(coords)); + + _controlsProgram->bind(); + _controlsProgram->setUniformValue("viewport", _uniformViewport); + _contentBuffer->write( + offset * 4 * sizeof(GLfloat), + coords, + sizeof(coords)); + _controlsProgram->setUniformValue("g_opacity", GLfloat(opacity)); + FillTexturedRectangle(*_f, &*_controlsProgram, offset); +} + // //void OverlayWidget::RendererGL::invalidate() { // _trackFrameIndex = -1; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_opengl.h b/Telegram/SourceFiles/media/view/media_view_overlay_opengl.h index 9cc233fba..b0ba15dc8 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_opengl.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_opengl.h @@ -48,7 +48,8 @@ private: const QImage &image, ContentGeometry geometry, bool semiTransparent, - bool fillTransparentBackground) override; + bool fillTransparentBackground, + int index = 0) override; void paintTransformedContent( not_null program, ContentGeometry geometry, @@ -72,6 +73,11 @@ private: void paintCaption(QRect outer, float64 opacity) override; void paintGroupThumbs(QRect outer, float64 opacity) override; void paintRoundedCorners(int radius) override; + void paintStoriesSiblingPart( + int index, + const QImage &image, + QRect rect, + float64 opacity = 1.) override; //void invalidate(); @@ -117,11 +123,11 @@ private: std::optional _fillProgram; std::optional _controlsProgram; std::optional _roundedCornersProgram; - Ui::GL::Textures<4> _textures; + Ui::GL::Textures<6> _textures; // image, sibling, right sibling, y, u, v QSize _rgbaSize; QSize _lumaSize; QSize _chromaSize; - qint64 _cacheKey = 0; + qint64 _cacheKeys[3] = { 0 }; // image, sibling, right sibling int _trackFrameIndex = 0; int _streamedIndex = 0; bool _chromaNV12 = false; @@ -136,6 +142,9 @@ private: Ui::GL::Image _groupThumbsImage; Ui::GL::Image _controlsImage; + static constexpr auto kStoriesSiblingPartsCount = 4; + Ui::GL::Image _storiesSiblingParts[kStoriesSiblingPartsCount]; + static constexpr auto kControlsCount = 5; [[nodiscard]] static Control ControlMeta( OverState control, diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_raster.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_raster.cpp index bd9675380..9a345e3d7 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_raster.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_raster.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/view/media_view_overlay_raster.h" #include "ui/painter.h" +#include "media/stories/media_stories_view.h" #include "media/view/media_view_pip.h" #include "platform/platform_overlay_widget.h" #include "styles/style_media_view.h" @@ -15,14 +16,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Media::View { OverlayWidget::RendererSW::RendererSW(not_null owner) -: _owner(owner) -, _transparentBrush(style::TransparentPlaceholder()) { + : _owner(owner) + , _transparentBrush(style::TransparentPlaceholder()) { } void OverlayWidget::RendererSW::paintFallback( - Painter &&p, - const QRegion &clip, - Ui::GL::Backend backend) { + Painter &&p, + const QRegion &clip, + Ui::GL::Backend backend) { _p = &p; _clip = &clip; _clipOuter = clip.boundingRect(); @@ -48,8 +49,8 @@ void OverlayWidget::RendererSW::paintBackground() { } QRect OverlayWidget::RendererSW::TransformRect( - QRectF geometry, - int rotation) { + QRectF geometry, + int rotation) { const auto center = geometry.center(); const auto rect = ((rotation % 180) == 90) ? QRectF( @@ -66,7 +67,7 @@ QRect OverlayWidget::RendererSW::TransformRect( } void OverlayWidget::RendererSW::paintTransformedVideoFrame( - ContentGeometry geometry) { + ContentGeometry geometry) { Expects(_owner->_streamed != nullptr); const auto rotation = int(geometry.rotation); @@ -79,10 +80,11 @@ void OverlayWidget::RendererSW::paintTransformedVideoFrame( } void OverlayWidget::RendererSW::paintTransformedStaticContent( - const QImage &image, - ContentGeometry geometry, - bool semiTransparent, - bool fillTransparentBackground) { + const QImage &image, + ContentGeometry geometry, + bool semiTransparent, + bool fillTransparentBackground, + int index) { const auto rotation = int(geometry.rotation); const auto rect = TransformRect(geometry.rect, rotation); if (!rect.intersects(_clipOuter)) { @@ -99,8 +101,8 @@ void OverlayWidget::RendererSW::paintTransformedStaticContent( } void OverlayWidget::RendererSW::paintControlsFade( - QRect geometry, - float64 opacity) { + QRect geometry, + float64 opacity) { _p->setOpacity(opacity); _p->setClipRect(geometry); const auto width = _owner->width(); @@ -134,9 +136,9 @@ void OverlayWidget::RendererSW::paintControlsFade( } void OverlayWidget::RendererSW::paintTransformedImage( - const QImage &image, - QRect rect, - int rotation) { + const QImage &image, + QRect rect, + int rotation) { PainterHighQualityEnabler hq(*_p); if (UsePainterRotation(rotation)) { if (rotation) { @@ -153,9 +155,9 @@ void OverlayWidget::RendererSW::paintTransformedImage( } void OverlayWidget::RendererSW::paintRadialLoading( - QRect inner, - bool radial, - float64 radialOpacity) { + QRect inner, + bool radial, + float64 radialOpacity) { _owner->paintRadialLoadingContent(*_p, inner, radial, radialOpacity); } @@ -164,8 +166,8 @@ void OverlayWidget::RendererSW::paintThemePreview(QRect outer) { } void OverlayWidget::RendererSW::paintDocumentBubble( - QRect outer, - QRect icon) { + QRect outer, + QRect icon) { if (outer.intersects(_clipOuter)) { _owner->paintDocumentBubbleContent(*_p, outer, icon, _clipOuter); if (icon.intersects(_clipOuter)) { @@ -184,12 +186,12 @@ void OverlayWidget::RendererSW::paintControlsStart() { } void OverlayWidget::RendererSW::paintControl( - OverState control, - QRect over, - float64 overOpacity, - QRect inner, - float64 innerOpacity, - const style::icon &icon) { + OverState control, + QRect over, + float64 overOpacity, + QRect inner, + float64 innerOpacity, + const style::icon &icon) { if (!over.isEmpty() && !over.intersects(_clipOuter)) { return; } @@ -230,6 +232,21 @@ void OverlayWidget::RendererSW::paintRoundedCorners(int radius) { // The RpWindow rounding overlay will do the job. } +void OverlayWidget::RendererSW::paintStoriesSiblingPart( + int index, + const QImage &image, + QRect rect, + float64 opacity) { + const auto changeOpacity = (opacity != 1.); + if (changeOpacity) { + _p->setOpacity(opacity); + } + _p->drawImage(rect, image); + if (changeOpacity) { + _p->setOpacity(1.); + } +} + void OverlayWidget::RendererSW::validateOverControlImage() { const auto size = QSize(st::mediaviewIconOver, st::mediaviewIconOver); const auto alpha = base::SafeRound(kOverBackgroundOpacity * 255); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_raster.h b/Telegram/SourceFiles/media/view/media_view_overlay_raster.h index 3df304236..e1b6ca453 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_raster.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_raster.h @@ -27,7 +27,8 @@ private: const QImage &image, ContentGeometry geometry, bool semiTransparent, - bool fillTransparentBackground) override; + bool fillTransparentBackground, + int index = 0) override; void paintTransformedImage( const QImage &image, QRect rect, @@ -52,6 +53,11 @@ private: void paintCaption(QRect outer, float64 opacity) override; void paintGroupThumbs(QRect outer, float64 opacity) override; void paintRoundedCorners(int radius) override; + void paintStoriesSiblingPart( + int index, + const QImage &image, + QRect rect, + float64 opacity = 1.) override; void validateOverControlImage(); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_renderer.h b/Telegram/SourceFiles/media/view/media_view_overlay_renderer.h index f4a8cb9bb..cd3ea698e 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_renderer.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_renderer.h @@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/view/media_view_overlay_widget.h" +namespace Media::Stories { +struct SiblingView; +} // namespace Media::Stories + namespace Media::View { class OverlayWidget::Renderer : public Ui::GL::Renderer { @@ -19,7 +23,8 @@ public: const QImage &image, ContentGeometry geometry, bool semiTransparent, - bool fillTransparentBackground) = 0; + bool fillTransparentBackground, + int index = 0) = 0; // image, left sibling, right sibling virtual void paintRadialLoading( QRect inner, bool radial, @@ -39,7 +44,11 @@ public: virtual void paintCaption(QRect outer, float64 opacity) = 0; virtual void paintGroupThumbs(QRect outer, float64 opacity) = 0; virtual void paintRoundedCorners(int radius) = 0; - + virtual void paintStoriesSiblingPart( + int index, + const QImage &image, + QRect rect, + float64 opacity = 1.) = 0; }; } // namespace Media::View diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 76111c5e0..f154a93b8 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -125,6 +125,9 @@ constexpr auto kIdsLimit = 48; // Preload next messages if we went further from current than that. constexpr auto kIdsPreloadAfter = 28; +constexpr auto kLeftSiblingTextureIndex = 1; +constexpr auto kRightSiblingTextureIndex = 2; + class PipDelegate final : public Pip::Delegate { public: PipDelegate(QWidget *parent, not_null session); @@ -1444,17 +1447,18 @@ bool OverlayWidget::updateControlsAnimation(crl::time now) { } _helper->setControlsOpacity(_controlsOpacity.current()); const auto content = finalContentRect(); + const auto siblingType = (_over == OverLeftStories) + ? Stories::SiblingType::Left + : Stories::SiblingType::Right; const auto toUpdate = QRegion() + (_over == OverLeftNav ? _leftNavOver : _leftNavIcon) + (_over == OverRightNav ? _rightNavOver : _rightNavIcon) + (_over == OverSave ? _saveNavOver : _saveNavIcon) + (_over == OverRotate ? _rotateNavOver : _rotateNavIcon) + (_over == OverMore ? _moreNavOver : _moreNavIcon) - + ((_stories && _over == OverLeftStories) - ? _stories->siblingLeft().geometry - : QRect()) - + ((_stories && _over == OverRightStories) - ? _stories->siblingRight().geometry + + ((_stories + && (_over == OverLeftStories && _over == OverRightStories)) + ? _stories->sibling(siblingType).layout.geometry : QRect()) + _headerNav + _nameNav @@ -1492,8 +1496,9 @@ QRect OverlayWidget::finalContentRect() const { } OverlayWidget::ContentGeometry OverlayWidget::contentGeometry() const { - const auto fade = _stories ? _stories->contentFade() : 0.; - const auto radius = _stories ? float64(st::storiesRadius) : 0.; + if (_stories) { + return storiesContentGeometry(_stories->contentLayout()); + } const auto controlsOpacity = _controlsOpacity.current(); const auto toRotation = qreal(finalContentRotation()); const auto toRectRotated = QRectF(finalContentRect()); @@ -1506,7 +1511,7 @@ OverlayWidget::ContentGeometry OverlayWidget::contentGeometry() const { toRectRotated.width()) : toRectRotated; if (!_geometryAnimation.animating()) { - return { toRect, toRotation, controlsOpacity, fade, radius }; + return { toRect, toRotation, controlsOpacity }; } const auto fromRect = _oldGeometry.rect; const auto fromRotation = _oldGeometry.rotation; @@ -1529,7 +1534,17 @@ OverlayWidget::ContentGeometry OverlayWidget::contentGeometry() const { fromRect.width() + (toRect.width() - fromRect.width()) * progress, fromRect.height() + (toRect.height() - fromRect.height()) * progress ); - return { useRect, useRotation, controlsOpacity, fade, radius }; + return { useRect, useRotation, controlsOpacity }; +} + +OverlayWidget::ContentGeometry OverlayWidget::storiesContentGeometry( + const Stories::ContentLayout &layout) const { + return { + .rect = QRectF(layout.geometry), + .controlsOpacity = 0., // #TODO stories ?.. + .fade = layout.fade, + .roundRadius = layout.radius, + }; } void OverlayWidget::updateContentRect() { @@ -1568,7 +1583,7 @@ void OverlayWidget::recountSkipTop() { void OverlayWidget::resizeContentByScreenSize() { if (_stories) { - const auto content = _stories->contentGeometry(); + const auto content = _stories->finalShownGeometry(); _x = content.x(); _y = content.y(); _w = content.width(); @@ -4005,6 +4020,11 @@ void OverlayWidget::storiesTogglePaused(bool paused) { } } +float64 OverlayWidget::storiesSiblingOver(Stories::SiblingType type) { + return (type == Stories::SiblingType::Left) + ? overLevel(OverLeftStories) + : overLevel(OverRightStories); +} void OverlayWidget::storiesRepaint() { update(); } @@ -4165,20 +4185,34 @@ void OverlayWidget::paint(not_null renderer) { } paintRadialLoading(renderer); if (_stories) { - const auto radius = float64(st::storiesRadius); - if (const auto left = _stories->siblingLeft()) { + using namespace Stories; + const auto paint = [&](const SiblingView &view, int index) { renderer->paintTransformedStaticContent( - left.image, - { .rect = left.geometry, .roundRadius = radius }, + view.image, + storiesContentGeometry(view.layout), false, // semi-transparent - false); // fill transparent background + false, // fill transparent background + index); + const auto base = (index - 1) * 2; + const auto userpicSize = view.userpic.size() + / view.userpic.devicePixelRatio(); + renderer->paintStoriesSiblingPart( + base, + view.userpic, + QRect(view.userpicPosition, userpicSize)); + const auto nameSize = view.name.size() + / view.name.devicePixelRatio(); + renderer->paintStoriesSiblingPart( + base + 1, + view.name, + QRect(view.namePosition, nameSize), + view.nameOpacity); + }; + if (const auto left = _stories->sibling(SiblingType::Left)) { + paint(left, kLeftSiblingTextureIndex); } - if (const auto right = _stories->siblingRight()) { - renderer->paintTransformedStaticContent( - right.image, - { .rect = right.geometry, .roundRadius = radius }, - false, // semi-transparent - false); // fill transparent background + if (const auto right = _stories->sibling(SiblingType::Right)) { + paint(right, kRightSiblingTextureIndex); } } } else { @@ -4924,7 +4958,7 @@ void OverlayWidget::setStoriesUser(UserData *user) { _storiesSession = session; const auto delegate = static_cast(this); _stories = std::make_unique(delegate); - _stories->contentGeometryValue( + _stories->finalShownGeometryValue( ) | rpl::skip(1) | rpl::start_with_next([=] { updateControlsGeometry(); }, _stories->lifetime()); @@ -5155,6 +5189,7 @@ void OverlayWidget::handleMouseMove(QPoint position) { } void OverlayWidget::updateOverRect(OverState state) { + using Type = Stories::SiblingType; switch (state) { case OverLeftNav: update(_stories ? _leftNavIcon : _leftNavOver); @@ -5163,10 +5198,14 @@ void OverlayWidget::updateOverRect(OverState state) { update(_stories ? _rightNavIcon : _rightNavOver); break; case OverLeftStories: - update(_stories ? _stories->siblingLeft().geometry : QRect()); + update(_stories + ? _stories->sibling(Type::Left).layout.geometry : + QRect()); break; case OverRightStories: - update(_stories ? _stories->siblingRight().geometry : QRect()); + update(_stories + ? _stories->sibling(Type::Right).layout.geometry + : QRect()); break; case OverName: update(_nameNav); break; case OverDate: update(_dateNav); break; @@ -5250,19 +5289,27 @@ void OverlayWidget::updateOver(QPoint pos) { if (_pressed || _dragging) return; + using SiblingType = Stories::SiblingType; if (_fullScreenVideo) { updateOverState(OverVideo); } else if (_leftNavVisible && _leftNav.contains(pos)) { updateOverState(OverLeftNav); - } else if (_stories && _stories->siblingLeft().geometry.contains(pos)) { - updateOverState(OverLeftStories); - } else if (_stories && _stories->siblingRight().geometry.contains(pos)) { - updateOverState(OverRightStories); } else if (_rightNavVisible && _rightNav.contains(pos)) { updateOverState(OverRightNav); + } else if (_stories + && _stories->sibling( + SiblingType::Left).layout.geometry.contains(pos)) { + updateOverState(OverLeftStories); + } else if (_stories + && _stories->sibling( + SiblingType::Right).layout.geometry.contains(pos)) { + updateOverState(OverRightStories); } else if (!_stories && _from && _nameNav.contains(pos)) { updateOverState(OverName); - } else if (!_stories && _message && _message->isRegular() && _dateNav.contains(pos)) { + } else if (!_stories + && _message + && _message->isRegular() + && _dateNav.contains(pos)) { updateOverState(OverDate); } else if (!_stories && _headerHasLink && _headerNav.contains(pos)) { updateOverState(OverHeader); @@ -5270,7 +5317,9 @@ void OverlayWidget::updateOver(QPoint pos) { updateOverState(OverSave); } else if (_rotateVisible && _rotateNav.contains(pos)) { updateOverState(OverRotate); - } else if (_document && documentBubbleShown() && _docIconRect.contains(pos)) { + } else if (_document + && documentBubbleShown() + && _docIconRect.contains(pos)) { updateOverState(OverIcon); } else if (_moreNav.contains(pos)) { updateOverState(OverMore); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index a95f5735d..49c4a6df7 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -66,6 +66,7 @@ enum class Error; namespace Media::Stories { class View; +struct ContentLayout; } // namespace Media::Stories namespace Media::View { @@ -235,6 +236,7 @@ private: void storiesJumpTo(Data::FullStoryId id) override; bool storiesPaused() override; void storiesTogglePaused(bool paused) override; + float64 storiesSiblingOver(Stories::SiblingType type) override; void storiesRepaint() override; void hideControls(bool force = false); @@ -390,6 +392,8 @@ private: [[nodiscard]] int finalContentRotation() const; [[nodiscard]] QRect finalContentRect() const; [[nodiscard]] ContentGeometry contentGeometry() const; + [[nodiscard]] ContentGeometry storiesContentGeometry( + const Stories::ContentLayout &layout) const; void updateContentRect(); void contentSizeChanged();