Compare commits

..

31 Commits

Author SHA1 Message Date
John Preston
2b04653f24 Beta version 2.9.7.
- Still debugging a video playback crash in 32 bit Windows version.
2021-08-23 12:21:09 +03:00
John Preston
868015da25 Add extensive video playback crash logs. 2021-08-23 12:17:40 +03:00
John Preston
7aeffa242e Beta version 2.9.6.
- Debugging a video playback crash in 32 bit Windows version.
2021-08-21 09:34:26 +03:00
John Preston
3136c0586e Add some more assertions to debug a crash. 2021-08-21 09:33:20 +03:00
John Preston
fb0fcbca7f Remove redundant -static-libstdc++ for Updater. 2021-08-20 21:14:39 +03:00
John Preston
0d449c037b Beta version 2.9.5: Fix hide workaround for media viewer. 2021-08-20 19:56:40 +03:00
John Preston
4d98230694 Beta version 2.9.5.
- Tile chat background patterns horizontally.
- Fix a rare crash in spellchecker on Windows.
- Fix animated chat backgrounds in Saved Messages.
- Fix "Sorry, group is inaccessible" message in scheduled voice chats.
2021-08-20 18:42:13 +03:00
John Preston
3d36e501a1 Add some assertions to debug a crash in video playback. 2021-08-20 18:42:13 +03:00
John Preston
b4eb9a0827 Fix single-colored patterns. 2021-08-20 16:24:26 +03:00
John Preston
aaf0015be4 Improve pattern wallpaper preview in Settings. 2021-08-20 16:14:09 +03:00
John Preston
1e8e163bb1 Tile patterns horizontally. 2021-08-20 15:55:10 +03:00
John Preston
f3f741e1eb Hide 'Tile' option in generated backgrounds. 2021-08-20 15:14:28 +03:00
John Preston
44f52ca6cd Fix solid color background preview. 2021-08-20 14:54:11 +03:00
John Preston
2b6e04bca3 Apply initial bubble opacity in QImage bubble backgrounds. 2021-08-20 14:54:11 +03:00
John Preston
784d57a2bc Use QImage bubbles background for some Media parts. 2021-08-20 14:54:11 +03:00
John Preston
f4fdadd3b0 Allow arbitrary QImage as outgoing bubbles background. 2021-08-20 14:54:11 +03:00
John Preston
1cc9a52461 Fix my speaking status freeze in voice chats. 2021-08-18 17:52:23 +03:00
John Preston
cd52982752 Hopefully fix an assertion violation in voice chats. 2021-08-18 17:45:21 +03:00
John Preston
4dd58b79e9 Fix forward of a bot message with a game from a group. 2021-08-18 16:43:22 +03:00
John Preston
c5c94276c2 Allow editing caption of forwarded audio file.
Fixes https://bugs.telegram.org/c/3025
2021-08-18 16:29:05 +03:00
John Preston
8e0b9b685c Show 'X subscribers' in channel ConfirmInviteBox.
Fixes https://bugs.telegram.org/c/2059
2021-08-18 15:57:52 +03:00
John Preston
1792bed721 Remove additional padding in shared contacts. 2021-08-18 15:52:04 +03:00
John Preston
a502cbc06e Move message link up in registered shared contacts.
Fixes #16804.
2021-08-18 15:48:30 +03:00
John Preston
ddda7b8c52 Fix crash in application Dock menu on macOS. 2021-08-18 15:44:38 +03:00
John Preston
0f19ba3231 Fix spellcheck crash on Windows on certain strings. 2021-08-18 15:22:27 +03:00
John Preston
36486a3d73 Fix creating a theme from a default one.
Regression was introduced in 79cc797aff.
2021-08-18 14:52:57 +03:00
John Preston
02f48a7781 Don't allow selected items together with text. 2021-08-18 14:52:49 +03:00
John Preston
c77f4dd794 Fix build on Linux. 2021-08-18 13:06:47 +03:00
John Preston
ca31a08182 Fix 'Sorry, group is inac...' in scheduled voice chat. 2021-08-18 11:34:16 +03:00
John Preston
33936195a1 Animate the chat background in Saved Messages. 2021-08-18 11:33:37 +03:00
John Preston
67bafa02fe Remove attempt of a shadow Qt build. 2021-08-17 18:15:55 +03:00
70 changed files with 1466 additions and 539 deletions

View File

@@ -1432,10 +1432,6 @@ if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR APPLE) AND NOT build_macstore AND NOT
endif()
endif()
if (LINUX)
target_link_options(Updater PRIVATE -static-libstdc++)
endif()
if (DESKTOP_APP_SPECIAL_TARGET)
add_executable(Packer)
init_target(Packer)

View File

@@ -9,7 +9,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="2.9.4.0" />
Version="2.9.7.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View File

@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,9,4,0
PRODUCTVERSION 2,9,4,0
FILEVERSION 2,9,7,0
PRODUCTVERSION 2,9,7,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "2.9.4.0"
VALUE "FileVersion", "2.9.7.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.9.4.0"
VALUE "ProductVersion", "2.9.7.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,9,4,0
PRODUCTVERSION 2,9,4,0
FILEVERSION 2,9,7,0
PRODUCTVERSION 2,9,7,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "2.9.4.0"
VALUE "FileVersion", "2.9.7.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.9.4.0"
VALUE "ProductVersion", "2.9.7.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -112,6 +112,11 @@ ConfirmInviteBox::ConfirmInviteBox(
? tr::lng_channel_invite_private(tr::now)
: (!_participants.empty() && _participants.size() < count)
? tr::lng_group_invite_members(tr::now, lt_count, count)
: (count > 0 && _isChannel)
? tr::lng_chat_status_subscribers(
tr::now,
lt_count_decimal,
count)
: (count > 0)
? tr::lng_chat_status_members(tr::now, lt_count_decimal, count)
: _isChannel

View File

