Show userpic / name on sibling stories.

This commit is contained in:
John Preston 2023-05-11 18:36:59 +04:00
parent a0e9e148b0
commit 30871ed116
15 changed files with 469 additions and 120 deletions

View File

@ -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<Layout> 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<ChatHelpers::Show> 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*> 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 {};
}

View File

@ -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<Ui::RpWidget*> wrap() const;
[[nodiscard]] Layout layout() const;
[[nodiscard]] rpl::producer<Layout> layoutValue() const;
[[nodiscard]] float64 contentFade() const;
[[nodiscard]] ContentLayout contentLayout() const;
[[nodiscard]] std::shared_ptr<ChatHelpers::Show> uiShow() const;
[[nodiscard]] auto stickerOrEmojiChosen() const
@ -90,8 +99,7 @@ public:
[[nodiscard]] bool canDownload() const;
void repaintSibling(not_null<Sibling*> sibling);
[[nodiscard]] SiblingView siblingLeft() const;
[[nodiscard]] SiblingView siblingRight() const;
[[nodiscard]] SiblingView sibling(SiblingType type) const;
void unfocusReply();

View File

@ -27,6 +27,11 @@ enum class JumpReason {
User,
};
enum class SiblingType {
Left,
Right,
};
class Delegate {
public:
[[nodiscard]] virtual not_null<Ui::RpWidget*> storiesWrap() = 0;
@ -36,6 +41,7 @@ public:
-> rpl::producer<ChatHelpers::FileChosen> = 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;
};

View File

@ -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>(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() {

View File

@ -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*> _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<style::TextStyle> _nameStyle;
std::optional<Ui::Text::String> _name;
QString _nameText;
int _nameAvailableWidth = 0;
int _nameFontSize = 0;
std::unique_ptr<Loader> _loader;
};

View File

@ -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<QRect> View::contentGeometryValue() const {
rpl::producer<QRect> 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() {

View File

@ -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<QRect> contentGeometryValue() const;
[[nodiscard]] float64 contentFade() const;
[[nodiscard]] SiblingView siblingLeft() const;
[[nodiscard]] SiblingView siblingRight() const;
[[nodiscard]] QRect finalShownGeometry() const;
[[nodiscard]] rpl::producer<QRect> finalShownGeometryValue() const;
[[nodiscard]] ContentLayout contentLayout() const;
[[nodiscard]] SiblingView sibling(SiblingType type) const;
void updatePlayback(const Player::TrackState &state);

View File

@ -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 }};

View File

@ -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;

View File

@ -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<QOpenGLShaderProgram*> 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<QOpenGLShaderProgram> _fillProgram;
std::optional<QOpenGLShaderProgram> _controlsProgram;
std::optional<QOpenGLShaderProgram> _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,

View File

@ -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<OverlayWidget*> 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);

View File

@ -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();

View File

@ -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

View File

@ -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<Main::Session*> 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*> 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<Stories::Delegate*>(this);
_stories = std::make_unique<Stories::View>(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);

View File

@ -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();