mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-18 23:27:09 +02:00
Show captions with darkening over stories.
This commit is contained in:
parent
0d3df824e3
commit
0331955ce7
12 changed files with 151 additions and 72 deletions
|
@ -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 ¤t = _layout.current();
|
||||
Assert(current.has_value());
|
||||
|
||||
return {
|
||||
.geometry = _layout.current()->content,
|
||||
.geometry = current->content,
|
||||
.fade = (_contentFadeAnimation.value(_contentFaded ? 1. : 0.)
|
||||
* kFullContentFade),
|
||||
.radius = float64(st::storiesRadius),
|
||||
.radius = st::storiesRadius,
|
||||
.headerOutside = (current->headerLayout == HeaderLayout::Outside),
|
||||
};
|
||||
}
|
||||
|
||||
TextWithEntities Controller::captionText() const {
|
||||
return _captionText;
|
||||
}
|
||||
|
||||
std::shared_ptr<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 });
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -21,7 +21,7 @@ namespace Media::Stories {
|
|||
namespace {
|
||||
|
||||
constexpr auto kNameOpacity = 1.;
|
||||
constexpr auto kDateOpacity = 0.6;
|
||||
constexpr auto kDateOpacity = 0.8;
|
||||
|
||||
} // namespace
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Reference in a new issue