/* 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 "window/themes/window_theme_preview.h" #include "lang/lang_keys.h" #include "platform/platform_window_title.h" #include "ui/text/text_options.h" #include "ui/text/text_utilities.h" #include "ui/image/image_prepare.h" #include "ui/emoji_config.h" #include "ui/chat/chat_theme.h" #include "ui/image/image_prepare.h" #include "styles/style_widgets.h" #include "styles/style_window.h" #include "styles/style_media_view.h" #include "styles/style_chat.h" #include "styles/style_dialogs.h" #include "styles/style_info.h" namespace Window { namespace Theme { namespace { QString fillLetters(const QString &name) { QList letters; QList levels; auto level = 0; auto letterFound = false; auto ch = name.constData(), end = ch + name.size(); while (ch != end) { auto emojiLength = 0; if (Ui::Emoji::Find(ch, end, &emojiLength)) { ch += emojiLength; } else if (ch->isHighSurrogate()) { ++ch; if (ch != end && ch->isLowSurrogate()) { ++ch; } } else if (!letterFound && ch->isLetterOrNumber()) { letterFound = true; if (ch + 1 != end && Ui::Text::IsDiac(*(ch + 1))) { letters.push_back(QString(ch, 2)); levels.push_back(level); ++ch; } else { letters.push_back(QString(ch, 1)); levels.push_back(level); } ++ch; } else { if (*ch == ' ') { level = 0; letterFound = false; } else if (letterFound && *ch == '-') { level = 1; letterFound = true; } ++ch; } } // We prefer the second letter to be after ' ', but it can also be after '-'. auto result = QString(); if (!letters.isEmpty()) { result += letters.front(); auto bestIndex = 0; auto bestLevel = 2; for (auto i = letters.size(); i != 1;) { if (levels[--i] < bestLevel) { bestIndex = i; bestLevel = levels[i]; } } if (bestIndex > 0) { result += letters[bestIndex]; } } return result.toUpper(); } class Generator { public: Generator( const Instance &theme, CurrentData &¤t, PreviewType type); [[nodiscard]] QImage generate(); private: enum class Status { None, Sent, Received }; struct Row { Ui::Text::String name; QString letters; enum class Type { User, Group, Channel }; Type type = Type::User; int peerIndex = 0; int unreadCounter = 0; bool muted = false; bool pinned = false; QString date; Ui::Text::String text; Status status = Status::None; bool selected = false; bool active = false; }; struct Bubble { int width = 0; int height = 0; bool outbg = false; Status status = Status::None; QString date; bool attached = false; bool tail = true; Ui::Text::String text = { st::msgMinWidth }; QVector waveform; int waveactive = 0; QString wavestatus; QImage photo; int photoWidth = 0; int photoHeight = 0; Ui::Text::String replyName = { st::msgMinWidth }; Ui::Text::String replyText = { st::msgMinWidth }; }; [[nodiscard]] bool extended() const; void prepare(); void addRow( QString name, int peerIndex, QString date, const TextWithEntities &text); void addBubble(Bubble bubble, int width, int height, QString date, Status status); void addAudioBubble(QVector waveform, int waveactive, QString wavestatus, QString date, Status status); void addTextBubble(QString text, QString date, Status status); void addDateBubble(QString date); void addPhotoBubble(QString image, QString caption, QString date, Status status); QSize computeSkipBlock(Status status, QString date); int computeInfoWidth(Status status, QString date); void generateData(); void paintHistoryList(); void paintHistoryBackground(); void paintTopBar(); void paintComposeArea(); void paintDialogs(); void paintDialogsList(); void paintHistoryShadows(); void paintRow(const Row &row); void paintBubble(const Bubble &bubble); void paintService(QString text); void paintUserpic(int x, int y, Row::Type type, int index, QString letters); void setTextPalette(const style::TextPalette &st); void restoreTextPalette(); const Instance &_theme; const style::palette &_palette; const CurrentData _current; const PreviewType _type; Painter *_p = nullptr; QRect _rect; QRect _inner; QRect _body; QRect _dialogs; QRect _dialogsList; QRect _topBar; QRect _composeArea; QRect _history; int _rowsTop = 0; std::vector _rows; Ui::Text::String _topBarName; QString _topBarStatus; bool _topBarStatusActive = false; int _historyBottom = 0; std::vector _bubbles; style::TextPalette _textPalette; }; bool Generator::extended() const { return (_type == PreviewType::Extended); } void Generator::prepare() { const auto size = extended() ? QRect( QPoint(), st::themePreviewSize).marginsAdded(st::themePreviewMargin).size() : st::themePreviewSize; _rect = QRect(QPoint(), size); _inner = extended() ? _rect.marginsRemoved(st::themePreviewMargin) : _rect; _body = extended() ? _inner.marginsRemoved(QMargins(0, Platform::PreviewTitleHeight(), 0, 0)) : _inner; _dialogs = QRect(_body.x(), _body.y(), st::themePreviewDialogsWidth, _body.height()); _dialogsList = _dialogs.marginsRemoved(QMargins(0, st::dialogsFilterPadding.y() + st::dialogsMenuToggle.height + st::dialogsFilterPadding.y(), 0, st::dialogsPadding.y())); _topBar = QRect(_dialogs.x() + _dialogs.width(), _dialogs.y(), _body.width() - _dialogs.width(), st::topBarHeight); _composeArea = QRect(_topBar.x(), _body.y() + _body.height() - st::historySendSize.height(), _topBar.width(), st::historySendSize.height()); _history = QRect(_topBar.x(), _topBar.y() + _topBar.height(), _topBar.width(), _body.height() - _topBar.height() - _composeArea.height()); generateData(); } void Generator::addRow( QString name, int peerIndex, QString date, const TextWithEntities &text) { Row row; row.name.setText(st::msgNameStyle, name, Ui::NameTextOptions()); row.letters = fillLetters(name); row.peerIndex = peerIndex; row.date = date; row.text.setMarkedText( st::dialogsTextStyle, text, Ui::DialogTextOptions()); _rows.push_back(std::move(row)); } void Generator::addBubble(Bubble bubble, int width, int height, QString date, Status status) { bubble.width = width; bubble.height = height; bubble.date = date; bubble.status = status; _bubbles.push_back(std::move(bubble)); } void Generator::addAudioBubble(QVector waveform, int waveactive, QString wavestatus, QString date, Status status) { Bubble bubble; bubble.waveform = waveform; bubble.waveactive = waveactive; bubble.wavestatus = wavestatus; auto skipBlock = computeSkipBlock(status, date); auto width = st::msgFileMinWidth; const auto &st = st::msgFileLayout; auto tleft = st.padding.left() + st.thumbSize + st.padding.right(); accumulate_max(width, tleft + st::normalFont->width(wavestatus) + skipBlock.width() + st::msgPadding.right()); accumulate_min(width, st::msgMaxWidth); auto height = st.padding.top() + st.thumbSize + st.padding.bottom(); addBubble(std::move(bubble), width, height, date, status); } QSize Generator::computeSkipBlock(Status status, QString date) { auto infoWidth = computeInfoWidth(status, date); auto width = st::msgDateSpace + infoWidth - st::msgDateDelta.x(); auto height = st::msgDateFont->height - st::msgDateDelta.y(); return QSize(width, height); } int Generator::computeInfoWidth(Status status, QString date) { auto result = st::msgDateFont->width(date); if (status != Status::None) { result += st::historySendStateSpace; } return result; } void Generator::addTextBubble(QString text, QString date, Status status) { Bubble bubble; auto skipBlock = computeSkipBlock(status, date); auto marked = TextWithEntities{ std::move(text) }; bubble.text.setMarkedText( st::messageTextStyle, std::move(marked), Ui::ItemTextDefaultOptions()); bubble.text.updateSkipBlock(skipBlock.width(), skipBlock.height()); auto width = _history.width() - st::msgMargin.left() - st::msgMargin.right(); accumulate_min(width, st::msgPadding.left() + bubble.text.maxWidth() + st::msgPadding.right()); accumulate_min(width, st::msgMaxWidth); auto textWidth = qMax(width - st::msgPadding.left() - st::msgPadding.right(), 1); auto textHeight = bubble.text.countHeight(textWidth); auto height = st::msgPadding.top() + textHeight + st::msgPadding.bottom(); addBubble(std::move(bubble), width, height, date, status); } void Generator::addDateBubble(QString date) { Bubble bubble; addBubble(std::move(bubble), 0, 0, date, Status::None); } void Generator::addPhotoBubble(QString image, QString caption, QString date, Status status) { Bubble bubble; bubble.photo.load(image); bubble.photoWidth = style::ConvertScale(bubble.photo.width() / 2); bubble.photoHeight = style::ConvertScale(bubble.photo.height() / 2); auto skipBlock = computeSkipBlock(status, date); auto marked = TextWithEntities{ std::move(caption) }; bubble.text.setMarkedText( st::messageTextStyle, std::move(marked), Ui::ItemTextDefaultOptions()); bubble.text.updateSkipBlock(skipBlock.width(), skipBlock.height()); auto width = _history.width() - st::msgMargin.left() - st::msgMargin.right(); accumulate_min(width, bubble.photoWidth); accumulate_min(width, st::msgMaxWidth); auto textWidth = qMax(width - st::msgPadding.left() - st::msgPadding.right(), 1); auto textHeight = bubble.text.countHeight(textWidth); auto height = st::mediaCaptionSkip + textHeight + st::msgPadding.bottom(); addBubble(std::move(bubble), width, height, date, status); } void Generator::generateData() { _rows.reserve(9); addRow( "Eva Summer", 0, "11:00", { .text = "We are too smart for this world. " + QString::fromUtf8("\xf0\x9f\xa4\xa3\xf0\x9f\x98\x82") }); _rows.back().active = true; _rows.back().pinned = true; addRow("Alexandra Smith", 7, "10:00", { .text = "This is amazing!" }); _rows.back().unreadCounter = 2; addRow( "Mike Apple", 2, "9:00", Ui::Text::PlainLink(QChar(55357) + QString() + QChar(56836) + " Sticker")); _rows.back().unreadCounter = 2; _rows.back().muted = true; addRow("Evening Club", 1, "8:00", Ui::Text::PlainLink("Eva: Photo")); _rows.back().type = Row::Type::Group; addRow( "Old Pirates", 6, "7:00", Ui::Text::PlainLink("Max:").append(" Yo-ho-ho!")); _rows.back().type = Row::Type::Group; addRow("Max Bright", 3, "6:00", { .text = "How about some coffee?" }); _rows.back().status = Status::Received; addRow("Natalie Parker", 4, "5:00", { .text = "OK, great)" }); _rows.back().status = Status::Received; addRow("Davy Jones", 5, "4:00", Ui::Text::PlainLink("Keynote.pdf")); _topBarName.setText(st::msgNameStyle, "Eva Summer", Ui::NameTextOptions()); _topBarStatus = "online"; _topBarStatusActive = true; addPhotoBubble(":/gui/art/themeimage.jpg", "To reach a port, we must sail. " + QString::fromUtf8("\xf0\x9f\xa5\xb8"), "7:00", Status::None); 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 }; auto waveform = QVector(base::array_size(wavedata)); memcpy(waveform.data(), wavedata, sizeof(wavedata)); addAudioBubble(waveform, 33, "0:07", "8:00", Status::None); _bubbles.back().outbg = true; _bubbles.back().status = Status::Received; addDateBubble("December 26"); 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); _bubbles.back().tail = false; _bubbles.back().outbg = true; addTextBubble("Mark Twain said that " + QString::fromUtf8("\xe2\x98\x9d\xef\xb8\x8f"), "10:00", Status::Received); _bubbles.back().outbg = true; _bubbles.back().attached = true; _bubbles.back().tail = true; addTextBubble("We are too smart for this world. " + QString::fromUtf8("\xf0\x9f\xa4\xa3\xf0\x9f\x98\x82"), "11:00", Status::None); _bubbles.back().replyName.setText(st::msgNameStyle, "Alex Cassio", Ui::NameTextOptions()); _bubbles.back().replyText.setText(st::messageTextStyle, "Mark Twain said that " + QString::fromUtf8("\xe2\x98\x9d\xef\xb8\x8f"), Ui::DialogTextOptions()); } Generator::Generator( const Instance &theme, CurrentData &¤t, PreviewType type) : _theme(theme) , _palette(_theme.palette) , _current(std::move(current)) , _type(type) { } QImage Generator::generate() { prepare(); auto result = QImage( _rect.size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(cRetinaFactor()); result.fill(st::themePreviewBg->c); { Painter p(&result); PainterHighQualityEnabler hq(p); _p = &p; _p->fillRect(_body, QColor(0, 0, 0)); _p->fillRect(_body, st::windowBg[_palette]); paintHistoryList(); paintTopBar(); paintComposeArea(); paintDialogs(); paintHistoryShadows(); } if (extended()) { Platform::PreviewWindowFramePaint(result, _palette, _body, _rect.width()); } return result; } void Generator::paintHistoryList() { paintHistoryBackground(); _historyBottom = _history.y() + _history.height(); _historyBottom -= st::historyPaddingBottom; _p->setClipping(true); for (auto i = _bubbles.size(); i != 0;) { auto &bubble = _bubbles[--i]; if (bubble.width > 0) { paintBubble(bubble); } else { paintService(bubble.date); } } _p->setClipping(false); } void Generator::paintHistoryBackground() { auto fromy = (-st::topBarHeight); auto background = _theme.background; auto tiled = _theme.tiled; if (background.isNull()) { const auto fakePaper = Data::WallPaper(_current.backgroundId); if (Data::IsThemeWallPaper(fakePaper)) { background = Ui::ReadBackgroundImage( u":/gui/art/background.tgv"_q, QByteArray(), true); const auto paper = Data::DefaultWallPaper(); background = Ui::PreparePatternImage( std::move(background), paper.backgroundColors(), paper.gradientRotation(), paper.patternOpacity()); tiled = false; } else { background = std::move(_current.backgroundImage); tiled = _current.backgroundTiled; } } background = std::move(background).convertToFormat( QImage::Format_ARGB32_Premultiplied); background.setDevicePixelRatio(cRetinaFactor()); _p->setClipRect(_history); if (tiled) { auto width = background.width(); auto height = background.height(); auto repeatTimesX = qCeil(_history.width() * cIntRetinaFactor() / float64(width)); auto repeatTimesY = qCeil((_history.height() - fromy) * cIntRetinaFactor() / float64(height)); auto imageForTiled = QImage( width * repeatTimesX, height * repeatTimesY, QImage::Format_ARGB32_Premultiplied); imageForTiled.setDevicePixelRatio(background.devicePixelRatio()); auto imageForTiledBytes = imageForTiled.bits(); auto bytesInLine = width * sizeof(uint32); for (auto timesY = 0; timesY != repeatTimesY; ++timesY) { auto imageBytes = background.constBits(); for (auto y = 0; y != height; ++y) { for (auto timesX = 0; timesX != repeatTimesX; ++timesX) { memcpy(imageForTiledBytes, imageBytes, bytesInLine); imageForTiledBytes += bytesInLine; } imageBytes += background.bytesPerLine(); imageForTiledBytes += imageForTiled.bytesPerLine() - (repeatTimesX * bytesInLine); } } _p->drawImage(_history.x(), _history.y() + fromy, imageForTiled); } else { PainterHighQualityEnabler hq(*_p); auto fill = QSize(_topBar.width(), _body.height()); const auto rects = Ui::ComputeChatBackgroundRects( fill, background.size()); auto to = rects.to; to.moveTop(to.top() + fromy); to.moveTopLeft(to.topLeft() + _history.topLeft()); _p->drawImage(to, background, rects.from); } _p->setClipping(false); } void Generator::paintTopBar() { _p->fillRect(_topBar, st::topBarBg[_palette]); auto right = st::topBarMenuToggle.width; st::topBarMenuToggle.icon[_palette].paint(*_p, _topBar.x() + _topBar.width() - right + st::topBarMenuToggle.iconPosition.x(), _topBar.y() + st::topBarMenuToggle.iconPosition.y(), _rect.width()); right += st::topBarSkip + st::topBarCall.width; st::topBarCall.icon[_palette].paint(*_p, _topBar.x() + _topBar.width() - right + st::topBarCall.iconPosition.x(), _topBar.y() + st::topBarCall.iconPosition.y(), _rect.width()); right += st::topBarSearch.width; st::topBarSearch.icon[_palette].paint(*_p, _topBar.x() + _topBar.width() - right + st::topBarSearch.iconPosition.x(), _topBar.y() + st::topBarSearch.iconPosition.y(), _rect.width()); auto decreaseWidth = st::topBarCall.width + st::topBarCallSkip + st::topBarSearch.width + st::topBarMenuToggle.width; auto nameleft = _topBar.x() + st::topBarArrowPadding.right(); auto nametop = _topBar.y() + st::topBarArrowPadding.top(); auto statustop = _topBar.y() + st::topBarHeight - st::topBarArrowPadding.bottom() - st::dialogsTextFont->height; auto namewidth = _topBar.x() + _topBar.width() - decreaseWidth - nameleft - st::topBarArrowPadding.right(); _p->setFont(st::dialogsTextFont); _p->setPen(_topBarStatusActive ? st::historyStatusFgActive[_palette] : st::historyStatusFg[_palette]); _p->drawText(nameleft, statustop + st::dialogsTextFont->ascent, _topBarStatus); _p->setPen(st::dialogsNameFg[_palette]); _topBarName.drawElided(*_p, nameleft, nametop, namewidth); } void Generator::paintComposeArea() { _p->fillRect(_composeArea, st::historyReplyBg[_palette]); auto controlsTop = _composeArea.y() + _composeArea.height() - st::historySendSize.height(); const auto attachIconLeft = (st::historyAttach.iconPosition.x() < 0) ? ((st::historyAttach.width - st::historyAttach.icon.width()) / 2) : st::historyAttach.iconPosition.x(); const auto attachIconTop = (st::historyAttach.iconPosition.y() < 0) ? ((st::historyAttach.height - st::historyAttach.icon.height()) / 2) : st::historyAttach.iconPosition.y(); st::historyAttach.icon[_palette].paint(*_p, _composeArea.x() + attachIconLeft, controlsTop + attachIconTop, _rect.width()); auto right = st::historySendRight + st::historySendSize.width(); st::historyRecordVoice[_palette].paintInCenter(*_p, QRect(_composeArea.x() + _composeArea.width() - right, controlsTop, st::historySendSize.width(), st::historySendSize.height())); const auto emojiIconLeft = (st::historyAttachEmoji.iconPosition.x() < 0) ? ((st::historyAttachEmoji.width - st::historyAttachEmoji.icon.width()) / 2) : st::historyAttachEmoji.iconPosition.x(); const auto emojiIconTop = (st::historyAttachEmoji.iconPosition.y() < 0) ? ((st::historyAttachEmoji.height - st::historyAttachEmoji.icon.height()) / 2) : st::historyAttachEmoji.iconPosition.y(); const auto &emojiIcon = st::historyAttachEmoji.icon[_palette]; right += st::historyAttachEmoji.width; auto attachEmojiLeft = _composeArea.x() + _composeArea.width() - right; _p->fillRect(attachEmojiLeft, controlsTop, st::historyAttachEmoji.width, st::historyAttachEmoji.height, st::historyComposeAreaBg[_palette]); emojiIcon.paint(*_p, attachEmojiLeft + emojiIconLeft, controlsTop + emojiIconTop, _rect.width()); auto pen = st::historyEmojiCircleFg[_palette]->p; pen.setWidthF(style::ConvertScaleExact(st::historyEmojiCircleLine)); pen.setCapStyle(Qt::RoundCap); _p->setPen(pen); _p->setBrush(Qt::NoBrush); PainterHighQualityEnabler hq(*_p); const auto skipx = emojiIcon.width() / 4; const auto skipy = emojiIcon.height() / 4; const auto inner = QRect( attachEmojiLeft + emojiIconLeft + skipx, controlsTop + emojiIconTop + skipy, emojiIcon.width() - 2 * skipx, emojiIcon.height() - 2 * skipy); _p->drawEllipse(inner); auto fieldLeft = _composeArea.x() + st::historyAttach.width; auto fieldTop = _composeArea.y() + _composeArea.height() - st::historyAttach.height + st::historySendPadding; auto fieldWidth = _composeArea.width() - st::historyAttach.width - st::historySendSize.width() - st::historySendRight - st::historyAttachEmoji.width; auto fieldHeight = st::historySendSize.height() - 2 * st::historySendPadding; auto field = QRect(fieldLeft, fieldTop, fieldWidth, fieldHeight); _p->fillRect(field, st::historyComposeField.textBg[_palette]); _p->save(); _p->setClipRect(field); _p->setFont(st::historyComposeField.font); _p->setPen(st::historyComposeField.placeholderFg[_palette]); auto placeholderRect = QRect( field.x() + st::historyComposeField.textMargins.left() + st::historyComposeField.placeholderMargins.left(), field.y() + st::historyComposeField.textMargins.top() + st::historyComposeField.placeholderMargins.top(), field.width() - st::historyComposeField.textMargins.left() - st::historyComposeField.textMargins.right(), field.height() - st::historyComposeField.textMargins.top() - st::historyComposeField.textMargins.bottom()); _p->drawText(placeholderRect, tr::lng_message_ph(tr::now), QTextOption(st::historyComposeField.placeholderAlign)); _p->restore(); _p->setClipping(false); } void Generator::paintDialogs() { _p->fillRect(_dialogs, st::dialogsBg[_palette]); const auto iconLeft = (st::dialogsMenuToggle.iconPosition.x() < 0) ? (st::dialogsMenuToggle.width - st::dialogsMenuToggle.icon.width()) / 2 : st::dialogsMenuToggle.iconPosition.x(); const auto iconTop = (st::dialogsMenuToggle.iconPosition.y() < 0) ? (st::dialogsMenuToggle.height - st::dialogsMenuToggle.icon.height()) / 2 : st::dialogsMenuToggle.iconPosition.y(); st::dialogsMenuToggle.icon[_palette].paint(*_p, _dialogs.x() + st::dialogsFilterPadding.x() + iconLeft, _dialogs.y() + st::dialogsFilterPadding.y() + iconTop, _rect.width()); auto filterLeft = _dialogs.x() + st::dialogsFilterPadding.x() + st::dialogsMenuToggle.width + st::dialogsFilterPadding.x(); auto filterRight = st::dialogsFilterSkip + st::dialogsFilterPadding.x(); auto filterWidth = _dialogs.x() + _dialogs.width() - filterLeft - filterRight; auto filterAreaHeight = st::topBarHeight; auto filterTop = _dialogs.y() + (filterAreaHeight - st::dialogsFilter.height) / 2; auto filter = QRect(filterLeft, filterTop, filterWidth, st::dialogsFilter.height); auto pen = st::dialogsFilter.borderColor[_palette]->p; pen.setWidth(st::dialogsFilter.borderWidth); _p->setPen(pen); _p->setBrush(st::dialogsFilter.bgColor[_palette]); { PainterHighQualityEnabler hq(*_p); _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.)); } if (!st::dialogsFilter.icon.empty()) { st::dialogsFilter.icon[_palette].paint(*_p, filter.x(), filter.y(), _rect.width()); } _p->save(); _p->setClipRect(filter); 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());; _p->setFont(st::dialogsFilter.font); _p->setPen(st::dialogsFilter.phColor[_palette]); _p->drawText(phRect, tr::lng_dlg_filter(tr::now), QTextOption(st::dialogsFilter.phAlign)); _p->restore(); _p->setClipping(false); paintDialogsList(); } void Generator::paintDialogsList() { _p->setClipRect(_dialogsList); _rowsTop = _dialogsList.y(); for (auto &row : _rows) { paintRow(row); _rowsTop += st::dialogsRowHeight; } _p->setClipping(false); } void Generator::paintRow(const Row &row) { auto x = _dialogsList.x(); auto y = _rowsTop; auto fullWidth = _dialogsList.width(); auto fullRect = QRect(x, y, fullWidth, st::dialogsRowHeight); if (row.active || row.selected) { _p->fillRect(fullRect, row.active ? st::dialogsBgActive[_palette] : st::dialogsBgOver[_palette]); } paintUserpic(x + st::dialogsPadding.x(), y + st::dialogsPadding.y(), row.type, row.peerIndex, row.letters); auto nameleft = x + st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPhotoPadding; auto namewidth = x + fullWidth - nameleft - st::dialogsPadding.x(); auto rectForName = QRect(nameleft, y + st::dialogsPadding.y() + st::dialogsNameTop, namewidth, st::msgNameFont->height); auto chatTypeIcon = ([&row]() -> const style::icon * { if (row.type == Row::Type::Group) { return &(row.active ? st::dialogsChatIconActive : (row.selected ? st::dialogsChatIconOver : st::dialogsChatIcon)); } else if (row.type == Row::Type::Channel) { return &(row.active ? st::dialogsChannelIconActive : (row.selected ? st::dialogsChannelIconOver : st::dialogsChannelIcon)); } return nullptr; })(); if (chatTypeIcon) { (*chatTypeIcon)[_palette].paint(*_p, rectForName.topLeft(), fullWidth); rectForName.setLeft(rectForName.left() + st::dialogsChatTypeSkip); } auto texttop = y + st::dialogsPadding.y() + st::msgNameFont->height + st::dialogsSkip; auto dateWidth = st::dialogsDateFont->width(row.date); rectForName.setWidth(rectForName.width() - dateWidth - st::dialogsDateSkip); _p->setFont(st::dialogsDateFont); _p->setPen(row.active ? st::dialogsDateFgActive[_palette] : (row.selected ? st::dialogsDateFgOver[_palette] : st::dialogsDateFg[_palette])); _p->drawText(rectForName.left() + rectForName.width() + st::dialogsDateSkip, rectForName.top() + st::msgNameFont->height - st::msgDateFont->descent, row.date); auto availableWidth = namewidth; if (row.unreadCounter) { auto counter = QString::number(row.unreadCounter); auto unreadRight = x + fullWidth - st::dialogsPadding.x(); auto unreadTop = texttop + st::dialogsTextFont->ascent - st::dialogsUnreadFont->ascent - (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2; auto unreadWidth = st::dialogsUnreadFont->width(counter); auto unreadRectWidth = unreadWidth + 2 * st::dialogsUnreadPadding; auto unreadRectHeight = st::dialogsUnreadHeight; accumulate_max(unreadRectWidth, unreadRectHeight); auto unreadRectLeft = unreadRight - unreadRectWidth; auto unreadRectTop = unreadTop; availableWidth -= unreadRectWidth + st::dialogsUnreadPadding; style::color bg[] = { st::dialogsUnreadBg, st::dialogsUnreadBgOver, st::dialogsUnreadBgActive, st::dialogsUnreadBgMuted, st::dialogsUnreadBgMutedOver, st::dialogsUnreadBgMutedActive }; auto index = (row.active ? 2 : row.selected ? 1 : 0) + (row.muted ? 3 : 0); _p->setPen(Qt::NoPen); _p->setBrush(bg[index][_palette]); _p->drawRoundedRect(QRectF(unreadRectLeft, unreadRectTop, unreadRectWidth, unreadRectHeight), unreadRectHeight / 2., unreadRectHeight / 2.); auto textTop = (unreadRectHeight - st::dialogsUnreadFont->height) / 2; _p->setFont(st::dialogsUnreadFont); _p->setPen(row.active ? st::dialogsUnreadFgActive[_palette] : (row.selected ? st::dialogsUnreadFgOver[_palette] : st::dialogsUnreadFg[_palette])); _p->drawText(unreadRectLeft + (unreadRectWidth - unreadWidth) / 2, unreadRectTop + textTop + st::dialogsUnreadFont->ascent, counter); } else if (row.pinned) { auto icon = (row.active ? st::dialogsPinnedIconActive[_palette] : (row.selected ? st::dialogsPinnedIconOver[_palette] : st::dialogsPinnedIcon[_palette])); icon.paint(*_p, x + fullWidth - st::dialogsPadding.x() - icon.width(), texttop, fullWidth); availableWidth -= icon.width() + st::dialogsUnreadPadding; } auto textRect = QRect(nameleft, texttop, availableWidth, st::dialogsTextFont->height); setTextPalette(row.active ? st::dialogsTextPaletteActive : (row.selected ? st::dialogsTextPaletteOver : st::dialogsTextPalette)); _p->setFont(st::dialogsTextFont); _p->setPen(row.active ? st::dialogsTextFgActive[_palette] : (row.selected ? st::dialogsTextFgOver[_palette] : st::dialogsTextFg[_palette])); row.text.drawElided(*_p, textRect.left(), textRect.top(), textRect.width(), textRect.height() / st::dialogsTextFont->height); restoreTextPalette(); auto sendStateIcon = ([&row]() -> const style::icon* { if (row.status == Status::Sent) { return &(row.active ? st::dialogsSentIconActive : (row.selected ? st::dialogsSentIconOver : st::dialogsSentIcon)); } else if (row.status == Status::Received) { return &(row.active ? st::dialogsReceivedIconActive : (row.selected ? st::dialogsReceivedIconOver : st::dialogsReceivedIcon)); } return nullptr; })(); if (sendStateIcon) { rectForName.setWidth(rectForName.width() - st::dialogsSendStateSkip); (*sendStateIcon)[_palette].paint(*_p, rectForName.topLeft() + QPoint(rectForName.width(), 0), fullWidth); } _p->setPen(row.active ? st::dialogsNameFgActive[_palette] : (row.selected ? st::dialogsNameFgOver[_palette] : st::dialogsNameFg[_palette])); row.name.drawElided(*_p, rectForName.left(), rectForName.top(), rectForName.width()); } void Generator::paintBubble(const Bubble &bubble) { auto height = bubble.height; if (!bubble.replyName.isEmpty()) { height += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); } auto isPhoto = !bubble.photo.isNull(); auto x = _history.x(); auto y = _historyBottom - st::msgMargin.bottom() - height; auto bubbleTop = y; auto bubbleHeight = height; if (isPhoto) { bubbleTop -= st::historyMessageRadius + 1; bubbleHeight += st::historyMessageRadius + 1; } auto left = bubble.outbg ? st::msgMargin.right() : st::msgMargin.left(); if (bubble.outbg) { left += _history.width() - st::msgMargin.left() - st::msgMargin.right() - bubble.width; } x += left; _p->setPen(Qt::NoPen); auto tailclip = st::historyMessageRadius + 1; if (bubble.tail) { if (bubble.outbg) { _p->setClipRegion(QRegion(_history) - QRect(x + bubble.width - tailclip, bubbleTop + bubbleHeight - tailclip, tailclip + st::historyMessageRadius, tailclip + st::historyMessageRadius)); } else { _p->setClipRegion(QRegion(_history) - QRect(x - st::historyMessageRadius, bubbleTop + bubbleHeight - tailclip, tailclip + st::historyMessageRadius, tailclip + st::historyMessageRadius)); } } auto sh = bubble.outbg ? st::msgOutShadow[_palette] : st::msgInShadow[_palette]; _p->setBrush(sh); _p->drawRoundedRect(x, bubbleTop + st::msgShadow, bubble.width, bubbleHeight, st::historyMessageRadius, st::historyMessageRadius); auto bg = bubble.outbg ? st::msgOutBg[_palette] : st::msgInBg[_palette]; _p->setBrush(bg); _p->drawRoundedRect(x, bubbleTop, bubble.width, bubbleHeight, st::historyMessageRadius, st::historyMessageRadius); if (bubble.tail) { _p->setClipRect(_history); if (bubble.outbg) { _p->fillRect(QRect(x + bubble.width - tailclip, bubbleTop + bubbleHeight - tailclip, tailclip, tailclip), bg); _p->fillRect(QRect(x + bubble.width - tailclip, bubbleTop + bubbleHeight, tailclip + st::historyBubbleTailOutRight.width(), st::msgShadow), sh); st::historyBubbleTailOutRight[_palette].paint(*_p, x + bubble.width, bubbleTop + bubbleHeight - st::historyBubbleTailOutRight.height(), _rect.width()); } else { _p->fillRect(QRect(x, bubbleTop + bubbleHeight - tailclip, tailclip, tailclip), bg); _p->fillRect(QRect(x - st::historyBubbleTailInLeft.width(), bubbleTop + bubbleHeight, tailclip + st::historyBubbleTailInLeft.width(), st::msgShadow), sh); st::historyBubbleTailInLeft[_palette].paint(*_p, x - st::historyBubbleTailInLeft.width(), bubbleTop + bubbleHeight - st::historyBubbleTailOutRight.height(), _rect.width()); } } auto trect = QRect(x, y, bubble.width, bubble.height); if (isPhoto) { trect = trect.marginsRemoved(QMargins(st::msgPadding.left(), st::mediaCaptionSkip, st::msgPadding.right(), st::msgPadding.bottom())); } else { trect = trect.marginsRemoved(st::msgPadding); } if (!bubble.replyName.isEmpty()) { auto h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); auto bar = (bubble.outbg ? st::msgOutReplyBarColor[_palette] : st::msgInReplyBarColor[_palette]); 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()); _p->fillRect(rbar, bar); _p->setPen(bubble.outbg ? st::msgOutServiceFg[_palette] : st::msgInServiceFg[_palette]); bubble.replyName.drawLeftElided(*_p, trect.x() + st::msgReplyBarSkip, trect.y() + st::msgReplyPadding.top(), bubble.width - st::msgReplyBarSkip, _rect.width()); _p->setPen(bubble.outbg ? st::historyTextOutFg[_palette] : st::historyTextInFg[_palette]); bubble.replyText.drawLeftElided(*_p, trect.x() + st::msgReplyBarSkip, trect.y() + st::msgReplyPadding.top() + st::msgServiceNameFont->height, bubble.width - st::msgReplyBarSkip, _rect.width()); trect.setY(trect.y() + h); } if (!bubble.text.isEmpty()) { setTextPalette(bubble.outbg ? st::outTextPalette : st::inTextPalette); _p->setPen(bubble.outbg ? st::historyTextOutFg[_palette] : st::historyTextInFg[_palette]); _p->setFont(st::msgFont); bubble.text.draw(*_p, trect.x(), trect.y(), trect.width()); } else if (!bubble.waveform.isEmpty()) { const auto &st = st::msgFileLayout; auto nameleft = x + st.padding.left() + st.thumbSize + st.padding.right(); auto nameright = st.padding.left(); auto statustop = y + st.statusTop; auto inner = style::rtlrect(x + st.padding.left(), y + st.padding.top(), st.thumbSize, st.thumbSize, _rect.width()); _p->setPen(Qt::NoPen); _p->setBrush(bubble.outbg ? st::msgFileOutBg[_palette] : st::msgFileInBg[_palette]); _p->drawEllipse(inner); auto icon = ([&bubble] { return &(bubble.outbg ? st::historyFileOutPlay : st::historyFileInPlay); })(); (*icon)[_palette].paintInCenter(*_p, inner); auto namewidth = x + bubble.width - nameleft - nameright; // rescale waveform by going in waveform.size * bar_count 1D grid auto active = bubble.outbg ? st::msgWaveformOutActive[_palette] : st::msgWaveformInActive[_palette]; auto inactive = bubble.outbg ? st::msgWaveformOutInactive[_palette] : st::msgWaveformInInactive[_palette]; auto wf_size = bubble.waveform.size(); auto availw = namewidth + st::msgWaveformSkip; auto bar_count = qMin(availw / (st::msgWaveformBar + st::msgWaveformSkip), wf_size); auto max_value = 0; auto max_delta = st::msgWaveformMax - st::msgWaveformMin; auto wave_bottom = y + st::msgFileLayout.padding.top() + st::msgWaveformMax; _p->setPen(Qt::NoPen); auto norm_value = uchar(31); for (auto i = 0, bar_x = 0, sum_i = 0; i < wf_size; ++i) { auto value = bubble.waveform[i]; if (sum_i + bar_count >= wf_size) { // draw bar sum_i = sum_i + bar_count - wf_size; if (sum_i < (bar_count + 1) / 2) { if (max_value < value) max_value = value; } auto bar_value = ((max_value * max_delta) + ((norm_value + 1) / 2)) / (norm_value + 1); if (i >= bubble.waveactive) { _p->fillRect(nameleft + bar_x, wave_bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, inactive); } else { _p->fillRect(nameleft + bar_x, wave_bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, active); } bar_x += st::msgWaveformBar + st::msgWaveformSkip; if (sum_i < (bar_count + 1) / 2) { max_value = 0; } else { max_value = value; } } else { if (max_value < value) max_value = value; sum_i += bar_count; } } auto status = bubble.outbg ? st::mediaOutFg[_palette] : st::mediaInFg[_palette]; _p->setFont(st::normalFont); _p->setPen(status); _p->drawTextLeft(nameleft, statustop, _rect.width(), bubble.wavestatus); } _p->setFont(st::msgDateFont); auto infoRight = x + bubble.width - st::msgPadding.right() + st::msgDateDelta.x(); auto infoBottom = y + height - st::msgPadding.bottom() + st::msgDateDelta.y(); _p->setPen(bubble.outbg ? st::msgOutDateFg[_palette] : st::msgInDateFg[_palette]); auto infoWidth = computeInfoWidth(bubble.status, bubble.date); auto dateX = infoRight - infoWidth; auto dateY = infoBottom - st::msgDateFont->height; _p->drawText(dateX, dateY + st::msgDateFont->ascent, bubble.date); auto icon = ([&bubble]() -> const style::icon * { if (bubble.status == Status::Sent) { return &st::historySentIcon; } else if (bubble.status == Status::Received) { return &st::historyReceivedIcon; } return nullptr; })(); if (icon) { (*icon)[_palette].paint(*_p, QPoint(infoRight, infoBottom) + st::historySendStatePosition, _rect.width()); } _historyBottom = y - (bubble.attached ? st::msgMarginTopAttached : st::msgMargin.top()); if (isPhoto) { auto image = bubble.photo.scaled(bubble.photoWidth * cIntRetinaFactor(), bubble.photoHeight * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); image.setDevicePixelRatio(cRetinaFactor()); _p->drawImage(x, y - bubble.photoHeight, image); _historyBottom -= bubble.photoHeight; } } void Generator::paintService(QString text) { auto bubbleHeight = st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom(); auto bubbleTop = _historyBottom - st::msgServiceMargin.bottom() - bubbleHeight; auto textWidth = st::msgServiceFont->width(text); auto bubbleWidth = st::msgServicePadding.left() + textWidth + st::msgServicePadding.right(); auto radius = bubbleHeight / 2; _p->setPen(Qt::NoPen); _p->setBrush(st::msgServiceBg[_palette]); auto bubbleLeft = _history.x() + (_history.width() - bubbleWidth) / 2; _p->drawRoundedRect(bubbleLeft, bubbleTop, bubbleWidth, bubbleHeight, radius, radius); _p->setPen(st::msgServiceFg[_palette]); _p->setFont(st::msgServiceFont); _p->drawText(bubbleLeft + st::msgServicePadding.left(), bubbleTop + st::msgServicePadding.top() + st::msgServiceFont->ascent, text); _historyBottom = bubbleTop - st::msgServiceMargin.top(); } void Generator::paintUserpic(int x, int y, Row::Type type, int index, QString letters) { style::color colors[] = { st::historyPeer1UserpicBg, st::historyPeer2UserpicBg, st::historyPeer3UserpicBg, st::historyPeer4UserpicBg, st::historyPeer5UserpicBg, st::historyPeer6UserpicBg, st::historyPeer7UserpicBg, st::historyPeer8UserpicBg, }; auto color = colors[index % base::array_size(colors)]; auto image = QImage(st::dialogsPhotoSize * cIntRetinaFactor(), st::dialogsPhotoSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); image.setDevicePixelRatio(cRetinaFactor()); image.fill(color[_palette]->c); { Painter p(&image); auto fontsize = (st::dialogsPhotoSize * 13) / 33; auto font = st::historyPeerUserpicFont->f; font.setPixelSize(fontsize); p.setFont(font); p.setBrush(Qt::NoBrush); p.setPen(st::historyPeerUserpicFg[_palette]); p.drawText(QRect(0, 0, st::dialogsPhotoSize, st::dialogsPhotoSize), letters, QTextOption(style::al_center)); } image = Images::Circle(std::move(image)); _p->drawImage(rtl() ? (_rect.width() - x - st::dialogsPhotoSize) : x, y, image); } void Generator::paintHistoryShadows() { _p->fillRect(_history.x() + st::lineWidth, _history.y(), _history.width() - st::lineWidth, st::lineWidth, st::shadowFg[_palette]); _p->fillRect(_history.x() + st::lineWidth, _history.y() + _history.height() - st::lineWidth, _history.width() - st::lineWidth, st::lineWidth, st::shadowFg[_palette]); _p->fillRect(_history.x(), _body.y(), st::lineWidth, _body.height(), st::shadowFg[_palette]); } void Generator::setTextPalette(const style::TextPalette &st) { _textPalette.linkFg = st.linkFg[_palette].clone(); _textPalette.monoFg = st.monoFg[_palette].clone(); _textPalette.selectBg = st.selectBg[_palette].clone(); _textPalette.selectFg = st.selectFg[_palette].clone(); _textPalette.selectLinkFg = st.selectLinkFg[_palette].clone(); _textPalette.selectMonoFg = st.selectMonoFg[_palette].clone(); _textPalette.selectOverlay = st.selectOverlay[_palette].clone(); _textPalette.spoilerBg = st.spoilerBg[_palette].clone(); _textPalette.spoilerActiveBg = st.spoilerActiveBg[_palette].clone(); _textPalette.spoilerActiveFg = st.spoilerActiveFg[_palette].clone(); _p->setTextPalette(_textPalette); } void Generator::restoreTextPalette() { _p->restoreTextPalette(); } } // namespace QString CachedThemePath(uint64 documentId) { return QString::fromLatin1("special://cached-%1").arg(documentId); } std::unique_ptr PreviewFromFile( const QByteArray &bytes, const QString &filepath, const Data::CloudTheme &cloud) { auto result = std::make_unique(); auto &object = result->object; object.cloud = cloud; object.pathAbsolute = filepath.isEmpty() ? CachedThemePath(cloud.documentId) : QFileInfo(filepath).absoluteFilePath(); object.pathRelative = filepath.isEmpty() ? object.pathAbsolute : QDir().relativeFilePath(filepath); const auto instance = &result->instance; const auto cache = &result->instance.cached; if (bytes.isEmpty()) { if (!LoadFromFile(filepath, instance, cache, &object.content)) { return nullptr; } } else { object.content = bytes; if (!LoadFromContent(bytes, instance, cache)) { return nullptr; } } return result; } std::unique_ptr GeneratePreview( const QByteArray &bytes, const QString &filepath, const Data::CloudTheme &cloud, CurrentData &&data, PreviewType type) { auto result = PreviewFromFile(bytes, filepath, cloud); if (!result) { return nullptr; } result->preview = Generator( result->instance, std::move(data), type ).generate(); return result; } QImage GeneratePreview( const QByteArray &bytes, const QString &filepath) { const auto preview = GeneratePreview( bytes, filepath, Data::CloudTheme(), CurrentData{ Data::ThemeWallPaper().id() }, PreviewType::Normal); return preview ? preview->preview : QImage(); } int DefaultPreviewTitleHeight() { return st::defaultWindowTitle.height; } void DefaultPreviewWindowTitle(Painter &p, const style::palette &palette, QRect body, int outerWidth) { auto titleRect = QRect(body.x(), body.y() - st::defaultWindowTitle.height, body.width(), st::defaultWindowTitle.height); p.fillRect(titleRect, QColor(0, 0, 0)); p.fillRect(titleRect, st::titleBgActive[palette]); auto right = st::defaultWindowTitle.close.width; st::defaultWindowTitle.close.icon[palette].paint(p, titleRect.x() + titleRect.width() - right + st::defaultWindowTitle.close.iconPosition.x(), titleRect.y() + st::windowTitleButtonClose.iconPosition.y(), outerWidth); right += st::defaultWindowTitle.maximize.width; 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); right += st::defaultWindowTitle.minimize.width; 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); p.fillRect(titleRect.x(), titleRect.y() + titleRect.height() - st::lineWidth, titleRect.width(), st::lineWidth, st::titleShadow[palette]); } void DefaultPreviewWindowFramePaint(QImage &preview, const style::palette &palette, QRect body, int outerWidth) { auto mask = QImage(st::windowShadow.size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); mask.setDevicePixelRatio(cRetinaFactor()); { Painter p(&mask); p.setCompositionMode(QPainter::CompositionMode_Source); st::windowShadow.paint(p, 0, 0, st::windowShadow.width(), QColor(0, 0, 0)); } auto maxSize = 0; auto currentInt = static_cast(0); auto lastLineInts = reinterpret_cast(mask.constBits() + (mask.height() - 1) * mask.bytesPerLine()); for (auto end = lastLineInts + mask.width(); lastLineInts != end; ++lastLineInts) { if (*lastLineInts < currentInt) { break; } currentInt = *lastLineInts; ++maxSize; } if (maxSize % cIntRetinaFactor()) { maxSize -= (maxSize % cIntRetinaFactor()); } auto size = maxSize / cIntRetinaFactor(); auto bottom = size; auto left = size - st::windowShadowShift; auto right = left; auto top = size - 2 * st::windowShadowShift; auto sprite = st::windowShadow[palette]; auto topLeft = QImage(sprite.size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); topLeft.setDevicePixelRatio(cRetinaFactor()); { Painter p(&topLeft); p.setCompositionMode(QPainter::CompositionMode_Source); sprite.paint(p, 0, 0, sprite.width()); } auto width = sprite.width(); auto height = sprite.height(); auto topRight = topLeft.mirrored(true, false); auto bottomRight = topLeft.mirrored(true, true); auto bottomLeft = topLeft.mirrored(false, true); Painter p(&preview); DefaultPreviewWindowTitle(p, palette, body, outerWidth); auto inner = QRect(body.x(), body.y() - st::defaultWindowTitle.height, body.width(), body.height() + st::defaultWindowTitle.height); p.setClipRegion(QRegion(inner.marginsAdded(QMargins(size, size, size, size))) - inner); p.drawImage(inner.x() - left, inner.y() - top, topLeft); p.drawImage(inner.x() + inner.width() + right - width, inner.y() - top, topRight); p.drawImage(inner.x() + inner.width() + right - width, inner.y() + inner.height() + bottom - height, bottomRight); p.drawImage(inner.x() - left, inner.y() + inner.height() + bottom - height, bottomLeft); 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())); 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())); 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())); 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())); } } // namespace Theme } // namespace Window