Closed version 10019009: bubbles with tails.

This commit is contained in:
John Preston 2016-11-18 19:27:47 +03:00
parent 31a66d66e2
commit d607f0768a
20 changed files with 272 additions and 117 deletions

View File

@ -79,7 +79,7 @@ linkCropLimit: 360px;
linkFont: normalFont; linkFont: normalFont;
linkOverFont: font(fsize underline); linkOverFont: font(fsize underline);
dateRadius: 10px; dateRadius: 6px;
buttonRadius: 3px; buttonRadius: 3px;
lnkText: #0f7dc7; lnkText: #0f7dc7;
@ -104,8 +104,8 @@ msgMinWidth: 190px;
msgPhotoSize: 33px; msgPhotoSize: 33px;
msgPhotoSkip: 40px; msgPhotoSkip: 40px;
msgPadding: margins(13px, 7px, 13px, 8px); msgPadding: margins(13px, 7px, 13px, 8px);
msgMargin: margins(13px, 10px, 53px, 2px); msgMargin: margins(16px, 6px, 56px, 2px);
msgMarginTopAttached: 3px; msgMarginTopAttached: 1px;
msgLnkPadding: 2px; // for media open / save links msgLnkPadding: 2px; // for media open / save links
msgBorder: #f0f0f0; msgBorder: #f0f0f0;
msgInBg: #ffffff; msgInBg: #ffffff;

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 B

View File

@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 0,10,19,8 FILEVERSION 0,10,19,9
PRODUCTVERSION 0,10,19,8 PRODUCTVERSION 0,10,19,9
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -51,10 +51,10 @@ BEGIN
BLOCK "040904b0" BLOCK "040904b0"
BEGIN BEGIN
VALUE "CompanyName", "Telegram Messenger LLP" VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileVersion", "0.10.19.8" VALUE "FileVersion", "0.10.19.9"
VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "LegalCopyright", "Copyright (C) 2014-2016"
VALUE "ProductName", "Telegram Desktop" VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "0.10.19.8" VALUE "ProductVersion", "0.10.19.9"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View File

@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 0,10,19,8 FILEVERSION 0,10,19,9
PRODUCTVERSION 0,10,19,8 PRODUCTVERSION 0,10,19,9
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -43,10 +43,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Telegram Messenger LLP" VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileDescription", "Telegram Updater" VALUE "FileDescription", "Telegram Updater"
VALUE "FileVersion", "0.10.19.8" VALUE "FileVersion", "0.10.19.9"
VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "LegalCopyright", "Copyright (C) 2014-2016"
VALUE "ProductName", "Telegram Desktop" VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "0.10.19.8" VALUE "ProductVersion", "0.10.19.9"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View File

