Paint phone frame + animate preview transitions.
This commit is contained in:
		
							parent
							
								
									25bd97a12b
								
							
						
					
					
						commit
						0e90092d59
					
				
					 1 changed files with 126 additions and 18 deletions
				
			
		| 
						 | 
				
			
			@ -339,6 +339,36 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &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<Ui::RpWidget*> VideoPreview(
 | 
			
		||||
		not_null<Ui::RpWidget*> parent,
 | 
			
		||||
		not_null<Window::SessionController*> controller,
 | 
			
		||||
| 
						 | 
				
			
			@ -368,24 +398,35 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
 | 
			
		|||
		QImage blurred;
 | 
			
		||||
		Media::Streaming::Instance instance;
 | 
			
		||||
		std::shared_ptr<Data::DocumentMedia> media;
 | 
			
		||||
		QPainterPath frame;
 | 
			
		||||
	};
 | 
			
		||||
	const auto state = lifetime.make_state<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<Data::DocumentMedia> &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<Data::DocumentMedia> &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<Data::DocumentMedia> &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<Ui::RpWidget*> widget;
 | 
			
		||||
		int leftFrom = 0;
 | 
			
		||||
		int leftTill = 0;
 | 
			
		||||
	};
 | 
			
		||||
	struct State {
 | 
			
		||||
		int leftFrom = 0;
 | 
			
		||||
		Ui::RpWidget *content = nullptr;
 | 
			
		||||
		Ui::Animations::Simple animation;
 | 
			
		||||
		std::vector<Hiding> hiding;
 | 
			
		||||
		rpl::variable<PremiumPreview> selected;
 | 
			
		||||
	};
 | 
			
		||||
	const auto state = outer->lifetime().make_state<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(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue