From 0e90092d5937f454f32fdc7488730a097d828fd7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 10 Jun 2022 17:14:45 +0400 Subject: [PATCH] Paint phone frame + animate preview transitions. --- .../SourceFiles/boxes/premium_preview_box.cpp | 144 +++++++++++++++--- 1 file changed, 126 insertions(+), 18 deletions(-) diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.cpp b/Telegram/SourceFiles/boxes/premium_preview_box.cpp index 6686fcf7e..99effaee0 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_preview_box.cpp @@ -339,6 +339,36 @@ void PreloadSticker(const std::shared_ptr &media) { return (i != end(videos)) ? i->second.get() : nullptr; } +[[nodiscard]] QPainterPath GenerateFrame( + int left, + int top, + int width, + int height) { + const auto radius = style::ConvertScaleExact(20.); + const auto thickness = style::ConvertScaleExact(6.); + const auto skip = thickness / 2.; + auto path = QPainterPath(); + path.moveTo(left - skip, top + height); + path.lineTo(left - skip, top - skip + radius); + path.arcTo( + left - skip, + top - skip, + radius * 2, + radius * 2, + 180, + -90); + path.lineTo(left + width + skip - radius, top - skip); + path.arcTo( + left + width + skip - 2 * radius, + top - skip, + radius * 2, + radius * 2, + 90, + -90); + path.lineTo(left + width + skip, top + height); + return path; +} + [[nodiscard]] not_null VideoPreview( not_null parent, not_null controller, @@ -368,24 +398,35 @@ void PreloadSticker(const std::shared_ptr &media) { QImage blurred; Media::Streaming::Instance instance; std::shared_ptr media; + QPainterPath frame; }; const auto state = lifetime.make_state(std::move(shared), [] {}); state->media = document->createMediaView(); if (const auto image = state->media->thumbnailInline()) { - state->blurred = image->original(); + if (image->width() > 0) { + const auto width = st::premiumVideoWidth; + const auto height = std::max( + int(base::SafeRound( + float64(width) * image->height() / image->width())), + 1); + using Option = Images::Option; + state->blurred = Images::Prepare( + image->original(), + QSize(width, height) * style::DevicePixelRatio(), + { .options = (Option::Blur + | Option::RoundLarge + | Option::RoundSkipBottomLeft + | Option::RoundSkipBottomRight), + }); + } } const auto width = st::premiumVideoWidth; - const auto height = state->blurred.size().isEmpty() - ? width - : int(base::SafeRound(float64(width) * state->blurred.height() - / state->blurred.width())); - if (!state->blurred.isNull()) { - const auto factor = style::DevicePixelRatio(); - state->blurred = state->blurred.scaled( - QSize(width, height) * factor, - Qt::IgnoreAspectRatio, - Qt::SmoothTransformation); - } + const auto height = state->blurred.height() + ? state->blurred.height() + : width; + const auto left = (st::boxWideWidth - width) / 2; + const auto top = st::premiumPreviewHeight - height; + state->frame = GenerateFrame(left, top, width, height); const auto check = [=] { if (state->instance.playerLocked()) { return; @@ -413,6 +454,14 @@ void PreloadSticker(const std::shared_ptr &media) { result->paintRequest( ) | rpl::start_with_next([=] { auto p = QPainter(result); + const auto paintFrame = [&](QColor color, float64 thickness) { + auto hq = PainterHighQualityEnabler(p); + auto pen = QPen(color); + pen.setWidthF(style::ConvertScaleExact(thickness)); + p.setPen(pen); + p.setBrush(Qt::NoBrush); + p.drawPath(state->frame); + }; check(); const auto left = (result->width() - width) / 2; @@ -422,8 +471,15 @@ void PreloadSticker(const std::shared_ptr &media) { const auto size = QSize(width, height) * style::DevicePixelRatio(); const auto frame = !ready ? state->blurred - : state->instance.frame({ .resize = size, .outer = size }); + : state->instance.frame({ + .resize = size, + .outer = size, + .radius = ImageRoundRadius::Large, + .corners = RectPart::TopLeft | RectPart::TopRight, + }); + paintFrame(QColor(0, 0, 0, 128), 12.); p.drawImage(QRect(left, top, width, height), frame); + paintFrame(Qt::black, 6.6); if (ready) { state->instance.markFrameShown(); } @@ -955,15 +1011,24 @@ void PreviewBox( const Descriptor &descriptor, const std::shared_ptr &media, const QImage &back) { - const auto size = QSize(st::boxWideWidth, st::premiumPreviewHeight); + const auto single = st::boxWideWidth; + const auto size = QSize(single, st::premiumPreviewHeight); box->setWidth(size.width()); box->setNoContentMargin(true); const auto outer = box->addRow( ChatBackPreview(box, size.height(), back), {}); + struct Hiding { + not_null widget; + int leftFrom = 0; + int leftTill = 0; + }; struct State { + int leftFrom = 0; Ui::RpWidget *content = nullptr; + Ui::Animations::Simple animation; + std::vector hiding; rpl::variable selected; }; const auto state = outer->lifetime().make_state(); @@ -988,10 +1053,53 @@ void PreviewBox( break; } - state->selected.changes( - ) | rpl::start_with_next([=](PremiumPreview section) { - delete base::take(state->content); - state->content = GenerateDefaultPreview(outer, controller, section); + state->selected.value( + ) | rpl::combine_previous( + ) | rpl::start_with_next([=](PremiumPreview was, PremiumPreview now) { + const auto animationCallback = [=] { + if (!state->animation.animating()) { + for (const auto &hiding : base::take(state->hiding)) { + delete hiding.widget; + } + state->leftFrom = 0; + state->content->move(0, 0); + } else { + const auto progress = state->animation.value(1.); + state->content->move( + anim::interpolate(state->leftFrom, 0, progress), + 0); + for (const auto &hiding : state->hiding) { + hiding.widget->move(anim::interpolate( + hiding.leftFrom, + hiding.leftTill, + progress), 0); + } + } + }; + animationCallback(); + const auto toLeft = int(now) > int(was); + auto start = state->content->x() + (toLeft ? single : -single); + for (const auto &hiding : state->hiding) { + const auto left = hiding.widget->x(); + if (toLeft && left + single > start) { + start = left + single; + } else if (!toLeft && left - single < start) { + start = left - single; + } + } + for (auto &hiding : state->hiding) { + hiding.leftFrom = hiding.widget->x(); + hiding.leftTill = hiding.leftFrom - start; + } + state->hiding.push_back({ + .widget = state->content, + .leftFrom = state->content->x(), + .leftTill = state->content->x() - start, + }); + state->leftFrom = start; + state->content = GenerateDefaultPreview(outer, controller, now); + state->animation.stop(); + state->animation.start(animationCallback, 0., 1., st::slideDuration); }, outer->lifetime()); auto title = state->selected.value(