1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4 
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "window/section_widget.h"
9 
10 #include "mainwidget.h"
11 #include "ui/ui_utility.h"
12 #include "ui/chat/chat_theme.h"
13 #include "data/data_peer.h"
14 #include "data/data_changes.h"
15 #include "data/data_session.h"
16 #include "data/data_cloud_themes.h"
17 #include "main/main_session.h"
18 #include "window/section_memento.h"
19 #include "window/window_slide_animation.h"
20 #include "window/window_session_controller.h"
21 #include "window/themes/window_theme.h"
22 
23 #include <rpl/range.h>
24 
25 namespace Window {
26 namespace {
27 
PeerThemeEmojiValue(not_null<PeerData * > peer)28 [[nodiscard]] rpl::producer<QString> PeerThemeEmojiValue(
29 		not_null<PeerData*> peer) {
30 	return peer->session().changes().peerFlagsValue(
31 		peer,
32 		Data::PeerUpdate::Flag::ChatThemeEmoji
33 	) | rpl::map([=] {
34 		return peer->themeEmoji();
35 	});
36 }
37 
MaybeChatThemeDataValueFromPeer(not_null<PeerData * > peer)38 [[nodiscard]] auto MaybeChatThemeDataValueFromPeer(
39 	not_null<PeerData*> peer)
40 -> rpl::producer<std::optional<Data::CloudTheme>> {
41 	return PeerThemeEmojiValue(
42 		peer
43 	) | rpl::map([=](const QString &emoji)
44 	-> rpl::producer<std::optional<Data::CloudTheme>> {
45 		return peer->owner().cloudThemes().themeForEmojiValue(emoji);
46 	}) | rpl::flatten_latest();
47 }
48 
49 struct ResolvedTheme {
50 	std::optional<Data::CloudTheme> theme;
51 	bool dark = false;
52 };
53 
MaybeCloudThemeValueFromPeer(not_null<PeerData * > peer)54 [[nodiscard]] auto MaybeCloudThemeValueFromPeer(
55 	not_null<PeerData*> peer)
56 -> rpl::producer<ResolvedTheme> {
57 	return rpl::combine(
58 		MaybeChatThemeDataValueFromPeer(peer),
59 		Theme::IsThemeDarkValue() | rpl::distinct_until_changed()
60 	) | rpl::map([](std::optional<Data::CloudTheme> theme, bool night) {
61 		return ResolvedTheme{ std::move(theme), night };
62 	});
63 }
64 
65 } // namespace
66 
AbstractSectionWidget(QWidget * parent,not_null<SessionController * > controller,rpl::producer<PeerData * > peerForBackground)67 AbstractSectionWidget::AbstractSectionWidget(
68 	QWidget *parent,
69 	not_null<SessionController*> controller,
70 	rpl::producer<PeerData*> peerForBackground)
71 : RpWidget(parent)
72 , _controller(controller) {
73 	std::move(
74 		peerForBackground
75 	) | rpl::map([=](PeerData *peer) -> rpl::producer<> {
76 		if (!peer) {
77 			return rpl::single(
78 				rpl::empty_value()
79 			) | rpl::then(
80 				controller->defaultChatTheme()->repaintBackgroundRequests()
81 			);
82 		}
83 		return ChatThemeValueFromPeer(
84 			controller,
85 			peer
86 		) | rpl::map([](const std::shared_ptr<Ui::ChatTheme> &theme) {
87 			return rpl::single(
88 				rpl::empty_value()
89 			) | rpl::then(
90 				theme->repaintBackgroundRequests()
91 			);
92 		}) | rpl::flatten_latest();
93 	}) | rpl::flatten_latest() | rpl::start_with_next([=] {
94 		update();
95 	}, lifetime());
96 }
97 
session() const98 Main::Session &AbstractSectionWidget::session() const {
99 	return _controller->session();
100 }
101 
SectionWidget(QWidget * parent,not_null<Window::SessionController * > controller,rpl::producer<PeerData * > peerForBackground)102 SectionWidget::SectionWidget(
103 	QWidget *parent,
104 	not_null<Window::SessionController*> controller,
105 	rpl::producer<PeerData*> peerForBackground)
106 : AbstractSectionWidget(parent, controller, std::move(peerForBackground)) {
107 }
108 
SectionWidget(QWidget * parent,not_null<Window::SessionController * > controller,not_null<PeerData * > peerForBackground)109 SectionWidget::SectionWidget(
110 	QWidget *parent,
111 	not_null<Window::SessionController*> controller,
112 	not_null<PeerData*> peerForBackground)
113 : AbstractSectionWidget(
114 	parent,
115 	controller,
116 	rpl::single(peerForBackground.get())) {
117 }
118 
setGeometryWithTopMoved(const QRect & newGeometry,int topDelta)119 void SectionWidget::setGeometryWithTopMoved(
120 		const QRect &newGeometry,
121 		int topDelta) {
122 	_topDelta = topDelta;
123 	bool willBeResized = (size() != newGeometry.size());
124 	if (geometry() != newGeometry) {
125 		auto weak = Ui::MakeWeak(this);
126 		setGeometry(newGeometry);
127 		if (!weak) {
128 			return;
129 		}
130 	}
131 	if (!willBeResized) {
132 		resizeEvent(nullptr);
133 	}
134 	_topDelta = 0;
135 }
136 
showAnimated(SlideDirection direction,const SectionSlideParams & params)137 void SectionWidget::showAnimated(
138 		SlideDirection direction,
139 		const SectionSlideParams &params) {
140 	if (_showAnimation) return;
141 
142 	showChildren();
143 	auto myContentCache = grabForShowAnimation(params);
144 	hideChildren();
145 	showAnimatedHook(params);
146 
147 	_showAnimation = std::make_unique<SlideAnimation>();
148 	_showAnimation->setDirection(direction);
149 	_showAnimation->setRepaintCallback([this] { update(); });
150 	_showAnimation->setFinishedCallback([this] { showFinished(); });
151 	_showAnimation->setPixmaps(
152 		params.oldContentCache,
153 		myContentCache);
154 	_showAnimation->setTopBarShadow(params.withTopBarShadow);
155 	_showAnimation->setWithFade(params.withFade);
156 	_showAnimation->start();
157 
158 	show();
159 }
160 
createMemento()161 std::shared_ptr<SectionMemento> SectionWidget::createMemento() {
162 	return nullptr;
163 }
164 
showFast()165 void SectionWidget::showFast() {
166 	show();
167 	showFinished();
168 }
169 
grabForShowAnimation(const SectionSlideParams & params)170 QPixmap SectionWidget::grabForShowAnimation(
171 		const SectionSlideParams &params) {
172 	return Ui::GrabWidget(this);
173 }
174 
PaintBackground(not_null<Window::SessionController * > controller,not_null<Ui::ChatTheme * > theme,not_null<QWidget * > widget,QRect clip)175 void SectionWidget::PaintBackground(
176 		not_null<Window::SessionController*> controller,
177 		not_null<Ui::ChatTheme*> theme,
178 		not_null<QWidget*> widget,
179 		QRect clip) {
180 	Painter p(widget);
181 
182 	const auto &background = theme->background();
183 	if (background.colorForFill) {
184 		p.fillRect(clip, *background.colorForFill);
185 		return;
186 	}
187 	const auto &gradient = background.gradientForFill;
188 	const auto fill = QSize(widget->width(), controller->content()->height());
189 	auto fromy = controller->content()->backgroundFromY();
190 	auto state = theme->backgroundState(fill);
191 	const auto paintCache = [&](const Ui::CachedBackground &cache) {
192 		const auto to = QRect(
193 			QPoint(cache.x, fromy + cache.y),
194 			cache.pixmap.size() / cIntRetinaFactor());
195 		if (cache.waitingForNegativePattern) {
196 			// While we wait for pattern being loaded we paint just gradient.
197 			// But in case of negative patter opacity we just fill-black.
198 			p.fillRect(to, Qt::black);
199 		} else if (cache.area == fill) {
200 			p.drawPixmap(to, cache.pixmap);
201 		} else {
202 			const auto sx = fill.width() / float64(cache.area.width());
203 			const auto sy = fill.height() / float64(cache.area.height());
204 			const auto round = [](float64 value) -> int {
205 				return (value >= 0.)
206 					? int(std::ceil(value))
207 					: int(std::floor(value));
208 			};
209 			const auto sto = QPoint(round(to.x() * sx), round(to.y() * sy));
210 			p.drawPixmap(
211 				sto.x(),
212 				sto.y(),
213 				round((to.x() + to.width()) * sx) - sto.x(),
214 				round((to.y() + to.height()) * sy) - sto.y(),
215 				cache.pixmap);
216 		}
217 	};
218 	const auto hasNow = !state.now.pixmap.isNull();
219 	const auto goodNow = hasNow && (state.now.area == fill);
220 	const auto useCache = goodNow || !gradient.isNull();
221 	if (useCache) {
222 		if (state.shown < 1. && !gradient.isNull()) {
223 			paintCache(state.was);
224 			p.setOpacity(state.shown);
225 		}
226 		paintCache(state.now);
227 		return;
228 	}
229 	const auto &prepared = background.prepared;
230 	if (prepared.isNull()) {
231 		return;
232 	} else if (background.isPattern) {
233 		const auto w = prepared.width() * fill.height() / prepared.height();
234 		const auto cx = qCeil(fill.width() / float64(w));
235 		const auto cols = (cx / 2) * 2 + 1;
236 		const auto xshift = (fill.width() - w * cols) / 2;
237 		for (auto i = 0; i != cols; ++i) {
238 			p.drawImage(
239 				QRect(xshift + i * w, 0, w, fill.height()),
240 				prepared,
241 				QRect(QPoint(), prepared.size()));
242 		}
243 	} else if (background.tile) {
244 		const auto &tiled = background.preparedForTiled;
245 		const auto left = clip.left();
246 		const auto top = clip.top();
247 		const auto right = clip.left() + clip.width();
248 		const auto bottom = clip.top() + clip.height();
249 		const auto w = tiled.width() / cRetinaFactor();
250 		const auto h = tiled.height() / cRetinaFactor();
251 		const auto sx = qFloor(left / w);
252 		const auto sy = qFloor((top - fromy) / h);
253 		const auto cx = qCeil(right / w);
254 		const auto cy = qCeil((bottom - fromy) / h);
255 		for (auto i = sx; i < cx; ++i) {
256 			for (auto j = sy; j < cy; ++j) {
257 				p.drawImage(QPointF(i * w, fromy + j * h), tiled);
258 			}
259 		}
260 	} else {
261 		const auto hq = PainterHighQualityEnabler(p);
262 		const auto rects = Ui::ComputeChatBackgroundRects(
263 			fill,
264 			prepared.size());
265 		auto to = rects.to;
266 		to.moveTop(to.top() + fromy);
267 		p.drawImage(to, prepared, rects.from);
268 	}
269 }
270 
paintEvent(QPaintEvent * e)271 void SectionWidget::paintEvent(QPaintEvent *e) {
272 	if (_showAnimation) {
273 		Painter p(this);
274 		_showAnimation->paintContents(p, e->rect());
275 	}
276 }
277 
showFinished()278 void SectionWidget::showFinished() {
279 	_showAnimation.reset();
280 	if (isHidden()) return;
281 
282 	showChildren();
283 	showFinishedHook();
284 
285 	setInnerFocus();
286 }
287 
desiredHeight() const288 rpl::producer<int> SectionWidget::desiredHeight() const {
289 	return rpl::single(height());
290 }
291 
292 SectionWidget::~SectionWidget() = default;
293 
ChatThemeValueFromPeer(not_null<SessionController * > controller,not_null<PeerData * > peer)294 auto ChatThemeValueFromPeer(
295 	not_null<SessionController*> controller,
296 	not_null<PeerData*> peer)
297 -> rpl::producer<std::shared_ptr<Ui::ChatTheme>> {
298 	auto cloud = MaybeCloudThemeValueFromPeer(
299 		peer
300 	) | rpl::map([=](ResolvedTheme resolved)
301 	-> rpl::producer<std::shared_ptr<Ui::ChatTheme>> {
302 		return resolved.theme
303 			? controller->cachedChatThemeValue(
304 				*resolved.theme,
305 				(resolved.dark
306 					? Data::CloudThemeType::Dark
307 					: Data::CloudThemeType::Light))
308 			: rpl::single(controller->defaultChatTheme());
309 	}) | rpl::flatten_latest(
310 	) | rpl::distinct_until_changed();
311 
312 	return rpl::combine(
313 		std::move(cloud),
314 		controller->peerThemeOverrideValue()
315 	) | rpl::map([=](
316 			std::shared_ptr<Ui::ChatTheme> &&cloud,
317 			PeerThemeOverride &&overriden) {
318 		return (overriden.peer == peer.get())
319 			? std::move(overriden.theme)
320 			: std::move(cloud);
321 	});
322 }
323 
324 } // namespace Window
325