@@ -589,11 +589,18 @@ QRect BackgroundPreviewBox::radialRect() const {
void BackgroundPreviewBox::paintTexts(Painter &p, crl::time ms) {
const auto height1 = _text1->height();
const auto height2 = _text2->height();
const auto context = HistoryView::PaintContext{
.bubblesPattern = nullptr, // #TODO bubbles
.viewport = rect(),
.clip = rect(),
.selection = TextSelection(),
.now = ms,
};
p.translate(0, textsTop());
paintDate(p);
_text1->draw(p, rect(), TextSelection(), ms);
_text1->draw(p, context);
p.translate(0, height1);
_text2->draw(p, rect(), TextSelection(), ms);
_text2->draw(p, context);
p.translate(0, height2);
}

View File

@@ -1715,13 +1715,13 @@ void GroupCall::handlePossibleCreateOrJoinResponse(
void GroupCall::handlePossibleCreateOrJoinResponse(
const MTPDgroupCall &data) {
setScheduledDate(data.vschedule_date().value_or_empty());
if (_acceptFields) {
if (!_instance && !_id) {
const auto input = MTP_inputGroupCall(
data.vid(),
data.vaccess_hash());
const auto scheduleDate = data.vschedule_date().value_or_empty();
setScheduledDate(scheduleDate);
if (const auto chat = _peer->asChat()) {
chat->setGroupCall(input, scheduleDate);
} else if (const auto group = _peer->asChannel()) {
@@ -1735,6 +1735,7 @@ void GroupCall::handlePossibleCreateOrJoinResponse(
} else if (_id != data.vid().v || !_instance) {
return;
}
setScheduledDate(data.vschedule_date().value_or_empty());
if (const auto streamDcId = data.vstream_dc_id()) {
_broadcastDcId = MTP::BareDcId(streamDcId->v);
}
@@ -2782,6 +2783,8 @@ void GroupCall::checkLastSpoke() {
|| muted() == MuteState::Active
|| muted() == MuteState::PushToTalk) {
real->applyLastSpoke(ssrc, when, now);
} else {
real->applyLastSpoke(ssrc, { crl::time(), crl::time() }, now);
}
}
_lastSpoke = std::move(list);

View File

@@ -246,7 +246,20 @@ private:
not_null<Row*> row,
const std::optional<Data::GroupCallParticipant> &was,
const Data::GroupCallParticipant *participant);
void updateRowInSoundingMap(
not_null<Row*> row,
bool wasSounding,
uint32 wasSsrc,
uint32 wasAdditionalSsrc,
const Data::GroupCallParticipant *participant);
void updateRowInSoundingMap(
not_null<Row*> row,
bool wasSounding,
uint32 wasSsrc,
bool nowSounding,
uint32 nowSsrc);
void removeRow(not_null<Row*> row);
void removeRowFromSoundingMap(not_null<Row*> row);
void updateRowLevel(not_null<Row*> row, float level);
void checkRowPosition(not_null<Row*> row);
[[nodiscard]] bool needToReorder(not_null<Row*> row) const;
@@ -804,14 +817,50 @@ void Members::Controller::updateRow(
: 0;
row->setSkipLevelUpdate(_skipRowLevelUpdate);
row->updateState(participant);
const auto wasNoSounding = _soundingRowBySsrc.empty();
updateRowInSoundingMap(
row,
wasSounding,
wasSsrc,
wasAdditionalSsrc,
participant);
const auto nowNoSounding = _soundingRowBySsrc.empty();
if (wasNoSounding && !nowNoSounding) {
_soundingAnimation.start();
} else if (nowNoSounding && !wasNoSounding) {
_soundingAnimation.stop();
}
delegate()->peerListUpdateRow(row);
}
void Members::Controller::updateRowInSoundingMap(
not_null<Row*> row,
bool wasSounding,
uint32 wasSsrc,
uint32 wasAdditionalSsrc,
const Data::GroupCallParticipant *participant) {
const auto nowSounding = row->sounding();
const auto nowSsrc = participant ? participant->ssrc : 0;
const auto nowAdditionalSsrc = participant
? GetAdditionalAudioSsrc(participant->videoParams)
: 0;
updateRowInSoundingMap(row, wasSounding, wasSsrc, nowSounding, nowSsrc);
updateRowInSoundingMap(
row,
wasSounding,
wasAdditionalSsrc,
nowSounding,
nowAdditionalSsrc);
}
const auto wasNoSounding = _soundingRowBySsrc.empty();
void Members::Controller::updateRowInSoundingMap(
not_null<Row*> row,
bool wasSounding,
uint32 wasSsrc,
bool nowSounding,
uint32 nowSsrc) {
if (wasSsrc == nowSsrc) {
if (nowSsrc && nowSounding != wasSounding) {
if (nowSounding) {
@@ -826,32 +875,14 @@ void Members::Controller::updateRow(
_soundingRowBySsrc.emplace(nowSsrc, row);
}
}
if (wasAdditionalSsrc == nowAdditionalSsrc) {
if (nowAdditionalSsrc && nowSounding != wasSounding) {
if (nowSounding) {
_soundingRowBySsrc.emplace(nowAdditionalSsrc, row);
} else {
_soundingRowBySsrc.remove(nowAdditionalSsrc);
}
}
} else {
_soundingRowBySsrc.remove(wasAdditionalSsrc);
if (nowSounding && nowAdditionalSsrc) {
_soundingRowBySsrc.emplace(nowAdditionalSsrc, row);
}
}
const auto nowNoSounding = _soundingRowBySsrc.empty();
if (wasNoSounding && !nowNoSounding) {
_soundingAnimation.start();
} else if (nowNoSounding && !wasNoSounding) {
_soundingAnimation.stop();
}
delegate()->peerListUpdateRow(row);
}
void Members::Controller::removeRow(not_null<Row*> row) {
removeRowFromSoundingMap(row);
delegate()->peerListRemoveRow(row);
}
void Members::Controller::removeRowFromSoundingMap(not_null<Row*> row) {
// There may be 0, 1 or 2 entries for a row.
for (auto i = begin(_soundingRowBySsrc); i != end(_soundingRowBySsrc);) {
if (i->second == row) {
@@ -860,7 +891,6 @@ void Members::Controller::removeRow(not_null<Row*> row) {
++i;
}
}
delegate()->peerListRemoveRow(row);
}
void Members::Controller::updateRowLevel(
@@ -944,18 +974,22 @@ void Members::Controller::prepareRows(not_null<Data::GroupCall*> real) {
auto changed = false;
auto count = delegate()->peerListFullRowsCount();
for (auto i = 0; i != count;) {
auto row = delegate()->peerListRowAt(i);
auto participantPeer = row->peer();
if (isMe(participantPeer)) {
const auto row = static_cast<Row*>(
delegate()->peerListRowAt(i).get());
removeRowFromSoundingMap(row);
const auto participantPeer = row->peer();
const auto me = isMe(participantPeer);
if (me) {
foundMe = true;
++i;
continue;
}
if (real->participantByPeer(participantPeer)) {
if (const auto found = real->participantByPeer(participantPeer)) {
updateRowInSoundingMap(row, false, 0, 0, found);
++i;
} else if (me) {
++i;
} else {
changed = true;
removeRow(static_cast<Row*>(row.get()));
removeRow(row);
--count;
}
}

View File

@@ -90,7 +90,18 @@ std::map<int, const char*> BetaLogs() {
},
{
2009004,
"- Choose one from dozens of new gorgeous animated backgrounds in Chat Settings > Chat background."
"- Choose one from dozens of new gorgeous animated backgrounds"
" in Chat Settings > Chat background.\n"
},
{
2009005,
"- Tile chat background patterns horizontally.\n"
"- Fix a rare crash in spellchecker on Windows.\n"
"- Fix animated chat backgrounds in Saved Messages.\n"
"- Fix \"Sorry, group is inaccessible\" message in scheduled voice chats.\n",
},
};
};

View File

@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
constexpr auto AppName = "Telegram Desktop"_cs;
constexpr auto AppFile = "Telegram"_cs;
constexpr auto AppVersion = 2009004;
constexpr auto AppVersionStr = "2.9.4";
constexpr auto AppVersion = 2009007;
constexpr auto AppVersionStr = "2.9.7";
constexpr auto AppBetaVersion = true;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -244,6 +244,14 @@ bool Media::forwardedBecomesUnread() const {
return false;
}
bool Media::dropForwardedInfo() const {
return false;
}
bool Media::forceForwardedInfo() const {
return false;
}
QString Media::errorTextForForward(not_null<PeerData*> peer) const {
return QString();
}
@@ -625,6 +633,10 @@ bool MediaFile::forwardedBecomesUnread() const {
|| _document->isVideoMessage();
}
bool MediaFile::dropForwardedInfo() const {
return _document->isSong();
}
QString MediaFile::errorTextForForward(not_null<PeerData*> peer) const {
if (const auto sticker = _document->sticker()) {
if (const auto error = Data::RestrictionError(
@@ -1140,6 +1152,10 @@ QString MediaGame::errorTextForForward(not_null<PeerData*> peer) const {
).value_or(QString());
}
bool MediaGame::dropForwardedInfo() const {
return true;
}
bool MediaGame::consumeMessageText(const TextWithEntities &text) {
_consumedText = text;
return true;
@@ -1347,6 +1363,10 @@ TextForMimeData MediaDice::clipboardText() const {
return { notificationText() };
}
bool MediaDice::forceForwardedInfo() const {
return true;
}
bool MediaDice::updateInlineResultMedia(const MTPMessageMedia &media) {
return updateSentMedia(media);
}

View File

@@ -102,6 +102,8 @@ public:
virtual bool allowsEditMedia() const;
virtual bool allowsRevoke(TimeId now) const;
virtual bool forwardedBecomesUnread() const;
virtual bool dropForwardedInfo() const;
virtual bool forceForwardedInfo() const;
virtual QString errorTextForForward(not_null<PeerData*> peer) const;
[[nodiscard]] virtual bool consumeMessageText(
@@ -191,6 +193,7 @@ public:
bool allowsEditCaption() const override;
bool allowsEditMedia() const override;
bool forwardedBecomesUnread() const override;
bool dropForwardedInfo() const override;
QString errorTextForForward(not_null<PeerData*> peer) const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override;
@@ -352,6 +355,7 @@ public:
QString pinnedTextSubstring() const override;
TextForMimeData clipboardText() const override;
QString errorTextForForward(not_null<PeerData*> peer) const override;
bool dropForwardedInfo() const override;
bool consumeMessageText(const TextWithEntities &text) override;
TextWithEntities consumedMessageText() const override;
@@ -442,6 +446,8 @@ public:
QString notificationText() const override;
QString pinnedTextSubstring() const override;
TextForMimeData clipboardText() const override;
bool forceForwardedInfo() const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override;
bool updateSentMedia(const MTPMessageMedia &media) override;
std::unique_ptr<HistoryView::Media> createView(

View File

@@ -366,6 +366,9 @@ WallPaper WallPaper::withUrlParams(
if (result._backgroundColors.empty()) {
result._backgroundColors = ColorsFromString(params.value("color"));
}
if (result._backgroundColors.empty()) {
result._backgroundColors = ColorsFromString(params.value("slug"));
}
if (const auto string = params.value("intensity"); !string.isEmpty()) {
auto ok = false;
const auto intensity = string.toInt(&ok);

View File

@@ -895,9 +895,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
Painter p(this);
auto ms = crl::now();
auto clip = e->rect();
if (_items.empty() && _upLoaded && _downLoaded) {
paintEmpty(p);
} else {
@@ -914,16 +912,26 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
return this->itemTop(elem) < bottom;
});
if (from != end) {
auto viewport = QRect(); // #TODO bubbles
auto top = itemTop(from->get());
auto context = HistoryView::PaintContext{
.bubblesPattern = nullptr,
.viewport = viewport.translated(0, -top),
.clip = clip.translated(0, -top),
.now = crl::now(),
};
p.translate(0, top);
for (auto i = from; i != to; ++i) {
const auto view = i->get();
const auto selection = (view == _selectedItem)
context.selection = (view == _selectedItem)
? _selectedText
: TextSelection();
view->draw(p, clip.translated(0, -top), selection, ms);
auto height = view->height();
view->draw(p, context);
const auto height = view->height();
top += height;
context.viewport.translate(0, -height);
context.clip.translate(0, -height);
p.translate(0, height);
}
p.translate(0, -top);

View File

@@ -560,7 +560,6 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
Painter p(this);
auto clip = e->rect();
auto ms = crl::now();
const auto historyDisplayedEmpty = _history->isDisplayedEmpty()
&& (!_migrated || _migrated->isDisplayedEmpty());
@@ -602,6 +601,8 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
} else {
seltoy += _dragSelTo->height();
}
const auto visibleAreaTopGlobal = mapToGlobal(
QPoint(0, _visibleAreaTop)).y();
auto mtop = migratedTop();
auto htop = historyTop();
@@ -613,15 +614,19 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
auto view = block->messages[iItem].get();
auto item = view->data();
auto y = mtop + block->y() + view->y();
p.save();
p.translate(0, y);
if (clip.y() < y + view->height()) while (y < drawToY) {
const auto selection = itemRenderSelection(
auto top = mtop + block->y() + view->y();
auto context = _controller->bubblesContext({
.visibleAreaTop = _visibleAreaTop,
.visibleAreaTopGlobal = visibleAreaTopGlobal,
.clip = clip,
}).translated(0, -top);
p.translate(0, top);
if (context.clip.y() < view->height()) while (top < drawToY) {
context.selection = itemRenderSelection(
view,
selfromy - mtop,
seltoy - mtop);
view->draw(p, clip.translated(0, -y), selection, ms);
view->draw(p, context);
if (item->hasViews()) {
_controller->content()->scheduleViewIncrement(item);
@@ -631,9 +636,10 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
_widget->enqueueMessageHighlight(view);
}
int32 h = view->height();
p.translate(0, h);
y += h;
const auto height = view->height();
top += height;
context.translate(0, -height);
p.translate(0, height);
++iItem;
if (iItem == block->messages.size()) {
@@ -647,7 +653,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
view = block->messages[iItem].get();
item = view->data();
}
p.restore();
p.translate(0, -top);
}
if (htop >= 0) {
auto iBlock = (_curHistory == _history ? _curBlock : 0);
@@ -656,21 +662,27 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
auto view = block->messages[iItem].get();
auto item = view->data();
auto readTill = (HistoryItem*)nullptr;
auto hclip = clip.intersected(QRect(0, hdrawtop, width(), clip.top() + clip.height()));
auto y = htop + block->y() + view->y();
p.save();
p.translate(0, y);
while (y < drawToY) {
const auto h = view->height();
if (hclip.y() < y + h && hdrawtop < y + h) {
const auto selection = itemRenderSelection(
auto top = htop + block->y() + view->y();
auto context = _controller->bubblesContext({
.visibleAreaTop = _visibleAreaTop,
.visibleAreaTopGlobal = visibleAreaTopGlobal,
.visibleAreaWidth = width(),
.clip = clip.intersected(
QRect(0, hdrawtop, width(), clip.top() + clip.height())
),
}).translated(0, -top);
p.translate(0, top);
while (top < drawToY) {
const auto height = view->height();
if (context.clip.y() < height && hdrawtop < top + height) {
context.selection = itemRenderSelection(
view,
selfromy - htop,
seltoy - htop);
view->draw(p, hclip.translated(0, -y), selection, ms);
view->draw(p, context);
const auto middle = y + h / 2;
const auto bottom = y + h;
const auto middle = top + height / 2;
const auto bottom = top + height;
if (_visibleAreaBottom >= bottom) {
const auto item = view->data();
if (!item->out() && item->unread()) {
@@ -688,8 +700,9 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
}
}
}
p.translate(0, h);
y += h;
top += height;
context.translate(0, -height);
p.translate(0, height);
++iItem;
if (iItem == block->messages.size()) {
@@ -703,7 +716,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
view = block->messages[iItem].get();
item = view->data();
}
p.restore();
p.translate(0, -top);
if (readTill && _widget->doWeReadServerHistory()) {
session().data().histories().readInboxTill(readTill);
@@ -3168,6 +3181,10 @@ void HistoryInner::addToSelection(
not_null<HistoryItem*> item) const {
const auto i = toItems->find(item);
if (i == toItems->cend()) {
if (toItems->size() == 1
&& toItems->begin()->second != FullSelection) {
toItems->clear();
}
toItems->emplace(item, FullSelection);
} else if (i->second != FullSelection) {
i->second = FullSelection;

View File

@@ -562,8 +562,14 @@ HistoryMessage::HistoryMessage(
auto config = CreateConfig();
if (original->Has<HistoryMessageForwarded>() || !original->history()->peer->isSelf()) {
// Server doesn't add "fwd_from" to non-forwarded messages from chat with yourself.
const auto originalMedia = original->media();
const auto dropForwardInfo = (originalMedia
&& originalMedia->dropForwardedInfo())
|| (original->history()->peer->isSelf()
&& !history->peer->isSelf()
&& !original->Has<HistoryMessageForwarded>()
&& (!originalMedia || !originalMedia->forceForwardedInfo()));
if (!dropForwardInfo) {
config.originalDate = original->dateOriginal();
if (const auto info = original->hiddenForwardedInfo()) {
config.senderNameOriginal = info->name;
@@ -595,6 +601,14 @@ HistoryMessage::HistoryMessage(
}
if (const auto fwdViaBot = original->viaBot()) {
config.viaBotId = peerToUser(fwdViaBot->id);
} else if (originalMedia && originalMedia->game()) {
if (const auto sender = original->senderOriginal()) {
if (const auto user = sender->asUser()) {
if (user->isBot()) {
config.viaBotId = peerToUser(user->id);
}
}
}
}
const auto fwdViewsCount = original->viewsCount();
if (fwdViewsCount > 0) {

View File

@@ -4929,7 +4929,7 @@ void HistoryWidget::startItemRevealAnimations() {
1.,
HistoryView::ListWidget::kItemRevealDuration,
anim::easeOutCirc);
if (item->out()) {
if (item->out() || _history->peer->isSelf()) {
controller()->rotateComplexGradientBackground();
}
}

View File

@@ -24,6 +24,7 @@ class SessionController;
namespace Ui {
class PathShiftGradient;
struct BubblePattern;
} // namespace Ui
namespace HistoryView {
@@ -191,6 +192,30 @@ struct DateBadge : public RuntimeComponent<DateBadge, Element> {
};
struct PaintContext {
const Ui::BubblePattern *bubblesPattern = nullptr;
QRect viewport;
QRect clip;
TextSelection selection;
crl::time now = 0;
void translate(int x, int y) {
viewport.translate(x, y);
clip.translate(x, y);
}
void translate(QPoint point) {
translate(point.x(), point.y());
}
[[nodiscard]] PaintContext translated(int x, int y) const {
auto result = *this;
result.translate(x, y);
return result;
}
[[nodiscard]] PaintContext translated(QPoint point) const {
return translated(point.x(), point.y());
}
};
class Element
: public Object
, public RuntimeComposer<Element>
@@ -261,11 +286,7 @@ public:
bool displayDate() const;
bool isInOneDayWithPrevious() const;
virtual void draw(
Painter &p,
QRect clip,
TextSelection selection,
crl::time ms) const = 0;
virtual void draw(Painter &p, const PaintContext &context) const = 0;
[[nodiscard]] virtual PointState pointState(QPoint point) const = 0;
[[nodiscard]] virtual TextState textState(
QPoint point,

View File

@@ -1612,17 +1612,22 @@ void ListWidget::paintEvent(QPaintEvent *e) {
return this->itemTop(elem) < bottom;
});
if (from != end(_items)) {
auto viewport = QRect(); // #TODO bubbles
auto top = itemTop(from->get());
auto context = HistoryView::PaintContext{
.bubblesPattern = nullptr,
.viewport = viewport.translated(0, -top),
.clip = clip.translated(0, -top),
.now = crl::now(),
};
p.translate(0, top);
for (auto i = from; i != to; ++i) {
const auto view = *i;
view->draw(
p,
clip.translated(0, -top),
itemRenderSelection(view),
ms);
view->draw(p, context);
const auto height = view->height();
top += height;
context.viewport.translate(0, -height);
context.clip.translate(0, -height);
p.translate(0, height);
}
p.translate(0, -top);

View File

@@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "ui/effects/ripple_animation.h"
#include "base/unixtime.h"
#include "ui/chat/message_bubble.h"
#include "ui/toast/toast.h"
#include "ui/text/text_utilities.h"
#include "ui/text/text_entity.h"
@@ -149,103 +150,6 @@ QString FastReplyText() {
return tr::lng_fast_reply(tr::now);
}
void PaintBubble(Painter &p, QRect rect, int outerWidth, bool selected, bool outbg, RectPart tailSide, RectParts skip) {
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 ? Ui::MessageOutSelectedCorners : Ui::MessageInSelectedCorners) : (outbg ? Ui::MessageOutCorners : Ui::MessageInCorners);
auto parts = RectPart::None | RectPart::NoTopBottom;
if (skip & RectPart::Top) {
if (skip & RectPart::Bottom) {
p.fillRect(rect, bg);
return;
}
rect.setTop(rect.y() - st::historyMessageRadius);
} else {
parts |= RectPart::FullTop;
}
if (skip & RectPart::Bottom) {
rect.setHeight(rect.height() + st::historyMessageRadius);
sh = nullptr;
tailSide = RectPart::None;
} else {
parts |= RectPart::Bottom;
}
if (tailSide == RectPart::Right) {
parts |= 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 (tailSide == RectPart::Left) {
parts |= 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 if (!(skip & RectPart::Bottom)) {
parts |= RectPart::FullBottom;
}
Ui::FillRoundRect(p, rect, bg, cors, sh, parts);
}
void PaintBubble(Painter &p, QRect rect, int outerWidth, bool selected, const std::vector<BubbleSelectionInterval> &selection, bool outbg, RectPart tailSide) {
if (selection.empty()) {
PaintBubble(
p,
rect,
outerWidth,
selected,
outbg,
tailSide,
RectPart::None);
return;
}
const auto left = rect.x();
const auto width = rect.width();
const auto top = rect.y();
const auto bottom = top + rect.height();
auto from = top;
for (const auto &selected : selection) {
if (selected.top > from) {
const auto skip = RectPart::Bottom
| (from > top ? RectPart::Top : RectPart::None);
PaintBubble(
p,
QRect(left, from, width, selected.top - from),
outerWidth,
false,
outbg,
tailSide,
skip);
}
const auto skip = ((selected.top > top)
? RectPart::Top
: RectPart::None)
| ((selected.top + selected.height < bottom)
? RectPart::Bottom
: RectPart::None);
PaintBubble(
p,
QRect(left, selected.top, width, selected.height),
outerWidth,
true,
outbg,
tailSide,
skip);
from = selected.top + selected.height;
}
if (from < bottom) {
PaintBubble(
p,
QRect(left, from, width, bottom - from),
outerWidth,
false,
outbg,
tailSide,
RectPart::Top);
}
}
style::color FromNameFg(PeerId peerId, bool selected) {
if (selected) {
const style::color colors[] = {
@@ -545,11 +449,7 @@ int Message::marginBottom() const {
return isHidden() ? 0 : st::msgMargin.bottom();
}
void Message::draw(
Painter &p,
QRect clip,
TextSelection selection,
crl::time ms) const {
void Message::draw(Painter &p, const PaintContext &context) const {
auto g = countGeometry();
if (g.width() < 1) {
return;
@@ -560,7 +460,7 @@ void Message::draw(
const auto outbg = hasOutLayout();
const auto bubble = drawBubble();
const auto selected = (selection == FullSelection);
const auto selected = (context.selection == FullSelection);
auto dateh = 0;
if (const auto date = Get<DateBadge>()) {
@@ -568,7 +468,7 @@ void Message::draw(
}
if (const auto bar = Get<UnreadBar>()) {
auto unreadbarh = bar->height();
if (clip.intersects(QRect(0, dateh, width(), unreadbarh))) {
if (context.clip.intersects(QRect(0, dateh, width(), unreadbarh))) {
p.translate(0, dateh);
bar->paint(p, 0, width(), delegate()->elementIsChatWide());
p.translate(0, -dateh);
@@ -587,8 +487,8 @@ void Message::draw(
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
auto mediaSelectionIntervals = (!selected && mediaDisplayed)
? media->getBubbleSelectionIntervals(selection)
: std::vector<BubbleSelectionInterval>();
? media->getBubbleSelectionIntervals(context.selection)
: std::vector<Ui::BubbleSelectionInterval>();
auto localMediaTop = 0;
const auto customHighlight = mediaDisplayed && media->customHighlight();
if (!mediaSelectionIntervals.empty() || customHighlight) {
@@ -633,7 +533,7 @@ void Message::draw(
g.setHeight(g.height() - keyboardHeight);
auto keyboardPosition = QPoint(g.left(), g.top() + g.height() + st::msgBotKbButton.margin);
p.translate(keyboardPosition);
keyboard->paint(p, g.width(), clip.translated(-keyboardPosition));
keyboard->paint(p, g.width(), context.clip.translated(-keyboardPosition));
p.translate(-keyboardPosition);
}
@@ -644,23 +544,30 @@ void Message::draw(
fromNameUpdated(g.width());
}
auto skipTail = isAttachedToNext()
const auto skipTail = isAttachedToNext()
|| (media && media->skipBubbleTail())
|| (keyboard != nullptr)
|| (context() == Context::Replies && data()->isDiscussionPost());
auto displayTail = skipTail
|| (this->context() == Context::Replies
&& data()->isDiscussionPost());
const auto displayTail = skipTail
? RectPart::None
: (outbg && !delegate()->elementIsChatWide())
? RectPart::Right
: RectPart::Left;
PaintBubble(
Ui::PaintBubble(
p,
g,
width(),
selected,
mediaSelectionIntervals,
outbg,
displayTail);
Ui::ComplexBubble{
.simple = Ui::SimpleBubble{
.geometry = g,
.pattern = context.bubblesPattern,
.patternViewport = context.viewport,
.outerWidth = width(),
.selected = selected,
.outbg = outbg,
.tailSide = displayTail,
},
.selection = mediaSelectionIntervals,
});
auto inner = g;
paintCommentsButton(p, inner, selected);
@@ -680,25 +587,29 @@ void Message::draw(
if (entry) {
trect.setHeight(trect.height() - entry->height());
}
paintText(p, trect, selection);
paintText(p, trect, context.selection);
if (mediaDisplayed) {
auto mediaHeight = media->height();
auto mediaLeft = inner.left();
auto mediaTop = (trect.y() + trect.height() - mediaHeight);
p.translate(mediaLeft, mediaTop);
media->draw(p, clip.translated(-mediaLeft, -mediaTop), skipTextSelection(selection), ms);
auto mediaContext = context.translated(-mediaLeft, -mediaTop);
mediaContext.selection = skipTextSelection(context.selection);
media->draw(p, mediaContext);
p.translate(-mediaLeft, -mediaTop);
}
if (entry) {
auto entryLeft = inner.left();
auto entryTop = trect.y() + trect.height();
p.translate(entryLeft, entryTop);
auto entrySelection = skipTextSelection(selection);
auto entryContext = context.translated(-entryLeft, -entryTop);
entryContext.selection = skipTextSelection(context.selection);
if (mediaDisplayed) {
entrySelection = media->skipSelection(entrySelection);
entryContext.selection = media->skipSelection(
entryContext.selection);
}
entry->draw(p, clip.translated(-entryLeft, -entryTop), entrySelection, ms);
entry->draw(p, entryContext);
p.translate(-entryLeft, -entryTop);
}
const auto needDrawInfo = entry
@@ -734,11 +645,13 @@ void Message::draw(
}
if (media) {
media->paintBubbleFireworks(p, g, ms);
media->paintBubbleFireworks(p, g, context.now);
}
} else if (media && media->isDisplayed()) {
p.translate(g.topLeft());
media->draw(p, clip.translated(-g.topLeft()), skipTextSelection(selection), ms);
auto mediaContext = context.translated(-g.topLeft());
mediaContext.selection = skipTextSelection(context.selection);
media->draw(p, mediaContext);
p.translate(-g.topLeft());
}
@@ -2128,10 +2041,8 @@ bool Message::displayForwardedFrom() const {
return false;
}
}
const auto media = this->media();
return !media
|| !media->isDisplayed()
|| !media->hideForwardedFrom();
const auto media = item->media();
return !media || !media->dropForwardedInfo();
}
return false;
}

View File

@@ -51,11 +51,7 @@ public:
int marginTop() const override;
int marginBottom() const override;
void draw(
Painter &p,
QRect clip,
TextSelection selection,
crl::time ms) const override;
void draw(Painter &p, const PaintContext &context) const override;
PointState pointState(QPoint point) const override;
TextState textState(
QPoint point,

View File

@@ -519,11 +519,7 @@ int Service::marginBottom() const {
return st::msgServiceMargin.bottom();
}
void Service::draw(
Painter &p,
QRect clip,
TextSelection selection,
crl::time ms) const {
void Service::draw(Painter &p, const PaintContext &context) const {
const auto item = message();
auto g = countGeometry();
if (g.width() < 1) {
@@ -533,6 +529,7 @@ void Service::draw(
auto height = this->height() - st::msgServiceMargin.top() - st::msgServiceMargin.bottom();
auto dateh = 0;
auto unreadbarh = 0;
auto clip = context.clip;
if (auto date = Get<DateBadge>()) {
dateh = date->height();
p.translate(0, dateh);
@@ -564,7 +561,9 @@ void Service::draw(
height -= st::msgServiceMargin.top() + media->height();
auto left = st::msgServiceMargin.left() + (g.width() - media->maxWidth()) / 2, top = st::msgServiceMargin.top() + height + st::msgServiceMargin.top();
p.translate(left, top);
media->draw(p, clip.translated(-left, -top), TextSelection(), ms);
auto mediaContext = context.translated(-left, -top);
mediaContext.selection = TextSelection();
media->draw(p, mediaContext);
p.translate(-left, -top);
}
@@ -575,7 +574,7 @@ void Service::draw(
p.setBrush(Qt::NoBrush);
p.setPen(st::msgServiceFg);
p.setFont(st::msgServiceFont);
item->_text.draw(p, trect.x(), trect.y(), trect.width(), Qt::AlignCenter, 0, -1, selection, false);
item->_text.draw(p, trect.x(), trect.y(), trect.width(), Qt::AlignCenter, 0, -1, context.selection, false);
p.restoreTextPalette();

View File

@@ -23,11 +23,7 @@ public:
int marginTop() const override;
int marginBottom() const override;
bool isHidden() const override;
void draw(
Painter &p,
QRect clip,
TextSelection selection,
crl::time ms) const override;
void draw(Painter &p, const PaintContext &context) const override;
PointState pointState(QPoint point) const override;
TextState textState(
QPoint point,
@@ -50,17 +46,6 @@ private:
int WideChatWidth();
struct PaintContext {
PaintContext(crl::time ms, const QRect &clip, TextSelection selection)
: ms(ms)
, clip(clip)
, selection(selection) {
}
crl::time ms;
const QRect &clip;
TextSelection selection;
};
class ServiceMessagePainter {
public:
static void paintDate(

View File

@@ -72,12 +72,12 @@ QSize Call::countOptimalSize() {
return { maxWidth, minHeight };
}
void Call::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const {
void Call::draw(Painter &p, const PaintContext &context) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
auto paintw = width();
auto outbg = _parent->hasOutLayout();
auto selected = (selection == FullSelection);
auto selected = (context.selection == FullSelection);
accumulate_min(paintw, maxWidth());

View File

@@ -22,7 +22,7 @@ public:
not_null<Element*> parent,
not_null<Data::Call*> call);
void draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const override;
void draw(Painter &p, const PaintContext &context) const override;
TextState textState(QPoint point, StateRequest request) const override;
bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {

View File

@@ -147,27 +147,18 @@ QSize Contact::countOptimalSize() {
accumulate_max(maxWidth, tleft + _name.maxWidth() + tright);
accumulate_min(maxWidth, st::msgMaxWidth);
auto minHeight = st.padding.top() + st.thumbSize + st.padding.bottom();
if (_userId) {
const auto msgsigned = item->Get<HistoryMessageSigned>();
const auto views = item->Get<HistoryMessageViews>();
if ((msgsigned && !msgsigned->isAnonymousRank)
|| (views
&& (views->views.count >= 0 || views->replies.count > 0))) {
minHeight += st::msgDateFont->height - st::msgDateDelta.y();
}
}
if (!isBubbleTop()) {
minHeight -= st::msgFileTopMinus;
}
return { maxWidth, minHeight };
}
void Contact::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const {
void Contact::draw(Painter &p, const PaintContext &context) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
auto paintw = width();
auto outbg = _parent->hasOutLayout();
bool selected = (selection == FullSelection);
bool selected = (context.selection == FullSelection);
accumulate_min(paintw, maxWidth());
@@ -177,7 +168,8 @@ void Contact::draw(Painter &p, const QRect &r, TextSelection selection, crl::tim
const auto nametop = st.nameTop - topMinus;
const auto nameright = st.padding.left();
const auto statustop = st.statusTop - topMinus;
const auto linktop = st.linkTop - topMinus;
const auto linkshift = st::msgDateFont->height / 2;
const auto linktop = st.linkTop - topMinus - linkshift;
if (_userId) {
QRect rthumb(style::rtlrect(st.padding.left(), st.padding.top() - topMinus, st.thumbSize, st.thumbSize, paintw));
if (_contact) {
@@ -222,7 +214,8 @@ TextState Contact::textState(QPoint point, StateRequest request) const {
const auto &st = _userId ? st::msgFileThumbLayout : st::msgFileLayout;
const auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus;
const auto nameleft = st.padding.left() + st.thumbSize + st.padding.right();
const auto linktop = st.linkTop - topMinus;
const auto linkshift = st::msgDateFont->height / 2;
const auto linktop = st.linkTop - topMinus - linkshift;
if (style::rtlrect(nameleft, linktop, _linkw, st::semiboldFont->height, width()).contains(point)) {
result.link = _linkl;
return result;

View File

@@ -29,7 +29,7 @@ public:
const QString &phone);
~Contact();
void draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const override;
void draw(Painter &p, const PaintContext &context) const override;
TextState textState(QPoint point, StateRequest request) const override;
bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {

View File

@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/image/image.h"
#include "ui/text/format_values.h"
#include "ui/text/format_song_document_name.h"
#include "ui/chat/message_bubble.h"
#include "ui/cached_round_corners.h"
#include "ui/ui_utility.h"
#include "layout/layout_selection.h" // FullSelection
@@ -325,19 +326,14 @@ QSize Document::countCurrentSize(int newWidth) {
return { newWidth, newHeight };
}
void Document::draw(
Painter &p,
const QRect &r,
TextSelection selection,
crl::time ms) const {
draw(p, width(), selection, ms, LayoutMode::Full);
void Document::draw(Painter &p, const PaintContext &context) const {
draw(p, width(), context, LayoutMode::Full);
}
void Document::draw(
Painter &p,
int width,
TextSelection selection,
crl::time ms,
const PaintContext &context,
LayoutMode mode) const {
if (width < st::msgPadding.left() + st::msgPadding.right() + 1) return;
@@ -349,7 +345,7 @@ void Document::draw(
_dataMedia->automaticLoad(_realParent->fullId(), _realParent);
}
bool loaded = dataLoaded(), displayLoading = _data->displayLoading();
bool selected = (selection == FullSelection);
bool selected = (context.selection == FullSelection);
int captionw = width - st::msgPadding.left() - st::msgPadding.right();
auto outbg = _parent->hasOutLayout();
@@ -507,19 +503,33 @@ void Document::draw(
}
return nullptr;
}();
if (previous && radialOpacity > 0. && radialOpacity < 1.) {
PaintInterpolatedIcon(p, *icon, *previous, radialOpacity, inner);
const auto paintContent = [&](Painter &q) {
if (previous && radialOpacity > 0. && radialOpacity < 1.) {
PaintInterpolatedIcon(q, *icon, *previous, radialOpacity, inner);
} else {
icon->paintInCenter(q, inner);
}
if (radial && !cornerDownload) {
QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
auto fg = outbg ? (selected ? st::historyFileOutRadialFgSelected : st::historyFileOutRadialFg) : (selected ? st::historyFileInRadialFgSelected : st::historyFileInRadialFg);
_animation->radial.draw(q, rinner, st::msgFileRadialLine, fg);
}
};
if (_data->isSongWithCover() || !usesBubblePattern(context)) {
paintContent(p);
} else {
icon->paintInCenter(p, inner);
Ui::PaintPatternBubblePart(
p,
context.viewport,
context.bubblesPattern->pixmap,
inner,
paintContent,
_iconCache);
}
if (radial && !cornerDownload) {
QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
auto fg = outbg ? (selected ? st::historyFileOutRadialFgSelected : st::historyFileOutRadialFg) : (selected ? st::historyFileInRadialFgSelected : st::historyFileInRadialFg);
_animation->radial.draw(p, rinner, st::msgFileRadialLine, fg);
}
drawCornerDownload(p, selected, mode);
drawCornerDownload(p, context, mode);
}
auto namewidth = width - nameleft - nameright;
auto statuswidth = namewidth;
@@ -595,7 +605,7 @@ void Document::draw(
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
captioned->_caption.draw(p, st::msgPadding.left(), bottom, captionw, style::al_left, 0, -1, selection);
captioned->_caption.draw(p, st::msgPadding.left(), bottom, captionw, style::al_left, 0, -1, context.selection);
}
}
@@ -625,7 +635,10 @@ bool Document::downloadInCorner() const {
&& IsServerMsgId(_realParent->id);
}
void Document::drawCornerDownload(Painter &p, bool selected, LayoutMode mode) const {
void Document::drawCornerDownload(
Painter &p,
const PaintContext &context,
LayoutMode mode) const {
if (dataLoaded()
|| _data->loadedInMediaCache()
|| !downloadInCorner()) {
@@ -633,6 +646,7 @@ void Document::drawCornerDownload(Painter &p, bool selected, LayoutMode mode) co
}
auto outbg = _parent->hasOutLayout();
auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus;
const auto selected = (context.selection == FullSelection);
const auto thumbed = false;
const auto &st = (mode == LayoutMode::Full)
? (thumbed ? st::msgFileThumbLayout : st::msgFileLayout)
@@ -640,11 +654,16 @@ void Document::drawCornerDownload(Painter &p, bool selected, LayoutMode mode) co
const auto shift = st::historyAudioDownloadShift;
const auto size = st::historyAudioDownloadSize;
const auto inner = style::rtlrect(st.padding.left() + shift, st.padding.top() - topMinus + shift, size, size, width());
auto pen = (selected
? (outbg ? st::msgOutBgSelected : st::msgInBgSelected)
: (outbg ? st::msgOutBg : st::msgInBg))->p;
pen.setWidth(st::lineWidth);
p.setPen(pen);
const auto bubblePattern = usesBubblePattern(context);
if (bubblePattern) {
p.setPen(Qt::NoPen);
} else {
auto pen = (selected
? (outbg ? st::msgOutBgSelected : st::msgInBgSelected)
: (outbg ? st::msgOutBg : st::msgInBg))->p;
pen.setWidth(st::lineWidth);
p.setPen(pen);
}
if (selected) {
p.setBrush(outbg ? st::msgFileOutBgSelected : st::msgFileInBgSelected);
} else {
@@ -660,11 +679,34 @@ void Document::drawCornerDownload(Painter &p, bool selected, LayoutMode mode) co
}
return &(outbg ? (selected ? st::historyAudioOutDownloadSelected : st::historyAudioOutDownload) : (selected ? st::historyAudioInDownloadSelected : st::historyAudioInDownload));
}();
icon->paintInCenter(p, inner);
if (_animation && _animation->radial.animating()) {
const auto rinner = inner.marginsRemoved(QMargins(st::historyAudioRadialLine, st::historyAudioRadialLine, st::historyAudioRadialLine, st::historyAudioRadialLine));
auto fg = outbg ? (selected ? st::historyFileOutRadialFgSelected : st::historyFileOutRadialFg) : (selected ? st::historyFileInRadialFgSelected : st::historyFileInRadialFg);
_animation->radial.draw(p, rinner, st::historyAudioRadialLine, fg);
const auto paintContent = [&](Painter &q) {
if (bubblePattern) {
auto hq = PainterHighQualityEnabler(q);
auto pen = st::msgOutBg->p;
pen.setWidth(st::lineWidth);
q.setPen(pen);
q.setBrush(Qt::NoBrush);
q.drawEllipse(inner);
}
icon->paintInCenter(q, inner);
if (_animation && _animation->radial.animating()) {
const auto rinner = inner.marginsRemoved(QMargins(st::historyAudioRadialLine, st::historyAudioRadialLine, st::historyAudioRadialLine, st::historyAudioRadialLine));
auto fg = outbg ? (selected ? st::historyFileOutRadialFgSelected : st::historyFileOutRadialFg) : (selected ? st::historyFileInRadialFgSelected : st::historyFileInRadialFg);
_animation->radial.draw(q, rinner, st::historyAudioRadialLine, fg);
}
};
if (bubblePattern) {
const auto add = st::lineWidth * 2;
const auto target = inner.marginsAdded({ add, add, add, add });
Ui::PaintPatternBubblePart(
p,
context.viewport,
context.bubblesPattern->pixmap,
target,
paintContent,
_cornerDownloadCache);
} else {
paintContent(p);
}
}
@@ -950,10 +992,6 @@ QMargins Document::bubbleMargins() const {
return QMargins(padding.left(), padding.top(), padding.left(), padding.bottom());
}
bool Document::hideForwardedFrom() const {
return _data->isSong();
}
QSize Document::sizeForGroupingOptimal(int maxWidth) const {
const auto thumbed = Get<HistoryDocumentThumbed>();
const auto &st = (thumbed ? st::msgFileThumbLayoutGrouped : st::msgFileLayoutGrouped);
@@ -982,9 +1020,7 @@ QSize Document::sizeForGrouping(int width) const {
void Document::drawGrouped(
Painter &p,
const QRect &clip,
TextSelection selection,
crl::time ms,
const PaintContext &context,
const QRect &geometry,
RectParts sides,
RectParts corners,
@@ -995,8 +1031,7 @@ void Document::drawGrouped(
draw(
p,
geometry.width(),
selection,
ms,
context.translated(-geometry.topLeft()),
LayoutMode::Grouped);
p.translate(-geometry.topLeft());
}

View File

@@ -34,7 +34,7 @@ public:
not_null<DocumentData*> document);
~Document();
void draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const override;
void draw(Painter &p, const PaintContext &context) const override;
TextState textState(QPoint point, StateRequest request) const override;
void updatePressed(QPoint point) override;
@@ -60,15 +60,12 @@ public:
return false;
}
QMargins bubbleMargins() const override;
bool hideForwardedFrom() const override;
QSize sizeForGroupingOptimal(int maxWidth) const override;
QSize sizeForGrouping(int width) const override;
void drawGrouped(
Painter &p,
const QRect &clip,
TextSelection selection,
crl::time ms,
const PaintContext &context,
const QRect &geometry,
RectParts sides,
RectParts corners,
@@ -110,8 +107,7 @@ private:
void draw(
Painter &p,
int width,
TextSelection selection,
crl::time ms,
const PaintContext &context,
LayoutMode mode) const;
[[nodiscard]] TextState textState(
QPoint point,
@@ -132,7 +128,10 @@ private:
bool updateStatusText() const; // returns showPause
[[nodiscard]] bool downloadInCorner() const;
void drawCornerDownload(Painter &p, bool selected, LayoutMode mode) const;
void drawCornerDownload(
Painter &p,
const PaintContext &context,
LayoutMode mode) const;
[[nodiscard]] TextState cornerDownloadTextState(
QPoint point,
StateRequest request,
@@ -140,6 +139,8 @@ private:
not_null<DocumentData*> _data;
mutable std::shared_ptr<Data::DocumentMedia> _dataMedia;
mutable QImage _iconCache;
mutable QImage _cornerDownloadCache;
};

View File

@@ -198,12 +198,12 @@ TextSelection Game::fromDescriptionSelection(
return ShiftItemSelection(selection, _title);
}
void Game::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const {
void Game::draw(Painter &p, const PaintContext &context) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
auto paintw = width();
auto outbg = _parent->hasOutLayout();
bool selected = (selection == FullSelection);
bool selected = (context.selection == FullSelection);
auto &barfg = selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor);
auto &semibold = selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg);
@@ -227,7 +227,7 @@ void Game::draw(Painter &p, const QRect &r, TextSelection selection, crl::time m
if (_title.hasSkipBlock()) {
endskip = _parent->skipBlockWidth();
}
_title.drawLeftElided(p, padding.left(), tshift, paintw, width(), _titleLines, style::al_left, 0, -1, endskip, false, selection);
_title.drawLeftElided(p, padding.left(), tshift, paintw, width(), _titleLines, style::al_left, 0, -1, endskip, false, context.selection);
tshift += _titleLines * lineHeight;
}
if (_descriptionLines) {
@@ -236,7 +236,7 @@ void Game::draw(Painter &p, const QRect &r, TextSelection selection, crl::time m
if (_description.hasSkipBlock()) {
endskip = _parent->skipBlockWidth();
}
_description.drawLeftElided(p, padding.left(), tshift, paintw, width(), _descriptionLines, style::al_left, 0, -1, endskip, false, toDescriptionSelection(selection));
_description.drawLeftElided(p, padding.left(), tshift, paintw, width(), _descriptionLines, style::al_left, 0, -1, endskip, false, toDescriptionSelection(context.selection));
tshift += _descriptionLines * lineHeight;
}
if (_attach) {
@@ -247,10 +247,11 @@ void Game::draw(Painter &p, const QRect &r, TextSelection selection, crl::time m
auto attachTop = tshift - bubble.top();
if (rtl()) attachLeft = width() - attachLeft - _attach->width();
auto attachSelection = selected ? FullSelection : TextSelection { 0, 0 };
auto attachContext = context.translated(-attachLeft, -attachTop);
attachContext.selection = selected ? FullSelection : TextSelection { 0, 0 };
p.translate(attachLeft, attachTop);
_attach->draw(p, r.translated(-attachLeft, -attachTop), attachSelection, ms);
_attach->draw(p, attachContext);
auto pixwidth = _attach->width();
auto pixheight = _attach->height();

View File

@@ -22,7 +22,7 @@ public:
void refreshParentId(not_null<HistoryItem*> realParent) override;
void draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const override;
void draw(Painter &p, const PaintContext &context) const override;
TextState textState(QPoint point, StateRequest request) const override;
[[nodiscard]] TextSelection adjustSelection(

View File

@@ -276,14 +276,14 @@ bool Gif::autoplayEnabled() const {
_data);
}
void Gif::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const {
void Gif::draw(Painter &p, const PaintContext &context) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
ensureDataMediaCreated();
const auto item = _parent->data();
const auto loaded = dataLoaded();
const auto displayLoading = item->isSending() || _data->displayLoading();
const auto selected = (selection == FullSelection);
const auto selected = (context.selection == FullSelection);
const auto autoPaused = _parent->delegate()->elementIsGifPaused();
const auto cornerDownload = downloadInCorner();
const auto canBePlayed = _dataMedia->canBePlayed();
@@ -613,7 +613,7 @@ void Gif::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms
}
if (!isRound && !_caption.isEmpty()) {
p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
_caption.draw(p, st::msgPadding.left(), painty + painth + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection);
_caption.draw(p, st::msgPadding.left(), painty + painth + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, context.selection);
} else if (!inWebPage) {
auto fullRight = paintx + usex + usew;
auto fullBottom = painty + painth;
@@ -896,9 +896,7 @@ QSize Gif::sizeForGrouping(int width) const {
void Gif::drawGrouped(
Painter &p,
const QRect &clip,
TextSelection selection,
crl::time ms,
const PaintContext &context,
const QRect &geometry,
RectParts sides,
RectParts corners,
@@ -909,7 +907,7 @@ void Gif::drawGrouped(
const auto item = _parent->data();
const auto loaded = dataLoaded();
const auto displayLoading = (item->id < 0) || _data->displayLoading();
const auto selected = (selection == FullSelection);
const auto selected = (context.selection == FullSelection);
const auto autoPaused = _parent->delegate()->elementIsGifPaused();
const auto fullFeatured = fullFeaturedGrouped(sides);
const auto cornerDownload = fullFeatured && downloadInCorner();

View File

@@ -45,7 +45,7 @@ public:
not_null<DocumentData*> document);
~Gif();
void draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const override;
void draw(Painter &p, const PaintContext &context) const override;
TextState textState(QPoint point, StateRequest request) const override;
[[nodiscard]] TextSelection adjustSelection(
@@ -73,9 +73,7 @@ public:
QSize sizeForGrouping(int width) const override;
void drawGrouped(
Painter &p,
const QRect &clip,
TextSelection selection,
crl::time ms,
const PaintContext &context,
const QRect &geometry,
RectParts sides,
RectParts corners,

View File

@@ -199,12 +199,12 @@ void Invoice::refreshParentId(not_null<HistoryItem*> realParent) {
}
}
void Invoice::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const {
void Invoice::draw(Painter &p, const PaintContext &context) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
auto paintw = width();
auto outbg = _parent->hasOutLayout();
bool selected = (selection == FullSelection);
bool selected = (context.selection == FullSelection);
auto &semibold = selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg);
@@ -226,14 +226,14 @@ void Invoice::draw(Painter &p, const QRect &r, TextSelection selection, crl::tim
if (_title.hasSkipBlock()) {
endskip = _parent->skipBlockWidth();
}
_title.drawLeftElided(p, padding.left(), tshift, paintw, width(), _titleHeight / lineHeight, style::al_left, 0, -1, endskip, false, selection);
_title.drawLeftElided(p, padding.left(), tshift, paintw, width(), _titleHeight / lineHeight, style::al_left, 0, -1, endskip, false, context.selection);
tshift += _titleHeight;
p.setTextPalette(selected ? (outbg ? st::outTextPaletteSelected : st::inTextPaletteSelected) : (outbg ? st::outTextPalette : st::inTextPalette));
}
if (_descriptionHeight) {
p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg);
_description.drawLeft(p, padding.left(), tshift, paintw, width(), style::al_left, 0, -1, toDescriptionSelection(selection));
_description.drawLeft(p, padding.left(), tshift, paintw, width(), style::al_left, 0, -1, toDescriptionSelection(context.selection));
tshift += _descriptionHeight;
}
if (_attach) {
@@ -244,10 +244,11 @@ void Invoice::draw(Painter &p, const QRect &r, TextSelection selection, crl::tim
auto attachTop = tshift - bubble.top();
if (rtl()) attachLeft = width() - attachLeft - _attach->width();
auto attachSelection = selected ? FullSelection : TextSelection { 0, 0 };
auto attachContext = context.translated(-attachLeft, -attachTop);
attachContext.selection = selected ? FullSelection : TextSelection { 0, 0 };
p.translate(attachLeft, attachTop);
_attach->draw(p, r.translated(-attachLeft, -attachTop), attachSelection, ms);
_attach->draw(p, attachContext);
auto pixwidth = _attach->width();
auto available = _status.maxWidth();

View File

@@ -34,7 +34,7 @@ public:
return false;
}
void draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const override;
void draw(Painter &p, const PaintContext &context) const override;
TextState textState(QPoint point, StateRequest request) const override;
[[nodiscard]] TextSelection adjustSelection(

View File

@@ -148,12 +148,12 @@ TextSelection Location::fromDescriptionSelection(
return ShiftItemSelection(selection, _title);
}
void Location::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const {
void Location::draw(Painter &p, const PaintContext &context) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
auto paintx = 0, painty = 0, paintw = width(), painth = height();
bool bubble = _parent->hasBubble();
auto outbg = _parent->hasOutLayout();
bool selected = (selection == FullSelection);
bool selected = (context.selection == FullSelection);
if (bubble) {
if (!_title.isEmpty() || !_description.isEmpty()) {
@@ -166,12 +166,12 @@ void Location::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti
if (!_title.isEmpty()) {
p.setPen(outbg ? st::webPageTitleOutFg : st::webPageTitleInFg);
_title.drawLeftElided(p, paintx + st::msgPadding.left(), painty, textw, width(), 2, style::al_left, 0, -1, 0, false, selection);
_title.drawLeftElided(p, paintx + st::msgPadding.left(), painty, textw, width(), 2, style::al_left, 0, -1, 0, false, context.selection);
painty += qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height);
}
if (!_description.isEmpty()) {
p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg);
_description.drawLeftElided(p, paintx + st::msgPadding.left(), painty, textw, width(), 3, style::al_left, 0, -1, 0, false, toDescriptionSelection(selection));
_description.drawLeftElided(p, paintx + st::msgPadding.left(), painty, textw, width(), 3, style::al_left, 0, -1, 0, false, toDescriptionSelection(context.selection));
painty += qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height);
}
if (!_title.isEmpty() || !_description.isEmpty()) {

View File

@@ -27,7 +27,7 @@ public:
const QString &description = QString());
~Location();
void draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const override;
void draw(Painter &p, const PaintContext &context) const override;
TextState textState(QPoint point, StateRequest request) const override;
[[nodiscard]] TextSelection adjustSelection(

View File

@@ -13,8 +13,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_cursor_state.h"
#include "lottie/lottie_single_player.h"
#include "storage/storage_shared_media.h"
#include "layout/layout_selection.h"
#include "data/data_document.h"
#include "ui/item_text_options.h"
#include "ui/chat/message_bubble.h"
#include "core/ui_integration.h"
#include "styles/style_chat.h"
@@ -166,6 +168,20 @@ TextSelection Media::unskipSelection(TextSelection selection) const {
return ShiftItemSelection(selection, fullSelectionLength());
}
auto Media::getBubbleSelectionIntervals(
TextSelection selection) const
-> std::vector<Ui::BubbleSelectionInterval> {
return {};
}
bool Media::usesBubblePattern(const PaintContext &context) const {
return (context.selection != FullSelection)
&& _parent->hasOutLayout()
&& context.bubblesPattern
&& !context.viewport.isEmpty()
&& !context.bubblesPattern->pixmap.size().isEmpty();
}
PointState Media::pointState(QPoint point) const {
return QRect(0, 0, width(), height()).contains(point)
? PointState::Inside

View File

@@ -29,6 +29,10 @@ class SinglePlayer;
struct ColorReplacements;
} // namespace Lottie
namespace Ui {
struct BubbleSelectionInterval;
} // namespace Ui
namespace HistoryView {
enum class PointState : char;
@@ -36,6 +40,7 @@ enum class CursorState : char;
enum class InfoDisplayType : char;
struct TextState;
struct StateRequest;
struct PaintContext;
class Element;
enum class MediaInBubbleState {
@@ -45,11 +50,6 @@ enum class MediaInBubbleState {
Bottom,
};
struct BubbleSelectionInterval {
int top = 0;
int height = 0;
};
[[nodiscard]] QString DocumentTimestampLinkBase(
not_null<DocumentData*> document,
FullMsgId context);
@@ -86,11 +86,7 @@ public:
}
virtual void drawHighlight(Painter &p, int top) const {
}
virtual void draw(
Painter &p,
const QRect &r,
TextSelection selection,
crl::time ms) const = 0;
virtual void draw(Painter &p, const PaintContext &context) const = 0;
[[nodiscard]] virtual PointState pointState(QPoint point) const;
[[nodiscard]] virtual TextState textState(
QPoint point,
@@ -125,9 +121,7 @@ public:
[[nodiscard]] virtual auto getBubbleSelectionIntervals(
TextSelection selection) const
-> std::vector<BubbleSelectionInterval> {
return {};
}
-> std::vector<Ui::BubbleSelectionInterval>;
// if we press and drag this link should we drag the item
[[nodiscard]] virtual bool dragItemByHandler(
@@ -173,9 +167,7 @@ public:
}
virtual void drawGrouped(
Painter &p,
const QRect &clip,
TextSelection selection,
crl::time ms,
const PaintContext &context,
const QRect &geometry,
RectParts sides,
RectParts corners,
@@ -202,9 +194,6 @@ public:
[[nodiscard]] virtual QMargins bubbleMargins() const {
return QMargins();
}
[[nodiscard]] virtual bool hideForwardedFrom() const {
return false;
}
[[nodiscard]] virtual bool overrideEditedDate() const {
return false;
@@ -305,6 +294,8 @@ protected:
virtual void playAnimation(bool autoplay) {
}
[[nodiscard]] bool usesBubblePattern(const PaintContext &context) const;
const not_null<Element*> _parent;
MediaInBubbleState _inBubbleState = MediaInBubbleState::None;

View File

@@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/storage_shared_media.h"
#include "lang/lang_keys.h"
#include "ui/grouped_layout.h"
#include "ui/chat/message_bubble.h"
#include "ui/text/text_options.h"
#include "layout/layout_selection.h"
#include "styles/style_chat.h"
@@ -280,21 +281,19 @@ void GroupedMedia::drawHighlight(Painter &p, int top) const {
}
}
void GroupedMedia::draw(
Painter &p,
const QRect &clip,
TextSelection selection,
crl::time ms) const {
void GroupedMedia::draw(Painter &p, const PaintContext &context) const {
auto wasCache = false;
auto nowCache = false;
const auto groupPadding = groupedPadding();
auto selection = context.selection;
const auto fullSelection = (selection == FullSelection);
const auto textSelection = (_mode == Mode::Column)
&& !fullSelection
&& !IsSubGroupSelection(selection);
for (auto i = 0, count = int(_parts.size()); i != count; ++i) {
const auto &part = _parts[i];
const auto partSelection = fullSelection
auto partContext = context;
partContext.selection = fullSelection
? FullSelection
: textSelection
? selection
@@ -312,9 +311,7 @@ void GroupedMedia::draw(
}
part.content->drawGrouped(
p,
clip,
partSelection,
ms,
partContext,
part.geometry.translated(0, groupPadding.top()),
part.sides,
cornersFromSides(part.sides),
@@ -509,8 +506,8 @@ TextForMimeData GroupedMedia::selectedText(
auto GroupedMedia::getBubbleSelectionIntervals(
TextSelection selection) const
-> std::vector<BubbleSelectionInterval> {
auto result = std::vector<BubbleSelectionInterval>();
-> std::vector<Ui::BubbleSelectionInterval> {
auto result = std::vector<Ui::BubbleSelectionInterval>();
for (auto i = 0, count = int(_parts.size()); i != count; ++i) {
const auto &part = _parts[i];
if (!IsGroupItemSelection(selection, i)) {
@@ -528,7 +525,7 @@ auto GroupedMedia::getBubbleSelectionIntervals(
const auto newHeight = std::max(
last.top + last.height - newTop,
geometry.top() + geometry.height() - newTop);
last = BubbleSelectionInterval{ newTop, newHeight };
last = Ui::BubbleSelectionInterval{ newTop, newHeight };
}
}
const auto groupPadding = groupedPadding();
@@ -695,10 +692,6 @@ bool GroupedMedia::needsBubble() const {
return _needBubble;
}
bool GroupedMedia::hideForwardedFrom() const {
return main()->hideForwardedFrom();
}
bool GroupedMedia::computeNeedBubble() const {
if (!_caption.isEmpty() || _mode == Mode::Column) {
return true;

View File

@@ -32,11 +32,7 @@ public:
void refreshParentId(not_null<HistoryItem*> realParent) override;
void drawHighlight(Painter &p, int top) const override;
void draw(
Painter &p,
const QRect &clip,
TextSelection selection,
crl::time ms) const override;
void draw(Painter &p, const PaintContext &context) const override;
PointState pointState(QPoint point) const override;
TextState textState(
QPoint point,
@@ -57,7 +53,7 @@ public:
TextForMimeData selectedText(TextSelection selection) const override;
std::vector<BubbleSelectionInterval> getBubbleSelectionIntervals(
std::vector<Ui::BubbleSelectionInterval> getBubbleSelectionIntervals(
TextSelection selection) const override;
void clickHandlerActiveChanged(
@@ -91,7 +87,6 @@ public:
bool customHighlight() const override {
return true;
}
bool hideForwardedFrom() const override;
void stopAnimation() override;
void checkAnimation() override;

View File

@@ -105,15 +105,11 @@ QSize UnwrappedMedia::countCurrentSize(int newWidth) {
return { newWidth, newHeight };
}
void UnwrappedMedia::draw(
Painter &p,
const QRect &r,
TextSelection selection,
crl::time ms) const {
void UnwrappedMedia::draw(Painter &p, const PaintContext &context) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
return;
}
bool selected = (selection == FullSelection);
bool selected = (context.selection == FullSelection);
const auto rightAligned = _parent->hasOutLayout()
&& !_parent->delegate()->elementIsChatWide();

View File

@@ -57,7 +57,7 @@ public:
not_null<Element*> parent,
std::unique_ptr<Content> content);
void draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const override;
void draw(Painter &p, const PaintContext &context) const override;
PointState pointState(QPoint point) const override;
TextState textState(QPoint point, StateRequest request) const override;

View File

@@ -223,12 +223,12 @@ QSize Photo::countCurrentSize(int newWidth) {
return { newWidth, newHeight };
}
void Photo::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const {
void Photo::draw(Painter &p, const PaintContext &context) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
ensureDataMediaCreated();
_dataMedia->automaticLoad(_realParent->fullId(), _parent->data());
auto selected = (selection == FullSelection);
auto selected = (context.selection == FullSelection);
auto loaded = _dataMedia->loaded();
auto displayLoading = _data->displayLoading();
@@ -330,7 +330,7 @@ void Photo::draw(Painter &p, const QRect &r, TextSelection selection, crl::time
if (!_caption.isEmpty()) {
auto outbg = _parent->hasOutLayout();
p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
_caption.draw(p, st::msgPadding.left(), painty + painth + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection);
_caption.draw(p, st::msgPadding.left(), painty + painth + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, context.selection);
} else if (!inWebPage) {
auto fullRight = paintx + paintw;
auto fullBottom = painty + painth;
@@ -489,9 +489,7 @@ QSize Photo::sizeForGrouping(int width) const {
void Photo::drawGrouped(
Painter &p,
const QRect &clip,
TextSelection selection,
crl::time ms,
const PaintContext &context,
const QRect &geometry,
RectParts sides,
RectParts corners,
@@ -503,7 +501,7 @@ void Photo::drawGrouped(
validateGroupedCache(geometry, corners, cacheKey, cache);
const auto selected = (selection == FullSelection);
const auto selected = (context.selection == FullSelection);
const auto loaded = _dataMedia->loaded();
const auto displayLoading = _data->displayLoading();
const auto bubble = _parent->hasBubble();

View File

@@ -37,7 +37,7 @@ public:
int width);
~Photo();
void draw(Painter &p, const QRect &clip, TextSelection selection, crl::time ms) const override;
void draw(Painter &p, const PaintContext &context) const override;
TextState textState(QPoint point, StateRequest request) const override;
[[nodiscard]] TextSelection adjustSelection(
@@ -62,9 +62,7 @@ public:
QSize sizeForGrouping(int width) const override;
void drawGrouped(
Painter &p,
const QRect &clip,
TextSelection selection,
crl::time ms,
const PaintContext &context,
const QRect &geometry,
RectParts sides,
RectParts corners,

View File

@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_element.h"
#include "history/view/history_view_cursor_state.h"
#include "calls/calls_instance.h"
#include "ui/chat/message_bubble.h"
#include "ui/text/text_options.h"
#include "ui/text/text_utilities.h"
#include "ui/text/format_values.h"
@@ -715,15 +716,15 @@ void Poll::updateAnswerVotes() {
}
}
void Poll::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const {
void Poll::draw(Painter &p, const PaintContext &context) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
auto paintw = width();
checkSendingAnimation();
_poll->checkResultsReload(_parent->data(), ms);
_poll->checkResultsReload(_parent->data(), context.now);
const auto outbg = _parent->hasOutLayout();
const auto selected = (selection == FullSelection);
const auto selected = (context.selection == FullSelection);
const auto &regular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg);
const auto padding = st::msgPadding;
@@ -734,14 +735,14 @@ void Poll::draw(Painter &p, const QRect &r, TextSelection selection, crl::time m
paintw -= padding.left() + padding.right();
p.setPen(outbg ? st::webPageTitleOutFg : st::webPageTitleInFg);
_question.drawLeft(p, padding.left(), tshift, paintw, width(), style::al_left, 0, -1, selection);
_question.drawLeft(p, padding.left(), tshift, paintw, width(), style::al_left, 0, -1, context.selection);
tshift += _question.countHeight(paintw) + st::historyPollSubtitleSkip;
p.setPen(regular);
_subtitle.drawLeftElided(p, padding.left(), tshift, paintw, width());
paintRecentVoters(p, padding.left() + _subtitle.maxWidth(), tshift, selection);
paintCloseByTimer(p, padding.left() + paintw, tshift, selection);
paintShowSolution(p, padding.left() + paintw, tshift, selection);
paintRecentVoters(p, padding.left() + _subtitle.maxWidth(), tshift, context);
paintCloseByTimer(p, padding.left() + paintw, tshift, context.selection);
paintShowSolution(p, padding.left() + paintw, tshift, context.selection);
tshift += st::msgDateFont->height + st::historyPollAnswersSkip;
const auto progress = _answersAnimation
@@ -773,14 +774,14 @@ void Poll::draw(Painter &p, const QRect &r, TextSelection selection, crl::time m
tshift,
paintw,
width(),
selection);
context.selection);
tshift += height;
}
if (!inlineFooter()) {
paintBottom(p, padding.left(), tshift, paintw, selection);
paintBottom(p, padding.left(), tshift, paintw, context.selection);
} else if (!_totalVotesLabel.isEmpty()) {
tshift += st::msgPadding.bottom();
paintInlineFooter(p, padding.left(), tshift, paintw, selection);
paintInlineFooter(p, padding.left(), tshift, paintw, context.selection);
}
}
@@ -864,7 +865,7 @@ void Poll::paintRecentVoters(
Painter &p,
int left,
int top,
TextSelection selection) const {
const PaintContext &context) const {
const auto count = int(_recentVoters.size());
if (!count) {
return;
@@ -875,7 +876,7 @@ void Poll::paintRecentVoters(
auto y = top;
const auto size = st::historyPollRecentVoterSize;
const auto outbg = _parent->hasOutLayout();
const auto selected = (selection == FullSelection);
const auto selected = (context.selection == FullSelection);
auto pen = (selected
? (outbg ? st::msgOutBgSelected : st::msgInBgSelected)
: (outbg ? st::msgOutBg : st::msgInBg))->p;
@@ -888,10 +889,26 @@ void Poll::paintRecentVoters(
if (!was && recent.userpic) {
created = true;
}
p.setPen(pen);
p.setBrush(Qt::NoBrush);
PainterHighQualityEnabler hq(p);
p.drawEllipse(x, y, size, size);
const auto paintContent = [&](Painter &p) {
p.setPen(pen);
p.setBrush(Qt::NoBrush);
PainterHighQualityEnabler hq(p);
p.drawEllipse(x, y, size, size);
};
if (usesBubblePattern(context)) {
const auto add = st::lineWidth * 2;
const auto target = QRect(x, y, size, size).marginsAdded(
{ add, add, add, add });
Ui::PaintPatternBubblePart(
p,
context.viewport,
context.bubblesPattern->pixmap,
target,
paintContent,
_userpicCircleCache);
} else {
paintContent(p);
}
x -= st::historyPollRecentVoterSkip;
}
if (created) {

View File

@@ -30,7 +30,7 @@ public:
not_null<PollData*> poll);
~Poll();
void draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const override;
void draw(Painter &p, const PaintContext &context) const override;
TextState textState(QPoint point, StateRequest request) const override;
bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
@@ -107,7 +107,7 @@ private:
Painter &p,
int left,
int top,
TextSelection selection) const;
const PaintContext &context) const;
void paintCloseByTimer(
Painter &p,
int right,
@@ -213,6 +213,7 @@ private:
mutable std::unique_ptr<Ui::FireworksAnimation> _fireworksAnimation;
Ui::Animations::Simple _wrongAnswerAnimation;
mutable QPoint _lastLinkPoint;
mutable QImage _userpicCircleCache;
mutable std::unique_ptr<CloseInformation> _close;

View File

@@ -150,7 +150,7 @@ QSize ThemeDocument::countCurrentSize(int newWidth) {
return { newWidth, newHeight };
}
void ThemeDocument::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const {
void ThemeDocument::draw(Painter &p, const PaintContext &context) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
ensureDataMediaCreated();
@@ -158,7 +158,7 @@ void ThemeDocument::draw(Painter &p, const QRect &r, TextSelection selection, cr
if (_data) {
_dataMedia->automaticLoad(_realParent->fullId(), _parent->data());
}
auto selected = (selection == FullSelection);
auto selected = (context.selection == FullSelection);
auto loaded = dataLoaded();
auto displayLoading = _data && _data->displayLoading();

View File

@@ -27,11 +27,7 @@ public:
const std::optional<Data::WallPaper> &params);
~ThemeDocument();
void draw(
Painter &p,
const QRect &clip,
TextSelection selection,
crl::time ms) const override;
void draw(Painter &p, const PaintContext &context) const override;
TextState textState(QPoint point, StateRequest request) const override;
DocumentData *getDocument() const override {

View File

@@ -448,12 +448,12 @@ void WebPage::unloadHeavyPart() {
_photoMedia = nullptr;
}
void WebPage::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const {
void WebPage::draw(Painter &p, const PaintContext &context) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
auto paintw = width();
auto outbg = _parent->hasOutLayout();
bool selected = (selection == FullSelection);
bool selected = (context.selection == FullSelection);
auto &barfg = selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor);
auto &semibold = selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg);
@@ -512,7 +512,7 @@ void WebPage::draw(Painter &p, const QRect &r, TextSelection selection, crl::tim
if (_siteName.hasSkipBlock()) {
endskip = _parent->skipBlockWidth();
}
_siteName.drawLeftElided(p, padding.left(), tshift, paintw, width(), _siteNameLines, style::al_left, 0, -1, endskip, false, selection);
_siteName.drawLeftElided(p, padding.left(), tshift, paintw, width(), _siteNameLines, style::al_left, 0, -1, endskip, false, context.selection);
tshift += lineHeight;
}
if (_titleLines) {
@@ -521,7 +521,7 @@ void WebPage::draw(Painter &p, const QRect &r, TextSelection selection, crl::tim
if (_title.hasSkipBlock()) {
endskip = _parent->skipBlockWidth();
}
_title.drawLeftElided(p, padding.left(), tshift, paintw, width(), _titleLines, style::al_left, 0, -1, endskip, false, toTitleSelection(selection));
_title.drawLeftElided(p, padding.left(), tshift, paintw, width(), _titleLines, style::al_left, 0, -1, endskip, false, toTitleSelection(context.selection));
tshift += _titleLines * lineHeight;
}
if (_descriptionLines) {
@@ -531,10 +531,10 @@ void WebPage::draw(Painter &p, const QRect &r, TextSelection selection, crl::tim
endskip = _parent->skipBlockWidth();
}
if (_descriptionLines > 0) {
_description.drawLeftElided(p, padding.left(), tshift, paintw, width(), _descriptionLines, style::al_left, 0, -1, endskip, false, toDescriptionSelection(selection));
_description.drawLeftElided(p, padding.left(), tshift, paintw, width(), _descriptionLines, style::al_left, 0, -1, endskip, false, toDescriptionSelection(context.selection));
tshift += _descriptionLines * lineHeight;
} else {
_description.drawLeft(p, padding.left(), tshift, paintw, width(), style::al_left, 0, -1, toDescriptionSelection(selection));
_description.drawLeft(p, padding.left(), tshift, paintw, width(), style::al_left, 0, -1, toDescriptionSelection(context.selection));
tshift += _description.countHeight(paintw);
}
}
@@ -548,8 +548,9 @@ void WebPage::draw(Painter &p, const QRect &r, TextSelection selection, crl::tim
p.translate(attachLeft, attachTop);
auto attachSelection = selected ? FullSelection : TextSelection { 0, 0 };
_attach->draw(p, r.translated(-attachLeft, -attachTop), attachSelection, ms);
auto attachContext = context.translated(-attachLeft, -attachTop);
attachContext.selection = selected ? FullSelection : TextSelection { 0, 0 };
_attach->draw(p, attachContext);
auto pixwidth = _attach->width();
auto pixheight = _attach->height();

View File

@@ -24,7 +24,7 @@ public:
void refreshParentId(not_null<HistoryItem*> realParent) override;
void draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const override;
void draw(Painter &p, const PaintContext &context) const override;
TextState textState(QPoint point, StateRequest request) const override;
bool hideMessageText() const override {

View File

@@ -12,6 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/concurrent_timer.h"
#include "core/crash_reports.h"
#include "zlib.h"
#define TO_LOG(x) debugLog(QString x)
namespace Media {
namespace Streaming {
namespace {
@@ -140,6 +144,9 @@ private:
[[nodiscard]] TimePoint trackTime() const;
void debugAssertKnownTime(int step, crl::time time) const;
void debugLog(const QString &entry) const;
const crl::weak_on_queue<VideoTrackObject> _weak;
PlaybackOptions _options;
@@ -167,6 +174,8 @@ private:
// For initial frame skipping for an exact seek.
FFmpeg::FramePointer _initialSkippingFrame;
mutable QStringList _debugLog;
};
VideoTrackObject::VideoTrackObject(
@@ -188,6 +197,17 @@ VideoTrackObject::VideoTrackObject(
Expects(_stream.duration > 1);
Expects(_ready != nullptr);
Expects(_error != nullptr);
TO_LOG(("created,speed:%1,mode:%2,position:%3,sync:%4,"
"loop:%5,wait:%6,duration:%7,initialized:%8"
).arg(options.speed
).arg(int(options.mode)
).arg(options.position
).arg(options.syncVideoByAudio ? "true" : "false"
).arg(options.loop ? "true" : "false"
).arg(options.waitForMarkAsShown ? "true" : "false"
).arg(_stream.duration
).arg(_shared->initialized() ? "true" : "false"));
}
rpl::producer<> VideoTrackObject::checkNextFrame() const {
@@ -209,9 +229,11 @@ void VideoTrackObject::process(std::vector<FFmpeg::Packet> &&packets) {
return;
}
if (packets.front().empty()) {
TO_LOG(("process,packets:%1,till_end").arg(packets.size()));
Assert(packets.size() == 1);
_readTillEnd = true;
} else if (!_readTillEnd) {
TO_LOG(("process,packets:%1,till:%2").arg(packets.size()).arg(durationByPacket(packets.back())));
//for (const auto &packet : packets) {
// // Maybe it is enough to count by list.back()?.. hope so.
// accumulate_max(
@@ -230,6 +252,7 @@ void VideoTrackObject::process(std::vector<FFmpeg::Packet> &&packets) {
}
for (auto i = begin(packets), e = end(packets); i != e; ++i) {
if (_shared->initialized()) {
TO_LOG(("queueing_packets,count:%1").arg(e - i));
_stream.queue.insert(
end(_stream.queue),
std::make_move_iterator(i),
@@ -237,6 +260,7 @@ void VideoTrackObject::process(std::vector<FFmpeg::Packet> &&packets) {
queueReadFrames();
break;
} else if (!tryReadFirstFrame(std::move(*i))) {
TO_LOG(("fail_read_first_frame"));
fail(Error::InvalidData);
break;
}
@@ -260,21 +284,105 @@ int VideoTrackObject::durationByPacket(const FFmpeg::Packet &packet) {
void VideoTrackObject::queueReadFrames(crl::time delay) {
if (delay > 0) {
TO_LOG(("queue_with_delay:%1").arg(delay));
_readFramesTimer.callOnce(delay);
} else if (!_queued) {
TO_LOG(("queue_without_delay"));
_queued = true;
_weak.with([](VideoTrackObject &that) {
that.TO_LOG(("unqueued_without_delay"));
that._queued = false;
that.readFrames();
});
} else {
TO_LOG(("queued_already"));
}
}
void VideoTrackObject::debugAssertKnownTime(int step, crl::time time) const {
if (time < kTimeUnknown / 2) {
CrashReports::SetAnnotation("DebugStep", QString::number(step));
CrashReports::SetAnnotation("CheckedValue", QString::number(time));
CrashReports::SetAnnotation(
"_syncTimePoint.trackTime",
QString::number(_syncTimePoint.trackTime));
CrashReports::SetAnnotation(
"_syncTimePoint.worldTime",
QString::number(_syncTimePoint.worldTime));
CrashReports::SetAnnotation(
"_pausedTime",
QString::number(_pausedTime));
CrashReports::SetAnnotation(
"_resumedTime",
QString::number(_resumedTime));
if (!_shared) {
CrashReports::SetAnnotation("_shared", "NULL");
} else {
CrashReports::SetAnnotation(
"_shared->initialized",
_shared->initialized() ? "true" : "false");
}
CrashReports::SetAnnotation("Now", QString::number(crl::now()));
const auto log = _debugLog.join('\n').toUtf8();
const auto compressed = [&] {
auto result = QByteArray(log.size() + 1024, char(0));
z_stream stream;
stream.zalloc = nullptr;
stream.zfree = nullptr;
stream.opaque = nullptr;
stream.avail_in = log.size();
stream.next_in = reinterpret_cast<Bytef*>(const_cast<char*>(log.data()));
stream.avail_out = result.size();
stream.next_out = reinterpret_cast<Bytef*>(result.data());
int res = deflateInit2(&stream, Z_BEST_COMPRESSION, Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
if (res != Z_OK) {
CrashReports::SetAnnotation("Log", "deflatInit2:" + QString::number(res));
return QByteArray();
}
{
const auto guard = gsl::finally([&] { deflateEnd(&stream); });
int res = deflate(&stream, Z_FINISH);
if (res != Z_OK && res != Z_STREAM_END) {
CrashReports::SetAnnotation("Log", "deflate:" + QString::number(res));
return QByteArray();
} else if (!stream.avail_out) {
CrashReports::SetAnnotation("Log", "deflate:no_avail_out");
return QByteArray();
}
}
result.resize(result.size() - stream.avail_out);
return result;
}();
if (!compressed.isEmpty()) {
CrashReports::SetAnnotation("Log", QString::fromUtf8(compressed.toBase64()));
}
Unexpected("Bad time value.");
}
}
void VideoTrackObject::debugLog(const QString &entry) const {
constexpr auto kMaxEntries = 2048;
if (_debugLog.size() >= kMaxEntries) {
return;
} else if (_debugLog.size() == kMaxEntries - 1) {
_debugLog.push_back("...");
return;
}
_debugLog.push_back("stp.worldTime:"
+ QString::number(_syncTimePoint.worldTime)
+ ";stp.trackTime:" + QString::number(_syncTimePoint.trackTime)
+ ";" + entry);
}
void VideoTrackObject::readFrames() {
if (interrupted()) {
return;
}
auto time = trackTime().trackTime;
TO_LOG(("reading_frames,time:%1").arg(time));
debugAssertKnownTime(1, time);
while (true) {
const auto result = readEnoughFrames(time);
v::match(result, [&](FrameResult result) {
@@ -285,6 +393,7 @@ void VideoTrackObject::readFrames() {
const auto duration = computeDuration();
Assert(duration != kDurationUnavailable);
time -= duration;
debugAssertKnownTime(2, time);
}
}, [&](Shared::PrepareNextCheck delay) {
Expects(delay == kTimeUnknown || delay > 0);
@@ -302,6 +411,7 @@ void VideoTrackObject::readFrames() {
auto VideoTrackObject::readEnoughFrames(crl::time trackTime)
-> ReadEnoughState {
TO_LOG(("reading_enough_frames,time:%1").arg(trackTime));
const auto dropStaleFrames = !_options.waitForMarkAsShown;
const auto state = _shared->prepareState(trackTime, dropStaleFrames);
return v::match(state, [&](Shared::PrepareFrame frame)
@@ -309,13 +419,16 @@ auto VideoTrackObject::readEnoughFrames(crl::time trackTime)
while (true) {
const auto result = readFrame(frame);
if (result != FrameResult::Done) {
TO_LOG(("read_enough_state,frame_result:%1").arg(int(result)));
return result;
} else if (!dropStaleFrames
|| !VideoTrack::IsStale(frame, trackTime)) {
TO_LOG(("read_enough_state,null"));
return v::null;
}
}
}, [&](Shared::PrepareNextCheck delay) -> ReadEnoughState {
TO_LOG(("read_enough_state,delay:%1").arg(delay));
Expects(delay == kTimeUnknown || delay > 0); // Debugging crash.
return delay;
@@ -327,6 +440,7 @@ auto VideoTrackObject::readEnoughFrames(crl::time trackTime)
bool VideoTrackObject::loopAround() {
const auto duration = computeDuration();
if (duration == kDurationUnavailable) {
TO_LOG(("loop_around,error_duration_unavailable"));
LOG(("Streaming Error: "
"Couldn't find out the real video stream duration."));
return false;
@@ -334,6 +448,7 @@ bool VideoTrackObject::loopAround() {
avcodec_flush_buffers(_stream.codec.get());
_loopingShift += duration;
_readTillEnd = false;
TO_LOG(("loop_around,duration:%1,shift:%2").arg(duration).arg(_loopingShift));
return true;
}
@@ -351,31 +466,38 @@ auto VideoTrackObject::readFrame(not_null<Frame*> frame) -> FrameResult {
if (const auto error = ReadNextFrame(_stream)) {
if (error.code() == AVERROR_EOF) {
if (!_options.loop) {
TO_LOG(("read_frame,error_eof"));
frame->position = kFinishedPosition;
frame->displayed = kTimeUnknown;
return FrameResult::Finished;
} else if (loopAround()) {
TO_LOG(("read_frame,error_looped"));
return FrameResult::Looped;
} else {
TO_LOG(("read_frame,error_bad_data"));
fail(Error::InvalidData);
return FrameResult::Error;
}
} else if (error.code() != AVERROR(EAGAIN) || _readTillEnd) {
TO_LOG(("read_frame,error_bad_data:%1,till_end:%2").arg(error.code()).arg(_readTillEnd ? "true" : "false"));
fail(Error::InvalidData);
return FrameResult::Error;
}
TO_LOG(("read_frame,error_again"));
Assert(_stream.queue.empty());
_waitingForData.fire({});
return FrameResult::Waiting;
}
const auto position = currentFramePosition();
if (position == kTimeUnknown) {
TO_LOG(("read_frame,error_current_position:%1").arg(position));
fail(Error::InvalidData);
return FrameResult::Error;
}
std::swap(frame->decoded, _stream.frame);
frame->position = position;
frame->displayed = kTimeUnknown;
TO_LOG(("read_frame,current_position:%1").arg(position));
return FrameResult::Done;
}
@@ -474,11 +596,14 @@ void VideoTrackObject::presentFrameIfNeeded() {
return;
}
const auto dropStaleFrames = !_options.waitForMarkAsShown;
const auto time = trackTime();
TO_LOG(("present_frame_check,world:%1,track:%2").arg(time.worldTime).arg(time.trackTime));
const auto presented = _shared->presentFrame(
this,
trackTime(),
time,
_options.speed,
dropStaleFrames);
TO_LOG(("present_frame_check,add:%1,position:%2,next:%3").arg(presented.addedWorldTimeDelay).arg(presented.displayPosition).arg(presented.nextCheckDelay));
addTimelineDelay(presented.addedWorldTimeDelay);
if (presented.displayPosition == kFinishedPosition) {
interrupt();
@@ -496,6 +621,7 @@ void VideoTrackObject::presentFrameIfNeeded() {
void VideoTrackObject::pause(crl::time time) {
Expects(_syncTimePoint.valid());
TO_LOG(("pause,time:%1,paused:%2").arg(time).arg(_pausedTime));
if (interrupted()) {
return;
} else if (_pausedTime == kTimeUnknown) {
@@ -506,6 +632,8 @@ void VideoTrackObject::pause(crl::time time) {
void VideoTrackObject::resume(crl::time time) {
Expects(_syncTimePoint.trackTime != kTimeUnknown);
TO_LOG(("resume,time:%1,paused:%2,resumed:%3").arg(time).arg(_pausedTime).arg(_resumedTime));
if (interrupted()) {
return;
}
@@ -526,11 +654,15 @@ void VideoTrackObject::resume(crl::time time) {
}
void VideoTrackObject::setSpeed(float64 speed) {
TO_LOG(("set_speed,speed:%1").arg(speed));
if (interrupted()) {
return;
}
if (_syncTimePoint.valid()) {
_syncTimePoint = trackTime();
const auto time = trackTime();
TO_LOG(("set_speed_changing_time,world:%1,track:%2").arg(time.worldTime).arg(time.trackTime));
_syncTimePoint = time;
debugAssertKnownTime(3, _syncTimePoint.trackTime);
}
_options.speed = speed;
}
@@ -539,17 +671,23 @@ void VideoTrackObject::setWaitForMarkAsShown(bool wait) {
if (interrupted()) {
return;
}
TO_LOG(("set_wait_for_mark,wait:%1").arg(wait ? "true" : "false"));
_options.waitForMarkAsShown = wait;
}
bool VideoTrackObject::interrupted() const {
return (_shared == nullptr);
if (_shared == nullptr) {
TO_LOG(("interruped_true"));
return true;
}
return false;
}
void VideoTrackObject::frameShown() {
if (interrupted()) {
return;
}
TO_LOG(("frame_shown"));
queueReadFrames();
}
@@ -559,6 +697,7 @@ void VideoTrackObject::addTimelineDelay(crl::time delayed) {
if (!delayed) {
return;
}
TO_LOG(("adding_delay:%1").arg(delayed));
_syncTimePoint.worldTime += delayed;
}
@@ -574,26 +713,34 @@ void VideoTrackObject::removeFrameRequest(const Instance *instance) {
bool VideoTrackObject::tryReadFirstFrame(FFmpeg::Packet &&packet) {
if (ProcessPacket(_stream, std::move(packet)).failed()) {
TO_LOG(("try_read_first_frame_process_failed"));
return false;
}
TO_LOG(("try_read_first_frame"));
while (true) {
if (const auto error = ReadNextFrame(_stream)) {
if (error.code() == AVERROR_EOF) {
if (!_initialSkippingFrame) {
TO_LOG(("try_read_first_frame_eof_bad"));
return false;
}
// Return the last valid frame if we seek too far.
_stream.frame = std::move(_initialSkippingFrame);
TO_LOG(("try_read_first_frame_eof_to_initial"));
return processFirstFrame();
} else if (error.code() != AVERROR(EAGAIN) || _readTillEnd) {
TO_LOG(("try_read_first_frame_error,end:%1").arg(_readTillEnd ? "true" : "false"));
return false;
} else {
// Waiting for more packets.
TO_LOG(("try_read_first_frame_waiting"));
return true;
}
} else if (!fillStateFromFrame()) {
TO_LOG(("try_read_first_frame_bad_state"));
return false;
} else if (_syncTimePoint.trackTime >= _options.position) {
TO_LOG(("try_read_first_frame_process"));
return processFirstFrame();
}
@@ -618,6 +765,8 @@ bool VideoTrackObject::processFirstFrame() {
if (frame.isNull()) {
return false;
}
debugAssertKnownTime(4, _syncTimePoint.trackTime);
TO_LOG(("process_first_frame"));
_shared->init(std::move(frame), _syncTimePoint.trackTime);
callReady();
queueReadFrames();
@@ -638,9 +787,12 @@ crl::time VideoTrackObject::currentFramePosition() const {
bool VideoTrackObject::fillStateFromFrame() {
const auto position = currentFramePosition();
if (position == kTimeUnknown) {
TO_LOG(("fill_state_from_frame_bad"));
return false;
}
TO_LOG(("fill_state_from_frame,position:%1").arg(position));
_syncTimePoint.trackTime = position;
debugAssertKnownTime(5, _syncTimePoint.trackTime);
return true;
}
@@ -663,30 +815,48 @@ void VideoTrackObject::callReady() {
data.state.receivedTill = _readTillEnd
? _stream.duration
: _syncTimePoint.trackTime;
TO_LOG(("call_ready,till:%1").arg(data.state.receivedTill));
base::take(_ready)({ data });
}
TimePoint VideoTrackObject::trackTime() const {
debugAssertKnownTime(7, _syncTimePoint.trackTime);
auto result = TimePoint();
result.worldTime = (_pausedTime != kTimeUnknown)
? _pausedTime
: crl::now();
if (!_syncTimePoint) {
result.trackTime = _syncTimePoint.trackTime;
TO_LOG(("track_time,paused:%1,result_world:%2").arg(_pausedTime).arg(result.worldTime));
return result;
}
debugAssertKnownTime(8, _syncTimePoint.worldTime);
debugAssertKnownTime(9, result.worldTime);
Assert(_resumedTime != kTimeUnknown);
if (_options.syncVideoByAudio && _audioId.externalPlayId()) {
const auto mixer = Media::Player::mixer();
const auto point = mixer->getExternalSyncTimePoint(_audioId);
if (point && point.worldTime > _resumedTime) {
TO_LOG(("track_time_sync,world:%1,track:%2,resumed:%3").arg(point.worldTime).arg(point.trackTime).arg(_resumedTime));
_syncTimePoint = point;
debugAssertKnownTime(6, _syncTimePoint.trackTime);
debugAssertKnownTime(10, _syncTimePoint.worldTime);
}
}
const auto adjust = (result.worldTime - _syncTimePoint.worldTime);
result.trackTime = _syncTimePoint.trackTime
+ crl::time(std::round(adjust * _options.speed));
TO_LOG(("track_time_adjusted,world:%1,adjust:%2,speed:%3,delta:%4,rounded:%5,casted:%6,final:%7"
).arg(result.worldTime
).arg(adjust
).arg(_options.speed
).arg(adjust * _options.speed
).arg(std::round(adjust * _options.speed)
).arg(crl::time(std::round(adjust * _options.speed))
).arg(result.trackTime));
debugAssertKnownTime(11, result.trackTime);
return result;
}
@@ -985,7 +1155,6 @@ VideoTrack::FrameWithIndex VideoTrack::Shared::frameForPaintWithIndex() {
.frame = frame,
.index = (_counterCycle * 2 * kFramesCount) + index,
};
}
VideoTrack::VideoTrack(

View File

@@ -4518,9 +4518,10 @@ void OverlayWidget::applyHideWindowWorkaround() {
_hideWorkaround->show();
_hideWorkaround->paintRequest(
) | rpl::start_with_next([=] {
QPainter(_hideWorkaround.get()).fillRect(_hideWorkaround->rect(), QColor(0, 1, 0, 1));
crl::on_main(_hideWorkaround.get(), [=] {
_hideWorkaround.reset();
const auto workaround = _hideWorkaround.release();
QPainter(workaround).fillRect(workaround->rect(), QColor(0, 1, 0, 1));
crl::on_main(workaround, [=] {
delete workaround;
});
}, _hideWorkaround->lifetime());
_hideWorkaround->update();

View File

@@ -200,6 +200,9 @@ ApplicationDelegate *_sharedDelegate = nil;
}
- (NSMenu *) applicationDockMenu:(NSApplication *)sender {
if (!Core::IsAppLaunched()) {
return nil;
}
RpMenu* dockMenu = [[[RpMenu alloc] initWithTitle: @""] autorelease];
[dockMenu setAutoenablesItems:false];

View File

@@ -57,6 +57,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "facades.h"
#include "styles/style_settings.h"
#include "styles/style_layers.h"
#include "styles/style_window.h"
namespace Settings {
namespace {
@@ -581,26 +582,28 @@ void BackgroundRow::updateImage() {
}
const auto &prepared = background->prepared();
if (!prepared.isNull()) {
const auto sx = (prepared.width() > prepared.height())
? ((prepared.width() - prepared.height()) / 2)
: 0;
const auto sy = (prepared.height() > prepared.width())
? ((prepared.height() - prepared.width()) / 2)
: 0;
const auto s = (prepared.width() > prepared.height())
? prepared.height()
: prepared.width();
const auto pattern = background->paper().isPattern();
const auto w = prepared.width();
const auto h = prepared.height();
const auto use = [&] {
if (!pattern) {
return std::min(w, h);
}
const auto scaledw = w * st::windowMinHeight / h;
const auto result = (w * size) / scaledw;
return std::min({ result, w, h });
}();
p.drawImage(
QRect(0, 0, size, size),
prepared,
QRect(sx, sy, s, s));
QRect((w - use) / 2, (h - use) / 2, use, use));
}
if (!gradient.isNull()
&& !prepared.isNull()
&& patternOpacity < 0.
&& patternOpacity > -1.) {
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
p.setOpacity(patternOpacity);
p.setOpacity(1. + patternOpacity);
p.fillRect(QRect(0, 0, size, size), Qt::black);
}
}
@@ -954,13 +957,16 @@ void SetupChatBackground(
AddSkip(container, st::settingsTileSkip);
const auto background = Window::Theme::Background();
const auto tile = inner->add(
object_ptr<Ui::Checkbox>(
object_ptr<Ui::SlideWrap<Ui::Checkbox>>(
inner,
tr::lng_settings_bg_tile(tr::now),
Window::Theme::Background()->tile(),
st::settingsCheckbox),
st::settingsSendTypePadding);
object_ptr<Ui::Checkbox>(
inner,
tr::lng_settings_bg_tile(tr::now),
background->tile(),
st::settingsCheckbox),
st::settingsSendTypePadding));
const auto adaptive = inner->add(
object_ptr<Ui::SlideWrap<Ui::Checkbox>>(
inner,
@@ -971,19 +977,25 @@ void SetupChatBackground(
st::settingsCheckbox),
st::settingsSendTypePadding));
tile->checkedChanges(
) | rpl::start_with_next([](bool checked) {
Window::Theme::Background()->setTile(checked);
tile->entity()->checkedChanges(
) | rpl::start_with_next([=](bool checked) {
background->setTile(checked);
}, tile->lifetime());
const auto shown = [=] {
return !background->paper().isPattern()
&& !background->colorForFill();
};
tile->toggle(shown(), anim::type::instant);
using Update = const Window::Theme::BackgroundUpdate;
Window::Theme::Background()->updates(
background->updates(
) | rpl::filter([](const Update &update) {
return (update.type == Update::Type::Changed);
}) | rpl::map([] {
return Window::Theme::Background()->tile();
}) | rpl::start_with_next([=](bool tiled) {
tile->setChecked(tiled);
return (update.type == Update::Type::Changed)
|| (update.type == Update::Type::New);
}) | rpl::start_with_next([=] {
tile->entity()->setChecked(background->tile());
tile->toggle(shown(), anim::type::instant);
}, tile->lifetime());
adaptive->toggleOn(controller->adaptive().chatLayoutValue(

View File

@@ -30,11 +30,44 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_common.h"
#include "api/api_updates.h"
#include "zlib.h"
namespace Settings {
namespace {
using SessionController = Window::SessionController;
[[nodiscard]] QByteArray UnpackRawGzip(const QByteArray &bytes) {
z_stream stream;
stream.zalloc = nullptr;
stream.zfree = nullptr;
stream.opaque = nullptr;
stream.avail_in = 0;
stream.next_in = nullptr;
int res = inflateInit2(&stream, -MAX_WBITS);
if (res != Z_OK) {
return QByteArray();
}
const auto guard = gsl::finally([&] { inflateEnd(&stream); });
auto result = QByteArray(1024 * 1024, char(0));
stream.avail_in = bytes.size();
stream.next_in = reinterpret_cast<Bytef*>(const_cast<char*>(bytes.data()));
stream.avail_out = 0;
while (!stream.avail_out) {
stream.avail_out = result.size();
stream.next_out = reinterpret_cast<Bytef*>(result.data());
int res = inflate(&stream, Z_NO_FLUSH);
if (res != Z_OK && res != Z_STREAM_END) {
return QByteArray();
} else if (!stream.avail_out) {
return QByteArray();
}
}
result.resize(result.size() - stream.avail_out);
return result;
}
auto GenerateCodes() {
auto codes = std::map<QString, Fn<void(SessionController*)>>();
codes.emplace(qsl("debugmode"), [](SessionController *window) {
@@ -197,6 +230,50 @@ auto GenerateCodes() {
Core::App().saveSettingsDelayed();
Ui::show(Box<InformBox>("All sound overrides were reset."));
});
codes.emplace(qsl("unpacklog"), [](SessionController *window) {
FileDialog::GetOpenPath(Core::App().getFileDialogParent(), "Open crash log file", "Crash dump (*.txt)", [=](const FileDialog::OpenResult &result) {
if (result.paths.isEmpty()) {
return;
}
auto f = QFile(result.paths.front());
if (!f.open(QIODevice::ReadOnly)) {
Ui::Toast::Show("Could not open log :(");
return;
}
const auto all = f.readAll();
const auto log = all.indexOf("Log: ");
if (log < 0) {
Ui::Toast::Show("Could not find log :(");
return;
}
const auto base = all.mid(log + 5);
const auto end = base.indexOf('\n');
if (end <= 0) {
Ui::Toast::Show("Could not find log end :(");
return;
}
const auto based = QByteArray::fromBase64(base.mid(0, end));
const auto uncompressed = UnpackRawGzip(based);
if (uncompressed.isEmpty()) {
Ui::Toast::Show("Could not unpack log :(");
return;
}
FileDialog::GetWritePath(Core::App().getFileDialogParent(), "Save detailed log", "Crash dump (*.txt)", QString(), [=](QString &&result) {
if (result.isEmpty()) {
return;
}
auto f = QFile(result);
if (!f.open(QIODevice::WriteOnly)) {
Ui::Toast::Show("Could not open details :(");
} else if (f.write(uncompressed) != uncompressed.size()) {
Ui::Toast::Show("Could not write details :(");
} else {
f.close();
Ui::Toast::Show("Done!");
}
});
});
});
return codes;
}

View File

@@ -709,8 +709,14 @@ object_ptr<Ui::RpWidget> ForwardsPrivacyController::setupAboveWidget(
Window::SectionWidget::PaintBackground(_controller, widget, rect);
Painter p(widget);
const auto context = HistoryView::PaintContext{
.bubblesPattern = nullptr, // #TODO bubbles
.viewport = widget->rect(),
.clip = widget->rect(),
.now = crl::now(),
};
p.translate(0, padding + view->marginBottom());
view->draw(p, widget->rect(), TextSelection(), crl::now());
view->draw(p, context);
PaintForwardedTooltip(p, view, *option);
}, widget->lifetime());

View File

@@ -564,13 +564,18 @@ void ConfirmContactBox::paintEvent(QPaintEvent *e) {
p.fillRect(e->rect(), st::boxBg);
const auto ms = crl::now();
const auto context = HistoryView::PaintContext{
.bubblesPattern = nullptr, // #TODO bubbles
.viewport = rect(),
.clip = rect(),
.now = crl::now(),
};
p.translate(st::boxPadding.left(), 0);
if (_comment) {
_comment->draw(p, rect(), TextSelection(), ms);
_comment->draw(p, context);
p.translate(0, _comment->height());
}
_contact->draw(p, rect(), TextSelection(), ms);
_contact->draw(p, context);
}
HistoryView::Context ConfirmContactBox::elementContext() {

View File

@@ -0,0 +1,428 @@
/*
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 "ui/chat/message_bubble.h"
#include "ui/cached_round_corners.h"
#include "ui/image/image_prepare.h"
#include "styles/style_chat.h"
namespace Ui {
namespace {
template <
typename FillBg, // fillBg(QRect rect)
typename FillSh, // fillSh(QRect rect)
typename FillRounded, // fillRounded(QRect rect, RectParts parts)
typename PaintTail> // paintTail(QPoint bottomPosition) -> tailWidth
void PaintBubbleGeneric(
const SimpleBubble &args,
FillBg &&fillBg,
FillSh &&fillSh,
FillRounded &&fillRounded,
PaintTail &&paintTail) {
auto parts = RectPart::None | RectPart::NoTopBottom;
auto rect = args.geometry;
if (args.skip & RectPart::Top) {
if (args.skip & RectPart::Bottom) {
fillBg(rect);
return;
}
rect.setTop(rect.y() - st::historyMessageRadius);
} else {
parts |= RectPart::FullTop;
}
const auto skipBottom = (args.skip & RectPart::Bottom);
if (skipBottom) {
rect.setHeight(rect.height() + st::historyMessageRadius);
} else {
parts |= RectPart::Bottom;
}
if (!skipBottom && args.tailSide == RectPart::Right) {
parts |= RectPart::BottomLeft;
fillBg({
rect.x() + rect.width() - st::historyMessageRadius,
rect.y() + rect.height() - st::historyMessageRadius,
st::historyMessageRadius,
st::historyMessageRadius });
const auto tailWidth = paintTail({
rect.x() + rect.width(),
rect.y() + rect.height() });
fillSh({
rect.x() + rect.width() - st::historyMessageRadius,
rect.y() + rect.height(),
st::historyMessageRadius + tailWidth,
st::msgShadow });
} else if (!skipBottom && args.tailSide == RectPart::Left) {
parts |= RectPart::BottomRight;
fillBg({
rect.x(),
rect.y() + rect.height() - st::historyMessageRadius,
st::historyMessageRadius,
st::historyMessageRadius });
const auto tailWidth = paintTail({
rect.x(),
rect.y() + rect.height() });
fillSh({
rect.x() - tailWidth,
rect.y() + rect.height(),
st::historyMessageRadius + tailWidth,
st::msgShadow });
} else if (!skipBottom) {
parts |= RectPart::FullBottom;
}
fillRounded(rect, parts);
}
void PaintPatternBubble(Painter &p, const SimpleBubble &args) {
const auto opacity = st::msgOutBg->c.alphaF();
const auto shadowOpacity = opacity * st::msgOutShadow->c.alphaF();
const auto pattern = args.pattern;
const auto sh = (args.skip & RectPart::Bottom)
? nullptr
: &st::msgOutShadow;
const auto &tail = (args.tailSide == RectPart::Right)
? pattern->tailRight
: pattern->tailLeft;
const auto tailShift = (args.tailSide == RectPart::Right
? QPoint(0, tail.height())
: QPoint(tail.width(), tail.height())) / int(tail.devicePixelRatio());
const auto fillBg = [&](const QRect &rect) {
const auto fill = rect.intersected(args.patternViewport);
if (!fill.isEmpty()) {
p.setClipRect(fill);
PaintPatternBubblePart(
p,
args.patternViewport,
pattern->pixmap,
fill);
p.setClipping(false);
}
};
const auto fillSh = [&](const QRect &rect) {
if (!(args.skip & RectPart::Bottom)) {
p.setOpacity(shadowOpacity);
fillBg(rect);
p.setOpacity(opacity);
}
};
const auto fillPattern = [&](
int x,
int y,
const QImage &mask,
QImage &cache) {
PaintPatternBubblePart(
p,
args.patternViewport,
pattern->pixmap,
QRect(QPoint(x, y), mask.size() / int(mask.devicePixelRatio())),
mask,
cache);
};
const auto fillCorner = [&](int x, int y, int index) {
fillPattern(
x,
y,
pattern->corners[index],
(index < 2) ? pattern->cornerTopCache : pattern->cornerBottomCache);
};
const auto fillRounded = [&](const QRect &rect, RectParts parts) {
const auto x = rect.x();
const auto y = rect.y();
const auto w = rect.width();
const auto h = rect.height();
const auto cornerWidth = pattern->corners[0].width()
/ style::DevicePixelRatio();
const auto cornerHeight = pattern->corners[0].height()
/ style::DevicePixelRatio();
if (w < 2 * cornerWidth || h < 2 * cornerHeight) {
return;
}
if (w > 2 * cornerWidth) {
if (parts & RectPart::Top) {
fillBg({
x + cornerWidth,
y,
w - 2 * cornerWidth,
cornerHeight });
}
if (parts & RectPart::Bottom) {
fillBg({
x + cornerWidth,
y + h - cornerHeight,
w - 2 * cornerWidth,
cornerHeight });
if (sh) {
fillSh({
x + cornerWidth,
y + h,
w - 2 * cornerWidth,
st::msgShadow });
}
}
}
if (h > 2 * cornerHeight) {
if ((parts & RectPart::NoTopBottom) == RectPart::NoTopBottom) {
fillBg({
x,
y + cornerHeight,
w,
h - 2 * cornerHeight });
} else {
if (parts & RectPart::Left) {
fillBg({
x,
y + cornerHeight,
cornerWidth,
h - 2 * cornerHeight });
}
if ((parts & RectPart::Center) && w > 2 * cornerWidth) {
fillBg({
x + cornerWidth,
y + cornerHeight,
w - 2 * cornerWidth,
h - 2 * cornerHeight });
}
if (parts & RectPart::Right) {
fillBg({
x + w - cornerWidth,
y + cornerHeight,
cornerWidth,
h - 2 * cornerHeight });
}
}
}
if (parts & RectPart::TopLeft) {
fillCorner(x, y, 0);
}
if (parts & RectPart::TopRight) {
fillCorner(x + w - cornerWidth, y, 1);
}
if (parts & RectPart::BottomLeft) {
fillCorner(x, y + h - cornerHeight, 2);
}
if (parts & RectPart::BottomRight) {
fillCorner(x + w - cornerWidth, y + h - cornerHeight, 3);
}
};
const auto paintTail = [&](QPoint bottomPosition) {
const auto position = bottomPosition - tailShift;
fillPattern(position.x(), position.y(), tail, pattern->tailCache);
return tail.width() / int(tail.devicePixelRatio());
};
p.setOpacity(opacity);
PaintBubbleGeneric(args, fillBg, fillSh, fillRounded, paintTail);
p.setOpacity(1.);
}
void PaintSolidBubble(Painter &p, const SimpleBubble &args) {
const auto &bg = args.selected
? (args.outbg ? st::msgOutBgSelected : st::msgInBgSelected)
: (args.outbg ? st::msgOutBg : st::msgInBg);
const auto sh = (args.skip & RectPart::Bottom)
? nullptr
: args.selected
? &(args.outbg ? st::msgOutShadowSelected : st::msgInShadowSelected)
: &(args.outbg ? st::msgOutShadow : st::msgInShadow);
const auto corners = args.selected
? (args.outbg
? MessageOutSelectedCorners
: MessageInSelectedCorners)
: (args.outbg ? MessageOutCorners : MessageInCorners);
const auto &tail = (args.tailSide == RectPart::Right)
? (args.selected
? st::historyBubbleTailOutRightSelected
: st::historyBubbleTailOutRight)
: args.selected
? (args.outbg
? st::historyBubbleTailOutLeftSelected
: st::historyBubbleTailInLeftSelected)
: (args.outbg
? st::historyBubbleTailOutLeft
: st::historyBubbleTailInLeft);
const auto tailShift = (args.tailSide == RectPart::Right)
? QPoint(0, tail.height())
: QPoint(tail.width(), tail.height());
PaintBubbleGeneric(args, [&](const QRect &rect) {
p.fillRect(rect, bg);
}, [&](const QRect &rect) {
p.fillRect(rect, *sh);
}, [&](const QRect &rect, RectParts parts) {
Ui::FillRoundRect(p, rect, bg, corners, sh, parts);
}, [&](const QPoint &bottomPosition) {
tail.paint(p, bottomPosition - tailShift, args.outerWidth);
return tail.width();
});
}
} // namespace
std::unique_ptr<BubblePattern> PrepareBubblePattern() {
auto result = std::make_unique<Ui::BubblePattern>();
result->corners = Images::CornersMask(st::historyMessageRadius);
const auto addShadow = [&](QImage &bottomCorner) {
auto result = QImage(
bottomCorner.width(),
(bottomCorner.height()
+ st::msgShadow * int(bottomCorner.devicePixelRatio())),
QImage::Format_ARGB32_Premultiplied);
result.fill(Qt::transparent);
result.setDevicePixelRatio(bottomCorner.devicePixelRatio());
auto p = QPainter(&result);
p.setOpacity(st::msgInShadow->c.alphaF());
p.drawImage(0, st::msgShadow, bottomCorner);
p.setOpacity(1.);
p.drawImage(0, 0, bottomCorner);
p.end();
bottomCorner = std::move(result);
};
addShadow(result->corners[2]);
addShadow(result->corners[3]);
result->tailLeft = st::historyBubbleTailOutLeft.instance(Qt::white);
result->tailRight = st::historyBubbleTailOutRight.instance(Qt::white);
result->tailCache = QImage(
result->tailLeft.size(),
QImage::Format_ARGB32_Premultiplied);
result->cornerTopCache = QImage(
result->corners[0].size(),
QImage::Format_ARGB32_Premultiplied);
result->cornerBottomCache = QImage(
result->corners[2].size(),
QImage::Format_ARGB32_Premultiplied);
return result;
}
void PaintBubble(Painter &p, const SimpleBubble &args) {
if (!args.selected
&& args.outbg
&& args.pattern
&& !args.patternViewport.isEmpty()
&& !args.pattern->pixmap.size().isEmpty()) {
PaintPatternBubble(p, args);
} else {
PaintSolidBubble(p, args);
}
}
void PaintBubble(Painter &p, const ComplexBubble &args) {
if (args.selection.empty()) {
PaintBubble(p, args.simple);
return;
}
const auto rect = args.simple.geometry;
const auto left = rect.x();
const auto width = rect.width();
const auto top = rect.y();
const auto bottom = top + rect.height();
const auto paintOne = [&](QRect geometry, bool selected, RectParts skip) {
auto simple = args.simple;
simple.geometry = geometry;
simple.selected = selected;
simple.skip = skip;
PaintBubble(p, simple);
};
auto from = top;
for (const auto &selected : args.selection) {
if (selected.top > from) {
const auto skip = RectPart::Bottom
| (from > top ? RectPart::Top : RectPart::None);
paintOne(
QRect(left, from, width, selected.top - from),
false,
skip);
}
const auto skip = ((selected.top > top)
? RectPart::Top
: RectPart::None)
| ((selected.top + selected.height < bottom)
? RectPart::Bottom
: RectPart::None);
paintOne(
QRect(left, selected.top, width, selected.height),
true,
skip);
from = selected.top + selected.height;
}
if (from < bottom) {
paintOne(
QRect(left, from, width, bottom - from),
false,
RectPart::Top);
}
}
void PaintPatternBubblePart(
QPainter &p,
const QRect &viewport,
const QPixmap &pixmap,
const QRect &target) {
// #TODO bubbles optimizes
const auto to = viewport;
const auto from = QRect(QPoint(), pixmap.size());
p.drawPixmap(to, pixmap, from);
}
void PaintPatternBubblePart(
QPainter &p,
const QRect &viewport,
const QPixmap &pixmap,
const QRect &target,
const QImage &mask,
QImage &cache) {
Expects(mask.bytesPerLine() == mask.width() * 4);
Expects(mask.format() == QImage::Format_ARGB32_Premultiplied);
if (cache.size() != mask.size()) {
cache = QImage(
mask.size(),
QImage::Format_ARGB32_Premultiplied);
}
cache.setDevicePixelRatio(mask.devicePixelRatio());
Assert(cache.bytesPerLine() == cache.width() * 4);
memcpy(cache.bits(), mask.constBits(), mask.sizeInBytes());
auto q = QPainter(&cache);
q.setCompositionMode(QPainter::CompositionMode_SourceIn);
PaintPatternBubblePart(
q,
viewport.translated(-target.topLeft()),
pixmap,
QRect(QPoint(), cache.size() / int(cache.devicePixelRatio())));
q.end();
p.drawImage(target, cache);
}
void PaintPatternBubblePart(
QPainter &p,
const QRect &viewport,
const QPixmap &pixmap,
const QRect &target,
Fn<void(Painter&)> paintContent,
QImage &cache) {
Expects(paintContent != nullptr);
if (cache.size() != target.size() * style::DevicePixelRatio()) {
cache = QImage(
target.size() * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied);
cache.setDevicePixelRatio(style::DevicePixelRatio());
}
cache.fill(Qt::transparent);
auto q = Painter(&cache);
q.translate(-target.topLeft());
paintContent(q);
q.setCompositionMode(QPainter::CompositionMode_SourceIn);
PaintPatternBubblePart(q, viewport, pixmap, target);
q.end();
p.drawImage(target, cache);
}
} // namespace Ui

View File

@@ -0,0 +1,74 @@
/*
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
*/
#pragma once
#include "ui/rect_part.h"
class Painter;
namespace Ui {
struct BubbleSelectionInterval {
int top = 0;
int height = 0;
};
struct BubblePattern {
QPixmap pixmap;
std::array<QImage, 4> corners;
QImage tailLeft;
QImage tailRight;
mutable QImage cornerTopCache;
mutable QImage cornerBottomCache;
mutable QImage tailCache;
};
[[nodiscard]] std::unique_ptr<BubblePattern> PrepareBubblePattern();
struct SimpleBubble {
QRect geometry;
const BubblePattern *pattern = nullptr;
QRect patternViewport;
int outerWidth = 0;
bool selected = false;
bool outbg = false;
RectPart tailSide = RectPart::None;
RectParts skip = RectPart::None;
};
struct ComplexBubble {
SimpleBubble simple;
const std::vector<BubbleSelectionInterval> &selection;
};
void PaintBubble(Painter &p, const SimpleBubble &args);
void PaintBubble(Painter &p, const ComplexBubble &args);
void PaintPatternBubblePart(
QPainter &p,
const QRect &viewport,
const QPixmap &pixmap,
const QRect &target);
void PaintPatternBubblePart(
QPainter &p,
const QRect &viewport,
const QPixmap &pixmap,
const QRect &target,
const QImage &mask,
QImage &cache);
void PaintPatternBubblePart(
QPainter &p,
const QRect &viewport,
const QPixmap &pixmap,
const QRect &target,
Fn<void(Painter&)> paintContent,
QImage &cache);
} // namespace Ui

View File

@@ -106,13 +106,12 @@ void SectionWidget::PaintBackground(
Painter p(widget);
const auto background = Window::Theme::Background();
const auto fullHeight = controller->content()->height();
if (const auto color = background->colorForFill()) {
p.fillRect(clip, *color);
return;
}
const auto gradient = background->gradientForFill();
const auto fill = QSize(widget->width(), fullHeight);
const auto fill = QSize(widget->width(), controller->content()->height());
auto fromy = controller->content()->backgroundFromY();
auto state = controller->backgroundState(fill);
const auto paintCache = [&](const CachedBackground &cache) {
@@ -149,9 +148,38 @@ void SectionWidget::PaintBackground(
paintCache(state.now);
return;
}
const auto patternOpacity = background->paper().patternOpacity();
const auto &prepared = background->prepared();
if (!prepared.isNull() && !background->tile()) {
if (prepared.isNull()) {
return;
} else if (background->paper().isPattern()) {
const auto w = prepared.width() * fill.height() / prepared.height();
const auto cx = qCeil(fill.width() / float64(w));
const auto cols = (cx / 2) * 2 + 1;
const auto xshift = (fill.width() - w * cols) / 2;
for (auto i = 0; i != cols; ++i) {
p.drawImage(
QRect(xshift + i * w, 0, w, fill.height()),
prepared,
QRect(QPoint(), prepared.size()));
}
} else if (background->tile()) {
const auto &tiled = background->preparedForTiled();
const auto left = clip.left();
const auto top = clip.top();
const auto right = clip.left() + clip.width();
const auto bottom = clip.top() + clip.height();
const auto w = tiled.width() / cRetinaFactor();
const auto h = tiled.height() / cRetinaFactor();
const auto sx = qFloor(left / w);
const auto sy = qFloor((top - fromy) / h);
const auto cx = qCeil(right / w);
const auto cy = qCeil((bottom - fromy) / h);
for (auto i = sx; i < cx; ++i) {
for (auto j = sy; j < cy; ++j) {
p.drawImage(QPointF(i * w, fromy + j * h), tiled);
}
}
} else {
const auto hq = PainterHighQualityEnabler(p);
const auto rects = Window::Theme::ComputeBackgroundRects(
fill,
@@ -159,25 +187,6 @@ void SectionWidget::PaintBackground(
auto to = rects.to;
to.moveTop(to.top() + fromy);
p.drawImage(to, prepared, rects.from);
return;
}
if (!prepared.isNull()) {
const auto &tiled = background->preparedForTiled();
auto left = clip.left();
auto top = clip.top();
auto right = clip.left() + clip.width();
auto bottom = clip.top() + clip.height();
auto w = tiled.width() / cRetinaFactor();
auto h = tiled.height() / cRetinaFactor();
auto sx = qFloor(left / w);
auto sy = qFloor((top - fromy) / h);
auto cx = qCeil(right / w);
auto cy = qCeil((bottom - fromy) / h);
for (auto i = sx; i < cx; ++i) {
for (auto j = sy; j < cy; ++j) {
p.drawImage(QPointF(i * w, fromy + j * h), tiled);
}
}
}
}

View File

@@ -164,6 +164,7 @@ bool isValidColorValue(QLatin1String value) {
skipWhitespacesAndComments(data, end);
if (data == end) break;
[[maybe_unused]] auto foundName = base::parse::readName(data, end);
skipWhitespacesAndComments(data, end);
if (data == end || *data != ':') {
return "error";

View File

@@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/delayed_activation.h"
#include "ui/chat/message_bubble.h"
#include "ui/toast/toast.h"
#include "ui/toasts/common_toasts.h"
#include "calls/calls_instance.h" // Core::App().calls().inCall().
@@ -63,6 +64,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_window.h"
#include "styles/style_dialogs.h"
#include "styles/style_layers.h" // st::boxLabel
#include "styles/style_chat.h" // st::historyMessageRadius
#include <QtGui/QGuiApplication>
@@ -84,7 +86,7 @@ constexpr auto kBackgroundFadeDuration = crl::time(200);
request.gradientColors,
request.gradientRotation)
: request.gradient;
if (request.tile || request.prepared.isNull()) {
if (request.isPattern || request.tile || request.prepared.isNull()) {
auto result = gradient.isNull()
? QImage(
request.area * cIntRetinaFactor(),
@@ -105,16 +107,25 @@ constexpr auto kBackgroundFadeDuration = crl::time(200);
QPainter::CompositionMode_DestinationIn);
}
}
const auto &tiled = request.preparedForTiled;
const auto tiled = request.isPattern
? request.prepared.scaled(
request.area.height() * cIntRetinaFactor(),
request.area.height() * cIntRetinaFactor(),
Qt::KeepAspectRatio,
Qt::SmoothTransformation)
: request.preparedForTiled;
const auto w = tiled.width() / cRetinaFactor();
const auto h = tiled.height() / cRetinaFactor();
auto sx = 0;
auto sy = 0;
const auto cx = qCeil(request.area.width() / w);
const auto cy = qCeil(request.area.height() / h);
for (int i = sx; i < cx; ++i) {
for (int j = sy; j < cy; ++j) {
p.drawImage(QPointF(i * w, j * h), tiled);
const auto rows = cy;
const auto cols = request.isPattern ? (((cx / 2) * 2) + 1) : cx;
const auto xshift = request.isPattern
? (request.area.width() - cols * w) / 2
: 0;
for (auto y = 0; y != rows; ++y) {
for (auto x = 0; x != cols; ++x) {
p.drawImage(QPointF(xshift + x * w, y * h), tiled);
}
}
if (!gradient.isNull()
@@ -135,34 +146,12 @@ constexpr auto kBackgroundFadeDuration = crl::time(200);
const auto rects = Window::Theme::ComputeBackgroundRects(
request.area,
request.prepared.size());
auto image = request.prepared.copy(rects.from).scaled(
auto result = request.prepared.copy(rects.from).scaled(
rects.to.width() * cIntRetinaFactor(),
rects.to.height() * cIntRetinaFactor(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
auto result = gradient.isNull()
? std::move(image)
: gradient.scaled(
image.size(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
result.setDevicePixelRatio(cRetinaFactor());
if (!gradient.isNull()) {
QPainter p(&result);
if (request.patternOpacity >= 0.) {
p.setCompositionMode(QPainter::CompositionMode_SoftLight);
p.setOpacity(request.patternOpacity);
} else {
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
}
p.drawImage(QRect(QPoint(), rects.to.size()), image);
if (request.patternOpacity < 0. && request.patternOpacity > -1.) {
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
p.setOpacity(1. + request.patternOpacity);
p.fillRect(QRect(QPoint(), rects.to.size()), Qt::black);
}
}
image = QImage();
return {
.image = std::move(result).convertToFormat(
QImage::Format_ARGB32_Premultiplied),
@@ -1445,6 +1434,49 @@ void SessionController::openDocument(
session().data().message(contextId));
}
void SessionController::setBubblesBackground(QImage image) {
_bubblesBackgroundPrepared = std::move(image);
if (!_bubblesBackground.area.isEmpty()) {
_bubblesBackground = CacheBackground({
.prepared = _bubblesBackgroundPrepared,
.area = _bubblesBackground.area,
});
}
if (!_bubblesBackgroundPattern) {
_bubblesBackgroundPattern = Ui::PrepareBubblePattern();
}
_bubblesBackgroundPattern->pixmap = _bubblesBackground.pixmap;
_repaintBackgroundRequests.fire({});
}
HistoryView::PaintContext SessionController::bubblesContext(
BubblesContextArgs &&args) {
const auto visibleAreaTopLocal = content()->mapFromGlobal(
QPoint(0, args.visibleAreaTopGlobal)).y();
const auto viewport = QRect(
0,
args.visibleAreaTop - visibleAreaTopLocal,
args.visibleAreaWidth,
content()->height());
_bubblesBackground.area = viewport.size();
//if (!_bubblesBackgroundPrepared.isNull()
// && _bubblesBackground.area != viewport.size()
// && !viewport.isEmpty()) {
// // #TODO bubbles delayed caching
// _bubblesBackground = CacheBackground({
// .prepared = _bubblesBackgroundPrepared,
// .area = viewport.size(),
// });
// _bubblesBackgroundPattern->pixmap = _bubblesBackground.pixmap;
//}
return {
.bubblesPattern = _bubblesBackgroundPattern.get(),
.viewport = viewport,
.clip = args.clip,
.now = crl::now(),
};
}
const BackgroundState &SessionController::backgroundState(QSize area) {
_backgroundState.shown = _backgroundFade.value(1.);
if (_backgroundState.now.pixmap.isNull()
@@ -1522,6 +1554,7 @@ auto SessionController::currentCacheRequest(QSize area, int addRotation) const
+ _backgroundAddRotation
+ addRotation) % 360,
.tile = background->tile(),
.isPattern = background->paper().isPattern(),
.recreateGradient = (addRotation != 0),
.gradient = gradient,
.gradientColors = (gradient.isNull()
@@ -1580,7 +1613,8 @@ void SessionController::setCachedBackground(CacheBackgroundResult &&cached) {
const auto background = Window::Theme::Background();
if (background->gradientForFill().isNull()
|| _backgroundState.now.pixmap.isNull()) {
|| _backgroundState.now.pixmap.isNull()
|| anim::Disabled()) {
_backgroundFade.stop();
_backgroundState.shown = 1.;
_backgroundState.now = std::move(cached);

View File

@@ -26,6 +26,10 @@ namespace Adaptive {
enum class WindowLayout;
} // namespace Adaptive
namespace HistoryView {
struct PaintContext;
} // namespace HistoryView
namespace ChatHelpers {
class TabbedSelector;
} // namespace ChatHelpers
@@ -46,6 +50,7 @@ class FormController;
namespace Ui {
class LayerWidget;
enum class ReportReason;
struct BubblePattern;
} // namespace Ui
namespace Window {
@@ -61,6 +66,7 @@ struct CacheBackgroundRequest {
QSize area;
int gradientRotation = 0;
bool tile = false;
bool isPattern = false;
bool recreateGradient = false;
QImage gradient;
std::vector<QColor> gradientColors;
@@ -439,6 +445,19 @@ public:
void toggleFiltersMenu(bool enabled);
[[nodiscard]] rpl::producer<> filtersMenuChanged() const;
void setBubblesBackground(QImage image);
const Ui::BubblePattern *bubblesBackgroundPattern() const {
return _bubblesBackgroundPattern.get();
}
struct BubblesContextArgs {
int visibleAreaTop = 0;
int visibleAreaTopGlobal = 0;
int visibleAreaWidth = 0;
QRect clip;
};
[[nodiscard]] HistoryView::PaintContext bubblesContext(
BubblesContextArgs &&args);
[[nodiscard]] const BackgroundState &backgroundState(QSize area);
[[nodiscard]] rpl::producer<> repaintBackgroundRequests() const;
void rotateComplexGradientBackground();
@@ -520,6 +539,10 @@ private:
QSize _willCacheForArea;
crl::time _lastAreaChangeTime = 0;
base::Timer _cacheBackgroundTimer;
CachedBackground _bubblesBackground;
QImage _bubblesBackgroundPrepared;
std::unique_ptr<Ui::BubblePattern> _bubblesBackgroundPattern;
rpl::event_stream<> _repaintBackgroundRequests;
rpl::lifetime _lifetime;

View File

@@ -1,7 +1,7 @@
AppVersion 2009004
AppVersion 2009007
AppVersionStrMajor 2.9
AppVersionStrSmall 2.9.4
AppVersionStr 2.9.4
AppVersionStrSmall 2.9.7
AppVersionStr 2.9.7
BetaChannel 1
AlphaVersion 0
AppVersionOriginal 2.9.4.beta
AppVersionOriginal 2.9.7.beta

View File

@@ -142,6 +142,8 @@ PRIVATE
ui/chat/group_call_userpics.h
ui/chat/message_bar.cpp
ui/chat/message_bar.h
ui/chat/message_bubble.cpp
ui/chat/message_bubble.h
ui/chat/pinned_bar.cpp
ui/chat/pinned_bar.h
ui/controls/call_mute_button.cpp

View File

@@ -1,3 +1,18 @@
2.9.7 beta (23.08.21)
- Still debugging a video playback crash in 32 bit Windows version.
2.9.6 beta (21.08.21)
- Debugging a video playback crash in 32 bit Windows version.
2.9.5 beta (20.08.21)
- Tile chat background patterns horizontally.
- Fix a rare crash in spellchecker on Windows.
- Fix animated chat backgrounds in Saved Messages.
- Fix "Sorry, group is inaccessible" message in scheduled voice chats.
2.9.4 beta (17.08.21)
- Choose one from dozens of new gorgeous animated backgrounds in Chat Settings > Chat background.

View File

@@ -203,10 +203,8 @@ Open **x64 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath***
cd qtbase
for /r %i in (..\..\patches\qtbase_5_15_2\*) do git apply %i
cd ..
mkdir build
cd build
..\configure ^
configure ^
-prefix "%LibrariesPath%\Qt-5.15.2" ^
-debug-and-release ^
-force-debug-info ^