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/themes/window_theme_preview.h"
9 
10 #include "lang/lang_keys.h"
11 #include "platform/platform_window_title.h"
12 #include "ui/text/text_options.h"
13 #include "ui/image/image_prepare.h"
14 #include "ui/emoji_config.h"
15 #include "ui/chat/chat_theme.h"
16 #include "ui/image/image_prepare.h"
17 #include "styles/style_widgets.h"
18 #include "styles/style_window.h"
19 #include "styles/style_media_view.h"
20 #include "styles/style_chat.h"
21 #include "styles/style_dialogs.h"
22 #include "styles/style_info.h"
23 
24 namespace Window {
25 namespace Theme {
26 namespace {
27 
fillLetters(const QString & name)28 QString fillLetters(const QString &name) {
29 	QList<QString> letters;
30 	QList<int> levels;
31 	auto level = 0;
32 	auto letterFound = false;
33 	auto ch = name.constData(), end = ch + name.size();
34 	while (ch != end) {
35 		auto emojiLength = 0;
36 		if (Ui::Emoji::Find(ch, end, &emojiLength)) {
37 			ch += emojiLength;
38 		} else if (ch->isHighSurrogate()) {
39 			++ch;
40 			if (ch != end && ch->isLowSurrogate()) {
41 				++ch;
42 			}
43 		} else if (!letterFound && ch->isLetterOrNumber()) {
44 			letterFound = true;
45 			if (ch + 1 != end && Ui::Text::IsDiac(*(ch + 1))) {
46 				letters.push_back(QString(ch, 2));
47 				levels.push_back(level);
48 				++ch;
49 			} else {
50 				letters.push_back(QString(ch, 1));
51 				levels.push_back(level);
52 			}
53 			++ch;
54 		} else {
55 			if (*ch == ' ') {
56 				level = 0;
57 				letterFound = false;
58 			} else if (letterFound && *ch == '-') {
59 				level = 1;
60 				letterFound = true;
61 			}
62 			++ch;
63 		}
64 	}
65 
66 	// We prefer the second letter to be after ' ', but it can also be after '-'.
67 	auto result = QString();
68 	if (!letters.isEmpty()) {
69 		result += letters.front();
70 		auto bestIndex = 0;
71 		auto bestLevel = 2;
72 		for (auto i = letters.size(); i != 1;) {
73 			if (levels[--i] < bestLevel) {
74 				bestIndex = i;
75 				bestLevel = levels[i];
76 			}
77 		}
78 		if (bestIndex > 0) {
79 			result += letters[bestIndex];
80 		}
81 	}
82 	return result.toUpper();
83 }
84 
85 class Generator {
86 public:
87 	Generator(
88 		const Instance &theme,
89 		CurrentData &&current,
90 		PreviewType type);
91 
92 	[[nodiscard]] QImage generate();
93 
94 private:
95 	enum class Status {
96 		None,
97 		Sent,
98 		Received
99 	};
100 	struct Row {
101 		Ui::Text::String name;
102 		QString letters;
103 		enum class Type {
104 			User,
105 			Group,
106 			Channel
107 		};
108 		Type type = Type::User;
109 		int peerIndex = 0;
110 		int unreadCounter = 0;
111 		bool muted = false;
112 		bool pinned = false;
113 		QString date;
114 		Ui::Text::String text;
115 		Status status = Status::None;
116 		bool selected = false;
117 		bool active = false;
118 	};
119 	struct Bubble {
120 		int width = 0;
121 		int height = 0;
122 		bool outbg = false;
123 		Status status = Status::None;
124 		QString date;
125 		bool attached = false;
126 		bool tail = true;
127 		Ui::Text::String text = { st::msgMinWidth };
128 		QVector<int> waveform;
129 		int waveactive = 0;
130 		QString wavestatus;
131 		QImage photo;
132 		int photoWidth = 0;
133 		int photoHeight = 0;
134 		Ui::Text::String replyName = { st::msgMinWidth };
135 		Ui::Text::String replyText = { st::msgMinWidth };
136 	};
137 
138 	[[nodiscard]] bool extended() const;
139 	void prepare();
140 
141 	void addRow(QString name, int peerIndex, QString date, QString text);
142 	void addBubble(Bubble bubble, int width, int height, QString date, Status status);
143 	void addAudioBubble(QVector<int> waveform, int waveactive, QString wavestatus, QString date, Status status);
144 	void addTextBubble(QString text, QString date, Status status);
145 	void addDateBubble(QString date);
146 	void addPhotoBubble(QString image, QString caption, QString date, Status status);
147 	QSize computeSkipBlock(Status status, QString date);
148 	int computeInfoWidth(Status status, QString date);
149 
150 	void generateData();
151 
152 	void paintHistoryList();
153 	void paintHistoryBackground();
154 	void paintTopBar();
155 	void paintComposeArea();
156 	void paintDialogs();
157 	void paintDialogsList();
158 	void paintHistoryShadows();
159 	void paintRow(const Row &row);
160 	void paintBubble(const Bubble &bubble);
161 	void paintService(QString text);
162 
163 	void paintUserpic(int x, int y, Row::Type type, int index, QString letters);
164 
165 	void setTextPalette(const style::TextPalette &st);
166 	void restoreTextPalette();
167 
168 	const Instance &_theme;
169 	const style::palette &_palette;
170 	const CurrentData _current;
171 	const PreviewType _type;
172 	Painter *_p = nullptr;
173 
174 	QRect _rect;
175 	QRect _inner;
176 	QRect _body;
177 	QRect _dialogs;
178 	QRect _dialogsList;
179 	QRect _topBar;
180 	QRect _composeArea;
181 	QRect _history;
182 
183 	int _rowsTop = 0;
184 	std::vector<Row> _rows;
185 
186 	Ui::Text::String _topBarName;
187 	QString _topBarStatus;
188 	bool _topBarStatusActive = false;
189 
190 	int _historyBottom = 0;
191 	std::vector<Bubble> _bubbles;
192 
193 	style::TextPalette _textPalette;
194 
195 };
196 
extended() const197 bool Generator::extended() const {
198 	return (_type == PreviewType::Extended);
199 }
200 
prepare()201 void Generator::prepare() {
202 	const auto size = extended()
203 		? QRect(
204 			QPoint(),
205 			st::themePreviewSize).marginsAdded(st::themePreviewMargin).size()
206 		: st::themePreviewSize;
207 	_rect = QRect(QPoint(), size);
208 	_inner = extended() ? _rect.marginsRemoved(st::themePreviewMargin) : _rect;
209 	_body = extended() ? _inner.marginsRemoved(QMargins(0, Platform::PreviewTitleHeight(), 0, 0)) : _inner;
210 	_dialogs = QRect(_body.x(), _body.y(), st::themePreviewDialogsWidth, _body.height());
211 	_dialogsList = _dialogs.marginsRemoved(QMargins(0, st::dialogsFilterPadding.y() + st::dialogsMenuToggle.height + st::dialogsFilterPadding.y(), 0, st::dialogsPadding.y()));
212 	_topBar = QRect(_dialogs.x() + _dialogs.width(), _dialogs.y(), _body.width() - _dialogs.width(), st::topBarHeight);
213 	_composeArea = QRect(_topBar.x(), _body.y() + _body.height() - st::historySendSize.height(), _topBar.width(), st::historySendSize.height());
214 	_history = QRect(_topBar.x(), _topBar.y() + _topBar.height(), _topBar.width(), _body.height() - _topBar.height() - _composeArea.height());
215 
216 	generateData();
217 }
218 
addRow(QString name,int peerIndex,QString date,QString text)219 void Generator::addRow(QString name, int peerIndex, QString date, QString text) {
220 	Row row;
221 	row.name.setText(st::msgNameStyle, name, Ui::NameTextOptions());
222 
223 	row.letters = fillLetters(name);
224 
225 	row.peerIndex = peerIndex;
226 	row.date = date;
227 	row.text.setRichText(st::dialogsTextStyle, text, Ui::DialogTextOptions());
228 	_rows.push_back(std::move(row));
229 }
230 
addBubble(Bubble bubble,int width,int height,QString date,Status status)231 void Generator::addBubble(Bubble bubble, int width, int height, QString date, Status status) {
232 	bubble.width = width;
233 	bubble.height = height;
234 	bubble.date = date;
235 	bubble.status = status;
236 	_bubbles.push_back(std::move(bubble));
237 }
238 
addAudioBubble(QVector<int> waveform,int waveactive,QString wavestatus,QString date,Status status)239 void Generator::addAudioBubble(QVector<int> waveform, int waveactive, QString wavestatus, QString date, Status status) {
240 	Bubble bubble;
241 	bubble.waveform = waveform;
242 	bubble.waveactive = waveactive;
243 	bubble.wavestatus = wavestatus;
244 
245 	auto skipBlock = computeSkipBlock(status, date);
246 
247 	auto width = st::msgFileMinWidth;
248 	const auto &st = st::msgFileLayout;
249 	auto tleft = st.padding.left() + st.thumbSize + st.padding.right();
250 	accumulate_max(width, tleft + st::normalFont->width(wavestatus) + skipBlock.width() + st::msgPadding.right());
251 	accumulate_min(width, st::msgMaxWidth);
252 
253 	auto height = st.padding.top() + st.thumbSize + st.padding.bottom();
254 	addBubble(std::move(bubble), width, height, date, status);
255 }
256 
computeSkipBlock(Status status,QString date)257 QSize Generator::computeSkipBlock(Status status, QString date) {
258 	auto infoWidth = computeInfoWidth(status, date);
259 	auto width = st::msgDateSpace + infoWidth - st::msgDateDelta.x();
260 	auto height = st::msgDateFont->height - st::msgDateDelta.y();
261 	return QSize(width, height);
262 }
263 
computeInfoWidth(Status status,QString date)264 int Generator::computeInfoWidth(Status status, QString date) {
265 	auto result = st::msgDateFont->width(date);
266 	if (status != Status::None) {
267 		result += st::historySendStateSpace;
268 	}
269 	return result;
270 }
271 
addTextBubble(QString text,QString date,Status status)272 void Generator::addTextBubble(QString text, QString date, Status status) {
273 	Bubble bubble;
274 	auto skipBlock = computeSkipBlock(status, date);
275 	bubble.text.setRichText(st::messageTextStyle, text + textcmdSkipBlock(skipBlock.width(), skipBlock.height()), Ui::ItemTextDefaultOptions());
276 
277 	auto width = _history.width() - st::msgMargin.left() - st::msgMargin.right();
278 	accumulate_min(width, st::msgPadding.left() + bubble.text.maxWidth() + st::msgPadding.right());
279 	accumulate_min(width, st::msgMaxWidth);
280 
281 	auto textWidth = qMax(width - st::msgPadding.left() - st::msgPadding.right(), 1);
282 	auto textHeight = bubble.text.countHeight(textWidth);
283 
284 	auto height = st::msgPadding.top() + textHeight + st::msgPadding.bottom();
285 	addBubble(std::move(bubble), width, height, date, status);
286 }
287 
addDateBubble(QString date)288 void Generator::addDateBubble(QString date) {
289 	Bubble bubble;
290 	addBubble(std::move(bubble), 0, 0, date, Status::None);
291 }
292 
addPhotoBubble(QString image,QString caption,QString date,Status status)293 void Generator::addPhotoBubble(QString image, QString caption, QString date, Status status) {
294 	Bubble bubble;
295 	bubble.photo.load(image);
296 	bubble.photoWidth = style::ConvertScale(bubble.photo.width() / 2);
297 	bubble.photoHeight = style::ConvertScale(bubble.photo.height() / 2);
298 	auto skipBlock = computeSkipBlock(status, date);
299 	bubble.text.setRichText(st::messageTextStyle, caption + textcmdSkipBlock(skipBlock.width(), skipBlock.height()), Ui::ItemTextDefaultOptions());
300 
301 	auto width = _history.width() - st::msgMargin.left() - st::msgMargin.right();
302 	accumulate_min(width, bubble.photoWidth);
303 	accumulate_min(width, st::msgMaxWidth);
304 
305 	auto textWidth = qMax(width - st::msgPadding.left() - st::msgPadding.right(), 1);
306 	auto textHeight = bubble.text.countHeight(textWidth);
307 
308 	auto height = st::mediaCaptionSkip + textHeight + st::msgPadding.bottom();
309 	addBubble(std::move(bubble), width, height, date, status);
310 }
311 
generateData()312 void Generator::generateData() {
313 	_rows.reserve(9);
314 	addRow("Eva Summer", 0, "11:00", "We are too smart for this world. " + QString::fromUtf8("\xf0\x9f\xa4\xa3\xf0\x9f\x98\x82"));
315 	_rows.back().active = true;
316 	_rows.back().pinned = true;
317 	addRow("Alexandra Smith", 7, "10:00", "This is amazing!");
318 	_rows.back().unreadCounter = 2;
319 	addRow("Mike Apple", 2, "9:00", textcmdLink(1, QChar(55357) + QString() + QChar(56836) + " Sticker"));
320 	_rows.back().unreadCounter = 2;
321 	_rows.back().muted = true;
322 	addRow("Evening Club", 1, "8:00", textcmdLink(1, "Eva: Photo"));
323 	_rows.back().type = Row::Type::Group;
324 	addRow("Old Pirates", 6, "7:00", textcmdLink(1, "Max:") + " Yo-ho-ho!");
325 	_rows.back().type = Row::Type::Group;
326 	addRow("Max Bright", 3, "6:00", "How about some coffee?");
327 	_rows.back().status = Status::Received;
328 	addRow("Natalie Parker", 4, "5:00", "OK, great)");
329 	_rows.back().status = Status::Received;
330 	addRow("Davy Jones", 5, "4:00", textcmdLink(1, "Keynote.pdf"));
331 
332 	_topBarName.setText(st::msgNameStyle, "Eva Summer", Ui::NameTextOptions());
333 	_topBarStatus = "online";
334 	_topBarStatusActive = true;
335 
336 	addPhotoBubble(":/gui/art/themeimage.jpg", "To reach a port, we must sail. " + QString::fromUtf8("\xf0\x9f\xa5\xb8"), "7:00", Status::None);
337 	int wavedata[] = { 0, 0, 0, 0, 27, 31, 4, 1, 0, 0, 23, 30, 18, 9, 7, 19, 4, 2, 2, 2, 0, 0, 15, 15, 15, 15, 3, 15, 19, 3, 2, 0, 0, 0, 0, 0, 3, 12, 16, 6, 4, 6, 14, 12, 2, 12, 12, 11, 3, 0, 7, 5, 7, 4, 7, 5, 2, 4, 0, 9, 5, 7, 6, 2, 2, 0, 0 };
338 	auto waveform = QVector<int>(base::array_size(wavedata));
339 	memcpy(waveform.data(), wavedata, sizeof(wavedata));
340 	addAudioBubble(waveform, 33, "0:07", "8:00", Status::None);
341 	_bubbles.back().outbg = true;
342 	_bubbles.back().status = Status::Received;
343 	addDateBubble("December 26");
344 	addTextBubble("Twenty years from now you will be more disappointed by the things that you didn't do than by the ones you did do. " + QString::fromUtf8("\xf0\x9f\xa7\x90"), "10:00", Status::Received);
345 	_bubbles.back().tail = false;
346 	_bubbles.back().outbg = true;
347 	addTextBubble("Mark Twain said that " + QString::fromUtf8("\xe2\x98\x9d\xef\xb8\x8f"), "10:00", Status::Received);
348 	_bubbles.back().outbg = true;
349 	_bubbles.back().attached = true;
350 	_bubbles.back().tail = true;
351 	addTextBubble("We are too smart for this world. " + QString::fromUtf8("\xf0\x9f\xa4\xa3\xf0\x9f\x98\x82"), "11:00", Status::None);
352 	_bubbles.back().replyName.setText(st::msgNameStyle, "Alex Cassio", Ui::NameTextOptions());
353 	_bubbles.back().replyText.setText(st::messageTextStyle, "Mark Twain said that " + QString::fromUtf8("\xe2\x98\x9d\xef\xb8\x8f"), Ui::DialogTextOptions());
354 }
355 
Generator(const Instance & theme,CurrentData && current,PreviewType type)356 Generator::Generator(
357 	const Instance &theme,
358 	CurrentData &&current,
359 	PreviewType type)
360 : _theme(theme)
361 , _palette(_theme.palette)
362 , _current(std::move(current))
363 , _type(type) {
364 }
365 
generate()366 QImage Generator::generate() {
367 	prepare();
368 
369 	auto result = QImage(
370 		_rect.size() * cIntRetinaFactor(),
371 		QImage::Format_ARGB32_Premultiplied);
372 	result.setDevicePixelRatio(cRetinaFactor());
373 	result.fill(st::themePreviewBg->c);
374 
375 	{
376 		Painter p(&result);
377 		PainterHighQualityEnabler hq(p);
378 		_p = &p;
379 
380 		_p->fillRect(_body, QColor(0, 0, 0));
381 		_p->fillRect(_body, st::windowBg[_palette]);
382 
383 		paintHistoryList();
384 		paintTopBar();
385 		paintComposeArea();
386 		paintDialogs();
387 		paintHistoryShadows();
388 	}
389 	if (extended()) {
390 		Platform::PreviewWindowFramePaint(result, _palette, _body, _rect.width());
391 	}
392 
393 	return result;
394 }
395 
paintHistoryList()396 void Generator::paintHistoryList() {
397 	paintHistoryBackground();
398 
399 	_historyBottom = _history.y() + _history.height();
400 	_historyBottom -= st::historyPaddingBottom;
401 	_p->setClipping(true);
402 	for (auto i = _bubbles.size(); i != 0;) {
403 		auto &bubble = _bubbles[--i];
404 		if (bubble.width > 0) {
405 			paintBubble(bubble);
406 		} else {
407 			paintService(bubble.date);
408 		}
409 	}
410 
411 	_p->setClipping(false);
412 }
413 
paintHistoryBackground()414 void Generator::paintHistoryBackground() {
415 	auto fromy = (-st::topBarHeight);
416 	auto background = _theme.background;
417 	auto tiled = _theme.tiled;
418 	if (background.isNull()) {
419 		const auto fakePaper = Data::WallPaper(_current.backgroundId);
420 		if (Data::IsThemeWallPaper(fakePaper)) {
421 			background = Ui::ReadBackgroundImage(
422 				u":/gui/art/background.tgv"_q,
423 				QByteArray(),
424 				true);
425 			const auto paper = Data::DefaultWallPaper();
426 			background = Ui::PreparePatternImage(
427 				std::move(background),
428 				paper.backgroundColors(),
429 				paper.gradientRotation(),
430 				paper.patternOpacity());
431 			tiled = false;
432 		} else {
433 			background = std::move(_current.backgroundImage);
434 			tiled = _current.backgroundTiled;
435 		}
436 	}
437 	background = std::move(background).convertToFormat(
438 		QImage::Format_ARGB32_Premultiplied);
439 	background.setDevicePixelRatio(cRetinaFactor());
440 	_p->setClipRect(_history);
441 	if (tiled) {
442 		auto width = background.width();
443 		auto height = background.height();
444 		auto repeatTimesX = qCeil(_history.width() * cIntRetinaFactor() / float64(width));
445 		auto repeatTimesY = qCeil((_history.height() - fromy) * cIntRetinaFactor() / float64(height));
446 		auto imageForTiled = QImage(
447 			width * repeatTimesX,
448 			height * repeatTimesY,
449 			QImage::Format_ARGB32_Premultiplied);
450 		imageForTiled.setDevicePixelRatio(background.devicePixelRatio());
451 		auto imageForTiledBytes = imageForTiled.bits();
452 		auto bytesInLine = width * sizeof(uint32);
453 		for (auto timesY = 0; timesY != repeatTimesY; ++timesY) {
454 			auto imageBytes = background.constBits();
455 			for (auto y = 0; y != height; ++y) {
456 				for (auto timesX = 0; timesX != repeatTimesX; ++timesX) {
457 					memcpy(imageForTiledBytes, imageBytes, bytesInLine);
458 					imageForTiledBytes += bytesInLine;
459 				}
460 				imageBytes += background.bytesPerLine();
461 				imageForTiledBytes += imageForTiled.bytesPerLine()
462 					- (repeatTimesX * bytesInLine);
463 			}
464 		}
465 		_p->drawImage(_history.x(), _history.y() + fromy, imageForTiled);
466 	} else {
467 		PainterHighQualityEnabler hq(*_p);
468 
469 		auto fill = QSize(_topBar.width(), _body.height());
470 		const auto rects = Ui::ComputeChatBackgroundRects(
471 			fill,
472 			background.size());
473 		auto to = rects.to;
474 		to.moveTop(to.top() + fromy);
475 		to.moveTopLeft(to.topLeft() + _history.topLeft());
476 		_p->drawImage(to, background, rects.from);
477 	}
478 	_p->setClipping(false);
479 }
480 
paintTopBar()481 void Generator::paintTopBar() {
482 	_p->fillRect(_topBar, st::topBarBg[_palette]);
483 
484 	auto right = st::topBarMenuToggle.width;
485 	st::topBarMenuToggle.icon[_palette].paint(*_p, _topBar.x() + _topBar.width() - right + st::topBarMenuToggle.iconPosition.x(), _topBar.y() + st::topBarMenuToggle.iconPosition.y(), _rect.width());
486 	right += st::topBarSkip + st::topBarCall.width;
487 	st::topBarCall.icon[_palette].paint(*_p, _topBar.x() + _topBar.width() - right + st::topBarCall.iconPosition.x(), _topBar.y() + st::topBarCall.iconPosition.y(), _rect.width());
488 	right += st::topBarSearch.width;
489 	st::topBarSearch.icon[_palette].paint(*_p, _topBar.x() + _topBar.width() - right + st::topBarSearch.iconPosition.x(), _topBar.y() + st::topBarSearch.iconPosition.y(), _rect.width());
490 
491 	auto decreaseWidth = st::topBarCall.width + st::topBarCallSkip + st::topBarSearch.width + st::topBarMenuToggle.width;
492 	auto nameleft = _topBar.x() + st::topBarArrowPadding.right();
493 	auto nametop = _topBar.y() + st::topBarArrowPadding.top();
494 	auto statustop = _topBar.y() + st::topBarHeight - st::topBarArrowPadding.bottom() - st::dialogsTextFont->height;
495 	auto namewidth = _topBar.x() + _topBar.width() - decreaseWidth - nameleft - st::topBarArrowPadding.right();
496 	_p->setFont(st::dialogsTextFont);
497 	_p->setPen(_topBarStatusActive ? st::historyStatusFgActive[_palette] : st::historyStatusFg[_palette]);
498 	_p->drawText(nameleft, statustop + st::dialogsTextFont->ascent, _topBarStatus);
499 
500 	_p->setPen(st::dialogsNameFg[_palette]);
501 	_topBarName.drawElided(*_p, nameleft, nametop, namewidth);
502 }
503 
paintComposeArea()504 void Generator::paintComposeArea() {
505 	_p->fillRect(_composeArea, st::historyReplyBg[_palette]);
506 
507 	auto controlsTop = _composeArea.y() + _composeArea.height() - st::historySendSize.height();
508 	const auto attachIconLeft = (st::historyAttach.iconPosition.x() < 0)
509 		? ((st::historyAttach.width - st::historyAttach.icon.width()) / 2)
510 		: st::historyAttach.iconPosition.x();
511 	const auto attachIconTop = (st::historyAttach.iconPosition.y() < 0)
512 		? ((st::historyAttach.height - st::historyAttach.icon.height()) / 2)
513 		: st::historyAttach.iconPosition.y();
514 	st::historyAttach.icon[_palette].paint(*_p, _composeArea.x() + attachIconLeft, controlsTop + attachIconTop, _rect.width());
515 	auto right = st::historySendRight + st::historySendSize.width();
516 	st::historyRecordVoice[_palette].paintInCenter(*_p, QRect(_composeArea.x() + _composeArea.width() - right, controlsTop, st::historySendSize.width(), st::historySendSize.height()));
517 
518 	const auto emojiIconLeft = (st::historyAttachEmoji.iconPosition.x() < 0)
519 		? ((st::historyAttachEmoji.width - st::historyAttachEmoji.icon.width()) / 2)
520 		: st::historyAttachEmoji.iconPosition.x();
521 	const auto emojiIconTop = (st::historyAttachEmoji.iconPosition.y() < 0)
522 		? ((st::historyAttachEmoji.height - st::historyAttachEmoji.icon.height()) / 2)
523 		: st::historyAttachEmoji.iconPosition.y();
524 	const auto &emojiIcon = st::historyAttachEmoji.icon[_palette];
525 	right += st::historyAttachEmoji.width;
526 	auto attachEmojiLeft = _composeArea.x() + _composeArea.width() - right;
527 	_p->fillRect(attachEmojiLeft, controlsTop, st::historyAttachEmoji.width, st::historyAttachEmoji.height, st::historyComposeAreaBg[_palette]);
528 	emojiIcon.paint(*_p, attachEmojiLeft + emojiIconLeft, controlsTop + emojiIconTop, _rect.width());
529 
530 	auto pen = st::historyEmojiCircleFg[_palette]->p;
531 	pen.setWidth(st::historyEmojiCircleLine);
532 	pen.setCapStyle(Qt::RoundCap);
533 	_p->setPen(pen);
534 	_p->setBrush(Qt::NoBrush);
535 
536 	PainterHighQualityEnabler hq(*_p);
537 	const auto skipx = emojiIcon.width() / 4;
538 	const auto skipy = emojiIcon.height() / 4;
539 	const auto inner = QRect(
540 		attachEmojiLeft + emojiIconLeft + skipx,
541 		controlsTop + emojiIconTop + skipy,
542 		emojiIcon.width() - 2 * skipx,
543 		emojiIcon.height() - 2 * skipy);
544 	_p->drawEllipse(inner);
545 
546 	auto fieldLeft = _composeArea.x() + st::historyAttach.width;
547 	auto fieldTop = _composeArea.y() + _composeArea.height() - st::historyAttach.height + st::historySendPadding;
548 	auto fieldWidth = _composeArea.width() - st::historyAttach.width - st::historySendSize.width() - st::historySendRight - st::historyAttachEmoji.width;
549 	auto fieldHeight = st::historySendSize.height() - 2 * st::historySendPadding;
550 	auto field = QRect(fieldLeft, fieldTop, fieldWidth, fieldHeight);
551 	_p->fillRect(field, st::historyComposeField.textBg[_palette]);
552 
553 	_p->save();
554 	_p->setClipRect(field);
555 	_p->setFont(st::historyComposeField.font);
556 	_p->setPen(st::historyComposeField.placeholderFg[_palette]);
557 
558 	auto placeholderRect = QRect(
559 		field.x() + st::historyComposeField.textMargins.left() + st::historyComposeField.placeholderMargins.left(),
560 		field.y() + st::historyComposeField.textMargins.top() + st::historyComposeField.placeholderMargins.top(),
561 		field.width() - st::historyComposeField.textMargins.left() - st::historyComposeField.textMargins.right(),
562 		field.height() - st::historyComposeField.textMargins.top() - st::historyComposeField.textMargins.bottom());
563 	_p->drawText(placeholderRect, tr::lng_message_ph(tr::now), QTextOption(st::historyComposeField.placeholderAlign));
564 
565 	_p->restore();
566 	_p->setClipping(false);
567 }
568 
paintDialogs()569 void Generator::paintDialogs() {
570 	_p->fillRect(_dialogs, st::dialogsBg[_palette]);
571 
572 	const auto iconLeft = (st::dialogsMenuToggle.iconPosition.x() < 0)
573 		? (st::dialogsMenuToggle.width - st::dialogsMenuToggle.icon.width()) / 2
574 		: st::dialogsMenuToggle.iconPosition.x();
575 	const auto iconTop = (st::dialogsMenuToggle.iconPosition.y() < 0)
576 		? (st::dialogsMenuToggle.height - st::dialogsMenuToggle.icon.height()) / 2
577 		: st::dialogsMenuToggle.iconPosition.y();
578 	st::dialogsMenuToggle.icon[_palette].paint(*_p, _dialogs.x() + st::dialogsFilterPadding.x() + iconLeft, _dialogs.y() + st::dialogsFilterPadding.y() + iconTop, _rect.width());
579 
580 	auto filterLeft = _dialogs.x() + st::dialogsFilterPadding.x() + st::dialogsMenuToggle.width + st::dialogsFilterPadding.x();
581 	auto filterRight = st::dialogsFilterSkip + st::dialogsFilterPadding.x();
582 	auto filterWidth = _dialogs.x() + _dialogs.width() - filterLeft - filterRight;
583 	auto filterAreaHeight = st::topBarHeight;
584 	auto filterTop = _dialogs.y() + (filterAreaHeight - st::dialogsFilter.height) / 2;
585 	auto filter = QRect(filterLeft, filterTop, filterWidth, st::dialogsFilter.height);
586 
587 	auto pen = st::dialogsFilter.borderColor[_palette]->p;
588 	pen.setWidth(st::dialogsFilter.borderWidth);
589 	_p->setPen(pen);
590 	_p->setBrush(st::dialogsFilter.bgColor[_palette]);
591 	{
592 		PainterHighQualityEnabler hq(*_p);
593 		_p->drawRoundedRect(QRectF(filter).marginsRemoved(QMarginsF(st::dialogsFilter.borderWidth / 2., st::dialogsFilter.borderWidth / 2., st::dialogsFilter.borderWidth / 2., st::dialogsFilter.borderWidth / 2.)), st::roundRadiusSmall - (st::dialogsFilter.borderWidth / 2.), st::roundRadiusSmall - (st::dialogsFilter.borderWidth / 2.));
594 	}
595 
596 	if (!st::dialogsFilter.icon.empty()) {
597 		st::dialogsFilter.icon[_palette].paint(*_p, filter.x(), filter.y(), _rect.width());
598 	}
599 
600 	_p->save();
601 	_p->setClipRect(filter);
602 	auto phRect = QRect(filter.x() + st::dialogsFilter.textMrg.left() + st::dialogsFilter.phPos.x(), filter.y() + st::dialogsFilter.textMrg.top() + st::dialogsFilter.phPos.y(), filter.width() - st::dialogsFilter.textMrg.left() - st::dialogsFilter.textMrg.right(), filter.height() - st::dialogsFilter.textMrg.top() - st::dialogsFilter.textMrg.bottom());;
603 	_p->setFont(st::dialogsFilter.font);
604 	_p->setPen(st::dialogsFilter.phColor[_palette]);
605 	_p->drawText(phRect, tr::lng_dlg_filter(tr::now), QTextOption(st::dialogsFilter.phAlign));
606 	_p->restore();
607 	_p->setClipping(false);
608 
609 	paintDialogsList();
610 }
611 
paintDialogsList()612 void Generator::paintDialogsList() {
613 	_p->setClipRect(_dialogsList);
614 	_rowsTop = _dialogsList.y();
615 	for (auto &row : _rows) {
616 		paintRow(row);
617 		_rowsTop += st::dialogsRowHeight;
618 	}
619 	_p->setClipping(false);
620 }
621 
paintRow(const Row & row)622 void Generator::paintRow(const Row &row) {
623 	auto x = _dialogsList.x();
624 	auto y = _rowsTop;
625 	auto fullWidth = _dialogsList.width();
626 	auto fullRect = QRect(x, y, fullWidth, st::dialogsRowHeight);
627 	if (row.active || row.selected) {
628 		_p->fillRect(fullRect, row.active ? st::dialogsBgActive[_palette] : st::dialogsBgOver[_palette]);
629 	}
630 	paintUserpic(x + st::dialogsPadding.x(), y + st::dialogsPadding.y(), row.type, row.peerIndex, row.letters);
631 
632 	auto nameleft = x + st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPhotoPadding;
633 	auto namewidth = x + fullWidth - nameleft - st::dialogsPadding.x();
634 	auto rectForName = QRect(nameleft, y + st::dialogsPadding.y() + st::dialogsNameTop, namewidth, st::msgNameFont->height);
635 
636 	auto chatTypeIcon = ([&row]() -> const style::icon * {
637 		if (row.type == Row::Type::Group) {
638 			return &(row.active ? st::dialogsChatIconActive : (row.selected ? st::dialogsChatIconOver : st::dialogsChatIcon));
639 		} else if (row.type == Row::Type::Channel) {
640 			return &(row.active ? st::dialogsChannelIconActive : (row.selected ? st::dialogsChannelIconOver : st::dialogsChannelIcon));
641 		}
642 		return nullptr;
643 	})();
644 	if (chatTypeIcon) {
645 		(*chatTypeIcon)[_palette].paint(*_p, rectForName.topLeft(), fullWidth);
646 		rectForName.setLeft(rectForName.left() + st::dialogsChatTypeSkip);
647 	}
648 
649 	auto texttop = y + st::dialogsPadding.y() + st::msgNameFont->height + st::dialogsSkip;
650 
651 	auto dateWidth = st::dialogsDateFont->width(row.date);
652 	rectForName.setWidth(rectForName.width() - dateWidth - st::dialogsDateSkip);
653 	_p->setFont(st::dialogsDateFont);
654 	_p->setPen(row.active ? st::dialogsDateFgActive[_palette] : (row.selected ? st::dialogsDateFgOver[_palette] : st::dialogsDateFg[_palette]));
655 	_p->drawText(rectForName.left() + rectForName.width() + st::dialogsDateSkip, rectForName.top() + st::msgNameFont->height - st::msgDateFont->descent, row.date);
656 
657 	auto availableWidth = namewidth;
658 	if (row.unreadCounter) {
659 		auto counter = QString::number(row.unreadCounter);
660 		auto unreadRight = x + fullWidth - st::dialogsPadding.x();
661 		auto unreadTop = texttop + st::dialogsTextFont->ascent - st::dialogsUnreadFont->ascent - (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2;
662 
663 		auto unreadWidth = st::dialogsUnreadFont->width(counter);
664 		auto unreadRectWidth = unreadWidth + 2 * st::dialogsUnreadPadding;
665 		auto unreadRectHeight = st::dialogsUnreadHeight;
666 		accumulate_max(unreadRectWidth, unreadRectHeight);
667 
668 		auto unreadRectLeft = unreadRight - unreadRectWidth;
669 		auto unreadRectTop = unreadTop;
670 		availableWidth -= unreadRectWidth + st::dialogsUnreadPadding;
671 
672 		style::color bg[] = {
673 			st::dialogsUnreadBg,
674 			st::dialogsUnreadBgOver,
675 			st::dialogsUnreadBgActive,
676 			st::dialogsUnreadBgMuted,
677 			st::dialogsUnreadBgMutedOver,
678 			st::dialogsUnreadBgMutedActive
679 		};
680 
681 		auto index = (row.active ? 2 : row.selected ? 1 : 0) + (row.muted ? 3 : 0);
682 		_p->setPen(Qt::NoPen);
683 		_p->setBrush(bg[index][_palette]);
684 		_p->drawRoundedRect(QRectF(unreadRectLeft, unreadRectTop, unreadRectWidth, unreadRectHeight), unreadRectHeight / 2., unreadRectHeight / 2.);
685 
686 		auto textTop = (unreadRectHeight - st::dialogsUnreadFont->height) / 2;
687 		_p->setFont(st::dialogsUnreadFont);
688 		_p->setPen(row.active ? st::dialogsUnreadFgActive[_palette] : (row.selected ? st::dialogsUnreadFgOver[_palette] : st::dialogsUnreadFg[_palette]));
689 		_p->drawText(unreadRectLeft + (unreadRectWidth - unreadWidth) / 2, unreadRectTop + textTop + st::dialogsUnreadFont->ascent, counter);
690 	} else if (row.pinned) {
691 		auto icon = (row.active ? st::dialogsPinnedIconActive[_palette] : (row.selected ? st::dialogsPinnedIconOver[_palette] : st::dialogsPinnedIcon[_palette]));
692 		icon.paint(*_p, x + fullWidth - st::dialogsPadding.x() - icon.width(), texttop, fullWidth);
693 		availableWidth -= icon.width() + st::dialogsUnreadPadding;
694 	}
695 	auto textRect = QRect(nameleft, texttop, availableWidth, st::dialogsTextFont->height);
696 	setTextPalette(row.active ? st::dialogsTextPaletteActive : (row.selected ? st::dialogsTextPaletteOver : st::dialogsTextPalette));
697 	_p->setFont(st::dialogsTextFont);
698 	_p->setPen(row.active ? st::dialogsTextFgActive[_palette] : (row.selected ? st::dialogsTextFgOver[_palette] : st::dialogsTextFg[_palette]));
699 	row.text.drawElided(*_p, textRect.left(), textRect.top(), textRect.width(), textRect.height() / st::dialogsTextFont->height);
700 	restoreTextPalette();
701 
702 	auto sendStateIcon = ([&row]() -> const style::icon* {
703 		if (row.status == Status::Sent) {
704 			return &(row.active ? st::dialogsSentIconActive : (row.selected ? st::dialogsSentIconOver : st::dialogsSentIcon));
705 		} else if (row.status == Status::Received) {
706 			return &(row.active ? st::dialogsReceivedIconActive : (row.selected ? st::dialogsReceivedIconOver : st::dialogsReceivedIcon));
707 		}
708 		return nullptr;
709 	})();
710 	if (sendStateIcon) {
711 		rectForName.setWidth(rectForName.width() - st::dialogsSendStateSkip);
712 		(*sendStateIcon)[_palette].paint(*_p, rectForName.topLeft() + QPoint(rectForName.width(), 0), fullWidth);
713 	}
714 	_p->setPen(row.active ? st::dialogsNameFgActive[_palette] : (row.selected ? st::dialogsNameFgOver[_palette] : st::dialogsNameFg[_palette]));
715 	row.name.drawElided(*_p, rectForName.left(), rectForName.top(), rectForName.width());
716 }
717 
paintBubble(const Bubble & bubble)718 void Generator::paintBubble(const Bubble &bubble) {
719 	auto height = bubble.height;
720 	if (!bubble.replyName.isEmpty()) {
721 		height += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom();
722 	}
723 	auto isPhoto = !bubble.photo.isNull();
724 
725 	auto x = _history.x();
726 	auto y = _historyBottom - st::msgMargin.bottom() - height;
727 	auto bubbleTop = y;
728 	auto bubbleHeight = height;
729 	if (isPhoto) {
730 		bubbleTop -= st::historyMessageRadius + 1;
731 		bubbleHeight += st::historyMessageRadius + 1;
732 	}
733 
734 	auto left = bubble.outbg ? st::msgMargin.right() : st::msgMargin.left();
735 	if (bubble.outbg) {
736 		left += _history.width() - st::msgMargin.left() - st::msgMargin.right() - bubble.width;
737 	}
738 	x += left;
739 
740 	_p->setPen(Qt::NoPen);
741 	auto tailclip = st::historyMessageRadius + 1;
742 	if (bubble.tail) {
743 		if (bubble.outbg) {
744 			_p->setClipRegion(QRegion(_history) - QRect(x + bubble.width - tailclip, bubbleTop + bubbleHeight - tailclip, tailclip + st::historyMessageRadius, tailclip + st::historyMessageRadius));
745 		} else {
746 			_p->setClipRegion(QRegion(_history) - QRect(x - st::historyMessageRadius, bubbleTop + bubbleHeight - tailclip, tailclip + st::historyMessageRadius, tailclip + st::historyMessageRadius));
747 		}
748 	}
749 	auto sh = bubble.outbg ? st::msgOutShadow[_palette] : st::msgInShadow[_palette];
750 	_p->setBrush(sh);
751 	_p->drawRoundedRect(x, bubbleTop + st::msgShadow, bubble.width, bubbleHeight, st::historyMessageRadius, st::historyMessageRadius);
752 	auto bg = bubble.outbg ? st::msgOutBg[_palette] : st::msgInBg[_palette];
753 	_p->setBrush(bg);
754 	_p->drawRoundedRect(x, bubbleTop, bubble.width, bubbleHeight, st::historyMessageRadius, st::historyMessageRadius);
755 	if (bubble.tail) {
756 		_p->setClipRect(_history);
757 		if (bubble.outbg) {
758 			_p->fillRect(QRect(x + bubble.width - tailclip, bubbleTop + bubbleHeight - tailclip, tailclip, tailclip), bg);
759 			_p->fillRect(QRect(x + bubble.width - tailclip, bubbleTop + bubbleHeight, tailclip + st::historyBubbleTailOutRight.width(), st::msgShadow), sh);
760 			st::historyBubbleTailOutRight[_palette].paint(*_p, x + bubble.width, bubbleTop + bubbleHeight - st::historyBubbleTailOutRight.height(), _rect.width());
761 		} else {
762 			_p->fillRect(QRect(x, bubbleTop + bubbleHeight - tailclip, tailclip, tailclip), bg);
763 			_p->fillRect(QRect(x - st::historyBubbleTailInLeft.width(), bubbleTop + bubbleHeight, tailclip + st::historyBubbleTailInLeft.width(), st::msgShadow), sh);
764 			st::historyBubbleTailInLeft[_palette].paint(*_p, x - st::historyBubbleTailInLeft.width(), bubbleTop + bubbleHeight - st::historyBubbleTailOutRight.height(), _rect.width());
765 		}
766 	}
767 
768 	auto trect = QRect(x, y, bubble.width, bubble.height);
769 	if (isPhoto) {
770 		trect = trect.marginsRemoved(QMargins(st::msgPadding.left(), st::mediaCaptionSkip, st::msgPadding.right(), st::msgPadding.bottom()));
771 	} else {
772 		trect = trect.marginsRemoved(st::msgPadding);
773 	}
774 	if (!bubble.replyName.isEmpty()) {
775 		auto h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom();
776 
777 		auto bar = (bubble.outbg ? st::msgOutReplyBarColor[_palette] : st::msgInReplyBarColor[_palette]);
778 		auto rbar = style::rtlrect(trect.x() + st::msgReplyBarPos.x(), trect.y() + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.width(), st::msgReplyBarSize.height(), _rect.width());
779 		_p->fillRect(rbar, bar);
780 
781 		_p->setPen(bubble.outbg ? st::msgOutServiceFg[_palette] : st::msgInServiceFg[_palette]);
782 		bubble.replyName.drawLeftElided(*_p, trect.x() + st::msgReplyBarSkip, trect.y() + st::msgReplyPadding.top(), bubble.width - st::msgReplyBarSkip, _rect.width());
783 
784 		_p->setPen(bubble.outbg ? st::historyTextOutFg[_palette] : st::historyTextInFg[_palette]);
785 		bubble.replyText.drawLeftElided(*_p, trect.x() + st::msgReplyBarSkip, trect.y() + st::msgReplyPadding.top() + st::msgServiceNameFont->height, bubble.width - st::msgReplyBarSkip, _rect.width());
786 
787 		trect.setY(trect.y() + h);
788 	}
789 
790 	if (!bubble.text.isEmpty()) {
791 		setTextPalette(bubble.outbg ? st::outTextPalette : st::inTextPalette);
792 		_p->setPen(bubble.outbg ? st::historyTextOutFg[_palette] : st::historyTextInFg[_palette]);
793 		_p->setFont(st::msgFont);
794 		bubble.text.draw(*_p, trect.x(), trect.y(), trect.width());
795 	} else if (!bubble.waveform.isEmpty()) {
796 		const auto &st = st::msgFileLayout;
797 		auto nameleft = x + st.padding.left() + st.thumbSize + st.padding.right();
798 		auto nameright = st.padding.left();
799 		auto statustop = y + st.statusTop;
800 
801 		auto inner = style::rtlrect(x + st.padding.left(), y + st.padding.top(), st.thumbSize, st.thumbSize, _rect.width());
802 		_p->setPen(Qt::NoPen);
803 		_p->setBrush(bubble.outbg ? st::msgFileOutBg[_palette] : st::msgFileInBg[_palette]);
804 
805 		_p->drawEllipse(inner);
806 
807 		auto icon = ([&bubble] {
808 			return &(bubble.outbg ? st::historyFileOutPlay : st::historyFileInPlay);
809 		})();
810 		(*icon)[_palette].paintInCenter(*_p, inner);
811 
812 		auto namewidth = x + bubble.width - nameleft - nameright;
813 
814 		// rescale waveform by going in waveform.size * bar_count 1D grid
815 		auto active = bubble.outbg ? st::msgWaveformOutActive[_palette] : st::msgWaveformInActive[_palette];
816 		auto inactive = bubble.outbg ? st::msgWaveformOutInactive[_palette] : st::msgWaveformInInactive[_palette];
817 		auto wf_size = bubble.waveform.size();
818 		auto availw = namewidth + st::msgWaveformSkip;
819 		auto bar_count = qMin(availw / (st::msgWaveformBar + st::msgWaveformSkip), wf_size);
820 		auto max_value = 0;
821 		auto max_delta = st::msgWaveformMax - st::msgWaveformMin;
822 		auto wave_bottom = y + st::msgFileLayout.padding.top() + st::msgWaveformMax;
823 		_p->setPen(Qt::NoPen);
824 		auto norm_value = uchar(31);
825 		for (auto i = 0, bar_x = 0, sum_i = 0; i < wf_size; ++i) {
826 			auto value = bubble.waveform[i];
827 			if (sum_i + bar_count >= wf_size) { // draw bar
828 				sum_i = sum_i + bar_count - wf_size;
829 				if (sum_i < (bar_count + 1) / 2) {
830 					if (max_value < value) max_value = value;
831 				}
832 				auto bar_value = ((max_value * max_delta) + ((norm_value + 1) / 2)) / (norm_value + 1);
833 
834 				if (i >= bubble.waveactive) {
835 					_p->fillRect(nameleft + bar_x, wave_bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, inactive);
836 				} else {
837 					_p->fillRect(nameleft + bar_x, wave_bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, active);
838 				}
839 				bar_x += st::msgWaveformBar + st::msgWaveformSkip;
840 
841 				if (sum_i < (bar_count + 1) / 2) {
842 					max_value = 0;
843 				} else {
844 					max_value = value;
845 				}
846 			} else {
847 				if (max_value < value) max_value = value;
848 
849 				sum_i += bar_count;
850 			}
851 		}
852 
853 		auto status = bubble.outbg ? st::mediaOutFg[_palette] : st::mediaInFg[_palette];
854 		_p->setFont(st::normalFont);
855 		_p->setPen(status);
856 		_p->drawTextLeft(nameleft, statustop, _rect.width(), bubble.wavestatus);
857 	}
858 
859 	_p->setFont(st::msgDateFont);
860 	auto infoRight = x + bubble.width - st::msgPadding.right() + st::msgDateDelta.x();
861 	auto infoBottom = y + height - st::msgPadding.bottom() + st::msgDateDelta.y();
862 	_p->setPen(bubble.outbg ? st::msgOutDateFg[_palette] : st::msgInDateFg[_palette]);
863 	auto infoWidth = computeInfoWidth(bubble.status, bubble.date);
864 
865 	auto dateX = infoRight - infoWidth;
866 	auto dateY = infoBottom - st::msgDateFont->height;
867 	_p->drawText(dateX, dateY + st::msgDateFont->ascent, bubble.date);
868 	auto icon = ([&bubble]() -> const style::icon * {
869 		if (bubble.status == Status::Sent) {
870 			return &st::historySentIcon;
871 		} else if (bubble.status == Status::Received) {
872 			return &st::historyReceivedIcon;
873 		}
874 		return nullptr;
875 	})();
876 	if (icon) {
877 		(*icon)[_palette].paint(*_p, QPoint(infoRight, infoBottom) + st::historySendStatePosition, _rect.width());
878 	}
879 
880 	_historyBottom = y - (bubble.attached ? st::msgMarginTopAttached : st::msgMargin.top());
881 
882 	if (isPhoto) {
883 		auto image = bubble.photo.scaled(bubble.photoWidth * cIntRetinaFactor(), bubble.photoHeight * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
884 		image.setDevicePixelRatio(cRetinaFactor());
885 		_p->drawImage(x, y - bubble.photoHeight, image);
886 		_historyBottom -= bubble.photoHeight;
887 	}
888 }
889 
paintService(QString text)890 void Generator::paintService(QString text) {
891 	auto bubbleHeight = st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom();
892 	auto bubbleTop = _historyBottom - st::msgServiceMargin.bottom() - bubbleHeight;
893 	auto textWidth = st::msgServiceFont->width(text);
894 	auto bubbleWidth = st::msgServicePadding.left() + textWidth + st::msgServicePadding.right();
895 	auto radius = bubbleHeight / 2;
896 	_p->setPen(Qt::NoPen);
897 	_p->setBrush(st::msgServiceBg[_palette]);
898 	auto bubbleLeft = _history.x() + (_history.width() - bubbleWidth) / 2;
899 	_p->drawRoundedRect(bubbleLeft, bubbleTop, bubbleWidth, bubbleHeight, radius, radius);
900 	_p->setPen(st::msgServiceFg[_palette]);
901 	_p->setFont(st::msgServiceFont);
902 	_p->drawText(bubbleLeft + st::msgServicePadding.left(), bubbleTop + st::msgServicePadding.top() + st::msgServiceFont->ascent, text);
903 	_historyBottom = bubbleTop - st::msgServiceMargin.top();
904 }
905 
paintUserpic(int x,int y,Row::Type type,int index,QString letters)906 void Generator::paintUserpic(int x, int y, Row::Type type, int index, QString letters) {
907 	style::color colors[] = {
908 		st::historyPeer1UserpicBg,
909 		st::historyPeer2UserpicBg,
910 		st::historyPeer3UserpicBg,
911 		st::historyPeer4UserpicBg,
912 		st::historyPeer5UserpicBg,
913 		st::historyPeer6UserpicBg,
914 		st::historyPeer7UserpicBg,
915 		st::historyPeer8UserpicBg,
916 	};
917 	auto color = colors[index % base::array_size(colors)];
918 
919 	auto image = QImage(st::dialogsPhotoSize * cIntRetinaFactor(), st::dialogsPhotoSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
920 	image.setDevicePixelRatio(cRetinaFactor());
921 	image.fill(color[_palette]->c);
922 	{
923 		Painter p(&image);
924 		auto fontsize = (st::dialogsPhotoSize * 13) / 33;
925 		auto font = st::historyPeerUserpicFont->f;
926 		font.setPixelSize(fontsize);
927 
928 		p.setFont(font);
929 		p.setBrush(Qt::NoBrush);
930 		p.setPen(st::historyPeerUserpicFg[_palette]);
931 		p.drawText(QRect(0, 0, st::dialogsPhotoSize, st::dialogsPhotoSize), letters, QTextOption(style::al_center));
932 	}
933 	Images::prepareCircle(image);
934 	_p->drawImage(rtl() ? (_rect.width() - x - st::dialogsPhotoSize) : x, y, image);
935 }
936 
paintHistoryShadows()937 void Generator::paintHistoryShadows() {
938 	_p->fillRect(_history.x() + st::lineWidth, _history.y(), _history.width() - st::lineWidth, st::lineWidth, st::shadowFg[_palette]);
939 	_p->fillRect(_history.x() + st::lineWidth, _history.y() + _history.height() - st::lineWidth, _history.width() - st::lineWidth, st::lineWidth, st::shadowFg[_palette]);
940 	_p->fillRect(_history.x(), _body.y(), st::lineWidth, _body.height(), st::shadowFg[_palette]);
941 }
942 
setTextPalette(const style::TextPalette & st)943 void Generator::setTextPalette(const style::TextPalette &st) {
944 	_textPalette.linkFg = st.linkFg[_palette].clone();
945 	_textPalette.monoFg = st.monoFg[_palette].clone();
946 	_textPalette.selectBg = st.selectBg[_palette].clone();
947 	_textPalette.selectFg = st.selectFg[_palette].clone();
948 	_textPalette.selectLinkFg = st.selectLinkFg[_palette].clone();
949 	_textPalette.selectMonoFg = st.selectMonoFg[_palette].clone();
950 	_textPalette.selectOverlay = st.selectOverlay[_palette].clone();
951 	_p->setTextPalette(_textPalette);
952 }
953 
restoreTextPalette()954 void Generator::restoreTextPalette() {
955 	_p->restoreTextPalette();
956 }
957 
958 } // namespace
959 
CachedThemePath(uint64 documentId)960 QString CachedThemePath(uint64 documentId) {
961 	return QString::fromLatin1("special://cached-%1").arg(documentId);
962 }
963 
PreviewFromFile(const QByteArray & bytes,const QString & filepath,const Data::CloudTheme & cloud)964 std::unique_ptr<Preview> PreviewFromFile(
965 		const QByteArray &bytes,
966 		const QString &filepath,
967 		const Data::CloudTheme &cloud) {
968 	auto result = std::make_unique<Preview>();
969 	auto &object = result->object;
970 	object.cloud = cloud;
971 	object.pathAbsolute = filepath.isEmpty()
972 		? CachedThemePath(cloud.documentId)
973 		: QFileInfo(filepath).absoluteFilePath();
974 	object.pathRelative = filepath.isEmpty()
975 		? object.pathAbsolute
976 		: QDir().relativeFilePath(filepath);
977 	const auto instance = &result->instance;
978 	const auto cache = &result->instance.cached;
979 	if (bytes.isEmpty()) {
980 		if (!LoadFromFile(filepath, instance, cache, &object.content)) {
981 			return nullptr;
982 		}
983 	} else {
984 		object.content = bytes;
985 		if (!LoadFromContent(bytes, instance, cache)) {
986 			return nullptr;
987 		}
988 	}
989 	return result;
990 }
991 
GeneratePreview(const QByteArray & bytes,const QString & filepath,const Data::CloudTheme & cloud,CurrentData && data,PreviewType type)992 std::unique_ptr<Preview> GeneratePreview(
993 		const QByteArray &bytes,
994 		const QString &filepath,
995 		const Data::CloudTheme &cloud,
996 		CurrentData &&data,
997 		PreviewType type) {
998 	auto result = PreviewFromFile(bytes, filepath, cloud);
999 	if (!result) {
1000 		return nullptr;
1001 	}
1002 	result->preview = Generator(
1003 		result->instance,
1004 		std::move(data),
1005 		type
1006 	).generate();
1007 	return result;
1008 }
1009 
GeneratePreview(const QByteArray & bytes,const QString & filepath)1010 QImage GeneratePreview(
1011 		const QByteArray &bytes,
1012 		const QString &filepath) {
1013 	const auto preview = GeneratePreview(
1014 		bytes,
1015 		filepath,
1016 		Data::CloudTheme(),
1017 		CurrentData{ Data::ThemeWallPaper().id() },
1018 		PreviewType::Normal);
1019 	return preview ? preview->preview : QImage();
1020 }
1021 
DefaultPreviewTitleHeight()1022 int DefaultPreviewTitleHeight() {
1023 	return st::defaultWindowTitle.height;
1024 }
1025 
DefaultPreviewWindowTitle(Painter & p,const style::palette & palette,QRect body,int outerWidth)1026 void DefaultPreviewWindowTitle(Painter &p, const style::palette &palette, QRect body, int outerWidth) {
1027 	auto titleRect = QRect(body.x(), body.y() - st::defaultWindowTitle.height, body.width(), st::defaultWindowTitle.height);
1028 	p.fillRect(titleRect, QColor(0, 0, 0));
1029 	p.fillRect(titleRect, st::titleBgActive[palette]);
1030 	auto right = st::defaultWindowTitle.close.width;
1031 	st::defaultWindowTitle.close.icon[palette].paint(p, titleRect.x() + titleRect.width() - right + st::defaultWindowTitle.close.iconPosition.x(), titleRect.y() + st::windowTitleButtonClose.iconPosition.y(), outerWidth);
1032 	right += st::defaultWindowTitle.maximize.width;
1033 	st::defaultWindowTitle.maximize.icon[palette].paint(p, titleRect.x() + titleRect.width() - right + st::defaultWindowTitle.maximize.iconPosition.x(), titleRect.y() + st::defaultWindowTitle.maximize.iconPosition.y(), outerWidth);
1034 	right += st::defaultWindowTitle.minimize.width;
1035 	st::defaultWindowTitle.minimize.icon[palette].paint(p, titleRect.x() + titleRect.width() - right + st::defaultWindowTitle.minimize.iconPosition.x(), titleRect.y() + st::defaultWindowTitle.minimize.iconPosition.y(), outerWidth);
1036 	p.fillRect(titleRect.x(), titleRect.y() + titleRect.height() - st::lineWidth, titleRect.width(), st::lineWidth, st::titleShadow[palette]);
1037 }
1038 
DefaultPreviewWindowFramePaint(QImage & preview,const style::palette & palette,QRect body,int outerWidth)1039 void DefaultPreviewWindowFramePaint(QImage &preview, const style::palette &palette, QRect body, int outerWidth) {
1040 	auto mask = QImage(st::windowShadow.size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
1041 	mask.setDevicePixelRatio(cRetinaFactor());
1042 	{
1043 		Painter p(&mask);
1044 		p.setCompositionMode(QPainter::CompositionMode_Source);
1045 		st::windowShadow.paint(p, 0, 0, st::windowShadow.width(), QColor(0, 0, 0));
1046 	}
1047 	auto maxSize = 0;
1048 	auto currentInt = static_cast<uint32>(0);
1049 	auto lastLineInts = reinterpret_cast<const uint32*>(mask.constBits() + (mask.height() - 1) * mask.bytesPerLine());
1050 	for (auto end = lastLineInts + mask.width(); lastLineInts != end; ++lastLineInts) {
1051 		if (*lastLineInts < currentInt) {
1052 			break;
1053 		}
1054 		currentInt = *lastLineInts;
1055 		++maxSize;
1056 	}
1057 	if (maxSize % cIntRetinaFactor()) {
1058 		maxSize -= (maxSize % cIntRetinaFactor());
1059 	}
1060 	auto size = maxSize / cIntRetinaFactor();
1061 	auto bottom = size;
1062 	auto left = size - st::windowShadowShift;
1063 	auto right = left;
1064 	auto top = size - 2 * st::windowShadowShift;
1065 
1066 	auto sprite = st::windowShadow[palette];
1067 	auto topLeft = QImage(sprite.size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
1068 	topLeft.setDevicePixelRatio(cRetinaFactor());
1069 	{
1070 		Painter p(&topLeft);
1071 		p.setCompositionMode(QPainter::CompositionMode_Source);
1072 		sprite.paint(p, 0, 0, sprite.width());
1073 	}
1074 	auto width = sprite.width();
1075 	auto height = sprite.height();
1076 	auto topRight = topLeft.mirrored(true, false);
1077 	auto bottomRight = topLeft.mirrored(true, true);
1078 	auto bottomLeft = topLeft.mirrored(false, true);
1079 
1080 	Painter p(&preview);
1081 	DefaultPreviewWindowTitle(p, palette, body, outerWidth);
1082 
1083 	auto inner = QRect(body.x(), body.y() - st::defaultWindowTitle.height, body.width(), body.height() + st::defaultWindowTitle.height);
1084 	p.setClipRegion(QRegion(inner.marginsAdded(QMargins(size, size, size, size))) - inner);
1085 	p.drawImage(inner.x() - left, inner.y() - top, topLeft);
1086 	p.drawImage(inner.x() + inner.width() + right - width, inner.y() - top, topRight);
1087 	p.drawImage(inner.x() + inner.width() + right - width, inner.y() + inner.height() + bottom - height, bottomRight);
1088 	p.drawImage(inner.x() - left, inner.y() + inner.height() + bottom - height, bottomLeft);
1089 	p.drawImage(QRect(inner.x() - left, inner.y() - top + height, left, top + inner.height() + bottom - 2 * height), topLeft, QRect(0, topLeft.height() - cIntRetinaFactor(), left * cIntRetinaFactor(), cIntRetinaFactor()));
1090 	p.drawImage(QRect(inner.x() - left + width, inner.y() - top, left + inner.width() + right - 2 * width, top), topLeft, QRect(topLeft.width() - cIntRetinaFactor(), 0, cIntRetinaFactor(), top * cIntRetinaFactor()));
1091 	p.drawImage(QRect(inner.x() + inner.width(), inner.y() - top + height, right, top + inner.height() + bottom - 2 * height), topRight, QRect(topRight.width() - right * cIntRetinaFactor(), topRight.height() - cIntRetinaFactor(), right * cIntRetinaFactor(), cIntRetinaFactor()));
1092 	p.drawImage(QRect(inner.x() - left + width, inner.y() + inner.height(), left + inner.width() + right - 2 * width, bottom), bottomRight, QRect(0, bottomRight.height() - bottom * cIntRetinaFactor(), cIntRetinaFactor(), bottom * cIntRetinaFactor()));
1093 }
1094 
1095 } // namespace Theme
1096 } // namespace Window
1097