Show captions with darkening over stories.

This commit is contained in:
John Preston 2023-05-12 13:41:25 +04:00
parent 0d3df824e3
commit 0331955ce7
12 changed files with 151 additions and 72 deletions

View file

@ -28,7 +28,7 @@ namespace {
constexpr auto kPhotoProgressInterval = crl::time(100); constexpr auto kPhotoProgressInterval = crl::time(100);
constexpr auto kPhotoDuration = 5 * crl::time(1000); constexpr auto kPhotoDuration = 5 * crl::time(1000);
constexpr auto kFullContentFade = 0.2; constexpr auto kFullContentFade = 0.35;
constexpr auto kSiblingMultiplier = 0.448; constexpr auto kSiblingMultiplier = 0.448;
constexpr auto kSiblingOutsidePart = 0.24; constexpr auto kSiblingOutsidePart = 0.24;
constexpr auto kSiblingUserpicSize = 0.3; constexpr auto kSiblingUserpicSize = 0.3;
@ -276,14 +276,22 @@ rpl::producer<Layout> Controller::layoutValue() const {
} }
ContentLayout Controller::contentLayout() const { ContentLayout Controller::contentLayout() const {
const auto &current = _layout.current();
Assert(current.has_value());
return { return {
.geometry = _layout.current()->content, .geometry = current->content,
.fade = (_contentFadeAnimation.value(_contentFaded ? 1. : 0.) .fade = (_contentFadeAnimation.value(_contentFaded ? 1. : 0.)
* kFullContentFade), * kFullContentFade),
.radius = float64(st::storiesRadius), .radius = st::storiesRadius,
.headerOutside = (current->headerLayout == HeaderLayout::Outside),
}; };
} }
TextWithEntities Controller::captionText() const {
return _captionText;
}
std::shared_ptr<ChatHelpers::Show> Controller::uiShow() const { std::shared_ptr<ChatHelpers::Show> Controller::uiShow() const {
return _delegate->storiesShow(); return _delegate->storiesShow();
} }
@ -325,6 +333,7 @@ void Controller::show(
return; return;
} }
_shown = id; _shown = id;
_captionText = item.caption;
_header->show({ .user = list.user, .date = item.date }); _header->show({ .user = list.user, .date = item.date });
_slider->show({ .index = _index, .total = list.total }); _slider->show({ .index = _index, .total = list.total });

View file

@ -77,6 +77,7 @@ public:
[[nodiscard]] Layout layout() const; [[nodiscard]] Layout layout() const;
[[nodiscard]] rpl::producer<Layout> layoutValue() const; [[nodiscard]] rpl::producer<Layout> layoutValue() const;
[[nodiscard]] ContentLayout contentLayout() const; [[nodiscard]] ContentLayout contentLayout() const;
[[nodiscard]] TextWithEntities captionText() const;
[[nodiscard]] std::shared_ptr<ChatHelpers::Show> uiShow() const; [[nodiscard]] std::shared_ptr<ChatHelpers::Show> uiShow() const;
[[nodiscard]] auto stickerOrEmojiChosen() const [[nodiscard]] auto stickerOrEmojiChosen() const
@ -134,6 +135,7 @@ private:
bool _contentFaded = false; bool _contentFaded = false;
Data::FullStoryId _shown; Data::FullStoryId _shown;
TextWithEntities _captionText;
std::optional<Data::StoriesList> _list; std::optional<Data::StoriesList> _list;
int _index = 0; int _index = 0;
bool _started = false; bool _started = false;

View file

