diff --git a/Telegram/Resources/style.txt b/Telegram/Resources/style.txt index 5fd98d4b2..53c3d366e 100644 --- a/Telegram/Resources/style.txt +++ b/Telegram/Resources/style.txt @@ -950,8 +950,9 @@ dlgUnreadColor: #FFF; dlgUnreadBG: #6fc766; dlgUnreadMutedBG: #bbb; dlgUnreadFont: font(12px bold); +dlgUnreadHeight: 19px; +dlgUnreadTop: 1px; dlgUnreadPaddingHor: 5px; -dlgUnreadPaddingVer: 1px; dlgUnreadRadius: 2px; dlgBG: #FFF; dlgHoverBG: #f5f5f5; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 5a1343229..29493a003 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -19,8 +19,10 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "lang.h" +#include "app.h" +#include "lang.h" +#include "dialogs/dialogs_layout.h" #include "audio.h" #include "application.h" #include "fileuploader.h" @@ -2072,6 +2074,8 @@ namespace { mainEmojiMap.clear(); otherEmojiMap.clear(); + Dialogs::Layout::clearStyleSheets(); + clearAllImages(); } diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp index 6963d923e..2e456d912 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp @@ -27,12 +27,14 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Dialogs { namespace Layout { -void RowPainter::paint(Painter &p, const Row *row, int w, bool active, bool selected, bool onlyBackground) { +namespace { + +template +void paintRow(Painter &p, History *history, HistoryItem *item, int w, bool active, bool selected, bool onlyBackground, PaintItemCallback paintItemCallback) { QRect fullRect(0, 0, w, st::dlgHeight); p.fillRect(fullRect, active ? st::dlgActiveBG : (selected ? st::dlgHoverBG : st::dlgBG)); if (onlyBackground) return; - History *history = row->history(); PeerData *userpicPeer = (history->peer->migrateTo() ? history->peer->migrateTo() : history->peer); userpicPeer->paintUserpicLeft(p, st::dlgPhotoSize, st::dlgPaddingHor, st::dlgPaddingVer, w); @@ -49,8 +51,7 @@ void RowPainter::paint(Painter &p, const Row *row, int w, bool active, bool sele rectForName.setLeft(rectForName.left() + st::dlgImgSkip); } - HistoryItem *last = history->lastMsg; - if (!last) { + if (!item) { p.setFont(st::dlgHistFont); p.setPen(active ? st::dlgActiveColor : st::dlgSystemColor); if (history->typing.isEmpty() && history->sendActions.isEmpty()) { @@ -60,7 +61,7 @@ void RowPainter::paint(Painter &p, const Row *row, int w, bool active, bool sele } } else { // draw date - QDateTime now(QDateTime::currentDateTime()), lastTime(last->date); + QDateTime now(QDateTime::currentDateTime()), lastTime(item->date); QDate nowDate(now.date()), lastDate(lastTime.date()); QString dt; if (lastDate == nowDate) { @@ -72,15 +73,15 @@ void RowPainter::paint(Painter &p, const Row *row, int w, bool active, bool sele } int32 dtWidth = st::dlgDateFont->width(dt); rectForName.setWidth(rectForName.width() - dtWidth - st::dlgDateSkip); - p.setFont(st::dlgDateFont->f); - p.setPen((active ? st::dlgActiveDateColor : st::dlgDateColor)->p); + p.setFont(st::dlgDateFont); + p.setPen(active ? st::dlgActiveDateColor : st::dlgDateColor); p.drawText(rectForName.left() + rectForName.width() + st::dlgDateSkip, rectForName.top() + st::msgNameFont->height - st::msgDateFont->descent, dt); // draw check - if (last->needCheck()) { + if (item->needCheck()) { const style::sprite *check; - if (last->id > 0) { - if (last->unread()) { + if (item->id > 0) { + if (item->unread()) { check = active ? &st::dlgActiveCheckImg : &st::dlgCheckImg; } else { check = active ? &st::dlgActiveDblCheckImg : &st::dlgDblCheckImg; @@ -89,10 +90,91 @@ void RowPainter::paint(Painter &p, const Row *row, int w, bool active, bool sele check = active ? &st::dlgActiveSendImg : &st::dlgSendImg; } rectForName.setWidth(rectForName.width() - check->pxWidth() - st::dlgCheckSkip); - p.drawPixmap(QPoint(rectForName.left() + rectForName.width() + st::dlgCheckLeft, rectForName.top() + st::dlgCheckTop), App::sprite(), *check); + p.drawSprite(QPoint(rectForName.left() + rectForName.width() + st::dlgCheckLeft, rectForName.top() + st::dlgCheckTop), *check); } - // draw unread + paintItemCallback(nameleft, namewidth); + } + + if (history->peer->isUser() && history->peer->isVerified()) { + rectForName.setWidth(rectForName.width() - st::verifiedCheck.pxWidth() - st::verifiedCheckPos.x()); + p.drawSprite(rectForName.topLeft() + QPoint(qMin(history->peer->dialogName().maxWidth(), rectForName.width()), 0) + st::verifiedCheckPos, (active ? st::verifiedCheckInv : st::verifiedCheck)); + } + + p.setPen(active ? st::dlgActiveColor : st::dlgNameColor); + history->peer->dialogName().drawElided(p, rectForName.left(), rectForName.top(), rectForName.width()); +} + +class UnreadBadgeStyle : public StyleSheet { +public: + QImage circle; + QPixmap left[4], right[4]; + style::color bg[4] = { st::dlgUnreadBG, st::dlgActiveUnreadBG, st::dlgUnreadMutedBG, st::dlgActiveUnreadMutedBG }; +}; +StyleSheetPointer unreadBadgeStyle; + +void createCircleMask(int size) { + if (!unreadBadgeStyle->circle.isNull()) return; + + unreadBadgeStyle->circle = QImage(size, size, QImage::Format::Format_Grayscale8); + unreadBadgeStyle->circle.setDevicePixelRatio(cRetinaFactor()); + + QPainter pcircle(&unreadBadgeStyle->circle); + pcircle.setRenderHint(QPainter::HighQualityAntialiasing, true); + pcircle.fillRect(0, 0, size, size, QColor(0, 0, 0)); + pcircle.setPen(Qt::NoPen); + pcircle.setBrush(QColor(255, 255, 255)); + pcircle.drawEllipse(0, 0, size, size); +} + +QImage colorizeCircleHalf(int size, int half, int xoffset, style::color color) { + int a = color->c.alpha() + 1; + int fg_r = color->c.red() * a, fg_g = color->c.green() * a, fg_b = color->c.blue() * a, fg_a = 255 * a; + + QImage result(size, size, QImage::Format_ARGB32_Premultiplied); + uchar *bits = result.bits(), *maskbits = unreadBadgeStyle->circle.bits(); + int bpl = result.bytesPerLine(), maskbpl = unreadBadgeStyle->circle.bytesPerLine(); + for (int x = 0; x < size; ++x) { + for (int y = 0; y < size; ++y) { + int s = y * bpl + (x * 4); + int o = maskbits[y * maskbpl + x + xoffset] + 1; + bits[s + 0] = (fg_b * o) >> 16; + bits[s + 1] = (fg_g * o) >> 16; + bits[s + 2] = (fg_r * o) >> 16; + bits[s + 3] = (fg_a * o) >> 16; + } + } + result.setDevicePixelRatio(cRetinaFactor()); + return result; +} + +void paintUnreadBadge(Painter &p, const QRect &rect, bool active, bool muted) { + int index = (active ? 0x01 : 0x00) | (muted ? 0x02 : 0x00); + int size = rect.height(), sizehalf = size / 2; + + unreadBadgeStyle.createIfNull(); + style::color bg = unreadBadgeStyle->bg[index]; + if (unreadBadgeStyle->left[index].isNull()) { + int imgsize = size * cIntRetinaFactor(), imgsizehalf = sizehalf * cIntRetinaFactor(); + createCircleMask(imgsize); + unreadBadgeStyle->left[index] = QPixmap::fromImage(colorizeCircleHalf(imgsize, imgsizehalf, 0, bg)); + unreadBadgeStyle->right[index] = QPixmap::fromImage(colorizeCircleHalf(imgsize, imgsizehalf, imgsize - imgsizehalf, bg)); + } + + int bar = rect.width() - 2 * sizehalf; + p.drawPixmap(rect.x(), rect.y(), unreadBadgeStyle->left[index]); + if (bar) { + p.fillRect(rect.x() + sizehalf, rect.y(), bar, rect.height(), bg); + } + p.drawPixmap(rect.x() + sizehalf + bar, rect.y(), unreadBadgeStyle->right[index]); +} + +} // namepsace + +void RowPainter::paint(Painter &p, const Row *row, int w, bool active, bool selected, bool onlyBackground) { + auto history = row->history(); + auto item = history->lastMsg; + paintRow(p, history, item, w, active, selected, onlyBackground, [&p, w, active, history, item](int nameleft, int namewidth) { int32 lastWidth = namewidth, unread = history->unreadCount; if (history->peer->migrateFrom()) { if (History *h = App::historyLoaded(history->peer->migrateFrom()->id)) { @@ -101,104 +183,62 @@ void RowPainter::paint(Painter &p, const Row *row, int w, bool active, bool sele } if (unread) { QString unreadStr = QString::number(unread); - int32 unreadWidth = st::dlgUnreadFont->width(unreadStr); - int32 unreadRectWidth = unreadWidth + 2 * st::dlgUnreadPaddingHor; - int32 unreadRectHeight = st::dlgUnreadFont->height + 2 * st::dlgUnreadPaddingVer; - int32 unreadRectLeft = w - st::dlgPaddingHor - unreadRectWidth; - int32 unreadRectTop = st::dlgHeight - st::dlgPaddingVer - unreadRectHeight; + int unreadWidth = st::dlgUnreadFont->width(unreadStr); + int unreadRectWidth = unreadWidth + 2 * st::dlgUnreadPaddingHor; + int unreadRectHeight = st::dlgUnreadHeight; + accumulate_max(unreadRectWidth, unreadRectHeight); + + int unreadRectLeft = w - st::dlgPaddingHor - unreadRectWidth; + int unreadRectTop = st::dlgHeight - st::dlgPaddingVer - unreadRectHeight; lastWidth -= unreadRectWidth + st::dlgUnreadPaddingHor; - p.setBrush(active ? (history->mute ? st::dlgActiveUnreadMutedBG : st::dlgActiveUnreadBG) : (history->mute ? st::dlgUnreadMutedBG : st::dlgUnreadBG)); - p.setPen(Qt::NoPen); - p.drawRoundedRect(unreadRectLeft, unreadRectTop, unreadRectWidth, unreadRectHeight, st::dlgUnreadRadius, st::dlgUnreadRadius); - p.setFont(st::dlgUnreadFont->f); + + paintUnreadBadge(p, QRect(unreadRectLeft, unreadRectTop, unreadRectWidth, unreadRectHeight), active, history->mute); + + p.setFont(st::dlgUnreadFont); p.setPen(active ? st::dlgActiveUnreadColor : st::dlgUnreadColor); - p.drawText(unreadRectLeft + st::dlgUnreadPaddingHor, unreadRectTop + st::dlgUnreadPaddingVer + st::dlgUnreadFont->ascent, unreadStr); + p.drawText(unreadRectLeft + (unreadRectWidth - unreadWidth) / 2, unreadRectTop + st::dlgUnreadTop + st::dlgUnreadFont->ascent, unreadStr); } if (history->typing.isEmpty() && history->sendActions.isEmpty()) { - last->drawInDialog(p, QRect(nameleft, st::dlgPaddingVer + st::dlgFont->height + st::dlgSep, lastWidth, st::dlgFont->height), active, history->textCachedFor, history->lastItemTextCache); + item->drawInDialog(p, QRect(nameleft, st::dlgPaddingVer + st::dlgFont->height + st::dlgSep, lastWidth, st::dlgFont->height), active, history->textCachedFor, history->lastItemTextCache); } else { p.setPen(active ? st::dlgActiveColor : st::dlgSystemColor); history->typingText.drawElided(p, nameleft, st::dlgPaddingVer + st::dlgFont->height + st::dlgSep, lastWidth); } - } - - if (history->peer->isUser() && history->peer->isVerified()) { - rectForName.setWidth(rectForName.width() - st::verifiedCheck.pxWidth() - st::verifiedCheckPos.x()); - p.drawSprite(rectForName.topLeft() + QPoint(qMin(history->peer->dialogName().maxWidth(), rectForName.width()), 0) + st::verifiedCheckPos, (active ? st::verifiedCheckInv : st::verifiedCheck)); - } - - p.setPen(active ? st::dlgActiveColor : st::dlgNameColor); - history->peer->dialogName().drawElided(p, rectForName.left(), rectForName.top(), rectForName.width()); + }); } void RowPainter::paint(Painter &p, const FakeRow *row, int w, bool active, bool selected, bool onlyBackground) { - QRect fullRect(0, 0, w, st::dlgHeight); - p.fillRect(fullRect, (active ? st::dlgActiveBG : (selected ? st::dlgHoverBG : st::dlgBG))->b); - if (onlyBackground) return; - auto item = row->item(); auto history = item->history(); - PeerData *userpicPeer = (history->peer->migrateTo() ? history->peer->migrateTo() : history->peer); - userpicPeer->paintUserpicLeft(p, st::dlgPhotoSize, st::dlgPaddingHor, st::dlgPaddingVer, w); + paintRow(p, history, item, w, active, selected, onlyBackground, [&p, row, active, item](int nameleft, int namewidth) { + int32 lastWidth = namewidth; + item->drawInDialog(p, QRect(nameleft, st::dlgPaddingVer + st::dlgFont->height + st::dlgSep, lastWidth, st::dlgFont->height), active, row->_cacheFor, row->_cache); + }); +} - int32 nameleft = st::dlgPaddingHor + st::dlgPhotoSize + st::dlgPhotoPadding; - int32 namewidth = w - nameleft - st::dlgPaddingHor; - QRect rectForName(nameleft, st::dlgPaddingVer + st::dlgNameTop, namewidth, st::msgNameFont->height); +namespace { - // draw chat icon - if (history->peer->isChat() || history->peer->isMegagroup()) { - p.drawSprite(QPoint(rectForName.left() + st::dlgChatImgPos.x(), rectForName.top() + st::dlgChatImgPos.y()), (active ? st::dlgActiveChatImg : st::dlgChatImg)); - rectForName.setLeft(rectForName.left() + st::dlgImgSkip); - } else if (history->peer->isChannel()) { - p.drawSprite(QPoint(rectForName.left() + st::dlgChannelImgPos.x(), rectForName.top() + st::dlgChannelImgPos.y()), (active ? st::dlgActiveChannelImg : st::dlgChannelImg)); - rectForName.setLeft(rectForName.left() + st::dlgImgSkip); +using StyleSheets = OrderedSet; +NeverFreedPointer styleSheets; + +} + +namespace internal { + +void registerStyleSheet(StyleSheet **p) { + styleSheets.makeIfNull(); + styleSheets->insert(p); +} + +} // namespace internal + +void clearStyleSheets() { + if (!styleSheets) return; + for (auto &p : *styleSheets) { + delete (*p); + *p = nullptr; } - - // draw date - QDateTime now(QDateTime::currentDateTime()), lastTime(item->date); - QDate nowDate(now.date()), lastDate(lastTime.date()); - QString dt; - if (lastDate == nowDate) { - dt = lastTime.toString(cTimeFormat()); - } else if (lastDate.year() == nowDate.year() && lastDate.weekNumber() == nowDate.weekNumber()) { - dt = langDayOfWeek(lastDate); - } else { - dt = lastDate.toString(qsl("d.MM.yy")); - } - int32 dtWidth = st::dlgDateFont->width(dt); - rectForName.setWidth(rectForName.width() - dtWidth - st::dlgDateSkip); - p.setFont(st::dlgDateFont); - p.setPen(active ? st::dlgActiveDateColor : st::dlgDateColor); - p.drawText(rectForName.left() + rectForName.width() + st::dlgDateSkip, rectForName.top() + st::msgNameFont->height - st::msgDateFont->descent, dt); - - // draw check - if (item->needCheck()) { - const style::sprite *check; - if (item->id > 0) { - if (item->unread()) { - check = active ? &st::dlgActiveCheckImg : &st::dlgCheckImg; - } else { - check = active ? &st::dlgActiveDblCheckImg : &st::dlgDblCheckImg; - } - } else { - check = active ? &st::dlgActiveSendImg : &st::dlgSendImg; - } - rectForName.setWidth(rectForName.width() - check->pxWidth() - st::dlgCheckSkip); - p.drawSprite(QPoint(rectForName.left() + rectForName.width() + st::dlgCheckLeft, rectForName.top() + st::dlgCheckTop), *check); - } - - // draw unread - int32 lastWidth = namewidth; - item->drawInDialog(p, QRect(nameleft, st::dlgPaddingVer + st::dlgFont->height + st::dlgSep, lastWidth, st::dlgFont->height), active, row->_cacheFor, row->_cache); - - if (history->peer->isUser() && history->peer->isVerified()) { - rectForName.setWidth(rectForName.width() - st::verifiedCheck.pxWidth() - st::verifiedCheckPos.x()); - p.drawSprite(rectForName.topLeft() + QPoint(qMin(history->peer->dialogName().maxWidth(), rectForName.width()), 0) + st::verifiedCheckPos, (active ? st::verifiedCheckInv : st::verifiedCheck)); - } - - p.setPen(active ? st::dlgActiveColor : st::dlgNameColor); - history->peer->dialogName().drawElided(p, rectForName.left(), rectForName.top(), rectForName.width()); - + styleSheets.clear(); } } // namespace Layout diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.h b/Telegram/SourceFiles/dialogs/dialogs_layout.h index de5d11813..fd1908625 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_layout.h +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.h @@ -33,5 +33,44 @@ public: static void paint(Painter &p, const FakeRow *row, int w, bool active, bool selected, bool onlyBackground); }; +// This will be moved somewhere outside as soon as anyone starts using that. +class StyleSheet { +public: + virtual ~StyleSheet() = 0; +}; +inline StyleSheet::~StyleSheet() = default; + +namespace internal { + +void registerStyleSheet(StyleSheet **p); + +} // namespace + +// Must be created in global scope! +template +class StyleSheetPointer { +public: + StyleSheetPointer() = default; + StyleSheetPointer(const StyleSheetPointer &other) = delete; + StyleSheetPointer &operator=(const StyleSheetPointer &other) = delete; + + void createIfNull() { + if (!_p) { + _p = new T(); + internal::registerStyleSheet(&_p); + } + } + T *operator->() { + t_assert(_p != nullptr); + return static_cast(_p); + } + +private: + StyleSheet *_p; + +}; + +void clearStyleSheets(); + } // namespace Layout } // namespace Dialogs diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 7395f92e5..3a2870a5a 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -287,7 +287,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { int32 iItem = (_curHistory == _history ? _curItem : 0); HistoryItem *item = block->items[iItem]; - QRect historyRect = r.intersected(QRect(0, hdrawtop, width(), r.height())); + QRect historyRect = r.intersected(QRect(0, hdrawtop, width(), r.top() + r.height())); int32 y = htop + block->y + item->y; p.save(); p.translate(0, y);