mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-03-25 04:38:23 +00:00
Closed version 10019009: bubbles with tails.
This commit is contained in:
parent
31a66d66e2
commit
d607f0768a
@ -79,7 +79,7 @@ linkCropLimit: 360px;
|
||||
linkFont: normalFont;
|
||||
linkOverFont: font(fsize underline);
|
||||
|
||||
dateRadius: 10px;
|
||||
dateRadius: 6px;
|
||||
buttonRadius: 3px;
|
||||
|
||||
lnkText: #0f7dc7;
|
||||
@ -104,8 +104,8 @@ msgMinWidth: 190px;
|
||||
msgPhotoSize: 33px;
|
||||
msgPhotoSkip: 40px;
|
||||
msgPadding: margins(13px, 7px, 13px, 8px);
|
||||
msgMargin: margins(13px, 10px, 53px, 2px);
|
||||
msgMarginTopAttached: 3px;
|
||||
msgMargin: margins(16px, 6px, 56px, 2px);
|
||||
msgMarginTopAttached: 1px;
|
||||
msgLnkPadding: 2px; // for media open / save links
|
||||
msgBorder: #f0f0f0;
|
||||
msgInBg: #ffffff;
|
||||
|
BIN
Telegram/Resources/icons/bubble_tail.png
Normal file
BIN
Telegram/Resources/icons/bubble_tail.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 144 B |
BIN
Telegram/Resources/icons/bubble_tail@2x.png
Normal file
BIN
Telegram/Resources/icons/bubble_tail@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 287 B |
@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 0,10,19,8
|
||||
PRODUCTVERSION 0,10,19,8
|
||||
FILEVERSION 0,10,19,9
|
||||
PRODUCTVERSION 0,10,19,9
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@ -51,10 +51,10 @@ BEGIN
|
||||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram Messenger LLP"
|
||||
VALUE "FileVersion", "0.10.19.8"
|
||||
VALUE "FileVersion", "0.10.19.9"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2016"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "0.10.19.8"
|
||||
VALUE "ProductVersion", "0.10.19.9"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 0,10,19,8
|
||||
PRODUCTVERSION 0,10,19,8
|
||||
FILEVERSION 0,10,19,9
|
||||
PRODUCTVERSION 0,10,19,9
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@ -43,10 +43,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram Messenger LLP"
|
||||
VALUE "FileDescription", "Telegram Updater"
|
||||
VALUE "FileVersion", "0.10.19.8"
|
||||
VALUE "FileVersion", "0.10.19.9"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2016"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "0.10.19.8"
|
||||
VALUE "ProductVersion", "0.10.19.9"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
@ -2198,6 +2198,7 @@ namespace {
|
||||
|
||||
int msgRadius() {
|
||||
static int MsgRadius = ([]() {
|
||||
return st::historyMessageRadius;
|
||||
auto minMsgHeight = (st::msgPadding.top() + st::msgFont->height + st::msgPadding.bottom());
|
||||
return minMsgHeight / 2;
|
||||
})();
|
||||
|
@ -22,7 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
|
||||
#include "core/utils.h"
|
||||
|
||||
#define BETA_VERSION_MACRO (10019008ULL)
|
||||
#define BETA_VERSION_MACRO (10019009ULL)
|
||||
|
||||
constexpr int AppVersion = 10020;
|
||||
constexpr str_const AppVersionStr = "0.10.20";
|
||||
|
@ -1637,18 +1637,22 @@ HistoryItem *History::addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex,
|
||||
t_assert(blockIndex >= 0);
|
||||
t_assert(blockIndex < blocks.size());
|
||||
t_assert(itemIndex >= 0);
|
||||
t_assert(itemIndex <= blocks.at(blockIndex)->items.size());
|
||||
t_assert(itemIndex <= blocks[blockIndex]->items.size());
|
||||
|
||||
HistoryBlock *block = blocks.at(blockIndex);
|
||||
auto block = blocks.at(blockIndex);
|
||||
|
||||
newItem->attachToBlock(block, itemIndex);
|
||||
block->items.insert(itemIndex, newItem);
|
||||
newItem->previousItemChanged();
|
||||
for (int i = itemIndex + 1, l = block->items.size(); i < l; ++i) {
|
||||
block->items.at(i)->setIndexInBlock(i);
|
||||
}
|
||||
if (itemIndex + 1 < block->items.size()) {
|
||||
block->items.at(itemIndex + 1)->previousItemChanged();
|
||||
for (int i = itemIndex + 1, l = block->items.size(); i < l; ++i) {
|
||||
block->items[i]->setIndexInBlock(i);
|
||||
}
|
||||
block->items[itemIndex + 1]->previousItemChanged();
|
||||
} else if (blockIndex + 1 < blocks.size() && !blocks[blockIndex + 1]->items.empty()) {
|
||||
blocks[blockIndex + 1]->items.front()->previousItemChanged();
|
||||
} else {
|
||||
newItem->nextItemChanged();
|
||||
}
|
||||
|
||||
return newItem;
|
||||
@ -1666,14 +1670,18 @@ HistoryBlock *History::finishBuildingFrontBlock() {
|
||||
t_assert(isBuildingFrontBlock());
|
||||
|
||||
// Some checks if there was some message history already
|
||||
HistoryBlock *block = _buildingFrontBlock->block;
|
||||
if (block && blocks.size() > 1) {
|
||||
HistoryItem *last = block->items.back(); // ... item, item, item, last ], [ first, item, item ...
|
||||
HistoryItem *first = blocks.at(1)->items.front();
|
||||
auto block = _buildingFrontBlock->block;
|
||||
if (block) {
|
||||
if (blocks.size() > 1) {
|
||||
auto last = block->items.back(); // ... item, item, item, last ], [ first, item, item ...
|
||||
auto first = blocks.at(1)->items.front();
|
||||
|
||||
// we've added a new front block, so previous item for
|
||||
// the old first item of a first block was changed
|
||||
first->previousItemChanged();
|
||||
// we've added a new front block, so previous item for
|
||||
// the old first item of a first block was changed
|
||||
first->previousItemChanged();
|
||||
} else {
|
||||
block->items.back()->nextItemChanged();
|
||||
}
|
||||
}
|
||||
|
||||
_buildingFrontBlock = nullptr;
|
||||
@ -2106,11 +2114,13 @@ void History::removeBlock(HistoryBlock *block) {
|
||||
|
||||
int index = block->indexInHistory();
|
||||
blocks.removeAt(index);
|
||||
for (int i = index, l = blocks.size(); i < l; ++i) {
|
||||
blocks.at(i)->setIndexInHistory(i);
|
||||
}
|
||||
if (index < blocks.size()) {
|
||||
for (int i = index, l = blocks.size(); i < l; ++i) {
|
||||
blocks.at(i)->setIndexInHistory(i);
|
||||
}
|
||||
blocks.at(index)->items.front()->previousItemChanged();
|
||||
} else if (!blocks.empty() && !blocks.back()->items.empty()) {
|
||||
blocks.back()->items.back()->nextItemChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@ -2176,6 +2186,8 @@ void HistoryBlock::removeItem(HistoryItem *item) {
|
||||
items.at(itemIndex)->previousItemChanged();
|
||||
} else if (blockIndex + 1 < history->blocks.size()) {
|
||||
history->blocks.at(blockIndex + 1)->items.front()->previousItemChanged();
|
||||
} else if (!history->blocks.empty() && !history->blocks.back()->items.empty()) {
|
||||
history->blocks.back()->items.back()->nextItemChanged();
|
||||
}
|
||||
|
||||
if (items.isEmpty()) {
|
||||
|
@ -412,3 +412,16 @@ mentionFg: #777777;
|
||||
mentionFgOver: #707070;
|
||||
mentionFgActive: #0080c0;
|
||||
mentionFgOverActive: #0077b3;
|
||||
|
||||
historyDateFadeDuration: 200;
|
||||
|
||||
historyPhotoLeft: 14px;
|
||||
historyMessageRadius: 6px;
|
||||
historyBubbleTailInLeft: icon {{ "bubble_tail", msgInBg }};
|
||||
historyBubbleTailInLeftSelected: icon {{ "bubble_tail", msgInBgSelected }};
|
||||
historyBubbleTailOutLeft: icon {{ "bubble_tail", msgOutBg }};
|
||||
historyBubbleTailOutLeftSelected: icon {{ "bubble_tail", msgOutBgSelected }};
|
||||
historyBubbleTailInRight: icon {{ "bubble_tail-flip_horizontal", msgInBg }};
|
||||
historyBubbleTailInRightSelected: icon {{ "bubble_tail-flip_horizontal", msgInBgSelected }};
|
||||
historyBubbleTailOutRight: icon {{ "bubble_tail-flip_horizontal", msgOutBg }};
|
||||
historyBubbleTailOutRightSelected: icon {{ "bubble_tail-flip_horizontal", msgOutBgSelected }};
|
||||
|
@ -621,6 +621,9 @@ void HistoryItem::finishEditionToEmpty() {
|
||||
if (auto next = nextItem()) {
|
||||
next->previousItemChanged();
|
||||
}
|
||||
if (auto previous = previousItem()) {
|
||||
previous->nextItemChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryItem::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
|
||||
@ -690,16 +693,22 @@ void HistoryItem::previousItemChanged() {
|
||||
recountAttachToPrevious();
|
||||
}
|
||||
|
||||
// Called only if there is no more next item! Not always when it changes!
|
||||
void HistoryItem::nextItemChanged() {
|
||||
setAttachToNext(false);
|
||||
}
|
||||
|
||||
void HistoryItem::recountAttachToPrevious() {
|
||||
bool attach = false;
|
||||
if (!isPost() && !Has<HistoryMessageDate>() && !Has<HistoryMessageUnreadBar>()) {
|
||||
if (auto previos = previousItem()) {
|
||||
attach = !previos->isPost()
|
||||
&& !previos->serviceMsg()
|
||||
&& !previos->isEmpty()
|
||||
&& previos->from() == from()
|
||||
&& (qAbs(previos->date.secsTo(date)) < kAttachMessageToPreviousSecondsDelta);
|
||||
if (auto previous = previousItem()) {
|
||||
if (!isPost() && !Has<HistoryMessageDate>() && !Has<HistoryMessageUnreadBar>()) {
|
||||
attach = !previous->isPost()
|
||||
&& !previous->serviceMsg()
|
||||
&& !previous->isEmpty()
|
||||
&& previous->from() == from()
|
||||
&& (qAbs(previous->date.secsTo(date)) < kAttachMessageToPreviousSecondsDelta);
|
||||
}
|
||||
previous->setAttachToNext(attach);
|
||||
}
|
||||
if (attach && !(_flags & MTPDmessage_ClientFlag::f_attach_to_previous)) {
|
||||
_flags |= MTPDmessage_ClientFlag::f_attach_to_previous;
|
||||
@ -710,6 +719,16 @@ void HistoryItem::recountAttachToPrevious() {
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryItem::setAttachToNext(bool attachToNext) {
|
||||
if (attachToNext && !(_flags & MTPDmessage_ClientFlag::f_attach_to_next)) {
|
||||
_flags |= MTPDmessage_ClientFlag::f_attach_to_next;
|
||||
Global::RefPendingRepaintItems().insert(this);
|
||||
} else if (!attachToNext && (_flags & MTPDmessage_ClientFlag::f_attach_to_next)) {
|
||||
_flags &= ~MTPDmessage_ClientFlag::f_attach_to_next;
|
||||
Global::RefPendingRepaintItems().insert(this);
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryItem::setId(MsgId newId) {
|
||||
history()->changeMsgId(id, newId);
|
||||
id = newId;
|
||||
|
@ -652,7 +652,9 @@ public:
|
||||
virtual bool hasBubble() const {
|
||||
return false;
|
||||
}
|
||||
virtual void previousItemChanged();
|
||||
|
||||
void previousItemChanged();
|
||||
void nextItemChanged();
|
||||
|
||||
virtual TextWithEntities selectedText(TextSelection selection) const {
|
||||
return { qsl("[-]"), EntitiesInText() };
|
||||
@ -845,6 +847,9 @@ public:
|
||||
bool isAttachedToPrevious() const {
|
||||
return _flags & MTPDmessage_ClientFlag::f_attach_to_previous;
|
||||
}
|
||||
bool isAttachedToNext() const {
|
||||
return _flags & MTPDmessage_ClientFlag::f_attach_to_next;
|
||||
}
|
||||
bool displayDate() const {
|
||||
return Has<HistoryMessageDate>();
|
||||
}
|
||||
@ -909,16 +914,20 @@ protected:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// this should be used only in previousItemChanged()
|
||||
// this should be called only from previousItemChanged()
|
||||
// to add required bits to the Composer mask
|
||||
// after that always use Has<HistoryMessageDate>()
|
||||
void recountDisplayDate();
|
||||
|
||||
// this should be used only in previousItemChanged() or when
|
||||
// this should be called only from previousItemChanged() or when
|
||||
// HistoryMessageDate or HistoryMessageUnreadBar bit is changed in the Composer mask
|
||||
// then the result should be cached in a client side flag MTPDmessage_ClientFlag::f_attach_to_previous
|
||||
void recountAttachToPrevious();
|
||||
|
||||
// this should be called only recountAttachToPrevious() of the next item
|
||||
// or when the next item is removed through nextItemChanged() call
|
||||
void setAttachToNext(bool attachToNext);
|
||||
|
||||
const HistoryMessageReplyMarkup *inlineReplyMarkup() const {
|
||||
return const_cast<HistoryItem*>(this)->inlineReplyMarkup();
|
||||
}
|
||||
|
@ -168,6 +168,9 @@ public:
|
||||
bool isBubbleBottom() const {
|
||||
return (_inBubbleState == MediaInBubbleState::Bottom) || (_inBubbleState == MediaInBubbleState::None);
|
||||
}
|
||||
virtual bool skipBubbleTail() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sometimes click on media in message is overloaded by the messsage:
|
||||
// (for example it can open a link or a game instead of opening media)
|
||||
|
@ -172,6 +172,9 @@ public:
|
||||
bool hideFromName() const override {
|
||||
return true;
|
||||
}
|
||||
bool skipBubbleTail() const override {
|
||||
return isBubbleBottom();
|
||||
}
|
||||
bool isReadyForOpen() const override {
|
||||
return _data->loaded();
|
||||
}
|
||||
@ -259,6 +262,9 @@ public:
|
||||
bool hideFromName() const override {
|
||||
return true;
|
||||
}
|
||||
bool skipBubbleTail() const override {
|
||||
return isBubbleBottom();
|
||||
}
|
||||
|
||||
protected:
|
||||
float64 dataProgress() const override {
|
||||
@ -484,6 +490,9 @@ public:
|
||||
bool hideFromName() const override {
|
||||
return true;
|
||||
}
|
||||
bool skipBubbleTail() const override {
|
||||
return isBubbleBottom();
|
||||
}
|
||||
bool isReadyForOpen() const override {
|
||||
return _data->loaded();
|
||||
}
|
||||
@ -882,6 +891,10 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool skipBubbleTail() const override {
|
||||
return isBubbleBottom();
|
||||
}
|
||||
|
||||
private:
|
||||
TextSelection toDescriptionSelection(TextSelection selection) const {
|
||||
return internal::unshiftSelection(selection, _title);
|
||||
|
@ -1290,12 +1290,11 @@ void HistoryMessage::draw(Painter &p, const QRect &r, TextSelection selection, u
|
||||
|
||||
auto mediaDisplayed = _media && _media->isDisplayed();
|
||||
auto top = marginTop();
|
||||
QRect r(left, top, width, height - top - marginBottom());
|
||||
auto r = QRect(left, top, width, height - top - marginBottom());
|
||||
|
||||
auto &bg = selected ? (outbg ? st::msgOutBgSelected : st::msgInBgSelected) : (outbg ? st::msgOutBg : st::msgInBg);
|
||||
auto &sh = selected ? (outbg ? st::msgOutShadowSelected : st::msgInShadowSelected) : (outbg ? st::msgOutShadow : st::msgInShadow);
|
||||
RoundCorners cors(selected ? (outbg ? MessageOutSelectedCorners : MessageInSelectedCorners) : (outbg ? MessageOutCorners : MessageInCorners));
|
||||
App::roundRect(p, r, bg, cors, &sh);
|
||||
auto skipTail = isAttachedToNext() || (_media && _media->skipBubbleTail());
|
||||
auto displayTail = skipTail ? HistoryLayout::BubbleTail::None : (outbg && !Adaptive::Wide()) ? HistoryLayout::BubbleTail::Right : HistoryLayout::BubbleTail::Left;
|
||||
HistoryLayout::paintBubble(p, r, _history->width, selected, outbg, displayTail);
|
||||
|
||||
QRect trect(r.marginsAdded(-st::msgPadding));
|
||||
if (mediaDisplayed && _media->isBubbleTop()) {
|
||||
@ -1335,7 +1334,7 @@ void HistoryMessage::draw(Painter &p, const QRect &r, TextSelection selection, u
|
||||
HistoryMessage::drawInfo(p, r.x() + r.width(), r.y() + r.height(), 2 * r.x() + r.width(), selected, InfoDisplayDefault);
|
||||
}
|
||||
} else if (_media) {
|
||||
int32 top = marginTop();
|
||||
auto top = marginTop();
|
||||
p.translate(left, top);
|
||||
_media->draw(p, r.translated(-left, -top), toMediaSelection(selection), ms);
|
||||
p.translate(-left, -top);
|
||||
@ -1744,7 +1743,7 @@ QString HistoryMessage::notificationHeader() const {
|
||||
}
|
||||
|
||||
bool HistoryMessage::displayFromPhoto() const {
|
||||
return hasFromPhoto() && !isAttachedToPrevious();
|
||||
return hasFromPhoto() && !isAttachedToNext();
|
||||
}
|
||||
|
||||
bool HistoryMessage::hasFromPhoto() const {
|
||||
|
@ -352,4 +352,27 @@ void serviceColorsUpdated() {
|
||||
}
|
||||
}
|
||||
|
||||
void paintBubble(Painter &p, QRect rect, int outerWidth, bool selected, bool outbg, BubbleTail tail) {
|
||||
auto &bg = selected ? (outbg ? st::msgOutBgSelected : st::msgInBgSelected) : (outbg ? st::msgOutBg : st::msgInBg);
|
||||
auto &sh = selected ? (outbg ? st::msgOutShadowSelected : st::msgInShadowSelected) : (outbg ? st::msgOutShadow : st::msgInShadow);
|
||||
auto cors = selected ? (outbg ? MessageOutSelectedCorners : MessageInSelectedCorners) : (outbg ? MessageOutCorners : MessageInCorners);
|
||||
auto parts = App::RectPart::TopFull | App::RectPart::NoTopBottom | App::RectPart::Bottom;
|
||||
if (tail == BubbleTail::Right) {
|
||||
parts |= App::RectPart::BottomLeft;
|
||||
p.fillRect(rect.x() + rect.width() - st::historyMessageRadius, rect.y() + rect.height() - st::historyMessageRadius, st::historyMessageRadius, st::historyMessageRadius, bg);
|
||||
auto &tail = selected ? st::historyBubbleTailOutRightSelected : st::historyBubbleTailOutRight;
|
||||
tail.paint(p, rect.x() + rect.width(), rect.y() + rect.height() - tail.height(), outerWidth);
|
||||
p.fillRect(rect.x() + rect.width() - st::historyMessageRadius, rect.y() + rect.height(), st::historyMessageRadius + tail.width(), st::msgShadow, sh);
|
||||
} else if (tail == BubbleTail::Left) {
|
||||
parts |= App::RectPart::BottomRight;
|
||||
p.fillRect(rect.x(), rect.y() + rect.height() - st::historyMessageRadius, st::historyMessageRadius, st::historyMessageRadius, bg);
|
||||
auto &tail = selected ? (outbg ? st::historyBubbleTailOutLeftSelected : st::historyBubbleTailInLeftSelected) : (outbg ? st::historyBubbleTailOutLeft : st::historyBubbleTailInLeft);
|
||||
tail.paint(p, rect.x() - tail.width(), rect.y() + rect.height() - tail.height(), outerWidth);
|
||||
p.fillRect(rect.x() - tail.width(), rect.y() + rect.height(), st::historyMessageRadius + tail.width(), st::msgShadow, sh);
|
||||
} else {
|
||||
parts |= App::RectPart::BottomFull;
|
||||
}
|
||||
App::roundRect(p, rect, bg, cors, &sh, parts);
|
||||
}
|
||||
|
||||
} // namespace HistoryLayout
|
||||
|
@ -52,4 +52,11 @@ void paintEmpty(Painter &p, int width, int height);
|
||||
|
||||
void serviceColorsUpdated();
|
||||
|
||||
enum class BubbleTail {
|
||||
None,
|
||||
Left,
|
||||
Right,
|
||||
};
|
||||
void paintBubble(Painter &p, QRect rect, int outerWidth, bool selected, bool outbg, BubbleTail tail);
|
||||
|
||||
} // namespace HistoryLayout
|
||||
|
@ -175,24 +175,29 @@ void HistoryInner::repaintItem(const HistoryItem *item) {
|
||||
}
|
||||
|
||||
namespace {
|
||||
// helper binary search for an item in a list that is not completely below the given bottom of the visible area
|
||||
// is applied once for blocks list in a history and once for items list in the found block
|
||||
template <typename T>
|
||||
int binarySearchBlocksOrItems(const T &list, int bottom) {
|
||||
int start = 0, end = list.size();
|
||||
while (end - start > 1) {
|
||||
int middle = (start + end) / 2;
|
||||
if (list.at(middle)->y >= bottom) {
|
||||
end = middle;
|
||||
} else {
|
||||
start = middle;
|
||||
}
|
||||
|
||||
// helper binary search for an item in a list that is not completely
|
||||
// above the given top of the visible area or below the given bottom of the visible area
|
||||
// is applied once for blocks list in a history and once for items list in the found block
|
||||
template <bool TopToBottom, typename T>
|
||||
int binarySearchBlocksOrItems(const T &list, int edge) {
|
||||
auto start = 0, end = list.size();
|
||||
while (end - start > 1) {
|
||||
auto middle = (start + end) / 2;
|
||||
auto top = list[middle]->y;
|
||||
auto chooseLeft = (TopToBottom ? (top <= edge) : (top < edge));
|
||||
if (chooseLeft) {
|
||||
start = middle;
|
||||
} else {
|
||||
end = middle;
|
||||
}
|
||||
return start;
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
template <typename Method>
|
||||
} // namespace
|
||||
|
||||
template <bool TopToBottom, typename Method>
|
||||
void HistoryInner::enumerateItemsInHistory(History *history, int historytop, Method method) {
|
||||
// no displayed messages in this history
|
||||
if (historytop < 0 || history->isEmpty()) {
|
||||
@ -202,43 +207,82 @@ void HistoryInner::enumerateItemsInHistory(History *history, int historytop, Met
|
||||
return;
|
||||
}
|
||||
|
||||
auto searchEdge = TopToBottom ? _visibleAreaTop : _visibleAreaBottom;
|
||||
|
||||
// binary search for blockIndex of the first block that is not completely below the visible area
|
||||
int blockIndex = binarySearchBlocksOrItems(history->blocks, _visibleAreaBottom - historytop);
|
||||
auto blockIndex = binarySearchBlocksOrItems<TopToBottom>(history->blocks, searchEdge - historytop);
|
||||
|
||||
// binary search for itemIndex of the first item that is not completely below the visible area
|
||||
HistoryBlock *block = history->blocks.at(blockIndex);
|
||||
int blocktop = historytop + block->y;
|
||||
int itemIndex = binarySearchBlocksOrItems(block->items, _visibleAreaBottom - blocktop);
|
||||
auto block = history->blocks.at(blockIndex);
|
||||
auto blocktop = historytop + block->y;
|
||||
auto blockbottom = blocktop + block->height;
|
||||
auto itemIndex = binarySearchBlocksOrItems<TopToBottom>(block->items, searchEdge - blocktop);
|
||||
|
||||
while (true) {
|
||||
while (itemIndex >= 0) {
|
||||
HistoryItem *item = block->items.at(itemIndex--);
|
||||
int itemtop = blocktop + item->y;
|
||||
int itembottom = itemtop + item->height();
|
||||
while (true) {
|
||||
auto item = block->items.at(itemIndex);
|
||||
auto itemtop = blocktop + item->y;
|
||||
auto itembottom = itemtop + item->height();
|
||||
|
||||
// binary search should've skipped all the items that are below the visible area
|
||||
t_assert(itemtop < _visibleAreaBottom);
|
||||
// binary search should've skipped all the items that are above / below the visible area
|
||||
if (TopToBottom) {
|
||||
t_assert(itembottom > _visibleAreaTop);
|
||||
} else {
|
||||
t_assert(itemtop < _visibleAreaBottom);
|
||||
}
|
||||
|
||||
if (!method(item, itemtop, itembottom)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// skip all the items that are above the visible area
|
||||
if (itemtop <= _visibleAreaTop) {
|
||||
// skip all the items that are below / above the visible area
|
||||
if (TopToBottom) {
|
||||
if (itembottom >= _visibleAreaBottom) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (itemtop <= _visibleAreaTop) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (TopToBottom) {
|
||||
if (++itemIndex >= block->items.size()) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (--itemIndex < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// skip all the rest blocks that are below / above the visible area
|
||||
if (TopToBottom) {
|
||||
if (blockbottom >= _visibleAreaBottom) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (blocktop <= _visibleAreaTop) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// skip all the rest blocks that are above the visible area
|
||||
if (blocktop <= _visibleAreaTop) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (--blockIndex < 0) {
|
||||
return;
|
||||
if (TopToBottom) {
|
||||
if (++blockIndex >= history->blocks.size()) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (--blockIndex < 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
block = history->blocks.at(blockIndex);
|
||||
blocktop = historytop + block->y;
|
||||
blockbottom = blocktop + block->height;
|
||||
if (TopToBottom) {
|
||||
itemIndex = 0;
|
||||
} else {
|
||||
block = history->blocks.at(blockIndex);
|
||||
blocktop = historytop + block->y;
|
||||
itemIndex = block->items.size() - 1;
|
||||
}
|
||||
}
|
||||
@ -250,47 +294,48 @@ void HistoryInner::enumerateUserpics(Method method) {
|
||||
return;
|
||||
}
|
||||
|
||||
// find and remember the bottom of an attached messages pack
|
||||
// -1 means we didn't find an attached to previous message yet
|
||||
int lowestAttachedItemBottom = -1;
|
||||
// find and remember the top of an attached messages pack
|
||||
// -1 means we didn't find an attached to next message yet
|
||||
int lowestAttachedItemTop = -1;
|
||||
|
||||
auto userpicCallback = [this, &lowestAttachedItemBottom, &method](HistoryItem *item, int itemtop, int itembottom) {
|
||||
auto userpicCallback = [this, &lowestAttachedItemTop, &method](HistoryItem *item, int itemtop, int itembottom) {
|
||||
// skip all service messages
|
||||
auto message = item->toHistoryMessage();
|
||||
if (!message) return true;
|
||||
|
||||
if (lowestAttachedItemBottom < 0 && message->isAttachedToPrevious()) {
|
||||
lowestAttachedItemBottom = itembottom - message->marginBottom();
|
||||
if (lowestAttachedItemTop < 0 && message->isAttachedToNext()) {
|
||||
lowestAttachedItemTop = itemtop + message->marginTop();
|
||||
}
|
||||
|
||||
// call method on a userpic for all messages that have it and for those who are not showing it
|
||||
// because of their attachment to the previous message if they are top-most visible
|
||||
if (message->displayFromPhoto() || (message->hasFromPhoto() && itemtop <= _visibleAreaTop)) {
|
||||
if (lowestAttachedItemBottom < 0) {
|
||||
lowestAttachedItemBottom = itembottom - message->marginBottom();
|
||||
// because of their attachment to the next message if they are bottom-most visible
|
||||
if (message->displayFromPhoto() || (message->hasFromPhoto() && itembottom >= _visibleAreaBottom)) {
|
||||
if (lowestAttachedItemTop < 0) {
|
||||
lowestAttachedItemTop = itemtop + message->marginTop();
|
||||
}
|
||||
// attach userpic to the top of the visible area with the same margin as it is from the left side
|
||||
int userpicTop = qMax(itemtop + message->marginTop(), _visibleAreaTop + st::msgMargin.left());
|
||||
// attach userpic to the bottom of the visible area with the same margin as the last message
|
||||
auto userpicMinBottomSkip = st::historyPaddingBottom + st::msgMargin.bottom();
|
||||
auto userpicBottom = qMin(itembottom - message->marginBottom(), _visibleAreaBottom - userpicMinBottomSkip);
|
||||
|
||||
// do not let the userpic go below the attached messages pack bottom line
|
||||
userpicTop = qMin(userpicTop, lowestAttachedItemBottom - st::msgPhotoSize);
|
||||
// do not let the userpic go above the attached messages pack top line
|
||||
userpicBottom = qMax(userpicBottom, lowestAttachedItemTop + st::msgPhotoSize);
|
||||
|
||||
// call the template callback function that was passed
|
||||
// and return if it finished everything it needed
|
||||
if (!method(message, userpicTop)) {
|
||||
if (!method(message, userpicBottom - st::msgPhotoSize)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// forget the found bottom of the pack, search for the next one from scratch
|
||||
if (!message->isAttachedToPrevious()) {
|
||||
lowestAttachedItemBottom = -1;
|
||||
// forget the found top of the pack, search for the next one from scratch
|
||||
if (!message->isAttachedToNext()) {
|
||||
lowestAttachedItemTop = -1;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
enumerateItems(userpicCallback);
|
||||
enumerateItems<EnumItemsDirection::TopToBottom>(userpicCallback);
|
||||
}
|
||||
|
||||
template <typename Method>
|
||||
@ -346,7 +391,7 @@ void HistoryInner::enumerateDates(Method method) {
|
||||
return true;
|
||||
};
|
||||
|
||||
enumerateItems(dateCallback);
|
||||
enumerateItems<EnumItemsDirection::BottomToTop>(dateCallback);
|
||||
}
|
||||
|
||||
void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||
@ -494,13 +539,13 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||
if (mtop >= 0 || htop >= 0) {
|
||||
enumerateUserpics([&p, &r](HistoryMessage *message, int userpicTop) {
|
||||
// stop the enumeration if the userpic is above the painted rect
|
||||
if (userpicTop + st::msgPhotoSize <= r.top()) {
|
||||
if (userpicTop >= r.top() + r.height()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// paint the userpic if it intersects the painted rect
|
||||
if (userpicTop < r.top() + r.height()) {
|
||||
message->from()->paintUserpicLeft(p, st::msgPhotoSize, st::msgMargin.left(), userpicTop, message->history()->width);
|
||||
if (userpicTop + st::msgPhotoSize > r.top()) {
|
||||
message->from()->paintUserpicLeft(p, st::msgPhotoSize, st::historyPhotoLeft, userpicTop, message->history()->width);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@ -1704,7 +1749,7 @@ void HistoryInner::toggleScrollDateShown() {
|
||||
_scrollDateShown = !_scrollDateShown;
|
||||
auto from = _scrollDateShown ? 0. : 1.;
|
||||
auto to = _scrollDateShown ? 1. : 0.;
|
||||
_scrollDateOpacity.start([this] { repaintScrollDateCallback(); }, from, to, st::historyAttach.duration);
|
||||
_scrollDateOpacity.start([this] { repaintScrollDateCallback(); }, from, to, st::historyDateFadeDuration);
|
||||
}
|
||||
|
||||
void HistoryInner::repaintScrollDateCallback() {
|
||||
@ -1975,7 +2020,7 @@ void HistoryInner::onUpdateSelected() {
|
||||
}
|
||||
dragState = item->getState(m.x(), m.y(), request);
|
||||
lnkhost = item;
|
||||
if (!dragState.link && m.x() >= st::msgMargin.left() && m.x() < st::msgMargin.left() + st::msgPhotoSize) {
|
||||
if (!dragState.link && m.x() >= st::historyPhotoLeft && m.x() < st::historyPhotoLeft + st::msgPhotoSize) {
|
||||
if (auto msg = item->toHistoryMessage()) {
|
||||
if (msg->hasFromPhoto()) {
|
||||
enumerateUserpics([&dragState, &lnkhost, &point](HistoryMessage *message, int userpicTop) -> bool {
|
||||
|
@ -278,24 +278,32 @@ private:
|
||||
HistoryItem *_scrollDateLastItem = nullptr;
|
||||
int _scrollDateLastItemTop = 0;
|
||||
|
||||
enum class EnumItemsDirection {
|
||||
TopToBottom,
|
||||
BottomToTop,
|
||||
};
|
||||
// this function finds all history items that are displayed and calls template method
|
||||
// for each found message (from the bottom to the top) in the passed history with passed top offset
|
||||
// for each found message (in given direction) in the passed history with passed top offset
|
||||
//
|
||||
// method has "bool (*Method)(HistoryItem *item, int itemtop, int itembottom)" signature
|
||||
// if it returns false the enumeration stops immidiately
|
||||
template <typename Method>
|
||||
template <bool TopToBottom, typename Method>
|
||||
void enumerateItemsInHistory(History *history, int historytop, Method method);
|
||||
|
||||
template <typename Method>
|
||||
template <EnumItemsDirection direction, typename Method>
|
||||
void enumerateItems(Method method) {
|
||||
enumerateItemsInHistory(_history, historyTop(), method);
|
||||
if (_migrated) {
|
||||
enumerateItemsInHistory(_migrated, migratedTop(), method);
|
||||
constexpr auto TopToBottom = (direction == EnumItemsDirection::TopToBottom);
|
||||
if (TopToBottom && _migrated) {
|
||||
enumerateItemsInHistory<TopToBottom>(_migrated, migratedTop(), method);
|
||||
}
|
||||
enumerateItemsInHistory<TopToBottom>(_history, historyTop(), method);
|
||||
if (!TopToBottom && _migrated) {
|
||||
enumerateItemsInHistory<TopToBottom>(_migrated, migratedTop(), method);
|
||||
}
|
||||
}
|
||||
|
||||
// this function finds all userpics on the left that are displayed and calls template method
|
||||
// for each found userpic (from the bottom to the top) using enumerateItems() method
|
||||
// for each found userpic (from the top to the bottom) using enumerateItems() method
|
||||
//
|
||||
// method has "bool (*Method)(HistoryMessage *message, int userpicTop)" signature
|
||||
// if it returns false the enumeration stops immidiately
|
||||
|
@ -999,17 +999,20 @@ enum class MTPDmessage_ClientFlag : int32 {
|
||||
// message is attached to previous one when displaying the history
|
||||
f_attach_to_previous = (1 << 25),
|
||||
|
||||
// message is attached to next one when displaying the history
|
||||
f_attach_to_next = (1 << 24),
|
||||
|
||||
// message was sent from inline bot, need to re-set media when sent
|
||||
f_from_inline_bot = (1 << 24),
|
||||
f_from_inline_bot = (1 << 23),
|
||||
|
||||
// message has a switch inline keyboard button, need to return to inline
|
||||
f_has_switch_inline_button = (1 << 23),
|
||||
f_has_switch_inline_button = (1 << 22),
|
||||
|
||||
// message is generated on the client side and should be unread
|
||||
f_clientside_unread = (1 << 22),
|
||||
f_clientside_unread = (1 << 21),
|
||||
|
||||
// update this when adding new client side flags
|
||||
MIN_FIELD = (1 << 22),
|
||||
MIN_FIELD = (1 << 21),
|
||||
};
|
||||
DEFINE_MTP_CLIENT_FLAGS(MTPDmessage)
|
||||
|
||||
|
@ -3,4 +3,4 @@ AppVersionStrMajor 0.10
|
||||
AppVersionStrSmall 0.10.20
|
||||
AppVersionStr 0.10.20
|
||||
AlphaChannel 0
|
||||
BetaVersion 10019008
|
||||
BetaVersion 10019009
|
||||
|
Loading…
Reference in New Issue
Block a user