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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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