665 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			665 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
This file is part of Telegram Desktop,
 | 
						|
the official desktop application for the Telegram messaging service.
 | 
						|
 | 
						|
For license and copyright information please follow this link:
 | 
						|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
						|
*/
 | 
						|
#include "ui/chat/message_bar.h"
 | 
						|
 | 
						|
#include "ui/effects/spoiler_mess.h"
 | 
						|
#include "ui/image/image_prepare.h"
 | 
						|
#include "ui/text/text_options.h"
 | 
						|
#include "ui/painter.h"
 | 
						|
#include "ui/power_saving.h"
 | 
						|
#include "styles/style_chat.h"
 | 
						|
#include "styles/style_chat_helpers.h"
 | 
						|
#include "styles/palette.h"
 | 
						|
 | 
						|
namespace Ui {
 | 
						|
namespace {
 | 
						|
 | 
						|
[[nodiscard]] int SameFirstPartLength(const QString &a, const QString &b) {
 | 
						|
	const auto &[i, j] = ranges::mismatch(a, b);
 | 
						|
	return (i - a.begin());
 | 
						|
}
 | 
						|
 | 
						|
[[nodiscard]] bool MuchDifferent(
 | 
						|
		int same,
 | 
						|
		const QString &a,
 | 
						|
		const QString &b) {
 | 
						|
	return (same * 2 < a.size()) || (same * 2 < b.size());
 | 
						|
}
 | 
						|
 | 
						|
[[nodiscard]] bool MuchDifferent(const QString &a, const QString &b) {
 | 
						|
	return MuchDifferent(SameFirstPartLength(a, b), a, b);
 | 
						|
}
 | 
						|
 | 
						|
[[nodiscard]] bool ComplexTitleAnimation(
 | 
						|
		int same,
 | 
						|
		const QString &a,
 | 
						|
		const QString &b) {
 | 
						|
	return !MuchDifferent(same, a, b)
 | 
						|
		&& (same != a.size() || same != b.size());
 | 
						|
}
 | 
						|
 | 
						|
} // namespace
 | 
						|
 | 
						|
MessageBar::MessageBar(
 | 
						|
	not_null<QWidget*> parent,
 | 
						|
	const style::MessageBar &st,
 | 
						|
	Fn<bool()> customEmojiPaused)
 | 
						|
: _st(st)
 | 
						|
, _widget(parent)
 | 
						|
, _customEmojiPaused(std::move(customEmojiPaused)) {
 | 
						|
	setup();
 | 
						|
 | 
						|
	style::PaletteChanged(
 | 
						|
	) | rpl::start_with_next([=] {
 | 
						|
		_topBarGradient = _bottomBarGradient = QPixmap();
 | 
						|
	}, _widget.lifetime());
 | 
						|
}
 | 
						|
 | 
						|
void MessageBar::customEmojiRepaint() {
 | 
						|
	if (_customEmojiRepaintScheduled) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	_customEmojiRepaintScheduled = true;
 | 
						|
	_widget.update();
 | 
						|
}
 | 
						|
 | 
						|
void MessageBar::setup() {
 | 
						|
	_widget.resize(0, st::historyReplyHeight);
 | 
						|
	_widget.paintRequest(
 | 
						|
	) | rpl::start_with_next([=](QRect rect) {
 | 
						|
		auto p = Painter(&_widget);
 | 
						|
		p.setInactive(_customEmojiPaused());
 | 
						|
		_customEmojiRepaintScheduled = false;
 | 
						|
		paint(p);
 | 
						|
	}, _widget.lifetime());
 | 
						|
}
 | 
						|
 | 
						|
void MessageBar::set(MessageBarContent &&content) {
 | 
						|
	_contentLifetime.destroy();
 | 
						|
	tweenTo(std::move(content));
 | 
						|
}
 | 
						|
 | 
						|
void MessageBar::set(rpl::producer<MessageBarContent> content) {
 | 
						|
	_contentLifetime.destroy();
 | 
						|
	std::move(
 | 
						|
		content
 | 
						|
	) | rpl::start_with_next([=](MessageBarContent &&content) {
 | 
						|
		tweenTo(std::move(content));
 | 
						|
	}, _contentLifetime);
 | 
						|
}
 | 
						|
 | 
						|
MessageBar::BodyAnimation MessageBar::DetectBodyAnimationType(
 | 
						|
		Animation *currentAnimation,
 | 
						|
		const MessageBarContent ¤tContent,
 | 
						|
		const MessageBarContent &nextContent) {
 | 
						|
	const auto now = currentAnimation
 | 
						|
		? currentAnimation->bodyAnimation
 | 
						|
		: BodyAnimation::None;
 | 
						|
	const auto somethingChanged = (currentContent.text != nextContent.text)
 | 
						|
		|| (currentContent.title != nextContent.title)
 | 
						|
		|| (currentContent.index != nextContent.index)
 | 
						|
		|| (currentContent.count != nextContent.count);
 | 
						|
	return (now == BodyAnimation::Full
 | 
						|
		|| MuchDifferent(currentContent.title, nextContent.title)
 | 
						|
		|| (currentContent.title.isEmpty() && somethingChanged))
 | 
						|
		? BodyAnimation::Full
 | 
						|
		: (now == BodyAnimation::Text || somethingChanged)
 | 
						|
		? BodyAnimation::Text
 | 
						|
		: BodyAnimation::None;
 | 
						|
}
 | 
						|
 | 
						|
void MessageBar::tweenTo(MessageBarContent &&content) {
 | 
						|
	Expects(content.count > 0);
 | 
						|
	Expects(content.index >= 0 && content.index < content.count);
 | 
						|
 | 
						|
	_widget.update();
 | 
						|
	if (!_st.duration || anim::Disabled() || _widget.size().isEmpty()) {
 | 
						|
		updateFromContent(std::move(content));
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	const auto hasImageChanged = (_content.preview.isNull()
 | 
						|
		!= content.preview.isNull());
 | 
						|
	const auto bodyChanged = (_content.index != content.index
 | 
						|
		|| _content.count != content.count
 | 
						|
		|| _content.title != content.title
 | 
						|
		|| _content.text != content.text
 | 
						|
		|| _content.preview.constBits() != content.preview.constBits());
 | 
						|
	const auto barCountChanged = (_content.count != content.count);
 | 
						|
	const auto barFrom = _content.index;
 | 
						|
	const auto barTo = content.index;
 | 
						|
	auto animation = Animation();
 | 
						|
	animation.bodyAnimation = DetectBodyAnimationType(
 | 
						|
		_animation.get(),
 | 
						|
		_content,
 | 
						|
		content);
 | 
						|
	animation.movingTo = (content.index > _content.index)
 | 
						|
		? RectPart::Top
 | 
						|
		: (content.index < _content.index)
 | 
						|
		? RectPart::Bottom
 | 
						|
		: RectPart::None;
 | 
						|
	animation.imageFrom = grabImagePart();
 | 
						|
	animation.spoilerFrom = std::move(_spoiler);
 | 
						|
	animation.bodyOrTextFrom = grabBodyOrTextPart(animation.bodyAnimation);
 | 
						|
	const auto sameLength = SameFirstPartLength(
 | 
						|
		_content.title,
 | 
						|
		content.title);
 | 
						|
	if (animation.bodyAnimation == BodyAnimation::Text
 | 
						|
		&& ComplexTitleAnimation(sameLength, _content.title, content.title)) {
 | 
						|
		animation.titleSame = grabTitleBase(sameLength);
 | 
						|
		animation.titleFrom = grabTitlePart(sameLength);
 | 
						|
	}
 | 
						|
	auto was = std::move(_animation);
 | 
						|
	updateFromContent(std::move(content));
 | 
						|
	animation.imageTo = grabImagePart();
 | 
						|
	animation.bodyOrTextTo = grabBodyOrTextPart(animation.bodyAnimation);
 | 
						|
	if (!animation.titleSame.isNull()) {
 | 
						|
		animation.titleTo = grabTitlePart(sameLength);
 | 
						|
	}
 | 
						|
	if (was) {
 | 
						|
		_animation = std::move(was);
 | 
						|
		std::swap(*_animation, animation);
 | 
						|
		_animation->imageShown = std::move(animation.imageShown);
 | 
						|
		_animation->barScroll = std::move(animation.barScroll);
 | 
						|
		_animation->barTop = std::move(animation.barTop);
 | 
						|
	} else {
 | 
						|
		_animation = std::make_unique<Animation>(std::move(animation));
 | 
						|
	}
 | 
						|
 | 
						|
	if (hasImageChanged) {
 | 
						|
		_animation->imageShown.start(
 | 
						|
			[=] { _widget.update(); },
 | 
						|
			_image.isNull() ? 1. : 0.,
 | 
						|
			_image.isNull() ? 0. : 1.,
 | 
						|
			_st.duration);
 | 
						|
	}
 | 
						|
	if (bodyChanged) {
 | 
						|
		_animation->bodyMoved.start(
 | 
						|
			[=] { _widget.update(); },
 | 
						|
			0.,
 | 
						|
			1.,
 | 
						|
			_st.duration);
 | 
						|
	}
 | 
						|
	if (barCountChanged) {
 | 
						|
		_animation->barScroll.stop();
 | 
						|
		_animation->barTop.stop();
 | 
						|
	} else if (barFrom != barTo) {
 | 
						|
		const auto wasState = countBarState(barFrom);
 | 
						|
		const auto nowState = countBarState(barTo);
 | 
						|
		_animation->barScroll.start(
 | 
						|
			[=] { _widget.update(); },
 | 
						|
			wasState.scroll,
 | 
						|
			nowState.scroll,
 | 
						|
			_st.duration);
 | 
						|
		_animation->barTop.start(
 | 
						|
			[] {},
 | 
						|
			wasState.offset,
 | 
						|
			nowState.offset,
 | 
						|
			_st.duration);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void MessageBar::updateFromContent(MessageBarContent &&content) {
 | 
						|
	_content = std::move(content);
 | 
						|
	_title.setText(_st.title, _content.title);
 | 
						|
	_text.setMarkedText(
 | 
						|
		_st.text,
 | 
						|
		_content.text,
 | 
						|
		Ui::DialogTextOptions(),
 | 
						|
		_content.context);
 | 
						|
	_image = prepareImage(_content.preview);
 | 
						|
	if (!_content.spoilerRepaint) {
 | 
						|
		_spoiler = nullptr;
 | 
						|
	} else if (!_spoiler) {
 | 
						|
		_spoiler = std::make_unique<SpoilerAnimation>(
 | 
						|
			_content.spoilerRepaint);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
QRect MessageBar::imageRect() const {
 | 
						|
	const auto left = st::msgReplyBarSkip + st::msgReplyBarSkip;
 | 
						|
	const auto top = (st::historyReplyHeight - st::historyReplyPreview) / 2;
 | 
						|
	const auto size = st::historyReplyPreview;
 | 
						|
	return QRect(left, top, size, size);
 | 
						|
}
 | 
						|
 | 
						|
QRect MessageBar::titleRangeRect(int from, int till) const {
 | 
						|
	auto result = bodyRect();
 | 
						|
	result.setHeight(st::msgServiceNameFont->height);
 | 
						|
	const auto left = from
 | 
						|
		? st::msgServiceNameFont->width(_content.title.mid(0, from))
 | 
						|
		: 0;
 | 
						|
	const auto right = (till <= _content.title.size())
 | 
						|
		? st::msgServiceNameFont->width(_content.title.mid(0, till))
 | 
						|
		: result.width();
 | 
						|
	result.setLeft(result.left() + left);
 | 
						|
	result.setWidth(right - left);
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
QRect MessageBar::bodyRect(bool withImage) const {
 | 
						|
	const auto innerLeft = st::msgReplyBarSkip + st::msgReplyBarSkip;
 | 
						|
	const auto imageSkip = st::historyReplyPreview + st::msgReplyBarSkip;
 | 
						|
	const auto left = innerLeft + (withImage ? imageSkip : 0);
 | 
						|
	const auto top = st::msgReplyPadding.top();
 | 
						|
	const auto width = _widget.width() - left - st::msgReplyPadding.right();
 | 
						|
	const auto height = (st::historyReplyHeight - 2 * top);
 | 
						|
	return QRect(left, top, width, height) - _content.margins;
 | 
						|
}
 | 
						|
 | 
						|
QRect MessageBar::bodyRect() const {
 | 
						|
	return bodyRect(!_image.isNull());
 | 
						|
}
 | 
						|
 | 
						|
QRect MessageBar::textRect() const {
 | 
						|
	auto result = bodyRect();
 | 
						|
	result.setTop(result.top() + st::msgServiceNameFont->height);
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
auto MessageBar::makeGrabGuard() {
 | 
						|
	auto imageShown = _animation
 | 
						|
		? std::move(_animation->imageShown)
 | 
						|
		: Ui::Animations::Simple();
 | 
						|
	auto spoiler = std::move(_spoiler);
 | 
						|
	auto fromSpoiler = _animation
 | 
						|
		? std::move(_animation->spoilerFrom)
 | 
						|
		: nullptr;
 | 
						|
	return gsl::finally([
 | 
						|
		&,
 | 
						|
		shown = std::move(imageShown),
 | 
						|
		spoiler = std::move(spoiler),
 | 
						|
		fromSpoiler = std::move(fromSpoiler)
 | 
						|
	]() mutable {
 | 
						|
		if (_animation) {
 | 
						|
			_animation->imageShown = std::move(shown);
 | 
						|
			_animation->spoilerFrom = std::move(fromSpoiler);
 | 
						|
		}
 | 
						|
		_spoiler = std::move(spoiler);
 | 
						|
	});
 | 
						|
}
 | 
						|
 | 
						|
QPixmap MessageBar::grabBodyOrTextPart(BodyAnimation type) {
 | 
						|
	return (type == BodyAnimation::Full)
 | 
						|
		? grabBodyPart()
 | 
						|
		: (type == BodyAnimation::Text)
 | 
						|
		? grabTextPart()
 | 
						|
		: QPixmap();
 | 
						|
}
 | 
						|
 | 
						|
QPixmap MessageBar::grabTitleBase(int till) {
 | 
						|
	return grabTitleRange(0, till);
 | 
						|
}
 | 
						|
 | 
						|
QPixmap MessageBar::grabTitlePart(int from) {
 | 
						|
	Expects(from <= _content.title.size());
 | 
						|
 | 
						|
	return grabTitleRange(from, _content.title.size());
 | 
						|
}
 | 
						|
 | 
						|
QPixmap MessageBar::grabTitleRange(int from, int till) {
 | 
						|
	const auto guard = makeGrabGuard();
 | 
						|
	return GrabWidget(widget(), titleRangeRect(from, till));
 | 
						|
}
 | 
						|
 | 
						|
QPixmap MessageBar::grabBodyPart() {
 | 
						|
	const auto guard = makeGrabGuard();
 | 
						|
	return GrabWidget(widget(), bodyRect());
 | 
						|
}
 | 
						|
 | 
						|
QPixmap MessageBar::grabTextPart() {
 | 
						|
	const auto guard = makeGrabGuard();
 | 
						|
	return GrabWidget(widget(), textRect());
 | 
						|
}
 | 
						|
 | 
						|
QPixmap MessageBar::grabImagePart() {
 | 
						|
	if (!_animation) {
 | 
						|
		return _image;
 | 
						|
	}
 | 
						|
	const auto guard = makeGrabGuard();
 | 
						|
	return (_animation->bodyMoved.animating()
 | 
						|
		&& !_animation->imageFrom.isNull()
 | 
						|
		&& !_animation->imageTo.isNull())
 | 
						|
		? GrabWidget(widget(), imageRect())
 | 
						|
		: _animation->imageFrom;
 | 
						|
}
 | 
						|
 | 
						|
void MessageBar::finishAnimating() {
 | 
						|
	if (_animation) {
 | 
						|
		_animation = nullptr;
 | 
						|
		_widget.update();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
QPixmap MessageBar::prepareImage(const QImage &preview) {
 | 
						|
	return QPixmap::fromImage(preview, Qt::ColorOnly);
 | 
						|
}
 | 
						|
 | 
						|
void MessageBar::paint(Painter &p) {
 | 
						|
	const auto progress = _animation ? _animation->bodyMoved.value(1.) : 1.;
 | 
						|
	const auto imageFinal = _image.isNull() ? 0. : 1.;
 | 
						|
	const auto imageShown = _animation
 | 
						|
		? _animation->imageShown.value(imageFinal)
 | 
						|
		: imageFinal;
 | 
						|
	if (progress == 1. && imageShown == imageFinal && _animation) {
 | 
						|
		_animation = nullptr;
 | 
						|
	}
 | 
						|
	const auto body = [&] {
 | 
						|
		if (!_animation || !_animation->imageShown.animating()) {
 | 
						|
			return bodyRect();
 | 
						|
		}
 | 
						|
		const auto noImage = bodyRect(false);
 | 
						|
		const auto withImage = bodyRect(true);
 | 
						|
		return QRect(
 | 
						|
			anim::interpolate(noImage.x(), withImage.x(), imageShown),
 | 
						|
			noImage.y(),
 | 
						|
			anim::interpolate(noImage.width(), withImage.width(), imageShown),
 | 
						|
			noImage.height());
 | 
						|
	}();
 | 
						|
	const auto text = textRect();
 | 
						|
	const auto image = imageRect();
 | 
						|
	const auto width = _widget.width();
 | 
						|
	const auto noShift = !_animation
 | 
						|
		|| (_animation->movingTo == RectPart::None);
 | 
						|
	const auto shiftFull = st::msgReplyBarSkip;
 | 
						|
	const auto shiftTo = noShift
 | 
						|
		? 0
 | 
						|
		: (_animation->movingTo == RectPart::Top)
 | 
						|
		? anim::interpolate(shiftFull, 0, progress)
 | 
						|
		: anim::interpolate(-shiftFull, 0, progress);
 | 
						|
	const auto shiftFrom = noShift
 | 
						|
		? 0
 | 
						|
		: (_animation->movingTo == RectPart::Top)
 | 
						|
		? (shiftTo - shiftFull)
 | 
						|
		: (shiftTo + shiftFull);
 | 
						|
	const auto now = crl::now();
 | 
						|
	const auto paused = p.inactive();
 | 
						|
	const auto pausedSpoiler = paused || On(PowerSaving::kChatSpoiler);
 | 
						|
 | 
						|
	paintLeftBar(p);
 | 
						|
 | 
						|
	if (!_animation) {
 | 
						|
		if (!_image.isNull()) {
 | 
						|
			paintImageWithSpoiler(
 | 
						|
				p,
 | 
						|
				image,
 | 
						|
				_image,
 | 
						|
				_spoiler.get(),
 | 
						|
				now,
 | 
						|
				pausedSpoiler);
 | 
						|
		}
 | 
						|
	} else if (!_animation->imageTo.isNull()
 | 
						|
		|| (!_animation->imageFrom.isNull()
 | 
						|
			&& _animation->imageShown.animating())) {
 | 
						|
		const auto rect = [&] {
 | 
						|
			if (!_animation->imageShown.animating()) {
 | 
						|
				return image;
 | 
						|
			}
 | 
						|
			const auto size = anim::interpolate(0, image.width(), imageShown);
 | 
						|
			return QRect(
 | 
						|
				image.x(),
 | 
						|
				image.y() + (image.height() - size) / 2,
 | 
						|
				size,
 | 
						|
				size);
 | 
						|
		}();
 | 
						|
		if (_animation->bodyMoved.animating()) {
 | 
						|
			p.setOpacity(1. - progress);
 | 
						|
			paintImageWithSpoiler(
 | 
						|
				p,
 | 
						|
				rect.translated(0, shiftFrom),
 | 
						|
				_animation->imageFrom,
 | 
						|
				_animation->spoilerFrom.get(),
 | 
						|
				now,
 | 
						|
				pausedSpoiler);
 | 
						|
			p.setOpacity(progress);
 | 
						|
			paintImageWithSpoiler(
 | 
						|
				p,
 | 
						|
				rect.translated(0, shiftTo),
 | 
						|
				_animation->imageTo,
 | 
						|
				_spoiler.get(),
 | 
						|
				now,
 | 
						|
				pausedSpoiler);
 | 
						|
			p.setOpacity(1.);
 | 
						|
		} else {
 | 
						|
			paintImageWithSpoiler(
 | 
						|
				p,
 | 
						|
				rect,
 | 
						|
				_image,
 | 
						|
				_spoiler.get(),
 | 
						|
				now,
 | 
						|
				pausedSpoiler);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (!_animation || _animation->bodyAnimation == BodyAnimation::None) {
 | 
						|
		if (_title.isEmpty()) {
 | 
						|
			// "Loading..." state.
 | 
						|
			p.setPen(st::historyComposeAreaFgService);
 | 
						|
			_text.drawLeftElided(
 | 
						|
				p,
 | 
						|
				body.x(),
 | 
						|
				body.y() + (body.height() - st::normalFont->height) / 2,
 | 
						|
				body.width(),
 | 
						|
				width);
 | 
						|
		} else {
 | 
						|
			p.setPen(_st.textFg);
 | 
						|
			_text.draw(p, {
 | 
						|
				.position = { body.x(), text.y() },
 | 
						|
				.outerWidth = width,
 | 
						|
				.availableWidth = body.width(),
 | 
						|
				.palette = &_st.textPalette,
 | 
						|
				.spoiler = Ui::Text::DefaultSpoilerCache(),
 | 
						|
				.now = now,
 | 
						|
				.pausedEmoji = paused || On(PowerSaving::kEmojiChat),
 | 
						|
				.pausedSpoiler = pausedSpoiler,
 | 
						|
				.elisionLines = 1,
 | 
						|
			});
 | 
						|
		}
 | 
						|
	} else if (_animation->bodyAnimation == BodyAnimation::Text) {
 | 
						|
		p.setOpacity(1. - progress);
 | 
						|
		p.drawPixmap(
 | 
						|
			body.x(),
 | 
						|
			text.y() + shiftFrom,
 | 
						|
			_animation->bodyOrTextFrom);
 | 
						|
		p.setOpacity(progress);
 | 
						|
		p.drawPixmap(body.x(), text.y() + shiftTo, _animation->bodyOrTextTo);
 | 
						|
		p.setOpacity(1.);
 | 
						|
	}
 | 
						|
	if (!_animation || _animation->bodyAnimation != BodyAnimation::Full) {
 | 
						|
		if (_animation && !_animation->titleSame.isNull()) {
 | 
						|
			const auto factor = style::DevicePixelRatio();
 | 
						|
			p.drawPixmap(body.x(), body.y(), _animation->titleSame);
 | 
						|
			p.setOpacity(1. - progress);
 | 
						|
			p.drawPixmap(
 | 
						|
				body.x() + (_animation->titleSame.width() / factor),
 | 
						|
				body.y() + shiftFrom,
 | 
						|
				_animation->titleFrom);
 | 
						|
			p.setOpacity(progress);
 | 
						|
			p.drawPixmap(
 | 
						|
				body.x() + (_animation->titleSame.width() / factor),
 | 
						|
				body.y() + shiftTo,
 | 
						|
				_animation->titleTo);
 | 
						|
			p.setOpacity(1.);
 | 
						|
		} else {
 | 
						|
			p.setPen(_st.titleFg);
 | 
						|
			_title.drawLeftElided(p, body.x(), body.y(), body.width(), width);
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		p.setOpacity(1. - progress);
 | 
						|
		p.drawPixmap(
 | 
						|
			body.x(),
 | 
						|
			body.y() + shiftFrom,
 | 
						|
			_animation->bodyOrTextFrom);
 | 
						|
		p.setOpacity(progress);
 | 
						|
		p.drawPixmap(body.x(), body.y() + shiftTo, _animation->bodyOrTextTo);
 | 
						|
		p.setOpacity(1.);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
auto MessageBar::countBarState(int index) const -> BarState {
 | 
						|
	Expects(index >= 0 && index < _content.count);
 | 
						|
 | 
						|
	auto result = BarState();
 | 
						|
	const auto line = st::msgReplyBarSize.width();
 | 
						|
	const auto height = st::msgReplyBarSize.height();
 | 
						|
	const auto count = _content.count;
 | 
						|
	const auto shownCount = std::min(count, 4);
 | 
						|
	const auto dividers = (shownCount - 1) * line;
 | 
						|
	const auto size = float64(st::msgReplyBarSize.height() - dividers)
 | 
						|
		/ shownCount;
 | 
						|
	const auto fullHeight = count * size + (count - 1) * line;
 | 
						|
	const auto topByIndex = [&](int index) {
 | 
						|
		return index * (size + line);
 | 
						|
	};
 | 
						|
	result.scroll = (count < 5 || index < 2)
 | 
						|
		? 0
 | 
						|
		: (index >= count - 2)
 | 
						|
		? (fullHeight - height)
 | 
						|
		: (topByIndex(index) - (height - size) / 2);
 | 
						|
	result.size = size;
 | 
						|
	result.skip = line;
 | 
						|
	result.offset = topByIndex(index);
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
auto MessageBar::countBarState() const -> BarState {
 | 
						|
	return countBarState(_content.index);
 | 
						|
}
 | 
						|
 | 
						|
void MessageBar::ensureGradientsCreated(int size) {
 | 
						|
	if (!_topBarGradient.isNull()) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	const auto rows = size * style::DevicePixelRatio() - 2;
 | 
						|
	auto bottomMask = QImage(
 | 
						|
		QSize(1, size) * style::DevicePixelRatio(),
 | 
						|
		QImage::Format_ARGB32_Premultiplied);
 | 
						|
	const auto step = ((1ULL << 24) - 1) / rows;
 | 
						|
	const auto limit = step * rows;
 | 
						|
	auto bits = bottomMask.bits();
 | 
						|
	const auto perLine = bottomMask.bytesPerLine();
 | 
						|
	for (auto counter = uint32(0); counter != limit; counter += step) {
 | 
						|
		const auto value = (counter >> 16);
 | 
						|
		memset(bits, int(value), perLine);
 | 
						|
		bits += perLine;
 | 
						|
	}
 | 
						|
	memset(bits, 255, perLine * 2);
 | 
						|
	auto bottom = style::colorizeImage(bottomMask, st::historyPinnedBg);
 | 
						|
	bottom.setDevicePixelRatio(style::DevicePixelRatio());
 | 
						|
	auto top = bottom.mirrored();
 | 
						|
	_bottomBarGradient = Images::PixmapFast(std::move(bottom));
 | 
						|
	_topBarGradient = Images::PixmapFast(std::move(top));
 | 
						|
}
 | 
						|
 | 
						|
void MessageBar::paintImageWithSpoiler(
 | 
						|
		QPainter &p,
 | 
						|
		QRect rect,
 | 
						|
		const QPixmap &image,
 | 
						|
		SpoilerAnimation *spoiler,
 | 
						|
		crl::time now,
 | 
						|
		bool paused) const {
 | 
						|
	p.drawPixmap(rect, image);
 | 
						|
	if (spoiler) {
 | 
						|
		const auto frame = DefaultImageSpoiler().frame(
 | 
						|
			spoiler->index(now, paused));
 | 
						|
		FillSpoilerRect(p, rect, frame);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void MessageBar::paintLeftBar(Painter &p) {
 | 
						|
	const auto state = countBarState();
 | 
						|
	const auto gradientSize = int(std::ceil(state.size * 2.5));
 | 
						|
	if (_content.count > 4) {
 | 
						|
		ensureGradientsCreated(gradientSize);
 | 
						|
	}
 | 
						|
 | 
						|
	const auto scroll = _animation
 | 
						|
		? _animation->barScroll.value(state.scroll)
 | 
						|
		: state.scroll;
 | 
						|
	const auto offset = _animation
 | 
						|
		? _animation->barTop.value(state.offset)
 | 
						|
		: state.offset;
 | 
						|
	const auto line = st::msgReplyBarSize.width();
 | 
						|
	const auto height = st::msgReplyBarSize.height();
 | 
						|
	const auto activeFrom = offset - scroll;
 | 
						|
	const auto activeTill = activeFrom + state.size;
 | 
						|
	const auto single = state.size + state.skip;
 | 
						|
 | 
						|
	const auto barSkip = st::msgReplyPadding.top() + st::msgReplyBarPos.y();
 | 
						|
	const auto fullHeight = barSkip + height + barSkip;
 | 
						|
	const auto bar = QRect(
 | 
						|
		st::msgReplyBarSkip + st::msgReplyBarPos.x(),
 | 
						|
		barSkip,
 | 
						|
		line,
 | 
						|
		state.size);
 | 
						|
	const auto paintFromScroll = std::max(scroll - barSkip, 0.);
 | 
						|
	const auto paintFrom = int(std::floor(paintFromScroll / single));
 | 
						|
	const auto paintTillScroll = (scroll + height + barSkip);
 | 
						|
	const auto paintTill = std::min(
 | 
						|
		int(std::floor(paintTillScroll / single)) + 1,
 | 
						|
		_content.count);
 | 
						|
 | 
						|
	p.setPen(Qt::NoPen);
 | 
						|
	const auto activeBrush = QBrush(st::msgInReplyBarColor);
 | 
						|
	const auto inactiveBrush = QBrush(QColor(
 | 
						|
		st::msgInReplyBarColor->c.red(),
 | 
						|
		st::msgInReplyBarColor->c.green(),
 | 
						|
		st::msgInReplyBarColor->c.blue(),
 | 
						|
		st::msgInReplyBarColor->c.alpha() / 3));
 | 
						|
	const auto radius = line / 2.;
 | 
						|
	auto hq = PainterHighQualityEnabler(p);
 | 
						|
	p.setClipRect(bar.x(), 0, bar.width(), fullHeight);
 | 
						|
	for (auto i = paintFrom; i != paintTill; ++i) {
 | 
						|
		const auto top = i * single - scroll;
 | 
						|
		const auto bottom = top + state.size;
 | 
						|
		const auto active = (top == activeFrom);
 | 
						|
		p.setBrush(active ? activeBrush : inactiveBrush);
 | 
						|
		p.drawRoundedRect(bar.translated(0, top), radius, radius);
 | 
						|
		if (active
 | 
						|
			|| bottom - line <= activeFrom
 | 
						|
			|| top + line >= activeTill) {
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
		const auto partFrom = std::max(top, activeFrom);
 | 
						|
		const auto partTill = std::min(bottom, activeTill);
 | 
						|
		p.setBrush(activeBrush);
 | 
						|
		p.drawRoundedRect(
 | 
						|
			QRect(bar.x(), bar.y() + partFrom, line, partTill - partFrom),
 | 
						|
			radius,
 | 
						|
			radius);
 | 
						|
	}
 | 
						|
	p.setClipping(false);
 | 
						|
	if (_content.count > 4) {
 | 
						|
		const auto firstScroll = countBarState(2).scroll;
 | 
						|
		const auto gradientTop = (scroll >= firstScroll)
 | 
						|
			? 0
 | 
						|
			: anim::interpolate(-gradientSize, 0, scroll / firstScroll);
 | 
						|
		const auto lastScroll = countBarState(_content.count - 3).scroll;
 | 
						|
		const auto largestScroll = countBarState(_content.count - 1).scroll;
 | 
						|
		const auto gradientBottom = (scroll <= lastScroll)
 | 
						|
			? fullHeight
 | 
						|
			: anim::interpolate(
 | 
						|
				fullHeight,
 | 
						|
				fullHeight + gradientSize,
 | 
						|
				(scroll - lastScroll) / (largestScroll - lastScroll));
 | 
						|
		if (gradientTop > -gradientSize) {
 | 
						|
			p.drawPixmap(
 | 
						|
				QRect(bar.x(), gradientTop, bar.width(), gradientSize),
 | 
						|
				_topBarGradient);
 | 
						|
		}
 | 
						|
		if (gradientBottom < fullHeight + gradientSize) {
 | 
						|
			p.drawPixmap(
 | 
						|
				QRect(
 | 
						|
					bar.x(),
 | 
						|
					gradientBottom - gradientSize,
 | 
						|
					bar.width(),
 | 
						|
					gradientSize),
 | 
						|
				_bottomBarGradient);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
} // namespace Ui
 |