@ -21,7 +21,7 @@ namespace Media::Stories {
namespace { namespace {
constexpr auto kNameOpacity = 1.; constexpr auto kNameOpacity = 1.;
constexpr auto kDateOpacity = 0.6; constexpr auto kDateOpacity = 0.8;
} // namespace } // namespace

View file

@ -256,7 +256,7 @@ SiblingView Sibling::view(const SiblingLayout &layout, float64 over) {
.layout = { .layout = {
.geometry = layout.geometry, .geometry = layout.geometry,
.fade = kSiblingFade * (1 - over) + kSiblingFadeOver * over, .fade = kSiblingFade * (1 - over) + kSiblingFadeOver * over,
.radius = float64(st::storiesRadius), .radius = st::storiesRadius,
}, },
.userpic = userpicImage(layout), .userpic = userpicImage(layout),
.userpicPosition = layout.userpic.topLeft(), .userpicPosition = layout.userpic.topLeft(),

View file

@ -79,6 +79,10 @@ SiblingView View::sibling(SiblingType type) const {
return _controller->sibling(type); return _controller->sibling(type);
} }
TextWithEntities View::captionText() const {
return _controller->captionText();
}
rpl::lifetime &View::lifetime() { rpl::lifetime &View::lifetime() {
return _controller->lifetime(); return _controller->lifetime();
} }

View file

@ -23,7 +23,8 @@ class Controller;
struct ContentLayout { struct ContentLayout {
QRect geometry; QRect geometry;
float64 fade = 0.; float64 fade = 0.;
float64 radius = 0.; int radius = 0;
bool headerOutside = false;
}; };
enum class SiblingType; enum class SiblingType;
@ -61,6 +62,7 @@ public:
[[nodiscard]] rpl::producer<QRect> finalShownGeometryValue() const; [[nodiscard]] rpl::producer<QRect> finalShownGeometryValue() const;
[[nodiscard]] ContentLayout contentLayout() const; [[nodiscard]] ContentLayout contentLayout() const;
[[nodiscard]] SiblingView sibling(SiblingType type) const; [[nodiscard]] SiblingView sibling(SiblingType type) const;
[[nodiscard]] TextWithEntities captionText() const;
void updatePlayback(const Player::TrackState &state); void updatePlayback(const Player::TrackState &state);

View file

@ -196,6 +196,7 @@ mediaviewSaveMsgStyle: TextStyle(defaultTextStyle) {
mediaviewTextPalette: TextPalette(defaultTextPalette) { mediaviewTextPalette: TextPalette(defaultTextPalette) {
linkFg: mediaviewTextLinkFg; linkFg: mediaviewTextLinkFg;
monoFg: mediaviewCaptionFg; monoFg: mediaviewCaptionFg;
spoilerFg: mediaviewCaptionFg;
} }
mediaviewCaptionStyle: defaultTextStyle; mediaviewCaptionStyle: defaultTextStyle;
@ -429,6 +430,8 @@ storiesHeaderDate: FlatLabel(defaultFlatLabel) {
textFg: mediaviewControlFg; textFg: mediaviewControlFg;
} }
storiesHeaderDatePosition: point(50px, 17px); storiesHeaderDatePosition: point(50px, 17px);
storiesShadowTop: icon{{ "mediaview/shadow_bottom-flip_vertical", windowShadowFg }};
storiesShadowBottom: mediaviewShadowBottom;
storiesControlsMinWidth: 200px; storiesControlsMinWidth: 200px;
storiesFieldMargin: margins(0px, 14px, 0px, 16px); storiesFieldMargin: margins(0px, 14px, 0px, 16px);
storiesAttach: IconButton(defaultIconButton) { storiesAttach: IconButton(defaultIconButton) {

View file

@ -36,13 +36,14 @@ constexpr auto kControlValues = 4 * 4 + 4 * 4; // over + icon
.header = R"( .header = R"(
uniform sampler2D f_texture; uniform sampler2D f_texture;
uniform vec4 shadowTopRect; uniform vec4 shadowTopRect;
uniform vec3 shadowBottomOpacityFullFade; uniform vec4 shadowBottomSkipOpacityFullFade;
)", )",
.body = R"( .body = R"(
float topHeight = shadowTopRect.w; float topHeight = shadowTopRect.w;
float bottomHeight = shadowBottomOpacityFullFade.x; float bottomHeight = shadowBottomSkipOpacityFullFade.x;
float opacity = shadowBottomOpacityFullFade.y; float bottomSkip = shadowBottomSkipOpacityFullFade.y;
float fullFade = shadowBottomOpacityFullFade.z; float opacity = shadowBottomSkipOpacityFullFade.z;
float fullFade = shadowBottomSkipOpacityFullFade.w;
float viewportHeight = shadowTopRect.y + topHeight; float viewportHeight = shadowTopRect.y + topHeight;
float fullHeight = topHeight + bottomHeight; float fullHeight = topHeight + bottomHeight;
float topY = min( float topY = min(
@ -50,7 +51,8 @@ uniform vec3 shadowBottomOpacityFullFade;
topHeight / fullHeight); topHeight / fullHeight);
float topX = (gl_FragCoord.x - shadowTopRect.x) / shadowTopRect.z; float topX = (gl_FragCoord.x - shadowTopRect.x) / shadowTopRect.z;
vec4 fadeTop = texture2D(f_texture, vec2(topX, topY)) * opacity; 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; vec4 fadeBottom = texture2D(f_texture, vec2(0.5, bottomY)) * opacity;
float fade = min((1. - fadeTop.a) * (1. - fadeBottom.a), fullFade); float fade = min((1. - fadeTop.a) * (1. - fadeBottom.a), fullFade);
result.rgb = result.rgb * fade; result.rgb = result.rgb * fade;
@ -496,16 +498,30 @@ void OverlayWidget::RendererGL::paintTransformedContent(
_contentBuffer->write(0, coords, sizeof(coords)); _contentBuffer->write(0, coords, sizeof(coords));
program->setUniformValue("viewport", _uniformViewport); program->setUniformValue("viewport", _uniformViewport);
const auto &top = st::mediaviewShadowTop.size(); if (_owner->_stories) {
const auto point = QPoint( const auto &top = st::storiesShadowTop.size();
_shadowTopFlip ? 0 : (_viewport.width() - top.width()), const auto shadowTop = geometry.topShadowShown
0); ? geometry.rect.y()
program->setUniformValue( : geometry.rect.y() - top.height();
"shadowTopRect", program->setUniformValue(
Uniform(transformRect(QRect(point, top)))); "shadowTopRect",
const auto &bottom = st::mediaviewShadowBottom; Uniform(transformRect(
program->setUniformValue("shadowBottomOpacityFullFade", QVector3D( 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, bottom.height() * _factor,
geometry.bottomShadowSkip * _factor,
geometry.controlsOpacity, geometry.controlsOpacity,
1.f - float(geometry.fade))); 1.f - float(geometry.fade)));
if (!fillTransparentBackground) { if (!fillTransparentBackground) {
@ -733,14 +749,23 @@ void OverlayWidget::RendererGL::invalidateControls() {
void OverlayWidget::RendererGL::validateControlsFade() { void OverlayWidget::RendererGL::validateControlsFade() {
const auto flip = !_owner->topShadowOnTheRight(); const auto flip = !_owner->topShadowOnTheRight();
const auto forStories = (_owner->_stories != nullptr);
if (!_controlsFadeImage.image().isNull() if (!_controlsFadeImage.image().isNull()
&& _shadowTopFlip == flip) { && _shadowTopFlip == flip
&& _shadowsForStories == forStories) {
return; return;
} }
_shadowTopFlip = flip; _shadowTopFlip = flip;
const auto width = st::mediaviewShadowTop.width(); _shadowsForStories = forStories;
const auto bottomTop = st::mediaviewShadowTop.height(); const auto &top = _shadowsForStories
const auto height = bottomTop + st::mediaviewShadowBottom.height(); ? 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( auto image = QImage(
QSize(width, height) * _factor, QSize(width, height) * _factor,
@ -749,10 +774,10 @@ void OverlayWidget::RendererGL::validateControlsFade() {
image.setDevicePixelRatio(_factor); image.setDevicePixelRatio(_factor);
auto p = QPainter(&image); auto p = QPainter(&image);
st::mediaviewShadowTop.paint(p, 0, 0, width); top.paint(p, 0, 0, width);
st::mediaviewShadowBottom.fill( bottom.fill(
p, p,
QRect(0, bottomTop, width, st::mediaviewShadowBottom.height())); QRect(0, bottomTop, width, bottom.height()));
p.end(); p.end();
if (flip) { if (flip) {
@ -760,12 +785,6 @@ void OverlayWidget::RendererGL::validateControlsFade() {
} }
_controlsFadeImage.setImage(std::move(image)); _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) { void OverlayWidget::RendererGL::paintFooter(QRect outer, float64 opacity) {

View file

@ -153,10 +153,8 @@ private:
// Last one is for the over circle image. // Last one is for the over circle image.
std::array<QRect, kControlsCount + 1> _controlsTextures; std::array<QRect, kControlsCount + 1> _controlsTextures;
QRect _shadowTopTexture; bool _shadowTopFlip = false;
QRect _shadowBottomTexture; bool _shadowsForStories = false;
bool _shadowTopFlip;
bool _blendingEnabled = false; bool _blendingEnabled = false;
rpl::lifetime _lifetime; rpl::lifetime _lifetime;

View file

@ -113,11 +113,16 @@ void OverlayWidget::RendererSW::paintControlsFade(
_p->setOpacity(opacity); _p->setOpacity(opacity);
_p->setClipRect(geometry); _p->setClipRect(geometry);
const auto width = _owner->width(); const auto width = _owner->width();
const auto &top = st::mediaviewShadowTop;
const auto flip = !_owner->topShadowOnTheRight(); const auto flip = !_owner->topShadowOnTheRight();
const auto topShadow = QRect( const auto stories = (_owner->_stories != nullptr);
QPoint(flip ? 0 : (width - top.width()), 0), const auto &top = stories
top.size()); ? 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 (topShadow.intersected(geometry).intersects(_clipOuter)) {
if (flip) { if (flip) {
if (_topShadowCache.isNull() if (_topShadowCache.isNull()
@ -131,7 +136,9 @@ void OverlayWidget::RendererSW::paintControlsFade(
top.paint(*_p, topShadow.topLeft(), width); top.paint(*_p, topShadow.topLeft(), width);
} }
} }
const auto &bottom = st::mediaviewShadowBottom; const auto &bottom = stories
? st::storiesShadowBottom
: st::mediaviewShadowBottom;
const auto bottomShadow = QRect( const auto bottomShadow = QRect(
QPoint(0, _owner->height() - bottom.height()), QPoint(0, _owner->height() - bottom.height()),
QSize(width, bottom.height())); QSize(width, bottom.height()));

View file

@ -127,6 +127,7 @@ constexpr auto kIdsPreloadAfter = 28;
constexpr auto kLeftSiblingTextureIndex = 1; constexpr auto kLeftSiblingTextureIndex = 1;
constexpr auto kRightSiblingTextureIndex = 2; constexpr auto kRightSiblingTextureIndex = 2;
constexpr auto kStoriesControlsOpacity = 1.;
class PipDelegate final : public Pip::Delegate { class PipDelegate final : public Pip::Delegate {
public: public:
@ -1200,27 +1201,35 @@ void OverlayWidget::refreshCaptionGeometry() {
_groupThumbs = nullptr; _groupThumbs = nullptr;
_groupThumbsRect = QRect(); _groupThumbsRect = QRect();
} }
const auto captionBottom = (_streamed && _streamed->controls) const auto captionBottom = _stories
? (_y + _h)
: (_streamed && _streamed->controls)
? (_streamed->controls->y() - st::mediaviewCaptionMargin.height()) ? (_streamed->controls->y() - st::mediaviewCaptionMargin.height())
: _groupThumbs : _groupThumbs
? _groupThumbsTop ? _groupThumbsTop
: height() - st::mediaviewCaptionMargin.height(); : height() - st::mediaviewCaptionMargin.height();
const auto captionWidth = std::min( const auto captionWidth = _stories
_groupThumbsAvailableWidth ? (_w
- st::mediaviewCaptionPadding.left() - st::mediaviewCaptionPadding.left()
- st::mediaviewCaptionPadding.right(), - st::mediaviewCaptionPadding.right())
_caption.maxWidth()); : std::min(
const auto captionHeight = std::min( (_groupThumbsAvailableWidth
_caption.countHeight(captionWidth), - st::mediaviewCaptionPadding.left()
height() / 4 - st::mediaviewCaptionPadding.right()),
_caption.maxWidth());
const auto maxHeight = (_stories ? (_h / 3) : (height() / 4))
- st::mediaviewCaptionPadding.top() - st::mediaviewCaptionPadding.top()
- st::mediaviewCaptionPadding.bottom() - 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( _captionRect = QRect(
(width() - captionWidth) / 2, (width() - captionWidth) / 2,
captionBottom (captionBottom
- captionHeight - captionHeight
- st::mediaviewCaptionPadding.bottom(), - st::mediaviewCaptionPadding.bottom()),
captionWidth, captionWidth,
captionHeight); captionHeight);
} }
@ -1497,7 +1506,14 @@ QRect OverlayWidget::finalContentRect() const {
OverlayWidget::ContentGeometry OverlayWidget::contentGeometry() const { OverlayWidget::ContentGeometry OverlayWidget::contentGeometry() const {
if (_stories) { 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 controlsOpacity = _controlsOpacity.current();
const auto toRotation = qreal(finalContentRotation()); const auto toRotation = qreal(finalContentRotation());
@ -1541,9 +1557,10 @@ OverlayWidget::ContentGeometry OverlayWidget::storiesContentGeometry(
const Stories::ContentLayout &layout) const { const Stories::ContentLayout &layout) const {
return { return {
.rect = QRectF(layout.geometry), .rect = QRectF(layout.geometry),
.controlsOpacity = 0., // #TODO stories ?.. .controlsOpacity = kStoriesControlsOpacity,
.fade = layout.fade, .fade = layout.fade,
.roundRadius = layout.radius, .roundRadius = layout.radius,
.topShadowShown = !layout.headerOutside,
}; };
} }
@ -2708,21 +2725,26 @@ void OverlayWidget::refreshFromLabel() {
void OverlayWidget::refreshCaption() { void OverlayWidget::refreshCaption() {
_caption = Ui::Text::String(); _caption = Ui::Text::String();
if (!_message) { const auto caption = [&] {
return; if (_message) {
} else if (const auto media = _message->media()) { if (const auto media = _message->media()) {
if (media->webpage()) { if (media->webpage()) {
return; return TextWithEntities();
}
}
return _message->translatedText();
} else if (_stories) {
return _stories->captionText();
} }
} return TextWithEntities();
const auto caption = _message->translatedText(); }();
if (caption.text.isEmpty()) { if (caption.text.isEmpty()) {
return; return;
} }
using namespace HistoryView; using namespace HistoryView;
_caption = Ui::Text::String(st::msgMinWidth); _caption = Ui::Text::String(st::msgMinWidth);
const auto duration = (_streamed && _document) const auto duration = (_streamed && _document && _message)
? DurationForTimestampLinks(_document) ? DurationForTimestampLinks(_document)
: 0; : 0;
const auto base = duration const auto base = duration
@ -2735,7 +2757,9 @@ void OverlayWidget::refreshCaption() {
update(captionGeometry()); update(captionGeometry());
}; };
const auto context = Core::MarkedTextContext{ const auto context = Core::MarkedTextContext{
.session = &_message->history()->session(), .session = (_stories
? _storiesSession
: &_message->history()->session()),
.customEmojiRepaint = captionRepaint, .customEmojiRepaint = captionRepaint,
}; };
_caption.setMarkedText( _caption.setMarkedText(
@ -2743,7 +2767,9 @@ void OverlayWidget::refreshCaption() {
(base.isEmpty() (base.isEmpty()
? caption ? caption
: AddTimestampLinks(caption, duration, base)), : AddTimestampLinks(caption, duration, base)),
Ui::ItemTextOptions(_message), (_message
? Ui::ItemTextOptions(_message)
: Ui::ItemTextDefaultOptions()),
context); context);
if (_caption.hasSpoilers()) { if (_caption.hasSpoilers()) {
const auto weak = Ui::MakeWeak(widget()); const auto weak = Ui::MakeWeak(widget());
@ -4627,10 +4653,15 @@ void OverlayWidget::paintCaptionContent(
QRect clip, QRect clip,
float64 opacity) { float64 opacity) {
const auto inner = outer.marginsRemoved(st::mediaviewCaptionPadding); const auto inner = outer.marginsRemoved(st::mediaviewCaptionPadding);
p.setOpacity(opacity); if (!_stories) {
p.setBrush(st::mediaviewCaptionBg); p.setOpacity(opacity);
p.setPen(Qt::NoPen); p.setBrush(st::mediaviewCaptionBg);
p.drawRoundedRect(outer, st::mediaviewCaptionRadius, st::mediaviewCaptionRadius); p.setPen(Qt::NoPen);
p.drawRoundedRect(
outer,
st::mediaviewCaptionRadius,
st::mediaviewCaptionRadius);
}
if (inner.intersects(clip)) { if (inner.intersects(clip)) {
p.setPen(st::mediaviewCaptionFg); p.setPen(st::mediaviewCaptionFg);
_caption.draw(p, { _caption.draw(p, {

View file

@ -166,8 +166,12 @@ private:
QRectF rect; QRectF rect;
qreal rotation = 0.; qreal rotation = 0.;
qreal controlsOpacity = 0.; qreal controlsOpacity = 0.;
// Stories.
qreal fade = 0.; qreal fade = 0.;
qreal roundRadius = 0.; int bottomShadowSkip = 0;
int roundRadius = 0;
bool topShadowShown = false;
}; };
struct StartStreaming { struct StartStreaming {
StartStreaming() : continueStreaming(false), startTime(0) { StartStreaming() : continueStreaming(false), startTime(0) {