@ -2198,6 +2198,7 @@ namespace {
int msgRadius() { int msgRadius() {
static int MsgRadius = ([]() { static int MsgRadius = ([]() {
return st::historyMessageRadius;
auto minMsgHeight = (st::msgPadding.top() + st::msgFont->height + st::msgPadding.bottom()); auto minMsgHeight = (st::msgPadding.top() + st::msgFont->height + st::msgPadding.bottom());
return minMsgHeight / 2; return minMsgHeight / 2;
})(); })();

View File

@ -22,7 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "core/utils.h" #include "core/utils.h"
#define BETA_VERSION_MACRO (10019008ULL) #define BETA_VERSION_MACRO (10019009ULL)
constexpr int AppVersion = 10020; constexpr int AppVersion = 10020;
constexpr str_const AppVersionStr = "0.10.20"; constexpr str_const AppVersionStr = "0.10.20";

View File

@ -1637,18 +1637,22 @@ HistoryItem *History::addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex,
t_assert(blockIndex >= 0); t_assert(blockIndex >= 0);
t_assert(blockIndex < blocks.size()); t_assert(blockIndex < blocks.size());
t_assert(itemIndex >= 0); 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); newItem->attachToBlock(block, itemIndex);
block->items.insert(itemIndex, newItem); block->items.insert(itemIndex, newItem);
newItem->previousItemChanged(); 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()) { 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; return newItem;
@ -1666,14 +1670,18 @@ HistoryBlock *History::finishBuildingFrontBlock() {
t_assert(isBuildingFrontBlock()); t_assert(isBuildingFrontBlock());
// Some checks if there was some message history already // Some checks if there was some message history already
HistoryBlock *block = _buildingFrontBlock->block; auto block = _buildingFrontBlock->block;
if (block && blocks.size() > 1) { if (block) {
HistoryItem *last = block->items.back(); // ... item, item, item, last ], [ first, item, item ... if (blocks.size() > 1) {
HistoryItem *first = blocks.at(1)->items.front(); 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 // we've added a new front block, so previous item for
// the old first item of a first block was changed // the old first item of a first block was changed
first->previousItemChanged(); first->previousItemChanged();
} else {
block->items.back()->nextItemChanged();
}
} }
_buildingFrontBlock = nullptr; _buildingFrontBlock = nullptr;
@ -2106,11 +2114,13 @@ void History::removeBlock(HistoryBlock *block) {
int index = block->indexInHistory(); int index = block->indexInHistory();
blocks.removeAt(index); blocks.removeAt(index);
for (int i = index, l = blocks.size(); i < l; ++i) {
blocks.at(i)->setIndexInHistory(i);
}
if (index < blocks.size()) { 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(); 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(); items.at(itemIndex)->previousItemChanged();
} else if (blockIndex + 1 < history->blocks.size()) { } else if (blockIndex + 1 < history->blocks.size()) {
history->blocks.at(blockIndex + 1)->items.front()->previousItemChanged(); 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()) { if (items.isEmpty()) {

View File

@ -412,3 +412,16 @@ mentionFg: #777777;
mentionFgOver: #707070; mentionFgOver: #707070;
mentionFgActive: #0080c0; mentionFgActive: #0080c0;
mentionFgOverActive: #0077b3; 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 }};

View File

@ -621,6 +621,9 @@ void HistoryItem::finishEditionToEmpty() {
if (auto next = nextItem()) { if (auto next = nextItem()) {
next->previousItemChanged(); next->previousItemChanged();
} }
if (auto previous = previousItem()) {
previous->nextItemChanged();
}
} }
void HistoryItem::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { void HistoryItem::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
@ -690,16 +693,22 @@ void HistoryItem::previousItemChanged() {
recountAttachToPrevious(); recountAttachToPrevious();
} }
// Called only if there is no more next item! Not always when it changes!
void HistoryItem::nextItemChanged() {
setAttachToNext(false);
}
void HistoryItem::recountAttachToPrevious() { void HistoryItem::recountAttachToPrevious() {
bool attach = false; bool attach = false;
if (!isPost() && !Has<HistoryMessageDate>() && !Has<HistoryMessageUnreadBar>()) { if (auto previous = previousItem()) {
if (auto previos = previousItem()) { if (!isPost() && !Has<HistoryMessageDate>() && !Has<HistoryMessageUnreadBar>()) {
attach = !previos->isPost() attach = !previous->isPost()
&& !previos->serviceMsg() && !previous->serviceMsg()
&& !previos->isEmpty() && !previous->isEmpty()
&& previos->from() == from() && previous->from() == from()
&& (qAbs(previos->date.secsTo(date)) < kAttachMessageToPreviousSecondsDelta); && (qAbs(previous->date.secsTo(date)) < kAttachMessageToPreviousSecondsDelta);
} }
previous->setAttachToNext(attach);
} }
if (attach && !(_flags & MTPDmessage_ClientFlag::f_attach_to_previous)) { if (attach && !(_flags & MTPDmessage_ClientFlag::f_attach_to_previous)) {
_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) { void HistoryItem::setId(MsgId newId) {
history()->changeMsgId(id, newId); history()->changeMsgId(id, newId);
id = newId; id = newId;

View File

@ -652,7 +652,9 @@ public:
virtual bool hasBubble() const { virtual bool hasBubble() const {
return false; return false;
} }
virtual void previousItemChanged();
void previousItemChanged();
void nextItemChanged();
virtual TextWithEntities selectedText(TextSelection selection) const { virtual TextWithEntities selectedText(TextSelection selection) const {
return { qsl("[-]"), EntitiesInText() }; return { qsl("[-]"), EntitiesInText() };
@ -845,6 +847,9 @@ public:
bool isAttachedToPrevious() const { bool isAttachedToPrevious() const {
return _flags & MTPDmessage_ClientFlag::f_attach_to_previous; return _flags & MTPDmessage_ClientFlag::f_attach_to_previous;
} }
bool isAttachedToNext() const {
return _flags & MTPDmessage_ClientFlag::f_attach_to_next;
}
bool displayDate() const { bool displayDate() const {
return Has<HistoryMessageDate>(); return Has<HistoryMessageDate>();
} }
@ -909,16 +914,20 @@ protected:
return nullptr; return nullptr;
} }
// this should be used only in previousItemChanged() // this should be called only from previousItemChanged()
// to add required bits to the Composer mask // to add required bits to the Composer mask
// after that always use Has<HistoryMessageDate>() // after that always use Has<HistoryMessageDate>()
void recountDisplayDate(); 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 // 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 // then the result should be cached in a client side flag MTPDmessage_ClientFlag::f_attach_to_previous
void recountAttachToPrevious(); 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 { const HistoryMessageReplyMarkup *inlineReplyMarkup() const {
return const_cast<HistoryItem*>(this)->inlineReplyMarkup(); return const_cast<HistoryItem*>(this)->inlineReplyMarkup();
} }

View File

@ -168,6 +168,9 @@ public:
bool isBubbleBottom() const { bool isBubbleBottom() const {
return (_inBubbleState == MediaInBubbleState::Bottom) || (_inBubbleState == MediaInBubbleState::None); return (_inBubbleState == MediaInBubbleState::Bottom) || (_inBubbleState == MediaInBubbleState::None);
} }
virtual bool skipBubbleTail() const {
return false;
}
// Sometimes click on media in message is overloaded by the messsage: // 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) // (for example it can open a link or a game instead of opening media)

View File

@ -172,6 +172,9 @@ public:
bool hideFromName() const override { bool hideFromName() const override {
return true; return true;
} }
bool skipBubbleTail() const override {
return isBubbleBottom();
}
bool isReadyForOpen() const override { bool isReadyForOpen() const override {
return _data->loaded(); return _data->loaded();
} }
@ -259,6 +262,9 @@ public:
bool hideFromName() const override { bool hideFromName() const override {
return true; return true;
} }
bool skipBubbleTail() const override {
return isBubbleBottom();
}
protected: protected:
float64 dataProgress() const override { float64 dataProgress() const override {
@ -484,6 +490,9 @@ public:
bool hideFromName() const override { bool hideFromName() const override {
return true; return true;
} }
bool skipBubbleTail() const override {
return isBubbleBottom();
}
bool isReadyForOpen() const override { bool isReadyForOpen() const override {
return _data->loaded(); return _data->loaded();
} }
@ -882,6 +891,10 @@ public:
return true; return true;
} }
bool skipBubbleTail() const override {
return isBubbleBottom();
}
private: private:
TextSelection toDescriptionSelection(TextSelection selection) const { TextSelection toDescriptionSelection(TextSelection selection) const {
return internal::unshiftSelection(selection, _title); return internal::unshiftSelection(selection, _title);

View File

@ -1290,12 +1290,11 @@ void HistoryMessage::draw(Painter &p, const QRect &r, TextSelection selection, u
auto mediaDisplayed = _media && _media->isDisplayed(); auto mediaDisplayed = _media && _media->isDisplayed();
auto top = marginTop(); 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 skipTail = isAttachedToNext() || (_media && _media->skipBubbleTail());
auto &sh = selected ? (outbg ? st::msgOutShadowSelected : st::msgInShadowSelected) : (outbg ? st::msgOutShadow : st::msgInShadow); auto displayTail = skipTail ? HistoryLayout::BubbleTail::None : (outbg && !Adaptive::Wide()) ? HistoryLayout::BubbleTail::Right : HistoryLayout::BubbleTail::Left;
RoundCorners cors(selected ? (outbg ? MessageOutSelectedCorners : MessageInSelectedCorners) : (outbg ? MessageOutCorners : MessageInCorners)); HistoryLayout::paintBubble(p, r, _history->width, selected, outbg, displayTail);
App::roundRect(p, r, bg, cors, &sh);
QRect trect(r.marginsAdded(-st::msgPadding)); QRect trect(r.marginsAdded(-st::msgPadding));
if (mediaDisplayed && _media->isBubbleTop()) { 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); HistoryMessage::drawInfo(p, r.x() + r.width(), r.y() + r.height(), 2 * r.x() + r.width(), selected, InfoDisplayDefault);
} }
} else if (_media) { } else if (_media) {
int32 top = marginTop(); auto top = marginTop();
p.translate(left, top); p.translate(left, top);
_media->draw(p, r.translated(-left, -top), toMediaSelection(selection), ms); _media->draw(p, r.translated(-left, -top), toMediaSelection(selection), ms);
p.translate(-left, -top); p.translate(-left, -top);
@ -1744,7 +1743,7 @@ QString HistoryMessage::notificationHeader() const {
} }
bool HistoryMessage::displayFromPhoto() const { bool HistoryMessage::displayFromPhoto() const {
return hasFromPhoto() && !isAttachedToPrevious(); return hasFromPhoto() && !isAttachedToNext();
} }
bool HistoryMessage::hasFromPhoto() const { bool HistoryMessage::hasFromPhoto() const {

View File

@ -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 } // namespace HistoryLayout

View File

@ -52,4 +52,11 @@ void paintEmpty(Painter &p, int width, int height);
void serviceColorsUpdated(); void serviceColorsUpdated();
enum class BubbleTail {
None,
Left,
Right,
};
void paintBubble(Painter &p, QRect rect, int outerWidth, bool selected, bool outbg, BubbleTail tail);
} // namespace HistoryLayout } // namespace HistoryLayout

View File

@ -175,24 +175,29 @@ void HistoryInner::repaintItem(const HistoryItem *item) {
} }
namespace { 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 // helper binary search for an item in a list that is not completely
template <typename T> // above the given top of the visible area or below the given bottom of the visible area
int binarySearchBlocksOrItems(const T &list, int bottom) { // is applied once for blocks list in a history and once for items list in the found block
int start = 0, end = list.size(); template <bool TopToBottom, typename T>
while (end - start > 1) { int binarySearchBlocksOrItems(const T &list, int edge) {
int middle = (start + end) / 2; auto start = 0, end = list.size();
if (list.at(middle)->y >= bottom) { while (end - start > 1) {
end = middle; auto middle = (start + end) / 2;
} else { auto top = list[middle]->y;
start = middle; 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) { void HistoryInner::enumerateItemsInHistory(History *history, int historytop, Method method) {
// no displayed messages in this history // no displayed messages in this history
if (historytop < 0 || history->isEmpty()) { if (historytop < 0 || history->isEmpty()) {
@ -202,43 +207,82 @@ void HistoryInner::enumerateItemsInHistory(History *history, int historytop, Met
return; return;
} }
auto searchEdge = TopToBottom ? _visibleAreaTop : _visibleAreaBottom;
// binary search for blockIndex of the first block that is not completely below the visible area // 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 // binary search for itemIndex of the first item that is not completely below the visible area
HistoryBlock *block = history->blocks.at(blockIndex); auto block = history->blocks.at(blockIndex);
int blocktop = historytop + block->y; auto blocktop = historytop + block->y;
int itemIndex = binarySearchBlocksOrItems(block->items, _visibleAreaBottom - blocktop); auto blockbottom = blocktop + block->height;
auto itemIndex = binarySearchBlocksOrItems<TopToBottom>(block->items, searchEdge - blocktop);
while (true) { while (true) {
while (itemIndex >= 0) { while (true) {
HistoryItem *item = block->items.at(itemIndex--); auto item = block->items.at(itemIndex);
int itemtop = blocktop + item->y; auto itemtop = blocktop + item->y;
int itembottom = itemtop + item->height(); auto itembottom = itemtop + item->height();
// binary search should've skipped all the items that are below the visible area // binary search should've skipped all the items that are above / below the visible area
t_assert(itemtop < _visibleAreaBottom); if (TopToBottom) {
t_assert(itembottom > _visibleAreaTop);
} else {
t_assert(itemtop < _visibleAreaBottom);
}
if (!method(item, itemtop, itembottom)) { if (!method(item, itemtop, itembottom)) {
return; return;
} }
// skip all the items that are above the visible area // skip all the items that are below / above the visible area
if (itemtop <= _visibleAreaTop) { 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; return;
} }
} }
// skip all the rest blocks that are above the visible area if (TopToBottom) {
if (blocktop <= _visibleAreaTop) { if (++blockIndex >= history->blocks.size()) {
return; return;
} }
} else {
if (--blockIndex < 0) { if (--blockIndex < 0) {
return; return;
}
}
block = history->blocks.at(blockIndex);
blocktop = historytop + block->y;
blockbottom = blocktop + block->height;
if (TopToBottom) {
itemIndex = 0;
} else { } else {
block = history->blocks.at(blockIndex);
blocktop = historytop + block->y;
itemIndex = block->items.size() - 1; itemIndex = block->items.size() - 1;
} }
} }
@ -250,47 +294,48 @@ void HistoryInner::enumerateUserpics(Method method) {
return; return;
} }
// find and remember the bottom of an attached messages pack // find and remember the top of an attached messages pack
// -1 means we didn't find an attached to previous message yet // -1 means we didn't find an attached to next message yet
int lowestAttachedItemBottom = -1; 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 // skip all service messages
auto message = item->toHistoryMessage(); auto message = item->toHistoryMessage();
if (!message) return true; if (!message) return true;
if (lowestAttachedItemBottom < 0 && message->isAttachedToPrevious()) { if (lowestAttachedItemTop < 0 && message->isAttachedToNext()) {
lowestAttachedItemBottom = itembottom - message->marginBottom(); lowestAttachedItemTop = itemtop + message->marginTop();
} }
// call method on a userpic for all messages that have it and for those who are not showing it // 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 // because of their attachment to the next message if they are bottom-most visible
if (message->displayFromPhoto() || (message->hasFromPhoto() && itemtop <= _visibleAreaTop)) { if (message->displayFromPhoto() || (message->hasFromPhoto() && itembottom >= _visibleAreaBottom)) {
if (lowestAttachedItemBottom < 0) { if (lowestAttachedItemTop < 0) {
lowestAttachedItemBottom = itembottom - message->marginBottom(); lowestAttachedItemTop = itemtop + message->marginTop();
} }
// attach userpic to the top of the visible area with the same margin as it is from the left side // attach userpic to the bottom of the visible area with the same margin as the last message
int userpicTop = qMax(itemtop + message->marginTop(), _visibleAreaTop + st::msgMargin.left()); 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 // do not let the userpic go above the attached messages pack top line
userpicTop = qMin(userpicTop, lowestAttachedItemBottom - st::msgPhotoSize); userpicBottom = qMax(userpicBottom, lowestAttachedItemTop + st::msgPhotoSize);
// call the template callback function that was passed // call the template callback function that was passed
// and return if it finished everything it needed // and return if it finished everything it needed
if (!method(message, userpicTop)) { if (!method(message, userpicBottom - st::msgPhotoSize)) {
return false; return false;
} }
} }
// forget the found bottom of the pack, search for the next one from scratch // forget the found top of the pack, search for the next one from scratch
if (!message->isAttachedToPrevious()) { if (!message->isAttachedToNext()) {
lowestAttachedItemBottom = -1; lowestAttachedItemTop = -1;
} }
return true; return true;
}; };
enumerateItems(userpicCallback); enumerateItems<EnumItemsDirection::TopToBottom>(userpicCallback);
} }
template <typename Method> template <typename Method>
@ -346,7 +391,7 @@ void HistoryInner::enumerateDates(Method method) {
return true; return true;
}; };
enumerateItems(dateCallback); enumerateItems<EnumItemsDirection::BottomToTop>(dateCallback);
} }
void HistoryInner::paintEvent(QPaintEvent *e) { void HistoryInner::paintEvent(QPaintEvent *e) {
@ -494,13 +539,13 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
if (mtop >= 0 || htop >= 0) { if (mtop >= 0 || htop >= 0) {
enumerateUserpics([&p, &r](HistoryMessage *message, int userpicTop) { enumerateUserpics([&p, &r](HistoryMessage *message, int userpicTop) {
// stop the enumeration if the userpic is above the painted rect // 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; return false;
} }
// paint the userpic if it intersects the painted rect // paint the userpic if it intersects the painted rect
if (userpicTop < r.top() + r.height()) { if (userpicTop + st::msgPhotoSize > r.top()) {
message->from()->paintUserpicLeft(p, st::msgPhotoSize, st::msgMargin.left(), userpicTop, message->history()->width); message->from()->paintUserpicLeft(p, st::msgPhotoSize, st::historyPhotoLeft, userpicTop, message->history()->width);
} }
return true; return true;
}); });
@ -1704,7 +1749,7 @@ void HistoryInner::toggleScrollDateShown() {
_scrollDateShown = !_scrollDateShown; _scrollDateShown = !_scrollDateShown;
auto from = _scrollDateShown ? 0. : 1.; auto from = _scrollDateShown ? 0. : 1.;
auto to = _scrollDateShown ? 1. : 0.; 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() { void HistoryInner::repaintScrollDateCallback() {
@ -1975,7 +2020,7 @@ void HistoryInner::onUpdateSelected() {
} }
dragState = item->getState(m.x(), m.y(), request); dragState = item->getState(m.x(), m.y(), request);
lnkhost = item; 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 (auto msg = item->toHistoryMessage()) {
if (msg->hasFromPhoto()) { if (msg->hasFromPhoto()) {
enumerateUserpics([&dragState, &lnkhost, &point](HistoryMessage *message, int userpicTop) -> bool { enumerateUserpics([&dragState, &lnkhost, &point](HistoryMessage *message, int userpicTop) -> bool {

View File

@ -278,24 +278,32 @@ private:
HistoryItem *_scrollDateLastItem = nullptr; HistoryItem *_scrollDateLastItem = nullptr;
int _scrollDateLastItemTop = 0; int _scrollDateLastItemTop = 0;
enum class EnumItemsDirection {
TopToBottom,
BottomToTop,
};
// this function finds all history items that are displayed and calls template method // 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 // method has "bool (*Method)(HistoryItem *item, int itemtop, int itembottom)" signature
// if it returns false the enumeration stops immidiately // if it returns false the enumeration stops immidiately
template <typename Method> template <bool TopToBottom, typename Method>
void enumerateItemsInHistory(History *history, int historytop, Method method); void enumerateItemsInHistory(History *history, int historytop, Method method);
template <typename Method> template <EnumItemsDirection direction, typename Method>
void enumerateItems(Method method) { void enumerateItems(Method method) {
enumerateItemsInHistory(_history, historyTop(), method); constexpr auto TopToBottom = (direction == EnumItemsDirection::TopToBottom);
if (_migrated) { if (TopToBottom && _migrated) {
enumerateItemsInHistory(_migrated, migratedTop(), method); 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 // 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 // method has "bool (*Method)(HistoryMessage *message, int userpicTop)" signature
// if it returns false the enumeration stops immidiately // if it returns false the enumeration stops immidiately

View File

@ -999,17 +999,20 @@ enum class MTPDmessage_ClientFlag : int32 {
// message is attached to previous one when displaying the history // message is attached to previous one when displaying the history
f_attach_to_previous = (1 << 25), 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 // 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 // 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 // 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 // update this when adding new client side flags
MIN_FIELD = (1 << 22), MIN_FIELD = (1 << 21),
}; };
DEFINE_MTP_CLIENT_FLAGS(MTPDmessage) DEFINE_MTP_CLIENT_FLAGS(MTPDmessage)

View File

@ -3,4 +3,4 @@ AppVersionStrMajor 0.10
AppVersionStrSmall 0.10.20 AppVersionStrSmall 0.10.20
AppVersionStr 0.10.20 AppVersionStr 0.10.20
AlphaChannel 0 AlphaChannel 0
BetaVersion 10019008 BetaVersion 10019009